snowbridge_pallet_outbound_queue/
lib.rs1#![cfg_attr(not(feature = "std"), no_std)]
91pub mod api;
92pub mod process_message_impl;
93pub mod send_message_impl;
94pub mod types;
95pub mod weights;
96
97#[cfg(feature = "runtime-benchmarks")]
98mod benchmarking;
99
100#[cfg(test)]
101mod mock;
102
103#[cfg(test)]
104mod test;
105
106use bridge_hub_common::{AggregateMessageOrigin, CustomDigestItem};
107use codec::Decode;
108use frame_support::{
109 storage::StorageStreamIter,
110 traits::{tokens::Balance, Contains, Defensive, EnqueueMessage, Get, ProcessMessageError},
111 weights::{Weight, WeightToFee},
112};
113use snowbridge_core::{BasicOperatingMode, ChannelId};
114use snowbridge_merkle_tree::merkle_root;
115use snowbridge_outbound_queue_primitives::v1::{
116 Fee, GasMeter, QueuedMessage, VersionedQueuedMessage, ETHER_DECIMALS,
117};
118use sp_core::{H256, U256};
119use sp_runtime::{
120 traits::{CheckedDiv, Hash},
121 DigestItem, Saturating,
122};
123use sp_std::prelude::*;
124pub use types::{CommittedMessage, ProcessMessageOriginOf};
125pub use weights::WeightInfo;
126
127pub use pallet::*;
128
129#[frame_support::pallet]
130pub mod pallet {
131 use super::*;
132 use frame_support::pallet_prelude::*;
133 use frame_system::pallet_prelude::*;
134 use snowbridge_core::PricingParameters;
135 use sp_arithmetic::FixedU128;
136
137 #[pallet::pallet]
138 pub struct Pallet<T>(_);
139
140 #[pallet::config]
141 pub trait Config: frame_system::Config {
142 #[allow(deprecated)]
143 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
144
145 type Hashing: Hash<Output = H256>;
146
147 type MessageQueue: EnqueueMessage<AggregateMessageOrigin>;
148
149 type GasMeter: GasMeter;
151
152 type Balance: Balance + From<u128>;
153
154 #[pallet::constant]
156 type Decimals: Get<u8>;
157
158 #[pallet::constant]
160 type MaxMessagePayloadSize: Get<u32>;
161
162 #[pallet::constant]
164 type MaxMessagesPerBlock: Get<u32>;
165
166 type Channels: Contains<ChannelId>;
168
169 type PricingParameters: Get<PricingParameters<Self::Balance>>;
170
171 type WeightToFee: WeightToFee<Balance = Self::Balance>;
173
174 type WeightInfo: WeightInfo;
176 }
177
178 #[pallet::event]
179 #[pallet::generate_deposit(pub(super) fn deposit_event)]
180 pub enum Event<T: Config> {
181 MessageQueued {
183 id: H256,
185 },
186 MessageAccepted {
189 id: H256,
191 nonce: u64,
193 },
194 MessagesCommitted {
196 root: H256,
198 count: u64,
200 },
201 OperatingModeChanged { mode: BasicOperatingMode },
203 }
204
205 #[pallet::error]
206 pub enum Error<T> {
207 MessageTooLarge,
209 Halted,
211 InvalidChannel,
213 }
214
215 #[pallet::storage]
222 #[pallet::unbounded]
223 pub(super) type Messages<T: Config> = StorageValue<_, Vec<CommittedMessage>, ValueQuery>;
224
225 #[pallet::storage]
229 #[pallet::unbounded]
230 #[pallet::getter(fn message_leaves)]
231 pub(super) type MessageLeaves<T: Config> = StorageValue<_, Vec<H256>, ValueQuery>;
232
233 #[pallet::storage]
235 pub type Nonce<T: Config> = StorageMap<_, Twox64Concat, ChannelId, u64, ValueQuery>;
236
237 #[pallet::storage]
239 #[pallet::getter(fn operating_mode)]
240 pub type OperatingMode<T: Config> = StorageValue<_, BasicOperatingMode, ValueQuery>;
241
242 #[pallet::hooks]
243 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T>
244 where
245 T::AccountId: AsRef<[u8]>,
246 {
247 fn on_initialize(_: BlockNumberFor<T>) -> Weight {
248 Messages::<T>::kill();
250 MessageLeaves::<T>::kill();
251 T::WeightInfo::commit()
253 }
254
255 fn on_finalize(_: BlockNumberFor<T>) {
256 Self::commit();
257 }
258
259 fn integrity_test() {
260 let decimals = T::Decimals::get();
261 assert!(decimals == 10 || decimals == 12, "Decimals should be 10 or 12");
262 }
263 }
264
265 #[pallet::call]
266 impl<T: Config> Pallet<T> {
267 #[pallet::call_index(0)]
269 #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))]
270 pub fn set_operating_mode(
271 origin: OriginFor<T>,
272 mode: BasicOperatingMode,
273 ) -> DispatchResult {
274 ensure_root(origin)?;
275 OperatingMode::<T>::put(mode);
276 Self::deposit_event(Event::OperatingModeChanged { mode });
277 Ok(())
278 }
279 }
280
281 impl<T: Config> Pallet<T> {
282 pub(crate) fn commit() {
284 let count = MessageLeaves::<T>::decode_len().unwrap_or_default() as u64;
285 if count == 0 {
286 return
287 }
288
289 let root = merkle_root::<<T as Config>::Hashing, _>(MessageLeaves::<T>::stream_iter());
291
292 let digest_item: DigestItem = CustomDigestItem::Snowbridge(root).into();
293
294 <frame_system::Pallet<T>>::deposit_log(digest_item);
296
297 Self::deposit_event(Event::MessagesCommitted { root, count });
298 }
299
300 pub(crate) fn do_process_message(
302 _: ProcessMessageOriginOf<T>,
303 mut message: &[u8],
304 ) -> Result<bool, ProcessMessageError> {
305 use ProcessMessageError::*;
306
307 ensure!(
310 MessageLeaves::<T>::decode_len().unwrap_or(0) <
311 T::MaxMessagesPerBlock::get() as usize,
312 Yield
313 );
314
315 let versioned_queued_message: VersionedQueuedMessage =
317 VersionedQueuedMessage::decode(&mut message).map_err(|_| Corrupt)?;
318
319 let queued_message: QueuedMessage =
321 versioned_queued_message.try_into().map_err(|_| Unsupported)?;
322
323 let nonce = <Nonce<T>>::try_mutate(
325 queued_message.channel_id,
326 |nonce| -> Result<u64, ProcessMessageError> {
327 *nonce = nonce.checked_add(1).ok_or(Unsupported)?;
328 Ok(*nonce)
329 },
330 )?;
331
332 let pricing_params = T::PricingParameters::get();
333 let command = queued_message.command.index();
334 let params = queued_message.command.abi_encode();
335 let max_dispatch_gas =
336 T::GasMeter::maximum_dispatch_gas_used_at_most(&queued_message.command);
337 let reward = pricing_params.rewards.remote;
338
339 let message = CommittedMessage {
341 channel_id: queued_message.channel_id,
342 nonce,
343 command,
344 params,
345 max_dispatch_gas,
346 max_fee_per_gas: pricing_params
347 .fee_per_gas
348 .try_into()
349 .defensive_unwrap_or(u128::MAX),
350 reward: reward.try_into().defensive_unwrap_or(u128::MAX),
351 id: queued_message.id,
352 };
353
354 let message_abi_encoded = ethabi::encode(&[message.clone().into()]);
356 let message_abi_encoded_hash = <T as Config>::Hashing::hash(&message_abi_encoded);
357
358 Messages::<T>::append(Box::new(message));
359 MessageLeaves::<T>::append(message_abi_encoded_hash);
360
361 Self::deposit_event(Event::MessageAccepted { id: queued_message.id, nonce });
362
363 Ok(true)
364 }
365
366 pub(crate) fn calculate_fee(
369 gas_used_at_most: u64,
370 params: PricingParameters<T::Balance>,
371 ) -> Fee<T::Balance> {
372 let fee = Self::calculate_remote_fee(
374 gas_used_at_most,
375 params.fee_per_gas,
376 params.rewards.remote,
377 );
378
379 let fee: u128 = fee.try_into().defensive_unwrap_or(u128::MAX);
381
382 let fee = FixedU128::from_inner(fee)
384 .saturating_mul(params.multiplier)
385 .checked_div(¶ms.exchange_rate)
386 .expect("exchange rate is not zero; qed")
387 .into_inner();
388
389 let fee = Self::convert_from_ether_decimals(fee);
391
392 Fee::from((Self::calculate_local_fee(), fee))
393 }
394
395 pub(crate) fn calculate_remote_fee(
397 gas_used_at_most: u64,
398 fee_per_gas: U256,
399 reward: U256,
400 ) -> U256 {
401 fee_per_gas.saturating_mul(gas_used_at_most.into()).saturating_add(reward)
402 }
403
404 pub(crate) fn calculate_local_fee() -> T::Balance {
406 T::WeightToFee::weight_to_fee(
407 &T::WeightInfo::do_process_message().saturating_add(T::WeightInfo::commit_single()),
408 )
409 }
410
411 pub(crate) fn convert_from_ether_decimals(value: u128) -> T::Balance {
415 let decimals = ETHER_DECIMALS.saturating_sub(T::Decimals::get()) as u32;
416 let denom = 10u128.saturating_pow(decimals);
417 value.checked_div(denom).expect("divisor is non-zero; qed").into()
418 }
419 }
420}