pallet_messenger/
fees.rs

1use crate::pallet::{
2    InboxFee, InboxFeesOnHold, InboxFeesOnHoldStartAt, InboxResponseMessageWeightTags,
3    InboxResponses, OutboxFee, OutboxFeesOnHold, OutboxFeesOnHoldStartAt,
4};
5use crate::{BalanceOf, Config, Error, Pallet};
6use frame_support::traits::fungible::{Balanced, Mutate};
7use frame_support::traits::tokens::{Fortitude, Precision, Preservation};
8use frame_support::weights::WeightToFee;
9use sp_core::Get;
10use sp_messenger::endpoint::{CollectedFee, Endpoint};
11use sp_messenger::messages::{ChainId, ChannelId, MessageId, Nonce};
12use sp_messenger::OnXDMRewards;
13use sp_runtime::traits::{CheckedAdd, CheckedMul, CheckedSub, Zero};
14use sp_runtime::{DispatchError, DispatchResult, Saturating};
15
16impl<T: Config> Pallet<T> {
17    /// Ensures the fees from the sender to complete the XDM request and response.
18    #[inline]
19    pub(crate) fn collect_fees_for_message_v1(
20        sender: &T::AccountId,
21        endpoint: &Endpoint,
22    ) -> Result<CollectedFee<BalanceOf<T>>, DispatchError> {
23        let handler = T::get_endpoint_handler(endpoint).ok_or(Error::<T>::NoMessageHandler)?;
24
25        let fee_multiplier = BalanceOf::<T>::from(T::FeeMultiplier::get());
26
27        // fees need to be paid for following
28        // - Execution on dst_chain. This is burned here and minted on dst_chain
29        let dst_chain_inbox_execution_fee =
30            T::AdjustedWeightToFee::weight_to_fee(&handler.message_weight());
31
32        // adjust the dst_chain fee with xdm multiplier
33        let dst_chain_fee = dst_chain_inbox_execution_fee
34            .checked_mul(&fee_multiplier)
35            .ok_or(Error::<T>::BalanceOverflow)?;
36
37        // - Execution of response on src_chain.
38        // - This is collected and given to operators once response is received.
39        let src_chain_outbox_response_execution_fee =
40            T::AdjustedWeightToFee::weight_to_fee(&handler.message_response_weight());
41
42        // adjust the src_chain fee with xdm multiplier
43        let src_chain_fee = src_chain_outbox_response_execution_fee
44            .checked_mul(&fee_multiplier)
45            .ok_or(Error::<T>::BalanceOverflow)?;
46
47        // burn the total fees
48        let total_fees = dst_chain_fee
49            .checked_add(&src_chain_fee)
50            .ok_or(Error::<T>::BalanceOverflow)?;
51        T::Currency::burn_from(
52            sender,
53            total_fees,
54            Preservation::Preserve,
55            Precision::Exact,
56            Fortitude::Polite,
57        )?;
58
59        Ok(CollectedFee {
60            src_chain_fee,
61            dst_chain_fee,
62        })
63    }
64
65    /// Rewards operators for executing an inbox message since src_chain signalled that responses are delivered.
66    /// Remove messages responses from Inbox responses.
67    /// All the messages with nonce <= latest_confirmed_nonce are deleted.
68    pub(crate) fn reward_operators_for_inbox_execution(
69        dst_chain_id: ChainId,
70        channel_id: ChannelId,
71        latest_confirmed_nonce: Option<Nonce>,
72    ) -> DispatchResult {
73        if latest_confirmed_nonce.is_none() {
74            return Ok(());
75        }
76
77        let mut current_nonce = latest_confirmed_nonce;
78        let mut inbox_fees = BalanceOf::<T>::zero();
79        let on_hold_start_at_nonce =
80            InboxFeesOnHoldStartAt::<T>::get(channel_id).unwrap_or(Nonce::MAX);
81        let mut on_hold_inbox_fees = BalanceOf::<T>::zero();
82        while let Some(nonce) = current_nonce {
83            if InboxResponses::<T>::take((dst_chain_id, channel_id, nonce)).is_none() {
84                // Make the loop efficient, by breaking as soon as there are no more responses.
85                // If we didn't break here, we'd spend a lot of CPU hashing missing StorageMap
86                // keys.
87                break;
88            }
89
90            // Remove weight tags for inbox response messages.
91            InboxResponseMessageWeightTags::<T>::remove((dst_chain_id, (channel_id, nonce)));
92
93            // For every inbox response we take, distribute the reward to the operators.
94            if let Some(inbox_fee) = InboxFee::<T>::take((dst_chain_id, (channel_id, nonce))) {
95                inbox_fees = inbox_fees.saturating_add(inbox_fee);
96                if on_hold_start_at_nonce <= nonce {
97                    on_hold_inbox_fees = on_hold_inbox_fees.saturating_add(inbox_fee);
98                }
99            }
100
101            current_nonce = nonce.checked_sub(Nonce::one())
102        }
103
104        if !inbox_fees.is_zero() {
105            if !on_hold_inbox_fees.is_zero() {
106                InboxFeesOnHold::<T>::mutate(|inbox_fees_on_hold| {
107                    *inbox_fees_on_hold = inbox_fees_on_hold
108                        .checked_sub(&on_hold_inbox_fees)
109                        .ok_or(Error::<T>::BalanceUnderflow)?;
110
111                    // If the `imbalance` is dropped without consuming it will increase the total issuance by
112                    // the same amount as we rescinded here, thus we need to manually `mem::forget` it.
113                    let imbalance = T::Currency::rescind(on_hold_inbox_fees);
114                    core::mem::forget(imbalance);
115
116                    Ok::<(), Error<T>>(())
117                })?;
118            }
119
120            Self::reward_operators(inbox_fees);
121        }
122
123        Ok(())
124    }
125
126    pub(crate) fn reward_operators_for_outbox_execution(
127        dst_chain_id: ChainId,
128        message_id: MessageId,
129    ) -> DispatchResult {
130        if let Some(fee) = OutboxFee::<T>::take((dst_chain_id, message_id)) {
131            let update_on_hold = OutboxFeesOnHoldStartAt::<T>::get(message_id.0)
132                .map(|start_at_nonce| start_at_nonce <= message_id.1)
133                .unwrap_or(false);
134            if update_on_hold {
135                OutboxFeesOnHold::<T>::mutate(|outbox_fees_on_hold| {
136                    *outbox_fees_on_hold = outbox_fees_on_hold
137                        .checked_sub(&fee)
138                        .ok_or(Error::<T>::BalanceUnderflow)?;
139
140                    // If the `imbalance` is dropped without consuming it will increase the total issuance by
141                    // the same amount as we rescinded here, thus we need to manually `mem::forget` it.
142                    let imbalance = T::Currency::rescind(fee);
143                    core::mem::forget(imbalance);
144
145                    Ok::<(), Error<T>>(())
146                })?;
147            }
148
149            Self::reward_operators(fee);
150        }
151        Ok(())
152    }
153
154    /// Increments the current block's relayer rewards.
155    fn reward_operators(reward: BalanceOf<T>) {
156        T::OnXDMRewards::on_xdm_rewards(reward)
157    }
158}