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 bridge_hub_common::{AggregateMessageOrigin, CustomDigestItem};
76use codec::Decode;
77use frame_support::{
78 storage::StorageStreamIter,
79 traits::{tokens::Balance, EnqueueMessage, Get, ProcessMessageError},
80 weights::{Weight, WeightToFee},
81};
82use snowbridge_core::{
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, 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 MessageQueue: EnqueueMessage<AggregateMessageOrigin>;
126
127 type GasMeter: GasMeter;
129
130 type Balance: Balance + From<u128>;
131
132 #[pallet::constant]
134 type MaxMessagePayloadSize: Get<u32>;
135
136 #[pallet::constant]
138 type MaxMessagesPerBlock: Get<u32>;
139
140 type OnNewCommitment: OnNewCommitment;
142
143 type WeightToFee: WeightToFee<Balance = Self::Balance>;
145
146 type WeightInfo: WeightInfo;
148
149 type Verifier: Verifier;
151
152 #[pallet::constant]
154 type GatewayAddress: Get<H160>;
155 type RewardKind: Parameter + MaxEncodedLen + Send + Sync + Copy + Clone;
157 #[pallet::constant]
159 type DefaultRewardKind: Get<Self::RewardKind>;
160 type RewardPayment: RewardLedger<Self::AccountId, Self::RewardKind, u128>;
162 type EthereumNetwork: Get<NetworkId>;
164 #[cfg(feature = "runtime-benchmarks")]
165 type Helper: BenchmarkHelper<Self>;
166 }
167
168 #[pallet::event]
169 #[pallet::generate_deposit(pub(super) fn deposit_event)]
170 pub enum Event<T: Config> {
171 MessageQueued {
173 message: Message,
175 },
176 MessageAccepted {
179 id: H256,
181 nonce: u64,
183 },
184 MessageRejected {
186 id: Option<H256>,
189 payload: Vec<u8>,
192 error: ProcessMessageError,
194 },
195 MessagePostponed {
197 payload: Vec<u8>,
200 reason: ProcessMessageError,
202 },
203 MessagesCommitted {
205 root: H256,
207 count: u64,
209 },
210 OperatingModeChanged { mode: BasicOperatingMode },
212 MessageDelivered { nonce: u64 },
214 }
215
216 #[pallet::error]
217 pub enum Error<T> {
218 MessageTooLarge,
220 Halted,
222 InvalidChannel,
224 InvalidEnvelope,
226 Verification(VerificationError),
228 InvalidGateway,
230 InvalidPendingNonce,
232 RewardPaymentFailed,
234 }
235
236 #[pallet::storage]
244 #[pallet::unbounded]
245 pub(super) type Messages<T: Config> = StorageValue<_, Vec<OutboundMessage>, ValueQuery>;
246
247 #[pallet::storage]
252 #[pallet::unbounded]
253 pub(super) type MessageLeaves<T: Config> = StorageValue<_, Vec<H256>, ValueQuery>;
254
255 #[pallet::storage]
257 pub type Nonce<T: Config> = StorageValue<_, u64, ValueQuery>;
258
259 #[pallet::storage]
261 pub type PendingOrders<T: Config> =
262 StorageMap<_, Twox64Concat, u64, PendingOrder<BlockNumberFor<T>>, OptionQuery>;
263
264 #[pallet::hooks]
265 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
266 fn on_initialize(_: BlockNumberFor<T>) -> Weight {
267 Messages::<T>::kill();
269 MessageLeaves::<T>::kill();
270 T::WeightInfo::on_initialize() + T::WeightInfo::commit()
272 }
273
274 fn on_finalize(_: BlockNumberFor<T>) {
275 Self::commit();
276 }
277 }
278
279 #[cfg(feature = "runtime-benchmarks")]
280 pub trait BenchmarkHelper<T> {
281 fn initialize_storage(beacon_header: BeaconHeader, block_roots_root: H256);
282 }
283
284 #[pallet::call]
285 impl<T: Config> Pallet<T>
286 where
287 <T as frame_system::Config>::AccountId: From<[u8; 32]>,
288 {
289 #[pallet::call_index(1)]
290 #[pallet::weight(T::WeightInfo::submit_delivery_receipt())]
291 pub fn submit_delivery_receipt(
292 origin: OriginFor<T>,
293 event: Box<EventProof>,
294 ) -> DispatchResult
295 where
296 <T as frame_system::Config>::AccountId: From<[u8; 32]>,
297 {
298 let relayer = ensure_signed(origin)?;
299
300 T::Verifier::verify(&event.event_log, &event.proof)
302 .map_err(|e| Error::<T>::Verification(e))?;
303
304 let receipt = DeliveryReceipt::try_from(&event.event_log)
305 .map_err(|_| Error::<T>::InvalidEnvelope)?;
306
307 Self::process_delivery_receipt(relayer, receipt)
308 }
309 }
310
311 impl<T: Config> Pallet<T> {
312 pub(crate) fn commit() {
314 let count = MessageLeaves::<T>::decode_len().unwrap_or_default() as u64;
315 if count == 0 {
316 return;
317 }
318
319 let root = merkle_root::<<T as Config>::Hashing, _>(MessageLeaves::<T>::stream_iter());
321
322 let digest_item: DigestItem = CustomDigestItem::SnowbridgeV2(root).into();
323
324 <frame_system::Pallet<T>>::deposit_log(digest_item);
326
327 T::OnNewCommitment::on_new_commitment(root);
328
329 Self::deposit_event(Event::MessagesCommitted { root, count });
330 }
331
332 pub(crate) fn do_process_message(
335 _: ProcessMessageOriginOf<T>,
336 mut message: &[u8],
337 ) -> Result<bool, ProcessMessageError> {
338 use ProcessMessageError::*;
339
340 let current_len = MessageLeaves::<T>::decode_len().unwrap_or(0);
343 if current_len >= T::MaxMessagesPerBlock::get() as usize {
344 Self::deposit_event(Event::MessagePostponed {
345 payload: message.to_vec(),
346 reason: Yield,
347 });
348 return Err(Yield);
349 }
350
351 let Message { origin, id, fee, commands } =
353 Message::decode(&mut message).map_err(|_| {
354 Self::deposit_event(Event::MessageRejected {
355 id: None,
356 payload: message.to_vec(),
357 error: Corrupt,
358 });
359 Corrupt
360 })?;
361
362 let commands: Vec<OutboundCommandWrapper> = commands
364 .into_iter()
365 .map(|command| OutboundCommandWrapper {
366 kind: command.index(),
367 gas: T::GasMeter::maximum_dispatch_gas_used_at_most(&command),
368 payload: command.abi_encode(),
369 })
370 .collect();
371
372 let nonce = <Nonce<T>>::get().checked_add(1).ok_or_else(|| {
373 Self::deposit_event(Event::MessageRejected {
374 id: None,
375 payload: message.to_vec(),
376 error: Unsupported,
377 });
378 Unsupported
379 })?;
380
381 let outbound_message = OutboundMessage {
382 origin,
383 nonce,
384 topic: id,
385 commands: commands.clone().try_into().map_err(|_| {
386 Self::deposit_event(Event::MessageRejected {
387 id: Some(id),
388 payload: message.to_vec(),
389 error: Corrupt,
390 });
391 Corrupt
392 })?,
393 };
394 Messages::<T>::append(outbound_message);
395
396 let abi_commands: Vec<CommandWrapper> = commands
400 .into_iter()
401 .map(|command| CommandWrapper {
402 kind: command.kind,
403 gas: command.gas,
404 payload: Bytes::from(command.payload),
405 })
406 .collect();
407 let committed_message = OutboundMessageWrapper {
408 origin: FixedBytes::from(origin.as_fixed_bytes()),
409 nonce,
410 topic: FixedBytes::from(id.as_fixed_bytes()),
411 commands: abi_commands,
412 };
413 let message_abi_encoded_hash =
414 <T as Config>::Hashing::hash(&committed_message.abi_encode());
415 MessageLeaves::<T>::append(message_abi_encoded_hash);
416
417 let order = PendingOrder {
423 nonce,
424 fee,
425 block_number: frame_system::Pallet::<T>::current_block_number(),
426 };
427 <PendingOrders<T>>::insert(nonce, order);
428
429 <Nonce<T>>::set(nonce);
430
431 Self::deposit_event(Event::MessageAccepted { id, nonce });
432
433 Ok(true)
434 }
435
436 pub fn process_delivery_receipt(
438 relayer: <T as frame_system::Config>::AccountId,
439 receipt: DeliveryReceipt,
440 ) -> DispatchResult
441 where
442 <T as frame_system::Config>::AccountId: From<[u8; 32]>,
443 {
444 ensure!(T::GatewayAddress::get() == receipt.gateway, Error::<T>::InvalidGateway);
446
447 let reward_account = if receipt.reward_address == [0u8; 32] {
448 relayer
449 } else {
450 receipt.reward_address.into()
451 };
452
453 let nonce = receipt.nonce;
454
455 let order = <PendingOrders<T>>::get(nonce).ok_or(Error::<T>::InvalidPendingNonce)?;
456
457 if order.fee > 0 {
458 T::RewardPayment::register_reward(
460 &reward_account,
461 T::DefaultRewardKind::get(),
462 order.fee,
463 );
464 }
465
466 <PendingOrders<T>>::remove(nonce);
467
468 Self::deposit_event(Event::MessageDelivered { nonce });
469
470 Ok(())
471 }
472 }
473
474 impl<T: Config> AddTip for Pallet<T> {
475 fn add_tip(nonce: u64, amount: u128) -> Result<(), AddTipError> {
476 ensure!(amount > 0, AddTipError::AmountZero);
477 PendingOrders::<T>::try_mutate_exists(nonce, |maybe_order| -> Result<(), AddTipError> {
478 match maybe_order {
479 Some(order) => {
480 order.fee = order.fee.saturating_add(amount);
481 Ok(())
482 },
483 None => Err(AddTipError::UnknownMessage),
484 }
485 })
486 }
487 }
488}