pezsnowbridge_pezpallet_inbound_queue_v2/
lib.rs1#![cfg_attr(not(feature = "std"), no_std)]
25
26extern crate alloc;
27
28#[cfg(feature = "runtime-benchmarks")]
29mod benchmarking;
30pub mod weights;
31
32#[cfg(test)]
33mod mock;
34
35#[cfg(test)]
36mod test;
37
38pub use crate::weights::WeightInfo;
39use pezbp_relayers::RewardLedger;
40use pezframe_system::ensure_signed;
41use pezsnowbridge_core::{
42 reward::{AddTip, AddTipError},
43 sparse_bitmap::{SparseBitmap, SparseBitmapImpl},
44 BasicOperatingMode,
45};
46use pezsnowbridge_inbound_queue_primitives::{
47 v2::{ConvertMessage, ConvertMessageError, Message},
48 EventProof, VerificationError, Verifier,
49};
50use pezsp_core::H160;
51use pezsp_runtime::traits::TryConvert;
52use pezsp_std::prelude::*;
53use xcm::prelude::{ExecuteXcm, Junction::*, Location, SendXcm, *};
54
55pub use pezpallet::*;
56
57pub const LOG_TARGET: &str = "pezsnowbridge-pezpallet-inbound-queue-v2";
58
59pub type AccountIdOf<T> = <T as pezframe_system::Config>::AccountId;
60
61pub type Nonce<T> = SparseBitmapImpl<crate::NonceBitmap<T>>;
62
63#[pezframe_support::pezpallet]
64pub mod pezpallet {
65 use super::*;
66
67 use pezframe_support::pezpallet_prelude::*;
68 use pezframe_system::pezpallet_prelude::*;
69
70 #[cfg(feature = "runtime-benchmarks")]
71 use pezsnowbridge_inbound_queue_primitives::EventFixture;
72
73 #[pezpallet::pezpallet]
74 pub struct Pezpallet<T>(_);
75
76 #[cfg(feature = "runtime-benchmarks")]
77 pub trait BenchmarkHelper<T> {
78 fn initialize_storage() -> EventFixture;
79 }
80
81 #[pezpallet::config]
82 pub trait Config: pezframe_system::Config {
83 #[allow(deprecated)]
84 type RuntimeEvent: From<Event<Self>>
85 + IsType<<Self as pezframe_system::Config>::RuntimeEvent>;
86 type Verifier: Verifier;
88 type XcmSender: SendXcm;
90 type XcmExecutor: ExecuteXcm<Self::RuntimeCall>;
92 #[pezpallet::constant]
94 type GatewayAddress: Get<H160>;
95 type AssetHubParaId: Get<u32>;
97 type MessageConverter: ConvertMessage;
99 #[cfg(feature = "runtime-benchmarks")]
100 type Helper: BenchmarkHelper<Self>;
101 type RewardKind: Parameter + MaxEncodedLen + Send + Sync + Copy + Clone;
103 #[pezpallet::constant]
106 type DefaultRewardKind: Get<Self::RewardKind>;
107 type RewardPayment: RewardLedger<Self::AccountId, Self::RewardKind, u128>;
109 type AccountToLocation: for<'a> TryConvert<&'a Self::AccountId, Location>;
111 type WeightInfo: WeightInfo;
112 }
113
114 #[pezpallet::event]
115 #[pezpallet::generate_deposit(pub(super) fn deposit_event)]
116 pub enum Event<T: Config> {
117 MessageReceived {
119 nonce: u64,
121 message_id: [u8; 32],
123 },
124 OperatingModeChanged { mode: BasicOperatingMode },
126 }
127
128 #[pezpallet::error]
129 pub enum Error<T> {
130 InvalidGateway,
132 InvalidAccount,
134 InvalidMessage,
136 InvalidNonce,
138 InvalidFee,
140 InvalidPayload,
142 InvalidChannel,
144 MaxNonceReached,
146 InvalidAccountConversion,
148 InvalidNetwork,
150 Halted,
152 FeesNotMet,
154 Unreachable,
157 SendFailure,
160 InvalidAsset,
162 CannotReanchor,
164 Verification(VerificationError),
166 }
167
168 impl<T: Config> From<SendError> for Error<T> {
169 fn from(e: SendError) -> Self {
170 match e {
171 SendError::Fees => Error::<T>::FeesNotMet,
172 SendError::NotApplicable => Error::<T>::Unreachable,
173 _ => Error::<T>::SendFailure,
174 }
175 }
176 }
177
178 impl<T: Config> From<ConvertMessageError> for Error<T> {
179 fn from(e: ConvertMessageError) -> Self {
180 match e {
181 ConvertMessageError::InvalidAsset => Error::<T>::InvalidAsset,
182 ConvertMessageError::CannotReanchor => Error::<T>::CannotReanchor,
183 ConvertMessageError::InvalidNetwork => Error::<T>::InvalidNetwork,
184 }
185 }
186 }
187
188 #[pezpallet::storage]
191 pub type NonceBitmap<T: Config> = StorageMap<_, Twox64Concat, u64, u128, ValueQuery>;
192
193 #[pezpallet::storage]
195 pub type OperatingMode<T: Config> = StorageValue<_, BasicOperatingMode, ValueQuery>;
196
197 #[pezpallet::storage]
201 pub type Tips<T: Config> = StorageMap<_, Blake2_128Concat, u64, u128, OptionQuery>;
202
203 #[pezpallet::call]
204 impl<T: Config> Pezpallet<T> {
205 #[pezpallet::call_index(0)]
207 #[pezpallet::weight(T::WeightInfo::submit())]
208 pub fn submit(origin: OriginFor<T>, event: Box<EventProof>) -> DispatchResult {
209 let who = ensure_signed(origin)?;
210 ensure!(!OperatingMode::<T>::get().is_halted(), Error::<T>::Halted);
211
212 T::Verifier::verify(&event.event_log, &event.proof)
214 .map_err(|e| Error::<T>::Verification(e))?;
215
216 let message =
218 Message::try_from(&event.event_log).map_err(|_| Error::<T>::InvalidMessage)?;
219
220 Self::process_message(who, message)
221 }
222
223 #[pezpallet::call_index(1)]
225 #[pezpallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))]
226 pub fn set_operating_mode(
227 origin: OriginFor<T>,
228 mode: BasicOperatingMode,
229 ) -> DispatchResult {
230 ensure_root(origin)?;
231 OperatingMode::<T>::set(mode);
232 Self::deposit_event(Event::OperatingModeChanged { mode });
233 Ok(())
234 }
235 }
236
237 impl<T: Config> Pezpallet<T> {
238 pub fn process_message(relayer: T::AccountId, message: Message) -> DispatchResult {
239 ensure!(T::GatewayAddress::get() == message.gateway, Error::<T>::InvalidGateway);
241
242 let (nonce, relayer_fee) = (message.nonce, message.relayer_fee);
243
244 ensure!(!Nonce::<T>::get(nonce), Error::<T>::InvalidNonce);
246
247 let xcm =
248 T::MessageConverter::convert(message).map_err(|error| Error::<T>::from(error))?;
249
250 let dest = Location::new(1, [Teyrchain(T::AssetHubParaId::get())]);
252
253 Nonce::<T>::set(nonce);
255
256 let message_id =
257 Self::send_xcm(dest.clone(), &relayer, xcm.clone()).map_err(|error| {
258 tracing::error!(target: LOG_TARGET, ?error, ?dest, ?xcm, "XCM send failed with error");
259 Error::<T>::from(error)
260 })?;
261
262 let tip = Tips::<T>::take(nonce).unwrap_or_default();
264 let total_tip = relayer_fee.saturating_add(tip);
265 if total_tip > 0 {
266 T::RewardPayment::register_reward(&relayer, T::DefaultRewardKind::get(), total_tip);
267 }
268
269 Self::deposit_event(Event::MessageReceived { nonce, message_id });
270
271 Ok(())
272 }
273
274 fn send_xcm(
275 dest: Location,
276 fee_payer: &T::AccountId,
277 xcm: Xcm<()>,
278 ) -> Result<XcmHash, SendError> {
279 let (ticket, fee) = validate_send::<T::XcmSender>(dest, xcm)?;
280 let fee_payer = T::AccountToLocation::try_convert(fee_payer).map_err(|err| {
281 tracing::error!(
282 target: LOG_TARGET,
283 ?err,
284 "Failed to convert account to XCM location",
285 );
286 SendError::NotApplicable
287 })?;
288 T::XcmExecutor::charge_fees(fee_payer.clone(), fee.clone()).map_err(|error| {
289 tracing::error!(
290 target: LOG_TARGET,
291 ?error,
292 "Charging fees failed with error",
293 );
294 SendError::Fees
295 })?;
296 T::XcmSender::deliver(ticket)
297 }
298 }
299
300 impl<T: Config> AddTip for Pezpallet<T> {
301 fn add_tip(nonce: u64, amount: u128) -> Result<(), AddTipError> {
302 ensure!(amount > 0, AddTipError::AmountZero);
303 ensure!(!Nonce::<T>::get(nonce.into()), AddTipError::NonceConsumed);
305 Tips::<T>::mutate(nonce, |tip| {
307 *tip = Some(tip.unwrap_or_default().saturating_add(amount));
308 });
309 return Ok(());
310 }
311 }
312}