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