Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Snowbridge: Add support for Ether #548

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

- ParaRegistration proxy for Polkadot and Kusama ([polkadot-fellows/runtimes#520](https://github.com/polkadot-fellows/runtimes/pull/520))
- Encointer: Swap community currency for KSM from community treasuries subject to democratic decision on allowance ([polkadot-fellows/runtimes#541](https://github.com/polkadot-fellows/runtimes/pull/541))

- Delegate stake pools in Kusama ([polkadot-fellows/runtimes#540](https://github.com/polkadot-fellows/runtimes/pull/540))
- Snowbridge: Add support for bridging Ether ([polkadot-fellows/runtimes#548](https://github.com/polkadot-fellows/runtimes/pull/548))

### Changed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ const INSUFFICIENT_XCM_FEE: u128 = 1000;
const XCM_FEE: u128 = 4_000_000_000;
const TOKEN_AMOUNT: u128 = 100_000_000_000;
const AH_BASE_FEE: u128 = 2_750_872_500_000u128;
const MIN_ETHER_BALANCE: u128 = 15_000_000_000_000;
const ETHER_TOKEN_ADDRESS: [u8; 20] = [0; 20];

#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
pub enum ControlCall {
Expand Down Expand Up @@ -389,7 +391,7 @@ fn send_token_from_ethereum_to_penpal() {
/// 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_from_ethereum_to_asset_hub() {
BridgeHubPolkadot::fund_para_sovereign(AssetHubPolkadot::para_id(), INITIAL_FUND);
// Fund ethereum sovereign account on AssetHub.
AssetHubPolkadot::fund_accounts(vec![(ethereum_sovereign_account(), INITIAL_FUND)]);
Expand Down Expand Up @@ -459,16 +461,15 @@ fn send_token_from_ethereum_to_asset_hub() {
});
}

/// Tests the full cycle of token transfers:
/// - registering a token on AssetHub
/// - sending a token to AssetHub
/// - returning the token to Ethereum
#[test]
fn send_weth_asset_from_asset_hub_to_ethereum() {
let assethub_sovereign = BridgeHubPolkadot::sovereign_account_id_of(Location::new(
1,
[Parachain(AssetHubPolkadot::para_id().into())],
));
// Performs a round trip tansfer of a token, asseting success.
fn send_token_from_ethereum_to_asset_hub_and_back_works(
token_address: H160,
amount: u128,
asset_location: Location,
) {
let assethub_sovereign = BridgeHubPolkadot::sovereign_account_id_of(
BridgeHubPolkadot::sibling_location_of(AssetHubPolkadot::para_id()),
);

BridgeHubPolkadot::fund_accounts(vec![
(assethub_sovereign.clone(), INITIAL_FUND),
Expand All @@ -481,12 +482,15 @@ fn send_weth_asset_from_asset_hub_to_ethereum() {

// Set base transfer fee to Ethereum on AH.
AssetHubPolkadot::execute_with(|| {
alistair-singh marked this conversation as resolved.
Show resolved Hide resolved
type RuntimeOrigin = <AssetHubPolkadot as Chain>::RuntimeOrigin;

assert_ok!(<AssetHubPolkadot as Chain>::System::set_storage(
<AssetHubPolkadot as Chain>::RuntimeOrigin::root(),
RuntimeOrigin::root(),
vec![(BridgeHubEthereumBaseFee::key().to_vec(), AH_BASE_FEE.encode())],
));
});

// Send Token from Bridge Hub (simulates received Command from Ethereum)
BridgeHubPolkadot::execute_with(|| {
type RuntimeEvent = <BridgeHubPolkadot as Chain>::RuntimeEvent;

Expand All @@ -511,32 +515,15 @@ fn send_weth_asset_from_asset_hub_to_ethereum() {
));

let message_id: H256 = [1; 32].into();
let message = VersionedMessage::V1(MessageV1 {
chain_id: CHAIN_ID,
command: Command::RegisterToken { token: WETH.into(), fee: XCM_FEE },
});
// Convert the message to XCM
let (xcm, _) = EthereumInboundQueue::do_convert(message_id, message).unwrap();
// Send the XCM
let _ = EthereumInboundQueue::send_xcm(xcm, AssetHubPolkadot::para_id()).unwrap();

// Check that the register token message was sent using xcm
assert_expected_events!(
BridgeHubPolkadot,
vec![
RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},
]
);

// Construct SendToken message and sent to inbound queue
let message = VersionedMessage::V1(MessageV1 {
chain_id: CHAIN_ID,
command: Command::SendToken {
token: WETH.into(),
token: token_address,
destination: Destination::AccountId32 {
id: AssetHubPolkadotReceiver::get().into(),
},
amount: TOKEN_AMOUNT,
amount,
fee: XCM_FEE,
},
});
Expand All @@ -545,7 +532,7 @@ fn send_weth_asset_from_asset_hub_to_ethereum() {
// Send the XCM
let _ = EthereumInboundQueue::send_xcm(xcm, AssetHubPolkadot::para_id()).unwrap();

// Check that the send token message was sent using xcm
// Check that the message was sent
assert_expected_events!(
BridgeHubPolkadot,
vec![
Expand All @@ -554,32 +541,30 @@ fn send_weth_asset_from_asset_hub_to_ethereum() {
);
});

// check treasury account balance on BH before
let treasury_account_before = BridgeHubPolkadot::execute_with(|| {
<<BridgeHubPolkadot as BridgeHubPolkadotPallet>::Balances as frame_support::traits::fungible::Inspect<_>>::balance(&RelayTreasuryPalletAccount::get())
});

// Receive Token on Asset Hub.
AssetHubPolkadot::execute_with(|| {
type RuntimeEvent = <AssetHubPolkadot as Chain>::RuntimeEvent;
type RuntimeOrigin = <AssetHubPolkadot as Chain>::RuntimeOrigin;

// Check that AssetHub has issued the foreign asset
// Check that the token was received and issued as a foreign asset on AssetHub
assert_expected_events!(
AssetHubPolkadot,
vec![
RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {},
RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, .. }) => {
asset_id: *asset_id == asset_location,
},
]
);
let assets = vec![Asset {
id: AssetId(Location::new(
2,
[
GlobalConsensus(Ethereum { chain_id: CHAIN_ID }),
AccountKey20 { network: None, key: WETH },
],
)),
fun: Fungible(TOKEN_AMOUNT),
}];
});

let treasury_account_before = BridgeHubPolkadot::execute_with(|| {
<<BridgeHubPolkadot as BridgeHubPolkadotPallet>::Balances as frame_support::traits::fungible::Inspect<_>>::balance(&RelayTreasuryPalletAccount::get())
});

// Send Token from Asset Hub back to Ethereum.
AssetHubPolkadot::execute_with(|| {
type RuntimeOrigin = <AssetHubPolkadot as Chain>::RuntimeOrigin;

let assets = vec![Asset { id: AssetId(asset_location), fun: Fungible(amount) }];
let versioned_assets = VersionedAssets::from(Assets::from(assets));

let destination = VersionedLocation::from(Location::new(
Expand All @@ -596,7 +581,7 @@ fn send_weth_asset_from_asset_hub_to_ethereum() {
<AssetHubPolkadot as AssetHubPolkadotPallet>::Balances::free_balance(
AssetHubPolkadotReceiver::get(),
);
// Send the Weth back to Ethereum
// Send the Token back to Ethereum
assert_ok!(
<AssetHubPolkadot as AssetHubPolkadotPallet>::PolkadotXcm::limited_reserve_transfer_assets(
RuntimeOrigin::signed(AssetHubPolkadotReceiver::get()),
Expand All @@ -617,10 +602,10 @@ fn send_weth_asset_from_asset_hub_to_ethereum() {
assert!(free_balance_diff > AH_BASE_FEE);
});

// Check that message with Token was queued on the BridgeHub
BridgeHubPolkadot::execute_with(|| {
type RuntimeEvent = <BridgeHubPolkadot as Chain>::RuntimeEvent;
// Check that the transfer token back to Ethereum message was queue in the Ethereum
// Outbound Queue
// check the outbound queue
assert_expected_events!(
BridgeHubPolkadot,
vec![
Expand All @@ -637,8 +622,8 @@ fn send_weth_asset_from_asset_hub_to_ethereum() {
assert!(
events.iter().any(|event| matches!(
event,
RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount })
if *who == RelayTreasuryPalletAccount::get() && *amount == local_fee
RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount: fee_minted })
if *who == RelayTreasuryPalletAccount::get() && *fee_minted == local_fee
)),
"Snowbridge sovereign takes local fee."
);
Expand All @@ -654,6 +639,55 @@ fn send_weth_asset_from_asset_hub_to_ethereum() {
});
}

/// Tests sending Ether from Ethereum to Asset Hub and back to Ethereum
#[test]
fn send_eth_asset_from_asset_hub_to_ethereum() {
let ether_location: Location = (Parent, Parent, EthereumNetwork::get()).into();

// Register Ether as foreign asset on AH.
AssetHubPolkadot::execute_with(|| {
type RuntimeEvent = <AssetHubPolkadot as Chain>::RuntimeEvent;
type RuntimeOrigin = <AssetHubPolkadot as Chain>::RuntimeOrigin;

assert_ok!(<AssetHubPolkadot as AssetHubPolkadotPallet>::ForeignAssets::force_create(
RuntimeOrigin::root(),
ether_location.clone(),
ethereum_sovereign_account().into(),
true,
MIN_ETHER_BALANCE,
));

assert_expected_events!(
AssetHubPolkadot,
vec![
RuntimeEvent::ForeignAssets(pallet_assets::Event::ForceCreated { .. }) => {},
]
);
});

// Perform a roundtrip transfer of Ether
send_token_from_ethereum_to_asset_hub_and_back_works(
ETHER_TOKEN_ADDRESS.into(),
MIN_ETHER_BALANCE + TOKEN_AMOUNT,
ether_location,
);
}

/// Tests the full cycle of token transfers:
/// - registering a token on AssetHub
/// - sending a token to AssetHub
/// - returning the token to Ethereum
#[test]
fn send_weth_asset_from_asset_hub_to_ethereum() {
// Register WETH on Asset Hub
register_weth_token_from_ethereum_to_asset_hub();

let weth_location: Location =
(Parent, Parent, EthereumNetwork::get(), AccountKey20 { network: None, key: WETH }).into();
// Perform a roundtrip transfer of WETH
send_token_from_ethereum_to_asset_hub_and_back_works(WETH.into(), TOKEN_AMOUNT, weth_location);
}

#[test]
fn register_weth_token_in_asset_hub_fail_for_insufficient_fee() {
BridgeHubPolkadot::fund_para_sovereign(AssetHubPolkadot::para_id(), INITIAL_FUND);
Expand Down
Loading