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