diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/envelope.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/envelope.rs index 31a8992442d8..00c9f13c3959 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/envelope.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/envelope.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2023 Snowfork -use snowbridge_core::{inbound::Log, ChannelId}; +use snowbridge_core::inbound::Log; use sp_core::{RuntimeDebug, H160, H256}; use sp_std::prelude::*; @@ -9,7 +9,7 @@ use alloy_primitives::B256; use alloy_sol_types::{sol, SolEvent}; sol! { - event OutboundMessageAccepted(bytes32 indexed channel_id, uint64 nonce, bytes32 indexed message_id, bytes payload); + event OutboundMessageAccepted(uint64 indexed nonce, bytes32 indexed message_id, uint32 indexed para_id, bytes32 reward_address, uint128 fee, bytes payload); } /// An inbound message that has had its outer envelope decoded. @@ -17,12 +17,16 @@ sol! { pub struct Envelope { /// The address of the outbound queue on Ethereum that emitted this message as an event log pub gateway: H160, - /// The message Channel - pub channel_id: ChannelId, /// A nonce for enforcing replay protection and ordering. pub nonce: u64, /// An id for tracing the message on its route (has no role in bridge consensus) pub message_id: H256, + /// Destination ParaId + pub para_id: u32, + /// The reward address + pub reward_address: [u8; 32], + /// Total fee paid on source chain + pub fee: u128, /// The inner payload generated from the source application. pub payload: Vec, } @@ -41,9 +45,11 @@ impl TryFrom<&Log> for Envelope { Ok(Self { gateway: log.address, - channel_id: ChannelId::from(event.channel_id.as_ref()), nonce: event.nonce, message_id: H256::from(event.message_id.as_ref()), + reward_address: event.reward_address.clone().into(), + fee: event.fee, + para_id: event.para_id, payload: event.payload, }) } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index 63da2b56ac50..b60bb60cb845 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -41,7 +41,7 @@ use envelope::Envelope; use frame_support::{ traits::{ fungible::{Inspect, Mutate}, - tokens::{Fortitude, Preservation}, + tokens::{Fortitude, Precision, Preservation}, }, weights::WeightToFee, PalletError, @@ -49,22 +49,18 @@ use frame_support::{ use frame_system::ensure_signed; use scale_info::TypeInfo; use sp_core::H160; -use sp_runtime::traits::Zero; use sp_std::vec; use xcm::prelude::{ - send_xcm, Junction::*, Location, SendError as XcmpSendError, SendXcm, Xcm, XcmContext, XcmHash, + send_xcm, Junction::*, Location, SendError as XcmpSendError, SendXcm, Xcm, XcmHash, }; -use xcm_executor::traits::TransactAsset; use snowbridge_core::{ inbound::{Message, VerificationError, Verifier}, - sibling_sovereign_account, BasicOperatingMode, Channel, ChannelId, ParaId, PricingParameters, - StaticLookup, + sibling_sovereign_account, BasicOperatingMode, ParaId, }; use snowbridge_router_primitives_v2::inbound::{ ConvertMessage, ConvertMessageError, VersionedMessage, }; -use sp_runtime::{traits::Saturating, SaturatedConversion, TokenError}; pub use weights::WeightInfo; @@ -76,7 +72,7 @@ type BalanceOf = pub use pallet::*; -pub const LOG_TARGET: &str = "snowbridge-inbound-queue"; +pub const LOG_TARGET: &str = "snowbridge-inbound-queue:v2"; #[frame_support::pallet] pub mod pallet { @@ -101,7 +97,7 @@ pub mod pallet { /// The verifier for inbound messages from Ethereum type Verifier: Verifier; - /// Message relayers are rewarded with this asset + /// Burn fees from relayer type Token: Mutate + Inspect; /// XCM message sender @@ -117,12 +113,6 @@ pub mod pallet { Balance = BalanceOf, >; - /// Lookup a channel descriptor - type ChannelLookup: StaticLookup; - - /// Lookup pricing parameters - type PricingParameters: Get>>; - type WeightInfo: WeightInfo; #[cfg(feature = "runtime-benchmarks")] @@ -136,9 +126,6 @@ pub mod pallet { /// The upper limit here only used to estimate delivery cost type MaxMessageSize: Get; - - /// To withdraw and deposit an asset. - type AssetTransactor: TransactAsset; } #[pallet::hooks] @@ -149,8 +136,6 @@ pub mod pallet { pub enum Event { /// A message was received from Ethereum MessageReceived { - /// The message channel - channel_id: ChannelId, /// The message nonce nonce: u64, /// ID of the XCM message which was forwarded to the final destination parachain @@ -215,9 +200,9 @@ pub mod pallet { } } - /// The current nonce for each channel + /// The nonce of the message been processed or not #[pallet::storage] - pub type Nonce = StorageMap<_, Twox64Concat, ChannelId, u64, ValueQuery>; + pub type Nonce = StorageMap<_, Identity, u64, bool, ValueQuery>; /// The current operating mode of the pallet. #[pallet::storage] @@ -244,36 +229,8 @@ pub mod pallet { // Verify that the message was submitted from the known Gateway contract ensure!(T::GatewayAddress::get() == envelope.gateway, Error::::InvalidGateway); - // Retrieve the registered channel for this message - let channel = - T::ChannelLookup::lookup(envelope.channel_id).ok_or(Error::::InvalidChannel)?; - - // Verify message nonce - >::try_mutate(envelope.channel_id, |nonce| -> DispatchResult { - if *nonce == u64::MAX { - return Err(Error::::MaxNonceReached.into()) - } - if envelope.nonce != nonce.saturating_add(1) { - Err(Error::::InvalidNonce.into()) - } else { - *nonce = nonce.saturating_add(1); - Ok(()) - } - })?; - - // 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); - let amount = T::Token::reducible_balance( - &sovereign_account, - Preservation::Preserve, - Fortitude::Polite, - ) - .min(delivery_cost); - if !amount.is_zero() { - T::Token::transfer(&sovereign_account, &who, amount, Preservation::Preserve)?; - } + // Verify the message has not been processed + ensure!(!>::contains_key(envelope.nonce), Error::::InvalidNonce); // Decode payload into `VersionedMessage` let message = VersionedMessage::decode_all(&mut envelope.payload.as_ref()) @@ -290,13 +247,33 @@ pub mod pallet { ); // Burning fees for teleport - Self::burn_fees(channel.para_id, fee)?; + T::Token::burn_from( + &who, + fee, + Preservation::Preserve, + Precision::BestEffort, + Fortitude::Polite, + )?; // Attempt to send XCM to a dest parachain - let message_id = Self::send_xcm(xcm, channel.para_id)?; + let message_id = Self::send_xcm(xcm, envelope.para_id.into())?; + + // Set nonce flag to true + >::try_mutate(envelope.nonce, |done| -> DispatchResult { + *done = true; + Ok(()) + })?; + + // Todo: Deposit fee to RewardLeger which should contains all of: + // a. The submit extrinsic cost on BH + // b. The delivery cost to AH + // c. The execution cost on AH + // d. The execution cost on destination chain(if any) + // e. The reward + + // T::RewardLeger::deposit(envelope.reward_address.into(), envelope.fee.into())?; Self::deposit_event(Event::MessageReceived { - channel_id: envelope.channel_id, nonce: envelope.nonce, message_id, fee_burned: fee, @@ -334,45 +311,5 @@ pub mod pallet { let (xcm_hash, _) = send_xcm::(dest, xcm).map_err(Error::::from)?; Ok(xcm_hash) } - - pub fn calculate_delivery_cost(length: u32) -> BalanceOf { - let weight_fee = T::WeightToFee::weight_to_fee(&T::WeightInfo::submit()); - let len_fee = T::LengthToFee::weight_to_fee(&Weight::from_parts(length as u64, 0)); - weight_fee - .saturating_add(len_fee) - .saturating_add(T::PricingParameters::get().rewards.local) - } - - /// Burn the amount of the fee embedded into the XCM for teleports - pub fn burn_fees(para_id: ParaId, fee: BalanceOf) -> DispatchResult { - let dummy_context = - XcmContext { origin: None, message_id: Default::default(), topic: None }; - let dest = Location::new(1, [Parachain(para_id.into())]); - let fees = (Location::parent(), fee.saturated_into::()).into(); - T::AssetTransactor::can_check_out(&dest, &fees, &dummy_context).map_err(|error| { - log::error!( - target: LOG_TARGET, - "XCM asset check out failed with error {:?}", error - ); - TokenError::FundsUnavailable - })?; - T::AssetTransactor::check_out(&dest, &fees, &dummy_context); - T::AssetTransactor::withdraw_asset(&fees, &dest, None).map_err(|error| { - log::error!( - target: LOG_TARGET, - "XCM asset withdraw failed with error {:?}", error - ); - TokenError::FundsUnavailable - })?; - Ok(()) - } - } - - /// API for accessing the delivery cost of a message - impl Get> for Pallet { - fn get() -> BalanceOf { - // Cost here based on MaxMessagePayloadSize(the worst case) - Self::calculate_delivery_cost(T::MaxMessageSize::get()) - } } } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs index 3e67d5ab738b..6bf805d9c91f 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs @@ -8,11 +8,10 @@ use snowbridge_beacon_primitives::{ types::deneb, BeaconHeader, ExecutionProof, Fork, ForkVersions, VersionedExecutionPayloadHeader, }; use snowbridge_core::{ - gwei, inbound::{Log, Proof, VerificationError}, - meth, Channel, ChannelId, PricingParameters, Rewards, StaticLookup, TokenId, + TokenId, }; -use snowbridge_router_primitives::inbound::MessageToXcm; +use snowbridge_router_primitives_v2::inbound::MessageToXcm; use sp_core::{H160, H256}; use sp_runtime::{ traits::{IdentifyAccount, IdentityLookup, MaybeEquivalence, Verify}, @@ -20,7 +19,6 @@ use sp_runtime::{ }; use sp_std::{convert::From, default::Default}; use xcm::{latest::SendXcm, prelude::*}; -use xcm_executor::AssetsInHolding; use crate::{self as inbound_queue}; @@ -151,63 +149,10 @@ impl SendXcm for MockXcmSender { parameter_types! { pub const OwnParaId: ParaId = ParaId::new(1013); - pub Parameters: PricingParameters = PricingParameters { - exchange_rate: FixedU128::from_rational(1, 400), - fee_per_gas: gwei(20), - rewards: Rewards { local: DOT, remote: meth(1) }, - multiplier: FixedU128::from_rational(1, 1), - }; } pub const DOT: u128 = 10_000_000_000; -pub struct MockChannelLookup; -impl StaticLookup for MockChannelLookup { - type Source = ChannelId; - type Target = Channel; - - fn lookup(channel_id: Self::Source) -> Option { - if channel_id != - hex!("c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539").into() - { - return None - } - Some(Channel { agent_id: H256::zero(), para_id: ASSET_HUB_PARAID.into() }) - } -} - -pub struct SuccessfulTransactor; -impl TransactAsset for SuccessfulTransactor { - fn can_check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult { - Ok(()) - } - - fn can_check_out(_dest: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult { - Ok(()) - } - - fn deposit_asset(_what: &Asset, _who: &Location, _context: Option<&XcmContext>) -> XcmResult { - Ok(()) - } - - fn withdraw_asset( - _what: &Asset, - _who: &Location, - _context: Option<&XcmContext>, - ) -> Result { - Ok(AssetsInHolding::default()) - } - - fn internal_transfer_asset( - _what: &Asset, - _from: &Location, - _to: &Location, - _context: &XcmContext, - ) -> Result { - Ok(AssetsInHolding::default()) - } -} - pub struct MockTokenIdConvert; impl MaybeEquivalence for MockTokenIdConvert { fn convert(_id: &TokenId) -> Option { @@ -235,14 +180,11 @@ impl inbound_queue::Config for Test { UniversalLocation, AssetHubFromEthereum, >; - type PricingParameters = Parameters; - type ChannelLookup = MockChannelLookup; #[cfg(feature = "runtime-benchmarks")] type Helper = Test; type WeightToFee = IdentityFee; type LengthToFee = IdentityFee; type MaxMessageSize = ConstU32<1024>; - type AssetTransactor = SuccessfulTransactor; } pub fn last_events(n: usize) -> Vec { diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs index 41c38460aabf..776dc3807898 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs @@ -36,8 +36,6 @@ fn test_submit_happy_path() { assert_ok!(InboundQueue::submit(origin.clone(), message.clone())); expect_events(vec![InboundQueueEvent::MessageReceived { - channel_id: hex!("c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539") - .into(), nonce: 1, message_id: [ 255, 125, 48, 71, 174, 185, 100, 26, 159, 43, 108, 6, 116, 218, 55, 155, 223, 143, @@ -48,10 +46,6 @@ fn test_submit_happy_path() { .into()]); let delivery_cost = InboundQueue::calculate_delivery_cost(message.encode().len() as u32); - assert!( - Parameters::get().rewards.local < delivery_cost, - "delivery cost exceeds pure reward" - ); assert_eq!(Balances::balance(&relayer), delivery_cost, "relayer was rewarded"); assert!( @@ -132,11 +126,6 @@ fn test_submit_with_invalid_nonce() { }; assert_ok!(InboundQueue::submit(origin.clone(), message.clone())); - let nonce: u64 = >::get(ChannelId::from(hex!( - "c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539" - ))); - assert_eq!(nonce, 1); - // Submit the same again assert_noop!( InboundQueue::submit(origin.clone(), message.clone()), diff --git a/bridges/snowbridge/primitives/router-v2/src/inbound/mod.rs b/bridges/snowbridge/primitives/router-v2/src/inbound/mod.rs index fbfc52d01c83..998008ab4871 100644 --- a/bridges/snowbridge/primitives/router-v2/src/inbound/mod.rs +++ b/bridges/snowbridge/primitives/router-v2/src/inbound/mod.rs @@ -24,13 +24,13 @@ const MINIMUM_DEPOSIT: u128 = 1; /// Instead having BridgeHub transcode the messages into XCM. #[derive(Clone, Encode, Decode, RuntimeDebug)] pub enum VersionedMessage { - V1(MessageV1), + V2(MessageV2), } -/// For V1, the ethereum side sends messages which are transcoded into XCM. These messages are +/// For V2, the ethereum side sends messages which are transcoded into XCM. These messages are /// self-contained, in that they can be transcoded using only information in the message. #[derive(Clone, Encode, Decode, RuntimeDebug)] -pub struct MessageV1 { +pub struct MessageV2 { /// EIP-155 chain id of the origin Ethereum network pub chain_id: u64, /// The command originating from the Gateway contract @@ -189,11 +189,11 @@ where use Command::*; use VersionedMessage::*; match message { - V1(MessageV1 { chain_id, command: RegisterToken { token, fee } }) => + V2(MessageV2 { chain_id, command: RegisterToken { token, fee } }) => Ok(Self::convert_register_token(message_id, chain_id, token, fee)), - V1(MessageV1 { chain_id, command: SendToken { token, destination, amount, fee } }) => + V2(MessageV2 { chain_id, command: SendToken { token, destination, amount, fee } }) => Ok(Self::convert_send_token(message_id, chain_id, token, destination, amount, fee)), - V1(MessageV1 { + V2(MessageV2 { chain_id, command: SendNativeToken { token_id, destination, amount, fee }, }) => Self::convert_send_native_token( diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs index 2ae0e59e06a1..515a15e52ce2 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs @@ -122,7 +122,6 @@ impl snowbridge_pallet_inbound_queue_v2::Config for Runtime { type XcmSender = XcmRouter; #[cfg(feature = "runtime-benchmarks")] type XcmSender = DoNothingRouter; - type ChannelLookup = EthereumSystem; type GatewayAddress = EthereumGatewayAddress; #[cfg(feature = "runtime-benchmarks")] type Helper = Runtime; @@ -140,8 +139,6 @@ impl snowbridge_pallet_inbound_queue_v2::Config for Runtime { type LengthToFee = ConstantMultiplier; type MaxMessageSize = ConstU32<2048>; type WeightInfo = crate::weights::snowbridge_pallet_inbound_queue_v2::WeightInfo; - type PricingParameters = EthereumSystem; - type AssetTransactor = ::AssetTransactor; } impl snowbridge_pallet_outbound_queue::Config for Runtime {