From b86a5aba31a453ba8726805bfbb8f14a637ad483 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Fri, 10 Jan 2025 09:07:52 +0200 Subject: [PATCH] port integration tests to westend --- .../bridges/bridge-hub-westend/src/lib.rs | 3 + .../bridges/bridge-hub-westend/src/lib.rs | 4 +- .../src/tests/snowbridge.rs | 730 +++++++++++++++++- 3 files changed, 730 insertions(+), 7 deletions(-) diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/lib.rs index b548e3b7e64c..1b6f79651887 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/lib.rs @@ -18,6 +18,7 @@ pub mod genesis; pub use bridge_hub_westend_runtime::{ self, xcm_config::XcmConfig as BridgeHubWestendXcmConfig, ExistentialDeposit as BridgeHubWestendExistentialDeposit, + RuntimeOrigin as BridgeHubWestendRuntimeOrigin, }; // Substrate @@ -47,6 +48,8 @@ decl_test_parachains! { PolkadotXcm: bridge_hub_westend_runtime::PolkadotXcm, Balances: bridge_hub_westend_runtime::Balances, EthereumSystem: bridge_hub_westend_runtime::EthereumSystem, + EthereumInboundQueue: bridge_hub_westend_runtime::EthereumInboundQueue, + EthereumOutboundQueue: bridge_hub_westend_runtime::EthereumOutboundQueue, } }, } diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs index 501ddb84d425..3d4d4f58e3b5 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs @@ -51,9 +51,11 @@ mod imports { }, bridge_hub_westend_emulated_chain::{ genesis::ED as BRIDGE_HUB_WESTEND_ED, BridgeHubWestendExistentialDeposit, - BridgeHubWestendParaPallet as BridgeHubWestendPallet, BridgeHubWestendXcmConfig, + BridgeHubWestendParaPallet as BridgeHubWestendPallet, BridgeHubWestendRuntimeOrigin, + BridgeHubWestendXcmConfig, }, penpal_emulated_chain::{ + self, penpal_runtime::xcm_config::{ CustomizableAssetFromSystemAssetHub as PenpalCustomizableAssetFromSystemAssetHub, LocalTeleportableToAssetHub as PenpalLocalTeleportableToAssetHub, 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..f9c2871ad19a 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 @@ -12,15 +12,20 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -use crate::imports::*; +use crate::{imports::*, tests::penpal_emulated_chain::penpal_runtime}; use asset_hub_westend_runtime::xcm_config::bridging::to_ethereum::DefaultBridgeHubEthereumBaseFee; -use bridge_hub_westend_runtime::EthereumInboundQueue; +use bridge_hub_westend_runtime::{ + bridge_to_ethereum_config::EthereumGatewayAddress, EthereumBeaconClient, EthereumInboundQueue, +}; use codec::{Decode, Encode}; -use emulated_integration_tests_common::RESERVABLE_ASSET_ID; +use emulated_integration_tests_common::{PENPAL_B_ID, RESERVABLE_ASSET_ID}; use frame_support::pallet_prelude::TypeInfo; use hex_literal::hex; use rococo_westend_system_emulated_network::asset_hub_westend_emulated_chain::genesis::AssetHubWestendAssetOwner; -use snowbridge_core::{outbound::OperatingMode, AssetMetadata, TokenIdOf}; +use snowbridge_core::{ + inbound::InboundQueueFixture, outbound::OperatingMode, AssetMetadata, TokenIdOf, +}; +use snowbridge_pallet_inbound_queue_fixtures::send_native_eth::make_send_native_eth_message; use snowbridge_router_primitives::inbound::{ Command, Destination, EthereumLocationsConverterFor, MessageV1, VersionedMessage, }; @@ -28,19 +33,25 @@ use sp_core::H256; use testnet_parachains_constants::westend::snowbridge::EthereumNetwork; 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 INITIAL_FUND: u128 = 5_000_000_000_000; const ETHEREUM_DESTINATION_ADDRESS: [u8; 20] = hex!("44a57ee2f2FCcb85FDa2B0B18EBD0D8D2333700e"); const XCM_FEE: u128 = 100_000_000_000; +const INSUFFICIENT_XCM_FEE: u128 = 1000; const TOKEN_AMOUNT: u128 = 100_000_000_000; +const TREASURY_ACCOUNT: [u8; 32] = + hex!("6d6f646c70792f74727372790000000000000000000000000000000000000000"); #[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)] pub enum ControlCall { #[codec(index = 3)] CreateAgent, #[codec(index = 4)] - CreateChannel { mode: OperatingMode }, + CreateChannel { + mode: OperatingMode, + }, + Pe, } #[allow(clippy::large_enum_variant)] @@ -50,6 +61,153 @@ pub enum SnowbridgeControl { Control(ControlCall), } +pub fn send_inbound_message(fixture: InboundQueueFixture) -> DispatchResult { + EthereumBeaconClient::store_finalized_header( + fixture.finalized_header, + fixture.block_roots_root, + ) + .unwrap(); + EthereumInboundQueue::submit( + BridgeHubWestendRuntimeOrigin::signed(BridgeHubWestendSender::get()), + fixture.message, + ) +} + +/// Create an agent on Ethereum. An agent is a representation of an entity in the Polkadot +/// ecosystem (like a parachain) on Ethereum. +#[test] +#[ignore] +fn create_agent() { + let origin_para: u32 = 1001; + // Fund the origin parachain sovereign account so that it can pay execution fees. + BridgeHubWestend::fund_para_sovereign(origin_para.into(), INITIAL_FUND); + + let sudo_origin = ::RuntimeOrigin::root(); + let destination = Westend::child_location_of(BridgeHubWestend::para_id()).into(); + + let create_agent_call = SnowbridgeControl::Control(ControlCall::CreateAgent {}); + // Construct XCM to create an agent for para 1001 + let remote_xcm = VersionedXcm::from(Xcm(vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + DescendOrigin(Parachain(origin_para).into()), + Transact { + origin_kind: OriginKind::Xcm, + call: create_agent_call.encode().into(), + fallback_max_weight: None, + }, + ])); + + // Westend Global Consensus + // Send XCM message from Relay Chain to Bridge Hub source Parachain + Westend::execute_with(|| { + assert_ok!(::XcmPallet::send( + sudo_origin, + bx!(destination), + bx!(remote_xcm), + )); + + type RuntimeEvent = ::RuntimeEvent; + // Check that the Transact message was sent + assert_expected_events!( + Westend, + vec![ + RuntimeEvent::XcmPallet(pallet_xcm::Event::Sent { .. }) => {}, + ] + ); + }); + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + // Check that a message was sent to Ethereum to create the agent + assert_expected_events!( + BridgeHubWestend, + vec![ + RuntimeEvent::EthereumSystem(snowbridge_pallet_system::Event::CreateAgent { + .. + }) => {}, + ] + ); + }); +} + +/// Create a channel for a consensus system. A channel is a bidirectional messaging channel +/// between BridgeHub and Ethereum. +#[test] +#[ignore] +fn create_channel() { + let origin_para: u32 = 1001; + // Fund AssetHub sovereign account so that it can pay execution fees. + BridgeHubWestend::fund_para_sovereign(origin_para.into(), INITIAL_FUND); + + let sudo_origin = ::RuntimeOrigin::root(); + let destination: VersionedLocation = + Westend::child_location_of(BridgeHubWestend::para_id()).into(); + + let create_agent_call = SnowbridgeControl::Control(ControlCall::CreateAgent {}); + // Construct XCM to create an agent for para 1001 + let create_agent_xcm = VersionedXcm::from(Xcm(vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + DescendOrigin(Parachain(origin_para).into()), + Transact { + origin_kind: OriginKind::Xcm, + call: create_agent_call.encode().into(), + fallback_max_weight: None, + }, + ])); + + let create_channel_call = + SnowbridgeControl::Control(ControlCall::CreateChannel { mode: OperatingMode::Normal }); + // Construct XCM to create a channel for para 1001 + let create_channel_xcm = VersionedXcm::from(Xcm(vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + DescendOrigin(Parachain(origin_para).into()), + Transact { + origin_kind: OriginKind::Xcm, + call: create_channel_call.encode().into(), + fallback_max_weight: None, + }, + ])); + + // Westend Global Consensus + // Send XCM message from Relay Chain to Bridge Hub source Parachain + Westend::execute_with(|| { + assert_ok!(::XcmPallet::send( + sudo_origin.clone(), + bx!(destination.clone()), + bx!(create_agent_xcm), + )); + + assert_ok!(::XcmPallet::send( + sudo_origin, + bx!(destination), + bx!(create_channel_xcm), + )); + + type RuntimeEvent = ::RuntimeEvent; + + assert_expected_events!( + Westend, + vec![ + RuntimeEvent::XcmPallet(pallet_xcm::Event::Sent { .. }) => {}, + ] + ); + }); + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // Check that the Channel was created + assert_expected_events!( + BridgeHubWestend, + vec![ + RuntimeEvent::EthereumSystem(snowbridge_pallet_system::Event::CreateChannel { + .. + }) => {}, + ] + ); + }); +} + /// Tests the registering of a token as an asset on AssetHub. #[test] fn register_weth_token_from_ethereum_to_asset_hub() { @@ -82,6 +240,566 @@ fn register_weth_token_from_ethereum_to_asset_hub() { }); } +/// Tests the registering of a token as an asset on AssetHub, and then subsequently sending +/// a token from Ethereum to AssetHub. +#[test] +fn send_weth_token_from_ethereum_to_asset_hub() { + let ethereum_network: NetworkId = EthereumNetwork::get().into(); + let origin_location = Location::new(2, ethereum_network); + let ethereum_sovereign: AccountId = + EthereumLocationsConverterFor::::convert_location(&origin_location).unwrap(); + + BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id().into(), INITIAL_FUND); + + // Fund ethereum sovereign on AssetHub + AssetHubWestend::fund_accounts(vec![ + (AssetHubWestendReceiver::get(), INITIAL_FUND), + (ethereum_sovereign, INITIAL_FUND), + ]); + + // Register the token + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + let message = VersionedMessage::V1(MessageV1 { + chain_id: CHAIN_ID, + command: Command::RegisterToken { token: WETH.into(), fee: XCM_FEE }, + }); + let (xcm, _) = EthereumInboundQueue::do_convert([0; 32].into(), message).unwrap(); + let _ = EthereumInboundQueue::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); + + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},] + ); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + assert_expected_events!( + AssetHubWestend, + vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Created { .. }) => {},] + ); + }); + + // Send the token + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + type EthereumInboundQueue = + ::EthereumInboundQueue; + let message_id: H256 = [0; 32].into(); + let message = VersionedMessage::V1(MessageV1 { + chain_id: CHAIN_ID, + command: Command::SendToken { + token: WETH.into(), + destination: Destination::AccountId32 { id: AssetHubWestendSender::get().into() }, + amount: 1_000_000, + fee: XCM_FEE, + }, + }); + let (xcm, _) = EthereumInboundQueue::do_convert(message_id, message).unwrap(); + assert_ok!(EthereumInboundQueue::send_xcm(xcm, AssetHubWestend::para_id().into())); + + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},] + ); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // Check that the token was received and issued as a foreign asset on AssetHub + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, + ] + ); + }); +} + +/// Tests sending a token to a 3rd party parachain, called PenPal. The token reserve is +/// still located on AssetHub. +#[test] +fn send_weth_from_ethereum_to_penpal() { + let asset_hub_sovereign = BridgeHubWestend::sovereign_account_id_of(Location::new( + 1, + [Parachain(AssetHubWestend::para_id().into())], + )); + // Fund AssetHub sovereign account so it can pay execution fees for the asset transfer + BridgeHubWestend::fund_accounts(vec![(asset_hub_sovereign.clone(), INITIAL_FUND)]); + + // Fund PenPal receiver (covering ED) + let native_id: Location = Parent.into(); + let receiver: AccountId = [ + 28, 189, 45, 67, 83, 10, 68, 112, 90, 208, 136, 175, 49, 62, 24, 248, 11, 83, 239, 22, 179, + 97, 119, 205, 75, 119, 184, 70, 242, 165, 240, 124, + ] + .into(); + PenpalB::mint_foreign_asset( + ::RuntimeOrigin::signed(PenpalAssetOwner::get()), + native_id, + receiver, + penpal_runtime::EXISTENTIAL_DEPOSIT, + ); + + PenpalB::execute_with(|| { + assert_ok!(::System::set_storage( + ::RuntimeOrigin::root(), + vec![( + PenpalCustomizableAssetFromSystemAssetHub::key().to_vec(), + Location::new(2, [GlobalConsensus(Ethereum { chain_id: CHAIN_ID })]).encode(), + )], + )); + }); + + let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); + + // The Weth asset location, identified by the contract address on Ethereum + let weth_asset_location: Location = + (Parent, Parent, ethereum_network_v5, AccountKey20 { network: None, key: WETH }).into(); + + let origin_location = (Parent, Parent, ethereum_network_v5).into(); + + // Fund ethereum sovereign on AssetHub + let ethereum_sovereign: AccountId = + EthereumLocationsConverterFor::::convert_location(&origin_location).unwrap(); + AssetHubWestend::fund_accounts(vec![(ethereum_sovereign.clone(), INITIAL_FUND)]); + + // Create asset on the Penpal parachain. + PenpalB::execute_with(|| { + assert_ok!(::ForeignAssets::force_create( + ::RuntimeOrigin::root(), + weth_asset_location.clone(), + asset_hub_sovereign.into(), + false, + 1000, + )); + + assert!(::ForeignAssets::asset_exists(weth_asset_location)); + }); + + // Register the token + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + let message = VersionedMessage::V1(MessageV1 { + chain_id: CHAIN_ID, + command: Command::RegisterToken { token: WETH.into(), fee: XCM_FEE }, + }); + let (xcm, _) = EthereumInboundQueue::do_convert([0; 32].into(), message).unwrap(); + let _ = EthereumInboundQueue::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); + + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},] + ); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + assert_expected_events!( + AssetHubWestend, + vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Created { .. }) => {},] + ); + }); + + // Send the token + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + type EthereumInboundQueue = + ::EthereumInboundQueue; + let message_id: H256 = [0; 32].into(); + let message = VersionedMessage::V1(MessageV1 { + chain_id: CHAIN_ID, + command: Command::SendToken { + token: WETH.into(), + destination: Destination::ForeignAccountId32 { + para_id: PENPAL_B_ID, + id: PenpalBReceiver::get().into(), + fee: XCM_FEE, + }, + amount: 1_000_000, + fee: XCM_FEE, + }, + }); + let (xcm, _) = EthereumInboundQueue::do_convert(message_id, message).unwrap(); + assert_ok!(EthereumInboundQueue::send_xcm(xcm, AssetHubWestend::para_id().into())); + + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},] + ); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + // Check that the assets were issued on AssetHub + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, + RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, + ] + ); + }); + + PenpalB::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + // Check that the assets were issued on PenPal + assert_expected_events!( + PenpalB, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, + ] + ); + }); +} + +/// Tests the full cycle of eth transfers: +/// - sending a token to AssetHub +/// - returning the token to Ethereum +#[test] +fn send_eth_asset_from_asset_hub_to_ethereum_and_back() { + let ethereum_network: NetworkId = EthereumNetwork::get().into(); + let origin_location = (Parent, Parent, ethereum_network).into(); + + use asset_hub_westend_runtime::xcm_config::bridging::to_ethereum::DefaultBridgeHubEthereumBaseFee; + let assethub_location = BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()); + let assethub_sovereign = BridgeHubWestend::sovereign_account_id_of(assethub_location); + let ethereum_sovereign: AccountId = + EthereumLocationsConverterFor::::convert_location(&origin_location).unwrap(); + + AssetHubWestend::force_default_xcm_version(Some(XCM_VERSION)); + BridgeHubWestend::force_default_xcm_version(Some(XCM_VERSION)); + AssetHubWestend::force_xcm_version(origin_location.clone(), XCM_VERSION); + + BridgeHubWestend::fund_accounts(vec![(assethub_sovereign.clone(), INITIAL_FUND)]); + AssetHubWestend::fund_accounts(vec![ + (AssetHubWestendReceiver::get(), INITIAL_FUND), + (ethereum_sovereign.clone(), INITIAL_FUND), + ]); + + // Register ETH + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type RuntimeOrigin = ::RuntimeOrigin; + assert_ok!(::ForeignAssets::force_create( + RuntimeOrigin::root(), + origin_location.clone(), + ethereum_sovereign.into(), + true, + 1000, + )); + + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::ForceCreated { .. }) => {}, + ] + ); + }); + const ETH_AMOUNT: u128 = 1_000_000_000_000_000_000; + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type RuntimeOrigin = ::RuntimeOrigin; + + // Set the gateway. This is needed because new fixtures use a different gateway address. + assert_ok!(::System::set_storage( + RuntimeOrigin::root(), + vec![( + EthereumGatewayAddress::key().to_vec(), + sp_core::H160(hex!("87d1f7fdfEe7f651FaBc8bFCB6E086C278b77A7d")).encode(), + )], + )); + + // Construct SendToken message and sent to inbound queue + assert_ok!(send_inbound_message(make_send_native_eth_message())); + + // Check that the send token message was sent using xcm + assert_expected_events!( + BridgeHubWestend, + vec![ + RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, + ] + ); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type RuntimeOrigin = ::RuntimeOrigin; + + let _issued_event = RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { + asset_id: origin_location.clone(), + owner: AssetHubWestendReceiver::get().into(), + amount: ETH_AMOUNT, + }); + // Check that AssetHub has issued the foreign asset + assert_expected_events!( + AssetHubWestend, + vec![ + _issued_event => {}, + ] + ); + let assets = + vec![Asset { id: AssetId(origin_location.clone()), fun: Fungible(ETH_AMOUNT) }]; + let multi_assets = VersionedAssets::from(Assets::from(assets)); + + let destination = origin_location.clone().into(); + + let beneficiary = VersionedLocation::from(Location::new( + 0, + [AccountKey20 { network: None, key: ETHEREUM_DESTINATION_ADDRESS.into() }], + )); + + let free_balance_before = + ::Balances::free_balance( + AssetHubWestendReceiver::get(), + ); + // Send the Weth back to Ethereum + ::PolkadotXcm::limited_reserve_transfer_assets( + RuntimeOrigin::signed(AssetHubWestendReceiver::get()), + Box::new(destination), + Box::new(beneficiary), + Box::new(multi_assets), + 0, + Unlimited, + ) + .unwrap(); + + let _burned_event = RuntimeEvent::ForeignAssets(pallet_assets::Event::Burned { + asset_id: origin_location.clone(), + owner: AssetHubWestendReceiver::get().into(), + balance: ETH_AMOUNT, + }); + // Check that AssetHub has issued the foreign asset + let _destination = origin_location.clone(); + assert_expected_events!( + AssetHubWestend, + vec![ + _burned_event => {}, + RuntimeEvent::PolkadotXcm(pallet_xcm::Event::Sent { + destination: _destination, .. + }) => {}, + ] + ); + + let free_balance_after = ::Balances::free_balance( + AssetHubWestendReceiver::get(), + ); + // Assert at least DefaultBridgeHubEthereumBaseFee charged from the sender + let free_balance_diff = free_balance_before - free_balance_after; + assert!(free_balance_diff > DefaultBridgeHubEthereumBaseFee::get()); + }); + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + // Check that the transfer token back to Ethereum message was queue in the Ethereum + // Outbound Queue + assert_expected_events!( + BridgeHubWestend, + vec![ + RuntimeEvent::EthereumOutboundQueue(snowbridge_pallet_outbound_queue::Event::MessageAccepted {..}) => {}, + RuntimeEvent::EthereumOutboundQueue(snowbridge_pallet_outbound_queue::Event::MessageQueued {..}) => {}, + ] + ); + + let events = BridgeHubWestend::events(); + // Check that the local fee was credited to the Snowbridge sovereign account + assert!( + events.iter().any(|event| matches!( + event, + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount: _ }) + if *who == TREASURY_ACCOUNT.into() + )), + "Snowbridge sovereign takes local fee." + ); + // Check that the remote fee was credited to the AssetHub sovereign account + assert!( + events.iter().any(|event| matches!( + event, + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount: _ }) + if *who == assethub_sovereign + )), + "AssetHub sovereign takes remote fee." + ); + }); +} + +#[test] +fn register_weth_token_in_asset_hub_fail_for_insufficient_fee() { + BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id().into(), INITIAL_FUND); + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type EthereumInboundQueue = + ::EthereumInboundQueue; + let message_id: H256 = [0; 32].into(); + let message = VersionedMessage::V1(MessageV1 { + chain_id: CHAIN_ID, + command: Command::RegisterToken { + token: WETH.into(), + // Insufficient fee which should trigger the trap + fee: INSUFFICIENT_XCM_FEE, + }, + }); + let (xcm, _) = EthereumInboundQueue::do_convert(message_id, message).unwrap(); + let _ = EthereumInboundQueue::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); + + assert_expected_events!( + BridgeHubWestend, + vec![ + RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, + ] + ); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success:false, .. }) => {}, + ] + ); + }); +} + +fn send_weth_from_ethereum_to_asset_hub_with_fee(account_id: [u8; 32], fee: u128) { + let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); + let weth_asset_location: Location = + Location::new(2, [ethereum_network_v5.into(), AccountKey20 { network: None, key: WETH }]); + // Fund asset hub sovereign on bridge hub + 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)]); + + // Register WETH + AssetHubWestend::execute_with(|| { + type RuntimeOrigin = ::RuntimeOrigin; + + assert_ok!(::ForeignAssets::force_create( + RuntimeOrigin::root(), + weth_asset_location.clone().try_into().unwrap(), + asset_hub_sovereign.into(), + false, + 1, + )); + + assert!(::ForeignAssets::asset_exists( + weth_asset_location.clone().try_into().unwrap(), + )); + }); + + // Send WETH to an existent account on asset hub + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + type EthereumInboundQueue = + ::EthereumInboundQueue; + let message_id: H256 = [0; 32].into(); + let message = VersionedMessage::V1(MessageV1 { + chain_id: CHAIN_ID, + command: Command::SendToken { + token: WETH.into(), + destination: Destination::AccountId32 { id: account_id }, + amount: 1_000_000, + fee, + }, + }); + let (xcm, _) = EthereumInboundQueue::do_convert(message_id, message).unwrap(); + assert_ok!(EthereumInboundQueue::send_xcm(xcm, AssetHubWestend::para_id().into())); + + // Check that the message was sent + assert_expected_events!( + BridgeHubWestend, + vec![ + RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, + ] + ); + }); +} + +#[test] +fn send_weth_from_ethereum_to_existent_account_on_asset_hub() { + send_weth_from_ethereum_to_asset_hub_with_fee(AssetHubWestendSender::get().into(), XCM_FEE); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // Check that the token was received and issued as a foreign asset on AssetHub + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, + ] + ); + }); +} + +#[test] +fn send_weth_from_ethereum_to_non_existent_account_on_asset_hub() { + send_weth_from_ethereum_to_asset_hub_with_fee([1; 32], XCM_FEE); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // Check that the token was received and issued as a foreign asset on AssetHub + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, + ] + ); + }); +} + +#[test] +fn send_weth_from_ethereum_to_non_existent_account_on_asset_hub_with_insufficient_fee() { + send_weth_from_ethereum_to_asset_hub_with_fee([1; 32], INSUFFICIENT_XCM_FEE); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // Check that the message was not processed successfully due to insufficient fee + + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success:false, .. }) => {}, + ] + ); + }); +} + +#[test] +fn send_weth_from_ethereum_to_non_existent_account_on_asset_hub_with_sufficient_fee_but_do_not_satisfy_ed( +) { + // On AH the xcm fee is 26_789_690 and the ED is 3_300_000 + send_weth_from_ethereum_to_asset_hub_with_fee([1; 32], 30_000_000); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // Check that the message was not processed successfully due to insufficient ED + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success:false, .. }) => {}, + ] + ); + }); +} + /// Tests the registering of a token as an asset on AssetHub, and then subsequently sending /// a token from Ethereum to AssetHub. #[test]