cfx_vm_interpreter/interpreter/
gasometer.rs

1// Copyright 2015-2018 Parity Technologies (UK) Ltd.
2// This file is part of Parity.
3
4// Parity is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Parity is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Parity.  If not, see <http://www.gnu.org/licenses/>.
16
17// Copyright 2019 Conflux Foundation. All rights reserved.
18// Conflux is free software and distributed under GNU General Public License.
19// See http://www.gnu.org/licenses/
20
21use 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    /// How much gas is provided to a CALL/CREATE, given that we need to deduct
79    /// `needed` for this operation and that we `requested` some.
80    pub fn gas_provided(
81        &self, spec: &Spec, needed: Gas, requested: Option<U256>,
82    ) -> vm::Result<Gas> {
83        // Try converting requested gas to `Gas` (`U256/u64`)
84        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    /// Determine how much gas is used by the given instruction, given the
115    /// machine's state.
116    ///
117    /// We guarantee that the final element of the returned tuple (`provided`)
118    /// will be `Some` iff the `instruction` is one of `CREATE`, or any of
119    /// the `CALL` variants. In this case, it will be the amount of gas
120    /// that the current context
121    /// provides to the child context.
122    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                    // CREATE operation in espace doesn't compute code_hash
346                    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            // s * memory_gas + s * s / quad_coeff_div
466            let a = overflowing!(s.overflow_mul(Gas::from(spec.memory_gas)));
467
468            // Calculate s*s/quad_coeff_div
469            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        // The only simple case without checking values
526        return Ok((spec.sstore_reset_gas, 0));
527    }
528
529    if current_gas <= spec.call_stipend.into() {
530        // Enough to trigger the OutOfGas, no need for further checks
531        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        // For eSpace only, the core space before cip645 has been filtted out.
543        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    // CIP-645(d, f): EIP-2200 + EIP-2929
556    let charge_gas = if is_noop {
557        // no storage op, just load cost for checking
558        spec.warm_access_gas
559    } else if is_clean && ori_val.is_zero() && space == Space::Ethereum {
560        // charge storage write gas + storage occupation gas
561        spec.sstore_set_gas * spec.evm_gas_ratio
562    } else if is_clean {
563        spec.sstore_reset_gas
564    } else {
565        // other operations has paid for storage write cost
566        spec.warm_access_gas
567    };
568
569    // CIP-645f: EIP-2929
570    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        // CIP-645g (EIP-3529) updates the value defined in CIP-645d (EIP-2200)
574        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                // First time release this entry
579                sstore_clears_schedule
580            }
581            (false, true, false) => {
582                // Used to release but occupy again, undo refund
583                -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            // charge storage write gas + storage occupation gas
594            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        // Enough to trigger the out-of-gas
625        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    // given
659    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    // when
665    let result = gasometer.mem_gas_cost(&spec, current_mem_size, &mem_size);
666
667    // then
668    if result.is_ok() {
669        assert!(false, "Should fail with OutOfGas");
670    }
671}
672
673#[test]
674fn test_calculate_mem_cost() {
675    // given
676    let gasometer = Gasometer::<usize>::new(0);
677    let spec = Spec::default();
678    let current_mem_size = 0;
679    let mem_size = 5;
680
681    // when
682    let (mem_cost, new_mem_gas, mem_size) = gasometer
683        .mem_gas_cost(&spec, current_mem_size, &mem_size)
684        .unwrap();
685
686    // then
687    assert_eq!(mem_cost, 3);
688    assert_eq!(new_mem_gas, 3);
689    assert_eq!(mem_size, 32);
690}