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