From 0b35bc6b245804ced805d2871092445e7809b289 Mon Sep 17 00:00:00 2001 From: ron Date: Tue, 3 Jun 2025 13:48:31 +0800 Subject: [PATCH 01/17] Register PNA with fee --- .../system-frontend/src/benchmarking.rs | 7 ++- .../pallets/system-frontend/src/lib.rs | 43 ++++++++++++++++++- .../pallets/system-frontend/src/mock.rs | 16 ++++++- .../pallets/system-frontend/src/tests.rs | 28 +++++++++--- .../pallets/system-v2/src/benchmarking.rs | 2 +- .../snowbridge/pallets/system-v2/src/lib.rs | 3 +- .../snowbridge/pallets/system-v2/src/tests.rs | 7 ++- 7 files changed, 92 insertions(+), 14 deletions(-) diff --git a/bridges/snowbridge/pallets/system-frontend/src/benchmarking.rs b/bridges/snowbridge/pallets/system-frontend/src/benchmarking.rs index f7d206879e151..b01bd67886806 100644 --- a/bridges/snowbridge/pallets/system-frontend/src/benchmarking.rs +++ b/bridges/snowbridge/pallets/system-frontend/src/benchmarking.rs @@ -26,8 +26,13 @@ mod benchmarks { decimals: 12, }; + let asset = Asset::from(( + Location::new(2, [GlobalConsensus(Ethereum { chain_id: 11155111 })]), + 1000, + )); + #[extrinsic_call] - _(origin as T::RuntimeOrigin, asset_id, asset_metadata); + _(origin as T::RuntimeOrigin, asset_id, asset_metadata, asset); Ok(()) } diff --git a/bridges/snowbridge/pallets/system-frontend/src/lib.rs b/bridges/snowbridge/pallets/system-frontend/src/lib.rs index d59464fa5cde7..b88ca61f961b7 100644 --- a/bridges/snowbridge/pallets/system-frontend/src/lib.rs +++ b/bridges/snowbridge/pallets/system-frontend/src/lib.rs @@ -62,6 +62,7 @@ pub enum EthereumSystemCall { sender: Box, asset_id: Box, metadata: AssetMetadata, + amount: u128, }, #[codec(index = 3)] AddTip { sender: AccountIdOf, message_id: MessageId, amount: u128 }, @@ -80,6 +81,7 @@ where #[frame_support::pallet] pub mod pallet { use super::*; + use xcm_executor::traits::ConvertLocation; #[pallet::pallet] pub struct Pallet(_); @@ -118,6 +120,8 @@ pub mod pallet { /// InteriorLocation of this pallet. type PalletLocation: Get; + type AccountIdConverter: ConvertLocation; + /// Weights for dispatching XCM to backend implementation of `register_token` type BackendWeightInfo: BackendWeightInfo; @@ -220,16 +224,49 @@ pub mod pallet { origin: OriginFor, asset_id: Box, metadata: AssetMetadata, + fee_asset: Asset, ) -> DispatchResult { ensure!(!Self::export_operating_mode().is_halted(), Error::::Halted); let asset_location: Location = (*asset_id).try_into().map_err(|_| Error::::UnsupportedLocationVersion)?; let origin_location = T::RegisterTokenOrigin::ensure_origin(origin, &asset_location)?; + let who = T::AccountIdConverter::convert_location(&origin_location) + .ok_or(Error::::LocationConversionFailed)?; + + let ether_location = T::EthereumLocation::get(); + let (fee_asset_location, fee_amount) = match fee_asset { + Asset { id: AssetId(ref loc), fun: Fungible(amount) } => (loc, amount), + _ => { + tracing::debug!(target: LOG_TARGET, ?fee_asset, "error matching fee asset"); + return Err(Error::::UnsupportedAsset.into()) + }, + }; + ensure!(fee_amount > 0, Error::::TipAmountZero); + + let ether_gained = if *fee_asset_location != ether_location { + Self::swap_and_burn( + who.clone(), + fee_asset_location.clone(), + ether_location, + fee_amount, + ) + .inspect_err(|&e| { + tracing::debug!(target: LOG_TARGET, ?e, "error swapping asset"); + })? + } else { + burn_for_teleport::(&origin_location, &fee_asset) + .map_err(|_| Error::::BurnError)?; + fee_amount + }; let dest = T::BridgeHubLocation::get(); - let call = - Self::build_register_token_call(origin_location.clone(), asset_location, metadata)?; + let call = Self::build_register_token_call( + origin_location.clone(), + asset_location, + metadata, + ether_gained, + )?; let remote_xcm = Self::build_remote_xcm(&call); let message_id = Self::send_xcm(origin_location, dest.clone(), remote_xcm.clone()) .map_err(|error| Error::::from(error))?; @@ -353,6 +390,7 @@ pub mod pallet { sender: Location, asset: Location, metadata: AssetMetadata, + amount: u128, ) -> Result, Error> { // reanchor locations relative to BH let sender = Self::reanchored(sender)?; @@ -362,6 +400,7 @@ pub mod pallet { sender: Box::new(VersionedLocation::from(sender)), asset_id: Box::new(VersionedLocation::from(asset)), metadata, + amount, }); Ok(call) diff --git a/bridges/snowbridge/pallets/system-frontend/src/mock.rs b/bridges/snowbridge/pallets/system-frontend/src/mock.rs index 38026b9253186..d4f6562d6a33a 100644 --- a/bridges/snowbridge/pallets/system-frontend/src/mock.rs +++ b/bridges/snowbridge/pallets/system-frontend/src/mock.rs @@ -7,11 +7,12 @@ use frame_support::{ derive_impl, parameter_types, traits::{AsEnsureOriginWithArg, Everything}, }; +use snowbridge_core::ParaId; use snowbridge_test_utils::mock_swap_executor::SwapExecutor; pub use snowbridge_test_utils::{mock_origin::pallet_xcm_origin, mock_xcm::*}; use sp_core::H256; use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, + traits::{AccountIdConversion, BlakeTwo256, IdentityLookup}, AccountId32, BuildStorage, }; use xcm::prelude::*; @@ -75,6 +76,18 @@ parameter_types! { pub PalletLocation: InteriorLocation = [PalletInstance(36)].into(); } +pub struct AccountIdConverter; +impl xcm_executor::traits::ConvertLocation for AccountIdConverter { + fn convert_location(ml: &Location) -> Option { + match ml.unpack() { + (0, [Junction::AccountId32 { id, .. }]) => + Some(::decode(&mut &*id.to_vec()).unwrap()), + (1, [Parachain(id)]) => Some(ParaId::from(*id).into_account_truncating()), + _ => None, + } + } +} + impl crate::Config for Test { type RuntimeEvent = RuntimeEvent; type RegisterTokenOrigin = AsEnsureOriginWithArg>; @@ -90,6 +103,7 @@ impl crate::Config for Test { type WeightInfo = (); #[cfg(feature = "runtime-benchmarks")] type Helper = (); + type AccountIdConverter = AccountIdConverter; } // Build genesis storage according to the mock runtime. diff --git a/bridges/snowbridge/pallets/system-frontend/src/tests.rs b/bridges/snowbridge/pallets/system-frontend/src/tests.rs index 16170387c3917..541b59359a97f 100644 --- a/bridges/snowbridge/pallets/system-frontend/src/tests.rs +++ b/bridges/snowbridge/pallets/system-frontend/src/tests.rs @@ -26,10 +26,15 @@ fn register_token() { decimals: 12, }; + let ether_location = Ether::get(); + let fee_amount = 1000; + let asset = Asset::from((ether_location.clone(), fee_amount)); + assert_ok!(EthereumSystemFrontend::register_token( origin.clone(), asset_id.clone(), - asset_metadata.clone() + asset_metadata.clone(), + asset.clone(), )); }); } @@ -46,11 +51,14 @@ fn register_token_fails_delivery_fees_not_met() { symbol: "pal".as_bytes().to_vec().try_into().unwrap(), decimals: 12, }; + let ether_location = Ether::get(); + let fee_amount = 1000; + let asset = Asset::from((ether_location.clone(), fee_amount)); set_charge_fees_override(|_, _| Err(XcmError::FeesNotMet)); assert_err!( - EthereumSystemFrontend::register_token(origin, asset_id, asset_metadata), + EthereumSystemFrontend::register_token(origin, asset_id, asset_metadata, asset.clone()), Error::::FeesNotMet, ); }); @@ -68,6 +76,9 @@ fn register_token_fails_unroutable() { symbol: "pal".as_bytes().to_vec().try_into().unwrap(), decimals: 12, }; + let ether_location = Ether::get(); + let fee_amount = 1000; + let asset = Asset::from((ether_location.clone(), fee_amount)); // Send XCM with overrides for `SendXcm` behavior to return `Unroutable` error on // validate @@ -79,7 +90,8 @@ fn register_token_fails_unroutable() { EthereumSystemFrontend::register_token( origin.clone(), asset_id.clone(), - asset_metadata.clone() + asset_metadata.clone(), + asset.clone(), ), Error::::SendFailure ); @@ -92,7 +104,7 @@ fn register_token_fails_unroutable() { ); assert_err!( - EthereumSystemFrontend::register_token(origin, asset_id, asset_metadata), + EthereumSystemFrontend::register_token(origin, asset_id, asset_metadata, asset.clone()), Error::::SendFailure ); }); @@ -114,11 +126,15 @@ fn test_switch_operating_mode() { symbol: "pal".as_bytes().to_vec().try_into().unwrap(), decimals: 12, }; + let ether_location = Ether::get(); + let fee_amount = 1000; + let asset = Asset::from((ether_location.clone(), fee_amount)); assert_noop!( EthereumSystemFrontend::register_token( origin.clone(), asset_id.clone(), - asset_metadata.clone() + asset_metadata.clone(), + asset.clone(), ), crate::Error::::Halted ); @@ -126,7 +142,7 @@ fn test_switch_operating_mode() { RawOrigin::Root.into(), BasicOperatingMode::Normal, )); - assert_ok!(EthereumSystemFrontend::register_token(origin, asset_id, asset_metadata),); + assert_ok!(EthereumSystemFrontend::register_token(origin, asset_id, asset_metadata, asset)); }); } diff --git a/bridges/snowbridge/pallets/system-v2/src/benchmarking.rs b/bridges/snowbridge/pallets/system-v2/src/benchmarking.rs index d967c851a92ce..7350baa919770 100644 --- a/bridges/snowbridge/pallets/system-v2/src/benchmarking.rs +++ b/bridges/snowbridge/pallets/system-v2/src/benchmarking.rs @@ -27,7 +27,7 @@ mod benchmarks { }; #[extrinsic_call] - _(origin as T::RuntimeOrigin, creator, asset, asset_metadata); + _(origin as T::RuntimeOrigin, creator, asset, asset_metadata, 1); Ok(()) } diff --git a/bridges/snowbridge/pallets/system-v2/src/lib.rs b/bridges/snowbridge/pallets/system-v2/src/lib.rs index 100727cae752c..095b263f8f257 100644 --- a/bridges/snowbridge/pallets/system-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/system-v2/src/lib.rs @@ -213,6 +213,7 @@ pub mod pallet { sender: Box, asset_id: Box, metadata: AssetMetadata, + amount: u128, ) -> DispatchResult { T::FrontendOrigin::ensure_origin(origin)?; @@ -237,7 +238,7 @@ pub mod pallet { }; let message_origin = Self::location_to_message_origin(sender_location)?; - Self::send(message_origin, command, 0)?; + Self::send(message_origin, command, amount)?; Self::deposit_event(Event::::RegisterToken { location: location.into(), diff --git a/bridges/snowbridge/pallets/system-v2/src/tests.rs b/bridges/snowbridge/pallets/system-v2/src/tests.rs index 6edf9e2243708..2475dfa67c399 100644 --- a/bridges/snowbridge/pallets/system-v2/src/tests.rs +++ b/bridges/snowbridge/pallets/system-v2/src/tests.rs @@ -17,6 +17,7 @@ fn register_tokens_succeeds() { Box::new(versioned_location.clone()), Box::new(versioned_location), Default::default(), + 1 )); }); } @@ -129,7 +130,8 @@ fn register_all_tokens_succeeds() { origin, Box::new(versioned_location.clone()), Box::new(versioned_location), - Default::default() + Default::default(), + 1 )); let reanchored_location = EthereumSystemV2::reanchor(tc.native.clone()).unwrap(); @@ -162,7 +164,8 @@ fn register_ethereum_native_token_fails() { origin, versioned_location.clone(), versioned_location.clone(), - Default::default() + Default::default(), + 1 ), Error::::LocationConversionFailed ); From f719fbe9fdf4258d9d50307673c3f38250681bc4 Mon Sep 17 00:00:00 2001 From: ron Date: Tue, 3 Jun 2025 14:04:12 +0800 Subject: [PATCH 02/17] Burn fee for adding a tip --- bridges/snowbridge/pallets/system-frontend/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bridges/snowbridge/pallets/system-frontend/src/lib.rs b/bridges/snowbridge/pallets/system-frontend/src/lib.rs index b88ca61f961b7..17c031ad214b7 100644 --- a/bridges/snowbridge/pallets/system-frontend/src/lib.rs +++ b/bridges/snowbridge/pallets/system-frontend/src/lib.rs @@ -316,6 +316,8 @@ pub mod pallet { tracing::debug!(target: LOG_TARGET, ?e, "error swapping asset"); })? } else { + burn_for_teleport::(&who.clone().into(), &asset) + .map_err(|_| Error::::BurnError)?; tip_amount }; From 09e61beb0d187dd8b2e57fae300d54bdf88110ff Mon Sep 17 00:00:00 2001 From: ron Date: Tue, 3 Jun 2025 14:24:01 +0800 Subject: [PATCH 03/17] Fix runtime --- .../assets/asset-hub-westend/src/bridge_to_ethereum_config.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/bridge_to_ethereum_config.rs index 1cede206ccb92..75e330e8ffbce 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/bridge_to_ethereum_config.rs @@ -185,4 +185,5 @@ impl snowbridge_pallet_system_frontend::Config for Runtime { type PalletLocation = SystemFrontendPalletLocation; type Swap = AssetConversion; type BackendWeightInfo = weights::snowbridge_pallet_system_backend::WeightInfo; + type AccountIdConverter = xcm_config::LocationToAccountId; } From d2f43b9467e623cffe5a99a3321f70c4f23cbc4a Mon Sep 17 00:00:00 2001 From: ron Date: Tue, 3 Jun 2025 18:30:51 +0800 Subject: [PATCH 04/17] Refactor function swap_fee_asset_and_burn --- .../pallets/system-frontend/src/lib.rs | 88 ++++++++----------- 1 file changed, 37 insertions(+), 51 deletions(-) diff --git a/bridges/snowbridge/pallets/system-frontend/src/lib.rs b/bridges/snowbridge/pallets/system-frontend/src/lib.rs index 17c031ad214b7..320161070b0c5 100644 --- a/bridges/snowbridge/pallets/system-frontend/src/lib.rs +++ b/bridges/snowbridge/pallets/system-frontend/src/lib.rs @@ -234,31 +234,7 @@ pub mod pallet { let who = T::AccountIdConverter::convert_location(&origin_location) .ok_or(Error::::LocationConversionFailed)?; - let ether_location = T::EthereumLocation::get(); - let (fee_asset_location, fee_amount) = match fee_asset { - Asset { id: AssetId(ref loc), fun: Fungible(amount) } => (loc, amount), - _ => { - tracing::debug!(target: LOG_TARGET, ?fee_asset, "error matching fee asset"); - return Err(Error::::UnsupportedAsset.into()) - }, - }; - ensure!(fee_amount > 0, Error::::TipAmountZero); - - let ether_gained = if *fee_asset_location != ether_location { - Self::swap_and_burn( - who.clone(), - fee_asset_location.clone(), - ether_location, - fee_amount, - ) - .inspect_err(|&e| { - tracing::debug!(target: LOG_TARGET, ?e, "error swapping asset"); - })? - } else { - burn_for_teleport::(&origin_location, &fee_asset) - .map_err(|_| Error::::BurnError)?; - fee_amount - }; + let ether_gained = Self::swap_fee_asset_and_burn(who, fee_asset)?; let dest = T::BridgeHubLocation::get(); let call = Self::build_register_token_call( @@ -294,32 +270,7 @@ pub mod pallet { { let who = ensure_signed(origin)?; - let ether_location = T::EthereumLocation::get(); - let (tip_asset_location, tip_amount) = match asset { - Asset { id: AssetId(ref loc), fun: Fungibility::Fungible(amount) } => (loc, amount), - _ => { - tracing::debug!(target: LOG_TARGET, ?asset, "error matching tip asset"); - return Err(Error::::UnsupportedAsset.into()) - }, - }; - - ensure!(tip_amount > 0, Error::::TipAmountZero); - - let ether_gained = if *tip_asset_location != ether_location { - Self::swap_and_burn( - who.clone(), - tip_asset_location.clone(), - ether_location, - tip_amount, - ) - .inspect_err(|&e| { - tracing::debug!(target: LOG_TARGET, ?e, "error swapping asset"); - })? - } else { - burn_for_teleport::(&who.clone().into(), &asset) - .map_err(|_| Error::::BurnError)?; - tip_amount - }; + let ether_gained = Self::swap_fee_asset_and_burn(who.clone(), asset)?; // Send the tip details to BH to be allocated to the reward in the Inbound/Outbound // pallet @@ -439,6 +390,41 @@ pub mod pallet { .reanchored(&T::BridgeHubLocation::get(), &T::UniversalLocation::get()) .map_err(|_| Error::::LocationConversionFailed) } + + fn swap_fee_asset_and_burn( + who: AccountIdOf, + fee_asset: Asset, + ) -> Result + where + ::AccountId: Into, + { + let ether_location = T::EthereumLocation::get(); + let (fee_asset_location, fee_amount) = match fee_asset { + Asset { id: AssetId(ref loc), fun: Fungible(amount) } => (loc, amount), + _ => { + tracing::debug!(target: LOG_TARGET, ?fee_asset, "error matching fee asset"); + return Err(Error::::UnsupportedAsset.into()) + }, + }; + ensure!(fee_amount > 0, Error::::TipAmountZero); + + let ether_gained = if *fee_asset_location != ether_location { + Self::swap_and_burn( + who.clone(), + fee_asset_location.clone(), + ether_location, + fee_amount, + ) + .inspect_err(|&e| { + tracing::debug!(target: LOG_TARGET, ?e, "error swapping asset"); + })? + } else { + burn_for_teleport::(&who.clone().into(), &fee_asset) + .map_err(|_| Error::::BurnError)?; + fee_amount + }; + Ok(ether_gained) + } } impl ExportPausedQuery for Pallet { From 8c85471faec0b69e8ea807dc23c2653083891396 Mon Sep 17 00:00:00 2001 From: ron Date: Tue, 3 Jun 2025 18:42:34 +0800 Subject: [PATCH 05/17] Refactor a common function send_transact_call --- .../pallets/system-frontend/src/lib.rs | 48 ++++++++----------- 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/bridges/snowbridge/pallets/system-frontend/src/lib.rs b/bridges/snowbridge/pallets/system-frontend/src/lib.rs index 320161070b0c5..65f82ce8dc473 100644 --- a/bridges/snowbridge/pallets/system-frontend/src/lib.rs +++ b/bridges/snowbridge/pallets/system-frontend/src/lib.rs @@ -236,25 +236,14 @@ pub mod pallet { let ether_gained = Self::swap_fee_asset_and_burn(who, fee_asset)?; - let dest = T::BridgeHubLocation::get(); let call = Self::build_register_token_call( origin_location.clone(), asset_location, metadata, ether_gained, )?; - let remote_xcm = Self::build_remote_xcm(&call); - let message_id = Self::send_xcm(origin_location, dest.clone(), remote_xcm.clone()) - .map_err(|error| Error::::from(error))?; - - Self::deposit_event(Event::::MessageSent { - origin: T::PalletLocation::get().into(), - destination: dest, - message: remote_xcm, - message_id, - }); - Ok(()) + Self::send_transact_call(origin_location, call) } /// Add an additional relayer tip for a committed message identified by `message_id`. @@ -274,22 +263,8 @@ pub mod pallet { // Send the tip details to BH to be allocated to the reward in the Inbound/Outbound // pallet - let dest = T::BridgeHubLocation::get(); let call = Self::build_add_tip_call(who.clone(), message_id.clone(), ether_gained); - let remote_xcm = Self::build_remote_xcm(&call); - let who_location: Location = who.into(); - - let xcm_message_id = Self::send_xcm(who_location, dest.clone(), remote_xcm.clone()) - .map_err(|error| Error::::from(error))?; - - Self::deposit_event(Event::::MessageSent { - origin: T::PalletLocation::get().into(), - destination: dest, - message: remote_xcm, - message_id: xcm_message_id, - }); - - Ok(()) + Self::send_transact_call(who.into(), call) } } @@ -425,6 +400,25 @@ pub mod pallet { }; Ok(ether_gained) } + + fn send_transact_call( + origin_location: Location, + call: BridgeHubRuntime, + ) -> DispatchResult { + let dest = T::BridgeHubLocation::get(); + let remote_xcm = Self::build_remote_xcm(&call); + let message_id = Self::send_xcm(origin_location, dest.clone(), remote_xcm.clone()) + .map_err(|error| Error::::from(error))?; + + Self::deposit_event(Event::::MessageSent { + origin: T::PalletLocation::get().into(), + destination: dest, + message: remote_xcm, + message_id, + }); + + Ok(()) + } } impl ExportPausedQuery for Pallet { From b759f5709d5e4138a4a962afd8ca924d8c832127 Mon Sep 17 00:00:00 2001 From: ron Date: Tue, 3 Jun 2025 23:00:34 +0800 Subject: [PATCH 06/17] Fix the breaking tests --- .../src/tests/snowbridge_common.rs | 7 +++++++ .../src/tests/snowbridge_v2_outbound.rs | 15 +++++++++++--- .../tests/snowbridge_v2_outbound_edge_case.rs | 11 +++++++++- .../snowbridge_v2_outbound_from_rococo.rs | 3 +++ .../asset-hub-westend/src/xcm_config.rs | 20 +++++++++---------- 5 files changed, 42 insertions(+), 14 deletions(-) diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_common.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_common.rs index a397f7beeb3f2..b1c3a8673b7a6 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_common.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_common.rs @@ -267,6 +267,7 @@ pub fn fund_on_ah() { ], )) .unwrap(); + let root_sovereign = LocationToAccountId::convert_location(&Location::here()).unwrap(); AssetHubWestend::execute_with(|| { assert_ok!(::ForeignAssets::mint_into( @@ -310,11 +311,17 @@ pub fn fund_on_ah() { &AssetHubWestendSender::get(), INITIAL_FUND, )); + assert_ok!(::ForeignAssets::mint_into( + ethereum().try_into().unwrap(), + &root_sovereign, + INITIAL_FUND, + )); }); AssetHubWestend::fund_accounts(vec![(snowbridge_sovereign(), INITIAL_FUND)]); AssetHubWestend::fund_accounts(vec![(penpal_sovereign.clone(), INITIAL_FUND)]); AssetHubWestend::fund_accounts(vec![(penpal_user_sovereign.clone(), INITIAL_FUND)]); + AssetHubWestend::fund_accounts(vec![(root_sovereign.clone(), INITIAL_FUND)]); } pub fn create_pools_on_ah() { diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs index 090614d3351ef..e7b166d586ed3 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs @@ -33,7 +33,7 @@ use xcm::v5::AssetTransferFilter; #[derive(Encode, Decode, Debug, PartialEq, Clone, TypeInfo)] pub enum EthereumSystemFrontendCall { #[codec(index = 1)] - RegisterToken { asset_id: Box, metadata: AssetMetadata }, + RegisterToken { asset_id: Box, metadata: AssetMetadata, fee_asset: Asset }, } #[allow(clippy::large_enum_variant)] @@ -132,6 +132,9 @@ pub fn register_relay_token_from_asset_hub_with_sudo() { AssetHubWestend::execute_with(|| { type RuntimeOrigin = ::RuntimeOrigin; + let fees_asset = + Asset { id: AssetId(ethereum()), fun: Fungible(REMOTE_FEE_AMOUNT_IN_ETHER) }; + assert_ok!( ::SnowbridgeSystemFrontend::register_token( RuntimeOrigin::root(), @@ -140,7 +143,8 @@ pub fn register_relay_token_from_asset_hub_with_sudo() { name: "wnd".as_bytes().to_vec().try_into().unwrap(), symbol: "wnd".as_bytes().to_vec().try_into().unwrap(), decimals: 12, - } + }, + fees_asset ) ); }); @@ -154,6 +158,9 @@ pub fn register_usdt_from_owner_on_asset_hub() { AssetHubWestend::execute_with(|| { type RuntimeOrigin = ::RuntimeOrigin; + let fees_asset = + Asset { id: AssetId(ethereum()), fun: Fungible(REMOTE_FEE_AMOUNT_IN_ETHER) }; + assert_ok!( ::SnowbridgeSystemFrontend::register_token( RuntimeOrigin::signed(AssetHubWestendAssetOwner::get()), @@ -162,7 +169,8 @@ pub fn register_usdt_from_owner_on_asset_hub() { name: "usdt".as_bytes().to_vec().try_into().unwrap(), symbol: "usdt".as_bytes().to_vec().try_into().unwrap(), decimals: 6, - } + }, + fees_asset ) ); }); @@ -714,6 +722,7 @@ fn register_token_from_penpal() { EthereumSystemFrontendCall::RegisterToken { asset_id: Box::new(VersionedLocation::from(foreign_asset_at_asset_hub)), metadata: Default::default(), + fee_asset: remote_fee_asset_on_ethereum.clone(), }, ); diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound_edge_case.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound_edge_case.rs index 807fe76c02f45..495cf6f6f5601 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound_edge_case.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound_edge_case.rs @@ -67,6 +67,7 @@ fn register_penpal_a_asset_from_penpal_b_will_fail() { EthereumSystemFrontendCall::RegisterToken { asset_id: Box::new(VersionedLocation::from(penpal_a_asset_at_asset_hub)), metadata: Default::default(), + fee_asset: remote_fee_asset_on_ethereum.clone(), }, ); @@ -181,6 +182,9 @@ pub fn register_usdt_not_from_owner_on_asset_hub_will_fail() { AssetHubWestend::execute_with(|| { type RuntimeOrigin = ::RuntimeOrigin; + let fees_asset = + Asset { id: AssetId(ethereum()), fun: Fungible(REMOTE_FEE_AMOUNT_IN_ETHER) }; + assert_noop!( ::SnowbridgeSystemFrontend::register_token( // The owner is Alice, while AssetHubWestendReceiver is Bob, so it should fail @@ -190,7 +194,8 @@ pub fn register_usdt_not_from_owner_on_asset_hub_will_fail() { name: "usdt".as_bytes().to_vec().try_into().unwrap(), symbol: "usdt".as_bytes().to_vec().try_into().unwrap(), decimals: 6, - } + }, + fees_asset ), BadOrigin ); @@ -205,6 +210,9 @@ pub fn register_relay_token_from_asset_hub_user_origin_will_fail() { AssetHubWestend::execute_with(|| { type RuntimeOrigin = ::RuntimeOrigin; + let fees_asset = + Asset { id: AssetId(ethereum()), fun: Fungible(REMOTE_FEE_AMOUNT_IN_ETHER) }; + assert_noop!( ::SnowbridgeSystemFrontend::register_token( RuntimeOrigin::signed(AssetHubWestendSender::get()), @@ -214,6 +222,7 @@ pub fn register_relay_token_from_asset_hub_user_origin_will_fail() { symbol: "wnd".as_bytes().to_vec().try_into().unwrap(), decimals: 12, }, + fees_asset ), BadOrigin ); diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound_from_rococo.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound_from_rococo.rs index 8ed0fd3048e12..b7348ed1e1a60 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound_from_rococo.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound_from_rococo.rs @@ -303,10 +303,13 @@ fn register_rococo_asset_on_ethereum_from_rah() { vec![], ); + let fee_asset = Asset { id: AssetId(ethereum()), fun: Fungible(REMOTE_FEE_AMOUNT_IN_ETHER) }; + let call = EthereumSystemFrontend::EthereumSystemFrontend(EthereumSystemFrontendCall::RegisterToken { asset_id: Box::new(VersionedLocation::from(bridged_asset_at_wah.clone())), metadata: Default::default(), + fee_asset, }) .encode(); 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 c1e20eb7675a0..7b6edae2c4cec 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 @@ -50,15 +50,15 @@ use xcm_builder::{ AccountId32Aliases, AliasChildLocation, AllowExplicitUnpaidExecutionFrom, AllowHrmpNotificationsFromRelayChain, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyRecursively, DenyReserveTransferToRelayChain, DenyThenTry, - DescribeAllTerminal, DescribeFamily, EnsureXcmOrigin, ExternalConsensusLocationsConverterFor, - FrameTransactionalProcessor, FungibleAdapter, FungiblesAdapter, HashedDescription, IsConcrete, - LocalMint, MatchedConvertedConcreteId, NetworkExportTableItem, NoChecking, NonFungiblesAdapter, - ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SendXcmFeeToAccount, - SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, - SignedToAccountId32, SingleAssetExchangeAdapter, SovereignSignedViaLocation, StartsWith, - StartsWithExplicitGlobalConsensus, TakeWeightCredit, TrailingSetTopicAsId, - UnpaidRemoteExporter, UsingComponents, WeightInfoBounds, WithComputedOrigin, - WithLatestLocationConverter, WithUniqueTopic, XcmFeeManagerFromComponents, + DescribeAllTerminal, DescribeFamily, DescribeTerminus, EnsureXcmOrigin, + ExternalConsensusLocationsConverterFor, FrameTransactionalProcessor, FungibleAdapter, + FungiblesAdapter, HashedDescription, IsConcrete, LocalMint, MatchedConvertedConcreteId, + NetworkExportTableItem, NoChecking, NonFungiblesAdapter, ParentAsSuperuser, ParentIsPreset, + RelayChainAsNative, SendXcmFeeToAccount, SiblingParachainAsNative, SiblingParachainConvertsVia, + SignedAccountId32AsNative, SignedToAccountId32, SingleAssetExchangeAdapter, + SovereignSignedViaLocation, StartsWith, StartsWithExplicitGlobalConsensus, TakeWeightCredit, + TrailingSetTopicAsId, UnpaidRemoteExporter, UsingComponents, WeightInfoBounds, + WithComputedOrigin, WithLatestLocationConverter, WithUniqueTopic, XcmFeeManagerFromComponents, }; use xcm_executor::XcmExecutor; @@ -97,7 +97,7 @@ pub type LocationToAccountId = ( // Straight up local `AccountId32` origins just alias directly to `AccountId`. AccountId32Aliases, // Foreign locations alias into accounts according to a hash of their standard description. - HashedDescription>, + HashedDescription)>, // Different global consensus locations sovereign accounts. ExternalConsensusLocationsConverterFor, ); From 5d433e9ceec68734181fae89a1928af2736a1e8a Mon Sep 17 00:00:00 2001 From: ron Date: Tue, 3 Jun 2025 23:14:10 +0800 Subject: [PATCH 07/17] Add prdoc --- prdoc/pr_8725.prdoc | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 prdoc/pr_8725.prdoc diff --git a/prdoc/pr_8725.prdoc b/prdoc/pr_8725.prdoc new file mode 100644 index 0000000000000..4ed28c2db11dc --- /dev/null +++ b/prdoc/pr_8725.prdoc @@ -0,0 +1,13 @@ +title: 'Snowbridge: register polkadot native asset with fee' +doc: +- audience: Runtime Dev + description: To enforce a fee for PNA registration. +crates: +- name: snowbridge-pallet-system-frontend + bump: patch +- name: snowbridge-pallet-system-v2 + bump: patch +- name: asset-hub-westend-runtime + bump: patch +- name: bridge-hub-westend-integration-tests + bump: none From d87e93e3d29fde15c2f4dab82957084cc1970166 Mon Sep 17 00:00:00 2001 From: ron Date: Wed, 4 Jun 2025 09:31:54 +0800 Subject: [PATCH 08/17] Fix benchmark --- .../src/bridge_to_ethereum_config.rs | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/bridge_to_ethereum_config.rs index 75e330e8ffbce..34a492a1c7cc0 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/bridge_to_ethereum_config.rs @@ -24,8 +24,6 @@ use crate::{ AccountId, AssetConversion, Assets, ForeignAssets, Runtime, RuntimeEvent, }; use assets_common::{matching::FromSiblingParachain, AssetIdForTrustBackedAssetsConvert}; -#[cfg(feature = "runtime-benchmarks")] -use benchmark_helpers::DoNothingRouter; use frame_support::{parameter_types, traits::EitherOf}; use frame_system::EnsureRootWithSuccess; use parachains_common::AssetIdForTrustBackedAssets; @@ -42,6 +40,9 @@ pub mod benchmark_helpers { }; use alloc::boxed::Box; use codec::Encode; + use frame_support::traits::fungibles::Mutate; + use testnet_parachains_constants::westend::snowbridge::EthereumLocation; + use westend_runtime_constants::currency::UNITS; use xcm::prelude::*; use xcm_executor::traits::ConvertLocation; @@ -66,16 +67,30 @@ pub mod benchmark_helpers { RuntimeOrigin::from(pallet_xcm::Origin::Xcm(location)) } - fn initialize_storage(asset_location: Location, asset_owner: Location) { - let asset_owner = LocationToAccountId::convert_location(&asset_owner).unwrap(); + fn initialize_storage(asset_location: Location, asset_owner_location: Location) { + let asset_owner = LocationToAccountId::convert_location(&asset_owner_location).unwrap(); ForeignAssets::force_create( RuntimeOrigin::root(), asset_location, - asset_owner.into(), + asset_owner.clone().into(), true, 1, ) - .unwrap() + .unwrap(); + ForeignAssets::force_create( + RuntimeOrigin::root(), + EthereumLocation::get(), + asset_owner.clone().into(), + true, + 1, + ) + .unwrap(); + ForeignAssets::mint_into( + EthereumLocation::get().into(), + &asset_owner, + 5_000_000 * UNITS, + ) + .unwrap(); } fn setup_pools(caller: AccountId, asset: Location) { @@ -176,7 +191,7 @@ impl snowbridge_pallet_system_frontend::Config for Runtime { #[cfg(not(feature = "runtime-benchmarks"))] type XcmSender = XcmRouter; #[cfg(feature = "runtime-benchmarks")] - type XcmSender = DoNothingRouter; + type XcmSender = benchmark_helpers::DoNothingRouter; type AssetTransactor = AssetTransactors; type EthereumLocation = FeeAsset; type XcmExecutor = XcmExecutor; From 629386ff5e1c5cb89b7666b221e34f7c0deb2aba Mon Sep 17 00:00:00 2001 From: ron Date: Wed, 4 Jun 2025 09:56:10 +0800 Subject: [PATCH 09/17] Fix semver --- prdoc/pr_8725.prdoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/prdoc/pr_8725.prdoc b/prdoc/pr_8725.prdoc index 4ed28c2db11dc..0d1ecdef17d3e 100644 --- a/prdoc/pr_8725.prdoc +++ b/prdoc/pr_8725.prdoc @@ -5,8 +5,10 @@ doc: crates: - name: snowbridge-pallet-system-frontend bump: patch + validate: false - name: snowbridge-pallet-system-v2 bump: patch + validate: false - name: asset-hub-westend-runtime bump: patch - name: bridge-hub-westend-integration-tests From 8f557052566cc4cbfd4a448e69f58fb50669d6c3 Mon Sep 17 00:00:00 2001 From: ron Date: Wed, 4 Jun 2025 21:27:52 +0800 Subject: [PATCH 10/17] Use Location as parameter --- .../pallets/system-frontend/src/lib.rs | 29 +++++++------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/bridges/snowbridge/pallets/system-frontend/src/lib.rs b/bridges/snowbridge/pallets/system-frontend/src/lib.rs index 65f82ce8dc473..c01f743712a6b 100644 --- a/bridges/snowbridge/pallets/system-frontend/src/lib.rs +++ b/bridges/snowbridge/pallets/system-frontend/src/lib.rs @@ -231,10 +231,8 @@ pub mod pallet { let asset_location: Location = (*asset_id).try_into().map_err(|_| Error::::UnsupportedLocationVersion)?; let origin_location = T::RegisterTokenOrigin::ensure_origin(origin, &asset_location)?; - let who = T::AccountIdConverter::convert_location(&origin_location) - .ok_or(Error::::LocationConversionFailed)?; - let ether_gained = Self::swap_fee_asset_and_burn(who, fee_asset)?; + let ether_gained = Self::swap_fee_asset_and_burn(origin_location.clone(), fee_asset)?; let call = Self::build_register_token_call( origin_location.clone(), @@ -259,7 +257,7 @@ pub mod pallet { { let who = ensure_signed(origin)?; - let ether_gained = Self::swap_fee_asset_and_burn(who.clone(), asset)?; + let ether_gained = Self::swap_fee_asset_and_burn(who.clone().into(), asset)?; // Send the tip details to BH to be allocated to the reward in the Inbound/Outbound // pallet @@ -283,17 +281,15 @@ pub mod pallet { /// teleportation. Returns the amount of Ether gained if successful, or a DispatchError if /// any step fails. fn swap_and_burn( - who: AccountIdOf, + origin: Location, tip_asset_location: Location, ether_location: Location, tip_amount: u128, - ) -> Result - where - ::AccountId: Into, - { + ) -> Result { // Swap tip asset to ether let swap_path = vec![tip_asset_location.clone(), ether_location.clone()]; - let who_location: Location = who.clone().into(); + let who = T::AccountIdConverter::convert_location(&origin) + .ok_or(Error::::LocationConversionFailed)?; let ether_gained = T::Swap::swap_exact_tokens_for_tokens( who.clone(), @@ -307,7 +303,7 @@ pub mod pallet { // Burn the ether let ether_asset = Asset::from((ether_location.clone(), ether_gained)); - burn_for_teleport::(&who_location, ðer_asset) + burn_for_teleport::(&origin, ðer_asset) .map_err(|_| Error::::BurnError)?; Ok(ether_gained) @@ -367,12 +363,9 @@ pub mod pallet { } fn swap_fee_asset_and_burn( - who: AccountIdOf, + origin: Location, fee_asset: Asset, - ) -> Result - where - ::AccountId: Into, - { + ) -> Result { let ether_location = T::EthereumLocation::get(); let (fee_asset_location, fee_amount) = match fee_asset { Asset { id: AssetId(ref loc), fun: Fungible(amount) } => (loc, amount), @@ -385,7 +378,7 @@ pub mod pallet { let ether_gained = if *fee_asset_location != ether_location { Self::swap_and_burn( - who.clone(), + origin.clone(), fee_asset_location.clone(), ether_location, fee_amount, @@ -394,7 +387,7 @@ pub mod pallet { tracing::debug!(target: LOG_TARGET, ?e, "error swapping asset"); })? } else { - burn_for_teleport::(&who.clone().into(), &fee_asset) + burn_for_teleport::(&origin, &fee_asset) .map_err(|_| Error::::BurnError)?; fee_amount }; From 029da092d46c2f4d3ba0ac5d685391dc4c053ffe Mon Sep 17 00:00:00 2001 From: ron Date: Wed, 4 Jun 2025 21:48:36 +0800 Subject: [PATCH 11/17] Allow zero fee --- bridges/snowbridge/pallets/system-frontend/src/lib.rs | 4 +++- .../src/tests/snowbridge_v2_outbound.rs | 11 +++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/bridges/snowbridge/pallets/system-frontend/src/lib.rs b/bridges/snowbridge/pallets/system-frontend/src/lib.rs index c01f743712a6b..7d2eeab49cba2 100644 --- a/bridges/snowbridge/pallets/system-frontend/src/lib.rs +++ b/bridges/snowbridge/pallets/system-frontend/src/lib.rs @@ -374,7 +374,9 @@ pub mod pallet { return Err(Error::::UnsupportedAsset.into()) }, }; - ensure!(fee_amount > 0, Error::::TipAmountZero); + if fee_amount == 0 { + return Ok(fee_amount) + } let ether_gained = if *fee_asset_location != ether_location { Self::swap_and_burn( diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs index e7b166d586ed3..aa6d17ecd8c21 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs @@ -132,8 +132,7 @@ pub fn register_relay_token_from_asset_hub_with_sudo() { AssetHubWestend::execute_with(|| { type RuntimeOrigin = ::RuntimeOrigin; - let fees_asset = - Asset { id: AssetId(ethereum()), fun: Fungible(REMOTE_FEE_AMOUNT_IN_ETHER) }; + let fees_asset = Asset { id: AssetId(ethereum()), fun: Fungible(0) }; assert_ok!( ::SnowbridgeSystemFrontend::register_token( @@ -148,6 +147,14 @@ pub fn register_relay_token_from_asset_hub_with_sudo() { ) ); }); + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::EthereumOutboundQueueV2(snowbridge_pallet_outbound_queue_v2::Event::MessageQueued{ .. }) => {},] + ); + }); } #[test] From 27063ec8219cc4f794e3b0bcd2ecd20226d158c1 Mon Sep 17 00:00:00 2001 From: ron Date: Wed, 4 Jun 2025 23:11:44 +0800 Subject: [PATCH 12/17] Remove the DescribeTerminus converter --- .../src/tests/snowbridge_common.rs | 7 ------- .../asset-hub-westend/src/xcm_config.rs | 20 +++++++++---------- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_common.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_common.rs index b1c3a8673b7a6..a397f7beeb3f2 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_common.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_common.rs @@ -267,7 +267,6 @@ pub fn fund_on_ah() { ], )) .unwrap(); - let root_sovereign = LocationToAccountId::convert_location(&Location::here()).unwrap(); AssetHubWestend::execute_with(|| { assert_ok!(::ForeignAssets::mint_into( @@ -311,17 +310,11 @@ pub fn fund_on_ah() { &AssetHubWestendSender::get(), INITIAL_FUND, )); - assert_ok!(::ForeignAssets::mint_into( - ethereum().try_into().unwrap(), - &root_sovereign, - INITIAL_FUND, - )); }); AssetHubWestend::fund_accounts(vec![(snowbridge_sovereign(), INITIAL_FUND)]); AssetHubWestend::fund_accounts(vec![(penpal_sovereign.clone(), INITIAL_FUND)]); AssetHubWestend::fund_accounts(vec![(penpal_user_sovereign.clone(), INITIAL_FUND)]); - AssetHubWestend::fund_accounts(vec![(root_sovereign.clone(), INITIAL_FUND)]); } pub fn create_pools_on_ah() { 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 7b6edae2c4cec..c1e20eb7675a0 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 @@ -50,15 +50,15 @@ use xcm_builder::{ AccountId32Aliases, AliasChildLocation, AllowExplicitUnpaidExecutionFrom, AllowHrmpNotificationsFromRelayChain, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyRecursively, DenyReserveTransferToRelayChain, DenyThenTry, - DescribeAllTerminal, DescribeFamily, DescribeTerminus, EnsureXcmOrigin, - ExternalConsensusLocationsConverterFor, FrameTransactionalProcessor, FungibleAdapter, - FungiblesAdapter, HashedDescription, IsConcrete, LocalMint, MatchedConvertedConcreteId, - NetworkExportTableItem, NoChecking, NonFungiblesAdapter, ParentAsSuperuser, ParentIsPreset, - RelayChainAsNative, SendXcmFeeToAccount, SiblingParachainAsNative, SiblingParachainConvertsVia, - SignedAccountId32AsNative, SignedToAccountId32, SingleAssetExchangeAdapter, - SovereignSignedViaLocation, StartsWith, StartsWithExplicitGlobalConsensus, TakeWeightCredit, - TrailingSetTopicAsId, UnpaidRemoteExporter, UsingComponents, WeightInfoBounds, - WithComputedOrigin, WithLatestLocationConverter, WithUniqueTopic, XcmFeeManagerFromComponents, + DescribeAllTerminal, DescribeFamily, EnsureXcmOrigin, ExternalConsensusLocationsConverterFor, + FrameTransactionalProcessor, FungibleAdapter, FungiblesAdapter, HashedDescription, IsConcrete, + LocalMint, MatchedConvertedConcreteId, NetworkExportTableItem, NoChecking, NonFungiblesAdapter, + ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SendXcmFeeToAccount, + SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, + SignedToAccountId32, SingleAssetExchangeAdapter, SovereignSignedViaLocation, StartsWith, + StartsWithExplicitGlobalConsensus, TakeWeightCredit, TrailingSetTopicAsId, + UnpaidRemoteExporter, UsingComponents, WeightInfoBounds, WithComputedOrigin, + WithLatestLocationConverter, WithUniqueTopic, XcmFeeManagerFromComponents, }; use xcm_executor::XcmExecutor; @@ -97,7 +97,7 @@ pub type LocationToAccountId = ( // Straight up local `AccountId32` origins just alias directly to `AccountId`. AccountId32Aliases, // Foreign locations alias into accounts according to a hash of their standard description. - HashedDescription)>, + HashedDescription>, // Different global consensus locations sovereign accounts. ExternalConsensusLocationsConverterFor, ); From 7f764c62783b553fa307ef0212dea48a15eee77c Mon Sep 17 00:00:00 2001 From: ron Date: Wed, 4 Jun 2025 23:47:03 +0800 Subject: [PATCH 13/17] Benchmark with swapped Ether --- .../system-frontend/src/benchmarking.rs | 14 +++++----- .../src/bridge_to_ethereum_config.rs | 27 +++++-------------- 2 files changed, 15 insertions(+), 26 deletions(-) diff --git a/bridges/snowbridge/pallets/system-frontend/src/benchmarking.rs b/bridges/snowbridge/pallets/system-frontend/src/benchmarking.rs index b01bd67886806..c5fc62cc6f678 100644 --- a/bridges/snowbridge/pallets/system-frontend/src/benchmarking.rs +++ b/bridges/snowbridge/pallets/system-frontend/src/benchmarking.rs @@ -7,6 +7,7 @@ use crate::Pallet as SnowbridgeControlFrontend; use frame_benchmarking::v2::*; use frame_system::RawOrigin; use xcm::prelude::{Location, *}; +use xcm_executor::traits::ConvertLocation; #[benchmarks(where ::AccountId: Into)] mod benchmarks { @@ -18,7 +19,11 @@ mod benchmarks { let asset_location: Location = Location::new(1, [Parachain(2000), GeneralIndex(1)]); let asset_id = Box::new(VersionedLocation::from(asset_location.clone())); - T::Helper::initialize_storage(asset_location, origin_location); + T::Helper::initialize_storage(asset_location, origin_location.clone()); + + let ether = T::EthereumLocation::get(); + let asset_owner = T::AccountIdConverter::convert_location(&origin_location).unwrap(); + T::Helper::setup_pools(asset_owner, ether.clone()); let asset_metadata = AssetMetadata { name: "pal".as_bytes().to_vec().try_into().unwrap(), @@ -26,13 +31,10 @@ mod benchmarks { decimals: 12, }; - let asset = Asset::from(( - Location::new(2, [GlobalConsensus(Ethereum { chain_id: 11155111 })]), - 1000, - )); + let fee_asset = Asset::from((Location::parent(), 1_000_000u128)); #[extrinsic_call] - _(origin as T::RuntimeOrigin, asset_id, asset_metadata, asset); + _(origin as T::RuntimeOrigin, asset_id, asset_metadata, fee_asset); Ok(()) } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/bridge_to_ethereum_config.rs index 34a492a1c7cc0..3fd8cd8aa7872 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/bridge_to_ethereum_config.rs @@ -40,9 +40,6 @@ pub mod benchmark_helpers { }; use alloc::boxed::Box; use codec::Encode; - use frame_support::traits::fungibles::Mutate; - use testnet_parachains_constants::westend::snowbridge::EthereumLocation; - use westend_runtime_constants::currency::UNITS; use xcm::prelude::*; use xcm_executor::traits::ConvertLocation; @@ -77,28 +74,18 @@ pub mod benchmark_helpers { 1, ) .unwrap(); - ForeignAssets::force_create( - RuntimeOrigin::root(), - EthereumLocation::get(), - asset_owner.clone().into(), - true, - 1, - ) - .unwrap(); - ForeignAssets::mint_into( - EthereumLocation::get().into(), - &asset_owner, - 5_000_000 * UNITS, - ) - .unwrap(); } fn setup_pools(caller: AccountId, asset: Location) { // Prefund the caller's account with DOT - Balances::force_set_balance(RuntimeOrigin::root(), caller.into(), 10_000_000_000_000) - .unwrap(); + Balances::force_set_balance( + RuntimeOrigin::root(), + caller.clone().into(), + 10_000_000_000_000, + ) + .unwrap(); - let asset_owner = LocationToAccountId::convert_location(&asset).unwrap(); + let asset_owner = caller.clone(); ForeignAssets::force_create( RuntimeOrigin::root(), asset.clone(), From 569c342894ac727cb92c6c45d3089858e9122de4 Mon Sep 17 00:00:00 2001 From: ron Date: Thu, 5 Jun 2025 00:26:31 +0800 Subject: [PATCH 14/17] More tests --- .../pallets/system-frontend/src/tests.rs | 25 +++++++++++++++++++ .../src/tests/snowbridge_v2_outbound.rs | 8 +++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/bridges/snowbridge/pallets/system-frontend/src/tests.rs b/bridges/snowbridge/pallets/system-frontend/src/tests.rs index 541b59359a97f..53412fe9557c2 100644 --- a/bridges/snowbridge/pallets/system-frontend/src/tests.rs +++ b/bridges/snowbridge/pallets/system-frontend/src/tests.rs @@ -265,3 +265,28 @@ fn tip_fails_due_to_swap_error() { ); }); } + +#[test] +fn register_token_with_non_ether_fee_asset_succeeds() { + new_test_ext().execute_with(|| { + let origin_location = Location::new(1, [Parachain(2000)]); + let origin = make_xcm_origin(origin_location.clone()); + let asset_location: Location = Location::new(1, [Parachain(2000), GeneralIndex(1)]); + let asset_id = Box::new(VersionedLocation::from(asset_location)); + let asset_metadata = AssetMetadata { + name: "pal".as_bytes().to_vec().try_into().unwrap(), + symbol: "pal".as_bytes().to_vec().try_into().unwrap(), + decimals: 12, + }; + + let fee_amount = 1000; + let fee_asset = Asset::from((Location::parent(), fee_amount)); + + assert_ok!(EthereumSystemFrontend::register_token( + origin.clone(), + asset_id.clone(), + asset_metadata.clone(), + fee_asset.clone(), + )); + }); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs index aa6d17ecd8c21..4924ca5edd0b2 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs @@ -162,11 +162,13 @@ pub fn register_usdt_from_owner_on_asset_hub() { fund_on_bh(); register_assets_on_ah(); fund_on_ah(); + set_up_eth_and_dot_pool(); AssetHubWestend::execute_with(|| { type RuntimeOrigin = ::RuntimeOrigin; + type RuntimeEvent = ::RuntimeEvent; let fees_asset = - Asset { id: AssetId(ethereum()), fun: Fungible(REMOTE_FEE_AMOUNT_IN_ETHER) }; + Asset { id: AssetId(Location::parent()), fun: Fungible(1_000_000_000u128) }; assert_ok!( ::SnowbridgeSystemFrontend::register_token( @@ -180,6 +182,10 @@ pub fn register_usdt_from_owner_on_asset_hub() { fees_asset ) ); + assert_expected_events!( + AssetHubWestend, + vec![RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::SwapExecuted { .. }) => {},] + ); }); BridgeHubWestend::execute_with(|| { From 5df1115b78964a40f20210635ae3dbd6411f2925 Mon Sep 17 00:00:00 2001 From: ron Date: Thu, 5 Jun 2025 00:50:23 +0800 Subject: [PATCH 15/17] Skip fee handling for Root location --- bridges/snowbridge/pallets/system-frontend/src/lib.rs | 5 +++-- .../bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/bridges/snowbridge/pallets/system-frontend/src/lib.rs b/bridges/snowbridge/pallets/system-frontend/src/lib.rs index 7d2eeab49cba2..9d46a7cb605ff 100644 --- a/bridges/snowbridge/pallets/system-frontend/src/lib.rs +++ b/bridges/snowbridge/pallets/system-frontend/src/lib.rs @@ -374,8 +374,9 @@ pub mod pallet { return Err(Error::::UnsupportedAsset.into()) }, }; - if fee_amount == 0 { - return Ok(fee_amount) + // Skip fee for root location + if fee_amount == 0 || origin.clone().is_here() { + return Ok(0) } let ether_gained = if *fee_asset_location != ether_location { diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs index 4924ca5edd0b2..2f9f06b46c5b8 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs @@ -132,7 +132,7 @@ pub fn register_relay_token_from_asset_hub_with_sudo() { AssetHubWestend::execute_with(|| { type RuntimeOrigin = ::RuntimeOrigin; - let fees_asset = Asset { id: AssetId(ethereum()), fun: Fungible(0) }; + let fees_asset = Asset { id: AssetId(ethereum()), fun: Fungible(1) }; assert_ok!( ::SnowbridgeSystemFrontend::register_token( From e2e004e9cf55c56cb588369d1dd27bbd8581b2ac Mon Sep 17 00:00:00 2001 From: ron Date: Thu, 5 Jun 2025 13:39:18 +0800 Subject: [PATCH 16/17] Add backend execution fee --- .../system-frontend/src/backend_weights.rs | 22 ++++++++++++++++++- .../pallets/system-frontend/src/lib.rs | 3 +++ .../snowbridge_pallet_system_backend.rs | 20 +++++++++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/bridges/snowbridge/pallets/system-frontend/src/backend_weights.rs b/bridges/snowbridge/pallets/system-frontend/src/backend_weights.rs index 201e28029a96a..75c188df91b3a 100644 --- a/bridges/snowbridge/pallets/system-frontend/src/backend_weights.rs +++ b/bridges/snowbridge/pallets/system-frontend/src/backend_weights.rs @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: 2023 Snowfork //! XCM Execution weights for invoking the backend implementation -use frame_support::weights::Weight; +use frame_support::weights::{constants::RocksDbWeight, Weight}; /// XCM Execution weights for invoking the backend implementation pub trait BackendWeightInfo { @@ -10,6 +10,9 @@ pub trait BackendWeightInfo { /// using `Transact`. fn transact_register_token() -> Weight; fn transact_add_tip() -> Weight; + fn do_process_message() -> Weight; + fn commit_single() -> Weight; + fn submit_delivery_receipt() -> Weight; } impl BackendWeightInfo for () { @@ -19,4 +22,21 @@ impl BackendWeightInfo for () { fn transact_add_tip() -> Weight { Weight::from_parts(100_000_000, 10000) } + fn do_process_message() -> Weight { + Weight::from_parts(39_000_000, 3485) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + fn commit_single() -> Weight { + Weight::from_parts(9_000_000, 1586) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + + fn submit_delivery_receipt() -> 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/system-frontend/src/lib.rs b/bridges/snowbridge/pallets/system-frontend/src/lib.rs index 9d46a7cb605ff..b99328968e5f2 100644 --- a/bridges/snowbridge/pallets/system-frontend/src/lib.rs +++ b/bridges/snowbridge/pallets/system-frontend/src/lib.rs @@ -219,6 +219,9 @@ pub mod pallet { #[pallet::weight( T::WeightInfo::register_token() .saturating_add(T::BackendWeightInfo::transact_register_token()) + .saturating_add(T::BackendWeightInfo::do_process_message()) + .saturating_add(T::BackendWeightInfo::commit_single()) + .saturating_add(T::BackendWeightInfo::submit_delivery_receipt()) )] pub fn register_token( origin: OriginFor, diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/snowbridge_pallet_system_backend.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/snowbridge_pallet_system_backend.rs index 4170cc5483cde..1cb91f2dcfdf2 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/snowbridge_pallet_system_backend.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/snowbridge_pallet_system_backend.rs @@ -37,4 +37,24 @@ impl snowbridge_pallet_system_frontend::BackendWeightIn .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } + + fn do_process_message() -> Weight { + Weight::from_parts(19_000_000, 0) + .saturating_add(Weight::from_parts(0, 1527)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(4)) + } + + fn commit_single() -> Weight { + Weight::from_parts(10_000_000, 0) + .saturating_add(Weight::from_parts(0, 1620)) + .saturating_add(T::DbWeight::get().reads(1)) + } + + fn submit_delivery_receipt() -> Weight { + Weight::from_parts(68_000_000, 0) + .saturating_add(Weight::from_parts(0, 3785)) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(1)) + } } From 5a3e96699ecb7037bb1cee16932d419e9e8a5fb1 Mon Sep 17 00:00:00 2001 From: ron Date: Thu, 5 Jun 2025 18:28:55 +0800 Subject: [PATCH 17/17] Check root origin first --- bridges/snowbridge/pallets/system-frontend/src/lib.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/bridges/snowbridge/pallets/system-frontend/src/lib.rs b/bridges/snowbridge/pallets/system-frontend/src/lib.rs index b99328968e5f2..af95fbcb0320d 100644 --- a/bridges/snowbridge/pallets/system-frontend/src/lib.rs +++ b/bridges/snowbridge/pallets/system-frontend/src/lib.rs @@ -235,7 +235,12 @@ pub mod pallet { (*asset_id).try_into().map_err(|_| Error::::UnsupportedLocationVersion)?; let origin_location = T::RegisterTokenOrigin::ensure_origin(origin, &asset_location)?; - let ether_gained = Self::swap_fee_asset_and_burn(origin_location.clone(), fee_asset)?; + let ether_gained = if origin_location.is_here() { + // Root origin/location does not pay any fees/tip. + 0 + } else { + Self::swap_fee_asset_and_burn(origin_location.clone(), fee_asset)? + }; let call = Self::build_register_token_call( origin_location.clone(), @@ -377,8 +382,7 @@ pub mod pallet { return Err(Error::::UnsupportedAsset.into()) }, }; - // Skip fee for root location - if fee_amount == 0 || origin.clone().is_here() { + if fee_amount == 0 { return Ok(0) }