From 8a88a8bbbf485b54d79f5137acf80725797e6933 Mon Sep 17 00:00:00 2001 From: ron Date: Wed, 11 Dec 2024 13:29:38 +0800 Subject: [PATCH 01/26] Ensure source always from AH --- bridges/snowbridge/primitives/router/src/outbound/mod.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bridges/snowbridge/primitives/router/src/outbound/mod.rs b/bridges/snowbridge/primitives/router/src/outbound/mod.rs index 3b5dbdb77c89..c24ae8908825 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/mod.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/mod.rs @@ -20,6 +20,8 @@ use sp_std::{iter::Peekable, marker::PhantomData, prelude::*}; use xcm::prelude::*; use xcm_executor::traits::{ConvertLocation, ExportXcm}; +pub const ASSET_HUB_PARA_ID:u32 = 1000; + pub struct EthereumBlobExporter< UniversalLocation, EthereumNetwork, @@ -94,13 +96,16 @@ where return Err(SendError::NotApplicable) } + // Check the location here can only be AssetHub sovereign + ensure!(local_sub.len() == 1, SendError::NotApplicable); let para_id = match local_sub.as_slice() { [Parachain(para_id)] => *para_id, _ => { log::error!(target: "xcm::ethereum_blob_exporter", "could not get parachain id from universal source '{local_sub:?}'."); - return Err(SendError::NotApplicable) + return Err(SendError::NotApplicable); }, }; + ensure!(para_id == ASSET_HUB_PARA_ID, SendError::NotApplicable); let source_location = Location::new(1, local_sub.clone()); From 53ee95eb5b4d08e06f65ae959d492b4d32727551 Mon Sep 17 00:00:00 2001 From: ron Date: Wed, 11 Dec 2024 14:01:25 +0800 Subject: [PATCH 02/26] More tests for edge cases --- .../bridge-hub-westend/src/tests/mod.rs | 1 + .../src/tests/snowbridge.rs | 2 +- .../src/tests/snowbridge_edge_case.rs | 93 +++++++++++++++++++ 3 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_edge_case.rs diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs index 6c1cdb98e8b2..f42ef8773abb 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 @@ -22,6 +22,7 @@ mod send_xcm; mod snowbridge; mod teleport; mod transact; +mod snowbridge_edge_case; pub(crate) fn asset_hub_rococo_location() -> Location { Location::new( 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..dfb3febe51db 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs @@ -31,7 +31,7 @@ use xcm_executor::traits::ConvertLocation; const INITIAL_FUND: u128 = 5_000_000_000_000; pub const CHAIN_ID: u64 = 11155111; pub const WETH: [u8; 20] = hex!("87d1f7fdfEe7f651FaBc8bFCB6E086C278b77A7d"); -const ETHEREUM_DESTINATION_ADDRESS: [u8; 20] = hex!("44a57ee2f2FCcb85FDa2B0B18EBD0D8D2333700e"); +pub const ETHEREUM_DESTINATION_ADDRESS: [u8; 20] = hex!("44a57ee2f2FCcb85FDa2B0B18EBD0D8D2333700e"); const XCM_FEE: u128 = 100_000_000_000; const TOKEN_AMOUNT: u128 = 100_000_000_000; diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_edge_case.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_edge_case.rs new file mode 100644 index 000000000000..58c720fd7fac --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_edge_case.rs @@ -0,0 +1,93 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use crate::imports::*; +use bridge_hub_westend_runtime::xcm_config::LocationToAccountId; +use xcm_executor::traits::ConvertLocation; + +use crate::tests::snowbridge::{CHAIN_ID,WETH,ETHEREUM_DESTINATION_ADDRESS}; + +const INITIAL_FUND: u128 = 5_000_000_000_000; +const TOKEN_AMOUNT: u128 = 100_000_000_000; + +pub fn bridge_hub() -> Location { + Location::new(1, Parachain(BridgeHubWestend::para_id().into())) +} + +pub fn beneficiary() -> Location { + Location::new(0, [AccountKey20 { network: None, key: ETHEREUM_DESTINATION_ADDRESS.into() }]) +} + +#[test] +fn user_export_message_from_ah_directly_will_fail() { + let sov_account_for_assethub_sender = LocationToAccountId::convert_location(&Location::new( + 1, + [ + Parachain(AssetHubWestend::para_id().into()), + AccountId32 { + network: Some(ByGenesis(WESTEND_GENESIS_HASH)), + id: AssetHubWestendSender::get().into(), + }, + ], + )) + .unwrap(); + BridgeHubWestend::fund_accounts(vec![(sov_account_for_assethub_sender, INITIAL_FUND)]); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type RuntimeOrigin = ::RuntimeOrigin; + + let local_fee_asset = + Asset { id: AssetId(Location::parent()), fun: Fungible(1_000_000_000_000) }; + + let weth_location_reanchored = + Location::new(0, [AccountKey20 { network: None, key: WETH.into() }]); + + let weth_asset = Asset { + id: AssetId(weth_location_reanchored.clone()), + fun: Fungible(TOKEN_AMOUNT * 1_000_000_000), + }; + + assert_ok!(::PolkadotXcm::send( + RuntimeOrigin::signed(AssetHubWestendSender::get()), + bx!(VersionedLocation::from(bridge_hub())), + bx!(VersionedXcm::from(Xcm(vec![ + WithdrawAsset(local_fee_asset.clone().into()), + BuyExecution { fees: local_fee_asset.clone(), weight_limit: Unlimited }, + ExportMessage { + network: Ethereum { chain_id: CHAIN_ID }, + destination: Here, + xcm: Xcm(vec![ + WithdrawAsset(weth_asset.clone().into()), + DepositAsset { assets: Wild(All), beneficiary: beneficiary() }, + SetTopic([0; 32]), + ]), + }, + ]))), + )); + + assert_expected_events!( + AssetHubWestend, + vec![RuntimeEvent::PolkadotXcm(pallet_xcm::Event::Sent{ .. }) => {},] + ); + }); + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed{ success:false, .. }) => {},] + ); + }); +} \ No newline at end of file From 525e6a685fe9066928f3604483f3da1356c2c6cb Mon Sep 17 00:00:00 2001 From: Ron Date: Fri, 13 Dec 2024 09:01:25 +0800 Subject: [PATCH 03/26] Update bridges/snowbridge/primitives/router/src/outbound/mod.rs Co-authored-by: Adrian Catangiu --- bridges/snowbridge/primitives/router/src/outbound/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bridges/snowbridge/primitives/router/src/outbound/mod.rs b/bridges/snowbridge/primitives/router/src/outbound/mod.rs index c24ae8908825..f307ea9541e5 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/mod.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/mod.rs @@ -97,9 +97,8 @@ where } // Check the location here can only be AssetHub sovereign - ensure!(local_sub.len() == 1, SendError::NotApplicable); let para_id = match local_sub.as_slice() { - [Parachain(para_id)] => *para_id, + [Parachain(para_id)] if *para_id == ASSET_HUB_PARA_ID => *para_id, _ => { log::error!(target: "xcm::ethereum_blob_exporter", "could not get parachain id from universal source '{local_sub:?}'."); return Err(SendError::NotApplicable); From c3b8b888c5b83730f261759f93a85c8a05b43908 Mon Sep 17 00:00:00 2001 From: Ron Date: Fri, 13 Dec 2024 09:01:33 +0800 Subject: [PATCH 04/26] Update bridges/snowbridge/primitives/router/src/outbound/mod.rs Co-authored-by: Adrian Catangiu --- bridges/snowbridge/primitives/router/src/outbound/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/bridges/snowbridge/primitives/router/src/outbound/mod.rs b/bridges/snowbridge/primitives/router/src/outbound/mod.rs index f307ea9541e5..d70509456131 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/mod.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/mod.rs @@ -104,7 +104,6 @@ where return Err(SendError::NotApplicable); }, }; - ensure!(para_id == ASSET_HUB_PARA_ID, SendError::NotApplicable); let source_location = Location::new(1, local_sub.clone()); From a7cb93d3755fd94978680cfb7b3c3dd5f000c919 Mon Sep 17 00:00:00 2001 From: Ron Date: Fri, 13 Dec 2024 09:01:42 +0800 Subject: [PATCH 05/26] Update bridges/snowbridge/primitives/router/src/outbound/mod.rs Co-authored-by: Adrian Catangiu --- bridges/snowbridge/primitives/router/src/outbound/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridges/snowbridge/primitives/router/src/outbound/mod.rs b/bridges/snowbridge/primitives/router/src/outbound/mod.rs index d70509456131..77502656c96d 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/mod.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/mod.rs @@ -100,7 +100,7 @@ where let para_id = match local_sub.as_slice() { [Parachain(para_id)] if *para_id == ASSET_HUB_PARA_ID => *para_id, _ => { - log::error!(target: "xcm::ethereum_blob_exporter", "could not get parachain id from universal source '{local_sub:?}'."); + log::debug!(target: "xcm::ethereum_blob_exporter", "only supports Asset Hub root location as the universal source '{local_sub:?}'."); return Err(SendError::NotApplicable); }, }; From 277d51b77a0a5f04dff29ae47fb9fbb51c9a944c Mon Sep 17 00:00:00 2001 From: ron Date: Fri, 13 Dec 2024 09:36:37 +0800 Subject: [PATCH 06/26] Load AssetHub's ParaId from runtime --- .gitignore | 1 + .../primitives/router/src/outbound/mod.rs | 18 +++++++++++++----- .../primitives/router/src/outbound/tests.rs | 17 +++++++++++++++++ .../src/bridge_to_ethereum_config.rs | 3 +++ .../src/bridge_to_ethereum_config.rs | 3 +++ 5 files changed, 37 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index d48287657085..d5e0f1875da7 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,4 @@ substrate.code-workspace target/ *.scale justfile +python-venv diff --git a/bridges/snowbridge/primitives/router/src/outbound/mod.rs b/bridges/snowbridge/primitives/router/src/outbound/mod.rs index 77502656c96d..a197aa59701b 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/mod.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/mod.rs @@ -20,14 +20,13 @@ use sp_std::{iter::Peekable, marker::PhantomData, prelude::*}; use xcm::prelude::*; use xcm_executor::traits::{ConvertLocation, ExportXcm}; -pub const ASSET_HUB_PARA_ID:u32 = 1000; - pub struct EthereumBlobExporter< UniversalLocation, EthereumNetwork, OutboundQueue, AgentHashedDescription, ConvertAssetId, + AssetHubParaId, >( PhantomData<( UniversalLocation, @@ -35,17 +34,25 @@ pub struct EthereumBlobExporter< OutboundQueue, AgentHashedDescription, ConvertAssetId, + AssetHubParaId, )>, ); -impl - ExportXcm +impl< + UniversalLocation, + EthereumNetwork, + OutboundQueue, + AgentHashedDescription, + ConvertAssetId, + AssetHubParaId, + > ExportXcm for EthereumBlobExporter< UniversalLocation, EthereumNetwork, OutboundQueue, AgentHashedDescription, ConvertAssetId, + AssetHubParaId, > where UniversalLocation: Get, @@ -53,6 +60,7 @@ where OutboundQueue: SendMessage, AgentHashedDescription: ConvertLocation, ConvertAssetId: MaybeEquivalence, + AssetHubParaId: Get, { type Ticket = (Vec, XcmHash); @@ -98,7 +106,7 @@ where // Check the location here can only be AssetHub sovereign let para_id = match local_sub.as_slice() { - [Parachain(para_id)] if *para_id == ASSET_HUB_PARA_ID => *para_id, + [Parachain(para_id)] if ParaId::from(*para_id) == AssetHubParaId::get() => *para_id, _ => { log::debug!(target: "xcm::ethereum_blob_exporter", "only supports Asset Hub root location as the universal source '{local_sub:?}'."); return Err(SendError::NotApplicable); diff --git a/bridges/snowbridge/primitives/router/src/outbound/tests.rs b/bridges/snowbridge/primitives/router/src/outbound/tests.rs index 44f81ce31b3a..fba7ce058d00 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/tests.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/tests.rs @@ -18,6 +18,7 @@ parameter_types! { UniversalLocation: InteriorLocation = [GlobalConsensus(RelayNetwork::get()), Parachain(1013)].into(); const BridgedNetwork: NetworkId = Ethereum{ chain_id: 1 }; const NonBridgedNetwork: NetworkId = Ethereum{ chain_id: 2 }; + pub AssetHubParaId: ParaId = ParaId::from(1000); } struct MockOkOutboundQueue; @@ -86,6 +87,7 @@ fn exporter_validate_with_unknown_network_yields_not_applicable() { MockOkOutboundQueue, AgentIdOf, MockTokenIdConvert, + AssetHubParaId, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::NotApplicable)); } @@ -105,6 +107,7 @@ fn exporter_validate_with_invalid_destination_yields_missing_argument() { MockOkOutboundQueue, AgentIdOf, MockTokenIdConvert, + AssetHubParaId, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::MissingArgument)); } @@ -127,6 +130,7 @@ fn exporter_validate_with_x8_destination_yields_not_applicable() { MockOkOutboundQueue, AgentIdOf, MockTokenIdConvert, + AssetHubParaId, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::NotApplicable)); } @@ -146,6 +150,7 @@ fn exporter_validate_without_universal_source_yields_missing_argument() { MockOkOutboundQueue, AgentIdOf, MockTokenIdConvert, + AssetHubParaId, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::MissingArgument)); } @@ -165,6 +170,7 @@ fn exporter_validate_without_global_universal_location_yields_not_applicable() { MockOkOutboundQueue, AgentIdOf, MockTokenIdConvert, + AssetHubParaId, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::NotApplicable)); } @@ -184,6 +190,7 @@ fn exporter_validate_without_global_bridge_location_yields_not_applicable() { MockOkOutboundQueue, AgentIdOf, MockTokenIdConvert, + AssetHubParaId, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::NotApplicable)); } @@ -204,6 +211,7 @@ fn exporter_validate_with_remote_universal_source_yields_not_applicable() { MockOkOutboundQueue, AgentIdOf, MockTokenIdConvert, + AssetHubParaId, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::NotApplicable)); } @@ -223,6 +231,7 @@ fn exporter_validate_without_para_id_in_source_yields_not_applicable() { MockOkOutboundQueue, AgentIdOf, MockTokenIdConvert, + AssetHubParaId, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::NotApplicable)); } @@ -243,6 +252,7 @@ fn exporter_validate_complex_para_id_in_source_yields_not_applicable() { MockOkOutboundQueue, AgentIdOf, MockTokenIdConvert, + AssetHubParaId, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::NotApplicable)); } @@ -263,6 +273,7 @@ fn exporter_validate_without_xcm_message_yields_missing_argument() { MockOkOutboundQueue, AgentIdOf, MockTokenIdConvert, + AssetHubParaId, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::MissingArgument)); } @@ -310,6 +321,7 @@ fn exporter_validate_with_max_target_fee_yields_unroutable() { MockOkOutboundQueue, AgentIdOf, MockTokenIdConvert, + AssetHubParaId, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::Unroutable)); @@ -337,6 +349,7 @@ fn exporter_validate_with_unparsable_xcm_yields_unroutable() { MockOkOutboundQueue, AgentIdOf, MockTokenIdConvert, + AssetHubParaId, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::Unroutable)); @@ -383,6 +396,7 @@ fn exporter_validate_xcm_success_case_1() { MockOkOutboundQueue, AgentIdOf, MockTokenIdConvert, + AssetHubParaId, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert!(result.is_ok()); @@ -396,6 +410,7 @@ fn exporter_deliver_with_submit_failure_yields_unroutable() { MockErrOutboundQueue, AgentIdOf, MockTokenIdConvert, + AssetHubParaId, >::deliver((hex!("deadbeef").to_vec(), XcmHash::default())); assert_eq!(result, Err(XcmSendError::Transport("other transport error"))) } @@ -1208,6 +1223,7 @@ fn exporter_validate_with_invalid_dest_does_not_alter_destination() { MockOkOutboundQueue, AgentIdOf, MockTokenIdConvert, + AssetHubParaId, >::validate( network, channel, &mut universal_source_wrapper, &mut dest_wrapper, &mut msg_wrapper ); @@ -1261,6 +1277,7 @@ fn exporter_validate_with_invalid_universal_source_does_not_alter_universal_sour MockOkOutboundQueue, AgentIdOf, MockTokenIdConvert, + AssetHubParaId, >::validate( network, channel, &mut universal_source_wrapper, &mut dest_wrapper, &mut msg_wrapper ); 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..180795e76a22 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 @@ -35,6 +35,7 @@ use testnet_parachains_constants::rococo::{ use crate::xcm_config::RelayNetwork; #[cfg(feature = "runtime-benchmarks")] use benchmark_helpers::DoNothingRouter; +use cumulus_primitives_core::ParaId; use frame_support::{parameter_types, weights::ConstantMultiplier}; use pallet_xcm::EnsureXcm; use sp_runtime::{ @@ -50,11 +51,13 @@ pub type SnowbridgeExporter = EthereumBlobExporter< snowbridge_pallet_outbound_queue::Pallet, snowbridge_core::AgentIdOf, EthereumSystem, + AssetHubParaId, >; // Ethereum Bridge parameter_types! { pub storage EthereumGatewayAddress: H160 = H160(hex_literal::hex!("EDa338E4dC46038493b885327842fD3E301CaB39")); + pub AssetHubParaId: ParaId = ParaId::from(rococo_runtime_constants::system_parachain::ASSET_HUB_ID); } parameter_types! { 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..fe298f45f3c0 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs @@ -36,6 +36,7 @@ use testnet_parachains_constants::westend::{ use crate::xcm_config::RelayNetwork; #[cfg(feature = "runtime-benchmarks")] use benchmark_helpers::DoNothingRouter; +use cumulus_primitives_core::ParaId; use frame_support::{parameter_types, weights::ConstantMultiplier}; use pallet_xcm::EnsureXcm; use sp_runtime::{ @@ -53,11 +54,13 @@ pub type SnowbridgeExporter = EthereumBlobExporter< snowbridge_pallet_outbound_queue::Pallet, snowbridge_core::AgentIdOf, EthereumSystem, + AssetHubParaId, >; // Ethereum Bridge parameter_types! { pub storage EthereumGatewayAddress: H160 = H160(hex_literal::hex!("EDa338E4dC46038493b885327842fD3E301CaB39")); + pub AssetHubParaId: ParaId = ParaId::from(westend_runtime_constants::system_parachain::ASSET_HUB_ID); } parameter_types! { From c2a8ba91cfecaecb1c4df768ac41909d4eaf18b2 Mon Sep 17 00:00:00 2001 From: ron Date: Fri, 13 Dec 2024 12:07:36 +0800 Subject: [PATCH 07/26] More tests --- .../src/tests/snowbridge_edge_case.rs | 210 ++++++++++++++---- 1 file changed, 170 insertions(+), 40 deletions(-) diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_edge_case.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_edge_case.rs index 58c720fd7fac..97c30c0c646b 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_edge_case.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_edge_case.rs @@ -14,52 +14,127 @@ // limitations under the License. use crate::imports::*; use bridge_hub_westend_runtime::xcm_config::LocationToAccountId; +use frame_support::traits::fungibles::Mutate; +use snowbridge_router_primitives::inbound::EthereumLocationsConverterFor; +use testnet_parachains_constants::westend::snowbridge::EthereumNetwork; use xcm_executor::traits::ConvertLocation; -use crate::tests::snowbridge::{CHAIN_ID,WETH,ETHEREUM_DESTINATION_ADDRESS}; +use crate::tests::snowbridge::{CHAIN_ID, ETHEREUM_DESTINATION_ADDRESS, WETH}; const INITIAL_FUND: u128 = 5_000_000_000_000; const TOKEN_AMOUNT: u128 = 100_000_000_000; pub fn bridge_hub() -> Location { - Location::new(1, Parachain(BridgeHubWestend::para_id().into())) + Location::new(1, Parachain(BridgeHubWestend::para_id().into())) } pub fn beneficiary() -> Location { - Location::new(0, [AccountKey20 { network: None, key: ETHEREUM_DESTINATION_ADDRESS.into() }]) + Location::new(0, [AccountKey20 { network: None, key: ETHEREUM_DESTINATION_ADDRESS.into() }]) +} + +pub fn ethereum() -> Location { + Location::new(2, [GlobalConsensus(EthereumNetwork::get())]) +} + +pub fn weth_location() -> Location { + Location::new( + 2, + [GlobalConsensus(EthereumNetwork::get()), AccountKey20 { network: None, key: WETH }], + ) +} + +pub fn fund_on_bh() { + let asset_hub_sovereign = BridgeHubWestend::sovereign_account_id_of(Location::new( + 1, + [Parachain(AssetHubWestend::para_id().into())], + )); + BridgeHubWestend::fund_accounts(vec![(asset_hub_sovereign.clone(), INITIAL_FUND)]); +} + +pub fn fund_on_ah() { + AssetHubWestend::fund_accounts(vec![(AssetHubWestendSender::get(), INITIAL_FUND)]); + AssetHubWestend::fund_accounts(vec![(AssetHubWestendReceiver::get(), INITIAL_FUND)]); + + AssetHubWestend::execute_with(|| { + assert_ok!(::ForeignAssets::mint_into( + weth_location().try_into().unwrap(), + &AssetHubWestendReceiver::get(), + INITIAL_FUND, + )); + assert_ok!(::ForeignAssets::mint_into( + weth_location().try_into().unwrap(), + &AssetHubWestendSender::get(), + INITIAL_FUND, + )); + }); + + let ethereum_sovereign: AccountId = + EthereumLocationsConverterFor::<[u8; 32]>::convert_location(&Location::new( + 2, + [GlobalConsensus(EthereumNetwork::get())], + )) + .unwrap() + .into(); + AssetHubWestend::fund_accounts(vec![(ethereum_sovereign.clone(), INITIAL_FUND)]); +} + +pub fn register_weth_on_ah() { + let ethereum_sovereign: AccountId = + EthereumLocationsConverterFor::<[u8; 32]>::convert_location(&Location::new( + 2, + [GlobalConsensus(EthereumNetwork::get())], + )) + .unwrap() + .into(); + + AssetHubWestend::execute_with(|| { + type RuntimeOrigin = ::RuntimeOrigin; + + assert_ok!(::ForeignAssets::force_create( + RuntimeOrigin::root(), + weth_location().try_into().unwrap(), + ethereum_sovereign.clone().into(), + true, + 1, + )); + + assert!(::ForeignAssets::asset_exists( + weth_location().try_into().unwrap(), + )); + }); } #[test] -fn user_export_message_from_ah_directly_will_fail() { - let sov_account_for_assethub_sender = LocationToAccountId::convert_location(&Location::new( - 1, - [ - Parachain(AssetHubWestend::para_id().into()), - AccountId32 { - network: Some(ByGenesis(WESTEND_GENESIS_HASH)), - id: AssetHubWestendSender::get().into(), - }, - ], - )) - .unwrap(); - BridgeHubWestend::fund_accounts(vec![(sov_account_for_assethub_sender, INITIAL_FUND)]); - - AssetHubWestend::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; - type RuntimeOrigin = ::RuntimeOrigin; - - let local_fee_asset = - Asset { id: AssetId(Location::parent()), fun: Fungible(1_000_000_000_000) }; - - let weth_location_reanchored = - Location::new(0, [AccountKey20 { network: None, key: WETH.into() }]); - - let weth_asset = Asset { - id: AssetId(weth_location_reanchored.clone()), - fun: Fungible(TOKEN_AMOUNT * 1_000_000_000), - }; - - assert_ok!(::PolkadotXcm::send( +fn user_send_message_bypass_exporter_on_ah_will_fail() { + let sov_account_for_assethub_sender = LocationToAccountId::convert_location(&Location::new( + 1, + [ + Parachain(AssetHubWestend::para_id().into()), + AccountId32 { + network: Some(ByGenesis(WESTEND_GENESIS_HASH)), + id: AssetHubWestendSender::get().into(), + }, + ], + )) + .unwrap(); + BridgeHubWestend::fund_accounts(vec![(sov_account_for_assethub_sender, INITIAL_FUND)]); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type RuntimeOrigin = ::RuntimeOrigin; + + let local_fee_asset = + Asset { id: AssetId(Location::parent()), fun: Fungible(1_000_000_000_000) }; + + let weth_location_reanchored = + Location::new(0, [AccountKey20 { network: None, key: WETH.into() }]); + + let weth_asset = Asset { + id: AssetId(weth_location_reanchored.clone()), + fun: Fungible(TOKEN_AMOUNT * 1_000_000_000), + }; + + assert_ok!(::PolkadotXcm::send( RuntimeOrigin::signed(AssetHubWestendSender::get()), bx!(VersionedLocation::from(bridge_hub())), bx!(VersionedXcm::from(Xcm(vec![ @@ -77,17 +152,72 @@ fn user_export_message_from_ah_directly_will_fail() { ]))), )); - assert_expected_events!( + assert_expected_events!( + AssetHubWestend, + vec![RuntimeEvent::PolkadotXcm(pallet_xcm::Event::Sent{ .. }) => {},] + ); + }); + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed{ success:false, .. }) => {},] + ); + }); +} + +#[test] +fn user_exploit_with_arbitrary_message_will_fail() { + fund_on_bh(); + register_weth_on_ah(); + fund_on_ah(); + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type RuntimeOrigin = ::RuntimeOrigin; + + let remote_fee_asset_location: Location = Location::new( + 2, + [EthereumNetwork::get().into(), AccountKey20 { network: None, key: WETH }], + ) + .into(); + + let remote_fee_asset: Asset = (remote_fee_asset_location.clone(), 1).into(); + + let assets = VersionedAssets::from(vec![remote_fee_asset]); + + let exploited_weth = Asset { + id: AssetId(Location::new(0, [AccountKey20 { network: None, key: WETH.into() }])), + // A big mount without burning + fun: Fungible(TOKEN_AMOUNT * 1_000_000_000), + }; + + assert_ok!(::PolkadotXcm::transfer_assets_using_type_and_then( + RuntimeOrigin::signed(AssetHubWestendSender::get()), + bx!(VersionedLocation::from(ethereum())), + bx!(assets), + bx!(TransferType::DestinationReserve), + bx!(VersionedAssetId::from(remote_fee_asset_location.clone())), + bx!(TransferType::DestinationReserve), + bx!(VersionedXcm::from(Xcm(vec![ + WithdrawAsset(exploited_weth.clone().into()), + DepositAsset { assets: Wild(All), beneficiary: beneficiary() }, + SetTopic([0; 32]), + ]))), + Unlimited + )); + + assert_expected_events!( AssetHubWestend, vec![RuntimeEvent::PolkadotXcm(pallet_xcm::Event::Sent{ .. }) => {},] ); - }); + }); - BridgeHubWestend::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; - assert_expected_events!( + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + assert_expected_events!( BridgeHubWestend, vec![RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed{ success:false, .. }) => {},] ); - }); -} \ No newline at end of file + }); +} From 7192a5a6bc8747010da6a716acb6a0e502f26d4c Mon Sep 17 00:00:00 2001 From: ron Date: Fri, 13 Dec 2024 21:44:06 +0800 Subject: [PATCH 08/26] Add prdoc --- prdoc/pr_6838.prdoc | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 prdoc/pr_6838.prdoc diff --git a/prdoc/pr_6838.prdoc b/prdoc/pr_6838.prdoc new file mode 100644 index 000000000000..988c465e6783 --- /dev/null +++ b/prdoc/pr_6838.prdoc @@ -0,0 +1,9 @@ +title: 'Snowbridge: Ensure source always from Assethub for exported message' +doc: +- audience: Runtime Dev + description: |- + Ensure the last hop of exported message is always from AH. + So user can't export message from non system parachain directly. +crates: +- name: snowbridge-router-primitives + bump: patch From 62f629be7ea9680a8effbd096dfe63419f8deff3 Mon Sep 17 00:00:00 2001 From: ron Date: Sat, 14 Dec 2024 01:56:17 +0800 Subject: [PATCH 09/26] Fix fmt --- .../emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 f42ef8773abb..460e37bcd321 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,9 +20,9 @@ mod claim_assets; mod register_bridged_assets; mod send_xcm; mod snowbridge; +mod snowbridge_edge_case; mod teleport; mod transact; -mod snowbridge_edge_case; pub(crate) fn asset_hub_rococo_location() -> Location { Location::new( From df1e673a98a949b673909e47a3ebd6624e547448 Mon Sep 17 00:00:00 2001 From: ron Date: Sat, 14 Dec 2024 11:42:38 +0800 Subject: [PATCH 10/26] Fix the comment --- .../bridge-hub-westend/src/tests/snowbridge_edge_case.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_edge_case.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_edge_case.rs index 97c30c0c646b..7bf24e39f1f0 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_edge_case.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_edge_case.rs @@ -188,7 +188,7 @@ fn user_exploit_with_arbitrary_message_will_fail() { let exploited_weth = Asset { id: AssetId(Location::new(0, [AccountKey20 { network: None, key: WETH.into() }])), - // A big mount without burning + // A big amount without burning fun: Fungible(TOKEN_AMOUNT * 1_000_000_000), }; @@ -200,6 +200,10 @@ fn user_exploit_with_arbitrary_message_will_fail() { bx!(VersionedAssetId::from(remote_fee_asset_location.clone())), bx!(TransferType::DestinationReserve), bx!(VersionedXcm::from(Xcm(vec![ + // Instructions inner are user provided and untrustworthy/dangerous! + // Currently it depends on EthereumBlobExporter on BH to check the message is legal + // and convert to Ethereum command, should be very careful to handle that. + // Or we may move the security check ahead to AH to fail earlier if possible. WithdrawAsset(exploited_weth.clone().into()), DepositAsset { assets: Wild(All), beneficiary: beneficiary() }, SetTopic([0; 32]), From 2bf5bb1af09f1bb0d6d40f2864d860258267b43a Mon Sep 17 00:00:00 2001 From: ron Date: Tue, 7 Jan 2025 10:12:58 +0800 Subject: [PATCH 11/26] Rename as WhitelistedParaId --- .../snowbridge/primitives/router/src/outbound/mod.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bridges/snowbridge/primitives/router/src/outbound/mod.rs b/bridges/snowbridge/primitives/router/src/outbound/mod.rs index a197aa59701b..04f4862644a7 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/mod.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/mod.rs @@ -26,7 +26,7 @@ pub struct EthereumBlobExporter< OutboundQueue, AgentHashedDescription, ConvertAssetId, - AssetHubParaId, + WhitelistedParaId, >( PhantomData<( UniversalLocation, @@ -34,7 +34,7 @@ pub struct EthereumBlobExporter< OutboundQueue, AgentHashedDescription, ConvertAssetId, - AssetHubParaId, + WhitelistedParaId, )>, ); @@ -44,7 +44,7 @@ impl< OutboundQueue, AgentHashedDescription, ConvertAssetId, - AssetHubParaId, + WhitelistedParaId, > ExportXcm for EthereumBlobExporter< UniversalLocation, @@ -52,7 +52,7 @@ impl< OutboundQueue, AgentHashedDescription, ConvertAssetId, - AssetHubParaId, + WhitelistedParaId, > where UniversalLocation: Get, @@ -60,7 +60,7 @@ where OutboundQueue: SendMessage, AgentHashedDescription: ConvertLocation, ConvertAssetId: MaybeEquivalence, - AssetHubParaId: Get, + WhitelistedParaId: Get, { type Ticket = (Vec, XcmHash); @@ -106,7 +106,7 @@ where // Check the location here can only be AssetHub sovereign let para_id = match local_sub.as_slice() { - [Parachain(para_id)] if ParaId::from(*para_id) == AssetHubParaId::get() => *para_id, + [Parachain(para_id)] if ParaId::from(*para_id) == WhitelistedParaId::get() => *para_id, _ => { log::debug!(target: "xcm::ethereum_blob_exporter", "only supports Asset Hub root location as the universal source '{local_sub:?}'."); return Err(SendError::NotApplicable); From 0a51fec9c1f0a241143023aabcbeb0db2554edf2 Mon Sep 17 00:00:00 2001 From: ron Date: Tue, 14 Jan 2025 02:28:00 +0800 Subject: [PATCH 12/26] Revert change --- .../primitives/router/src/outbound/mod.rs | 28 ++++----- .../primitives/router/src/outbound/tests.rs | 57 +++++++++++++------ .../src/bridge_to_ethereum_config.rs | 3 - .../src/bridge_to_ethereum_config.rs | 3 - 4 files changed, 52 insertions(+), 39 deletions(-) diff --git a/bridges/snowbridge/primitives/router/src/outbound/mod.rs b/bridges/snowbridge/primitives/router/src/outbound/mod.rs index 04f4862644a7..b150989bd361 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/mod.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/mod.rs @@ -2,6 +2,8 @@ // SPDX-FileCopyrightText: 2023 Snowfork //! Converts XCM messages into simpler commands that can be processed by the Gateway contract +mod barriers; +pub use barriers::DenyFirstExportMessageFrom; #[cfg(test)] mod tests; @@ -26,7 +28,6 @@ pub struct EthereumBlobExporter< OutboundQueue, AgentHashedDescription, ConvertAssetId, - WhitelistedParaId, >( PhantomData<( UniversalLocation, @@ -34,25 +35,17 @@ pub struct EthereumBlobExporter< OutboundQueue, AgentHashedDescription, ConvertAssetId, - WhitelistedParaId, )>, ); -impl< - UniversalLocation, - EthereumNetwork, - OutboundQueue, - AgentHashedDescription, - ConvertAssetId, - WhitelistedParaId, - > ExportXcm +impl + ExportXcm for EthereumBlobExporter< UniversalLocation, EthereumNetwork, OutboundQueue, AgentHashedDescription, ConvertAssetId, - WhitelistedParaId, > where UniversalLocation: Get, @@ -60,7 +53,6 @@ where OutboundQueue: SendMessage, AgentHashedDescription: ConvertLocation, ConvertAssetId: MaybeEquivalence, - WhitelistedParaId: Get, { type Ticket = (Vec, XcmHash); @@ -104,12 +96,11 @@ where return Err(SendError::NotApplicable) } - // Check the location here can only be AssetHub sovereign let para_id = match local_sub.as_slice() { - [Parachain(para_id)] if ParaId::from(*para_id) == WhitelistedParaId::get() => *para_id, + [Parachain(para_id)] => *para_id, _ => { - log::debug!(target: "xcm::ethereum_blob_exporter", "only supports Asset Hub root location as the universal source '{local_sub:?}'."); - return Err(SendError::NotApplicable); + log::error!(target: "xcm::ethereum_blob_exporter", "could not get parachain id from universal source '{local_sub:?}'."); + return Err(SendError::NotApplicable) }, }; @@ -300,8 +291,13 @@ where let (token, amount) = match reserve_asset { Asset { id: AssetId(inner_location), fun: Fungible(amount) } => match inner_location.unpack() { + // Get the ERC20 contract address of the token. (0, [AccountKey20 { network, key }]) if self.network_matches(network) => Some((H160(*key), *amount)), + // If there is no ERC20 contract address in the location then signal to the + // gateway that is a native Ether transfer by using + // `0x0000000000000000000000000000000000000000` as the token address. + (0, []) => Some((H160([0; 20]), *amount)), _ => None, }, _ => None, diff --git a/bridges/snowbridge/primitives/router/src/outbound/tests.rs b/bridges/snowbridge/primitives/router/src/outbound/tests.rs index fba7ce058d00..2a60f9f3e0ea 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/tests.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/tests.rs @@ -18,7 +18,6 @@ parameter_types! { UniversalLocation: InteriorLocation = [GlobalConsensus(RelayNetwork::get()), Parachain(1013)].into(); const BridgedNetwork: NetworkId = Ethereum{ chain_id: 1 }; const NonBridgedNetwork: NetworkId = Ethereum{ chain_id: 2 }; - pub AssetHubParaId: ParaId = ParaId::from(1000); } struct MockOkOutboundQueue; @@ -87,7 +86,6 @@ fn exporter_validate_with_unknown_network_yields_not_applicable() { MockOkOutboundQueue, AgentIdOf, MockTokenIdConvert, - AssetHubParaId, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::NotApplicable)); } @@ -107,7 +105,6 @@ fn exporter_validate_with_invalid_destination_yields_missing_argument() { MockOkOutboundQueue, AgentIdOf, MockTokenIdConvert, - AssetHubParaId, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::MissingArgument)); } @@ -130,7 +127,6 @@ fn exporter_validate_with_x8_destination_yields_not_applicable() { MockOkOutboundQueue, AgentIdOf, MockTokenIdConvert, - AssetHubParaId, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::NotApplicable)); } @@ -150,7 +146,6 @@ fn exporter_validate_without_universal_source_yields_missing_argument() { MockOkOutboundQueue, AgentIdOf, MockTokenIdConvert, - AssetHubParaId, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::MissingArgument)); } @@ -170,7 +165,6 @@ fn exporter_validate_without_global_universal_location_yields_not_applicable() { MockOkOutboundQueue, AgentIdOf, MockTokenIdConvert, - AssetHubParaId, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::NotApplicable)); } @@ -190,7 +184,6 @@ fn exporter_validate_without_global_bridge_location_yields_not_applicable() { MockOkOutboundQueue, AgentIdOf, MockTokenIdConvert, - AssetHubParaId, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::NotApplicable)); } @@ -211,7 +204,6 @@ fn exporter_validate_with_remote_universal_source_yields_not_applicable() { MockOkOutboundQueue, AgentIdOf, MockTokenIdConvert, - AssetHubParaId, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::NotApplicable)); } @@ -231,7 +223,6 @@ fn exporter_validate_without_para_id_in_source_yields_not_applicable() { MockOkOutboundQueue, AgentIdOf, MockTokenIdConvert, - AssetHubParaId, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::NotApplicable)); } @@ -252,7 +243,6 @@ fn exporter_validate_complex_para_id_in_source_yields_not_applicable() { MockOkOutboundQueue, AgentIdOf, MockTokenIdConvert, - AssetHubParaId, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::NotApplicable)); } @@ -273,7 +263,6 @@ fn exporter_validate_without_xcm_message_yields_missing_argument() { MockOkOutboundQueue, AgentIdOf, MockTokenIdConvert, - AssetHubParaId, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::MissingArgument)); } @@ -321,7 +310,6 @@ fn exporter_validate_with_max_target_fee_yields_unroutable() { MockOkOutboundQueue, AgentIdOf, MockTokenIdConvert, - AssetHubParaId, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::Unroutable)); @@ -349,7 +337,6 @@ fn exporter_validate_with_unparsable_xcm_yields_unroutable() { MockOkOutboundQueue, AgentIdOf, MockTokenIdConvert, - AssetHubParaId, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::Unroutable)); @@ -396,7 +383,6 @@ fn exporter_validate_xcm_success_case_1() { MockOkOutboundQueue, AgentIdOf, MockTokenIdConvert, - AssetHubParaId, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert!(result.is_ok()); @@ -410,7 +396,6 @@ fn exporter_deliver_with_submit_failure_yields_unroutable() { MockErrOutboundQueue, AgentIdOf, MockTokenIdConvert, - AssetHubParaId, >::deliver((hex!("deadbeef").to_vec(), XcmHash::default())); assert_eq!(result, Err(XcmSendError::Transport("other transport error"))) } @@ -530,6 +515,46 @@ fn xcm_converter_convert_with_wildcard_all_asset_filter_succeeds() { assert_eq!(result, Ok((expected_payload, [0; 32]))); } +#[test] +fn xcm_converter_convert_with_native_eth_succeeds() { + let network = BridgedNetwork::get(); + + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + // The asset is `{ parents: 0, interior: X1(Here) }` relative to ethereum. + let assets: Assets = vec![Asset { id: AssetId([].into()), fun: Fungible(1000) }].into(); + let filter: AssetFilter = Wild(All); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + + // The token address that is expected to be sent should be + // `0x0000000000000000000000000000000000000000`. The solidity will + // interpret this as a transfer of ETH. + let expected_payload = Command::AgentExecute { + agent_id: Default::default(), + command: AgentExecuteCommand::TransferToken { + token: H160([0; 20]), + recipient: beneficiary_address.into(), + amount: 1000, + }, + }; + let result = converter.convert(); + assert_eq!(result, Ok((expected_payload, [0; 32]))); +} + #[test] fn xcm_converter_convert_with_fees_less_than_reserve_yields_success() { let network = BridgedNetwork::get(); @@ -1223,7 +1248,6 @@ fn exporter_validate_with_invalid_dest_does_not_alter_destination() { MockOkOutboundQueue, AgentIdOf, MockTokenIdConvert, - AssetHubParaId, >::validate( network, channel, &mut universal_source_wrapper, &mut dest_wrapper, &mut msg_wrapper ); @@ -1277,7 +1301,6 @@ fn exporter_validate_with_invalid_universal_source_does_not_alter_universal_sour MockOkOutboundQueue, AgentIdOf, MockTokenIdConvert, - AssetHubParaId, >::validate( network, channel, &mut universal_source_wrapper, &mut dest_wrapper, &mut msg_wrapper ); 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 180795e76a22..be7005b5379a 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 @@ -35,7 +35,6 @@ use testnet_parachains_constants::rococo::{ use crate::xcm_config::RelayNetwork; #[cfg(feature = "runtime-benchmarks")] use benchmark_helpers::DoNothingRouter; -use cumulus_primitives_core::ParaId; use frame_support::{parameter_types, weights::ConstantMultiplier}; use pallet_xcm::EnsureXcm; use sp_runtime::{ @@ -51,13 +50,11 @@ pub type SnowbridgeExporter = EthereumBlobExporter< snowbridge_pallet_outbound_queue::Pallet, snowbridge_core::AgentIdOf, EthereumSystem, - AssetHubParaId, >; // Ethereum Bridge parameter_types! { pub storage EthereumGatewayAddress: H160 = H160(hex_literal::hex!("EDa338E4dC46038493b885327842fD3E301CaB39")); - pub AssetHubParaId: ParaId = ParaId::from(rococo_runtime_constants::system_parachain::ASSET_HUB_ID); } parameter_types! { 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 fe298f45f3c0..94921fd8af9a 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs @@ -36,7 +36,6 @@ use testnet_parachains_constants::westend::{ use crate::xcm_config::RelayNetwork; #[cfg(feature = "runtime-benchmarks")] use benchmark_helpers::DoNothingRouter; -use cumulus_primitives_core::ParaId; use frame_support::{parameter_types, weights::ConstantMultiplier}; use pallet_xcm::EnsureXcm; use sp_runtime::{ @@ -54,13 +53,11 @@ pub type SnowbridgeExporter = EthereumBlobExporter< snowbridge_pallet_outbound_queue::Pallet, snowbridge_core::AgentIdOf, EthereumSystem, - AssetHubParaId, >; // Ethereum Bridge parameter_types! { pub storage EthereumGatewayAddress: H160 = H160(hex_literal::hex!("EDa338E4dC46038493b885327842fD3E301CaB39")); - pub AssetHubParaId: ParaId = ParaId::from(westend_runtime_constants::system_parachain::ASSET_HUB_ID); } parameter_types! { From e2e022859d0ccc5b846f80165c47811ddd112488 Mon Sep 17 00:00:00 2001 From: ron Date: Tue, 14 Jan 2025 02:35:11 +0800 Subject: [PATCH 13/26] Barrier DenyFirstExportMessageFrom --- Cargo.lock | 1 + .../snowbridge/primitives/router/Cargo.toml | 3 + .../router/src/outbound/barriers.rs | 98 +++++++++++++++++++ .../primitives/router/src/outbound/mod.rs | 4 +- .../bridge-hub-westend/src/xcm_config.rs | 16 ++- .../runtimes/constants/src/westend.rs | 3 +- 6 files changed, 119 insertions(+), 6 deletions(-) create mode 100644 bridges/snowbridge/primitives/router/src/outbound/barriers.rs diff --git a/Cargo.lock b/Cargo.lock index b0fb0586be38..e4959ec682d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25327,6 +25327,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/primitives/router/Cargo.toml b/bridges/snowbridge/primitives/router/Cargo.toml index e44cca077ef3..44e8cbdfc30c 100644 --- a/bridges/snowbridge/primitives/router/Cargo.toml +++ b/bridges/snowbridge/primitives/router/Cargo.toml @@ -23,6 +23,7 @@ sp-runtime = { workspace = true } sp-std = { workspace = true } xcm = { workspace = true } +xcm-builder = { workspace = true } xcm-executor = { workspace = true } snowbridge-core = { workspace = true } @@ -43,6 +44,7 @@ std = [ "sp-io/std", "sp-runtime/std", "sp-std/std", + "xcm-builder/std", "xcm-executor/std", "xcm/std", ] @@ -50,6 +52,7 @@ runtime-benchmarks = [ "frame-support/runtime-benchmarks", "snowbridge-core/runtime-benchmarks", "sp-runtime/runtime-benchmarks", + "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", "xcm/runtime-benchmarks", ] diff --git a/bridges/snowbridge/primitives/router/src/outbound/barriers.rs b/bridges/snowbridge/primitives/router/src/outbound/barriers.rs new file mode 100644 index 000000000000..84964474d08d --- /dev/null +++ b/bridges/snowbridge/primitives/router/src/outbound/barriers.rs @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork + +use core::{marker::PhantomData, ops::ControlFlow}; +use frame_support::traits::{Contains, ProcessMessageError}; +use xcm::prelude::{ExportMessage, Instruction, Location, NetworkId, Weight}; +use xcm_builder::{CreateMatcher, MatchXcm}; +use xcm_executor::traits::{Properties, ShouldExecute}; + +pub struct DenyFirstExportMessageFrom(PhantomData<(From, To)>); + +impl ShouldExecute for DenyFirstExportMessageFrom +where + From: Contains, + To: Contains, +{ + fn should_execute( + origin: &Location, + message: &mut [Instruction], + _max_weight: Weight, + _properties: &mut Properties, + ) -> Result<(), ProcessMessageError> { + message.matcher().match_next_inst_while( + |_| true, + |inst| match inst { + ExportMessage { network, .. } => + if To::contains(network) && From::contains(origin) { + return Err(ProcessMessageError::Unsupported) + } else { + Ok(ControlFlow::Continue(())) + }, + _ => Ok(ControlFlow::Continue(())), + }, + )?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use frame_support::{ + assert_err, parameter_types, + traits::{Equals, Everything, EverythingBut}, + }; + use xcm::prelude::*; + + parameter_types! { + pub AssetHubLocation: Location = Location::new(1, Parachain(1000)); + } + + #[test] + fn deny_export_message_from_source() { + let mut xcm: Vec> = + vec![ExportMessage { network: Polkadot, destination: Here, xcm: Default::default() }]; + + let result = DenyFirstExportMessageFrom::< + EverythingBut>, + Everything, + >::should_execute( + &Location::parent(), + &mut *xcm, + Weight::zero(), + &mut Properties { weight_credit: Weight::zero(), message_id: None }, + ); + assert_err!(result, ProcessMessageError::Unsupported); + } + + #[test] + fn allow_export_message_from_source() { + let mut xcm: Vec> = + vec![ExportMessage { network: Polkadot, destination: Here, xcm: Default::default() }]; + + let result = DenyFirstExportMessageFrom::< + EverythingBut>, + Everything, + >::should_execute( + &AssetHubLocation::get(), + &mut *xcm, + Weight::zero(), + &mut Properties { weight_credit: Weight::zero(), message_id: None }, + ); + assert!(result.is_ok()); + } + + #[test] + fn allow_xcm_without_export_message() { + let mut xcm: Vec> = vec![ClearOrigin]; + + let result = DenyFirstExportMessageFrom::::should_execute( + &Location::parent(), + &mut *xcm, + Weight::zero(), + &mut Properties { weight_credit: Weight::zero(), message_id: None }, + ); + assert!(result.is_ok()); + } +} diff --git a/bridges/snowbridge/primitives/router/src/outbound/mod.rs b/bridges/snowbridge/primitives/router/src/outbound/mod.rs index b150989bd361..9fb4dd3b5690 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/mod.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/mod.rs @@ -2,8 +2,6 @@ // SPDX-FileCopyrightText: 2023 Snowfork //! Converts XCM messages into simpler commands that can be processed by the Gateway contract -mod barriers; -pub use barriers::DenyFirstExportMessageFrom; #[cfg(test)] mod tests; @@ -22,6 +20,8 @@ use sp_std::{iter::Peekable, marker::PhantomData, prelude::*}; use xcm::prelude::*; use xcm_executor::traits::{ConvertLocation, ExportXcm}; +pub mod barriers; + pub struct EthereumBlobExporter< UniversalLocation, EthereumNetwork, 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..b16638e12014 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 @@ -21,7 +21,10 @@ use super::{ }; use frame_support::{ parameter_types, - traits::{tokens::imbalance::ResolveTo, ConstU32, Contains, Equals, Everything, Nothing}, + traits::{ + tokens::imbalance::ResolveTo, ConstU32, Contains, Equals, Everything, EverythingBut, + Nothing, + }, }; use frame_system::EnsureRoot; use pallet_collator_selection::StakingPotAccountId; @@ -35,10 +38,11 @@ use parachains_common::{ }; use polkadot_parachain_primitives::primitives::Sibling; use polkadot_runtime_common::xcm_sender::ExponentialPrice; +use snowbridge_router_primitives::outbound::barriers::DenyFirstExportMessageFrom; use snowbridge_runtime_common::XcmExportFeeToSibling; use sp_runtime::traits::AccountIdConversion; use sp_std::marker::PhantomData; -use testnet_parachains_constants::westend::snowbridge::EthereumNetwork; +use testnet_parachains_constants::westend::snowbridge::{AssetHubLocation, EthereumNetwork}; use xcm::latest::{prelude::*, WESTEND_GENESIS_HASH}; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowHrmpNotificationsFromRelayChain, @@ -129,7 +133,13 @@ impl Contains for ParentOrParentsPlurality { pub type Barrier = TrailingSetTopicAsId< DenyThenTry< - DenyReserveTransferToRelayChain, + ( + DenyReserveTransferToRelayChain, + DenyFirstExportMessageFrom< + EverythingBut>, + Equals, + >, + ), ( // Allow local users to buy weight credit. TakeWeightCredit, diff --git a/cumulus/parachains/runtimes/constants/src/westend.rs b/cumulus/parachains/runtimes/constants/src/westend.rs index 8c4c0c594359..76f327b07bd0 100644 --- a/cumulus/parachains/runtimes/constants/src/westend.rs +++ b/cumulus/parachains/runtimes/constants/src/westend.rs @@ -171,7 +171,7 @@ pub mod time { pub mod snowbridge { use frame_support::parameter_types; - use xcm::prelude::{Location, NetworkId}; + use xcm::prelude::{Location, NetworkId, Parachain}; /// The pallet index of the Ethereum inbound queue pallet in the bridge hub runtime. pub const INBOUND_QUEUE_PALLET_INDEX: u8 = 80; @@ -183,6 +183,7 @@ pub mod snowbridge { /// pub EthereumNetwork: NetworkId = NetworkId::Ethereum { chain_id: 11155111 }; pub EthereumLocation: Location = Location::new(2, EthereumNetwork::get()); + pub AssetHubLocation: Location = Location::new(1, Parachain(1000)); } } From 7f529edc80b6812716f3023fc390ade15c7fe396 Mon Sep 17 00:00:00 2001 From: ron Date: Tue, 14 Jan 2025 11:10:53 +0800 Subject: [PATCH 14/26] Rename --- .../router/src/outbound/barriers.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/bridges/snowbridge/primitives/router/src/outbound/barriers.rs b/bridges/snowbridge/primitives/router/src/outbound/barriers.rs index 84964474d08d..5dcdd8ba480d 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/barriers.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/barriers.rs @@ -7,12 +7,15 @@ use xcm::prelude::{ExportMessage, Instruction, Location, NetworkId, Weight}; use xcm_builder::{CreateMatcher, MatchXcm}; use xcm_executor::traits::{Properties, ShouldExecute}; -pub struct DenyFirstExportMessageFrom(PhantomData<(From, To)>); +pub struct DenyFirstExportMessageFrom( + PhantomData<(FromOrigin, ToGlobalConsensus)>, +); -impl ShouldExecute for DenyFirstExportMessageFrom +impl ShouldExecute + for DenyFirstExportMessageFrom where - From: Contains, - To: Contains, + FromOrigin: Contains, + ToGlobalConsensus: Contains, { fn should_execute( origin: &Location, @@ -24,7 +27,7 @@ where |_| true, |inst| match inst { ExportMessage { network, .. } => - if To::contains(network) && From::contains(origin) { + if ToGlobalConsensus::contains(network) && FromOrigin::contains(origin) { return Err(ProcessMessageError::Unsupported) } else { Ok(ControlFlow::Continue(())) @@ -59,7 +62,7 @@ mod tests { Everything, >::should_execute( &Location::parent(), - &mut *xcm, + &mut xcm, Weight::zero(), &mut Properties { weight_credit: Weight::zero(), message_id: None }, ); @@ -76,7 +79,7 @@ mod tests { Everything, >::should_execute( &AssetHubLocation::get(), - &mut *xcm, + &mut xcm, Weight::zero(), &mut Properties { weight_credit: Weight::zero(), message_id: None }, ); @@ -89,7 +92,7 @@ mod tests { let result = DenyFirstExportMessageFrom::::should_execute( &Location::parent(), - &mut *xcm, + &mut xcm, Weight::zero(), &mut Properties { weight_credit: Weight::zero(), message_id: None }, ); From ccf9a280cdd50d19a7257274591a2ce7070e9271 Mon Sep 17 00:00:00 2001 From: ron Date: Tue, 14 Jan 2025 11:53:33 +0800 Subject: [PATCH 15/26] Fix xcm config --- .../router/src/outbound/barriers.rs | 25 ++++++++ .../bridge-hub-westend/src/xcm_config.rs | 62 +++++++++---------- 2 files changed, 56 insertions(+), 31 deletions(-) diff --git a/bridges/snowbridge/primitives/router/src/outbound/barriers.rs b/bridges/snowbridge/primitives/router/src/outbound/barriers.rs index 5dcdd8ba480d..4d41b0bdef03 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/barriers.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/barriers.rs @@ -47,9 +47,11 @@ mod tests { traits::{Equals, Everything, EverythingBut}, }; use xcm::prelude::*; + use xcm_builder::{DenyReserveTransferToRelayChain, DenyThenTry, TakeWeightCredit}; parameter_types! { pub AssetHubLocation: Location = Location::new(1, Parachain(1000)); + pub EthereumNetwork: NetworkId = Ethereum { chain_id: 1 }; } #[test] @@ -98,4 +100,27 @@ mod tests { ); assert!(result.is_ok()); } + + #[test] + fn deny_with_reserve_transfer_to_relay_chain() { + let mut xcm: Vec> = vec![DepositReserveAsset { + assets: Wild(All), + dest: Location { parents: 1, interior: Here }, + xcm: Default::default(), + }]; + + let result = DenyThenTry::< + DenyFirstExportMessageFrom< + EverythingBut>, + Equals, + >, + DenyThenTry, + >::should_execute( + &AssetHubLocation::get(), + &mut xcm, + Weight::zero(), + &mut Properties { weight_credit: Weight::zero(), message_id: None }, + ); + assert_err!(result, ProcessMessageError::Unsupported); + } } 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 b16638e12014..dc9c0255ddd3 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 @@ -133,38 +133,38 @@ impl Contains for ParentOrParentsPlurality { pub type Barrier = TrailingSetTopicAsId< DenyThenTry< - ( + DenyFirstExportMessageFrom< + EverythingBut>, + Equals, + >, + DenyThenTry< DenyReserveTransferToRelayChain, - DenyFirstExportMessageFrom< - EverythingBut>, - Equals, - >, - ), - ( - // Allow local users to buy weight credit. - TakeWeightCredit, - // Expected responses are OK. - AllowKnownQueryResponses, - WithComputedOrigin< - ( - // If the message is one that immediately attempts to pay for execution, then - // allow it. - AllowTopLevelPaidExecutionFrom, - // Parent, its pluralities (i.e. governance bodies) and relay treasury pallet - // get free execution. - AllowExplicitUnpaidExecutionFrom<( - ParentOrParentsPlurality, - Equals, - )>, - // Subscriptions for version tracking are OK. - AllowSubscriptionsFrom, - // HRMP notifications from the relay chain are OK. - AllowHrmpNotificationsFromRelayChain, - ), - UniversalLocation, - ConstU32<8>, - >, - ), + ( + // Allow local users to buy weight credit. + TakeWeightCredit, + // Expected responses are OK. + AllowKnownQueryResponses, + WithComputedOrigin< + ( + // If the message is one that immediately attempts to pay for execution, + // then allow it. + AllowTopLevelPaidExecutionFrom, + // Parent, its pluralities (i.e. governance bodies) and relay treasury + // pallet get free execution. + AllowExplicitUnpaidExecutionFrom<( + ParentOrParentsPlurality, + Equals, + )>, + // Subscriptions for version tracking are OK. + AllowSubscriptionsFrom, + // HRMP notifications from the relay chain are OK. + AllowHrmpNotificationsFromRelayChain, + ), + UniversalLocation, + ConstU32<8>, + >, + ), + >, >, >; From 5226eedd2686ddc729a3708754d37f7d4bbfbc4f Mon Sep 17 00:00:00 2001 From: ron Date: Tue, 14 Jan 2025 11:54:05 +0800 Subject: [PATCH 16/26] Cleanup --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index d5e0f1875da7..d48287657085 100644 --- a/.gitignore +++ b/.gitignore @@ -41,4 +41,3 @@ substrate.code-workspace target/ *.scale justfile -python-venv From e779308cb38b9fdc9fdbe70bf8613bf0eec48df3 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Tue, 14 Jan 2025 10:55:47 -0300 Subject: [PATCH 17/26] Update prdoc/pr_6838.prdoc --- prdoc/pr_6838.prdoc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/prdoc/pr_6838.prdoc b/prdoc/pr_6838.prdoc index 988c465e6783..c09bd52b462c 100644 --- a/prdoc/pr_6838.prdoc +++ b/prdoc/pr_6838.prdoc @@ -6,4 +6,8 @@ doc: So user can't export message from non system parachain directly. crates: - name: snowbridge-router-primitives + bump: minor +- name: testnet-parachains-constants + bump: patch +- name: bridge-hub-westend-runtime bump: patch From 3a66cdbb0df3ab04ca101932d363bf9266b6e3be Mon Sep 17 00:00:00 2001 From: ron Date: Wed, 15 Jan 2025 01:30:04 +0800 Subject: [PATCH 18/26] More tests --- .../router/src/outbound/barriers.rs | 23 -------- .../snowbridge/runtime/test-common/src/lib.rs | 53 +++++++++++++++++++ .../bridge-hub-westend/tests/snowbridge.rs | 49 +++++++++++++++-- 3 files changed, 98 insertions(+), 27 deletions(-) diff --git a/bridges/snowbridge/primitives/router/src/outbound/barriers.rs b/bridges/snowbridge/primitives/router/src/outbound/barriers.rs index 4d41b0bdef03..3f2a44de7ad0 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/barriers.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/barriers.rs @@ -100,27 +100,4 @@ mod tests { ); assert!(result.is_ok()); } - - #[test] - fn deny_with_reserve_transfer_to_relay_chain() { - let mut xcm: Vec> = vec![DepositReserveAsset { - assets: Wild(All), - dest: Location { parents: 1, interior: Here }, - xcm: Default::default(), - }]; - - let result = DenyThenTry::< - DenyFirstExportMessageFrom< - EverythingBut>, - Equals, - >, - DenyThenTry, - >::should_execute( - &AssetHubLocation::get(), - &mut xcm, - Weight::zero(), - &mut Properties { weight_credit: Weight::zero(), message_id: None }, - ); - assert_err!(result, ProcessMessageError::Unsupported); - } } diff --git a/bridges/snowbridge/runtime/test-common/src/lib.rs b/bridges/snowbridge/runtime/test-common/src/lib.rs index 5441dd822cac..c58d202e21b4 100644 --- a/bridges/snowbridge/runtime/test-common/src/lib.rs +++ b/bridges/snowbridge/runtime/test-common/src/lib.rs @@ -623,3 +623,56 @@ pub fn ethereum_to_polkadot_message_extrinsics_work( assert_ok!(sync_committee_outcome); }); } + +#[allow(clippy::too_many_arguments)] +pub fn send_transfer_token_message_from_source_other_than_asset_hub_failure( + ethereum_chain_id: u64, + collator_session_key: CollatorSessionKeys, + runtime_para_id: u32, + assethub_parachain_id: u32, + another_parachain_id: u32, + initial_amount: u128, + weth_contract_address: H160, + destination_address: H160, + fee_amount: u128, + expected_error: XcmError, +) where + Runtime: frame_system::Config + + pallet_balances::Config + + pallet_session::Config + + pallet_xcm::Config + + parachain_info::Config + + pallet_collator_selection::Config + + cumulus_pallet_parachain_system::Config + + snowbridge_pallet_outbound_queue::Config + + snowbridge_pallet_system::Config + + pallet_timestamp::Config, + XcmConfig: xcm_executor::Config, + ValidatorIdOf: From>, +{ + ExtBuilder::::default() + .with_collators(collator_session_key.collators()) + .with_session_keys(collator_session_key.session_keys()) + .with_para_id(runtime_para_id.into()) + .with_tracing() + .build() + .execute_with(|| { + >::initialize( + runtime_para_id.into(), + assethub_parachain_id.into(), + ) + .unwrap(); + + // fund asset hub sovereign account enough so it can pay fees + initial_fund::(assethub_parachain_id, initial_amount); + + let outcome = send_transfer_token_message::( + ethereum_chain_id, + another_parachain_id, + weth_contract_address, + destination_address, + fee_amount, + ); + assert_err!(outcome.ensure_complete(), expected_error); + }); +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/snowbridge.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/snowbridge.rs index bc570ef7f74b..ac885d7e411c 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/snowbridge.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/snowbridge.rs @@ -20,13 +20,15 @@ use bp_asset_hub_westend::ASSET_HUB_WESTEND_PARACHAIN_ID; use bp_bridge_hub_westend::BRIDGE_HUB_WESTEND_PARACHAIN_ID; use bp_polkadot_core::Signature; use bridge_hub_westend_runtime::{ - bridge_to_rococo_config, xcm_config::XcmConfig, AllPalletsWithoutSystem, - BridgeRejectObsoleteHeadersAndMessages, Executive, MessageQueueServiceWeight, Runtime, - RuntimeCall, RuntimeEvent, SessionKeys, TxExtension, UncheckedExtrinsic, + bridge_to_rococo_config, + xcm_config::{Barrier, XcmConfig}, + AllPalletsWithoutSystem, BridgeRejectObsoleteHeadersAndMessages, Executive, + MessageQueueServiceWeight, Runtime, RuntimeCall, RuntimeEvent, SessionKeys, TxExtension, + UncheckedExtrinsic, }; use codec::{Decode, Encode}; use cumulus_primitives_core::XcmError::{FailedToTransactAsset, NotHoldingFees}; -use frame_support::parameter_types; +use frame_support::{assert_err, parameter_types, traits::ProcessMessageError}; use parachains_common::{AccountId, AuraId, Balance}; use snowbridge_pallet_ethereum_client::WeightInfo; use sp_core::H160; @@ -35,9 +37,15 @@ use sp_runtime::{ generic::{Era, SignedPayload}, AccountId32, }; +use xcm::prelude::{ + All, AssetFilter, DepositReserveAsset, Here, Instruction, Location, Parachain, Weight, Wild, + XcmError, +}; +use xcm_executor::traits::{Properties, ShouldExecute}; parameter_types! { pub const DefaultBridgeHubEthereumBaseFee: Balance = 2_750_872_500_000; + pub AssetHubLocation: Location = Location::new(1, Parachain(ASSET_HUB_WESTEND_PARACHAIN_ID)); } fn collator_session_keys() -> bridge_hub_test_utils::CollatorSessionKeys { @@ -200,3 +208,36 @@ fn construct_and_apply_extrinsic( let r = Executive::apply_extrinsic(xt); r.unwrap() } + +#[test] +pub fn transfer_token_to_ethereum_from_source_other_than_asset_hub_failure() { + snowbridge_runtime_test_common::send_transfer_token_message_from_source_other_than_asset_hub_failure::( + 11155111, + collator_session_keys(), + BRIDGE_HUB_WESTEND_PARACHAIN_ID, + ASSET_HUB_WESTEND_PARACHAIN_ID, + 2000, + DefaultBridgeHubEthereumBaseFee::get(), + H160::random(), + H160::random(), + DefaultBridgeHubEthereumBaseFee::get(), + XcmError::Barrier, + ) +} + +#[test] +fn deny_reserve_transfer_to_relay_chain() { + let mut xcm: Vec> = vec![DepositReserveAsset { + assets: AssetFilter::try_from(Wild(All)).unwrap(), + dest: Location { parents: 1, interior: Here }, + xcm: Default::default(), + }]; + + let result = Barrier::should_execute( + &AssetHubLocation::get(), + &mut xcm, + Weight::zero(), + &mut Properties { weight_credit: Weight::zero(), message_id: None }, + ); + assert_err!(result, ProcessMessageError::Unsupported); +} From 15ae2b3aca65782f81b73b2a80f24ff3efa39274 Mon Sep 17 00:00:00 2001 From: Ron Date: Wed, 15 Jan 2025 01:34:13 +0800 Subject: [PATCH 19/26] Update cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_edge_case.rs Co-authored-by: Francisco Aguirre --- .../bridge-hub-westend/src/tests/snowbridge_edge_case.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_edge_case.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_edge_case.rs index 7bf24e39f1f0..32b7faf67259 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_edge_case.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_edge_case.rs @@ -52,8 +52,10 @@ pub fn fund_on_bh() { } pub fn fund_on_ah() { - AssetHubWestend::fund_accounts(vec![(AssetHubWestendSender::get(), INITIAL_FUND)]); - AssetHubWestend::fund_accounts(vec![(AssetHubWestendReceiver::get(), INITIAL_FUND)]); + AssetHubWestend::fund_accounts(vec![ + (AssetHubWestendSender::get(), INITIAL_FUND), + (AssetHubWestendReceiver::get(), INITIAL_FUND), + ]); AssetHubWestend::execute_with(|| { assert_ok!(::ForeignAssets::mint_into( From e792f7db45c507189165e924cbbd8ac4a226bf69 Mon Sep 17 00:00:00 2001 From: ron Date: Wed, 15 Jan 2025 01:43:54 +0800 Subject: [PATCH 20/26] Cleanup --- .../src/tests/snowbridge_edge_case.rs | 30 +++++++------------ 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_edge_case.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_edge_case.rs index 32b7faf67259..7c6ea5b5fa71 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_edge_case.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_edge_case.rs @@ -43,6 +43,12 @@ pub fn weth_location() -> Location { ) } +pub fn ethereum_sovereign_account() -> AccountId { + EthereumLocationsConverterFor::<[u8; 32]>::convert_location(ðereum()) + .unwrap() + .into() +} + pub fn fund_on_bh() { let asset_hub_sovereign = BridgeHubWestend::sovereign_account_id_of(Location::new( 1, @@ -53,8 +59,9 @@ pub fn fund_on_bh() { pub fn fund_on_ah() { AssetHubWestend::fund_accounts(vec![ - (AssetHubWestendSender::get(), INITIAL_FUND), - (AssetHubWestendReceiver::get(), INITIAL_FUND), + (AssetHubWestendSender::get(), INITIAL_FUND), + (AssetHubWestendReceiver::get(), INITIAL_FUND), + (ethereum_sovereign_account(), INITIAL_FUND), ]); AssetHubWestend::execute_with(|| { @@ -69,33 +76,16 @@ pub fn fund_on_ah() { INITIAL_FUND, )); }); - - let ethereum_sovereign: AccountId = - EthereumLocationsConverterFor::<[u8; 32]>::convert_location(&Location::new( - 2, - [GlobalConsensus(EthereumNetwork::get())], - )) - .unwrap() - .into(); - AssetHubWestend::fund_accounts(vec![(ethereum_sovereign.clone(), INITIAL_FUND)]); } pub fn register_weth_on_ah() { - let ethereum_sovereign: AccountId = - EthereumLocationsConverterFor::<[u8; 32]>::convert_location(&Location::new( - 2, - [GlobalConsensus(EthereumNetwork::get())], - )) - .unwrap() - .into(); - AssetHubWestend::execute_with(|| { type RuntimeOrigin = ::RuntimeOrigin; assert_ok!(::ForeignAssets::force_create( RuntimeOrigin::root(), weth_location().try_into().unwrap(), - ethereum_sovereign.clone().into(), + ethereum_sovereign_account().into(), true, 1, )); From d0027eab82a14556ae85680bec0dd239e7d79db6 Mon Sep 17 00:00:00 2001 From: Ron Date: Wed, 15 Jan 2025 01:45:34 +0800 Subject: [PATCH 21/26] Update bridges/snowbridge/primitives/router/src/outbound/barriers.rs Co-authored-by: Francisco Aguirre --- bridges/snowbridge/primitives/router/src/outbound/barriers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridges/snowbridge/primitives/router/src/outbound/barriers.rs b/bridges/snowbridge/primitives/router/src/outbound/barriers.rs index 3f2a44de7ad0..d527bf8655ae 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/barriers.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/barriers.rs @@ -57,7 +57,7 @@ mod tests { #[test] fn deny_export_message_from_source() { let mut xcm: Vec> = - vec![ExportMessage { network: Polkadot, destination: Here, xcm: Default::default() }]; + vec![ExportMessage { network: EthereumNetwork::get(), destination: Here, xcm: Default::default() }]; let result = DenyFirstExportMessageFrom::< EverythingBut>, From fcadbd7b8337e69543f80abdaacdcb13b77bc89f Mon Sep 17 00:00:00 2001 From: Ron Date: Wed, 15 Jan 2025 01:48:41 +0800 Subject: [PATCH 22/26] Update bridges/snowbridge/primitives/router/src/outbound/barriers.rs Co-authored-by: Branislav Kontur --- bridges/snowbridge/primitives/router/src/outbound/barriers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridges/snowbridge/primitives/router/src/outbound/barriers.rs b/bridges/snowbridge/primitives/router/src/outbound/barriers.rs index d527bf8655ae..0a76afd19536 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/barriers.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/barriers.rs @@ -74,7 +74,7 @@ mod tests { #[test] fn allow_export_message_from_source() { let mut xcm: Vec> = - vec![ExportMessage { network: Polkadot, destination: Here, xcm: Default::default() }]; + vec![ExportMessage { network: EthereumNetwork::get(), destination: Here, xcm: Default::default() }]; let result = DenyFirstExportMessageFrom::< EverythingBut>, From 4b51b60600cb9b2d98559bf0cf01ec857a380f34 Mon Sep 17 00:00:00 2001 From: Ron Date: Wed, 15 Jan 2025 01:48:53 +0800 Subject: [PATCH 23/26] Update bridges/snowbridge/primitives/router/src/outbound/barriers.rs Co-authored-by: Branislav Kontur --- bridges/snowbridge/primitives/router/src/outbound/barriers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridges/snowbridge/primitives/router/src/outbound/barriers.rs b/bridges/snowbridge/primitives/router/src/outbound/barriers.rs index 0a76afd19536..90a632d37d66 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/barriers.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/barriers.rs @@ -72,7 +72,7 @@ mod tests { } #[test] - fn allow_export_message_from_source() { + fn allow_export_message_from_asset_hub() { let mut xcm: Vec> = vec![ExportMessage { network: EthereumNetwork::get(), destination: Here, xcm: Default::default() }]; From 67018416ebbf7949668c4db79eaa6e9fb82e9eba Mon Sep 17 00:00:00 2001 From: Ron Date: Wed, 15 Jan 2025 01:49:38 +0800 Subject: [PATCH 24/26] Update bridges/snowbridge/primitives/router/src/outbound/barriers.rs Co-authored-by: Branislav Kontur --- bridges/snowbridge/primitives/router/src/outbound/barriers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridges/snowbridge/primitives/router/src/outbound/barriers.rs b/bridges/snowbridge/primitives/router/src/outbound/barriers.rs index 90a632d37d66..5f08c47a6889 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/barriers.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/barriers.rs @@ -55,7 +55,7 @@ mod tests { } #[test] - fn deny_export_message_from_source() { + fn deny_export_message_from_source_other_than_asset_hub() { let mut xcm: Vec> = vec![ExportMessage { network: EthereumNetwork::get(), destination: Here, xcm: Default::default() }]; From efa5114174b3b1f1882ccf35f752158aba69fa48 Mon Sep 17 00:00:00 2001 From: ron Date: Wed, 15 Jan 2025 01:51:22 +0800 Subject: [PATCH 25/26] Fix format --- .../primitives/router/src/outbound/barriers.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/bridges/snowbridge/primitives/router/src/outbound/barriers.rs b/bridges/snowbridge/primitives/router/src/outbound/barriers.rs index 5f08c47a6889..d43bb41b81a9 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/barriers.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/barriers.rs @@ -56,8 +56,11 @@ mod tests { #[test] fn deny_export_message_from_source_other_than_asset_hub() { - let mut xcm: Vec> = - vec![ExportMessage { network: EthereumNetwork::get(), destination: Here, xcm: Default::default() }]; + let mut xcm: Vec> = vec![ExportMessage { + network: EthereumNetwork::get(), + destination: Here, + xcm: Default::default(), + }]; let result = DenyFirstExportMessageFrom::< EverythingBut>, @@ -73,8 +76,11 @@ mod tests { #[test] fn allow_export_message_from_asset_hub() { - let mut xcm: Vec> = - vec![ExportMessage { network: EthereumNetwork::get(), destination: Here, xcm: Default::default() }]; + let mut xcm: Vec> = vec![ExportMessage { + network: EthereumNetwork::get(), + destination: Here, + xcm: Default::default(), + }]; let result = DenyFirstExportMessageFrom::< EverythingBut>, From 421b4efa38d21c05daee5470201f5b563a3d607f Mon Sep 17 00:00:00 2001 From: ron Date: Wed, 15 Jan 2025 02:21:28 +0800 Subject: [PATCH 26/26] Fix clippy --- bridges/snowbridge/primitives/router/src/outbound/barriers.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/bridges/snowbridge/primitives/router/src/outbound/barriers.rs b/bridges/snowbridge/primitives/router/src/outbound/barriers.rs index d43bb41b81a9..d0510cb61388 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/barriers.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/barriers.rs @@ -47,7 +47,6 @@ mod tests { traits::{Equals, Everything, EverythingBut}, }; use xcm::prelude::*; - use xcm_builder::{DenyReserveTransferToRelayChain, DenyThenTry, TakeWeightCredit}; parameter_types! { pub AssetHubLocation: Location = Location::new(1, Parachain(1000));