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