diff --git a/bridges/snowbridge/Cargo.toml b/bridges/snowbridge/Cargo.toml index a9e9ed9abf95e..ca70de1d0e074 100644 --- a/bridges/snowbridge/Cargo.toml +++ b/bridges/snowbridge/Cargo.toml @@ -52,3 +52,16 @@ derivable_impls = { level = "allow", priority = 2 } # false pos stable_sort_primitive = { level = "allow", priority = 2 } # prefer stable sort extra-unused-type-parameters = { level = "allow", priority = 2 } # stylistic default_constructed_unit_structs = { level = "allow", priority = 2 } # stylistic + +[workspace.dependencies] +polkavm-linker = "0.8.2" +polkavm-derive = "0.8.0" +log = { version = "0.4.20", default-features = false } +quote = { version = "1.0.33" } +serde = { version = "1.0.197", default-features = false } +serde-big-array = { version = "0.3.2" } +serde_derive = { version = "1.0.117" } +serde_json = { version = "1.0.114", default-features = false } +serde_yaml = { version = "0.9" } +syn = { version = "2.0.50" } +thiserror = { version = "1.0.48" } diff --git a/bridges/snowbridge/pallets/inbound-queue/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue/src/lib.rs index bdc21fcf03702..b3b34cd96422f 100644 --- a/bridges/snowbridge/pallets/inbound-queue/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue/src/lib.rs @@ -44,7 +44,7 @@ use envelope::Envelope; use frame_support::{ traits::{ fungible::{Inspect, Mutate}, - tokens::Preservation, + tokens::{Fortitude, Preservation}, }, weights::WeightToFee, PalletError, @@ -52,6 +52,7 @@ use frame_support::{ use frame_system::ensure_signed; use scale_info::TypeInfo; use sp_core::{H160, H256}; +use sp_runtime::traits::Zero; use sp_std::{convert::TryFrom, vec}; use xcm::prelude::{ send_xcm, Instruction::SetTopic, Junction::*, Location, SendError as XcmpSendError, SendXcm, @@ -261,11 +262,19 @@ pub mod pallet { } })?; - // Reward relayer from the sovereign account of the destination parachain - // Expected to fail if sovereign account has no funds + // Reward relayer from the sovereign account of the destination parachain, only if funds + // are available let sovereign_account = sibling_sovereign_account::(channel.para_id); let delivery_cost = Self::calculate_delivery_cost(message.encode().len() as u32); - T::Token::transfer(&sovereign_account, &who, delivery_cost, Preservation::Preserve)?; + let amount = T::Token::reducible_balance( + &sovereign_account, + Preservation::Preserve, + Fortitude::Polite, + ) + .min(delivery_cost); + if amount > BalanceOf::::zero() { + T::Token::transfer(&sovereign_account, &who, amount, Preservation::Preserve)?; + } // Decode message into XCM let (xcm, fee) = diff --git a/bridges/snowbridge/pallets/inbound-queue/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue/src/mock.rs index 110f611c67660..a641479080b40 100644 --- a/bridges/snowbridge/pallets/inbound-queue/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue/src/mock.rs @@ -4,7 +4,7 @@ use super::*; use frame_support::{ parameter_types, - traits::{ConstU128, ConstU32, Everything}, + traits::{ConstU32, Everything}, weights::IdentityFee, }; use hex_literal::hex; @@ -74,6 +74,10 @@ impl frame_system::Config for Test { type Block = Block; } +parameter_types! { + pub const ExistentialDeposit: u128 = 1; +} + impl pallet_balances::Config for Test { type MaxLocks = (); type MaxReserves = (); @@ -81,7 +85,7 @@ impl pallet_balances::Config for Test { type Balance = Balance; type RuntimeEvent = RuntimeEvent; type DustRemoval = (); - type ExistentialDeposit = ConstU128<1>; + type ExistentialDeposit = ExistentialDeposit; type AccountStore = System; type WeightInfo = (); type FreezeIdentifier = (); diff --git a/bridges/snowbridge/pallets/inbound-queue/src/test.rs b/bridges/snowbridge/pallets/inbound-queue/src/test.rs index 9a47e475b8c99..3c1016eed8b68 100644 --- a/bridges/snowbridge/pallets/inbound-queue/src/test.rs +++ b/bridges/snowbridge/pallets/inbound-queue/src/test.rs @@ -6,7 +6,7 @@ use frame_support::{assert_noop, assert_ok}; use hex_literal::hex; use snowbridge_core::{inbound::Proof, ChannelId}; use sp_keyring::AccountKeyring as Keyring; -use sp_runtime::{DispatchError, TokenError}; +use sp_runtime::DispatchError; use sp_std::convert::From; use crate::{Error, Event as InboundQueueEvent}; @@ -150,12 +150,12 @@ fn test_submit_with_invalid_nonce() { } #[test] -fn test_submit_no_funds_to_reward_relayers() { +fn test_submit_no_funds_to_reward_relayers_just_ignore() { new_tester().execute_with(|| { let relayer: AccountId = Keyring::Bob.into(); let origin = RuntimeOrigin::signed(relayer); - // Reset balance of sovereign_account to zero so to trigger the FundsUnavailable error + // Reset balance of sovereign_account to zero first let sovereign_account = sibling_sovereign_account::(ASSET_HUB_PARAID.into()); Balances::set_balance(&sovereign_account, 0); @@ -168,10 +168,8 @@ fn test_submit_no_funds_to_reward_relayers() { data: Default::default(), }, }; - assert_noop!( - InboundQueue::submit(origin.clone(), message.clone()), - TokenError::FundsUnavailable - ); + // Check submit successfully in case no funds available + assert_ok!(InboundQueue::submit(origin.clone(), message.clone())); }); } @@ -210,3 +208,46 @@ fn test_set_operating_mode_root_only() { ); }); } + +#[test] +fn test_submit_no_funds_to_reward_relayers_and_ed_preserved() { + new_tester().execute_with(|| { + let relayer: AccountId = Keyring::Bob.into(); + let origin = RuntimeOrigin::signed(relayer); + + // Reset balance of sovereign account to (ED+1) first + let sovereign_account = sibling_sovereign_account::(ASSET_HUB_PARAID.into()); + Balances::set_balance(&sovereign_account, ExistentialDeposit::get() + 1); + + // Submit message successfully + let message = Message { + event_log: mock_event_log(), + proof: Proof { + block_hash: Default::default(), + tx_index: Default::default(), + data: Default::default(), + }, + }; + assert_ok!(InboundQueue::submit(origin.clone(), message.clone())); + + // Check balance of sovereign account to ED + let amount = Balances::balance(&sovereign_account); + assert_eq!(amount, ExistentialDeposit::get()); + + // Submit another message with nonce set as 2 + let mut event_log = mock_event_log(); + event_log.data[31] = 2; + let message = Message { + event_log, + proof: Proof { + block_hash: Default::default(), + tx_index: Default::default(), + data: Default::default(), + }, + }; + assert_ok!(InboundQueue::submit(origin.clone(), message.clone())); + // Check balance of sovereign account as ED does not change + let amount = Balances::balance(&sovereign_account); + assert_eq!(amount, ExistentialDeposit::get()); + }); +} 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 a5957ee339306..ab329fbd242b9 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 @@ -29,7 +29,7 @@ use snowbridge_pallet_inbound_queue_fixtures::{ use snowbridge_pallet_system; use snowbridge_router_primitives::inbound::GlobalConsensusEthereumConvertsFor; use sp_core::H256; -use sp_runtime::{ArithmeticError::Underflow, DispatchError::Arithmetic}; +use sp_runtime::{DispatchError::Token, TokenError::FundsUnavailable}; use testnet_parachains_constants::rococo::snowbridge::EthereumNetwork; const INITIAL_FUND: u128 = 5_000_000_000 * ROCOCO_ED; @@ -532,6 +532,6 @@ fn send_token_from_ethereum_to_asset_hub_fail_for_insufficient_fund() { BridgeHubRococo::fund_para_sovereign(AssetHubRococo::para_id().into(), 1_000); BridgeHubRococo::execute_with(|| { - assert_err!(send_inbound_message(make_register_token_message()), Arithmetic(Underflow)); + assert_err!(send_inbound_message(make_register_token_message()), Token(FundsUnavailable)); }); }