snowbridge_pallet_system/
lib.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
3//! Governance API for controlling the Ethereum side of the bridge
4//!
5//! # Extrinsics
6//!
7//! ## Agents
8//!
9//! Agents are smart contracts on Ethereum that act as proxies for consensus systems on Polkadot
10//! networks.
11//!
12//! * [`Call::create_agent`]: Create agent for a sibling parachain
13//! * [`Call::transfer_native_from_agent`]: Withdraw ether from an agent
14//!
15//! The `create_agent` extrinsic should be called via an XCM `Transact` instruction from the sibling
16//! parachain.
17//!
18//! ## Channels
19//!
20//! Each sibling parachain has its own dedicated messaging channel for sending and receiving
21//! messages. As a prerequisite to creating a channel, the sibling should have already created
22//! an agent using the `create_agent` extrinsic.
23//!
24//! * [`Call::create_channel`]: Create channel for a sibling
25//! * [`Call::update_channel`]: Update a channel for a sibling
26//!
27//! ## Governance
28//!
29//! Only Polkadot governance itself can call these extrinsics. Delivery fees are waived.
30//!
31//! * [`Call::upgrade`]`: Upgrade the gateway contract
32//! * [`Call::set_operating_mode`]: Update the operating mode of the gateway contract
33//! * [`Call::force_update_channel`]: Allow root to update a channel for a sibling
34//! * [`Call::force_transfer_native_from_agent`]: Allow root to withdraw ether from an agent
35//!
36//! Typically, Polkadot governance will use the `force_transfer_native_from_agent` and
37//! `force_update_channel` and extrinsics to manage agents and channels for system parachains.
38//!
39//! ## Polkadot-native tokens on Ethereum
40//!
41//! Tokens deposited on AssetHub pallet can be bridged to Ethereum as wrapped ERC20 tokens. As a
42//! prerequisite, the token should be registered first.
43//!
44//! * [`Call::register_token`]: Register a token location as a wrapped ERC20 contract on Ethereum.
45#![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
96/// Ensure origin location is a sibling
97fn 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
110/// Hash the location to produce an agent id
111pub 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/// Whether a fee should be withdrawn to an account for sending an outbound message
124#[derive(Clone, PartialEq, RuntimeDebug)]
125pub enum PaysFee<T>
126where
127	T: Config,
128{
129	/// Fully charge includes (local + remote fee)
130	Yes(AccountIdOf<T>),
131	/// Partially charge includes local fee only
132	Partial(AccountIdOf<T>),
133	/// No charge
134	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		/// Send messages to Ethereum
153		type OutboundQueue: SendMessage<Balance = BalanceOf<Self>>;
154
155		/// Origin check for XCM locations that can create agents
156		type SiblingOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = Location>;
157
158		/// Converts Location to AgentId
159		type AgentIdOf: ConvertLocation<AgentId>;
160
161		/// Token reserved for control operations
162		type Token: Mutate<Self::AccountId>;
163
164		/// TreasuryAccount to collect fees
165		#[pallet::constant]
166		type TreasuryAccount: Get<Self::AccountId>;
167
168		/// Number of decimal places of local currency
169		type DefaultPricingParameters: Get<PricingParametersOf<Self>>;
170
171		/// Cost of delivering a message from Ethereum
172		#[pallet::constant]
173		type InboundDeliveryCost: Get<BalanceOf<Self>>;
174
175		type WeightInfo: WeightInfo;
176
177		/// This chain's Universal Location.
178		type UniversalLocation: Get<InteriorLocation>;
179
180		// The bridges configured Ethereum location
181		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		/// An Upgrade message was sent to the Gateway
191		Upgrade {
192			impl_address: H160,
193			impl_code_hash: H256,
194			initializer_params_hash: Option<H256>,
195		},
196		/// An CreateAgent message was sent to the Gateway
197		CreateAgent {
198			location: Box<Location>,
199			agent_id: AgentId,
200		},
201		/// An CreateChannel message was sent to the Gateway
202		CreateChannel {
203			channel_id: ChannelId,
204			agent_id: AgentId,
205		},
206		/// An UpdateChannel message was sent to the Gateway
207		UpdateChannel {
208			channel_id: ChannelId,
209			mode: OperatingMode,
210		},
211		/// An SetOperatingMode message was sent to the Gateway
212		SetOperatingMode {
213			mode: OperatingMode,
214		},
215		/// An TransferNativeFromAgent message was sent to the Gateway
216		TransferNativeFromAgent {
217			agent_id: AgentId,
218			recipient: H160,
219			amount: u128,
220		},
221		/// A SetTokenTransferFees message was sent to the Gateway
222		SetTokenTransferFees {
223			create_asset_xcm: u128,
224			transfer_asset_xcm: u128,
225			register_token: U256,
226		},
227		PricingParametersChanged {
228			params: PricingParametersOf<T>,
229		},
230		/// Register Polkadot-native token as a wrapped ERC20 token on Ethereum
231		RegisterToken {
232			/// Location of Polkadot-native token
233			location: VersionedLocation,
234			/// ID of Polkadot-native token on Ethereum
235			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	/// The set of registered agents
255	#[pallet::storage]
256	#[pallet::getter(fn agents)]
257	pub type Agents<T: Config> = StorageMap<_, Twox64Concat, AgentId, (), OptionQuery>;
258
259	/// The set of registered channels
260	#[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	/// Lookup table for foreign token ID to native location relative to ethereum
270	#[pallet::storage]
271	pub type ForeignToNativeId<T: Config> =
272		StorageMap<_, Blake2_128Concat, TokenId, xcm::v5::Location, OptionQuery>;
273
274	/// Lookup table for native location relative to ethereum to foreign token ID
275	#[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		// Own parachain id
283		pub para_id: ParaId,
284		// AssetHub's parachain id
285		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		/// Sends command to the Gateway contract to upgrade itself with a new implementation
300		/// contract
301		///
302		/// Fee required: No
303		///
304		/// - `origin`: Must be `Root`.
305		/// - `impl_address`: The address of the implementation contract.
306		/// - `impl_code_hash`: The codehash of the implementation contract.
307		/// - `initializer`: Optionally call an initializer on the implementation contract.
308		#[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		/// Sends a message to the Gateway contract to change its operating mode
337		///
338		/// Fee required: No
339		///
340		/// - `origin`: Must be `Location`
341		#[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		/// Set pricing parameters on both sides of the bridge
354		///
355		/// Fee required: No
356		///
357		/// - `origin`: Must be root
358		#[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		/// Sends a command to the Gateway contract to instantiate a new agent contract representing
380		/// `origin`.
381		///
382		/// Fee required: Yes
383		///
384		/// - `origin`: Must be `Location` of a sibling parachain
385		#[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			// Ensure that origin location is some consensus system on a sibling parachain
391			let (para_id, agent_id) = ensure_sibling::<T>(&origin_location)?;
392
393			// Record the agent id or fail if it has already been created
394			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		/// Sends a message to the Gateway contract to create a new channel representing `origin`
409		///
410		/// Fee required: Yes
411		///
412		/// This extrinsic is permissionless, so a fee is charged to prevent spamming and pay
413		/// for execution costs on the remote side.
414		///
415		/// The message is sent over the bridge on BridgeHub's own channel to the Gateway.
416		///
417		/// - `origin`: Must be `Location`
418		/// - `mode`: Initial operating mode of the channel
419		#[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			// Ensure that origin location is a sibling parachain
425			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		/// Sends a message to the Gateway contract to update a channel configuration
444		///
445		/// The origin must already have a channel initialized, as this message is sent over it.
446		///
447		/// A partial fee will be charged for local processing only.
448		///
449		/// - `origin`: Must be `Location`
450		/// - `mode`: Initial operating mode of the channel
451		#[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			// Ensure that origin location is a sibling parachain
457			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			// Parachains send the update message on their own channel
467			Self::send(channel_id, command, pays_fee)?;
468
469			Self::deposit_event(Event::<T>::UpdateChannel { channel_id, mode });
470			Ok(())
471		}
472
473		/// Sends a message to the Gateway contract to update an arbitrary channel
474		///
475		/// Fee required: No
476		///
477		/// - `origin`: Must be root
478		/// - `channel_id`: ID of channel
479		/// - `mode`: Initial operating mode of the channel
480		/// - `outbound_fee`: Fee charged to users for sending outbound messages to Polkadot
481		#[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		/// Sends a message to the Gateway contract to transfer ether from an agent to `recipient`.
500		///
501		/// A partial fee will be charged for local processing only.
502		///
503		/// - `origin`: Must be `Location`
504		#[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			// Ensure that origin location is some consensus system on a sibling parachain
514			let (para_id, agent_id) = ensure_sibling::<T>(&origin_location)?;
515
516			// Since the origin is also the owner of the channel, they only need to pay
517			// the local processing fee.
518			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		/// Sends a message to the Gateway contract to transfer ether from an agent to `recipient`.
530		///
531		/// Privileged. Can only be called by root.
532		///
533		/// Fee required: No
534		///
535		/// - `origin`: Must be root
536		/// - `location`: Location used to resolve the agent
537		/// - `recipient`: Recipient of funds
538		/// - `amount`: Amount to transfer
539		#[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			// Ensure that location is some consensus system on a sibling parachain
550			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		/// Sends a message to the Gateway contract to update fee related parameters for
567		/// token transfers.
568		///
569		/// Privileged. Can only be called by root.
570		///
571		/// Fee required: No
572		///
573		/// - `origin`: Must be root
574		/// - `create_asset_xcm`: The XCM execution cost for creating a new asset class on AssetHub,
575		///   in DOT
576		/// - `transfer_asset_xcm`: The XCM execution cost for performing a reserve transfer on
577		///   AssetHub, in DOT
578		/// - `register_token`: The Ether fee for registering a new token, to discourage spamming
579		#[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			// Basic validation of new costs. Particularly for token registration, we want to ensure
590			// its relatively expensive to discourage spamming. Like at least 100 USD.
591			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		/// Registers a Polkadot-native token as a wrapped ERC20 token on Ethereum.
612		/// Privileged. Can only be called by root.
613		///
614		/// Fee required: No
615		///
616		/// - `origin`: Must be root
617		/// - `location`: Location of the asset (relative to this chain)
618		/// - `metadata`: Metadata to include in the instantiated ERC20 contract on Ethereum
619		#[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		/// Send `command` to the Gateway on the Channel identified by `channel_id`
642		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		/// Issue a `Command::TransferNativeFromAgent` command. The command will be sent on the
667		/// channel `channel_id`
668		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		/// Initializes agents and channels.
689		pub fn initialize(para_id: ParaId, asset_hub_para_id: ParaId) -> Result<(), DispatchError> {
690			// Asset Hub
691			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			// Governance channels
702			let bridge_hub_agent_id = agent_id_of::<T>(&Location::here())?;
703			// Agent for BridgeHub
704			Agents::<T>::insert(bridge_hub_agent_id, ());
705
706			// Primary governance channel
707			Channels::<T>::insert(
708				PRIMARY_GOVERNANCE_CHANNEL,
709				Channel { agent_id: bridge_hub_agent_id, para_id },
710			);
711
712			// Secondary governance channel
713			Channels::<T>::insert(
714				SECONDARY_GOVERNANCE_CHANNEL,
715				Channel { agent_id: bridge_hub_agent_id, para_id },
716			);
717
718			Ok(())
719		}
720
721		/// Checks if the pallet has been initialized.
722		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			// reanchor to Ethereum context
735			let location = location
736				.clone()
737				.reanchored(&ethereum_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}