1#![cfg_attr(not(feature = "std"), no_std)]
46#[cfg(test)]
47mod mock;
48
49#[cfg(test)]
50mod tests;
51
52#[cfg(feature = "runtime-benchmarks")]
53mod benchmarking;
54pub mod migration;
55
56pub mod api;
57pub mod weights;
58pub use weights::*;
59
60use frame_support::{
61 pallet_prelude::*,
62 traits::{
63 fungible::{Inspect, Mutate},
64 tokens::Preservation,
65 Contains, EnsureOrigin,
66 },
67};
68use frame_system::pallet_prelude::*;
69use snowbridge_core::{
70 meth,
71 outbound::{Command, Initializer, Message, OperatingMode, SendError, SendMessage},
72 sibling_sovereign_account, AgentId, AssetMetadata, Channel, ChannelId, ParaId,
73 PricingParameters as PricingParametersRecord, TokenId, TokenIdOf, PRIMARY_GOVERNANCE_CHANNEL,
74 SECONDARY_GOVERNANCE_CHANNEL,
75};
76use sp_core::{RuntimeDebug, H160, H256};
77use sp_io::hashing::blake2_256;
78use sp_runtime::{
79 traits::{BadOrigin, MaybeEquivalence},
80 DispatchError, SaturatedConversion,
81};
82use sp_std::prelude::*;
83use xcm::prelude::*;
84use xcm_executor::traits::ConvertLocation;
85
86#[cfg(feature = "runtime-benchmarks")]
87use frame_support::traits::OriginTrait;
88
89pub use pallet::*;
90
91pub type BalanceOf<T> =
92 <<T as pallet::Config>::Token as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
93pub type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
94pub type PricingParametersOf<T> = PricingParametersRecord<BalanceOf<T>>;
95
96fn ensure_sibling<T>(location: &Location) -> Result<(ParaId, H256), DispatchError>
98where
99 T: Config,
100{
101 match location.unpack() {
102 (1, [Parachain(para_id)]) => {
103 let agent_id = agent_id_of::<T>(location)?;
104 Ok(((*para_id).into(), agent_id))
105 },
106 _ => Err(BadOrigin.into()),
107 }
108}
109
110pub fn agent_id_of<T: Config>(location: &Location) -> Result<H256, DispatchError> {
112 T::AgentIdOf::convert_location(location).ok_or(Error::<T>::LocationConversionFailed.into())
113}
114
115#[cfg(feature = "runtime-benchmarks")]
116pub trait BenchmarkHelper<O>
117where
118 O: OriginTrait,
119{
120 fn make_xcm_origin(location: Location) -> O;
121}
122
123#[derive(Clone, PartialEq, RuntimeDebug)]
125pub enum PaysFee<T>
126where
127 T: Config,
128{
129 Yes(AccountIdOf<T>),
131 Partial(AccountIdOf<T>),
133 No,
135}
136
137#[frame_support::pallet]
138pub mod pallet {
139 use frame_support::dispatch::PostDispatchInfo;
140 use snowbridge_core::StaticLookup;
141 use sp_core::U256;
142
143 use super::*;
144
145 #[pallet::pallet]
146 #[pallet::storage_version(migration::STORAGE_VERSION)]
147 pub struct Pallet<T>(_);
148
149 #[pallet::config]
150 pub trait Config: frame_system::Config {
151 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
152
153 type OutboundQueue: SendMessage<Balance = BalanceOf<Self>>;
155
156 type SiblingOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = Location>;
158
159 type AgentIdOf: ConvertLocation<AgentId>;
161
162 type Token: Mutate<Self::AccountId>;
164
165 #[pallet::constant]
167 type TreasuryAccount: Get<Self::AccountId>;
168
169 type DefaultPricingParameters: Get<PricingParametersOf<Self>>;
171
172 #[pallet::constant]
174 type InboundDeliveryCost: Get<BalanceOf<Self>>;
175
176 type WeightInfo: WeightInfo;
177
178 type UniversalLocation: Get<InteriorLocation>;
180
181 type EthereumLocation: Get<Location>;
183
184 #[cfg(feature = "runtime-benchmarks")]
185 type Helper: BenchmarkHelper<Self::RuntimeOrigin>;
186 }
187
188 #[pallet::event]
189 #[pallet::generate_deposit(pub(super) fn deposit_event)]
190 pub enum Event<T: Config> {
191 Upgrade {
193 impl_address: H160,
194 impl_code_hash: H256,
195 initializer_params_hash: Option<H256>,
196 },
197 CreateAgent {
199 location: Box<Location>,
200 agent_id: AgentId,
201 },
202 CreateChannel {
204 channel_id: ChannelId,
205 agent_id: AgentId,
206 },
207 UpdateChannel {
209 channel_id: ChannelId,
210 mode: OperatingMode,
211 },
212 SetOperatingMode {
214 mode: OperatingMode,
215 },
216 TransferNativeFromAgent {
218 agent_id: AgentId,
219 recipient: H160,
220 amount: u128,
221 },
222 SetTokenTransferFees {
224 create_asset_xcm: u128,
225 transfer_asset_xcm: u128,
226 register_token: U256,
227 },
228 PricingParametersChanged {
229 params: PricingParametersOf<T>,
230 },
231 RegisterToken {
233 location: VersionedLocation,
235 foreign_token_id: H256,
237 },
238 }
239
240 #[pallet::error]
241 pub enum Error<T> {
242 LocationConversionFailed,
243 AgentAlreadyCreated,
244 NoAgent,
245 ChannelAlreadyCreated,
246 NoChannel,
247 UnsupportedLocationVersion,
248 InvalidLocation,
249 Send(SendError),
250 InvalidTokenTransferFees,
251 InvalidPricingParameters,
252 InvalidUpgradeParameters,
253 }
254
255 #[pallet::storage]
257 #[pallet::getter(fn agents)]
258 pub type Agents<T: Config> = StorageMap<_, Twox64Concat, AgentId, (), OptionQuery>;
259
260 #[pallet::storage]
262 #[pallet::getter(fn channels)]
263 pub type Channels<T: Config> = StorageMap<_, Twox64Concat, ChannelId, Channel, OptionQuery>;
264
265 #[pallet::storage]
266 #[pallet::getter(fn parameters)]
267 pub type PricingParameters<T: Config> =
268 StorageValue<_, PricingParametersOf<T>, ValueQuery, T::DefaultPricingParameters>;
269
270 #[pallet::storage]
272 pub type ForeignToNativeId<T: Config> =
273 StorageMap<_, Blake2_128Concat, TokenId, xcm::v5::Location, OptionQuery>;
274
275 #[pallet::storage]
277 pub type NativeToForeignId<T: Config> =
278 StorageMap<_, Blake2_128Concat, xcm::v5::Location, TokenId, OptionQuery>;
279
280 #[pallet::genesis_config]
281 #[derive(frame_support::DefaultNoBound)]
282 pub struct GenesisConfig<T: Config> {
283 pub para_id: ParaId,
285 pub asset_hub_para_id: ParaId,
287 #[serde(skip)]
288 pub _config: PhantomData<T>,
289 }
290
291 #[pallet::genesis_build]
292 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
293 fn build(&self) {
294 Pallet::<T>::initialize(self.para_id, self.asset_hub_para_id).expect("infallible; qed");
295 }
296 }
297
298 #[pallet::call]
299 impl<T: Config> Pallet<T> {
300 #[pallet::call_index(0)]
310 #[pallet::weight((T::WeightInfo::upgrade(), DispatchClass::Operational))]
311 pub fn upgrade(
312 origin: OriginFor<T>,
313 impl_address: H160,
314 impl_code_hash: H256,
315 initializer: Option<Initializer>,
316 ) -> DispatchResult {
317 ensure_root(origin)?;
318
319 ensure!(
320 !impl_address.eq(&H160::zero()) && !impl_code_hash.eq(&H256::zero()),
321 Error::<T>::InvalidUpgradeParameters
322 );
323
324 let initializer_params_hash: Option<H256> =
325 initializer.as_ref().map(|i| H256::from(blake2_256(i.params.as_ref())));
326 let command = Command::Upgrade { impl_address, impl_code_hash, initializer };
327 Self::send(PRIMARY_GOVERNANCE_CHANNEL, command, PaysFee::<T>::No)?;
328
329 Self::deposit_event(Event::<T>::Upgrade {
330 impl_address,
331 impl_code_hash,
332 initializer_params_hash,
333 });
334 Ok(())
335 }
336
337 #[pallet::call_index(1)]
343 #[pallet::weight((T::WeightInfo::set_operating_mode(), DispatchClass::Operational))]
344 pub fn set_operating_mode(origin: OriginFor<T>, mode: OperatingMode) -> DispatchResult {
345 ensure_root(origin)?;
346
347 let command = Command::SetOperatingMode { mode };
348 Self::send(PRIMARY_GOVERNANCE_CHANNEL, command, PaysFee::<T>::No)?;
349
350 Self::deposit_event(Event::<T>::SetOperatingMode { mode });
351 Ok(())
352 }
353
354 #[pallet::call_index(2)]
360 #[pallet::weight((T::WeightInfo::set_pricing_parameters(), DispatchClass::Operational))]
361 pub fn set_pricing_parameters(
362 origin: OriginFor<T>,
363 params: PricingParametersOf<T>,
364 ) -> DispatchResult {
365 ensure_root(origin)?;
366 params.validate().map_err(|_| Error::<T>::InvalidPricingParameters)?;
367 PricingParameters::<T>::put(params.clone());
368
369 let command = Command::SetPricingParameters {
370 exchange_rate: params.exchange_rate.into(),
371 delivery_cost: T::InboundDeliveryCost::get().saturated_into::<u128>(),
372 multiplier: params.multiplier.into(),
373 };
374 Self::send(PRIMARY_GOVERNANCE_CHANNEL, command, PaysFee::<T>::No)?;
375
376 Self::deposit_event(Event::PricingParametersChanged { params });
377 Ok(())
378 }
379
380 #[pallet::call_index(3)]
387 #[pallet::weight(T::WeightInfo::create_agent())]
388 pub fn create_agent(origin: OriginFor<T>) -> DispatchResult {
389 let origin_location: Location = T::SiblingOrigin::ensure_origin(origin)?;
390
391 let (para_id, agent_id) = ensure_sibling::<T>(&origin_location)?;
393
394 ensure!(!Agents::<T>::contains_key(agent_id), Error::<T>::AgentAlreadyCreated);
396 Agents::<T>::insert(agent_id, ());
397
398 let command = Command::CreateAgent { agent_id };
399 let pays_fee = PaysFee::<T>::Yes(sibling_sovereign_account::<T>(para_id));
400 Self::send(SECONDARY_GOVERNANCE_CHANNEL, command, pays_fee)?;
401
402 Self::deposit_event(Event::<T>::CreateAgent {
403 location: Box::new(origin_location),
404 agent_id,
405 });
406 Ok(())
407 }
408
409 #[pallet::call_index(4)]
421 #[pallet::weight(T::WeightInfo::create_channel())]
422 pub fn create_channel(origin: OriginFor<T>, mode: OperatingMode) -> DispatchResult {
423 let origin_location: Location = T::SiblingOrigin::ensure_origin(origin)?;
424
425 let (para_id, agent_id) = ensure_sibling::<T>(&origin_location)?;
427
428 let channel_id: ChannelId = para_id.into();
429
430 ensure!(Agents::<T>::contains_key(agent_id), Error::<T>::NoAgent);
431 ensure!(!Channels::<T>::contains_key(channel_id), Error::<T>::ChannelAlreadyCreated);
432
433 let channel = Channel { agent_id, para_id };
434 Channels::<T>::insert(channel_id, channel);
435
436 let command = Command::CreateChannel { channel_id, agent_id, mode };
437 let pays_fee = PaysFee::<T>::Yes(sibling_sovereign_account::<T>(para_id));
438 Self::send(SECONDARY_GOVERNANCE_CHANNEL, command, pays_fee)?;
439
440 Self::deposit_event(Event::<T>::CreateChannel { channel_id, agent_id });
441 Ok(())
442 }
443
444 #[pallet::call_index(5)]
453 #[pallet::weight(T::WeightInfo::update_channel())]
454 pub fn update_channel(origin: OriginFor<T>, mode: OperatingMode) -> DispatchResult {
455 let origin_location: Location = T::SiblingOrigin::ensure_origin(origin)?;
456
457 let (para_id, _) = ensure_sibling::<T>(&origin_location)?;
459
460 let channel_id: ChannelId = para_id.into();
461
462 ensure!(Channels::<T>::contains_key(channel_id), Error::<T>::NoChannel);
463
464 let command = Command::UpdateChannel { channel_id, mode };
465 let pays_fee = PaysFee::<T>::Partial(sibling_sovereign_account::<T>(para_id));
466
467 Self::send(channel_id, command, pays_fee)?;
469
470 Self::deposit_event(Event::<T>::UpdateChannel { channel_id, mode });
471 Ok(())
472 }
473
474 #[pallet::call_index(6)]
483 #[pallet::weight(T::WeightInfo::force_update_channel())]
484 pub fn force_update_channel(
485 origin: OriginFor<T>,
486 channel_id: ChannelId,
487 mode: OperatingMode,
488 ) -> DispatchResult {
489 ensure_root(origin)?;
490
491 ensure!(Channels::<T>::contains_key(channel_id), Error::<T>::NoChannel);
492
493 let command = Command::UpdateChannel { channel_id, mode };
494 Self::send(PRIMARY_GOVERNANCE_CHANNEL, command, PaysFee::<T>::No)?;
495
496 Self::deposit_event(Event::<T>::UpdateChannel { channel_id, mode });
497 Ok(())
498 }
499
500 #[pallet::call_index(7)]
506 #[pallet::weight(T::WeightInfo::transfer_native_from_agent())]
507 pub fn transfer_native_from_agent(
508 origin: OriginFor<T>,
509 recipient: H160,
510 amount: u128,
511 ) -> DispatchResult {
512 let origin_location: Location = T::SiblingOrigin::ensure_origin(origin)?;
513
514 let (para_id, agent_id) = ensure_sibling::<T>(&origin_location)?;
516
517 let pays_fee = PaysFee::<T>::Partial(sibling_sovereign_account::<T>(para_id));
520
521 Self::do_transfer_native_from_agent(
522 agent_id,
523 para_id.into(),
524 recipient,
525 amount,
526 pays_fee,
527 )
528 }
529
530 #[pallet::call_index(8)]
541 #[pallet::weight(T::WeightInfo::force_transfer_native_from_agent())]
542 pub fn force_transfer_native_from_agent(
543 origin: OriginFor<T>,
544 location: Box<VersionedLocation>,
545 recipient: H160,
546 amount: u128,
547 ) -> DispatchResult {
548 ensure_root(origin)?;
549
550 let location: Location =
552 (*location).try_into().map_err(|_| Error::<T>::UnsupportedLocationVersion)?;
553 let (_, agent_id) =
554 ensure_sibling::<T>(&location).map_err(|_| Error::<T>::InvalidLocation)?;
555
556 let pays_fee = PaysFee::<T>::No;
557
558 Self::do_transfer_native_from_agent(
559 agent_id,
560 PRIMARY_GOVERNANCE_CHANNEL,
561 recipient,
562 amount,
563 pays_fee,
564 )
565 }
566
567 #[pallet::call_index(9)]
581 #[pallet::weight((T::WeightInfo::set_token_transfer_fees(), DispatchClass::Operational))]
582 pub fn set_token_transfer_fees(
583 origin: OriginFor<T>,
584 create_asset_xcm: u128,
585 transfer_asset_xcm: u128,
586 register_token: U256,
587 ) -> DispatchResult {
588 ensure_root(origin)?;
589
590 ensure!(
593 create_asset_xcm > 0 && transfer_asset_xcm > 0 && register_token > meth(100),
594 Error::<T>::InvalidTokenTransferFees
595 );
596
597 let command = Command::SetTokenTransferFees {
598 create_asset_xcm,
599 transfer_asset_xcm,
600 register_token,
601 };
602 Self::send(PRIMARY_GOVERNANCE_CHANNEL, command, PaysFee::<T>::No)?;
603
604 Self::deposit_event(Event::<T>::SetTokenTransferFees {
605 create_asset_xcm,
606 transfer_asset_xcm,
607 register_token,
608 });
609 Ok(())
610 }
611
612 #[pallet::call_index(10)]
621 #[pallet::weight(T::WeightInfo::register_token())]
622 pub fn register_token(
623 origin: OriginFor<T>,
624 location: Box<VersionedLocation>,
625 metadata: AssetMetadata,
626 ) -> DispatchResultWithPostInfo {
627 ensure_root(origin)?;
628
629 let location: Location =
630 (*location).try_into().map_err(|_| Error::<T>::UnsupportedLocationVersion)?;
631
632 Self::do_register_token(&location, metadata, PaysFee::<T>::No)?;
633
634 Ok(PostDispatchInfo {
635 actual_weight: Some(T::WeightInfo::register_token()),
636 pays_fee: Pays::No,
637 })
638 }
639 }
640
641 impl<T: Config> Pallet<T> {
642 fn send(channel_id: ChannelId, command: Command, pays_fee: PaysFee<T>) -> DispatchResult {
644 let message = Message { id: None, channel_id, command };
645 let (ticket, fee) =
646 T::OutboundQueue::validate(&message).map_err(|err| Error::<T>::Send(err))?;
647
648 let payment = match pays_fee {
649 PaysFee::Yes(account) => Some((account, fee.total())),
650 PaysFee::Partial(account) => Some((account, fee.local)),
651 PaysFee::No => None,
652 };
653
654 if let Some((payer, fee)) = payment {
655 T::Token::transfer(
656 &payer,
657 &T::TreasuryAccount::get(),
658 fee,
659 Preservation::Preserve,
660 )?;
661 }
662
663 T::OutboundQueue::deliver(ticket).map_err(|err| Error::<T>::Send(err))?;
664 Ok(())
665 }
666
667 pub fn do_transfer_native_from_agent(
670 agent_id: H256,
671 channel_id: ChannelId,
672 recipient: H160,
673 amount: u128,
674 pays_fee: PaysFee<T>,
675 ) -> DispatchResult {
676 ensure!(Agents::<T>::contains_key(agent_id), Error::<T>::NoAgent);
677
678 let command = Command::TransferNativeFromAgent { agent_id, recipient, amount };
679 Self::send(channel_id, command, pays_fee)?;
680
681 Self::deposit_event(Event::<T>::TransferNativeFromAgent {
682 agent_id,
683 recipient,
684 amount,
685 });
686 Ok(())
687 }
688
689 pub fn initialize(para_id: ParaId, asset_hub_para_id: ParaId) -> Result<(), DispatchError> {
691 let asset_hub_location: Location =
693 ParentThen(Parachain(asset_hub_para_id.into()).into()).into();
694 let asset_hub_agent_id = agent_id_of::<T>(&asset_hub_location)?;
695 let asset_hub_channel_id: ChannelId = asset_hub_para_id.into();
696 Agents::<T>::insert(asset_hub_agent_id, ());
697 Channels::<T>::insert(
698 asset_hub_channel_id,
699 Channel { agent_id: asset_hub_agent_id, para_id: asset_hub_para_id },
700 );
701
702 let bridge_hub_agent_id = agent_id_of::<T>(&Location::here())?;
704 Agents::<T>::insert(bridge_hub_agent_id, ());
706
707 Channels::<T>::insert(
709 PRIMARY_GOVERNANCE_CHANNEL,
710 Channel { agent_id: bridge_hub_agent_id, para_id },
711 );
712
713 Channels::<T>::insert(
715 SECONDARY_GOVERNANCE_CHANNEL,
716 Channel { agent_id: bridge_hub_agent_id, para_id },
717 );
718
719 Ok(())
720 }
721
722 pub(crate) fn is_initialized() -> bool {
724 let primary_exists = Channels::<T>::contains_key(PRIMARY_GOVERNANCE_CHANNEL);
725 let secondary_exists = Channels::<T>::contains_key(SECONDARY_GOVERNANCE_CHANNEL);
726 primary_exists && secondary_exists
727 }
728
729 pub(crate) fn do_register_token(
730 location: &Location,
731 metadata: AssetMetadata,
732 pays_fee: PaysFee<T>,
733 ) -> Result<(), DispatchError> {
734 let ethereum_location = T::EthereumLocation::get();
735 let location = location
737 .clone()
738 .reanchored(ðereum_location, &T::UniversalLocation::get())
739 .map_err(|_| Error::<T>::LocationConversionFailed)?;
740
741 let token_id = TokenIdOf::convert_location(&location)
742 .ok_or(Error::<T>::LocationConversionFailed)?;
743
744 if !ForeignToNativeId::<T>::contains_key(token_id) {
745 NativeToForeignId::<T>::insert(location.clone(), token_id);
746 ForeignToNativeId::<T>::insert(token_id, location.clone());
747 }
748
749 let command = Command::RegisterForeignToken {
750 token_id,
751 name: metadata.name.into_inner(),
752 symbol: metadata.symbol.into_inner(),
753 decimals: metadata.decimals,
754 };
755 Self::send(SECONDARY_GOVERNANCE_CHANNEL, command, pays_fee)?;
756
757 Self::deposit_event(Event::<T>::RegisterToken {
758 location: location.clone().into(),
759 foreign_token_id: token_id,
760 });
761
762 Ok(())
763 }
764 }
765
766 impl<T: Config> StaticLookup for Pallet<T> {
767 type Source = ChannelId;
768 type Target = Channel;
769 fn lookup(channel_id: Self::Source) -> Option<Self::Target> {
770 Channels::<T>::get(channel_id)
771 }
772 }
773
774 impl<T: Config> Contains<ChannelId> for Pallet<T> {
775 fn contains(channel_id: &ChannelId) -> bool {
776 Channels::<T>::get(channel_id).is_some()
777 }
778 }
779
780 impl<T: Config> Get<PricingParametersOf<T>> for Pallet<T> {
781 fn get() -> PricingParametersOf<T> {
782 PricingParameters::<T>::get()
783 }
784 }
785
786 impl<T: Config> MaybeEquivalence<TokenId, Location> for Pallet<T> {
787 fn convert(foreign_id: &TokenId) -> Option<Location> {
788 ForeignToNativeId::<T>::get(foreign_id)
789 }
790 fn convert_back(location: &Location) -> Option<TokenId> {
791 NativeToForeignId::<T>::get(location)
792 }
793 }
794}