pezsnowbridge_pezpallet_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 pezbridge_hub_common::AggregateMessageOrigin;
107use codec::Decode;
108use pezframe_support::{
109 storage::StorageStreamIter,
110 traits::{tokens::Balance, Contains, Defensive, EnqueueMessage, Get, ProcessMessageError},
111 weights::{Weight, WeightToFee},
112};
113use pezsnowbridge_core::{digest_item::SnowbridgeDigestItem, BasicOperatingMode, ChannelId};
114use pezsnowbridge_merkle_tree::merkle_root;
115use pezsnowbridge_outbound_queue_primitives::v1::{
116 Fee, GasMeter, QueuedMessage, VersionedQueuedMessage, ETHER_DECIMALS,
117};
118use pezsp_core::{H256, U256};
119use pezsp_runtime::{
120 traits::{CheckedDiv, Hash},
121 DigestItem, Saturating,
122};
123use pezsp_std::prelude::*;
124pub use types::{CommittedMessage, ProcessMessageOriginOf};
125pub use weights::WeightInfo;
126
127pub use pezpallet::*;
128
129#[pezframe_support::pezpallet]
130pub mod pezpallet {
131 use super::*;
132 use pezframe_support::pezpallet_prelude::*;
133 use pezframe_system::pezpallet_prelude::*;
134 use pezsnowbridge_core::PricingParameters;
135 use pezsp_arithmetic::FixedU128;
136
137 #[pezpallet::pezpallet]
138 pub struct Pezpallet<T>(_);
139
140 #[pezpallet::config]
141 pub trait Config: pezframe_system::Config {
142 #[allow(deprecated)]
143 type RuntimeEvent: From<Event<Self>>
144 + IsType<<Self as pezframe_system::Config>::RuntimeEvent>;
145
146 type Hashing: Hash<Output = H256>;
147
148 type MessageQueue: EnqueueMessage<AggregateMessageOrigin>;
149
150 type GasMeter: GasMeter;
152
153 type Balance: Balance + From<u128>;
154
155 #[pezpallet::constant]
157 type Decimals: Get<u8>;
158
159 #[pezpallet::constant]
161 type MaxMessagePayloadSize: Get<u32>;
162
163 #[pezpallet::constant]
165 type MaxMessagesPerBlock: Get<u32>;
166
167 type Channels: Contains<ChannelId>;
169
170 type PricingParameters: Get<PricingParameters<Self::Balance>>;
171
172 type WeightToFee: WeightToFee<Balance = Self::Balance>;
174
175 type WeightInfo: WeightInfo;
177 }
178
179 #[pezpallet::event]
180 #[pezpallet::generate_deposit(pub(super) fn deposit_event)]
181 pub enum Event<T: Config> {
182 MessageQueued {
184 id: H256,
186 },
187 MessageAccepted {
190 id: H256,
192 nonce: u64,
194 },
195 MessagesCommitted {
197 root: H256,
199 count: u64,
201 },
202 OperatingModeChanged { mode: BasicOperatingMode },
204 }
205
206 #[pezpallet::error]
207 pub enum Error<T> {
208 MessageTooLarge,
210 Halted,
212 InvalidChannel,
214 }
215
216 #[pezpallet::storage]
223 #[pezpallet::unbounded]
224 pub(super) type Messages<T: Config> = StorageValue<_, Vec<CommittedMessage>, ValueQuery>;
225
226 #[pezpallet::storage]
230 #[pezpallet::unbounded]
231 #[pezpallet::getter(fn message_leaves)]
232 pub(super) type MessageLeaves<T: Config> = StorageValue<_, Vec<H256>, ValueQuery>;
233
234 #[pezpallet::storage]
236 pub type Nonce<T: Config> = StorageMap<_, Twox64Concat, ChannelId, u64, ValueQuery>;
237
238 #[pezpallet::storage]
240 #[pezpallet::getter(fn operating_mode)]
241 pub type OperatingMode<T: Config> = StorageValue<_, BasicOperatingMode, ValueQuery>;
242
243 #[pezpallet::hooks]
244 impl<T: Config> Hooks<BlockNumberFor<T>> for Pezpallet<T>
245 where
246 T::AccountId: AsRef<[u8]>,
247 {
248 fn on_initialize(_: BlockNumberFor<T>) -> Weight {
249 Messages::<T>::kill();
251 MessageLeaves::<T>::kill();
252 T::WeightInfo::commit()
254 }
255
256 fn on_finalize(_: BlockNumberFor<T>) {
257 Self::commit();
258 }
259
260 fn integrity_test() {
261 let decimals = T::Decimals::get();
262 assert!(decimals == 10 || decimals == 12, "Decimals should be 10 or 12");
263 }
264 }
265
266 #[pezpallet::call]
267 impl<T: Config> Pezpallet<T> {
268 #[pezpallet::call_index(0)]
270 #[pezpallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))]
271 pub fn set_operating_mode(
272 origin: OriginFor<T>,
273 mode: BasicOperatingMode,
274 ) -> DispatchResult {
275 ensure_root(origin)?;
276 OperatingMode::<T>::put(mode);
277 Self::deposit_event(Event::OperatingModeChanged { mode });
278 Ok(())
279 }
280 }
281
282 impl<T: Config> Pezpallet<T> {
283 pub(crate) fn commit() {
285 let count = MessageLeaves::<T>::decode_len().unwrap_or_default() as u64;
286 if count == 0 {
287 return;
288 }
289
290 let root = merkle_root::<<T as Config>::Hashing, _>(MessageLeaves::<T>::stream_iter());
292
293 let digest_item: DigestItem = SnowbridgeDigestItem::Snowbridge(root).into();
294
295 <pezframe_system::Pezpallet<T>>::deposit_log(digest_item);
297
298 Self::deposit_event(Event::MessagesCommitted { root, count });
299 }
300
301 pub(crate) fn do_process_message(
303 _: ProcessMessageOriginOf<T>,
304 mut message: &[u8],
305 ) -> Result<bool, ProcessMessageError> {
306 use ProcessMessageError::*;
307
308 ensure!(
311 MessageLeaves::<T>::decode_len().unwrap_or(0)
312 < T::MaxMessagesPerBlock::get() as usize,
313 Yield
314 );
315
316 let versioned_queued_message: VersionedQueuedMessage =
318 VersionedQueuedMessage::decode(&mut message).map_err(|_| Corrupt)?;
319
320 let queued_message: QueuedMessage =
322 versioned_queued_message.try_into().map_err(|_| Unsupported)?;
323
324 let nonce = <Nonce<T>>::try_mutate(
326 queued_message.channel_id,
327 |nonce| -> Result<u64, ProcessMessageError> {
328 *nonce = nonce.checked_add(1).ok_or(Unsupported)?;
329 Ok(*nonce)
330 },
331 )?;
332
333 let pricing_params = T::PricingParameters::get();
334 let command = queued_message.command.index();
335 let params = queued_message.command.abi_encode();
336 let max_dispatch_gas =
337 T::GasMeter::maximum_dispatch_gas_used_at_most(&queued_message.command);
338 let reward = pricing_params.rewards.remote;
339
340 let message = CommittedMessage {
342 channel_id: queued_message.channel_id,
343 nonce,
344 command,
345 params,
346 max_dispatch_gas,
347 max_fee_per_gas: pricing_params
348 .fee_per_gas
349 .try_into()
350 .defensive_unwrap_or(u128::MAX),
351 reward: reward.try_into().defensive_unwrap_or(u128::MAX),
352 id: queued_message.id,
353 };
354
355 let message_abi_encoded = ethabi::encode(&[message.clone().into()]);
357 let message_abi_encoded_hash = <T as Config>::Hashing::hash(&message_abi_encoded);
358
359 Messages::<T>::append(Box::new(message));
360 MessageLeaves::<T>::append(message_abi_encoded_hash);
361
362 Self::deposit_event(Event::MessageAccepted { id: queued_message.id, nonce });
363
364 Ok(true)
365 }
366
367 pub(crate) fn calculate_fee(
370 gas_used_at_most: u64,
371 params: PricingParameters<T::Balance>,
372 ) -> Fee<T::Balance> {
373 let fee = Self::calculate_remote_fee(
375 gas_used_at_most,
376 params.fee_per_gas,
377 params.rewards.remote,
378 );
379
380 let fee: u128 = fee.try_into().defensive_unwrap_or(u128::MAX);
382
383 let fee = FixedU128::from_inner(fee)
385 .saturating_mul(params.multiplier)
386 .checked_div(¶ms.exchange_rate)
387 .expect("exchange rate is not zero; qed")
388 .into_inner();
389
390 let fee = Self::convert_from_ether_decimals(fee);
392
393 Fee::from((Self::calculate_local_fee(), fee))
394 }
395
396 pub(crate) fn calculate_remote_fee(
398 gas_used_at_most: u64,
399 fee_per_gas: U256,
400 reward: U256,
401 ) -> U256 {
402 fee_per_gas.saturating_mul(gas_used_at_most.into()).saturating_add(reward)
403 }
404
405 pub(crate) fn calculate_local_fee() -> T::Balance {
407 T::WeightToFee::weight_to_fee(
408 &T::WeightInfo::do_process_message().saturating_add(T::WeightInfo::commit_single()),
409 )
410 }
411
412 pub(crate) fn convert_from_ether_decimals(value: u128) -> T::Balance {
416 let decimals = ETHER_DECIMALS.saturating_sub(T::Decimals::get()) as u32;
417 let denom = 10u128.saturating_pow(decimals);
418 value.checked_div(denom).expect("divisor is non-zero; qed").into()
419 }
420 }
421}