openzeppelin_relayer/services/gas/handlers/
optimism.rs1use crate::{
2 constants::{DEFAULT_GAS_LIMIT, OPTIMISM_GAS_PRICE_ORACLE_ADDRESS},
3 domain::evm::PriceParams,
4 models::{EvmTransactionData, TransactionError, U256},
5 services::provider::evm::EvmProviderTrait,
6};
7use alloy::{
8 primitives::{Address, Bytes, TxKind},
9 rpc::types::{TransactionInput, TransactionRequest},
10};
11
12#[derive(Debug, Clone)]
13pub struct OptimismFeeData {
14 pub l1_base_fee: U256,
15 pub base_fee: U256,
16 pub decimals: U256,
17 pub blob_base_fee: U256,
18 pub base_fee_scalar: U256,
19 pub blob_base_fee_scalar: U256,
20}
21
22#[derive(Debug, Clone)]
25pub struct OptimismPriceHandler<P> {
26 provider: P,
27 oracle_address: Address,
28}
29
30impl<P: EvmProviderTrait> OptimismPriceHandler<P> {
31 pub fn new(provider: P) -> Self {
32 Self {
33 provider,
34 oracle_address: OPTIMISM_GAS_PRICE_ORACLE_ADDRESS.parse().unwrap(),
35 }
36 }
37
38 const FN_SELECTOR_L1_BASE_FEE: [u8; 4] = [81, 155, 75, 211];
41 const FN_SELECTOR_BASE_FEE: [u8; 4] = [110, 242, 92, 58];
43 const FN_SELECTOR_DECIMALS: [u8; 4] = [49, 60, 229, 103];
45 const FN_SELECTOR_BLOB_BASE_FEE: [u8; 4] = [248, 32, 97, 64];
47 const FN_SELECTOR_BASE_FEE_SCALAR: [u8; 4] = [197, 152, 89, 24];
49 const FN_SELECTOR_BLOB_BASE_FEE_SCALAR: [u8; 4] = [104, 213, 220, 166];
51
52 fn create_contract_call(&self, selector: [u8; 4]) -> TransactionRequest {
53 let mut data = Vec::with_capacity(4);
54 data.extend_from_slice(&selector);
55 TransactionRequest {
56 to: Some(TxKind::Call(self.oracle_address)),
57 input: TransactionInput::from(Bytes::from(data)),
58 ..Default::default()
59 }
60 }
61
62 async fn read_u256(&self, selector: [u8; 4]) -> Result<U256, TransactionError> {
63 let call = self.create_contract_call(selector);
64 let bytes = self
65 .provider
66 .call_contract(&call)
67 .await
68 .map_err(|e| TransactionError::UnexpectedError(e.to_string()))?;
69 Ok(U256::from_be_slice(bytes.as_ref()))
70 }
71
72 fn calculate_compressed_tx_size(tx: &EvmTransactionData) -> U256 {
73 let data_bytes: Vec<u8> = tx
74 .data
75 .as_ref()
76 .and_then(|hex_str| hex::decode(hex_str.trim_start_matches("0x")).ok())
77 .unwrap_or_default();
78
79 let zero_bytes = U256::from(data_bytes.iter().filter(|&b| *b == 0).count());
80 let non_zero_bytes = U256::from(data_bytes.len()) - zero_bytes;
81
82 (zero_bytes * U256::from(4)) + (non_zero_bytes * U256::from(16))
83 }
84
85 pub async fn fetch_fee_data(&self) -> Result<OptimismFeeData, TransactionError> {
86 let (l1_base_fee, base_fee, decimals, blob_base_fee, base_fee_scalar, blob_base_fee_scalar) =
87 tokio::try_join!(
88 self.read_u256(Self::FN_SELECTOR_L1_BASE_FEE),
89 self.read_u256(Self::FN_SELECTOR_BASE_FEE),
90 self.read_u256(Self::FN_SELECTOR_DECIMALS),
91 self.read_u256(Self::FN_SELECTOR_BLOB_BASE_FEE),
92 self.read_u256(Self::FN_SELECTOR_BASE_FEE_SCALAR),
93 self.read_u256(Self::FN_SELECTOR_BLOB_BASE_FEE_SCALAR)
94 )
95 .map_err(|e| TransactionError::UnexpectedError(e.to_string()))?;
96
97 Ok(OptimismFeeData {
98 l1_base_fee,
99 base_fee,
100 decimals,
101 blob_base_fee,
102 base_fee_scalar,
103 blob_base_fee_scalar,
104 })
105 }
106
107 pub fn calculate_fee(
108 &self,
109 fee_data: &OptimismFeeData,
110 tx: &EvmTransactionData,
111 ) -> Result<U256, TransactionError> {
112 let calldata_gas_used = Self::calculate_compressed_tx_size(tx);
127
128 let ecotone_divisor = U256::from(1_000_000 * 16);
129 let calldata_cost_per_byte = U256::from(fee_data.l1_base_fee)
130 .saturating_mul(U256::from(16))
131 .saturating_mul(U256::from(fee_data.base_fee_scalar));
132 let blob_cost_per_byte = U256::from(fee_data.blob_base_fee)
133 .saturating_mul(U256::from(fee_data.blob_base_fee_scalar));
134 let fee = calldata_cost_per_byte
135 .saturating_add(blob_cost_per_byte)
136 .saturating_mul(U256::from(calldata_gas_used))
137 .wrapping_div(ecotone_divisor);
138 Ok(fee)
139 }
140
141 pub async fn handle_price_params(
142 &self,
143 tx: &EvmTransactionData,
144 mut original_params: PriceParams,
145 ) -> Result<PriceParams, TransactionError> {
146 let fee_data = self.fetch_fee_data().await?;
148 let l1_data_cost = self.calculate_fee(&fee_data, tx)?;
149
150 original_params.extra_fee = Some(l1_data_cost);
152
153 let gas_limit = tx.gas_limit.unwrap_or(DEFAULT_GAS_LIMIT);
155 let value = tx.value;
156 let is_eip1559 = original_params.max_fee_per_gas.is_some();
157
158 original_params.total_cost =
159 original_params.calculate_total_cost(is_eip1559, gas_limit, value);
160
161 Ok(original_params)
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168 use crate::services::provider::evm::MockEvmProviderTrait;
169
170 #[tokio::test]
171 async fn test_optimism_price_handler() {
172 let mut mock_provider = MockEvmProviderTrait::new();
173
174 mock_provider.expect_call_contract().returning(|_| {
176 Box::pin(async { Ok(vec![0u8; 32].into()) })
178 });
179
180 let handler = OptimismPriceHandler::new(mock_provider);
181
182 let tx = EvmTransactionData {
183 from: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string(),
184 to: Some("0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string()),
185 value: U256::from(1_000_000_000_000_000_000u128),
186 data: Some("0x1234567890abcdef".to_string()),
187 gas_limit: Some(21000),
188 gas_price: Some(20_000_000_000),
189 max_fee_per_gas: None,
190 max_priority_fee_per_gas: None,
191 speed: None,
192 nonce: None,
193 chain_id: 10, hash: None,
195 signature: None,
196 raw: None,
197 };
198
199 let original_params = PriceParams {
200 gas_price: Some(20_000_000_000),
201 max_fee_per_gas: None,
202 max_priority_fee_per_gas: None,
203 is_min_bumped: None,
204 extra_fee: None,
205 total_cost: U256::ZERO,
206 };
207
208 let result = handler.handle_price_params(&tx, original_params).await;
209
210 assert!(result.is_ok());
211 let handled_params = result.unwrap();
212
213 assert_eq!(handled_params.gas_price, Some(20_000_000_000));
215
216 assert!(handled_params.extra_fee.is_some());
218
219 assert!(handled_params.total_cost > U256::ZERO);
221 }
222
223 #[test]
224 fn test_calculate_compressed_tx_size() {
225 let empty_tx = EvmTransactionData {
227 from: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string(),
228 to: Some("0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string()),
229 value: U256::from(1_000_000_000_000_000_000u128),
230 data: None,
231 gas_limit: Some(21000),
232 gas_price: Some(20_000_000_000),
233 max_fee_per_gas: None,
234 max_priority_fee_per_gas: None,
235 speed: None,
236 nonce: None,
237 chain_id: 10,
238 hash: None,
239 signature: None,
240 raw: None,
241 };
242
243 let size =
244 OptimismPriceHandler::<MockEvmProviderTrait>::calculate_compressed_tx_size(&empty_tx);
245 assert_eq!(size, U256::ZERO);
246
247 let data_tx = EvmTransactionData {
249 data: Some("0x00001234".to_string()), ..empty_tx
251 };
252
253 let size =
254 OptimismPriceHandler::<MockEvmProviderTrait>::calculate_compressed_tx_size(&data_tx);
255 let expected = U256::from(2) * U256::from(4) + U256::from(2) * U256::from(16);
257 assert_eq!(size, expected);
258 }
259
260 #[test]
261 fn test_calculate_fee_with_specific_data_and_fee_data() {
262 let mock_provider = MockEvmProviderTrait::new();
263 let handler = OptimismPriceHandler::new(mock_provider);
264
265 let fee_data = OptimismFeeData {
266 l1_base_fee: U256::from(422079632u64),
267 base_fee: U256::from(138u64),
268 decimals: U256::from(6u64),
269 blob_base_fee: U256::from(2u64),
270 base_fee_scalar: U256::from(5227u64),
271 blob_base_fee_scalar: U256::from(1014213u64),
272 };
273
274 let tx = EvmTransactionData {
275 from: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string(),
276 to: Some("0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string()),
277 value: U256::ZERO,
278 data: Some("0xaf524e5852ba824bfabc2bcfcdf7f0edbb486ebb05e1836c90e78047efeb949990f72e5f00000000000000000000000000000000000000000000000000000000000000600f0b4ec422bb6297b5ded2971c583488bc1a714a2b11201bb32988080dec689b0000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000653636313431353161306633613839373337393633303932650000000000000000363831306565633032393766616339373337303865316531000000000000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000014cab1a2f55e1c3f8905f46c0f9f73746b7fc160c5000000000000000000000000".to_string()),
279 gas_limit: Some(21000),
280 gas_price: Some(20_000_000_000),
281 max_fee_per_gas: None,
282 max_priority_fee_per_gas: None,
283 speed: None,
284 nonce: None,
285 chain_id: 10,
286 hash: None,
287 signature: None,
288 raw: None,
289 };
290
291 let result = handler.calculate_fee(&fee_data, &tx);
292 assert!(result.is_ok());
293 let fee = result.unwrap();
294 assert_eq!(fee, U256::from(7342268088u64));
295 }
296}