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