1#![cfg_attr(not(feature = "std"), no_std)]
21#[cfg(test)]
22mod mock;
23
24#[cfg(test)]
25mod tests;
26
27#[cfg(feature = "runtime-benchmarks")]
28mod benchmarking;
29pub mod migration;
30
31pub mod api;
32pub mod weights;
33pub use weights::*;
34
35use frame_support::{
36 pallet_prelude::*,
37 traits::{
38 fungible::{Inspect, Mutate},
39 tokens::Preservation,
40 Contains, EnsureOrigin,
41 },
42};
43use frame_system::pallet_prelude::*;
44use snowbridge_core::{
45 meth, AgentId, AssetMetadata, Channel, ChannelId, ParaId,
46 PricingParameters as PricingParametersRecord, TokenId, TokenIdOf, PRIMARY_GOVERNANCE_CHANNEL,
47 SECONDARY_GOVERNANCE_CHANNEL,
48};
49use snowbridge_outbound_queue_primitives::{
50 v1::{Command, Initializer, Message, SendMessage},
51 OperatingMode, SendError,
52};
53use sp_core::{RuntimeDebug, H160, H256};
54use sp_io::hashing::blake2_256;
55use sp_runtime::{traits::MaybeEquivalence, DispatchError, SaturatedConversion};
56use sp_std::prelude::*;
57use xcm::prelude::*;
58use xcm_executor::traits::ConvertLocation;
59
60#[cfg(feature = "runtime-benchmarks")]
61use frame_support::traits::OriginTrait;
62
63pub use pallet::*;
64
65pub type BalanceOf<T> =
66 <<T as pallet::Config>::Token as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
67pub type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
68pub type PricingParametersOf<T> = PricingParametersRecord<BalanceOf<T>>;
69
70pub fn agent_id_of<T: Config>(location: &Location) -> Result<H256, DispatchError> {
72 T::AgentIdOf::convert_location(location).ok_or(Error::<T>::LocationConversionFailed.into())
73}
74
75#[cfg(feature = "runtime-benchmarks")]
76pub trait BenchmarkHelper<O>
77where
78 O: OriginTrait,
79{
80 fn make_xcm_origin(location: Location) -> O;
81}
82
83#[derive(Clone, PartialEq, RuntimeDebug)]
85pub enum PaysFee<T>
86where
87 T: Config,
88{
89 Yes(AccountIdOf<T>),
91 Partial(AccountIdOf<T>),
93 No,
95}
96
97#[frame_support::pallet]
98pub mod pallet {
99 use frame_support::dispatch::PostDispatchInfo;
100 use snowbridge_core::StaticLookup;
101 use sp_core::U256;
102
103 use super::*;
104
105 #[pallet::pallet]
106 pub struct Pallet<T>(_);
107
108 #[pallet::config]
109 pub trait Config: frame_system::Config {
110 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
111
112 type OutboundQueue: SendMessage<Balance = BalanceOf<Self>>;
114
115 type SiblingOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = Location>;
117
118 type AgentIdOf: ConvertLocation<AgentId>;
120
121 type Token: Mutate<Self::AccountId>;
123
124 #[pallet::constant]
126 type TreasuryAccount: Get<Self::AccountId>;
127
128 type DefaultPricingParameters: Get<PricingParametersOf<Self>>;
130
131 #[pallet::constant]
133 type InboundDeliveryCost: Get<BalanceOf<Self>>;
134
135 type WeightInfo: WeightInfo;
136
137 type UniversalLocation: Get<InteriorLocation>;
139
140 type EthereumLocation: Get<Location>;
142
143 #[cfg(feature = "runtime-benchmarks")]
144 type Helper: BenchmarkHelper<Self::RuntimeOrigin>;
145 }
146
147 #[pallet::event]
148 #[pallet::generate_deposit(pub(super) fn deposit_event)]
149 pub enum Event<T: Config> {
150 Upgrade {
152 impl_address: H160,
153 impl_code_hash: H256,
154 initializer_params_hash: Option<H256>,
155 },
156 CreateAgent {
158 location: Box<Location>,
159 agent_id: AgentId,
160 },
161 CreateChannel {
163 channel_id: ChannelId,
164 agent_id: AgentId,
165 },
166 UpdateChannel {
168 channel_id: ChannelId,
169 mode: OperatingMode,
170 },
171 SetOperatingMode {
173 mode: OperatingMode,
174 },
175 TransferNativeFromAgent {
177 agent_id: AgentId,
178 recipient: H160,
179 amount: u128,
180 },
181 SetTokenTransferFees {
183 create_asset_xcm: u128,
184 transfer_asset_xcm: u128,
185 register_token: U256,
186 },
187 PricingParametersChanged {
188 params: PricingParametersOf<T>,
189 },
190 RegisterToken {
192 location: VersionedLocation,
194 foreign_token_id: H256,
196 },
197 }
198
199 #[pallet::error]
200 pub enum Error<T> {
201 LocationConversionFailed,
202 AgentAlreadyCreated,
203 NoAgent,
204 ChannelAlreadyCreated,
205 NoChannel,
206 UnsupportedLocationVersion,
207 InvalidLocation,
208 Send(SendError),
209 InvalidTokenTransferFees,
210 InvalidPricingParameters,
211 InvalidUpgradeParameters,
212 }
213
214 #[pallet::storage]
216 #[pallet::getter(fn agents)]
217 pub type Agents<T: Config> = StorageMap<_, Twox64Concat, AgentId, (), OptionQuery>;
218
219 #[pallet::storage]
221 #[pallet::getter(fn channels)]
222 pub type Channels<T: Config> = StorageMap<_, Twox64Concat, ChannelId, Channel, OptionQuery>;
223
224 #[pallet::storage]
225 #[pallet::getter(fn parameters)]
226 pub type PricingParameters<T: Config> =
227 StorageValue<_, PricingParametersOf<T>, ValueQuery, T::DefaultPricingParameters>;
228
229 #[pallet::storage]
231 pub type ForeignToNativeId<T: Config> =
232 StorageMap<_, Blake2_128Concat, TokenId, xcm::v5::Location, OptionQuery>;
233
234 #[pallet::storage]
236 pub type NativeToForeignId<T: Config> =
237 StorageMap<_, Blake2_128Concat, xcm::v5::Location, TokenId, OptionQuery>;
238
239 #[pallet::genesis_config]
240 #[derive(frame_support::DefaultNoBound)]
241 pub struct GenesisConfig<T: Config> {
242 pub para_id: ParaId,
244 pub asset_hub_para_id: ParaId,
246 #[serde(skip)]
247 pub _config: PhantomData<T>,
248 }
249
250 #[pallet::genesis_build]
251 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
252 fn build(&self) {
253 Pallet::<T>::initialize(self.para_id, self.asset_hub_para_id).expect("infallible; qed");
254 }
255 }
256
257 #[pallet::call]
258 impl<T: Config> Pallet<T> {
259 #[pallet::call_index(0)]
269 #[pallet::weight((T::WeightInfo::upgrade(), DispatchClass::Operational))]
270 pub fn upgrade(
271 origin: OriginFor<T>,
272 impl_address: H160,
273 impl_code_hash: H256,
274 initializer: Option<Initializer>,
275 ) -> DispatchResult {
276 ensure_root(origin)?;
277
278 ensure!(
279 !impl_address.eq(&H160::zero()) && !impl_code_hash.eq(&H256::zero()),
280 Error::<T>::InvalidUpgradeParameters
281 );
282
283 let initializer_params_hash: Option<H256> =
284 initializer.as_ref().map(|i| H256::from(blake2_256(i.params.as_ref())));
285 let command = Command::Upgrade { impl_address, impl_code_hash, initializer };
286 Self::send(PRIMARY_GOVERNANCE_CHANNEL, command, PaysFee::<T>::No)?;
287
288 Self::deposit_event(Event::<T>::Upgrade {
289 impl_address,
290 impl_code_hash,
291 initializer_params_hash,
292 });
293 Ok(())
294 }
295
296 #[pallet::call_index(1)]
302 #[pallet::weight((T::WeightInfo::set_operating_mode(), DispatchClass::Operational))]
303 pub fn set_operating_mode(origin: OriginFor<T>, mode: OperatingMode) -> DispatchResult {
304 ensure_root(origin)?;
305
306 let command = Command::SetOperatingMode { mode };
307 Self::send(PRIMARY_GOVERNANCE_CHANNEL, command, PaysFee::<T>::No)?;
308
309 Self::deposit_event(Event::<T>::SetOperatingMode { mode });
310 Ok(())
311 }
312
313 #[pallet::call_index(2)]
319 #[pallet::weight((T::WeightInfo::set_pricing_parameters(), DispatchClass::Operational))]
320 pub fn set_pricing_parameters(
321 origin: OriginFor<T>,
322 params: PricingParametersOf<T>,
323 ) -> DispatchResult {
324 ensure_root(origin)?;
325 params.validate().map_err(|_| Error::<T>::InvalidPricingParameters)?;
326 PricingParameters::<T>::put(params.clone());
327
328 let command = Command::SetPricingParameters {
329 exchange_rate: params.exchange_rate.into(),
330 delivery_cost: T::InboundDeliveryCost::get().saturated_into::<u128>(),
331 multiplier: params.multiplier.into(),
332 };
333 Self::send(PRIMARY_GOVERNANCE_CHANNEL, command, PaysFee::<T>::No)?;
334
335 Self::deposit_event(Event::PricingParametersChanged { params });
336 Ok(())
337 }
338
339 #[pallet::call_index(9)]
353 #[pallet::weight((T::WeightInfo::set_token_transfer_fees(), DispatchClass::Operational))]
354 pub fn set_token_transfer_fees(
355 origin: OriginFor<T>,
356 create_asset_xcm: u128,
357 transfer_asset_xcm: u128,
358 register_token: U256,
359 ) -> DispatchResult {
360 ensure_root(origin)?;
361
362 ensure!(
365 create_asset_xcm > 0 && transfer_asset_xcm > 0 && register_token > meth(100),
366 Error::<T>::InvalidTokenTransferFees
367 );
368
369 let command = Command::SetTokenTransferFees {
370 create_asset_xcm,
371 transfer_asset_xcm,
372 register_token,
373 };
374 Self::send(PRIMARY_GOVERNANCE_CHANNEL, command, PaysFee::<T>::No)?;
375
376 Self::deposit_event(Event::<T>::SetTokenTransferFees {
377 create_asset_xcm,
378 transfer_asset_xcm,
379 register_token,
380 });
381 Ok(())
382 }
383
384 #[pallet::call_index(10)]
393 #[pallet::weight(T::WeightInfo::register_token())]
394 pub fn register_token(
395 origin: OriginFor<T>,
396 location: Box<VersionedLocation>,
397 metadata: AssetMetadata,
398 ) -> DispatchResultWithPostInfo {
399 ensure_root(origin)?;
400
401 let location: Location =
402 (*location).try_into().map_err(|_| Error::<T>::UnsupportedLocationVersion)?;
403
404 Self::do_register_token(&location, metadata, PaysFee::<T>::No)?;
405
406 Ok(PostDispatchInfo {
407 actual_weight: Some(T::WeightInfo::register_token()),
408 pays_fee: Pays::No,
409 })
410 }
411 }
412
413 impl<T: Config> Pallet<T> {
414 fn send(channel_id: ChannelId, command: Command, pays_fee: PaysFee<T>) -> DispatchResult {
416 let message = Message { id: None, channel_id, command };
417 let (ticket, fee) =
418 T::OutboundQueue::validate(&message).map_err(|err| Error::<T>::Send(err))?;
419
420 let payment = match pays_fee {
421 PaysFee::Yes(account) => Some((account, fee.total())),
422 PaysFee::Partial(account) => Some((account, fee.local)),
423 PaysFee::No => None,
424 };
425
426 if let Some((payer, fee)) = payment {
427 T::Token::transfer(
428 &payer,
429 &T::TreasuryAccount::get(),
430 fee,
431 Preservation::Preserve,
432 )?;
433 }
434
435 T::OutboundQueue::deliver(ticket).map_err(|err| Error::<T>::Send(err))?;
436 Ok(())
437 }
438
439 pub fn initialize(para_id: ParaId, asset_hub_para_id: ParaId) -> Result<(), DispatchError> {
441 let asset_hub_location: Location =
443 ParentThen(Parachain(asset_hub_para_id.into()).into()).into();
444 let asset_hub_agent_id = agent_id_of::<T>(&asset_hub_location)?;
445 let asset_hub_channel_id: ChannelId = asset_hub_para_id.into();
446 Agents::<T>::insert(asset_hub_agent_id, ());
447 Channels::<T>::insert(
448 asset_hub_channel_id,
449 Channel { agent_id: asset_hub_agent_id, para_id: asset_hub_para_id },
450 );
451
452 let bridge_hub_agent_id = agent_id_of::<T>(&Location::here())?;
454 Agents::<T>::insert(bridge_hub_agent_id, ());
456
457 Channels::<T>::insert(
459 PRIMARY_GOVERNANCE_CHANNEL,
460 Channel { agent_id: bridge_hub_agent_id, para_id },
461 );
462
463 Channels::<T>::insert(
465 SECONDARY_GOVERNANCE_CHANNEL,
466 Channel { agent_id: bridge_hub_agent_id, para_id },
467 );
468
469 Ok(())
470 }
471
472 pub(crate) fn is_initialized() -> bool {
474 let primary_exists = Channels::<T>::contains_key(PRIMARY_GOVERNANCE_CHANNEL);
475 let secondary_exists = Channels::<T>::contains_key(SECONDARY_GOVERNANCE_CHANNEL);
476 primary_exists && secondary_exists
477 }
478
479 pub(crate) fn do_register_token(
480 location: &Location,
481 metadata: AssetMetadata,
482 pays_fee: PaysFee<T>,
483 ) -> Result<(), DispatchError> {
484 let ethereum_location = T::EthereumLocation::get();
485 let location = location
487 .clone()
488 .reanchored(ðereum_location, &T::UniversalLocation::get())
489 .map_err(|_| Error::<T>::LocationConversionFailed)?;
490
491 let token_id = TokenIdOf::convert_location(&location)
492 .ok_or(Error::<T>::LocationConversionFailed)?;
493
494 if !ForeignToNativeId::<T>::contains_key(token_id) {
495 NativeToForeignId::<T>::insert(location.clone(), token_id);
496 ForeignToNativeId::<T>::insert(token_id, location.clone());
497 }
498
499 let command = Command::RegisterForeignToken {
500 token_id,
501 name: metadata.name.into_inner(),
502 symbol: metadata.symbol.into_inner(),
503 decimals: metadata.decimals,
504 };
505 Self::send(SECONDARY_GOVERNANCE_CHANNEL, command, pays_fee)?;
506
507 Self::deposit_event(Event::<T>::RegisterToken {
508 location: location.clone().into(),
509 foreign_token_id: token_id,
510 });
511
512 Ok(())
513 }
514 }
515
516 impl<T: Config> StaticLookup for Pallet<T> {
517 type Source = ChannelId;
518 type Target = Channel;
519 fn lookup(channel_id: Self::Source) -> Option<Self::Target> {
520 Channels::<T>::get(channel_id)
521 }
522 }
523
524 impl<T: Config> Contains<ChannelId> for Pallet<T> {
525 fn contains(channel_id: &ChannelId) -> bool {
526 Channels::<T>::get(channel_id).is_some()
527 }
528 }
529
530 impl<T: Config> Get<PricingParametersOf<T>> for Pallet<T> {
531 fn get() -> PricingParametersOf<T> {
532 PricingParameters::<T>::get()
533 }
534 }
535
536 impl<T: Config> MaybeEquivalence<TokenId, Location> for Pallet<T> {
537 fn convert(foreign_id: &TokenId) -> Option<Location> {
538 ForeignToNativeId::<T>::get(foreign_id)
539 }
540 fn convert_back(location: &Location) -> Option<TokenId> {
541 NativeToForeignId::<T>::get(location)
542 }
543 }
544}