1#![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
58pub 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 #[pallet::config]
88 pub trait Config:
89 polkadot_sdk::frame_system::Config + pallet_ismp::Config + pallet_hyperbridge::Config
90 {
91 type Dispatcher: IsmpDispatcher<Account = Self::AccountId, Balance = Self::Balance>;
93
94 type NativeCurrency: Currency<Self::AccountId>;
96
97 type AssetAdmin: Get<Self::AccountId>;
100
101 type CreateOrigin: EnsureOrigin<Self::RuntimeOrigin>;
103
104 type Assets: fungibles::Mutate<Self::AccountId>
106 + fungibles::metadata::Inspect<Self::AccountId>;
107
108 type NativeAssetId: Get<AssetId<Self>>;
110
111 #[pallet::constant]
113 type Decimals: Get<u8>;
114
115 type EvmToSubstrate: EvmToSubstrate<Self>;
118
119 type WeightInfo: WeightInfo;
121 }
122
123 #[pallet::storage]
126 pub type SupportedAssets<T: Config> =
127 StorageMap<_, Blake2_128Concat, AssetId<T>, H256, OptionQuery>;
128
129 #[pallet::storage]
131 pub type NativeAssets<T: Config> =
132 StorageMap<_, Blake2_128Concat, AssetId<T>, bool, ValueQuery>;
133
134 #[pallet::storage]
137 pub type LocalAssets<T: Config> = StorageMap<_, Identity, H256, AssetId<T>, OptionQuery>;
138
139 #[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 #[pallet::storage]
153 pub type TokenGatewayAddresses<T: Config> =
154 StorageMap<_, Blake2_128Concat, StateMachine, Vec<u8>, OptionQuery>;
155
156 #[pallet::event]
158 #[pallet::generate_deposit(pub(super) fn deposit_event)]
159 pub enum Event<T: Config> {
160 AssetTeleported {
162 from: T::AccountId,
164 to: H256,
166 amount: <T::NativeCurrency as Currency<T::AccountId>>::Balance,
168 dest: StateMachine,
170 commitment: H256,
172 },
173
174 AssetReceived {
176 beneficiary: T::AccountId,
178 amount: <<T as Config>::NativeCurrency as Currency<T::AccountId>>::Balance,
180 source: StateMachine,
182 },
183
184 AssetRefunded {
186 beneficiary: T::AccountId,
188 amount: <<T as Config>::NativeCurrency as Currency<T::AccountId>>::Balance,
190 source: StateMachine,
192 },
193
194 ERC6160AssetRegistrationDispatched {
196 commitment: H256,
198 },
199 AssetRegisteredLocally {
201 local_id: AssetId<T>,
203 asset_id: H256,
205 },
206 }
207
208 #[pallet::error]
210 pub enum Error<T> {
211 UnregisteredAsset,
213 AssetTeleportError,
215 CoprocessorNotConfigured,
217 DispatchError,
219 AssetCreationError,
221 AssetDecimalsNotFound,
223 NotInitialized,
225 UnknownAsset,
227 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 #[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 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 let imbalance = <T as Config>::NativeCurrency::burn(params.amount);
271 <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 <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 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 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 #[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 #[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 #[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 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 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 let imbalance = <T as Config>::NativeCurrency::issue(amount.into());
527 <T as Config>::NativeCurrency::resolve_creating(&beneficiary, imbalance);
529 }
530 } else {
531 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 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 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 T::EvmToSubstrate::convert(H160::from_slice(&body.from[12..]))
624 } else {
625 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 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 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 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}