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