Skip to main content

pallet_token_gateway/
lib.rs

1// Copyright (C) Polytope Labs Ltd.
2// SPDX-License-Identifier: Apache-2.0
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// 	http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16#![doc = include_str!("../README.md")]
17#![cfg_attr(not(feature = "std"), no_std)]
18
19extern crate alloc;
20use alloc::format;
21
22pub mod impls;
23pub mod types;
24
25mod benchmarking;
26mod weights;
27use crate::impls::{convert_to_balance, convert_to_erc20};
28use alloy_sol_types::SolValue;
29use anyhow::anyhow;
30use codec::{Decode, Encode};
31use frame_support::{
32	ensure,
33	traits::{
34		fungibles::{self, Mutate},
35		tokens::Preservation,
36		Currency, ExistenceRequirement,
37	},
38};
39use polkadot_sdk::*;
40pub use weights::WeightInfo;
41
42use ismp::{
43	events::Meta,
44	router::{PostRequest, Request, Response, Timeout},
45};
46
47use sp_core::{Get, H160, U256};
48use sp_runtime::{traits::Dispatchable, MultiSignature};
49use token_gateway_primitives::PALLET_TOKEN_GATEWAY_ID;
50use types::{AssetId, Body, BodyWithCall, EvmToSubstrate, RequestBody, SubstrateCalldata};
51
52use alloc::{string::ToString, vec, vec::Vec};
53use frame_system::RawOrigin;
54use ismp::module::IsmpModule;
55use polkadot_sdk::sp_runtime::Weight;
56use primitive_types::H256;
57
58// Re-export pallet items so that they can be accessed from the crate namespace.
59pub use pallet::*;
60
61const ETHEREUM_MESSAGE_PREFIX: &'static str = "\x19Ethereum Signed Message:\n";
62
63#[frame_support::pallet]
64pub mod pallet {
65	use alloc::collections::BTreeMap;
66	use types::{AssetRegistration, PrecisionUpdate, TeleportParams};
67
68	use super::*;
69	use frame_support::{
70		pallet_prelude::*,
71		traits::{
72			tokens::{Fortitude, Precision, Preservation},
73			Currency, ExistenceRequirement, WithdrawReasons,
74		},
75	};
76	use frame_system::pallet_prelude::*;
77	use ismp::{
78		dispatcher::{DispatchPost, DispatchRequest, FeeMetadata, IsmpDispatcher},
79		host::StateMachine,
80	};
81
82	#[pallet::pallet]
83	#[pallet::without_storage_info]
84	pub struct Pallet<T>(_);
85
86	/// The pallet's configuration trait.
87	#[pallet::config]
88	pub trait Config:
89		polkadot_sdk::frame_system::Config + pallet_ismp::Config + pallet_hyperbridge::Config
90	{
91		/// The [`IsmpDispatcher`] for dispatching cross-chain requests
92		type Dispatcher: IsmpDispatcher<Account = Self::AccountId, Balance = Self::Balance>;
93
94		/// A currency implementation for interacting with the native asset
95		type NativeCurrency: Currency<Self::AccountId>;
96
97		/// A funded account that would be set as asset admin and also make payments for asset
98		/// creation
99		type AssetAdmin: Get<Self::AccountId>;
100
101		/// Account that is authorized to create and update assets.
102		type CreateOrigin: EnsureOrigin<Self::RuntimeOrigin>;
103
104		/// Fungible asset implementation
105		type Assets: fungibles::Mutate<Self::AccountId>
106			+ fungibles::metadata::Inspect<Self::AccountId>;
107
108		/// The native asset ID
109		type NativeAssetId: Get<AssetId<Self>>;
110
111		/// The decimals of the native currency
112		#[pallet::constant]
113		type Decimals: Get<u8>;
114
115		/// A trait that converts an evm address to a substrate account
116		/// Used for authenticating incoming cross-chain runtime calls.
117		type EvmToSubstrate: EvmToSubstrate<Self>;
118
119		/// Weight information for extrinsics in this pallet
120		type WeightInfo: WeightInfo;
121	}
122
123	/// Assets supported by this instance of token gateway
124	/// A map of the local asset id to the token gateway asset id
125	#[pallet::storage]
126	pub type SupportedAssets<T: Config> =
127		StorageMap<_, Blake2_128Concat, AssetId<T>, H256, OptionQuery>;
128
129	/// Assets that originate from this chain
130	#[pallet::storage]
131	pub type NativeAssets<T: Config> =
132		StorageMap<_, Blake2_128Concat, AssetId<T>, bool, ValueQuery>;
133
134	/// Assets supported by this instance of token gateway
135	/// A map of the token gateway asset id to the local asset id
136	#[pallet::storage]
137	pub type LocalAssets<T: Config> = StorageMap<_, Identity, H256, AssetId<T>, OptionQuery>;
138
139	/// The decimals used by the EVM counterpart of this asset
140	#[pallet::storage]
141	pub type Precisions<T: Config> = StorageDoubleMap<
142		_,
143		Blake2_128Concat,
144		AssetId<T>,
145		Blake2_128Concat,
146		StateMachine,
147		u8,
148		OptionQuery,
149	>;
150
151	/// The token gateway adresses on different chains
152	#[pallet::storage]
153	pub type TokenGatewayAddresses<T: Config> =
154		StorageMap<_, Blake2_128Concat, StateMachine, Vec<u8>, OptionQuery>;
155
156	/// Pallet events that functions in this pallet can emit.
157	#[pallet::event]
158	#[pallet::generate_deposit(pub(super) fn deposit_event)]
159	pub enum Event<T: Config> {
160		/// An asset has been teleported
161		AssetTeleported {
162			/// Source account
163			from: T::AccountId,
164			/// beneficiary account on destination
165			to: H256,
166			/// Amount transferred
167			amount: <T::NativeCurrency as Currency<T::AccountId>>::Balance,
168			/// Destination chain
169			dest: StateMachine,
170			/// Request commitment
171			commitment: H256,
172		},
173
174		/// An asset has been received and transferred to the beneficiary's account
175		AssetReceived {
176			/// beneficiary account on relaychain
177			beneficiary: T::AccountId,
178			/// Amount transferred
179			amount: <<T as Config>::NativeCurrency as Currency<T::AccountId>>::Balance,
180			/// Destination chain
181			source: StateMachine,
182		},
183
184		/// An asset has been refunded and transferred to the beneficiary's account
185		AssetRefunded {
186			/// beneficiary account on relaychain
187			beneficiary: T::AccountId,
188			/// Amount transferred
189			amount: <<T as Config>::NativeCurrency as Currency<T::AccountId>>::Balance,
190			/// Destination chain
191			source: StateMachine,
192		},
193
194		/// ERC6160 asset creation request dispatched to hyperbridge
195		ERC6160AssetRegistrationDispatched {
196			/// Request commitment
197			commitment: H256,
198		},
199		/// An asset has been registered locally
200		AssetRegisteredLocally {
201			/// The local asset id
202			local_id: AssetId<T>,
203			/// The token gateway asset id
204			asset_id: H256,
205		},
206	}
207
208	/// Errors that can be returned by this pallet.
209	#[pallet::error]
210	pub enum Error<T> {
211		/// A asset that has not been registered
212		UnregisteredAsset,
213		/// Error while teleporting asset
214		AssetTeleportError,
215		/// Coprocessor was not configured in the runtime
216		CoprocessorNotConfigured,
217		/// Asset or update Dispatch Error
218		DispatchError,
219		/// Asset Id creation failed
220		AssetCreationError,
221		/// Asset decimals not found
222		AssetDecimalsNotFound,
223		/// Protocol Params have not been initialized
224		NotInitialized,
225		/// Unknown Asset
226		UnknownAsset,
227		/// Only root or asset owner can update asset
228		NotAssetOwner,
229	}
230
231	#[pallet::call]
232	impl<T: Config> Pallet<T>
233	where
234		<T as frame_system::Config>::AccountId: From<[u8; 32]>,
235		u128: From<<<T as Config>::NativeCurrency as Currency<T::AccountId>>::Balance>,
236		<T as pallet_ismp::Config>::Balance:
237			From<<<T as Config>::NativeCurrency as Currency<T::AccountId>>::Balance>,
238		<<T as Config>::Assets as fungibles::Inspect<T::AccountId>>::Balance:
239			From<<<T as Config>::NativeCurrency as Currency<T::AccountId>>::Balance>,
240		<<T as Config>::Assets as fungibles::Inspect<T::AccountId>>::Balance: From<u128>,
241		[u8; 32]: From<<T as frame_system::Config>::AccountId>,
242	{
243		/// Teleports a registered asset
244		/// locks the asset and dispatches a request to token gateway on the destination
245		#[pallet::call_index(0)]
246		#[pallet::weight(T::WeightInfo::teleport())]
247		pub fn teleport(
248			origin: OriginFor<T>,
249			params: TeleportParams<
250				AssetId<T>,
251				<<T as Config>::NativeCurrency as Currency<T::AccountId>>::Balance,
252			>,
253		) -> DispatchResult {
254			let who = ensure_signed(origin)?;
255			let dispatcher = <T as Config>::Dispatcher::default();
256			let asset_id = SupportedAssets::<T>::get(params.asset_id.clone())
257				.ok_or_else(|| Error::<T>::UnregisteredAsset)?;
258			let decimals = if params.asset_id == T::NativeAssetId::get() {
259				// Custody funds in pallet
260				let is_native = NativeAssets::<T>::get(T::NativeAssetId::get());
261				if is_native {
262					<T as Config>::NativeCurrency::transfer(
263						&who,
264						&Self::pallet_account(),
265						params.amount,
266						ExistenceRequirement::AllowDeath,
267					)?;
268				} else {
269					// Reduce total supply
270					let imbalance = <T as Config>::NativeCurrency::burn(params.amount);
271					// Burn amount from account
272					<T as Config>::NativeCurrency::settle(
273						&who,
274						imbalance,
275						WithdrawReasons::TRANSFER,
276						ExistenceRequirement::AllowDeath,
277					)
278					.map_err(|_| Error::<T>::AssetTeleportError)?;
279				}
280
281				T::Decimals::get()
282			} else {
283				let is_native = NativeAssets::<T>::get(params.asset_id.clone());
284				if is_native {
285					<T as Config>::Assets::transfer(
286						params.asset_id.clone(),
287						&who,
288						&Self::pallet_account(),
289						params.amount.into(),
290						Preservation::Expendable,
291					)?;
292				} else {
293					// Assets that do not originate from this chain are burned
294					<T as Config>::Assets::burn_from(
295						params.asset_id.clone(),
296						&who,
297						params.amount.into(),
298						Preservation::Expendable,
299						Precision::Exact,
300						Fortitude::Polite,
301					)?;
302				}
303
304				<T::Assets as fungibles::metadata::Inspect<T::AccountId>>::decimals(
305					params.asset_id.clone(),
306				)
307			};
308
309			let to = params.recepient.0;
310			let from: [u8; 32] = who.clone().into();
311			let erc_decimals = Precisions::<T>::get(params.asset_id, params.destination)
312				.ok_or_else(|| Error::<T>::AssetDecimalsNotFound)?;
313
314			let body = match params.call_data {
315				Some(data) => {
316					let body = BodyWithCall {
317						amount: {
318							let amount: u128 = params.amount.into();
319							let bytes =
320								convert_to_erc20(amount, erc_decimals, decimals).to_big_endian();
321							alloy_primitives::U256::from_be_bytes(bytes)
322						},
323						asset_id: asset_id.0.into(),
324						redeem: params.redeem,
325						from: from.into(),
326						to: to.into(),
327						data: data.into(),
328					};
329
330					// Prefix with the handleIncomingAsset enum variant
331					let mut encoded = vec![0];
332					encoded.extend_from_slice(&BodyWithCall::abi_encode(&body));
333					encoded
334				},
335
336				None => {
337					let body = Body {
338						amount: {
339							let amount: u128 = params.amount.into();
340							let bytes =
341								convert_to_erc20(amount, erc_decimals, decimals).to_big_endian();
342							alloy_primitives::U256::from_be_bytes(bytes)
343						},
344						asset_id: asset_id.0.into(),
345						redeem: params.redeem,
346						from: from.into(),
347						to: to.into(),
348					};
349
350					// Prefix with the handleIncomingAsset enum variant
351					let mut encoded = vec![0];
352					encoded.extend_from_slice(&Body::abi_encode(&body));
353					encoded
354				},
355			};
356
357			let dispatch_post = DispatchPost {
358				dest: params.destination,
359				from: PALLET_TOKEN_GATEWAY_ID.to_vec(),
360				to: params.token_gateway,
361				timeout: params.timeout,
362				body,
363			};
364
365			let metadata = FeeMetadata { payer: who.clone(), fee: params.relayer_fee.into() };
366			let commitment = dispatcher
367				.dispatch_request(DispatchRequest::Post(dispatch_post), metadata)
368				.map_err(|_| Error::<T>::AssetTeleportError)?;
369
370			Self::deposit_event(Event::<T>::AssetTeleported {
371				from: who,
372				to: params.recepient,
373				dest: params.destination,
374				amount: params.amount,
375				commitment,
376			});
377			Ok(())
378		}
379
380		/// Set the token gateway address for specified chains
381		#[pallet::call_index(1)]
382		#[pallet::weight(T::WeightInfo::set_token_gateway_addresses(addresses.len() as u32))]
383		pub fn set_token_gateway_addresses(
384			origin: OriginFor<T>,
385			addresses: BTreeMap<StateMachine, Vec<u8>>,
386		) -> DispatchResult {
387			T::CreateOrigin::ensure_origin(origin)?;
388			for (chain, address) in addresses {
389				TokenGatewayAddresses::<T>::insert(chain, address.clone());
390			}
391			Ok(())
392		}
393
394		/// Registers a multi-chain ERC6160 asset without sending any dispatch request.
395		/// You should use register_asset_locally when you want to enable token gateway transfers
396		/// for an asset that already exists on an external chain.
397		#[pallet::call_index(2)]
398		#[pallet::weight(T::WeightInfo::create_erc6160_asset(asset.precision.len() as u32))]
399		pub fn create_erc6160_asset(
400			origin: OriginFor<T>,
401			asset: AssetRegistration<AssetId<T>>,
402		) -> DispatchResult {
403			T::CreateOrigin::ensure_origin(origin)?;
404
405			let asset_id: H256 = sp_io::hashing::keccak_256(asset.reg.symbol.as_ref()).into();
406
407			SupportedAssets::<T>::insert(asset.local_id.clone(), asset_id.clone());
408			NativeAssets::<T>::insert(asset.local_id.clone(), asset.native);
409			LocalAssets::<T>::insert(asset_id, asset.local_id.clone());
410			for (state_machine, precision) in asset.precision {
411				Precisions::<T>::insert(asset.local_id.clone(), state_machine, precision);
412			}
413
414			Self::deposit_event(Event::<T>::AssetRegisteredLocally {
415				local_id: asset.local_id,
416				asset_id,
417			});
418
419			Ok(())
420		}
421
422		/// Update the precision for an existing asset
423		#[pallet::call_index(4)]
424		#[pallet::weight(T::WeightInfo::update_asset_precision(update.precisions.len() as u32))]
425		pub fn update_asset_precision(
426			origin: OriginFor<T>,
427			update: PrecisionUpdate<AssetId<T>>,
428		) -> DispatchResult {
429			T::CreateOrigin::ensure_origin(origin)?;
430			for (chain, precision) in update.precisions {
431				Precisions::<T>::insert(update.asset_id.clone(), chain, precision);
432			}
433			Ok(())
434		}
435	}
436
437	// Hack for implementing the [`Default`] bound needed for
438	// [`IsmpDispatcher`](ismp::dispatcher::IsmpDispatcher) and
439	// [`IsmpModule`](ismp::module::IsmpModule)
440	impl<T> Default for Pallet<T> {
441		fn default() -> Self {
442			Self(PhantomData)
443		}
444	}
445}
446
447impl<T: Config> IsmpModule for Pallet<T>
448where
449	<T as frame_system::Config>::AccountId: From<[u8; 32]>,
450	<<T as Config>::NativeCurrency as Currency<T::AccountId>>::Balance: From<u128>,
451	<<T as Config>::Assets as fungibles::Inspect<T::AccountId>>::Balance: From<u128>,
452{
453	fn on_accept(
454		&self,
455		PostRequest { body, from, source, dest, nonce, .. }: PostRequest,
456	) -> Result<Weight, anyhow::Error> {
457		let expected = TokenGatewayAddresses::<T>::get(source)
458			.ok_or_else(|| anyhow!("Not configured to receive assets from {source:?}"))?;
459		ensure!(
460			from == expected,
461			ismp::error::Error::ModuleDispatchError {
462				msg: "Token Gateway: Unknown source contract address".to_string(),
463				meta: Meta { source, dest, nonce },
464			}
465		);
466
467		// Body has 5 fixed-size fields (uint256 + bytes32 + bool + bytes32 + bytes32) = 160 bytes
468		// BodyWithCall adds dynamic bytes field, so it will be > 160 bytes
469		let body: RequestBody = if body.len() > types::BODY_BYTES_SIZE_WITH_DISCRIMINATOR {
470			BodyWithCall::abi_decode(&mut &body[1..])
471				.map_err(|e| anyhow!("Token Gateway: Failed to decode BodyWithCall: {e:?}"))?
472				.into()
473		} else if body.len() == types::BODY_BYTES_SIZE_WITH_DISCRIMINATOR {
474			Body::abi_decode_validate(&mut &body[1..])
475				.map_err(|e| anyhow!("Token Gateway: Failed to decode Body: {e:?}"))?
476				.into()
477		} else {
478			Err(anyhow!(
479				"Token Gateway: Invalid body length: {} bytes (expected {} or more)",
480				body.len(),
481				types::BODY_BYTES_SIZE_WITH_DISCRIMINATOR
482			))?
483		};
484		let local_asset_id =
485			LocalAssets::<T>::get(H256::from(body.asset_id.0)).ok_or_else(|| {
486				ismp::error::Error::ModuleDispatchError {
487					msg: "Token Gateway: Unknown asset".to_string(),
488					meta: Meta { source, dest, nonce },
489				}
490			})?;
491
492		let decimals = if local_asset_id == T::NativeAssetId::get() {
493			T::Decimals::get()
494		} else {
495			<T::Assets as fungibles::metadata::Inspect<T::AccountId>>::decimals(
496				local_asset_id.clone(),
497			)
498		};
499		let erc_decimals = Precisions::<T>::get(local_asset_id.clone(), source)
500			.ok_or_else(|| anyhow!("Asset decimals not configured"))?;
501		let amount = convert_to_balance(
502			U256::from_big_endian(&body.amount.to_be_bytes::<32>()),
503			erc_decimals,
504			decimals,
505		)
506		.map_err(|_| ismp::error::Error::ModuleDispatchError {
507			msg: "Token Gateway: Trying to withdraw Invalid amount".to_string(),
508			meta: Meta { source, dest, nonce },
509		})?;
510		let beneficiary: T::AccountId = body.to.0.into();
511		if local_asset_id == T::NativeAssetId::get() {
512			let is_native = NativeAssets::<T>::get(T::NativeAssetId::get());
513			if is_native {
514				<T as Config>::NativeCurrency::transfer(
515					&Pallet::<T>::pallet_account(),
516					&beneficiary,
517					amount.into(),
518					ExistenceRequirement::AllowDeath,
519				)
520				.map_err(|_| ismp::error::Error::ModuleDispatchError {
521					msg: "Token Gateway: Failed to complete asset transfer".to_string(),
522					meta: Meta { source, dest, nonce },
523				})?;
524			} else {
525				// Increase total supply
526				let imbalance = <T as Config>::NativeCurrency::issue(amount.into());
527				// Mint into the beneficiary account
528				<T as Config>::NativeCurrency::resolve_creating(&beneficiary, imbalance);
529			}
530		} else {
531			// Assets that do not originate from this chain are minted
532			let is_native = NativeAssets::<T>::get(local_asset_id.clone());
533			if is_native {
534				<T as Config>::Assets::transfer(
535					local_asset_id,
536					&Pallet::<T>::pallet_account(),
537					&beneficiary,
538					amount.into(),
539					Preservation::Expendable,
540				)
541				.map_err(|_| ismp::error::Error::ModuleDispatchError {
542					msg: "Token Gateway: Failed to complete asset transfer".to_string(),
543					meta: Meta { source, dest, nonce },
544				})?;
545			} else {
546				<T as Config>::Assets::mint_into(local_asset_id, &beneficiary, amount.into())
547					.map_err(|_| ismp::error::Error::ModuleDispatchError {
548						msg: "Token Gateway: Failed to complete asset transfer".to_string(),
549						meta: Meta { source, dest, nonce },
550					})?;
551			}
552		}
553
554		if let Some(call_data) = body.data {
555			let substrate_data = SubstrateCalldata::decode(&mut &call_data.0[..])
556				.map_err(|err| anyhow!("Calldata decode error: {err:?}"))?;
557
558			let origin = if let Some(signature) = substrate_data.signature {
559				let multi_signature = MultiSignature::decode(&mut &*signature)
560					.map_err(|err| anyhow!("Signature decode error: {err:?}"))?;
561
562				// Verify signature against encoded runtime call
563				let nonce = frame_system::Pallet::<T>::account_nonce(beneficiary.clone());
564
565				match multi_signature {
566					MultiSignature::Ed25519(sig) => {
567						let payload = (nonce, substrate_data.runtime_call.clone()).encode();
568						let message = sp_io::hashing::keccak_256(&payload);
569						let pub_key = body.to.0.as_slice().try_into().map_err(|_| {
570							anyhow!("Failed to decode beneficiary as Ed25519 public key")
571						})?;
572						if !sp_io::crypto::ed25519_verify(&sig, message.as_ref(), &pub_key) {
573							Err(anyhow!(
574							"Failed to verify ed25519 signature before dispatching token gateway call"
575						))?
576						}
577					},
578					MultiSignature::Sr25519(sig) => {
579						let payload = (nonce, substrate_data.runtime_call.clone()).encode();
580						let message = sp_io::hashing::keccak_256(&payload);
581						let pub_key = body.to.0.as_slice().try_into().map_err(|_| {
582							anyhow!("Failed to decode beneficiary as Sr25519 public key")
583						})?;
584						if !sp_io::crypto::sr25519_verify(&sig, message.as_ref(), &pub_key) {
585							Err(anyhow!(
586							"Failed to verify sr25519 signature before dispatching token gateway call"
587						))?
588						}
589					},
590					MultiSignature::Ecdsa(sig) => {
591						let payload = (nonce, substrate_data.runtime_call.clone()).encode();
592						// Following EIP-191 convention https://eips.ethereum.org/EIPS/eip-191
593						let preimage = vec![
594							format!("{ETHEREUM_MESSAGE_PREFIX}{}", payload.len())
595								.as_bytes()
596								.to_vec(),
597							payload,
598						]
599						.concat();
600						let message = sp_io::hashing::keccak_256(&preimage);
601						let pub_key = sp_io::crypto::secp256k1_ecdsa_recover(&sig.0, &message)
602							.map_err(|_| {
603								anyhow!("Failed to recover ecdsa public key from signature")
604							})?;
605						let eth_address =
606							H160::from_slice(&sp_io::hashing::keccak_256(&pub_key[..])[12..]);
607						let substrate_account = T::EvmToSubstrate::convert(eth_address);
608						if substrate_account != beneficiary {
609							Err(anyhow!(
610								"Failed to verify signature before dispatching token gateway call"
611							))?
612						}
613					},
614					MultiSignature::Eth(_) => {
615						Err(anyhow!("Eth signature type is not supported"))?
616					},
617				};
618
619				beneficiary.clone().into()
620			} else {
621				if source.is_evm() {
622					// sender is evm account
623					T::EvmToSubstrate::convert(H160::from_slice(&body.from[12..]))
624				} else {
625					// sender is substrate account
626					body.from.0.into()
627				}
628			};
629
630			let runtime_call = T::RuntimeCall::decode(&mut &*substrate_data.runtime_call)
631				.map_err(|err| anyhow!("RuntimeCall decode error: {err:?}"))?;
632			runtime_call
633				.dispatch(RawOrigin::Signed(origin.clone()).into())
634				.map_err(|e| anyhow!("Call dispatch executed with error {:?}", e.error))?;
635
636			// Increase account nonce to ensure the call cannot be replayed
637			frame_system::Pallet::<T>::inc_account_nonce(origin.clone());
638		}
639
640		Self::deposit_event(Event::<T>::AssetReceived {
641			beneficiary,
642			amount: amount.into(),
643			source,
644		});
645		Ok(T::DbWeight::get().reads_writes(0, 0))
646	}
647
648	fn on_response(&self, _response: Response) -> Result<Weight, anyhow::Error> {
649		Err(anyhow!("Module does not accept responses".to_string()))
650	}
651
652	fn on_timeout(&self, request: Timeout) -> Result<Weight, anyhow::Error> {
653		match request {
654			Timeout::Request(Request::Post(PostRequest { body, source, dest, nonce, .. })) => {
655				// Body has 5 fixed-size fields (uint256 + bytes32 + bool + bytes32 + bytes32) = 160
656				// bytes BodyWithCall adds dynamic bytes field, so it will be > 160 bytes
657				let body: RequestBody = if body.len() > types::BODY_BYTES_SIZE_WITH_DISCRIMINATOR {
658					BodyWithCall::abi_decode(&mut &body[1..])
659						.map_err(|e| {
660							anyhow!("Token Gateway: Failed to decode BodyWithCall: {e:?}")
661						})?
662						.into()
663				} else if body.len() == types::BODY_BYTES_SIZE_WITH_DISCRIMINATOR {
664					Body::abi_decode_validate(&mut &body[1..])
665						.map_err(|e| anyhow!("Token Gateway: Failed to decode Body: {e:?}"))?
666						.into()
667				} else {
668					Err(anyhow!(
669						"Token Gateway: Invalid body length: {} bytes (expected {} or more)",
670						body.len(),
671						types::BODY_BYTES_SIZE_WITH_DISCRIMINATOR
672					))?
673				};
674				let beneficiary = body.from.0.into();
675				let local_asset_id = LocalAssets::<T>::get(H256::from(body.asset_id.0))
676					.ok_or_else(|| ismp::error::Error::ModuleDispatchError {
677						msg: "Token Gateway: Unknown asset".to_string(),
678						meta: Meta { source, dest, nonce },
679					})?;
680				let decimals = if local_asset_id == T::NativeAssetId::get() {
681					T::Decimals::get()
682				} else {
683					<T::Assets as fungibles::metadata::Inspect<T::AccountId>>::decimals(
684						local_asset_id.clone(),
685					)
686				};
687				let erc_decimals = Precisions::<T>::get(local_asset_id.clone(), dest)
688					.ok_or_else(|| anyhow!("Asset decimals not configured"))?;
689				let amount = convert_to_balance(
690					U256::from_big_endian(&body.amount.to_be_bytes::<32>()),
691					erc_decimals,
692					decimals,
693				)
694				.map_err(|_| ismp::error::Error::ModuleDispatchError {
695					msg: "Token Gateway: Trying to withdraw Invalid amount".to_string(),
696					meta: Meta { source, dest, nonce },
697				})?;
698
699				if local_asset_id == T::NativeAssetId::get() {
700					let is_native = NativeAssets::<T>::get(T::NativeAssetId::get());
701					if is_native {
702						<T as Config>::NativeCurrency::transfer(
703							&Pallet::<T>::pallet_account(),
704							&beneficiary,
705							amount.into(),
706							ExistenceRequirement::AllowDeath,
707						)
708						.map_err(|_| ismp::error::Error::ModuleDispatchError {
709							msg: "Token Gateway: Failed to complete asset transfer".to_string(),
710							meta: Meta { source, dest, nonce },
711						})?;
712					} else {
713						let imbalance = <T as Config>::NativeCurrency::issue(amount.into());
714						<T as Config>::NativeCurrency::resolve_creating(&beneficiary, imbalance);
715					}
716				} else {
717					// Assets that do not originate from this chain are minted
718					let is_native = NativeAssets::<T>::get(local_asset_id.clone());
719					if is_native {
720						<T as Config>::Assets::transfer(
721							local_asset_id,
722							&Pallet::<T>::pallet_account(),
723							&beneficiary,
724							amount.into(),
725							Preservation::Expendable,
726						)
727						.map_err(|_| ismp::error::Error::ModuleDispatchError {
728							msg: "Token Gateway: Failed to complete asset transfer".to_string(),
729							meta: Meta { source, dest, nonce },
730						})?;
731					} else {
732						<T as Config>::Assets::mint_into(
733							local_asset_id,
734							&beneficiary,
735							amount.into(),
736						)
737						.map_err(|_| ismp::error::Error::ModuleDispatchError {
738							msg: "Token Gateway: Failed to complete asset transfer".to_string(),
739							meta: Meta { source, dest, nonce },
740						})?;
741					}
742				}
743
744				Pallet::<T>::deposit_event(Event::<T>::AssetRefunded {
745					beneficiary,
746					amount: amount.into(),
747					source: dest,
748				});
749				Ok(T::DbWeight::get().reads_writes(0, 0))
750			},
751			Timeout::Request(Request::Get(get)) => Err(ismp::error::Error::ModuleDispatchError {
752				msg: "Tried to timeout unsupported request type".to_string(),
753				meta: Meta { source: get.source, dest: get.dest, nonce: get.nonce },
754			})?,
755
756			Timeout::Response(response) => Err(ismp::error::Error::ModuleDispatchError {
757				msg: "Tried to timeout unsupported request type".to_string(),
758				meta: Meta {
759					source: response.source_chain(),
760					dest: response.dest_chain(),
761					nonce: response.nonce(),
762				},
763			})?,
764		}
765	}
766}