#![cfg_attr(not(feature = "std"), no_std)]
pub mod api;
pub mod process_message_impl;
pub mod send_message_impl;
pub mod types;
pub mod weights;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod test;
use bridge_hub_common::AggregateMessageOrigin;
use codec::Decode;
use frame_support::{
storage::StorageStreamIter,
traits::{tokens::Balance, Contains, Defensive, EnqueueMessage, Get, ProcessMessageError},
weights::{Weight, WeightToFee},
};
use snowbridge_core::{digest_item::SnowbridgeDigestItem, BasicOperatingMode, ChannelId};
use snowbridge_merkle_tree::merkle_root;
use snowbridge_outbound_queue_primitives::v1::{
Fee, GasMeter, QueuedMessage, VersionedQueuedMessage, ETHER_DECIMALS,
};
use sp_core::{H256, U256};
use sp_runtime::{
traits::{CheckedDiv, Hash},
DigestItem, Saturating,
};
use sp_std::prelude::*;
pub use types::{CommittedMessage, ProcessMessageOriginOf};
pub use weights::WeightInfo;
pub use pallet::*;
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
use snowbridge_core::PricingParameters;
use sp_arithmetic::FixedU128;
#[pallet::pallet]
pub struct Pallet<T>(_);
#[pallet::config]
pub trait Config: frame_system::Config {
#[allow(deprecated)]
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
type Hashing: Hash<Output = H256>;
type MessageQueue: EnqueueMessage<AggregateMessageOrigin>;
type GasMeter: GasMeter;
type Balance: Balance + From<u128>;
#[pallet::constant]
type Decimals: Get<u8>;
#[pallet::constant]
type MaxMessagePayloadSize: Get<u32>;
#[pallet::constant]
type MaxMessagesPerBlock: Get<u32>;
type Channels: Contains<ChannelId>;
type PricingParameters: Get<PricingParameters<Self::Balance>>;
type WeightToFee: WeightToFee<Balance = Self::Balance>;
type WeightInfo: WeightInfo;
}
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
MessageQueued {
id: H256,
},
MessageAccepted {
id: H256,
nonce: u64,
},
MessagesCommitted {
root: H256,
count: u64,
},
OperatingModeChanged { mode: BasicOperatingMode },
}
#[pallet::error]
pub enum Error<T> {
MessageTooLarge,
Halted,
InvalidChannel,
}
#[pallet::storage]
#[pallet::unbounded]
pub(super) type Messages<T: Config> = StorageValue<_, Vec<CommittedMessage>, ValueQuery>;
#[pallet::storage]
#[pallet::unbounded]
#[pallet::getter(fn message_leaves)]
pub(super) type MessageLeaves<T: Config> = StorageValue<_, Vec<H256>, ValueQuery>;
#[pallet::storage]
pub type Nonce<T: Config> = StorageMap<_, Twox64Concat, ChannelId, u64, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn operating_mode)]
pub type OperatingMode<T: Config> = StorageValue<_, BasicOperatingMode, ValueQuery>;
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T>
where
T::AccountId: AsRef<[u8]>,
{
fn on_initialize(_: BlockNumberFor<T>) -> Weight {
Messages::<T>::kill();
MessageLeaves::<T>::kill();
T::WeightInfo::commit()
}
fn on_finalize(_: BlockNumberFor<T>) {
Self::commit();
}
fn integrity_test() {
let decimals = T::Decimals::get();
assert!(decimals == 10 || decimals == 12, "Decimals should be 10 or 12");
}
}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[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>::put(mode);
Self::deposit_event(Event::OperatingModeChanged { mode });
Ok(())
}
}
impl<T: Config> Pallet<T> {
pub(crate) fn commit() {
let count = MessageLeaves::<T>::decode_len().unwrap_or_default() as u64;
if count == 0 {
return;
}
let root = merkle_root::<<T as Config>::Hashing, _>(MessageLeaves::<T>::stream_iter());
let digest_item: DigestItem = SnowbridgeDigestItem::Snowbridge(root).into();
<frame_system::Pallet<T>>::deposit_log(digest_item);
Self::deposit_event(Event::MessagesCommitted { root, count });
}
pub(crate) fn do_process_message(
_: ProcessMessageOriginOf<T>,
mut message: &[u8],
) -> Result<bool, ProcessMessageError> {
use ProcessMessageError::*;
ensure!(
MessageLeaves::<T>::decode_len().unwrap_or(0) <
T::MaxMessagesPerBlock::get() as usize,
Yield
);
let versioned_queued_message: VersionedQueuedMessage =
VersionedQueuedMessage::decode(&mut message).map_err(|_| Corrupt)?;
let queued_message: QueuedMessage =
versioned_queued_message.try_into().map_err(|_| Unsupported)?;
let nonce = <Nonce<T>>::try_mutate(
queued_message.channel_id,
|nonce| -> Result<u64, ProcessMessageError> {
*nonce = nonce.checked_add(1).ok_or(Unsupported)?;
Ok(*nonce)
},
)?;
let pricing_params = T::PricingParameters::get();
let command = queued_message.command.index();
let params = queued_message.command.abi_encode();
let max_dispatch_gas =
T::GasMeter::maximum_dispatch_gas_used_at_most(&queued_message.command);
let reward = pricing_params.rewards.remote;
let message = CommittedMessage {
channel_id: queued_message.channel_id,
nonce,
command,
params,
max_dispatch_gas,
max_fee_per_gas: pricing_params
.fee_per_gas
.try_into()
.defensive_unwrap_or(u128::MAX),
reward: reward.try_into().defensive_unwrap_or(u128::MAX),
id: queued_message.id,
};
let message_abi_encoded = ethabi::encode(&[message.clone().into()]);
let message_abi_encoded_hash = <T as Config>::Hashing::hash(&message_abi_encoded);
Messages::<T>::append(Box::new(message));
MessageLeaves::<T>::append(message_abi_encoded_hash);
Self::deposit_event(Event::MessageAccepted { id: queued_message.id, nonce });
Ok(true)
}
pub(crate) fn calculate_fee(
gas_used_at_most: u64,
params: PricingParameters<T::Balance>,
) -> Fee<T::Balance> {
let fee = Self::calculate_remote_fee(
gas_used_at_most,
params.fee_per_gas,
params.rewards.remote,
);
let fee: u128 = fee.try_into().defensive_unwrap_or(u128::MAX);
let fee = FixedU128::from_inner(fee)
.saturating_mul(params.multiplier)
.checked_div(¶ms.exchange_rate)
.expect("exchange rate is not zero; qed")
.into_inner();
let fee = Self::convert_from_ether_decimals(fee);
Fee::from((Self::calculate_local_fee(), fee))
}
pub(crate) fn calculate_remote_fee(
gas_used_at_most: u64,
fee_per_gas: U256,
reward: U256,
) -> U256 {
fee_per_gas.saturating_mul(gas_used_at_most.into()).saturating_add(reward)
}
pub(crate) fn calculate_local_fee() -> T::Balance {
T::WeightToFee::weight_to_fee(
&T::WeightInfo::do_process_message().saturating_add(T::WeightInfo::commit_single()),
)
}
pub(crate) fn convert_from_ether_decimals(value: u128) -> T::Balance {
let decimals = ETHER_DECIMALS.saturating_sub(T::Decimals::get()) as u32;
let denom = 10u128.saturating_pow(decimals);
value.checked_div(denom).expect("divisor is non-zero; qed").into()
}
}
}