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::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/// Location that either sends or receives transfers between chains.
50#[derive(Debug, Encode, Decode, Clone, Eq, PartialEq, TypeInfo)]
51pub struct Location {
52    /// Unique identity of chain.
53    pub chain_id: ChainId,
54    /// Unique account on chain.
55    pub account_id: MultiAccountId,
56}
57
58/// Transfer of funds from one chain to another.
59#[derive(Debug, Encode, Decode, Clone, Eq, PartialEq, TypeInfo)]
60pub struct Transfer<Balance> {
61    /// Amount being transferred between entities.
62    pub amount: Balance,
63    /// Sender location of the transfer.
64    pub sender: Location,
65    /// Receiver location of the transfer.
66    pub receiver: Location,
67}
68
69/// Balance type used by the pallet.
70pub(crate) type BalanceOf<T> =
71    <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
72
73type MessageIdOf<T> = <<T as Config>::Sender as sp_messenger::endpoint::Sender<
74    <T as frame_system::Config>::AccountId,
75>>::MessageId;
76
77#[frame_support::pallet]
78mod pallet {
79    use crate::weights::WeightInfo;
80    use crate::{BalanceOf, Location, MessageIdOf, MultiAccountId, Transfer, TryConvertBack};
81    #[cfg(not(feature = "std"))]
82    use alloc::vec::Vec;
83    use frame_support::pallet_prelude::*;
84    use frame_support::traits::{Currency, ExistenceRequirement, WithdrawReasons};
85    use frame_support::weights::Weight;
86    use frame_system::pallet_prelude::*;
87    use parity_scale_codec::{Decode, Encode};
88    use sp_domains::execution_receipt::Transfers;
89    use sp_domains::{DomainId, DomainsTransfersTracker};
90    use sp_messenger::endpoint::{
91        Endpoint, EndpointHandler as EndpointHandlerT, EndpointId, EndpointRequest,
92        EndpointResponse, Sender,
93    };
94    use sp_messenger::messages::ChainId;
95    use sp_runtime::traits::Convert;
96    use sp_std::vec;
97
98    #[pallet::config]
99    pub trait Config: frame_system::Config {
100        /// Event type for this pallet.
101        type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
102
103        /// Gets the chain_id of the current execution environment.
104        type SelfChainId: Get<ChainId>;
105
106        /// Gets the endpoint_id of this pallet in a given execution environment.
107        type SelfEndpointId: Get<EndpointId>;
108
109        /// Currency used by this pallet.
110        type Currency: Currency<Self::AccountId>;
111
112        /// Sender used to transfer funds.
113        type Sender: Sender<Self::AccountId>;
114
115        /// MultiAccountID <> T::AccountId converter.
116        type AccountIdConverter: TryConvertBack<Self::AccountId, MultiAccountId>;
117
118        /// Weight information for extrinsics in this pallet.
119        type WeightInfo: WeightInfo;
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                Self::note_transfer(T::SelfChainId::get(), dst_chain_id, amount)?
300            } else {
301                ChainTransfers::<T>::try_mutate(|transfers| {
302                    Self::update_transfer_out(transfers, dst_chain_id, amount)
303                })?;
304            }
305
306            Ok(())
307        }
308    }
309
310    #[pallet::hooks]
311    impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
312        fn on_initialize(_n: BlockNumberFor<T>) -> Weight {
313            // NOTE: set the `ChainTransfers` to an empty value instead of removing the value completely
314            // so we can generate a storage proof to prove the empty value, which is required by the fraud
315            // proof.
316            ChainTransfers::<T>::set(Default::default());
317            T::DbWeight::get().writes(1)
318        }
319    }
320
321    impl<T: Config> Pallet<T> {
322        pub fn transfers_storage_key() -> Vec<u8> {
323            use frame_support::storage::generator::StorageValue;
324            ChainTransfers::<T>::storage_value_final_key().to_vec()
325        }
326    }
327
328    /// Endpoint handler implementation for pallet transporter.
329    #[derive(Debug)]
330    pub struct EndpointHandler<T>(pub PhantomData<T>);
331
332    impl<T: Config> EndpointHandlerT<MessageIdOf<T>> for EndpointHandler<T> {
333        fn message(
334            &self,
335            src_chain_id: ChainId,
336            message_id: MessageIdOf<T>,
337            req: EndpointRequest,
338            pre_check_result: DispatchResult,
339        ) -> EndpointResponse {
340            // decode payload
341            let dst_endpoint = req.dst_endpoint;
342            let req = match Transfer::decode(&mut req.payload.as_slice()) {
343                Ok(req) => req,
344                Err(_) => return Err(Error::<T>::InvalidPayload.into()),
345            };
346
347            let pre_check_handler = || {
348                // ensure message is not from the self
349                ensure!(
350                    T::SelfChainId::get() != src_chain_id,
351                    Error::<T>::InvalidTransferRequest
352                );
353
354                // check the endpoint id
355                ensure!(
356                    dst_endpoint == Endpoint::Id(T::SelfEndpointId::get()),
357                    Error::<T>::UnexpectedMessage
358                );
359
360                pre_check_result
361            };
362
363            let amount = req.amount;
364            let response = match pre_check_handler() {
365                Ok(_) => Pallet::<T>::finalize_transfer(src_chain_id, message_id, req),
366                Err(err) => Err(err),
367            };
368
369            if response.is_err() {
370                // if this is consensus chain, then reject the transfer
371                // else update the Transfers storage with rejected transfer
372                if T::SelfChainId::get().is_consensus_chain() {
373                    Pallet::<T>::reject_transfer(src_chain_id, T::SelfChainId::get(), amount)?;
374                } else {
375                    ChainTransfers::<T>::try_mutate(|transfers| {
376                        Pallet::<T>::update_transfer_rejected(transfers, src_chain_id, amount)
377                    })?;
378                }
379            }
380
381            response
382        }
383
384        fn message_weight(&self) -> Weight {
385            T::WeightInfo::message()
386        }
387
388        fn message_response(
389            &self,
390            dst_chain_id: ChainId,
391            message_id: MessageIdOf<T>,
392            req: EndpointRequest,
393            resp: EndpointResponse,
394        ) -> DispatchResult {
395            // ensure request is valid
396            let transfer = OutgoingTransfers::<T>::take(dst_chain_id, message_id)
397                .ok_or(Error::<T>::MissingTransferRequest)?;
398            ensure!(
399                req.payload == transfer.encode(),
400                Error::<T>::InvalidTransferRequest
401            );
402
403            // process response
404            match resp {
405                Ok(_) => {
406                    // transfer is successful
407                    frame_system::Pallet::<T>::deposit_event(
408                        Into::<<T as Config>::RuntimeEvent>::into(
409                            Event::<T>::OutgoingTransferSuccessful {
410                                chain_id: dst_chain_id,
411                                message_id,
412                            },
413                        ),
414                    );
415                }
416                Err(err) => {
417                    // transfer failed
418                    // revert burned funds
419                    let account_id =
420                        T::AccountIdConverter::try_convert_back(transfer.sender.account_id)
421                            .ok_or(Error::<T>::InvalidAccountId)?;
422
423                    // if this is consensus chain, then revert the transfer
424                    // else update the Transfers storage with reverted transfer
425                    if T::SelfChainId::get().is_consensus_chain() {
426                        Pallet::<T>::claim_rejected_transfer(
427                            T::SelfChainId::get(),
428                            dst_chain_id,
429                            transfer.amount,
430                        )?;
431                    } else {
432                        ChainTransfers::<T>::try_mutate(|transfers| {
433                            Pallet::<T>::update_transfer_revert(
434                                transfers,
435                                dst_chain_id,
436                                transfer.amount,
437                            )
438                        })?;
439                    }
440
441                    let _imbalance = T::Currency::deposit_creating(&account_id, transfer.amount);
442                    frame_system::Pallet::<T>::deposit_event(
443                        Into::<<T as Config>::RuntimeEvent>::into(
444                            Event::<T>::OutgoingTransferFailed {
445                                chain_id: dst_chain_id,
446                                message_id,
447                                err,
448                            },
449                        ),
450                    );
451                }
452            }
453
454            Ok(())
455        }
456
457        fn message_response_weight(&self) -> Weight {
458            T::WeightInfo::message_response()
459        }
460    }
461}
462
463impl<T: Config> sp_domains::DomainsTransfersTracker<BalanceOf<T>> for Pallet<T> {
464    type Error = Error<T>;
465
466    fn initialize_domain_balance(
467        domain_id: DomainId,
468        amount: BalanceOf<T>,
469    ) -> Result<(), Self::Error> {
470        Self::ensure_consensus_chain()?;
471
472        ensure!(
473            !DomainBalances::<T>::contains_key(domain_id),
474            Error::DomainBalanceAlreadyInitialized
475        );
476
477        DomainBalances::<T>::set(domain_id, amount);
478        Ok(())
479    }
480
481    fn note_transfer(
482        from_chain_id: ChainId,
483        to_chain_id: ChainId,
484        amount: BalanceOf<T>,
485    ) -> Result<(), Self::Error> {
486        Self::ensure_consensus_chain()?;
487
488        UnconfirmedTransfers::<T>::try_mutate(from_chain_id, to_chain_id, |total_amount| {
489            if let Some(domain_id) = from_chain_id.maybe_domain_chain() {
490                DomainBalances::<T>::try_mutate(domain_id, |current_balance| {
491                    *current_balance = current_balance
492                        .checked_sub(&amount)
493                        .ok_or(Error::LowBalanceOnDomain)?;
494                    Ok(())
495                })?;
496            }
497
498            *total_amount = total_amount
499                .checked_add(&amount)
500                .ok_or(Error::BalanceOverflow)?;
501            Ok(())
502        })?;
503
504        Ok(())
505    }
506
507    fn confirm_transfer(
508        from_chain_id: ChainId,
509        to_chain_id: ChainId,
510        amount: BalanceOf<T>,
511    ) -> Result<(), Self::Error> {
512        Self::ensure_consensus_chain()?;
513        UnconfirmedTransfers::<T>::try_mutate(from_chain_id, to_chain_id, |total_amount| {
514            *total_amount = total_amount
515                .checked_sub(&amount)
516                .ok_or(Error::BalanceUnderflow)?;
517
518            if let Some(domain_id) = to_chain_id.maybe_domain_chain() {
519                DomainBalances::<T>::try_mutate(domain_id, |current_balance| {
520                    *current_balance = current_balance
521                        .checked_add(&amount)
522                        .ok_or(Error::BalanceOverflow)?;
523                    Ok(())
524                })?;
525            }
526
527            Ok(())
528        })?;
529
530        Ok(())
531    }
532
533    fn claim_rejected_transfer(
534        from_chain_id: ChainId,
535        to_chain_id: ChainId,
536        amount: BalanceOf<T>,
537    ) -> Result<(), Self::Error> {
538        Self::ensure_consensus_chain()?;
539        CancelledTransfers::<T>::try_mutate(from_chain_id, to_chain_id, |total_amount| {
540            *total_amount = total_amount
541                .checked_sub(&amount)
542                .ok_or(Error::BalanceUnderflow)?;
543
544            if let Some(domain_id) = from_chain_id.maybe_domain_chain() {
545                DomainBalances::<T>::try_mutate(domain_id, |current_balance| {
546                    *current_balance = current_balance
547                        .checked_add(&amount)
548                        .ok_or(Error::BalanceOverflow)?;
549                    Ok(())
550                })?;
551            }
552
553            Ok(())
554        })?;
555
556        Ok(())
557    }
558
559    fn reject_transfer(
560        from_chain_id: ChainId,
561        to_chain_id: ChainId,
562        amount: BalanceOf<T>,
563    ) -> Result<(), Self::Error> {
564        Self::ensure_consensus_chain()?;
565        UnconfirmedTransfers::<T>::try_mutate(from_chain_id, to_chain_id, |total_amount| {
566            *total_amount = total_amount
567                .checked_sub(&amount)
568                .ok_or(Error::BalanceUnderflow)?;
569
570            CancelledTransfers::<T>::try_mutate(from_chain_id, to_chain_id, |total_amount| {
571                *total_amount = total_amount
572                    .checked_add(&amount)
573                    .ok_or(Error::BalanceOverflow)?;
574                Ok(())
575            })?;
576
577            Ok(())
578        })?;
579
580        Ok(())
581    }
582
583    fn reduce_domain_balance(domain_id: DomainId, amount: BalanceOf<T>) -> Result<(), Self::Error> {
584        DomainBalances::<T>::try_mutate(domain_id, |current_balance| {
585            *current_balance = current_balance
586                .checked_sub(&amount)
587                .ok_or(Error::LowBalanceOnDomain)?;
588            Ok(())
589        })
590    }
591}
592
593impl<T: Config> NoteChainTransfer<BalanceOf<T>> for Pallet<T> {
594    fn note_transfer_in(amount: BalanceOf<T>, from_chain_id: ChainId) -> bool {
595        if T::SelfChainId::get().is_consensus_chain() {
596            Pallet::<T>::confirm_transfer(from_chain_id, T::SelfChainId::get(), amount).is_ok()
597        } else {
598            ChainTransfers::<T>::try_mutate(|transfers| {
599                Pallet::<T>::update_transfer_in(transfers, from_chain_id, amount)
600            })
601            .is_ok()
602        }
603    }
604
605    fn note_transfer_out(amount: BalanceOf<T>, to_chain_id: ChainId) -> bool {
606        if T::SelfChainId::get().is_consensus_chain() {
607            Self::note_transfer(T::SelfChainId::get(), to_chain_id, amount).is_ok()
608        } else {
609            ChainTransfers::<T>::try_mutate(|transfers| {
610                Self::update_transfer_out(transfers, to_chain_id, amount)
611            })
612            .is_ok()
613        }
614    }
615}
616
617impl<T: Config> Pallet<T> {
618    fn ensure_consensus_chain() -> Result<(), Error<T>> {
619        ensure!(
620            T::SelfChainId::get().is_consensus_chain(),
621            Error::NonConsensusChain
622        );
623
624        Ok(())
625    }
626
627    fn finalize_transfer(
628        src_chain_id: ChainId,
629        message_id: MessageIdOf<T>,
630        req: Transfer<BalanceOf<T>>,
631    ) -> EndpointResponse {
632        // mint the funds to dst_account
633        let account_id = T::AccountIdConverter::try_convert_back(req.receiver.account_id)
634            .ok_or(Error::<T>::InvalidAccountId)?;
635
636        // if this is consensus chain, then confirm the transfer
637        // else add transfer to storage to send through ER to consensus chain
638        if T::SelfChainId::get().is_consensus_chain() {
639            Pallet::<T>::confirm_transfer(src_chain_id, T::SelfChainId::get(), req.amount)?
640        } else {
641            ChainTransfers::<T>::try_mutate(|transfers| {
642                Pallet::<T>::update_transfer_in(transfers, src_chain_id, req.amount)
643            })?;
644        }
645
646        let _imbalance = T::Currency::deposit_creating(&account_id, req.amount);
647
648        frame_system::Pallet::<T>::deposit_event(Into::<<T as Config>::RuntimeEvent>::into(
649            Event::<T>::IncomingTransferSuccessful {
650                chain_id: src_chain_id,
651                message_id,
652                amount: req.amount,
653            },
654        ));
655        Ok(vec![])
656    }
657
658    fn update_transfer_out(
659        transfers: &mut Transfers<BalanceOf<T>>,
660        to_chain_id: ChainId,
661        amount: BalanceOf<T>,
662    ) -> DispatchResult {
663        let total_transfer =
664            if let Some(current_transfer_amount) = transfers.transfers_out.get(&to_chain_id) {
665                current_transfer_amount
666                    .checked_add(&amount)
667                    .ok_or(Error::<T>::BalanceOverflow)?
668            } else {
669                amount
670            };
671        transfers.transfers_out.insert(to_chain_id, total_transfer);
672        Ok(())
673    }
674
675    fn update_transfer_in(
676        transfers: &mut Transfers<BalanceOf<T>>,
677        from_chain_id: ChainId,
678        amount: BalanceOf<T>,
679    ) -> DispatchResult {
680        let total_transfer =
681            if let Some(current_transfer_amount) = transfers.transfers_in.get(&from_chain_id) {
682                current_transfer_amount
683                    .checked_add(&amount)
684                    .ok_or(Error::<T>::BalanceOverflow)?
685            } else {
686                amount
687            };
688        transfers.transfers_in.insert(from_chain_id, total_transfer);
689        Ok(())
690    }
691
692    fn update_transfer_revert(
693        transfers: &mut Transfers<BalanceOf<T>>,
694        to_chain_id: ChainId,
695        amount: BalanceOf<T>,
696    ) -> DispatchResult {
697        let total_transfer = if let Some(current_transfer_amount) =
698            transfers.rejected_transfers_claimed.get(&to_chain_id)
699        {
700            current_transfer_amount
701                .checked_add(&amount)
702                .ok_or(Error::<T>::BalanceOverflow)?
703        } else {
704            amount
705        };
706        transfers
707            .rejected_transfers_claimed
708            .insert(to_chain_id, total_transfer);
709        Ok(())
710    }
711
712    fn update_transfer_rejected(
713        transfers: &mut Transfers<BalanceOf<T>>,
714        from_chain_id: ChainId,
715        amount: BalanceOf<T>,
716    ) -> DispatchResult {
717        let total_transfer = if let Some(current_transfer_amount) =
718            transfers.transfers_rejected.get(&from_chain_id)
719        {
720            current_transfer_amount
721                .checked_add(&amount)
722                .ok_or(Error::<T>::BalanceOverflow)?
723        } else {
724            amount
725        };
726        transfers
727            .transfers_rejected
728            .insert(from_chain_id, total_transfer);
729        Ok(())
730    }
731}