snowbridge_pallet_outbound_queue_v2/
lib.rs1#![cfg_attr(not(feature = "std"), no_std)]
52pub mod api;
53pub mod process_message_impl;
54pub mod send_message_impl;
55pub mod types;
56pub mod weights;
57
58#[cfg(feature = "runtime-benchmarks")]
59mod benchmarking;
60
61#[cfg(test)]
62mod mock;
63
64#[cfg(test)]
65mod test;
66
67#[cfg(feature = "runtime-benchmarks")]
68mod fixture;
69
70use alloy_core::{
71 primitives::{Bytes, FixedBytes},
72 sol_types::SolValue,
73};
74use bp_relayers::RewardLedger;
75use codec::{Decode, FullCodec};
76use frame_support::{
77 storage::StorageStreamIter,
78 traits::{tokens::Balance, EnqueueMessage, Get, ProcessMessageError},
79 weights::{Weight, WeightToFee},
80};
81use snowbridge_core::{
82 digest_item::SnowbridgeDigestItem,
83 reward::{AddTip, AddTipError},
84 BasicOperatingMode,
85};
86use snowbridge_merkle_tree::merkle_root;
87use snowbridge_outbound_queue_primitives::{
88 v2::{
89 abi::{CommandWrapper, OutboundMessageWrapper},
90 DeliveryReceipt, GasMeter, Message, OutboundCommandWrapper, OutboundMessage,
91 },
92 EventProof, VerificationError, Verifier,
93};
94use sp_core::{H160, H256};
95use sp_runtime::{
96 traits::{BlockNumberProvider, Debug, Hash},
97 DigestItem,
98};
99use sp_std::prelude::*;
100pub use types::{OnNewCommitment, PendingOrder, ProcessMessageOriginOf};
101pub use weights::WeightInfo;
102use xcm::prelude::NetworkId;
103
104#[cfg(feature = "runtime-benchmarks")]
105use snowbridge_beacon_primitives::BeaconHeader;
106
107pub use pallet::*;
108
109#[frame_support::pallet]
110pub mod pallet {
111 use super::*;
112 use frame_support::pallet_prelude::*;
113 use frame_system::pallet_prelude::*;
114
115 #[pallet::pallet]
116 pub struct Pallet<T>(_);
117
118 #[pallet::config]
119 pub trait Config: frame_system::Config {
120 #[allow(deprecated)]
121 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
122
123 type Hashing: Hash<Output = H256>;
124
125 type AggregateMessageOrigin: FullCodec
126 + MaxEncodedLen
127 + Clone
128 + Eq
129 + PartialEq
130 + TypeInfo
131 + Debug
132 + From<H256>;
133
134 type MessageQueue: EnqueueMessage<Self::AggregateMessageOrigin>;
135
136 type GasMeter: GasMeter;
138
139 type Balance: Balance + From<u128>;
140
141 #[pallet::constant]
143 type MaxMessagePayloadSize: Get<u32>;
144
145 #[pallet::constant]
147 type MaxMessagesPerBlock: Get<u32>;
148
149 type OnNewCommitment: OnNewCommitment;
151
152 type WeightToFee: WeightToFee<Balance = Self::Balance>;
154
155 type WeightInfo: WeightInfo;
157
158 type Verifier: Verifier;
160
161 #[pallet::constant]
163 type GatewayAddress: Get<H160>;
164 type RewardKind: Parameter + MaxEncodedLen + Send + Sync + Copy + Clone;
166 #[pallet::constant]
168 type DefaultRewardKind: Get<Self::RewardKind>;
169 type RewardPayment: RewardLedger<Self::AccountId, Self::RewardKind, u128>;
171 type EthereumNetwork: Get<NetworkId>;
173 #[cfg(feature = "runtime-benchmarks")]
174 type Helper: BenchmarkHelper<Self>;
175 }
176
177 #[pallet::event]
178 #[pallet::generate_deposit(pub fn deposit_event)]
179 pub enum Event<T: Config> {
180 MessageQueued {
182 message: Message,
184 },
185 MessageAccepted {
188 id: H256,
190 nonce: u64,
192 },
193 MessageRejected {
195 id: Option<H256>,
198 payload: Vec<u8>,
201 error: ProcessMessageError,
203 },
204 MessagePostponed {
206 payload: Vec<u8>,
209 reason: ProcessMessageError,
211 },
212 MessagesCommitted {
214 root: H256,
216 count: u64,
218 },
219 OperatingModeChanged { mode: BasicOperatingMode },
221 MessageDelivered { nonce: u64 },
223 }
224
225 #[pallet::error]
226 pub enum Error<T> {
227 MessageTooLarge,
229 Halted,
231 InvalidChannel,
233 InvalidEnvelope,
235 Verification(VerificationError),
237 InvalidGateway,
239 InvalidPendingNonce,
241 RewardPaymentFailed,
243 }
244
245 #[pallet::storage]
253 #[pallet::unbounded]
254 pub type Messages<T: Config> = StorageValue<_, Vec<OutboundMessage>, ValueQuery>;
255
256 #[pallet::storage]
261 #[pallet::unbounded]
262 pub type MessageLeaves<T: Config> = StorageValue<_, Vec<H256>, ValueQuery>;
263
264 #[pallet::storage]
266 pub type Nonce<T: Config> = StorageValue<_, u64, ValueQuery>;
267
268 #[pallet::storage]
270 pub type PendingOrders<T: Config> =
271 StorageMap<_, Twox64Concat, u64, PendingOrder<BlockNumberFor<T>>, OptionQuery>;
272
273 #[pallet::hooks]
274 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
275 fn on_initialize(_: BlockNumberFor<T>) -> Weight {
276 Messages::<T>::kill();
278 MessageLeaves::<T>::kill();
279 T::WeightInfo::on_initialize() + T::WeightInfo::commit()
281 }
282
283 fn on_finalize(_: BlockNumberFor<T>) {
284 Self::commit();
285 }
286 }
287
288 #[cfg(feature = "runtime-benchmarks")]
289 pub trait BenchmarkHelper<T> {
290 fn initialize_storage(beacon_header: BeaconHeader, block_roots_root: H256);
291 }
292
293 #[pallet::call]
294 impl<T: Config> Pallet<T>
295 where
296 <T as frame_system::Config>::AccountId: From<[u8; 32]>,
297 {
298 #[pallet::call_index(1)]
299 #[pallet::weight(T::WeightInfo::submit_delivery_receipt())]
300 pub fn submit_delivery_receipt(
301 origin: OriginFor<T>,
302 event: Box<EventProof>,
303 ) -> DispatchResult
304 where
305 <T as frame_system::Config>::AccountId: From<[u8; 32]>,
306 {
307 let relayer = ensure_signed(origin)?;
308
309 T::Verifier::verify(&event.event_log, &event.proof)
311 .map_err(|e| Error::<T>::Verification(e))?;
312
313 let receipt = DeliveryReceipt::try_from(&event.event_log)
314 .map_err(|_| Error::<T>::InvalidEnvelope)?;
315
316 Self::process_delivery_receipt(relayer, receipt)
317 }
318 }
319
320 impl<T: Config> Pallet<T> {
321 pub(crate) fn commit() {
323 let count = MessageLeaves::<T>::decode_len().unwrap_or_default() as u64;
324 if count == 0 {
325 return;
326 }
327
328 let root = merkle_root::<<T as Config>::Hashing, _>(MessageLeaves::<T>::stream_iter());
330
331 let digest_item: DigestItem = SnowbridgeDigestItem::SnowbridgeV2(root).into();
332
333 <frame_system::Pallet<T>>::deposit_log(digest_item);
335
336 T::OnNewCommitment::on_new_commitment(root);
337
338 Self::deposit_event(Event::MessagesCommitted { root, count });
339 }
340
341 pub(crate) fn do_process_message(
344 _: ProcessMessageOriginOf<T>,
345 mut message: &[u8],
346 ) -> Result<bool, ProcessMessageError> {
347 use ProcessMessageError::*;
348
349 let current_len = MessageLeaves::<T>::decode_len().unwrap_or(0);
352 if current_len >= T::MaxMessagesPerBlock::get() as usize {
353 Self::deposit_event(Event::MessagePostponed {
354 payload: message.to_vec(),
355 reason: Yield,
356 });
357 return Err(Yield);
358 }
359
360 let Message { origin, id, fee, commands } =
362 Message::decode(&mut message).map_err(|_| {
363 Self::deposit_event(Event::MessageRejected {
364 id: None,
365 payload: message.to_vec(),
366 error: Corrupt,
367 });
368 Corrupt
369 })?;
370
371 let commands: Vec<OutboundCommandWrapper> = commands
373 .into_iter()
374 .map(|command| OutboundCommandWrapper {
375 kind: command.index(),
376 gas: T::GasMeter::maximum_dispatch_gas_used_at_most(&command),
377 payload: command.abi_encode(),
378 })
379 .collect();
380
381 let nonce = <Nonce<T>>::get().checked_add(1).ok_or_else(|| {
382 Self::deposit_event(Event::MessageRejected {
383 id: None,
384 payload: message.to_vec(),
385 error: Unsupported,
386 });
387 Unsupported
388 })?;
389
390 let outbound_message = OutboundMessage {
391 origin,
392 nonce,
393 topic: id,
394 commands: commands.clone().try_into().map_err(|_| {
395 Self::deposit_event(Event::MessageRejected {
396 id: Some(id),
397 payload: message.to_vec(),
398 error: Corrupt,
399 });
400 Corrupt
401 })?,
402 };
403 Messages::<T>::append(outbound_message);
404
405 let abi_commands: Vec<CommandWrapper> = commands
409 .into_iter()
410 .map(|command| CommandWrapper {
411 kind: command.kind,
412 gas: command.gas,
413 payload: Bytes::from(command.payload),
414 })
415 .collect();
416 let committed_message = OutboundMessageWrapper {
417 origin: FixedBytes::from(origin.as_fixed_bytes()),
418 nonce,
419 topic: FixedBytes::from(id.as_fixed_bytes()),
420 commands: abi_commands,
421 };
422 let message_abi_encoded_hash =
423 <T as Config>::Hashing::hash(&committed_message.abi_encode());
424 MessageLeaves::<T>::append(message_abi_encoded_hash);
425
426 let order = PendingOrder {
432 nonce,
433 fee,
434 block_number: frame_system::Pallet::<T>::current_block_number(),
435 };
436 <PendingOrders<T>>::insert(nonce, order);
437
438 <Nonce<T>>::set(nonce);
439
440 Self::deposit_event(Event::MessageAccepted { id, nonce });
441
442 Ok(true)
443 }
444
445 pub fn process_delivery_receipt(
447 relayer: <T as frame_system::Config>::AccountId,
448 receipt: DeliveryReceipt,
449 ) -> DispatchResult
450 where
451 <T as frame_system::Config>::AccountId: From<[u8; 32]>,
452 {
453 ensure!(T::GatewayAddress::get() == receipt.gateway, Error::<T>::InvalidGateway);
455
456 let reward_account = if receipt.reward_address == [0u8; 32] {
457 relayer
458 } else {
459 receipt.reward_address.into()
460 };
461
462 let nonce = receipt.nonce;
463
464 let order = <PendingOrders<T>>::get(nonce).ok_or(Error::<T>::InvalidPendingNonce)?;
465
466 if order.fee > 0 {
467 T::RewardPayment::register_reward(
469 &reward_account,
470 T::DefaultRewardKind::get(),
471 order.fee,
472 );
473 }
474
475 <PendingOrders<T>>::remove(nonce);
476
477 Self::deposit_event(Event::MessageDelivered { nonce });
478
479 Ok(())
480 }
481 }
482
483 impl<T: Config> AddTip for Pallet<T> {
484 fn add_tip(nonce: u64, amount: u128) -> Result<(), AddTipError> {
485 ensure!(amount > 0, AddTipError::AmountZero);
486 PendingOrders::<T>::try_mutate_exists(nonce, |maybe_order| -> Result<(), AddTipError> {
487 match maybe_order {
488 Some(order) => {
489 order.fee = order.fee.saturating_add(amount);
490 Ok(())
491 },
492 None => Err(AddTipError::UnknownMessage),
493 }
494 })
495 }
496 }
497}