1use alloy_primitives::{B256, U256};
2use core::cmp::min;
3use thiserror::Error;
4
5#[derive(Debug)]
7pub struct CallFees {
8 pub max_priority_fee_per_gas: Option<U256>,
10 pub gas_price: U256,
17 pub max_fee_per_blob_gas: Option<U256>,
19}
20
21impl CallFees {
22 pub fn ensure_fees(
41 call_gas_price: Option<U256>,
42 call_max_fee: Option<U256>,
43 call_priority_fee: Option<U256>,
44 block_base_fee: U256,
45 blob_versioned_hashes: Option<&[B256]>,
46 max_fee_per_blob_gas: Option<U256>,
47 block_blob_fee: Option<U256>,
48 ) -> Result<Self, CallFeesError> {
49 fn get_effective_gas_price(
52 max_fee_per_gas: Option<U256>,
53 max_priority_fee_per_gas: Option<U256>,
54 block_base_fee: U256,
55 ) -> Result<U256, CallFeesError> {
56 match max_fee_per_gas {
57 Some(max_fee) => {
58 let max_priority_fee_per_gas = max_priority_fee_per_gas.unwrap_or(U256::ZERO);
59
60 if !(max_fee.is_zero() && max_priority_fee_per_gas.is_zero()) &&
62 max_fee < block_base_fee
63 {
64 return Err(CallFeesError::FeeCapTooLow);
66 }
67 if max_fee < max_priority_fee_per_gas {
68 return Err(
69 CallFeesError::TipAboveFeeCap,
71 );
72 }
73 Ok(min(
75 max_fee,
76 block_base_fee
77 .checked_add(max_priority_fee_per_gas)
78 .ok_or(CallFeesError::TipVeryHigh)?,
79 ))
80 }
81 None => Ok(block_base_fee
82 .checked_add(max_priority_fee_per_gas.unwrap_or(U256::ZERO))
83 .ok_or(CallFeesError::TipVeryHigh)?),
84 }
85 }
86
87 let has_blob_hashes =
88 blob_versioned_hashes.as_ref().map(|blobs| !blobs.is_empty()).unwrap_or(false);
89
90 match (call_gas_price, call_max_fee, call_priority_fee, max_fee_per_blob_gas) {
91 (gas_price, None, None, None) => {
92 let gas_price = gas_price.unwrap_or(U256::ZERO);
95 Ok(Self {
96 gas_price,
97 max_priority_fee_per_gas: None,
98 max_fee_per_blob_gas: has_blob_hashes.then_some(block_blob_fee).flatten(),
99 })
100 }
101 (None, max_fee_per_gas, max_priority_fee_per_gas, None) => {
102 let effective_gas_price = get_effective_gas_price(
104 max_fee_per_gas,
105 max_priority_fee_per_gas,
106 block_base_fee,
107 )?;
108 let max_fee_per_blob_gas = has_blob_hashes.then_some(block_blob_fee).flatten();
109
110 Ok(Self {
111 gas_price: effective_gas_price,
112 max_priority_fee_per_gas,
113 max_fee_per_blob_gas,
114 })
115 }
116 (None, max_fee_per_gas, max_priority_fee_per_gas, Some(max_fee_per_blob_gas)) => {
117 let effective_gas_price = get_effective_gas_price(
119 max_fee_per_gas,
120 max_priority_fee_per_gas,
121 block_base_fee,
122 )?;
123 if !has_blob_hashes {
125 return Err(CallFeesError::BlobTransactionMissingBlobHashes);
127 }
128
129 Ok(Self {
130 gas_price: effective_gas_price,
131 max_priority_fee_per_gas,
132 max_fee_per_blob_gas: Some(max_fee_per_blob_gas),
133 })
134 }
135 _ => {
136 Err(CallFeesError::ConflictingFeeFieldsInRequest)
138 }
139 }
140 }
141}
142
143#[derive(Debug, Error)]
145pub enum CallFeesError {
146 #[error("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")]
149 ConflictingFeeFieldsInRequest,
150 #[error("max fee per gas less than block base fee")]
152 FeeCapTooLow,
153 #[error("max priority fee per gas higher than max fee per gas")]
156 TipAboveFeeCap,
157 #[error("max priority fee per gas higher than 2^256-1")]
159 TipVeryHigh,
160 #[error("blob transaction missing blob hashes")]
162 BlobTransactionMissingBlobHashes,
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168 use alloy_consensus::constants::GWEI_TO_WEI;
169
170 #[test]
171 fn test_ensure_0_fallback() {
172 let CallFees { gas_price, .. } =
173 CallFees::ensure_fees(None, None, None, U256::from(99), None, None, Some(U256::ZERO))
174 .unwrap();
175 assert!(gas_price.is_zero());
176 }
177
178 #[test]
179 fn test_ensure_max_fee_0_exception() {
180 let CallFees { gas_price, .. } =
181 CallFees::ensure_fees(None, Some(U256::ZERO), None, U256::from(99), None, None, None)
182 .unwrap();
183 assert!(gas_price.is_zero());
184 }
185
186 #[test]
187 fn test_blob_fees() {
188 let CallFees { gas_price, max_fee_per_blob_gas, .. } =
189 CallFees::ensure_fees(None, None, None, U256::from(99), None, None, Some(U256::ZERO))
190 .unwrap();
191 assert!(gas_price.is_zero());
192 assert_eq!(max_fee_per_blob_gas, None);
193
194 let CallFees { gas_price, max_fee_per_blob_gas, .. } = CallFees::ensure_fees(
195 None,
196 None,
197 None,
198 U256::from(99),
199 Some(&[B256::from(U256::ZERO)]),
200 None,
201 Some(U256::from(99)),
202 )
203 .unwrap();
204 assert!(gas_price.is_zero());
205 assert_eq!(max_fee_per_blob_gas, Some(U256::from(99)));
206 }
207
208 #[test]
209 fn test_eip_1559_fees() {
210 let CallFees { gas_price, .. } = CallFees::ensure_fees(
211 None,
212 Some(U256::from(25 * GWEI_TO_WEI)),
213 Some(U256::from(15 * GWEI_TO_WEI)),
214 U256::from(15 * GWEI_TO_WEI),
215 None,
216 None,
217 Some(U256::ZERO),
218 )
219 .unwrap();
220 assert_eq!(gas_price, U256::from(25 * GWEI_TO_WEI));
221
222 let CallFees { gas_price, .. } = CallFees::ensure_fees(
223 None,
224 Some(U256::from(25 * GWEI_TO_WEI)),
225 Some(U256::from(5 * GWEI_TO_WEI)),
226 U256::from(15 * GWEI_TO_WEI),
227 None,
228 None,
229 Some(U256::ZERO),
230 )
231 .unwrap();
232 assert_eq!(gas_price, U256::from(20 * GWEI_TO_WEI));
233
234 let CallFees { gas_price, .. } = CallFees::ensure_fees(
235 None,
236 Some(U256::from(30 * GWEI_TO_WEI)),
237 Some(U256::from(30 * GWEI_TO_WEI)),
238 U256::from(15 * GWEI_TO_WEI),
239 None,
240 None,
241 Some(U256::ZERO),
242 )
243 .unwrap();
244 assert_eq!(gas_price, U256::from(30 * GWEI_TO_WEI));
245
246 let call_fees = CallFees::ensure_fees(
247 None,
248 Some(U256::from(30 * GWEI_TO_WEI)),
249 Some(U256::from(31 * GWEI_TO_WEI)),
250 U256::from(15 * GWEI_TO_WEI),
251 None,
252 None,
253 Some(U256::ZERO),
254 );
255 assert!(call_fees.is_err());
256
257 let call_fees = CallFees::ensure_fees(
258 None,
259 Some(U256::from(5 * GWEI_TO_WEI)),
260 Some(U256::from(GWEI_TO_WEI)),
261 U256::from(15 * GWEI_TO_WEI),
262 None,
263 None,
264 Some(U256::ZERO),
265 );
266 assert!(call_fees.is_err());
267
268 let call_fees = CallFees::ensure_fees(
269 None,
270 Some(U256::MAX),
271 Some(U256::MAX),
272 U256::from(5 * GWEI_TO_WEI),
273 None,
274 None,
275 Some(U256::ZERO),
276 );
277 assert!(call_fees.is_err());
278 }
279}