From 0e63e66196fba06aa496a5249a0c9d997a549829 Mon Sep 17 00:00:00 2001 From: Hamy Ratoanina Date: Wed, 15 Nov 2023 11:20:14 -0500 Subject: [PATCH] Implement out of gas exception (#1328) * Implement out of gas exception * Use gas constants in gas_cost_for_opcode * Remove comment --- evm/src/cpu/kernel/asm/core/exception.asm | 134 +++++++++++++++++++++- evm/src/cpu/kernel/assembler.rs | 33 +++++- evm/src/cpu/kernel/ast.rs | 9 +- evm/src/cpu/kernel/evm_asm.pest | 3 +- evm/src/cpu/kernel/parser.rs | 14 ++- evm/src/witness/transition.rs | 18 +++ 6 files changed, 202 insertions(+), 9 deletions(-) diff --git a/evm/src/cpu/kernel/asm/core/exception.asm b/evm/src/cpu/kernel/asm/core/exception.asm index 0f86b9ae..ce996579 100644 --- a/evm/src/cpu/kernel/asm/core/exception.asm +++ b/evm/src/cpu/kernel/asm/core/exception.asm @@ -24,8 +24,31 @@ global exception_jumptable: global exc_out_of_gas: - // TODO - %jump(fault_exception) + // stack: trap_info + %ctx_gas_limit + // stack: gas_limit, trap_info + DUP2 %shr_const(192) + // stack: gas_used, gas_limit, trap_info + DUP2 DUP2 + // stack: gas_used, gas_limit, gas_used, gas_limit, trap_info + // If gas_used is already over the limit, panic. The exception should have + // been raised earlier. + GT %jumpi(panic) + // stack: gas_used, gas_limit, trap_info + DUP3 %opcode_from_exp_trap_info + // stack: opcode, gas_used, gas_limit, trap_info + %add_const(gas_cost_for_opcode) + %mload_kernel_code + // stack: gas_cost, gas_used, gas_limit, trap_info + ADD + // stack: new_gas_used, gas_limit, trap_info + GT + // stack: is_oog, trap_info + SWAP1 POP + // stack: is_oog + %jumpi(fault_exception) + // If we didn't jump, we shouldn't have raised the exception. + PANIC global exc_invalid_opcode: @@ -302,3 +325,110 @@ min_stack_len_for_opcode: BYTES 2 // 0xfd, REVERT BYTES 0 // 0xfe, invalid BYTES 1 // 0xff, SELFDESTRUCT + +// A zero indicates either that the opcode is kernel-only, +// or that it's handled with a syscall. +gas_cost_for_opcode: + BYTES 0 // 0x00, STOP + BYTES @GAS_VERYLOW // 0x01, ADD + BYTES @GAS_LOW // 0x02, MUL + BYTES @GAS_VERYLOW // 0x03, SUB + BYTES @GAS_LOW // 0x04, DIV + BYTES @GAS_LOW // 0x05, SDIV + BYTES @GAS_LOW // 0x06, MOD + BYTES @GAS_LOW // 0x07, SMOD + BYTES @GAS_MID // 0x08, ADDMOD + BYTES @GAS_MID // 0x09, MULMOD + BYTES 0 // 0x0a, EXP + BYTES 0 // 0x0b, SIGNEXTEND + %rep 4 // 0x0c-0x0f, invalid + BYTES 0 + %endrep + + BYTES @GAS_VERYLOW // 0x10, LT + BYTES @GAS_VERYLOW // 0x11, GT + BYTES @GAS_VERYLOW // 0x12, SLT + BYTES @GAS_VERYLOW // 0x13, SGT + BYTES @GAS_VERYLOW // 0x14, EQ + BYTES @GAS_VERYLOW // 0x15, ISZERO + BYTES @GAS_VERYLOW // 0x16, AND + BYTES @GAS_VERYLOW // 0x17, OR + BYTES @GAS_VERYLOW // 0x18, XOR + BYTES @GAS_VERYLOW // 0x19, NOT + BYTES @GAS_VERYLOW // 0x1a, BYTE + BYTES @GAS_VERYLOW // 0x1b, SHL + BYTES @GAS_VERYLOW // 0x1c, SHR + BYTES @GAS_VERYLOW // 0x1d, SAR + BYTES 0 // 0x1e, invalid + BYTES 0 // 0x1f, invalid + + BYTES 0 // 0x20, KECCAK256 + %rep 15 // 0x21-0x2f, invalid + BYTES 0 + %endrep + + %rep 25 //0x30-0x48, only syscalls + BYTES 0 + %endrep + + %rep 7 // 0x49-0x4f, invalid + BYTES 0 + %endrep + + BYTES @GAS_BASE // 0x50, POP + BYTES 0 // 0x51, MLOAD + BYTES 0 // 0x52, MSTORE + BYTES 0 // 0x53, MSTORE8 + BYTES 0 // 0x54, SLOAD + BYTES 0 // 0x55, SSTORE + BYTES @GAS_MID // 0x56, JUMP + BYTES @GAS_HIGH // 0x57, JUMPI + BYTES @GAS_BASE // 0x58, PC + BYTES 0 // 0x59, MSIZE + BYTES 0 // 0x5a, GAS + BYTES @GAS_JUMPDEST // 0x5b, JUMPDEST + %rep 3 // 0x5c-0x5e, invalid + BYTES 0 + %endrep + + BYTES @GAS_BASE // 0x5f, PUSH0 + %rep 32 // 0x60-0x7f, PUSH1-PUSH32 + BYTES @GAS_VERYLOW + %endrep + + %rep 16 // 0x80-0x8f, DUP1-DUP16 + BYTES @GAS_VERYLOW + %endrep + + %rep 16 // 0x90-0x9f, SWAP1-SWAP16 + BYTES @GAS_VERYLOW + %endrep + + BYTES 0 // 0xa0, LOG0 + BYTES 0 // 0xa1, LOG1 + BYTES 0 // 0xa2, LOG2 + BYTES 0 // 0xa3, LOG3 + BYTES 0 // 0xa4, LOG4 + %rep 11 // 0xa5-0xaf, invalid + BYTES 0 + %endrep + + %rep 64 // 0xb0-0xef, invalid + BYTES 0 + %endrep + + BYTES 0 // 0xf0, CREATE + BYTES 0 // 0xf1, CALL + BYTES 0 // 0xf2, CALLCODE + BYTES 0 // 0xf3, RETURN + BYTES 0 // 0xf4, DELEGATECALL + BYTES 0 // 0xf5, CREATE2 + %rep 4 // 0xf6-0xf9, invalid + BYTES 0 + %endrep + BYTES 0 // 0xfa, STATICCALL + BYTES 0 // 0xfb, invalid + BYTES 0 // 0xfc, invalid + BYTES 0 // 0xfd, REVERT + BYTES 0 // 0xfe, invalid + BYTES 0 // 0xff, SELFDESTRUCT diff --git a/evm/src/cpu/kernel/assembler.rs b/evm/src/cpu/kernel/assembler.rs index d708d228..b8314bdb 100644 --- a/evm/src/cpu/kernel/assembler.rs +++ b/evm/src/cpu/kernel/assembler.rs @@ -8,7 +8,7 @@ use keccak_hash::keccak; use log::debug; use serde::{Deserialize, Serialize}; -use super::ast::PushTarget; +use super::ast::{BytesTarget, PushTarget}; use crate::cpu::kernel::ast::Item::LocalLabelDeclaration; use crate::cpu::kernel::ast::{File, Item, StackReplacement}; use crate::cpu::kernel::opcodes::{get_opcode, get_push_opcode}; @@ -277,6 +277,23 @@ fn inline_constants(body: Vec, constants: &HashMap) -> Vec { code.push(get_opcode(&opcode)); } - Item::Bytes(bytes) => code.extend(bytes), + Item::Bytes(targets) => { + for target in targets { + match target { + BytesTarget::Literal(n) => code.push(n), + BytesTarget::Constant(c) => panic!("Constant wasn't inlined: {c}"), + } + } + } Item::Jumptable(labels) => { for label in labels { let bytes = look_up_label(&label, &local_labels, global_labels); @@ -507,7 +531,10 @@ mod tests { #[test] fn literal_bytes() { let file = File { - body: vec![Item::Bytes(vec![0x12, 42]), Item::Bytes(vec![0xFE, 255])], + body: vec![ + Item::Bytes(vec![BytesTarget::Literal(0x12), BytesTarget::Literal(42)]), + Item::Bytes(vec![BytesTarget::Literal(0xFE), BytesTarget::Literal(255)]), + ], }; let code = assemble(vec![file], HashMap::new(), false).code; assert_eq!(code, vec![0x12, 42, 0xfe, 255]); diff --git a/evm/src/cpu/kernel/ast.rs b/evm/src/cpu/kernel/ast.rs index ed4f6dbb..0af3bdab 100644 --- a/evm/src/cpu/kernel/ast.rs +++ b/evm/src/cpu/kernel/ast.rs @@ -33,7 +33,7 @@ pub(crate) enum Item { /// Any opcode besides a PUSH opcode. StandardOp(String), /// Literal hex data; should contain an even number of hex chars. - Bytes(Vec), + Bytes(Vec), /// Creates a table of addresses from a list of labels. Jumptable(Vec), } @@ -75,3 +75,10 @@ pub(crate) enum PushTarget { MacroVar(String), Constant(String), } + +/// The target of a `BYTES` item. +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub(crate) enum BytesTarget { + Literal(u8), + Constant(String), +} diff --git a/evm/src/cpu/kernel/evm_asm.pest b/evm/src/cpu/kernel/evm_asm.pest index 3243aecc..40dec03b 100644 --- a/evm/src/cpu/kernel/evm_asm.pest +++ b/evm/src/cpu/kernel/evm_asm.pest @@ -34,7 +34,8 @@ local_label_decl = ${ identifier ~ ":" } macro_label_decl = ${ "%%" ~ identifier ~ ":" } macro_label = ${ "%%" ~ identifier } -bytes_item = { ^"BYTES " ~ literal ~ ("," ~ literal)* } +bytes_item = { ^"BYTES " ~ bytes_target ~ ("," ~ bytes_target)* } +bytes_target = { literal | constant } jumptable_item = { ^"JUMPTABLE " ~ identifier ~ ("," ~ identifier)* } push_instruction = { ^"PUSH " ~ push_target } push_target = { literal | identifier | macro_label | variable | constant } diff --git a/evm/src/cpu/kernel/parser.rs b/evm/src/cpu/kernel/parser.rs index a17439e2..7864acfe 100644 --- a/evm/src/cpu/kernel/parser.rs +++ b/evm/src/cpu/kernel/parser.rs @@ -4,7 +4,7 @@ use ethereum_types::U256; use pest::iterators::Pair; use pest::Parser; -use super::ast::StackPlaceholder; +use super::ast::{BytesTarget, StackPlaceholder}; use crate::cpu::kernel::ast::{File, Item, PushTarget, StackReplacement}; /// Parses EVM assembly code. @@ -38,7 +38,7 @@ fn parse_item(item: Pair) -> Item { Rule::macro_label_decl => { Item::MacroLabelDeclaration(item.into_inner().next().unwrap().as_str().into()) } - Rule::bytes_item => Item::Bytes(item.into_inner().map(parse_literal_u8).collect()), + Rule::bytes_item => Item::Bytes(item.into_inner().map(parse_bytes_target).collect()), Rule::jumptable_item => { Item::Jumptable(item.into_inner().map(|i| i.as_str().into()).collect()) } @@ -167,6 +167,16 @@ fn parse_push_target(target: Pair) -> PushTarget { } } +fn parse_bytes_target(target: Pair) -> BytesTarget { + assert_eq!(target.as_rule(), Rule::bytes_target); + let inner = target.into_inner().next().unwrap(); + match inner.as_rule() { + Rule::literal => BytesTarget::Literal(parse_literal_u8(inner)), + Rule::constant => BytesTarget::Constant(inner.into_inner().next().unwrap().as_str().into()), + _ => panic!("Unexpected {:?}", inner.as_rule()), + } +} + fn parse_literal_u8(literal: Pair) -> u8 { let literal = literal.into_inner().next().unwrap(); match literal.as_rule() { diff --git a/evm/src/witness/transition.rs b/evm/src/witness/transition.rs index 189e9e42..ca4e3275 100644 --- a/evm/src/witness/transition.rs +++ b/evm/src/witness/transition.rs @@ -6,6 +6,7 @@ use super::memory::{MemoryOp, MemoryOpKind}; use super::util::fill_channel_with_value; use crate::cpu::columns::CpuColumnsView; use crate::cpu::kernel::aggregator::KERNEL; +use crate::cpu::kernel::constants::context_metadata::ContextMetadata; use crate::cpu::stack::{ EQ_STACK_BEHAVIOR, IS_ZERO_STACK_BEHAVIOR, JUMPI_OP, JUMP_OP, STACK_BEHAVIORS, }; @@ -273,6 +274,23 @@ fn perform_op( state.registers.gas_used += gas_to_charge(op); + let gas_limit_address = MemoryAddress { + context: state.registers.context, + segment: Segment::ContextMetadata as usize, + virt: ContextMetadata::GasLimit as usize, + }; + if !state.registers.is_kernel { + let gas_limit = TryInto::::try_into(state.memory.get(gas_limit_address)); + match gas_limit { + Ok(limit) => { + if state.registers.gas_used > limit { + return Err(ProgramError::OutOfGas); + } + } + Err(_) => return Err(ProgramError::IntegerTooLarge), + } + } + Ok(()) }