From 07808bd5e5f72b6779bdb8bce52b9f37c63e300a Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Wed, 20 Nov 2024 16:31:39 +0200 Subject: [PATCH 01/52] inbound queue v2 --- Cargo.toml | 5 + .../pallets/inbound-queue-v2/Cargo.toml | 94 ++++ .../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/runtime-api/Cargo.toml | 30 + .../inbound-queue-v2/runtime-api/README.md | 3 + .../inbound-queue-v2/runtime-api/src/lib.rs | 15 + .../pallets/inbound-queue-v2/src/api.rs | 16 + .../inbound-queue-v2/src/benchmarking.rs | 38 ++ .../pallets/inbound-queue-v2/src/envelope.rs | 47 ++ .../pallets/inbound-queue-v2/src/lib.rs | 240 ++++++++ .../pallets/inbound-queue-v2/src/mock.rs | 264 +++++++++ .../pallets/inbound-queue-v2/src/test.rs | 308 +++++++++++ .../pallets/inbound-queue-v2/src/weights.rs | 31 ++ .../pallets/inbound-queue/src/lib.rs | 2 +- .../pallets/inbound-queue/src/mock.rs | 2 +- .../snowbridge/primitives/router/Cargo.toml | 6 + .../primitives/router/src/inbound/mod.rs | 500 +---------------- .../primitives/router/src/inbound/v1.rs | 520 ++++++++++++++++++ .../primitives/router/src/inbound/v2.rs | 247 +++++++++ .../src/bridge_to_ethereum_config.rs | 21 +- .../bridge-hubs/bridge-hub-westend/src/lib.rs | 2 + 26 files changed, 2247 insertions(+), 475 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 100644 bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/send_token.rs create mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/send_token_to_penpal.rs create mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/Cargo.toml create mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/README.md create mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/src/lib.rs create mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs create mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/src/benchmarking.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/primitives/router/src/inbound/v1.rs create mode 100644 bridges/snowbridge/primitives/router/src/inbound/v2.rs diff --git a/Cargo.toml b/Cargo.toml index 533ea4c9e878..3af053cc74e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,9 @@ members = [ "bridges/snowbridge/pallets/ethereum-client/fixtures", "bridges/snowbridge/pallets/inbound-queue", "bridges/snowbridge/pallets/inbound-queue/fixtures", + "bridges/snowbridge/pallets/inbound-queue-v2", + "bridges/snowbridge/pallets/inbound-queue-v2/fixtures", + "bridges/snowbridge/pallets/inbound-queue-v2/runtime-api", "bridges/snowbridge/pallets/outbound-queue", "bridges/snowbridge/pallets/outbound-queue/merkle-tree", "bridges/snowbridge/pallets/outbound-queue/runtime-api", @@ -1228,6 +1231,8 @@ snowbridge-pallet-ethereum-client = { path = "bridges/snowbridge/pallets/ethereu 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-system = { path = "bridges/snowbridge/pallets/system", default-features = false } snowbridge-router-primitives = { path = "bridges/snowbridge/primitives/router", 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..fcbb41743f45 --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue-v2/Cargo.toml @@ -0,0 +1,94 @@ +[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 } +hex = { 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..05a4a473a28a --- /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..5ab12490d040 --- /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 100644 index 000000000000..52da807efd31 --- /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 100644 index 000000000000..4b4e78b63513 --- /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/runtime-api/Cargo.toml b/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/Cargo.toml new file mode 100644 index 000000000000..9b03370ec891 --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "snowbridge-inbound-queue-v2-runtime-api" +description = "Snowbridge Inbound Queue V2 Runtime API" +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] +sp-api = { workspace = true } +snowbridge-core = { workspace = true } +snowbridge-router-primitives = { workspace = true } +xcm = { workspace = true } + +[features] +default = ["std"] +std = [ + "snowbridge-core/std", + "snowbridge-router-primitives/std", + "sp-api/std", + "xcm/std", +] diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/README.md b/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/README.md new file mode 100644 index 000000000000..89b6b0e157c5 --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/README.md @@ -0,0 +1,3 @@ +# Ethereum Inbound Queue V2 Runtime API + +Provides an API to dry-run inbound messages to get the XCM (and its execution cost) that will be executed on AssetHub. diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/src/lib.rs new file mode 100644 index 000000000000..03720b7ca3d2 --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/src/lib.rs @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +#![cfg_attr(not(feature = "std"), no_std)] + +use snowbridge_core::inbound::Proof; +use snowbridge_router_primitives::inbound::v2::Message; +use xcm::latest::Xcm; + +sp_api::decl_runtime_apis! { + pub trait InboundQueueApiV2 + { + /// Dry runs the provided message on AH to provide the XCM payload and execution cost. + fn dry_run(message: Message, proof: Proof) -> (Xcm<()>, u128); + } +} diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs new file mode 100644 index 000000000000..47207df7383c --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! Implements the dry-run API. + +use crate::{Config, Error}; +use snowbridge_core::inbound::Proof; +use snowbridge_router_primitives::inbound::v2::{ConvertMessage, Message}; +use xcm::latest::Xcm; + +pub fn dry_run(message: Message, _proof: Proof) -> Result, Error> + where + T: Config, +{ + let xcm = T::MessageConverter::convert(message).map_err(|e| Error::::ConvertMessage(e))?; + Ok(xcm) +} diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/benchmarking.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/benchmarking.rs new file mode 100644 index 000000000000..b6d2a9739f3d --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/benchmarking.rs @@ -0,0 +1,38 @@ +// 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, + ); + + #[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..41353954e5b2 --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-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 OutboundMessageAccepted(uint64 indexed nonce, uint128 fee, 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, + /// A nonce for enforcing replay protection and ordering. + pub nonce: u64, + /// Total fee paid in Ether on Ethereum, should cover all the cost + pub fee: u128, + /// 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, + nonce: event.nonce, + fee: event.fee, + 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..91c0acaa97a1 --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -0,0 +1,240 @@ +// 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)] +pub mod api; +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::PalletError; +use frame_system::ensure_signed; +use scale_info::TypeInfo; +use sp_core::H160; +use sp_std::vec; +use xcm::prelude::{send_xcm, Junction::*, Location, SendError as XcmpSendError, SendXcm}; + +use snowbridge_core::{ + inbound::{Message, VerificationError, Verifier}, + BasicOperatingMode, +}; +use snowbridge_router_primitives::inbound::v2::{ConvertMessage, Message as MessageV2}; +pub use weights::WeightInfo; + +#[cfg(feature = "runtime-benchmarks")] +use snowbridge_beacon_primitives::BeaconHeader; + +use snowbridge_router_primitives::inbound::v2::ConvertMessageError; + +pub use pallet::*; + +pub const LOG_TARGET: &str = "snowbridge-inbound-queue:v2"; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[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; + + /// XCM message sender + type XcmSender: SendXcm; + /// Address of the Gateway contract + #[pallet::constant] + type GatewayAddress: Get; + type WeightInfo: WeightInfo; + /// AssetHub parachain ID + type AssetHubParaId: Get; + type MessageConverter: ConvertMessage; + #[cfg(feature = "runtime-benchmarks")] + type Helper: BenchmarkHelper; + } + + #[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 nonce + nonce: u64, + /// ID of the XCM message which was forwarded to the final destination parachain + message_id: [u8; 32], + }, + /// 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 nonce of the message been processed or not + #[pallet::storage] + pub type Nonce = StorageMap<_, Identity, u64, bool, 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); + + // Verify the message has not been processed + ensure!(!Nonce::::contains_key(envelope.nonce), Error::::InvalidNonce); + + // Decode payload into `MessageV2` + let message = MessageV2::decode_all(&mut envelope.payload.as_ref()) + .map_err(|_| Error::::InvalidPayload)?; + + let xcm = + T::MessageConverter::convert(message).map_err(|e| Error::::ConvertMessage(e))?; + + // Todo: Deposit fee(in Ether) to RewardLeger which should cover all of: + // T::RewardLeger::deposit(who, envelope.fee.into())?; + // a. The submit extrinsic cost on BH + // b. The delivery cost to AH + // c. The execution cost on AH + // d. The execution cost on destination chain(if any) + // e. The reward + + // Attempt to forward XCM to AH + let dest = Location::new(1, [Parachain(T::AssetHubParaId::get())]); + let (message_id, _) = send_xcm::(dest, xcm).map_err(Error::::from)?; + Self::deposit_event(Event::MessageReceived { nonce: envelope.nonce, message_id }); + + // Set nonce flag to true + Nonce::::insert(envelope.nonce, ()); + + 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(()) + } + } +} 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..f36535d88c3a --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs @@ -0,0 +1,264 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use super::*; + +use crate::{self as inbound_queue}; +use frame_support::{derive_impl, parameter_types, traits::ConstU32}; +use hex_literal::hex; +use snowbridge_beacon_primitives::{ + types::deneb, BeaconHeader, ExecutionProof, Fork, ForkVersions, VersionedExecutionPayloadHeader, +}; +use snowbridge_core::{ + inbound::{Log, Proof, VerificationError}, + TokenId, +}; +use snowbridge_router_primitives::inbound::v2::MessageToXcm; +use sp_core::H160; +use sp_runtime::{ + traits::{IdentifyAccount, IdentityLookup, MaybeEquivalence, Verify}, + BuildStorage, MultiSignature, +}; +use sp_std::{convert::From, default::Default}; +use xcm::{latest::SendXcm, prelude::*}; + +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"]; + +#[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) + } +} + +pub struct MockTokenIdConvert; +impl MaybeEquivalence for MockTokenIdConvert { + fn convert(_id: &TokenId) -> Option { + Some(Location::parent()) + } + fn convert_back(_loc: &Location) -> Option { + None + } +} + +parameter_types! { + pub const EthereumNetwork: xcm::v5::NetworkId = xcm::v5::NetworkId::Ethereum { chain_id: 11155111 }; + pub const GatewayAddress: H160 = H160(GATEWAY_ADDRESS); + pub const InboundQueuePalletInstance: u8 = 80; + pub AssetHubLocation: InteriorLocation = Parachain(1000).into(); +} + +impl inbound_queue::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Verifier = MockVerifier; + type XcmSender = MockXcmSender; + type WeightInfo = (); + type GatewayAddress = GatewayAddress; + type AssetHubParaId = ConstU32<1000>; + type MessageConverter = + MessageToXcm; + #[cfg(feature = "runtime-benchmarks")] + type Helper = Test; +} + +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); +} + +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![], + } +} 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..148a01b3efe1 --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs @@ -0,0 +1,308 @@ +// 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; +use sp_keyring::AccountKeyring as Keyring; +use sp_runtime::DispatchError; + +use crate::{mock::*, Error, Event as InboundQueueEvent}; +use codec::DecodeLimit; +use snowbridge_router_primitives::inbound::v2::InboundAsset; +use sp_core::H256; +use xcm::{ + opaque::latest::{ + prelude::{ClearOrigin, ReceiveTeleportedAsset}, + Asset, + }, + prelude::*, + VersionedXcm, MAX_XCM_DECODE_DEPTH, +}; + +#[test] +fn test_submit_happy_path() { + new_tester().execute_with(|| { + let relayer: AccountId = Keyring::Bob.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(), + }, + }; + + assert_ok!(InboundQueue::submit(origin.clone(), message.clone())); + expect_events(vec![InboundQueueEvent::MessageReceived { + 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, + ], + } + .into()]); + }); +} + +#[test] +fn test_submit_xcm_invalid_channel() { + new_tester().execute_with(|| { + let relayer: AccountId = Keyring::Bob.into(); + let origin = RuntimeOrigin::signed(relayer); + + // 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); + + // 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); + + // 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())); + + // Submit the same again + assert_noop!( + InboundQueue::submit(origin.clone(), message.clone()), + Error::::InvalidNonce + ); + }); +} + +#[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_send_native_erc20_token_payload() { + new_tester().execute_with(|| { + // To generate test data: forge test --match-test testSendEther -vvvv + let payload = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf04005615deb798bb3e4dfa0139dfa1b3d433cc23b72f0000b2d3595bf00600000000000000000000").to_vec(); + let message = MessageV2::decode(&mut payload.as_ref()); + assert_ok!(message.clone()); + + let inbound_message = message.unwrap(); + + let expected_origin: H160 = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf").into(); + let expected_token_id: H160 = hex!("5615deb798bb3e4dfa0139dfa1b3d433cc23b72f").into(); + let expected_value = 500000000000000000u128; + let expected_xcm: Vec = vec![]; + let expected_claimer: Option> = None; + + assert_eq!(expected_origin, inbound_message.origin); + assert_eq!(1, inbound_message.assets.len()); + if let InboundAsset::NativeTokenERC20 { token_id, value } = &inbound_message.assets[0] { + assert_eq!(expected_token_id, *token_id); + assert_eq!(expected_value, *value); + } else { + panic!("Expected NativeTokenERC20 asset"); + } + assert_eq!(expected_xcm, inbound_message.xcm); + assert_eq!(expected_claimer, inbound_message.claimer); + }); +} + +#[test] +fn test_send_foreign_erc20_token_payload() { + new_tester().execute_with(|| { + let payload = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf040197874824853fb4ad04794ccfd1cc8d2a7463839cfcbc6a315a1045c60ab85f400000b2d3595bf00600000000000000000000").to_vec(); + let message = MessageV2::decode(&mut payload.as_ref()); + assert_ok!(message.clone()); + + let inbound_message = message.unwrap(); + + let expected_origin: H160 = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf").into(); + let expected_token_id: H256 = hex!("97874824853fb4ad04794ccfd1cc8d2a7463839cfcbc6a315a1045c60ab85f40").into(); + let expected_value = 500000000000000000u128; + let expected_xcm: Vec = vec![]; + let expected_claimer: Option> = None; + + assert_eq!(expected_origin, inbound_message.origin); + assert_eq!(1, inbound_message.assets.len()); + if let InboundAsset::ForeignTokenERC20 { token_id, value } = &inbound_message.assets[0] { + assert_eq!(expected_token_id, *token_id); + assert_eq!(expected_value, *value); + } else { + panic!("Expected ForeignTokenERC20 asset"); + } + assert_eq!(expected_xcm, inbound_message.xcm); + assert_eq!(expected_claimer, inbound_message.claimer); + }); +} + +#[test] +fn test_register_token_inbound_message_with_xcm_and_claimer() { + new_tester().execute_with(|| { + let payload = hex!("5991a2df15a8f6a256d3ec51e99254cd3fb576a904005615deb798bb3e4dfa0139dfa1b3d433cc23b72f00000000000000000000000000000000300508020401000002286bee0a015029e3b139f4393adda86303fcdaa35f60bb7092bf").to_vec(); + let message = MessageV2::decode(&mut payload.as_ref()); + assert_ok!(message.clone()); + + let inbound_message = message.unwrap(); + + let expected_origin: H160 = hex!("5991a2df15a8f6a256d3ec51e99254cd3fb576a9").into(); + let expected_token_id: H160 = hex!("5615deb798bb3e4dfa0139dfa1b3d433cc23b72f").into(); + let expected_value = 0u128; + let expected_xcm: Vec = hex!("0508020401000002286bee0a").to_vec(); + let expected_claimer: Option> = Some(hex!("29E3b139f4393aDda86303fcdAa35F60Bb7092bF").to_vec()); + + assert_eq!(expected_origin, inbound_message.origin); + assert_eq!(1, inbound_message.assets.len()); + if let InboundAsset::NativeTokenERC20 { token_id, value } = &inbound_message.assets[0] { + assert_eq!(expected_token_id, *token_id); + assert_eq!(expected_value, *value); + } else { + panic!("Expected NativeTokenERC20 asset"); + } + assert_eq!(expected_xcm, inbound_message.xcm); + assert_eq!(expected_claimer, inbound_message.claimer); + + // decode xcm + let versioned_xcm = VersionedXcm::<()>::decode_with_depth_limit( + MAX_XCM_DECODE_DEPTH, + &mut inbound_message.xcm.as_ref(), + ); + + assert_ok!(versioned_xcm.clone()); + + // Check if decoding was successful + let decoded_instructions = match versioned_xcm.unwrap() { + VersionedXcm::V5(decoded) => decoded, + _ => { + panic!("unexpected xcm version found") + } + }; + + let mut decoded_instructions = decoded_instructions.into_iter(); + let decoded_first = decoded_instructions.next().take(); + assert!(decoded_first.is_some()); + let decoded_second = decoded_instructions.next().take(); + assert!(decoded_second.is_some()); + assert_eq!(ClearOrigin, decoded_second.unwrap(), "Second instruction (ClearOrigin) does not match."); + }); +} + +#[test] +fn encode_xcm() { + new_tester().execute_with(|| { + let total_fee_asset: Asset = (Location::parent(), 1_000_000_000).into(); + + let instructions: Xcm<()> = + vec![ReceiveTeleportedAsset(total_fee_asset.into()), ClearOrigin].into(); + + let versioned_xcm_message = VersionedXcm::V5(instructions.clone()); + + let xcm_bytes = VersionedXcm::encode(&versioned_xcm_message); + let hex_string = hex::encode(xcm_bytes.clone()); + + println!("xcm hex: {}", hex_string); + + let versioned_xcm = VersionedXcm::<()>::decode_with_depth_limit( + MAX_XCM_DECODE_DEPTH, + &mut xcm_bytes.as_ref(), + ); + + assert_ok!(versioned_xcm.clone()); + + // Check if decoding was successful + let decoded_instructions = match versioned_xcm.unwrap() { + VersionedXcm::V5(decoded) => decoded, + _ => { + panic!("unexpected xcm version found") + }, + }; + + let mut original_instructions = instructions.into_iter(); + let mut decoded_instructions = decoded_instructions.into_iter(); + + let original_first = original_instructions.next().take(); + let decoded_first = decoded_instructions.next().take(); + assert_eq!( + original_first, decoded_first, + "First instruction (ReceiveTeleportedAsset) does not match." + ); + + let original_second = original_instructions.next().take(); + let decoded_second = decoded_instructions.next().take(); + assert_eq!( + original_second, decoded_second, + "Second instruction (ClearOrigin) does not match." + ); + }); +} 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/primitives/router/Cargo.toml b/bridges/snowbridge/primitives/router/Cargo.toml index ee8d481cec12..aa4b3177c00b 100644 --- a/bridges/snowbridge/primitives/router/Cargo.toml +++ b/bridges/snowbridge/primitives/router/Cargo.toml @@ -17,6 +17,7 @@ scale-info = { features = ["derive"], workspace = true } log = { workspace = true } frame-support = { workspace = true } +frame-system = { workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } @@ -24,6 +25,7 @@ sp-std = { workspace = true } xcm = { workspace = true } xcm-executor = { workspace = true } +xcm-builder = { workspace = true } snowbridge-core = { workspace = true } @@ -36,6 +38,7 @@ default = ["std"] std = [ "codec/std", "frame-support/std", + "frame-system/std", "log/std", "scale-info/std", "snowbridge-core/std", @@ -43,12 +46,15 @@ std = [ "sp-io/std", "sp-runtime/std", "sp-std/std", + "xcm-builder/std", "xcm-executor/std", "xcm/std", ] runtime-benchmarks = [ "frame-support/runtime-benchmarks", + "frame-system/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..1e43bd7544cf 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/mod.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/mod.rs @@ -1,479 +1,37 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2023 Snowfork -//! Converts messages from Ethereum to XCM messages - -#[cfg(test)] -mod tests; - -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, *}; +// SPDX-FileCopyrightText: 2021-2022 Parity Technologies (UK) Ltd. + +pub mod v1; +pub mod v2; +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, +pub struct GlobalConsensusEthereumConvertsFor(PhantomData); +impl ConvertLocation for GlobalConsensusEthereumConvertsFor + where + AccountId: From<[u8; 32]> + Clone, { - _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>; + fn convert_location(location: &Location) -> Option { + match location.unpack() { + (2, [GlobalConsensus(Ethereum { chain_id })]) => + Some(Self::from_chain_id(chain_id).into()), + (2, [GlobalConsensus(Ethereum { chain_id }), AccountKey20 { network: _, key }]) => + Some(Self::from_chain_id_with_key(chain_id, *key).into()), + _ => None, + } + } +} +impl GlobalConsensusEthereumConvertsFor { + pub fn from_chain_id(chain_id: &u64) -> [u8; 32] { + (b"ethereum-chain", chain_id).using_encoded(blake2_256) + } + pub fn from_chain_id_with_key(chain_id: &u64, key: [u8; 20]) -> [u8; 32] { + (b"ethereum-chain", chain_id, key).using_encoded(blake2_256) + } } 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 - AccountId: From<[u8; 32]> + Clone, -{ - fn convert_location(location: &Location) -> Option { - match location.unpack() { - (2, [GlobalConsensus(Ethereum { chain_id })]) => - Some(Self::from_chain_id(chain_id).into()), - (2, [GlobalConsensus(Ethereum { chain_id }), AccountKey20 { network: _, key }]) => - Some(Self::from_chain_id_with_key(chain_id, *key).into()), - _ => None, - } - } -} - -impl EthereumLocationsConverterFor { - pub fn from_chain_id(chain_id: &u64) -> [u8; 32] { - (b"ethereum-chain", chain_id).using_encoded(blake2_256) - } - pub fn from_chain_id_with_key(chain_id: &u64, key: [u8; 20]) -> [u8; 32] { - (b"ethereum-chain", chain_id, key).using_encoded(blake2_256) - } -} 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..d413674970b2 --- /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, GlobalConsensusEthereumConvertsFor}; +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 = GlobalConsensusEthereumConvertsFor::<[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, GlobalConsensusEthereumConvertsFor}; + 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 = + GlobalConsensusEthereumConvertsFor::<[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!( + GlobalConsensusEthereumConvertsFor::<[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..69cacdb995fa --- /dev/null +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! Converts messages from Ethereum to XCM messages + +use codec::{Decode, DecodeLimit, Encode}; +use core::marker::PhantomData; +use frame_support::PalletError; +use scale_info::TypeInfo; +use snowbridge_core::TokenId; +use sp_core::{Get, RuntimeDebug, H160, H256}; +use sp_runtime::traits::MaybeEquivalence; +use sp_std::prelude::*; +use xcm::{ + prelude::{Junction::AccountKey20, *}, + MAX_XCM_DECODE_DEPTH, +}; + +const LOG_TARGET: &str = "snowbridge-router-primitives"; + +/// 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 { + V2(Message), +} + +/// The ethereum side sends messages which are transcoded into XCM on BH. These messages are +/// self-contained, in that they can be transcoded using only information in the message. +#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct Message { + /// The origin address + pub origin: H160, + /// The assets + pub assets: Vec, + // The command originating from the Gateway contract + pub xcm: Vec, + // The claimer in the case that funds get trapped. + pub claimer: Option>, +} + +#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] +pub enum Asset { + NativeTokenERC20 { + /// The native token ID + token_id: H160, + /// The monetary value of the asset + value: u128, + }, + ForeignTokenERC20 { + /// The foreign token ID + token_id: H256, + /// The monetary value of the asset + value: u128, + }, +} + +/// Reason why a message conversion failed. +#[derive(Copy, Clone, TypeInfo, PalletError, Encode, Decode, RuntimeDebug)] +pub enum ConvertMessageError { + /// The XCM provided with the message could not be decoded into XCM. + InvalidXCM, + /// The XCM provided with the message could not be decoded into versioned XCM. + InvalidVersionedXCM, + /// Invalid claimer MultiAddress provided in payload. + InvalidClaimer, + /// Invalid foreign ERC20 token ID + InvalidAsset, +} + +pub trait ConvertMessage { + fn convert(message: Message) -> Result, ConvertMessageError>; +} + +pub struct MessageToXcm + where + EthereumNetwork: Get, + InboundQueuePalletInstance: Get, + ConvertAssetId: MaybeEquivalence, +{ + _phantom: PhantomData<(EthereumNetwork, InboundQueuePalletInstance, ConvertAssetId)>, +} + +impl ConvertMessage +for MessageToXcm + where + EthereumNetwork: Get, + InboundQueuePalletInstance: Get, + ConvertAssetId: MaybeEquivalence, +{ + fn convert(message: Message) -> Result, ConvertMessageError> { + let mut message_xcm: Xcm<()> = Xcm::new(); + if message.xcm.len() > 0 { + // Decode xcm + let versioned_xcm = VersionedXcm::<()>::decode_with_depth_limit( + MAX_XCM_DECODE_DEPTH, + &mut message.xcm.as_ref(), + ) + .map_err(|_| ConvertMessageError::InvalidVersionedXCM)?; + message_xcm = versioned_xcm.try_into().map_err(|_| ConvertMessageError::InvalidXCM)?; + } + + log::debug!(target: LOG_TARGET,"xcm decoded as {:?}", message_xcm); + + let network = EthereumNetwork::get(); + + let origin_location = Location::new(2, GlobalConsensus(network)) + .push_interior(AccountKey20 { key: message.origin.into(), network: None }) + .map_err(|_| ConvertMessageError::InvalidXCM)?; + + let network = EthereumNetwork::get(); + + let fee_asset = Location::new(1, Here); + let fee_value = 1_000_000_000u128; // TODO get from command + let fee: xcm::prelude::Asset = (fee_asset, fee_value).into(); + let mut instructions = vec![ + ReceiveTeleportedAsset(fee.clone().into()), + BuyExecution { fees: fee, weight_limit: Unlimited }, + DescendOrigin(PalletInstance(InboundQueuePalletInstance::get()).into()), + UniversalOrigin(GlobalConsensus(network)), + ]; + + for asset in &message.assets { + match asset { + Asset::NativeTokenERC20 { token_id, value } => { + let token_location: Location = Location::new( + 2, + [ + GlobalConsensus(EthereumNetwork::get()), + AccountKey20 { network: None, key: (*token_id).into() }, + ], + ); + instructions.push(ReserveAssetDeposited((token_location, *value).into())); + }, + Asset::ForeignTokenERC20 { token_id, value } => { + let asset_id = ConvertAssetId::convert(&token_id) + .ok_or(ConvertMessageError::InvalidAsset)?; + instructions.push(WithdrawAsset((asset_id, *value).into())); + }, + } + } + + if let Some(claimer) = message.claimer { + let claimer = Junction::decode(&mut claimer.as_ref()) + .map_err(|_| ConvertMessageError::InvalidClaimer)?; + let claimer_location: Location = Location::new(0, [claimer.into()]); + instructions.push(SetAssetClaimer { location: claimer_location }); + } + + // Set the alias origin to the original sender on Ethereum. Important to be before the + // arbitrary XCM that is appended to the message on the next line. + instructions.push(AliasOrigin(origin_location.into())); + + // Add the XCM sent in the message to the end of the xcm instruction + instructions.extend(message_xcm.0); + + Ok(instructions.into()) + } +} + +#[cfg(test)] +mod tests { + use crate::inbound::{ + v2::{ConvertMessage, Message, MessageToXcm}, + CallIndex, GlobalConsensusEthereumConvertsFor, + }; + use codec::Decode; + use frame_support::{assert_ok, parameter_types}; + use hex_literal::hex; + use sp_runtime::traits::ConstU8; + 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 = + GlobalConsensusEthereumConvertsFor::<[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!( + GlobalConsensusEthereumConvertsFor::<[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()); + } + } + + #[test] + fn test_convert_message() { + let payload = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf040197874824853fb4ad04794ccfd1cc8d2a7463839cfcbc6a315a1045c60ab85f400000b2d3595bf00600000000000000000000").to_vec(); + let message = Message::decode(&mut payload.as_ref()); + assert_ok!(message.clone()); + let result = MessageToXcm::>::convert(message.unwrap()); + assert_ok!(result); + } +} 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..def9b1af6207 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,7 +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_router_primitives::{inbound::MessageToXcm, outbound::EthereumBlobExporter}; +use snowbridge_router_primitives::{ + outbound::{v1::EthereumBlobExporter, v2::EthereumBlobExporter as EthereumBlobExporterV2}, +}; use sp_core::H160; use testnet_parachains_constants::westend::{ currency::*, @@ -84,7 +86,7 @@ impl snowbridge_pallet_inbound_queue::Config for Runtime { type GatewayAddress = EthereumGatewayAddress; #[cfg(feature = "runtime-benchmarks")] type Helper = Runtime; - type MessageConverter = MessageToXcm< + type MessageConverter = snowbridge_router_primitives::inbound::v1::MessageToXcm< CreateAssetCall, CreateAssetDeposit, ConstU8, @@ -102,6 +104,21 @@ 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; + #[cfg(not(feature = "runtime-benchmarks"))] + type XcmSender = XcmRouter; + #[cfg(feature = "runtime-benchmarks")] + type XcmSender = DoNothingRouter; + type GatewayAddress = EthereumGatewayAddress; + #[cfg(feature = "runtime-benchmarks")] + type Helper = Runtime; + type WeightInfo = crate::weights::snowbridge_pallet_inbound_queue_v2::WeightInfo; + type AssetHubParaId = ConstU32<1000>; + type MessageConverter = snowbridge_router_primitives::inbound::v2::MessageToXcm, EthereumSystem>; +} + impl snowbridge_pallet_outbound_queue::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Hashing = Keccak256; 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..8f8f1c93bd6d 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 @@ -563,6 +563,7 @@ 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, // Message Queue. Importantly, is registered last so that messages are processed after // the `on_initialize` hooks of bridging pallets. @@ -621,6 +622,7 @@ mod benches { [snowbridge_pallet_outbound_queue, EthereumOutboundQueue] [snowbridge_pallet_system, EthereumSystem] [snowbridge_pallet_ethereum_client, EthereumBeaconClient] + [snowbridge_pallet_inbound_queue_v2, EthereumInboundQueueV2] ); } From 6f238eb925a3d637b30483ab69bbff168922c683 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Thu, 21 Nov 2024 08:58:41 +0200 Subject: [PATCH 02/52] adds sparse bitmap --- Cargo.lock | 53 ++++++ .../pallets/inbound-queue-v2/src/lib.rs | 2 +- bridges/snowbridge/primitives/core/src/lib.rs | 1 + .../primitives/core/src/sparse_bitmap.rs | 157 ++++++++++++++++++ 4 files changed, 212 insertions(+), 1 deletion(-) create mode 100644 bridges/snowbridge/primitives/core/src/sparse_bitmap.rs diff --git a/Cargo.lock b/Cargo.lock index 330c2563d976..6d58f682ae76 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24785,6 +24785,16 @@ dependencies = [ "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "snowbridge-inbound-queue-v2-runtime-api" +version = "0.2.0" +dependencies = [ + "snowbridge-core 0.2.0", + "snowbridge-router-primitives 0.9.0", + "sp-api 26.0.0", + "staging-xcm 7.0.0", +] + [[package]] name = "snowbridge-milagro-bls" version = "1.5.4" @@ -25010,6 +25020,47 @@ 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", + "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" @@ -25108,6 +25159,7 @@ name = "snowbridge-router-primitives" version = "0.9.0" dependencies = [ "frame-support 28.0.0", + "frame-system 28.0.0", "hex-literal", "log", "parity-scale-codec", @@ -25118,6 +25170,7 @@ 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-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index 91c0acaa97a1..16d312809ede 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -168,7 +168,7 @@ pub mod pallet { /// The nonce of the message been processed or not #[pallet::storage] - pub type Nonce = StorageMap<_, Identity, u64, bool, ValueQuery>; + pub type Nonce = StorageMap<_, Twox64Concat, u128, u128, ValueQuery>; /// The current operating mode of the pallet. #[pallet::storage] diff --git a/bridges/snowbridge/primitives/core/src/lib.rs b/bridges/snowbridge/primitives/core/src/lib.rs index 7ad129a52542..d88e387d7c24 100644 --- a/bridges/snowbridge/primitives/core/src/lib.rs +++ b/bridges/snowbridge/primitives/core/src/lib.rs @@ -14,6 +14,7 @@ pub mod operating_mode; pub mod outbound; pub mod pricing; pub mod ringbuffer; +pub mod sparse_bitmap; pub use location::{AgentId, AgentIdOf, TokenId, TokenIdOf}; pub use polkadot_parachain_primitives::primitives::{ diff --git a/bridges/snowbridge/primitives/core/src/sparse_bitmap.rs b/bridges/snowbridge/primitives/core/src/sparse_bitmap.rs new file mode 100644 index 000000000000..894f159ef64e --- /dev/null +++ b/bridges/snowbridge/primitives/core/src/sparse_bitmap.rs @@ -0,0 +1,157 @@ +use frame_support::storage::StorageMap; +use sp_std::marker::PhantomData; + +pub trait SparseBitmap +where + BitMap: StorageMap, +{ + fn get(index: u128) -> bool; + fn set(index: u128); +} + +pub struct SparseBitmapImpl(PhantomData); + +impl SparseBitmap for SparseBitmapImpl +where + BitMap: StorageMap, +{ + fn get(index: u128) -> bool { + // Calculate bucket and mask + let bucket = index >> 7; // Divide by 2^7 (128 bits) + let mask = 1u128 << (index & 127); // Mask for the bit in the bucket + + // Retrieve bucket and check bit + let bucket_value = BitMap::get(bucket); + bucket_value & mask != 0 + } + + fn set(index: u128) { + // Calculate bucket and mask + let bucket = index >> 7; // Divide by 2^7 (128 bits) + let mask = 1u128 << (index & 127); // Mask for the bit in the bucket + + // Mutate the storage to set the bit + BitMap::mutate(bucket, |value| { + *value |= mask; // Set the bit in the bucket + }); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use frame_support::{ + storage::{generator::StorageMap as StorageMapHelper, storage_prefix}, + Twox64Concat, + }; + use sp_io::TestExternalities; + pub struct MockStorageMap; + + impl StorageMapHelper for MockStorageMap { + type Query = u128; + type Hasher = Twox64Concat; + fn pallet_prefix() -> &'static [u8] { + b"MyModule" + } + + fn storage_prefix() -> &'static [u8] { + b"MyStorageMap" + } + + fn prefix_hash() -> [u8; 32] { + storage_prefix(Self::pallet_prefix(), Self::storage_prefix()) + } + + fn from_optional_value_to_query(v: Option) -> Self::Query { + v.unwrap_or_default() + } + + fn from_query_to_optional_value(v: Self::Query) -> Option { + Some(v) + } + } + + type TestSparseBitmap = SparseBitmapImpl; + + #[test] + fn test_sparse_bitmap_set_and_get() { + TestExternalities::default().execute_with(|| { + let index = 300; + let bucket = index >> 7; + let mask = 1u128 << (index & 127); + + // Test initial state + assert_eq!(MockStorageMap::get(bucket), 0); + assert!(!TestSparseBitmap::get(index)); + + // Set the bit + TestSparseBitmap::set(index); + + // Test after setting + assert_eq!(MockStorageMap::get(bucket), mask); + assert!(TestSparseBitmap::get(index)); + }); + } + + #[test] + fn test_sparse_bitmap_multiple_sets() { + TestExternalities::default().execute_with(|| { + let index1 = 300; + let index2 = 305; // Same bucket, different bit + let bucket = index1 >> 7; + + let mask1 = 1u128 << (index1 & 127); + let mask2 = 1u128 << (index2 & 127); + + // Test initial state + assert_eq!(MockStorageMap::get(bucket), 0); + assert!(!TestSparseBitmap::get(index1)); + assert!(!TestSparseBitmap::get(index2)); + + // Set the first bit + TestSparseBitmap::set(index1); + + // Test after first set + assert_eq!(MockStorageMap::get(bucket), mask1); + assert!(TestSparseBitmap::get(index1)); + assert!(!TestSparseBitmap::get(index2)); + + // Set the second bit + TestSparseBitmap::set(index2); + + // Test after second set + assert_eq!(MockStorageMap::get(bucket), mask1 | mask2); // Bucket should contain both masks + assert!(TestSparseBitmap::get(index1)); + assert!(TestSparseBitmap::get(index2)); + }) + } + + #[test] + fn test_sparse_bitmap_different_buckets() { + TestExternalities::default().execute_with(|| { + let index1 = 300; // Bucket 1 + let index2 = 300 + (1 << 7); // Bucket 2 (128 bits apart) + + let bucket1 = index1 >> 7; + let bucket2 = index2 >> 7; + + let mask1 = 1u128 << (index1 & 127); + let mask2 = 1u128 << (index2 & 127); + + // Test initial state + assert_eq!(MockStorageMap::get(bucket1), 0); + assert_eq!(MockStorageMap::get(bucket2), 0); + + // Set bits in different buckets + TestSparseBitmap::set(index1); + TestSparseBitmap::set(index2); + + // Test after setting + assert_eq!(MockStorageMap::get(bucket1), mask1); // Bucket 1 should contain mask1 + assert_eq!(MockStorageMap::get(bucket2), mask2); // Bucket 2 should contain mask2 + + assert!(TestSparseBitmap::get(index1)); + assert!(TestSparseBitmap::get(index2)); + }) + } +} From b0115cdcec5b48282416889cfa5320138044d1fa Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Thu, 21 Nov 2024 09:19:45 +0200 Subject: [PATCH 03/52] use sparse bitmap --- .../pallets/inbound-queue-v2/src/api.rs | 8 +- .../inbound-queue-v2/src/benchmarking.rs | 32 +- .../pallets/inbound-queue-v2/src/envelope.rs | 40 +- .../pallets/inbound-queue-v2/src/lib.rs | 355 +++++++++--------- .../pallets/inbound-queue-v2/src/mock.rs | 190 +++++----- .../pallets/inbound-queue-v2/src/test.rs | 260 ++++++------- .../pallets/inbound-queue-v2/src/types.rs | 5 + .../primitives/core/src/sparse_bitmap.rs | 3 + 8 files changed, 452 insertions(+), 441 deletions(-) create mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/src/types.rs diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs index 47207df7383c..a285a7c5af42 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs @@ -8,9 +8,9 @@ use snowbridge_router_primitives::inbound::v2::{ConvertMessage, Message}; use xcm::latest::Xcm; pub fn dry_run(message: Message, _proof: Proof) -> Result, Error> - where - T: Config, +where + T: Config, { - let xcm = T::MessageConverter::convert(message).map_err(|e| Error::::ConvertMessage(e))?; - Ok(xcm) + let xcm = T::MessageConverter::convert(message).map_err(|e| Error::::ConvertMessage(e))?; + Ok(xcm) } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/benchmarking.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/benchmarking.rs index b6d2a9739f3d..4c5df07b27ac 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/benchmarking.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/benchmarking.rs @@ -10,29 +10,29 @@ use snowbridge_pallet_inbound_queue_fixtures_v2::register_token::make_register_t #[benchmarks] mod benchmarks { - use super::*; + use super::*; - #[benchmark] - fn submit() -> Result<(), BenchmarkError> { - let caller: T::AccountId = whitelisted_caller(); + #[benchmark] + fn submit() -> Result<(), BenchmarkError> { + let caller: T::AccountId = whitelisted_caller(); - let create_message = make_register_token_message(); + let create_message = make_register_token_message(); - T::Helper::initialize_storage( - create_message.finalized_header, - create_message.block_roots_root, - ); + T::Helper::initialize_storage( + create_message.finalized_header, + create_message.block_roots_root, + ); - #[block] - { - assert_ok!(InboundQueue::::submit( + #[block] + { + assert_ok!(InboundQueue::::submit( RawOrigin::Signed(caller.clone()).into(), create_message.message, )); - } + } - Ok(()) - } + Ok(()) + } - impl_benchmark_test_suite!(InboundQueue, crate::mock::new_tester(), crate::mock::Test); + 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 index 41353954e5b2..8c9b137c64ba 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/envelope.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/envelope.rs @@ -15,33 +15,33 @@ sol! { /// 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, - /// Total fee paid in Ether on Ethereum, should cover all the cost - pub fee: u128, - /// The inner payload generated from the source application. - pub payload: Vec, + /// 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, + /// Total fee paid in Ether on Ethereum, should cover all the cost + pub fee: u128, + /// 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; + type Error = EnvelopeDecodeError; - fn try_from(log: &Log) -> Result { - let topics: Vec = log.topics.iter().map(|x| B256::from_slice(x.as_ref())).collect(); + 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)?; + let event = OutboundMessageAccepted::decode_log(topics, &log.data, true) + .map_err(|_| EnvelopeDecodeError)?; - Ok(Self { - gateway: log.address, - nonce: event.nonce, - fee: event.fee, - payload: event.payload, - }) - } + Ok(Self { + gateway: log.address, + nonce: event.nonce, + fee: event.fee, + 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 index 16d312809ede..fddfc4e6df56 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -27,6 +27,7 @@ mod envelope; #[cfg(feature = "runtime-benchmarks")] mod benchmarking; +mod types; pub mod weights; @@ -43,11 +44,12 @@ use frame_system::ensure_signed; use scale_info::TypeInfo; use sp_core::H160; use sp_std::vec; +use types::Nonce; use xcm::prelude::{send_xcm, Junction::*, Location, SendError as XcmpSendError, SendXcm}; use snowbridge_core::{ - inbound::{Message, VerificationError, Verifier}, - BasicOperatingMode, + inbound::{Message, VerificationError, Verifier}, + BasicOperatingMode, }; use snowbridge_router_primitives::inbound::v2::{ConvertMessage, Message as MessageV2}; pub use weights::WeightInfo; @@ -55,6 +57,7 @@ pub use weights::WeightInfo; #[cfg(feature = "runtime-benchmarks")] use snowbridge_beacon_primitives::BeaconHeader; +use snowbridge_core::sparse_bitmap::SparseBitmap; use snowbridge_router_primitives::inbound::v2::ConvertMessageError; pub use pallet::*; @@ -63,178 +66,178 @@ pub const LOG_TARGET: &str = "snowbridge-inbound-queue:v2"; #[frame_support::pallet] pub mod pallet { - use super::*; - - use frame_support::pallet_prelude::*; - use frame_system::pallet_prelude::*; - - #[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; - - /// XCM message sender - type XcmSender: SendXcm; - /// Address of the Gateway contract - #[pallet::constant] - type GatewayAddress: Get; - type WeightInfo: WeightInfo; - /// AssetHub parachain ID - type AssetHubParaId: Get; - type MessageConverter: ConvertMessage; - #[cfg(feature = "runtime-benchmarks")] - type Helper: BenchmarkHelper; - } - - #[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 nonce - nonce: u64, - /// ID of the XCM message which was forwarded to the final destination parachain - message_id: [u8; 32], - }, - /// 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 nonce of the message been processed or not - #[pallet::storage] - pub type Nonce = StorageMap<_, Twox64Concat, u128, u128, 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); - - // Verify the message has not been processed - ensure!(!Nonce::::contains_key(envelope.nonce), Error::::InvalidNonce); - - // Decode payload into `MessageV2` - let message = MessageV2::decode_all(&mut envelope.payload.as_ref()) - .map_err(|_| Error::::InvalidPayload)?; - - let xcm = - T::MessageConverter::convert(message).map_err(|e| Error::::ConvertMessage(e))?; - - // Todo: Deposit fee(in Ether) to RewardLeger which should cover all of: - // T::RewardLeger::deposit(who, envelope.fee.into())?; - // a. The submit extrinsic cost on BH - // b. The delivery cost to AH - // c. The execution cost on AH - // d. The execution cost on destination chain(if any) - // e. The reward - - // Attempt to forward XCM to AH - let dest = Location::new(1, [Parachain(T::AssetHubParaId::get())]); - let (message_id, _) = send_xcm::(dest, xcm).map_err(Error::::from)?; - Self::deposit_event(Event::MessageReceived { nonce: envelope.nonce, message_id }); - - // Set nonce flag to true - Nonce::::insert(envelope.nonce, ()); - - 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(()) - } - } + use super::*; + + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[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; + + /// XCM message sender + type XcmSender: SendXcm; + /// Address of the Gateway contract + #[pallet::constant] + type GatewayAddress: Get; + type WeightInfo: WeightInfo; + /// AssetHub parachain ID + type AssetHubParaId: Get; + type MessageConverter: ConvertMessage; + #[cfg(feature = "runtime-benchmarks")] + type Helper: BenchmarkHelper; + } + + #[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 nonce + nonce: u64, + /// ID of the XCM message which was forwarded to the final destination parachain + message_id: [u8; 32], + }, + /// 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 nonce of the message been processed or not + #[pallet::storage] + pub type NoncesBitmap = StorageMap<_, Twox64Concat, u128, u128, 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); + + // Verify the message has not been processed + ensure!(!>::get(envelope.nonce.into()), Error::::InvalidNonce); + + // Decode payload into `MessageV2` + let message = MessageV2::decode_all(&mut envelope.payload.as_ref()) + .map_err(|_| Error::::InvalidPayload)?; + + let xcm = + T::MessageConverter::convert(message).map_err(|e| Error::::ConvertMessage(e))?; + + // Todo: Deposit fee(in Ether) to RewardLeger which should cover all of: + // T::RewardLeger::deposit(who, envelope.fee.into())?; + // a. The submit extrinsic cost on BH + // b. The delivery cost to AH + // c. The execution cost on AH + // d. The execution cost on destination chain(if any) + // e. The reward + + // Attempt to forward XCM to AH + let dest = Location::new(1, [Parachain(T::AssetHubParaId::get())]); + let (message_id, _) = send_xcm::(dest, xcm).map_err(Error::::from)?; + Self::deposit_event(Event::MessageReceived { nonce: envelope.nonce, message_id }); + + // Set nonce flag to true + >::set(envelope.nonce.into()); + + 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(()) + } + } } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs index f36535d88c3a..63768340c193 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs @@ -6,17 +6,17 @@ use crate::{self as inbound_queue}; use frame_support::{derive_impl, parameter_types, traits::ConstU32}; use hex_literal::hex; use snowbridge_beacon_primitives::{ - types::deneb, BeaconHeader, ExecutionProof, Fork, ForkVersions, VersionedExecutionPayloadHeader, + types::deneb, BeaconHeader, ExecutionProof, Fork, ForkVersions, VersionedExecutionPayloadHeader, }; use snowbridge_core::{ - inbound::{Log, Proof, VerificationError}, - TokenId, + inbound::{Log, Proof, VerificationError}, + TokenId, }; use snowbridge_router_primitives::inbound::v2::MessageToXcm; use sp_core::H160; use sp_runtime::{ - traits::{IdentifyAccount, IdentityLookup, MaybeEquivalence, Verify}, - BuildStorage, MultiSignature, + traits::{IdentifyAccount, IdentityLookup, MaybeEquivalence, Verify}, + BuildStorage, MultiSignature, }; use sp_std::{convert::From, default::Default}; use xcm::{latest::SendXcm, prelude::*}; @@ -40,10 +40,10 @@ 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; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type AccountData = pallet_balances::AccountData; + type Block = Block; } parameter_types! { @@ -52,9 +52,9 @@ parameter_types! { #[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] impl pallet_balances::Config for Test { - type Balance = Balance; - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = System; + type Balance = Balance; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; } parameter_types! { @@ -83,63 +83,63 @@ parameter_types! { } impl snowbridge_pallet_ethereum_client::Config for Test { - type RuntimeEvent = RuntimeEvent; - type ForkVersions = ChainForkVersions; - type FreeHeadersInterval = ConstU32<32>; - type WeightInfo = (); + 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(()) - } + fn verify(_: &Log, _: &Proof) -> Result<(), VerificationError> { + Ok(()) + } } const GATEWAY_ADDRESS: [u8; 20] = hex!["eda338e4dc46038493b885327842fd3e301cab39"]; #[cfg(feature = "runtime-benchmarks")] impl BenchmarkHelper for Test { - // not implemented since the MockVerifier is used for tests - fn initialize_storage(_: BeaconHeader, _: H256) {} + // 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<()>; + 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 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) - } + fn deliver(xcm: Self::Ticket) -> core::result::Result { + let hash = xcm.using_encoded(sp_io::hashing::blake2_256); + Ok(hash) + } } pub struct MockTokenIdConvert; impl MaybeEquivalence for MockTokenIdConvert { - fn convert(_id: &TokenId) -> Option { - Some(Location::parent()) - } - fn convert_back(_loc: &Location) -> Option { - None - } + fn convert(_id: &TokenId) -> Option { + Some(Location::parent()) + } + fn convert_back(_loc: &Location) -> Option { + None + } } parameter_types! { @@ -150,41 +150,41 @@ parameter_types! { } impl inbound_queue::Config for Test { - type RuntimeEvent = RuntimeEvent; - type Verifier = MockVerifier; - type XcmSender = MockXcmSender; - type WeightInfo = (); - type GatewayAddress = GatewayAddress; - type AssetHubParaId = ConstU32<1000>; - type MessageConverter = - MessageToXcm; - #[cfg(feature = "runtime-benchmarks")] - type Helper = Test; + type RuntimeEvent = RuntimeEvent; + type Verifier = MockVerifier; + type XcmSender = MockXcmSender; + type WeightInfo = (); + type GatewayAddress = GatewayAddress; + type AssetHubParaId = ConstU32<1000>; + type MessageConverter = + MessageToXcm; + #[cfg(feature = "runtime-benchmarks")] + type Helper = Test; } pub fn last_events(n: usize) -> Vec { - frame_system::Pallet::::events() - .into_iter() - .rev() - .take(n) - .rev() - .map(|e| e.event) - .collect() + 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); + assert_eq!(last_events(e.len()), e); } pub fn setup() { - System::set_block_number(1); + 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 + 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: @@ -192,7 +192,7 @@ pub fn new_tester() -> sp_io::TestExternalities { // ./make-bindings // cargo test --test register_token -- --nocapture pub fn mock_event_log() -> Log { - Log { + Log { // gateway address address: hex!("eda338e4dc46038493b885327842fd3e301cab39").into(), topics: vec![ @@ -208,7 +208,7 @@ pub fn mock_event_log() -> Log { } pub fn mock_event_log_invalid_channel() -> Log { - Log { + Log { address: hex!("eda338e4dc46038493b885327842fd3e301cab39").into(), topics: vec![ hex!("7153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84f").into(), @@ -221,7 +221,7 @@ pub fn mock_event_log_invalid_channel() -> Log { } pub fn mock_event_log_invalid_gateway() -> Log { - Log { + Log { // gateway address address: H160::zero(), topics: vec![ @@ -237,28 +237,28 @@ pub fn mock_event_log_invalid_gateway() -> Log { } 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![], - } + 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![], + } } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs index 148a01b3efe1..d2720f2dcf05 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs @@ -13,146 +13,146 @@ use codec::DecodeLimit; use snowbridge_router_primitives::inbound::v2::InboundAsset; use sp_core::H256; use xcm::{ - opaque::latest::{ - prelude::{ClearOrigin, ReceiveTeleportedAsset}, - Asset, - }, - prelude::*, - VersionedXcm, MAX_XCM_DECODE_DEPTH, + opaque::latest::{ + prelude::{ClearOrigin, ReceiveTeleportedAsset}, + Asset, + }, + prelude::*, + VersionedXcm, MAX_XCM_DECODE_DEPTH, }; #[test] fn test_submit_happy_path() { - new_tester().execute_with(|| { - let relayer: AccountId = Keyring::Bob.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(), - }, - }; - - assert_ok!(InboundQueue::submit(origin.clone(), message.clone())); - expect_events(vec![InboundQueueEvent::MessageReceived { - 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, - ], - } - .into()]); - }); + new_tester().execute_with(|| { + let relayer: AccountId = Keyring::Bob.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(), + }, + }; + + assert_ok!(InboundQueue::submit(origin.clone(), message.clone())); + expect_events(vec![InboundQueueEvent::MessageReceived { + 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, + ], + } + .into()]); + }); } #[test] fn test_submit_xcm_invalid_channel() { - new_tester().execute_with(|| { - let relayer: AccountId = Keyring::Bob.into(); - let origin = RuntimeOrigin::signed(relayer); - - // 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!( + new_tester().execute_with(|| { + let relayer: AccountId = Keyring::Bob.into(); + let origin = RuntimeOrigin::signed(relayer); + + // 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); - - // 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!( + new_tester().execute_with(|| { + let relayer: AccountId = Keyring::Bob.into(); + let origin = RuntimeOrigin::signed(relayer); + + // 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); - - // 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())); - - // Submit the same again - assert_noop!( + new_tester().execute_with(|| { + let relayer: AccountId = Keyring::Bob.into(); + let origin = RuntimeOrigin::signed(relayer); + + // 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())); + + // Submit the same again + assert_noop!( InboundQueue::submit(origin.clone(), message.clone()), Error::::InvalidNonce ); - }); + }); } #[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( + 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); - }); + assert_noop!(InboundQueue::submit(origin, message), Error::::Halted); + }); } #[test] fn test_set_operating_mode_root_only() { - new_tester().execute_with(|| { - assert_noop!( + new_tester().execute_with(|| { + assert_noop!( InboundQueue::set_operating_mode( RuntimeOrigin::signed(Keyring::Bob.into()), snowbridge_core::BasicOperatingMode::Halted ), DispatchError::BadOrigin ); - }); + }); } #[test] fn test_send_native_erc20_token_payload() { - new_tester().execute_with(|| { + new_tester().execute_with(|| { // To generate test data: forge test --match-test testSendEther -vvvv let payload = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf04005615deb798bb3e4dfa0139dfa1b3d433cc23b72f0000b2d3595bf00600000000000000000000").to_vec(); let message = MessageV2::decode(&mut payload.as_ref()); @@ -181,7 +181,7 @@ fn test_send_native_erc20_token_payload() { #[test] fn test_send_foreign_erc20_token_payload() { - new_tester().execute_with(|| { + new_tester().execute_with(|| { let payload = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf040197874824853fb4ad04794ccfd1cc8d2a7463839cfcbc6a315a1045c60ab85f400000b2d3595bf00600000000000000000000").to_vec(); let message = MessageV2::decode(&mut payload.as_ref()); assert_ok!(message.clone()); @@ -209,7 +209,7 @@ fn test_send_foreign_erc20_token_payload() { #[test] fn test_register_token_inbound_message_with_xcm_and_claimer() { - new_tester().execute_with(|| { + new_tester().execute_with(|| { let payload = hex!("5991a2df15a8f6a256d3ec51e99254cd3fb576a904005615deb798bb3e4dfa0139dfa1b3d433cc23b72f00000000000000000000000000000000300508020401000002286bee0a015029e3b139f4393adda86303fcdaa35f60bb7092bf").to_vec(); let message = MessageV2::decode(&mut payload.as_ref()); assert_ok!(message.clone()); @@ -260,49 +260,49 @@ fn test_register_token_inbound_message_with_xcm_and_claimer() { #[test] fn encode_xcm() { - new_tester().execute_with(|| { - let total_fee_asset: Asset = (Location::parent(), 1_000_000_000).into(); + new_tester().execute_with(|| { + let total_fee_asset: Asset = (Location::parent(), 1_000_000_000).into(); - let instructions: Xcm<()> = - vec![ReceiveTeleportedAsset(total_fee_asset.into()), ClearOrigin].into(); + let instructions: Xcm<()> = + vec![ReceiveTeleportedAsset(total_fee_asset.into()), ClearOrigin].into(); - let versioned_xcm_message = VersionedXcm::V5(instructions.clone()); + let versioned_xcm_message = VersionedXcm::V5(instructions.clone()); - let xcm_bytes = VersionedXcm::encode(&versioned_xcm_message); - let hex_string = hex::encode(xcm_bytes.clone()); + let xcm_bytes = VersionedXcm::encode(&versioned_xcm_message); + let hex_string = hex::encode(xcm_bytes.clone()); - println!("xcm hex: {}", hex_string); + println!("xcm hex: {}", hex_string); - let versioned_xcm = VersionedXcm::<()>::decode_with_depth_limit( - MAX_XCM_DECODE_DEPTH, - &mut xcm_bytes.as_ref(), - ); + let versioned_xcm = VersionedXcm::<()>::decode_with_depth_limit( + MAX_XCM_DECODE_DEPTH, + &mut xcm_bytes.as_ref(), + ); - assert_ok!(versioned_xcm.clone()); + assert_ok!(versioned_xcm.clone()); - // Check if decoding was successful - let decoded_instructions = match versioned_xcm.unwrap() { - VersionedXcm::V5(decoded) => decoded, - _ => { - panic!("unexpected xcm version found") - }, - }; + // Check if decoding was successful + let decoded_instructions = match versioned_xcm.unwrap() { + VersionedXcm::V5(decoded) => decoded, + _ => { + panic!("unexpected xcm version found") + }, + }; - let mut original_instructions = instructions.into_iter(); - let mut decoded_instructions = decoded_instructions.into_iter(); + let mut original_instructions = instructions.into_iter(); + let mut decoded_instructions = decoded_instructions.into_iter(); - let original_first = original_instructions.next().take(); - let decoded_first = decoded_instructions.next().take(); - assert_eq!( - original_first, decoded_first, - "First instruction (ReceiveTeleportedAsset) does not match." - ); + let original_first = original_instructions.next().take(); + let decoded_first = decoded_instructions.next().take(); + assert_eq!( + original_first, decoded_first, + "First instruction (ReceiveTeleportedAsset) does not match." + ); - let original_second = original_instructions.next().take(); - let decoded_second = decoded_instructions.next().take(); - assert_eq!( - original_second, decoded_second, - "Second instruction (ClearOrigin) does not match." - ); - }); + let original_second = original_instructions.next().take(); + let decoded_second = decoded_instructions.next().take(); + assert_eq!( + original_second, decoded_second, + "Second instruction (ClearOrigin) does not match." + ); + }); } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/types.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/types.rs new file mode 100644 index 000000000000..eecf07b1f830 --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/types.rs @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use snowbridge_core::sparse_bitmap::SparseBitmapImpl; + +pub type Nonce = SparseBitmapImpl>; diff --git a/bridges/snowbridge/primitives/core/src/sparse_bitmap.rs b/bridges/snowbridge/primitives/core/src/sparse_bitmap.rs index 894f159ef64e..810c4747c382 100644 --- a/bridges/snowbridge/primitives/core/src/sparse_bitmap.rs +++ b/bridges/snowbridge/primitives/core/src/sparse_bitmap.rs @@ -1,6 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork use frame_support::storage::StorageMap; use sp_std::marker::PhantomData; +/// Sparse bitmap implementation. pub trait SparseBitmap where BitMap: StorageMap, From c27def22aeffcb3c9042fdf57b85747f0fc101e9 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Thu, 21 Nov 2024 10:13:24 +0200 Subject: [PATCH 04/52] use payfees --- bridges/snowbridge/primitives/router/src/inbound/v2.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index 69cacdb995fa..b8f1ab8ac782 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -115,7 +115,7 @@ for MessageToXcm let fee: xcm::prelude::Asset = (fee_asset, fee_value).into(); let mut instructions = vec![ ReceiveTeleportedAsset(fee.clone().into()), - BuyExecution { fees: fee, weight_limit: Unlimited }, + PayFees { asset: fee }, DescendOrigin(PalletInstance(InboundQueuePalletInstance::get()).into()), UniversalOrigin(GlobalConsensus(network)), ]; From 93b451604a4452915faddea5650e66d113802e57 Mon Sep 17 00:00:00 2001 From: Clara van Staden Date: Mon, 25 Nov 2024 07:30:16 +0200 Subject: [PATCH 05/52] Update bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs Co-authored-by: Vincent Geddes <117534+vgeddes@users.noreply.github.com> --- bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index fddfc4e6df56..c9374651c563 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -199,7 +199,7 @@ pub mod pallet { ensure!(T::GatewayAddress::get() == envelope.gateway, Error::::InvalidGateway); // Verify the message has not been processed - ensure!(!>::get(envelope.nonce.into()), Error::::InvalidNonce); + ensure!(!Nonce::::get(envelope.nonce.into()), Error::::InvalidNonce); // Decode payload into `MessageV2` let message = MessageV2::decode_all(&mut envelope.payload.as_ref()) From 3a131345eb4325a16e95ec7de44d40acf1f53023 Mon Sep 17 00:00:00 2001 From: Clara van Staden Date: Mon, 25 Nov 2024 07:36:47 +0200 Subject: [PATCH 06/52] Update bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs Co-authored-by: Vincent Geddes <117534+vgeddes@users.noreply.github.com> --- bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index c9374651c563..4faebedd45b8 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -222,7 +222,7 @@ pub mod pallet { Self::deposit_event(Event::MessageReceived { nonce: envelope.nonce, message_id }); // Set nonce flag to true - >::set(envelope.nonce.into()); + Nonce::::set(envelope.nonce.into()); Ok(()) } From 7e234629e09c8882dc14fa4d5a3bf6368f1f5a03 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Mon, 25 Nov 2024 07:37:23 +0200 Subject: [PATCH 07/52] rename NonceBitmap --- bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs | 2 +- bridges/snowbridge/pallets/inbound-queue-v2/src/types.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index 4faebedd45b8..1a85454218b9 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -171,7 +171,7 @@ pub mod pallet { /// The nonce of the message been processed or not #[pallet::storage] - pub type NoncesBitmap = StorageMap<_, Twox64Concat, u128, u128, ValueQuery>; + pub type NonceBitmap = StorageMap<_, Twox64Concat, u128, u128, ValueQuery>; /// The current operating mode of the pallet. #[pallet::storage] diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/types.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/types.rs index eecf07b1f830..150f6028b129 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/types.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/types.rs @@ -2,4 +2,4 @@ // SPDX-FileCopyrightText: 2023 Snowfork use snowbridge_core::sparse_bitmap::SparseBitmapImpl; -pub type Nonce = SparseBitmapImpl>; +pub type Nonce = SparseBitmapImpl>; From 68f5a8e586de211f94eeeee0a023a61bfc083fb6 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Mon, 25 Nov 2024 08:09:52 +0200 Subject: [PATCH 08/52] config T::XcmPrologueFee: Balance --- .../snowbridge/pallets/inbound-queue-v2/src/lib.rs | 13 +++++++++++-- .../snowbridge/pallets/inbound-queue-v2/src/mock.rs | 10 ++++++---- .../snowbridge/pallets/inbound-queue-v2/src/test.rs | 11 +++++------ 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index 1a85454218b9..cc95bb602a56 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -39,13 +39,19 @@ mod test; use codec::{Decode, DecodeAll, Encode}; use envelope::Envelope; -use frame_support::PalletError; use frame_system::ensure_signed; use scale_info::TypeInfo; use sp_core::H160; use sp_std::vec; use types::Nonce; use xcm::prelude::{send_xcm, Junction::*, Location, SendError as XcmpSendError, SendXcm}; +use frame_support::{ + traits::{ + fungible::{Inspect, Mutate}, + }, + PalletError, +}; +use frame_system::pallet_prelude::*; use snowbridge_core::{ inbound::{Message, VerificationError, Verifier}, @@ -64,12 +70,13 @@ pub use pallet::*; pub const LOG_TARGET: &str = "snowbridge-inbound-queue:v2"; +type BalanceOf = +<::Token as Inspect<::AccountId>>::Balance; #[frame_support::pallet] pub mod pallet { use super::*; use frame_support::pallet_prelude::*; - use frame_system::pallet_prelude::*; #[pallet::pallet] pub struct Pallet(_); @@ -95,6 +102,8 @@ pub mod pallet { /// AssetHub parachain ID type AssetHubParaId: Get; type MessageConverter: ConvertMessage; + type XcmPrologueFee: Get>; + type Token: Mutate + Inspect; #[cfg(feature = "runtime-benchmarks")] type Helper: BenchmarkHelper; } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs index 63768340c193..8d39dc0931f4 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs @@ -2,8 +2,8 @@ // SPDX-FileCopyrightText: 2023 Snowfork use super::*; -use crate::{self as inbound_queue}; -use frame_support::{derive_impl, parameter_types, traits::ConstU32}; +use crate::{self as inbound_queue_v2}; +use frame_support::{derive_impl, parameter_types, traits::{ConstU32, ConstU128}}; use hex_literal::hex; use snowbridge_beacon_primitives::{ types::deneb, BeaconHeader, ExecutionProof, Fork, ForkVersions, VersionedExecutionPayloadHeader, @@ -29,7 +29,7 @@ frame_support::construct_runtime!( 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}, + InboundQueue: inbound_queue_v2::{Pallet, Call, Storage, Event}, } ); @@ -149,7 +149,7 @@ parameter_types! { pub AssetHubLocation: InteriorLocation = Parachain(1000).into(); } -impl inbound_queue::Config for Test { +impl inbound_queue_v2::Config for Test { type RuntimeEvent = RuntimeEvent; type Verifier = MockVerifier; type XcmSender = MockXcmSender; @@ -158,6 +158,8 @@ impl inbound_queue::Config for Test { type AssetHubParaId = ConstU32<1000>; type MessageConverter = MessageToXcm; + type Token = Balances; + type XcmPrologueFee = ConstU128<1_000_000_000>; #[cfg(feature = "runtime-benchmarks")] type Helper = Test; } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs index d2720f2dcf05..9554a702cf07 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs @@ -10,12 +10,11 @@ use sp_runtime::DispatchError; use crate::{mock::*, Error, Event as InboundQueueEvent}; use codec::DecodeLimit; -use snowbridge_router_primitives::inbound::v2::InboundAsset; +use snowbridge_router_primitives::inbound::v2::Asset; use sp_core::H256; use xcm::{ opaque::latest::{ prelude::{ClearOrigin, ReceiveTeleportedAsset}, - Asset, }, prelude::*, VersionedXcm, MAX_XCM_DECODE_DEPTH, @@ -168,7 +167,7 @@ fn test_send_native_erc20_token_payload() { assert_eq!(expected_origin, inbound_message.origin); assert_eq!(1, inbound_message.assets.len()); - if let InboundAsset::NativeTokenERC20 { token_id, value } = &inbound_message.assets[0] { + if let Asset::NativeTokenERC20 { token_id, value } = &inbound_message.assets[0] { assert_eq!(expected_token_id, *token_id); assert_eq!(expected_value, *value); } else { @@ -196,7 +195,7 @@ fn test_send_foreign_erc20_token_payload() { assert_eq!(expected_origin, inbound_message.origin); assert_eq!(1, inbound_message.assets.len()); - if let InboundAsset::ForeignTokenERC20 { token_id, value } = &inbound_message.assets[0] { + if let Asset::ForeignTokenERC20 { token_id, value } = &inbound_message.assets[0] { assert_eq!(expected_token_id, *token_id); assert_eq!(expected_value, *value); } else { @@ -224,7 +223,7 @@ fn test_register_token_inbound_message_with_xcm_and_claimer() { assert_eq!(expected_origin, inbound_message.origin); assert_eq!(1, inbound_message.assets.len()); - if let InboundAsset::NativeTokenERC20 { token_id, value } = &inbound_message.assets[0] { + if let Asset::NativeTokenERC20 { token_id, value } = &inbound_message.assets[0] { assert_eq!(expected_token_id, *token_id); assert_eq!(expected_value, *value); } else { @@ -261,7 +260,7 @@ fn test_register_token_inbound_message_with_xcm_and_claimer() { #[test] fn encode_xcm() { new_tester().execute_with(|| { - let total_fee_asset: Asset = (Location::parent(), 1_000_000_000).into(); + let total_fee_asset: xcm::opaque::latest::Asset = (Location::parent(), 1_000_000_000).into(); let instructions: Xcm<()> = vec![ReceiveTeleportedAsset(total_fee_asset.into()), ClearOrigin].into(); From 02e808118589ab3df03a11baf5f51db336ca9ec1 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Mon, 25 Nov 2024 09:13:28 +0200 Subject: [PATCH 09/52] burn fees --- Cargo.lock | 1 + .../pallets/inbound-queue-v2/src/lib.rs | 37 +++++++++++----- .../pallets/inbound-queue-v2/src/mock.rs | 39 ++++++++++++++++- .../pallets/inbound-queue-v2/src/test.rs | 7 ++-- bridges/snowbridge/primitives/core/Cargo.toml | 5 ++- .../snowbridge/primitives/core/src/fees.rs | 42 +++++++++++++++++++ bridges/snowbridge/primitives/core/src/lib.rs | 1 + 7 files changed, 116 insertions(+), 16 deletions(-) create mode 100644 bridges/snowbridge/primitives/core/src/fees.rs diff --git a/Cargo.lock b/Cargo.lock index 6d58f682ae76..a371125e7842 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24703,6 +24703,7 @@ dependencies = [ "frame-system 28.0.0", "hex", "hex-literal", + "log", "parity-scale-codec", "polkadot-parachain-primitives 6.0.0", "scale-info", diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index cc95bb602a56..22ec58fe9996 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -39,26 +39,25 @@ mod test; use codec::{Decode, DecodeAll, Encode}; use envelope::Envelope; -use frame_system::ensure_signed; +use frame_support::{ + traits::fungible::{Inspect, Mutate}, + PalletError, +}; +use frame_system::{ensure_signed, pallet_prelude::*}; use scale_info::TypeInfo; use sp_core::H160; use sp_std::vec; use types::Nonce; use xcm::prelude::{send_xcm, Junction::*, Location, SendError as XcmpSendError, SendXcm}; -use frame_support::{ - traits::{ - fungible::{Inspect, Mutate}, - }, - PalletError, -}; -use frame_system::pallet_prelude::*; use snowbridge_core::{ + fees::burn_fees, inbound::{Message, VerificationError, Verifier}, BasicOperatingMode, }; use snowbridge_router_primitives::inbound::v2::{ConvertMessage, Message as MessageV2}; pub use weights::WeightInfo; +use xcm_executor::traits::TransactAsset; #[cfg(feature = "runtime-benchmarks")] use snowbridge_beacon_primitives::BeaconHeader; @@ -70,8 +69,9 @@ pub use pallet::*; pub const LOG_TARGET: &str = "snowbridge-inbound-queue:v2"; +pub type AccountIdOf = ::AccountId; type BalanceOf = -<::Token as Inspect<::AccountId>>::Balance; + <::Token as Inspect<::AccountId>>::Balance; #[frame_support::pallet] pub mod pallet { use super::*; @@ -104,6 +104,7 @@ pub mod pallet { type MessageConverter: ConvertMessage; type XcmPrologueFee: Get>; type Token: Mutate + Inspect; + type AssetTransactor: TransactAsset; #[cfg(feature = "runtime-benchmarks")] type Helper: BenchmarkHelper; } @@ -129,6 +130,8 @@ pub mod pallet { pub enum Error { /// Message came from an invalid outbound channel on the Ethereum side. InvalidGateway, + /// Account could not be converted to bytes + InvalidAccount, /// Message has an invalid envelope. InvalidEnvelope, /// Message has an unexpected nonce. @@ -193,7 +196,7 @@ pub mod pallet { #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::submit())] pub fn submit(origin: OriginFor, message: Message) -> DispatchResult { - let _who = ensure_signed(origin)?; + let who = ensure_signed(origin)?; ensure!(!Self::operating_mode().is_halted(), Error::::Halted); // submit message to verifier for verification @@ -217,6 +220,12 @@ pub mod pallet { let xcm = T::MessageConverter::convert(message).map_err(|e| Error::::ConvertMessage(e))?; + // Burn the required fees for the static XCM message part + burn_fees::>( + Self::account_to_location(who)?, + T::XcmPrologueFee::get(), + )?; + // Todo: Deposit fee(in Ether) to RewardLeger which should cover all of: // T::RewardLeger::deposit(who, envelope.fee.into())?; // a. The submit extrinsic cost on BH @@ -249,4 +258,12 @@ pub mod pallet { Ok(()) } } + + impl Pallet { + pub fn account_to_location(account: AccountIdOf) -> Result> { + let account_bytes: [u8; 32] = + account.encode().try_into().map_err(|_| Error::::InvalidAccount)?; + Ok(Location::new(0, [AccountId32 { network: None, id: account_bytes }])) + } + } } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs index 8d39dc0931f4..d406d5825f7b 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs @@ -3,7 +3,10 @@ use super::*; use crate::{self as inbound_queue_v2}; -use frame_support::{derive_impl, parameter_types, traits::{ConstU32, ConstU128}}; +use frame_support::{ + derive_impl, parameter_types, + traits::{ConstU128, ConstU32}, +}; use hex_literal::hex; use snowbridge_beacon_primitives::{ types::deneb, BeaconHeader, ExecutionProof, Fork, ForkVersions, VersionedExecutionPayloadHeader, @@ -20,6 +23,7 @@ use sp_runtime::{ }; use sp_std::{convert::From, default::Default}; use xcm::{latest::SendXcm, prelude::*}; +use xcm_executor::{traits::TransactAsset, AssetsInHolding}; type Block = frame_system::mocking::MockBlock; @@ -160,10 +164,43 @@ impl inbound_queue_v2::Config for Test { MessageToXcm; type Token = Balances; type XcmPrologueFee = ConstU128<1_000_000_000>; + type AssetTransactor = SuccessfulTransactor; #[cfg(feature = "runtime-benchmarks")] type Helper = Test; } +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 fn last_events(n: usize) -> Vec { frame_system::Pallet::::events() .into_iter() diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs index 9554a702cf07..0b9e49ca43b3 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs @@ -13,9 +13,7 @@ use codec::DecodeLimit; use snowbridge_router_primitives::inbound::v2::Asset; use sp_core::H256; use xcm::{ - opaque::latest::{ - prelude::{ClearOrigin, ReceiveTeleportedAsset}, - }, + opaque::latest::prelude::{ClearOrigin, ReceiveTeleportedAsset}, prelude::*, VersionedXcm, MAX_XCM_DECODE_DEPTH, }; @@ -260,7 +258,8 @@ fn test_register_token_inbound_message_with_xcm_and_claimer() { #[test] fn encode_xcm() { new_tester().execute_with(|| { - let total_fee_asset: xcm::opaque::latest::Asset = (Location::parent(), 1_000_000_000).into(); + let total_fee_asset: xcm::opaque::latest::Asset = + (Location::parent(), 1_000_000_000).into(); let instructions: Xcm<()> = vec![ReceiveTeleportedAsset(total_fee_asset.into()), ClearOrigin].into(); diff --git a/bridges/snowbridge/primitives/core/Cargo.toml b/bridges/snowbridge/primitives/core/Cargo.toml index fa37c795b2d1..4f5935a9fa43 100644 --- a/bridges/snowbridge/primitives/core/Cargo.toml +++ b/bridges/snowbridge/primitives/core/Cargo.toml @@ -16,6 +16,7 @@ serde = { optional = true, features = ["alloc", "derive"], workspace = true } codec = { workspace = true } scale-info = { features = ["derive"], workspace = true } hex-literal = { workspace = true, default-features = true } +log = { workspace = true } polkadot-parachain-primitives = { workspace = true } xcm = { workspace = true } @@ -33,9 +34,10 @@ snowbridge-beacon-primitives = { workspace = true } ethabi = { workspace = true } +xcm-executor = { workspace = true } + [dev-dependencies] hex = { workspace = true, default-features = true } -xcm-executor = { workspace = true, default-features = true } [features] default = ["std"] @@ -54,6 +56,7 @@ std = [ "sp-runtime/std", "sp-std/std", "xcm-builder/std", + "xcm-executor/std", "xcm/std", ] serde = ["dep:serde", "scale-info/serde"] diff --git a/bridges/snowbridge/primitives/core/src/fees.rs b/bridges/snowbridge/primitives/core/src/fees.rs new file mode 100644 index 000000000000..a9ae0407fbfe --- /dev/null +++ b/bridges/snowbridge/primitives/core/src/fees.rs @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use log; +use sp_runtime::{DispatchResult, SaturatedConversion, Saturating, TokenError}; +use xcm::opaque::latest::{Location, XcmContext}; +use xcm_executor::traits::TransactAsset; +const LOG_TARGET: &str = "xcm_fees"; + +/// Burns the fees embedded in the XCM for teleports. +pub fn burn_fees(dest: Location, fee: Balance) -> DispatchResult +where + AssetTransactor: TransactAsset, + Balance: Saturating + TryInto + Copy, +{ + let dummy_context = XcmContext { origin: None, message_id: Default::default(), topic: None }; + let fees = (Location::parent(), fee.saturated_into::()).into(); + + // Check if the asset can be checked out + 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 + })?; + + // Check out the asset + AssetTransactor::check_out(&dest, &fees, &dummy_context); + + // Withdraw the asset and handle potential errors + AssetTransactor::withdraw_asset(&fees, &dest, None).map_err(|error| { + log::error!( + target: LOG_TARGET, + "XCM asset withdraw failed with error {:?}", + error + ); + TokenError::FundsUnavailable + })?; + + Ok(()) +} diff --git a/bridges/snowbridge/primitives/core/src/lib.rs b/bridges/snowbridge/primitives/core/src/lib.rs index d88e387d7c24..558ac43d0d31 100644 --- a/bridges/snowbridge/primitives/core/src/lib.rs +++ b/bridges/snowbridge/primitives/core/src/lib.rs @@ -8,6 +8,7 @@ #[cfg(test)] mod tests; +pub mod fees; pub mod inbound; pub mod location; pub mod operating_mode; From ee0bc2703795abc6b56d10039fa35ae60fa24af3 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Mon, 25 Nov 2024 10:23:04 +0200 Subject: [PATCH 10/52] fix westend config --- Cargo.lock | 1 + .../primitives/router/src/inbound/mod.rs | 42 +++++------ .../primitives/router/src/inbound/v1.rs | 10 +-- .../src/tests/snowbridge.rs | 5 +- .../src/bridge_to_ethereum_config.rs | 2 +- .../bridge-hubs/bridge-hub-westend/Cargo.toml | 1 + .../src/bridge_to_ethereum_config.rs | 9 +-- .../bridge-hub-westend/src/weights/mod.rs | 1 + .../snowbridge_pallet_inbound_queue_v2.rs | 69 +++++++++++++++++++ 9 files changed, 109 insertions(+), 31 deletions(-) create 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 a371125e7842..5db995eae135 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2826,6 +2826,7 @@ dependencies = [ "snowbridge-outbound-queue-runtime-api 0.2.0", "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-system 0.2.0", "snowbridge-router-primitives 0.9.0", diff --git a/bridges/snowbridge/primitives/router/src/inbound/mod.rs b/bridges/snowbridge/primitives/router/src/inbound/mod.rs index 1e43bd7544cf..5bf5258f3c4c 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/mod.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/mod.rs @@ -10,28 +10,30 @@ use sp_std::marker::PhantomData; use xcm::prelude::{AccountKey20, Ethereum, GlobalConsensus, Location}; use xcm_executor::traits::ConvertLocation; -pub struct GlobalConsensusEthereumConvertsFor(PhantomData); -impl ConvertLocation for GlobalConsensusEthereumConvertsFor - where - AccountId: From<[u8; 32]> + Clone, +pub struct EthereumLocationsConverterFor(PhantomData); +impl ConvertLocation for EthereumLocationsConverterFor +where + AccountId: From<[u8; 32]> + Clone, { - fn convert_location(location: &Location) -> Option { - match location.unpack() { - (2, [GlobalConsensus(Ethereum { chain_id })]) => - Some(Self::from_chain_id(chain_id).into()), - (2, [GlobalConsensus(Ethereum { chain_id }), AccountKey20 { network: _, key }]) => - Some(Self::from_chain_id_with_key(chain_id, *key).into()), - _ => None, - } - } + fn convert_location(location: &Location) -> Option { + match location.unpack() { + (2, [GlobalConsensus(Ethereum { chain_id })]) => + Some(Self::from_chain_id(chain_id).into()), + (2, [GlobalConsensus(Ethereum { chain_id }), AccountKey20 { network: _, key }]) => + Some(Self::from_chain_id_with_key(chain_id, *key).into()), + _ => None, + } + } } -impl GlobalConsensusEthereumConvertsFor { - pub fn from_chain_id(chain_id: &u64) -> [u8; 32] { - (b"ethereum-chain", chain_id).using_encoded(blake2_256) - } - pub fn from_chain_id_with_key(chain_id: &u64, key: [u8; 20]) -> [u8; 32] { - (b"ethereum-chain", chain_id, key).using_encoded(blake2_256) - } + +impl EthereumLocationsConverterFor { + pub fn from_chain_id(chain_id: &u64) -> [u8; 32] { + (b"ethereum-chain", chain_id).using_encoded(blake2_256) + } + pub fn from_chain_id_with_key(chain_id: &u64, key: [u8; 20]) -> [u8; 32] { + (b"ethereum-chain", chain_id, key).using_encoded(blake2_256) + } } + pub type CallIndex = [u8; 2]; diff --git a/bridges/snowbridge/primitives/router/src/inbound/v1.rs b/bridges/snowbridge/primitives/router/src/inbound/v1.rs index d413674970b2..9e2191d651a0 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v1.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v1.rs @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: 2023 Snowfork //! Converts messages from Ethereum to XCM messages -use crate::inbound::{CallIndex, GlobalConsensusEthereumConvertsFor}; +use crate::inbound::{CallIndex, EthereumLocationsConverterFor}; use codec::{Decode, Encode}; use core::marker::PhantomData; use frame_support::{traits::tokens::Balance as BalanceT, PalletError}; @@ -247,7 +247,7 @@ MessageToXcm< let bridge_location = Location::new(2, GlobalConsensus(network)); - let owner = GlobalConsensusEthereumConvertsFor::<[u8; 32]>::from_chain_id(&chain_id); + 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(); @@ -409,6 +409,8 @@ MessageToXcm< // 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), }?; @@ -471,7 +473,7 @@ mod tests { let contract_location = Location::new(2, [GlobalConsensus(NETWORK)]); let account = - GlobalConsensusEthereumConvertsFor::<[u8; 32]>::convert_location(&contract_location) + EthereumLocationsConverterFor::<[u8; 32]>::convert_location(&contract_location) .unwrap(); assert_eq!(account, expected_account); @@ -482,7 +484,7 @@ mod tests { let contract_location = Location::new(2, [GlobalConsensus(Polkadot), Parachain(1000)]); assert_eq!( - GlobalConsensusEthereumConvertsFor::<[u8; 32]>::convert_location(&contract_location), + EthereumLocationsConverterFor::<[u8; 32]>::convert_location(&contract_location), None, ); } 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..1f6fbf3d930f 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 @@ -21,8 +21,9 @@ 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_router_primitives::inbound::{ - Command, Destination, EthereumLocationsConverterFor, MessageV1, VersionedMessage, +use snowbridge_router_primitives::inbound::EthereumLocationsConverterFor; +use snowbridge_router_primitives::inbound::v1::{ + Command, Destination, 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 be7005b5379a..16fcaeab5bad 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::EthereumBlobExporter}; use sp_core::H160; 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 637e7c710640..5169a51c3702 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml @@ -110,6 +110,7 @@ snowbridge-system-runtime-api = { workspace = true } snowbridge-core = { workspace = true } snowbridge-pallet-ethereum-client = { workspace = true } snowbridge-pallet-inbound-queue = { workspace = true } +snowbridge-pallet-inbound-queue-v2 = { workspace = true } snowbridge-pallet-outbound-queue = { workspace = true } snowbridge-outbound-queue-runtime-api = { workspace = true } snowbridge-router-primitives = { workspace = true } 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 def9b1af6207..5d783aac34cc 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,9 +25,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::{ - outbound::{v1::EthereumBlobExporter, v2::EthereumBlobExporter as EthereumBlobExporterV2}, -}; +use snowbridge_router_primitives::outbound::EthereumBlobExporter; use sp_core::H160; use testnet_parachains_constants::westend::{ currency::*, @@ -41,7 +39,7 @@ use benchmark_helpers::DoNothingRouter; use frame_support::{parameter_types, weights::ConstantMultiplier}; use pallet_xcm::EnsureXcm; use sp_runtime::{ - traits::{ConstU32, ConstU8, Keccak256}, + traits::{ConstU32, ConstU8, ConstU128, Keccak256}, FixedU128, }; use xcm::prelude::{GlobalConsensus, InteriorLocation, Location, Parachain}; @@ -116,6 +114,9 @@ impl snowbridge_pallet_inbound_queue_v2::Config for Runtime { type Helper = Runtime; type WeightInfo = crate::weights::snowbridge_pallet_inbound_queue_v2::WeightInfo; type AssetHubParaId = ConstU32<1000>; + type Token = Balances; + type XcmPrologueFee = ConstU128<1_000_000_000>; + type AssetTransactor = ::AssetTransactor; type MessageConverter = snowbridge_router_primitives::inbound::v2::MessageToXcm, EthereumSystem>; } 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..ee8ad5f31794 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,6 +47,7 @@ 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_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 new file mode 100644 index 000000000000..8cfa14981b3a --- /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_v2`. +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 430927224cc1fa2d09e648c2421047aec19ef5ca Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Mon, 25 Nov 2024 10:40:32 +0200 Subject: [PATCH 11/52] use sendcontroller --- Cargo.lock | 1 + .../pallets/inbound-queue-v2/Cargo.toml | 2 ++ .../pallets/inbound-queue-v2/src/lib.rs | 24 ++++++++++++------- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5db995eae135..ef3f6cc673ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25060,6 +25060,7 @@ 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-v2/Cargo.toml b/bridges/snowbridge/pallets/inbound-queue-v2/Cargo.toml index fcbb41743f45..ecebc677e997 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/Cargo.toml +++ b/bridges/snowbridge/pallets/inbound-queue-v2/Cargo.toml @@ -34,6 +34,7 @@ sp-runtime = { workspace = true } xcm = { workspace = true } xcm-executor = { workspace = true } +xcm-builder = { workspace = true } snowbridge-core = { workspace = true } snowbridge-router-primitives = { workspace = true } @@ -69,6 +70,7 @@ std = [ "sp-runtime/std", "sp-std/std", "xcm-executor/std", + "xcm-builder/std", "xcm/std", ] runtime-benchmarks = [ diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index 22ec58fe9996..f7937e26d0d9 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -48,23 +48,25 @@ use scale_info::TypeInfo; use sp_core::H160; use sp_std::vec; use types::Nonce; -use xcm::prelude::{send_xcm, Junction::*, Location, SendError as XcmpSendError, SendXcm}; +use xcm::prelude::{Junction::*, Location, SendError as XcmpSendError}; use snowbridge_core::{ fees::burn_fees, inbound::{Message, VerificationError, Verifier}, + sparse_bitmap::SparseBitmap, BasicOperatingMode, }; -use snowbridge_router_primitives::inbound::v2::{ConvertMessage, Message as MessageV2}; +use snowbridge_router_primitives::inbound::v2::{ + ConvertMessage, ConvertMessageError, Message as MessageV2, +}; pub use weights::WeightInfo; +use xcm::{VersionedLocation, VersionedXcm}; +use xcm_builder::SendController; use xcm_executor::traits::TransactAsset; #[cfg(feature = "runtime-benchmarks")] use snowbridge_beacon_primitives::BeaconHeader; -use snowbridge_core::sparse_bitmap::SparseBitmap; -use snowbridge_router_primitives::inbound::v2::ConvertMessageError; - pub use pallet::*; pub const LOG_TARGET: &str = "snowbridge-inbound-queue:v2"; @@ -94,7 +96,7 @@ pub mod pallet { type Verifier: Verifier; /// XCM message sender - type XcmSender: SendXcm; + type XcmSender: SendController<::RuntimeOrigin>; /// Address of the Gateway contract #[pallet::constant] type GatewayAddress: Get; @@ -196,7 +198,7 @@ pub mod pallet { #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::submit())] pub fn submit(origin: OriginFor, message: Message) -> DispatchResult { - let who = ensure_signed(origin)?; + let who = ensure_signed(origin.clone())?; ensure!(!Self::operating_mode().is_halted(), Error::::Halted); // submit message to verifier for verification @@ -235,8 +237,12 @@ pub mod pallet { // e. The reward // Attempt to forward XCM to AH - let dest = Location::new(1, [Parachain(T::AssetHubParaId::get())]); - let (message_id, _) = send_xcm::(dest, xcm).map_err(Error::::from)?; + let versioned_dest = Box::new(VersionedLocation::V5(Location::new( + 1, + [Parachain(T::AssetHubParaId::get())], + ))); + let versioned_xcm = Box::new(VersionedXcm::V5(xcm)); + let message_id = T::XcmSender::send(origin, versioned_dest, versioned_xcm)?; // TODO origin should be this parachain, maybe Self::deposit_event(Event::MessageReceived { nonce: envelope.nonce, message_id }); // Set nonce flag to true From de00eb6b5acee49a9b86498266fa9eebd7ef3822 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Mon, 25 Nov 2024 12:13:55 +0200 Subject: [PATCH 12/52] runtime config for sendcontroller --- .../pallets/inbound-queue-v2/src/lib.rs | 5 ++- .../pallets/inbound-queue-v2/src/mock.rs | 42 +++++++++---------- .../src/tests/snowbridge.rs | 6 +-- .../src/bridge_to_ethereum_config.rs | 14 ++++--- 4 files changed, 37 insertions(+), 30 deletions(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index f7937e26d0d9..12d67d3115dd 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -22,6 +22,8 @@ //! * [`Call::submit`]: Submit a message for verification and dispatch the final destination //! parachain. #![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; pub mod api; mod envelope; @@ -48,6 +50,7 @@ use scale_info::TypeInfo; use sp_core::H160; use sp_std::vec; use types::Nonce; +use alloc::boxed::Box; use xcm::prelude::{Junction::*, Location, SendError as XcmpSendError}; use snowbridge_core::{ @@ -242,7 +245,7 @@ pub mod pallet { [Parachain(T::AssetHubParaId::get())], ))); let versioned_xcm = Box::new(VersionedXcm::V5(xcm)); - let message_id = T::XcmSender::send(origin, versioned_dest, versioned_xcm)?; // TODO origin should be this parachain, maybe + let message_id = T::XcmSender::send(origin, versioned_dest, versioned_xcm)?; Self::deposit_event(Event::MessageReceived { nonce: envelope.nonce, message_id }); // Set nonce flag to true diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs index d406d5825f7b..e724b2f4e842 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs @@ -22,8 +22,11 @@ use sp_runtime::{ BuildStorage, MultiSignature, }; use sp_std::{convert::From, default::Default}; -use xcm::{latest::SendXcm, prelude::*}; +use xcm::prelude::*; use xcm_executor::{traits::TransactAsset, AssetsInHolding}; +use xcm_builder::SendControllerWeightInfo; +use sp_runtime::DispatchError; +use sp_core::H256; type Block = frame_system::mocking::MockBlock; @@ -110,29 +113,26 @@ impl BenchmarkHelper for Test { 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())) - } +pub struct MockXcmSenderWeights; + +impl SendControllerWeightInfo for MockXcmSenderWeights { + fn send() -> Weight { + return Weight::default(); } +} + +// Mock XCM sender that always succeeds +pub struct MockXcmSender; - fn deliver(xcm: Self::Ticket) -> core::result::Result { - let hash = xcm.using_encoded(sp_io::hashing::blake2_256); - Ok(hash) +impl SendController for MockXcmSender { + type WeightInfo = MockXcmSenderWeights; + fn send( + _origin: mock::RuntimeOrigin, + _dest: Box, + _message: Box>, + ) -> Result { + Ok(H256::random().into()) } } 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 1f6fbf3d930f..3055043dd79c 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 @@ -21,9 +21,9 @@ 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_router_primitives::inbound::EthereumLocationsConverterFor; -use snowbridge_router_primitives::inbound::v1::{ - Command, Destination, MessageV1, VersionedMessage, +use snowbridge_router_primitives::inbound::{ + v1::{Command, Destination, MessageV1, VersionedMessage}, + EthereumLocationsConverterFor, }; use sp_core::H256; use testnet_parachains_constants::westend::snowbridge::EthereumNetwork; 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 5d783aac34cc..e9c4f3ad288b 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 @@ -14,14 +14,14 @@ // 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, xcm_config::{TreasuryAccount, UniversalLocation}, Balances, EthereumInboundQueue, EthereumOutboundQueue, EthereumSystem, MessageQueue, Runtime, RuntimeEvent, TransactionByteFee, }; +#[cfg(not(feature = "runtime-benchmarks"))] +use crate::{PolkadotXcm, XcmRouter}; use parachains_common::{AccountId, Balance}; use snowbridge_beacon_primitives::{Fork, ForkVersions}; use snowbridge_core::{gwei, meth, AllowSiblingsOnly, PricingParameters, Rewards}; @@ -39,7 +39,7 @@ use benchmark_helpers::DoNothingRouter; use frame_support::{parameter_types, weights::ConstantMultiplier}; use pallet_xcm::EnsureXcm; use sp_runtime::{ - traits::{ConstU32, ConstU8, ConstU128, Keccak256}, + traits::{ConstU128, ConstU32, ConstU8, Keccak256}, FixedU128, }; use xcm::prelude::{GlobalConsensus, InteriorLocation, Location, Parachain}; @@ -106,7 +106,7 @@ impl snowbridge_pallet_inbound_queue_v2::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Verifier = snowbridge_pallet_ethereum_client::Pallet; #[cfg(not(feature = "runtime-benchmarks"))] - type XcmSender = XcmRouter; + type XcmSender = PolkadotXcm; #[cfg(feature = "runtime-benchmarks")] type XcmSender = DoNothingRouter; type GatewayAddress = EthereumGatewayAddress; @@ -117,7 +117,11 @@ impl snowbridge_pallet_inbound_queue_v2::Config for Runtime { type Token = Balances; type XcmPrologueFee = ConstU128<1_000_000_000>; type AssetTransactor = ::AssetTransactor; - type MessageConverter = snowbridge_router_primitives::inbound::v2::MessageToXcm, EthereumSystem>; + type MessageConverter = snowbridge_router_primitives::inbound::v2::MessageToXcm< + EthereumNetwork, + ConstU8, + EthereumSystem, + >; } impl snowbridge_pallet_outbound_queue::Config for Runtime { From 3653b7f5ec02cf511bc66f41d151c067fe3da92c Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Mon, 25 Nov 2024 13:15:50 +0200 Subject: [PATCH 13/52] wip --- .../pallets/inbound-queue-v2/src/lib.rs | 39 +++++------- .../primitives/router/src/inbound/v2.rs | 4 ++ .../bridge-hub-westend/src/tests/mod.rs | 1 + .../src/tests/snowbridge_v2.rs | 62 +++++++++++++++++++ 4 files changed, 83 insertions(+), 23 deletions(-) create mode 100644 cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index 12d67d3115dd..0ca2ee10be7d 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -51,7 +51,7 @@ use sp_core::H160; use sp_std::vec; use types::Nonce; use alloc::boxed::Box; -use xcm::prelude::{Junction::*, Location, SendError as XcmpSendError}; +use xcm::prelude::{Junction::*, Location, *}; use snowbridge_core::{ fees::burn_fees, @@ -170,22 +170,6 @@ pub mod pallet { 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 nonce of the message been processed or not #[pallet::storage] pub type NonceBitmap = StorageMap<_, Twox64Concat, u128, u128, ValueQuery>; @@ -240,12 +224,8 @@ pub mod pallet { // e. The reward // Attempt to forward XCM to AH - let versioned_dest = Box::new(VersionedLocation::V5(Location::new( - 1, - [Parachain(T::AssetHubParaId::get())], - ))); - let versioned_xcm = Box::new(VersionedXcm::V5(xcm)); - let message_id = T::XcmSender::send(origin, versioned_dest, versioned_xcm)?; + + let message_id = Self::send_xcm(origin, xcm, T::AssetHubParaId::get())?; Self::deposit_event(Event::MessageReceived { nonce: envelope.nonce, message_id }); // Set nonce flag to true @@ -274,5 +254,18 @@ pub mod pallet { account.encode().try_into().map_err(|_| Error::::InvalidAccount)?; Ok(Location::new(0, [AccountId32 { network: None, id: account_bytes }])) } + + pub fn send_xcm(origin: OriginFor, xcm: Xcm<()>, dest_para_id: u32) -> Result { + let versioned_dest = Box::new(VersionedLocation::V5(Location::new( + 1, + [Parachain(dest_para_id)], + ))); + let versioned_xcm = Box::new(VersionedXcm::V5(xcm)); + Ok(T::XcmSender::send(origin, versioned_dest, versioned_xcm)?) + } + + pub fn do_convert(message: MessageV2) -> Result, Error> { + Ok(T::MessageConverter::convert(message).map_err(|e| Error::::ConvertMessage(e))?) + } } } diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index b8f1ab8ac782..133021a5f7a5 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -140,6 +140,8 @@ for MessageToXcm } } + log::debug!(target: LOG_TARGET,"extracted assets"); + if let Some(claimer) = message.claimer { let claimer = Junction::decode(&mut claimer.as_ref()) .map_err(|_| ConvertMessageError::InvalidClaimer)?; @@ -147,6 +149,8 @@ for MessageToXcm instructions.push(SetAssetClaimer { location: claimer_location }); } + log::debug!(target: LOG_TARGET,"extracted claimer"); + // Set the alias origin to the original sender on Ethereum. Important to be before the // arbitrary XCM that is appended to the message on the next line. instructions.push(AliasOrigin(origin_location.into())); 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_v2.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs new file mode 100644 index 000000000000..2c51c25a26f8 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs @@ -0,0 +1,62 @@ +// 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 hex_literal::hex; +use bridge_hub_westend_runtime::EthereumInboundQueueV2; +use snowbridge_router_primitives::inbound::v2::Message; +use bridge_hub_westend_runtime::RuntimeOrigin; +use sp_core::H160; +use snowbridge_router_primitives::inbound::v2::Asset::NativeTokenERC20; + +/// Calculates the XCM prologue fee for sending an XCM to AH. +const INITIAL_FUND: u128 = 5_000_000_000_000; +#[test] +fn xcm_prologue_fee() { + BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id().into(), INITIAL_FUND); + + let relayer = BridgeHubWestendSender::get(); + let claimer = AssetHubWestendReceiver::get(); + BridgeHubWestend::fund_accounts(vec![ + (relayer.clone(), INITIAL_FUND), + ]); + + let token_id_1 = H160::random(); + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + let claimer = AccountId32{network: None, id: claimer.into()}; + let claimer_bytes = claimer.encode(); + + let message = Message{ + origin: H160::random(), + assets: vec![ + NativeTokenERC20 { + token_id: token_id_1, + value: 1_000_000_000, + } + ], + xcm: hex!().to_vec(), + claimer: Some(claimer_bytes) + }; + let xcm = EthereumInboundQueueV2::do_convert(message).unwrap(); + let _ = EthereumInboundQueueV2::send_xcm(RuntimeOrigin::root(relayer.clone()), xcm, AssetHubWestend::para_id().into()).unwrap(); + + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},] + ); + }); +} From 990bf4761644e0d2280d677004b5f164560ed69d Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Tue, 26 Nov 2024 06:34:13 +0200 Subject: [PATCH 14/52] tests --- bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs | 2 +- bridges/snowbridge/primitives/router/src/inbound/v2.rs | 5 ++--- .../bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs | 2 +- .../bridge-hubs/bridge-hub-westend/src/xcm_config.rs | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index 0ca2ee10be7d..c50f66c81303 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -265,7 +265,7 @@ pub mod pallet { } pub fn do_convert(message: MessageV2) -> Result, Error> { - Ok(T::MessageConverter::convert(message).map_err(|e| Error::::ConvertMessage(e))?) + Ok(T::MessageConverter::convert(message, T::XcmPrologueFee::get().into()).map_err(|e| Error::::ConvertMessage(e))?) } } } diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index 133021a5f7a5..cc34ba104a9a 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -88,7 +88,7 @@ for MessageToXcm InboundQueuePalletInstance: Get, ConvertAssetId: MaybeEquivalence, { - fn convert(message: Message) -> Result, ConvertMessageError> { + fn convert(message: Message, xcm_prologue_fee: u128) -> Result, ConvertMessageError> { let mut message_xcm: Xcm<()> = Xcm::new(); if message.xcm.len() > 0 { // Decode xcm @@ -111,8 +111,7 @@ for MessageToXcm let network = EthereumNetwork::get(); let fee_asset = Location::new(1, Here); - let fee_value = 1_000_000_000u128; // TODO get from command - let fee: xcm::prelude::Asset = (fee_asset, fee_value).into(); + let fee: xcm::prelude::Asset = (fee_asset, xcm_prologue_fee).into(); let mut instructions = vec![ ReceiveTeleportedAsset(fee.clone().into()), PayFees { asset: 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 index 2c51c25a26f8..e54e5934f94e 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 @@ -52,7 +52,7 @@ fn xcm_prologue_fee() { claimer: Some(claimer_bytes) }; let xcm = EthereumInboundQueueV2::do_convert(message).unwrap(); - let _ = EthereumInboundQueueV2::send_xcm(RuntimeOrigin::root(relayer.clone()), xcm, AssetHubWestend::para_id().into()).unwrap(); + let _ = EthereumInboundQueueV2::send_xcm(RuntimeOrigin::signed(relayer.clone()), xcm, AssetHubWestend::para_id().into()).unwrap(); assert_expected_events!( BridgeHubWestend, 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 befb63ef9709..38d9bec0c0f8 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 @@ -250,7 +250,7 @@ impl pallet_xcm::Config for Runtime { type RuntimeEvent = RuntimeEvent; type XcmRouter = XcmRouter; // We want to disallow users sending (arbitrary) XCMs from this chain. - type SendXcmOrigin = EnsureXcmOrigin; + type SendXcmOrigin = EnsureXcmOrigin; // We support local origins dispatching XCM executions. type ExecuteXcmOrigin = EnsureXcmOrigin; type XcmExecuteFilter = Everything; From 9f7829c8fbfdc984f4f81fa50755d92ef491bca6 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Tue, 26 Nov 2024 09:51:30 +0200 Subject: [PATCH 15/52] xcm fee --- .../pallets/inbound-queue-v2/src/api.rs | 7 +- .../pallets/inbound-queue-v2/src/lib.rs | 31 +- .../pallets/inbound-queue-v2/src/mock.rs | 21 +- .../primitives/router/src/inbound/mod.rs | 1 - .../primitives/router/src/inbound/v1.rs | 878 +++++++++--------- .../primitives/router/src/inbound/v2.rs | 379 ++++---- .../src/tests/snowbridge_v2.rs | 99 +- .../src/bridge_to_ethereum_config.rs | 8 +- 8 files changed, 756 insertions(+), 668 deletions(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs index a285a7c5af42..532a1b453366 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs @@ -2,15 +2,18 @@ // SPDX-FileCopyrightText: 2023 Snowfork //! Implements the dry-run API. -use crate::{Config, Error}; +use crate::{Config, Error, Junction::AccountId32, Location}; use snowbridge_core::inbound::Proof; use snowbridge_router_primitives::inbound::v2::{ConvertMessage, Message}; +use sp_core::H256; use xcm::latest::Xcm; pub fn dry_run(message: Message, _proof: Proof) -> Result, Error> where T: Config, { - let xcm = T::MessageConverter::convert(message).map_err(|e| Error::::ConvertMessage(e))?; + let dummy_origin = Location::new(0, AccountId32 { id: H256::zero().into(), network: None }); + let xcm = T::MessageConverter::convert(message, dummy_origin) + .map_err(|e| Error::::ConvertMessage(e))?; Ok(xcm) } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index c50f66c81303..12b0f417576f 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -39,6 +39,7 @@ mod mock; #[cfg(test)] mod test; +use alloc::boxed::Box; use codec::{Decode, DecodeAll, Encode}; use envelope::Envelope; use frame_support::{ @@ -50,7 +51,6 @@ use scale_info::TypeInfo; use sp_core::H160; use sp_std::vec; use types::Nonce; -use alloc::boxed::Box; use xcm::prelude::{Junction::*, Location, *}; use snowbridge_core::{ @@ -141,6 +141,8 @@ pub mod pallet { InvalidEnvelope, /// Message has an unexpected nonce. InvalidNonce, + /// Fee provided is invalid. + InvalidFee, /// Message has an invalid payload. InvalidPayload, /// Message channel is invalid @@ -206,12 +208,13 @@ pub mod pallet { let message = MessageV2::decode_all(&mut envelope.payload.as_ref()) .map_err(|_| Error::::InvalidPayload)?; - let xcm = - T::MessageConverter::convert(message).map_err(|e| Error::::ConvertMessage(e))?; + let origin_account_location = Self::account_to_location(who)?; + + let xcm = Self::do_convert(message, origin_account_location.clone())?; // Burn the required fees for the static XCM message part burn_fees::>( - Self::account_to_location(who)?, + origin_account_location, T::XcmPrologueFee::get(), )?; @@ -255,17 +258,23 @@ pub mod pallet { Ok(Location::new(0, [AccountId32 { network: None, id: account_bytes }])) } - pub fn send_xcm(origin: OriginFor, xcm: Xcm<()>, dest_para_id: u32) -> Result { - let versioned_dest = Box::new(VersionedLocation::V5(Location::new( - 1, - [Parachain(dest_para_id)], - ))); + pub fn send_xcm( + origin: OriginFor, + xcm: Xcm<()>, + dest_para_id: u32, + ) -> Result { + let versioned_dest = + Box::new(VersionedLocation::V5(Location::new(1, [Parachain(dest_para_id)]))); let versioned_xcm = Box::new(VersionedXcm::V5(xcm)); Ok(T::XcmSender::send(origin, versioned_dest, versioned_xcm)?) } - pub fn do_convert(message: MessageV2) -> Result, Error> { - Ok(T::MessageConverter::convert(message, T::XcmPrologueFee::get().into()).map_err(|e| Error::::ConvertMessage(e))?) + pub fn do_convert( + message: MessageV2, + origin_account_location: Location, + ) -> Result, Error> { + Ok(T::MessageConverter::convert(message, origin_account_location) + .map_err(|e| Error::::ConvertMessage(e))?) } } } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs index e724b2f4e842..105863f5772f 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs @@ -16,17 +16,15 @@ use snowbridge_core::{ TokenId, }; use snowbridge_router_primitives::inbound::v2::MessageToXcm; -use sp_core::H160; +use sp_core::{H160, H256}; use sp_runtime::{ traits::{IdentifyAccount, IdentityLookup, MaybeEquivalence, Verify}, - BuildStorage, MultiSignature, + BuildStorage, DispatchError, MultiSignature, }; use sp_std::{convert::From, default::Default}; use xcm::prelude::*; -use xcm_executor::{traits::TransactAsset, AssetsInHolding}; use xcm_builder::SendControllerWeightInfo; -use sp_runtime::DispatchError; -use sp_core::H256; +use xcm_executor::{traits::TransactAsset, AssetsInHolding}; type Block = frame_system::mocking::MockBlock; @@ -113,7 +111,6 @@ impl BenchmarkHelper for Test { fn initialize_storage(_: BeaconHeader, _: H256) {} } - pub struct MockXcmSenderWeights; impl SendControllerWeightInfo for MockXcmSenderWeights { @@ -153,6 +150,8 @@ parameter_types! { pub AssetHubLocation: InteriorLocation = Parachain(1000).into(); } +const XCM_PROLOGUE_FEE: u128 = 1_000_000_000_000; + impl inbound_queue_v2::Config for Test { type RuntimeEvent = RuntimeEvent; type Verifier = MockVerifier; @@ -160,10 +159,14 @@ impl inbound_queue_v2::Config for Test { type WeightInfo = (); type GatewayAddress = GatewayAddress; type AssetHubParaId = ConstU32<1000>; - type MessageConverter = - MessageToXcm; + type MessageConverter = MessageToXcm< + EthereumNetwork, + InboundQueuePalletInstance, + MockTokenIdConvert, + ConstU128, + >; type Token = Balances; - type XcmPrologueFee = ConstU128<1_000_000_000>; + type XcmPrologueFee = ConstU128; type AssetTransactor = SuccessfulTransactor; #[cfg(feature = "runtime-benchmarks")] type Helper = Test; diff --git a/bridges/snowbridge/primitives/router/src/inbound/mod.rs b/bridges/snowbridge/primitives/router/src/inbound/mod.rs index 5bf5258f3c4c..69fa554df265 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/mod.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/mod.rs @@ -35,5 +35,4 @@ impl EthereumLocationsConverterFor { } } - pub type CallIndex = [u8; 2]; diff --git a/bridges/snowbridge/primitives/router/src/inbound/v1.rs b/bridges/snowbridge/primitives/router/src/inbound/v1.rs index 9e2191d651a0..2d3cf4e55b86 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v1.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v1.rs @@ -20,444 +20,444 @@ const MINIMUM_DEPOSIT: u128 = 1; /// Instead having BridgeHub transcode the messages into XCM. #[derive(Clone, Encode, Decode, RuntimeDebug)] pub enum VersionedMessage { - V1(MessageV1), + 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, + /// 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, - }, + /// 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, - }, + /// 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, + CreateAssetCall, + CreateAssetDeposit, + InboundQueuePalletInstance, + AccountId, + Balance, + ConvertAssetId, + EthereumUniversalLocation, + GlobalAssetHubLocation, > where - CreateAssetCall: Get, - CreateAssetDeposit: Get, - Balance: BalanceT, - ConvertAssetId: MaybeEquivalence, - EthereumUniversalLocation: Get, - GlobalAssetHubLocation: Get, + CreateAssetCall: Get, + CreateAssetDeposit: Get, + Balance: BalanceT, + ConvertAssetId: MaybeEquivalence, + EthereumUniversalLocation: Get, + GlobalAssetHubLocation: Get, { - _phantom: PhantomData<( - CreateAssetCall, - CreateAssetDeposit, - InboundQueuePalletInstance, - AccountId, - Balance, - ConvertAssetId, - EthereumUniversalLocation, - GlobalAssetHubLocation, - )>, + _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, + /// 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>; + 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, + 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, - ), - } - } + 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, + 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())) - } + 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())) + } } #[cfg(test)] mod tests { - use crate::inbound::{CallIndex, GlobalConsensusEthereumConvertsFor}; - use frame_support::{assert_ok, parameter_types}; - use hex_literal::hex; - use xcm::prelude::*; - use xcm_executor::traits::ConvertLocation; + use crate::inbound::{CallIndex, GlobalConsensusEthereumConvertsFor}; + 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 }; + const NETWORK: NetworkId = Ethereum { chain_id: 11155111 }; - parameter_types! { + parameter_types! { pub EthereumNetwork: NetworkId = NETWORK; pub const CreateAssetCall: CallIndex = [1, 1]; @@ -466,57 +466,57 @@ mod tests { 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)]); + #[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(); + let account = + EthereumLocationsConverterFor::<[u8; 32]>::convert_location(&contract_location) + .unwrap(); - assert_eq!(account, expected_account); - } + 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)]); + #[test] + fn test_contract_location_with_incorrect_location_fails_convert() { + let contract_location = Location::new(2, [GlobalConsensus(Polkadot), Parachain(1000)]); - assert_eq!( + 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!( + } + + #[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()); - } - } + 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 index cc34ba104a9a..63b997a1f454 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -11,8 +11,8 @@ use sp_core::{Get, RuntimeDebug, H160, H256}; use sp_runtime::traits::MaybeEquivalence; use sp_std::prelude::*; use xcm::{ - prelude::{Junction::AccountKey20, *}, - MAX_XCM_DECODE_DEPTH, + prelude::{Junction::AccountKey20, *}, + MAX_XCM_DECODE_DEPTH, }; const LOG_TARGET: &str = "snowbridge-router-primitives"; @@ -22,161 +22,176 @@ const LOG_TARGET: &str = "snowbridge-router-primitives"; /// Instead having BridgeHub transcode the messages into XCM. #[derive(Clone, Encode, Decode, RuntimeDebug)] pub enum VersionedMessage { - V2(Message), + V2(Message), } /// The ethereum side sends messages which are transcoded into XCM on BH. These messages are /// self-contained, in that they can be transcoded using only information in the message. #[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] pub struct Message { - /// The origin address - pub origin: H160, - /// The assets - pub assets: Vec, - // The command originating from the Gateway contract - pub xcm: Vec, - // The claimer in the case that funds get trapped. - pub claimer: Option>, + /// The origin address + pub origin: H160, + /// The assets + pub assets: Vec, + // The command originating from the Gateway contract + pub xcm: Vec, + // The claimer in the case that funds get trapped. + pub claimer: Option>, } #[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] pub enum Asset { - NativeTokenERC20 { - /// The native token ID - token_id: H160, - /// The monetary value of the asset - value: u128, - }, - ForeignTokenERC20 { - /// The foreign token ID - token_id: H256, - /// The monetary value of the asset - value: u128, - }, + NativeTokenERC20 { + /// The native token ID + token_id: H160, + /// The monetary value of the asset + value: u128, + }, + ForeignTokenERC20 { + /// The foreign token ID + token_id: H256, + /// The monetary value of the asset + value: u128, + }, } /// Reason why a message conversion failed. #[derive(Copy, Clone, TypeInfo, PalletError, Encode, Decode, RuntimeDebug)] pub enum ConvertMessageError { - /// The XCM provided with the message could not be decoded into XCM. - InvalidXCM, - /// The XCM provided with the message could not be decoded into versioned XCM. - InvalidVersionedXCM, - /// Invalid claimer MultiAddress provided in payload. - InvalidClaimer, - /// Invalid foreign ERC20 token ID - InvalidAsset, + /// The XCM provided with the message could not be decoded into XCM. + InvalidXCM, + /// The XCM provided with the message could not be decoded into versioned XCM. + InvalidVersionedXCM, + /// Invalid claimer MultiAddress provided in payload. + InvalidClaimer, + /// Invalid foreign ERC20 token ID + InvalidAsset, } pub trait ConvertMessage { - fn convert(message: Message) -> Result, ConvertMessageError>; + fn convert(message: Message, origin_account: Location) -> Result, ConvertMessageError>; } -pub struct MessageToXcm - where - EthereumNetwork: Get, - InboundQueuePalletInstance: Get, - ConvertAssetId: MaybeEquivalence, +pub struct MessageToXcm +where + EthereumNetwork: Get, + InboundQueuePalletInstance: Get, + ConvertAssetId: MaybeEquivalence, + XcmPrologueFee: Get, { - _phantom: PhantomData<(EthereumNetwork, InboundQueuePalletInstance, ConvertAssetId)>, + _phantom: + PhantomData<(EthereumNetwork, InboundQueuePalletInstance, ConvertAssetId, XcmPrologueFee)>, } -impl ConvertMessage -for MessageToXcm - where - EthereumNetwork: Get, - InboundQueuePalletInstance: Get, - ConvertAssetId: MaybeEquivalence, +impl ConvertMessage + for MessageToXcm +where + EthereumNetwork: Get, + InboundQueuePalletInstance: Get, + ConvertAssetId: MaybeEquivalence, + XcmPrologueFee: Get, { - fn convert(message: Message, xcm_prologue_fee: u128) -> Result, ConvertMessageError> { - let mut message_xcm: Xcm<()> = Xcm::new(); - if message.xcm.len() > 0 { - // Decode xcm - let versioned_xcm = VersionedXcm::<()>::decode_with_depth_limit( - MAX_XCM_DECODE_DEPTH, - &mut message.xcm.as_ref(), - ) - .map_err(|_| ConvertMessageError::InvalidVersionedXCM)?; - message_xcm = versioned_xcm.try_into().map_err(|_| ConvertMessageError::InvalidXCM)?; - } - - log::debug!(target: LOG_TARGET,"xcm decoded as {:?}", message_xcm); - - let network = EthereumNetwork::get(); - - let origin_location = Location::new(2, GlobalConsensus(network)) - .push_interior(AccountKey20 { key: message.origin.into(), network: None }) - .map_err(|_| ConvertMessageError::InvalidXCM)?; - - let network = EthereumNetwork::get(); - - let fee_asset = Location::new(1, Here); - let fee: xcm::prelude::Asset = (fee_asset, xcm_prologue_fee).into(); - let mut instructions = vec![ - ReceiveTeleportedAsset(fee.clone().into()), - PayFees { asset: fee }, - DescendOrigin(PalletInstance(InboundQueuePalletInstance::get()).into()), - UniversalOrigin(GlobalConsensus(network)), - ]; - - for asset in &message.assets { - match asset { - Asset::NativeTokenERC20 { token_id, value } => { - let token_location: Location = Location::new( - 2, - [ - GlobalConsensus(EthereumNetwork::get()), - AccountKey20 { network: None, key: (*token_id).into() }, - ], - ); - instructions.push(ReserveAssetDeposited((token_location, *value).into())); - }, - Asset::ForeignTokenERC20 { token_id, value } => { - let asset_id = ConvertAssetId::convert(&token_id) - .ok_or(ConvertMessageError::InvalidAsset)?; - instructions.push(WithdrawAsset((asset_id, *value).into())); - }, - } - } - - log::debug!(target: LOG_TARGET,"extracted assets"); - - if let Some(claimer) = message.claimer { - let claimer = Junction::decode(&mut claimer.as_ref()) - .map_err(|_| ConvertMessageError::InvalidClaimer)?; - let claimer_location: Location = Location::new(0, [claimer.into()]); - instructions.push(SetAssetClaimer { location: claimer_location }); - } - - log::debug!(target: LOG_TARGET,"extracted claimer"); - - // Set the alias origin to the original sender on Ethereum. Important to be before the - // arbitrary XCM that is appended to the message on the next line. - instructions.push(AliasOrigin(origin_location.into())); - - // Add the XCM sent in the message to the end of the xcm instruction - instructions.extend(message_xcm.0); - - Ok(instructions.into()) - } + fn convert( + message: Message, + origin_account_location: Location, + ) -> Result, ConvertMessageError> { + let mut message_xcm: Xcm<()> = Xcm::new(); + if message.xcm.len() > 0 { + // Decode xcm + let versioned_xcm = VersionedXcm::<()>::decode_with_depth_limit( + MAX_XCM_DECODE_DEPTH, + &mut message.xcm.as_ref(), + ) + .map_err(|_| ConvertMessageError::InvalidVersionedXCM)?; + message_xcm = versioned_xcm.try_into().map_err(|_| ConvertMessageError::InvalidXCM)?; + } + + log::debug!(target: LOG_TARGET,"xcm decoded as {:?}", message_xcm); + + let network = EthereumNetwork::get(); + + let origin_location = Location::new( + 2, + [GlobalConsensus(network), AccountKey20 { key: message.origin.into(), network: None }], + ); + + let network = EthereumNetwork::get(); + + let fee_asset = Location::new(1, Here); + let fee: xcm::prelude::Asset = (fee_asset.clone(), XcmPrologueFee::get()).into(); + let mut instructions = vec![ + ReceiveTeleportedAsset(fee.clone().into()), + PayFees { asset: fee }, + DescendOrigin(PalletInstance(InboundQueuePalletInstance::get()).into()), + UniversalOrigin(GlobalConsensus(network)), + ]; + + for asset in &message.assets { + match asset { + Asset::NativeTokenERC20 { token_id, value } => { + let token_location: Location = Location::new( + 2, + [ + GlobalConsensus(EthereumNetwork::get()), + AccountKey20 { network: None, key: (*token_id).into() }, + ], + ); + instructions.push(ReserveAssetDeposited((token_location, *value).into())); + }, + Asset::ForeignTokenERC20 { token_id, value } => { + let asset_id = ConvertAssetId::convert(&token_id) + .ok_or(ConvertMessageError::InvalidAsset)?; + instructions.push(WithdrawAsset((asset_id, *value).into())); + }, + } + } + + if let Some(claimer) = message.claimer { + let claimer = Junction::decode(&mut claimer.as_ref()) + .map_err(|_| ConvertMessageError::InvalidClaimer)?; + let claimer_location: Location = Location::new(0, [claimer.into()]); + instructions.push(SetAssetClaimer { location: claimer_location }); + } + + // Set the alias origin to the original sender on Ethereum. Important to be before the + // arbitrary XCM that is appended to the message on the next line. + instructions.push(AliasOrigin(origin_location.into())); + + // Add the XCM sent in the message to the end of the xcm instruction + instructions.extend(message_xcm.0); + + let appendix = vec![ + RefundSurplus, + // Refund excess fees to the relayer + // TODO maybe refund all fees to the relayer instead of just DOT? + DepositAsset { + assets: Wild(AllOf { id: AssetId(fee_asset.into()), fun: WildFungible }), + beneficiary: origin_account_location, + }, + ]; + + instructions.extend(appendix); + + Ok(instructions.into()) + } } #[cfg(test)] mod tests { - use crate::inbound::{ - v2::{ConvertMessage, Message, MessageToXcm}, - CallIndex, GlobalConsensusEthereumConvertsFor, - }; - use codec::Decode; - use frame_support::{assert_ok, parameter_types}; - use hex_literal::hex; - use sp_runtime::traits::ConstU8; - use xcm::prelude::*; - use xcm_executor::traits::ConvertLocation; - - const NETWORK: NetworkId = Ethereum { chain_id: 11155111 }; - - parameter_types! { + use crate::inbound::{ + v2::{ConvertMessage, Message, MessageToXcm}, + CallIndex, GlobalConsensusEthereumConvertsFor, + }; + use codec::Decode; + use frame_support::{assert_ok, parameter_types}; + use hex_literal::hex; + use sp_runtime::traits::ConstU8; + 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]; @@ -185,66 +200,66 @@ mod tests { 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)]); + #[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 = - GlobalConsensusEthereumConvertsFor::<[u8; 32]>::convert_location(&contract_location) - .unwrap(); + let account = + GlobalConsensusEthereumConvertsFor::<[u8; 32]>::convert_location(&contract_location) + .unwrap(); - assert_eq!(account, expected_account); - } + 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)]); + #[test] + fn test_contract_location_with_incorrect_location_fails_convert() { + let contract_location = Location::new(2, [GlobalConsensus(Polkadot), Parachain(1000)]); - assert_eq!( + assert_eq!( GlobalConsensusEthereumConvertsFor::<[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!( + } + + #[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()); - } - } - - #[test] - fn test_convert_message() { - let payload = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf040197874824853fb4ad04794ccfd1cc8d2a7463839cfcbc6a315a1045c60ab85f400000b2d3595bf00600000000000000000000").to_vec(); - let message = Message::decode(&mut payload.as_ref()); - assert_ok!(message.clone()); - let result = MessageToXcm::>::convert(message.unwrap()); - assert_ok!(result); - } + assert_eq!(reanchored_asset_with_ethereum_context, asset.clone()); + } + } + + #[test] + fn test_convert_message() { + let payload = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf040197874824853fb4ad04794ccfd1cc8d2a7463839cfcbc6a315a1045c60ab85f400000b2d3595bf00600000000000000000000").to_vec(); + let message = Message::decode(&mut payload.as_ref()); + assert_ok!(message.clone()); + let result = MessageToXcm::>::convert(message.unwrap()); + assert_ok!(result); + } } 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 e54e5934f94e..8e5cbf8ff4d6 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,50 +13,103 @@ // See the License for the specific language governing permissions and // limitations under the License. use crate::imports::*; -use hex_literal::hex; -use bridge_hub_westend_runtime::EthereumInboundQueueV2; -use snowbridge_router_primitives::inbound::v2::Message; -use bridge_hub_westend_runtime::RuntimeOrigin; +use bridge_hub_westend_runtime::{EthereumInboundQueueV2, RuntimeOrigin}; +use frame_support::weights::WeightToFee; +use snowbridge_router_primitives::inbound::v2::{Asset::NativeTokenERC20, Message}; use sp_core::H160; -use snowbridge_router_primitives::inbound::v2::Asset::NativeTokenERC20; +use testnet_parachains_constants::westend::fee::WeightToFee as WeightCalculator; /// Calculates the XCM prologue fee for sending an XCM to AH. const INITIAL_FUND: u128 = 5_000_000_000_000; +use testnet_parachains_constants::westend::snowbridge::EthereumNetwork; #[test] fn xcm_prologue_fee() { BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id().into(), INITIAL_FUND); + let asset_hub_sovereign = BridgeHubWestend::sovereign_account_id_of(Location::new( + 1, + [Parachain(AssetHubWestend::para_id().into())], + )); + let relayer = BridgeHubWestendSender::get(); - let claimer = AssetHubWestendReceiver::get(); - BridgeHubWestend::fund_accounts(vec![ - (relayer.clone(), INITIAL_FUND), - ]); + let receiver = AssetHubWestendReceiver::get(); + BridgeHubWestend::fund_accounts(vec![(relayer.clone(), INITIAL_FUND)]); + + let mut token_ids = Vec::new(); + for _ in 0..8 { + token_ids.push(H160::random()); + } + + let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); + + AssetHubWestend::execute_with(|| { + type RuntimeOrigin = ::RuntimeOrigin; + + for token_id in token_ids.iter() { + let token_id = *token_id; + + let asset_location = Location::new( + 2, + [ + GlobalConsensus(ethereum_network_v5), + AccountKey20 { network: None, key: token_id.into() }, + ], + ); - let token_id_1 = H160::random(); + assert_ok!(::ForeignAssets::force_create( + RuntimeOrigin::root(), + asset_location.clone(), + asset_hub_sovereign.clone().into(), + false, + 1, + )); + + assert!(::ForeignAssets::asset_exists( + asset_location.clone().try_into().unwrap(), + )); + } + }); + + let native_tokens: Vec = token_ids + .iter() + .map(|token_id| NativeTokenERC20 { token_id: *token_id, value: 3_000_000_000 }) + .collect(); BridgeHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; - let claimer = AccountId32{network: None, id: claimer.into()}; + let claimer = AccountId32 { network: None, id: receiver.clone().into() }; let claimer_bytes = claimer.encode(); + let origin = H160::random(); + let relayer_location = + Location::new(0, AccountId32 { network: None, id: relayer.clone().into() }); - let message = Message{ - origin: H160::random(), - assets: vec![ - NativeTokenERC20 { - token_id: token_id_1, - value: 1_000_000_000, - } - ], - xcm: hex!().to_vec(), - claimer: Some(claimer_bytes) + let message_xcm_instructions = + vec![DepositAsset { assets: Wild(AllCounted(8)), beneficiary: receiver.into() }]; + let message_xcm: Xcm<()> = message_xcm_instructions.into(); + let versioned_message_xcm = VersionedXcm::V5(message_xcm); + + let message = Message { + origin, + assets: native_tokens, + xcm: versioned_message_xcm.encode(), + claimer: Some(claimer_bytes), }; - let xcm = EthereumInboundQueueV2::do_convert(message).unwrap(); - let _ = EthereumInboundQueueV2::send_xcm(RuntimeOrigin::signed(relayer.clone()), xcm, AssetHubWestend::para_id().into()).unwrap(); + let xcm = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); + let _ = EthereumInboundQueueV2::send_xcm( + RuntimeOrigin::signed(relayer.clone()), + xcm, + AssetHubWestend::para_id().into(), + ) + .unwrap(); assert_expected_events!( BridgeHubWestend, vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},] ); }); + + let execution_fee = WeightCalculator::weight_to_fee(&Weight::from_parts(1410450000, 33826)); + let buffered_fee = execution_fee * 2; + println!("buffered execution fee for prologue for 8 assets: {}", buffered_fee); } 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 e9c4f3ad288b..624127823518 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 @@ -72,6 +72,11 @@ parameter_types! { pub AssetHubFromEthereum: Location = Location::new(1,[GlobalConsensus(RelayNetwork::get()),Parachain(westend_runtime_constants::system_parachain::ASSET_HUB_ID)]); pub EthereumUniversalLocation: InteriorLocation = [GlobalConsensus(EthereumNetwork::get())].into(); } + +/// The XCM execution fee on AH for the static part of the XCM message (not the user provided parts). +/// Calculated with integration test snowbridge_v2::xcm_prologue_fee +const XCM_PROLOGUE_FEE: u128 = 67_652_000_000; + impl snowbridge_pallet_inbound_queue::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Verifier = snowbridge_pallet_ethereum_client::Pallet; @@ -115,12 +120,13 @@ impl snowbridge_pallet_inbound_queue_v2::Config for Runtime { type WeightInfo = crate::weights::snowbridge_pallet_inbound_queue_v2::WeightInfo; type AssetHubParaId = ConstU32<1000>; type Token = Balances; - type XcmPrologueFee = ConstU128<1_000_000_000>; + type XcmPrologueFee = ConstU128; type AssetTransactor = ::AssetTransactor; type MessageConverter = snowbridge_router_primitives::inbound::v2::MessageToXcm< EthereumNetwork, ConstU8, EthereumSystem, + ConstU128, >; } From 11839dcff2e2348dc4fe392280ffb90de4f726bd Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Tue, 26 Nov 2024 09:57:22 +0200 Subject: [PATCH 16/52] adds comments --- .../snowbridge/pallets/inbound-queue-v2/src/lib.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index 12b0f417576f..f4c6d99276bc 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -94,21 +94,23 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { type RuntimeEvent: From> + IsType<::RuntimeEvent>; - - /// The verifier for inbound messages from Ethereum + /// The verifier for inbound messages from Ethereum. type Verifier: Verifier; - - /// XCM message sender + /// XCM message sender. type XcmSender: SendController<::RuntimeOrigin>; - /// Address of the Gateway contract + /// Address of the Gateway contract. #[pallet::constant] type GatewayAddress: Get; type WeightInfo: WeightInfo; - /// AssetHub parachain ID + /// AssetHub parachain ID. type AssetHubParaId: Get; + /// Convert a command from Ethereum to an XCM message. type MessageConverter: ConvertMessage; + /// The AH XCM execution fee for the static part of the XCM message. type XcmPrologueFee: Get>; + /// Used to burn fees from the origin account (the relayer), which will be teleported to AH. type Token: Mutate + Inspect; + /// Used to burn fees. type AssetTransactor: TransactAsset; #[cfg(feature = "runtime-benchmarks")] type Helper: BenchmarkHelper; From 89746964578648edd2e9eebea83ad39b065d4d01 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Wed, 27 Nov 2024 09:26:34 +0200 Subject: [PATCH 17/52] revert sendcontroller --- .../pallets/inbound-queue-v2/src/lib.rs | 45 +++++++++++-------- .../pallets/inbound-queue-v2/src/mock.rs | 42 ++++++++--------- .../src/tests/snowbridge_v2.rs | 9 +--- .../src/bridge_to_ethereum_config.rs | 4 +- .../bridge-hub-westend/src/xcm_config.rs | 2 +- 5 files changed, 53 insertions(+), 49 deletions(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index f4c6d99276bc..53cc9cc4f8ef 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -39,7 +39,6 @@ mod mock; #[cfg(test)] mod test; -use alloc::boxed::Box; use codec::{Decode, DecodeAll, Encode}; use envelope::Envelope; use frame_support::{ @@ -48,11 +47,6 @@ use frame_support::{ }; use frame_system::{ensure_signed, pallet_prelude::*}; use scale_info::TypeInfo; -use sp_core::H160; -use sp_std::vec; -use types::Nonce; -use xcm::prelude::{Junction::*, Location, *}; - use snowbridge_core::{ fees::burn_fees, inbound::{Message, VerificationError, Verifier}, @@ -62,9 +56,11 @@ use snowbridge_core::{ use snowbridge_router_primitives::inbound::v2::{ ConvertMessage, ConvertMessageError, Message as MessageV2, }; +use sp_core::H160; +use sp_std::vec; +use types::Nonce; pub use weights::WeightInfo; -use xcm::{VersionedLocation, VersionedXcm}; -use xcm_builder::SendController; +use xcm::prelude::{send_xcm, Junction::*, Location, SendError as XcmpSendError, SendXcm, *}; use xcm_executor::traits::TransactAsset; #[cfg(feature = "runtime-benchmarks")] @@ -97,7 +93,7 @@ pub mod pallet { /// The verifier for inbound messages from Ethereum. type Verifier: Verifier; /// XCM message sender. - type XcmSender: SendController<::RuntimeOrigin>; + type XcmSender: SendXcm; /// Address of the Gateway contract. #[pallet::constant] type GatewayAddress: Get; @@ -174,6 +170,22 @@ pub mod pallet { 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 nonce of the message been processed or not #[pallet::storage] pub type NonceBitmap = StorageMap<_, Twox64Concat, u128, u128, ValueQuery>; @@ -230,7 +242,7 @@ pub mod pallet { // Attempt to forward XCM to AH - let message_id = Self::send_xcm(origin, xcm, T::AssetHubParaId::get())?; + let message_id = Self::send_xcm(xcm, T::AssetHubParaId::get())?; Self::deposit_event(Event::MessageReceived { nonce: envelope.nonce, message_id }); // Set nonce flag to true @@ -260,15 +272,10 @@ pub mod pallet { Ok(Location::new(0, [AccountId32 { network: None, id: account_bytes }])) } - pub fn send_xcm( - origin: OriginFor, - xcm: Xcm<()>, - dest_para_id: u32, - ) -> Result { - let versioned_dest = - Box::new(VersionedLocation::V5(Location::new(1, [Parachain(dest_para_id)]))); - let versioned_xcm = Box::new(VersionedXcm::V5(xcm)); - Ok(T::XcmSender::send(origin, versioned_dest, versioned_xcm)?) + pub fn send_xcm(xcm: Xcm<()>, dest_para_id: u32) -> Result> { + let dest = Location::new(1, [Parachain(dest_para_id)]); + let (message_id, _) = send_xcm::(dest, xcm).map_err(Error::::from)?; + Ok(message_id) } pub fn do_convert( diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs index 105863f5772f..e04ff64a37a4 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs @@ -16,14 +16,13 @@ use snowbridge_core::{ TokenId, }; use snowbridge_router_primitives::inbound::v2::MessageToXcm; -use sp_core::{H160, H256}; +use sp_core::H160; use sp_runtime::{ traits::{IdentifyAccount, IdentityLookup, MaybeEquivalence, Verify}, - BuildStorage, DispatchError, MultiSignature, + BuildStorage, MultiSignature, }; use sp_std::{convert::From, default::Default}; -use xcm::prelude::*; -use xcm_builder::SendControllerWeightInfo; +use xcm::{latest::SendXcm, prelude::*}; use xcm_executor::{traits::TransactAsset, AssetsInHolding}; type Block = frame_system::mocking::MockBlock; @@ -111,25 +110,28 @@ impl BenchmarkHelper for Test { fn initialize_storage(_: BeaconHeader, _: H256) {} } -pub struct MockXcmSenderWeights; - -impl SendControllerWeightInfo for MockXcmSenderWeights { - fn send() -> Weight { - return Weight::default(); - } -} - // 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())) + } + } -impl SendController for MockXcmSender { - type WeightInfo = MockXcmSenderWeights; - fn send( - _origin: mock::RuntimeOrigin, - _dest: Box, - _message: Box>, - ) -> Result { - Ok(H256::random().into()) + fn deliver(xcm: Self::Ticket) -> core::result::Result { + let hash = xcm.using_encoded(sp_io::hashing::blake2_256); + Ok(hash) } } 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 8e5cbf8ff4d6..59f0b9b107ff 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,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. use crate::imports::*; -use bridge_hub_westend_runtime::{EthereumInboundQueueV2, RuntimeOrigin}; +use bridge_hub_westend_runtime::EthereumInboundQueueV2; use frame_support::weights::WeightToFee; use snowbridge_router_primitives::inbound::v2::{Asset::NativeTokenERC20, Message}; use sp_core::H160; @@ -96,12 +96,7 @@ fn xcm_prologue_fee() { claimer: Some(claimer_bytes), }; let xcm = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); - let _ = EthereumInboundQueueV2::send_xcm( - RuntimeOrigin::signed(relayer.clone()), - xcm, - AssetHubWestend::para_id().into(), - ) - .unwrap(); + let _ = EthereumInboundQueueV2::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); assert_expected_events!( BridgeHubWestend, 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 624127823518..a9e3e48e4f50 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 @@ -21,7 +21,7 @@ use crate::{ RuntimeEvent, TransactionByteFee, }; #[cfg(not(feature = "runtime-benchmarks"))] -use crate::{PolkadotXcm, XcmRouter}; +use crate::XcmRouter; use parachains_common::{AccountId, Balance}; use snowbridge_beacon_primitives::{Fork, ForkVersions}; use snowbridge_core::{gwei, meth, AllowSiblingsOnly, PricingParameters, Rewards}; @@ -111,7 +111,7 @@ impl snowbridge_pallet_inbound_queue_v2::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Verifier = snowbridge_pallet_ethereum_client::Pallet; #[cfg(not(feature = "runtime-benchmarks"))] - type XcmSender = PolkadotXcm; + type XcmSender = XcmRouter; #[cfg(feature = "runtime-benchmarks")] type XcmSender = DoNothingRouter; type GatewayAddress = EthereumGatewayAddress; 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 38d9bec0c0f8..befb63ef9709 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 @@ -250,7 +250,7 @@ impl pallet_xcm::Config for Runtime { type RuntimeEvent = RuntimeEvent; type XcmRouter = XcmRouter; // We want to disallow users sending (arbitrary) XCMs from this chain. - type SendXcmOrigin = EnsureXcmOrigin; + type SendXcmOrigin = EnsureXcmOrigin; // We support local origins dispatching XCM executions. type ExecuteXcmOrigin = EnsureXcmOrigin; type XcmExecuteFilter = Everything; From 75d3c064dd781fbef03cb5c24adcde299028b9ce Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Wed, 27 Nov 2024 11:44:21 +0200 Subject: [PATCH 18/52] adds test for xcm register command --- Cargo.lock | 1 + .../bridges/bridge-hub-westend/Cargo.toml | 1 + .../src/tests/snowbridge_v2.rs | 60 +++++++++++++++++++ 3 files changed, 62 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index ef3f6cc673ff..f7352d583e40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2731,6 +2731,7 @@ dependencies = [ "cumulus-pallet-xcmp-queue 0.7.0", "emulated-integration-tests-common", "frame-support 28.0.0", + "hex", "hex-literal", "log", "pallet-asset-conversion 10.0.0", 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..fc3cbc835b04 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 @@ -12,6 +12,7 @@ workspace = true [dependencies] hex-literal = { workspace = true, default-features = true } +hex = { workspace = true, default-features = true } codec = { workspace = true } log = { workspace = true } scale-info = { workspace = true } 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 59f0b9b107ff..d194e1948bba 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 @@ -17,6 +17,11 @@ use bridge_hub_westend_runtime::EthereumInboundQueueV2; use frame_support::weights::WeightToFee; use snowbridge_router_primitives::inbound::v2::{Asset::NativeTokenERC20, Message}; use sp_core::H160; +use sp_core::H256; +use codec::Encode; +use sp_runtime::MultiAddress; +use bridge_hub_westend_runtime::bridge_to_ethereum_config::CreateAssetCall; +use bridge_hub_westend_runtime::bridge_to_ethereum_config::CreateAssetDeposit; use testnet_parachains_constants::westend::fee::WeightToFee as WeightCalculator; /// Calculates the XCM prologue fee for sending an XCM to AH. @@ -108,3 +113,58 @@ fn xcm_prologue_fee() { let buffered_fee = execution_fee * 2; println!("buffered execution fee for prologue for 8 assets: {}", buffered_fee); } + +#[test] +fn register_token_xcm() { + BridgeHubWestend::execute_with(|| { + let weth_contract = H160::zero(); // weth contract placeholder + let token: H160 = H160::zero(); // token id placeholder + let owner: H256 = H256::zero(); // bridge placeholder + let dot_to_eth_rate: u128 = 2_500_000_000_000_000; + + let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); + let weth_asset = Location::new( + 2, + [ + GlobalConsensus(ethereum_network_v5), + AccountKey20 { network: None, key: weth_contract.into() }, + ], + ); + let dot_asset = Location::new(1, Here); + + let weth_asset_creation: u128 = (CreateAssetDeposit::get() * dot_to_eth_rate) / 10_u128.pow(18); + let weth_fee: xcm::prelude::Asset = (weth_asset, weth_asset_creation).into(); + let dot_fee: xcm::prelude::Asset = (dot_asset, CreateAssetDeposit::get()).into(); + + println!("weth_asset_creation: {:?}", weth_asset_creation); + + let asset_id = Location::new( + 2, + [GlobalConsensus(ethereum_network_v5), AccountKey20 { network: None, key: token.into() }], + ); + + let register_token_xcm = + vec![ + ExchangeAsset { give: weth_fee.into(), want: dot_fee.clone().into(), maximal: false }, + PayFees { asset: dot_fee }, + Transact { + origin_kind: OriginKind::Xcm, + call: ( + CreateAssetCall::get(), + asset_id, + MultiAddress::<[u8; 32], ()>::Id(owner.into()), + 1, + ) + .encode() + .into(), + }, + ]; + let message_xcm: Xcm<()> = register_token_xcm.into(); + let versioned_message_xcm = VersionedXcm::V5(message_xcm); + + let xcm_bytes = versioned_message_xcm.encode(); + let hex_string = hex::encode(xcm_bytes); + println!("register token hex: {:x?}", hex_string); + }); +} + From c666d235fde1832dbb7047a2e8e32196fe241985 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Wed, 27 Nov 2024 12:33:56 +0200 Subject: [PATCH 19/52] update test --- .../src/tests/snowbridge_v2.rs | 23 +++++-------------- 1 file changed, 6 insertions(+), 17 deletions(-) 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 d194e1948bba..b54cf0d3a753 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 @@ -117,27 +117,17 @@ fn xcm_prologue_fee() { #[test] fn register_token_xcm() { BridgeHubWestend::execute_with(|| { - let weth_contract = H160::zero(); // weth contract placeholder - let token: H160 = H160::zero(); // token id placeholder - let owner: H256 = H256::zero(); // bridge placeholder - let dot_to_eth_rate: u128 = 2_500_000_000_000_000; + //let token: H160 = H160::zero(); // token id placeholder + let token: H160 = H160::random(); // token id placeholder + //let owner: H256 = H256::zero(); // bridge owner placeholder + let owner: H256 = H256::random(); // bridge owner placeholder + println!("token: {:x?}", token); + println!("owner: {:x?}", owner); let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); - let weth_asset = Location::new( - 2, - [ - GlobalConsensus(ethereum_network_v5), - AccountKey20 { network: None, key: weth_contract.into() }, - ], - ); let dot_asset = Location::new(1, Here); - - let weth_asset_creation: u128 = (CreateAssetDeposit::get() * dot_to_eth_rate) / 10_u128.pow(18); - let weth_fee: xcm::prelude::Asset = (weth_asset, weth_asset_creation).into(); let dot_fee: xcm::prelude::Asset = (dot_asset, CreateAssetDeposit::get()).into(); - println!("weth_asset_creation: {:?}", weth_asset_creation); - let asset_id = Location::new( 2, [GlobalConsensus(ethereum_network_v5), AccountKey20 { network: None, key: token.into() }], @@ -145,7 +135,6 @@ fn register_token_xcm() { let register_token_xcm = vec![ - ExchangeAsset { give: weth_fee.into(), want: dot_fee.clone().into(), maximal: false }, PayFees { asset: dot_fee }, Transact { origin_kind: OriginKind::Xcm, From 42d688a28a31e76e58043955d3fbef2c5cb4a677 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Thu, 28 Nov 2024 10:28:09 +0200 Subject: [PATCH 20/52] runtime api --- Cargo.lock | 3 + Cargo.toml | 1 + .../inbound-queue-v2/runtime-api/Cargo.toml | 12 ++- .../inbound-queue-v2/runtime-api/src/lib.rs | 7 +- .../pallets/inbound-queue-v2/src/api.rs | 22 ++++-- .../pallets/inbound-queue-v2/src/lib.rs | 10 ++- .../pallets/inbound-queue-v2/src/mock.rs | 2 + .../src/tests/snowbridge_v2.rs | 78 +++++++++++-------- .../bridge-hubs/bridge-hub-westend/Cargo.toml | 3 +- .../src/bridge_to_ethereum_config.rs | 10 ++- .../bridge-hubs/bridge-hub-westend/src/lib.rs | 8 ++ 11 files changed, 104 insertions(+), 52 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f7352d583e40..5b76010033b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2824,6 +2824,7 @@ dependencies = [ "serde_json", "snowbridge-beacon-primitives 0.2.0", "snowbridge-core 0.2.0", + "snowbridge-inbound-queue-v2-runtime-api", "snowbridge-outbound-queue-runtime-api 0.2.0", "snowbridge-pallet-ethereum-client 0.2.0", "snowbridge-pallet-inbound-queue 0.2.0", @@ -24792,9 +24793,11 @@ dependencies = [ name = "snowbridge-inbound-queue-v2-runtime-api" version = "0.2.0" dependencies = [ + "frame-support 28.0.0", "snowbridge-core 0.2.0", "snowbridge-router-primitives 0.9.0", "sp-api 26.0.0", + "sp-runtime 31.0.1", "staging-xcm 7.0.0", ] diff --git a/Cargo.toml b/Cargo.toml index 3af053cc74e1..270e4e268d5e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1233,6 +1233,7 @@ snowbridge-pallet-inbound-queue = { path = "bridges/snowbridge/pallets/inbound-q 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-inbound-queue-v2-runtime-api = { path = "bridges/snowbridge/pallets/inbound-queue-v2/runtime-api", default-features = false } snowbridge-pallet-outbound-queue = { path = "bridges/snowbridge/pallets/outbound-queue", 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 } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/Cargo.toml b/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/Cargo.toml index 9b03370ec891..c9c38a44dd54 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/Cargo.toml +++ b/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/Cargo.toml @@ -15,16 +15,20 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-api = { workspace = true } -snowbridge-core = { workspace = true } -snowbridge-router-primitives = { workspace = true } -xcm = { workspace = true } +frame-support = { workspace = true, default-features = false } +sp-api = { workspace = true, default-features = false } +sp-runtime = { workspace = true, default-features = false } +snowbridge-core = { workspace = true, default-features = false } +snowbridge-router-primitives = { workspace = true, default-features = false } +xcm = { workspace = true, default-features = false } [features] default = ["std"] std = [ + "frame-support/std", "snowbridge-core/std", "snowbridge-router-primitives/std", + "sp-runtime/std", "sp-api/std", "xcm/std", ] diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/src/lib.rs index 03720b7ca3d2..d899f7477b45 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/src/lib.rs @@ -2,14 +2,15 @@ // SPDX-FileCopyrightText: 2023 Snowfork #![cfg_attr(not(feature = "std"), no_std)] -use snowbridge_core::inbound::Proof; +use frame_support::traits::tokens::Balance as BalanceT; use snowbridge_router_primitives::inbound::v2::Message; use xcm::latest::Xcm; +use sp_runtime::DispatchError; sp_api::decl_runtime_apis! { - pub trait InboundQueueApiV2 + pub trait InboundQueueApiV2 where Balance: BalanceT { /// Dry runs the provided message on AH to provide the XCM payload and execution cost. - fn dry_run(message: Message, proof: Proof) -> (Xcm<()>, u128); + fn dry_run(message: Message) -> Result<(Xcm<()>, Balance), DispatchError>; } } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs index 532a1b453366..241d1372f7ea 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs @@ -2,18 +2,30 @@ // SPDX-FileCopyrightText: 2023 Snowfork //! Implements the dry-run API. -use crate::{Config, Error, Junction::AccountId32, Location}; -use snowbridge_core::inbound::Proof; +use crate::{weights::WeightInfo, Config, Error, Junction::AccountId32, Location}; +use frame_support::weights::WeightToFee; use snowbridge_router_primitives::inbound::v2::{ConvertMessage, Message}; -use sp_core::H256; +use sp_core::{Get, H256}; +use sp_runtime::{DispatchError, Saturating}; use xcm::latest::Xcm; -pub fn dry_run(message: Message, _proof: Proof) -> Result, Error> +pub fn dry_run(message: Message) -> Result<(Xcm<()>, T::Balance), DispatchError> where T: Config, { + // Convert message to XCM let dummy_origin = Location::new(0, AccountId32 { id: H256::zero().into(), network: None }); let xcm = T::MessageConverter::convert(message, dummy_origin) .map_err(|e| Error::::ConvertMessage(e))?; - Ok(xcm) + + // Calculate fee. Consists of the cost of the "submit" extrinsic as well as the XCM execution + // prologue fee (static XCM part of the message that is execution on AH). + let weight_fee = T::WeightToFee::weight_to_fee(&T::WeightInfo::submit()); + let xcm_prologue_fee = T::XcmPrologueFee::get(); + let fee: u128 = weight_fee + .saturating_add(xcm_prologue_fee) + .try_into() + .map_err(|_| Error::::InvalidFee)?; + + Ok((xcm, fee.into())) } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index 53cc9cc4f8ef..6da0ddcdb673 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -42,7 +42,11 @@ mod test; use codec::{Decode, DecodeAll, Encode}; use envelope::Envelope; use frame_support::{ - traits::fungible::{Inspect, Mutate}, + traits::{ + fungible::{Inspect, Mutate}, + tokens::Balance, + }, + weights::WeightToFee, PalletError, }; use frame_system::{ensure_signed, pallet_prelude::*}; @@ -98,6 +102,8 @@ pub mod pallet { #[pallet::constant] type GatewayAddress: Get; type WeightInfo: WeightInfo; + /// Convert a weight value into deductible balance type. + type WeightToFee: WeightToFee>; /// AssetHub parachain ID. type AssetHubParaId: Get; /// Convert a command from Ethereum to an XCM message. @@ -106,6 +112,8 @@ pub mod pallet { type XcmPrologueFee: Get>; /// Used to burn fees from the origin account (the relayer), which will be teleported to AH. type Token: Mutate + Inspect; + /// Used for the dry run API implementation. + type Balance: Balance + From; /// Used to burn fees. type AssetTransactor: TransactAsset; #[cfg(feature = "runtime-benchmarks")] diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs index e04ff64a37a4..4c895f85bc8c 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs @@ -159,6 +159,7 @@ impl inbound_queue_v2::Config for Test { type Verifier = MockVerifier; type XcmSender = MockXcmSender; type WeightInfo = (); + type WeightToFee = IdentityFee; type GatewayAddress = GatewayAddress; type AssetHubParaId = ConstU32<1000>; type MessageConverter = MessageToXcm< @@ -168,6 +169,7 @@ impl inbound_queue_v2::Config for Test { ConstU128, >; type Token = Balances; + type Balance = u128; type XcmPrologueFee = ConstU128; type AssetTransactor = SuccessfulTransactor; #[cfg(feature = "runtime-benchmarks")] 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 b54cf0d3a753..a103195c8fb8 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,15 +13,19 @@ // See the License for the specific language governing permissions and // limitations under the License. use crate::imports::*; -use bridge_hub_westend_runtime::EthereumInboundQueueV2; +use bridge_hub_westend_runtime::{ + bridge_to_ethereum_config::{CreateAssetCall, CreateAssetDeposit}, + EthereumInboundQueueV2, +}; +use codec::Encode; use frame_support::weights::WeightToFee; -use snowbridge_router_primitives::inbound::v2::{Asset::NativeTokenERC20, Message}; +use hex_literal::hex; +use snowbridge_router_primitives::inbound::{ + v2::{Asset::NativeTokenERC20, Message}, + EthereumLocationsConverterFor, +}; use sp_core::H160; -use sp_core::H256; -use codec::Encode; use sp_runtime::MultiAddress; -use bridge_hub_westend_runtime::bridge_to_ethereum_config::CreateAssetCall; -use bridge_hub_westend_runtime::bridge_to_ethereum_config::CreateAssetDeposit; use testnet_parachains_constants::westend::fee::WeightToFee as WeightCalculator; /// Calculates the XCM prologue fee for sending an XCM to AH. @@ -117,12 +121,8 @@ fn xcm_prologue_fee() { #[test] fn register_token_xcm() { BridgeHubWestend::execute_with(|| { - //let token: H160 = H160::zero(); // token id placeholder - let token: H160 = H160::random(); // token id placeholder - //let owner: H256 = H256::zero(); // bridge owner placeholder - let owner: H256 = H256::random(); // bridge owner placeholder + let token: H160 = hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").into(); // token id placeholder println!("token: {:x?}", token); - println!("owner: {:x?}", owner); let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); let dot_asset = Location::new(1, Here); @@ -130,30 +130,40 @@ fn register_token_xcm() { let asset_id = Location::new( 2, - [GlobalConsensus(ethereum_network_v5), AccountKey20 { network: None, key: token.into() }], - ); - - let register_token_xcm = - vec![ - PayFees { asset: dot_fee }, - Transact { - origin_kind: OriginKind::Xcm, - call: ( - CreateAssetCall::get(), - asset_id, - MultiAddress::<[u8; 32], ()>::Id(owner.into()), - 1, - ) - .encode() - .into(), - }, - ]; - let message_xcm: Xcm<()> = register_token_xcm.into(); - let versioned_message_xcm = VersionedXcm::V5(message_xcm); + [ + GlobalConsensus(ethereum_network_v5), + AccountKey20 { network: None, key: token.into() }, + ], + ); - let xcm_bytes = versioned_message_xcm.encode(); - let hex_string = hex::encode(xcm_bytes); - println!("register token hex: {:x?}", hex_string); + println!( + "register token mainnet: {:x?}", + get_xcm_hex(1u64, asset_id.clone(), dot_fee.clone()) + ); + println!("register token sepolia: {:x?}", get_xcm_hex(11155111u64, asset_id, dot_fee)); }); } +fn get_xcm_hex(chain_id: u64, asset_id: Location, dot_fee: xcm::prelude::Asset) -> String { + let owner = EthereumLocationsConverterFor::<[u8; 32]>::from_chain_id(&chain_id); + + let register_token_xcm = vec![ + PayFees { asset: dot_fee }, + Transact { + origin_kind: OriginKind::Xcm, + call: ( + CreateAssetCall::get(), + asset_id, + MultiAddress::<[u8; 32], ()>::Id(owner.into()), + 1, + ) + .encode() + .into(), + }, + ]; + let message_xcm: Xcm<()> = register_token_xcm.into(); + let versioned_message_xcm = VersionedXcm::V5(message_xcm); + + let xcm_bytes = versioned_message_xcm.encode(); + hex::encode(xcm_bytes) +} 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 5169a51c3702..29c21c875d13 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml @@ -111,12 +111,12 @@ snowbridge-core = { workspace = true } snowbridge-pallet-ethereum-client = { workspace = true } snowbridge-pallet-inbound-queue = { workspace = true } snowbridge-pallet-inbound-queue-v2 = { workspace = true } +snowbridge-inbound-queue-v2-runtime-api = { workspace = true } snowbridge-pallet-outbound-queue = { workspace = true } snowbridge-outbound-queue-runtime-api = { workspace = true } snowbridge-router-primitives = { workspace = true } snowbridge-runtime-common = { workspace = true } - [dev-dependencies] bridge-hub-test-utils = { workspace = true, default-features = true } bridge-runtime-common = { features = ["integrity-test"], workspace = true, default-features = true } @@ -187,6 +187,7 @@ std = [ "snowbridge-beacon-primitives/std", "snowbridge-core/std", "snowbridge-outbound-queue-runtime-api/std", + "snowbridge-inbound-queue-v2-runtime-api/std", "snowbridge-pallet-ethereum-client/std", "snowbridge-pallet-inbound-queue/std", "snowbridge-pallet-outbound-queue/std", 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 a9e3e48e4f50..e1d3cc42364a 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 @@ -14,14 +14,14 @@ // 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, xcm_config::{TreasuryAccount, UniversalLocation}, Balances, EthereumInboundQueue, EthereumOutboundQueue, EthereumSystem, MessageQueue, Runtime, RuntimeEvent, TransactionByteFee, }; -#[cfg(not(feature = "runtime-benchmarks"))] -use crate::XcmRouter; use parachains_common::{AccountId, Balance}; use snowbridge_beacon_primitives::{Fork, ForkVersions}; use snowbridge_core::{gwei, meth, AllowSiblingsOnly, PricingParameters, Rewards}; @@ -73,8 +73,8 @@ parameter_types! { pub EthereumUniversalLocation: InteriorLocation = [GlobalConsensus(EthereumNetwork::get())].into(); } -/// The XCM execution fee on AH for the static part of the XCM message (not the user provided parts). -/// Calculated with integration test snowbridge_v2::xcm_prologue_fee +/// The XCM execution fee on AH for the static part of the XCM message (not the user provided +/// parts). Calculated with integration test snowbridge_v2::xcm_prologue_fee const XCM_PROLOGUE_FEE: u128 = 67_652_000_000; impl snowbridge_pallet_inbound_queue::Config for Runtime { @@ -118,8 +118,10 @@ impl snowbridge_pallet_inbound_queue_v2::Config for Runtime { #[cfg(feature = "runtime-benchmarks")] type Helper = Runtime; type WeightInfo = crate::weights::snowbridge_pallet_inbound_queue_v2::WeightInfo; + type WeightToFee = WeightToFee; type AssetHubParaId = ConstU32<1000>; type Token = Balances; + type Balance = Balance; type XcmPrologueFee = ConstU128; type AssetTransactor = ::AssetTransactor; type MessageConverter = snowbridge_router_primitives::inbound::v2::MessageToXcm< 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 8f8f1c93bd6d..fe43ba51c7f2 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 @@ -51,6 +51,8 @@ use sp_runtime::{ ApplyExtrinsicResult, }; +use snowbridge_router_primitives::inbound::v2::Message; +use sp_runtime::DispatchError; #[cfg(feature = "std")] use sp_version::NativeVersion; use sp_version::RuntimeVersion; @@ -901,6 +903,12 @@ impl_runtime_apis! { } } + impl snowbridge_inbound_queue_v2_runtime_api::InboundQueueApiV2 for Runtime { + fn dry_run(message: Message) -> Result<(Xcm<()>, Balance), DispatchError> { + snowbridge_pallet_inbound_queue_v2::api::dry_run::(message) + } + } + impl snowbridge_system_runtime_api::ControlApi for Runtime { fn agent_id(location: VersionedLocation) -> Option { snowbridge_pallet_system::api::agent_id::(location) From 92e341963f9ae18a0acbd60e2b9f1e1068c894a7 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Thu, 28 Nov 2024 10:29:34 +0200 Subject: [PATCH 21/52] adds comment --- .../bridge-hub-westend/src/bridge_to_ethereum_config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 e1d3cc42364a..4d236b801384 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 @@ -74,7 +74,7 @@ parameter_types! { } /// The XCM execution fee on AH for the static part of the XCM message (not the user provided -/// parts). Calculated with integration test snowbridge_v2::xcm_prologue_fee +/// xcm). Teleported from BH. Calculated with integration test snowbridge_v2::xcm_prologue_fee. const XCM_PROLOGUE_FEE: u128 = 67_652_000_000; impl snowbridge_pallet_inbound_queue::Config for Runtime { From 37ecd53dfcd643aadf6e34803ec15bd36616b3cf Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Thu, 28 Nov 2024 11:23:33 +0200 Subject: [PATCH 22/52] update test --- .../src/tests/snowbridge_v2.rs | 52 +++++++++++-------- 1 file changed, 31 insertions(+), 21 deletions(-) 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 a103195c8fb8..73a3250566b3 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 @@ -121,33 +121,43 @@ fn xcm_prologue_fee() { #[test] fn register_token_xcm() { BridgeHubWestend::execute_with(|| { - let token: H160 = hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").into(); // token id placeholder - println!("token: {:x?}", token); - - let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); - let dot_asset = Location::new(1, Here); - let dot_fee: xcm::prelude::Asset = (dot_asset, CreateAssetDeposit::get()).into(); - - let asset_id = Location::new( - 2, - [ - GlobalConsensus(ethereum_network_v5), - AccountKey20 { network: None, key: token.into() }, - ], - ); - - println!( - "register token mainnet: {:x?}", - get_xcm_hex(1u64, asset_id.clone(), dot_fee.clone()) - ); - println!("register token sepolia: {:x?}", get_xcm_hex(11155111u64, asset_id, dot_fee)); + println!("register token mainnet: {:x?}", get_xcm_hex(1u64)); + println!("===============================",); + println!("register token sepolia: {:x?}", get_xcm_hex(11155111u64)); }); } -fn get_xcm_hex(chain_id: u64, asset_id: Location, dot_fee: xcm::prelude::Asset) -> String { +fn get_xcm_hex(chain_id: u64) -> String { let owner = EthereumLocationsConverterFor::<[u8; 32]>::from_chain_id(&chain_id); + let weth_token_id: H160 = hex!("be68fc2d8249eb60bfcf0e71d5a0d2f2e292c4ed").into(); // TODO insert token id + let token: H160 = hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").into(); // token id placeholder + let weth_amount = 300_000_000_000_000u128; + + let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); + let dot_asset = Location::new(1, Here); + let dot_fee: xcm::prelude::Asset = (dot_asset, CreateAssetDeposit::get()).into(); + + println!("register token id: {:x?}", token); + println!("weth token id: {:x?}", weth_token_id); + println!("weth_amount: {:x?}", hex::encode(weth_amount.encode())); + println!("dot asset: {:x?}", hex::encode(dot_fee.encode())); + + let weth_asset = Location::new( + 2, + [ + GlobalConsensus(ethereum_network_v5), + AccountKey20 { network: None, key: weth_token_id.into() }, + ], + ); + let weth_fee: xcm::prelude::Asset = (weth_asset, weth_amount).into(); // TODO replace Weth fee acmount + + let asset_id = Location::new( + 2, + [GlobalConsensus(ethereum_network_v5), AccountKey20 { network: None, key: token.into() }], + ); let register_token_xcm = vec![ + ExchangeAsset { give: weth_fee.into(), want: dot_fee.clone().into(), maximal: false }, PayFees { asset: dot_fee }, Transact { origin_kind: OriginKind::Xcm, From 69a660be5bb9e67e4371b90a3c0be54ef60f0ad1 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Thu, 28 Nov 2024 19:35:45 +0200 Subject: [PATCH 23/52] update tests --- .../pallets/inbound-queue-v2/src/mock.rs | 1 + .../primitives/router/src/inbound/mod.rs | 74 +++++++++++++ .../primitives/router/src/inbound/v1.rs | 2 +- .../primitives/router/src/inbound/v2.rs | 103 +++++------------- 4 files changed, 105 insertions(+), 75 deletions(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs index 4c895f85bc8c..b069c352ae8a 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs @@ -6,6 +6,7 @@ use crate::{self as inbound_queue_v2}; use frame_support::{ derive_impl, parameter_types, traits::{ConstU128, ConstU32}, + weights::IdentityFee, }; use hex_literal::hex; use snowbridge_beacon_primitives::{ diff --git a/bridges/snowbridge/primitives/router/src/inbound/mod.rs b/bridges/snowbridge/primitives/router/src/inbound/mod.rs index 69fa554df265..b494bc5b0e64 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/mod.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/mod.rs @@ -36,3 +36,77 @@ impl EthereumLocationsConverterFor { } pub type CallIndex = [u8; 2]; + +#[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/v1.rs b/bridges/snowbridge/primitives/router/src/inbound/v1.rs index 2d3cf4e55b86..42a04d0dc6c9 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v1.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v1.rs @@ -449,7 +449,7 @@ where #[cfg(test)] mod tests { - use crate::inbound::{CallIndex, GlobalConsensusEthereumConvertsFor}; + use crate::inbound::{CallIndex, EthereumLocationsConverterFor}; use frame_support::{assert_ok, parameter_types}; use hex_literal::hex; use xcm::prelude::*; diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index 63b997a1f454..aa4cd4d951b9 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -17,14 +17,6 @@ use xcm::{ const LOG_TARGET: &str = "snowbridge-router-primitives"; -/// 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 { - V2(Message), -} - /// The ethereum side sends messages which are transcoded into XCM on BH. These messages are /// self-contained, in that they can be transcoded using only information in the message. #[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] @@ -33,12 +25,15 @@ pub struct Message { pub origin: H160, /// The assets pub assets: Vec, - // The command originating from the Gateway contract + /// The command originating from the Gateway contract pub xcm: Vec, - // The claimer in the case that funds get trapped. + /// The claimer in the case that funds get trapped. pub claimer: Option>, } +/// An asset that will be transacted on AH. The asset will be reserved/withdrawn and placed into +/// the holding register. The user needs to provide additional xcm to deposit the asset +/// in a beneficiary account. #[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] pub enum Asset { NativeTokenERC20 { @@ -178,88 +173,48 @@ where #[cfg(test)] mod tests { - use crate::inbound::{ - v2::{ConvertMessage, Message, MessageToXcm}, - CallIndex, GlobalConsensusEthereumConvertsFor, - }; + use crate::inbound::v2::{ConvertMessage, Message, MessageToXcm}; use codec::Decode; use frame_support::{assert_ok, parameter_types}; use hex_literal::hex; - use sp_runtime::traits::ConstU8; + use sp_core::H256; + use sp_runtime::traits::{ConstU128, ConstU8}; use xcm::prelude::*; - use xcm_executor::traits::ConvertLocation; + + use snowbridge_core::TokenId; + use sp_runtime::traits::MaybeEquivalence; 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 = - GlobalConsensusEthereumConvertsFor::<[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!( - GlobalConsensusEthereumConvertsFor::<[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()); + pub struct MockTokenIdConvert; + impl MaybeEquivalence for MockTokenIdConvert { + fn convert(_id: &TokenId) -> Option { + Some(Location::parent()) + } + fn convert_back(_loc: &Location) -> Option { + None } } #[test] - fn test_convert_message() { + fn convert_message() { let payload = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf040197874824853fb4ad04794ccfd1cc8d2a7463839cfcbc6a315a1045c60ab85f400000b2d3595bf00600000000000000000000").to_vec(); + let origin_account = + Location::new(0, AccountId32 { id: H256::random().into(), network: None }); + let message = Message::decode(&mut payload.as_ref()); assert_ok!(message.clone()); - let result = MessageToXcm::>::convert(message.unwrap()); + + let result = MessageToXcm::< + EthereumNetwork, + ConstU8<80>, + MockTokenIdConvert, + ConstU128<1_000_000_000_000>, + >::convert(message.unwrap(), origin_account); assert_ok!(result); } } From 71929f67f5e0e3691c12c690699abf08591f2e3a Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Thu, 28 Nov 2024 19:49:31 +0200 Subject: [PATCH 24/52] prdoc --- prdoc/pr_6697.prdoc | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 prdoc/pr_6697.prdoc diff --git a/prdoc/pr_6697.prdoc b/prdoc/pr_6697.prdoc new file mode 100644 index 000000000000..cf2c5cbe845d --- /dev/null +++ b/prdoc/pr_6697.prdoc @@ -0,0 +1,22 @@ +title: 'Snowbridge Unordered Message Delivery - Inbound Queue' +doc: +- audience: Node Dev + description: |- + New pallets for unordered message delivery for Snowbridge, specifically the Inbound Queue part. No breaking changes + are made in this PR, only new functionality added. + +crates: +- name: snowbridge-pallet-inbound-queue-v2 + bump: minor +- name: snowbridge-inbound-queue-v2-runtime-api + bump: minor +- name: snowbridge-pallet-inbound-queue-fixtures-v2 + bump: minor +- name: snowbridge-core + bump: major +- name: snowbridge-router-primitives + bump: major +- name: bridge-hub-westend-integration-tests + bump: major +- name: bridge-hub-westend-runtime + bump: major From 57359607a89e776b09c5c03a0875bb017450b688 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Thu, 28 Nov 2024 20:22:26 +0200 Subject: [PATCH 25/52] adds register token test --- .../src/tests/snowbridge_v2.rs | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) 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 73a3250566b3..53cdbe7d88c0 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 @@ -31,6 +31,100 @@ use testnet_parachains_constants::westend::fee::WeightToFee as WeightCalculator; /// Calculates the XCM prologue fee for sending an XCM to AH. const INITIAL_FUND: u128 = 5_000_000_000_000; use testnet_parachains_constants::westend::snowbridge::EthereumNetwork; + +#[test] +fn register_token_v2() { + BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id().into(), INITIAL_FUND); + + let asset_hub_sovereign = BridgeHubWestend::sovereign_account_id_of(Location::new( + 1, + [Parachain(AssetHubWestend::para_id().into())], + )); + + let relayer = BridgeHubWestendSender::get(); + let receiver = AssetHubWestendReceiver::get(); + BridgeHubWestend::fund_accounts(vec![(relayer.clone(), INITIAL_FUND)]); + + let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); + + let chain_id = 11155111u64; + let claimer = AccountId32 { network: None, id: receiver.clone().into() }; + let claimer_bytes = claimer.encode(); + let origin = H160::random(); + let relayer_location = + Location::new(0, AccountId32 { network: None, id: relayer.clone().into() }); + + let owner = EthereumLocationsConverterFor::<[u8; 32]>::from_chain_id(&chain_id); + let weth_token_id: H160 = hex!("fff9976782d46cc05630d1f6ebab18b2324d6b14").into(); + let token: H160 = hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").into(); + let weth_amount = 300_000_000_000_000u128; + + let assets = vec![NativeTokenERC20 { token_id: weth_token_id, value: weth_amount }]; + + let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); + let dot_asset = Location::new(1, Here); + let dot_fee: xcm::prelude::Asset = (dot_asset, CreateAssetDeposit::get()).into(); + + let weth_asset = Location::new( + 2, + [ + GlobalConsensus(ethereum_network_v5), + AccountKey20 { network: None, key: weth_token_id.into() }, + ], + ); + let weth_fee: xcm::prelude::Asset = (weth_asset, weth_amount).into(); + + let asset_id = Location::new( + 2, + [GlobalConsensus(ethereum_network_v5), AccountKey20 { network: None, key: token.into() }], + ); + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + let register_token_instructions = vec![ + ExchangeAsset { give: weth_fee.into(), want: dot_fee.clone().into(), maximal: false }, + PayFees { asset: dot_fee }, + Transact { + origin_kind: OriginKind::Xcm, + call: ( + CreateAssetCall::get(), + asset_id, + MultiAddress::<[u8; 32], ()>::Id(owner.into()), + 1, + ) + .encode() + .into(), + }, + ]; + let xcm: Xcm<()> = register_token_instructions.into(); + let versioned_message_xcm = VersionedXcm::V5(xcm); + + let message = Message { + origin, + assets, + xcm: versioned_message_xcm.encode(), + claimer: Some(claimer_bytes), + }; + let xcm = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); + let _ = EthereumInboundQueueV2::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; + + assert_expected_events!( + AssetHubWestend, + vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Created { .. }) => {},] + ); + }); +} + #[test] fn xcm_prologue_fee() { BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id().into(), INITIAL_FUND); From a935ff28d99c1e0b2ad253d689dfaeecfd021c25 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Thu, 28 Nov 2024 21:50:50 +0200 Subject: [PATCH 26/52] enable exchange asset on AH --- .../assets/asset-hub-westend/src/weights/xcm/mod.rs | 2 +- .../src/weights/xcm/pallet_xcm_benchmarks_fungible.rs | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/mod.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/mod.rs index 35ff2dc367c0..dc8ed7d667b7 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/mod.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/mod.rs @@ -119,7 +119,7 @@ impl XcmWeightInfo for AssetHubWestendXcmWeight { assets.weigh_assets(XcmFungibleWeight::::deposit_reserve_asset()) } fn exchange_asset(_give: &AssetFilter, _receive: &Assets, _maximal: &bool) -> Weight { - Weight::MAX + XcmFungibleWeight::::exchange_asset() } fn initiate_reserve_withdraw( assets: &AssetFilter, diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs index 97e59c24dd89..52c941f69cd2 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs @@ -220,4 +220,14 @@ impl WeightInfo { .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(4)) } + + pub fn exchange_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `159` + // Estimated: `6196` + // Minimum execution time: 87_253_000 picoseconds. + Weight::from_parts(88_932_000, 6196) + .saturating_add(T::DbWeight::get().reads(9)) + .saturating_add(T::DbWeight::get().writes(4)) + } } From 373d63a7948cf59002236a8f9431ff9040c0ecf5 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Fri, 29 Nov 2024 10:14:47 +0200 Subject: [PATCH 27/52] fix transact --- .../bridge-hub-westend/src/tests/snowbridge_v2.rs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) 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 53cdbe7d88c0..9883db10ddd1 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 @@ -36,11 +36,6 @@ use testnet_parachains_constants::westend::snowbridge::EthereumNetwork; fn register_token_v2() { BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id().into(), INITIAL_FUND); - let asset_hub_sovereign = BridgeHubWestend::sovereign_account_id_of(Location::new( - 1, - [Parachain(AssetHubWestend::para_id().into())], - )); - let relayer = BridgeHubWestendSender::get(); let receiver = AssetHubWestendReceiver::get(); BridgeHubWestend::fund_accounts(vec![(relayer.clone(), INITIAL_FUND)]); @@ -57,13 +52,12 @@ fn register_token_v2() { let owner = EthereumLocationsConverterFor::<[u8; 32]>::from_chain_id(&chain_id); let weth_token_id: H160 = hex!("fff9976782d46cc05630d1f6ebab18b2324d6b14").into(); let token: H160 = hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").into(); - let weth_amount = 300_000_000_000_000u128; + let weth_amount = 9_000_000_000_000_000_000_000u128; let assets = vec![NativeTokenERC20 { token_id: weth_token_id, value: weth_amount }]; let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); let dot_asset = Location::new(1, Here); - let dot_fee: xcm::prelude::Asset = (dot_asset, CreateAssetDeposit::get()).into(); let weth_asset = Location::new( 2, @@ -81,17 +75,15 @@ fn register_token_v2() { BridgeHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; - let register_token_instructions = vec![ - ExchangeAsset { give: weth_fee.into(), want: dot_fee.clone().into(), maximal: false }, - PayFees { asset: dot_fee }, + PayFees { asset: weth_fee.into() }, Transact { origin_kind: OriginKind::Xcm, call: ( CreateAssetCall::get(), asset_id, MultiAddress::<[u8; 32], ()>::Id(owner.into()), - 1, + 1u128, ) .encode() .into(), From a61ceef724dd163dbbb993d84d1e5abfefe378a8 Mon Sep 17 00:00:00 2001 From: ron Date: Fri, 29 Nov 2024 20:15:14 +0800 Subject: [PATCH 28/52] Fix for register token --- .../primitives/router/src/inbound/v2.rs | 2 +- .../src/tests/snowbridge_v2.rs | 66 +++++++++++++++---- 2 files changed, 53 insertions(+), 15 deletions(-) diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index aa4cd4d951b9..59ab2ea9ee0a 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -150,7 +150,7 @@ where // Set the alias origin to the original sender on Ethereum. Important to be before the // arbitrary XCM that is appended to the message on the next line. - instructions.push(AliasOrigin(origin_location.into())); + // instructions.push(AliasOrigin(origin_location.into())); // Add the XCM sent in the message to the end of the xcm instruction instructions.extend(message_xcm.0); 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 9883db10ddd1..899e89f32888 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 @@ -18,7 +18,7 @@ use bridge_hub_westend_runtime::{ EthereumInboundQueueV2, }; use codec::Encode; -use frame_support::weights::WeightToFee; +use frame_support::{traits::fungibles::Mutate, weights::WeightToFee}; use hex_literal::hex; use snowbridge_router_primitives::inbound::{ v2::{Asset::NativeTokenERC20, Message}, @@ -31,6 +31,50 @@ use testnet_parachains_constants::westend::fee::WeightToFee as WeightCalculator; /// Calculates the XCM prologue fee for sending an XCM to AH. const INITIAL_FUND: u128 = 5_000_000_000_000; use testnet_parachains_constants::westend::snowbridge::EthereumNetwork; +const WETH: [u8; 20] = hex!("fff9976782d46cc05630d1f6ebab18b2324d6b14"); +const WETH_FEE: u128 = 1_000_000_000_000; + +pub fn weth_location() -> Location { + Location::new( + 2, + [ + GlobalConsensus(EthereumNetwork::get().into()), + AccountKey20 { network: None, key: WETH.into() }, + ], + ) +} + +pub fn register_weth() { + let assethub_location = BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()); + let assethub_sovereign = BridgeHubWestend::sovereign_account_id_of(assethub_location); + AssetHubWestend::execute_with(|| { + type RuntimeOrigin = ::RuntimeOrigin; + + assert_ok!(::ForeignAssets::force_create( + RuntimeOrigin::root(), + weth_location().try_into().unwrap(), + assethub_sovereign.clone().into(), + true, + 1000, //ED will be used as exchange rate by default when used to PayFees with + )); + + assert!(::ForeignAssets::asset_exists( + weth_location().try_into().unwrap(), + )); + + assert_ok!(::ForeignAssets::mint_into( + weth_location().try_into().unwrap(), + &AssetHubWestendReceiver::get(), + 1000000, + )); + + assert_ok!(::ForeignAssets::mint_into( + weth_location().try_into().unwrap(), + &AssetHubWestendSender::get(), + 1000000, + )); + }); +} #[test] fn register_token_v2() { @@ -40,7 +84,7 @@ fn register_token_v2() { let receiver = AssetHubWestendReceiver::get(); BridgeHubWestend::fund_accounts(vec![(relayer.clone(), INITIAL_FUND)]); - let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); + register_weth(); let chain_id = 11155111u64; let claimer = AccountId32 { network: None, id: receiver.clone().into() }; @@ -50,23 +94,16 @@ fn register_token_v2() { Location::new(0, AccountId32 { network: None, id: relayer.clone().into() }); let owner = EthereumLocationsConverterFor::<[u8; 32]>::from_chain_id(&chain_id); - let weth_token_id: H160 = hex!("fff9976782d46cc05630d1f6ebab18b2324d6b14").into(); + AssetHubWestend::fund_accounts(vec![(owner.into(), INITIAL_FUND)]); + let token: H160 = hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").into(); - let weth_amount = 9_000_000_000_000_000_000_000u128; + let weth_amount = 9_000_000_000_000u128; - let assets = vec![NativeTokenERC20 { token_id: weth_token_id, value: weth_amount }]; + let assets = vec![NativeTokenERC20 { token_id: WETH.into(), value: weth_amount }]; let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); - let dot_asset = Location::new(1, Here); - let weth_asset = Location::new( - 2, - [ - GlobalConsensus(ethereum_network_v5), - AccountKey20 { network: None, key: weth_token_id.into() }, - ], - ); - let weth_fee: xcm::prelude::Asset = (weth_asset, weth_amount).into(); + let weth_fee: xcm::prelude::Asset = (weth_location(), WETH_FEE).into(); let asset_id = Location::new( 2, @@ -88,6 +125,7 @@ fn register_token_v2() { .encode() .into(), }, + ExpectTransactStatus(MaybeErrorCode::Success), ]; let xcm: Xcm<()> = register_token_instructions.into(); let versioned_message_xcm = VersionedXcm::V5(xcm); From 7e64403ac977dd2acb8e7baac3e245c93c32355a Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Mon, 2 Dec 2024 13:46:15 +0200 Subject: [PATCH 29/52] add pool --- .../src/tests/snowbridge_v2.rs | 78 +++++++++++++++++-- 1 file changed, 73 insertions(+), 5 deletions(-) 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 899e89f32888..db86166743ad 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 @@ -44,6 +44,10 @@ pub fn weth_location() -> Location { ) } +pub fn dot_location() -> Location { + Location::new(1, Here) +} + pub fn register_weth() { let assethub_location = BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()); let assethub_sovereign = BridgeHubWestend::sovereign_account_id_of(assethub_location); @@ -76,16 +80,72 @@ pub fn register_weth() { }); } +pub(crate) fn set_up_weth_pool_with_wnd_on_ah_westend(asset: v5::Location) { + let wnd: v5::Location = v5::Parent.into(); + let assethub_location = BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()); + let owner = BridgeHubWestend::sovereign_account_id_of(assethub_location); + + AssetHubWestend::fund_accounts(vec![ + (owner.clone(), 3_000_000_000_000), + ]); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + let signed_owner = ::RuntimeOrigin::signed(owner.clone()); + + type RuntimeOrigin = ::RuntimeOrigin; + + assert_ok!(::ForeignAssets::mint( + signed_owner.clone(), + asset.clone().into(), + owner.clone().into(), + 3_000_000_000_000, + )); + + assert_ok!(::AssetConversion::create_pool( + signed_owner.clone(), + Box::new(wnd.clone()), + Box::new(asset.clone()), + )); + + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {}, + ] + ); + + assert_ok!(::AssetConversion::add_liquidity( + signed_owner.clone(), + Box::new(wnd), + Box::new(asset), + 1_000_000_000_000, + 2_000_000_000_000, + 1, + 1, + owner.into() + )); + /* + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {..}) => {}, + ] + );*/ + }); +} + #[test] fn register_token_v2() { - BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id().into(), INITIAL_FUND); - let relayer = BridgeHubWestendSender::get(); let receiver = AssetHubWestendReceiver::get(); BridgeHubWestend::fund_accounts(vec![(relayer.clone(), INITIAL_FUND)]); register_weth(); + set_up_weth_pool_with_wnd_on_ah_westend(weth_location()); + let chain_id = 11155111u64; let claimer = AccountId32 { network: None, id: receiver.clone().into() }; let claimer_bytes = claimer.encode(); @@ -93,8 +153,7 @@ fn register_token_v2() { let relayer_location = Location::new(0, AccountId32 { network: None, id: relayer.clone().into() }); - let owner = EthereumLocationsConverterFor::<[u8; 32]>::from_chain_id(&chain_id); - AssetHubWestend::fund_accounts(vec![(owner.into(), INITIAL_FUND)]); + let bridge_owner = EthereumLocationsConverterFor::<[u8; 32]>::from_chain_id(&chain_id); let token: H160 = hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").into(); let weth_amount = 9_000_000_000_000u128; @@ -110,16 +169,25 @@ fn register_token_v2() { [GlobalConsensus(ethereum_network_v5), AccountKey20 { network: None, key: token.into() }], ); + let dot_asset = Location::new(1, Here); + let dot_fee: xcm::prelude::Asset = (dot_asset, CreateAssetDeposit::get()).into(); + BridgeHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; let register_token_instructions = vec![ + // Exchange weth for dot to pay the asset creation deposit + ExchangeAsset { give: weth_fee.clone().into(), want: dot_fee.clone().into(), maximal: false }, + // Deposit the dot deposit into the bridge sovereign account (where the asset creation fee + // will be deducted from) + DepositAsset { assets: dot_fee.into(), beneficiary: bridge_owner.into() }, + // Pay for the transact execution PayFees { asset: weth_fee.into() }, Transact { origin_kind: OriginKind::Xcm, call: ( CreateAssetCall::get(), asset_id, - MultiAddress::<[u8; 32], ()>::Id(owner.into()), + MultiAddress::<[u8; 32], ()>::Id(bridge_owner.into()), 1u128, ) .encode() From d77a1ee566a6a322dda831da725efac0b4781087 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Mon, 2 Dec 2024 14:09:25 +0200 Subject: [PATCH 30/52] fix pool owner --- .../src/tests/snowbridge_v2.rs | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) 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 db86166743ad..0f2e0f118ad0 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 @@ -83,7 +83,8 @@ pub fn register_weth() { pub(crate) fn set_up_weth_pool_with_wnd_on_ah_westend(asset: v5::Location) { let wnd: v5::Location = v5::Parent.into(); let assethub_location = BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()); - let owner = BridgeHubWestend::sovereign_account_id_of(assethub_location); + let owner = AssetHubWestendSender::get(); + let bh_sovereign = BridgeHubWestend::sovereign_account_id_of(assethub_location); AssetHubWestend::fund_accounts(vec![ (owner.clone(), 3_000_000_000_000), @@ -93,11 +94,19 @@ pub(crate) fn set_up_weth_pool_with_wnd_on_ah_westend(asset: v5::Location) { type RuntimeEvent = ::RuntimeEvent; let signed_owner = ::RuntimeOrigin::signed(owner.clone()); + let signed_bh_sovereign = ::RuntimeOrigin::signed(bh_sovereign.clone()); type RuntimeOrigin = ::RuntimeOrigin; assert_ok!(::ForeignAssets::mint( - signed_owner.clone(), + signed_bh_sovereign.clone(), + asset.clone().into(), + bh_sovereign.clone().into(), + 3_500_000_000_000, + )); + + assert_ok!(::ForeignAssets::transfer( + signed_bh_sovereign.clone(), asset.clone().into(), owner.clone().into(), 3_000_000_000_000, @@ -126,13 +135,13 @@ pub(crate) fn set_up_weth_pool_with_wnd_on_ah_westend(asset: v5::Location) { 1, owner.into() )); - /* + assert_expected_events!( AssetHubWestend, vec![ RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {..}) => {}, ] - );*/ + ); }); } @@ -163,6 +172,7 @@ fn register_token_v2() { let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); let weth_fee: xcm::prelude::Asset = (weth_location(), WETH_FEE).into(); + let weth_transact_fee: xcm::prelude::Asset = (weth_location(), WETH_FEE / 2).into(); let asset_id = Location::new( 2, @@ -181,7 +191,7 @@ fn register_token_v2() { // will be deducted from) DepositAsset { assets: dot_fee.into(), beneficiary: bridge_owner.into() }, // Pay for the transact execution - PayFees { asset: weth_fee.into() }, + //PayFees { asset: weth_transact_fee.into() }, Transact { origin_kind: OriginKind::Xcm, call: ( From f40cfd4f6db05793cfc59ab51b127dace97dd10e Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Mon, 2 Dec 2024 21:43:56 +0200 Subject: [PATCH 31/52] use weth for execution fees on AH --- .../pallets/inbound-queue-v2/src/api.rs | 6 +- .../pallets/inbound-queue-v2/src/lib.rs | 12 --- .../pallets/inbound-queue-v2/src/mock.rs | 10 +- .../pallets/inbound-queue-v2/src/test.rs | 31 ++---- .../primitives/router/src/inbound/v2.rs | 27 +++--- .../src/tests/snowbridge_v2.rs | 96 +------------------ .../src/bridge_to_ethereum_config.rs | 9 +- 7 files changed, 34 insertions(+), 157 deletions(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs index 241d1372f7ea..f54f4a3a0de0 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs @@ -5,8 +5,8 @@ use crate::{weights::WeightInfo, Config, Error, Junction::AccountId32, Location}; use frame_support::weights::WeightToFee; use snowbridge_router_primitives::inbound::v2::{ConvertMessage, Message}; -use sp_core::{Get, H256}; -use sp_runtime::{DispatchError, Saturating}; +use sp_core::H256; +use sp_runtime::{DispatchError}; use xcm::latest::Xcm; pub fn dry_run(message: Message) -> Result<(Xcm<()>, T::Balance), DispatchError> @@ -21,9 +21,7 @@ where // Calculate fee. Consists of the cost of the "submit" extrinsic as well as the XCM execution // prologue fee (static XCM part of the message that is execution on AH). let weight_fee = T::WeightToFee::weight_to_fee(&T::WeightInfo::submit()); - let xcm_prologue_fee = T::XcmPrologueFee::get(); let fee: u128 = weight_fee - .saturating_add(xcm_prologue_fee) .try_into() .map_err(|_| Error::::InvalidFee)?; diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index 6da0ddcdb673..00273ff107e0 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -52,7 +52,6 @@ use frame_support::{ use frame_system::{ensure_signed, pallet_prelude::*}; use scale_info::TypeInfo; use snowbridge_core::{ - fees::burn_fees, inbound::{Message, VerificationError, Verifier}, sparse_bitmap::SparseBitmap, BasicOperatingMode, @@ -65,7 +64,6 @@ use sp_std::vec; use types::Nonce; pub use weights::WeightInfo; use xcm::prelude::{send_xcm, Junction::*, Location, SendError as XcmpSendError, SendXcm, *}; -use xcm_executor::traits::TransactAsset; #[cfg(feature = "runtime-benchmarks")] use snowbridge_beacon_primitives::BeaconHeader; @@ -108,14 +106,10 @@ pub mod pallet { type AssetHubParaId: Get; /// Convert a command from Ethereum to an XCM message. type MessageConverter: ConvertMessage; - /// The AH XCM execution fee for the static part of the XCM message. - type XcmPrologueFee: Get>; /// Used to burn fees from the origin account (the relayer), which will be teleported to AH. type Token: Mutate + Inspect; /// Used for the dry run API implementation. type Balance: Balance + From; - /// Used to burn fees. - type AssetTransactor: TransactAsset; #[cfg(feature = "runtime-benchmarks")] type Helper: BenchmarkHelper; } @@ -234,12 +228,6 @@ pub mod pallet { let xcm = Self::do_convert(message, origin_account_location.clone())?; - // Burn the required fees for the static XCM message part - burn_fees::>( - origin_account_location, - T::XcmPrologueFee::get(), - )?; - // Todo: Deposit fee(in Ether) to RewardLeger which should cover all of: // T::RewardLeger::deposit(who, envelope.fee.into())?; // a. The submit extrinsic cost on BH diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs index b069c352ae8a..69eaa473fff7 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs @@ -5,7 +5,7 @@ use super::*; use crate::{self as inbound_queue_v2}; use frame_support::{ derive_impl, parameter_types, - traits::{ConstU128, ConstU32}, + traits::ConstU32, weights::IdentityFee, }; use hex_literal::hex; @@ -104,6 +104,7 @@ impl Verifier for MockVerifier { } const GATEWAY_ADDRESS: [u8; 20] = hex!["eda338e4dc46038493b885327842fd3e301cab39"]; +const WETH_ADDRESS: [u8; 20] = hex!["fff9976782d46cc05630d1f6ebab18b2324d6b14"]; #[cfg(feature = "runtime-benchmarks")] impl BenchmarkHelper for Test { @@ -149,12 +150,11 @@ impl MaybeEquivalence for MockTokenIdConvert { parameter_types! { pub const EthereumNetwork: xcm::v5::NetworkId = xcm::v5::NetworkId::Ethereum { chain_id: 11155111 }; pub const GatewayAddress: H160 = H160(GATEWAY_ADDRESS); + pub const WethAddress: H160 = H160(WETH_ADDRESS); pub const InboundQueuePalletInstance: u8 = 80; pub AssetHubLocation: InteriorLocation = Parachain(1000).into(); } -const XCM_PROLOGUE_FEE: u128 = 1_000_000_000_000; - impl inbound_queue_v2::Config for Test { type RuntimeEvent = RuntimeEvent; type Verifier = MockVerifier; @@ -167,12 +167,10 @@ impl inbound_queue_v2::Config for Test { EthereumNetwork, InboundQueuePalletInstance, MockTokenIdConvert, - ConstU128, + WethAddress, >; type Token = Balances; type Balance = u128; - type XcmPrologueFee = ConstU128; - type AssetTransactor = SuccessfulTransactor; #[cfg(feature = "runtime-benchmarks")] type Helper = Test; } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs index 0b9e49ca43b3..cdaf95e4b267 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs @@ -46,27 +46,6 @@ fn test_submit_happy_path() { }); } -#[test] -fn test_submit_xcm_invalid_channel() { - new_tester().execute_with(|| { - let relayer: AccountId = Keyring::Bob.into(); - let origin = RuntimeOrigin::signed(relayer); - - // 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(|| { @@ -151,7 +130,7 @@ fn test_set_operating_mode_root_only() { fn test_send_native_erc20_token_payload() { new_tester().execute_with(|| { // To generate test data: forge test --match-test testSendEther -vvvv - let payload = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf04005615deb798bb3e4dfa0139dfa1b3d433cc23b72f0000b2d3595bf00600000000000000000000").to_vec(); + let payload = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf0030ef7dba020000000000000000000004005615deb798bb3e4dfa0139dfa1b3d433cc23b72f0000b2d3595bf00600000000000000000000").to_vec(); let message = MessageV2::decode(&mut payload.as_ref()); assert_ok!(message.clone()); @@ -179,12 +158,13 @@ fn test_send_native_erc20_token_payload() { #[test] fn test_send_foreign_erc20_token_payload() { new_tester().execute_with(|| { - let payload = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf040197874824853fb4ad04794ccfd1cc8d2a7463839cfcbc6a315a1045c60ab85f400000b2d3595bf00600000000000000000000").to_vec(); + let payload = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf0030ef7dba0200000000000000000000040197874824853fb4ad04794ccfd1cc8d2a7463839cfcbc6a315a1045c60ab85f400000b2d3595bf00600000000000000000000").to_vec(); let message = MessageV2::decode(&mut payload.as_ref()); assert_ok!(message.clone()); - let inbound_message = message.unwrap(); + let inbound_message = message.unwrap(); + let expected_fee = 3_000_000_000_000u128; let expected_origin: H160 = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf").into(); let expected_token_id: H256 = hex!("97874824853fb4ad04794ccfd1cc8d2a7463839cfcbc6a315a1045c60ab85f40").into(); let expected_value = 500000000000000000u128; @@ -192,6 +172,7 @@ fn test_send_foreign_erc20_token_payload() { let expected_claimer: Option> = None; assert_eq!(expected_origin, inbound_message.origin); + assert_eq!(expected_fee, inbound_message.fee); assert_eq!(1, inbound_message.assets.len()); if let Asset::ForeignTokenERC20 { token_id, value } = &inbound_message.assets[0] { assert_eq!(expected_token_id, *token_id); @@ -207,7 +188,7 @@ fn test_send_foreign_erc20_token_payload() { #[test] fn test_register_token_inbound_message_with_xcm_and_claimer() { new_tester().execute_with(|| { - let payload = hex!("5991a2df15a8f6a256d3ec51e99254cd3fb576a904005615deb798bb3e4dfa0139dfa1b3d433cc23b72f00000000000000000000000000000000300508020401000002286bee0a015029e3b139f4393adda86303fcdaa35f60bb7092bf").to_vec(); + let payload = hex!("5991a2df15a8f6a256d3ec51e99254cd3fb576a90030ef7dba020000000000000000000004005615deb798bb3e4dfa0139dfa1b3d433cc23b72f00000000000000000000000000000000300508020401000002286bee0a015029e3b139f4393adda86303fcdaa35f60bb7092bf").to_vec(); let message = MessageV2::decode(&mut payload.as_ref()); assert_ok!(message.clone()); diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index 59ab2ea9ee0a..c9c8edb28921 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -11,7 +11,7 @@ use sp_core::{Get, RuntimeDebug, H160, H256}; use sp_runtime::traits::MaybeEquivalence; use sp_std::prelude::*; use xcm::{ - prelude::{Junction::AccountKey20, *}, + prelude::{Asset as XcmAsset, Junction::AccountKey20, *}, MAX_XCM_DECODE_DEPTH, }; @@ -23,6 +23,8 @@ const LOG_TARGET: &str = "snowbridge-router-primitives"; pub struct Message { /// The origin address pub origin: H160, + /// Fee in weth to cover the xcm execution on AH. + pub fee: u128, /// The assets pub assets: Vec, /// The command originating from the Gateway contract @@ -67,24 +69,24 @@ pub trait ConvertMessage { fn convert(message: Message, origin_account: Location) -> Result, ConvertMessageError>; } -pub struct MessageToXcm +pub struct MessageToXcm where EthereumNetwork: Get, InboundQueuePalletInstance: Get, ConvertAssetId: MaybeEquivalence, - XcmPrologueFee: Get, + WethAddress: Get, { _phantom: - PhantomData<(EthereumNetwork, InboundQueuePalletInstance, ConvertAssetId, XcmPrologueFee)>, + PhantomData<(EthereumNetwork, InboundQueuePalletInstance, ConvertAssetId, WethAddress)>, } -impl ConvertMessage - for MessageToXcm +impl ConvertMessage + for MessageToXcm where EthereumNetwork: Get, InboundQueuePalletInstance: Get, ConvertAssetId: MaybeEquivalence, - XcmPrologueFee: Get, + WethAddress: Get, { fn convert( message: Message, @@ -112,13 +114,14 @@ where let network = EthereumNetwork::get(); - let fee_asset = Location::new(1, Here); - let fee: xcm::prelude::Asset = (fee_asset.clone(), XcmPrologueFee::get()).into(); + // use weth as asset + let fee_asset = Location::new(2, [GlobalConsensus(EthereumNetwork::get()), AccountKey20 { network: None, key: WethAddress::get().into() } ]); + let fee: XcmAsset = (fee_asset.clone(), message.fee).into(); let mut instructions = vec![ - ReceiveTeleportedAsset(fee.clone().into()), - PayFees { asset: fee }, DescendOrigin(PalletInstance(InboundQueuePalletInstance::get()).into()), UniversalOrigin(GlobalConsensus(network)), + ReserveAssetDeposited(fee.clone().into()), + PayFees { asset: fee }, ]; for asset in &message.assets { @@ -150,6 +153,7 @@ where // Set the alias origin to the original sender on Ethereum. Important to be before the // arbitrary XCM that is appended to the message on the next line. + // TODO allow address from Ethereum to create foreign assets on AH // instructions.push(AliasOrigin(origin_location.into())); // Add the XCM sent in the message to the end of the xcm instruction @@ -158,7 +162,6 @@ where let appendix = vec![ RefundSurplus, // Refund excess fees to the relayer - // TODO maybe refund all fees to the relayer instead of just DOT? DepositAsset { assets: Wild(AllOf { id: AssetId(fee_asset.into()), fun: WildFungible }), beneficiary: origin_account_location, 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 0f2e0f118ad0..18b48b3dbc7e 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 @@ -32,7 +32,6 @@ use testnet_parachains_constants::westend::fee::WeightToFee as WeightCalculator; const INITIAL_FUND: u128 = 5_000_000_000_000; use testnet_parachains_constants::westend::snowbridge::EthereumNetwork; const WETH: [u8; 20] = hex!("fff9976782d46cc05630d1f6ebab18b2324d6b14"); -const WETH_FEE: u128 = 1_000_000_000_000; pub fn weth_location() -> Location { Location::new( @@ -147,6 +146,7 @@ pub(crate) fn set_up_weth_pool_with_wnd_on_ah_westend(asset: v5::Location) { #[test] fn register_token_v2() { + // Whole register token fee is 374_851_000_000 let relayer = BridgeHubWestendSender::get(); let receiver = AssetHubWestendReceiver::get(); BridgeHubWestend::fund_accounts(vec![(relayer.clone(), INITIAL_FUND)]); @@ -171,8 +171,9 @@ fn register_token_v2() { let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); - let weth_fee: xcm::prelude::Asset = (weth_location(), WETH_FEE).into(); - let weth_transact_fee: xcm::prelude::Asset = (weth_location(), WETH_FEE / 2).into(); + let weth_fee_value: u128 = 1_000_000_000_000; + let weth_fee: xcm::prelude::Asset = (weth_location(), weth_fee_value).into(); + let weth_transact_fee: xcm::prelude::Asset = (weth_location(), weth_fee_value).into(); let asset_id = Location::new( 2, @@ -191,7 +192,6 @@ fn register_token_v2() { // will be deducted from) DepositAsset { assets: dot_fee.into(), beneficiary: bridge_owner.into() }, // Pay for the transact execution - //PayFees { asset: weth_transact_fee.into() }, Transact { origin_kind: OriginKind::Xcm, call: ( @@ -210,6 +210,7 @@ fn register_token_v2() { let message = Message { origin, + fee: 1_500_000_000_000u128, assets, xcm: versioned_message_xcm.encode(), claimer: Some(claimer_bytes), @@ -233,93 +234,6 @@ fn register_token_v2() { }); } -#[test] -fn xcm_prologue_fee() { - BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id().into(), INITIAL_FUND); - - let asset_hub_sovereign = BridgeHubWestend::sovereign_account_id_of(Location::new( - 1, - [Parachain(AssetHubWestend::para_id().into())], - )); - - let relayer = BridgeHubWestendSender::get(); - let receiver = AssetHubWestendReceiver::get(); - BridgeHubWestend::fund_accounts(vec![(relayer.clone(), INITIAL_FUND)]); - - let mut token_ids = Vec::new(); - for _ in 0..8 { - token_ids.push(H160::random()); - } - - let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); - - AssetHubWestend::execute_with(|| { - type RuntimeOrigin = ::RuntimeOrigin; - - for token_id in token_ids.iter() { - let token_id = *token_id; - - let asset_location = Location::new( - 2, - [ - GlobalConsensus(ethereum_network_v5), - AccountKey20 { network: None, key: token_id.into() }, - ], - ); - - assert_ok!(::ForeignAssets::force_create( - RuntimeOrigin::root(), - asset_location.clone(), - asset_hub_sovereign.clone().into(), - false, - 1, - )); - - assert!(::ForeignAssets::asset_exists( - asset_location.clone().try_into().unwrap(), - )); - } - }); - - let native_tokens: Vec = token_ids - .iter() - .map(|token_id| NativeTokenERC20 { token_id: *token_id, value: 3_000_000_000 }) - .collect(); - - BridgeHubWestend::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; - - let claimer = AccountId32 { network: None, id: receiver.clone().into() }; - let claimer_bytes = claimer.encode(); - let origin = H160::random(); - let relayer_location = - Location::new(0, AccountId32 { network: None, id: relayer.clone().into() }); - - let message_xcm_instructions = - vec![DepositAsset { assets: Wild(AllCounted(8)), beneficiary: receiver.into() }]; - let message_xcm: Xcm<()> = message_xcm_instructions.into(); - let versioned_message_xcm = VersionedXcm::V5(message_xcm); - - let message = Message { - origin, - assets: native_tokens, - xcm: versioned_message_xcm.encode(), - claimer: Some(claimer_bytes), - }; - let xcm = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); - let _ = EthereumInboundQueueV2::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); - - assert_expected_events!( - BridgeHubWestend, - vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},] - ); - }); - - let execution_fee = WeightCalculator::weight_to_fee(&Weight::from_parts(1410450000, 33826)); - let buffered_fee = execution_fee * 2; - println!("buffered execution fee for prologue for 8 assets: {}", buffered_fee); -} - #[test] fn register_token_xcm() { BridgeHubWestend::execute_with(|| { 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 4d236b801384..e998909fd2da 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 @@ -71,12 +71,9 @@ parameter_types! { }; pub AssetHubFromEthereum: Location = Location::new(1,[GlobalConsensus(RelayNetwork::get()),Parachain(westend_runtime_constants::system_parachain::ASSET_HUB_ID)]); pub EthereumUniversalLocation: InteriorLocation = [GlobalConsensus(EthereumNetwork::get())].into(); + pub WethAddress: H160 = H160(hex_literal::hex!("fff9976782d46cc05630d1f6ebab18b2324d6b14")); } -/// The XCM execution fee on AH for the static part of the XCM message (not the user provided -/// xcm). Teleported from BH. Calculated with integration test snowbridge_v2::xcm_prologue_fee. -const XCM_PROLOGUE_FEE: u128 = 67_652_000_000; - impl snowbridge_pallet_inbound_queue::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Verifier = snowbridge_pallet_ethereum_client::Pallet; @@ -122,13 +119,11 @@ impl snowbridge_pallet_inbound_queue_v2::Config for Runtime { type AssetHubParaId = ConstU32<1000>; type Token = Balances; type Balance = Balance; - type XcmPrologueFee = ConstU128; - type AssetTransactor = ::AssetTransactor; type MessageConverter = snowbridge_router_primitives::inbound::v2::MessageToXcm< EthereumNetwork, ConstU8, EthereumSystem, - ConstU128, + WethAddress, >; } From 9b8117b0b76dffdba3641286d7b8dee7e205ab86 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Mon, 2 Dec 2024 21:54:33 +0200 Subject: [PATCH 32/52] cleanup test --- .../src/tests/snowbridge_v2.rs | 23 ++++++------------- .../src/bridge_to_ethereum_config.rs | 2 +- 2 files changed, 8 insertions(+), 17 deletions(-) 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 18b48b3dbc7e..aef3afb2350c 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 @@ -18,7 +18,7 @@ use bridge_hub_westend_runtime::{ EthereumInboundQueueV2, }; use codec::Encode; -use frame_support::{traits::fungibles::Mutate, weights::WeightToFee}; +use frame_support::{traits::fungibles::Mutate}; use hex_literal::hex; use snowbridge_router_primitives::inbound::{ v2::{Asset::NativeTokenERC20, Message}, @@ -26,7 +26,6 @@ use snowbridge_router_primitives::inbound::{ }; use sp_core::H160; use sp_runtime::MultiAddress; -use testnet_parachains_constants::westend::fee::WeightToFee as WeightCalculator; /// Calculates the XCM prologue fee for sending an XCM to AH. const INITIAL_FUND: u128 = 5_000_000_000_000; @@ -43,10 +42,6 @@ pub fn weth_location() -> Location { ) } -pub fn dot_location() -> Location { - Location::new(1, Here) -} - pub fn register_weth() { let assethub_location = BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()); let assethub_sovereign = BridgeHubWestend::sovereign_account_id_of(assethub_location); @@ -95,8 +90,6 @@ pub(crate) fn set_up_weth_pool_with_wnd_on_ah_westend(asset: v5::Location) { let signed_owner = ::RuntimeOrigin::signed(owner.clone()); let signed_bh_sovereign = ::RuntimeOrigin::signed(bh_sovereign.clone()); - type RuntimeOrigin = ::RuntimeOrigin; - assert_ok!(::ForeignAssets::mint( signed_bh_sovereign.clone(), asset.clone().into(), @@ -165,15 +158,13 @@ fn register_token_v2() { let bridge_owner = EthereumLocationsConverterFor::<[u8; 32]>::from_chain_id(&chain_id); let token: H160 = hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").into(); - let weth_amount = 9_000_000_000_000u128; - - let assets = vec![NativeTokenERC20 { token_id: WETH.into(), value: weth_amount }]; let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); - let weth_fee_value: u128 = 1_000_000_000_000; - let weth_fee: xcm::prelude::Asset = (weth_location(), weth_fee_value).into(); - let weth_transact_fee: xcm::prelude::Asset = (weth_location(), weth_fee_value).into(); + // Used to pay the asset creation deposit. + let weth_asset_value = 9_000_000_000_000u128; + let assets = vec![NativeTokenERC20 { token_id: WETH.into(), value: weth_asset_value }]; + let asset_deposit_weth: xcm::prelude::Asset = (weth_location(), weth_asset_value).into(); let asset_id = Location::new( 2, @@ -187,11 +178,11 @@ fn register_token_v2() { type RuntimeEvent = ::RuntimeEvent; let register_token_instructions = vec![ // Exchange weth for dot to pay the asset creation deposit - ExchangeAsset { give: weth_fee.clone().into(), want: dot_fee.clone().into(), maximal: false }, + ExchangeAsset { give: asset_deposit_weth.clone().into(), want: dot_fee.clone().into(), maximal: false }, // Deposit the dot deposit into the bridge sovereign account (where the asset creation fee // will be deducted from) DepositAsset { assets: dot_fee.into(), beneficiary: bridge_owner.into() }, - // Pay for the transact execution + // Call to create the asset. Transact { origin_kind: OriginKind::Xcm, call: ( 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 e998909fd2da..0bd1a736eb3d 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 @@ -39,7 +39,7 @@ use benchmark_helpers::DoNothingRouter; use frame_support::{parameter_types, weights::ConstantMultiplier}; use pallet_xcm::EnsureXcm; use sp_runtime::{ - traits::{ConstU128, ConstU32, ConstU8, Keccak256}, + traits::{ConstU32, ConstU8, Keccak256}, FixedU128, }; use xcm::prelude::{GlobalConsensus, InteriorLocation, Location, Parachain}; From c2804dc4d642d0d4c1113a8946a0e7a6be722d6a Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Tue, 3 Dec 2024 12:35:27 +0200 Subject: [PATCH 33/52] fix aliasorigin --- .../pallets/inbound-queue-v2/src/api.rs | 6 +- .../pallets/inbound-queue-v2/src/mock.rs | 14 +--- .../primitives/router/src/inbound/v2.rs | 68 ++++++++++++++----- .../src/tests/snowbridge_v2.rs | 26 ++++--- .../src/bridge_to_ethereum_config.rs | 1 + 5 files changed, 72 insertions(+), 43 deletions(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs index f54f4a3a0de0..beb96b1cb50d 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs @@ -6,7 +6,7 @@ use crate::{weights::WeightInfo, Config, Error, Junction::AccountId32, Location} use frame_support::weights::WeightToFee; use snowbridge_router_primitives::inbound::v2::{ConvertMessage, Message}; use sp_core::H256; -use sp_runtime::{DispatchError}; +use sp_runtime::DispatchError; use xcm::latest::Xcm; pub fn dry_run(message: Message) -> Result<(Xcm<()>, T::Balance), DispatchError> @@ -21,9 +21,7 @@ where // Calculate fee. Consists of the cost of the "submit" extrinsic as well as the XCM execution // prologue fee (static XCM part of the message that is execution on AH). let weight_fee = T::WeightToFee::weight_to_fee(&T::WeightInfo::submit()); - let fee: u128 = weight_fee - .try_into() - .map_err(|_| Error::::InvalidFee)?; + let fee: u128 = weight_fee.try_into().map_err(|_| Error::::InvalidFee)?; Ok((xcm, fee.into())) } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs index 69eaa473fff7..28db523bd7a7 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs @@ -3,11 +3,7 @@ use super::*; use crate::{self as inbound_queue_v2}; -use frame_support::{ - derive_impl, parameter_types, - traits::ConstU32, - weights::IdentityFee, -}; +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, @@ -163,12 +159,8 @@ impl inbound_queue_v2::Config for Test { type WeightToFee = IdentityFee; type GatewayAddress = GatewayAddress; type AssetHubParaId = ConstU32<1000>; - type MessageConverter = MessageToXcm< - EthereumNetwork, - InboundQueuePalletInstance, - MockTokenIdConvert, - WethAddress, - >; + type MessageConverter = + MessageToXcm; type Token = Balances; type Balance = u128; #[cfg(feature = "runtime-benchmarks")] diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index c9c8edb28921..b50184413ac5 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -63,30 +63,56 @@ pub enum ConvertMessageError { InvalidClaimer, /// Invalid foreign ERC20 token ID InvalidAsset, + /// The origin could not be added to the interior location of the origin location. + InvalidOrigin, } pub trait ConvertMessage { fn convert(message: Message, origin_account: Location) -> Result, ConvertMessageError>; } -pub struct MessageToXcm -where +pub struct MessageToXcm< + EthereumNetwork, + InboundQueuePalletInstance, + ConvertAssetId, + WethAddress, + GatewayProxyAddress, +> where EthereumNetwork: Get, InboundQueuePalletInstance: Get, ConvertAssetId: MaybeEquivalence, WethAddress: Get, + GatewayProxyAddress: Get, { - _phantom: - PhantomData<(EthereumNetwork, InboundQueuePalletInstance, ConvertAssetId, WethAddress)>, + _phantom: PhantomData<( + EthereumNetwork, + InboundQueuePalletInstance, + ConvertAssetId, + WethAddress, + GatewayProxyAddress, + )>, } -impl ConvertMessage - for MessageToXcm +impl< + EthereumNetwork, + InboundQueuePalletInstance, + ConvertAssetId, + WethAddress, + GatewayProxyAddress, + > ConvertMessage + for MessageToXcm< + EthereumNetwork, + InboundQueuePalletInstance, + ConvertAssetId, + WethAddress, + GatewayProxyAddress, + > where EthereumNetwork: Get, InboundQueuePalletInstance: Get, ConvertAssetId: MaybeEquivalence, WethAddress: Get, + GatewayProxyAddress: Get, { fn convert( message: Message, @@ -107,15 +133,14 @@ where let network = EthereumNetwork::get(); - let origin_location = Location::new( + // use weth as asset + let fee_asset = Location::new( 2, - [GlobalConsensus(network), AccountKey20 { key: message.origin.into(), network: None }], + [ + GlobalConsensus(EthereumNetwork::get()), + AccountKey20 { network: None, key: WethAddress::get().into() }, + ], ); - - let network = EthereumNetwork::get(); - - // use weth as asset - let fee_asset = Location::new(2, [GlobalConsensus(EthereumNetwork::get()), AccountKey20 { network: None, key: WethAddress::get().into() } ]); let fee: XcmAsset = (fee_asset.clone(), message.fee).into(); let mut instructions = vec![ DescendOrigin(PalletInstance(InboundQueuePalletInstance::get()).into()), @@ -151,10 +176,19 @@ where instructions.push(SetAssetClaimer { location: claimer_location }); } - // Set the alias origin to the original sender on Ethereum. Important to be before the - // arbitrary XCM that is appended to the message on the next line. - // TODO allow address from Ethereum to create foreign assets on AH - // instructions.push(AliasOrigin(origin_location.into())); + let mut origin_location = Location::new(2, [GlobalConsensus(network)]); + if message.origin == GatewayProxyAddress::get() { + // If the message origin is the gateway proxy contract, set the origin to + // Ethereum, for consistency with v1. + instructions.push(AliasOrigin(origin_location)); + } else { + // Set the alias origin to the original sender on Ethereum. Important to be before the + // arbitrary XCM that is appended to the message on the next line. + origin_location + .push_interior(AccountKey20 { key: message.origin.into(), network: None }) + .map_err(|_| ConvertMessageError::InvalidOrigin)?; + instructions.push(AliasOrigin(origin_location.into())); + } // Add the XCM sent in the message to the end of the xcm instruction instructions.extend(message_xcm.0); 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 aef3afb2350c..bd714fac1d0a 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,11 +14,11 @@ // limitations under the License. use crate::imports::*; use bridge_hub_westend_runtime::{ - bridge_to_ethereum_config::{CreateAssetCall, CreateAssetDeposit}, + bridge_to_ethereum_config::{CreateAssetCall, CreateAssetDeposit, EthereumGatewayAddress}, EthereumInboundQueueV2, }; use codec::Encode; -use frame_support::{traits::fungibles::Mutate}; +use frame_support::traits::fungibles::Mutate; use hex_literal::hex; use snowbridge_router_primitives::inbound::{ v2::{Asset::NativeTokenERC20, Message}, @@ -80,15 +80,14 @@ pub(crate) fn set_up_weth_pool_with_wnd_on_ah_westend(asset: v5::Location) { let owner = AssetHubWestendSender::get(); let bh_sovereign = BridgeHubWestend::sovereign_account_id_of(assethub_location); - AssetHubWestend::fund_accounts(vec![ - (owner.clone(), 3_000_000_000_000), - ]); + AssetHubWestend::fund_accounts(vec![(owner.clone(), 3_000_000_000_000)]); AssetHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; let signed_owner = ::RuntimeOrigin::signed(owner.clone()); - let signed_bh_sovereign = ::RuntimeOrigin::signed(bh_sovereign.clone()); + let signed_bh_sovereign = + ::RuntimeOrigin::signed(bh_sovereign.clone()); assert_ok!(::ForeignAssets::mint( signed_bh_sovereign.clone(), @@ -151,13 +150,13 @@ fn register_token_v2() { let chain_id = 11155111u64; let claimer = AccountId32 { network: None, id: receiver.clone().into() }; let claimer_bytes = claimer.encode(); - let origin = H160::random(); + let relayer_location = Location::new(0, AccountId32 { network: None, id: relayer.clone().into() }); let bridge_owner = EthereumLocationsConverterFor::<[u8; 32]>::from_chain_id(&chain_id); - let token: H160 = hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").into(); + let token: H160 = hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").into(); // a is the token let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); @@ -178,9 +177,13 @@ fn register_token_v2() { type RuntimeEvent = ::RuntimeEvent; let register_token_instructions = vec![ // Exchange weth for dot to pay the asset creation deposit - ExchangeAsset { give: asset_deposit_weth.clone().into(), want: dot_fee.clone().into(), maximal: false }, - // Deposit the dot deposit into the bridge sovereign account (where the asset creation fee - // will be deducted from) + ExchangeAsset { + give: asset_deposit_weth.clone().into(), + want: dot_fee.clone().into(), + maximal: false, + }, + // Deposit the dot deposit into the bridge sovereign account (where the asset creation + // fee will be deducted from) DepositAsset { assets: dot_fee.into(), beneficiary: bridge_owner.into() }, // Call to create the asset. Transact { @@ -198,6 +201,7 @@ fn register_token_v2() { ]; let xcm: Xcm<()> = register_token_instructions.into(); let versioned_message_xcm = VersionedXcm::V5(xcm); + let origin = EthereumGatewayAddress::get(); let message = Message { origin, 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 0bd1a736eb3d..469a0ebdb2fb 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 @@ -124,6 +124,7 @@ impl snowbridge_pallet_inbound_queue_v2::Config for Runtime { ConstU8, EthereumSystem, WethAddress, + EthereumGatewayAddress, >; } From 02750d68fcf10a98f774c847596625e751b35b03 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Wed, 4 Dec 2024 10:31:14 +0200 Subject: [PATCH 34/52] remove unnecessary alias origin --- .../primitives/router/src/inbound/v2.rs | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index b50184413ac5..f730d0adedeb 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -63,8 +63,6 @@ pub enum ConvertMessageError { InvalidClaimer, /// Invalid foreign ERC20 token ID InvalidAsset, - /// The origin could not be added to the interior location of the origin location. - InvalidOrigin, } pub trait ConvertMessage { @@ -176,17 +174,17 @@ where instructions.push(SetAssetClaimer { location: claimer_location }); } - let mut origin_location = Location::new(2, [GlobalConsensus(network)]); - if message.origin == GatewayProxyAddress::get() { - // If the message origin is the gateway proxy contract, set the origin to - // Ethereum, for consistency with v1. - instructions.push(AliasOrigin(origin_location)); - } else { - // Set the alias origin to the original sender on Ethereum. Important to be before the - // arbitrary XCM that is appended to the message on the next line. - origin_location - .push_interior(AccountKey20 { key: message.origin.into(), network: None }) - .map_err(|_| ConvertMessageError::InvalidOrigin)?; + // If the message origin is not the gateway proxy contract, set the origin to + // the original sender on Ethereum. Important to be before the arbitrary XCM that is + // appended to the message on the next line. + if message.origin != GatewayProxyAddress::get() { + let origin_location = Location::new( + 2, + [ + GlobalConsensus(network), + AccountKey20 { key: message.origin.into(), network: None }, + ], + ); instructions.push(AliasOrigin(origin_location.into())); } From 40e85eae7b373f34060548ab7ba9a7c68fdd85fa Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Thu, 5 Dec 2024 09:11:55 +0200 Subject: [PATCH 35/52] separate inbound pallet index IDs for v1 and v2 --- .../pallets/inbound-queue-v2/src/mock.rs | 2 +- .../assets/asset-hub-westend/src/xcm_config.rs | 16 ++++++++++++---- .../src/bridge_to_ethereum_config.rs | 6 +++--- .../parachains/runtimes/constants/src/westend.rs | 3 ++- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs index 28db523bd7a7..c981c99bf3aa 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs @@ -147,7 +147,7 @@ parameter_types! { pub const EthereumNetwork: xcm::v5::NetworkId = xcm::v5::NetworkId::Ethereum { chain_id: 11155111 }; pub const GatewayAddress: H160 = H160(GATEWAY_ADDRESS); pub const WethAddress: H160 = H160(WETH_ADDRESS); - pub const InboundQueuePalletInstance: u8 = 80; + pub const InboundQueuePalletInstance: u8 = 84; pub AssetHubLocation: InteriorLocation = Parachain(1000).into(); } 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 b4e938f1f8b5..8045a4c9255f 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 @@ -649,7 +649,7 @@ pub mod bridging { use assets_common::matching::FromNetwork; use sp_std::collections::btree_set::BTreeSet; use testnet_parachains_constants::westend::snowbridge::{ - EthereumNetwork, INBOUND_QUEUE_PALLET_INDEX, + EthereumNetwork, INBOUND_QUEUE_PALLET_INDEX_V1, INBOUND_QUEUE_PALLET_INDEX_V2 }; parameter_types! { @@ -659,11 +659,18 @@ pub mod bridging { /// Polkadot uses 10 decimals, Kusama,Rococo,Westend 12 decimals. pub const DefaultBridgeHubEthereumBaseFee: Balance = 2_750_872_500_000; pub storage BridgeHubEthereumBaseFee: Balance = DefaultBridgeHubEthereumBaseFee::get(); - pub SiblingBridgeHubWithEthereumInboundQueueInstance: Location = Location::new( + pub SiblingBridgeHubWithEthereumInboundQueueV1Instance: Location = Location::new( 1, [ Parachain(SiblingBridgeHubParaId::get()), - PalletInstance(INBOUND_QUEUE_PALLET_INDEX) + PalletInstance(INBOUND_QUEUE_PALLET_INDEX_V1) + ] + ); + pub SiblingBridgeHubWithEthereumInboundQueueV2Instance: Location = Location::new( + 1, + [ + Parachain(SiblingBridgeHubParaId::get()), + PalletInstance(INBOUND_QUEUE_PALLET_INDEX_V2) ] ); @@ -684,7 +691,8 @@ pub mod bridging { /// Universal aliases pub UniversalAliases: BTreeSet<(Location, Junction)> = BTreeSet::from_iter( sp_std::vec![ - (SiblingBridgeHubWithEthereumInboundQueueInstance::get(), GlobalConsensus(EthereumNetwork::get().into())), + (SiblingBridgeHubWithEthereumInboundQueueV1Instance::get(), GlobalConsensus(EthereumNetwork::get().into())), + (SiblingBridgeHubWithEthereumInboundQueueV2Instance::get(), GlobalConsensus(EthereumNetwork::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 469a0ebdb2fb..b46291f09e9e 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 @@ -30,7 +30,7 @@ use sp_core::H160; use testnet_parachains_constants::westend::{ currency::*, fee::WeightToFee, - snowbridge::{EthereumLocation, EthereumNetwork, INBOUND_QUEUE_PALLET_INDEX}, + snowbridge::{EthereumLocation, EthereumNetwork, INBOUND_QUEUE_PALLET_INDEX_V1, INBOUND_QUEUE_PALLET_INDEX_V2}, }; use crate::xcm_config::RelayNetwork; @@ -89,7 +89,7 @@ impl snowbridge_pallet_inbound_queue::Config for Runtime { type MessageConverter = snowbridge_router_primitives::inbound::v1::MessageToXcm< CreateAssetCall, CreateAssetDeposit, - ConstU8, + ConstU8, AccountId, Balance, EthereumSystem, @@ -121,7 +121,7 @@ impl snowbridge_pallet_inbound_queue_v2::Config for Runtime { type Balance = Balance; type MessageConverter = snowbridge_router_primitives::inbound::v2::MessageToXcm< EthereumNetwork, - ConstU8, + ConstU8, EthereumSystem, WethAddress, EthereumGatewayAddress, diff --git a/cumulus/parachains/runtimes/constants/src/westend.rs b/cumulus/parachains/runtimes/constants/src/westend.rs index 8c4c0c594359..b880e229bc1f 100644 --- a/cumulus/parachains/runtimes/constants/src/westend.rs +++ b/cumulus/parachains/runtimes/constants/src/westend.rs @@ -174,7 +174,8 @@ pub mod snowbridge { use xcm::prelude::{Location, NetworkId}; /// The pallet index of the Ethereum inbound queue pallet in the bridge hub runtime. - pub const INBOUND_QUEUE_PALLET_INDEX: u8 = 80; + pub const INBOUND_QUEUE_PALLET_INDEX_V1: u8 = 80; + pub const INBOUND_QUEUE_PALLET_INDEX_V2: u8 = 84; parameter_types! { /// Network and location for the Ethereum chain. On Westend, the Ethereum chain bridged From d70ac6c3f2a87eb739214278dfd87accac15aa55 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Thu, 5 Dec 2024 09:19:49 +0200 Subject: [PATCH 36/52] fix reentrancy bug --- bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index 00273ff107e0..ffe21dfe4fd5 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -238,12 +238,12 @@ pub mod pallet { // Attempt to forward XCM to AH - let message_id = Self::send_xcm(xcm, T::AssetHubParaId::get())?; - Self::deposit_event(Event::MessageReceived { nonce: envelope.nonce, message_id }); - // Set nonce flag to true Nonce::::set(envelope.nonce.into()); + let message_id = Self::send_xcm(xcm, T::AssetHubParaId::get())?; + Self::deposit_event(Event::MessageReceived { nonce: envelope.nonce, message_id }); + Ok(()) } From bc45bbd9e86591919c0fc65e27c19e2d640af65b Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Thu, 5 Dec 2024 09:36:21 +0200 Subject: [PATCH 37/52] send all assets in a single instruction --- .../primitives/router/src/inbound/v2.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index f730d0adedeb..8177c66052a5 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -146,10 +146,13 @@ where ReserveAssetDeposited(fee.clone().into()), PayFees { asset: fee }, ]; + let mut reserve_assets = vec![]; + let mut withdraw_assets = vec![]; for asset in &message.assets { match asset { Asset::NativeTokenERC20 { token_id, value } => { + let token_location: Location = Location::new( 2, [ @@ -157,16 +160,25 @@ where AccountKey20 { network: None, key: (*token_id).into() }, ], ); - instructions.push(ReserveAssetDeposited((token_location, *value).into())); + let asset: XcmAsset = (token_location, *value).into(); + reserve_assets.push(asset); }, Asset::ForeignTokenERC20 { token_id, value } => { let asset_id = ConvertAssetId::convert(&token_id) .ok_or(ConvertMessageError::InvalidAsset)?; - instructions.push(WithdrawAsset((asset_id, *value).into())); + let asset: XcmAsset = (asset_id, *value).into(); + withdraw_assets.push(asset); }, } } + if reserve_assets.len() > 0 { + instructions.push(ReserveAssetDeposited(reserve_assets.into())); + } + if withdraw_assets.len() > 0 { + instructions.push(WithdrawAsset(withdraw_assets.into())); + } + if let Some(claimer) = message.claimer { let claimer = Junction::decode(&mut claimer.as_ref()) .map_err(|_| ConvertMessageError::InvalidClaimer)?; From 9ffb3e1613c1b3067b3b02812604cb5cf89bd24b Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Thu, 5 Dec 2024 09:43:37 +0200 Subject: [PATCH 38/52] use descend origin instead of alias origin --- bridges/snowbridge/primitives/router/src/inbound/v2.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index 8177c66052a5..2872c13c9b9d 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -190,14 +190,7 @@ where // the original sender on Ethereum. Important to be before the arbitrary XCM that is // appended to the message on the next line. if message.origin != GatewayProxyAddress::get() { - let origin_location = Location::new( - 2, - [ - GlobalConsensus(network), - AccountKey20 { key: message.origin.into(), network: None }, - ], - ); - instructions.push(AliasOrigin(origin_location.into())); + instructions.push(DescendOrigin(AccountKey20 { key: message.origin.into(), network: None}.into())); } // Add the XCM sent in the message to the end of the xcm instruction From 85d355e1d8d5d2e4d0f8d4c5a2d580c92611593a Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Thu, 5 Dec 2024 09:56:37 +0200 Subject: [PATCH 39/52] use claimer for refund surplus, otherwise relayer --- .../snowbridge/primitives/router/src/inbound/v2.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index 2872c13c9b9d..d03a6c1c4eba 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -152,7 +152,6 @@ where for asset in &message.assets { match asset { Asset::NativeTokenERC20 { token_id, value } => { - let token_location: Location = Location::new( 2, [ @@ -179,10 +178,13 @@ where instructions.push(WithdrawAsset(withdraw_assets.into())); } + let mut refund_surplus_to = origin_account_location; + if let Some(claimer) = message.claimer { let claimer = Junction::decode(&mut claimer.as_ref()) .map_err(|_| ConvertMessageError::InvalidClaimer)?; let claimer_location: Location = Location::new(0, [claimer.into()]); + refund_surplus_to = claimer_location.clone(); instructions.push(SetAssetClaimer { location: claimer_location }); } @@ -190,7 +192,9 @@ where // the original sender on Ethereum. Important to be before the arbitrary XCM that is // appended to the message on the next line. if message.origin != GatewayProxyAddress::get() { - instructions.push(DescendOrigin(AccountKey20 { key: message.origin.into(), network: None}.into())); + instructions.push(DescendOrigin( + AccountKey20 { key: message.origin.into(), network: None }.into(), + )); } // Add the XCM sent in the message to the end of the xcm instruction @@ -198,10 +202,10 @@ where let appendix = vec![ RefundSurplus, - // Refund excess fees to the relayer + // Refund excess fees to the claimer, if present, otherwise the relayer DepositAsset { assets: Wild(AllOf { id: AssetId(fee_asset.into()), fun: WildFungible }), - beneficiary: origin_account_location, + beneficiary: refund_surplus_to, }, ]; From f1263e4a2e400f5acafc708ab7c71fb92bf63cd3 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Thu, 5 Dec 2024 21:14:16 +0200 Subject: [PATCH 40/52] more tests --- .../src/tests/snowbridge_v2.rs | 506 ++++++++++++------ 1 file changed, 356 insertions(+), 150 deletions(-) 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 bd714fac1d0a..fc2f5533cf97 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 @@ -18,7 +18,6 @@ use bridge_hub_westend_runtime::{ EthereumInboundQueueV2, }; use codec::Encode; -use frame_support::traits::fungibles::Mutate; use hex_literal::hex; use snowbridge_router_primitives::inbound::{ v2::{Asset::NativeTokenERC20, Message}, @@ -26,171 +25,333 @@ use snowbridge_router_primitives::inbound::{ }; use sp_core::H160; use sp_runtime::MultiAddress; +use sp_core::H256; +use asset_hub_westend_runtime::ForeignAssets; /// Calculates the XCM prologue fee for sending an XCM to AH. const INITIAL_FUND: u128 = 5_000_000_000_000; use testnet_parachains_constants::westend::snowbridge::EthereumNetwork; const WETH: [u8; 20] = hex!("fff9976782d46cc05630d1f6ebab18b2324d6b14"); +/// An ERC-20 token to be registered and sent. +const TOKEN_ID: [u8; 20] = hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"); +const CHAIN_ID: u64 = 11155111u64; pub fn weth_location() -> Location { + erc20_token_location(WETH.into()) +} + +pub fn erc20_token_location(token_id: H160) -> Location { Location::new( 2, [ GlobalConsensus(EthereumNetwork::get().into()), - AccountKey20 { network: None, key: WETH.into() }, + AccountKey20 { network: None, key: token_id.into() }, ], ) } -pub fn register_weth() { - let assethub_location = BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()); - let assethub_sovereign = BridgeHubWestend::sovereign_account_id_of(assethub_location); - AssetHubWestend::execute_with(|| { - type RuntimeOrigin = ::RuntimeOrigin; +#[test] +fn register_token_v2() { + let relayer = BridgeHubWestendSender::get(); + let receiver = AssetHubWestendReceiver::get(); + BridgeHubWestend::fund_accounts(vec![(relayer.clone(), INITIAL_FUND)]); - assert_ok!(::ForeignAssets::force_create( - RuntimeOrigin::root(), - weth_location().try_into().unwrap(), - assethub_sovereign.clone().into(), - true, - 1000, //ED will be used as exchange rate by default when used to PayFees with - )); + register_foreign_asset(weth_location()); - assert!(::ForeignAssets::asset_exists( - weth_location().try_into().unwrap(), - )); + set_up_weth_and_dot_pool(weth_location()); - assert_ok!(::ForeignAssets::mint_into( - weth_location().try_into().unwrap(), - &AssetHubWestendReceiver::get(), - 1000000, - )); + let claimer = AccountId32 { network: None, id: receiver.clone().into() }; + let claimer_bytes = claimer.encode(); - assert_ok!(::ForeignAssets::mint_into( - weth_location().try_into().unwrap(), - &AssetHubWestendSender::get(), - 1000000, - )); - }); -} + let relayer_location = + Location::new(0, AccountId32 { network: None, id: relayer.clone().into() }); -pub(crate) fn set_up_weth_pool_with_wnd_on_ah_westend(asset: v5::Location) { - let wnd: v5::Location = v5::Parent.into(); - let assethub_location = BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()); - let owner = AssetHubWestendSender::get(); - let bh_sovereign = BridgeHubWestend::sovereign_account_id_of(assethub_location); + let bridge_owner = EthereumLocationsConverterFor::<[u8; 32]>::from_chain_id(&CHAIN_ID); - AssetHubWestend::fund_accounts(vec![(owner.clone(), 3_000_000_000_000)]); + let token: H160 = TOKEN_ID.into(); + let asset_id = erc20_token_location(token.into()); + + let dot_asset = Location::new(1, Here); + let dot_fee: xcm::prelude::Asset = (dot_asset, CreateAssetDeposit::get()).into(); + + // Used to pay the asset creation deposit. + let weth_asset_value = 9_000_000_000_000u128; + let assets = vec![NativeTokenERC20 { token_id: WETH.into(), value: weth_asset_value }]; + let asset_deposit: xcm::prelude::Asset = (weth_location(), weth_asset_value).into(); + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + let instructions = vec![ + // Exchange weth for dot to pay the asset creation deposit + ExchangeAsset { + give: asset_deposit.clone().into(), + want: dot_fee.clone().into(), + maximal: false, + }, + // Deposit the dot deposit into the bridge sovereign account (where the asset creation + // fee will be deducted from) + DepositAsset { assets: dot_fee.into(), beneficiary: bridge_owner.into() }, + // Call to create the asset. + Transact { + origin_kind: OriginKind::Xcm, + call: ( + CreateAssetCall::get(), + asset_id, + MultiAddress::<[u8; 32], ()>::Id(bridge_owner.into()), + 1u128, + ) + .encode() + .into(), + }, + ExpectTransactStatus(MaybeErrorCode::Success), + ]; + let xcm: Xcm<()> = instructions.into(); + let versioned_message_xcm = VersionedXcm::V5(xcm); + let origin = EthereumGatewayAddress::get(); + + let message = Message { + origin, + fee: 1_500_000_000_000u128, + assets, + xcm: versioned_message_xcm.encode(), + claimer: Some(claimer_bytes), + }; + let xcm = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); + let _ = EthereumInboundQueueV2::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; - let signed_owner = ::RuntimeOrigin::signed(owner.clone()); - let signed_bh_sovereign = - ::RuntimeOrigin::signed(bh_sovereign.clone()); + assert_expected_events!( + AssetHubWestend, + vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Created { .. }) => {},] + ); + }); +} - assert_ok!(::ForeignAssets::mint( - signed_bh_sovereign.clone(), - asset.clone().into(), - bh_sovereign.clone().into(), - 3_500_000_000_000, - )); +#[test] +fn send_token_v2() { + let relayer = BridgeHubWestendSender::get(); + let relayer_location = + Location::new(0, AccountId32 { network: None, id: relayer.clone().into() }); - assert_ok!(::ForeignAssets::transfer( - signed_bh_sovereign.clone(), - asset.clone().into(), - owner.clone().into(), - 3_000_000_000_000, - )); + let token: H160 = TOKEN_ID.into(); + let token_location = erc20_token_location(token); - assert_ok!(::AssetConversion::create_pool( - signed_owner.clone(), - Box::new(wnd.clone()), - Box::new(asset.clone()), - )); + let beneficiary_acc_id: H256 = H256::random(); + let beneficiary_acc_bytes: [u8; 32] = beneficiary_acc_id.into(); + let beneficiary = + Location::new(0, AccountId32 { network: None, id: beneficiary_acc_id.into() }); + + let claimer_acc_id = H256::random(); + let claimer_acc_id_bytes: [u8; 32] = claimer_acc_id.into(); + let claimer = AccountId32 { network: None, id: claimer_acc_id.into() }; + let claimer_bytes = claimer.encode(); + + register_foreign_asset(weth_location()); + register_foreign_asset(token_location.clone()); + + let token_transfer_value = 2_000_000_000_000u128; + + let assets = vec![ + // to pay fees + NativeTokenERC20 { token_id: WETH.into(), value: 1_500_000_000_000u128 }, + // the token being transferred + NativeTokenERC20 { token_id: token.into(), value: token_transfer_value } + ]; + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + let instructions = vec![ + DepositAsset { assets: Wild(AllOf { + id: AssetId(token_location.clone()), + fun: WildFungibility::Fungible, + }), beneficiary }, + ]; + let xcm: Xcm<()> = instructions.into(); + let versioned_message_xcm = VersionedXcm::V5(xcm); + let origin = EthereumGatewayAddress::get(); + + let message = Message { + origin, + fee: 1_500_000_000_000u128, + assets, + xcm: versioned_message_xcm.encode(), + claimer: Some(claimer_bytes), + }; + + let xcm = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); + let _ = EthereumInboundQueueV2::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); assert_expected_events!( - AssetHubWestend, - vec![ - RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {}, - ] + BridgeHubWestend, + vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},] ); + }); - assert_ok!(::AssetConversion::add_liquidity( - signed_owner.clone(), - Box::new(wnd), - Box::new(asset), - 1_000_000_000_000, - 2_000_000_000_000, - 1, - 1, - owner.into() - )); + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + // Check that the token was received and issued as a foreign asset on AssetHub assert_expected_events!( AssetHubWestend, - vec![ - RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {..}) => {}, - ] + vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {},] + ); + + // Beneficiary received the token transfer value + assert_eq!( + ForeignAssets::balance(token_location, AccountId::from(beneficiary_acc_bytes)), + token_transfer_value + ); + + // Claimer received weth refund for fees paid + assert!( + ForeignAssets::balance(weth_location(), AccountId::from(claimer_acc_id_bytes)) > 0 ); }); } #[test] -fn register_token_v2() { - // Whole register token fee is 374_851_000_000 +fn send_weth_v2() { let relayer = BridgeHubWestendSender::get(); - let receiver = AssetHubWestendReceiver::get(); - BridgeHubWestend::fund_accounts(vec![(relayer.clone(), INITIAL_FUND)]); - - register_weth(); + let relayer_location = + Location::new(0, AccountId32 { network: None, id: relayer.clone().into() }); - set_up_weth_pool_with_wnd_on_ah_westend(weth_location()); + let beneficiary_acc_id: H256 = H256::random(); + let beneficiary_acc_bytes: [u8; 32] = beneficiary_acc_id.into(); + let beneficiary = + Location::new(0, AccountId32 { network: None, id: beneficiary_acc_id.into() }); - let chain_id = 11155111u64; - let claimer = AccountId32 { network: None, id: receiver.clone().into() }; + let claimer_acc_id = H256::random(); + let claimer_acc_id_bytes: [u8; 32] = claimer_acc_id.into(); + let claimer = AccountId32 { network: None, id: claimer_acc_id.into() }; let claimer_bytes = claimer.encode(); + register_foreign_asset(weth_location()); + + let token_transfer_value = 2_000_000_000_000u128; + + let assets = vec![ + // to pay fees + NativeTokenERC20 { token_id: WETH.into(), value: token_transfer_value }, + // the token being transferred + ]; + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + let instructions = vec![ + DepositAsset { assets: Wild(AllOf { + id: AssetId(weth_location().clone()), + fun: WildFungibility::Fungible, + }), beneficiary }, + ]; + let xcm: Xcm<()> = instructions.into(); + let versioned_message_xcm = VersionedXcm::V5(xcm); + let origin = EthereumGatewayAddress::get(); + + let message = Message { + origin, + fee: 1_500_000_000_000u128, + assets, + xcm: versioned_message_xcm.encode(), + claimer: Some(claimer_bytes), + }; + + let xcm = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); + let _ = EthereumInboundQueueV2::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; + + // Check that the token was received and issued as a foreign asset on AssetHub + assert_expected_events!( + AssetHubWestend, + vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {},] + ); + + // Beneficiary received the token transfer value + assert_eq!( + ForeignAssets::balance(weth_location(), AccountId::from(beneficiary_acc_bytes)), + token_transfer_value + ); + + // Claimer received weth refund for fees paid + assert!( + ForeignAssets::balance(weth_location(), AccountId::from(claimer_acc_id_bytes)) > 0 + ); + }); +} + +#[test] +fn register_and_send_tokens_v2() { + let relayer = BridgeHubWestendSender::get(); let relayer_location = Location::new(0, AccountId32 { network: None, id: relayer.clone().into() }); - let bridge_owner = EthereumLocationsConverterFor::<[u8; 32]>::from_chain_id(&chain_id); + let token: H160 = TOKEN_ID.into(); + let token_location = erc20_token_location(token); - let token: H160 = hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").into(); // a is the token + let bridge_owner = EthereumLocationsConverterFor::<[u8; 32]>::from_chain_id(&CHAIN_ID); - let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); + let beneficiary_acc_id: H256 = H256::random(); + let beneficiary_acc_bytes: [u8; 32] = beneficiary_acc_id.into(); + let beneficiary = + Location::new(0, AccountId32 { network: None, id: beneficiary_acc_id.clone().into() }); - // Used to pay the asset creation deposit. - let weth_asset_value = 9_000_000_000_000u128; - let assets = vec![NativeTokenERC20 { token_id: WETH.into(), value: weth_asset_value }]; - let asset_deposit_weth: xcm::prelude::Asset = (weth_location(), weth_asset_value).into(); + AssetHubWestend::fund_accounts(vec![(sp_runtime::AccountId32::from(beneficiary_acc_bytes), 3_000_000_000_000)]); - let asset_id = Location::new( - 2, - [GlobalConsensus(ethereum_network_v5), AccountKey20 { network: None, key: token.into() }], - ); + let claimer_acc_id = H256::random(); + let claimer_acc_id_bytes: [u8; 32] = claimer_acc_id.into(); + let claimer = AccountId32 { network: None, id: claimer_acc_id.into() }; + let claimer_bytes = claimer.encode(); + + register_foreign_asset(weth_location()); + + set_up_weth_and_dot_pool(weth_location()); + + let token_transfer_value = 2_000_000_000_000u128; + let weth_transfer_value = 2_500_000_000_000u128; let dot_asset = Location::new(1, Here); let dot_fee: xcm::prelude::Asset = (dot_asset, CreateAssetDeposit::get()).into(); + // Used to pay the asset creation deposit. + let weth_asset_value = 9_000_000_000_000u128; + let asset_deposit: xcm::prelude::Asset = (weth_location(), weth_asset_value).into(); + + let assets = vec![ + // to pay fees and transfer assets + NativeTokenERC20 { token_id: WETH.into(), value: 2_800_000_000_000u128 }, + // the token being transferred + NativeTokenERC20 { token_id: token.into(), value: token_transfer_value } + ]; + BridgeHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; - let register_token_instructions = vec![ - // Exchange weth for dot to pay the asset creation deposit + let instructions = vec![ ExchangeAsset { - give: asset_deposit_weth.clone().into(), + give: asset_deposit.clone().into(), want: dot_fee.clone().into(), maximal: false, }, - // Deposit the dot deposit into the bridge sovereign account (where the asset creation - // fee will be deducted from) DepositAsset { assets: dot_fee.into(), beneficiary: bridge_owner.into() }, - // Call to create the asset. Transact { origin_kind: OriginKind::Xcm, call: ( CreateAssetCall::get(), - asset_id, + token_location.clone(), MultiAddress::<[u8; 32], ()>::Id(bridge_owner.into()), 1u128, ) @@ -198,8 +359,12 @@ fn register_token_v2() { .into(), }, ExpectTransactStatus(MaybeErrorCode::Success), + DepositAsset { assets: Wild(AllOf { + id: AssetId(token_location.clone()), + fun: WildFungibility::Fungible, + }), beneficiary: beneficiary.clone() }, ]; - let xcm: Xcm<()> = register_token_instructions.into(); + let xcm: Xcm<()> = instructions.into(); let versioned_message_xcm = VersionedXcm::V5(xcm); let origin = EthereumGatewayAddress::get(); @@ -210,6 +375,7 @@ fn register_token_v2() { xcm: versioned_message_xcm.encode(), claimer: Some(claimer_bytes), }; + let xcm = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); let _ = EthereumInboundQueueV2::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); @@ -222,69 +388,109 @@ fn register_token_v2() { AssetHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; + // The token was created assert_expected_events!( AssetHubWestend, vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Created { .. }) => {},] ); + + // Check that the token was received and issued as a foreign asset on AssetHub + assert_expected_events!( + AssetHubWestend, + vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {},] + ); + + // Beneficiary received the token transfer value + assert_eq!( + ForeignAssets::balance(token_location, AccountId::from(beneficiary_acc_bytes)), + token_transfer_value + ); + + // Claimer received weth refund for fees paid + assert!( + ForeignAssets::balance(weth_location(), AccountId::from(claimer_acc_id_bytes)) > 0 + ); }); } -#[test] -fn register_token_xcm() { - BridgeHubWestend::execute_with(|| { - println!("register token mainnet: {:x?}", get_xcm_hex(1u64)); - println!("===============================",); - println!("register token sepolia: {:x?}", get_xcm_hex(11155111u64)); +pub fn register_foreign_asset(token_location: Location) { + let assethub_location = BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()); + let assethub_sovereign = BridgeHubWestend::sovereign_account_id_of(assethub_location); + AssetHubWestend::execute_with(|| { + type RuntimeOrigin = ::RuntimeOrigin; + + assert_ok!(::ForeignAssets::force_create( + RuntimeOrigin::root(), + token_location.clone().try_into().unwrap(), + assethub_sovereign.clone().into(), + true, + 1000, + )); + + assert!(::ForeignAssets::asset_exists( + token_location.clone().try_into().unwrap(), + )); }); } -fn get_xcm_hex(chain_id: u64) -> String { - let owner = EthereumLocationsConverterFor::<[u8; 32]>::from_chain_id(&chain_id); - let weth_token_id: H160 = hex!("be68fc2d8249eb60bfcf0e71d5a0d2f2e292c4ed").into(); // TODO insert token id - let token: H160 = hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").into(); // token id placeholder - let weth_amount = 300_000_000_000_000u128; +pub(crate) fn set_up_weth_and_dot_pool(asset: v5::Location) { + let wnd: v5::Location = v5::Parent.into(); + let assethub_location = BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()); + let owner = AssetHubWestendSender::get(); + let bh_sovereign = BridgeHubWestend::sovereign_account_id_of(assethub_location); - let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); - let dot_asset = Location::new(1, Here); - let dot_fee: xcm::prelude::Asset = (dot_asset, CreateAssetDeposit::get()).into(); + AssetHubWestend::fund_accounts(vec![(owner.clone(), 3_000_000_000_000)]); - println!("register token id: {:x?}", token); - println!("weth token id: {:x?}", weth_token_id); - println!("weth_amount: {:x?}", hex::encode(weth_amount.encode())); - println!("dot asset: {:x?}", hex::encode(dot_fee.encode())); + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; - let weth_asset = Location::new( - 2, - [ - GlobalConsensus(ethereum_network_v5), - AccountKey20 { network: None, key: weth_token_id.into() }, - ], - ); - let weth_fee: xcm::prelude::Asset = (weth_asset, weth_amount).into(); // TODO replace Weth fee acmount + let signed_owner = ::RuntimeOrigin::signed(owner.clone()); + let signed_bh_sovereign = + ::RuntimeOrigin::signed(bh_sovereign.clone()); - let asset_id = Location::new( - 2, - [GlobalConsensus(ethereum_network_v5), AccountKey20 { network: None, key: token.into() }], - ); - - let register_token_xcm = vec![ - ExchangeAsset { give: weth_fee.into(), want: dot_fee.clone().into(), maximal: false }, - PayFees { asset: dot_fee }, - Transact { - origin_kind: OriginKind::Xcm, - call: ( - CreateAssetCall::get(), - asset_id, - MultiAddress::<[u8; 32], ()>::Id(owner.into()), - 1, - ) - .encode() - .into(), - }, - ]; - let message_xcm: Xcm<()> = register_token_xcm.into(); - let versioned_message_xcm = VersionedXcm::V5(message_xcm); + assert_ok!(::ForeignAssets::mint( + signed_bh_sovereign.clone(), + asset.clone().into(), + bh_sovereign.clone().into(), + 3_500_000_000_000, + )); + + assert_ok!(::ForeignAssets::transfer( + signed_bh_sovereign.clone(), + asset.clone().into(), + owner.clone().into(), + 3_000_000_000_000, + )); + + assert_ok!(::AssetConversion::create_pool( + signed_owner.clone(), + Box::new(wnd.clone()), + Box::new(asset.clone()), + )); - let xcm_bytes = versioned_message_xcm.encode(); - hex::encode(xcm_bytes) + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {}, + ] + ); + + assert_ok!(::AssetConversion::add_liquidity( + signed_owner.clone(), + Box::new(wnd), + Box::new(asset), + 1_000_000_000_000, + 2_000_000_000_000, + 1, + 1, + owner.into() + )); + + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {..}) => {}, + ] + ); + }); } From 6af760bb5451ad17572ae70120be7b943f60f19b Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Thu, 5 Dec 2024 21:14:37 +0200 Subject: [PATCH 41/52] allow invalid xcm --- .../snowbridge/primitives/router/src/inbound/v2.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index d03a6c1c4eba..15c58e1315e7 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -118,13 +118,16 @@ where ) -> Result, ConvertMessageError> { let mut message_xcm: Xcm<()> = Xcm::new(); if message.xcm.len() > 0 { - // Decode xcm - let versioned_xcm = VersionedXcm::<()>::decode_with_depth_limit( + // Allow xcm decode failure so that assets can be trapped on AH instead of this + // message failing but funds are already locked on Ethereum. + if let Ok(versioned_xcm) = VersionedXcm::<()>::decode_with_depth_limit( MAX_XCM_DECODE_DEPTH, &mut message.xcm.as_ref(), - ) - .map_err(|_| ConvertMessageError::InvalidVersionedXCM)?; - message_xcm = versioned_xcm.try_into().map_err(|_| ConvertMessageError::InvalidXCM)?; + ) { + if let Ok(decoded_xcm) = versioned_xcm.try_into() { + message_xcm = decoded_xcm; + } + } } log::debug!(target: LOG_TARGET,"xcm decoded as {:?}", message_xcm); From b3687603853b007f1aeedc590ff52b0e1634eac6 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Fri, 6 Dec 2024 10:50:44 +0200 Subject: [PATCH 42/52] allow invalid claimer --- .../primitives/router/src/inbound/v2.rs | 18 +- .../src/tests/snowbridge_v2.rs | 204 +++++++++++++++--- 2 files changed, 184 insertions(+), 38 deletions(-) diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index 15c58e1315e7..b154983aec59 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -55,12 +55,6 @@ pub enum Asset { /// Reason why a message conversion failed. #[derive(Copy, Clone, TypeInfo, PalletError, Encode, Decode, RuntimeDebug)] pub enum ConvertMessageError { - /// The XCM provided with the message could not be decoded into XCM. - InvalidXCM, - /// The XCM provided with the message could not be decoded into versioned XCM. - InvalidVersionedXCM, - /// Invalid claimer MultiAddress provided in payload. - InvalidClaimer, /// Invalid foreign ERC20 token ID InvalidAsset, } @@ -184,11 +178,13 @@ where let mut refund_surplus_to = origin_account_location; if let Some(claimer) = message.claimer { - let claimer = Junction::decode(&mut claimer.as_ref()) - .map_err(|_| ConvertMessageError::InvalidClaimer)?; - let claimer_location: Location = Location::new(0, [claimer.into()]); - refund_surplus_to = claimer_location.clone(); - instructions.push(SetAssetClaimer { location: claimer_location }); + // If the claimer can be decoded, add it to the message. If the claimer decoding fails, + // do not add it to the message, because it will cause the xcm to fail. + if let Ok(claimer) = Junction::decode(&mut claimer.as_ref()) { + let claimer_location: Location = Location::new(0, [claimer.into()]); + refund_surplus_to = claimer_location.clone(); + instructions.push(SetAssetClaimer { location: claimer_location }); + } } // If the message origin is not the gateway proxy contract, set the origin to 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 fc2f5533cf97..66a34504a2cf 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,6 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. use crate::imports::*; +use asset_hub_westend_runtime::ForeignAssets; use bridge_hub_westend_runtime::{ bridge_to_ethereum_config::{CreateAssetCall, CreateAssetDeposit, EthereumGatewayAddress}, EthereumInboundQueueV2, @@ -23,10 +24,8 @@ use snowbridge_router_primitives::inbound::{ v2::{Asset::NativeTokenERC20, Message}, EthereumLocationsConverterFor, }; -use sp_core::H160; +use sp_core::{H160, H256}; use sp_runtime::MultiAddress; -use sp_core::H256; -use asset_hub_westend_runtime::ForeignAssets; /// Calculates the XCM prologue fee for sending an XCM to AH. const INITIAL_FUND: u128 = 5_000_000_000_000; @@ -163,17 +162,18 @@ fn send_token_v2() { // to pay fees NativeTokenERC20 { token_id: WETH.into(), value: 1_500_000_000_000u128 }, // the token being transferred - NativeTokenERC20 { token_id: token.into(), value: token_transfer_value } + NativeTokenERC20 { token_id: token.into(), value: token_transfer_value }, ]; BridgeHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; - let instructions = vec![ - DepositAsset { assets: Wild(AllOf { + let instructions = vec![DepositAsset { + assets: Wild(AllOf { id: AssetId(token_location.clone()), fun: WildFungibility::Fungible, - }), beneficiary }, - ]; + }), + beneficiary, + }]; let xcm: Xcm<()> = instructions.into(); let versioned_message_xcm = VersionedXcm::V5(xcm); let origin = EthereumGatewayAddress::get(); @@ -211,9 +211,7 @@ fn send_token_v2() { ); // Claimer received weth refund for fees paid - assert!( - ForeignAssets::balance(weth_location(), AccountId::from(claimer_acc_id_bytes)) > 0 - ); + assert!(ForeignAssets::balance(weth_location(), AccountId::from(claimer_acc_id_bytes)) > 0); }); } @@ -245,12 +243,13 @@ fn send_weth_v2() { BridgeHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; - let instructions = vec![ - DepositAsset { assets: Wild(AllOf { + let instructions = vec![DepositAsset { + assets: Wild(AllOf { id: AssetId(weth_location().clone()), fun: WildFungibility::Fungible, - }), beneficiary }, - ]; + }), + beneficiary, + }]; let xcm: Xcm<()> = instructions.into(); let versioned_message_xcm = VersionedXcm::V5(xcm); let origin = EthereumGatewayAddress::get(); @@ -288,14 +287,12 @@ fn send_weth_v2() { ); // Claimer received weth refund for fees paid - assert!( - ForeignAssets::balance(weth_location(), AccountId::from(claimer_acc_id_bytes)) > 0 - ); + assert!(ForeignAssets::balance(weth_location(), AccountId::from(claimer_acc_id_bytes)) > 0); }); } #[test] -fn register_and_send_tokens_v2() { +fn register_and_send_multiple_tokens_v2() { let relayer = BridgeHubWestendSender::get(); let relayer_location = Location::new(0, AccountId32 { network: None, id: relayer.clone().into() }); @@ -310,7 +307,11 @@ fn register_and_send_tokens_v2() { let beneficiary = Location::new(0, AccountId32 { network: None, id: beneficiary_acc_id.clone().into() }); - AssetHubWestend::fund_accounts(vec![(sp_runtime::AccountId32::from(beneficiary_acc_bytes), 3_000_000_000_000)]); + // To satisfy ED + AssetHubWestend::fund_accounts(vec![( + sp_runtime::AccountId32::from(beneficiary_acc_bytes), + 3_000_000_000_000, + )]); let claimer_acc_id = H256::random(); let claimer_acc_id_bytes: [u8; 32] = claimer_acc_id.into(); @@ -335,7 +336,7 @@ fn register_and_send_tokens_v2() { // to pay fees and transfer assets NativeTokenERC20 { token_id: WETH.into(), value: 2_800_000_000_000u128 }, // the token being transferred - NativeTokenERC20 { token_id: token.into(), value: token_transfer_value } + NativeTokenERC20 { token_id: token.into(), value: token_transfer_value }, ]; BridgeHubWestend::execute_with(|| { @@ -347,6 +348,7 @@ fn register_and_send_tokens_v2() { maximal: false, }, DepositAsset { assets: dot_fee.into(), beneficiary: bridge_owner.into() }, + // register new token Transact { origin_kind: OriginKind::Xcm, call: ( @@ -359,10 +361,22 @@ fn register_and_send_tokens_v2() { .into(), }, ExpectTransactStatus(MaybeErrorCode::Success), - DepositAsset { assets: Wild(AllOf { - id: AssetId(token_location.clone()), - fun: WildFungibility::Fungible, - }), beneficiary: beneficiary.clone() }, + // deposit new token to beneficiary + DepositAsset { + assets: Wild(AllOf { + id: AssetId(token_location.clone()), + fun: WildFungibility::Fungible, + }), + beneficiary: beneficiary.clone(), + }, + // deposit weth to beneficiary + DepositAsset { + assets: Wild(AllOf { + id: AssetId(weth_location()), + fun: WildFungibility::Fungible, + }), + beneficiary: beneficiary.clone(), + }, ]; let xcm: Xcm<()> = instructions.into(); let versioned_message_xcm = VersionedXcm::V5(xcm); @@ -406,13 +420,149 @@ fn register_and_send_tokens_v2() { token_transfer_value ); - // Claimer received weth refund for fees paid + // Beneficiary received the weth transfer value assert!( - ForeignAssets::balance(weth_location(), AccountId::from(claimer_acc_id_bytes)) > 0 + ForeignAssets::balance(weth_location(), AccountId::from(beneficiary_acc_bytes)) > + weth_transfer_value + ); + + // Claimer received weth refund for fees paid + assert!(ForeignAssets::balance(weth_location(), AccountId::from(claimer_acc_id_bytes)) > 0); + }); +} + +#[test] +fn invalid_xcm_traps_funds_on_ah() { + let relayer = BridgeHubWestendSender::get(); + let relayer_location = + Location::new(0, AccountId32 { network: None, id: relayer.clone().into() }); + + let token: H160 = TOKEN_ID.into(); + let claimer = AccountId32 { network: None, id: H256::random().into() }; + let claimer_bytes = claimer.encode(); + let beneficiary_acc_bytes: [u8; 32] = H256::random().into(); + + AssetHubWestend::fund_accounts(vec![( + sp_runtime::AccountId32::from(beneficiary_acc_bytes), + 3_000_000_000_000, + )]); + + register_foreign_asset(weth_location()); + + set_up_weth_and_dot_pool(weth_location()); + + let assets = vec![ + // to pay fees and transfer assets + NativeTokenERC20 { token_id: WETH.into(), value: 2_800_000_000_000u128 }, + // the token being transferred + NativeTokenERC20 { token_id: token.into(), value: 2_000_000_000_000u128 }, + ]; + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + // invalid xcm + let instructions = hex!("02806c072d50e2c7cd6821d1f084cbb4"); + let origin = EthereumGatewayAddress::get(); + + let message = Message { + origin, + fee: 1_500_000_000_000u128, + assets, + xcm: instructions.to_vec(), + claimer: Some(claimer_bytes), + }; + + let xcm = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); + let _ = EthereumInboundQueueV2::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; + + // Assets are trapped + assert_expected_events!( + AssetHubWestend, + vec![RuntimeEvent::PolkadotXcm(pallet_xcm::Event::AssetsTrapped { .. }) => {},] ); }); } +#[test] +fn invalid_claimer_does_not_fail_the_message() { + let relayer = BridgeHubWestendSender::get(); + let relayer_location = + Location::new(0, AccountId32 { network: None, id: relayer.clone().into() }); + + let beneficiary_acc: [u8; 32] = H256::random().into(); + let beneficiary = Location::new(0, AccountId32 { network: None, id: beneficiary_acc.into() }); + + register_foreign_asset(weth_location()); + + let token_transfer_value = 2_000_000_000_000u128; + + let assets = vec![ + // to pay fees + NativeTokenERC20 { token_id: WETH.into(), value: token_transfer_value }, + // the token being transferred + ]; + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + let instructions = vec![DepositAsset { + assets: Wild(AllOf { + id: AssetId(weth_location().clone()), + fun: WildFungibility::Fungible, + }), + beneficiary, + }]; + let xcm: Xcm<()> = instructions.into(); + let versioned_message_xcm = VersionedXcm::V5(xcm); + let origin = EthereumGatewayAddress::get(); + + let message = Message { + origin, + fee: 1_500_000_000_000u128, + assets, + xcm: versioned_message_xcm.encode(), + // Set an invalid claimer + claimer: Some(hex!("2b7ce7bc7e87e4d6619da21487c7a53f").to_vec()), + }; + + let xcm = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); + let _ = EthereumInboundQueueV2::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); + + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},] + ); + }); + + // Message still processes successfully + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // Check that the token was received and issued as a foreign asset on AssetHub + assert_expected_events!( + AssetHubWestend, + vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {},] + ); + + // Beneficiary received the token transfer value + assert_eq!( + ForeignAssets::balance(weth_location(), AccountId::from(beneficiary_acc)), + token_transfer_value + ); + + // Relayer (instead of claimer) received weth refund for fees paid + assert!(ForeignAssets::balance(weth_location(), AccountId::from(relayer)) > 0); + }); +} + pub fn register_foreign_asset(token_location: Location) { let assethub_location = BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()); let assethub_sovereign = BridgeHubWestend::sovereign_account_id_of(assethub_location); From 9939927a434e70aa68924c65e4d8b3526147293d Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Fri, 6 Dec 2024 10:53:22 +0200 Subject: [PATCH 43/52] move claimer before assets --- .../primitives/router/src/inbound/v2.rs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index b154983aec59..e0cc59f08769 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -146,6 +146,16 @@ where let mut reserve_assets = vec![]; let mut withdraw_assets = vec![]; + if let Some(claimer) = message.claimer { + // If the claimer can be decoded, add it to the message. If the claimer decoding fails, + // do not add it to the message, because it will cause the xcm to fail. + if let Ok(claimer) = Junction::decode(&mut claimer.as_ref()) { + let claimer_location: Location = Location::new(0, [claimer.into()]); + refund_surplus_to = claimer_location.clone(); + instructions.push(SetAssetClaimer { location: claimer_location }); + } + } + for asset in &message.assets { match asset { Asset::NativeTokenERC20 { token_id, value } => { @@ -177,16 +187,6 @@ where let mut refund_surplus_to = origin_account_location; - if let Some(claimer) = message.claimer { - // If the claimer can be decoded, add it to the message. If the claimer decoding fails, - // do not add it to the message, because it will cause the xcm to fail. - if let Ok(claimer) = Junction::decode(&mut claimer.as_ref()) { - let claimer_location: Location = Location::new(0, [claimer.into()]); - refund_surplus_to = claimer_location.clone(); - instructions.push(SetAssetClaimer { location: claimer_location }); - } - } - // If the message origin is not the gateway proxy contract, set the origin to // the original sender on Ethereum. Important to be before the arbitrary XCM that is // appended to the message on the next line. From 8f11a76b6301c733aaa935a702fbe030645a98b8 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Fri, 6 Dec 2024 11:38:01 +0200 Subject: [PATCH 44/52] apply weight at most change --- bridges/snowbridge/primitives/router/src/inbound/v1.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/bridges/snowbridge/primitives/router/src/inbound/v1.rs b/bridges/snowbridge/primitives/router/src/inbound/v1.rs index 42a04d0dc6c9..b78c9ca78b43 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v1.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v1.rs @@ -273,6 +273,7 @@ where // Call create_asset on foreign assets pallet. Transact { origin_kind: OriginKind::Xcm, + fallback_max_weight: None, call: ( create_call_index, asset_id, From 2484821a7f89d369a99fb3938c76f574f69696ba Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Mon, 9 Dec 2024 12:44:25 +0200 Subject: [PATCH 45/52] penpal test --- Cargo.lock | 1 + .../primitives/router/src/inbound/v2.rs | 4 +- .../bridge-hub-rococo/src/tests/snowbridge.rs | 5 +- .../bridges/bridge-hub-westend/Cargo.toml | 1 + .../src/tests/snowbridge.rs | 117 ++++++ .../src/tests/snowbridge_v2.rs | 344 ++++++++++++++++++ .../bridge-hubs/bridge-hub-westend/src/lib.rs | 19 +- 7 files changed, 487 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cdc690ad36b5..f482bea693a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2748,6 +2748,7 @@ dependencies = [ "pallet-xcm-bridge-hub 0.2.0", "parachains-common 7.0.0", "parity-scale-codec", + "penpal-emulated-chain", "rococo-westend-system-emulated-network", "scale-info", "snowbridge-core 0.2.0", diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index e0cc59f08769..0757869d3045 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -146,6 +146,8 @@ where let mut reserve_assets = vec![]; let mut withdraw_assets = vec![]; + let mut refund_surplus_to = origin_account_location; + if let Some(claimer) = message.claimer { // If the claimer can be decoded, add it to the message. If the claimer decoding fails, // do not add it to the message, because it will cause the xcm to fail. @@ -185,8 +187,6 @@ where instructions.push(WithdrawAsset(withdraw_assets.into())); } - let mut refund_surplus_to = origin_account_location; - // If the message origin is not the gateway proxy contract, set the origin to // the original sender on Ethereum. Important to be before the arbitrary XCM that is // appended to the message on the next line. 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 c72d5045ddc0..8331138f777b 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 @@ -25,7 +25,10 @@ use snowbridge_pallet_inbound_queue_fixtures::{ }; use snowbridge_pallet_system; use snowbridge_router_primitives::inbound::{ - Command, Destination, EthereumLocationsConverterFor, MessageV1, VersionedMessage, + EthereumLocationsConverterFor +}; +use snowbridge_router_primitives::inbound::v1::{ + Command, Destination, MessageV1, VersionedMessage, }; use sp_core::H256; use sp_runtime::{DispatchError::Token, TokenError::FundsUnavailable}; 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 fc3cbc835b04..ec518175fc61 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 @@ -44,6 +44,7 @@ rococo-westend-system-emulated-network = { workspace = true } testnet-parachains-constants = { features = ["rococo", "westend"], workspace = true, default-features = true } asset-hub-westend-runtime = { workspace = true } bridge-hub-westend-runtime = { workspace = true } +penpal-emulated-chain = { workspace = true } # Snowbridge snowbridge-core = { 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 3055043dd79c..be9323775e84 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 @@ -28,6 +28,8 @@ use snowbridge_router_primitives::inbound::{ use sp_core::H256; use testnet_parachains_constants::westend::snowbridge::EthereumNetwork; use xcm_executor::traits::ConvertLocation; +use penpal_emulated_chain::PARA_ID_B; +use penpal_emulated_chain::penpal_runtime; const INITIAL_FUND: u128 = 5_000_000_000_000; pub const CHAIN_ID: u64 = 11155111; @@ -441,6 +443,121 @@ fn transfer_relay_token() { }); } +/// Tests sending a token to a 3rd party parachain, called PenPal. The token reserve is +/// still located on AssetHub. +#[test] +fn send_token_from_ethereum_to_penpal() { + let asset_hub_sovereign = BridgeHubWestend::sovereign_account_id_of(Location::new( + 1, + [Parachain(AssetHubWestend::para_id().into())], + )); + // Fund AssetHub sovereign account so it can pay execution fees for the asset transfer + BridgeHubWestend::fund_accounts(vec![(asset_hub_sovereign.clone(), INITIAL_FUND)]); + // Fund PenPal receiver (covering ED) + PenpalB::fund_accounts(vec![(PenpalBReceiver::get(), INITIAL_FUND)]); + + 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(), + )], + )); + }); + + let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); + + // The Weth asset location, identified by the contract address on Ethereum + let weth_asset_location: Location = + (Parent, Parent, ethereum_network_v5, AccountKey20 { network: None, key: WETH }).into(); + + let origin_location = (Parent, Parent, ethereum_network_v5).into(); + + // Fund ethereum sovereign on AssetHub + let ethereum_sovereign: AccountId = + EthereumLocationsConverterFor::::convert_location(&origin_location).unwrap(); + AssetHubWestend::fund_accounts(vec![(ethereum_sovereign.clone(), INITIAL_FUND)]); + + // Create asset on the Penpal parachain. + PenpalB::execute_with(|| { + assert_ok!(::ForeignAssets::force_create( + ::RuntimeOrigin::root(), + weth_asset_location.clone(), + asset_hub_sovereign.clone().into(), + false, + 1000, + )); + + assert!(::ForeignAssets::asset_exists(weth_asset_location.clone())); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeOrigin = ::RuntimeOrigin; + + assert_ok!(::ForeignAssets::force_create( + RuntimeOrigin::root(), + weth_asset_location.clone().try_into().unwrap(), + asset_hub_sovereign.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::ForeignAccountId32 { + para_id: PARA_ID_B, + id: PenpalBReceiver::get().into(), + fee: 100_000_000_000u128, + }, + 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; + // Check that the assets were issued on AssetHub + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, + RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, + ] + ); + }); + + PenpalB::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + // Check that the assets were issued on PenPal + assert_expected_events!( + PenpalB, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, + ] + ); + }); +} + #[test] fn transfer_ah_token() { let assethub_sovereign = BridgeHubWestend::sovereign_account_id_of( 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 66a34504a2cf..24779ae3da80 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 @@ -26,6 +26,8 @@ use snowbridge_router_primitives::inbound::{ }; use sp_core::{H160, H256}; use sp_runtime::MultiAddress; +use emulated_integration_tests_common::RESERVABLE_ASSET_ID; +use penpal_emulated_chain::PARA_ID_B; /// Calculates the XCM prologue fee for sending an XCM to AH. const INITIAL_FUND: u128 = 5_000_000_000_000; @@ -93,6 +95,7 @@ fn register_token_v2() { // Call to create the asset. Transact { origin_kind: OriginKind::Xcm, + fallback_max_weight: None, call: ( CreateAssetCall::get(), asset_id, @@ -351,6 +354,7 @@ fn register_and_send_multiple_tokens_v2() { // register new token Transact { origin_kind: OriginKind::Xcm, + fallback_max_weight: None, call: ( CreateAssetCall::get(), token_location.clone(), @@ -431,6 +435,283 @@ fn register_and_send_multiple_tokens_v2() { }); } +#[test] +fn send_token_to_penpal_v2() { + let relayer = BridgeHubWestendSender::get(); + let relayer_location = + Location::new(0, AccountId32 { network: None, id: relayer.clone().into() }); + + let token: H160 = TOKEN_ID.into(); + let token_location = erc20_token_location(token); + + let beneficiary_acc_id: H256 = H256::random(); + let beneficiary_acc_bytes: [u8; 32] = beneficiary_acc_id.into(); + let beneficiary = + Location::new(0, AccountId32 { network: None, id: beneficiary_acc_id.into() }); + + let claimer_acc_id = H256::random(); + let claimer = AccountId32 { network: None, id: claimer_acc_id.into() }; + let claimer_bytes = claimer.encode(); + + // To pay fees on Penpal. + let weth_fee_penpal: xcm::prelude::Asset = (weth_location(), 3_000_000_000_000u128).into(); + + register_foreign_asset(weth_location()); + register_foreign_asset(token_location.clone()); + + // To satisfy ED + PenpalB::fund_accounts(vec![( + sp_runtime::AccountId32::from(beneficiary_acc_bytes), + 3_000_000_000_000, + )]); + + let penpal_location = BridgeHubWestend::sibling_location_of(PenpalB::para_id()); + let penpal_sovereign = BridgeHubWestend::sovereign_account_id_of(penpal_location); + PenpalB::execute_with(|| { + type RuntimeOrigin = ::RuntimeOrigin; + + // Register token on Penpal + assert_ok!(::ForeignAssets::force_create( + RuntimeOrigin::root(), + token_location.clone().try_into().unwrap(), + penpal_sovereign.clone().into(), + true, + 1000, + )); + + assert!(::ForeignAssets::asset_exists( + token_location.clone().try_into().unwrap(), + )); + + // Register weth on Penpal + assert_ok!(::ForeignAssets::force_create( + RuntimeOrigin::root(), + weth_location().try_into().unwrap(), + penpal_sovereign.clone().into(), + true, + 1000, + )); + + assert!(::ForeignAssets::asset_exists( + weth_location().try_into().unwrap(), + )); + + assert_ok!(::System::set_storage( + ::RuntimeOrigin::root(), + vec![( + PenpalCustomizableAssetFromSystemAssetHub::key().to_vec(), + Location::new(2, [GlobalConsensus(Ethereum { chain_id: CHAIN_ID })]).encode(), + )], + )); + }); + + set_up_weth_and_dot_pool(weth_location()); + + set_up_weth_and_dot_pool_on_penpal(weth_location()); + + let token_transfer_value = 2_000_000_000_000u128; + + let assets = vec![ + // to pay fees + NativeTokenERC20 { token_id: WETH.into(), value: 5_000_000_000_000u128 }, + // the token being transferred + NativeTokenERC20 { token_id: token.into(), value: token_transfer_value }, + ]; + + let token_asset: xcm::prelude::Asset = (token_location.clone(), token_transfer_value).into(); + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + let instructions = vec![ + // Send message to Penpal + DepositReserveAsset { + // Send the token plus some weth for execution fees + assets: Definite(vec![weth_fee_penpal.clone(), token_asset].into()), + // Penpal + dest: Location::new(1, [Parachain(PARA_ID_B)]), + xcm: vec![ + // Pay fees on Penpal. + PayFees { asset: weth_fee_penpal }, + // Deposit assets to beneficiary. + DepositAsset { assets: Wild(AllOf { + id: AssetId(token_location.clone()), + fun: WildFungibility::Fungible, + }), + beneficiary: beneficiary.clone(), }, + SetTopic(H256::random().into()), + ] + .into(), + }, + ]; + let xcm: Xcm<()> = instructions.into(); + let versioned_message_xcm = VersionedXcm::V5(xcm); + let origin = EthereumGatewayAddress::get(); + + let message = Message { + origin, + fee: 1_000_000_000_000u128, + assets, + xcm: versioned_message_xcm.encode(), + claimer: Some(claimer_bytes), + }; + + let xcm = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); + let _ = EthereumInboundQueueV2::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; + // Check that the assets were issued on AssetHub + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, + RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, + ] + ); + }); + + PenpalB::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // Check that the token was received and issued as a foreign asset on PenpalB + assert_expected_events!( + PenpalB, + vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {},] + ); + + // Beneficiary received the token transfer value + assert_eq!( + ForeignAssets::balance(token_location, AccountId::from(beneficiary_acc_bytes)), + token_transfer_value + ); + }); +} + +/* +#[test] +fn send_foreign_erc20_token_back_to_polkadot() { + let claimer = AccountId32 { network: None, id: H256::random().into() }; + let claimer_bytes = claimer.encode(); + + let asset_id: Location = + [PalletInstance(ASSETS_PALLET_ID), GeneralIndex(RESERVABLE_ASSET_ID.into())].into(); + + let asset_id_in_bh: Location = Location::new( + 1, + [ + Parachain(AssetHubWestend::para_id().into()), + PalletInstance(ASSETS_PALLET_ID), + GeneralIndex(RESERVABLE_ASSET_ID.into()), + ], + ); + + let asset_id_after_reanchored = Location::new( + 1, + [ + GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)), + Parachain(AssetHubWestend::para_id().into()), + ], + ) + .appended_with(asset_id.clone().interior) + .unwrap(); + + // Register token + BridgeHubWestend::execute_with(|| { + type RuntimeOrigin = ::RuntimeOrigin; + + assert_ok!(::EthereumSystem::register_token( + RuntimeOrigin::root(), + Box::new(VersionedLocation::from(asset_id_in_bh.clone())), + AssetMetadata { + name: "ah_asset".as_bytes().to_vec().try_into().unwrap(), + symbol: "ah_asset".as_bytes().to_vec().try_into().unwrap(), + decimals: 12, + }, + )); + }); + + let ethereum_sovereign: AccountId = + EthereumLocationsConverterFor::<[u8; 32]>::convert_location(ðereum_destination) + .unwrap() + .into(); + AssetHubWestend::fund_accounts(vec![(ethereum_sovereign.clone(), INITIAL_FUND)]); + + // Mint some token into Snowbridge sovereign to mimic locked assets + AssetHubWestend::mint_asset( + ::RuntimeOrigin::signed(ethereum_sovereign), + RESERVABLE_ASSET_ID, + AssetHubWestendSender::get(), + TOKEN_AMOUNT, + ); + + let token_id = TokenIdOf::convert_location(&asset_id_after_reanchored).unwrap(); + let asset: Asset = (asset_id_after_reanchored, amount).into(); + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + let instructions = vec![ + WithdrawAsset(asset.clone().into()), + DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, + SetTopic(message_id.into()), + ]; + let xcm: Xcm<()> = instructions.into(); + let versioned_message_xcm = VersionedXcm::V5(xcm); + let origin = EthereumGatewayAddress::get(); + + let message = Message { + origin, + fee: 1_500_000_000_000u128, + assets, + xcm: versioned_message_xcm.encode(), + claimer: Some(claimer_bytes), + }; + + let xcm = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); + let _ = EthereumInboundQueueV2::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; + + assert_expected_events!( + AssetHubWestend, + vec![RuntimeEvent::Assets(pallet_assets::Event::Burned{..}) => {},] + ); + + let events = AssetHubWestend::events(); + + // Check that the native token burnt from some reserved account + assert!( + events.iter().any(|event| matches!( + event, + RuntimeEvent::Assets(pallet_assets::Event::Burned { owner, .. }) + if *owner == ethereum_sovereign.clone(), + )), + "token burnt from Ethereum sovereign account." + ); + + // Check that the token was minted to beneficiary + assert!( + events.iter().any(|event| matches!( + event, + RuntimeEvent::Assets(pallet_assets::Event::Issued { owner, .. }) + if *owner == AssetHubWestendReceiver::get() + )), + "Token minted to beneficiary." + ); + }); +}*/ + #[test] fn invalid_xcm_traps_funds_on_ah() { let relayer = BridgeHubWestendSender::get(); @@ -644,3 +925,66 @@ pub(crate) fn set_up_weth_and_dot_pool(asset: v5::Location) { ); }); } + + +pub(crate) fn set_up_weth_and_dot_pool_on_penpal(asset: v5::Location) { + let wnd: v5::Location = v5::Parent.into(); + let penpal_location = BridgeHubWestend::sibling_location_of(PenpalB::para_id()); + let owner = PenpalBSender::get(); + let bh_sovereign = BridgeHubWestend::sovereign_account_id_of(penpal_location); + + PenpalB::fund_accounts(vec![(owner.clone(), 3_000_000_000_000)]); + + PenpalB::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + let signed_owner = ::RuntimeOrigin::signed(owner.clone()); + let signed_bh_sovereign = + ::RuntimeOrigin::signed(bh_sovereign.clone()); + + assert_ok!(::ForeignAssets::mint( + signed_bh_sovereign.clone(), + asset.clone().into(), + bh_sovereign.clone().into(), + 3_500_000_000_000, + )); + + assert_ok!(::ForeignAssets::transfer( + signed_bh_sovereign.clone(), + asset.clone().into(), + owner.clone().into(), + 3_000_000_000_000, + )); + + assert_ok!(::AssetConversion::create_pool( + signed_owner.clone(), + Box::new(wnd.clone()), + Box::new(asset.clone()), + )); + + assert_expected_events!( + PenpalB, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {}, + ] + ); + + assert_ok!(::AssetConversion::add_liquidity( + signed_owner.clone(), + Box::new(wnd), + Box::new(asset), + 1_000_000_000_000, + 2_000_000_000_000, + 1, + 1, + owner.into() + )); + + assert_expected_events!( + PenpalB, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {..}) => {}, + ] + ); + }); +} 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 44d6fb6d72f1..b653e3990137 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 @@ -50,7 +50,7 @@ use sp_runtime::{ transaction_validity::{TransactionSource, TransactionValidity}, ApplyExtrinsicResult, }; - +use frame_support::traits::Contains; use snowbridge_router_primitives::inbound::v2::Message; use sp_runtime::DispatchError; #[cfg(feature = "std")] @@ -267,6 +267,22 @@ parameter_types! { } // Configure FRAME pallets to include in runtime. +pub struct BaseFilter; +impl Contains for BaseFilter { + fn contains(call: &RuntimeCall) -> bool { + // Disallow these Snowbridge system calls. + if matches!( + call, + RuntimeCall::EthereumSystem(snowbridge_pallet_system::Call::create_agent { .. }) + ) || matches!( + call, + RuntimeCall::EthereumSystem(snowbridge_pallet_system::Call::create_channel { .. }) + ) { + return false + } + return true + } +} #[derive_impl(frame_system::config_preludes::ParaChainDefaultConfig)] impl frame_system::Config for Runtime { @@ -299,6 +315,7 @@ impl frame_system::Config for Runtime { /// The action to take on a Runtime Upgrade type OnSetCode = cumulus_pallet_parachain_system::ParachainSetCode; type MaxConsumers = frame_support::traits::ConstU32<16>; + type BaseCallFilter = BaseFilter; } impl pallet_timestamp::Config for Runtime { From 373c878045ae6f5fac5955c0e1bb00d998994588 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Mon, 9 Dec 2024 12:59:24 +0200 Subject: [PATCH 46/52] pna progress --- .../src/tests/snowbridge.rs | 231 +++++++++--------- .../src/tests/snowbridge_v2.rs | 29 ++- 2 files changed, 139 insertions(+), 121 deletions(-) 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 be9323775e84..a55aa9f9c353 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 @@ -289,6 +289,122 @@ fn send_weth_asset_from_asset_hub_to_ethereum() { }); } + +/// Tests sending a token to a 3rd party parachain, called PenPal. The token reserve is +/// still located on AssetHub. +#[test] +fn send_token_from_ethereum_to_penpal() { + let asset_hub_sovereign = BridgeHubWestend::sovereign_account_id_of(Location::new( + 1, + [Parachain(AssetHubWestend::para_id().into())], + )); + // Fund AssetHub sovereign account so it can pay execution fees for the asset transfer + BridgeHubWestend::fund_accounts(vec![(asset_hub_sovereign.clone(), INITIAL_FUND)]); + // Fund PenPal receiver (covering ED) + PenpalB::fund_accounts(vec![(PenpalBReceiver::get(), INITIAL_FUND)]); + + 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(), + )], + )); + }); + + let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); + + // The Weth asset location, identified by the contract address on Ethereum + let weth_asset_location: Location = + (Parent, Parent, ethereum_network_v5, AccountKey20 { network: None, key: WETH }).into(); + + let origin_location = (Parent, Parent, ethereum_network_v5).into(); + + // Fund ethereum sovereign on AssetHub + let ethereum_sovereign: AccountId = + EthereumLocationsConverterFor::::convert_location(&origin_location).unwrap(); + AssetHubWestend::fund_accounts(vec![(ethereum_sovereign.clone(), INITIAL_FUND)]); + + // Create asset on the Penpal parachain. + PenpalB::execute_with(|| { + assert_ok!(::ForeignAssets::force_create( + ::RuntimeOrigin::root(), + weth_asset_location.clone(), + asset_hub_sovereign.clone().into(), + false, + 1000, + )); + + assert!(::ForeignAssets::asset_exists(weth_asset_location.clone())); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeOrigin = ::RuntimeOrigin; + + assert_ok!(::ForeignAssets::force_create( + RuntimeOrigin::root(), + weth_asset_location.clone().try_into().unwrap(), + asset_hub_sovereign.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::ForeignAccountId32 { + para_id: PARA_ID_B, + id: PenpalBReceiver::get().into(), + fee: 100_000_000_000u128, + }, + 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; + // Check that the assets were issued on AssetHub + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, + RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, + ] + ); + }); + + PenpalB::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + // Check that the assets were issued on PenPal + assert_expected_events!( + PenpalB, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, + ] + ); + }); +} + #[test] fn transfer_relay_token() { let assethub_sovereign = BridgeHubWestend::sovereign_account_id_of( @@ -443,121 +559,6 @@ fn transfer_relay_token() { }); } -/// Tests sending a token to a 3rd party parachain, called PenPal. The token reserve is -/// still located on AssetHub. -#[test] -fn send_token_from_ethereum_to_penpal() { - let asset_hub_sovereign = BridgeHubWestend::sovereign_account_id_of(Location::new( - 1, - [Parachain(AssetHubWestend::para_id().into())], - )); - // Fund AssetHub sovereign account so it can pay execution fees for the asset transfer - BridgeHubWestend::fund_accounts(vec![(asset_hub_sovereign.clone(), INITIAL_FUND)]); - // Fund PenPal receiver (covering ED) - PenpalB::fund_accounts(vec![(PenpalBReceiver::get(), INITIAL_FUND)]); - - 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(), - )], - )); - }); - - let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); - - // The Weth asset location, identified by the contract address on Ethereum - let weth_asset_location: Location = - (Parent, Parent, ethereum_network_v5, AccountKey20 { network: None, key: WETH }).into(); - - let origin_location = (Parent, Parent, ethereum_network_v5).into(); - - // Fund ethereum sovereign on AssetHub - let ethereum_sovereign: AccountId = - EthereumLocationsConverterFor::::convert_location(&origin_location).unwrap(); - AssetHubWestend::fund_accounts(vec![(ethereum_sovereign.clone(), INITIAL_FUND)]); - - // Create asset on the Penpal parachain. - PenpalB::execute_with(|| { - assert_ok!(::ForeignAssets::force_create( - ::RuntimeOrigin::root(), - weth_asset_location.clone(), - asset_hub_sovereign.clone().into(), - false, - 1000, - )); - - assert!(::ForeignAssets::asset_exists(weth_asset_location.clone())); - }); - - AssetHubWestend::execute_with(|| { - type RuntimeOrigin = ::RuntimeOrigin; - - assert_ok!(::ForeignAssets::force_create( - RuntimeOrigin::root(), - weth_asset_location.clone().try_into().unwrap(), - asset_hub_sovereign.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::ForeignAccountId32 { - para_id: PARA_ID_B, - id: PenpalBReceiver::get().into(), - fee: 100_000_000_000u128, - }, - 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; - // Check that the assets were issued on AssetHub - assert_expected_events!( - AssetHubWestend, - vec![ - RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, - RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, - ] - ); - }); - - PenpalB::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; - // Check that the assets were issued on PenPal - assert_expected_events!( - PenpalB, - vec![ - RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, - ] - ); - }); -} - #[test] fn transfer_ah_token() { let assethub_sovereign = BridgeHubWestend::sovereign_account_id_of( 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 24779ae3da80..5ee51cda9295 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 @@ -28,6 +28,11 @@ use sp_core::{H160, H256}; use sp_runtime::MultiAddress; use emulated_integration_tests_common::RESERVABLE_ASSET_ID; use penpal_emulated_chain::PARA_ID_B; +use snowbridge_core::AssetMetadata; +use xcm_executor::traits::ConvertLocation; +use snowbridge_core::TokenIdOf; +use snowbridge_router_primitives::inbound::v2::Asset::ForeignTokenERC20; +const TOKEN_AMOUNT: u128 = 100_000_000_000; /// Calculates the XCM prologue fee for sending an XCM to AH. const INITIAL_FUND: u128 = 5_000_000_000_000; @@ -592,11 +597,16 @@ fn send_token_to_penpal_v2() { }); } -/* #[test] fn send_foreign_erc20_token_back_to_polkadot() { + let relayer = BridgeHubWestendSender::get(); + let relayer_location = + Location::new(0, AccountId32 { network: None, id: relayer.clone().into() }); + let claimer = AccountId32 { network: None, id: H256::random().into() }; let claimer_bytes = claimer.encode(); + let beneficiary = + Location::new(0, AccountId32 { network: None, id: AssetHubWestendReceiver::get().into() }); let asset_id: Location = [PalletInstance(ASSETS_PALLET_ID), GeneralIndex(RESERVABLE_ASSET_ID.into())].into(); @@ -620,6 +630,8 @@ fn send_foreign_erc20_token_back_to_polkadot() { .appended_with(asset_id.clone().interior) .unwrap(); + let ethereum_destination = Location::new(2, [GlobalConsensus(Ethereum { chain_id: CHAIN_ID })]); + // Register token BridgeHubWestend::execute_with(|| { type RuntimeOrigin = ::RuntimeOrigin; @@ -641,23 +653,28 @@ fn send_foreign_erc20_token_back_to_polkadot() { .into(); AssetHubWestend::fund_accounts(vec![(ethereum_sovereign.clone(), INITIAL_FUND)]); - // Mint some token into Snowbridge sovereign to mimic locked assets AssetHubWestend::mint_asset( - ::RuntimeOrigin::signed(ethereum_sovereign), + ::RuntimeOrigin::signed(AssetHubWestendAssetOwner::get()), RESERVABLE_ASSET_ID, AssetHubWestendSender::get(), TOKEN_AMOUNT, ); let token_id = TokenIdOf::convert_location(&asset_id_after_reanchored).unwrap(); - let asset: Asset = (asset_id_after_reanchored, amount).into(); + let asset: Asset = (asset_id_after_reanchored, TOKEN_AMOUNT).into(); + + let assets = vec![ + // to pay fees + NativeTokenERC20 { token_id: WETH.into(), value: 2_000_000_000_000u128 }, + // the token being transferred + ForeignTokenERC20 { token_id: token_id.into(), value: TOKEN_AMOUNT }, + ]; BridgeHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; let instructions = vec![ WithdrawAsset(asset.clone().into()), DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, - SetTopic(message_id.into()), ]; let xcm: Xcm<()> = instructions.into(); let versioned_message_xcm = VersionedXcm::V5(xcm); @@ -710,7 +727,7 @@ fn send_foreign_erc20_token_back_to_polkadot() { "Token minted to beneficiary." ); }); -}*/ +} #[test] fn invalid_xcm_traps_funds_on_ah() { From 30d0cf4242758c5558e9fd83c6c2c507692f2d52 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Mon, 9 Dec 2024 19:35:09 +0200 Subject: [PATCH 47/52] finish integration tests --- .../primitives/router/src/inbound/v2.rs | 24 +++++++-- .../src/tests/snowbridge.rs | 10 ++-- .../src/tests/snowbridge_v2.rs | 50 +++++++++---------- .../src/bridge_to_ethereum_config.rs | 2 + 4 files changed, 53 insertions(+), 33 deletions(-) diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index 0757869d3045..23b51eb4a5f7 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -55,8 +55,10 @@ pub enum Asset { /// Reason why a message conversion failed. #[derive(Copy, Clone, TypeInfo, PalletError, Encode, Decode, RuntimeDebug)] pub enum ConvertMessageError { - /// Invalid foreign ERC20 token ID + /// Invalid foreign ERC-20 token ID InvalidAsset, + /// Cannot reachor a foreign ERC-20 asset location. + CannotReanchor, } pub trait ConvertMessage { @@ -69,12 +71,16 @@ pub struct MessageToXcm< ConvertAssetId, WethAddress, GatewayProxyAddress, + EthereumUniversalLocation, + GlobalAssetHubLocation, > where EthereumNetwork: Get, InboundQueuePalletInstance: Get, ConvertAssetId: MaybeEquivalence, WethAddress: Get, GatewayProxyAddress: Get, + EthereumUniversalLocation: Get, + GlobalAssetHubLocation: Get, { _phantom: PhantomData<( EthereumNetwork, @@ -82,6 +88,8 @@ pub struct MessageToXcm< ConvertAssetId, WethAddress, GatewayProxyAddress, + EthereumUniversalLocation, + GlobalAssetHubLocation, )>, } @@ -91,6 +99,8 @@ impl< ConvertAssetId, WethAddress, GatewayProxyAddress, + EthereumUniversalLocation, + GlobalAssetHubLocation, > ConvertMessage for MessageToXcm< EthereumNetwork, @@ -98,6 +108,8 @@ impl< ConvertAssetId, WethAddress, GatewayProxyAddress, + EthereumUniversalLocation, + GlobalAssetHubLocation, > where EthereumNetwork: Get, @@ -105,6 +117,8 @@ where ConvertAssetId: MaybeEquivalence, WethAddress: Get, GatewayProxyAddress: Get, + EthereumUniversalLocation: Get, + GlobalAssetHubLocation: Get, { fn convert( message: Message, @@ -172,9 +186,13 @@ where reserve_assets.push(asset); }, Asset::ForeignTokenERC20 { token_id, value } => { - let asset_id = ConvertAssetId::convert(&token_id) + let asset_loc = ConvertAssetId::convert(&token_id) .ok_or(ConvertMessageError::InvalidAsset)?; - let asset: XcmAsset = (asset_id, *value).into(); + let mut reanchored_asset_loc = asset_loc.clone(); + reanchored_asset_loc + .reanchor(&GlobalAssetHubLocation::get(), &EthereumUniversalLocation::get()) + .map_err(|_| ConvertMessageError::CannotReanchor)?; + let asset: XcmAsset = (reanchored_asset_loc, *value).into(); withdraw_assets.push(asset); }, } 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 a55aa9f9c353..48eeb07a7c6a 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 @@ -19,6 +19,7 @@ use codec::{Decode, Encode}; use emulated_integration_tests_common::RESERVABLE_ASSET_ID; use frame_support::pallet_prelude::TypeInfo; use hex_literal::hex; +use penpal_emulated_chain::PARA_ID_B; 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::{ @@ -28,8 +29,6 @@ use snowbridge_router_primitives::inbound::{ use sp_core::H256; use testnet_parachains_constants::westend::snowbridge::EthereumNetwork; use xcm_executor::traits::ConvertLocation; -use penpal_emulated_chain::PARA_ID_B; -use penpal_emulated_chain::penpal_runtime; const INITIAL_FUND: u128 = 5_000_000_000_000; pub const CHAIN_ID: u64 = 11155111; @@ -38,7 +37,7 @@ const ETHEREUM_DESTINATION_ADDRESS: [u8; 20] = hex!("44a57ee2f2FCcb85FDa2B0B18EB const XCM_FEE: u128 = 100_000_000_000; const TOKEN_AMOUNT: u128 = 100_000_000_000; -#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)] +#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeIfmtnfo)] pub enum ControlCall { #[codec(index = 3)] CreateAgent, @@ -289,7 +288,6 @@ fn send_weth_asset_from_asset_hub_to_ethereum() { }); } - /// Tests sending a token to a 3rd party parachain, called PenPal. The token reserve is /// still located on AssetHub. #[test] @@ -336,7 +334,9 @@ fn send_token_from_ethereum_to_penpal() { 1000, )); - assert!(::ForeignAssets::asset_exists(weth_asset_location.clone())); + assert!(::ForeignAssets::asset_exists( + weth_asset_location.clone() + )); }); AssetHubWestend::execute_with(|| { 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 5ee51cda9295..6bc41f212b6e 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 @@ -19,19 +19,20 @@ use bridge_hub_westend_runtime::{ EthereumInboundQueueV2, }; use codec::Encode; +use emulated_integration_tests_common::RESERVABLE_ASSET_ID; use hex_literal::hex; +use penpal_emulated_chain::PARA_ID_B; +use snowbridge_core::{AssetMetadata, TokenIdOf}; use snowbridge_router_primitives::inbound::{ - v2::{Asset::NativeTokenERC20, Message}, + v2::{ + Asset::{ForeignTokenERC20, NativeTokenERC20}, + Message, + }, EthereumLocationsConverterFor, }; use sp_core::{H160, H256}; use sp_runtime::MultiAddress; -use emulated_integration_tests_common::RESERVABLE_ASSET_ID; -use penpal_emulated_chain::PARA_ID_B; -use snowbridge_core::AssetMetadata; use xcm_executor::traits::ConvertLocation; -use snowbridge_core::TokenIdOf; -use snowbridge_router_primitives::inbound::v2::Asset::ForeignTokenERC20; const TOKEN_AMOUNT: u128 = 100_000_000_000; /// Calculates the XCM prologue fee for sending an XCM to AH. @@ -537,14 +538,16 @@ fn send_token_to_penpal_v2() { // Pay fees on Penpal. PayFees { asset: weth_fee_penpal }, // Deposit assets to beneficiary. - DepositAsset { assets: Wild(AllOf { - id: AssetId(token_location.clone()), - fun: WildFungibility::Fungible, - }), - beneficiary: beneficiary.clone(), }, + DepositAsset { + assets: Wild(AllOf { + id: AssetId(token_location.clone()), + fun: WildFungibility::Fungible, + }), + beneficiary: beneficiary.clone(), + }, SetTopic(H256::random().into()), ] - .into(), + .into(), }, ]; let xcm: Xcm<()> = instructions.into(); @@ -611,6 +614,8 @@ fn send_foreign_erc20_token_back_to_polkadot() { let asset_id: Location = [PalletInstance(ASSETS_PALLET_ID), GeneralIndex(RESERVABLE_ASSET_ID.into())].into(); + register_foreign_asset(weth_location()); + let asset_id_in_bh: Location = Location::new( 1, [ @@ -627,8 +632,8 @@ fn send_foreign_erc20_token_back_to_polkadot() { Parachain(AssetHubWestend::para_id().into()), ], ) - .appended_with(asset_id.clone().interior) - .unwrap(); + .appended_with(asset_id.clone().interior) + .unwrap(); let ethereum_destination = Location::new(2, [GlobalConsensus(Ethereum { chain_id: CHAIN_ID })]); @@ -653,36 +658,33 @@ fn send_foreign_erc20_token_back_to_polkadot() { .into(); AssetHubWestend::fund_accounts(vec![(ethereum_sovereign.clone(), INITIAL_FUND)]); + // Mint the asset into the bridge sovereign account, to mimic locked funds AssetHubWestend::mint_asset( ::RuntimeOrigin::signed(AssetHubWestendAssetOwner::get()), RESERVABLE_ASSET_ID, - AssetHubWestendSender::get(), + ethereum_sovereign.clone(), TOKEN_AMOUNT, ); let token_id = TokenIdOf::convert_location(&asset_id_after_reanchored).unwrap(); - let asset: Asset = (asset_id_after_reanchored, TOKEN_AMOUNT).into(); let assets = vec![ // to pay fees - NativeTokenERC20 { token_id: WETH.into(), value: 2_000_000_000_000u128 }, + NativeTokenERC20 { token_id: WETH.into(), value: 3_000_000_000_000u128 }, // the token being transferred ForeignTokenERC20 { token_id: token_id.into(), value: TOKEN_AMOUNT }, ]; BridgeHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; - let instructions = vec![ - WithdrawAsset(asset.clone().into()), - DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, - ]; + let instructions = vec![DepositAsset { assets: Wild(AllCounted(2)), beneficiary }]; let xcm: Xcm<()> = instructions.into(); let versioned_message_xcm = VersionedXcm::V5(xcm); let origin = EthereumGatewayAddress::get(); let message = Message { origin, - fee: 1_500_000_000_000u128, + fee: 3_500_000_000_000u128, assets, xcm: versioned_message_xcm.encode(), claimer: Some(claimer_bytes), @@ -943,7 +945,6 @@ pub(crate) fn set_up_weth_and_dot_pool(asset: v5::Location) { }); } - pub(crate) fn set_up_weth_and_dot_pool_on_penpal(asset: v5::Location) { let wnd: v5::Location = v5::Parent.into(); let penpal_location = BridgeHubWestend::sibling_location_of(PenpalB::para_id()); @@ -956,8 +957,7 @@ pub(crate) fn set_up_weth_and_dot_pool_on_penpal(asset: v5::Location) { type RuntimeEvent = ::RuntimeEvent; let signed_owner = ::RuntimeOrigin::signed(owner.clone()); - let signed_bh_sovereign = - ::RuntimeOrigin::signed(bh_sovereign.clone()); + let signed_bh_sovereign = ::RuntimeOrigin::signed(bh_sovereign.clone()); assert_ok!(::ForeignAssets::mint( signed_bh_sovereign.clone(), 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 b46291f09e9e..b05fa357fb98 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 @@ -125,6 +125,8 @@ impl snowbridge_pallet_inbound_queue_v2::Config for Runtime { EthereumSystem, WethAddress, EthereumGatewayAddress, + EthereumUniversalLocation, + AssetHubFromEthereum, >; } From a35eee11d3bb6c48a54855e1bdb0b0504eeea81c Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Tue, 10 Dec 2024 11:48:33 +0200 Subject: [PATCH 48/52] adds tests, changes message format --- .../pallets/inbound-queue-v2/src/api.rs | 2 +- .../pallets/inbound-queue-v2/src/lib.rs | 6 +- .../pallets/inbound-queue-v2/src/mock.rs | 5 +- .../pallets/inbound-queue-v2/src/test.rs | 159 --------- .../primitives/router/src/inbound/v2.rs | 319 ++++++++++++++++-- .../src/tests/snowbridge.rs | 2 +- .../src/tests/snowbridge_v2.rs | 48 ++- 7 files changed, 334 insertions(+), 207 deletions(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs index beb96b1cb50d..8efc6eb2a280 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs @@ -15,7 +15,7 @@ where { // Convert message to XCM let dummy_origin = Location::new(0, AccountId32 { id: H256::zero().into(), network: None }); - let xcm = T::MessageConverter::convert(message, dummy_origin) + let (xcm, _) = T::MessageConverter::convert(message, dummy_origin) .map_err(|e| Error::::ConvertMessage(e))?; // Calculate fee. Consists of the cost of the "submit" extrinsic as well as the XCM execution diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index ffe21dfe4fd5..43c24a44b23d 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -226,10 +226,10 @@ pub mod pallet { let origin_account_location = Self::account_to_location(who)?; - let xcm = Self::do_convert(message, origin_account_location.clone())?; + let (xcm, _relayer_reward) = Self::do_convert(message, origin_account_location.clone())?; // Todo: Deposit fee(in Ether) to RewardLeger which should cover all of: - // T::RewardLeger::deposit(who, envelope.fee.into())?; + // T::RewardLeger::deposit(who, relayer_reward.into())?; // a. The submit extrinsic cost on BH // b. The delivery cost to AH // c. The execution cost on AH @@ -277,7 +277,7 @@ pub mod pallet { pub fn do_convert( message: MessageV2, origin_account_location: Location, - ) -> Result, Error> { + ) -> Result<(Xcm<()>, u128), Error> { Ok(T::MessageConverter::convert(message, origin_account_location) .map_err(|e| Error::::ConvertMessage(e))?) } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs index c981c99bf3aa..e96797fec96d 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs @@ -149,6 +149,9 @@ parameter_types! { pub const WethAddress: H160 = H160(WETH_ADDRESS); pub const InboundQueuePalletInstance: u8 = 84; pub AssetHubLocation: InteriorLocation = Parachain(1000).into(); + pub UniversalLocation: InteriorLocation = + [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)), Parachain(1002)].into(); + pub AssetHubFromEthereum: Location = Location::new(1,[GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)),Parachain(1000)]); } impl inbound_queue_v2::Config for Test { @@ -160,7 +163,7 @@ impl inbound_queue_v2::Config for Test { type GatewayAddress = GatewayAddress; type AssetHubParaId = ConstU32<1000>; type MessageConverter = - MessageToXcm; + MessageToXcm; type Token = Balances; type Balance = u128; #[cfg(feature = "runtime-benchmarks")] diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs index cdaf95e4b267..84b6a51e6c7f 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs @@ -126,162 +126,3 @@ fn test_set_operating_mode_root_only() { }); } -#[test] -fn test_send_native_erc20_token_payload() { - new_tester().execute_with(|| { - // To generate test data: forge test --match-test testSendEther -vvvv - let payload = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf0030ef7dba020000000000000000000004005615deb798bb3e4dfa0139dfa1b3d433cc23b72f0000b2d3595bf00600000000000000000000").to_vec(); - let message = MessageV2::decode(&mut payload.as_ref()); - assert_ok!(message.clone()); - - let inbound_message = message.unwrap(); - - let expected_origin: H160 = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf").into(); - let expected_token_id: H160 = hex!("5615deb798bb3e4dfa0139dfa1b3d433cc23b72f").into(); - let expected_value = 500000000000000000u128; - let expected_xcm: Vec = vec![]; - let expected_claimer: Option> = None; - - assert_eq!(expected_origin, inbound_message.origin); - assert_eq!(1, inbound_message.assets.len()); - if let Asset::NativeTokenERC20 { token_id, value } = &inbound_message.assets[0] { - assert_eq!(expected_token_id, *token_id); - assert_eq!(expected_value, *value); - } else { - panic!("Expected NativeTokenERC20 asset"); - } - assert_eq!(expected_xcm, inbound_message.xcm); - assert_eq!(expected_claimer, inbound_message.claimer); - }); -} - -#[test] -fn test_send_foreign_erc20_token_payload() { - new_tester().execute_with(|| { - let payload = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf0030ef7dba0200000000000000000000040197874824853fb4ad04794ccfd1cc8d2a7463839cfcbc6a315a1045c60ab85f400000b2d3595bf00600000000000000000000").to_vec(); - let message = MessageV2::decode(&mut payload.as_ref()); - assert_ok!(message.clone()); - - let inbound_message = message.unwrap(); - - let expected_fee = 3_000_000_000_000u128; - let expected_origin: H160 = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf").into(); - let expected_token_id: H256 = hex!("97874824853fb4ad04794ccfd1cc8d2a7463839cfcbc6a315a1045c60ab85f40").into(); - let expected_value = 500000000000000000u128; - let expected_xcm: Vec = vec![]; - let expected_claimer: Option> = None; - - assert_eq!(expected_origin, inbound_message.origin); - assert_eq!(expected_fee, inbound_message.fee); - assert_eq!(1, inbound_message.assets.len()); - if let Asset::ForeignTokenERC20 { token_id, value } = &inbound_message.assets[0] { - assert_eq!(expected_token_id, *token_id); - assert_eq!(expected_value, *value); - } else { - panic!("Expected ForeignTokenERC20 asset"); - } - assert_eq!(expected_xcm, inbound_message.xcm); - assert_eq!(expected_claimer, inbound_message.claimer); - }); -} - -#[test] -fn test_register_token_inbound_message_with_xcm_and_claimer() { - new_tester().execute_with(|| { - let payload = hex!("5991a2df15a8f6a256d3ec51e99254cd3fb576a90030ef7dba020000000000000000000004005615deb798bb3e4dfa0139dfa1b3d433cc23b72f00000000000000000000000000000000300508020401000002286bee0a015029e3b139f4393adda86303fcdaa35f60bb7092bf").to_vec(); - let message = MessageV2::decode(&mut payload.as_ref()); - assert_ok!(message.clone()); - - let inbound_message = message.unwrap(); - - let expected_origin: H160 = hex!("5991a2df15a8f6a256d3ec51e99254cd3fb576a9").into(); - let expected_token_id: H160 = hex!("5615deb798bb3e4dfa0139dfa1b3d433cc23b72f").into(); - let expected_value = 0u128; - let expected_xcm: Vec = hex!("0508020401000002286bee0a").to_vec(); - let expected_claimer: Option> = Some(hex!("29E3b139f4393aDda86303fcdAa35F60Bb7092bF").to_vec()); - - assert_eq!(expected_origin, inbound_message.origin); - assert_eq!(1, inbound_message.assets.len()); - if let Asset::NativeTokenERC20 { token_id, value } = &inbound_message.assets[0] { - assert_eq!(expected_token_id, *token_id); - assert_eq!(expected_value, *value); - } else { - panic!("Expected NativeTokenERC20 asset"); - } - assert_eq!(expected_xcm, inbound_message.xcm); - assert_eq!(expected_claimer, inbound_message.claimer); - - // decode xcm - let versioned_xcm = VersionedXcm::<()>::decode_with_depth_limit( - MAX_XCM_DECODE_DEPTH, - &mut inbound_message.xcm.as_ref(), - ); - - assert_ok!(versioned_xcm.clone()); - - // Check if decoding was successful - let decoded_instructions = match versioned_xcm.unwrap() { - VersionedXcm::V5(decoded) => decoded, - _ => { - panic!("unexpected xcm version found") - } - }; - - let mut decoded_instructions = decoded_instructions.into_iter(); - let decoded_first = decoded_instructions.next().take(); - assert!(decoded_first.is_some()); - let decoded_second = decoded_instructions.next().take(); - assert!(decoded_second.is_some()); - assert_eq!(ClearOrigin, decoded_second.unwrap(), "Second instruction (ClearOrigin) does not match."); - }); -} - -#[test] -fn encode_xcm() { - new_tester().execute_with(|| { - let total_fee_asset: xcm::opaque::latest::Asset = - (Location::parent(), 1_000_000_000).into(); - - let instructions: Xcm<()> = - vec![ReceiveTeleportedAsset(total_fee_asset.into()), ClearOrigin].into(); - - let versioned_xcm_message = VersionedXcm::V5(instructions.clone()); - - let xcm_bytes = VersionedXcm::encode(&versioned_xcm_message); - let hex_string = hex::encode(xcm_bytes.clone()); - - println!("xcm hex: {}", hex_string); - - let versioned_xcm = VersionedXcm::<()>::decode_with_depth_limit( - MAX_XCM_DECODE_DEPTH, - &mut xcm_bytes.as_ref(), - ); - - assert_ok!(versioned_xcm.clone()); - - // Check if decoding was successful - let decoded_instructions = match versioned_xcm.unwrap() { - VersionedXcm::V5(decoded) => decoded, - _ => { - panic!("unexpected xcm version found") - }, - }; - - let mut original_instructions = instructions.into_iter(); - let mut decoded_instructions = decoded_instructions.into_iter(); - - let original_first = original_instructions.next().take(); - let decoded_first = decoded_instructions.next().take(); - assert_eq!( - original_first, decoded_first, - "First instruction (ReceiveTeleportedAsset) does not match." - ); - - let original_second = original_instructions.next().take(); - let decoded_second = decoded_instructions.next().take(); - assert_eq!( - original_second, decoded_second, - "Second instruction (ClearOrigin) does not match." - ); - }); -} diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index 23b51eb4a5f7..45d6276b4764 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -23,14 +23,18 @@ const LOG_TARGET: &str = "snowbridge-router-primitives"; pub struct Message { /// The origin address pub origin: H160, - /// Fee in weth to cover the xcm execution on AH. - pub fee: u128, /// The assets pub assets: Vec, /// The command originating from the Gateway contract pub xcm: Vec, /// The claimer in the case that funds get trapped. pub claimer: Option>, + /// The full value of the assets. + pub value: u128, + /// Fee in eth to cover the xcm execution on AH. + pub execution_fee: u128, + /// Relayer reward in eth. Needs to cover all costs of sending a message. + pub relayer_fee: u128, } /// An asset that will be transacted on AH. The asset will be reserved/withdrawn and placed into @@ -53,7 +57,7 @@ pub enum Asset { } /// Reason why a message conversion failed. -#[derive(Copy, Clone, TypeInfo, PalletError, Encode, Decode, RuntimeDebug)] +#[derive(Copy, Clone, TypeInfo, PalletError, Encode, Decode, RuntimeDebug, PartialEq)] pub enum ConvertMessageError { /// Invalid foreign ERC-20 token ID InvalidAsset, @@ -62,7 +66,10 @@ pub enum ConvertMessageError { } pub trait ConvertMessage { - fn convert(message: Message, origin_account: Location) -> Result, ConvertMessageError>; + fn convert( + message: Message, + origin_account: Location, + ) -> Result<(Xcm<()>, u128), ConvertMessageError>; } pub struct MessageToXcm< @@ -123,7 +130,7 @@ where fn convert( message: Message, origin_account_location: Location, - ) -> Result, ConvertMessageError> { + ) -> Result<(Xcm<()>, u128), ConvertMessageError> { let mut message_xcm: Xcm<()> = Xcm::new(); if message.xcm.len() > 0 { // Allow xcm decode failure so that assets can be trapped on AH instead of this @@ -150,7 +157,7 @@ where AccountKey20 { network: None, key: WethAddress::get().into() }, ], ); - let fee: XcmAsset = (fee_asset.clone(), message.fee).into(); + let fee: XcmAsset = (fee_asset.clone(), message.execution_fee).into(); let mut instructions = vec![ DescendOrigin(PalletInstance(InboundQueuePalletInstance::get()).into()), UniversalOrigin(GlobalConsensus(network)), @@ -228,27 +235,35 @@ where instructions.extend(appendix); - Ok(instructions.into()) + Ok((instructions.into(), message.relayer_fee)) } } #[cfg(test)] mod tests { - use crate::inbound::v2::{ConvertMessage, Message, MessageToXcm}; - use codec::Decode; - use frame_support::{assert_ok, parameter_types}; + use crate::inbound::v2::{ + Asset::{ForeignTokenERC20, NativeTokenERC20}, + ConvertMessage, ConvertMessageError, Message, MessageToXcm, + }; + use codec::Encode; + use frame_support::{assert_err, assert_ok, parameter_types}; use hex_literal::hex; - use sp_core::H256; - use sp_runtime::traits::{ConstU128, ConstU8}; - use xcm::prelude::*; - use snowbridge_core::TokenId; + use sp_core::{H160, H256}; use sp_runtime::traits::MaybeEquivalence; - - const NETWORK: NetworkId = Ethereum { chain_id: 11155111 }; + use xcm::{opaque::latest::WESTEND_GENESIS_HASH, prelude::*}; + const GATEWAY_ADDRESS: [u8; 20] = hex!["eda338e4dc46038493b885327842fd3e301cab39"]; + const WETH_ADDRESS: [u8; 20] = hex!["fff9976782d46cc05630d1f6ebab18b2324d6b14"]; parameter_types! { - pub EthereumNetwork: NetworkId = NETWORK; + pub const EthereumNetwork: xcm::v5::NetworkId = xcm::v5::NetworkId::Ethereum { chain_id: 11155111 }; + pub const GatewayAddress: H160 = H160(GATEWAY_ADDRESS); + pub const WethAddress: H160 = H160(WETH_ADDRESS); + pub const InboundQueuePalletInstance: u8 = 84; + pub AssetHubLocation: InteriorLocation = Parachain(1000).into(); + pub UniversalLocation: InteriorLocation = + [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)), Parachain(1002)].into(); + pub AssetHubFromEthereum: Location = Location::new(1,[GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)),Parachain(1000)]); } pub struct MockTokenIdConvert; @@ -261,21 +276,273 @@ mod tests { } } + pub struct MockFailedTokenConvert; + impl MaybeEquivalence for MockFailedTokenConvert { + fn convert(_id: &TokenId) -> Option { + None + } + fn convert_back(_loc: &Location) -> Option { + None + } + } + #[test] - fn convert_message() { - let payload = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf040197874824853fb4ad04794ccfd1cc8d2a7463839cfcbc6a315a1045c60ab85f400000b2d3595bf00600000000000000000000").to_vec(); + fn test_successful_message() { let origin_account = - Location::new(0, AccountId32 { id: H256::random().into(), network: None }); + Location::new(0, [AccountId32 { network: None, id: H256::random().into() }]); + let origin: H160 = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf").into(); + let native_token_id: H160 = hex!("5615deb798bb3e4dfa0139dfa1b3d433cc23b72f").into(); + let foreign_token_id: H256 = + hex!("37a6c666da38711a963d938eafdd09314fd3f95a96a3baffb55f26560f4ecdd8").into(); + let beneficiary = + hex!("908783d8cd24c9e02cee1d26ab9c46d458621ad0150b626c536a40b9df3f09c6").into(); + let message_id: H256 = + hex!("8b69c7e376e28114618e829a7ec768dbda28357d359ba417a3bd79b11215059d").into(); + let token_value = 3_000_000_000_000u128; + let assets = vec![ + NativeTokenERC20 { token_id: native_token_id, value: token_value }, + ForeignTokenERC20 { token_id: foreign_token_id, value: token_value }, + ]; + let instructions = vec![ + DepositAsset { assets: Wild(AllCounted(1).into()), beneficiary }, + SetTopic(message_id.into()), + ]; + let xcm: Xcm<()> = instructions.into(); + let versioned_xcm = VersionedXcm::V5(xcm); + let claimer_account = AccountId32 { network: None, id: H256::random().into() }; + let claimer: Option> = Some(claimer_account.clone().encode()); + let value = 6_000_000_000_000u128; + let execution_fee = 1_000_000_000_000u128; + let relayer_fee = 5_000_000_000_000u128; - let message = Message::decode(&mut payload.as_ref()); - assert_ok!(message.clone()); + let message = Message { + origin: origin.clone(), + assets, + xcm: versioned_xcm.encode(), + claimer, + value, + execution_fee, + relayer_fee, + }; let result = MessageToXcm::< EthereumNetwork, - ConstU8<80>, + InboundQueuePalletInstance, MockTokenIdConvert, - ConstU128<1_000_000_000_000>, - >::convert(message.unwrap(), origin_account); - assert_ok!(result); + WethAddress, + GatewayAddress, + UniversalLocation, + AssetHubFromEthereum, + >::convert(message, origin_account); + + assert_ok!(result.clone()); + + let (xcm, _) = result.unwrap(); + + let mut instructions = xcm.into_iter(); + + let mut asset_claimer_found = false; + let mut commands_found = 0; + while let Some(instruction) = instructions.next() { + if let SetAssetClaimer { ref location } = instruction { + assert_eq!(Location::new(0, [claimer_account]), location.clone()); + asset_claimer_found = true; + } + if let DescendOrigin(ref location) = instruction { + commands_found = commands_found + 1; + if commands_found == 2 { + let junctions: Junctions = + AccountKey20 { key: origin.into(), network: None }.into(); + assert_eq!(junctions, location.clone()); + } + } + } + // SetAssetClaimer must be in the message. + assert!(asset_claimer_found); + // The first DescendOrigin to descend into the InboundV2 pallet index and the DescendOrigin + // into the message.origin + assert!(commands_found == 2); + } + + #[test] + fn test_invalid_foreign_erc20() { + let origin_account = + Location::new(0, [AccountId32 { network: None, id: H256::random().into() }]); + let origin: H160 = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf").into(); + let token_id: H256 = + hex!("37a6c666da38711a963d938eafdd09314fd3f95a96a3baffb55f26560f4ecdd8").into(); + let beneficiary = + hex!("908783d8cd24c9e02cee1d26ab9c46d458621ad0150b626c536a40b9df3f09c6").into(); + let message_id: H256 = + hex!("8b69c7e376e28114618e829a7ec768dbda28357d359ba417a3bd79b11215059d").into(); + let token_value = 3_000_000_000_000u128; + let assets = vec![ForeignTokenERC20 { token_id, value: token_value }]; + let instructions = vec![ + DepositAsset { assets: Wild(AllCounted(1).into()), beneficiary }, + SetTopic(message_id.into()), + ]; + let xcm: Xcm<()> = instructions.into(); + let versioned_xcm = VersionedXcm::V5(xcm); + let claimer_account = AccountId32 { network: None, id: H256::random().into() }; + let claimer: Option> = Some(claimer_account.clone().encode()); + let value = 6_000_000_000_000u128; + let execution_fee = 1_000_000_000_000u128; + let relayer_fee = 5_000_000_000_000u128; + + let message = Message { + origin, + assets, + xcm: versioned_xcm.encode(), + claimer, + value, + execution_fee, + relayer_fee, + }; + + let result = MessageToXcm::< + EthereumNetwork, + InboundQueuePalletInstance, + MockFailedTokenConvert, + WethAddress, + GatewayAddress, + UniversalLocation, + AssetHubFromEthereum, + >::convert(message, origin_account); + + assert_err!(result.clone(), ConvertMessageError::InvalidAsset); + } + + #[test] + fn test_invalid_claimer() { + let origin_account = + Location::new(0, [AccountId32 { network: None, id: H256::random().into() }]); + let origin: H160 = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf").into(); + let token_id: H256 = + hex!("37a6c666da38711a963d938eafdd09314fd3f95a96a3baffb55f26560f4ecdd8").into(); + let beneficiary = + hex!("908783d8cd24c9e02cee1d26ab9c46d458621ad0150b626c536a40b9df3f09c6").into(); + let message_id: H256 = + hex!("8b69c7e376e28114618e829a7ec768dbda28357d359ba417a3bd79b11215059d").into(); + let token_value = 3_000_000_000_000u128; + let assets = vec![ForeignTokenERC20 { token_id, value: token_value }]; + let instructions = vec![ + DepositAsset { assets: Wild(AllCounted(1).into()), beneficiary }, + SetTopic(message_id.into()), + ]; + let xcm: Xcm<()> = instructions.into(); + let versioned_xcm = VersionedXcm::V5(xcm); + // Invalid claimer location, cannot be decoded into a Junction + let claimer: Option> = + Some(hex!("43581a7d43757158624921ab0e9e112a1d7da93cbe64782d563e8e1144a06c3c").to_vec()); + let value = 6_000_000_000_000u128; + let execution_fee = 1_000_000_000_000u128; + let relayer_fee = 5_000_000_000_000u128; + + let message = Message { + origin, + assets, + xcm: versioned_xcm.encode(), + claimer, + value, + execution_fee, + relayer_fee, + }; + + let result = MessageToXcm::< + EthereumNetwork, + InboundQueuePalletInstance, + MockTokenIdConvert, + WethAddress, + GatewayAddress, + UniversalLocation, + AssetHubFromEthereum, + >::convert(message, origin_account.clone()); + + // Invalid claimer does not break the message conversion + assert_ok!(result.clone()); + + let (xcm, _) = result.unwrap(); + + let mut result_instructions = xcm.clone().into_iter(); + + let mut found = false; + while let Some(instruction) = result_instructions.next() { + if let SetAssetClaimer { .. } = instruction { + found = true; + break; + } + } + // SetAssetClaimer should not be in the message. + assert!(!found); + + // Find the last two instructions to check the appendix is correct. + let mut second_last = None; + let mut last = None; + + for instruction in xcm.into_iter() { + second_last = last; + last = Some(instruction); + } + + // Check if both instructions are found + assert!(last.is_some()); + assert!(second_last.is_some()); + + let fee_asset = Location::new( + 2, + [ + GlobalConsensus(EthereumNetwork::get()), + AccountKey20 { network: None, key: WethAddress::get().into() }, + ], + ); + assert_eq!( + last, + Some(DepositAsset { + assets: Wild(AllOf { id: AssetId(fee_asset), fun: WildFungibility::Fungible }), + // beneficiary is the relayer + beneficiary: origin_account + }) + ); + } + + #[test] + fn test_invalid_xcm() { + let origin_account = + Location::new(0, [AccountId32 { network: None, id: H256::random().into() }]); + let origin: H160 = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf").into(); + let token_id: H256 = + hex!("37a6c666da38711a963d938eafdd09314fd3f95a96a3baffb55f26560f4ecdd8").into(); + let token_value = 3_000_000_000_000u128; + let assets = vec![ForeignTokenERC20 { token_id, value: token_value }]; + // invalid xcm + let versioned_xcm = hex!("8b69c7e376e28114618e829a7ec7").to_vec(); + let claimer_account = AccountId32 { network: None, id: H256::random().into() }; + let claimer: Option> = Some(claimer_account.clone().encode()); + let value = 6_000_000_000_000u128; + let execution_fee = 1_000_000_000_000u128; + let relayer_fee = 5_000_000_000_000u128; + + let message = Message { + origin, + assets, + xcm: versioned_xcm, + claimer: Some(claimer.encode()), + value, + execution_fee, + relayer_fee, + }; + + let result = MessageToXcm::< + EthereumNetwork, + InboundQueuePalletInstance, + MockTokenIdConvert, + WethAddress, + GatewayAddress, + UniversalLocation, + AssetHubFromEthereum, + >::convert(message, origin_account.clone()); + + // Invalid xcm does not break the message, allowing funds to be trapped on AH. + assert_ok!(result.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 48eeb07a7c6a..c5926913bd70 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 @@ -37,7 +37,7 @@ const ETHEREUM_DESTINATION_ADDRESS: [u8; 20] = hex!("44a57ee2f2FCcb85FDa2B0B18EB const XCM_FEE: u128 = 100_000_000_000; const TOKEN_AMOUNT: u128 = 100_000_000_000; -#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeIfmtnfo)] +#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)] pub enum ControlCall { #[codec(index = 3)] CreateAgent, 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 6bc41f212b6e..101af63adfca 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 @@ -119,12 +119,14 @@ fn register_token_v2() { let message = Message { origin, - fee: 1_500_000_000_000u128, assets, xcm: versioned_message_xcm.encode(), claimer: Some(claimer_bytes), + value: 3_500_000_000_000u128, + execution_fee: 1_500_000_000_000u128, + relayer_fee: 1_500_000_000_000u128, }; - let xcm = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); + let (xcm, _) = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); let _ = EthereumInboundQueueV2::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); assert_expected_events!( @@ -189,13 +191,15 @@ fn send_token_v2() { let message = Message { origin, - fee: 1_500_000_000_000u128, assets, xcm: versioned_message_xcm.encode(), claimer: Some(claimer_bytes), + value: 3_500_000_000_000u128, + execution_fee: 1_500_000_000_000u128, + relayer_fee: 1_500_000_000_000u128, }; - let xcm = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); + let (xcm, _) = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); let _ = EthereumInboundQueueV2::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); assert_expected_events!( @@ -265,13 +269,15 @@ fn send_weth_v2() { let message = Message { origin, - fee: 1_500_000_000_000u128, assets, xcm: versioned_message_xcm.encode(), claimer: Some(claimer_bytes), + value: 3_500_000_000_000u128, + execution_fee: 1_500_000_000_000u128, + relayer_fee: 1_500_000_000_000u128, }; - let xcm = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); + let (xcm, _) = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); let _ = EthereumInboundQueueV2::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); assert_expected_events!( @@ -394,13 +400,15 @@ fn register_and_send_multiple_tokens_v2() { let message = Message { origin, - fee: 1_500_000_000_000u128, assets, xcm: versioned_message_xcm.encode(), claimer: Some(claimer_bytes), + value: 3_500_000_000_000u128, + execution_fee: 1_500_000_000_000u128, + relayer_fee: 1_500_000_000_000u128, }; - let xcm = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); + let (xcm, _) = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); let _ = EthereumInboundQueueV2::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); assert_expected_events!( @@ -556,13 +564,15 @@ fn send_token_to_penpal_v2() { let message = Message { origin, - fee: 1_000_000_000_000u128, assets, xcm: versioned_message_xcm.encode(), claimer: Some(claimer_bytes), + value: 3_500_000_000_000u128, + execution_fee: 1_500_000_000_000u128, + relayer_fee: 1_500_000_000_000u128, }; - let xcm = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); + let (xcm, _) = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); let _ = EthereumInboundQueueV2::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); assert_expected_events!( @@ -684,13 +694,15 @@ fn send_foreign_erc20_token_back_to_polkadot() { let message = Message { origin, - fee: 3_500_000_000_000u128, assets, xcm: versioned_message_xcm.encode(), claimer: Some(claimer_bytes), + value: 1_500_000_000_000u128, + execution_fee: 3_500_000_000_000u128, + relayer_fee: 1_500_000_000_000u128, }; - let xcm = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); + let (xcm, _) = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); let _ = EthereumInboundQueueV2::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); assert_expected_events!( @@ -766,13 +778,15 @@ fn invalid_xcm_traps_funds_on_ah() { let message = Message { origin, - fee: 1_500_000_000_000u128, assets, xcm: instructions.to_vec(), claimer: Some(claimer_bytes), + value: 1_500_000_000_000u128, + execution_fee: 1_500_000_000_000u128, + relayer_fee: 1_500_000_000_000u128, }; - let xcm = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); + let (xcm, _) = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); let _ = EthereumInboundQueueV2::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); assert_expected_events!( @@ -826,14 +840,16 @@ fn invalid_claimer_does_not_fail_the_message() { let message = Message { origin, - fee: 1_500_000_000_000u128, assets, xcm: versioned_message_xcm.encode(), // Set an invalid claimer claimer: Some(hex!("2b7ce7bc7e87e4d6619da21487c7a53f").to_vec()), + value: 1_500_000_000_000u128, + execution_fee: 1_500_000_000_000u128, + relayer_fee: 1_500_000_000_000u128, }; - let xcm = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); + let (xcm, _) = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); let _ = EthereumInboundQueueV2::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); assert_expected_events!( From 6208dfdfd28caed20b38ee3c669cf7ea2cb02a9f Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Tue, 10 Dec 2024 11:49:05 +0200 Subject: [PATCH 49/52] fmt --- bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs | 3 ++- .../snowbridge/pallets/inbound-queue-v2/src/mock.rs | 10 ++++++++-- .../snowbridge/pallets/inbound-queue-v2/src/test.rs | 1 - 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index 43c24a44b23d..7320bf9188a6 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -226,7 +226,8 @@ pub mod pallet { let origin_account_location = Self::account_to_location(who)?; - let (xcm, _relayer_reward) = Self::do_convert(message, origin_account_location.clone())?; + let (xcm, _relayer_reward) = + Self::do_convert(message, origin_account_location.clone())?; // Todo: Deposit fee(in Ether) to RewardLeger which should cover all of: // T::RewardLeger::deposit(who, relayer_reward.into())?; diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs index e96797fec96d..bdd2f3ea9bd0 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs @@ -162,8 +162,14 @@ impl inbound_queue_v2::Config for Test { type WeightToFee = IdentityFee; type GatewayAddress = GatewayAddress; type AssetHubParaId = ConstU32<1000>; - type MessageConverter = - MessageToXcm; + type MessageConverter = MessageToXcm< + EthereumNetwork, + InboundQueuePalletInstance, + MockTokenIdConvert, + WethAddress, + UniversalLocation, + AssetHubFromEthereum, + >; type Token = Balances; type Balance = u128; #[cfg(feature = "runtime-benchmarks")] diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs index 84b6a51e6c7f..a272cbf525fa 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs @@ -125,4 +125,3 @@ fn test_set_operating_mode_root_only() { ); }); } - From d0624e2b8c97f06125325375805385cf7fc36085 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Tue, 10 Dec 2024 12:46:30 +0200 Subject: [PATCH 50/52] fmt --- .../primitives/router/src/inbound/v2.rs | 131 +++++++++++++++++- 1 file changed, 126 insertions(+), 5 deletions(-) diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index 45d6276b4764..28452f3d99b1 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -243,7 +243,7 @@ where mod tests { use crate::inbound::v2::{ Asset::{ForeignTokenERC20, NativeTokenERC20}, - ConvertMessage, ConvertMessageError, Message, MessageToXcm, + ConvertMessage, ConvertMessageError, Message, MessageToXcm, XcmAsset, }; use codec::Encode; use frame_support::{assert_err, assert_ok, parameter_types}; @@ -342,26 +342,147 @@ mod tests { let mut instructions = xcm.into_iter(); let mut asset_claimer_found = false; - let mut commands_found = 0; + let mut pay_fees_found = false; + let mut descend_origin_found = 0; + let mut reserve_deposited_found = 0; + let mut withdraw_assets_found = 0; while let Some(instruction) = instructions.next() { if let SetAssetClaimer { ref location } = instruction { assert_eq!(Location::new(0, [claimer_account]), location.clone()); asset_claimer_found = true; } if let DescendOrigin(ref location) = instruction { - commands_found = commands_found + 1; - if commands_found == 2 { + descend_origin_found = descend_origin_found + 1; + // The second DescendOrigin should be the message.origin (sender) + if descend_origin_found == 2 { let junctions: Junctions = AccountKey20 { key: origin.into(), network: None }.into(); assert_eq!(junctions, location.clone()); } } + if let PayFees { ref asset } = instruction { + let fee_asset = Location::new( + 2, + [ + GlobalConsensus(EthereumNetwork::get()), + AccountKey20 { network: None, key: WethAddress::get().into() }, + ], + ); + assert_eq!(asset.id, AssetId(fee_asset)); + assert_eq!(asset.fun, Fungible(execution_fee)); + pay_fees_found = true; + } + if let ReserveAssetDeposited(ref reserve_assets) = instruction { + reserve_deposited_found = reserve_deposited_found + 1; + if reserve_deposited_found == 1 { + let fee_asset = Location::new( + 2, + [ + GlobalConsensus(EthereumNetwork::get()), + AccountKey20 { network: None, key: WethAddress::get().into() }, + ], + ); + let fee: XcmAsset = (fee_asset, execution_fee).into(); + let fee_assets: Assets = fee.into(); + assert_eq!(fee_assets, reserve_assets.clone()); + } + if reserve_deposited_found == 2 { + let token_asset = Location::new( + 2, + [ + GlobalConsensus(EthereumNetwork::get()), + AccountKey20 { network: None, key: native_token_id.into() }, + ], + ); + let token: XcmAsset = (token_asset, token_value).into(); + let token_assets: Assets = token.into(); + assert_eq!(token_assets, reserve_assets.clone()); + } + } + if let WithdrawAsset(ref withdraw_assets) = instruction { + withdraw_assets_found = withdraw_assets_found + 1; + let token_asset = Location::new(2, Here); + let token: XcmAsset = (token_asset, token_value).into(); + let token_assets: Assets = token.into(); + assert_eq!(token_assets, withdraw_assets.clone()); + } } // SetAssetClaimer must be in the message. assert!(asset_claimer_found); + // PayFees must be in the message. + assert!(pay_fees_found); // The first DescendOrigin to descend into the InboundV2 pallet index and the DescendOrigin // into the message.origin - assert!(commands_found == 2); + assert!(descend_origin_found == 2); + // Expecting two ReserveAssetDeposited instructions, one for the fee and one for the token + // being transferred. + assert!(reserve_deposited_found == 2); + // Expecting one WithdrawAsset for the foreign ERC-20 + assert!(withdraw_assets_found == 1); + } + + #[test] + fn test_message_with_gateway_origin_does_not_descend_origin_into_sender() { + let origin_account = + Location::new(0, [AccountId32 { network: None, id: H256::random().into() }]); + let origin: H160 = GatewayAddress::get(); + let native_token_id: H160 = hex!("5615deb798bb3e4dfa0139dfa1b3d433cc23b72f").into(); + let foreign_token_id: H256 = + hex!("37a6c666da38711a963d938eafdd09314fd3f95a96a3baffb55f26560f4ecdd8").into(); + let beneficiary = + hex!("908783d8cd24c9e02cee1d26ab9c46d458621ad0150b626c536a40b9df3f09c6").into(); + let message_id: H256 = + hex!("8b69c7e376e28114618e829a7ec768dbda28357d359ba417a3bd79b11215059d").into(); + let token_value = 3_000_000_000_000u128; + let assets = vec![ + NativeTokenERC20 { token_id: native_token_id, value: token_value }, + ForeignTokenERC20 { token_id: foreign_token_id, value: token_value }, + ]; + let instructions = vec![ + DepositAsset { assets: Wild(AllCounted(1).into()), beneficiary }, + SetTopic(message_id.into()), + ]; + let xcm: Xcm<()> = instructions.into(); + let versioned_xcm = VersionedXcm::V5(xcm); + let claimer_account = AccountId32 { network: None, id: H256::random().into() }; + let claimer: Option> = Some(claimer_account.clone().encode()); + let value = 6_000_000_000_000u128; + let execution_fee = 1_000_000_000_000u128; + let relayer_fee = 5_000_000_000_000u128; + + let message = Message { + origin: origin.clone(), + assets, + xcm: versioned_xcm.encode(), + claimer, + value, + execution_fee, + relayer_fee, + }; + + let result = MessageToXcm::< + EthereumNetwork, + InboundQueuePalletInstance, + MockTokenIdConvert, + WethAddress, + GatewayAddress, + UniversalLocation, + AssetHubFromEthereum, + >::convert(message, origin_account); + + assert_ok!(result.clone()); + + let (xcm, _) = result.unwrap(); + + let mut instructions = xcm.into_iter(); + let mut commands_found = 0; + while let Some(instruction) = instructions.next() { + if let DescendOrigin(ref _location) = instruction { + commands_found = commands_found + 1; + } + } + // There should only be 1 DescendOrigin in the message. + assert!(commands_found == 1); } #[test] From c66fcb1d8b0de015f497d8f3dc7fe11c3368dc26 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Tue, 10 Dec 2024 13:57:10 +0200 Subject: [PATCH 51/52] change fee asset from weth to eth --- .../pallets/inbound-queue-v2/src/mock.rs | 65 +----------- .../pallets/inbound-queue-v2/src/test.rs | 63 +----------- .../primitives/router/src/inbound/v2.rs | 51 ++-------- .../src/tests/snowbridge_v2.rs | 99 +++++++++---------- .../src/bridge_to_ethereum_config.rs | 1 - 5 files changed, 57 insertions(+), 222 deletions(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs index bdd2f3ea9bd0..974142553aaf 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs @@ -20,7 +20,7 @@ use sp_runtime::{ }; use sp_std::{convert::From, default::Default}; use xcm::{latest::SendXcm, prelude::*}; -use xcm_executor::{traits::TransactAsset, AssetsInHolding}; +use xcm::opaque::latest::WESTEND_GENESIS_HASH; type Block = frame_system::mocking::MockBlock; @@ -100,7 +100,6 @@ impl Verifier for MockVerifier { } const GATEWAY_ADDRESS: [u8; 20] = hex!["eda338e4dc46038493b885327842fd3e301cab39"]; -const WETH_ADDRESS: [u8; 20] = hex!["fff9976782d46cc05630d1f6ebab18b2324d6b14"]; #[cfg(feature = "runtime-benchmarks")] impl BenchmarkHelper for Test { @@ -146,7 +145,6 @@ impl MaybeEquivalence for MockTokenIdConvert { parameter_types! { pub const EthereumNetwork: xcm::v5::NetworkId = xcm::v5::NetworkId::Ethereum { chain_id: 11155111 }; pub const GatewayAddress: H160 = H160(GATEWAY_ADDRESS); - pub const WethAddress: H160 = H160(WETH_ADDRESS); pub const InboundQueuePalletInstance: u8 = 84; pub AssetHubLocation: InteriorLocation = Parachain(1000).into(); pub UniversalLocation: InteriorLocation = @@ -166,7 +164,7 @@ impl inbound_queue_v2::Config for Test { EthereumNetwork, InboundQueuePalletInstance, MockTokenIdConvert, - WethAddress, + GatewayAddress, UniversalLocation, AssetHubFromEthereum, >; @@ -176,52 +174,6 @@ impl inbound_queue_v2::Config for Test { type Helper = Test; } -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 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); } @@ -253,19 +205,6 @@ pub fn mock_event_log() -> Log { } } -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 diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs index a272cbf525fa..db321576917e 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs @@ -3,48 +3,11 @@ use super::*; use frame_support::{assert_noop, assert_ok}; -use hex_literal::hex; use snowbridge_core::inbound::Proof; use sp_keyring::AccountKeyring as Keyring; use sp_runtime::DispatchError; -use crate::{mock::*, Error, Event as InboundQueueEvent}; -use codec::DecodeLimit; -use snowbridge_router_primitives::inbound::v2::Asset; -use sp_core::H256; -use xcm::{ - opaque::latest::prelude::{ClearOrigin, ReceiveTeleportedAsset}, - prelude::*, - VersionedXcm, MAX_XCM_DECODE_DEPTH, -}; - -#[test] -fn test_submit_happy_path() { - new_tester().execute_with(|| { - let relayer: AccountId = Keyring::Bob.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(), - }, - }; - - assert_ok!(InboundQueue::submit(origin.clone(), message.clone())); - expect_events(vec![InboundQueueEvent::MessageReceived { - 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, - ], - } - .into()]); - }); -} +use crate::{mock::*, Error}; #[test] fn test_submit_with_invalid_gateway() { @@ -67,30 +30,6 @@ fn test_submit_with_invalid_gateway() { }); } -#[test] -fn test_submit_with_invalid_nonce() { - new_tester().execute_with(|| { - let relayer: AccountId = Keyring::Bob.into(); - let origin = RuntimeOrigin::signed(relayer); - - // 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())); - - // Submit the same again - assert_noop!( - InboundQueue::submit(origin.clone(), message.clone()), - Error::::InvalidNonce - ); - }); -} - #[test] fn test_set_operating_mode() { new_tester().execute_with(|| { diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index 28452f3d99b1..2defe5166bcf 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -76,7 +76,6 @@ pub struct MessageToXcm< EthereumNetwork, InboundQueuePalletInstance, ConvertAssetId, - WethAddress, GatewayProxyAddress, EthereumUniversalLocation, GlobalAssetHubLocation, @@ -84,7 +83,6 @@ pub struct MessageToXcm< EthereumNetwork: Get, InboundQueuePalletInstance: Get, ConvertAssetId: MaybeEquivalence, - WethAddress: Get, GatewayProxyAddress: Get, EthereumUniversalLocation: Get, GlobalAssetHubLocation: Get, @@ -93,7 +91,6 @@ pub struct MessageToXcm< EthereumNetwork, InboundQueuePalletInstance, ConvertAssetId, - WethAddress, GatewayProxyAddress, EthereumUniversalLocation, GlobalAssetHubLocation, @@ -104,7 +101,6 @@ impl< EthereumNetwork, InboundQueuePalletInstance, ConvertAssetId, - WethAddress, GatewayProxyAddress, EthereumUniversalLocation, GlobalAssetHubLocation, @@ -113,7 +109,6 @@ impl< EthereumNetwork, InboundQueuePalletInstance, ConvertAssetId, - WethAddress, GatewayProxyAddress, EthereumUniversalLocation, GlobalAssetHubLocation, @@ -122,7 +117,6 @@ where EthereumNetwork: Get, InboundQueuePalletInstance: Get, ConvertAssetId: MaybeEquivalence, - WethAddress: Get, GatewayProxyAddress: Get, EthereumUniversalLocation: Get, GlobalAssetHubLocation: Get, @@ -149,19 +143,14 @@ where let network = EthereumNetwork::get(); - // use weth as asset - let fee_asset = Location::new( - 2, - [ - GlobalConsensus(EthereumNetwork::get()), - AccountKey20 { network: None, key: WethAddress::get().into() }, - ], - ); + // use eth as asset + let fee_asset = Location::new(2, [GlobalConsensus(EthereumNetwork::get())]); let fee: XcmAsset = (fee_asset.clone(), message.execution_fee).into(); + let eth: XcmAsset = (fee_asset.clone(), message.execution_fee.saturating_add(message.value)).into(); let mut instructions = vec![ DescendOrigin(PalletInstance(InboundQueuePalletInstance::get()).into()), UniversalOrigin(GlobalConsensus(network)), - ReserveAssetDeposited(fee.clone().into()), + ReserveAssetDeposited(eth.into()), PayFees { asset: fee }, ]; let mut reserve_assets = vec![]; @@ -253,12 +242,9 @@ mod tests { use sp_runtime::traits::MaybeEquivalence; use xcm::{opaque::latest::WESTEND_GENESIS_HASH, prelude::*}; const GATEWAY_ADDRESS: [u8; 20] = hex!["eda338e4dc46038493b885327842fd3e301cab39"]; - const WETH_ADDRESS: [u8; 20] = hex!["fff9976782d46cc05630d1f6ebab18b2324d6b14"]; - parameter_types! { pub const EthereumNetwork: xcm::v5::NetworkId = xcm::v5::NetworkId::Ethereum { chain_id: 11155111 }; pub const GatewayAddress: H160 = H160(GATEWAY_ADDRESS); - pub const WethAddress: H160 = H160(WETH_ADDRESS); pub const InboundQueuePalletInstance: u8 = 84; pub AssetHubLocation: InteriorLocation = Parachain(1000).into(); pub UniversalLocation: InteriorLocation = @@ -329,7 +315,6 @@ mod tests { EthereumNetwork, InboundQueuePalletInstance, MockTokenIdConvert, - WethAddress, GatewayAddress, UniversalLocation, AssetHubFromEthereum, @@ -361,13 +346,7 @@ mod tests { } } if let PayFees { ref asset } = instruction { - let fee_asset = Location::new( - 2, - [ - GlobalConsensus(EthereumNetwork::get()), - AccountKey20 { network: None, key: WethAddress::get().into() }, - ], - ); + let fee_asset = Location::new(2, [GlobalConsensus(EthereumNetwork::get())]); assert_eq!(asset.id, AssetId(fee_asset)); assert_eq!(asset.fun, Fungible(execution_fee)); pay_fees_found = true; @@ -375,13 +354,7 @@ mod tests { if let ReserveAssetDeposited(ref reserve_assets) = instruction { reserve_deposited_found = reserve_deposited_found + 1; if reserve_deposited_found == 1 { - let fee_asset = Location::new( - 2, - [ - GlobalConsensus(EthereumNetwork::get()), - AccountKey20 { network: None, key: WethAddress::get().into() }, - ], - ); + let fee_asset = Location::new(2, [GlobalConsensus(EthereumNetwork::get())]); let fee: XcmAsset = (fee_asset, execution_fee).into(); let fee_assets: Assets = fee.into(); assert_eq!(fee_assets, reserve_assets.clone()); @@ -464,7 +437,6 @@ mod tests { EthereumNetwork, InboundQueuePalletInstance, MockTokenIdConvert, - WethAddress, GatewayAddress, UniversalLocation, AssetHubFromEthereum, @@ -524,7 +496,6 @@ mod tests { EthereumNetwork, InboundQueuePalletInstance, MockFailedTokenConvert, - WethAddress, GatewayAddress, UniversalLocation, AssetHubFromEthereum, @@ -573,7 +544,6 @@ mod tests { EthereumNetwork, InboundQueuePalletInstance, MockTokenIdConvert, - WethAddress, GatewayAddress, UniversalLocation, AssetHubFromEthereum, @@ -609,13 +579,7 @@ mod tests { assert!(last.is_some()); assert!(second_last.is_some()); - let fee_asset = Location::new( - 2, - [ - GlobalConsensus(EthereumNetwork::get()), - AccountKey20 { network: None, key: WethAddress::get().into() }, - ], - ); + let fee_asset = Location::new(2, [GlobalConsensus(EthereumNetwork::get())]); assert_eq!( last, Some(DepositAsset { @@ -657,7 +621,6 @@ mod tests { EthereumNetwork, InboundQueuePalletInstance, MockTokenIdConvert, - WethAddress, GatewayAddress, UniversalLocation, AssetHubFromEthereum, 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 101af63adfca..6fb1f0354bce 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 @@ -43,6 +43,10 @@ const WETH: [u8; 20] = hex!("fff9976782d46cc05630d1f6ebab18b2324d6b14"); const TOKEN_ID: [u8; 20] = hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"); const CHAIN_ID: u64 = 11155111u64; +pub fn eth_location() -> Location { + Location::new(2, [GlobalConsensus(EthereumNetwork::get().into())]) +} + pub fn weth_location() -> Location { erc20_token_location(WETH.into()) } @@ -63,9 +67,9 @@ fn register_token_v2() { let receiver = AssetHubWestendReceiver::get(); BridgeHubWestend::fund_accounts(vec![(relayer.clone(), INITIAL_FUND)]); - register_foreign_asset(weth_location()); + register_foreign_asset(eth_location()); - set_up_weth_and_dot_pool(weth_location()); + set_up_eth_and_dot_pool(eth_location()); let claimer = AccountId32 { network: None, id: receiver.clone().into() }; let claimer_bytes = claimer.encode(); @@ -81,15 +85,13 @@ fn register_token_v2() { let dot_asset = Location::new(1, Here); let dot_fee: xcm::prelude::Asset = (dot_asset, CreateAssetDeposit::get()).into(); - // Used to pay the asset creation deposit. - let weth_asset_value = 9_000_000_000_000u128; - let assets = vec![NativeTokenERC20 { token_id: WETH.into(), value: weth_asset_value }]; - let asset_deposit: xcm::prelude::Asset = (weth_location(), weth_asset_value).into(); + let eth_asset_value = 9_000_000_000_000u128; + let asset_deposit: xcm::prelude::Asset = (eth_location(), eth_asset_value).into(); BridgeHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; let instructions = vec![ - // Exchange weth for dot to pay the asset creation deposit + // Exchange eth for dot to pay the asset creation deposit ExchangeAsset { give: asset_deposit.clone().into(), want: dot_fee.clone().into(), @@ -119,10 +121,11 @@ fn register_token_v2() { let message = Message { origin, - assets, + assets: vec![], xcm: versioned_message_xcm.encode(), claimer: Some(claimer_bytes), - value: 3_500_000_000_000u128, + // Used to pay the asset creation deposit. + value: 9_000_000_000_000u128, execution_fee: 1_500_000_000_000u128, relayer_fee: 1_500_000_000_000u128, }; @@ -164,14 +167,12 @@ fn send_token_v2() { let claimer = AccountId32 { network: None, id: claimer_acc_id.into() }; let claimer_bytes = claimer.encode(); - register_foreign_asset(weth_location()); + register_foreign_asset(eth_location()); register_foreign_asset(token_location.clone()); let token_transfer_value = 2_000_000_000_000u128; let assets = vec![ - // to pay fees - NativeTokenERC20 { token_id: WETH.into(), value: 1_500_000_000_000u128 }, // the token being transferred NativeTokenERC20 { token_id: token.into(), value: token_transfer_value }, ]; @@ -194,7 +195,7 @@ fn send_token_v2() { assets, xcm: versioned_message_xcm.encode(), claimer: Some(claimer_bytes), - value: 3_500_000_000_000u128, + value: 1_500_000_000_000u128, execution_fee: 1_500_000_000_000u128, relayer_fee: 1_500_000_000_000u128, }; @@ -223,8 +224,8 @@ fn send_token_v2() { token_transfer_value ); - // Claimer received weth refund for fees paid - assert!(ForeignAssets::balance(weth_location(), AccountId::from(claimer_acc_id_bytes)) > 0); + // Claimer received eth refund for fees paid + assert!(ForeignAssets::balance(eth_location(), AccountId::from(claimer_acc_id_bytes)) > 0); }); } @@ -244,14 +245,14 @@ fn send_weth_v2() { let claimer = AccountId32 { network: None, id: claimer_acc_id.into() }; let claimer_bytes = claimer.encode(); + register_foreign_asset(eth_location()); register_foreign_asset(weth_location()); let token_transfer_value = 2_000_000_000_000u128; let assets = vec![ - // to pay fees - NativeTokenERC20 { token_id: WETH.into(), value: token_transfer_value }, // the token being transferred + NativeTokenERC20 { token_id: WETH.into(), value: token_transfer_value }, ]; BridgeHubWestend::execute_with(|| { @@ -289,7 +290,7 @@ fn send_weth_v2() { AssetHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; - // Check that the token was received and issued as a foreign asset on AssetHub + // Check that the weth was received and issued as a foreign asset on AssetHub assert_expected_events!( AssetHubWestend, vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {},] @@ -301,8 +302,8 @@ fn send_weth_v2() { token_transfer_value ); - // Claimer received weth refund for fees paid - assert!(ForeignAssets::balance(weth_location(), AccountId::from(claimer_acc_id_bytes)) > 0); + // Claimer received eth refund for fees paid + assert!(ForeignAssets::balance(eth_location(), AccountId::from(claimer_acc_id_bytes)) > 0); }); } @@ -333,9 +334,10 @@ fn register_and_send_multiple_tokens_v2() { let claimer = AccountId32 { network: None, id: claimer_acc_id.into() }; let claimer_bytes = claimer.encode(); + register_foreign_asset(eth_location()); register_foreign_asset(weth_location()); - set_up_weth_and_dot_pool(weth_location()); + set_up_eth_and_dot_pool(eth_location()); let token_transfer_value = 2_000_000_000_000u128; let weth_transfer_value = 2_500_000_000_000u128; @@ -344,13 +346,11 @@ fn register_and_send_multiple_tokens_v2() { let dot_fee: xcm::prelude::Asset = (dot_asset, CreateAssetDeposit::get()).into(); // Used to pay the asset creation deposit. - let weth_asset_value = 9_000_000_000_000u128; - let asset_deposit: xcm::prelude::Asset = (weth_location(), weth_asset_value).into(); + let eth_asset_value = 9_000_000_000_000u128; + let asset_deposit: xcm::prelude::Asset = (eth_location(), eth_asset_value).into(); let assets = vec![ - // to pay fees and transfer assets NativeTokenERC20 { token_id: WETH.into(), value: 2_800_000_000_000u128 }, - // the token being transferred NativeTokenERC20 { token_id: token.into(), value: token_transfer_value }, ]; @@ -444,8 +444,8 @@ fn register_and_send_multiple_tokens_v2() { weth_transfer_value ); - // Claimer received weth refund for fees paid - assert!(ForeignAssets::balance(weth_location(), AccountId::from(claimer_acc_id_bytes)) > 0); + // Claimer received eth refund for fees paid + assert!(ForeignAssets::balance(eth_location(), AccountId::from(claimer_acc_id_bytes)) > 0); }); } @@ -468,9 +468,9 @@ fn send_token_to_penpal_v2() { let claimer_bytes = claimer.encode(); // To pay fees on Penpal. - let weth_fee_penpal: xcm::prelude::Asset = (weth_location(), 3_000_000_000_000u128).into(); + let eth_fee_penpal: xcm::prelude::Asset = (eth_location(), 3_000_000_000_000u128).into(); - register_foreign_asset(weth_location()); + register_foreign_asset(eth_location()); register_foreign_asset(token_location.clone()); // To satisfy ED @@ -497,17 +497,17 @@ fn send_token_to_penpal_v2() { token_location.clone().try_into().unwrap(), )); - // Register weth on Penpal + // Register eth on Penpal assert_ok!(::ForeignAssets::force_create( RuntimeOrigin::root(), - weth_location().try_into().unwrap(), + eth_location().try_into().unwrap(), penpal_sovereign.clone().into(), true, 1000, )); assert!(::ForeignAssets::asset_exists( - weth_location().try_into().unwrap(), + eth_location().try_into().unwrap(), )); assert_ok!(::System::set_storage( @@ -519,15 +519,12 @@ fn send_token_to_penpal_v2() { )); }); - set_up_weth_and_dot_pool(weth_location()); - - set_up_weth_and_dot_pool_on_penpal(weth_location()); + set_up_eth_and_dot_pool(eth_location()); + set_up_eth_and_dot_pool_on_penpal(eth_location()); let token_transfer_value = 2_000_000_000_000u128; let assets = vec![ - // to pay fees - NativeTokenERC20 { token_id: WETH.into(), value: 5_000_000_000_000u128 }, // the token being transferred NativeTokenERC20 { token_id: token.into(), value: token_transfer_value }, ]; @@ -538,13 +535,13 @@ fn send_token_to_penpal_v2() { let instructions = vec![ // Send message to Penpal DepositReserveAsset { - // Send the token plus some weth for execution fees - assets: Definite(vec![weth_fee_penpal.clone(), token_asset].into()), + // Send the token plus some eth for execution fees + assets: Definite(vec![eth_fee_penpal.clone(), token_asset].into()), // Penpal dest: Location::new(1, [Parachain(PARA_ID_B)]), xcm: vec![ // Pay fees on Penpal. - PayFees { asset: weth_fee_penpal }, + PayFees { asset: eth_fee_penpal }, // Deposit assets to beneficiary. DepositAsset { assets: Wild(AllOf { @@ -624,7 +621,7 @@ fn send_foreign_erc20_token_back_to_polkadot() { let asset_id: Location = [PalletInstance(ASSETS_PALLET_ID), GeneralIndex(RESERVABLE_ASSET_ID.into())].into(); - register_foreign_asset(weth_location()); + register_foreign_asset(eth_location()); let asset_id_in_bh: Location = Location::new( 1, @@ -679,8 +676,6 @@ fn send_foreign_erc20_token_back_to_polkadot() { let token_id = TokenIdOf::convert_location(&asset_id_after_reanchored).unwrap(); let assets = vec![ - // to pay fees - NativeTokenERC20 { token_id: WETH.into(), value: 3_000_000_000_000u128 }, // the token being transferred ForeignTokenERC20 { token_id: token_id.into(), value: TOKEN_AMOUNT }, ]; @@ -759,12 +754,12 @@ fn invalid_xcm_traps_funds_on_ah() { 3_000_000_000_000, )]); - register_foreign_asset(weth_location()); + register_foreign_asset(eth_location()); - set_up_weth_and_dot_pool(weth_location()); + set_up_eth_and_dot_pool(eth_location()); let assets = vec![ - // to pay fees and transfer assets + // to transfer assets NativeTokenERC20 { token_id: WETH.into(), value: 2_800_000_000_000u128 }, // the token being transferred NativeTokenERC20 { token_id: token.into(), value: 2_000_000_000_000u128 }, @@ -815,14 +810,14 @@ fn invalid_claimer_does_not_fail_the_message() { let beneficiary_acc: [u8; 32] = H256::random().into(); let beneficiary = Location::new(0, AccountId32 { network: None, id: beneficiary_acc.into() }); + register_foreign_asset(eth_location()); register_foreign_asset(weth_location()); let token_transfer_value = 2_000_000_000_000u128; let assets = vec![ - // to pay fees - NativeTokenERC20 { token_id: WETH.into(), value: token_transfer_value }, // the token being transferred + NativeTokenERC20 { token_id: WETH.into(), value: token_transfer_value }, ]; BridgeHubWestend::execute_with(|| { @@ -874,8 +869,8 @@ fn invalid_claimer_does_not_fail_the_message() { token_transfer_value ); - // Relayer (instead of claimer) received weth refund for fees paid - assert!(ForeignAssets::balance(weth_location(), AccountId::from(relayer)) > 0); + // Relayer (instead of claimer) received eth refund for fees paid + assert!(ForeignAssets::balance(eth_location(), AccountId::from(relayer)) > 0); }); } @@ -899,7 +894,7 @@ pub fn register_foreign_asset(token_location: Location) { }); } -pub(crate) fn set_up_weth_and_dot_pool(asset: v5::Location) { +pub(crate) fn set_up_eth_and_dot_pool(asset: v5::Location) { let wnd: v5::Location = v5::Parent.into(); let assethub_location = BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()); let owner = AssetHubWestendSender::get(); @@ -961,7 +956,7 @@ pub(crate) fn set_up_weth_and_dot_pool(asset: v5::Location) { }); } -pub(crate) fn set_up_weth_and_dot_pool_on_penpal(asset: v5::Location) { +pub(crate) fn set_up_eth_and_dot_pool_on_penpal(asset: v5::Location) { let wnd: v5::Location = v5::Parent.into(); let penpal_location = BridgeHubWestend::sibling_location_of(PenpalB::para_id()); let owner = PenpalBSender::get(); 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 b05fa357fb98..2de53d503118 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 @@ -123,7 +123,6 @@ impl snowbridge_pallet_inbound_queue_v2::Config for Runtime { EthereumNetwork, ConstU8, EthereumSystem, - WethAddress, EthereumGatewayAddress, EthereumUniversalLocation, AssetHubFromEthereum, From ddece3ce4ca91dc906772fcadfa1f886912dc98b Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Tue, 10 Dec 2024 15:14:54 +0200 Subject: [PATCH 52/52] adds abi decoding --- Cargo.lock | 1 + .../pallets/inbound-queue-v2/src/mock.rs | 3 +- .../snowbridge/primitives/router/Cargo.toml | 2 + .../primitives/router/src/inbound/mod.rs | 1 + .../primitives/router/src/inbound/payload.rs | 91 +++++++++++++++++++ .../primitives/router/src/inbound/v2.rs | 5 +- 6 files changed, 99 insertions(+), 4 deletions(-) create mode 100644 bridges/snowbridge/primitives/router/src/inbound/payload.rs diff --git a/Cargo.lock b/Cargo.lock index f482bea693a1..3a5a84a672bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25196,6 +25196,7 @@ dependencies = [ name = "snowbridge-router-primitives" version = "0.9.0" dependencies = [ + "alloy-sol-types", "frame-support 28.0.0", "frame-system 28.0.0", "hex-literal", diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs index 974142553aaf..e603ab8e270a 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs @@ -19,8 +19,7 @@ use sp_runtime::{ BuildStorage, MultiSignature, }; use sp_std::{convert::From, default::Default}; -use xcm::{latest::SendXcm, prelude::*}; -use xcm::opaque::latest::WESTEND_GENESIS_HASH; +use xcm::{latest::SendXcm, opaque::latest::WESTEND_GENESIS_HASH, prelude::*}; type Block = frame_system::mocking::MockBlock; diff --git a/bridges/snowbridge/primitives/router/Cargo.toml b/bridges/snowbridge/primitives/router/Cargo.toml index aa4b3177c00b..77c8f3c1e9e2 100644 --- a/bridges/snowbridge/primitives/router/Cargo.toml +++ b/bridges/snowbridge/primitives/router/Cargo.toml @@ -15,6 +15,7 @@ workspace = true codec = { workspace = true } scale-info = { features = ["derive"], workspace = true } log = { workspace = true } +alloy-sol-types = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } @@ -36,6 +37,7 @@ hex-literal = { workspace = true, default-features = true } [features] default = ["std"] std = [ + "alloy-sol-types/std", "codec/std", "frame-support/std", "frame-system/std", diff --git a/bridges/snowbridge/primitives/router/src/inbound/mod.rs b/bridges/snowbridge/primitives/router/src/inbound/mod.rs index b494bc5b0e64..fc0a32163b49 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/mod.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/mod.rs @@ -2,6 +2,7 @@ // SPDX-FileCopyrightText: 2023 Snowfork // SPDX-FileCopyrightText: 2021-2022 Parity Technologies (UK) Ltd. +pub mod payload; pub mod v1; pub mod v2; use codec::Encode; diff --git a/bridges/snowbridge/primitives/router/src/inbound/payload.rs b/bridges/snowbridge/primitives/router/src/inbound/payload.rs new file mode 100644 index 000000000000..3caa5641427f --- /dev/null +++ b/bridges/snowbridge/primitives/router/src/inbound/payload.rs @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use crate::inbound::v2::{ + Asset::{ForeignTokenERC20, NativeTokenERC20}, + Message, +}; +use alloy_sol_types::{sol, SolType}; +use sp_core::{RuntimeDebug, H160, H256}; + +sol! { + struct AsNativeTokenERC20 { + address token_id; + uint128 value; + } +} + +sol! { + struct AsForeignTokenERC20 { + bytes32 token_id; + uint128 value; + } +} + +sol! { + struct EthereumAsset { + uint8 kind; + bytes data; + } +} + +sol! { + struct Payload { + address origin; + EthereumAsset[] assets; + bytes xcm; + bytes claimer; + uint128 value; + uint128 executionFee; + uint128 relayerFee; + } +} + +#[derive(Copy, Clone, RuntimeDebug)] +pub struct PayloadDecodeError; +impl TryFrom<&[u8]> for Message { + type Error = PayloadDecodeError; + + fn try_from(encoded_payload: &[u8]) -> Result { + let decoded_payload = + Payload::abi_decode(&encoded_payload, true).map_err(|_| PayloadDecodeError)?; + + let mut substrate_assets = vec![]; + + for asset in decoded_payload.assets { + match asset.kind { + 0 => { + let native_data = AsNativeTokenERC20::abi_decode(&asset.data, true) + .map_err(|_| PayloadDecodeError)?; + substrate_assets.push(NativeTokenERC20 { + token_id: H160::from(native_data.token_id.as_ref()), + value: native_data.value, + }); + }, + 1 => { + let foreign_data = AsForeignTokenERC20::abi_decode(&asset.data, true) + .map_err(|_| PayloadDecodeError)?; + substrate_assets.push(ForeignTokenERC20 { + token_id: H256::from(foreign_data.token_id.as_ref()), + value: foreign_data.value, + }); + }, + _ => return Err(PayloadDecodeError), + } + } + + let mut claimer = None; + if decoded_payload.claimer.len() > 0 { + claimer = Some(decoded_payload.claimer); + } + + Ok(Self { + origin: H160::from(decoded_payload.origin.as_ref()), + assets: substrate_assets, + xcm: decoded_payload.xcm, + claimer, + value: decoded_payload.value, + execution_fee: decoded_payload.executionFee, + relayer_fee: decoded_payload.relayerFee, + }) + } +} diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index 2defe5166bcf..eb5cdcece065 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -146,7 +146,8 @@ where // use eth as asset let fee_asset = Location::new(2, [GlobalConsensus(EthereumNetwork::get())]); let fee: XcmAsset = (fee_asset.clone(), message.execution_fee).into(); - let eth: XcmAsset = (fee_asset.clone(), message.execution_fee.saturating_add(message.value)).into(); + let eth: XcmAsset = + (fee_asset.clone(), message.execution_fee.saturating_add(message.value)).into(); let mut instructions = vec![ DescendOrigin(PalletInstance(InboundQueuePalletInstance::get()).into()), UniversalOrigin(GlobalConsensus(network)), @@ -355,7 +356,7 @@ mod tests { reserve_deposited_found = reserve_deposited_found + 1; if reserve_deposited_found == 1 { let fee_asset = Location::new(2, [GlobalConsensus(EthereumNetwork::get())]); - let fee: XcmAsset = (fee_asset, execution_fee).into(); + let fee: XcmAsset = (fee_asset, execution_fee + value).into(); let fee_assets: Assets = fee.into(); assert_eq!(fee_assets, reserve_assets.clone()); }