openzeppelin_relayer/services/gas/
optimism_extra_fee.rs1use alloy::{
2 consensus::{TxEip1559, TxLegacy},
3 hex::FromHex,
4 primitives::{Address, Bytes},
5 rpc::types::{TransactionInput, TransactionRequest},
6};
7use async_trait::async_trait;
8use serde::{Deserialize, Serialize};
9use solana_sdk::packet::Encode;
10
11use crate::{
12 constants::OPTIMISM_GAS_PRICE_ORACLE_ADDRESS,
13 models::{EvmTransactionData, EvmTransactionDataTrait, TransactionError, U256},
14 services::EvmProviderTrait,
15};
16
17use super::NetworkExtraFeeCalculatorServiceTrait;
18
19const FN_SELECTOR_L1_BASE_FEE: [u8; 4] = [81, 155, 75, 211]; const FN_SELECTOR_BASE_FEE: [u8; 4] = [110, 242, 92, 58]; const FN_SELECTOR_DECIMALS: [u8; 4] = [49, 60, 229, 103]; const FN_SELECTOR_BLOB_BASE_FEE: [u8; 4] = [248, 32, 97, 64]; const FN_SELECTOR_BASE_FEE_SCALAR: [u8; 4] = [197, 152, 89, 24]; const FN_SELECTOR_BLOB_BASE_FEE_SCALAR: [u8; 4] = [104, 213, 220, 166]; #[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct OptimismModifiers {
29 pub l1_base_fee: U256,
30 pub base_fee: U256,
31 pub decimals: U256,
32 pub blob_base_fee: U256,
33 pub base_fee_scalar: u32,
34 pub blob_base_fee_scalar: u32,
35}
36
37#[cfg(test)]
38impl Default for OptimismModifiers {
39 fn default() -> Self {
40 Self {
41 l1_base_fee: U256::ZERO,
42 base_fee: U256::ZERO,
43 decimals: U256::ZERO,
44 blob_base_fee: U256::ZERO,
45 base_fee_scalar: 0,
46 blob_base_fee_scalar: 0,
47 }
48 }
49}
50
51pub struct OptimismExtraFeeService<P> {
52 provider: P,
53}
54
55impl<P> OptimismExtraFeeService<P> {
56 pub fn new(provider: P) -> Self {
63 Self { provider }
64 }
65}
66
67impl<P: EvmProviderTrait> OptimismExtraFeeService<P> {
68 fn create_contract_call(
76 &self,
77 bytes_fn_selector: Vec<u8>,
78 ) -> Result<TransactionRequest, TransactionError> {
79 let oracle_address = Address::from_hex(OPTIMISM_GAS_PRICE_ORACLE_ADDRESS)
80 .map_err(|e| TransactionError::UnexpectedError(e.to_string()))?;
81
82 let fn_selector = Bytes::from(bytes_fn_selector);
83 let tx = TransactionRequest::default()
84 .to(oracle_address)
85 .input(TransactionInput::new(fn_selector));
86
87 Ok(tx)
88 }
89
90 async fn get_fee(&self, fn_selector: Vec<u8>) -> Result<U256, TransactionError> {
98 let tx = self.create_contract_call(fn_selector)?;
99 let result = self.provider.call_contract(&tx).await?;
100 Ok(U256::from_be_slice(result.as_ref()))
101 }
102
103 pub async fn get_modifiers(&self) -> Result<OptimismModifiers, TransactionError> {
109 let (l1_base_fee, base_fee, decimals, blob_base_fee, base_fee_scalar, blob_base_fee_scalar) =
110 tokio::try_join!(
111 self.get_fee(FN_SELECTOR_L1_BASE_FEE.to_vec()),
112 self.get_fee(FN_SELECTOR_BASE_FEE.to_vec()),
113 self.get_fee(FN_SELECTOR_DECIMALS.to_vec()),
114 self.get_fee(FN_SELECTOR_BLOB_BASE_FEE.to_vec()),
115 self.get_fee(FN_SELECTOR_BASE_FEE_SCALAR.to_vec()),
116 self.get_fee(FN_SELECTOR_BLOB_BASE_FEE_SCALAR.to_vec()),
117 )
118 .map_err(|e| TransactionError::UnexpectedError(e.to_string()))?;
119
120 let base_fee_scalar: u32 = base_fee_scalar.try_into().map_err(|e| {
121 TransactionError::UnexpectedError(format!("Failed to convert base fee scalar: {}", e))
122 })?;
123
124 let blob_base_fee_scalar: u32 = blob_base_fee_scalar.try_into().map_err(|e| {
125 TransactionError::UnexpectedError(format!(
126 "Failed to convert blob base fee scalar: {}",
127 e
128 ))
129 })?;
130
131 Ok(OptimismModifiers {
132 l1_base_fee,
133 base_fee,
134 decimals,
135 blob_base_fee,
136 base_fee_scalar,
137 blob_base_fee_scalar,
138 })
139 }
140}
141
142#[async_trait]
143impl<P: EvmProviderTrait> NetworkExtraFeeCalculatorServiceTrait for OptimismExtraFeeService<P> {
144 async fn get_extra_fee(&self, tx_data: &EvmTransactionData) -> Result<U256, TransactionError> {
151 let bytes = if tx_data.is_eip1559() {
152 let tx_eip1559 = TxEip1559::try_from(tx_data)?;
153 let mut bytes = Vec::new();
154 tx_eip1559.encode(&mut bytes).map_err(|e| {
155 TransactionError::InvalidType(format!("Failed to encode transaction: {}", e))
156 })?;
157 bytes
158 } else {
159 let tx_legacy = TxLegacy::try_from(tx_data)?;
160 let mut bytes = Vec::new();
161 tx_legacy.encode(&mut bytes).map_err(|e| {
162 TransactionError::InvalidType(format!("Failed to encode transaction: {}", e))
163 })?;
164 bytes
165 };
166
167 let zero_bytes = U256::from(bytes.iter().filter(|&b| *b == 0).count());
170 let non_zero_bytes = U256::from(bytes.len()) - zero_bytes;
171
172 let tx_compressed_size =
173 ((zero_bytes * U256::from(4)) + (non_zero_bytes * U256::from(16))) / U256::from(16);
174
175 let gas_modifiers = self.get_modifiers().await?;
176
177 let weighted_gas_price =
178 U256::from(16) * U256::from(gas_modifiers.base_fee_scalar) * gas_modifiers.base_fee
179 + U256::from(gas_modifiers.blob_base_fee_scalar) * gas_modifiers.blob_base_fee;
180
181 let l1_data_fee = tx_compressed_size * weighted_gas_price;
182
183 Ok(l1_data_fee)
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190 use crate::services::{MockEvmProviderTrait, ProviderError};
191 use alloy::primitives::TxKind;
192
193 fn setup_mock_provider_for_modifiers() -> MockEvmProviderTrait {
194 let mut mock_provider = MockEvmProviderTrait::new();
195
196 let l1_base_fee_bytes = U256::from(10_000_000_000u64).to_be_bytes::<32>();
197 mock_provider
198 .expect_call_contract()
199 .times(1)
200 .returning(move |_| {
201 Box::pin(async move { Ok(Bytes::from(l1_base_fee_bytes.to_vec())) })
202 });
203
204 let base_fee_bytes = U256::from(1_000_000_000u64).to_be_bytes::<32>();
205 mock_provider
206 .expect_call_contract()
207 .times(1)
208 .returning(move |_| Box::pin(async move { Ok(Bytes::from(base_fee_bytes.to_vec())) }));
209
210 let decimals_bytes = U256::from(9u64).to_be_bytes::<32>();
211 mock_provider
212 .expect_call_contract()
213 .times(1)
214 .returning(move |_| Box::pin(async move { Ok(Bytes::from(decimals_bytes.to_vec())) }));
215
216 let blob_base_fee_bytes = U256::from(100u64).to_be_bytes::<32>();
217 mock_provider
218 .expect_call_contract()
219 .times(1)
220 .returning(move |_| {
221 Box::pin(async move { Ok(Bytes::from(blob_base_fee_bytes.to_vec())) })
222 });
223
224 let base_fee_scalar_bytes = U256::from(684000u64).to_be_bytes::<32>();
225 mock_provider
226 .expect_call_contract()
227 .times(1)
228 .returning(move |_| {
229 Box::pin(async move { Ok(Bytes::from(base_fee_scalar_bytes.to_vec())) })
230 });
231
232 let blob_base_fee_scalar_bytes = U256::from(50000u64).to_be_bytes::<32>();
233 mock_provider
234 .expect_call_contract()
235 .times(1)
236 .returning(move |_| {
237 Box::pin(async move { Ok(Bytes::from(blob_base_fee_scalar_bytes.to_vec())) })
238 });
239
240 mock_provider
241 }
242
243 fn create_test_evm_transaction_data(is_eip1559: bool) -> EvmTransactionData {
244 let mut tx_data = EvmTransactionData {
245 from: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string(),
246 to: Some("0xa24Cea55A6171FbA0935c9e171c4Efe5Ba28DF91".to_string()),
247 value: U256::from(1000000000),
248 data: Some("0x0123".to_string()),
249 nonce: Some(1),
250 chain_id: 10,
251 gas_limit: Some(21000),
252 hash: None,
253 signature: None,
254 speed: None,
255 raw: None,
256 gas_price: None,
257 max_fee_per_gas: None,
258 max_priority_fee_per_gas: None,
259 };
260
261 if is_eip1559 {
262 tx_data.max_fee_per_gas = Some(30000000000);
263 tx_data.max_priority_fee_per_gas = Some(2000000000);
264 } else {
265 tx_data.gas_price = Some(20000000000);
266 }
267
268 tx_data
269 }
270
271 #[test]
272 fn test_create_contract_call() {
273 let mock_provider = MockEvmProviderTrait::new();
274 let service = OptimismExtraFeeService::new(mock_provider);
275
276 let result = service.create_contract_call(FN_SELECTOR_L1_BASE_FEE.to_vec());
277 assert!(result.is_ok());
278
279 let tx_request = result.unwrap();
280
281 let expected_address = Address::from_hex(OPTIMISM_GAS_PRICE_ORACLE_ADDRESS).unwrap();
282 assert_eq!(tx_request.to, Some(TxKind::Call(expected_address)));
283
284 assert!(matches!(tx_request.input, TransactionInput { .. }));
285 }
286
287 #[tokio::test]
288 async fn test_get_modifiers() {
289 let mock_provider = setup_mock_provider_for_modifiers();
290 let service = OptimismExtraFeeService::new(mock_provider);
291
292 let modifiers = service.get_modifiers().await;
293 assert!(
294 modifiers.is_ok(),
295 "Failed to get modifiers: {:?}",
296 modifiers.err()
297 );
298
299 let modifiers = modifiers.unwrap();
300
301 assert_eq!(
302 modifiers.l1_base_fee,
303 U256::from(10_000_000_000u64),
304 "L1 base fee mismatch"
305 );
306 assert_eq!(
307 modifiers.base_fee,
308 U256::from(1_000_000_000u64),
309 "Base fee mismatch"
310 );
311 assert_eq!(modifiers.decimals, U256::from(9u64), "Decimals mismatch");
312 assert_eq!(
313 modifiers.blob_base_fee,
314 U256::from(100u64),
315 "Blob base fee mismatch"
316 );
317 assert_eq!(
318 modifiers.base_fee_scalar, 684000,
319 "Base fee scalar mismatch"
320 );
321 assert_eq!(
322 modifiers.blob_base_fee_scalar, 50000,
323 "Blob base fee scalar mismatch"
324 );
325 }
326
327 #[tokio::test]
328 async fn test_get_extra_fee_eip1559_transaction() {
329 let mock_provider = setup_mock_provider_for_modifiers();
330 let service = OptimismExtraFeeService::new(mock_provider);
331
332 let tx_data = create_test_evm_transaction_data(true);
333 let extra_fee = service.get_extra_fee(&tx_data).await;
334
335 assert!(
336 extra_fee.is_ok(),
337 "Failed to get extra fee: {:?}",
338 extra_fee.err()
339 );
340
341 let extra_fee = extra_fee.unwrap();
342 assert!(
343 extra_fee > U256::ZERO,
344 "Extra fee should be greater than zero"
345 );
346 }
347
348 #[tokio::test]
349 async fn test_get_extra_fee_legacy_transaction() {
350 let mock_provider = setup_mock_provider_for_modifiers();
351 let service = OptimismExtraFeeService::new(mock_provider);
352
353 let tx_data = create_test_evm_transaction_data(false);
354 let extra_fee = service.get_extra_fee(&tx_data).await;
355
356 assert!(
357 extra_fee.is_ok(),
358 "Failed to get extra fee: {:?}",
359 extra_fee.err()
360 );
361
362 let extra_fee = extra_fee.unwrap();
363 assert!(
364 extra_fee > U256::ZERO,
365 "Extra fee should be greater than zero"
366 );
367 }
368
369 #[tokio::test]
370 async fn test_get_modifiers_error_handling() {
371 let mut mock_provider = MockEvmProviderTrait::new();
372
373 mock_provider.expect_call_contract().returning(|_| {
374 Box::pin(async { Err(ProviderError::Other("Simulated RPC error".to_string())) })
375 });
376
377 let service = OptimismExtraFeeService::new(mock_provider);
378 let result = service.get_modifiers().await;
379
380 assert!(result.is_err());
381 if let Err(e) = result {
382 match e {
383 TransactionError::UnexpectedError(msg) => {
384 assert!(msg.contains("Simulated RPC error"));
385 }
386 _ => panic!("Expected UnexpectedError but got {:?}", e),
387 }
388 }
389 }
390}