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