diff --git a/cumulus b/cumulus index d57a27c854..ec8ff0c606 160000 --- a/cumulus +++ b/cumulus @@ -1 +1 @@ -Subproject commit d57a27c8545b22895b80cfa3ae279a4809b29069 +Subproject commit ec8ff0c606cf567a657469003b73b1991468c4df diff --git a/parachain/primitives/router/src/outbound/mod.rs b/parachain/primitives/router/src/outbound/mod.rs index df771a4385..34b3f232e1 100644 --- a/parachain/primitives/router/src/outbound/mod.rs +++ b/parachain/primitives/router/src/outbound/mod.rs @@ -108,11 +108,6 @@ where SendError::Unroutable })?; - if max_target_fee.is_some() { - log::error!(target: "xcm::ethereum_blob_exporter", "unroutable due not supporting max target fee."); - return Err(SendError::Unroutable) - } - // local_sub is relative to the relaychain. No conversion needed. let local_sub_location: MultiLocation = local_sub.into(); let agent_id = match AgentHashedDescription::convert_location(&local_sub_location) { @@ -135,9 +130,13 @@ where log::info!(target: "xcm::ethereum_blob_exporter", "message validated: location = {local_sub_location:?}, agent_id = '{agent_id:?}'"); - // TODO (SNO-581): Make sure we charge fees for message delivery. Currently this is set to - // zero. - Ok((ticket.encode(), MultiAssets::default())) + let mut fees = MultiAssets::new(); + if max_target_fee.is_some() { + let fee = max_target_fee.unwrap(); + fees.push((*fee).clone()); + } + + Ok((ticket.encode(), fees)) } fn deliver(blob: Vec) -> Result { diff --git a/smoketest/src/constants.rs b/smoketest/src/constants.rs index a021eca908..136f5bc171 100644 --- a/smoketest/src/constants.rs +++ b/smoketest/src/constants.rs @@ -4,6 +4,7 @@ pub const ETHEREUM_API: &str = "ws://localhost:8546"; pub const ETHEREUM_HTTP_API: &str = "http://localhost:8545"; pub const BRIDGE_HUB_WS_URL: &str = "ws://127.0.0.1:11144"; pub const BRIDGE_HUB_PARA_ID: u32 = 1013; +pub const ASSET_HUB_WS_URL: &str = "ws://127.0.0.1:12144"; pub const TEMPLATE_NODE_WS_URL: &str = "ws://127.0.0.1:13144"; @@ -17,6 +18,7 @@ pub const ETHEREUM_ADDRESS: [u8; 20] = hex!("90A987B944Cb1dCcE5564e5FDeCD7a54D3d // GatewayProxy in local setup pub const GATEWAY_PROXY_CONTRACT: [u8; 20] = hex!("EDa338E4dC46038493b885327842fD3E301CaB39"); +pub const WETH_CONTRACT: [u8; 20] = hex!("87d1f7fdfEe7f651FaBc8bFCB6E086C278b77A7d"); // Agent for sibling parachain 1001 pub const SIBLING_AGENT_ID: [u8; 32] = diff --git a/smoketest/src/helper.rs b/smoketest/src/helper.rs index d66f9a2e71..76e4353e9b 100644 --- a/smoketest/src/helper.rs +++ b/smoketest/src/helper.rs @@ -16,7 +16,7 @@ use std::time::Duration; use subxt::blocks::ExtrinsicEvents; use subxt::events::StaticEvent; use subxt::tx::{PairSigner, TxPayload}; -use subxt::{Config, OnlineClient, PolkadotConfig}; +use subxt::{Config, OnlineClient, PolkadotConfig, SubstrateConfig}; use templateXcm::{ v3::{junction::Junction, junctions::Junctions, multilocation::MultiLocation}, VersionedMultiLocation, VersionedXcm, @@ -36,6 +36,20 @@ impl Config for TemplateConfig { type ExtrinsicParams = ::ExtrinsicParams; } +/// Custom config that works with Statemint +pub enum AssetHubConfig {} + +impl Config for AssetHubConfig { + type Index = ::Index; + type Hash = ::Hash; + type AccountId = ::AccountId; + type Address = ::Address; + type Signature = ::Signature; + type Hasher = ::Hasher; + type Header = ::Header; + type ExtrinsicParams = ::ExtrinsicParams; +} + pub struct TestClients { pub bridge_hub_client: Box>, pub template_client: Box>, diff --git a/smoketest/tests/transfer_token.rs b/smoketest/tests/transfer_token.rs new file mode 100644 index 0000000000..207e1807ce --- /dev/null +++ b/smoketest/tests/transfer_token.rs @@ -0,0 +1,114 @@ +use ethers::{ + providers::{Provider, Ws}, + types::Address, +}; +use std::{sync::Arc, time::Duration}; +use ethers::prelude::Middleware; +use snowbridge_smoketest::contracts::weth9::WETH9; +use subxt::{ + tx::{PairSigner}, + OnlineClient, PolkadotConfig, +}; +use snowbridge_smoketest::constants::{ASSET_HUB_WS_URL, ETHEREUM_API, GATEWAY_PROXY_CONTRACT, WETH_CONTRACT}; +use sp_core::{sr25519::Pair, Pair as PairT}; +use snowbridge_smoketest::{ + contracts::weth9::{TransferFilter}, + parachains::{ + assethub::api::runtime_types::xcm::{ + v3::{ + junction::{Junction, NetworkId}, + junctions::Junctions, + multiasset::{AssetId, Fungibility, MultiAsset, MultiAssets}, + multilocation::MultiLocation, + }, + VersionedMultiAssets, VersionedMultiLocation, + }, + assethub::{self}, + }, +}; +use hex_literal::hex; +use sp_core::bytes::to_hex; +use subxt::tx::TxPayload; +use assethub::api::bridge_transfer::calls::TransactionApi; +use futures::StreamExt; +use snowbridge_smoketest::helper::{AssetHubConfig, TemplateConfig}; + +const DESTINATION_ADDRESS: [u8; 20] = hex!("44a57ee2f2FCcb85FDa2B0B18EBD0D8D2333700e"); + +#[tokio::test] +async fn transfer_token() { + let ethereum_provider = Provider::::connect(ETHEREUM_API) + .await + .unwrap() + .interval(Duration::from_millis(10u64)); + + + let ethereum_client = Arc::new(ethereum_provider); + + let weth_addr: Address = WETH_CONTRACT.into(); + let weth = WETH9::new(weth_addr, ethereum_client.clone()); + + let assethub: OnlineClient = + OnlineClient::from_url(ASSET_HUB_WS_URL).await.unwrap(); + + let keypair: Pair = Pair::from_string("//Ferdie", None).expect("cannot create keypair"); + + let signer: PairSigner = PairSigner::new(keypair); + + let amount: u128 = 1_000_000_000; + let assets = VersionedMultiAssets::V3(MultiAssets(vec![MultiAsset { + id: AssetId::Concrete(MultiLocation { + parents: 2, + interior: Junctions::X3( + Junction::GlobalConsensus(NetworkId::Ethereum { chain_id: 15 }), + Junction::AccountKey20 { network: None, key: GATEWAY_PROXY_CONTRACT.into() }, + Junction::AccountKey20 { network: None, key: WETH_CONTRACT.into() }, + ), + }), + fun: Fungibility::Fungible(amount), + }])); + + let destination = VersionedMultiLocation::V3(MultiLocation { + parents: 2, + interior: Junctions::X2( + Junction::GlobalConsensus(NetworkId::Ethereum { chain_id: 15 }), + Junction::AccountKey20 { network: None, key: DESTINATION_ADDRESS.into() }, + ), + }); + + let bridge_transfer_call = TransactionApi.transfer_asset_via_bridge(assets, destination); + + let result = assethub + .tx() + .sign_and_submit_then_watch_default(&bridge_transfer_call, &signer) + .await + .expect("send through call.") + .wait_for_finalized_success() + .await + .expect("call success"); + + println!("bridge_transfer call issued at assethub block hash {:?}", result.block_hash()); + + let wait_for_blocks = 50; + let mut stream = ethereum_client.subscribe_blocks().await.unwrap().take(wait_for_blocks); + + let mut transfer_event_found = false; + while let Some(block) = stream.next().await { + println!("Polling ethereum block {:?} for transfer event", block.number.unwrap()); + if let Ok(transfers) = + weth.event::().at_block_hash(block.hash.unwrap()).query().await + { + for transfer in transfers { + println!("Transfer event found at ethereum block {:?}", block.number.unwrap()); + assert_eq!(transfer.src, DESTINATION_ADDRESS.into()); + assert_eq!(transfer.dst, DESTINATION_ADDRESS.into()); + assert_eq!(transfer.wad, amount.into()); + transfer_event_found = true; + } + } + if transfer_event_found { + break; + } + } + assert!(transfer_event_found); +}