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	#[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		/// Send messages to Ethereum
154		type OutboundQueue: SendMessage<Balance = BalanceOf<Self>>;
155
156		/// Origin check for XCM locations that can create agents
157		type SiblingOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = Location>;
158
159		/// Converts Location to AgentId
160		type AgentIdOf: ConvertLocation<AgentId>;
161
162		/// Token reserved for control operations
163		type Token: Mutate<Self::AccountId>;
164
165		/// TreasuryAccount to collect fees
166		#[pallet::constant]
167		type TreasuryAccount: Get<Self::AccountId>;
168
169		/// Number of decimal places of local currency
170		type DefaultPricingParameters: Get<PricingParametersOf<Self>>;
171
172		/// Cost of delivering a message from Ethereum
173		#[pallet::constant]
174		type InboundDeliveryCost: Get<BalanceOf<Self>>;
175
176		type WeightInfo: WeightInfo;
177
178		/// This chain's Universal Location.
179		type UniversalLocation: Get<InteriorLocation>;
180
181		// The bridges configured Ethereum location
182		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		/// An Upgrade message was sent to the Gateway
192		Upgrade {
193			impl_address: H160,
194			impl_code_hash: H256,
195			initializer_params_hash: Option<H256>,
196		},
197		/// An CreateAgent message was sent to the Gateway
198		CreateAgent {
199			location: Box<Location>,
200			agent_id: AgentId,
201		},
202		/// An CreateChannel message was sent to the Gateway
203		CreateChannel {
204			channel_id: ChannelId,
205			agent_id: AgentId,
206		},
207		/// An UpdateChannel message was sent to the Gateway
208		UpdateChannel {
209			channel_id: ChannelId,
210			mode: OperatingMode,
211		},
212		/// An SetOperatingMode message was sent to the Gateway
213		SetOperatingMode {
214			mode: OperatingMode,
215		},
216		/// An TransferNativeFromAgent message was sent to the Gateway
217		TransferNativeFromAgent {
218			agent_id: AgentId,
219			recipient: H160,
220			amount: u128,
221		},
222		/// A SetTokenTransferFees message was sent to the Gateway
223		SetTokenTransferFees {
224			create_asset_xcm: u128,
225			transfer_asset_xcm: u128,
226			register_token: U256,
227		},
228		PricingParametersChanged {
229			params: PricingParametersOf<T>,
230		},
231		/// Register Polkadot-native token as a wrapped ERC20 token on Ethereum
232		RegisterToken {
233			/// Location of Polkadot-native token
234			location: VersionedLocation,
235			/// ID of Polkadot-native token on Ethereum
236			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	/// The set of registered agents
256	#[pallet::storage]
257	#[pallet::getter(fn agents)]
258	pub type Agents<T: Config> = StorageMap<_, Twox64Concat, AgentId, (), OptionQuery>;
259
260	/// The set of registered channels
261	#[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	/// Lookup table for foreign token ID to native location relative to ethereum
271	#[pallet::storage]
272	pub type ForeignToNativeId<T: Config> =
273		StorageMap<_, Blake2_128Concat, TokenId, xcm::v5::Location, OptionQuery>;
274
275	/// Lookup table for native location relative to ethereum to foreign token ID
276	#[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		// Own parachain id
284		pub para_id: ParaId,
285		// AssetHub's parachain id
286		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		/// Sends command to the Gateway contract to upgrade itself with a new implementation
301		/// contract
302		///
303		/// Fee required: No
304		///
305		/// - `origin`: Must be `Root`.
306		/// - `impl_address`: The address of the implementation contract.
307		/// - `impl_code_hash`: The codehash of the implementation contract.
308		/// - `initializer`: Optionally call an initializer on the implementation contract.
309		#[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		/// Sends a message to the Gateway contract to change its operating mode
338		///
339		/// Fee required: No
340		///
341		/// - `origin`: Must be `Location`
342		#[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		/// Set pricing parameters on both sides of the bridge
355		///
356		/// Fee required: No
357		///
358		/// - `origin`: Must be root
359		#[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		/// Sends a command to the Gateway contract to instantiate a new agent contract representing
381		/// `origin`.
382		///
383		/// Fee required: Yes
384		///
385		/// - `origin`: Must be `Location` of a sibling parachain
386		#[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			// Ensure that origin location is some consensus system on a sibling parachain
392			let (para_id, agent_id) = ensure_sibling::<T>(&origin_location)?;
393
394			// Record the agent id or fail if it has already been created
395			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		/// Sends a message to the Gateway contract to create a new channel representing `origin`
410		///
411		/// Fee required: Yes
412		///
413		/// This extrinsic is permissionless, so a fee is charged to prevent spamming and pay
414		/// for execution costs on the remote side.
415		///
416		/// The message is sent over the bridge on BridgeHub's own channel to the Gateway.
417		///
418		/// - `origin`: Must be `Location`
419		/// - `mode`: Initial operating mode of the channel
420		#[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			// Ensure that origin location is a sibling parachain
426			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		/// Sends a message to the Gateway contract to update a channel configuration
445		///
446		/// The origin must already have a channel initialized, as this message is sent over it.
447		///
448		/// A partial fee will be charged for local processing only.
449		///
450		/// - `origin`: Must be `Location`
451		/// - `mode`: Initial operating mode of the channel
452		#[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			// Ensure that origin location is a sibling parachain
458			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			// Parachains send the update message on their own channel
468			Self::send(channel_id, command, pays_fee)?;
469
470			Self::deposit_event(Event::<T>::UpdateChannel { channel_id, mode });
471			Ok(())
472		}
473
474		/// Sends a message to the Gateway contract to update an arbitrary channel
475		///
476		/// Fee required: No
477		///
478		/// - `origin`: Must be root
479		/// - `channel_id`: ID of channel
480		/// - `mode`: Initial operating mode of the channel
481		/// - `outbound_fee`: Fee charged to users for sending outbound messages to Polkadot
482		#[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		/// Sends a message to the Gateway contract to transfer ether from an agent to `recipient`.
501		///
502		/// A partial fee will be charged for local processing only.
503		///
504		/// - `origin`: Must be `Location`
505		#[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			// Ensure that origin location is some consensus system on a sibling parachain
515			let (para_id, agent_id) = ensure_sibling::<T>(&origin_location)?;
516
517			// Since the origin is also the owner of the channel, they only need to pay
518			// the local processing fee.
519			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		/// Sends a message to the Gateway contract to transfer ether from an agent to `recipient`.
531		///
532		/// Privileged. Can only be called by root.
533		///
534		/// Fee required: No
535		///
536		/// - `origin`: Must be root
537		/// - `location`: Location used to resolve the agent
538		/// - `recipient`: Recipient of funds
539		/// - `amount`: Amount to transfer
540		#[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			// Ensure that location is some consensus system on a sibling parachain
551			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		/// Sends a message to the Gateway contract to update fee related parameters for
568		/// token transfers.
569		///
570		/// Privileged. Can only be called by root.
571		///
572		/// Fee required: No
573		///
574		/// - `origin`: Must be root
575		/// - `create_asset_xcm`: The XCM execution cost for creating a new asset class on AssetHub,
576		///   in DOT
577		/// - `transfer_asset_xcm`: The XCM execution cost for performing a reserve transfer on
578		///   AssetHub, in DOT
579		/// - `register_token`: The Ether fee for registering a new token, to discourage spamming
580		#[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			// Basic validation of new costs. Particularly for token registration, we want to ensure
591			// its relatively expensive to discourage spamming. Like at least 100 USD.
592			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		/// Registers a Polkadot-native token as a wrapped ERC20 token on Ethereum.
613		/// Privileged. Can only be called by root.
614		///
615		/// Fee required: No
616		///
617		/// - `origin`: Must be root
618		/// - `location`: Location of the asset (relative to this chain)
619		/// - `metadata`: Metadata to include in the instantiated ERC20 contract on Ethereum
620		#[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		/// Send `command` to the Gateway on the Channel identified by `channel_id`
643		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		/// Issue a `Command::TransferNativeFromAgent` command. The command will be sent on the
668		/// channel `channel_id`
669		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		/// Initializes agents and channels.
690		pub fn initialize(para_id: ParaId, asset_hub_para_id: ParaId) -> Result<(), DispatchError> {
691			// Asset Hub
692			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			// Governance channels
703			let bridge_hub_agent_id = agent_id_of::<T>(&Location::here())?;
704			// Agent for BridgeHub
705			Agents::<T>::insert(bridge_hub_agent_id, ());
706
707			// Primary governance channel
708			Channels::<T>::insert(
709				PRIMARY_GOVERNANCE_CHANNEL,
710				Channel { agent_id: bridge_hub_agent_id, para_id },
711			);
712
713			// Secondary governance channel
714			Channels::<T>::insert(
715				SECONDARY_GOVERNANCE_CHANNEL,
716				Channel { agent_id: bridge_hub_agent_id, para_id },
717			);
718
719			Ok(())
720		}
721
722		/// Checks if the pallet has been initialized.
723		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			// reanchor to Ethereum context
736			let location = location
737				.clone()
738				.reanchored(&ethereum_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}