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