pezsnowbridge_pezpallet_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 pezbp_relayers::RewardLedger;
75use codec::{Decode, FullCodec};
76use pezframe_support::{
77 storage::StorageStreamIter,
78 traits::{tokens::Balance, EnqueueMessage, Get, ProcessMessageError},
79 weights::{Weight, WeightToFee},
80};
81use pezsnowbridge_core::{
82 digest_item::SnowbridgeDigestItem,
83 reward::{AddTip, AddTipError},
84 BasicOperatingMode,
85};
86use pezsnowbridge_merkle_tree::merkle_root;
87use pezsnowbridge_outbound_queue_primitives::{
88 v2::{
89 abi::{CommandWrapper, OutboundMessageWrapper},
90 DeliveryReceipt, GasMeter, Message, OutboundCommandWrapper, OutboundMessage,
91 },
92 EventProof, VerificationError, Verifier,
93};
94use pezsp_core::{H160, H256};
95use pezsp_runtime::{
96 traits::{BlockNumberProvider, Debug, Hash},
97 DigestItem,
98};
99use pezsp_std::prelude::*;
100pub use types::{OnNewCommitment, PendingOrder, ProcessMessageOriginOf};
101pub use weights::WeightInfo;
102use xcm::prelude::NetworkId;
103
104#[cfg(feature = "runtime-benchmarks")]
105use pezsnowbridge_beacon_primitives::BeaconHeader;
106
107pub use pezpallet::*;
108
109#[pezframe_support::pezpallet]
110pub mod pezpallet {
111 use super::*;
112 use pezframe_support::pezpallet_prelude::*;
113 use pezframe_system::pezpallet_prelude::*;
114
115 #[pezpallet::pezpallet]
116 pub struct Pezpallet<T>(_);
117
118 #[pezpallet::config]
119 pub trait Config: pezframe_system::Config {
120 #[allow(deprecated)]
121 type RuntimeEvent: From<Event<Self>>
122 + IsType<<Self as pezframe_system::Config>::RuntimeEvent>;
123
124 type Hashing: Hash<Output = H256>;
125
126 type AggregateMessageOrigin: FullCodec
127 + MaxEncodedLen
128 + Clone
129 + Eq
130 + PartialEq
131 + TypeInfo
132 + Debug
133 + From<H256>;
134
135 type MessageQueue: EnqueueMessage<Self::AggregateMessageOrigin>;
136
137 type GasMeter: GasMeter;
139
140 type Balance: Balance + From<u128>;
141
142 #[pezpallet::constant]
144 type MaxMessagePayloadSize: Get<u32>;
145
146 #[pezpallet::constant]
148 type MaxMessagesPerBlock: Get<u32>;
149
150 type OnNewCommitment: OnNewCommitment;
152
153 type WeightToFee: WeightToFee<Balance = Self::Balance>;
155
156 type WeightInfo: WeightInfo;
158
159 type Verifier: Verifier;
161
162 #[pezpallet::constant]
164 type GatewayAddress: Get<H160>;
165 type RewardKind: Parameter + MaxEncodedLen + Send + Sync + Copy + Clone;
167 #[pezpallet::constant]
170 type DefaultRewardKind: Get<Self::RewardKind>;
171 type RewardPayment: RewardLedger<Self::AccountId, Self::RewardKind, u128>;
173 type EthereumNetwork: Get<NetworkId>;
175 #[cfg(feature = "runtime-benchmarks")]
176 type Helper: BenchmarkHelper<Self>;
177 }
178
179 #[pezpallet::event]
180 #[pezpallet::generate_deposit(pub fn deposit_event)]
181 pub enum Event<T: Config> {
182 MessageQueued {
184 message: Message,
186 },
187 MessageAccepted {
190 id: H256,
192 nonce: u64,
194 },
195 MessageRejected {
197 id: Option<H256>,
200 payload: Vec<u8>,
203 error: ProcessMessageError,
205 },
206 MessagePostponed {
208 payload: Vec<u8>,
211 reason: ProcessMessageError,
213 },
214 MessagesCommitted {
216 root: H256,
218 count: u64,
220 },
221 OperatingModeChanged { mode: BasicOperatingMode },
223 MessageDelivered { nonce: u64 },
225 }
226
227 #[pezpallet::error]
228 pub enum Error<T> {
229 MessageTooLarge,
231 Halted,
233 InvalidChannel,
235 InvalidEnvelope,
237 Verification(VerificationError),
239 InvalidGateway,
241 InvalidPendingNonce,
243 RewardPaymentFailed,
245 }
246
247 #[pezpallet::storage]
255 #[pezpallet::unbounded]
256 pub type Messages<T: Config> = StorageValue<_, Vec<OutboundMessage>, ValueQuery>;
257
258 #[pezpallet::storage]
263 #[pezpallet::unbounded]
264 pub type MessageLeaves<T: Config> = StorageValue<_, Vec<H256>, ValueQuery>;
265
266 #[pezpallet::storage]
268 pub type Nonce<T: Config> = StorageValue<_, u64, ValueQuery>;
269
270 #[pezpallet::storage]
272 pub type PendingOrders<T: Config> =
273 StorageMap<_, Twox64Concat, u64, PendingOrder<BlockNumberFor<T>>, OptionQuery>;
274
275 #[pezpallet::hooks]
276 impl<T: Config> Hooks<BlockNumberFor<T>> for Pezpallet<T> {
277 fn on_initialize(_: BlockNumberFor<T>) -> Weight {
278 Messages::<T>::kill();
280 MessageLeaves::<T>::kill();
281 T::WeightInfo::on_initialize() + T::WeightInfo::commit()
283 }
284
285 fn on_finalize(_: BlockNumberFor<T>) {
286 Self::commit();
287 }
288 }
289
290 #[cfg(feature = "runtime-benchmarks")]
291 pub trait BenchmarkHelper<T> {
292 fn initialize_storage(beacon_header: BeaconHeader, block_roots_root: H256);
293 }
294
295 #[pezpallet::call]
296 impl<T: Config> Pezpallet<T>
297 where
298 <T as pezframe_system::Config>::AccountId: From<[u8; 32]>,
299 {
300 #[pezpallet::call_index(1)]
301 #[pezpallet::weight(T::WeightInfo::submit_delivery_receipt())]
302 pub fn submit_delivery_receipt(
303 origin: OriginFor<T>,
304 event: Box<EventProof>,
305 ) -> DispatchResult
306 where
307 <T as pezframe_system::Config>::AccountId: From<[u8; 32]>,
308 {
309 let relayer = ensure_signed(origin)?;
310
311 T::Verifier::verify(&event.event_log, &event.proof)
313 .map_err(|e| Error::<T>::Verification(e))?;
314
315 let receipt = DeliveryReceipt::try_from(&event.event_log)
316 .map_err(|_| Error::<T>::InvalidEnvelope)?;
317
318 Self::process_delivery_receipt(relayer, receipt)
319 }
320 }
321
322 impl<T: Config> Pezpallet<T> {
323 pub(crate) fn commit() {
325 let count = MessageLeaves::<T>::decode_len().unwrap_or_default() as u64;
326 if count == 0 {
327 return;
328 }
329
330 let root = merkle_root::<<T as Config>::Hashing, _>(MessageLeaves::<T>::stream_iter());
332
333 let digest_item: DigestItem = SnowbridgeDigestItem::SnowbridgeV2(root).into();
334
335 <pezframe_system::Pezpallet<T>>::deposit_log(digest_item);
337
338 T::OnNewCommitment::on_new_commitment(root);
339
340 Self::deposit_event(Event::MessagesCommitted { root, count });
341 }
342
343 pub(crate) fn do_process_message(
346 _: ProcessMessageOriginOf<T>,
347 mut message: &[u8],
348 ) -> Result<bool, ProcessMessageError> {
349 use ProcessMessageError::*;
350
351 let current_len = MessageLeaves::<T>::decode_len().unwrap_or(0);
354 if current_len >= T::MaxMessagesPerBlock::get() as usize {
355 Self::deposit_event(Event::MessagePostponed {
356 payload: message.to_vec(),
357 reason: Yield,
358 });
359 return Err(Yield);
360 }
361
362 let Message { origin, id, fee, commands } =
364 Message::decode(&mut message).map_err(|_| {
365 Self::deposit_event(Event::MessageRejected {
366 id: None,
367 payload: message.to_vec(),
368 error: Corrupt,
369 });
370 Corrupt
371 })?;
372
373 let commands: Vec<OutboundCommandWrapper> = commands
375 .into_iter()
376 .map(|command| OutboundCommandWrapper {
377 kind: command.index(),
378 gas: T::GasMeter::maximum_dispatch_gas_used_at_most(&command),
379 payload: command.abi_encode(),
380 })
381 .collect();
382
383 let nonce = <Nonce<T>>::get().checked_add(1).ok_or_else(|| {
384 Self::deposit_event(Event::MessageRejected {
385 id: None,
386 payload: message.to_vec(),
387 error: Unsupported,
388 });
389 Unsupported
390 })?;
391
392 let outbound_message = OutboundMessage {
393 origin,
394 nonce,
395 topic: id,
396 commands: commands.clone().try_into().map_err(|_| {
397 Self::deposit_event(Event::MessageRejected {
398 id: Some(id),
399 payload: message.to_vec(),
400 error: Corrupt,
401 });
402 Corrupt
403 })?,
404 };
405 Messages::<T>::append(outbound_message);
406
407 let abi_commands: Vec<CommandWrapper> = commands
411 .into_iter()
412 .map(|command| CommandWrapper {
413 kind: command.kind,
414 gas: command.gas,
415 payload: Bytes::from(command.payload),
416 })
417 .collect();
418 let committed_message = OutboundMessageWrapper {
419 origin: FixedBytes::from(origin.as_fixed_bytes()),
420 nonce,
421 topic: FixedBytes::from(id.as_fixed_bytes()),
422 commands: abi_commands,
423 };
424 let message_abi_encoded_hash =
425 <T as Config>::Hashing::hash(&committed_message.abi_encode());
426 MessageLeaves::<T>::append(message_abi_encoded_hash);
427
428 let order = PendingOrder {
434 nonce,
435 fee,
436 block_number: pezframe_system::Pezpallet::<T>::current_block_number(),
437 };
438 <PendingOrders<T>>::insert(nonce, order);
439
440 <Nonce<T>>::set(nonce);
441
442 Self::deposit_event(Event::MessageAccepted { id, nonce });
443
444 Ok(true)
445 }
446
447 pub fn process_delivery_receipt(
449 relayer: <T as pezframe_system::Config>::AccountId,
450 receipt: DeliveryReceipt,
451 ) -> DispatchResult
452 where
453 <T as pezframe_system::Config>::AccountId: From<[u8; 32]>,
454 {
455 ensure!(T::GatewayAddress::get() == receipt.gateway, Error::<T>::InvalidGateway);
457
458 let reward_account = if receipt.reward_address == [0u8; 32] {
459 relayer
460 } else {
461 receipt.reward_address.into()
462 };
463
464 let nonce = receipt.nonce;
465
466 let order = <PendingOrders<T>>::get(nonce).ok_or(Error::<T>::InvalidPendingNonce)?;
467
468 if order.fee > 0 {
469 T::RewardPayment::register_reward(
471 &reward_account,
472 T::DefaultRewardKind::get(),
473 order.fee,
474 );
475 }
476
477 <PendingOrders<T>>::remove(nonce);
478
479 Self::deposit_event(Event::MessageDelivered { nonce });
480
481 Ok(())
482 }
483 }
484
485 impl<T: Config> AddTip for Pezpallet<T> {
486 fn add_tip(nonce: u64, amount: u128) -> Result<(), AddTipError> {
487 ensure!(amount > 0, AddTipError::AmountZero);
488 PendingOrders::<T>::try_mutate_exists(nonce, |maybe_order| -> Result<(), AddTipError> {
489 match maybe_order {
490 Some(order) => {
491 order.fee = order.fee.saturating_add(amount);
492 Ok(())
493 },
494 None => Err(AddTipError::UnknownMessage),
495 }
496 })
497 }
498 }
499}