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