pallet_ismp/fee_handler.rs
1// Copyright (c) 2025 Polytope Labs.
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
16use core::marker::PhantomData;
17
18use alloc::vec::Vec;
19use codec::{Decode, Encode};
20use frame_support::{
21 dispatch::{DispatchResultWithPostInfo, Pays, PostDispatchInfo},
22 traits::{Currency, ExistenceRequirement, Get},
23};
24use impl_trait_for_tuples::impl_for_tuples;
25use ismp::messaging::{Message, MessageWithWeight};
26use polkadot_sdk::{
27 frame_support::{weights::WeightToFee, PalletId},
28 sp_runtime::{traits::AccountIdConversion, Weight},
29 *,
30};
31use sp_runtime::{
32 traits::{MaybeDisplay, Member, Zero},
33 DispatchError,
34};
35
36use crypto_utils::verification::Signature;
37use ismp::events::Event;
38
39/// Trait for handling fee calculations and settlements in the ISMP protocol.
40///
41/// This trait defines the interface for fee handling strategies in cross-chain message processing.
42/// Implementations can define various fee models based on the specific requirements of their
43/// blockchain ecosystem, economic incentives, and governance preferences.
44///
45/// The ISMP protocol supports multiple types of messages, including requests, responses,
46/// timeouts, and consensus messages, each potentially requiring different fee structures.
47/// This trait allows for creating custom fee handling logic that can be tailored to specific
48/// use cases.
49///
50/// ## Fee Handling Strategies
51///
52/// Implementations of this trait can support various fee models including:
53///
54/// * **Weight-based fees**: Charging based on computational resources used
55/// * **Message-type-based fees**: Different fees for different message types
56/// * **Subsidized models**: Where certain operations have reduced or zero fees
57/// * **Incentive structures**: Where relayers or validators receive rewards
58/// * **Market-based mechanisms**: Where fees adjust based on network congestion
59///
60/// ## Implementation Notes
61///
62/// Fee handlers should be designed with the following considerations:
63///
64/// 1. **Efficiency**: Fee calculation should be computationally inexpensive
65/// 2. **Fairness**: Fees should fairly reflect resource usage
66/// 3. **Economic security**: Fee models should prevent spam and DoS attacks
67/// 4. **Incentive alignment**: Fee structures should encourage proper protocol participation
68pub trait FeeHandler {
69 /// Process a batch of successfully executed messages and calculate appropriate fees.
70 ///
71 /// This method is invoked once a batch of messages have been successfully processed.
72 /// It is the responsibility of implementers to calculate and return the appropriate
73 /// `PostDispatchInfo` for fee calculation and settlement based on the messages processed.
74 ///
75 /// ## Parameters
76 ///
77 /// * `messages` - A vector of ISMP protocol messages that have been processed. This includes
78 /// various message types such as requests, responses, timeouts, and consensus messages.
79 ///
80 /// ## Returns
81 ///
82 /// Returns a `DispatchResultWithPostInfo` which includes:
83 ///
84 /// * `actual_weight` - The computational weight consumed by processing the messages
85 /// * `pays_fee` - Whether the operation should incur fees or not
86 ///
87 /// ## Design Flexibility
88 ///
89 /// This method is deliberately designed to provide flexibility and support a wide range of fee
90 /// collection strategies across different blockchain ecosystems. It can accommodate various
91 /// economic models including:
92 ///
93 /// * Traditional fee payments where users pay for message processing
94 /// * "Negative fees" or incentive structures where relayers receive rewards
95 /// * Hybrid models with different fee structures for different message types
96 /// * Context-aware pricing based on network conditions or message priority
97 ///
98 /// ## Implementation Considerations
99 ///
100 /// Implementers should consider:
101 ///
102 /// * The computational cost of processing different message types
103 /// * Economic incentives for relayers and validators
104 /// * Prevention of spam and denial-of-service attacks
105 /// * Fairness across different types of network participants
106 fn on_executed(
107 messages: Vec<MessageWithWeight>,
108 events: Vec<Event>,
109 ) -> DispatchResultWithPostInfo;
110}
111
112/// A weight-based fee handler implementation that calculates and charges fees based on message
113/// processing weight.
114///
115/// This implementation computes the weight consumed by a batch of messages, converts this weight
116/// into a fee, and charges the fee to the message originator's account. The behavior is
117/// configurable through a `ChargePolicy`.
118///
119/// ## Type Parameters
120///
121/// * `AccountId` - The account identifier type used to identify fee payers.
122/// * `C` - The `Currency` trait implementation for handling balances.
123/// * `W` - A type implementing `WeightToFee` to convert computational `Weight` into a `Balance`.
124/// * `T` - A `Get<AccountId>` implementation that returns the Treasury's account ID.
125/// * `Policy` - A type implementing `ChargePolicy` to determine if fees should be charged.
126///
127/// ## Examples
128///
129/// This handler is typically configured in a runtime to establish a weight-based fee model.
130///
131/// ```ignore
132/// // In the runtime configuration
133/// use pallet_ismp::fee_handler::{self, WeightToFee};
134/// use frame_support::weights::WeightToFee as SubstrateWeightToFee;
135///
136/// // An adapter to use the runtime's default WeightToFee implementation
137/// pub struct IsmpWeightToFee;
138/// impl WeightToFee for IsmpWeightToFee {
139/// type Balance = Balance;
140/// fn convert(weight: Weight) -> Self::Balance {
141/// <Runtime as pallet_transaction_payment::Config>::WeightToFee::weight_to_fee(&weight)
142/// }
143/// }
144///
145/// // Define the fee handler type
146/// type FeeHandler = fee_handler::WeightFeeHandler<
147/// AccountId,
148/// Balances,
149/// IsmpWeightToFee,
150/// TreasuryPalletId.
151/// true
152/// >;
153/// ```
154pub struct WeightFeeHandler<AccountId, C, W, T, const POLICY: bool>(
155 PhantomData<(AccountId, C, W, T)>,
156);
157
158type BalanceOf<C, AccountId> = <C as Currency<AccountId>>::Balance;
159
160impl<AccountId, C, W, T, const POLICY: bool> FeeHandler
161 for WeightFeeHandler<AccountId, C, W, T, POLICY>
162where
163 AccountId: Member + MaybeDisplay + Decode + Encode,
164 C: Currency<AccountId>,
165 W: WeightToFee<Balance = BalanceOf<C, AccountId>>,
166 T: Get<PalletId>,
167{
168 fn on_executed(
169 messages: Vec<MessageWithWeight>,
170 _events: Vec<Event>,
171 ) -> DispatchResultWithPostInfo {
172 if !POLICY {
173 return Ok(PostDispatchInfo { actual_weight: None, pays_fee: Pays::No })
174 }
175 let mut total_weight = Weight::zero();
176 let treasury_account: AccountId = T::get().into_account_truncating();
177
178 for message in &messages {
179 let weight = message.weight;
180 total_weight.saturating_accrue(weight);
181 let fee = W::weight_to_fee(&weight);
182
183 if fee.is_zero() {
184 continue
185 }
186
187 let originator = match message.message.clone() {
188 Message::Request(msg) => {
189 let data = sp_io::hashing::keccak_256(&msg.requests.encode());
190 Signature::decode(&mut &msg.signer[..])
191 .ok()
192 .and_then(|sig| sig.verify_and_get_sr25519_pubkey(&data, None).ok())
193 },
194 Message::Response(msg) => {
195 let data = sp_io::hashing::keccak_256(&msg.requests.encode());
196 Signature::decode(&mut &msg.signer[..])
197 .ok()
198 .and_then(|sig| sig.verify_and_get_sr25519_pubkey(&data, None).ok())
199 },
200 _ => None,
201 };
202
203 if let Some(originator_bytes) = originator {
204 if let Ok(account) = AccountId::decode(&mut &originator_bytes[..]) {
205 C::transfer(&account, &treasury_account, fee, ExistenceRequirement::KeepAlive)
206 .map_err(|_| DispatchError::Other("Failed to transfer fee to treasury"))?;
207 }
208 }
209 }
210
211 Ok(PostDispatchInfo { actual_weight: Some(total_weight), pays_fee: Pays::Yes })
212 }
213}
214
215#[impl_for_tuples(5)]
216impl FeeHandler for TupleIdentifier {
217 fn on_executed(
218 messages: Vec<MessageWithWeight>,
219 events: Vec<Event>,
220 ) -> DispatchResultWithPostInfo {
221 for_tuples!( #(
222 <TupleIdentifier as FeeHandler>::on_executed(messages.clone(), events.clone())?;
223 )* );
224
225 Ok(Default::default())
226 }
227}