ethrex_rpc/eth/
gas_price.rs1use crate::rpc::{RpcApiContext, RpcHandler};
7use crate::utils::RpcErr;
8use ethrex_blockchain::BlockchainType;
9use serde_json::Value;
10
11#[derive(Debug, Clone)]
28pub struct GasPrice;
29
30impl RpcHandler for GasPrice {
31 fn parse(_: &Option<Vec<Value>>) -> Result<Self, RpcErr> {
32 Ok(GasPrice {})
33 }
34
35 async fn handle(&self, context: RpcApiContext) -> Result<Value, RpcErr> {
36 let latest_block_number = context.storage.get_latest_block_number().await?;
37 let latest_header = context
38 .storage
39 .get_block_header(latest_block_number)?
40 .ok_or(RpcErr::Internal(format!(
41 "Missing latest block with number {latest_block_number}"
42 )))?;
43 let Some(base_fee) = latest_header.base_fee_per_gas else {
44 return Err(RpcErr::Internal(
45 "Error calculating gas price: missing base_fee on block".to_string(),
46 ));
47 };
48 let estimated_gas_tip = context
49 .gas_tip_estimator
50 .lock()
51 .await
52 .estimate_gas_tip(&context.storage)
53 .await?;
54 let mut gas_price = base_fee + estimated_gas_tip;
56
57 if let BlockchainType::L2(l2_config) = &context.blockchain.options.r#type {
59 let fee_config = *l2_config
60 .fee_config
61 .read()
62 .map_err(|_| RpcErr::Internal("Fee config lock was poisoned".to_string()))?;
63 if let Some(operator_fee_config) = &fee_config.operator_fee_config {
64 gas_price += operator_fee_config.operator_fee_per_gas;
65 }
66 }
67
68 let gas_as_hex = format!("0x{gas_price:x}");
69 Ok(serde_json::Value::String(gas_as_hex))
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use super::GasPrice;
76 use crate::test_utils::{
77 BASE_PRICE_IN_WEI, add_eip1559_tx_blocks, add_legacy_tx_blocks, add_mixed_tx_blocks,
78 setup_store,
79 };
80
81 use crate::test_utils::default_context_with_storage;
82 use crate::{
83 rpc::{RpcHandler, map_http_requests},
84 utils::{RpcRequest, parse_json_hex},
85 };
86 use ethrex_common::types::MIN_GAS_TIP;
87 use serde_json::json;
88
89 #[tokio::test]
90 async fn test_for_legacy_txs() {
91 let storage = setup_store().await;
92 let context = default_context_with_storage(storage).await;
93
94 add_legacy_tx_blocks(&context.storage, 100, 10).await;
95
96 let gas_price = GasPrice {};
97 let response = gas_price.handle(context).await.unwrap();
98 let parsed_result = parse_json_hex(&response).unwrap();
99 assert_eq!(parsed_result, 2 * BASE_PRICE_IN_WEI);
100 }
101
102 #[tokio::test]
103 async fn test_for_eip_1559_txs() {
104 let storage = setup_store().await;
105 let context = default_context_with_storage(storage).await;
106
107 add_eip1559_tx_blocks(&context.storage, 100, 10).await;
108
109 let gas_price = GasPrice {};
110 let response = gas_price.handle(context).await.unwrap();
111 let parsed_result = parse_json_hex(&response).unwrap();
112 assert_eq!(parsed_result, 2 * BASE_PRICE_IN_WEI);
113 }
114
115 #[tokio::test]
116 async fn test_with_mixed_transactions() {
117 let storage = setup_store().await;
118 let context = default_context_with_storage(storage).await;
119
120 add_mixed_tx_blocks(&context.storage, 100, 10).await;
121
122 let gas_price = GasPrice {};
123 let response = gas_price.handle(context).await.unwrap();
124 let parsed_result = parse_json_hex(&response).unwrap();
125 assert_eq!(parsed_result, 2 * BASE_PRICE_IN_WEI);
126 }
127
128 #[tokio::test]
129 async fn test_with_not_enough_blocks_or_transactions() {
130 let storage = setup_store().await;
131 let context = default_context_with_storage(storage).await;
132
133 add_mixed_tx_blocks(&context.storage, 100, 0).await;
134
135 let gas_price = GasPrice {};
136 let response = gas_price.handle(context).await.unwrap();
137 let parsed_result = parse_json_hex(&response).unwrap();
138 assert_eq!(parsed_result, BASE_PRICE_IN_WEI + MIN_GAS_TIP);
139 }
140
141 #[tokio::test]
142 async fn test_with_no_blocks_but_genesis() {
143 let storage = setup_store().await;
144 let context = default_context_with_storage(storage).await;
145 let gas_price = GasPrice {};
146 let expected_gas_price = BASE_PRICE_IN_WEI + MIN_GAS_TIP;
148 let response = gas_price.handle(context).await.unwrap();
149 let parsed_result = parse_json_hex(&response).unwrap();
150 assert_eq!(parsed_result, expected_gas_price);
151 }
152
153 #[tokio::test]
154 async fn request_smoke_test() {
155 let raw_json = json!(
156 {
157 "jsonrpc":"2.0",
158 "method":"eth_gasPrice",
159 "id":1
160 });
161 let expected_response = json!("0x3b9aca00");
162 let request: RpcRequest = serde_json::from_value(raw_json).expect("Test json is not valid");
163 let storage = setup_store().await;
164 let context = default_context_with_storage(storage).await;
165
166 add_legacy_tx_blocks(&context.storage, 100, 1).await;
167
168 let response = map_http_requests(&request, context).await.unwrap();
169 assert_eq!(response, expected_response)
170 }
171}