1use revm::{
2 context_interface::cfg::{GasId, GasParams},
3 primitives::OnceLock,
4};
5use tempo_chainspec::{
6 constants::gas::{SSTORE_CREATE_COST, SSTORE_SET_COST},
7 hardfork::TempoHardfork,
8};
9
10const CONTRACT_CREATE_COST: u64 = 500_000;
12const NEW_ACCOUNT_COST: u64 = 250_000;
13const CODE_DEPOSIT_COST_T1: u64 = 1_000;
14const EIP7702_PER_EMPTY_ACCOUNT_COST_T1: u64 = 12_500;
15
16const T4_SSTORE_SET_REGULAR: u64 = 20_000;
28const T4_NEW_ACCOUNT_REGULAR: u64 = 25_000;
29const T4_CREATE_REGULAR: u64 = 32_000;
30const T4_CODE_DEPOSIT_REGULAR: u64 = 200;
31
32const T4_SSTORE_SET_STATE: u64 = SSTORE_CREATE_COST - T4_SSTORE_SET_REGULAR; const T4_NEW_ACCOUNT_STATE: u64 = NEW_ACCOUNT_COST - T4_NEW_ACCOUNT_REGULAR; const T4_CREATE_STATE: u64 = CONTRACT_CREATE_COST - T4_CREATE_REGULAR; const T4_CODE_DEPOSIT_STATE: u64 = 2_300;
37
38const T4_SSTORE_SET_REFUND: u64 = T4_SSTORE_SET_STATE + 17_800; #[inline]
49pub fn tempo_gas_params_with_amsterdam(
50 spec: TempoHardfork,
51 amsterdam_eip8037_enabled: bool,
52) -> GasParams {
53 if amsterdam_eip8037_enabled {
54 static TABLE: OnceLock<GasParams> = OnceLock::new();
55 return TABLE.get_or_init(amsterdam_gas_params).clone();
56 }
57
58 if spec.is_t7() {
61 static TABLE: OnceLock<GasParams> = OnceLock::new();
62 return TABLE.get_or_init(t7_gas_params).clone();
63 }
64
65 if spec.is_t1() {
66 static TABLE: OnceLock<GasParams> = OnceLock::new();
67 return TABLE.get_or_init(t1_gas_params).clone();
68 }
69
70 GasParams::new_spec(spec.into())
71}
72
73fn t7_gas_params() -> GasParams {
82 let mut gas_params = t1_gas_params();
87 gas_params.override_gas([
88 (GasId::sstore_set_without_load_cost(), SSTORE_SET_COST),
91 (GasId::sstore_set_refund(), SSTORE_SET_COST),
94 (GasId::sstore_clearing_slot_refund(), 0),
98 ]);
99 gas_params
100}
101
102fn amsterdam_gas_params() -> GasParams {
104 let mut gas_params = GasParams::new_spec(TempoHardfork::T4.into());
105 gas_params.override_gas([
109 (GasId::sstore_set_without_load_cost(), T4_SSTORE_SET_REGULAR),
111 (GasId::sstore_set_state_gas(), T4_SSTORE_SET_STATE),
112 (GasId::sstore_set_refund(), T4_SSTORE_SET_REFUND),
113 (GasId::tx_create_cost(), T4_CREATE_REGULAR),
115 (GasId::create(), T4_CREATE_REGULAR),
116 (GasId::create_state_gas(), T4_CREATE_STATE),
117 (GasId::new_account_cost(), T4_NEW_ACCOUNT_REGULAR),
119 (GasId::new_account_state_gas(), T4_NEW_ACCOUNT_STATE),
120 (
121 GasId::new_account_cost_for_selfdestruct(),
122 T4_NEW_ACCOUNT_REGULAR,
123 ),
124 (GasId::code_deposit_cost(), T4_CODE_DEPOSIT_REGULAR),
126 (GasId::code_deposit_state_gas(), T4_CODE_DEPOSIT_STATE),
127 (
129 GasId::tx_eip7702_per_empty_account_cost(),
130 T4_NEW_ACCOUNT_REGULAR,
131 ),
132 (GasId::tx_eip7702_auth_refund(), 0),
134 (GasId::tx_eip7702_state_gas_bytecode(), 0),
139 ]);
140 gas_params
141}
142
143fn t1_gas_params() -> GasParams {
145 let mut gas_params = GasParams::new_spec(TempoHardfork::T1.into());
146 gas_params.override_gas([
148 (GasId::sstore_set_without_load_cost(), SSTORE_CREATE_COST),
149 (GasId::tx_create_cost(), CONTRACT_CREATE_COST),
150 (GasId::create(), CONTRACT_CREATE_COST),
151 (GasId::new_account_cost(), NEW_ACCOUNT_COST),
152 (GasId::new_account_cost_for_selfdestruct(), NEW_ACCOUNT_COST),
153 (GasId::code_deposit_cost(), CODE_DEPOSIT_COST_T1),
154 (
155 GasId::tx_eip7702_per_empty_account_cost(),
156 EIP7702_PER_EMPTY_ACCOUNT_COST_T1,
157 ),
158 (GasId::tx_eip7702_auth_refund(), 0),
160 ]);
161 gas_params
162}
163
164#[inline]
169pub fn tempo_gas_params(spec: TempoHardfork) -> GasParams {
170 tempo_gas_params_with_amsterdam(spec, false)
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176
177 #[test]
178 fn test_tempo_override_gas_params_are_cached() {
179 let t1 = tempo_gas_params_with_amsterdam(TempoHardfork::T1, false);
180 let t5 = tempo_gas_params_with_amsterdam(TempoHardfork::T5, false);
181 assert!(
182 std::ptr::eq(t1.table(), t5.table()),
183 "T1+ TIP-1000 gas params should share the cached table"
184 );
185
186 let amsterdam_t4 = tempo_gas_params_with_amsterdam(TempoHardfork::T4, true);
187 let amsterdam_t5 = tempo_gas_params_with_amsterdam(TempoHardfork::T5, true);
188 assert!(
189 std::ptr::eq(amsterdam_t4.table(), amsterdam_t5.table()),
190 "Amsterdam gas params should share the cached table"
191 );
192 }
193
194 #[test]
198 fn test_t7_gas_params_sstore_residual() {
199 let gas_params = tempo_gas_params_with_amsterdam(TempoHardfork::T7, false);
200
201 assert_eq!(
204 gas_params.get(GasId::sstore_set_without_load_cost()),
205 5_000,
206 "T7 SSTORE creation charges only the 5k residual"
207 );
208 assert!(
209 gas_params.get(GasId::sstore_set_without_load_cost())
210 >= gas_params.get(GasId::sstore_set_refund()),
211 "T7 restore-to-original-zero refund must not exceed the residual set charge"
212 );
213 assert_eq!(
214 gas_params.get(GasId::sstore_clearing_slot_refund()),
215 0,
216 "TIP-1060 removes the legacy SSTORE clearing refund"
217 );
218
219 assert_eq!(gas_params.get(GasId::new_account_cost()), 250_000);
221 assert_eq!(gas_params.get(GasId::tx_create_cost()), 500_000);
222 assert_eq!(gas_params.get(GasId::create()), 500_000);
223 assert_eq!(gas_params.get(GasId::code_deposit_cost()), 1_000);
224
225 let upstream = GasParams::new_spec(TempoHardfork::T7.into());
227 assert_eq!(
228 gas_params.get(GasId::sstore_set_state_gas()),
229 upstream.get(GasId::sstore_set_state_gas()),
230 "T7 (EIP-8037 disabled) must not split SSTORE into state gas"
231 );
232
233 let t8 = tempo_gas_params_with_amsterdam(TempoHardfork::T8, false);
235 assert!(
236 std::ptr::eq(gas_params.table(), t8.table()),
237 "T7+ TIP-1060 gas params should share the cached table"
238 );
239 }
240
241 #[test]
242 fn test_t1_gas_params_no_state_gas_split() {
243 let gas_params = tempo_gas_params_with_amsterdam(TempoHardfork::T1, false);
244
245 assert_eq!(
247 gas_params.get(GasId::sstore_set_without_load_cost()),
248 250_000
249 );
250 assert_eq!(gas_params.get(GasId::new_account_cost()), 250_000);
251 assert_eq!(gas_params.get(GasId::tx_create_cost()), 500_000);
252 assert_eq!(gas_params.get(GasId::create()), 500_000);
253 assert_eq!(gas_params.get(GasId::code_deposit_cost()), 1_000);
254
255 let upstream = GasParams::new_spec(TempoHardfork::T1.into());
257 assert_eq!(
258 gas_params.get(GasId::sstore_set_state_gas()),
259 upstream.get(GasId::sstore_set_state_gas()),
260 "T1 should not override state gas params"
261 );
262 assert_eq!(
263 gas_params.get(GasId::new_account_state_gas()),
264 upstream.get(GasId::new_account_state_gas()),
265 );
266 assert_eq!(
267 gas_params.get(GasId::create_state_gas()),
268 upstream.get(GasId::create_state_gas()),
269 );
270 }
271
272 #[test]
286 fn test_t4_gas_params_splits_storage_costs() {
287 let gas_params = tempo_gas_params_with_amsterdam(TempoHardfork::T4, true);
288
289 assert_eq!(
293 gas_params.get(GasId::sstore_set_without_load_cost()),
294 20_000,
295 "SSTORE set_without_load matches the retained zero->non-zero write component"
296 );
297 assert_eq!(
298 gas_params.get(GasId::new_account_cost()),
299 25_000,
300 "Account creation regular gas per spec"
301 );
302 assert_eq!(
303 gas_params.get(GasId::new_account_cost_for_selfdestruct()),
304 25_000
305 );
306 assert_eq!(
307 gas_params.get(GasId::tx_create_cost()),
308 32_000,
309 "CREATE base regular gas per spec"
310 );
311 assert_eq!(
312 gas_params.get(GasId::create()),
313 32_000,
314 "CREATE base regular gas per spec"
315 );
316 assert_eq!(gas_params.get(GasId::code_deposit_cost()), 200);
317
318 assert_eq!(
320 gas_params.get(GasId::sstore_set_state_gas()),
321 230_000,
322 "SSTORE state gas per spec"
323 );
324 assert_eq!(
325 gas_params.get(GasId::new_account_state_gas()),
326 225_000,
327 "Account creation state gas per spec"
328 );
329 assert_eq!(
330 gas_params.get(GasId::create_state_gas()),
331 468_000,
332 "CREATE base state gas per spec"
333 );
334 assert_eq!(gas_params.get(GasId::code_deposit_state_gas()), 2_300);
335
336 assert_eq!(
338 gas_params.get(GasId::tx_eip7702_per_empty_account_cost()),
339 25_000,
340 "EIP-7702 per auth total = 25k regular + 225k state per spec"
341 );
342 assert_eq!(
343 gas_params.tx_eip7702_per_empty_account_cost(),
344 250_000,
345 "EIP-7702 per auth state gas per spec"
346 );
347 assert_eq!(
348 gas_params.new_account_state_gas(),
349 225_000,
350 "EIP-7702 per auth state gas per spec"
351 );
352 assert_eq!(
353 gas_params.tx_eip7702_per_empty_account_cost() - gas_params.new_account_state_gas(),
354 25_000,
355 "EIP-7702 per auth regular gas = total - state = 25k"
356 );
357 assert_eq!(
358 gas_params.tx_eip7702_auth_refund_regular(),
359 0,
360 "TIP-1000: no refund for existing accounts on T1+"
361 );
362
363 assert_eq!(
366 gas_params.get(GasId::sstore_set_refund()),
367 247_800,
368 "SSTORE set refund = state(230k) + regular(17.8k) per spec"
369 );
370 }
371
372 #[test]
379 fn test_t4_totals_match_spec() {
380 let t4 = tempo_gas_params_with_amsterdam(TempoHardfork::T4, true);
381
382 let warm_sstore_regular =
384 t4.get(GasId::sstore_set_without_load_cost()) + t4.warm_storage_read_cost();
385 assert_eq!(
386 warm_sstore_regular + t4.get(GasId::sstore_set_state_gas()),
387 250_100,
388 "warm SSTORE total must be 250,100"
389 );
390
391 let cold_sstore_regular = warm_sstore_regular + t4.cold_storage_cost();
393 assert_eq!(
394 cold_sstore_regular + t4.get(GasId::sstore_set_state_gas()),
395 252_200,
396 "cold SSTORE total must include Berlin cold slot access charging"
397 );
398
399 assert_eq!(
401 t4.get(GasId::new_account_cost()) + t4.get(GasId::new_account_state_gas()),
402 250_000,
403 "new_account total must be 250,000"
404 );
405
406 assert_eq!(
408 t4.get(GasId::create()) + t4.get(GasId::create_state_gas()),
409 500_000,
410 "CREATE total must be 500,000"
411 );
412
413 assert_eq!(
415 t4.get(GasId::code_deposit_cost()) + t4.get(GasId::code_deposit_state_gas()),
416 2_500,
417 "code_deposit total must be 2,500/byte"
418 );
419
420 assert_eq!(
422 t4.tx_eip7702_per_empty_account_cost(),
423 250_000,
424 "EIP-7702 per auth total must be 250,000"
425 );
426 }
427}