diff --git a/bridges/snowbridge/primitives/router/src/outbound/mod.rs b/bridges/snowbridge/primitives/router/src/outbound/mod.rs index ddc36ce8cb61..3d705f7cdeb0 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/mod.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/mod.rs @@ -117,7 +117,7 @@ where })?; // convert fee to Asset - let fee = Asset::from((Location::parent(), fee.total())).into(); + let fee = Asset::from((Location::parent(), fee.local)).into(); Ok(((ticket.encode(), message_id), fee)) } @@ -148,12 +148,12 @@ enum XcmConverterError { DepositAssetExpected, NoReserveAssets, FilterDoesNotConsumeAllAssets, - TooManyAssets, ZeroAssetTransfer, BeneficiaryResolutionFailed, AssetResolutionFailed, - InvalidFeeAsset, SetTopicExpected, + FeeAssetExpected, + FeeAssetInvalid, } macro_rules! match_expression { @@ -202,10 +202,13 @@ impl<'a, Call> XcmConverter<'a, Call> { } // Get the fee asset item from BuyExecution or continue parsing. - let fee_asset = match_expression!(self.peek(), Ok(BuyExecution { fees, .. }), fees); - if fee_asset.is_some() { - let _ = self.next(); - } + let fee_asset = match_expression!(self.next()?, BuyExecution { fees, .. }, fees) + .ok_or(FeeAssetExpected)?; + ensure!(fee_asset.clone().id == AssetId::from(Location::parent()), FeeAssetInvalid); + let _fee_amount = match fee_asset.clone().fun { + Fungible(fee_amount) => Ok(fee_amount), + _ => Err(FeeAssetInvalid), + }?; let (deposit_assets, beneficiary) = match_expression!( self.next()?, @@ -233,18 +236,8 @@ impl<'a, Call> XcmConverter<'a, Call> { return Err(FilterDoesNotConsumeAllAssets) } - // We only support a single asset at a time. - ensure!(reserve_assets.len() == 1, TooManyAssets); let reserve_asset = reserve_assets.get(0).ok_or(AssetResolutionFailed)?; - // If there was a fee specified verify it. - if let Some(fee_asset) = fee_asset { - // The fee asset must be the same as the reserve asset. - if fee_asset.id != reserve_asset.id || fee_asset.fun > reserve_asset.fun { - return Err(InvalidFeeAsset) - } - } - let (token, amount) = match reserve_asset { Asset { id: AssetId(inner_location), fun: Fungible(amount) } => match inner_location.unpack() { diff --git a/bridges/snowbridge/primitives/router/src/outbound/tests.rs b/bridges/snowbridge/primitives/router/src/outbound/tests.rs index 111243bb45a7..54513396c765 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/tests.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/tests.rs @@ -345,14 +345,14 @@ fn exporter_validate_xcm_success_case_1() { fun: Fungible(1000), }] .into(); - let fee = assets.clone().get(0).unwrap().clone(); + let fee_asset = Asset { id: AssetId::from(Location::parent()), fun: Fungible(1000) }; let filter: AssetFilter = assets.clone().into(); let mut message: Option> = Some( vec![ WithdrawAsset(assets.clone()), ClearOrigin, - BuyExecution { fees: fee, weight_limit: Unlimited }, + BuyExecution { fees: fee_asset, weight_limit: Unlimited }, DepositAsset { assets: filter, beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), @@ -398,44 +398,12 @@ fn xcm_converter_convert_success() { }] .into(); let filter: AssetFilter = assets.clone().into(); + let fee_asset = Asset { id: AssetId::from(Location::parent()), fun: Fungible(1000) }; 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::new(&message, &network); - let expected_payload = AgentExecuteCommand::TransferToken { - token: token_address.into(), - recipient: beneficiary_address.into(), - amount: 1000, - }; - let result = converter.convert(); - assert_eq!(result, Ok((expected_payload, [0; 32]))); -} - -#[test] -fn xcm_converter_convert_without_buy_execution_yields_success() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let assets: Assets = vec![Asset { - id: AssetId([AccountKey20 { network: None, key: token_address }].into()), - fun: Fungible(1000), - }] - .into(); - let filter: AssetFilter = assets.clone().into(); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), + BuyExecution { fees: fee_asset, weight_limit: Unlimited }, DepositAsset { assets: filter, beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), @@ -467,40 +435,7 @@ fn xcm_converter_convert_with_wildcard_all_asset_filter_succeeds() { .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::new(&message, &network); - let expected_payload = AgentExecuteCommand::TransferToken { - token: token_address.into(), - 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(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let asset_location: Location = [AccountKey20 { network: None, key: token_address }].into(); - let fee_asset = Asset { id: AssetId(asset_location.clone()), fun: Fungible(500) }; - - let assets: Assets = vec![Asset { id: AssetId(asset_location), fun: Fungible(1000) }].into(); - - let filter: AssetFilter = assets.clone().into(); + let fee_asset = Asset { id: AssetId::from(Location::parent()), fun: Fungible(1000) }; let message: Xcm<()> = vec![ WithdrawAsset(assets.clone()), @@ -537,9 +472,11 @@ fn xcm_converter_convert_without_set_topic_yields_set_topic_expected() { .into(); let filter: AssetFilter = assets.clone().into(); + let fee_asset = Asset { id: AssetId::from(Location::parent()), fun: Fungible(1000) }; + let message: Xcm<()> = vec![ WithdrawAsset(assets.clone()), - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + BuyExecution { fees: fee_asset, weight_limit: Unlimited }, DepositAsset { assets: filter, beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), @@ -569,67 +506,6 @@ fn xcm_converter_convert_with_partial_message_yields_unexpected_end_of_xcm() { assert_eq!(result.err(), Some(XcmConverterError::UnexpectedEndOfXcm)); } -#[test] -fn xcm_converter_with_different_fee_asset_fails() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let asset_location = [AccountKey20 { network: None, key: token_address }].into(); - let fee_asset = - Asset { id: AssetId(Location { parents: 0, interior: Here }), fun: Fungible(1000) }; - - let assets: Assets = vec![Asset { id: AssetId(asset_location), fun: Fungible(1000) }].into(); - - let filter: AssetFilter = assets.clone().into(); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: fee_asset, weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = XcmConverter::new(&message, &network); - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::InvalidFeeAsset)); -} - -#[test] -fn xcm_converter_with_fees_greater_than_reserve_fails() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let asset_location: Location = [AccountKey20 { network: None, key: token_address }].into(); - let fee_asset = Asset { id: AssetId(asset_location.clone()), fun: Fungible(1001) }; - - let assets: Assets = vec![Asset { id: AssetId(asset_location), fun: Fungible(1000) }].into(); - - let filter: AssetFilter = assets.clone().into(); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: fee_asset, weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = XcmConverter::new(&message, &network); - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::InvalidFeeAsset)); -} - #[test] fn xcm_converter_convert_with_empty_xcm_yields_unexpected_end_of_xcm() { let network = BridgedNetwork::get(); @@ -656,10 +532,12 @@ fn xcm_converter_convert_with_extra_instructions_yields_end_of_xcm_message_expec .into(); let filter: AssetFilter = assets.clone().into(); + let fee_asset = Asset { id: AssetId::from(Location::parent()), fun: Fungible(1000) }; + let message: Xcm<()> = vec![ WithdrawAsset(assets.clone()), ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + BuyExecution { fees: fee_asset, weight_limit: Unlimited }, DepositAsset { assets: filter, beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), @@ -716,10 +594,12 @@ fn xcm_converter_convert_without_withdraw_asset_yields_deposit_expected() { }] .into(); + let fee_asset = Asset { id: AssetId::from(Location::parent()), fun: Fungible(1000) }; + let message: Xcm<()> = vec![ WithdrawAsset(assets.clone()), ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + BuyExecution { fees: fee_asset, weight_limit: Unlimited }, SetTopic([0; 32]), ] .into(); @@ -733,22 +613,17 @@ fn xcm_converter_convert_without_withdraw_asset_yields_deposit_expected() { fn xcm_converter_convert_without_assets_yields_no_reserve_assets() { let network = BridgedNetwork::get(); - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); let assets: Assets = vec![].into(); let filter: AssetFilter = assets.clone().into(); - let fee = Asset { - id: AssetId(AccountKey20 { network: None, key: token_address }.into()), - fun: Fungible(1000), - }; + let fee_asset = Asset { id: AssetId::from(Location::parent()), fun: Fungible(1000) }; let message: Xcm<()> = vec![ WithdrawAsset(assets.clone()), ClearOrigin, - BuyExecution { fees: fee, weight_limit: Unlimited }, + BuyExecution { fees: fee_asset, weight_limit: Unlimited }, DepositAsset { assets: filter, beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), @@ -762,44 +637,6 @@ fn xcm_converter_convert_without_assets_yields_no_reserve_assets() { assert_eq!(result.err(), Some(XcmConverterError::NoReserveAssets)); } -#[test] -fn xcm_converter_convert_with_two_assets_yields_too_many_assets() { - let network = BridgedNetwork::get(); - - let token_address_1: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let token_address_2: [u8; 20] = hex!("1100000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let assets: Assets = vec![ - Asset { - id: AssetId(AccountKey20 { network: None, key: token_address_1 }.into()), - fun: Fungible(1000), - }, - Asset { - id: AssetId(AccountKey20 { network: None, key: token_address_2 }.into()), - fun: Fungible(500), - }, - ] - .into(); - let filter: AssetFilter = assets.clone().into(); - - 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::new(&message, &network); - - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::TooManyAssets)); -} - #[test] fn xcm_converter_convert_without_consuming_filter_yields_filter_does_not_consume_all_assets() { let network = BridgedNetwork::get(); @@ -814,10 +651,12 @@ fn xcm_converter_convert_without_consuming_filter_yields_filter_does_not_consume .into(); let filter: AssetFilter = Wild(WildAsset::AllCounted(0)); + let fee_asset = Asset { id: AssetId::from(Location::parent()), fun: Fungible(1000) }; + let message: Xcm<()> = vec![ WithdrawAsset(assets.clone()), ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + BuyExecution { fees: fee_asset, weight_limit: Unlimited }, DepositAsset { assets: filter, beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), @@ -845,10 +684,12 @@ fn xcm_converter_convert_with_zero_amount_asset_yields_zero_asset_transfer() { .into(); let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); + let fee_asset = Asset { id: AssetId::from(Location::parent()), fun: Fungible(1000) }; + let message: Xcm<()> = vec![ WithdrawAsset(assets.clone()), ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + BuyExecution { fees: fee_asset, weight_limit: Unlimited }, DepositAsset { assets: filter, beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), @@ -875,10 +716,12 @@ fn xcm_converter_convert_non_ethereum_asset_yields_asset_resolution_failed() { .into(); let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); + let fee_asset = Asset { id: AssetId::from(Location::parent()), fun: Fungible(1000) }; + let message: Xcm<()> = vec![ WithdrawAsset(assets.clone()), ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + BuyExecution { fees: fee_asset, weight_limit: Unlimited }, DepositAsset { assets: filter, beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), @@ -908,10 +751,12 @@ fn xcm_converter_convert_non_ethereum_chain_asset_yields_asset_resolution_failed .into(); let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); + let fee_asset = Asset { id: AssetId::from(Location::parent()), fun: Fungible(1000) }; + let message: Xcm<()> = vec![ WithdrawAsset(assets.clone()), ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + BuyExecution { fees: fee_asset, weight_limit: Unlimited }, DepositAsset { assets: filter, beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), @@ -941,10 +786,12 @@ fn xcm_converter_convert_non_ethereum_chain_yields_asset_resolution_failed() { .into(); let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); + let fee_asset = Asset { id: AssetId::from(Location::parent()), fun: Fungible(1000) }; + let message: Xcm<()> = vec![ WithdrawAsset(assets.clone()), ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + BuyExecution { fees: fee_asset, weight_limit: Unlimited }, DepositAsset { assets: filter, beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), @@ -973,10 +820,13 @@ fn xcm_converter_convert_with_non_ethereum_beneficiary_yields_beneficiary_resolu }] .into(); let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); + + let fee_asset = Asset { id: AssetId::from(Location::parent()), fun: Fungible(1000) }; + let message: Xcm<()> = vec![ WithdrawAsset(assets.clone()), ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + BuyExecution { fees: fee_asset, weight_limit: Unlimited }, DepositAsset { assets: filter, beneficiary: [ @@ -1010,10 +860,12 @@ fn xcm_converter_convert_with_non_ethereum_chain_beneficiary_yields_beneficiary_ .into(); let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); + let fee_asset = Asset { id: AssetId::from(Location::parent()), fun: Fungible(1000) }; + let message: Xcm<()> = vec![ WithdrawAsset(assets.clone()), ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + BuyExecution { fees: fee_asset, weight_limit: Unlimited }, DepositAsset { assets: filter, beneficiary: AccountKey20 { diff --git a/bridges/snowbridge/runtime/test-common/src/lib.rs b/bridges/snowbridge/runtime/test-common/src/lib.rs index 8f36313e360f..51bf04eb30d1 100644 --- a/bridges/snowbridge/runtime/test-common/src/lib.rs +++ b/bridges/snowbridge/runtime/test-common/src/lib.rs @@ -67,10 +67,12 @@ where }; let assets = vec![asset.clone()]; + let fee_asset = Asset { id: AssetId::from(Location::parent()), fun: Fungible(1000) }; + let inner_xcm = Xcm(vec![ WithdrawAsset(Assets::from(assets.clone())), ClearOrigin, - BuyExecution { fees: asset, weight_limit: Unlimited }, + BuyExecution { fees: fee_asset, weight_limit: Unlimited }, DepositAsset { assets: Wild(All), beneficiary: Location::new( diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs index 8856fe8eca51..955903a5c631 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs @@ -383,6 +383,7 @@ fn send_weth_asset_from_asset_hub_to_ethereum() { use asset_hub_rococo_runtime::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 bridgehub_location = AssetHubRococo::sibling_location_of(BridgeHubRococo::para_id()); AssetHubRococo::force_default_xcm_version(Some(XCM_VERSION)); BridgeHubRococo::force_default_xcm_version(Some(XCM_VERSION)); @@ -392,80 +393,103 @@ fn send_weth_asset_from_asset_hub_to_ethereum() { ); BridgeHubRococo::fund_accounts(vec![(assethub_sovereign.clone(), INITIAL_FUND)]); - AssetHubRococo::fund_accounts(vec![(AssetHubRococoReceiver::get(), INITIAL_FUND)]); const WETH_AMOUNT: u128 = 1_000_000_000; + const FEE_AMOUNT: u128 = 2_750_872_500_000; + const LOCAL_FEE_AMOUNT: u128 = 16903333; + const REMOTE_FEE_AMOUNT: u128 = FEE_AMOUNT - LOCAL_FEE_AMOUNT; - BridgeHubRococo::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; + let weth_asset_location: Location = Location::new( + 2, + [EthereumNetwork::get().into(), AccountKey20 { network: None, key: WETH }], + ); - // Construct RegisterToken message and sent to inbound queue - send_inbound_message(make_register_token_message()).unwrap(); + // Register WETH and Mint some to AssetHubRococoReceiver + AssetHubRococo::execute_with(|| { + type RuntimeOrigin = ::RuntimeOrigin; - // Check that the register token message was sent using xcm - assert_expected_events!( - BridgeHubRococo, - vec![ - RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, - ] - ); + assert_ok!(::ForeignAssets::force_create( + RuntimeOrigin::root(), + weth_asset_location.clone().try_into().unwrap(), + AssetHubRococoReceiver::get().into(), + false, + 1, + )); - // Construct SendToken message and sent to inbound queue - send_inbound_message(make_send_token_message()).unwrap(); + assert!(::ForeignAssets::asset_exists( + weth_asset_location.clone().try_into().unwrap(), + )); - // Check that the send token message was sent using xcm - assert_expected_events!( - BridgeHubRococo, - vec![ - RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, - ] - ); + assert_ok!(::ForeignAssets::mint( + RuntimeOrigin::signed(AssetHubRococoReceiver::get()), + weth_asset_location.clone().try_into().unwrap(), + AssetHubRococoReceiver::get().into(), + WETH_AMOUNT, + )); }); AssetHubRococo::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; type RuntimeOrigin = ::RuntimeOrigin; - // Check that AssetHub has issued the foreign asset - assert_expected_events!( - AssetHubRococo, - vec![ - RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, - ] - ); - let assets = vec![Asset { - id: AssetId(Location::new( - 2, - [ - GlobalConsensus(Ethereum { chain_id: CHAIN_ID }), - AccountKey20 { network: None, key: WETH }, - ], - )), - fun: Fungible(WETH_AMOUNT), - }]; - let multi_assets = VersionedAssets::V4(Assets::from(assets)); - - let destination = VersionedLocation::V4(Location::new( - 2, - [GlobalConsensus(Ethereum { chain_id: CHAIN_ID })], - )); + // DOT as fee asset + let fee_asset = Asset { id: AssetId(Location::parent()), fun: Fungible(FEE_AMOUNT) }; + + let remote_fee_asset = + Asset { id: AssetId(Location::parent()), fun: Fungible(REMOTE_FEE_AMOUNT) }; + + let local_fee_asset = + Asset { id: AssetId(Location::parent()), fun: Fungible(LOCAL_FEE_AMOUNT) }; + + let weth_asset = Asset { id: weth_asset_location.into(), fun: Fungible(WETH_AMOUNT) }; - let beneficiary = VersionedLocation::V4(Location::new( + // Send both assets to BH + let assets = vec![fee_asset.clone(), weth_asset.clone()]; + + let destination = Location::new(2, [GlobalConsensus(Ethereum { chain_id: CHAIN_ID })]); + + let beneficiary = Location::new( 0, [AccountKey20 { network: None, key: ETHEREUM_DESTINATION_ADDRESS.into() }], - )); + ); + // Internal xcm of InitiateReserveWithdraw, WithdrawAssets + ClearOrigin instructions will + // be appended to the front of the list by the xcm executor + let withdraw_xcm_on_bh = Xcm(vec![ + BuyExecution { fees: remote_fee_asset.clone(), weight_limit: Unlimited }, + DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, + ]); + + let teleport_xcm_on_bh = Xcm(vec![ + BuyExecution { fees: local_fee_asset.clone(), weight_limit: Unlimited }, + DepositAsset { + assets: Wild(AllCounted(1)), + beneficiary: (AccountId32 { id: assethub_sovereign.into(), network: None },).into(), + }, + ]); + + let xcms = VersionedXcm::from(Xcm(vec![ + WithdrawAsset(assets.clone().into()), + SetFeesMode { jit_withdraw: true }, + InitiateReserveWithdraw { + assets: Definite(vec![remote_fee_asset.clone(), weth_asset.clone()].into()), + // with reserve set to Ethereum destination, the ExportMessage will + // be appended to the front of the list by the SovereignPaidRemoteExporter + reserve: destination, + xcm: withdraw_xcm_on_bh, + }, + InitiateTeleport { + assets: Definite(vec![local_fee_asset.clone()].into()), + xcm: teleport_xcm_on_bh, + dest: bridgehub_location, + }, + ])); let free_balance_before = ::Balances::free_balance( AssetHubRococoReceiver::get(), ); - // Send the Weth back to Ethereum - ::PolkadotXcm::limited_reserve_transfer_assets( + ::PolkadotXcm::execute( RuntimeOrigin::signed(AssetHubRococoReceiver::get()), - Box::new(destination), - Box::new(beneficiary), - Box::new(multi_assets), - 0, - Unlimited, + bx!(xcms), + Weight::from(8_000_000_000), ) .unwrap(); let free_balance_after = ::Balances::free_balance( @@ -486,25 +510,6 @@ fn send_weth_asset_from_asset_hub_to_ethereum() { 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." - ); }); } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs index 664d2b9c9dd5..0054cf306044 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs @@ -635,11 +635,8 @@ pub mod bridging { use super::*; parameter_types! { - /// User fee for ERC20 token transfer back to Ethereum. - /// (initially was calculated by test `OutboundQueue::calculate_fees` - ETH/ROC 1/400 and fee_per_gas 20 GWEI = 2200698000000 + *25%) - /// Needs to be more than fee calculated from DefaultFeeConfig FeeConfigRecord in snowbridge:parachain/pallets/outbound-queue/src/lib.rs - /// Polkadot uses 10 decimals, Kusama and Rococo 12 decimals. - pub const DefaultBridgeHubEthereumBaseFee: Balance = 2_750_872_500_000; + /// User fee for delivery cost on bridge hub. Leave some buffer here for avoid spamming + pub const DefaultBridgeHubEthereumBaseFee: Balance = 4_000_000_000; pub storage BridgeHubEthereumBaseFee: Balance = DefaultBridgeHubEthereumBaseFee::get(); pub SiblingBridgeHubWithEthereumInboundQueueInstance: Location = Location::new( 1, diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs index a0d2e91dffd2..4d479fff47c7 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs @@ -38,11 +38,9 @@ use parachains_common::{ }; use polkadot_parachain_primitives::primitives::Sibling; use polkadot_runtime_common::xcm_sender::ExponentialPrice; -use snowbridge_runtime_common::XcmExportFeeToSibling; use sp_core::Get; use sp_runtime::traits::AccountIdConversion; use sp_std::marker::PhantomData; -use testnet_parachains_constants::rococo::snowbridge::EthereumNetwork; use xcm::latest::prelude::*; use xcm_builder::{ deposit_or_burn_fee, AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, @@ -52,10 +50,10 @@ use xcm_builder::{ ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, - XcmFeeToAccount, + XcmFeeManagerFromComponents, XcmFeeToAccount, }; use xcm_executor::{ - traits::{FeeManager, FeeReason, FeeReason::Export, TransactAsset}, + traits::{FeeReason, TransactAsset}, XcmExecutor, }; @@ -205,7 +203,7 @@ impl xcm_executor::Config for XcmConfig { type SubscriptionService = PolkadotXcm; type PalletInstancesInfo = AllPalletsWithSystem; type MaxAssetsIntoHolding = MaxAssetsIntoHolding; - type FeeManager = XcmFeeManagerFromComponentsBridgeHub< + type FeeManager = XcmFeeManagerFromComponents< WaivedLocations, ( XcmExportFeeToRelayerRewardAccounts< @@ -215,14 +213,6 @@ impl xcm_executor::Config for XcmConfig { crate::bridge_to_westend_config::BridgeHubWestendChainId, crate::bridge_to_westend_config::AssetHubRococoToAssetHubWestendMessagesLane, >, - XcmExportFeeToSibling< - bp_rococo::Balance, - AccountId, - TokenLocation, - EthereumNetwork, - Self::AssetTransactor, - crate::EthereumOutboundQueue, - >, XcmFeeToAccount, ), >; @@ -383,22 +373,3 @@ impl< fee } } - -pub struct XcmFeeManagerFromComponentsBridgeHub( - PhantomData<(WaivedLocations, HandleFee)>, -); -impl, FeeHandler: HandleFee> FeeManager - for XcmFeeManagerFromComponentsBridgeHub -{ - fn is_waived(origin: Option<&Location>, fee_reason: FeeReason) -> bool { - let Some(loc) = origin else { return false }; - if let Export { network, destination: Here } = fee_reason { - return !(network == EthereumNetwork::get()) - } - WaivedLocations::contains(loc) - } - - fn handle_fee(fee: Assets, context: Option<&XcmContext>, reason: FeeReason) { - FeeHandler::handle_fee(fee, context, reason); - } -} diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/snowbridge.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/snowbridge.rs index 5960ab7b5505..733126c03461 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/snowbridge.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/snowbridge.rs @@ -25,7 +25,7 @@ use bridge_hub_rococo_runtime::{ SignedExtra, UncheckedExtrinsic, }; use codec::{Decode, Encode}; -use cumulus_primitives_core::XcmError::{FailedToTransactAsset, NotHoldingFees}; +use cumulus_primitives_core::XcmError::{FailedToTransactAsset, TooExpensive}; use frame_support::parameter_types; use parachains_common::{AccountId, AuraId, Balance}; use snowbridge_pallet_ethereum_client::WeightInfo; @@ -90,8 +90,8 @@ pub fn transfer_token_to_ethereum_fee_not_enough() { H160::random(), H160::random(), // fee not enough - 1_000_000_000, - NotHoldingFees, + 4_000_000, + TooExpensive, ) }