Skip to main content

pallet_transporter/
lib.rs

1// Copyright (C) 2021 Subspace Labs, Inc.
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//! Pallet transporter used to move funds between chains.
17
18#![cfg_attr(not(feature = "std"), no_std)]
19// TODO: Remove once FRAME macros stop generating `.clone()` calls on `Copy` types with user spans
20#![expect(clippy::clone_on_copy, reason = "Comes from Substrate")]
21#![forbid(unsafe_code)]
22#![warn(rust_2018_idioms)]
23
24#[cfg(feature = "runtime-benchmarks")]
25mod benchmarking;
26pub mod migrations;
27#[cfg(test)]
28mod mock;
29#[cfg(test)]
30mod tests;
31pub mod weights;
32
33#[cfg(not(feature = "std"))]
34extern crate alloc;
35
36use domain_runtime_primitives::{MultiAccountId, TryConvertBack};
37use frame_support::dispatch::DispatchResult;
38use frame_support::ensure;
39use frame_support::pallet_prelude::StorageVersion;
40use frame_support::traits::Currency;
41pub use pallet::*;
42use parity_scale_codec::{Decode, DecodeWithMemTracking, Encode};
43use scale_info::TypeInfo;
44use sp_domains::execution_receipt::Transfers;
45use sp_domains::{DomainId, DomainsTransfersTracker};
46use sp_messenger::NoteChainTransfer;
47use sp_messenger::endpoint::EndpointResponse;
48use sp_messenger::messages::ChainId;
49use sp_runtime::traits::{CheckedAdd, CheckedSub, Get};
50use sp_std::vec;
51pub use weights::WeightInfo;
52
53/// Zero EVM address.
54/// Used to ensure dst_account is not ZERO address.
55const ZERO_EVM_ADDRESS: MultiAccountId = MultiAccountId::AccountId20([0; 20]);
56const ZERO_SUBSTRATE_ADDRESS: MultiAccountId = MultiAccountId::AccountId32([0; 32]);
57
58/// Location that either sends or receives transfers between chains.
59#[derive(Debug, Encode, Decode, Clone, Eq, PartialEq, TypeInfo, DecodeWithMemTracking)]
60pub struct Location {
61    /// Unique identity of chain.
62    pub chain_id: ChainId,
63    /// Unique account on chain.
64    pub account_id: MultiAccountId,
65}
66
67/// Transfer of funds from one chain to another.
68#[derive(Debug, Encode, Decode, Clone, Eq, PartialEq, TypeInfo)]
69pub struct Transfer<Balance> {
70    /// Amount being transferred between entities.
71    pub amount: Balance,
72    /// Sender location of the transfer.
73    pub sender: Location,
74    /// Receiver location of the transfer.
75    pub receiver: Location,
76}
77
78/// Balance type used by the pallet.
79pub type BalanceOf<T> =
80    <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
81
82type MessageIdOf<T> = <<T as Config>::Sender as sp_messenger::endpoint::Sender<
83    <T as frame_system::Config>::AccountId,
84>>::MessageId;
85
86const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
87
88#[frame_support::pallet]
89mod pallet {
90    use crate::weights::WeightInfo;
91    use crate::{
92        BalanceOf, Location, MessageIdOf, MultiAccountId, STORAGE_VERSION, Transfer,
93        TryConvertBack, ZERO_EVM_ADDRESS, ZERO_SUBSTRATE_ADDRESS,
94    };
95    #[cfg(not(feature = "std"))]
96    use alloc::vec::Vec;
97    use frame_support::pallet_prelude::*;
98    use frame_support::traits::{Currency, ExistenceRequirement, WithdrawReasons};
99    use frame_support::weights::Weight;
100    use frame_system::pallet_prelude::*;
101    use parity_scale_codec::{Decode, Encode};
102    use sp_domains::execution_receipt::Transfers;
103    use sp_domains::{DomainId, DomainsTransfersTracker};
104    use sp_messenger::endpoint::{
105        Endpoint, EndpointHandler as EndpointHandlerT, EndpointId, EndpointRequest,
106        EndpointResponse, Sender,
107    };
108    use sp_messenger::messages::ChainId;
109    use sp_runtime::traits::Convert;
110
111    #[pallet::config]
112    pub trait Config: frame_system::Config<RuntimeEvent: From<Event<Self>>> {
113        /// Gets the chain_id of the current execution environment.
114        type SelfChainId: Get<ChainId>;
115
116        /// Gets the endpoint_id of this pallet in a given execution environment.
117        type SelfEndpointId: Get<EndpointId>;
118
119        /// Currency used by this pallet.
120        type Currency: Currency<Self::AccountId>;
121
122        /// Sender used to transfer funds.
123        type Sender: Sender<Self::AccountId>;
124
125        /// MultiAccountID <> T::AccountId converter.
126        type AccountIdConverter: TryConvertBack<Self::AccountId, MultiAccountId>;
127
128        /// Weight information for extrinsics in this pallet.
129        type WeightInfo: WeightInfo;
130
131        /// Minimum transfer amount.
132        type MinimumTransfer: Get<BalanceOf<Self>>;
133    }
134
135    /// Pallet transporter to move funds between chains.
136    #[pallet::pallet]
137    #[pallet::without_storage_info]
138    #[pallet::storage_version(STORAGE_VERSION)]
139    pub struct Pallet<T>(_);
140
141    /// All the outgoing transfers on this execution environment.
142    #[pallet::storage]
143    #[pallet::getter(fn outgoing_transfers)]
144    pub(super) type OutgoingTransfers<T: Config> = StorageDoubleMap<
145        _,
146        Identity,
147        ChainId,
148        Identity,
149        MessageIdOf<T>,
150        Transfer<BalanceOf<T>>,
151        OptionQuery,
152    >;
153
154    /// Domain balances.
155    #[pallet::storage]
156    #[pallet::getter(fn domain_balances)]
157    pub(super) type DomainBalances<T: Config> =
158        StorageMap<_, Identity, DomainId, BalanceOf<T>, ValueQuery>;
159
160    /// All-domains supply: network supply that has left consensus `total_issuance` but still
161    /// exists, kept equal to Σ DomainBalances + Σ UnconfirmedTransfers + Σ CancelledTransfers
162    /// (held on domains, in-flight across XDM, and rejected awaiting reclaim). Read on the
163    /// consensus runtime and added to `total_issuance` to price storage off network-wide supply.
164    #[pallet::storage]
165    #[pallet::getter(fn all_domains_supply)]
166    pub(super) type AllDomainsSupply<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
167
168    /// A temporary storage that tracks total transfers from this chain.
169    /// Clears on on_initialize for every block.
170    #[pallet::storage]
171    #[pallet::getter(fn chain_transfers)]
172    pub(super) type ChainTransfers<T: Config> =
173        StorageValue<_, Transfers<BalanceOf<T>>, ValueQuery>;
174
175    /// Storage to track unconfirmed transfers between different chains.
176    #[pallet::storage]
177    #[pallet::getter(fn unconfirmed_transfers)]
178    pub(super) type UnconfirmedTransfers<T: Config> =
179        StorageDoubleMap<_, Identity, ChainId, Identity, ChainId, BalanceOf<T>, ValueQuery>;
180
181    /// Storage to track cancelled transfers between different chains.
182    #[pallet::storage]
183    #[pallet::getter(fn cancelled_transfers)]
184    pub(super) type CancelledTransfers<T: Config> =
185        StorageDoubleMap<_, Identity, ChainId, Identity, ChainId, BalanceOf<T>, ValueQuery>;
186
187    /// Events emitted by pallet-transporter.
188    #[pallet::event]
189    #[pallet::generate_deposit(pub (super) fn deposit_event)]
190    pub enum Event<T: Config> {
191        /// Emits when there is a new outgoing transfer.
192        OutgoingTransferInitiated {
193            /// Destination chain the transfer is bound to.
194            chain_id: ChainId,
195            /// Id of the transfer.
196            message_id: MessageIdOf<T>,
197            /// Amount transferred from this chain
198            amount: BalanceOf<T>,
199        },
200
201        /// Emits when a given outgoing transfer was failed on dst_chain.
202        OutgoingTransferFailed {
203            /// Destination chain the transfer is bound to.
204            chain_id: ChainId,
205            /// Id of the transfer.
206            message_id: MessageIdOf<T>,
207            /// Error from dst_chain endpoint.
208            err: DispatchError,
209        },
210
211        /// Emits when a given outgoing transfer was successful.
212        OutgoingTransferSuccessful {
213            /// Destination chain the transfer is bound to.
214            chain_id: ChainId,
215            /// Id of the transfer.
216            message_id: MessageIdOf<T>,
217        },
218
219        /// Emits when a given incoming transfer was successfully processed.
220        IncomingTransferSuccessful {
221            /// Source chain the transfer is coming from.
222            chain_id: ChainId,
223            /// Id of the transfer.
224            message_id: MessageIdOf<T>,
225            /// Amount transferred to this chain.
226            amount: BalanceOf<T>,
227        },
228    }
229
230    /// Errors emitted by pallet-transporter.
231    #[pallet::error]
232    pub enum Error<T> {
233        /// Emits when the account has low balance to make a transfer.
234        LowBalance,
235        /// Failed to decode transfer payload.
236        InvalidPayload,
237        /// Emits when the request for a response received is missing.
238        MissingTransferRequest,
239        /// Emits when the request doesn't match the expected one..
240        InvalidTransferRequest,
241        /// Emits when the incoming message is not bound to this chain.
242        UnexpectedMessage,
243        /// Emits when the account id type is invalid.
244        InvalidAccountId,
245        /// Emits when from_chain do not have enough funds to finalize the transfer.
246        LowBalanceOnDomain,
247        /// Emits when the transfer tracking was called from non-consensus chain
248        NonConsensusChain,
249        /// Emits when balance overflow
250        BalanceOverflow,
251        /// Emits when balance underflow
252        BalanceUnderflow,
253        /// Emits when domain balance is already initialized
254        DomainBalanceAlreadyInitialized,
255        /// Emits when the requested transfer amount is less than Minimum transfer amount.
256        MinimumTransferAmount,
257    }
258
259    #[pallet::call]
260    impl<T: Config> Pallet<T> {
261        /// Initiates transfer of funds from account on src_chain to account on dst_chain.
262        /// Funds are burned on src_chain first and are minted on dst_chain using Messenger.
263        #[pallet::call_index(0)]
264        #[pallet::weight(T::WeightInfo::transfer())]
265        pub fn transfer(
266            origin: OriginFor<T>,
267            dst_location: Location,
268            amount: BalanceOf<T>,
269        ) -> DispatchResult {
270            let sender = ensure_signed(origin)?;
271            ensure!(
272                amount >= T::MinimumTransfer::get(),
273                Error::<T>::MinimumTransferAmount
274            );
275
276            ensure!(
277                dst_location.account_id != ZERO_EVM_ADDRESS,
278                Error::<T>::InvalidAccountId
279            );
280
281            ensure!(
282                dst_location.account_id != ZERO_SUBSTRATE_ADDRESS,
283                Error::<T>::InvalidAccountId
284            );
285
286            // burn transfer amount
287            let _imbalance = T::Currency::withdraw(
288                &sender,
289                amount,
290                WithdrawReasons::TRANSFER,
291                ExistenceRequirement::KeepAlive,
292            )
293            .map_err(|_| Error::<T>::LowBalance)?;
294
295            // initiate transfer
296            let dst_chain_id = dst_location.chain_id;
297            let transfer = Transfer {
298                amount,
299                sender: Location {
300                    chain_id: T::SelfChainId::get(),
301                    account_id: T::AccountIdConverter::convert(sender.clone()),
302                },
303                receiver: dst_location,
304            };
305
306            // send message
307            let message_id = T::Sender::send_message(
308                &sender,
309                dst_chain_id,
310                EndpointRequest {
311                    src_endpoint: Endpoint::Id(T::SelfEndpointId::get()),
312                    // destination endpoint must be transporter with same id
313                    dst_endpoint: Endpoint::Id(T::SelfEndpointId::get()),
314                    payload: transfer.encode(),
315                },
316            )?;
317
318            OutgoingTransfers::<T>::insert(dst_chain_id, message_id, transfer);
319            Self::deposit_event(Event::<T>::OutgoingTransferInitiated {
320                chain_id: dst_chain_id,
321                message_id,
322                amount,
323            });
324
325            // if this is consensus chain, then note the transfer
326            // else add transfer to storage to send through ER to consensus chain
327            if T::SelfChainId::get().is_consensus_chain() {
328                Self::note_transfer(T::SelfChainId::get(), dst_chain_id, amount)?
329            } else {
330                ChainTransfers::<T>::try_mutate(|transfers| {
331                    Self::update_transfer_out(transfers, dst_chain_id, amount)
332                })?;
333            }
334
335            Ok(())
336        }
337    }
338
339    #[pallet::hooks]
340    impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
341        fn on_initialize(_n: BlockNumberFor<T>) -> Weight {
342            // NOTE: set the `ChainTransfers` to an empty value instead of removing the value completely
343            // so we can generate a storage proof to prove the empty value, which is required by the fraud
344            // proof.
345            ChainTransfers::<T>::set(Default::default());
346            T::DbWeight::get().writes(1)
347        }
348    }
349
350    impl<T: Config> Pallet<T> {
351        pub fn transfers_storage_key() -> Vec<u8> {
352            use frame_support::storage::generator::StorageValue;
353            ChainTransfers::<T>::storage_value_final_key().to_vec()
354        }
355
356        pub(super) fn increase_all_domains_supply(amount: BalanceOf<T>) {
357            let current = Self::all_domains_supply();
358            let updated = current.checked_add(&amount).unwrap_or_else(|| {
359                log::error!(
360                    target: "runtime::transporter",
361                    "all-domains supply overflow adding {amount:?}; keeping current (drift)"
362                );
363                current
364            });
365            AllDomainsSupply::<T>::put(updated);
366        }
367
368        pub(super) fn decrease_all_domains_supply(amount: BalanceOf<T>) {
369            let current = Self::all_domains_supply();
370            let updated = current.checked_sub(&amount).unwrap_or_else(|| {
371                log::error!(
372                    target: "runtime::transporter",
373                    "all-domains supply underflow subtracting {amount:?}; flooring at zero (drift)"
374                );
375                BalanceOf::<T>::default()
376            });
377            AllDomainsSupply::<T>::put(updated);
378        }
379    }
380
381    /// Endpoint handler implementation for pallet transporter.
382    #[derive(Debug)]
383    pub struct EndpointHandler<T>(pub PhantomData<T>);
384
385    impl<T: Config> EndpointHandlerT<MessageIdOf<T>> for EndpointHandler<T> {
386        fn message(
387            &self,
388            src_chain_id: ChainId,
389            message_id: MessageIdOf<T>,
390            req: EndpointRequest,
391            pre_check_result: DispatchResult,
392        ) -> EndpointResponse {
393            // decode payload
394            let dst_endpoint = req.dst_endpoint;
395            let req = match Transfer::decode(&mut req.payload.as_slice()) {
396                Ok(req) => req,
397                Err(_) => return Err(Error::<T>::InvalidPayload.into()),
398            };
399
400            let pre_check_handler = || {
401                // ensure message is not from the self
402                ensure!(
403                    T::SelfChainId::get() != src_chain_id,
404                    Error::<T>::InvalidTransferRequest
405                );
406
407                // check the endpoint id
408                ensure!(
409                    dst_endpoint == Endpoint::Id(T::SelfEndpointId::get()),
410                    Error::<T>::UnexpectedMessage
411                );
412
413                pre_check_result
414            };
415
416            let amount = req.amount;
417            let response = match pre_check_handler() {
418                Ok(_) => Pallet::<T>::finalize_transfer(src_chain_id, message_id, req),
419                Err(err) => Err(err),
420            };
421
422            if response.is_err() {
423                // if this is consensus chain, then reject the transfer
424                // else update the Transfers storage with rejected transfer
425                if T::SelfChainId::get().is_consensus_chain() {
426                    Pallet::<T>::reject_transfer(src_chain_id, T::SelfChainId::get(), amount)?;
427                } else {
428                    ChainTransfers::<T>::try_mutate(|transfers| {
429                        Pallet::<T>::update_transfer_rejected(transfers, src_chain_id, amount)
430                    })?;
431                }
432            }
433
434            response
435        }
436
437        fn message_weight(&self) -> Weight {
438            T::WeightInfo::message()
439        }
440
441        fn message_response(
442            &self,
443            dst_chain_id: ChainId,
444            message_id: MessageIdOf<T>,
445            req: EndpointRequest,
446            resp: EndpointResponse,
447        ) -> DispatchResult {
448            // ensure request is valid
449            let transfer = OutgoingTransfers::<T>::take(dst_chain_id, message_id)
450                .ok_or(Error::<T>::MissingTransferRequest)?;
451            ensure!(
452                req.payload == transfer.encode(),
453                Error::<T>::InvalidTransferRequest
454            );
455
456            // process response
457            match resp {
458                Ok(_) => {
459                    // transfer is successful
460                    frame_system::Pallet::<T>::deposit_event(Into::<
461                        <T as frame_system::Config>::RuntimeEvent,
462                    >::into(
463                        Event::<T>::OutgoingTransferSuccessful {
464                            chain_id: dst_chain_id,
465                            message_id,
466                        },
467                    ));
468                }
469                Err(err) => {
470                    // transfer failed
471                    // revert burned funds
472                    let account_id =
473                        T::AccountIdConverter::try_convert_back(transfer.sender.account_id)
474                            .ok_or(Error::<T>::InvalidAccountId)?;
475
476                    // if this is consensus chain, then revert the transfer
477                    // else update the Transfers storage with reverted transfer
478                    if T::SelfChainId::get().is_consensus_chain() {
479                        Pallet::<T>::claim_rejected_transfer(
480                            T::SelfChainId::get(),
481                            dst_chain_id,
482                            transfer.amount,
483                        )?;
484                    } else {
485                        ChainTransfers::<T>::try_mutate(|transfers| {
486                            Pallet::<T>::update_transfer_revert(
487                                transfers,
488                                dst_chain_id,
489                                transfer.amount,
490                            )
491                        })?;
492                    }
493
494                    let _imbalance = T::Currency::deposit_creating(&account_id, transfer.amount);
495                    frame_system::Pallet::<T>::deposit_event(Into::<
496                        <T as frame_system::Config>::RuntimeEvent,
497                    >::into(
498                        Event::<T>::OutgoingTransferFailed {
499                            chain_id: dst_chain_id,
500                            message_id,
501                            err,
502                        },
503                    ));
504                }
505            }
506
507            Ok(())
508        }
509
510        fn message_response_weight(&self) -> Weight {
511            T::WeightInfo::message_response()
512        }
513    }
514}
515
516impl<T: Config> sp_domains::DomainsTransfersTracker<BalanceOf<T>> for Pallet<T> {
517    type Error = Error<T>;
518
519    fn initialize_domain_balance(
520        domain_id: DomainId,
521        amount: BalanceOf<T>,
522    ) -> Result<(), Self::Error> {
523        Self::ensure_consensus_chain()?;
524
525        ensure!(
526            !DomainBalances::<T>::contains_key(domain_id),
527            Error::DomainBalanceAlreadyInitialized
528        );
529
530        DomainBalances::<T>::set(domain_id, amount);
531        Self::increase_all_domains_supply(amount);
532        Ok(())
533    }
534
535    fn note_transfer(
536        from_chain_id: ChainId,
537        to_chain_id: ChainId,
538        amount: BalanceOf<T>,
539    ) -> Result<(), Self::Error> {
540        Self::ensure_consensus_chain()?;
541
542        UnconfirmedTransfers::<T>::try_mutate(from_chain_id, to_chain_id, |total_amount| {
543            if let Some(domain_id) = from_chain_id.maybe_domain_chain() {
544                DomainBalances::<T>::try_mutate(domain_id, |current_balance| {
545                    *current_balance = current_balance
546                        .checked_sub(&amount)
547                        .ok_or(Error::LowBalanceOnDomain)?;
548                    Ok(())
549                })?;
550            }
551
552            *total_amount = total_amount
553                .checked_add(&amount)
554                .ok_or(Error::BalanceOverflow)?;
555            Ok(())
556        })?;
557
558        // Net: +amount from a consensus source; a domain source's DomainBalances debit cancels it.
559        if from_chain_id.maybe_domain_chain().is_none() {
560            Self::increase_all_domains_supply(amount);
561        }
562
563        Ok(())
564    }
565
566    fn confirm_transfer(
567        from_chain_id: ChainId,
568        to_chain_id: ChainId,
569        amount: BalanceOf<T>,
570    ) -> Result<(), Self::Error> {
571        Self::ensure_consensus_chain()?;
572        UnconfirmedTransfers::<T>::try_mutate(from_chain_id, to_chain_id, |total_amount| {
573            *total_amount = total_amount
574                .checked_sub(&amount)
575                .ok_or(Error::BalanceUnderflow)?;
576
577            if let Some(domain_id) = to_chain_id.maybe_domain_chain() {
578                DomainBalances::<T>::try_mutate(domain_id, |current_balance| {
579                    *current_balance = current_balance
580                        .checked_add(&amount)
581                        .ok_or(Error::BalanceOverflow)?;
582                    Ok(())
583                })?;
584            }
585
586            Ok(())
587        })?;
588
589        // Net: -amount to a consensus destination; a domain destination's DomainBalances credit cancels it.
590        if to_chain_id.maybe_domain_chain().is_none() {
591            Self::decrease_all_domains_supply(amount);
592        }
593
594        Ok(())
595    }
596
597    fn claim_rejected_transfer(
598        from_chain_id: ChainId,
599        to_chain_id: ChainId,
600        amount: BalanceOf<T>,
601    ) -> Result<(), Self::Error> {
602        Self::ensure_consensus_chain()?;
603        CancelledTransfers::<T>::try_mutate(from_chain_id, to_chain_id, |total_amount| {
604            *total_amount = total_amount
605                .checked_sub(&amount)
606                .ok_or(Error::BalanceUnderflow)?;
607
608            if let Some(domain_id) = from_chain_id.maybe_domain_chain() {
609                DomainBalances::<T>::try_mutate(domain_id, |current_balance| {
610                    *current_balance = current_balance
611                        .checked_add(&amount)
612                        .ok_or(Error::BalanceOverflow)?;
613                    Ok(())
614                })?;
615            }
616
617            Ok(())
618        })?;
619
620        // Net: -amount when reclaimed on consensus; a domain reclaim's DomainBalances credit cancels it.
621        if from_chain_id.maybe_domain_chain().is_none() {
622            Self::decrease_all_domains_supply(amount);
623        }
624
625        Ok(())
626    }
627
628    fn reject_transfer(
629        from_chain_id: ChainId,
630        to_chain_id: ChainId,
631        amount: BalanceOf<T>,
632    ) -> Result<(), Self::Error> {
633        Self::ensure_consensus_chain()?;
634        // No net aggregate change: the amount moves from the unconfirmed to the cancelled bucket,
635        // both already counted in the aggregate.
636        UnconfirmedTransfers::<T>::try_mutate(from_chain_id, to_chain_id, |total_amount| {
637            *total_amount = total_amount
638                .checked_sub(&amount)
639                .ok_or(Error::BalanceUnderflow)?;
640
641            CancelledTransfers::<T>::try_mutate(from_chain_id, to_chain_id, |total_amount| {
642                *total_amount = total_amount
643                    .checked_add(&amount)
644                    .ok_or(Error::BalanceOverflow)?;
645                Ok(())
646            })?;
647
648            Ok(())
649        })?;
650
651        Ok(())
652    }
653
654    fn reduce_domain_balance(domain_id: DomainId, amount: BalanceOf<T>) -> Result<(), Self::Error> {
655        DomainBalances::<T>::try_mutate(domain_id, |current_balance| {
656            *current_balance = current_balance
657                .checked_sub(&amount)
658                .ok_or(Error::LowBalanceOnDomain)?;
659            Ok(())
660        })?;
661        Self::decrease_all_domains_supply(amount);
662        Ok(())
663    }
664}
665
666impl<T: Config> NoteChainTransfer<BalanceOf<T>> for Pallet<T> {
667    fn note_transfer_in(amount: BalanceOf<T>, from_chain_id: ChainId) -> bool {
668        let result: DispatchResult = if T::SelfChainId::get().is_consensus_chain() {
669            Pallet::<T>::confirm_transfer(from_chain_id, T::SelfChainId::get(), amount)
670                .map_err(Into::into)
671        } else {
672            ChainTransfers::<T>::try_mutate(|transfers| {
673                Pallet::<T>::update_transfer_in(transfers, from_chain_id, amount)
674            })
675        };
676        if let Err(err) = result {
677            log::error!(
678                target: "runtime::transporter",
679                "note_transfer_in from {from_chain_id:?} failed: {err:?}"
680            );
681            return false;
682        }
683        true
684    }
685
686    fn note_transfer_out(amount: BalanceOf<T>, to_chain_id: ChainId) -> bool {
687        let result: DispatchResult = if T::SelfChainId::get().is_consensus_chain() {
688            Self::note_transfer(T::SelfChainId::get(), to_chain_id, amount).map_err(Into::into)
689        } else {
690            ChainTransfers::<T>::try_mutate(|transfers| {
691                Self::update_transfer_out(transfers, to_chain_id, amount)
692            })
693        };
694        if let Err(err) = result {
695            log::error!(
696                target: "runtime::transporter",
697                "note_transfer_out to {to_chain_id:?} failed: {err:?}"
698            );
699            return false;
700        }
701        true
702    }
703}
704
705impl<T: Config> Pallet<T> {
706    fn ensure_consensus_chain() -> Result<(), Error<T>> {
707        ensure!(
708            T::SelfChainId::get().is_consensus_chain(),
709            Error::NonConsensusChain
710        );
711
712        Ok(())
713    }
714
715    fn finalize_transfer(
716        src_chain_id: ChainId,
717        message_id: MessageIdOf<T>,
718        req: Transfer<BalanceOf<T>>,
719    ) -> EndpointResponse {
720        // mint the funds to dst_account
721        let account_id = T::AccountIdConverter::try_convert_back(req.receiver.account_id)
722            .ok_or(Error::<T>::InvalidAccountId)?;
723
724        // if this is consensus chain, then confirm the transfer
725        // else add transfer to storage to send through ER to consensus chain
726        if T::SelfChainId::get().is_consensus_chain() {
727            Pallet::<T>::confirm_transfer(src_chain_id, T::SelfChainId::get(), req.amount)?
728        } else {
729            ChainTransfers::<T>::try_mutate(|transfers| {
730                Pallet::<T>::update_transfer_in(transfers, src_chain_id, req.amount)
731            })?;
732        }
733
734        let _imbalance = T::Currency::deposit_creating(&account_id, req.amount);
735
736        frame_system::Pallet::<T>::deposit_event(
737            Into::<<T as frame_system::Config>::RuntimeEvent>::into(
738                Event::<T>::IncomingTransferSuccessful {
739                    chain_id: src_chain_id,
740                    message_id,
741                    amount: req.amount,
742                },
743            ),
744        );
745        Ok(vec![])
746    }
747
748    fn update_transfer_out(
749        transfers: &mut Transfers<BalanceOf<T>>,
750        to_chain_id: ChainId,
751        amount: BalanceOf<T>,
752    ) -> DispatchResult {
753        let total_transfer =
754            if let Some(current_transfer_amount) = transfers.transfers_out.get(&to_chain_id) {
755                current_transfer_amount
756                    .checked_add(&amount)
757                    .ok_or(Error::<T>::BalanceOverflow)?
758            } else {
759                amount
760            };
761        transfers.transfers_out.insert(to_chain_id, total_transfer);
762        Ok(())
763    }
764
765    fn update_transfer_in(
766        transfers: &mut Transfers<BalanceOf<T>>,
767        from_chain_id: ChainId,
768        amount: BalanceOf<T>,
769    ) -> DispatchResult {
770        let total_transfer =
771            if let Some(current_transfer_amount) = transfers.transfers_in.get(&from_chain_id) {
772                current_transfer_amount
773                    .checked_add(&amount)
774                    .ok_or(Error::<T>::BalanceOverflow)?
775            } else {
776                amount
777            };
778        transfers.transfers_in.insert(from_chain_id, total_transfer);
779        Ok(())
780    }
781
782    fn update_transfer_revert(
783        transfers: &mut Transfers<BalanceOf<T>>,
784        to_chain_id: ChainId,
785        amount: BalanceOf<T>,
786    ) -> DispatchResult {
787        let total_transfer = if let Some(current_transfer_amount) =
788            transfers.rejected_transfers_claimed.get(&to_chain_id)
789        {
790            current_transfer_amount
791                .checked_add(&amount)
792                .ok_or(Error::<T>::BalanceOverflow)?
793        } else {
794            amount
795        };
796        transfers
797            .rejected_transfers_claimed
798            .insert(to_chain_id, total_transfer);
799        Ok(())
800    }
801
802    fn update_transfer_rejected(
803        transfers: &mut Transfers<BalanceOf<T>>,
804        from_chain_id: ChainId,
805        amount: BalanceOf<T>,
806    ) -> DispatchResult {
807        let total_transfer = if let Some(current_transfer_amount) =
808            transfers.transfers_rejected.get(&from_chain_id)
809        {
810            current_transfer_amount
811                .checked_add(&amount)
812                .ok_or(Error::<T>::BalanceOverflow)?
813        } else {
814            amount
815        };
816        transfers
817            .transfers_rejected
818            .insert(from_chain_id, total_transfer);
819        Ok(())
820    }
821}