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