1use std::future::IntoFuture;
2
3use crate::{
4 fillers::{FillerControlFlow, TxFiller},
5 provider::SendableTx,
6 utils::Eip1559Estimation,
7 Provider,
8};
9use alloy_eips::eip4844::BLOB_TX_MIN_BLOB_GASPRICE;
10use alloy_json_rpc::RpcError;
11use alloy_network::{Network, TransactionBuilder, TransactionBuilder4844};
12use alloy_rpc_types_eth::BlockNumberOrTag;
13use alloy_transport::TransportResult;
14use futures::FutureExt;
15
16#[doc(hidden)]
18#[derive(Clone, Copy, Debug, PartialEq, Eq)]
19pub enum GasFillable {
20 Legacy { gas_limit: u64, gas_price: u128 },
21 Eip1559 { gas_limit: u64, estimate: Eip1559Estimation },
22}
23
24#[derive(Clone, Copy, Debug, Default)]
65pub struct GasFiller;
66
67impl GasFiller {
68 async fn prepare_legacy<P, N>(
69 &self,
70 provider: &P,
71 tx: &N::TransactionRequest,
72 ) -> TransportResult<GasFillable>
73 where
74 P: Provider<N>,
75 N: Network,
76 {
77 let gas_price_fut = tx.gas_price().map_or_else(
78 || provider.get_gas_price().right_future(),
79 |gas_price| async move { Ok(gas_price) }.left_future(),
80 );
81
82 let gas_limit_fut = tx.gas_limit().map_or_else(
83 || provider.estimate_gas(tx.clone()).into_future().right_future(),
84 |gas_limit| async move { Ok(gas_limit) }.left_future(),
85 );
86
87 let (gas_price, gas_limit) = futures::try_join!(gas_price_fut, gas_limit_fut)?;
88
89 Ok(GasFillable::Legacy { gas_limit, gas_price })
90 }
91
92 async fn prepare_1559<P, N>(
93 &self,
94 provider: &P,
95 tx: &N::TransactionRequest,
96 ) -> TransportResult<GasFillable>
97 where
98 P: Provider<N>,
99 N: Network,
100 {
101 let gas_limit_fut = tx.gas_limit().map_or_else(
102 || provider.estimate_gas(tx.clone()).into_future().right_future(),
103 |gas_limit| async move { Ok(gas_limit) }.left_future(),
104 );
105
106 let eip1559_fees_fut = if let (Some(max_fee_per_gas), Some(max_priority_fee_per_gas)) =
107 (tx.max_fee_per_gas(), tx.max_priority_fee_per_gas())
108 {
109 async move { Ok(Eip1559Estimation { max_fee_per_gas, max_priority_fee_per_gas }) }
110 .left_future()
111 } else {
112 provider.estimate_eip1559_fees().right_future()
113 };
114
115 let (gas_limit, estimate) = futures::try_join!(gas_limit_fut, eip1559_fees_fut)?;
116
117 Ok(GasFillable::Eip1559 { gas_limit, estimate })
118 }
119}
120
121impl<N: Network> TxFiller<N> for GasFiller {
122 type Fillable = GasFillable;
123
124 fn status(&self, tx: &<N as Network>::TransactionRequest) -> FillerControlFlow {
125 if tx.gas_price().is_some() && tx.gas_limit().is_some() {
127 return FillerControlFlow::Finished;
128 }
129
130 if tx.max_fee_per_gas().is_some()
132 && tx.max_priority_fee_per_gas().is_some()
133 && tx.gas_limit().is_some()
134 {
135 return FillerControlFlow::Finished;
136 }
137
138 FillerControlFlow::Ready
139 }
140
141 fn fill_sync(&self, _tx: &mut SendableTx<N>) {}
142
143 async fn prepare<P>(
144 &self,
145 provider: &P,
146 tx: &<N as Network>::TransactionRequest,
147 ) -> TransportResult<Self::Fillable>
148 where
149 P: Provider<N>,
150 {
151 if tx.gas_price().is_some() {
152 self.prepare_legacy(provider, tx).await
153 } else {
154 match self.prepare_1559(provider, tx).await {
155 Ok(estimate) => Ok(estimate),
157 Err(RpcError::UnsupportedFeature(_)) => self.prepare_legacy(provider, tx).await,
158 Err(e) => Err(e),
159 }
160 }
161 }
162
163 async fn fill(
164 &self,
165 fillable: Self::Fillable,
166 mut tx: SendableTx<N>,
167 ) -> TransportResult<SendableTx<N>> {
168 if let Some(builder) = tx.as_mut_builder() {
169 match fillable {
170 GasFillable::Legacy { gas_limit, gas_price } => {
171 builder.set_gas_limit(gas_limit);
172 builder.set_gas_price(gas_price);
173 }
174 GasFillable::Eip1559 { gas_limit, estimate } => {
175 builder.set_gas_limit(gas_limit);
176 builder.set_max_fee_per_gas(estimate.max_fee_per_gas);
177 builder.set_max_priority_fee_per_gas(estimate.max_priority_fee_per_gas);
178 }
179 }
180 };
181 Ok(tx)
182 }
183}
184
185#[derive(Clone, Copy, Debug, Default)]
187pub struct BlobGasFiller;
188
189impl<N: Network> TxFiller<N> for BlobGasFiller
190where
191 N::TransactionRequest: TransactionBuilder4844,
192{
193 type Fillable = u128;
194
195 fn status(&self, tx: &<N as Network>::TransactionRequest) -> FillerControlFlow {
196 if tx.blob_sidecar().is_none()
199 || tx.max_fee_per_blob_gas().is_some_and(|gas| gas >= BLOB_TX_MIN_BLOB_GASPRICE)
200 {
201 return FillerControlFlow::Finished;
202 }
203
204 FillerControlFlow::Ready
205 }
206
207 fn fill_sync(&self, _tx: &mut SendableTx<N>) {}
208
209 async fn prepare<P>(
210 &self,
211 provider: &P,
212 tx: &<N as Network>::TransactionRequest,
213 ) -> TransportResult<Self::Fillable>
214 where
215 P: Provider<N>,
216 {
217 if let Some(max_fee_per_blob_gas) = tx.max_fee_per_blob_gas() {
218 if max_fee_per_blob_gas >= BLOB_TX_MIN_BLOB_GASPRICE {
219 return Ok(max_fee_per_blob_gas);
220 }
221 }
222
223 provider
224 .get_fee_history(2, BlockNumberOrTag::Latest, &[])
225 .await?
226 .base_fee_per_blob_gas
227 .last()
228 .ok_or(RpcError::NullResp)
229 .copied()
230 }
231
232 async fn fill(
233 &self,
234 fillable: Self::Fillable,
235 mut tx: SendableTx<N>,
236 ) -> TransportResult<SendableTx<N>> {
237 if let Some(builder) = tx.as_mut_builder() {
238 builder.set_max_fee_per_blob_gas(fillable);
239 }
240 Ok(tx)
241 }
242}
243
244#[cfg(feature = "reqwest")]
245#[cfg(test)]
246mod tests {
247 use super::*;
248 use crate::ProviderBuilder;
249 use alloy_consensus::{SidecarBuilder, SimpleCoder, Transaction};
250 use alloy_eips::eip4844::DATA_GAS_PER_BLOB;
251 use alloy_primitives::{address, U256};
252 use alloy_rpc_types_eth::TransactionRequest;
253
254 #[tokio::test]
255 async fn no_gas_price_or_limit() {
256 let provider = ProviderBuilder::new().on_anvil_with_wallet();
257
258 let tx = TransactionRequest {
260 value: Some(U256::from(100)),
261 to: Some(address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045").into()),
262 chain_id: Some(31337),
263 ..Default::default()
264 };
265
266 let tx = provider.send_transaction(tx).await.unwrap();
267
268 let receipt = tx.get_receipt().await.unwrap();
269
270 assert_eq!(receipt.effective_gas_price, 1_000_000_001);
271 assert_eq!(receipt.gas_used, 21000);
272 }
273
274 #[tokio::test]
275 async fn no_gas_limit() {
276 let provider = ProviderBuilder::new().on_anvil_with_wallet();
277
278 let gas_price = provider.get_gas_price().await.unwrap();
279 let tx = TransactionRequest {
280 value: Some(U256::from(100)),
281 to: Some(address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045").into()),
282 gas_price: Some(gas_price),
283 ..Default::default()
284 };
285
286 let tx = provider.send_transaction(tx).await.unwrap();
287
288 let receipt = tx.get_receipt().await.unwrap();
289
290 assert_eq!(receipt.gas_used, 21000);
291 }
292
293 #[tokio::test]
294 async fn no_max_fee_per_blob_gas() {
295 let provider = ProviderBuilder::new().on_anvil_with_wallet();
296
297 let sidecar: SidecarBuilder<SimpleCoder> = SidecarBuilder::from_slice(b"Hello World");
298 let sidecar = sidecar.build().unwrap();
299
300 let tx = TransactionRequest {
301 to: Some(address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045").into()),
302 sidecar: Some(sidecar),
303 ..Default::default()
304 };
305
306 let tx = provider.send_transaction(tx).await.unwrap();
307
308 let receipt = tx.get_receipt().await.unwrap();
309
310 let tx = provider.get_transaction_by_hash(receipt.transaction_hash).await.unwrap().unwrap();
311
312 assert!(tx.max_fee_per_blob_gas().unwrap() >= BLOB_TX_MIN_BLOB_GASPRICE);
313 assert_eq!(receipt.gas_used, 21000);
314 assert_eq!(
315 receipt.blob_gas_used.expect("Expected to be EIP-4844 transaction"),
316 DATA_GAS_PER_BLOB
317 );
318 }
319
320 #[tokio::test]
321 async fn zero_max_fee_per_blob_gas() {
322 let provider = ProviderBuilder::new().on_anvil_with_wallet();
323
324 let sidecar: SidecarBuilder<SimpleCoder> = SidecarBuilder::from_slice(b"Hello World");
325 let sidecar = sidecar.build().unwrap();
326
327 let tx = TransactionRequest {
328 to: Some(address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045").into()),
329 max_fee_per_blob_gas: Some(0),
330 sidecar: Some(sidecar),
331 ..Default::default()
332 };
333
334 let tx = provider.send_transaction(tx).await.unwrap();
335
336 let receipt = tx.get_receipt().await.unwrap();
337
338 let tx = provider.get_transaction_by_hash(receipt.transaction_hash).await.unwrap().unwrap();
339
340 assert!(tx.max_fee_per_blob_gas().unwrap() >= BLOB_TX_MIN_BLOB_GASPRICE);
341 assert_eq!(receipt.gas_used, 21000);
342 assert_eq!(
343 receipt.blob_gas_used.expect("Expected to be EIP-4844 transaction"),
344 DATA_GAS_PER_BLOB
345 );
346 }
347}