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