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