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::{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    /// How much gas is provided to a CALL/CREATE, given that we need to deduct
80    /// `needed` for this operation and that we `requested` some.
81    pub fn gas_provided(
82        &self, spec: &Spec, needed: Gas, requested: Option<U256>,
83    ) -> vm::Result<Gas> {
84        // Try converting requested gas to `Gas` (`U256/u64`)
85        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    /// Determine how much gas is used by the given instruction, given the
116    /// machine's state.
117    ///
118    /// We guarantee that the final element of the returned tuple (`provided`)
119    /// will be `Some` iff the `instruction` is one of `CREATE`, or any of
120    /// the `CALL` variants. In this case, it will be the amount of gas
121    /// that the current context
122    /// provides to the child context.
123    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                    // 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 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        // For eSpace only, the core space before cip645 has been filtted out.
542        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    // CIP-645(d, f): EIP-2200 + EIP-2929
555    let charge_gas = if is_noop {
556        // no storage op, just load cost for checking
557        spec.warm_access_gas
558    } else if is_clean && ori_val.is_zero() && space == Space::Ethereum {
559        // charge storage write gas + storage occupation gas
560        spec.sstore_set_gas * spec.evm_gas_ratio
561    } else if is_clean {
562        spec.sstore_reset_gas
563    } else {
564        // other operations has paid for storage write cost
565        spec.warm_access_gas
566    };
567
568    // CIP-645f: EIP-2929
569    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        // CIP-645g (EIP-3529) updates the value defined in CIP-645d (EIP-2200)
573        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                // First time release this entry
578                sstore_clears_schedule
579            }
580            (false, true, false) => {
581                // Used to release but occupy again, undo refund
582                -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            // charge storage write gas + storage occupation gas
593            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        // Enough to trigger the out-of-gas
624        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    // given
645    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    // when
651    let result = gasometer.mem_gas_cost(&spec, current_mem_size, &mem_size);
652
653    // then
654    if result.is_ok() {
655        assert!(false, "Should fail with OutOfGas");
656    }
657}
658
659#[test]
660fn test_calculate_mem_cost() {
661    // given
662    let gasometer = Gasometer::<usize>::new(0);
663    let spec = Spec::default();
664    let current_mem_size = 0;
665    let mem_size = 5;
666
667    // when
668    let (mem_cost, new_mem_gas, mem_size) = gasometer
669        .mem_gas_cost(&spec, current_mem_size, &mem_size)
670        .unwrap();
671
672    // then
673    assert_eq!(mem_cost, 3);
674    assert_eq!(new_mem_gas, 3);
675    assert_eq!(mem_size, 32);
676}