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#[cfg(feature = "runtime-benchmarks")]
55use {snowbridge_beacon_primitives::BeaconHeader, sp_core::H256};
56
57pub use pallet::*;
58
59pub const LOG_TARGET: &str = "snowbridge-pallet-inbound-queue-v2";
60
61pub type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
62
63pub type Nonce<T> = SparseBitmapImpl<crate::NonceBitmap<T>>;
64
65#[frame_support::pallet]
66pub mod pallet {
67 use super::*;
68
69 use frame_support::pallet_prelude::*;
70 use frame_system::pallet_prelude::*;
71
72 #[pallet::pallet]
73 pub struct Pallet<T>(_);
74
75 #[cfg(feature = "runtime-benchmarks")]
76 pub trait BenchmarkHelper<T> {
77 fn initialize_storage(beacon_header: BeaconHeader, block_roots_root: H256);
78 }
79
80 #[pallet::config]
81 pub trait Config: frame_system::Config {
82 #[allow(deprecated)]
83 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
84 type Verifier: Verifier;
86 type XcmSender: SendXcm;
88 type XcmExecutor: ExecuteXcm<Self::RuntimeCall>;
90 #[pallet::constant]
92 type GatewayAddress: Get<H160>;
93 type AssetHubParaId: Get<u32>;
95 type MessageConverter: ConvertMessage;
97 #[cfg(feature = "runtime-benchmarks")]
98 type Helper: BenchmarkHelper<Self>;
99 type RewardKind: Parameter + MaxEncodedLen + Send + Sync + Copy + Clone;
101 #[pallet::constant]
103 type DefaultRewardKind: Get<Self::RewardKind>;
104 type RewardPayment: RewardLedger<Self::AccountId, Self::RewardKind, u128>;
106 type AccountToLocation: for<'a> TryConvert<&'a Self::AccountId, Location>;
108 type WeightInfo: WeightInfo;
109 }
110
111 #[pallet::event]
112 #[pallet::generate_deposit(pub(super) fn deposit_event)]
113 pub enum Event<T: Config> {
114 MessageReceived {
116 nonce: u64,
118 message_id: [u8; 32],
120 },
121 OperatingModeChanged { mode: BasicOperatingMode },
123 }
124
125 #[pallet::error]
126 pub enum Error<T> {
127 InvalidGateway,
129 InvalidAccount,
131 InvalidMessage,
133 InvalidNonce,
135 InvalidFee,
137 InvalidPayload,
139 InvalidChannel,
141 MaxNonceReached,
143 InvalidAccountConversion,
145 InvalidNetwork,
147 Halted,
149 FeesNotMet,
151 Unreachable,
154 SendFailure,
157 InvalidAsset,
159 CannotReanchor,
161 Verification(VerificationError),
163 }
164
165 impl<T: Config> From<SendError> for Error<T> {
166 fn from(e: SendError) -> Self {
167 match e {
168 SendError::Fees => Error::<T>::FeesNotMet,
169 SendError::NotApplicable => Error::<T>::Unreachable,
170 _ => Error::<T>::SendFailure,
171 }
172 }
173 }
174
175 impl<T: Config> From<ConvertMessageError> for Error<T> {
176 fn from(e: ConvertMessageError) -> Self {
177 match e {
178 ConvertMessageError::InvalidAsset => Error::<T>::InvalidAsset,
179 ConvertMessageError::CannotReanchor => Error::<T>::CannotReanchor,
180 ConvertMessageError::InvalidNetwork => Error::<T>::InvalidNetwork,
181 }
182 }
183 }
184
185 #[pallet::storage]
188 pub type NonceBitmap<T: Config> = StorageMap<_, Twox64Concat, u64, u128, ValueQuery>;
189
190 #[pallet::storage]
192 pub type OperatingMode<T: Config> = StorageValue<_, BasicOperatingMode, ValueQuery>;
193
194 #[pallet::storage]
198 pub type Tips<T: Config> = StorageMap<_, Blake2_128Concat, u64, u128, OptionQuery>;
199
200 #[pallet::call]
201 impl<T: Config> Pallet<T> {
202 #[pallet::call_index(0)]
204 #[pallet::weight(T::WeightInfo::submit())]
205 pub fn submit(origin: OriginFor<T>, event: Box<EventProof>) -> DispatchResult {
206 let who = ensure_signed(origin)?;
207 ensure!(!OperatingMode::<T>::get().is_halted(), Error::<T>::Halted);
208
209 T::Verifier::verify(&event.event_log, &event.proof)
211 .map_err(|e| Error::<T>::Verification(e))?;
212
213 let message =
215 Message::try_from(&event.event_log).map_err(|_| Error::<T>::InvalidMessage)?;
216
217 Self::process_message(who, message)
218 }
219
220 #[pallet::call_index(1)]
222 #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))]
223 pub fn set_operating_mode(
224 origin: OriginFor<T>,
225 mode: BasicOperatingMode,
226 ) -> DispatchResult {
227 ensure_root(origin)?;
228 OperatingMode::<T>::set(mode);
229 Self::deposit_event(Event::OperatingModeChanged { mode });
230 Ok(())
231 }
232 }
233
234 impl<T: Config> Pallet<T> {
235 pub fn process_message(relayer: T::AccountId, message: Message) -> DispatchResult {
236 ensure!(T::GatewayAddress::get() == message.gateway, Error::<T>::InvalidGateway);
238
239 let (nonce, relayer_fee) = (message.nonce, message.relayer_fee);
240
241 ensure!(!Nonce::<T>::get(nonce), Error::<T>::InvalidNonce);
243
244 let xcm =
245 T::MessageConverter::convert(message).map_err(|error| Error::<T>::from(error))?;
246
247 let dest = Location::new(1, [Parachain(T::AssetHubParaId::get())]);
249
250 Nonce::<T>::set(nonce);
252
253 let message_id =
254 Self::send_xcm(dest.clone(), &relayer, xcm.clone()).map_err(|error| {
255 tracing::error!(target: LOG_TARGET, ?error, ?dest, ?xcm, "XCM send failed with error");
256 Error::<T>::from(error)
257 })?;
258
259 if !relayer_fee.is_zero() {
261 T::RewardPayment::register_reward(
262 &relayer,
263 T::DefaultRewardKind::get(),
264 relayer_fee,
265 );
266 }
267
268 Self::deposit_event(Event::MessageReceived { nonce, message_id });
269
270 Ok(())
271 }
272
273 fn send_xcm(
274 dest: Location,
275 fee_payer: &T::AccountId,
276 xcm: Xcm<()>,
277 ) -> Result<XcmHash, SendError> {
278 let (ticket, fee) = validate_send::<T::XcmSender>(dest, xcm)?;
279 let fee_payer = T::AccountToLocation::try_convert(fee_payer).map_err(|err| {
280 tracing::error!(
281 target: LOG_TARGET,
282 ?err,
283 "Failed to convert account to XCM location",
284 );
285 SendError::NotApplicable
286 })?;
287 T::XcmExecutor::charge_fees(fee_payer.clone(), fee.clone()).map_err(|error| {
288 tracing::error!(
289 target: LOG_TARGET,
290 ?error,
291 "Charging fees failed with error",
292 );
293 SendError::Fees
294 })?;
295 T::XcmSender::deliver(ticket)
296 }
297 }
298
299 impl<T: Config> AddTip for Pallet<T> {
300 fn add_tip(nonce: u64, amount: u128) -> Result<(), AddTipError> {
301 ensure!(amount > 0, AddTipError::AmountZero);
302 ensure!(!Nonce::<T>::get(nonce.into()), AddTipError::NonceConsumed);
304 Tips::<T>::mutate(nonce, |tip| {
306 *tip = Some(tip.unwrap_or_default().saturating_add(amount));
307 });
308 return Ok(())
309 }
310 }
311}