Skip to main content

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}