Skip to content

Commit

Permalink
Rebase inbound queue
Browse files Browse the repository at this point in the history
  • Loading branch information
yrong committed Oct 24, 2024
1 parent 4796240 commit 0e9cca0
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 789 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,6 @@ mod benchmarks {
create_message.block_roots_root,
);

let sovereign_account = sibling_sovereign_account::<T>(1000u32.into());

let minimum_balance = T::Token::minimum_balance();

// So that the receiving account exists
assert_ok!(T::Token::mint_into(&caller, minimum_balance));
// Fund the sovereign account (parachain sovereign account) so it can transfer a reward
// fee to the caller account
assert_ok!(T::Token::mint_into(
&sovereign_account,
3_000_000_000_000u128
.try_into()
.unwrap_or_else(|_| panic!("unable to cast sovereign account balance")),
));

#[block]
{
assert_ok!(InboundQueue::<T>::submit(
Expand Down
15 changes: 6 additions & 9 deletions bridges/snowbridge/pallets/inbound-queue-v2/src/envelope.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,26 @@
// 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_core::{RuntimeDebug, H160};
use sp_std::prelude::*;

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, 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,
/// Total fee paid in Ether on Ethereum, should cover all the cost
pub fee: u128,
/// The inner payload generated from the source application.
pub payload: Vec<u8>,
}
Expand All @@ -41,9 +39,8 @@ 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()),
fee: event.fee,
payload: event.payload,
})
}
Expand Down
203 changes: 41 additions & 162 deletions bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,49 +38,35 @@ mod test;

use codec::{Decode, DecodeAll, Encode};
use envelope::Envelope;
use frame_support::{
traits::{
fungible::{Inspect, Mutate},
tokens::{Fortitude, Preservation},
},
weights::WeightToFee,
PalletError,
};
use frame_support::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,
use xcm::{
prelude::{send_xcm, Junction::*, Location, SendError as XcmpSendError, SendXcm, Xcm},
VersionedXcm, MAX_XCM_DECODE_DEPTH,
};
use xcm_executor::traits::TransactAsset;

use snowbridge_core::{
inbound::{Message, VerificationError, Verifier},
sibling_sovereign_account, BasicOperatingMode, Channel, ChannelId, ParaId, PricingParameters,
StaticLookup,
};
use snowbridge_router_primitives::inbound::v2::{
ConvertMessage, ConvertMessageError, VersionedMessage,
BasicOperatingMode,
};
use sp_runtime::{traits::Saturating, SaturatedConversion, TokenError};
use snowbridge_router_primitives::inbound::v2::Message as MessageV2;

pub use weights::WeightInfo;

#[cfg(feature = "runtime-benchmarks")]
use snowbridge_beacon_primitives::BeaconHeader;

type BalanceOf<T> =
<<T as pallet::Config>::Token as Inspect<<T as frame_system::Config>::AccountId>>::Balance;

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 {
use super::*;
use codec::DecodeLimit;

use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
Expand All @@ -101,44 +87,17 @@ pub mod pallet {
/// The verifier for inbound messages from Ethereum
type Verifier: Verifier;

/// Message relayers are rewarded with this asset
type Token: Mutate<Self::AccountId> + Inspect<Self::AccountId>;

/// XCM message sender
type XcmSender: SendXcm;

// Address of the Gateway contract
/// Address of the Gateway contract
#[pallet::constant]
type GatewayAddress: Get<H160>;

/// Convert inbound message to XCM
type MessageConverter: ConvertMessage<
AccountId = Self::AccountId,
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")]
type Helper: BenchmarkHelper<Self>;

/// Convert a weight value into deductible balance type.
type WeightToFee: WeightToFee<Balance = BalanceOf<Self>>;

/// Convert a length value into deductible balance type
type LengthToFee: WeightToFee<Balance = BalanceOf<Self>>;

/// 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,14 +108,10 @@ 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
message_id: [u8; 32],
/// Fee burned for the teleport
fee_burned: BalanceOf<T>,
},
/// Set OperatingMode
OperatingModeChanged { mode: BasicOperatingMode },
Expand Down Expand Up @@ -184,8 +139,6 @@ pub mod pallet {
Verification(VerificationError),
/// XCMP send failure
Send(SendError),
/// Message conversion error
ConvertMessage(ConvertMessageError),
}

#[derive(Clone, Encode, Decode, Eq, PartialEq, Debug, TypeInfo, PalletError)]
Expand Down Expand Up @@ -215,9 +168,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 @@ -230,7 +183,7 @@ pub mod pallet {
#[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::submit())]
pub fn submit(origin: OriginFor<T>, message: Message) -> DispatchResult {
let who = ensure_signed(origin)?;
let _who = ensure_signed(origin)?;
ensure!(!Self::operating_mode().is_halted(), Error::<T>::Halted);

// submit message to verifier for verification
Expand All @@ -244,63 +197,46 @@ 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())
// Decode payload into `MessageV2`
let message = MessageV2::decode_all(&mut envelope.payload.as_ref())
.map_err(|_| Error::<T>::InvalidPayload)?;

// Decode message into XCM
let (xcm, fee) = Self::do_convert(envelope.message_id, message.clone())?;
// Decode xcm
let versioned_xcm = VersionedXcm::<()>::decode_with_depth_limit(
MAX_XCM_DECODE_DEPTH,
&mut message.xcm.as_ref(),
)
.map_err(|_| Error::<T>::InvalidPayload)?;
let xcm: Xcm<()> = versioned_xcm.try_into().map_err(|_| <Error<T>>::InvalidPayload)?;

log::info!(
target: LOG_TARGET,
"💫 xcm decoded as {:?} with fee {:?}",
"💫 xcm decoded as {:?}",
xcm,
fee
);

// Burning fees for teleport
Self::burn_fees(channel.para_id, fee)?;
// Set nonce flag to true
<Nonce<T>>::try_mutate(envelope.nonce, |done| -> DispatchResult {
*done = true;
Ok(())
})?;

// Todo: Deposit fee(in Ether) to RewardLeger which should cover all of:
// T::RewardLeger::deposit(who, envelope.fee.into())?;
// 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

// Attempt to send XCM to a dest parachain
let message_id = Self::send_xcm(xcm, channel.para_id)?;
// Attempt to forward XCM to AH
let dest = Location::new(1, [Parachain(1000)]);
let (message_id, _) = send_xcm::<T::XcmSender>(dest, xcm).map_err(Error::<T>::from)?;

Self::deposit_event(Event::MessageReceived {
channel_id: envelope.channel_id,
nonce: envelope.nonce,
message_id,
fee_burned: fee,
});
Self::deposit_event(Event::MessageReceived { nonce: envelope.nonce, message_id });

Ok(())
}
Expand All @@ -318,61 +254,4 @@ pub mod pallet {
Ok(())
}
}

impl<T: Config> Pallet<T> {
pub fn do_convert(
message_id: H256,
message: VersionedMessage,
) -> Result<(Xcm<()>, BalanceOf<T>), Error<T>> {
let (xcm, fee) = T::MessageConverter::convert(message_id, message)
.map_err(|e| Error::<T>::ConvertMessage(e))?;
Ok((xcm, fee))
}

pub fn send_xcm(xcm: Xcm<()>, dest: ParaId) -> Result<XcmHash, Error<T>> {
let dest = Location::new(1, [Parachain(dest.into())]);
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())
}
}
}
Loading

0 comments on commit 0e9cca0

Please sign in to comment.