Skip to content

Commit

Permalink
Inbound queue v2
Browse files Browse the repository at this point in the history
  • Loading branch information
yrong committed Oct 18, 2024
1 parent d8895c2 commit 738a2de
Show file tree
Hide file tree
Showing 6 changed files with 51 additions and 180 deletions.
16 changes: 11 additions & 5 deletions bridges/snowbridge/pallets/inbound-queue-v2/src/envelope.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <[email protected]>
use snowbridge_core::{inbound::Log, ChannelId};
use snowbridge_core::inbound::Log;

use sp_core::{RuntimeDebug, H160, H256};
use sp_std::prelude::*;
Expand All @@ -9,20 +9,24 @@ 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.
#[derive(Clone, RuntimeDebug)]
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<u8>,
}
Expand All @@ -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,
})
}
Expand Down
127 changes: 32 additions & 95 deletions bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,30 +41,26 @@ use envelope::Envelope;
use frame_support::{
traits::{
fungible::{Inspect, Mutate},
tokens::{Fortitude, Preservation},
tokens::{Fortitude, Precision, Preservation},
},
weights::WeightToFee,
PalletError,
};
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;

Expand All @@ -76,7 +72,7 @@ type BalanceOf<T> =

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 {
Expand All @@ -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<Self::AccountId> + Inspect<Self::AccountId>;

/// XCM message sender
Expand All @@ -117,12 +113,6 @@ pub mod pallet {
Balance = BalanceOf<Self>,
>;

/// Lookup a channel descriptor
type ChannelLookup: StaticLookup<Source = ChannelId, Target = Channel>;

/// Lookup pricing parameters
type PricingParameters: Get<PricingParameters<BalanceOf<Self>>>;

type WeightInfo: WeightInfo;

#[cfg(feature = "runtime-benchmarks")]
Expand All @@ -136,9 +126,6 @@ pub mod pallet {

/// The upper limit here only used to estimate delivery cost
type MaxMessageSize: Get<u32>;

/// To withdraw and deposit an asset.
type AssetTransactor: TransactAsset;
}

#[pallet::hooks]
Expand All @@ -149,8 +136,6 @@ pub mod pallet {
pub enum Event<T: Config> {
/// 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
Expand Down Expand Up @@ -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<T: Config> = StorageMap<_, Twox64Concat, ChannelId, u64, ValueQuery>;
pub type Nonce<T: Config> = StorageMap<_, Identity, u64, bool, ValueQuery>;

/// The current operating mode of the pallet.
#[pallet::storage]
Expand All @@ -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::<T>::InvalidGateway);

// Retrieve the registered channel for this message
let channel =
T::ChannelLookup::lookup(envelope.channel_id).ok_or(Error::<T>::InvalidChannel)?;

// Verify message nonce
<Nonce<T>>::try_mutate(envelope.channel_id, |nonce| -> DispatchResult {
if *nonce == u64::MAX {
return Err(Error::<T>::MaxNonceReached.into())
}
if envelope.nonce != nonce.saturating_add(1) {
Err(Error::<T>::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::<T>(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!(!<Nonce<T>>::contains_key(envelope.nonce), Error::<T>::InvalidNonce);

// Decode payload into `VersionedMessage`
let message = VersionedMessage::decode_all(&mut envelope.payload.as_ref())
Expand All @@ -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
<Nonce<T>>::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,
Expand Down Expand Up @@ -334,45 +311,5 @@ pub mod pallet {
let (xcm_hash, _) = send_xcm::<T::XcmSender>(dest, xcm).map_err(Error::<T>::from)?;
Ok(xcm_hash)
}

pub fn calculate_delivery_cost(length: u32) -> BalanceOf<T> {
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<T>) -> 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::<u128>()).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<T: Config> Get<BalanceOf<T>> for Pallet<T> {
fn get() -> BalanceOf<T> {
// Cost here based on MaxMessagePayloadSize(the worst case)
Self::calculate_delivery_cost(T::MaxMessageSize::get())
}
}
}
62 changes: 2 additions & 60 deletions bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,17 @@ 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},
BuildStorage, FixedU128, MultiSignature,
};
use sp_std::{convert::From, default::Default};
use xcm::{latest::SendXcm, prelude::*};
use xcm_executor::AssetsInHolding;

use crate::{self as inbound_queue};

Expand Down Expand Up @@ -151,63 +149,10 @@ impl SendXcm for MockXcmSender {

parameter_types! {
pub const OwnParaId: ParaId = ParaId::new(1013);
pub Parameters: PricingParameters<u128> = 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<Self::Target> {
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<AssetsInHolding, XcmError> {
Ok(AssetsInHolding::default())
}

fn internal_transfer_asset(
_what: &Asset,
_from: &Location,
_to: &Location,
_context: &XcmContext,
) -> Result<AssetsInHolding, XcmError> {
Ok(AssetsInHolding::default())
}
}

pub struct MockTokenIdConvert;
impl MaybeEquivalence<TokenId, Location> for MockTokenIdConvert {
fn convert(_id: &TokenId) -> Option<Location> {
Expand Down Expand Up @@ -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<u128>;
type LengthToFee = IdentityFee<u128>;
type MaxMessageSize = ConstU32<1024>;
type AssetTransactor = SuccessfulTransactor;
}

pub fn last_events(n: usize) -> Vec<RuntimeEvent> {
Expand Down
11 changes: 0 additions & 11 deletions bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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!(
Expand Down Expand Up @@ -132,11 +126,6 @@ fn test_submit_with_invalid_nonce() {
};
assert_ok!(InboundQueue::submit(origin.clone(), message.clone()));

let nonce: u64 = <Nonce<Test>>::get(ChannelId::from(hex!(
"c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539"
)));
assert_eq!(nonce, 1);

// Submit the same again
assert_noop!(
InboundQueue::submit(origin.clone(), message.clone()),
Expand Down
Loading

0 comments on commit 738a2de

Please sign in to comment.