1use cfx_types::{u256_to_h256_be, Space, U256};
22use cfx_vm_types::{self as vm, Spec};
23use primitives::extract_7702_payload;
24use std::cmp;
25use vm::BlockHashSource;
26
27use super::{
28 instructions::{self, Instruction, InstructionInfo},
29 stack::Stack,
30 u256_to_address,
31};
32use crate::CostType;
33
34macro_rules! overflowing {
35 ($x:expr) => {{
36 let (v, overflow) = $x;
37 if overflow {
38 return Err(vm::Error::OutOfGas);
39 }
40 v
41 }};
42}
43
44enum Request<Cost: CostType> {
45 Gas(Cost),
46 GasMem(Cost, Cost),
47 GasMemProvide(Cost, Cost, Option<U256>),
48 GasMemCopy(Cost, Cost, Cost),
49}
50
51pub struct InstructionRequirements<Cost> {
52 pub gas_cost: Cost,
53 pub provide_gas: Option<Cost>,
54 pub memory_total_gas: Cost,
55 pub memory_required_size: usize,
56 pub gas_refund: i64,
57}
58
59pub struct Gasometer<Gas> {
60 pub current_gas: Gas,
61 pub current_mem_gas: Gas,
62}
63
64impl<Gas: CostType> Gasometer<Gas> {
65 pub fn new(current_gas: Gas) -> Self {
66 Gasometer {
67 current_gas,
68 current_mem_gas: Gas::from(0),
69 }
70 }
71
72 pub fn verify_gas(&self, gas_cost: &Gas) -> vm::Result<()> {
73 match &self.current_gas < gas_cost {
74 true => Err(vm::Error::OutOfGas),
75 false => Ok(()),
76 }
77 }
78
79 pub fn gas_provided(
82 &self, spec: &Spec, needed: Gas, requested: Option<U256>,
83 ) -> vm::Result<Gas> {
84 let requested = requested.map(Gas::from_u256);
86
87 match spec.sub_gas_cap_divisor {
88 Some(cap_divisor) if self.current_gas >= needed => {
89 let gas_remaining = self.current_gas - needed;
90 let max_gas_provided = match cap_divisor {
91 64 => gas_remaining - (gas_remaining >> 6),
92 cap_divisor => {
93 gas_remaining - gas_remaining / Gas::from(cap_divisor)
94 }
95 };
96
97 if let Some(Ok(r)) = requested {
98 Ok(cmp::min(r, max_gas_provided))
99 } else {
100 Ok(max_gas_provided)
101 }
102 }
103 _ => {
104 if let Some(r) = requested {
105 r
106 } else if self.current_gas >= needed {
107 Ok(self.current_gas - needed)
108 } else {
109 Ok(0.into())
110 }
111 }
112 }
113 }
114
115 pub fn requirements(
124 &mut self, context: &dyn vm::Context, instruction: Instruction,
125 info: &InstructionInfo, stack: &dyn Stack<U256>,
126 current_mem_size: usize,
127 ) -> vm::Result<InstructionRequirements<Gas>> {
128 let spec = context.spec();
129 let tier = info.tier.idx();
130 let default_gas = Gas::from(spec.tier_step_gas[tier]);
131
132 let account_access_gas = |idx: usize| {
133 let address = u256_to_address(stack.peek(idx));
134 if context.is_warm_account(address) {
135 spec.warm_access_gas
136 } else {
137 spec.cold_account_access_cost
138 }
139 };
140
141 let mut gas_refund = 0;
142
143 let cost = match instruction {
144 instructions::JUMPDEST => Request::Gas(Gas::from(1)),
145 instructions::SSTORE => {
146 let (to_charge_gas, to_refund_gas) =
147 calc_sstore_gas(context, stack, self.current_gas)?;
148 gas_refund += to_refund_gas;
149 Request::Gas(Gas::from(to_charge_gas))
150 }
151 instructions::SLOAD => {
152 let gas = if spec.cip645.eip_cold_warm_access {
153 let key = u256_to_h256_be(*stack.peek(0));
154 if context.is_warm_storage_entry(&key)? {
155 spec.warm_access_gas
156 } else {
157 spec.cold_sload_gas
158 }
159 } else {
160 spec.sload_gas()
161 };
162 Request::Gas(Gas::from(gas))
163 }
164 instructions::BALANCE => {
165 let gas = if spec.cip645.eip_cold_warm_access {
166 account_access_gas(0)
167 } else {
168 spec.balance_gas
169 };
170 Request::Gas(Gas::from(gas))
171 }
172 instructions::EXTCODESIZE => {
173 let gas = if spec.cip645.eip_cold_warm_access {
174 account_access_gas(0)
175 } else {
176 spec.extcodesize_gas
177 };
178 Request::Gas(Gas::from(gas))
179 }
180 instructions::EXTCODEHASH => {
181 let gas = if spec.cip645.eip_cold_warm_access {
182 account_access_gas(0)
183 } else {
184 spec.extcodehash_gas
185 };
186 Request::Gas(Gas::from(gas))
187 }
188 instructions::SUICIDE => {
189 let mut gas = Gas::from(spec.suicide_gas);
190
191 let is_value_transfer = !context.origin_balance()?.is_zero();
192 let address = u256_to_address(stack.peek(0));
193 if spec.cip645.eip_cold_warm_access
194 && !context.is_warm_account(address)
195 {
196 gas += Gas::from(spec.cold_account_access_cost);
197 }
198 if is_value_transfer
199 && !context.exists_and_not_null(&address)?
200 {
201 let ratio = if context.space() == Space::Ethereum {
202 spec.evm_gas_ratio
203 } else {
204 1
205 };
206 gas = overflowing!(gas.overflow_add(
207 (spec.suicide_to_new_account_cost * ratio).into()
208 ));
209 }
210
211 Request::Gas(gas)
212 }
213 instructions::MSTORE | instructions::MLOAD => Request::GasMem(
214 default_gas,
215 mem_needed_const(stack.peek(0), 32)?,
216 ),
217 instructions::MSTORE8 => Request::GasMem(
218 default_gas,
219 mem_needed_const(stack.peek(0), 1)?,
220 ),
221 instructions::RETURN | instructions::REVERT => Request::GasMem(
222 default_gas,
223 mem_needed(stack.peek(0), stack.peek(1))?,
224 ),
225 instructions::SHA3 => {
226 let words =
227 overflowing!(to_word_size(Gas::from_u256(*stack.peek(1))?));
228 let gas = overflowing!(Gas::from(spec.sha3_gas).overflow_add(
229 overflowing!(
230 Gas::from(spec.sha3_word_gas).overflow_mul(words)
231 )
232 ));
233 Request::GasMem(gas, mem_needed(stack.peek(0), stack.peek(1))?)
234 }
235 instructions::CALLDATACOPY
236 | instructions::CODECOPY
237 | instructions::RETURNDATACOPY => Request::GasMemCopy(
238 default_gas,
239 mem_needed(stack.peek(0), stack.peek(2))?,
240 Gas::from_u256(*stack.peek(2))?,
241 ),
242 instructions::JUMPSUB_MCOPY if spec.cancun_opcodes => {
243 let dst_mem_needed = mem_needed(stack.peek(0), stack.peek(2))?;
244 let src_mem_needed = mem_needed(stack.peek(1), stack.peek(2))?;
245 let copy_mem_needed = if spec.cip645.fix_eip5656 {
246 std::cmp::max(dst_mem_needed, src_mem_needed)
247 } else {
248 dst_mem_needed
249 };
250 Request::GasMemCopy(
251 default_gas,
252 copy_mem_needed,
253 Gas::from_u256(*stack.peek(2))?,
254 )
255 }
256 instructions::BEGINSUB_TLOAD if spec.cancun_opcodes => {
257 Request::Gas(Gas::from(spec.warm_access_gas))
258 }
259 instructions::RETURNSUB_TSTORE if spec.cancun_opcodes => {
260 Request::Gas(Gas::from(spec.warm_access_gas))
261 }
262 instructions::EXTCODECOPY => {
263 let base_gas = if spec.cip645.eip_cold_warm_access {
264 account_access_gas(0)
265 } else {
266 spec.extcodecopy_base_gas
267 };
268 Request::GasMemCopy(
269 base_gas.into(),
270 mem_needed(stack.peek(1), stack.peek(3))?,
271 Gas::from_u256(*stack.peek(3))?,
272 )
273 }
274 instructions::LOG0
275 | instructions::LOG1
276 | instructions::LOG2
277 | instructions::LOG3
278 | instructions::LOG4 => {
279 let no_of_topics = instruction.log_topics().expect(
280 "log_topics always return some for LOG* instructions; qed",
281 );
282 let log_gas = spec.log_gas + spec.log_topic_gas * no_of_topics;
283
284 let data_gas = overflowing!(Gas::from_u256(*stack.peek(1))?
285 .overflow_mul(Gas::from(spec.log_data_gas)));
286 let gas =
287 overflowing!(data_gas.overflow_add(Gas::from(log_gas)));
288 Request::GasMem(gas, mem_needed(stack.peek(0), stack.peek(1))?)
289 }
290 instructions::CALL | instructions::CALLCODE => {
291 let mut gas =
292 Gas::from(calc_call_gas(context, stack, self.current_gas)?);
293 let mem = cmp::max(
294 mem_needed(stack.peek(5), stack.peek(6))?,
295 mem_needed(stack.peek(3), stack.peek(4))?,
296 );
297
298 let address = u256_to_address(stack.peek(1));
299 let is_value_transfer = !stack.peek(2).is_zero();
300
301 if instruction == instructions::CALL
302 && is_value_transfer
303 && !context.exists_and_not_null(&address)?
304 {
305 let ratio = if context.space() == Space::Ethereum {
306 spec.evm_gas_ratio
307 } else {
308 1
309 };
310 gas = overflowing!(gas.overflow_add(
311 (spec.call_new_account_gas * ratio).into()
312 ));
313 }
314
315 if is_value_transfer {
316 gas =
317 overflowing!(gas
318 .overflow_add(spec.call_value_transfer_gas.into()));
319 }
320
321 let requested = *stack.peek(0);
322
323 Request::GasMemProvide(gas, mem, Some(requested))
324 }
325 instructions::DELEGATECALL | instructions::STATICCALL => {
326 let gas =
327 Gas::from(calc_call_gas(context, stack, self.current_gas)?);
328 let mem = cmp::max(
329 mem_needed(stack.peek(4), stack.peek(5))?,
330 mem_needed(stack.peek(2), stack.peek(3))?,
331 );
332 let requested = *stack.peek(0);
333
334 Request::GasMemProvide(gas, mem, Some(requested))
335 }
336 instructions::CREATE | instructions::CREATE2 => {
337 let start = stack.peek(1);
338 let len = stack.peek(2);
339 let base = Gas::from(spec.create_gas);
340 let word = overflowing!(to_word_size(Gas::from_u256(*len)?));
341
342 let sha3_word_price = if instruction == instructions::CREATE
343 && context.space() == Space::Ethereum
344 {
345 0
347 } else {
348 spec.sha3_word_gas
349 };
350 let init_code_word_price = if spec.cip645.eip3860 {
351 spec.init_code_word_gas
352 } else {
353 0
354 };
355 let word_price = sha3_word_price + init_code_word_price;
356 let word_gas =
357 overflowing!(Gas::from(word_price).overflow_mul(word));
358
359 let gas = overflowing!(base.overflow_add(word_gas));
360 let mem = mem_needed(start, len)?;
361
362 Request::GasMemProvide(gas, mem, None)
363 }
364 instructions::EXP => {
365 let expon = stack.peek(1);
366 let bytes = ((expon.bits() + 7) / 8) as usize;
367 let gas = Gas::from(spec.exp_gas + spec.exp_byte_gas * bytes);
368 Request::Gas(gas)
369 }
370 instructions::BLOCKHASH => {
371 let block_number = stack.peek(0);
372 let gas = if context.space() == Space::Ethereum
373 && spec.align_evm
374 {
375 spec.blockhash_gas
376 } else if !spec.cip645.blockhash_gas {
377 match context.blockhash_source() {
378 BlockHashSource::Env => spec.blockhash_gas,
379 BlockHashSource::State => spec.sload_gas(),
380 }
381 } else if block_number > &U256::from(u64::MAX) {
382 spec.warm_access_gas
383 } else {
384 let block_number = block_number.as_u64();
385 let env = context.env();
386
387 let diff = match context.space() {
388 Space::Native => env.number.checked_sub(block_number),
389 Space::Ethereum => {
390 env.epoch_height.checked_sub(block_number)
391 }
392 };
393 if diff.map_or(false, |x| x > 256 && x < 65536) {
394 spec.cold_sload_gas
395 } else {
396 spec.warm_access_gas
397 }
398 };
399
400 Request::Gas(Gas::from(gas))
401 }
402 _ => Request::Gas(default_gas),
403 };
404
405 Ok(match cost {
406 Request::Gas(gas) => InstructionRequirements {
407 gas_cost: gas,
408 provide_gas: None,
409 memory_required_size: 0,
410 memory_total_gas: self.current_mem_gas,
411 gas_refund,
412 },
413 Request::GasMem(gas, mem_size) => {
414 let (mem_gas_cost, new_mem_gas, new_mem_size) =
415 self.mem_gas_cost(spec, current_mem_size, &mem_size)?;
416 let gas = overflowing!(gas.overflow_add(mem_gas_cost));
417 InstructionRequirements {
418 gas_cost: gas,
419 provide_gas: None,
420 memory_required_size: new_mem_size,
421 memory_total_gas: new_mem_gas,
422 gas_refund,
423 }
424 }
425 Request::GasMemProvide(gas, mem_size, requested) => {
426 let (mem_gas_cost, new_mem_gas, new_mem_size) =
427 self.mem_gas_cost(spec, current_mem_size, &mem_size)?;
428 let gas = overflowing!(gas.overflow_add(mem_gas_cost));
429 let provided = self.gas_provided(spec, gas, requested)?;
430 let total_gas = overflowing!(gas.overflow_add(provided));
431
432 InstructionRequirements {
433 gas_cost: total_gas,
434 provide_gas: Some(provided),
435 memory_required_size: new_mem_size,
436 memory_total_gas: new_mem_gas,
437 gas_refund,
438 }
439 }
440 Request::GasMemCopy(gas, mem_size, copy) => {
441 let (mem_gas_cost, new_mem_gas, new_mem_size) =
442 self.mem_gas_cost(spec, current_mem_size, &mem_size)?;
443 let copy = overflowing!(to_word_size(copy));
444 let copy_gas =
445 overflowing!(Gas::from(spec.copy_gas).overflow_mul(copy));
446 let gas = overflowing!(gas.overflow_add(copy_gas));
447 let gas = overflowing!(gas.overflow_add(mem_gas_cost));
448
449 InstructionRequirements {
450 gas_cost: gas,
451 provide_gas: None,
452 memory_required_size: new_mem_size,
453 memory_total_gas: new_mem_gas,
454 gas_refund,
455 }
456 }
457 })
458 }
459
460 fn mem_gas_cost(
461 &self, spec: &Spec, current_mem_size: usize, mem_size: &Gas,
462 ) -> vm::Result<(Gas, Gas, usize)> {
463 let gas_for_mem = |mem_size: Gas| {
464 let s = mem_size >> 5;
465 let a = overflowing!(s.overflow_mul(Gas::from(spec.memory_gas)));
467
468 assert_eq!(spec.quad_coeff_div, 512);
470 let b = overflowing!(s.overflow_mul_shr(s, 9));
471 Ok(overflowing!(a.overflow_add(b)))
472 };
473
474 let current_mem_size = Gas::from(current_mem_size);
475 let req_mem_size_rounded = overflowing!(to_word_size(*mem_size)) << 5;
476
477 let (mem_gas_cost, new_mem_gas) =
478 if req_mem_size_rounded > current_mem_size {
479 let new_mem_gas = gas_for_mem(req_mem_size_rounded)?;
480 (new_mem_gas - self.current_mem_gas, new_mem_gas)
481 } else {
482 (Gas::from(0), self.current_mem_gas)
483 };
484
485 Ok((mem_gas_cost, new_mem_gas, req_mem_size_rounded.as_usize()))
486 }
487}
488
489#[inline]
490fn mem_needed_const<Gas: CostType>(mem: &U256, add: usize) -> vm::Result<Gas> {
491 Gas::from_u256(overflowing!(mem.overflowing_add(U256::from(add))))
492}
493
494#[inline]
495fn mem_needed<Gas: CostType>(offset: &U256, size: &U256) -> vm::Result<Gas> {
496 if size.is_zero() {
497 return Ok(Gas::from(0));
498 }
499
500 Gas::from_u256(overflowing!(offset.overflowing_add(*size)))
501}
502
503#[inline]
504fn add_gas_usize<Gas: CostType>(value: Gas, num: usize) -> (Gas, bool) {
505 value.overflow_add(Gas::from(num))
506}
507
508#[inline]
509fn to_word_size<Gas: CostType>(value: Gas) -> (Gas, bool) {
510 let (gas, overflow) = add_gas_usize(value, 31);
511 if overflow {
512 return (gas, overflow);
513 }
514
515 (gas >> 5, false)
516}
517
518fn calc_sstore_gas<Gas: CostType>(
519 context: &dyn vm::Context, stack: &dyn Stack<U256>, current_gas: Gas,
520) -> vm::Result<(usize, i64)> {
521 let spec = context.spec();
522 let space = context.space();
523
524 if space == Space::Native && !spec.cip645.eip_sstore_and_refund_gas {
525 return Ok((spec.sstore_reset_gas, 0));
527 }
528
529 if current_gas <= spec.call_stipend.into() {
530 return Ok((spec.call_stipend + 1, 0));
532 }
533
534 let key = u256_to_h256_be(*stack.peek(0));
535
536 let new_val = *stack.peek(1);
537 let warm_val = context.is_warm_storage_entry(&key)?;
538 let cur_val = context.storage_at(&key[..])?;
539
540 if !spec.cip645.eip_sstore_and_refund_gas {
541 return Ok(if cur_val.is_zero() && !new_val.is_zero() {
543 (spec.sstore_set_gas * spec.evm_gas_ratio, 0)
544 } else {
545 (spec.sstore_reset_gas, 0)
546 });
547 }
548
549 let ori_val = context.origin_storage_at(&key[..])?.unwrap();
550
551 let is_noop = new_val == cur_val;
552 let is_clean = ori_val == cur_val;
553
554 let charge_gas = if is_noop {
556 spec.warm_access_gas
558 } else if is_clean && ori_val.is_zero() && space == Space::Ethereum {
559 spec.sstore_set_gas * spec.evm_gas_ratio
561 } else if is_clean {
562 spec.sstore_reset_gas
563 } else {
564 spec.warm_access_gas
566 };
567
568 let cold_warm_gas = if warm_val { 0 } else { spec.cold_sload_gas };
570
571 let sstore_clear_refund_gas = if space == Space::Ethereum && !is_noop {
572 let sstore_clears_schedule =
574 (spec.sstore_reset_gas + spec.access_list_storage_key_gas) as i64;
575 match (ori_val.is_zero(), cur_val.is_zero(), new_val.is_zero()) {
576 (false, false, true) => {
577 sstore_clears_schedule
579 }
580 (false, true, false) => {
581 -sstore_clears_schedule
583 }
584 _ => 0,
585 }
586 } else {
587 0
588 };
589
590 let not_write_db_refund_gas = if ori_val == new_val && !is_clean {
591 if ori_val.is_zero() && space == Space::Ethereum {
592 spec.sstore_set_gas * spec.evm_gas_ratio - spec.warm_access_gas
594 } else {
595 spec.sstore_reset_gas - spec.warm_access_gas
596 }
597 } else {
598 0
599 };
600
601 Ok((
602 charge_gas + cold_warm_gas,
603 sstore_clear_refund_gas + not_write_db_refund_gas as i64,
604 ))
605}
606
607fn calc_call_gas<Gas: CostType>(
608 context: &dyn vm::Context, stack: &dyn Stack<U256>, current_gas: Gas,
609) -> vm::Result<usize> {
610 let spec = context.spec();
611 if !spec.cip645.eip_cold_warm_access {
612 return Ok(spec.call_gas);
613 }
614
615 let address = u256_to_address(stack.peek(1));
616 let call_gas = if context.is_warm_account(address) {
617 spec.warm_access_gas
618 } else {
619 spec.cold_account_access_cost
620 };
621
622 if current_gas < call_gas.into() {
623 return Ok(call_gas);
625 }
626
627 let maybe_code = context.extcode(&address)?;
628 let Some(delegated_address) =
629 maybe_code.and_then(|code| extract_7702_payload(&code))
630 else {
631 return Ok(call_gas);
632 };
633
634 Ok(call_gas
635 + if context.is_warm_account(delegated_address) {
636 spec.warm_access_gas
637 } else {
638 spec.cold_account_access_cost
639 })
640}
641
642#[test]
643fn test_mem_gas_cost() {
644 let gasometer = Gasometer::<U256>::new(U256::zero());
646 let spec = Spec::default();
647 let current_mem_size = 5;
648 let mem_size = !U256::zero();
649
650 let result = gasometer.mem_gas_cost(&spec, current_mem_size, &mem_size);
652
653 if result.is_ok() {
655 assert!(false, "Should fail with OutOfGas");
656 }
657}
658
659#[test]
660fn test_calculate_mem_cost() {
661 let gasometer = Gasometer::<usize>::new(0);
663 let spec = Spec::default();
664 let current_mem_size = 0;
665 let mem_size = 5;
666
667 let (mem_cost, new_mem_gas, mem_size) = gasometer
669 .mem_gas_cost(&spec, current_mem_size, &mem_size)
670 .unwrap();
671
672 assert_eq!(mem_cost, 3);
674 assert_eq!(new_mem_gas, 3);
675 assert_eq!(mem_size, 32);
676}