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 pub struct Pallet<T>(_);
147
148 #[pallet::config]
149 pub trait Config: frame_system::Config {
150 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
151
152 type OutboundQueue: SendMessage<Balance = BalanceOf<Self>>;
154
155 type SiblingOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = Location>;
157
158 type AgentIdOf: ConvertLocation<AgentId>;
160
161 type Token: Mutate<Self::AccountId>;
163
164 #[pallet::constant]
166 type TreasuryAccount: Get<Self::AccountId>;
167
168 type DefaultPricingParameters: Get<PricingParametersOf<Self>>;
170
171 #[pallet::constant]
173 type InboundDeliveryCost: Get<BalanceOf<Self>>;
174
175 type WeightInfo: WeightInfo;
176
177 type UniversalLocation: Get<InteriorLocation>;
179
180 type EthereumLocation: Get<Location>;
182
183 #[cfg(feature = "runtime-benchmarks")]
184 type Helper: BenchmarkHelper<Self::RuntimeOrigin>;
185 }
186
187 #[pallet::event]
188 #[pallet::generate_deposit(pub(super) fn deposit_event)]
189 pub enum Event<T: Config> {
190 Upgrade {
192 impl_address: H160,
193 impl_code_hash: H256,
194 initializer_params_hash: Option<H256>,
195 },
196 CreateAgent {
198 location: Box<Location>,
199 agent_id: AgentId,
200 },
201 CreateChannel {
203 channel_id: ChannelId,
204 agent_id: AgentId,
205 },
206 UpdateChannel {
208 channel_id: ChannelId,
209 mode: OperatingMode,
210 },
211 SetOperatingMode {
213 mode: OperatingMode,
214 },
215 TransferNativeFromAgent {
217 agent_id: AgentId,
218 recipient: H160,
219 amount: u128,
220 },
221 SetTokenTransferFees {
223 create_asset_xcm: u128,
224 transfer_asset_xcm: u128,
225 register_token: U256,
226 },
227 PricingParametersChanged {
228 params: PricingParametersOf<T>,
229 },
230 RegisterToken {
232 location: VersionedLocation,
234 foreign_token_id: H256,
236 },
237 }
238
239 #[pallet::error]
240 pub enum Error<T> {
241 LocationConversionFailed,
242 AgentAlreadyCreated,
243 NoAgent,
244 ChannelAlreadyCreated,
245 NoChannel,
246 UnsupportedLocationVersion,
247 InvalidLocation,
248 Send(SendError),
249 InvalidTokenTransferFees,
250 InvalidPricingParameters,
251 InvalidUpgradeParameters,
252 }
253
254 #[pallet::storage]
256 #[pallet::getter(fn agents)]
257 pub type Agents<T: Config> = StorageMap<_, Twox64Concat, AgentId, (), OptionQuery>;
258
259 #[pallet::storage]
261 #[pallet::getter(fn channels)]
262 pub type Channels<T: Config> = StorageMap<_, Twox64Concat, ChannelId, Channel, OptionQuery>;
263
264 #[pallet::storage]
265 #[pallet::getter(fn parameters)]
266 pub type PricingParameters<T: Config> =
267 StorageValue<_, PricingParametersOf<T>, ValueQuery, T::DefaultPricingParameters>;
268
269 #[pallet::storage]
271 pub type ForeignToNativeId<T: Config> =
272 StorageMap<_, Blake2_128Concat, TokenId, xcm::v5::Location, OptionQuery>;
273
274 #[pallet::storage]
276 pub type NativeToForeignId<T: Config> =
277 StorageMap<_, Blake2_128Concat, xcm::v5::Location, TokenId, OptionQuery>;
278
279 #[pallet::genesis_config]
280 #[derive(frame_support::DefaultNoBound)]
281 pub struct GenesisConfig<T: Config> {
282 pub para_id: ParaId,
284 pub asset_hub_para_id: ParaId,
286 #[serde(skip)]
287 pub _config: PhantomData<T>,
288 }
289
290 #[pallet::genesis_build]
291 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
292 fn build(&self) {
293 Pallet::<T>::initialize(self.para_id, self.asset_hub_para_id).expect("infallible; qed");
294 }
295 }
296
297 #[pallet::call]
298 impl<T: Config> Pallet<T> {
299 #[pallet::call_index(0)]
309 #[pallet::weight((T::WeightInfo::upgrade(), DispatchClass::Operational))]
310 pub fn upgrade(
311 origin: OriginFor<T>,
312 impl_address: H160,
313 impl_code_hash: H256,
314 initializer: Option<Initializer>,
315 ) -> DispatchResult {
316 ensure_root(origin)?;
317
318 ensure!(
319 !impl_address.eq(&H160::zero()) && !impl_code_hash.eq(&H256::zero()),
320 Error::<T>::InvalidUpgradeParameters
321 );
322
323 let initializer_params_hash: Option<H256> =
324 initializer.as_ref().map(|i| H256::from(blake2_256(i.params.as_ref())));
325 let command = Command::Upgrade { impl_address, impl_code_hash, initializer };
326 Self::send(PRIMARY_GOVERNANCE_CHANNEL, command, PaysFee::<T>::No)?;
327
328 Self::deposit_event(Event::<T>::Upgrade {
329 impl_address,
330 impl_code_hash,
331 initializer_params_hash,
332 });
333 Ok(())
334 }
335
336 #[pallet::call_index(1)]
342 #[pallet::weight((T::WeightInfo::set_operating_mode(), DispatchClass::Operational))]
343 pub fn set_operating_mode(origin: OriginFor<T>, mode: OperatingMode) -> DispatchResult {
344 ensure_root(origin)?;
345
346 let command = Command::SetOperatingMode { mode };
347 Self::send(PRIMARY_GOVERNANCE_CHANNEL, command, PaysFee::<T>::No)?;
348
349 Self::deposit_event(Event::<T>::SetOperatingMode { mode });
350 Ok(())
351 }
352
353 #[pallet::call_index(2)]
359 #[pallet::weight((T::WeightInfo::set_pricing_parameters(), DispatchClass::Operational))]
360 pub fn set_pricing_parameters(
361 origin: OriginFor<T>,
362 params: PricingParametersOf<T>,
363 ) -> DispatchResult {
364 ensure_root(origin)?;
365 params.validate().map_err(|_| Error::<T>::InvalidPricingParameters)?;
366 PricingParameters::<T>::put(params.clone());
367
368 let command = Command::SetPricingParameters {
369 exchange_rate: params.exchange_rate.into(),
370 delivery_cost: T::InboundDeliveryCost::get().saturated_into::<u128>(),
371 multiplier: params.multiplier.into(),
372 };
373 Self::send(PRIMARY_GOVERNANCE_CHANNEL, command, PaysFee::<T>::No)?;
374
375 Self::deposit_event(Event::PricingParametersChanged { params });
376 Ok(())
377 }
378
379 #[pallet::call_index(3)]
386 #[pallet::weight(T::WeightInfo::create_agent())]
387 pub fn create_agent(origin: OriginFor<T>) -> DispatchResult {
388 let origin_location: Location = T::SiblingOrigin::ensure_origin(origin)?;
389
390 let (para_id, agent_id) = ensure_sibling::<T>(&origin_location)?;
392
393 ensure!(!Agents::<T>::contains_key(agent_id), Error::<T>::AgentAlreadyCreated);
395 Agents::<T>::insert(agent_id, ());
396
397 let command = Command::CreateAgent { agent_id };
398 let pays_fee = PaysFee::<T>::Yes(sibling_sovereign_account::<T>(para_id));
399 Self::send(SECONDARY_GOVERNANCE_CHANNEL, command, pays_fee)?;
400
401 Self::deposit_event(Event::<T>::CreateAgent {
402 location: Box::new(origin_location),
403 agent_id,
404 });
405 Ok(())
406 }
407
408 #[pallet::call_index(4)]
420 #[pallet::weight(T::WeightInfo::create_channel())]
421 pub fn create_channel(origin: OriginFor<T>, mode: OperatingMode) -> DispatchResult {
422 let origin_location: Location = T::SiblingOrigin::ensure_origin(origin)?;
423
424 let (para_id, agent_id) = ensure_sibling::<T>(&origin_location)?;
426
427 let channel_id: ChannelId = para_id.into();
428
429 ensure!(Agents::<T>::contains_key(agent_id), Error::<T>::NoAgent);
430 ensure!(!Channels::<T>::contains_key(channel_id), Error::<T>::ChannelAlreadyCreated);
431
432 let channel = Channel { agent_id, para_id };
433 Channels::<T>::insert(channel_id, channel);
434
435 let command = Command::CreateChannel { channel_id, agent_id, mode };
436 let pays_fee = PaysFee::<T>::Yes(sibling_sovereign_account::<T>(para_id));
437 Self::send(SECONDARY_GOVERNANCE_CHANNEL, command, pays_fee)?;
438
439 Self::deposit_event(Event::<T>::CreateChannel { channel_id, agent_id });
440 Ok(())
441 }
442
443 #[pallet::call_index(5)]
452 #[pallet::weight(T::WeightInfo::update_channel())]
453 pub fn update_channel(origin: OriginFor<T>, mode: OperatingMode) -> DispatchResult {
454 let origin_location: Location = T::SiblingOrigin::ensure_origin(origin)?;
455
456 let (para_id, _) = ensure_sibling::<T>(&origin_location)?;
458
459 let channel_id: ChannelId = para_id.into();
460
461 ensure!(Channels::<T>::contains_key(channel_id), Error::<T>::NoChannel);
462
463 let command = Command::UpdateChannel { channel_id, mode };
464 let pays_fee = PaysFee::<T>::Partial(sibling_sovereign_account::<T>(para_id));
465
466 Self::send(channel_id, command, pays_fee)?;
468
469 Self::deposit_event(Event::<T>::UpdateChannel { channel_id, mode });
470 Ok(())
471 }
472
473 #[pallet::call_index(6)]
482 #[pallet::weight(T::WeightInfo::force_update_channel())]
483 pub fn force_update_channel(
484 origin: OriginFor<T>,
485 channel_id: ChannelId,
486 mode: OperatingMode,
487 ) -> DispatchResult {
488 ensure_root(origin)?;
489
490 ensure!(Channels::<T>::contains_key(channel_id), Error::<T>::NoChannel);
491
492 let command = Command::UpdateChannel { channel_id, mode };
493 Self::send(PRIMARY_GOVERNANCE_CHANNEL, command, PaysFee::<T>::No)?;
494
495 Self::deposit_event(Event::<T>::UpdateChannel { channel_id, mode });
496 Ok(())
497 }
498
499 #[pallet::call_index(7)]
505 #[pallet::weight(T::WeightInfo::transfer_native_from_agent())]
506 pub fn transfer_native_from_agent(
507 origin: OriginFor<T>,
508 recipient: H160,
509 amount: u128,
510 ) -> DispatchResult {
511 let origin_location: Location = T::SiblingOrigin::ensure_origin(origin)?;
512
513 let (para_id, agent_id) = ensure_sibling::<T>(&origin_location)?;
515
516 let pays_fee = PaysFee::<T>::Partial(sibling_sovereign_account::<T>(para_id));
519
520 Self::do_transfer_native_from_agent(
521 agent_id,
522 para_id.into(),
523 recipient,
524 amount,
525 pays_fee,
526 )
527 }
528
529 #[pallet::call_index(8)]
540 #[pallet::weight(T::WeightInfo::force_transfer_native_from_agent())]
541 pub fn force_transfer_native_from_agent(
542 origin: OriginFor<T>,
543 location: Box<VersionedLocation>,
544 recipient: H160,
545 amount: u128,
546 ) -> DispatchResult {
547 ensure_root(origin)?;
548
549 let location: Location =
551 (*location).try_into().map_err(|_| Error::<T>::UnsupportedLocationVersion)?;
552 let (_, agent_id) =
553 ensure_sibling::<T>(&location).map_err(|_| Error::<T>::InvalidLocation)?;
554
555 let pays_fee = PaysFee::<T>::No;
556
557 Self::do_transfer_native_from_agent(
558 agent_id,
559 PRIMARY_GOVERNANCE_CHANNEL,
560 recipient,
561 amount,
562 pays_fee,
563 )
564 }
565
566 #[pallet::call_index(9)]
580 #[pallet::weight((T::WeightInfo::set_token_transfer_fees(), DispatchClass::Operational))]
581 pub fn set_token_transfer_fees(
582 origin: OriginFor<T>,
583 create_asset_xcm: u128,
584 transfer_asset_xcm: u128,
585 register_token: U256,
586 ) -> DispatchResult {
587 ensure_root(origin)?;
588
589 ensure!(
592 create_asset_xcm > 0 && transfer_asset_xcm > 0 && register_token > meth(100),
593 Error::<T>::InvalidTokenTransferFees
594 );
595
596 let command = Command::SetTokenTransferFees {
597 create_asset_xcm,
598 transfer_asset_xcm,
599 register_token,
600 };
601 Self::send(PRIMARY_GOVERNANCE_CHANNEL, command, PaysFee::<T>::No)?;
602
603 Self::deposit_event(Event::<T>::SetTokenTransferFees {
604 create_asset_xcm,
605 transfer_asset_xcm,
606 register_token,
607 });
608 Ok(())
609 }
610
611 #[pallet::call_index(10)]
620 #[pallet::weight(T::WeightInfo::register_token())]
621 pub fn register_token(
622 origin: OriginFor<T>,
623 location: Box<VersionedLocation>,
624 metadata: AssetMetadata,
625 ) -> DispatchResultWithPostInfo {
626 ensure_root(origin)?;
627
628 let location: Location =
629 (*location).try_into().map_err(|_| Error::<T>::UnsupportedLocationVersion)?;
630
631 Self::do_register_token(&location, metadata, PaysFee::<T>::No)?;
632
633 Ok(PostDispatchInfo {
634 actual_weight: Some(T::WeightInfo::register_token()),
635 pays_fee: Pays::No,
636 })
637 }
638 }
639
640 impl<T: Config> Pallet<T> {
641 fn send(channel_id: ChannelId, command: Command, pays_fee: PaysFee<T>) -> DispatchResult {
643 let message = Message { id: None, channel_id, command };
644 let (ticket, fee) =
645 T::OutboundQueue::validate(&message).map_err(|err| Error::<T>::Send(err))?;
646
647 let payment = match pays_fee {
648 PaysFee::Yes(account) => Some((account, fee.total())),
649 PaysFee::Partial(account) => Some((account, fee.local)),
650 PaysFee::No => None,
651 };
652
653 if let Some((payer, fee)) = payment {
654 T::Token::transfer(
655 &payer,
656 &T::TreasuryAccount::get(),
657 fee,
658 Preservation::Preserve,
659 )?;
660 }
661
662 T::OutboundQueue::deliver(ticket).map_err(|err| Error::<T>::Send(err))?;
663 Ok(())
664 }
665
666 pub fn do_transfer_native_from_agent(
669 agent_id: H256,
670 channel_id: ChannelId,
671 recipient: H160,
672 amount: u128,
673 pays_fee: PaysFee<T>,
674 ) -> DispatchResult {
675 ensure!(Agents::<T>::contains_key(agent_id), Error::<T>::NoAgent);
676
677 let command = Command::TransferNativeFromAgent { agent_id, recipient, amount };
678 Self::send(channel_id, command, pays_fee)?;
679
680 Self::deposit_event(Event::<T>::TransferNativeFromAgent {
681 agent_id,
682 recipient,
683 amount,
684 });
685 Ok(())
686 }
687
688 pub fn initialize(para_id: ParaId, asset_hub_para_id: ParaId) -> Result<(), DispatchError> {
690 let asset_hub_location: Location =
692 ParentThen(Parachain(asset_hub_para_id.into()).into()).into();
693 let asset_hub_agent_id = agent_id_of::<T>(&asset_hub_location)?;
694 let asset_hub_channel_id: ChannelId = asset_hub_para_id.into();
695 Agents::<T>::insert(asset_hub_agent_id, ());
696 Channels::<T>::insert(
697 asset_hub_channel_id,
698 Channel { agent_id: asset_hub_agent_id, para_id: asset_hub_para_id },
699 );
700
701 let bridge_hub_agent_id = agent_id_of::<T>(&Location::here())?;
703 Agents::<T>::insert(bridge_hub_agent_id, ());
705
706 Channels::<T>::insert(
708 PRIMARY_GOVERNANCE_CHANNEL,
709 Channel { agent_id: bridge_hub_agent_id, para_id },
710 );
711
712 Channels::<T>::insert(
714 SECONDARY_GOVERNANCE_CHANNEL,
715 Channel { agent_id: bridge_hub_agent_id, para_id },
716 );
717
718 Ok(())
719 }
720
721 pub(crate) fn is_initialized() -> bool {
723 let primary_exists = Channels::<T>::contains_key(PRIMARY_GOVERNANCE_CHANNEL);
724 let secondary_exists = Channels::<T>::contains_key(SECONDARY_GOVERNANCE_CHANNEL);
725 primary_exists && secondary_exists
726 }
727
728 pub(crate) fn do_register_token(
729 location: &Location,
730 metadata: AssetMetadata,
731 pays_fee: PaysFee<T>,
732 ) -> Result<(), DispatchError> {
733 let ethereum_location = T::EthereumLocation::get();
734 let location = location
736 .clone()
737 .reanchored(ðereum_location, &T::UniversalLocation::get())
738 .map_err(|_| Error::<T>::LocationConversionFailed)?;
739
740 let token_id = TokenIdOf::convert_location(&location)
741 .ok_or(Error::<T>::LocationConversionFailed)?;
742
743 if !ForeignToNativeId::<T>::contains_key(token_id) {
744 NativeToForeignId::<T>::insert(location.clone(), token_id);
745 ForeignToNativeId::<T>::insert(token_id, location.clone());
746 }
747
748 let command = Command::RegisterForeignToken {
749 token_id,
750 name: metadata.name.into_inner(),
751 symbol: metadata.symbol.into_inner(),
752 decimals: metadata.decimals,
753 };
754 Self::send(SECONDARY_GOVERNANCE_CHANNEL, command, pays_fee)?;
755
756 Self::deposit_event(Event::<T>::RegisterToken {
757 location: location.clone().into(),
758 foreign_token_id: token_id,
759 });
760
761 Ok(())
762 }
763 }
764
765 impl<T: Config> StaticLookup for Pallet<T> {
766 type Source = ChannelId;
767 type Target = Channel;
768 fn lookup(channel_id: Self::Source) -> Option<Self::Target> {
769 Channels::<T>::get(channel_id)
770 }
771 }
772
773 impl<T: Config> Contains<ChannelId> for Pallet<T> {
774 fn contains(channel_id: &ChannelId) -> bool {
775 Channels::<T>::get(channel_id).is_some()
776 }
777 }
778
779 impl<T: Config> Get<PricingParametersOf<T>> for Pallet<T> {
780 fn get() -> PricingParametersOf<T> {
781 PricingParameters::<T>::get()
782 }
783 }
784
785 impl<T: Config> MaybeEquivalence<TokenId, Location> for Pallet<T> {
786 fn convert(foreign_id: &TokenId) -> Option<Location> {
787 ForeignToNativeId::<T>::get(foreign_id)
788 }
789 fn convert_back(location: &Location) -> Option<TokenId> {
790 NativeToForeignId::<T>::get(location)
791 }
792 }
793}