1#[cfg(not(feature = "std"))]
4pub mod prelude {
5 pub use alloc::vec::Vec;
6}
7
8#[cfg(feature = "std")]
9pub mod prelude {
10 pub use std::vec::Vec;
11}
12
13#[cfg(feature = "tracing")]
14pub mod tracing;
15
16#[cfg(feature = "tracing")]
17macro_rules! event {
18 ($x:expr) => {
19 use self::tracing::Event::*;
20 self::tracing::with(|listener| listener.event($x));
21 };
22}
23#[cfg(feature = "force-debug")]
24macro_rules! log_gas {
25 ($self:expr, $($arg:tt)*) => (
26 log::trace!(target: "evm", "Gasometer {} [Gas used: {}, Gas left: {}]", format_args!($($arg)*),
27 $self.total_used_gas(), $self.gas());
28 #[cfg(feature = "print-debug")]
29 println!("\t# {} [{} | {}]", format_args!($($arg)*), $self.total_used_gas(), $self.gas());
30 );
31}
32
33#[cfg(not(feature = "force-debug"))]
34macro_rules! log_gas {
35 ($self:expr, $($arg:tt)*) => {};
36}
37
38#[cfg(not(feature = "tracing"))]
39macro_rules! event {
40 ($x:expr) => {};
41}
42
43mod consts;
44mod costs;
45mod memory;
46mod utils;
47
48use crate::core::utils::U256_ZERO;
49use crate::core::{ExitError, Opcode, Stack};
50use crate::prelude::*;
51use crate::runtime::{Config, Handler};
52use core::cmp::max;
53use primitive_types::{H160, H256, U256};
54
55macro_rules! try_or_fail {
56 ( $inner:expr, $e:expr ) => {
57 match $e {
58 Ok(value) => value,
59 Err(e) => {
60 $inner = Err(e.clone());
61 return Err(e);
62 }
63 }
64 };
65}
66
67#[cfg(feature = "tracing")]
68#[derive(Debug, Copy, Clone)]
69pub struct Snapshot {
70 pub gas_limit: u64,
71 pub memory_gas: u64,
72 pub used_gas: u64,
73 pub refunded_gas: i64,
74}
75
76#[cfg(feature = "tracing")]
77impl Snapshot {
78 #[must_use]
79 const fn new<'config>(gas_limit: u64, inner: &'config Inner<'config>) -> Self {
80 Self {
81 gas_limit,
82 memory_gas: inner.memory_gas,
83 used_gas: inner.used_gas,
84 refunded_gas: inner.refunded_gas,
85 }
86 }
87}
88
89#[derive(Clone, Debug)]
91pub struct Gasometer<'config> {
92 gas_limit: u64,
93 config: &'config Config,
94 inner: Result<Inner<'config>, ExitError>,
95}
96
97impl<'config> Gasometer<'config> {
98 #[must_use]
100 pub const fn new(gas_limit: u64, config: &'config Config) -> Self {
101 Self {
102 gas_limit,
103 config,
104 inner: Ok(Inner {
105 memory_gas: 0,
106 used_gas: 0,
107 refunded_gas: 0,
108 floor_gas: 0,
109 config,
110 }),
111 }
112 }
113
114 #[inline]
119 pub fn gas_cost(&self, cost: GasCost, gas: u64) -> Result<u64, ExitError> {
120 match self.inner.as_ref() {
121 Ok(inner) => inner.gas_cost(cost, gas),
122 Err(e) => Err(e.clone()),
123 }
124 }
125
126 #[inline]
127 fn inner_mut(&mut self) -> Result<&mut Inner<'config>, ExitError> {
128 self.inner.as_mut().map_err(|e| e.clone())
129 }
130
131 #[inline]
133 #[must_use]
134 pub const fn config(&self) -> &'config Config {
135 self.config
136 }
137
138 #[inline]
140 #[must_use]
141 pub const fn gas_limit(&self) -> u64 {
142 self.gas_limit
143 }
144
145 #[inline]
147 #[must_use]
148 pub fn floor_gas(&self) -> u64 {
149 self.inner.as_ref().map_or(0, |inner| inner.floor_gas)
150 }
151
152 #[inline]
154 #[must_use]
155 pub fn gas(&self) -> u64 {
156 self.inner.as_ref().map_or(0, |inner| {
157 self.gas_limit - inner.used_gas - inner.memory_gas
158 })
159 }
160
161 #[inline]
163 #[must_use]
164 pub const fn total_used_gas(&self) -> u64 {
165 match self.inner.as_ref() {
166 Ok(inner) => inner.used_gas + inner.memory_gas,
167 Err(_) => self.gas_limit,
168 }
169 }
170
171 #[inline]
173 #[must_use]
174 pub fn refunded_gas(&self) -> i64 {
175 self.inner.as_ref().map_or(0, |inner| inner.refunded_gas)
176 }
177
178 pub fn fail(&mut self) -> ExitError {
180 self.inner = Err(ExitError::OutOfGas);
181 ExitError::OutOfGas
182 }
183
184 #[inline]
189 pub fn record_cost(&mut self, cost: u64) -> Result<(), ExitError> {
190 event!(RecordCost {
191 cost,
192 snapshot: self.snapshot(),
193 });
194
195 let all_gas_cost = self.total_used_gas() + cost;
196 if self.gas_limit < all_gas_cost {
197 self.inner = Err(ExitError::OutOfGas);
198 return Err(ExitError::OutOfGas);
199 }
200
201 self.inner_mut()?.used_gas += cost;
202 log_gas!(self, "record_cost: {}", cost);
203 Ok(())
204 }
205
206 #[inline]
207 pub fn record_refund(&mut self, refund: i64) -> Result<(), ExitError> {
212 event!(RecordRefund {
213 refund,
214 snapshot: self.snapshot(),
215 });
216 log_gas!(self, "record_refund: -{}", refund);
217
218 self.inner_mut()?.refunded_gas += refund;
219 Ok(())
220 }
221
222 pub fn record_authority_refund(&mut self, refunded_accounts: u64) -> Result<(), ExitError> {
228 let refund = i64::try_from(
229 refunded_accounts
230 * (self.config.gas_per_empty_account_cost - self.config.gas_per_auth_base_cost),
231 )
232 .unwrap_or(i64::MAX);
233 self.record_refund(refund)
234 }
235
236 #[allow(clippy::as_conversions)]
242 #[inline]
243 pub fn record_deposit(&mut self, len: usize) -> Result<(), ExitError> {
244 let cost = len as u64 * u64::from(consts::G_CODEDEPOSIT);
245 self.record_cost(cost)
246 }
247
248 pub fn record_dynamic_cost(
253 &mut self,
254 cost: GasCost,
255 memory: Option<MemoryCost>,
256 ) -> Result<(), ExitError> {
257 let gas = self.gas();
258 let inner_mut = match &mut self.inner {
261 Ok(inner) => inner,
262 Err(err) => return Err(err.clone()),
263 };
264
265 let memory_gas = match memory {
266 Some(memory) => try_or_fail!(self.inner, inner_mut.memory_gas(memory)),
267 None => inner_mut.memory_gas,
268 };
269 let gas_cost = try_or_fail!(self.inner, inner_mut.gas_cost(cost, gas));
270 let gas_refund = inner_mut.gas_refund(cost);
271 let used_gas = inner_mut.used_gas;
272
273 #[cfg(feature = "tracing")]
274 let gas_limit = self.gas_limit;
275 event!(RecordDynamicCost {
276 gas_cost,
277 memory_gas,
278 gas_refund,
279 snapshot: Some(Snapshot::new(gas_limit, inner_mut)),
280 });
281
282 let all_gas_cost = memory_gas
283 .checked_add(used_gas.saturating_add(gas_cost))
284 .ok_or(ExitError::OutOfGas)?;
285 if self.gas_limit < all_gas_cost {
286 self.inner = Err(ExitError::OutOfGas);
287 return Err(ExitError::OutOfGas);
288 }
289
290 let after_gas = self.gas_limit - all_gas_cost;
291 try_or_fail!(self.inner, inner_mut.extra_check(cost, after_gas));
292
293 inner_mut.used_gas += gas_cost;
294 inner_mut.memory_gas = memory_gas;
295 inner_mut.refunded_gas += gas_refund;
296
297 log_gas!(
299 self,
300 "record_dynamic_cost: {gas_cost} - {memory_gas} - {gas_refund}"
301 );
302
303 Ok(())
304 }
305
306 #[inline]
311 pub fn record_stipend(&mut self, stipend: u64) -> Result<(), ExitError> {
312 event!(RecordStipend {
313 stipend,
314 snapshot: self.snapshot(),
315 });
316
317 self.inner_mut()?.used_gas -= stipend;
318 log_gas!(self, "record_stipent: {}", stipend);
319 Ok(())
320 }
321
322 #[must_use]
325 pub fn calculate_intrinsic_gas_and_gas_floor(
326 data: &[u8],
327 access_list: &[(H160, Vec<H256>)],
328 authorization_list_len: usize,
329 config: &Config,
330 is_contract_creation: bool,
331 ) -> (u64, u64) {
332 let cost = if is_contract_creation {
333 create_transaction_cost(data, access_list)
334 } else {
335 call_transaction_cost(data, access_list, authorization_list_len)
336 };
337 Self::intrinsic_gas_and_gas_floor(cost, config)
338 }
339
340 #[must_use]
346 pub const fn intrinsic_gas_and_gas_floor(cost: TransactionCost, config: &Config) -> (u64, u64) {
347 match cost {
348 #[allow(clippy::as_conversions)]
350 TransactionCost::Call {
351 zero_data_len,
352 non_zero_data_len,
353 access_list_address_len,
354 access_list_storage_len,
355 authorization_list_len,
356 } => {
357 #[deny(clippy::let_and_return)]
358 let cost = config.gas_transaction_call
359 + zero_data_len as u64 * config.gas_transaction_zero_data
360 + non_zero_data_len as u64 * config.gas_transaction_non_zero_data
361 + access_list_address_len as u64 * config.gas_access_list_address
362 + access_list_storage_len as u64 * config.gas_access_list_storage_key
363 + authorization_list_len as u64 * config.gas_per_empty_account_cost;
364
365 let floor_gas = if config.has_floor_gas {
366 let tokens_in_calldata = (zero_data_len + non_zero_data_len * 4) as u64;
369 tokens_in_calldata * config.total_cost_floor_per_token
370 + config.gas_transaction_call
371 } else {
372 0
373 };
374
375 (cost, floor_gas)
376 }
377 #[allow(clippy::as_conversions)]
379 TransactionCost::Create {
380 zero_data_len,
381 non_zero_data_len,
382 access_list_address_len,
383 access_list_storage_len,
384 initcode_cost,
385 } => {
386 let mut cost = config.gas_transaction_create
387 + zero_data_len as u64 * config.gas_transaction_zero_data
388 + non_zero_data_len as u64 * config.gas_transaction_non_zero_data
389 + access_list_address_len as u64 * config.gas_access_list_address
390 + access_list_storage_len as u64 * config.gas_access_list_storage_key;
391 if config.max_initcode_size.is_some() {
392 cost += initcode_cost;
393 }
394
395 let floor_gas = if config.has_floor_gas {
396 let tokens_in_calldata = (zero_data_len + non_zero_data_len * 4) as u64;
399 tokens_in_calldata * config.total_cost_floor_per_token
400 + config.gas_transaction_call
401 } else {
402 0
403 };
404
405 (cost, floor_gas)
406 }
407 }
408 }
409
410 pub fn verify_transaction(&mut self, cost: TransactionCost) -> Result<(u64, u64), ExitError> {
420 let (gas_cost, floor_gas) = Self::intrinsic_gas_and_gas_floor(cost, self.config);
421 #[cfg(feature = "force-debug")]
422 match cost {
423 TransactionCost::Call {
424 zero_data_len,
425 non_zero_data_len,
426 access_list_address_len,
427 access_list_storage_len,
428 authorization_list_len,
429 } => {
430 log_gas!(
431 self,
432 "Record Call {} [gas_transaction_call: {}, zero_data_len: {}, non_zero_data_len: {}, access_list_address_len: {}, access_list_storage_len: {}, authorization_list_len: {}]",
433 cost,
434 self.config.gas_transaction_call,
435 zero_data_len,
436 non_zero_data_len,
437 access_list_address_len,
438 access_list_storage_len,
439 authorization_list_len
440 );
441 }
442 TransactionCost::Create {
443 zero_data_len,
444 non_zero_data_len,
445 access_list_address_len,
446 access_list_storage_len,
447 initcode_cost,
448 } => {
449 log_gas!(
450 self,
451 "Record Create {} [gas_transaction_create: {}, zero_data_len: {}, non_zero_data_len: {}, access_list_address_len: {}, access_list_storage_len: {}, initcode_cost: {}]",
452 cost,
453 self.config.gas_transaction_create,
454 zero_data_len,
455 non_zero_data_len,
456 access_list_address_len,
457 access_list_storage_len,
458 initcode_cost
459 );
460 }
461 }
462 if self.gas() < gas_cost {
463 self.inner = Err(ExitError::OutOfGas);
464 return Err(ExitError::OutOfGas);
465 }
466 if self.config.has_floor_gas && self.gas_limit() < floor_gas {
470 self.inner = Err(ExitError::OutOfGas);
471 return Err(ExitError::OutOfGas);
472 }
473
474 Ok((gas_cost, floor_gas))
475 }
476
477 pub fn record_transaction(&mut self, cost: TransactionCost) -> Result<(), ExitError> {
485 let (gas_cost, floor_gas) = self.verify_transaction(cost)?;
486
487 event!(RecordTransaction {
488 cost: gas_cost,
489 snapshot: self.snapshot(),
490 });
491
492 if self.config.has_floor_gas {
494 self.inner_mut()?.floor_gas = floor_gas;
495 }
496
497 self.inner_mut()?.used_gas += gas_cost;
498 Ok(())
499 }
500
501 #[cfg(feature = "tracing")]
502 #[must_use]
503 pub fn snapshot(&self) -> Option<Snapshot> {
504 self.inner
505 .as_ref()
506 .ok()
507 .map(|inner| Snapshot::new(self.gas_limit, inner))
508 }
509}
510
511#[allow(clippy::naive_bytecount)]
513#[must_use]
514pub fn call_transaction_cost(
515 data: &[u8],
516 access_list: &[(H160, Vec<H256>)],
517 authorization_list_len: usize,
518) -> TransactionCost {
519 let zero_data_len = data.iter().filter(|v| **v == 0).count();
520 let non_zero_data_len = data.len() - zero_data_len;
521 let (access_list_address_len, access_list_storage_len) = count_access_list(access_list);
522
523 TransactionCost::Call {
524 zero_data_len,
525 non_zero_data_len,
526 access_list_address_len,
527 access_list_storage_len,
528 authorization_list_len,
529 }
530}
531
532#[allow(clippy::naive_bytecount)]
534#[must_use]
535pub fn create_transaction_cost(data: &[u8], access_list: &[(H160, Vec<H256>)]) -> TransactionCost {
536 let zero_data_len = data.iter().filter(|v| **v == 0).count();
537 let non_zero_data_len = data.len() - zero_data_len;
538 let (access_list_address_len, access_list_storage_len) = count_access_list(access_list);
539 let initcode_cost = init_code_cost(data);
540
541 TransactionCost::Create {
542 zero_data_len,
543 non_zero_data_len,
544 access_list_address_len,
545 access_list_storage_len,
546 initcode_cost,
547 }
548}
549
550#[allow(clippy::as_conversions)]
553#[must_use]
554pub const fn init_code_cost(data: &[u8]) -> u64 {
555 2 * (data.len() as u64).div_ceil(32)
559}
560
561fn count_access_list(access_list: &[(H160, Vec<H256>)]) -> (usize, usize) {
563 let access_list_address_len = access_list.len();
564 let access_list_storage_len = access_list.iter().map(|(_, keys)| keys.len()).sum();
565
566 (access_list_address_len, access_list_storage_len)
567}
568
569#[allow(clippy::too_many_lines)]
570#[inline]
571#[must_use]
572pub fn static_opcode_cost(opcode: Opcode) -> Option<u32> {
573 static TABLE: [Option<u32>; 256] = {
574 let mut table = [None; 256];
575
576 table[Opcode::STOP.as_usize()] = Some(consts::G_ZERO);
577 table[Opcode::CALLDATASIZE.as_usize()] = Some(consts::G_BASE);
578 table[Opcode::CODESIZE.as_usize()] = Some(consts::G_BASE);
579 table[Opcode::POP.as_usize()] = Some(consts::G_BASE);
580 table[Opcode::PC.as_usize()] = Some(consts::G_BASE);
581 table[Opcode::MSIZE.as_usize()] = Some(consts::G_BASE);
582
583 table[Opcode::ADDRESS.as_usize()] = Some(consts::G_BASE);
584 table[Opcode::ORIGIN.as_usize()] = Some(consts::G_BASE);
585 table[Opcode::CALLER.as_usize()] = Some(consts::G_BASE);
586 table[Opcode::CALLVALUE.as_usize()] = Some(consts::G_BASE);
587 table[Opcode::COINBASE.as_usize()] = Some(consts::G_BASE);
588 table[Opcode::TIMESTAMP.as_usize()] = Some(consts::G_BASE);
589 table[Opcode::NUMBER.as_usize()] = Some(consts::G_BASE);
590 table[Opcode::PREVRANDAO.as_usize()] = Some(consts::G_BASE);
591 table[Opcode::GASLIMIT.as_usize()] = Some(consts::G_BASE);
592 table[Opcode::GASPRICE.as_usize()] = Some(consts::G_BASE);
593 table[Opcode::GAS.as_usize()] = Some(consts::G_BASE);
594
595 table[Opcode::ADD.as_usize()] = Some(consts::G_VERYLOW);
596 table[Opcode::SUB.as_usize()] = Some(consts::G_VERYLOW);
597 table[Opcode::NOT.as_usize()] = Some(consts::G_VERYLOW);
598 table[Opcode::LT.as_usize()] = Some(consts::G_VERYLOW);
599 table[Opcode::GT.as_usize()] = Some(consts::G_VERYLOW);
600 table[Opcode::SLT.as_usize()] = Some(consts::G_VERYLOW);
601 table[Opcode::SGT.as_usize()] = Some(consts::G_VERYLOW);
602 table[Opcode::EQ.as_usize()] = Some(consts::G_VERYLOW);
603 table[Opcode::ISZERO.as_usize()] = Some(consts::G_VERYLOW);
604 table[Opcode::AND.as_usize()] = Some(consts::G_VERYLOW);
605 table[Opcode::OR.as_usize()] = Some(consts::G_VERYLOW);
606 table[Opcode::XOR.as_usize()] = Some(consts::G_VERYLOW);
607 table[Opcode::BYTE.as_usize()] = Some(consts::G_VERYLOW);
608 table[Opcode::CALLDATALOAD.as_usize()] = Some(consts::G_VERYLOW);
609 table[Opcode::PUSH1.as_usize()] = Some(consts::G_VERYLOW);
610 table[Opcode::PUSH2.as_usize()] = Some(consts::G_VERYLOW);
611 table[Opcode::PUSH3.as_usize()] = Some(consts::G_VERYLOW);
612 table[Opcode::PUSH4.as_usize()] = Some(consts::G_VERYLOW);
613 table[Opcode::PUSH5.as_usize()] = Some(consts::G_VERYLOW);
614 table[Opcode::PUSH6.as_usize()] = Some(consts::G_VERYLOW);
615 table[Opcode::PUSH7.as_usize()] = Some(consts::G_VERYLOW);
616 table[Opcode::PUSH8.as_usize()] = Some(consts::G_VERYLOW);
617 table[Opcode::PUSH9.as_usize()] = Some(consts::G_VERYLOW);
618 table[Opcode::PUSH10.as_usize()] = Some(consts::G_VERYLOW);
619 table[Opcode::PUSH11.as_usize()] = Some(consts::G_VERYLOW);
620 table[Opcode::PUSH12.as_usize()] = Some(consts::G_VERYLOW);
621 table[Opcode::PUSH13.as_usize()] = Some(consts::G_VERYLOW);
622 table[Opcode::PUSH14.as_usize()] = Some(consts::G_VERYLOW);
623 table[Opcode::PUSH15.as_usize()] = Some(consts::G_VERYLOW);
624 table[Opcode::PUSH16.as_usize()] = Some(consts::G_VERYLOW);
625 table[Opcode::PUSH17.as_usize()] = Some(consts::G_VERYLOW);
626 table[Opcode::PUSH18.as_usize()] = Some(consts::G_VERYLOW);
627 table[Opcode::PUSH19.as_usize()] = Some(consts::G_VERYLOW);
628 table[Opcode::PUSH20.as_usize()] = Some(consts::G_VERYLOW);
629 table[Opcode::PUSH21.as_usize()] = Some(consts::G_VERYLOW);
630 table[Opcode::PUSH22.as_usize()] = Some(consts::G_VERYLOW);
631 table[Opcode::PUSH23.as_usize()] = Some(consts::G_VERYLOW);
632 table[Opcode::PUSH24.as_usize()] = Some(consts::G_VERYLOW);
633 table[Opcode::PUSH25.as_usize()] = Some(consts::G_VERYLOW);
634 table[Opcode::PUSH26.as_usize()] = Some(consts::G_VERYLOW);
635 table[Opcode::PUSH27.as_usize()] = Some(consts::G_VERYLOW);
636 table[Opcode::PUSH28.as_usize()] = Some(consts::G_VERYLOW);
637 table[Opcode::PUSH29.as_usize()] = Some(consts::G_VERYLOW);
638 table[Opcode::PUSH30.as_usize()] = Some(consts::G_VERYLOW);
639 table[Opcode::PUSH31.as_usize()] = Some(consts::G_VERYLOW);
640 table[Opcode::PUSH32.as_usize()] = Some(consts::G_VERYLOW);
641 table[Opcode::DUP1.as_usize()] = Some(consts::G_VERYLOW);
642 table[Opcode::DUP2.as_usize()] = Some(consts::G_VERYLOW);
643 table[Opcode::DUP3.as_usize()] = Some(consts::G_VERYLOW);
644 table[Opcode::DUP4.as_usize()] = Some(consts::G_VERYLOW);
645 table[Opcode::DUP5.as_usize()] = Some(consts::G_VERYLOW);
646 table[Opcode::DUP6.as_usize()] = Some(consts::G_VERYLOW);
647 table[Opcode::DUP7.as_usize()] = Some(consts::G_VERYLOW);
648 table[Opcode::DUP8.as_usize()] = Some(consts::G_VERYLOW);
649 table[Opcode::DUP9.as_usize()] = Some(consts::G_VERYLOW);
650 table[Opcode::DUP10.as_usize()] = Some(consts::G_VERYLOW);
651 table[Opcode::DUP11.as_usize()] = Some(consts::G_VERYLOW);
652 table[Opcode::DUP12.as_usize()] = Some(consts::G_VERYLOW);
653 table[Opcode::DUP13.as_usize()] = Some(consts::G_VERYLOW);
654 table[Opcode::DUP14.as_usize()] = Some(consts::G_VERYLOW);
655 table[Opcode::DUP15.as_usize()] = Some(consts::G_VERYLOW);
656 table[Opcode::DUP16.as_usize()] = Some(consts::G_VERYLOW);
657 table[Opcode::SWAP1.as_usize()] = Some(consts::G_VERYLOW);
658 table[Opcode::SWAP2.as_usize()] = Some(consts::G_VERYLOW);
659 table[Opcode::SWAP3.as_usize()] = Some(consts::G_VERYLOW);
660 table[Opcode::SWAP4.as_usize()] = Some(consts::G_VERYLOW);
661 table[Opcode::SWAP5.as_usize()] = Some(consts::G_VERYLOW);
662 table[Opcode::SWAP6.as_usize()] = Some(consts::G_VERYLOW);
663 table[Opcode::SWAP7.as_usize()] = Some(consts::G_VERYLOW);
664 table[Opcode::SWAP8.as_usize()] = Some(consts::G_VERYLOW);
665 table[Opcode::SWAP9.as_usize()] = Some(consts::G_VERYLOW);
666 table[Opcode::SWAP10.as_usize()] = Some(consts::G_VERYLOW);
667 table[Opcode::SWAP11.as_usize()] = Some(consts::G_VERYLOW);
668 table[Opcode::SWAP12.as_usize()] = Some(consts::G_VERYLOW);
669 table[Opcode::SWAP13.as_usize()] = Some(consts::G_VERYLOW);
670 table[Opcode::SWAP14.as_usize()] = Some(consts::G_VERYLOW);
671 table[Opcode::SWAP15.as_usize()] = Some(consts::G_VERYLOW);
672 table[Opcode::SWAP16.as_usize()] = Some(consts::G_VERYLOW);
673
674 table[Opcode::MUL.as_usize()] = Some(consts::G_LOW);
675 table[Opcode::DIV.as_usize()] = Some(consts::G_LOW);
676 table[Opcode::SDIV.as_usize()] = Some(consts::G_LOW);
677 table[Opcode::MOD.as_usize()] = Some(consts::G_LOW);
678 table[Opcode::SMOD.as_usize()] = Some(consts::G_LOW);
679 table[Opcode::SIGNEXTEND.as_usize()] = Some(consts::G_LOW);
680
681 table[Opcode::ADDMOD.as_usize()] = Some(consts::G_MID);
682 table[Opcode::MULMOD.as_usize()] = Some(consts::G_MID);
683 table[Opcode::JUMP.as_usize()] = Some(consts::G_MID);
684
685 table[Opcode::JUMPI.as_usize()] = Some(consts::G_HIGH);
686 table[Opcode::JUMPDEST.as_usize()] = Some(consts::G_JUMPDEST);
687
688 table
689 };
690
691 TABLE[opcode.as_usize()]
692}
693
694fn get_and_set_warm<H: Handler>(handler: &mut H, target: H160) -> (bool, Option<bool>) {
696 let delegated_designator_is_cold =
697 handler
698 .get_authority_target(target)
699 .map(|authority_target| {
700 if handler.is_cold(authority_target, None) {
701 handler.warm_target((authority_target, None));
702 true
703 } else {
704 false
705 }
706 });
707 let target_is_cold = handler.is_cold(target, None);
708 if target_is_cold {
709 handler.warm_target((target, None));
710 }
711 (target_is_cold, delegated_designator_is_cold)
712}
713
714fn get_and_set_non_delegated_warm<H: Handler>(handler: &mut H, target: H160) -> bool {
717 let target_is_cold = handler.is_cold(target, None);
718 if target_is_cold {
719 handler.warm_target((target, None));
720 }
721 target_is_cold
722}
723
724#[allow(
729 clippy::nonminimal_bool,
730 clippy::cognitive_complexity,
731 clippy::too_many_lines,
732 clippy::match_same_arms
733)]
734pub fn dynamic_opcode_cost<H: Handler>(
735 address: H160,
736 opcode: Opcode,
737 stack: &Stack,
738 is_static: bool,
739 config: &Config,
740 handler: &mut H,
741) -> Result<(GasCost, Option<MemoryCost>), ExitError> {
742 let gas_cost = match opcode {
743 Opcode::RETURN => GasCost::Zero,
744
745 Opcode::MLOAD | Opcode::MSTORE | Opcode::MSTORE8 => GasCost::VeryLow,
746
747 Opcode::REVERT if config.has_revert => GasCost::Zero,
748 Opcode::REVERT => GasCost::Invalid(opcode),
749
750 Opcode::CHAINID if config.has_chain_id => GasCost::Base,
751 Opcode::CHAINID => GasCost::Invalid(opcode),
752
753 Opcode::SHL | Opcode::SHR | Opcode::SAR if config.has_bitwise_shifting => GasCost::VeryLow,
754 Opcode::SHL | Opcode::SHR | Opcode::SAR => GasCost::Invalid(opcode),
755
756 Opcode::SELFBALANCE if config.has_self_balance => GasCost::Low,
757 Opcode::SELFBALANCE => GasCost::Invalid(opcode),
758
759 Opcode::BASEFEE if config.has_base_fee => GasCost::Base,
760 Opcode::BASEFEE => GasCost::Invalid(opcode),
761
762 Opcode::BLOBBASEFEE if config.has_blob_base_fee => GasCost::Base,
763 Opcode::BLOBBASEFEE => GasCost::Invalid(opcode),
764
765 Opcode::BLOBHASH if config.has_shard_blob_transactions => GasCost::VeryLow,
766 Opcode::BLOBHASH => GasCost::Invalid(opcode),
767
768 Opcode::TLOAD if config.has_transient_storage => GasCost::WarmStorageRead,
769 Opcode::TLOAD => GasCost::Invalid(opcode),
770
771 Opcode::TSTORE if !is_static && config.has_transient_storage => GasCost::WarmStorageRead,
772 Opcode::TSTORE => GasCost::Invalid(opcode),
773
774 Opcode::MCOPY if config.has_mcopy => GasCost::VeryLowCopy {
775 len: stack.peek(2)?,
776 },
777 Opcode::MCOPY => GasCost::Invalid(opcode),
778
779 Opcode::EXTCODESIZE => {
780 let target = stack.peek_h256(0)?.into();
781 let target_is_cold = get_and_set_non_delegated_warm(handler, target);
782 GasCost::ExtCodeSize { target_is_cold }
783 }
784 Opcode::BALANCE => {
785 let target = stack.peek_h256(0)?.into();
786 let target_is_cold = handler.is_cold(target, None);
787 if target_is_cold {
788 handler.warm_target((target, None));
789 }
790 GasCost::Balance { target_is_cold }
791 }
792 Opcode::BLOCKHASH => GasCost::BlockHash,
793
794 Opcode::EXTCODEHASH if config.has_ext_code_hash => {
795 let target = stack.peek_h256(0)?.into();
796 let target_is_cold = get_and_set_non_delegated_warm(handler, target);
797 GasCost::ExtCodeHash { target_is_cold }
798 }
799 Opcode::EXTCODEHASH => GasCost::Invalid(opcode),
800
801 Opcode::CALLCODE => {
802 let target = stack.peek_h256(1)?.into();
803 let (target_is_cold, delegated_designator_is_cold) = get_and_set_warm(handler, target);
804 GasCost::CallCode {
805 value: stack.peek(2)?,
806 gas: stack.peek(0)?,
807 target_is_cold,
808 delegated_designator_is_cold,
809 target_exists: {
810 handler.record_external_operation(crate::core::ExternalOperation::IsEmpty)?;
811 handler.exists(target)
812 },
813 }
814 }
815 Opcode::STATICCALL => {
816 let target = stack.peek_h256(1)?.into();
817 let (target_is_cold, delegated_designator_is_cold) = get_and_set_warm(handler, target);
818 GasCost::StaticCall {
819 gas: stack.peek(0)?,
820 target_is_cold,
821 delegated_designator_is_cold,
822 target_exists: {
823 handler.record_external_operation(crate::core::ExternalOperation::IsEmpty)?;
824 handler.exists(target)
825 },
826 }
827 }
828 Opcode::SHA3 => GasCost::Sha3 {
829 len: stack.peek(1)?,
830 },
831 Opcode::EXTCODECOPY => {
832 let target = stack.peek_h256(0)?.into();
833 let target_is_cold = get_and_set_non_delegated_warm(handler, target);
834 GasCost::ExtCodeCopy {
835 target_is_cold,
836 len: stack.peek(3)?,
837 }
838 }
839 Opcode::CALLDATACOPY | Opcode::CODECOPY => GasCost::VeryLowCopy {
840 len: stack.peek(2)?,
841 },
842 Opcode::EXP => GasCost::Exp {
843 power: stack.peek(1)?,
844 },
845 Opcode::SLOAD => {
846 let index = stack.peek_h256(0)?;
847 let target_is_cold = handler.is_cold(address, Some(index));
848 if target_is_cold {
849 handler.warm_target((address, Some(index)));
850 }
851 GasCost::SLoad { target_is_cold }
852 }
853
854 Opcode::DELEGATECALL if config.has_delegate_call => {
855 let target = stack.peek_h256(1)?.into();
856 let (target_is_cold, delegated_designator_is_cold) = get_and_set_warm(handler, target);
857 GasCost::DelegateCall {
858 gas: stack.peek(0)?,
859 target_is_cold,
860 delegated_designator_is_cold,
861 target_exists: {
862 handler.record_external_operation(crate::core::ExternalOperation::IsEmpty)?;
863 handler.exists(target)
864 },
865 }
866 }
867 Opcode::DELEGATECALL => GasCost::Invalid(opcode),
868
869 Opcode::RETURNDATASIZE if config.has_return_data => GasCost::Base,
870 Opcode::RETURNDATACOPY if config.has_return_data => GasCost::VeryLowCopy {
871 len: stack.peek(2)?,
872 },
873 Opcode::RETURNDATASIZE | Opcode::RETURNDATACOPY => GasCost::Invalid(opcode),
874
875 Opcode::SSTORE if !is_static => {
876 let index = stack.peek_h256(0)?;
877 let value = stack.peek_h256(1)?;
878 let target_is_cold = handler.is_cold(address, Some(index));
879 if target_is_cold {
880 handler.warm_target((address, Some(index)));
881 }
882 GasCost::SStore {
883 original: handler.original_storage(address, index),
884 current: handler.storage(address, index),
885 new: value,
886 target_is_cold,
887 }
888 }
889 Opcode::LOG0 if !is_static => GasCost::Log {
890 n: 0,
891 len: stack.peek(1)?,
892 },
893 Opcode::LOG1 if !is_static => GasCost::Log {
894 n: 1,
895 len: stack.peek(1)?,
896 },
897 Opcode::LOG2 if !is_static => GasCost::Log {
898 n: 2,
899 len: stack.peek(1)?,
900 },
901 Opcode::LOG3 if !is_static => GasCost::Log {
902 n: 3,
903 len: stack.peek(1)?,
904 },
905 Opcode::LOG4 if !is_static => GasCost::Log {
906 n: 4,
907 len: stack.peek(1)?,
908 },
909 Opcode::CREATE if !is_static => GasCost::Create,
910 Opcode::CREATE2 if !is_static && config.has_create2 => GasCost::Create2 {
911 len: stack.peek(2)?,
912 },
913 Opcode::SELFDESTRUCT if !is_static => {
914 let target = stack.peek_h256(0)?.into();
915 let target_is_cold = handler.is_cold(target, None);
916 if target_is_cold {
917 handler.warm_target((target, None));
918 }
919 GasCost::Suicide {
920 value: handler.balance(address),
921 target_is_cold,
922 target_exists: {
923 handler.record_external_operation(crate::core::ExternalOperation::IsEmpty)?;
924 handler.exists(target)
925 },
926 already_removed: handler.deleted(address),
927 }
928 }
929 Opcode::CALL if !is_static || (is_static && stack.peek(2)? == U256_ZERO) => {
930 let target = stack.peek_h256(1)?.into();
931 let (target_is_cold, delegated_designator_is_cold) = get_and_set_warm(handler, target);
932 GasCost::Call {
933 value: stack.peek(2)?,
934 gas: stack.peek(0)?,
935 target_is_cold,
936 delegated_designator_is_cold,
937 target_exists: {
938 handler.record_external_operation(crate::core::ExternalOperation::IsEmpty)?;
939 handler.exists(target)
940 },
941 }
942 }
943
944 Opcode::PUSH0 if config.has_push0 => GasCost::Base,
945
946 _ => GasCost::Invalid(opcode),
947 };
948
949 let memory_cost = match opcode {
950 Opcode::SHA3
951 | Opcode::RETURN
952 | Opcode::REVERT
953 | Opcode::LOG0
954 | Opcode::LOG1
955 | Opcode::LOG2
956 | Opcode::LOG3
957 | Opcode::LOG4 => Some(peek_memory_cost(stack, 0, 1)?),
958
959 Opcode::CODECOPY | Opcode::CALLDATACOPY | Opcode::RETURNDATACOPY => {
960 Some(peek_memory_cost(stack, 0, 2)?)
961 }
962
963 Opcode::EXTCODECOPY => Some(peek_memory_cost(stack, 1, 3)?),
964
965 Opcode::MLOAD | Opcode::MSTORE => Some(MemoryCost {
966 offset: stack.peek_usize(0)?,
967 len: 32,
968 }),
969
970 Opcode::MCOPY => {
971 let len = stack.peek_usize(2)?;
972 if len == 0 {
973 None
974 } else {
975 Some(MemoryCost {
976 offset: {
977 let src = stack.peek_usize(0)?;
978 let dst = stack.peek_usize(1)?;
979 max(src, dst)
980 },
981 len,
982 })
983 }
984 }
985
986 Opcode::MSTORE8 => Some(MemoryCost {
987 offset: stack.peek_usize(0)?,
988 len: 1,
989 }),
990
991 Opcode::CREATE | Opcode::CREATE2 => Some(peek_memory_cost(stack, 1, 2)?),
992
993 Opcode::CALL | Opcode::CALLCODE => {
994 Some(peek_memory_cost(stack, 3, 4)?.join(peek_memory_cost(stack, 5, 6)?))
995 }
996
997 Opcode::DELEGATECALL | Opcode::STATICCALL => {
998 Some(peek_memory_cost(stack, 2, 3)?.join(peek_memory_cost(stack, 4, 5)?))
999 }
1000
1001 _ => None,
1002 };
1003
1004 Ok((gas_cost, memory_cost))
1005}
1006
1007fn peek_memory_cost(
1008 stack: &Stack,
1009 offset_index: usize,
1010 len_index: usize,
1011) -> Result<MemoryCost, ExitError> {
1012 let len = stack.peek_usize(len_index)?;
1013
1014 if len == 0 {
1015 return Ok(MemoryCost {
1016 offset: usize::MAX,
1017 len,
1018 });
1019 }
1020
1021 let offset = stack.peek_usize(offset_index)?;
1022 Ok(MemoryCost { offset, len })
1023}
1024
1025#[derive(Clone, Debug)]
1027struct Inner<'config> {
1028 memory_gas: u64,
1029 used_gas: u64,
1030 refunded_gas: i64,
1031 config: &'config Config,
1032 floor_gas: u64,
1033}
1034
1035impl Inner<'_> {
1036 fn memory_gas(&self, memory: MemoryCost) -> Result<u64, ExitError> {
1037 let from = memory.offset;
1038 let len = memory.len;
1039
1040 if len == 0 {
1041 return Ok(self.memory_gas);
1042 }
1043
1044 let end = from.checked_add(len).ok_or(ExitError::OutOfGas)?;
1045
1046 let rem = end % 32;
1047 let new = if rem == 0 { end / 32 } else { end / 32 + 1 };
1048
1049 Ok(max(self.memory_gas, memory::memory_gas(new)?))
1050 }
1051
1052 fn extra_check(&self, cost: GasCost, after_gas: u64) -> Result<(), ExitError> {
1053 match cost {
1054 GasCost::Call { gas, .. }
1055 | GasCost::CallCode { gas, .. }
1056 | GasCost::DelegateCall { gas, .. }
1057 | GasCost::StaticCall { gas, .. } => {
1058 costs::call_extra_check(gas, after_gas, self.config)
1059 }
1060 _ => Ok(()),
1061 }
1062 }
1063
1064 #[allow(clippy::too_many_lines)]
1066 fn gas_cost(&self, cost: GasCost, gas: u64) -> Result<u64, ExitError> {
1067 Ok(match cost {
1068 GasCost::Call {
1069 value,
1070 target_is_cold,
1071 delegated_designator_is_cold,
1072 target_exists,
1073 ..
1074 } => costs::call_cost(
1075 value,
1076 target_is_cold,
1077 delegated_designator_is_cold,
1078 true,
1079 true,
1080 !target_exists,
1081 self.config,
1082 ),
1083 GasCost::CallCode {
1084 value,
1085 target_is_cold,
1086 delegated_designator_is_cold,
1087 target_exists,
1088 ..
1089 } => costs::call_cost(
1090 value,
1091 target_is_cold,
1092 delegated_designator_is_cold,
1093 true,
1094 false,
1095 !target_exists,
1096 self.config,
1097 ),
1098 GasCost::DelegateCall {
1099 target_is_cold,
1100 delegated_designator_is_cold,
1101 target_exists,
1102 ..
1103 } => costs::call_cost(
1104 U256_ZERO,
1105 target_is_cold,
1106 delegated_designator_is_cold,
1107 false,
1108 false,
1109 !target_exists,
1110 self.config,
1111 ),
1112 GasCost::StaticCall {
1113 target_is_cold,
1114 delegated_designator_is_cold,
1115 target_exists,
1116 ..
1117 } => costs::call_cost(
1118 U256_ZERO,
1119 target_is_cold,
1120 delegated_designator_is_cold,
1121 false,
1122 true,
1123 !target_exists,
1124 self.config,
1125 ),
1126
1127 GasCost::Suicide {
1128 value,
1129 target_is_cold,
1130 target_exists,
1131 ..
1132 } => costs::suicide_cost(value, target_is_cold, target_exists, self.config),
1133 GasCost::SStore {
1134 original,
1135 current,
1136 new,
1137 target_is_cold,
1138 } => costs::sstore_cost(original, current, new, gas, target_is_cold, self.config)?,
1139
1140 GasCost::Sha3 { len } => costs::sha3_cost(len)?,
1141 GasCost::Log { n, len } => costs::log_cost(n, len)?,
1142 GasCost::VeryLowCopy { len } => costs::verylowcopy_cost(len)?,
1143 GasCost::Exp { power } => costs::exp_cost(power, self.config)?,
1144 GasCost::Create => u64::from(consts::G_CREATE),
1145 GasCost::Create2 { len } => costs::create2_cost(len)?,
1146 GasCost::SLoad { target_is_cold } => costs::sload_cost(target_is_cold, self.config),
1147
1148 GasCost::Zero => u64::from(consts::G_ZERO),
1149 GasCost::Base => u64::from(consts::G_BASE),
1150 GasCost::VeryLow => u64::from(consts::G_VERYLOW),
1151 GasCost::Low => u64::from(consts::G_LOW),
1152 GasCost::Invalid(opcode) => return Err(ExitError::InvalidCode(opcode)),
1153
1154 GasCost::ExtCodeSize { target_is_cold } => costs::non_delegated_access_cost(
1155 target_is_cold,
1156 self.config.gas_ext_code,
1157 self.config,
1158 ),
1159 GasCost::ExtCodeCopy {
1160 target_is_cold,
1161 len,
1162 } => costs::ext_codecopy_cost(len, target_is_cold, self.config)?,
1163 GasCost::ExtCodeHash { target_is_cold } => costs::non_delegated_access_cost(
1164 target_is_cold,
1165 self.config.gas_ext_code_hash,
1166 self.config,
1167 ),
1168
1169 GasCost::Balance { target_is_cold } => costs::non_delegated_access_cost(
1170 target_is_cold,
1171 self.config.gas_balance,
1172 self.config,
1173 ),
1174 GasCost::BlockHash => u64::from(consts::G_BLOCKHASH),
1175 GasCost::WarmStorageRead => costs::storage_read_warm(self.config),
1176 })
1177 }
1178
1179 fn gas_refund(&self, cost: GasCost) -> i64 {
1180 match cost {
1181 _ if self.config.estimate => 0,
1182
1183 GasCost::SStore {
1184 original,
1185 current,
1186 new,
1187 ..
1188 } => costs::sstore_refund(original, current, new, self.config),
1189 GasCost::Suicide {
1190 already_removed, ..
1191 } if !self.config.decrease_clears_refund => costs::suicide_refund(already_removed),
1192 _ => 0,
1193 }
1194 }
1195}
1196
1197#[derive(Debug, Clone, Copy)]
1199pub enum GasCost {
1200 Zero,
1202 Base,
1204 VeryLow,
1206 Low,
1208 Invalid(Opcode),
1210
1211 ExtCodeSize {
1213 target_is_cold: bool,
1215 },
1216 Balance {
1218 target_is_cold: bool,
1220 },
1221 BlockHash,
1223 ExtCodeHash {
1225 target_is_cold: bool,
1227 },
1228
1229 Call {
1231 value: U256,
1233 gas: U256,
1235 target_is_cold: bool,
1237 delegated_designator_is_cold: Option<bool>,
1239 target_exists: bool,
1241 },
1242 CallCode {
1244 value: U256,
1246 gas: U256,
1248 target_is_cold: bool,
1250 delegated_designator_is_cold: Option<bool>,
1252 target_exists: bool,
1254 },
1255 DelegateCall {
1257 gas: U256,
1259 target_is_cold: bool,
1261 delegated_designator_is_cold: Option<bool>,
1263 target_exists: bool,
1265 },
1266 StaticCall {
1268 gas: U256,
1270 target_is_cold: bool,
1272 delegated_designator_is_cold: Option<bool>,
1274 target_exists: bool,
1276 },
1277 Suicide {
1279 value: U256,
1281 target_is_cold: bool,
1283 target_exists: bool,
1285 already_removed: bool,
1287 },
1288 SStore {
1290 original: H256,
1292 current: H256,
1294 new: H256,
1296 target_is_cold: bool,
1298 },
1299 Sha3 {
1301 len: U256,
1303 },
1304 Log {
1306 n: u8,
1308 len: U256,
1310 },
1311 ExtCodeCopy {
1313 target_is_cold: bool,
1315 len: U256,
1317 },
1318 VeryLowCopy {
1320 len: U256,
1322 },
1323 Exp {
1325 power: U256,
1327 },
1328 Create,
1330 Create2 {
1332 len: U256,
1334 },
1335 SLoad {
1337 target_is_cold: bool,
1339 },
1340 WarmStorageRead,
1341}
1342
1343#[derive(Debug, Clone, Copy)]
1345pub enum StorageTarget {
1346 None,
1348 Address(H160),
1350 Slot(H160, H256),
1352}
1353
1354#[derive(Debug, Clone, Copy)]
1356pub struct MemoryCost {
1357 pub offset: usize,
1359 pub len: usize,
1361}
1362
1363#[derive(Debug, Clone, Copy)]
1365pub enum TransactionCost {
1366 Call {
1368 zero_data_len: usize,
1370 non_zero_data_len: usize,
1372 access_list_address_len: usize,
1374 access_list_storage_len: usize,
1376 authorization_list_len: usize,
1378 },
1379 Create {
1381 zero_data_len: usize,
1383 non_zero_data_len: usize,
1385 access_list_address_len: usize,
1387 access_list_storage_len: usize,
1389 initcode_cost: u64,
1391 },
1392}
1393
1394impl MemoryCost {
1395 #[must_use]
1397 pub const fn join(self, other: Self) -> Self {
1398 if self.len == 0 {
1399 return other;
1400 }
1401
1402 if other.len == 0 {
1403 return self;
1404 }
1405
1406 let self_end = self.offset.saturating_add(self.len);
1407 let other_end = other.offset.saturating_add(other.len);
1408
1409 if self_end >= other_end {
1410 self
1411 } else {
1412 other
1413 }
1414 }
1415}