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