1use cfx_types::{Address, Space, H256, U256};
22use cfx_vm_types::{self as vm, Spec};
23use std::{cmp, sync::Arc};
24use vm::{BlockHashSource, CODE_PREFIX_7702};
25
26use super::{
27 instructions::{self, Instruction, InstructionInfo},
28 stack::Stack,
29 u256_to_address,
30};
31use crate::CostType;
32
33macro_rules! overflowing {
34 ($x:expr) => {{
35 let (v, overflow) = $x;
36 if overflow {
37 return Err(vm::Error::OutOfGas);
38 }
39 v
40 }};
41}
42
43enum Request<Cost: CostType> {
44 Gas(Cost),
45 GasMem(Cost, Cost),
46 GasMemProvide(Cost, Cost, Option<U256>),
47 GasMemCopy(Cost, Cost, Cost),
48}
49
50pub struct InstructionRequirements<Cost> {
51 pub gas_cost: Cost,
52 pub provide_gas: Option<Cost>,
53 pub memory_total_gas: Cost,
54 pub memory_required_size: usize,
55 pub gas_refund: i64,
56}
57
58pub struct Gasometer<Gas> {
59 pub current_gas: Gas,
60 pub current_mem_gas: Gas,
61}
62
63impl<Gas: CostType> Gasometer<Gas> {
64 pub fn new(current_gas: Gas) -> Self {
65 Gasometer {
66 current_gas,
67 current_mem_gas: Gas::from(0),
68 }
69 }
70
71 pub fn verify_gas(&self, gas_cost: &Gas) -> vm::Result<()> {
72 match &self.current_gas < gas_cost {
73 true => Err(vm::Error::OutOfGas),
74 false => Ok(()),
75 }
76 }
77
78 pub fn gas_provided(
81 &self, spec: &Spec, needed: Gas, requested: Option<U256>,
82 ) -> vm::Result<Gas> {
83 let requested = requested.map(Gas::from_u256);
85
86 match spec.sub_gas_cap_divisor {
87 Some(cap_divisor) if self.current_gas >= needed => {
88 let gas_remaining = self.current_gas - needed;
89 let max_gas_provided = match cap_divisor {
90 64 => gas_remaining - (gas_remaining >> 6),
91 cap_divisor => {
92 gas_remaining - gas_remaining / Gas::from(cap_divisor)
93 }
94 };
95
96 if let Some(Ok(r)) = requested {
97 Ok(cmp::min(r, max_gas_provided))
98 } else {
99 Ok(max_gas_provided)
100 }
101 }
102 _ => {
103 if let Some(r) = requested {
104 r
105 } else if self.current_gas >= needed {
106 Ok(self.current_gas - needed)
107 } else {
108 Ok(0.into())
109 }
110 }
111 }
112 }
113
114 pub fn requirements(
123 &mut self, context: &dyn vm::Context, instruction: Instruction,
124 info: &InstructionInfo, stack: &dyn Stack<U256>,
125 current_mem_size: usize,
126 ) -> vm::Result<InstructionRequirements<Gas>> {
127 let spec = context.spec();
128 let tier = info.tier.idx();
129 let default_gas = Gas::from(spec.tier_step_gas[tier]);
130
131 let account_access_gas = |idx: usize| {
132 let address = u256_to_address(stack.peek(idx));
133 if context.is_warm_account(address) {
134 spec.warm_access_gas
135 } else {
136 spec.cold_account_access_cost
137 }
138 };
139
140 let mut gas_refund = 0;
141
142 let cost = match instruction {
143 instructions::JUMPDEST => Request::Gas(Gas::from(1)),
144 instructions::SSTORE => {
145 let (to_charge_gas, to_refund_gas) =
146 calc_sstore_gas(context, stack, self.current_gas)?;
147 gas_refund += to_refund_gas;
148 Request::Gas(Gas::from(to_charge_gas))
149 }
150 instructions::SLOAD => {
151 let gas = if spec.cip645.eip_cold_warm_access {
152 let mut key = H256::zero();
153 stack.peek(0).to_big_endian(&mut key.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 mut key = H256::zero();
535 stack.peek(0).to_big_endian(&mut key.0);
536
537 let new_val = *stack.peek(1);
538 let warm_val = context.is_warm_storage_entry(&key)?;
539 let cur_val = context.storage_at(&key[..])?;
540
541 if !spec.cip645.eip_sstore_and_refund_gas {
542 return Ok(if cur_val.is_zero() && !new_val.is_zero() {
544 (spec.sstore_set_gas * spec.evm_gas_ratio, 0)
545 } else {
546 (spec.sstore_reset_gas, 0)
547 });
548 }
549
550 let ori_val = context.origin_storage_at(&key[..])?.unwrap();
551
552 let is_noop = new_val == cur_val;
553 let is_clean = ori_val == cur_val;
554
555 let charge_gas = if is_noop {
557 spec.warm_access_gas
559 } else if is_clean && ori_val.is_zero() && space == Space::Ethereum {
560 spec.sstore_set_gas * spec.evm_gas_ratio
562 } else if is_clean {
563 spec.sstore_reset_gas
564 } else {
565 spec.warm_access_gas
567 };
568
569 let cold_warm_gas = if warm_val { 0 } else { spec.cold_sload_gas };
571
572 let sstore_clear_refund_gas = if space == Space::Ethereum && !is_noop {
573 let sstore_clears_schedule =
575 (spec.sstore_reset_gas + spec.access_list_storage_key_gas) as i64;
576 match (ori_val.is_zero(), cur_val.is_zero(), new_val.is_zero()) {
577 (false, false, true) => {
578 sstore_clears_schedule
580 }
581 (false, true, false) => {
582 -sstore_clears_schedule
584 }
585 _ => 0,
586 }
587 } else {
588 0
589 };
590
591 let not_write_db_refund_gas = if ori_val == new_val && !is_clean {
592 if ori_val.is_zero() && space == Space::Ethereum {
593 spec.sstore_set_gas * spec.evm_gas_ratio - spec.warm_access_gas
595 } else {
596 spec.sstore_reset_gas - spec.warm_access_gas
597 }
598 } else {
599 0
600 };
601
602 Ok((
603 charge_gas + cold_warm_gas,
604 sstore_clear_refund_gas + not_write_db_refund_gas as i64,
605 ))
606}
607
608fn calc_call_gas<Gas: CostType>(
609 context: &dyn vm::Context, stack: &dyn Stack<U256>, current_gas: Gas,
610) -> vm::Result<usize> {
611 let spec = context.spec();
612 if !spec.cip645.eip_cold_warm_access {
613 return Ok(spec.call_gas);
614 }
615
616 let address = u256_to_address(stack.peek(1));
617 let call_gas = if context.is_warm_account(address) {
618 spec.warm_access_gas
619 } else {
620 spec.cold_account_access_cost
621 };
622
623 if current_gas < call_gas.into() {
624 return Ok(call_gas);
626 }
627
628 let Some(delegated_address) = delegated_address(context.extcode(&address)?)
629 else {
630 return Ok(call_gas);
631 };
632
633 Ok(call_gas
634 + if context.is_warm_account(delegated_address) {
635 spec.warm_access_gas
636 } else {
637 spec.cold_account_access_cost
638 })
639}
640
641fn delegated_address(extcode: Option<Arc<Vec<u8>>>) -> Option<Address> {
642 let code = extcode?;
643 if !code.starts_with(CODE_PREFIX_7702) {
644 return None;
645 }
646
647 let (_prefix, payload) = code.split_at(CODE_PREFIX_7702.len());
648
649 if payload.len() == Address::len_bytes() {
650 Some(Address::from_slice(payload))
651 } else {
652 None
653 }
654}
655
656#[test]
657fn test_mem_gas_cost() {
658 let gasometer = Gasometer::<U256>::new(U256::zero());
660 let spec = Spec::default();
661 let current_mem_size = 5;
662 let mem_size = !U256::zero();
663
664 let result = gasometer.mem_gas_cost(&spec, current_mem_size, &mem_size);
666
667 if result.is_ok() {
669 assert!(false, "Should fail with OutOfGas");
670 }
671}
672
673#[test]
674fn test_calculate_mem_cost() {
675 let gasometer = Gasometer::<usize>::new(0);
677 let spec = Spec::default();
678 let current_mem_size = 0;
679 let mem_size = 5;
680
681 let (mem_cost, new_mem_gas, mem_size) = gasometer
683 .mem_gas_cost(&spec, current_mem_size, &mem_size)
684 .unwrap();
685
686 assert_eq!(mem_cost, 3);
688 assert_eq!(new_mem_gas, 3);
689 assert_eq!(mem_size, 32);
690}