snowbridge_pallet_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 bp_relayers::RewardLedger;
40use frame_system::ensure_signed;
41use snowbridge_core::{
42 reward::{AddTip, AddTipError},
43 sparse_bitmap::{SparseBitmap, SparseBitmapImpl},
44 BasicOperatingMode,
45};
46use snowbridge_inbound_queue_primitives::{
47 v2::{ConvertMessage, ConvertMessageError, Message},
48 EventProof, VerificationError, Verifier,
49};
50use sp_core::H160;
51use sp_runtime::traits::TryConvert;
52use sp_std::prelude::*;
53use xcm::prelude::{ExecuteXcm, Junction::*, Location, SendXcm, *};
54
55pub use pallet::*;
56
57pub const LOG_TARGET: &str = "snowbridge-pallet-inbound-queue-v2";
58
59pub type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
60
61pub type Nonce<T> = SparseBitmapImpl<crate::NonceBitmap<T>>;
62
63#[frame_support::pallet]
64pub mod pallet {
65 use super::*;
66
67 use frame_support::pallet_prelude::*;
68 use frame_system::pallet_prelude::*;
69
70 #[cfg(feature = "runtime-benchmarks")]
71 use snowbridge_inbound_queue_primitives::EventFixture;
72
73 #[pallet::pallet]
74 pub struct Pallet<T>(_);
75
76 #[cfg(feature = "runtime-benchmarks")]
77 pub trait BenchmarkHelper<T> {
78 fn initialize_storage() -> EventFixture;
79 }
80
81 #[pallet::config]
82 pub trait Config: frame_system::Config {
83 #[allow(deprecated)]
84 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
85 type Verifier: Verifier;
87 type XcmSender: SendXcm;
89 type XcmExecutor: ExecuteXcm<Self::RuntimeCall>;
91 #[pallet::constant]
93 type GatewayAddress: Get<H160>;
94 type AssetHubParaId: Get<u32>;
96 type MessageConverter: ConvertMessage;
98 #[cfg(feature = "runtime-benchmarks")]
99 type Helper: BenchmarkHelper<Self>;
100 type RewardKind: Parameter + MaxEncodedLen + Send + Sync + Copy + Clone;
102 #[pallet::constant]
104 type DefaultRewardKind: Get<Self::RewardKind>;
105 type RewardPayment: RewardLedger<Self::AccountId, Self::RewardKind, u128>;
107 type AccountToLocation: for<'a> TryConvert<&'a Self::AccountId, Location>;
109 type WeightInfo: WeightInfo;
110 }
111
112 #[pallet::event]
113 #[pallet::generate_deposit(pub(super) fn deposit_event)]
114 pub enum Event<T: Config> {
115 MessageReceived {
117 nonce: u64,
119 message_id: [u8; 32],
121 },
122 OperatingModeChanged { mode: BasicOperatingMode },
124 }
125
126 #[pallet::error]
127 pub enum Error<T> {
128 InvalidGateway,
130 InvalidAccount,
132 InvalidMessage,
134 InvalidNonce,
136 InvalidFee,
138 InvalidPayload,
140 InvalidChannel,
142 MaxNonceReached,
144 InvalidAccountConversion,
146 InvalidNetwork,
148 Halted,
150 FeesNotMet,
152 Unreachable,
155 SendFailure,
158 InvalidAsset,
160 CannotReanchor,
162 Verification(VerificationError),
164 }
165
166 impl<T: Config> From<SendError> for Error<T> {
167 fn from(e: SendError) -> Self {
168 match e {
169 SendError::Fees => Error::<T>::FeesNotMet,
170 SendError::NotApplicable => Error::<T>::Unreachable,
171 _ => Error::<T>::SendFailure,
172 }
173 }
174 }
175
176 impl<T: Config> From<ConvertMessageError> for Error<T> {
177 fn from(e: ConvertMessageError) -> Self {
178 match e {
179 ConvertMessageError::InvalidAsset => Error::<T>::InvalidAsset,
180 ConvertMessageError::CannotReanchor => Error::<T>::CannotReanchor,
181 ConvertMessageError::InvalidNetwork => Error::<T>::InvalidNetwork,
182 }
183 }
184 }
185
186 #[pallet::storage]
189 pub type NonceBitmap<T: Config> = StorageMap<_, Twox64Concat, u64, u128, ValueQuery>;
190
191 #[pallet::storage]
193 pub type OperatingMode<T: Config> = StorageValue<_, BasicOperatingMode, ValueQuery>;
194
195 #[pallet::storage]
199 pub type Tips<T: Config> = StorageMap<_, Blake2_128Concat, u64, u128, OptionQuery>;
200
201 #[pallet::call]
202 impl<T: Config> Pallet<T> {
203 #[pallet::call_index(0)]
205 #[pallet::weight(T::WeightInfo::submit())]
206 pub fn submit(origin: OriginFor<T>, event: Box<EventProof>) -> DispatchResult {
207 let who = ensure_signed(origin)?;
208 ensure!(!OperatingMode::<T>::get().is_halted(), Error::<T>::Halted);
209
210 T::Verifier::verify(&event.event_log, &event.proof)
212 .map_err(|e| Error::<T>::Verification(e))?;
213
214 let message =
216 Message::try_from(&event.event_log).map_err(|_| Error::<T>::InvalidMessage)?;
217
218 Self::process_message(who, message)
219 }
220
221 #[pallet::call_index(1)]
223 #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))]
224 pub fn set_operating_mode(
225 origin: OriginFor<T>,
226 mode: BasicOperatingMode,
227 ) -> DispatchResult {
228 ensure_root(origin)?;
229 OperatingMode::<T>::set(mode);
230 Self::deposit_event(Event::OperatingModeChanged { mode });
231 Ok(())
232 }
233 }
234
235 impl<T: Config> Pallet<T> {
236 pub fn process_message(relayer: T::AccountId, message: Message) -> DispatchResult {
237 ensure!(T::GatewayAddress::get() == message.gateway, Error::<T>::InvalidGateway);
239
240 let (nonce, relayer_fee) = (message.nonce, message.relayer_fee);
241
242 ensure!(!Nonce::<T>::get(nonce), Error::<T>::InvalidNonce);
244
245 let xcm =
246 T::MessageConverter::convert(message).map_err(|error| Error::<T>::from(error))?;
247
248 let dest = Location::new(1, [Parachain(T::AssetHubParaId::get())]);
250
251 Nonce::<T>::set(nonce);
253
254 let message_id =
255 Self::send_xcm(dest.clone(), &relayer, xcm.clone()).map_err(|error| {
256 tracing::error!(target: LOG_TARGET, ?error, ?dest, ?xcm, "XCM send failed with error");
257 Error::<T>::from(error)
258 })?;
259
260 if !relayer_fee.is_zero() {
262 T::RewardPayment::register_reward(
263 &relayer,
264 T::DefaultRewardKind::get(),
265 relayer_fee,
266 );
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 Pallet<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}