From d18493d99dcf624dba8b7f4c6b831a84cf863018 Mon Sep 17 00:00:00 2001 From: rakita Date: Wed, 19 Jun 2024 03:20:35 +0200 Subject: [PATCH 1/8] eof fixes --- bins/revm-test/src/bin/burntpix/main.rs | 2 +- crates/interpreter/src/instruction_result.rs | 19 +++- .../interpreter/src/instructions/contract.rs | 31 ++++--- crates/interpreter/src/interpreter.rs | 8 +- crates/interpreter/src/interpreter_action.rs | 4 +- .../interpreter_action/eof_create_inputs.rs | 93 +++++++++++++------ .../interpreter_action/eof_create_outcome.rs | 71 -------------- crates/interpreter/src/lib.rs | 2 +- crates/precompile/src/lib.rs | 2 +- crates/primitives/src/bytecode/eof/header.rs | 32 +++---- crates/primitives/src/result.rs | 5 + crates/primitives/src/specification.rs | 11 +++ crates/revm/src/context/inner_evm_context.rs | 54 +++++++++-- crates/revm/src/evm.rs | 45 ++------- crates/revm/src/frame.rs | 15 +-- .../src/handler/handle_types/execution.rs | 12 +-- crates/revm/src/handler/mainnet/execution.rs | 12 +-- crates/revm/src/inspector.rs | 25 +++-- 18 files changed, 224 insertions(+), 219 deletions(-) delete mode 100644 crates/interpreter/src/interpreter_action/eof_create_outcome.rs diff --git a/bins/revm-test/src/bin/burntpix/main.rs b/bins/revm-test/src/bin/burntpix/main.rs index 8cd9eea66c..df2d0f6f96 100644 --- a/bins/revm-test/src/bin/burntpix/main.rs +++ b/bins/revm-test/src/bin/burntpix/main.rs @@ -100,7 +100,7 @@ fn try_from_hex_to_u32(hex: &str) -> eyre::Result { } fn insert_account_info(cache_db: &mut CacheDB, addr: Address, code: Bytes) { - let code_hash = hex::encode(keccak256(code.clone())); + let code_hash = hex::encode(keccak256(&code)); let account_info = AccountInfo::new( U256::from(0), 0, diff --git a/crates/interpreter/src/instruction_result.rs b/crates/interpreter/src/instruction_result.rs index 121e12ead7..4d51efe9ce 100644 --- a/crates/interpreter/src/instruction_result.rs +++ b/crates/interpreter/src/instruction_result.rs @@ -53,6 +53,12 @@ pub enum InstructionResult { EOFOpcodeDisabledInLegacy, /// EOF function stack overflow EOFFunctionStackOverflow, + /// Invalid EOF initcode, + InvalidEOFInitCode, + /// Aux data overflow, new aux data is larger tha u16 max size. + EofAuxDataOverflow, + /// Aud data is smaller then already present data size. + EofAuxDataTooSmall, } impl From for InstructionResult { @@ -94,6 +100,8 @@ impl From for InstructionResult { HaltReason::CallNotAllowedInsideStatic => Self::CallNotAllowedInsideStatic, HaltReason::OutOfFunds => Self::OutOfFunds, HaltReason::CallTooDeep => Self::CallTooDeep, + HaltReason::EofAuxDataOverflow => Self::EofAuxDataOverflow, + HaltReason::EofAuxDataTooSmall => Self::EofAuxDataTooSmall, #[cfg(feature = "optimism")] HaltReason::FailedDeposit => Self::FatalExternalError, } @@ -114,7 +122,10 @@ macro_rules! return_ok { #[macro_export] macro_rules! return_revert { () => { - InstructionResult::Revert | InstructionResult::CallTooDeep | InstructionResult::OutOfFunds + InstructionResult::Revert + | InstructionResult::CallTooDeep + | InstructionResult::OutOfFunds + | InstructionResult::InvalidEOFInitCode }; } @@ -146,6 +157,8 @@ macro_rules! return_error { | InstructionResult::ReturnContractInNotInitEOF | InstructionResult::EOFOpcodeDisabledInLegacy | InstructionResult::EOFFunctionStackOverflow + | InstructionResult::EofAuxDataTooSmall + | InstructionResult::EofAuxDataOverflow }; } @@ -267,10 +280,14 @@ impl From for SuccessOrHalt { InstructionResult::CreateInitCodeSizeLimit => { Self::Halt(HaltReason::CreateInitCodeSizeLimit) } + // TODO(EOF) - revise if Revert should have subenum. + InstructionResult::InvalidEOFInitCode => Self::Revert, InstructionResult::FatalExternalError => Self::FatalExternalError, InstructionResult::EOFOpcodeDisabledInLegacy => Self::Halt(HaltReason::OpcodeNotFound), InstructionResult::EOFFunctionStackOverflow => Self::FatalExternalError, InstructionResult::ReturnContract => Self::Success(SuccessReason::EofReturnContract), + InstructionResult::EofAuxDataOverflow => Self::Halt(HaltReason::EofAuxDataOverflow), + InstructionResult::EofAuxDataTooSmall => Self::Halt(HaltReason::EofAuxDataTooSmall), } } } diff --git a/crates/interpreter/src/instructions/contract.rs b/crates/interpreter/src/instructions/contract.rs index aac3d38f76..000da7427d 100644 --- a/crates/interpreter/src/instructions/contract.rs +++ b/crates/interpreter/src/instructions/contract.rs @@ -1,7 +1,7 @@ mod call_helpers; pub use call_helpers::{calc_call_gas, get_memory_input_and_out_ranges, resize_memory}; -use revm_primitives::{keccak256, BerlinSpec}; +use revm_primitives::{eof::EofHeader, keccak256, BerlinSpec}; use crate::{ gas::{self, cost_per_word, EOF_CREATE_GAS, KECCAK256WORD}, @@ -11,7 +11,7 @@ use crate::{ InstructionResult, InterpreterAction, InterpreterResult, LoadAccountResult, MAX_INITCODE_SIZE, }; use core::cmp::max; -use std::boxed::Box; +use std::{boxed::Box, os::macos::raw::stat}; /// EOF Create instruction pub fn eofcreate(interpreter: &mut Interpreter, _host: &mut H) { @@ -67,7 +67,7 @@ pub fn eofcreate(interpreter: &mut Interpreter, _host: &mut H) // Send container for execution container is preverified. interpreter.instruction_result = InstructionResult::CallOrCreate; interpreter.next_action = InterpreterAction::EOFCreate { - inputs: Box::new(EOFCreateInputs::new( + inputs: Box::new(EOFCreateInputs::new_opcode( interpreter.contract.target_address, created_address, value, @@ -92,10 +92,11 @@ pub fn return_contract(interpreter: &mut Interpreter, _host: & .body .container_section .get(deploy_container_index as usize) - .expect("EOF is checked"); + .expect("EOF is checked") + .clone(); // convert to EOF so we can check data section size. - let new_eof = Eof::decode(container.clone()).expect("Container is verified"); + let (eof_header, _) = EofHeader::decode(&container).expect("valid EOF header"); let aux_slice = if aux_data_size != 0 { let aux_data_offset = as_usize_or_fail!(interpreter, aux_data_offset); @@ -108,26 +109,32 @@ pub fn return_contract(interpreter: &mut Interpreter, _host: & &[] }; - let new_data_size = new_eof.body.data_section.len() + aux_slice.len(); + let static_aux_size = eof_header.eof_size() - container.len(); + + // data_size - static_aux_size give us current data `container` size. + // and with aux_slice len we can calculate new data size. + let new_data_size = eof_header.data_size as usize - static_aux_size + aux_slice.len(); if new_data_size > 0xFFFF { // aux data is too big - interpreter.instruction_result = InstructionResult::FatalExternalError; + interpreter.instruction_result = InstructionResult::EofAuxDataOverflow; return; } - if new_data_size < new_eof.header.data_size as usize { + if new_data_size < eof_header.data_size as usize { // aux data is too small - interpreter.instruction_result = InstructionResult::FatalExternalError; + interpreter.instruction_result = InstructionResult::EofAuxDataTooSmall; return; } + let new_data_size = (new_data_size as u16).to_be_bytes(); - // append data bytes - let output = [new_eof.raw(), aux_slice].concat().into(); + let mut output = [&container, aux_slice].concat(); + output[eof_header.data_size_raw_i()..][..2].clone_from_slice(&new_data_size); + let output: Bytes = output.into(); let result = InstructionResult::ReturnContract; interpreter.instruction_result = result; interpreter.next_action = crate::InterpreterAction::Return { result: InterpreterResult { - output, + output: output, gas: interpreter.gas, result, }, diff --git a/crates/interpreter/src/interpreter.rs b/crates/interpreter/src/interpreter.rs index d74f7d08bc..4fa5816505 100644 --- a/crates/interpreter/src/interpreter.rs +++ b/crates/interpreter/src/interpreter.rs @@ -9,7 +9,6 @@ pub use contract::Contract; pub use shared_memory::{num_words, SharedMemory, EMPTY_SHARED_MEMORY}; pub use stack::{Stack, STACK_LIMIT}; -use crate::EOFCreateOutcome; use crate::{ gas, primitives::Bytes, push, push_b256, return_ok, return_revert, CallOutcome, CreateOutcome, FunctionStack, Gas, Host, InstructionResult, InterpreterAction, @@ -192,7 +191,7 @@ impl Interpreter { } } - pub fn insert_eofcreate_outcome(&mut self, create_outcome: EOFCreateOutcome) { + pub fn insert_eofcreate_outcome(&mut self, create_outcome: CreateOutcome) { self.instruction_result = InstructionResult::Continue; let instruction_result = create_outcome.instruction_result(); @@ -206,7 +205,10 @@ impl Interpreter { match instruction_result { InstructionResult::ReturnContract => { - push_b256!(self, create_outcome.address.into_word()); + push_b256!( + self, + create_outcome.address.expect("EOF Address").into_word() + ); self.gas.erase_cost(create_outcome.gas().remaining()); self.gas.record_refund(create_outcome.gas().refunded()); } diff --git a/crates/interpreter/src/interpreter_action.rs b/crates/interpreter/src/interpreter_action.rs index 1459b0226e..1d2c27d622 100644 --- a/crates/interpreter/src/interpreter_action.rs +++ b/crates/interpreter/src/interpreter_action.rs @@ -3,14 +3,12 @@ mod call_outcome; mod create_inputs; mod create_outcome; mod eof_create_inputs; -mod eof_create_outcome; pub use call_inputs::{CallInputs, CallScheme, CallValue}; pub use call_outcome::CallOutcome; pub use create_inputs::{CreateInputs, CreateScheme}; pub use create_outcome::CreateOutcome; -pub use eof_create_inputs::EOFCreateInputs; -pub use eof_create_outcome::EOFCreateOutcome; +pub use eof_create_inputs::{EOFCreateInputs, EOFCreateKind}; use crate::InterpreterResult; use std::boxed::Box; diff --git a/crates/interpreter/src/interpreter_action/eof_create_inputs.rs b/crates/interpreter/src/interpreter_action/eof_create_inputs.rs index 4dc8190be5..e9ef13b3bb 100644 --- a/crates/interpreter/src/interpreter_action/eof_create_inputs.rs +++ b/crates/interpreter/src/interpreter_action/eof_create_inputs.rs @@ -1,5 +1,36 @@ -use crate::primitives::{eof::EofDecodeError, Address, Bytes, Eof, TxEnv, U256}; -use std::boxed::Box; +use crate::primitives::{Address, Bytes, Eof, TxEnv, U256}; + +/// EOF create can be called from two places: +/// * EOFCREATE opcode +/// * Creation transaction. +/// +/// Creation transaction uses initdata and packs EOF and initdata inside it. +/// This eof bytecode needs to be validated. +/// +/// Opcode creation uses already validated EOF bytecode, and input from Interpreter memory. +/// Address is already known and is passed as an argument. +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum EOFCreateKind { + Tx { + initdata: Bytes, + }, + Opcode { + initcode: Eof, + input: Bytes, + created_address: Address, + }, +} + +impl Default for EOFCreateKind { + fn default() -> Self { + EOFCreateKind::Opcode { + initcode: Eof::default(), + input: Bytes::default(), + created_address: Address::default(), + } + } +} /// Inputs for EOF create call. #[derive(Debug, Default, Clone, PartialEq, Eq)] @@ -7,42 +38,42 @@ use std::boxed::Box; pub struct EOFCreateInputs { /// Caller of Eof Craate pub caller: Address, - /// New contract address. - pub created_address: Address, /// Values of ether transfered pub value: U256, - /// Init eof code that is going to be executed. - pub eof_init_code: Eof, - /// Call data the input of the EOFCREATE call. - pub input: Bytes, /// Gas limit for the create call. pub gas_limit: u64, + /// EOF Create kind + pub kind: EOFCreateKind, } impl EOFCreateInputs { - /// Returns boxed EOFCreateInput or error. - /// Internally calls [`Self::new_tx`]. - pub fn new_tx_boxed(tx: &TxEnv, nonce: u64) -> Result, EofDecodeError> { - Ok(Box::new(Self::new_tx(tx, nonce)?)) - } - /// Create new EOF crate input from transaction that has concatenated eof init code and calldata. /// /// Legacy transaction still have optional nonce so we need to obtain it. - pub fn new_tx(tx: &TxEnv, nonce: u64) -> Result { - let (eof_init_code, input) = Eof::decode_dangling(tx.data.clone())?; - Ok(EOFCreateInputs { - caller: tx.caller, - created_address: tx.caller.create(nonce), - value: tx.value, - eof_init_code, - gas_limit: tx.gas_limit, - input, - }) + pub fn new(caller: Address, value: U256, gas_limit: u64, kind: EOFCreateKind) -> Self { + //let (eof_init_code, input) = Eof::decode_dangling(tx.data.clone())?; + EOFCreateInputs { + caller: caller, + value: value, + gas_limit, + kind, + } + } + + /// Creates new EOFCreateInputs from transaction. + pub fn new_tx(tx: &TxEnv, gas_limit: u64) -> Self { + EOFCreateInputs::new( + tx.caller, + tx.value, + gas_limit, + EOFCreateKind::Tx { + initdata: tx.data.clone(), + }, + ) } /// Returns a new instance of EOFCreateInput. - pub fn new( + pub fn new_opcode( caller: Address, created_address: Address, value: U256, @@ -50,13 +81,15 @@ impl EOFCreateInputs { gas_limit: u64, input: Bytes, ) -> EOFCreateInputs { - EOFCreateInputs { + EOFCreateInputs::new( caller, - created_address, value, - eof_init_code, gas_limit, - input, - } + EOFCreateKind::Opcode { + initcode: eof_init_code, + input, + created_address, + }, + ) } } diff --git a/crates/interpreter/src/interpreter_action/eof_create_outcome.rs b/crates/interpreter/src/interpreter_action/eof_create_outcome.rs deleted file mode 100644 index beeda24e50..0000000000 --- a/crates/interpreter/src/interpreter_action/eof_create_outcome.rs +++ /dev/null @@ -1,71 +0,0 @@ -use crate::{Gas, InstructionResult, InterpreterResult}; -use revm_primitives::{Address, Bytes}; - -/// Represents the outcome of a create operation in an interpreter. -/// -/// This struct holds the result of the operation along with an optional address. -/// It provides methods to determine the next action based on the result of the operation. -#[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct EOFCreateOutcome { - /// The result of the interpreter operation. - pub result: InterpreterResult, - /// An optional address associated with the create operation. - pub address: Address, -} - -impl EOFCreateOutcome { - /// Constructs a new [`EOFCreateOutcome`]. - /// - /// # Arguments - /// - /// * `result` - An `InterpreterResult` representing the result of the interpreter operation. - /// * `address` - An optional `Address` associated with the create operation. - /// * `return_memory_range` - The memory range that Revert bytes are going to be written. - /// - /// # Returns - /// - /// A new [`EOFCreateOutcome`] instance. - pub fn new(result: InterpreterResult, address: Address) -> Self { - Self { result, address } - } - - /// Retrieves a reference to the [`InstructionResult`] from the [`InterpreterResult`]. - /// - /// This method provides access to the `InstructionResult` which represents the - /// outcome of the instruction execution. It encapsulates the result information - /// such as whether the instruction was executed successfully, resulted in a revert, - /// or encountered a fatal error. - /// - /// # Returns - /// - /// A reference to the `InstructionResult`. - pub fn instruction_result(&self) -> &InstructionResult { - &self.result.result - } - - /// Retrieves a reference to the output bytes from the `InterpreterResult`. - /// - /// This method returns the output of the interpreted operation. The output is - /// typically used when the operation successfully completes and returns data. - /// - /// # Returns - /// - /// A reference to the output `Bytes`. - pub fn output(&self) -> &Bytes { - &self.result.output - } - - /// Retrieves a reference to the `Gas` details from the `InterpreterResult`. - /// - /// This method provides access to the gas details of the operation, which includes - /// information about gas used, remaining, and refunded. It is essential for - /// understanding the gas consumption of the operation. - /// - /// # Returns - /// - /// A reference to the `Gas` details. - pub fn gas(&self) -> &Gas { - &self.result.gas - } -} diff --git a/crates/interpreter/src/lib.rs b/crates/interpreter/src/lib.rs index 64167dd410..da95f0895e 100644 --- a/crates/interpreter/src/lib.rs +++ b/crates/interpreter/src/lib.rs @@ -37,7 +37,7 @@ pub use interpreter::{ }; pub use interpreter_action::{ CallInputs, CallOutcome, CallScheme, CallValue, CreateInputs, CreateOutcome, CreateScheme, - EOFCreateInputs, EOFCreateOutcome, InterpreterAction, + EOFCreateInputs, EOFCreateKind, InterpreterAction, }; pub use opcode::{Instruction, OpCode, OPCODE_INFO_JUMPTABLE}; pub use primitives::{MAX_CODE_SIZE, MAX_INITCODE_SIZE}; diff --git a/crates/precompile/src/lib.rs b/crates/precompile/src/lib.rs index e930f2cdfc..1425f3421a 100644 --- a/crates/precompile/src/lib.rs +++ b/crates/precompile/src/lib.rs @@ -281,7 +281,7 @@ impl PrecompileSpecId { ISTANBUL | MUIR_GLACIER => Self::ISTANBUL, BERLIN | LONDON | ARROW_GLACIER | GRAY_GLACIER | MERGE | SHANGHAI => Self::BERLIN, CANCUN => Self::CANCUN, - PRAGUE => Self::PRAGUE, + PRAGUE | PRAGUE_EOF => Self::PRAGUE, LATEST => Self::LATEST, #[cfg(feature = "optimism")] BEDROCK | REGOLITH | CANYON => Self::BERLIN, diff --git a/crates/primitives/src/bytecode/eof/header.rs b/crates/primitives/src/bytecode/eof/header.rs index 1074f066a4..c348296872 100644 --- a/crates/primitives/src/bytecode/eof/header.rs +++ b/crates/primitives/src/bytecode/eof/header.rs @@ -62,25 +62,23 @@ fn consume_header_section_size(input: &[u8]) -> Result<(&[u8], Vec, usize), impl EofHeader { /// Length of the header in bytes. /// - /// Length is calculated as: - /// magic 2 byte + - /// version 1 byte + - /// types section 3 bytes + - /// code section 3 bytes + - /// num_code_sections * 2 + - /// if num_container_sections != 0 { container section 3 bytes} + - /// num_container_sections * 2 + - /// data section 3 bytes + - /// terminator 1 byte - /// /// It is minimum 15 bytes (there is at least one code section). pub fn size(&self) -> usize { - let optional_container_sizes = if self.container_sizes.is_empty() { - 0 - } else { - 3 + self.container_sizes.len() * 2 - }; - 13 + self.code_sizes.len() * 2 + optional_container_sizes + 2 + // magic + 1 + // version + 3 + // types section + 3 + // code section + 2 * self.code_sizes.len() + // num_code_sections + if self.container_sizes.is_empty() { 0 } else { 3 + 2 * self.container_sizes.len() } + // container + 3 + // data section. + 1 // terminator + } + + /// Return index where data size starts. + /// Data size is two bytes long. + pub fn data_size_raw_i(&self) -> usize { + // termination(1byte) + code size(2) bytes. + self.size() - 3 } /// Returns number of types. diff --git a/crates/primitives/src/result.rs b/crates/primitives/src/result.rs index 28f35ce82c..3e9c242f9f 100644 --- a/crates/primitives/src/result.rs +++ b/crates/primitives/src/result.rs @@ -423,6 +423,11 @@ pub enum HaltReason { OutOfFunds, CallTooDeep, + /// Aux data overflow, new aux data is larger tha u16 max size. + EofAuxDataOverflow, + /// Aud data is smaller then already present data size. + EofAuxDataTooSmall, + /* Optimism errors */ #[cfg(feature = "optimism")] FailedDeposit, diff --git a/crates/primitives/src/specification.rs b/crates/primitives/src/specification.rs index d7c1d3e6cf..ed45f904f9 100644 --- a/crates/primitives/src/specification.rs +++ b/crates/primitives/src/specification.rs @@ -29,6 +29,7 @@ pub enum SpecId { SHANGHAI = 16, // Shanghai 17034870 (Timestamp: 1681338455) CANCUN = 17, // Cancun 19426587 (Timestamp: 1710338135) PRAGUE = 18, // Praque TBD + PRAGUE_EOF = 19, // Praque+EOF TBD #[default] LATEST = u8::MAX, } @@ -144,6 +145,7 @@ impl From for &'static str { SpecId::SHANGHAI => "Shanghai", SpecId::CANCUN => "Cancun", SpecId::PRAGUE => "Prague", + SpecId::PRAGUE_EOF => "PragueEOF", #[cfg(feature = "optimism")] SpecId::BEDROCK => "Bedrock", #[cfg(feature = "optimism")] @@ -200,6 +202,7 @@ spec!(MERGE, MergeSpec); spec!(SHANGHAI, ShanghaiSpec); spec!(CANCUN, CancunSpec); spec!(PRAGUE, PragueSpec); +spec!(PRAGUE_EOF, PragueEofSpec); spec!(LATEST, LatestSpec); @@ -278,6 +281,10 @@ macro_rules! spec_to_generic { use $crate::PragueSpec as SPEC; $e } + $crate::SpecId::PRAGUE_EOF => { + use $crate::PragueEofSpec as SPEC; + $e + } } }}; } @@ -345,6 +352,10 @@ macro_rules! spec_to_generic { use $crate::PragueSpec as SPEC; $e } + $crate::SpecId::PRAGUE_EOF => { + use $crate::PragueEofSpec as SPEC; + $e + } $crate::SpecId::BEDROCK => { use $crate::BedrockSpec as SPEC; $e diff --git a/crates/revm/src/context/inner_evm_context.rs b/crates/revm/src/context/inner_evm_context.rs index c83d5ec14a..a15ac46467 100644 --- a/crates/revm/src/context/inner_evm_context.rs +++ b/crates/revm/src/context/inner_evm_context.rs @@ -1,7 +1,8 @@ use crate::{ db::Database, interpreter::{ - analysis::to_analysed, gas, return_ok, Contract, CreateInputs, EOFCreateInputs, Gas, + analysis::{to_analysed, validate_eof}, + gas, return_ok, Contract, CreateInputs, EOFCreateInputs, EOFCreateKind, Gas, InstructionResult, Interpreter, InterpreterResult, LoadAccountResult, SStoreResult, SelfDestructResult, MAX_CODE_SIZE, }, @@ -254,10 +255,41 @@ impl InnerEvmContext { gas: Gas::new(inputs.gas_limit), output: Bytes::new(), }, - inputs.created_address, + None, )) }; + let (input, initcode, created_address) = match &inputs.kind { + EOFCreateKind::Opcode { + initcode, + input, + created_address, + } => (input.clone(), initcode.clone(), *created_address), + EOFCreateKind::Tx { initdata } => { + // get nonce from tx (if set) or from account (if not). + // Nonce for call is bumped in deduct_caller while + // for CREATE it is not (it is done inside exec handlers). + let nonce = self.env.tx.nonce.unwrap_or_else(|| { + let caller = self.env.tx.caller; + self.load_account(caller) + .map(|(a, _)| a.info.nonce) + .unwrap_or_default() + }); + + // decode eof and init code. + let Ok((eof, input)) = Eof::decode_dangling(initdata.clone()) else { + return return_error(InstructionResult::InvalidEOFInitCode); + }; + + if validate_eof(&eof).is_err() { + // TODO (EOF) new error type. + return return_error(InstructionResult::InvalidEOFInitCode); + } + + (input, eof, self.env.tx.caller.create(nonce)) + } + }; + // Check depth if self.journaled_state.depth() > CALL_STACK_LIMIT { return return_error(InstructionResult::CallTooDeep); @@ -279,12 +311,12 @@ impl InnerEvmContext { // Load account so it needs to be marked as warm for access list. self.journaled_state - .load_account(inputs.created_address, &mut self.db)?; + .load_account(created_address, &mut self.db)?; // create account, transfer funds and make the journal checkpoint. let checkpoint = match self.journaled_state.create_account_checkpoint( inputs.caller, - inputs.created_address, + created_address, inputs.value, spec_id, ) { @@ -295,11 +327,11 @@ impl InnerEvmContext { }; let contract = Contract::new( - inputs.input.clone(), + input.clone(), // fine to clone as it is Bytes. - Bytecode::Eof(Arc::new(inputs.eof_init_code.clone())), + Bytecode::Eof(Arc::new(initcode.clone())), None, - inputs.created_address, + created_address, inputs.caller, inputs.value, ); @@ -309,7 +341,7 @@ impl InnerEvmContext { interpreter.set_is_eof_init(); Ok(FrameOrResult::new_eofcreate_frame( - inputs.created_address, + created_address, checkpoint, interpreter, )) @@ -334,6 +366,12 @@ impl InnerEvmContext { return; } + if interpreter_result.output.len() > MAX_CODE_SIZE { + self.journaled_state.checkpoint_revert(journal_checkpoint); + interpreter_result.result = InstructionResult::CreateContractSizeLimit; + return; + } + // deduct gas for code deployment. let gas_for_code = interpreter_result.output.len() as u64 * gas::CODEDEPOSIT; if !interpreter_result.gas.record_cost(gas_for_code) { diff --git a/crates/revm/src/evm.rs b/crates/revm/src/evm.rs index d5b853d220..67b519f8fe 100644 --- a/crates/revm/src/evm.rs +++ b/crates/revm/src/evm.rs @@ -3,11 +3,10 @@ use crate::{ db::{Database, DatabaseCommit, EmptyDB}, handler::Handler, interpreter::{ - analysis::validate_eof, CallInputs, CreateInputs, EOFCreateInputs, EOFCreateOutcome, Gas, - Host, InstructionResult, InterpreterAction, InterpreterResult, SharedMemory, + CallInputs, CreateInputs, EOFCreateInputs, Host, InterpreterAction, SharedMemory, }, primitives::{ - specification::SpecId, BlockEnv, Bytes, CfgEnv, EVMError, EVMResult, EnvWithHandlerCfg, + specification::SpecId, BlockEnv, CfgEnv, EVMError, EVMResult, EnvWithHandlerCfg, ExecutionResult, HandlerCfg, ResultAndState, TransactTo, TxEnv, }, Context, ContextWithHandlerCfg, Frame, FrameOrResult, FrameResult, @@ -359,42 +358,10 @@ impl Evm<'_, EXT, DB> { .filter(|&t| t == [0xEF, 00]) .is_some() { - // TODO Should we just check 0xEF it seems excessive to switch to legacy only - // if it 0xEF00? - - // get nonce from tx (if set) or from account (if not). - // Nonce for call is bumped in deduct_caller while - // for CREATE it is not (it is done inside exec handlers). - let nonce = ctx.evm.env.tx.nonce.unwrap_or_else(|| { - let caller = ctx.evm.env.tx.caller; - ctx.evm - .load_account(caller) - .map(|(a, _)| a.info.nonce) - .unwrap_or_default() - }); - - // Create EOFCreateInput from transaction initdata. - let eofcreate = EOFCreateInputs::new_tx_boxed(&ctx.evm.env.tx, nonce) - .ok() - .and_then(|eofcreate| { - // validate EOF initcode - validate_eof(&eofcreate.eof_init_code).ok()?; - Some(eofcreate) - }); - - if let Some(eofcreate) = eofcreate { - exec.eofcreate(ctx, eofcreate)? - } else { - // Return result, as code is invalid. - FrameOrResult::Result(FrameResult::EOFCreate(EOFCreateOutcome::new( - InterpreterResult::new( - InstructionResult::Stop, - Bytes::new(), - Gas::new(gas_limit), - ), - ctx.env().tx.caller.create(nonce), - ))) - } + exec.eofcreate( + ctx, + Box::new(EOFCreateInputs::new_tx(&ctx.evm.env.tx, gas_limit)), + )? } else { // Safe to unwrap because we are sure that it is create tx. exec.create( diff --git a/crates/revm/src/frame.rs b/crates/revm/src/frame.rs index c36ffe0707..2ef01db561 100644 --- a/crates/revm/src/frame.rs +++ b/crates/revm/src/frame.rs @@ -4,9 +4,7 @@ use crate::{ JournalCheckpoint, }; use core::ops::Range; -use revm_interpreter::{ - CallOutcome, CreateOutcome, EOFCreateOutcome, Gas, InstructionResult, InterpreterResult, -}; +use revm_interpreter::{CallOutcome, CreateOutcome, Gas, InstructionResult, InterpreterResult}; use std::boxed::Box; /// Call CallStackFrame. @@ -59,7 +57,7 @@ pub enum Frame { pub enum FrameResult { Call(CallOutcome), Create(CreateOutcome), - EOFCreate(EOFCreateOutcome), + EOFCreate(CreateOutcome), } impl FrameResult { @@ -82,7 +80,7 @@ impl FrameResult { Output::Create(outcome.result.output.clone(), outcome.address) } FrameResult::EOFCreate(outcome) => { - Output::Create(outcome.result.output.clone(), Some(outcome.address)) + Output::Create(outcome.result.output.clone(), outcome.address) } } } @@ -277,8 +275,11 @@ impl FrameOrResult { })) } - pub fn new_eofcreate_result(interpreter_result: InterpreterResult, address: Address) -> Self { - FrameOrResult::Result(FrameResult::EOFCreate(EOFCreateOutcome { + pub fn new_eofcreate_result( + interpreter_result: InterpreterResult, + address: Option
, + ) -> Self { + FrameOrResult::Result(FrameResult::EOFCreate(CreateOutcome { result: interpreter_result, address, })) diff --git a/crates/revm/src/handler/handle_types/execution.rs b/crates/revm/src/handler/handle_types/execution.rs index 9e17def07c..27d31eb118 100644 --- a/crates/revm/src/handler/handle_types/execution.rs +++ b/crates/revm/src/handler/handle_types/execution.rs @@ -6,8 +6,8 @@ use crate::{ CallFrame, Context, CreateFrame, Frame, FrameOrResult, FrameResult, }; use revm_interpreter::{ - opcode::InstructionTables, CallOutcome, CreateOutcome, EOFCreateInputs, EOFCreateOutcome, - InterpreterAction, InterpreterResult, + opcode::InstructionTables, CallOutcome, CreateOutcome, EOFCreateInputs, InterpreterAction, + InterpreterResult, }; use std::{boxed::Box, sync::Arc}; @@ -102,7 +102,7 @@ pub type FrameEOFCreateReturnHandle<'a, EXT, DB> = Arc< &mut Context, Box, InterpreterResult, - ) -> Result::Error>> + ) -> Result::Error>> + 'a, >; @@ -111,7 +111,7 @@ pub type InsertEOFCreateOutcomeHandle<'a, EXT, DB> = Arc< dyn Fn( &mut Context, &mut Frame, - EOFCreateOutcome, + CreateOutcome, ) -> Result<(), EVMError<::Error>> + 'a, >; @@ -267,7 +267,7 @@ impl<'a, EXT, DB: Database> ExecutionHandler<'a, EXT, DB> { context: &mut Context, frame: Box, interpreter_result: InterpreterResult, - ) -> Result> { + ) -> Result> { (self.eofcreate_return)(context, frame, interpreter_result) } @@ -277,7 +277,7 @@ impl<'a, EXT, DB: Database> ExecutionHandler<'a, EXT, DB> { &self, context: &mut Context, frame: &mut Frame, - outcome: EOFCreateOutcome, + outcome: CreateOutcome, ) -> Result<(), EVMError> { (self.insert_eofcreate_outcome)(context, frame, outcome) } diff --git a/crates/revm/src/handler/mainnet/execution.rs b/crates/revm/src/handler/mainnet/execution.rs index 4ee557f81b..928d173508 100644 --- a/crates/revm/src/handler/mainnet/execution.rs +++ b/crates/revm/src/handler/mainnet/execution.rs @@ -10,8 +10,8 @@ use crate::{ }; use core::mem; use revm_interpreter::{ - opcode::InstructionTables, CallOutcome, EOFCreateInputs, EOFCreateOutcome, InterpreterAction, - InterpreterResult, EMPTY_SHARED_MEMORY, + opcode::InstructionTables, CallOutcome, EOFCreateInputs, InterpreterAction, InterpreterResult, + EMPTY_SHARED_MEMORY, }; use std::boxed::Box; @@ -174,15 +174,15 @@ pub fn eofcreate_return( context: &mut Context, frame: Box, mut interpreter_result: InterpreterResult, -) -> Result> { +) -> Result> { context.evm.eofcreate_return::( &mut interpreter_result, frame.created_address, frame.frame_data.checkpoint, ); - Ok(EOFCreateOutcome::new( + Ok(CreateOutcome::new( interpreter_result, - frame.created_address, + Some(frame.created_address), )) } @@ -190,7 +190,7 @@ pub fn eofcreate_return( pub fn insert_eofcreate_outcome( context: &mut Context, frame: &mut Frame, - outcome: EOFCreateOutcome, + outcome: CreateOutcome, ) -> Result<(), EVMError> { core::mem::replace(&mut context.evm.error, Ok(()))?; frame diff --git a/crates/revm/src/inspector.rs b/crates/revm/src/inspector.rs index 26551c8d8d..3225ed3546 100644 --- a/crates/revm/src/inspector.rs +++ b/crates/revm/src/inspector.rs @@ -1,10 +1,3 @@ -use crate::{ - interpreter::{CallInputs, CreateInputs, EOFCreateInputs, EOFCreateOutcome, Interpreter}, - primitives::{db::Database, Address, Log, U256}, - EvmContext, -}; -use auto_impl::auto_impl; - #[cfg(feature = "std")] mod customprinter; #[cfg(all(feature = "std", feature = "serde-json"))] @@ -13,10 +6,16 @@ mod gas; mod handler_register; mod noop; -// Exports. - pub use handler_register::{inspector_handle_register, GetInspector}; -use revm_interpreter::{CallOutcome, CreateOutcome}; + +use crate::{ + interpreter::{ + CallInputs, CallOutcome, CreateInputs, CreateOutcome, EOFCreateInputs, Interpreter, + }, + primitives::{db::Database, Address, Log, U256}, + EvmContext, +}; +use auto_impl::auto_impl; /// [Inspector] implementations. pub mod inspectors { @@ -142,7 +141,7 @@ pub trait Inspector { &mut self, context: &mut EvmContext, inputs: &mut EOFCreateInputs, - ) -> Option { + ) -> Option { let _ = context; let _ = inputs; None @@ -153,8 +152,8 @@ pub trait Inspector { &mut self, context: &mut EvmContext, inputs: &EOFCreateInputs, - outcome: EOFCreateOutcome, - ) -> EOFCreateOutcome { + outcome: CreateOutcome, + ) -> CreateOutcome { let _ = context; let _ = inputs; outcome From b48150e18aeb8ed7ca87e16f0c8a104faf1d0dca Mon Sep 17 00:00:00 2001 From: rakita Date: Wed, 19 Jun 2024 13:02:25 +0200 Subject: [PATCH 2/8] fix(eof): create initcode starting with 0xff00 --- crates/interpreter/src/instruction_result.rs | 8 ++++++-- crates/revm/src/context/inner_evm_context.rs | 12 +++++++----- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/crates/interpreter/src/instruction_result.rs b/crates/interpreter/src/instruction_result.rs index 4d51efe9ce..f3bc4a420f 100644 --- a/crates/interpreter/src/instruction_result.rs +++ b/crates/interpreter/src/instruction_result.rs @@ -16,6 +16,10 @@ pub enum InstructionResult { Revert = 0x10, // revert opcode CallTooDeep, OutOfFunds, + /// Revert if CREATE/CREATE2 starts with 0xEF00 + CreateInitCodeStartingEF00, + /// Invalid EOF initcode, + InvalidEOFInitCode, // Actions CallOrCreate = 0x20, @@ -53,8 +57,6 @@ pub enum InstructionResult { EOFOpcodeDisabledInLegacy, /// EOF function stack overflow EOFFunctionStackOverflow, - /// Invalid EOF initcode, - InvalidEOFInitCode, /// Aux data overflow, new aux data is larger tha u16 max size. EofAuxDataOverflow, /// Aud data is smaller then already present data size. @@ -126,6 +128,7 @@ macro_rules! return_revert { | InstructionResult::CallTooDeep | InstructionResult::OutOfFunds | InstructionResult::InvalidEOFInitCode + | InstructionResult::CreateInitCodeStartingEF00 }; } @@ -240,6 +243,7 @@ impl From for SuccessOrHalt { InstructionResult::Return => Self::Success(SuccessReason::Return), InstructionResult::SelfDestruct => Self::Success(SuccessReason::SelfDestruct), InstructionResult::Revert => Self::Revert, + InstructionResult::CreateInitCodeStartingEF00 => Self::Revert, InstructionResult::CallOrCreate => Self::InternalCallOrCreate, // used only in interpreter loop InstructionResult::CallTooDeep => Self::Halt(HaltReason::CallTooDeep), // not gonna happen for first call InstructionResult::OutOfFunds => Self::Halt(HaltReason::OutOfFunds), // Check for first call is done separately. diff --git a/crates/revm/src/context/inner_evm_context.rs b/crates/revm/src/context/inner_evm_context.rs index a15ac46467..8844aa75c3 100644 --- a/crates/revm/src/context/inner_evm_context.rs +++ b/crates/revm/src/context/inner_evm_context.rs @@ -399,14 +399,11 @@ impl InnerEvmContext { spec_id: SpecId, inputs: &CreateInputs, ) -> Result> { - // Prepare crate. - let gas = Gas::new(inputs.gas_limit); - let return_error = |e| { Ok(FrameOrResult::new_create_result( InterpreterResult { result: e, - gas, + gas: Gas::new(inputs.gas_limit), output: Bytes::new(), }, None, @@ -418,6 +415,11 @@ impl InnerEvmContext { return return_error(InstructionResult::CallTooDeep); } + // Prague EOF + if spec_id.is_enabled_in(PRAGUE) && inputs.init_code.get(..2) == Some(&[0xEF, 00]) { + return return_error(InstructionResult::CreateInitCodeStartingEF00); + } + // Fetch balance of caller. let (caller_balance, _) = self.balance(inputs.caller)?; @@ -475,7 +477,7 @@ impl InnerEvmContext { Ok(FrameOrResult::new_create_frame( created_address, checkpoint, - Interpreter::new(contract, gas.limit(), false), + Interpreter::new(contract, inputs.gas_limit, false), )) } From be30f41f999f6d2c25fa2ab9e0a28bbe662fc9ee Mon Sep 17 00:00:00 2001 From: rakita Date: Wed, 19 Jun 2024 15:24:35 +0200 Subject: [PATCH 3/8] Include PragueEOF --- bins/revme/src/cmd/statetest/models/spec.rs | 2 ++ crates/interpreter/src/instructions/contract.rs | 7 ++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/bins/revme/src/cmd/statetest/models/spec.rs b/bins/revme/src/cmd/statetest/models/spec.rs index dac05f04a9..50903323a2 100644 --- a/bins/revme/src/cmd/statetest/models/spec.rs +++ b/bins/revme/src/cmd/statetest/models/spec.rs @@ -24,6 +24,7 @@ pub enum SpecName { Shanghai, Cancun, Prague, + PragueEOF, #[serde(other)] Unknown, } @@ -46,6 +47,7 @@ impl SpecName { Self::Shanghai => SpecId::SHANGHAI, Self::Cancun => SpecId::CANCUN, Self::Prague => SpecId::PRAGUE, + Self::PragueEOF => SpecId::PRAGUE_EOF, Self::ByzantiumToConstantinopleAt5 | Self::Constantinople => { panic!("Overridden with PETERSBURG") } diff --git a/crates/interpreter/src/instructions/contract.rs b/crates/interpreter/src/instructions/contract.rs index 000da7427d..38ec14c750 100644 --- a/crates/interpreter/src/instructions/contract.rs +++ b/crates/interpreter/src/instructions/contract.rs @@ -1,17 +1,18 @@ mod call_helpers; pub use call_helpers::{calc_call_gas, get_memory_input_and_out_ranges, resize_memory}; -use revm_primitives::{eof::EofHeader, keccak256, BerlinSpec}; use crate::{ gas::{self, cost_per_word, EOF_CREATE_GAS, KECCAK256WORD}, interpreter::Interpreter, - primitives::{Address, Bytes, Eof, Spec, SpecId::*, U256}, + primitives::{ + eof::EofHeader, keccak256, Address, BerlinSpec, Bytes, Eof, Spec, SpecId::*, U256, + }, CallInputs, CallScheme, CallValue, CreateInputs, CreateScheme, EOFCreateInputs, Host, InstructionResult, InterpreterAction, InterpreterResult, LoadAccountResult, MAX_INITCODE_SIZE, }; use core::cmp::max; -use std::{boxed::Box, os::macos::raw::stat}; +use std::boxed::Box; /// EOF Create instruction pub fn eofcreate(interpreter: &mut Interpreter, _host: &mut H) { From e851e4830d349d5d7638a83c57631c24ff682fa6 Mon Sep 17 00:00:00 2001 From: rakita Date: Wed, 19 Jun 2024 15:34:44 +0200 Subject: [PATCH 4/8] spec missing --- crates/primitives/src/specification.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/primitives/src/specification.rs b/crates/primitives/src/specification.rs index ed45f904f9..51d18010be 100644 --- a/crates/primitives/src/specification.rs +++ b/crates/primitives/src/specification.rs @@ -66,6 +66,7 @@ pub enum SpecId { ECOTONE = 21, FJORD = 22, PRAGUE = 23, + PRAGUE_EOF = 24, #[default] LATEST = u8::MAX, } @@ -108,6 +109,7 @@ impl From<&str> for SpecId { "Shanghai" => Self::SHANGHAI, "Cancun" => Self::CANCUN, "Prague" => Self::PRAGUE, + "PragueEOF" => Self::PRAGUE_EOF, #[cfg(feature = "optimism")] "Bedrock" => SpecId::BEDROCK, #[cfg(feature = "optimism")] @@ -417,6 +419,7 @@ mod tests { #[cfg(feature = "optimism")] spec_to_generic!(FJORD, assert_eq!(SPEC::SPEC_ID, FJORD)); spec_to_generic!(PRAGUE, assert_eq!(SPEC::SPEC_ID, PRAGUE)); + spec_to_generic!(PRAGUE_EOF, assert_eq!(SPEC::SPEC_ID, PRAGUE_EOF)); spec_to_generic!(LATEST, assert_eq!(SPEC::SPEC_ID, LATEST)); } } From 520f0ba0613080fe78fc3bc028bc04f1e0228271 Mon Sep 17 00:00:00 2001 From: rakita Date: Thu, 20 Jun 2024 12:58:37 +0200 Subject: [PATCH 5/8] add helper function to get address --- .../src/interpreter_action/eof_create_inputs.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/interpreter/src/interpreter_action/eof_create_inputs.rs b/crates/interpreter/src/interpreter_action/eof_create_inputs.rs index e9ef13b3bb..c650b3ed40 100644 --- a/crates/interpreter/src/interpreter_action/eof_create_inputs.rs +++ b/crates/interpreter/src/interpreter_action/eof_create_inputs.rs @@ -22,6 +22,16 @@ pub enum EOFCreateKind { }, } +impl EOFCreateKind { + /// Returns created address + pub fn created_address(&self) -> Option<&Address> { + match self { + EOFCreateKind::Opcode { created_address, .. } => Some(created_address), + EOFCreateKind::Tx { .. } => None, + } + } +} + impl Default for EOFCreateKind { fn default() -> Self { EOFCreateKind::Opcode { From 696864b1c914e11a4b1511ad5673b03d6dc621d7 Mon Sep 17 00:00:00 2001 From: rakita Date: Thu, 20 Jun 2024 14:43:50 +0200 Subject: [PATCH 6/8] clippy --- crates/interpreter/src/instructions/contract.rs | 3 ++- .../interpreter/src/interpreter_action/eof_create_inputs.rs | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/interpreter/src/instructions/contract.rs b/crates/interpreter/src/instructions/contract.rs index 38ec14c750..ea58ad4062 100644 --- a/crates/interpreter/src/instructions/contract.rs +++ b/crates/interpreter/src/instructions/contract.rs @@ -128,6 +128,7 @@ pub fn return_contract(interpreter: &mut Interpreter, _host: & let new_data_size = (new_data_size as u16).to_be_bytes(); let mut output = [&container, aux_slice].concat(); + // set new data size in eof bytes as we know exact index. output[eof_header.data_size_raw_i()..][..2].clone_from_slice(&new_data_size); let output: Bytes = output.into(); @@ -135,7 +136,7 @@ pub fn return_contract(interpreter: &mut Interpreter, _host: & interpreter.instruction_result = result; interpreter.next_action = crate::InterpreterAction::Return { result: InterpreterResult { - output: output, + output, gas: interpreter.gas, result, }, diff --git a/crates/interpreter/src/interpreter_action/eof_create_inputs.rs b/crates/interpreter/src/interpreter_action/eof_create_inputs.rs index 5d1acc7324..30bc525a4e 100644 --- a/crates/interpreter/src/interpreter_action/eof_create_inputs.rs +++ b/crates/interpreter/src/interpreter_action/eof_create_inputs.rs @@ -65,8 +65,8 @@ impl EOFCreateInputs { pub fn new(caller: Address, value: U256, gas_limit: u64, kind: EOFCreateKind) -> Self { //let (eof_init_code, input) = Eof::decode_dangling(tx.data.clone())?; EOFCreateInputs { - caller: caller, - value: value, + caller, + value, gas_limit, kind, } From ccc34b732df4f7a3111b03c1d3b2c23459425e33 Mon Sep 17 00:00:00 2001 From: rakita Date: Thu, 20 Jun 2024 14:47:28 +0200 Subject: [PATCH 7/8] include box --- crates/revm/src/evm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/revm/src/evm.rs b/crates/revm/src/evm.rs index 60c6ced05e..72ec1f79c5 100644 --- a/crates/revm/src/evm.rs +++ b/crates/revm/src/evm.rs @@ -12,7 +12,7 @@ use crate::{ Context, ContextWithHandlerCfg, Frame, FrameOrResult, FrameResult, }; use core::fmt; -use std::vec::Vec; +use std::{boxed::Box, vec::Vec}; /// EVM call stack limit. pub const CALL_STACK_LIMIT: u64 = 1024; From 9c1aac334778236cf310e8f4682231321ace0ee0 Mon Sep 17 00:00:00 2001 From: rakita Date: Thu, 20 Jun 2024 14:56:55 +0200 Subject: [PATCH 8/8] move EOF to PragueEOF --- crates/revm/src/context/inner_evm_context.rs | 2 +- crates/revm/src/evm.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/revm/src/context/inner_evm_context.rs b/crates/revm/src/context/inner_evm_context.rs index bea0d570d2..c166295148 100644 --- a/crates/revm/src/context/inner_evm_context.rs +++ b/crates/revm/src/context/inner_evm_context.rs @@ -416,7 +416,7 @@ impl InnerEvmContext { } // Prague EOF - if spec_id.is_enabled_in(PRAGUE) && inputs.init_code.get(..2) == Some(&[0xEF, 00]) { + if spec_id.is_enabled_in(PRAGUE_EOF) && inputs.init_code.get(..2) == Some(&[0xEF, 00]) { return return_error(InstructionResult::CreateInitCodeStartingEF00); } diff --git a/crates/revm/src/evm.rs b/crates/revm/src/evm.rs index 72ec1f79c5..9c67a8e984 100644 --- a/crates/revm/src/evm.rs +++ b/crates/revm/src/evm.rs @@ -349,7 +349,7 @@ impl Evm<'_, EXT, DB> { )?, TxKind::Create => { // if first byte of data is magic 0xEF00, then it is EOFCreate. - if spec_id.is_enabled_in(SpecId::PRAGUE) + if spec_id.is_enabled_in(SpecId::PRAGUE_EOF) && ctx .env() .tx