snowbridge_pallet_inbound_queue_v2/
lib.rs#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
pub mod weights;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod test;
pub use crate::weights::WeightInfo;
use frame_support::{
traits::{
fungible::{Inspect, Mutate},
tokens::Balance,
},
weights::WeightToFee,
};
use frame_system::ensure_signed;
use snowbridge_core::{
sparse_bitmap::{SparseBitmap, SparseBitmapImpl},
BasicOperatingMode,
};
use snowbridge_inbound_queue_primitives::{
v2::{ConvertMessage, ConvertMessageError, Message},
EventProof, VerificationError, Verifier,
};
use sp_core::H160;
use sp_runtime::traits::TryConvert;
use sp_std::prelude::*;
use xcm::prelude::{ExecuteXcm, Junction::*, Location, SendXcm, *};
use bp_relayers::RewardLedger;
#[cfg(feature = "runtime-benchmarks")]
use {snowbridge_beacon_primitives::BeaconHeader, sp_core::H256};
pub use pallet::*;
pub const LOG_TARGET: &str = "snowbridge-pallet-inbound-queue-v2";
pub type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
type BalanceOf<T> =
<<T as pallet::Config>::Token as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
pub type Nonce<T> = SparseBitmapImpl<crate::NonceBitmap<T>>;
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
#[pallet::pallet]
pub struct Pallet<T>(_);
#[cfg(feature = "runtime-benchmarks")]
pub trait BenchmarkHelper<T> {
fn initialize_storage(beacon_header: BeaconHeader, block_roots_root: H256);
}
#[pallet::config]
pub trait Config: frame_system::Config {
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
type Verifier: Verifier;
type XcmSender: SendXcm;
type XcmExecutor: ExecuteXcm<Self::RuntimeCall>;
type EthereumNetwork: Get<NetworkId>;
#[pallet::constant]
type GatewayAddress: Get<H160>;
type AssetHubParaId: Get<u32>;
type MessageConverter: ConvertMessage;
#[cfg(feature = "runtime-benchmarks")]
type Helper: BenchmarkHelper<Self>;
type Balance: Balance + From<u128>;
type RewardKind: Parameter + MaxEncodedLen + Send + Sync + Copy + Clone;
#[pallet::constant]
type DefaultRewardKind: Get<Self::RewardKind>;
type RewardPayment: RewardLedger<Self::AccountId, Self::RewardKind, u128>;
type WeightInfo: WeightInfo;
type WeightToFee: WeightToFee<Balance = BalanceOf<Self>>;
type Token: Mutate<Self::AccountId> + Inspect<Self::AccountId>;
type AccountToLocation: for<'a> TryConvert<&'a Self::AccountId, Location>;
}
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
MessageReceived {
nonce: u64,
message_id: [u8; 32],
},
OperatingModeChanged { mode: BasicOperatingMode },
FeesPaid { paying: Location, fees: Assets },
}
#[pallet::error]
pub enum Error<T> {
InvalidGateway,
InvalidAccount,
InvalidMessage,
InvalidNonce,
InvalidFee,
InvalidPayload,
InvalidChannel,
MaxNonceReached,
InvalidAccountConversion,
InvalidNetwork,
Halted,
FeesNotMet,
Unreachable,
SendFailure,
InvalidAsset,
CannotReanchor,
Verification(VerificationError),
}
impl<T: Config> From<SendError> for Error<T> {
fn from(e: SendError) -> Self {
match e {
SendError::Fees => Error::<T>::FeesNotMet,
SendError::NotApplicable => Error::<T>::Unreachable,
_ => Error::<T>::SendFailure,
}
}
}
impl<T: Config> From<ConvertMessageError> for Error<T> {
fn from(e: ConvertMessageError) -> Self {
match e {
ConvertMessageError::InvalidAsset => Error::<T>::InvalidAsset,
ConvertMessageError::CannotReanchor => Error::<T>::CannotReanchor,
ConvertMessageError::InvalidNetwork => Error::<T>::InvalidNetwork,
}
}
}
#[pallet::storage]
pub type NonceBitmap<T: Config> = StorageMap<_, Twox64Concat, u128, u128, ValueQuery>;
#[pallet::storage]
pub type OperatingMode<T: Config> = StorageValue<_, BasicOperatingMode, ValueQuery>;
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::submit())]
pub fn submit(origin: OriginFor<T>, event: Box<EventProof>) -> DispatchResult {
let who = ensure_signed(origin)?;
ensure!(!OperatingMode::<T>::get().is_halted(), Error::<T>::Halted);
T::Verifier::verify(&event.event_log, &event.proof)
.map_err(|e| Error::<T>::Verification(e))?;
let message =
Message::try_from(&event.event_log).map_err(|_| Error::<T>::InvalidMessage)?;
Self::process_message(who, message)
}
#[pallet::call_index(1)]
#[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))]
pub fn set_operating_mode(
origin: OriginFor<T>,
mode: BasicOperatingMode,
) -> DispatchResult {
ensure_root(origin)?;
OperatingMode::<T>::set(mode);
Self::deposit_event(Event::OperatingModeChanged { mode });
Ok(())
}
}
impl<T: Config> Pallet<T> {
pub fn process_message(relayer: T::AccountId, message: Message) -> DispatchResult {
ensure!(T::GatewayAddress::get() == message.gateway, Error::<T>::InvalidGateway);
let (nonce, relayer_fee) = (message.nonce, message.relayer_fee);
ensure!(!Nonce::<T>::get(nonce.into()), Error::<T>::InvalidNonce);
let xcm =
T::MessageConverter::convert(message).map_err(|error| Error::<T>::from(error))?;
let dest = Location::new(1, [Parachain(T::AssetHubParaId::get())]);
let message_id =
Self::send_xcm(dest.clone(), &relayer, xcm.clone()).map_err(|error| {
tracing::error!(target: LOG_TARGET, ?error, ?dest, ?xcm, "XCM send failed with error");
Error::<T>::from(error)
})?;
T::RewardPayment::register_reward(&relayer, T::DefaultRewardKind::get(), relayer_fee);
Nonce::<T>::set(nonce.into());
Self::deposit_event(Event::MessageReceived { nonce, message_id });
Ok(())
}
fn send_xcm(
dest: Location,
fee_payer: &T::AccountId,
xcm: Xcm<()>,
) -> Result<XcmHash, SendError> {
let (ticket, fee) = validate_send::<T::XcmSender>(dest, xcm)?;
let fee_payer = T::AccountToLocation::try_convert(fee_payer).map_err(|err| {
tracing::error!(
target: LOG_TARGET,
?err,
"Failed to convert account to XCM location",
);
SendError::NotApplicable
})?;
T::XcmExecutor::charge_fees(fee_payer.clone(), fee.clone()).map_err(|error| {
tracing::error!(
target: LOG_TARGET,
?error,
"Charging fees failed with error",
);
SendError::Fees
})?;
Self::deposit_event(Event::FeesPaid { paying: fee_payer, fees: fee });
T::XcmSender::deliver(ticket)
}
}
}