Skip to content
Snippets Groups Projects
Commit ec4d7e04 authored by Alistair Singh's avatar Alistair Singh Committed by github-actions[bot]
Browse files

Snowbridge: Support bridging native ETH (#6855)


Changes:
1. Use the 0x0000000000000000000000000000000000000000 token address as
Native ETH.
2. Convert it to/from `{ parents: 2, interior:
X1(GlobalConsensus(Ethereum{chain_id: 1})) }` when encountered.

Onchain changes:
This will require a governance request to register native ETH (with the
above location) in the foreign assets pallet and make it sufficient.

Related solidity changes:
https://github.com/Snowfork/snowbridge/pull/1354

TODO:
- [x] Emulated Tests

---------

Co-authored-by: default avatarVincent Geddes <117534+vgeddes@users.noreply.github.com>
Co-authored-by: default avatarBastian Köcher <git@kchr.de>
Co-authored-by: default avatarBastian Köcher <info@kchr.de>
(cherry picked from commit 4059282f)
parent 05b250a2
No related merge requests found
Pipeline #511008 waiting for manual action with stages
in 47 minutes and 35 seconds
Showing
with 478 additions and 31 deletions
......@@ -3,5 +3,6 @@
#![cfg_attr(not(feature = "std"), no_std)]
pub mod register_token;
pub mod send_native_eth;
pub mod send_token;
pub mod send_token_to_penpal;
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
// Generated, do not edit!
// See ethereum client README.md for instructions to generate
use hex_literal::hex;
use snowbridge_beacon_primitives::{
types::deneb, AncestryProof, BeaconHeader, ExecutionProof, VersionedExecutionPayloadHeader,
};
use snowbridge_core::inbound::{InboundQueueFixture, Log, Message, Proof};
use sp_core::U256;
use sp_std::vec;
pub fn make_send_native_eth_message() -> InboundQueueFixture {
InboundQueueFixture {
message: Message {
event_log: Log {
address: hex!("87d1f7fdfee7f651fabc8bfcb6e086c278b77a7d").into(),
topics: vec![
hex!("7153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84f").into(),
hex!("c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539").into(),
hex!("5f7060e971b0dc81e63f0aa41831091847d97c1a4693ac450cc128c7214e65e0").into(),
],
data: hex!("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000005f00a736aa0000000000010000000000000000000000000000000000000000008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48000064a7b3b6e00d000000000000000000e8764817000000000000000000000000").into(),
},
proof: Proof {
receipt_proof: (vec![
hex!("17cd4d05dde30703008a4f213205923630cff8e6bc9d5d95a52716bfb5551fd7").to_vec(),
], vec![
hex!("f903b4822080b903ae02f903aa018301a7fcb9010000000000000000000000000020000000000000000000004000000000000000000400000000000000000000001000000000000000000000000000000000000000000000000000000001080000000000000000000000000000000000000000080000000000020000000000000000000800010100000000000000000000000000000000000200000000000000000000000000001000000040080008000000000000000000040000000021000000002000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000200000000000000f9029ff9015d9487d1f7fdfee7f651fabc8bfcb6e086c278b77a7df884a024c5d2de620c6e25186ae16f6919eba93b6e2c1a33857cc419d9f3a00d6967e9a00000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000090a987b944cb1dcce5564e5fdecd7a54d3de27fea000000000000000000000000000000000000000000000000000000000000003e8b8c000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000208eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48f9013c9487d1f7fdfee7f651fabc8bfcb6e086c278b77a7df863a07153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84fa0c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539a05f7060e971b0dc81e63f0aa41831091847d97c1a4693ac450cc128c7214e65e0b8c000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000005f00a736aa0000000000010000000000000000000000000000000000000000008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48000064a7b3b6e00d000000000000000000e8764817000000000000000000000000").to_vec(),
]),
execution_proof: ExecutionProof {
header: BeaconHeader {
slot: 246,
proposer_index: 7,
parent_root: hex!("4faaac5d2fa0b8884fe1175c7cac1c92aac9eba5a20b4302edb98a56428c5974").into(),
state_root: hex!("882c13f1d56df781e3444a78cae565bfa1c89822c86cdb0daea71f5351231580").into(),
body_root: hex!("c47eb72204b1ca567396dacef8b0214027eb7f0789330b55166085d1f9cb4c65").into(),
},
ancestry_proof: Some(AncestryProof {
header_branch: vec![
hex!("38e2454bc93c4cfafcea772b8531e4802bbd2561366620699096dd4e591bc488").into(),
hex!("3d7389fb144ccaeca8b8e1667ce1d1538dfceb50bf1e49c4b368a223f051fda3").into(),
hex!("0d49c9c24137ad4d86ebca2f36a159573a68b5d5d60e317776c77cc8b6093034").into(),
hex!("0fadc6735bcdc2793a5039a806fbf39984c39374ed4d272c1147e1c23df88983").into(),
hex!("3a058ad4b169eebb4c754c8488d41e56a7a0e5f8b55b5ec67452a8d326585c69").into(),
hex!("de200426caa9bc03f8e0033b4ef4df1db6501924b5c10fb7867e76db942b903c").into(),
hex!("48b578632bc40eebb517501f179ffdd06d762c03e9383df16fc651eeddd18806").into(),
hex!("98d9d6904b2a6a285db4c4ae59a07100cd38ec4d9fb7a16a10fe83ec99e6ba1d").into(),
hex!("1b2bbae6e684864b714654a60778664e63ba6c3c9bed8074ec1a0380fe5042e6").into(),
hex!("eb907a888eadf5a7e2bd0a3a5a9369e409c7aa688bd4cde758d5b608c6c82785").into(),
hex!("ffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b").into(),
hex!("6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220").into(),
hex!("b7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f").into(),
],
finalized_block_root: hex!("440615588532ce496a93d189cb0ef1df7cf67d529faee0fd03213ce26ea115e5").into(),
}),
execution_header: VersionedExecutionPayloadHeader::Deneb(deneb::ExecutionPayloadHeader {
parent_hash: hex!("a8c89213b7d7d2ac76462d89e6a7384374db905b657ad803d3c86f88f86c39df").into(),
fee_recipient: hex!("0000000000000000000000000000000000000000").into(),
state_root: hex!("a1e8175213a6a43da17fae65109245867cbc60e3ada16b8ac28c6b208761c772").into(),
receipts_root: hex!("17cd4d05dde30703008a4f213205923630cff8e6bc9d5d95a52716bfb5551fd7").into(),
logs_bloom: hex!("00000000000000000000000020000000000000000000004000000000000000000400000000000000000000001000000000000000000000000000000000000000000000000000000001080000000000000000000000000000000000000000080000000000020000000000000000000800010100000000000000000000000000000000000200000000000000000000000000001000000040080008000000000000000000040000000021000000002000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000200000000000000").into(),
prev_randao: hex!("b9b26dc14ea8c57d069fde0c94ad31c2558365c3986a0c06558470f8c02e62ce").into(),
block_number: 246,
gas_limit: 62908420,
gas_used: 108540,
timestamp: 1734718384,
extra_data: hex!("d983010e08846765746888676f312e32322e358664617277696e").into(),
base_fee_per_gas: U256::from(7u64),
block_hash: hex!("878195e2ea83c74d475363d03d41a7fbfc4026d6e5bcffb713928253984a64a7").into(),
transactions_root: hex!("909139b3137666b4551b629ce6d9fb7e5e6f6def8a48d078448ec6600fe63c7f").into(),
withdrawals_root: hex!("792930bbd5baac43bcc798ee49aa8185ef76bb3b44ba62b91d86ae569e4bb535").into(),
blob_gas_used: 0,
excess_blob_gas: 0,
}),
execution_branch: vec![
hex!("5d78e26ea639df17c2194ff925f782b9522009d58cfc60e3d34ba79a19f8faf1").into(),
hex!("b46f0c01805fe212e15907981b757e6c496b0cb06664224655613dcec82505bb").into(),
hex!("db56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71").into(),
hex!("3d84b2809a36450186e5169995a5e3cab55d751aee90fd8456b33d871ccaa463").into(),
],
}
},
},
finalized_header: BeaconHeader {
slot: 608,
proposer_index: 3,
parent_root: hex!("f10c2349530dbd339a72886270e2e304bb68155af68c918c850acd9ab341350f").into(),
state_root: hex!("6df0ef4cbb4986a84ff0763727402b88636e6b5535022cd3ad6967b8dd799402").into(),
body_root: hex!("f66fc1c022f07f91c777ad5c464625fc0b43d3e7a45650567dce60011210f574").into(),
},
block_roots_root: hex!("1c0dbf54db070770f5e573b72afe0aac2b0e3cf312107d1cd73bf64d7a2ed90c").into(),
}
}
use crate::inbound::{MessageToXcm, TokenId};
use frame_support::parameter_types;
use sp_runtime::{
traits::{IdentifyAccount, MaybeEquivalence, Verify},
MultiSignature,
};
use xcm::{latest::WESTEND_GENESIS_HASH, prelude::*};
pub const CHAIN_ID: u64 = 11155111;
pub const NETWORK: NetworkId = Ethereum { chain_id: CHAIN_ID };
parameter_types! {
pub EthereumNetwork: NetworkId = NETWORK;
pub const CreateAssetCall: [u8;2] = [53, 0];
pub const CreateAssetExecutionFee: u128 = 2_000_000_000;
pub const CreateAssetDeposit: u128 = 100_000_000_000;
pub const SendTokenExecutionFee: u128 = 1_000_000_000;
pub const InboundQueuePalletInstance: u8 = 80;
pub UniversalLocation: InteriorLocation =
[GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)), Parachain(1002)].into();
pub AssetHubFromEthereum: Location = Location::new(1,[GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)),Parachain(1000)]);
}
type Signature = MultiSignature;
type AccountId = <<Signature as Verify>::Signer as IdentifyAccount>::AccountId;
type Balance = u128;
pub(crate) struct MockTokenIdConvert;
impl MaybeEquivalence<TokenId, Location> for MockTokenIdConvert {
fn convert(_id: &TokenId) -> Option<Location> {
Some(Location::parent())
}
fn convert_back(_loc: &Location) -> Option<TokenId> {
None
}
}
pub(crate) type MessageConverter = MessageToXcm<
CreateAssetCall,
CreateAssetDeposit,
InboundQueuePalletInstance,
AccountId,
Balance,
MockTokenIdConvert,
UniversalLocation,
AssetHubFromEthereum,
>;
......@@ -2,6 +2,8 @@
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
//! Converts messages from Ethereum to XCM messages
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
......@@ -394,10 +396,16 @@ where
// Convert ERC20 token address to a location that can be understood by Assets Hub.
fn convert_token_address(network: NetworkId, token: H160) -> Location {
Location::new(
2,
[GlobalConsensus(network), AccountKey20 { network: None, key: token.into() }],
)
// If the token is `0x0000000000000000000000000000000000000000` then return the location of
// native Ether.
if token == H160([0; 20]) {
Location::new(2, [GlobalConsensus(network)])
} else {
Location::new(
2,
[GlobalConsensus(network), AccountKey20 { network: None, key: token.into() }],
)
}
}
/// Constructs an XCM message destined for AssetHub that withdraws assets from the sovereign
......
use super::EthereumLocationsConverterFor;
use crate::inbound::CallIndex;
use frame_support::{assert_ok, parameter_types};
use crate::inbound::{
mock::*, Command, ConvertMessage, Destination, MessageV1, VersionedMessage, H160,
};
use frame_support::assert_ok;
use hex_literal::hex;
use xcm::prelude::*;
use xcm_executor::traits::ConvertLocation;
const NETWORK: NetworkId = Ethereum { chain_id: 11155111 };
parameter_types! {
pub EthereumNetwork: NetworkId = NETWORK;
pub const CreateAssetCall: CallIndex = [1, 1];
pub const CreateAssetExecutionFee: u128 = 123;
pub const CreateAssetDeposit: u128 = 891;
pub const SendTokenExecutionFee: u128 = 592;
}
#[test]
fn test_ethereum_network_converts_successfully() {
let expected_account: [u8; 32] =
......@@ -81,3 +72,74 @@ fn test_reanchor_all_assets() {
assert_eq!(reanchored_asset_with_ethereum_context, asset.clone());
}
}
#[test]
fn test_convert_send_token_with_weth() {
const WETH: H160 = H160([0xff; 20]);
const AMOUNT: u128 = 1_000_000;
const FEE: u128 = 1_000;
const ACCOUNT_ID: [u8; 32] = [0xBA; 32];
const MESSAGE: VersionedMessage = VersionedMessage::V1(MessageV1 {
chain_id: CHAIN_ID,
command: Command::SendToken {
token: WETH,
destination: Destination::AccountId32 { id: ACCOUNT_ID },
amount: AMOUNT,
fee: FEE,
},
});
let result = MessageConverter::convert([1; 32].into(), MESSAGE);
assert_ok!(&result);
let (xcm, fee) = result.unwrap();
assert_eq!(FEE, fee);
let expected_assets = ReserveAssetDeposited(
vec![Asset {
id: AssetId(Location {
parents: 2,
interior: Junctions::X2(
[GlobalConsensus(NETWORK), AccountKey20 { network: None, key: WETH.into() }]
.into(),
),
}),
fun: Fungible(AMOUNT),
}]
.into(),
);
let actual_assets = xcm.into_iter().find(|x| matches!(x, ReserveAssetDeposited(..)));
assert_eq!(actual_assets, Some(expected_assets))
}
#[test]
fn test_convert_send_token_with_eth() {
const ETH: H160 = H160([0x00; 20]);
const AMOUNT: u128 = 1_000_000;
const FEE: u128 = 1_000;
const ACCOUNT_ID: [u8; 32] = [0xBA; 32];
const MESSAGE: VersionedMessage = VersionedMessage::V1(MessageV1 {
chain_id: CHAIN_ID,
command: Command::SendToken {
token: ETH,
destination: Destination::AccountId32 { id: ACCOUNT_ID },
amount: AMOUNT,
fee: FEE,
},
});
let result = MessageConverter::convert([1; 32].into(), MESSAGE);
assert_ok!(&result);
let (xcm, fee) = result.unwrap();
assert_eq!(FEE, fee);
let expected_assets = ReserveAssetDeposited(
vec![Asset {
id: AssetId(Location {
parents: 2,
interior: Junctions::X1([GlobalConsensus(NETWORK)].into()),
}),
fun: Fungible(AMOUNT),
}]
.into(),
);
let actual_assets = xcm.into_iter().find(|x| matches!(x, ReserveAssetDeposited(..)));
assert_eq!(actual_assets, Some(expected_assets))
}
......@@ -289,8 +289,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,
......
......@@ -515,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::<MockTokenIdConvert, ()>::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();
......
......@@ -16,7 +16,8 @@
pub mod genesis;
pub use bridge_hub_rococo_runtime::{
xcm_config::XcmConfig as BridgeHubRococoXcmConfig, EthereumBeaconClient, EthereumInboundQueue,
self as bridge_hub_rococo_runtime, xcm_config::XcmConfig as BridgeHubRococoXcmConfig,
EthereumBeaconClient, EthereumInboundQueue,
ExistentialDeposit as BridgeHubRococoExistentialDeposit,
RuntimeOrigin as BridgeHubRococoRuntimeOrigin,
};
......
......@@ -50,6 +50,7 @@ mod imports {
AssetHubWestendParaPallet as AssetHubWestendPallet,
},
bridge_hub_rococo_emulated_chain::{
bridge_hub_rococo_runtime::bridge_to_ethereum_config::EthereumGatewayAddress,
genesis::ED as BRIDGE_HUB_ROCOCO_ED, BridgeHubRococoExistentialDeposit,
BridgeHubRococoParaPallet as BridgeHubRococoPallet, BridgeHubRococoRuntimeOrigin,
BridgeHubRococoXcmConfig, EthereumBeaconClient, EthereumInboundQueue,
......
......@@ -20,8 +20,8 @@ use hex_literal::hex;
use rococo_westend_system_emulated_network::BridgeHubRococoParaSender as BridgeHubRococoSender;
use snowbridge_core::{inbound::InboundQueueFixture, outbound::OperatingMode};
use snowbridge_pallet_inbound_queue_fixtures::{
register_token::make_register_token_message, send_token::make_send_token_message,
send_token_to_penpal::make_send_token_to_penpal_message,
register_token::make_register_token_message, send_native_eth::make_send_native_eth_message,
send_token::make_send_token_message, send_token_to_penpal::make_send_token_to_penpal_message,
};
use snowbridge_pallet_system;
use snowbridge_router_primitives::inbound::{
......@@ -238,7 +238,7 @@ 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_token_from_ethereum_to_asset_hub() {
fn send_weth_token_from_ethereum_to_asset_hub() {
BridgeHubRococo::fund_para_sovereign(AssetHubRococo::para_id().into(), INITIAL_FUND);
// Fund ethereum sovereign on AssetHub
......@@ -278,7 +278,7 @@ fn send_token_from_ethereum_to_asset_hub() {
/// Tests sending a token to a 3rd party parachain, called PenPal. The token reserve is
/// still located on AssetHub.
#[test]
fn send_token_from_ethereum_to_penpal() {
fn send_weth_from_ethereum_to_penpal() {
let asset_hub_sovereign = BridgeHubRococo::sovereign_account_id_of(Location::new(
1,
[Parachain(AssetHubRococo::para_id().into())],
......@@ -515,6 +515,176 @@ fn send_weth_asset_from_asset_hub_to_ethereum() {
});
}
/// 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 ahr_xcm_config::bridging::to_ethereum::DefaultBridgeHubEthereumBaseFee;
let assethub_location = BridgeHubRococo::sibling_location_of(AssetHubRococo::para_id());
let assethub_sovereign = BridgeHubRococo::sovereign_account_id_of(assethub_location);
let ethereum_sovereign: AccountId =
EthereumLocationsConverterFor::<AccountId>::convert_location(&origin_location).unwrap();
AssetHubRococo::force_default_xcm_version(Some(XCM_VERSION));
BridgeHubRococo::force_default_xcm_version(Some(XCM_VERSION));
AssetHubRococo::force_xcm_version(origin_location.clone(), XCM_VERSION);
BridgeHubRococo::fund_accounts(vec![(assethub_sovereign.clone(), INITIAL_FUND)]);
AssetHubRococo::fund_accounts(vec![
(AssetHubRococoReceiver::get(), INITIAL_FUND),
(ethereum_sovereign.clone(), INITIAL_FUND),
]);
// Register ETH
AssetHubRococo::execute_with(|| {
type RuntimeEvent = <AssetHubRococo as Chain>::RuntimeEvent;
type RuntimeOrigin = <AssetHubRococo as Chain>::RuntimeOrigin;
assert_ok!(<AssetHubRococo as AssetHubRococoPallet>::ForeignAssets::force_create(
RuntimeOrigin::root(),
origin_location.clone(),
ethereum_sovereign.into(),
true,
1000,
));
assert_expected_events!(
AssetHubRococo,
vec![
RuntimeEvent::ForeignAssets(pallet_assets::Event::ForceCreated { .. }) => {},
]
);
});
const ETH_AMOUNT: u128 = 1_000_000_000_000_000_000;
BridgeHubRococo::execute_with(|| {
type RuntimeEvent = <BridgeHubRococo as Chain>::RuntimeEvent;
type RuntimeOrigin = <BridgeHubRococo as Chain>::RuntimeOrigin;
// Set the gateway. This is needed because new fixtures use a different gateway address.
assert_ok!(<BridgeHubRococo as Chain>::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!(
BridgeHubRococo,
vec![
RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},
]
);
});
AssetHubRococo::execute_with(|| {
type RuntimeEvent = <AssetHubRococo as Chain>::RuntimeEvent;
type RuntimeOrigin = <AssetHubRococo as Chain>::RuntimeOrigin;
let _issued_event = RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued {
asset_id: origin_location.clone(),
owner: AssetHubRococoReceiver::get().into(),
amount: ETH_AMOUNT,
});
// Check that AssetHub has issued the foreign asset
assert_expected_events!(
AssetHubRococo,
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 = <AssetHubRococo as AssetHubRococoPallet>::Balances::free_balance(
AssetHubRococoReceiver::get(),
);
// Send the Weth back to Ethereum
<AssetHubRococo as AssetHubRococoPallet>::PolkadotXcm::limited_reserve_transfer_assets(
RuntimeOrigin::signed(AssetHubRococoReceiver::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: AssetHubRococoReceiver::get().into(),
balance: ETH_AMOUNT,
});
// Check that AssetHub has issued the foreign asset
let _destination = origin_location.clone();
assert_expected_events!(
AssetHubRococo,
vec![
_burned_event => {},
RuntimeEvent::PolkadotXcm(pallet_xcm::Event::Sent {
destination: _destination, ..
}) => {},
]
);
let free_balance_after = <AssetHubRococo as AssetHubRococoPallet>::Balances::free_balance(
AssetHubRococoReceiver::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());
});
BridgeHubRococo::execute_with(|| {
type RuntimeEvent = <BridgeHubRococo as Chain>::RuntimeEvent;
// Check that the transfer token back to Ethereum message was queue in the Ethereum
// Outbound Queue
assert_expected_events!(
BridgeHubRococo,
vec![
RuntimeEvent::EthereumOutboundQueue(snowbridge_pallet_outbound_queue::Event::MessageAccepted {..}) => {},
RuntimeEvent::EthereumOutboundQueue(snowbridge_pallet_outbound_queue::Event::MessageQueued {..}) => {},
]
);
let events = BridgeHubRococo::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() && *amount == 16903333
)),
"Snowbridge sovereign takes local fee."
);
// Check that the remote fee was credited to the AssetHub sovereign account
assert!(
events.iter().any(|event| matches!(
event,
RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount })
if *who == assethub_sovereign && *amount == 2680000000000,
)),
"AssetHub sovereign takes remote fee."
);
});
}
#[test]
fn send_token_from_ethereum_to_asset_hub_fail_for_insufficient_fund() {
// Insufficient fund
......@@ -565,7 +735,7 @@ fn register_weth_token_in_asset_hub_fail_for_insufficient_fee() {
});
}
fn send_token_from_ethereum_to_asset_hub_with_fee(account_id: [u8; 32], fee: u128) {
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 }]);
......@@ -623,8 +793,8 @@ fn send_token_from_ethereum_to_asset_hub_with_fee(account_id: [u8; 32], fee: u12
}
#[test]
fn send_token_from_ethereum_to_existent_account_on_asset_hub() {
send_token_from_ethereum_to_asset_hub_with_fee(AssetHubRococoSender::get().into(), XCM_FEE);
fn send_weth_from_ethereum_to_existent_account_on_asset_hub() {
send_weth_from_ethereum_to_asset_hub_with_fee(AssetHubRococoSender::get().into(), XCM_FEE);
AssetHubRococo::execute_with(|| {
type RuntimeEvent = <AssetHubRococo as Chain>::RuntimeEvent;
......@@ -640,8 +810,8 @@ fn send_token_from_ethereum_to_existent_account_on_asset_hub() {
}
#[test]
fn send_token_from_ethereum_to_non_existent_account_on_asset_hub() {
send_token_from_ethereum_to_asset_hub_with_fee([1; 32], XCM_FEE);
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);
AssetHubRococo::execute_with(|| {
type RuntimeEvent = <AssetHubRococo as Chain>::RuntimeEvent;
......@@ -657,8 +827,8 @@ fn send_token_from_ethereum_to_non_existent_account_on_asset_hub() {
}
#[test]
fn send_token_from_ethereum_to_non_existent_account_on_asset_hub_with_insufficient_fee() {
send_token_from_ethereum_to_asset_hub_with_fee([1; 32], INSUFFICIENT_XCM_FEE);
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);
AssetHubRococo::execute_with(|| {
type RuntimeEvent = <AssetHubRococo as Chain>::RuntimeEvent;
......@@ -675,10 +845,10 @@ fn send_token_from_ethereum_to_non_existent_account_on_asset_hub_with_insufficie
}
#[test]
fn send_token_from_ethereum_to_non_existent_account_on_asset_hub_with_sufficient_fee_but_do_not_satisfy_ed(
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_token_from_ethereum_to_asset_hub_with_fee([1; 32], 30_000_000);
send_weth_from_ethereum_to_asset_hub_with_fee([1; 32], 30_000_000);
AssetHubRococo::execute_with(|| {
type RuntimeEvent = <AssetHubRococo as Chain>::RuntimeEvent;
......
# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0
# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json
title: Snowbridge - Support bridging native ETH
doc:
- audience: Runtime User
description:
Support Native ETH as an asset type instead of only supporting WETH. WETH is still supported, but adds
support for ETH in the inbound and outbound routers.
crates:
- name: snowbridge-router-primitives
bump: minor
- name: snowbridge-pallet-inbound-queue-fixtures
bump: minor
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment