1use crate::{
4 gas::{self, log2floor, ISTANBUL_SLOAD_GAS, SSTORE_RESET, SSTORE_SET, WARM_SSTORE_RESET},
5 num_words,
6};
7use context_interface::context::SStoreResult;
8use primitives::{
9 hardfork::SpecId::{self},
10 U256,
11};
12use std::sync::Arc;
13
14#[derive(Clone, Debug, PartialEq, Eq, Hash)]
16pub struct GasParams {
17 table: Arc<[u64; 256]>,
19 ptr: *const u64,
21}
22
23#[cfg(feature = "serde")]
24mod serde {
25 use super::{Arc, GasParams};
26 use std::vec::Vec;
27
28 #[derive(serde::Serialize, serde::Deserialize)]
29 struct GasParamsSerde {
30 table: Vec<u64>,
31 }
32
33 #[cfg(feature = "serde")]
34 impl serde::Serialize for GasParams {
35 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
36 where
37 S: serde::Serializer,
38 {
39 GasParamsSerde {
40 table: self.table.to_vec(),
41 }
42 .serialize(serializer)
43 }
44 }
45
46 impl<'de> serde::Deserialize<'de> for GasParams {
47 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
48 where
49 D: serde::Deserializer<'de>,
50 {
51 let table = GasParamsSerde::deserialize(deserializer)?;
52 if table.table.len() != 256 {
53 return Err(serde::de::Error::custom("Invalid gas params length"));
54 }
55 Ok(Self::new(Arc::new(table.table.try_into().unwrap())))
56 }
57 }
58}
59
60impl Default for GasParams {
61 fn default() -> Self {
62 Self::new_spec(SpecId::default())
63 }
64}
65
66impl GasParams {
67 #[inline]
69 pub fn new(table: Arc<[u64; 256]>) -> Self {
70 Self {
71 ptr: table.as_ptr(),
72 table,
73 }
74 }
75
76 pub fn override_gas(&mut self, values: impl IntoIterator<Item = (GasId, u64)>) {
92 let mut table = *self.table.clone();
93 for (id, value) in values.into_iter() {
94 table[id.as_usize()] = value;
95 }
96 *self = Self::new(Arc::new(table));
97 }
98
99 #[inline]
101 pub fn table(&self) -> &[u64; 256] {
102 &self.table
103 }
104
105 #[inline]
107 pub fn new_spec(spec: SpecId) -> Self {
108 let mut table = [0; 256];
109
110 table[GasId::exp_byte_gas().as_usize()] = 10;
111 table[GasId::logdata().as_usize()] = gas::LOGDATA;
112 table[GasId::logtopic().as_usize()] = gas::LOGTOPIC;
113 table[GasId::copy_per_word().as_usize()] = gas::COPY;
114 table[GasId::extcodecopy_per_word().as_usize()] = gas::COPY;
115 table[GasId::mcopy_per_word().as_usize()] = gas::COPY;
116 table[GasId::keccak256_per_word().as_usize()] = gas::KECCAK256WORD;
117 table[GasId::memory_linear_cost().as_usize()] = gas::MEMORY;
118 table[GasId::memory_quadratic_reduction().as_usize()] = 512;
119 table[GasId::initcode_per_word().as_usize()] = gas::INITCODE_WORD_COST;
120 table[GasId::create().as_usize()] = gas::CREATE;
121 table[GasId::call_stipend_reduction().as_usize()] = 64;
122 table[GasId::transfer_value_cost().as_usize()] = gas::CALLVALUE;
123 table[GasId::cold_account_additional_cost().as_usize()] = 0;
124 table[GasId::new_account_cost().as_usize()] = gas::NEWACCOUNT;
125 table[GasId::warm_storage_read_cost().as_usize()] = 0;
126 table[GasId::sstore_static().as_usize()] = SSTORE_RESET;
128 table[GasId::sstore_set_without_load_cost().as_usize()] = SSTORE_SET - SSTORE_RESET;
130 table[GasId::sstore_reset_without_cold_load_cost().as_usize()] = 0;
132 table[GasId::sstore_clearing_slot_refund().as_usize()] = 15000;
134 table[GasId::selfdestruct_refund().as_usize()] = 24000;
135 table[GasId::call_stipend().as_usize()] = gas::CALL_STIPEND;
136 table[GasId::cold_storage_additional_cost().as_usize()] = 0;
137 table[GasId::cold_storage_cost().as_usize()] = 0;
138 table[GasId::new_account_cost_for_selfdestruct().as_usize()] = 0;
139
140 if spec.is_enabled_in(SpecId::TANGERINE) {
141 table[GasId::new_account_cost_for_selfdestruct().as_usize()] = gas::NEWACCOUNT;
142 }
143
144 if spec.is_enabled_in(SpecId::SPURIOUS_DRAGON) {
145 table[GasId::exp_byte_gas().as_usize()] = 50;
146 }
147
148 if spec.is_enabled_in(SpecId::ISTANBUL) {
149 table[GasId::sstore_static().as_usize()] = gas::ISTANBUL_SLOAD_GAS;
150 table[GasId::sstore_set_without_load_cost().as_usize()] =
151 SSTORE_SET - ISTANBUL_SLOAD_GAS;
152 table[GasId::sstore_reset_without_cold_load_cost().as_usize()] =
153 SSTORE_RESET - ISTANBUL_SLOAD_GAS;
154 }
155
156 if spec.is_enabled_in(SpecId::BERLIN) {
157 table[GasId::sstore_static().as_usize()] = gas::WARM_STORAGE_READ_COST;
158 table[GasId::cold_account_additional_cost().as_usize()] =
159 gas::COLD_ACCOUNT_ACCESS_COST_ADDITIONAL;
160 table[GasId::cold_storage_additional_cost().as_usize()] =
161 gas::COLD_SLOAD_COST - gas::WARM_STORAGE_READ_COST;
162 table[GasId::cold_storage_cost().as_usize()] = gas::COLD_SLOAD_COST;
163 table[GasId::warm_storage_read_cost().as_usize()] = gas::WARM_STORAGE_READ_COST;
164
165 table[GasId::sstore_reset_without_cold_load_cost().as_usize()] =
166 WARM_SSTORE_RESET - gas::WARM_STORAGE_READ_COST;
167 table[GasId::sstore_set_without_load_cost().as_usize()] =
168 SSTORE_SET - gas::WARM_STORAGE_READ_COST;
169 }
170
171 if spec.is_enabled_in(SpecId::LONDON) {
172 table[GasId::sstore_clearing_slot_refund().as_usize()] =
177 WARM_SSTORE_RESET + gas::ACCESS_LIST_STORAGE_KEY;
178
179 table[GasId::selfdestruct_refund().as_usize()] = 0;
180 }
181
182 Self::new(Arc::new(table))
183 }
184
185 #[inline]
187 pub const fn get(&self, id: GasId) -> u64 {
188 unsafe { *self.ptr.add(id.as_usize()) }
189 }
190
191 #[inline]
193 pub fn exp_cost(&self, power: U256) -> u64 {
194 if power.is_zero() {
195 return 0;
196 }
197 self.get(GasId::exp_byte_gas())
199 .saturating_mul(log2floor(power) / 8 + 1)
200 }
201
202 #[inline]
204 pub fn selfdestruct_refund(&self) -> i64 {
205 self.get(GasId::selfdestruct_refund()) as i64
206 }
207
208 #[inline]
210 pub fn selfdestruct_cost(&self, should_charge_topup: bool, is_cold: bool) -> u64 {
211 let mut gas = 0;
212
213 if should_charge_topup {
215 gas += self.new_account_cost_for_selfdestruct();
216 }
217
218 if is_cold {
219 gas += self.cold_account_additional_cost() + self.warm_storage_read_cost();
225 }
226 gas
227 }
228
229 #[inline]
231 pub fn extcodecopy(&self, len: usize) -> u64 {
232 self.get(GasId::extcodecopy_per_word())
233 .saturating_mul(num_words(len) as u64)
234 }
235
236 #[inline]
238 pub fn mcopy_cost(&self, len: usize) -> u64 {
239 self.get(GasId::mcopy_per_word())
240 .saturating_mul(num_words(len) as u64)
241 }
242
243 #[inline]
245 pub fn sstore_static_gas(&self) -> u64 {
246 self.get(GasId::sstore_static())
247 }
248
249 #[inline]
251 pub fn sstore_set_without_load_cost(&self) -> u64 {
252 self.get(GasId::sstore_set_without_load_cost())
253 }
254
255 #[inline]
257 pub fn sstore_reset_without_cold_load_cost(&self) -> u64 {
258 self.get(GasId::sstore_reset_without_cold_load_cost())
259 }
260
261 #[inline]
263 pub fn sstore_clearing_slot_refund(&self) -> u64 {
264 self.get(GasId::sstore_clearing_slot_refund())
265 }
266
267 #[inline]
271 pub fn sstore_dynamic_gas(&self, is_istanbul: bool, vals: &SStoreResult, is_cold: bool) -> u64 {
272 if !is_istanbul {
275 if vals.is_present_zero() && !vals.is_new_zero() {
276 return self.sstore_set_without_load_cost();
277 } else {
278 return self.sstore_reset_without_cold_load_cost();
279 }
280 }
281
282 let mut gas = 0;
283
284 if is_cold {
286 gas += self.cold_storage_cost();
287 }
288
289 if vals.new_values_changes_present() && vals.is_original_eq_present() {
291 gas += if vals.is_original_zero() {
292 self.sstore_set_without_load_cost()
295 } else {
296 self.sstore_reset_without_cold_load_cost()
298 };
299 }
300 gas
301 }
302
303 #[inline]
305 pub fn sstore_refund(&self, is_istanbul: bool, vals: &SStoreResult) -> i64 {
306 let sstore_clearing_slot_refund = self.sstore_clearing_slot_refund() as i64;
308
309 if !is_istanbul {
310 if !vals.is_present_zero() && vals.is_new_zero() {
312 return sstore_clearing_slot_refund;
313 }
314 return 0;
315 }
316
317 if vals.is_new_eq_present() {
319 return 0;
320 }
321
322 if vals.is_original_eq_present() && vals.is_new_zero() {
325 return sstore_clearing_slot_refund;
326 }
327
328 let mut refund = 0;
329 if !vals.is_original_zero() {
331 if vals.is_present_zero() {
333 refund -= sstore_clearing_slot_refund;
335 } else if vals.is_new_zero() {
337 refund += sstore_clearing_slot_refund;
339 }
340 }
341
342 if vals.is_original_eq_new() {
344 if vals.is_original_zero() {
346 refund += self.sstore_set_without_load_cost() as i64;
348 } else {
350 refund += self.sstore_reset_without_cold_load_cost() as i64;
352 }
353 }
354 refund
355 }
356
357 #[inline]
359 pub const fn log_cost(&self, n: u8, len: u64) -> u64 {
360 self.get(GasId::logdata())
361 .saturating_mul(len)
362 .saturating_add(self.get(GasId::logtopic()) * n as u64)
363 }
364
365 #[inline]
367 pub fn keccak256_cost(&self, len: usize) -> u64 {
368 self.get(GasId::keccak256_per_word())
369 .saturating_mul(num_words(len) as u64)
370 }
371
372 #[inline]
374 pub fn memory_cost(&self, len: usize) -> u64 {
375 let len = len as u64;
376 self.get(GasId::memory_linear_cost())
377 .saturating_mul(len)
378 .saturating_add(
379 (len.saturating_mul(len))
380 .saturating_div(self.get(GasId::memory_quadratic_reduction())),
381 )
382 }
383
384 #[inline]
386 pub fn initcode_cost(&self, len: usize) -> u64 {
387 self.get(GasId::initcode_per_word())
388 .saturating_mul(num_words(len) as u64)
389 }
390
391 #[inline]
393 pub fn create_cost(&self) -> u64 {
394 self.get(GasId::create())
395 }
396
397 #[inline]
399 pub fn create2_cost(&self, len: usize) -> u64 {
400 self.get(GasId::create()).saturating_add(
401 self.get(GasId::keccak256_per_word())
402 .saturating_mul(num_words(len) as u64),
403 )
404 }
405
406 #[inline]
408 pub fn call_stipend(&self) -> u64 {
409 self.get(GasId::call_stipend())
410 }
411
412 #[inline]
414 pub fn call_stipend_reduction(&self, gas_limit: u64) -> u64 {
415 gas_limit - gas_limit / self.get(GasId::call_stipend_reduction())
416 }
417
418 #[inline]
420 pub fn transfer_value_cost(&self) -> u64 {
421 self.get(GasId::transfer_value_cost())
422 }
423
424 #[inline]
426 pub fn cold_account_additional_cost(&self) -> u64 {
427 self.get(GasId::cold_account_additional_cost())
428 }
429
430 #[inline]
432 pub fn cold_storage_additional_cost(&self) -> u64 {
433 self.get(GasId::cold_storage_additional_cost())
434 }
435
436 #[inline]
438 pub fn cold_storage_cost(&self) -> u64 {
439 self.get(GasId::cold_storage_cost())
440 }
441
442 #[inline]
444 pub fn new_account_cost(&self, is_spurious_dragon: bool, transfers_value: bool) -> u64 {
445 if !is_spurious_dragon || transfers_value {
449 return self.get(GasId::new_account_cost());
450 }
451 0
452 }
453
454 #[inline]
456 pub fn new_account_cost_for_selfdestruct(&self) -> u64 {
457 self.get(GasId::new_account_cost_for_selfdestruct())
458 }
459
460 #[inline]
462 pub fn warm_storage_read_cost(&self) -> u64 {
463 self.get(GasId::warm_storage_read_cost())
464 }
465
466 #[inline]
468 pub fn copy_cost(&self, len: usize) -> u64 {
469 self.copy_per_word_cost(num_words(len))
470 }
471
472 #[inline]
474 pub fn copy_per_word_cost(&self, word_num: usize) -> u64 {
475 self.get(GasId::copy_per_word())
476 .saturating_mul(word_num as u64)
477 }
478}
479
480#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
482pub struct GasId(u8);
483
484impl GasId {
485 pub const fn new(id: u8) -> Self {
487 Self(id)
488 }
489
490 pub const fn as_u8(&self) -> u8 {
492 self.0
493 }
494
495 pub const fn as_usize(&self) -> usize {
497 self.0 as usize
498 }
499
500 pub const fn name(&self) -> &'static str {
512 match self.0 {
513 x if x == Self::exp_byte_gas().as_u8() => "exp_byte_gas",
514 x if x == Self::extcodecopy_per_word().as_u8() => "extcodecopy_per_word",
515 x if x == Self::copy_per_word().as_u8() => "copy_per_word",
516 x if x == Self::logdata().as_u8() => "logdata",
517 x if x == Self::logtopic().as_u8() => "logtopic",
518 x if x == Self::mcopy_per_word().as_u8() => "mcopy_per_word",
519 x if x == Self::keccak256_per_word().as_u8() => "keccak256_per_word",
520 x if x == Self::memory_linear_cost().as_u8() => "memory_linear_cost",
521 x if x == Self::memory_quadratic_reduction().as_u8() => "memory_quadratic_reduction",
522 x if x == Self::initcode_per_word().as_u8() => "initcode_per_word",
523 x if x == Self::create().as_u8() => "create",
524 x if x == Self::call_stipend_reduction().as_u8() => "call_stipend_reduction",
525 x if x == Self::transfer_value_cost().as_u8() => "transfer_value_cost",
526 x if x == Self::cold_account_additional_cost().as_u8() => {
527 "cold_account_additional_cost"
528 }
529 x if x == Self::new_account_cost().as_u8() => "new_account_cost",
530 x if x == Self::warm_storage_read_cost().as_u8() => "warm_storage_read_cost",
531 x if x == Self::sstore_static().as_u8() => "sstore_static",
532 x if x == Self::sstore_set_without_load_cost().as_u8() => {
533 "sstore_set_without_load_cost"
534 }
535 x if x == Self::sstore_reset_without_cold_load_cost().as_u8() => {
536 "sstore_reset_without_cold_load_cost"
537 }
538 x if x == Self::sstore_clearing_slot_refund().as_u8() => "sstore_clearing_slot_refund",
539 x if x == Self::selfdestruct_refund().as_u8() => "selfdestruct_refund",
540 x if x == Self::call_stipend().as_u8() => "call_stipend",
541 x if x == Self::cold_storage_additional_cost().as_u8() => {
542 "cold_storage_additional_cost"
543 }
544 x if x == Self::cold_storage_cost().as_u8() => "cold_storage_cost",
545 x if x == Self::new_account_cost_for_selfdestruct().as_u8() => {
546 "new_account_cost_for_selfdestruct"
547 }
548 _ => "unknown",
549 }
550 }
551
552 pub fn from_name(s: &str) -> Option<GasId> {
566 match s {
567 "exp_byte_gas" => Some(Self::exp_byte_gas()),
568 "extcodecopy_per_word" => Some(Self::extcodecopy_per_word()),
569 "copy_per_word" => Some(Self::copy_per_word()),
570 "logdata" => Some(Self::logdata()),
571 "logtopic" => Some(Self::logtopic()),
572 "mcopy_per_word" => Some(Self::mcopy_per_word()),
573 "keccak256_per_word" => Some(Self::keccak256_per_word()),
574 "memory_linear_cost" => Some(Self::memory_linear_cost()),
575 "memory_quadratic_reduction" => Some(Self::memory_quadratic_reduction()),
576 "initcode_per_word" => Some(Self::initcode_per_word()),
577 "create" => Some(Self::create()),
578 "call_stipend_reduction" => Some(Self::call_stipend_reduction()),
579 "transfer_value_cost" => Some(Self::transfer_value_cost()),
580 "cold_account_additional_cost" => Some(Self::cold_account_additional_cost()),
581 "new_account_cost" => Some(Self::new_account_cost()),
582 "warm_storage_read_cost" => Some(Self::warm_storage_read_cost()),
583 "sstore_static" => Some(Self::sstore_static()),
584 "sstore_set_without_load_cost" => Some(Self::sstore_set_without_load_cost()),
585 "sstore_reset_without_cold_load_cost" => {
586 Some(Self::sstore_reset_without_cold_load_cost())
587 }
588 "sstore_clearing_slot_refund" => Some(Self::sstore_clearing_slot_refund()),
589 "selfdestruct_refund" => Some(Self::selfdestruct_refund()),
590 "call_stipend" => Some(Self::call_stipend()),
591 "cold_storage_additional_cost" => Some(Self::cold_storage_additional_cost()),
592 "cold_storage_cost" => Some(Self::cold_storage_cost()),
593 "new_account_cost_for_selfdestruct" => Some(Self::new_account_cost_for_selfdestruct()),
594 _ => None,
595 }
596 }
597
598 pub const fn exp_byte_gas() -> GasId {
600 Self::new(1)
601 }
602
603 pub const fn extcodecopy_per_word() -> GasId {
605 Self::new(2)
606 }
607
608 pub const fn copy_per_word() -> GasId {
610 Self::new(3)
611 }
612
613 pub const fn logdata() -> GasId {
615 Self::new(4)
616 }
617
618 pub const fn logtopic() -> GasId {
620 Self::new(5)
621 }
622
623 pub const fn mcopy_per_word() -> GasId {
625 Self::new(6)
626 }
627
628 pub const fn keccak256_per_word() -> GasId {
630 Self::new(7)
631 }
632
633 pub const fn memory_linear_cost() -> GasId {
635 Self::new(8)
636 }
637
638 pub const fn memory_quadratic_reduction() -> GasId {
640 Self::new(9)
641 }
642
643 pub const fn initcode_per_word() -> GasId {
645 Self::new(10)
646 }
647
648 pub const fn create() -> GasId {
650 Self::new(11)
651 }
652
653 pub const fn call_stipend_reduction() -> GasId {
655 Self::new(12)
656 }
657
658 pub const fn transfer_value_cost() -> GasId {
660 Self::new(13)
661 }
662
663 pub const fn cold_account_additional_cost() -> GasId {
665 Self::new(14)
666 }
667
668 pub const fn new_account_cost() -> GasId {
670 Self::new(15)
671 }
672
673 pub const fn warm_storage_read_cost() -> GasId {
677 Self::new(16)
678 }
679
680 pub const fn sstore_static() -> GasId {
683 Self::new(17)
684 }
685
686 pub const fn sstore_set_without_load_cost() -> GasId {
688 Self::new(18)
689 }
690
691 pub const fn sstore_reset_without_cold_load_cost() -> GasId {
693 Self::new(19)
694 }
695
696 pub const fn sstore_clearing_slot_refund() -> GasId {
698 Self::new(20)
699 }
700
701 pub const fn selfdestruct_refund() -> GasId {
703 Self::new(21)
704 }
705
706 pub const fn call_stipend() -> GasId {
708 Self::new(22)
709 }
710
711 pub const fn cold_storage_additional_cost() -> GasId {
713 Self::new(23)
714 }
715
716 pub const fn cold_storage_cost() -> GasId {
718 Self::new(24)
719 }
720
721 pub const fn new_account_cost_for_selfdestruct() -> GasId {
723 Self::new(25)
724 }
725}
726
727#[cfg(test)]
728mod tests {
729 use super::*;
730 use std::collections::HashSet;
731
732 #[test]
733 fn test_gas_id_name_and_from_str_coverage() {
734 let mut unique_names = HashSet::new();
735 let mut known_gas_ids = 0;
736
737 for i in 0..=255 {
739 let gas_id = GasId::new(i);
740 let name = gas_id.name();
741
742 if name != "unknown" {
744 unique_names.insert(name);
745 }
746 }
747
748 for name in &unique_names {
750 if let Some(gas_id) = GasId::from_name(name) {
751 known_gas_ids += 1;
752 assert_eq!(gas_id.name(), *name, "Round-trip failed for {}", name);
754 }
755 }
756
757 println!("Total unique named GasIds: {}", unique_names.len());
758 println!("GasIds resolvable via from_str: {}", known_gas_ids);
759
760 assert_eq!(
762 unique_names.len(),
763 known_gas_ids,
764 "Not all unique names are resolvable via from_str"
765 );
766
767 assert_eq!(
769 unique_names.len(),
770 25,
771 "Expected 25 unique GasIds, found {}",
772 unique_names.len()
773 );
774 }
775}