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::MaybeConvert, 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 #[pallet::storage_version(migration::STORAGE_VERSION)]
107 pub struct Pallet<T>(_);
108
109 #[pallet::config]
110 pub trait Config: frame_system::Config {
111 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
112
113 type OutboundQueue: SendMessage<Balance = BalanceOf<Self>>;
115
116 type SiblingOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = Location>;
118
119 type AgentIdOf: ConvertLocation<AgentId>;
121
122 type Token: Mutate<Self::AccountId>;
124
125 #[pallet::constant]
127 type TreasuryAccount: Get<Self::AccountId>;
128
129 type DefaultPricingParameters: Get<PricingParametersOf<Self>>;
131
132 #[pallet::constant]
134 type InboundDeliveryCost: Get<BalanceOf<Self>>;
135
136 type WeightInfo: WeightInfo;
137
138 type UniversalLocation: Get<InteriorLocation>;
140
141 type EthereumLocation: Get<Location>;
143
144 #[cfg(feature = "runtime-benchmarks")]
145 type Helper: BenchmarkHelper<Self::RuntimeOrigin>;
146 }
147
148 #[pallet::event]
149 #[pallet::generate_deposit(pub(super) fn deposit_event)]
150 pub enum Event<T: Config> {
151 Upgrade {
153 impl_address: H160,
154 impl_code_hash: H256,
155 initializer_params_hash: Option<H256>,
156 },
157 CreateAgent {
159 location: Box<Location>,
160 agent_id: AgentId,
161 },
162 CreateChannel {
164 channel_id: ChannelId,
165 agent_id: AgentId,
166 },
167 UpdateChannel {
169 channel_id: ChannelId,
170 mode: OperatingMode,
171 },
172 SetOperatingMode {
174 mode: OperatingMode,
175 },
176 TransferNativeFromAgent {
178 agent_id: AgentId,
179 recipient: H160,
180 amount: u128,
181 },
182 SetTokenTransferFees {
184 create_asset_xcm: u128,
185 transfer_asset_xcm: u128,
186 register_token: U256,
187 },
188 PricingParametersChanged {
189 params: PricingParametersOf<T>,
190 },
191 RegisterToken {
193 location: VersionedLocation,
195 foreign_token_id: H256,
197 },
198 }
199
200 #[pallet::error]
201 pub enum Error<T> {
202 LocationConversionFailed,
203 AgentAlreadyCreated,
204 NoAgent,
205 ChannelAlreadyCreated,
206 NoChannel,
207 UnsupportedLocationVersion,
208 InvalidLocation,
209 Send(SendError),
210 InvalidTokenTransferFees,
211 InvalidPricingParameters,
212 InvalidUpgradeParameters,
213 }
214
215 #[pallet::storage]
217 #[pallet::getter(fn agents)]
218 pub type Agents<T: Config> = StorageMap<_, Twox64Concat, AgentId, (), OptionQuery>;
219
220 #[pallet::storage]
222 #[pallet::getter(fn channels)]
223 pub type Channels<T: Config> = StorageMap<_, Twox64Concat, ChannelId, Channel, OptionQuery>;
224
225 #[pallet::storage]
226 #[pallet::getter(fn parameters)]
227 pub type PricingParameters<T: Config> =
228 StorageValue<_, PricingParametersOf<T>, ValueQuery, T::DefaultPricingParameters>;
229
230 #[pallet::storage]
232 pub type ForeignToNativeId<T: Config> =
233 StorageMap<_, Blake2_128Concat, TokenId, Location, OptionQuery>;
234
235 #[pallet::genesis_config]
236 #[derive(frame_support::DefaultNoBound)]
237 pub struct GenesisConfig<T: Config> {
238 pub para_id: ParaId,
240 pub asset_hub_para_id: ParaId,
242 #[serde(skip)]
243 pub _config: PhantomData<T>,
244 }
245
246 #[pallet::genesis_build]
247 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
248 fn build(&self) {
249 Pallet::<T>::initialize(self.para_id, self.asset_hub_para_id).expect("infallible; qed");
250 }
251 }
252
253 #[pallet::call]
254 impl<T: Config> Pallet<T> {
255 #[pallet::call_index(0)]
265 #[pallet::weight((T::WeightInfo::upgrade(), DispatchClass::Operational))]
266 pub fn upgrade(
267 origin: OriginFor<T>,
268 impl_address: H160,
269 impl_code_hash: H256,
270 initializer: Option<Initializer>,
271 ) -> DispatchResult {
272 ensure_root(origin)?;
273
274 ensure!(
275 !impl_address.eq(&H160::zero()) && !impl_code_hash.eq(&H256::zero()),
276 Error::<T>::InvalidUpgradeParameters
277 );
278
279 let initializer_params_hash: Option<H256> =
280 initializer.as_ref().map(|i| H256::from(blake2_256(i.params.as_ref())));
281 let command = Command::Upgrade { impl_address, impl_code_hash, initializer };
282 Self::send(PRIMARY_GOVERNANCE_CHANNEL, command, PaysFee::<T>::No)?;
283
284 Self::deposit_event(Event::<T>::Upgrade {
285 impl_address,
286 impl_code_hash,
287 initializer_params_hash,
288 });
289 Ok(())
290 }
291
292 #[pallet::call_index(1)]
298 #[pallet::weight((T::WeightInfo::set_operating_mode(), DispatchClass::Operational))]
299 pub fn set_operating_mode(origin: OriginFor<T>, mode: OperatingMode) -> DispatchResult {
300 ensure_root(origin)?;
301
302 let command = Command::SetOperatingMode { mode };
303 Self::send(PRIMARY_GOVERNANCE_CHANNEL, command, PaysFee::<T>::No)?;
304
305 Self::deposit_event(Event::<T>::SetOperatingMode { mode });
306 Ok(())
307 }
308
309 #[pallet::call_index(2)]
315 #[pallet::weight((T::WeightInfo::set_pricing_parameters(), DispatchClass::Operational))]
316 pub fn set_pricing_parameters(
317 origin: OriginFor<T>,
318 params: PricingParametersOf<T>,
319 ) -> DispatchResult {
320 ensure_root(origin)?;
321 params.validate().map_err(|_| Error::<T>::InvalidPricingParameters)?;
322 PricingParameters::<T>::put(params.clone());
323
324 let command = Command::SetPricingParameters {
325 exchange_rate: params.exchange_rate.into(),
326 delivery_cost: T::InboundDeliveryCost::get().saturated_into::<u128>(),
327 multiplier: params.multiplier.into(),
328 };
329 Self::send(PRIMARY_GOVERNANCE_CHANNEL, command, PaysFee::<T>::No)?;
330
331 Self::deposit_event(Event::PricingParametersChanged { params });
332 Ok(())
333 }
334
335 #[pallet::call_index(9)]
349 #[pallet::weight((T::WeightInfo::set_token_transfer_fees(), DispatchClass::Operational))]
350 pub fn set_token_transfer_fees(
351 origin: OriginFor<T>,
352 create_asset_xcm: u128,
353 transfer_asset_xcm: u128,
354 register_token: U256,
355 ) -> DispatchResult {
356 ensure_root(origin)?;
357
358 ensure!(
361 create_asset_xcm > 0 && transfer_asset_xcm > 0 && register_token > meth(100),
362 Error::<T>::InvalidTokenTransferFees
363 );
364
365 let command = Command::SetTokenTransferFees {
366 create_asset_xcm,
367 transfer_asset_xcm,
368 register_token,
369 };
370 Self::send(PRIMARY_GOVERNANCE_CHANNEL, command, PaysFee::<T>::No)?;
371
372 Self::deposit_event(Event::<T>::SetTokenTransferFees {
373 create_asset_xcm,
374 transfer_asset_xcm,
375 register_token,
376 });
377 Ok(())
378 }
379
380 #[pallet::call_index(10)]
389 #[pallet::weight(T::WeightInfo::register_token())]
390 pub fn register_token(
391 origin: OriginFor<T>,
392 location: Box<VersionedLocation>,
393 metadata: AssetMetadata,
394 ) -> DispatchResultWithPostInfo {
395 ensure_root(origin)?;
396
397 let location: Location =
398 (*location).try_into().map_err(|_| Error::<T>::UnsupportedLocationVersion)?;
399
400 Self::do_register_token(&location, metadata, PaysFee::<T>::No)?;
401
402 Ok(PostDispatchInfo {
403 actual_weight: Some(T::WeightInfo::register_token()),
404 pays_fee: Pays::No,
405 })
406 }
407 }
408
409 impl<T: Config> Pallet<T> {
410 fn send(channel_id: ChannelId, command: Command, pays_fee: PaysFee<T>) -> DispatchResult {
412 let message = Message { id: None, channel_id, command };
413 let (ticket, fee) =
414 T::OutboundQueue::validate(&message).map_err(|err| Error::<T>::Send(err))?;
415
416 let payment = match pays_fee {
417 PaysFee::Yes(account) => Some((account, fee.total())),
418 PaysFee::Partial(account) => Some((account, fee.local)),
419 PaysFee::No => None,
420 };
421
422 if let Some((payer, fee)) = payment {
423 T::Token::transfer(
424 &payer,
425 &T::TreasuryAccount::get(),
426 fee,
427 Preservation::Preserve,
428 )?;
429 }
430
431 T::OutboundQueue::deliver(ticket).map_err(|err| Error::<T>::Send(err))?;
432 Ok(())
433 }
434
435 pub fn initialize(para_id: ParaId, asset_hub_para_id: ParaId) -> Result<(), DispatchError> {
437 let asset_hub_location: Location =
439 ParentThen(Parachain(asset_hub_para_id.into()).into()).into();
440 let asset_hub_agent_id = agent_id_of::<T>(&asset_hub_location)?;
441 let asset_hub_channel_id: ChannelId = asset_hub_para_id.into();
442 Agents::<T>::insert(asset_hub_agent_id, ());
443 Channels::<T>::insert(
444 asset_hub_channel_id,
445 Channel { agent_id: asset_hub_agent_id, para_id: asset_hub_para_id },
446 );
447
448 let bridge_hub_agent_id = agent_id_of::<T>(&Location::here())?;
450 Agents::<T>::insert(bridge_hub_agent_id, ());
452
453 Channels::<T>::insert(
455 PRIMARY_GOVERNANCE_CHANNEL,
456 Channel { agent_id: bridge_hub_agent_id, para_id },
457 );
458
459 Channels::<T>::insert(
461 SECONDARY_GOVERNANCE_CHANNEL,
462 Channel { agent_id: bridge_hub_agent_id, para_id },
463 );
464
465 Ok(())
466 }
467
468 pub(crate) fn is_initialized() -> bool {
470 let primary_exists = Channels::<T>::contains_key(PRIMARY_GOVERNANCE_CHANNEL);
471 let secondary_exists = Channels::<T>::contains_key(SECONDARY_GOVERNANCE_CHANNEL);
472 primary_exists && secondary_exists
473 }
474
475 pub(crate) fn do_register_token(
476 location: &Location,
477 metadata: AssetMetadata,
478 pays_fee: PaysFee<T>,
479 ) -> Result<(), DispatchError> {
480 let ethereum_location = T::EthereumLocation::get();
481 let location = location
483 .clone()
484 .reanchored(ðereum_location, &T::UniversalLocation::get())
485 .map_err(|_| Error::<T>::LocationConversionFailed)?;
486
487 let token_id = TokenIdOf::convert_location(&location)
488 .ok_or(Error::<T>::LocationConversionFailed)?;
489
490 if !ForeignToNativeId::<T>::contains_key(token_id) {
491 ForeignToNativeId::<T>::insert(token_id, location.clone());
492 }
493
494 let command = Command::RegisterForeignToken {
495 token_id,
496 name: metadata.name.into_inner(),
497 symbol: metadata.symbol.into_inner(),
498 decimals: metadata.decimals,
499 };
500 Self::send(SECONDARY_GOVERNANCE_CHANNEL, command, pays_fee)?;
501
502 Self::deposit_event(Event::<T>::RegisterToken {
503 location: location.clone().into(),
504 foreign_token_id: token_id,
505 });
506
507 Ok(())
508 }
509 }
510
511 impl<T: Config> StaticLookup for Pallet<T> {
512 type Source = ChannelId;
513 type Target = Channel;
514 fn lookup(channel_id: Self::Source) -> Option<Self::Target> {
515 Channels::<T>::get(channel_id)
516 }
517 }
518
519 impl<T: Config> Contains<ChannelId> for Pallet<T> {
520 fn contains(channel_id: &ChannelId) -> bool {
521 Channels::<T>::get(channel_id).is_some()
522 }
523 }
524
525 impl<T: Config> Get<PricingParametersOf<T>> for Pallet<T> {
526 fn get() -> PricingParametersOf<T> {
527 PricingParameters::<T>::get()
528 }
529 }
530
531 impl<T: Config> MaybeConvert<TokenId, Location> for Pallet<T> {
532 fn maybe_convert(foreign_id: TokenId) -> Option<Location> {
533 ForeignToNativeId::<T>::get(foreign_id)
534 }
535 }
536}