snowbridge_pallet_system_v2/
lib.rs1#![cfg_attr(not(feature = "std"), no_std)]
19#[cfg(test)]
20mod mock;
21
22#[cfg(test)]
23mod tests;
24
25#[cfg(feature = "runtime-benchmarks")]
26mod benchmarking;
27
28pub mod api;
29pub mod weights;
30pub use weights::*;
31
32use frame_support::{pallet_prelude::*, traits::EnsureOrigin};
33use frame_system::pallet_prelude::*;
34pub use pallet::*;
35use snowbridge_core::{
36 reward::{
37 AddTip, MessageId,
38 MessageId::{Inbound, Outbound},
39 },
40 AgentIdOf as LocationHashOf, AssetMetadata, TokenId, TokenIdOf,
41};
42use snowbridge_outbound_queue_primitives::{
43 v2::{Command, Initializer, Message, SendMessage},
44 OperatingMode, SendError,
45};
46use snowbridge_pallet_system::ForeignToNativeId;
47use sp_core::{H160, H256};
48use sp_io::hashing::blake2_256;
49use sp_runtime::traits::MaybeConvert;
50use sp_std::prelude::*;
51use xcm::prelude::*;
52use xcm_executor::traits::ConvertLocation;
53
54#[cfg(feature = "runtime-benchmarks")]
55use frame_support::traits::OriginTrait;
56
57pub const LOG_TARGET: &str = "snowbridge-system-v2";
58
59pub type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
60#[cfg(feature = "runtime-benchmarks")]
61pub trait BenchmarkHelper<O>
62where
63 O: OriginTrait,
64{
65 fn make_xcm_origin(location: Location) -> O;
66}
67
68#[frame_support::pallet]
69pub mod pallet {
70 use super::*;
71
72 #[pallet::pallet]
73 pub struct Pallet<T>(_);
74
75 #[pallet::config]
76 pub trait Config: frame_system::Config + snowbridge_pallet_system::Config {
77 #[allow(deprecated)]
78 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
79 type OutboundQueue: SendMessage + AddTip;
81 type InboundQueue: AddTip;
83 type FrontendOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = Location>;
85 type GovernanceOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = Location>;
87 type WeightInfo: WeightInfo;
88 #[cfg(feature = "runtime-benchmarks")]
89 type Helper: BenchmarkHelper<Self::RuntimeOrigin>;
90 }
91
92 #[pallet::event]
93 #[pallet::generate_deposit(pub(super) fn deposit_event)]
94 pub enum Event<T: Config> {
95 Upgrade { impl_address: H160, impl_code_hash: H256, initializer_params_hash: H256 },
97 SetOperatingMode { mode: OperatingMode },
99 RegisterToken {
101 location: VersionedLocation,
103 foreign_token_id: H256,
105 },
106 TipProcessed {
109 sender: AccountIdOf<T>,
111 message_id: MessageId,
113 amount: u128,
115 success: bool,
118 },
119 }
120
121 #[pallet::error]
122 pub enum Error<T> {
123 LocationReanchorFailed,
125 LocationConversionFailed,
127 UnsupportedLocationVersion,
129 Send(SendError),
131 InvalidUpgradeParameters,
134 }
135
136 #[pallet::storage]
141 pub type LostTips<T: Config> =
142 StorageMap<_, Blake2_128Concat, AccountIdOf<T>, u128, ValueQuery>;
143
144 #[pallet::call]
145 impl<T: Config> Pallet<T> {
146 #[pallet::call_index(0)]
156 #[pallet::weight((<T as pallet::Config>::WeightInfo::upgrade(), DispatchClass::Operational))]
157 pub fn upgrade(
158 origin: OriginFor<T>,
159 impl_address: H160,
160 impl_code_hash: H256,
161 initializer: Initializer,
162 ) -> DispatchResult {
163 let origin_location = T::GovernanceOrigin::ensure_origin(origin)?;
164 let origin = Self::location_to_message_origin(origin_location)?;
165
166 ensure!(
167 !impl_address.eq(&H160::zero()) && !impl_code_hash.eq(&H256::zero()),
168 Error::<T>::InvalidUpgradeParameters
169 );
170
171 let initializer_params_hash: H256 = blake2_256(initializer.params.as_ref()).into();
172
173 let command = Command::Upgrade { impl_address, impl_code_hash, initializer };
174 Self::send(origin, command, 0)?;
175
176 Self::deposit_event(Event::<T>::Upgrade {
177 impl_address,
178 impl_code_hash,
179 initializer_params_hash,
180 });
181 Ok(())
182 }
183
184 #[pallet::call_index(1)]
190 #[pallet::weight((<T as pallet::Config>::WeightInfo::set_operating_mode(), DispatchClass::Operational))]
191 pub fn set_operating_mode(origin: OriginFor<T>, mode: OperatingMode) -> DispatchResult {
192 let origin_location = T::GovernanceOrigin::ensure_origin(origin)?;
193 let origin = Self::location_to_message_origin(origin_location)?;
194
195 let command = Command::SetOperatingMode { mode };
196 Self::send(origin, command, 0)?;
197
198 Self::deposit_event(Event::<T>::SetOperatingMode { mode });
199 Ok(())
200 }
201
202 #[pallet::call_index(2)]
210 #[pallet::weight(<T as pallet::Config>::WeightInfo::register_token())]
211 pub fn register_token(
212 origin: OriginFor<T>,
213 sender: Box<VersionedLocation>,
214 asset_id: Box<VersionedLocation>,
215 metadata: AssetMetadata,
216 amount: u128,
217 ) -> DispatchResult {
218 T::FrontendOrigin::ensure_origin(origin)?;
219
220 let sender_location: Location =
221 (*sender).try_into().map_err(|_| Error::<T>::UnsupportedLocationVersion)?;
222 let asset_location: Location =
223 (*asset_id).try_into().map_err(|_| Error::<T>::UnsupportedLocationVersion)?;
224
225 let location = Self::reanchor(asset_location)?;
226 let token_id = TokenIdOf::convert_location(&location)
227 .ok_or(Error::<T>::LocationConversionFailed)?;
228
229 if !ForeignToNativeId::<T>::contains_key(token_id) {
230 ForeignToNativeId::<T>::insert(token_id, location.clone());
231 }
232
233 let command = Command::RegisterForeignToken {
234 token_id,
235 name: metadata.name.into_inner(),
236 symbol: metadata.symbol.into_inner(),
237 decimals: metadata.decimals,
238 };
239
240 let message_origin = Self::location_to_message_origin(sender_location)?;
241 Self::send(message_origin, command, amount)?;
242
243 Self::deposit_event(Event::<T>::RegisterToken {
244 location: location.into(),
245 foreign_token_id: token_id,
246 });
247
248 Ok(())
249 }
250
251 #[pallet::call_index(3)]
252 #[pallet::weight(<T as pallet::Config>::WeightInfo::add_tip())]
253 pub fn add_tip(
254 origin: OriginFor<T>,
255 sender: AccountIdOf<T>,
256 message_id: MessageId,
257 amount: u128,
258 ) -> DispatchResult {
259 T::FrontendOrigin::ensure_origin(origin)?;
260
261 let result = match message_id {
262 Inbound(nonce) => <T as pallet::Config>::InboundQueue::add_tip(nonce, amount),
263 Outbound(nonce) => <T as pallet::Config>::OutboundQueue::add_tip(nonce, amount),
264 };
265
266 if let Err(ref e) = result {
267 tracing::debug!(target: LOG_TARGET, ?e, ?message_id, ?amount, "error adding tip");
268 LostTips::<T>::mutate(&sender, |lost_tip| {
269 *lost_tip = lost_tip.saturating_add(amount);
270 });
271 }
272
273 Self::deposit_event(Event::<T>::TipProcessed {
274 sender,
275 message_id,
276 amount,
277 success: result.is_ok(),
278 });
279
280 Ok(())
281 }
282 }
283
284 impl<T: Config> Pallet<T> {
285 fn send(origin: H256, command: Command, fee: u128) -> DispatchResult {
287 let message = Message {
288 origin,
289 id: frame_system::unique((origin, &command, fee)).into(),
290 fee,
291 commands: BoundedVec::try_from(vec![command]).unwrap(),
292 };
293
294 let ticket = <T as pallet::Config>::OutboundQueue::validate(&message)
295 .map_err(|err| Error::<T>::Send(err))?;
296
297 <T as pallet::Config>::OutboundQueue::deliver(ticket)
298 .map_err(|err| Error::<T>::Send(err))?;
299 Ok(())
300 }
301
302 pub fn reanchor(location: Location) -> Result<Location, Error<T>> {
304 location
305 .reanchored(&T::EthereumLocation::get(), &T::UniversalLocation::get())
306 .map_err(|_| Error::<T>::LocationReanchorFailed)
307 }
308
309 pub fn location_to_message_origin(location: Location) -> Result<H256, Error<T>> {
310 let reanchored_location = Self::reanchor(location)?;
311 LocationHashOf::convert_location(&reanchored_location)
312 .ok_or(Error::<T>::LocationConversionFailed)
313 }
314 }
315
316 impl<T: Config> MaybeConvert<TokenId, Location> for Pallet<T> {
317 fn maybe_convert(foreign_id: TokenId) -> Option<Location> {
318 snowbridge_pallet_system::Pallet::<T>::maybe_convert(foreign_id)
319 }
320 }
321}