From 2c8da65c308b91c814fc00cd0566726d17ea9510 Mon Sep 17 00:00:00 2001 From: rakita Date: Wed, 10 Jul 2024 16:43:11 +0200 Subject: [PATCH 1/2] chore: Refactor code, and check is precompile for create colision --- crates/revm/src/context/evm_context.rs | 219 ++++++++++++++++++- crates/revm/src/context/inner_evm_context.rs | 210 +----------------- crates/revm/src/journaled_state.rs | 5 +- 3 files changed, 222 insertions(+), 212 deletions(-) diff --git a/crates/revm/src/context/evm_context.rs b/crates/revm/src/context/evm_context.rs index 543a8ab125..0f2531f6c0 100644 --- a/crates/revm/src/context/evm_context.rs +++ b/crates/revm/src/context/evm_context.rs @@ -5,16 +5,21 @@ use super::inner_evm_context::InnerEvmContext; use crate::{ db::Database, interpreter::{ - return_ok, CallInputs, Contract, Gas, InstructionResult, Interpreter, InterpreterResult, + analysis::validate_eof, return_ok, CallInputs, Contract, CreateInputs, EOFCreateInputs, + EOFCreateKind, Gas, InstructionResult, Interpreter, InterpreterResult, + }, + primitives::{ + keccak256, Address, Bytecode, Bytes, CreateScheme, EVMError, Env, Eof, + SpecId::{self, *}, + B256, EOF_MAGIC_BYTES, U256, }, - primitives::{Address, Bytes, EVMError, Env, EOF_MAGIC_BYTES, U256}, ContextPrecompiles, FrameOrResult, CALL_STACK_LIMIT, }; use core::{ fmt, ops::{Deref, DerefMut}, }; -use std::boxed::Box; +use std::{boxed::Box, sync::Arc}; /// EVM context that contains the inner EVM context and precompiles. pub struct EvmContext { @@ -238,6 +243,214 @@ impl EvmContext { )) } } + + /// Make create frame. + #[inline] + pub fn make_create_frame( + &mut self, + spec_id: SpecId, + inputs: &CreateInputs, + ) -> Result> { + let return_error = |e| { + Ok(FrameOrResult::new_create_result( + InterpreterResult { + result: e, + gas: Gas::new(inputs.gas_limit), + output: Bytes::new(), + }, + None, + )) + }; + + // Check depth + if self.journaled_state.depth() > CALL_STACK_LIMIT { + return return_error(InstructionResult::CallTooDeep); + } + + //self.precompiles + + // Prague EOF + if spec_id.is_enabled_in(PRAGUE_EOF) && inputs.init_code.get(..2) == Some(&EOF_MAGIC_BYTES) + { + return return_error(InstructionResult::CreateInitCodeStartingEF00); + } + + // Fetch balance of caller. + let (caller_balance, _) = self.balance(inputs.caller)?; + + // Check if caller has enough balance to send to the created contract. + if caller_balance < inputs.value { + return return_error(InstructionResult::OutOfFunds); + } + + // Increase nonce of caller and check if it overflows + let old_nonce; + if let Some(nonce) = self.journaled_state.inc_nonce(inputs.caller) { + old_nonce = nonce - 1; + } else { + return return_error(InstructionResult::Return); + } + + // Create address + let mut init_code_hash = B256::ZERO; + let created_address = match inputs.scheme { + CreateScheme::Create => inputs.caller.create(old_nonce), + CreateScheme::Create2 { salt } => { + init_code_hash = keccak256(&inputs.init_code); + inputs.caller.create2(salt.to_be_bytes(), init_code_hash) + } + }; + + // created address is not allowed to be a precompile. + if self.precompiles.contains(&created_address) { + return return_error(InstructionResult::CreateCollision); + } + + // warm load account. + self.load_account(created_address)?; + + // create account, transfer funds and make the journal checkpoint. + let checkpoint = match self.journaled_state.create_account_checkpoint( + inputs.caller, + created_address, + inputs.value, + spec_id, + ) { + Ok(checkpoint) => checkpoint, + Err(e) => { + return return_error(e); + } + }; + + let bytecode = Bytecode::new_raw(inputs.init_code.clone()); + + let contract = Contract::new( + Bytes::new(), + bytecode, + Some(init_code_hash), + created_address, + None, + inputs.caller, + inputs.value, + ); + + Ok(FrameOrResult::new_create_frame( + created_address, + checkpoint, + Interpreter::new(contract, inputs.gas_limit, false), + )) + } + + /// Make create frame. + #[inline] + pub fn make_eofcreate_frame( + &mut self, + spec_id: SpecId, + inputs: &EOFCreateInputs, + ) -> Result> { + let return_error = |e| { + Ok(FrameOrResult::new_eofcreate_result( + InterpreterResult { + result: e, + gas: Gas::new(inputs.gas_limit), + output: Bytes::new(), + }, + None, + )) + }; + + let (input, initcode, created_address) = match &inputs.kind { + EOFCreateKind::Opcode { + initcode, + input, + created_address, + } => (input.clone(), initcode.clone(), *created_address), + EOFCreateKind::Tx { initdata } => { + // Use nonce from tx (if set) or from account (if not). + // Nonce for call is bumped in deduct_caller + // TODO(make this part of nonce increment code) + 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); + } + + // Fetch balance of caller. + let (caller_balance, _) = self.balance(inputs.caller)?; + + // Check if caller has enough balance to send to the created contract. + if caller_balance < inputs.value { + return return_error(InstructionResult::OutOfFunds); + } + + // Increase nonce of caller and check if it overflows + if self.journaled_state.inc_nonce(inputs.caller).is_none() { + // can't happen on mainnet. + return return_error(InstructionResult::Return); + } + + // created address is not allowed to be a precompile. + if self.precompiles.contains(&created_address) { + return return_error(InstructionResult::CreateCollision); + } + + // Load account so it needs to be marked as warm for access list. + self.load_account(created_address)?; + + // create account, transfer funds and make the journal checkpoint. + let checkpoint = match self.journaled_state.create_account_checkpoint( + inputs.caller, + created_address, + inputs.value, + spec_id, + ) { + Ok(checkpoint) => checkpoint, + Err(e) => { + return return_error(e); + } + }; + + let contract = Contract::new( + input.clone(), + // fine to clone as it is Bytes. + Bytecode::Eof(Arc::new(initcode.clone())), + None, + created_address, + None, + inputs.caller, + inputs.value, + ); + + let mut interpreter = Interpreter::new(contract, inputs.gas_limit, false); + // EOF init will enable RETURNCONTRACT opcode. + interpreter.set_is_eof_init(); + + Ok(FrameOrResult::new_eofcreate_frame( + created_address, + checkpoint, + interpreter, + )) + } } /// Test utilities for the [`EvmContext`]. diff --git a/crates/revm/src/context/inner_evm_context.rs b/crates/revm/src/context/inner_evm_context.rs index 36ddad1b21..b2314cbb35 100644 --- a/crates/revm/src/context/inner_evm_context.rs +++ b/crates/revm/src/context/inner_evm_context.rs @@ -1,19 +1,17 @@ use crate::{ db::Database, interpreter::{ - analysis::{to_analysed, validate_eof}, - gas, return_ok, Contract, CreateInputs, EOFCreateInputs, EOFCreateKind, Gas, - InstructionResult, Interpreter, InterpreterResult, LoadAccountResult, SStoreResult, - SelfDestructResult, MAX_CODE_SIZE, + analysis::to_analysed, gas, return_ok, InstructionResult, InterpreterResult, + LoadAccountResult, SStoreResult, SelfDestructResult, MAX_CODE_SIZE, }, journaled_state::JournaledState, primitives::{ - keccak256, AccessListItem, Account, Address, AnalysisKind, Bytecode, Bytes, CreateScheme, - EVMError, Env, Eof, HashSet, Spec, + AccessListItem, Account, Address, AnalysisKind, Bytecode, Bytes, EVMError, Env, Eof, + HashSet, Spec, SpecId::{self, *}, B256, EOF_MAGIC_BYTES, EOF_MAGIC_HASH, U256, }, - FrameOrResult, JournalCheckpoint, CALL_STACK_LIMIT, + JournalCheckpoint, }; use std::{boxed::Box, sync::Arc, vec::Vec}; @@ -254,113 +252,6 @@ impl InnerEvmContext { .selfdestruct(address, target, &mut self.db) } - /// Make create frame. - #[inline] - pub fn make_eofcreate_frame( - &mut self, - spec_id: SpecId, - inputs: &EOFCreateInputs, - ) -> Result> { - let return_error = |e| { - Ok(FrameOrResult::new_eofcreate_result( - InterpreterResult { - result: e, - gas: Gas::new(inputs.gas_limit), - output: Bytes::new(), - }, - None, - )) - }; - - let (input, initcode, created_address) = match &inputs.kind { - EOFCreateKind::Opcode { - initcode, - input, - created_address, - } => (input.clone(), initcode.clone(), *created_address), - EOFCreateKind::Tx { initdata } => { - // Use nonce from tx (if set) or from account (if not). - // Nonce for call is bumped in deduct_caller - // TODO(make this part of nonce increment code) - 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); - } - - // Fetch balance of caller. - let (caller_balance, _) = self.balance(inputs.caller)?; - - // Check if caller has enough balance to send to the created contract. - if caller_balance < inputs.value { - return return_error(InstructionResult::OutOfFunds); - } - - // Increase nonce of caller and check if it overflows - if self.journaled_state.inc_nonce(inputs.caller).is_none() { - // can't happen on mainnet. - return return_error(InstructionResult::Return); - } - - // Load account so it needs to be marked as warm for access list. - self.journaled_state - .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, - created_address, - inputs.value, - spec_id, - ) { - Ok(checkpoint) => checkpoint, - Err(e) => { - return return_error(e); - } - }; - - let contract = Contract::new( - input.clone(), - // fine to clone as it is Bytes. - Bytecode::Eof(Arc::new(initcode.clone())), - None, - created_address, - None, - inputs.caller, - inputs.value, - ); - - let mut interpreter = Interpreter::new(contract, inputs.gas_limit, false); - // EOF init will enable RETURNCONTRACT opcode. - interpreter.set_is_eof_init(); - - Ok(FrameOrResult::new_eofcreate_frame( - created_address, - checkpoint, - interpreter, - )) - } - /// If error is present revert changes, otherwise save EOF bytecode. pub fn eofcreate_return( &mut self, @@ -406,97 +297,6 @@ impl InnerEvmContext { .set_code(address, Bytecode::Eof(Arc::new(bytecode))); } - /// Make create frame. - #[inline] - pub fn make_create_frame( - &mut self, - spec_id: SpecId, - inputs: &CreateInputs, - ) -> Result> { - let return_error = |e| { - Ok(FrameOrResult::new_create_result( - InterpreterResult { - result: e, - gas: Gas::new(inputs.gas_limit), - output: Bytes::new(), - }, - None, - )) - }; - - // Check depth - if self.journaled_state.depth() > CALL_STACK_LIMIT { - return return_error(InstructionResult::CallTooDeep); - } - - // Prague EOF - if spec_id.is_enabled_in(PRAGUE_EOF) && inputs.init_code.get(..2) == Some(&EOF_MAGIC_BYTES) - { - return return_error(InstructionResult::CreateInitCodeStartingEF00); - } - - // Fetch balance of caller. - let (caller_balance, _) = self.balance(inputs.caller)?; - - // Check if caller has enough balance to send to the created contract. - if caller_balance < inputs.value { - return return_error(InstructionResult::OutOfFunds); - } - - // Increase nonce of caller and check if it overflows - let old_nonce; - if let Some(nonce) = self.journaled_state.inc_nonce(inputs.caller) { - old_nonce = nonce - 1; - } else { - return return_error(InstructionResult::Return); - } - - // Create address - let mut init_code_hash = B256::ZERO; - let created_address = match inputs.scheme { - CreateScheme::Create => inputs.caller.create(old_nonce), - CreateScheme::Create2 { salt } => { - init_code_hash = keccak256(&inputs.init_code); - inputs.caller.create2(salt.to_be_bytes(), init_code_hash) - } - }; - - // Load account so it needs to be marked as warm for access list. - self.journaled_state - .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, - created_address, - inputs.value, - spec_id, - ) { - Ok(checkpoint) => checkpoint, - Err(e) => { - return return_error(e); - } - }; - - let bytecode = Bytecode::new_raw(inputs.init_code.clone()); - - let contract = Contract::new( - Bytes::new(), - bytecode, - Some(init_code_hash), - created_address, - None, - inputs.caller, - inputs.value, - ); - - Ok(FrameOrResult::new_create_frame( - created_address, - checkpoint, - Interpreter::new(contract, inputs.gas_limit, false), - )) - } - /// Handles call return. #[inline] pub fn call_return( diff --git a/crates/revm/src/journaled_state.rs b/crates/revm/src/journaled_state.rs index 6ab8c9859c..bea240895c 100644 --- a/crates/revm/src/journaled_state.rs +++ b/crates/revm/src/journaled_state.rs @@ -265,10 +265,7 @@ impl JournaledState { // Bytecode is not empty. // Nonce is not zero // Account is not precompile. - if account.info.code_hash != KECCAK_EMPTY - || account.info.nonce != 0 - || self.warm_preloaded_addresses.contains(&address) - { + if account.info.code_hash != KECCAK_EMPTY || account.info.nonce != 0 { self.checkpoint_revert(checkpoint); return Err(InstructionResult::CreateCollision); } From a3453d0948e8e07068b1dc7989178a5c7bf7a229 Mon Sep 17 00:00:00 2001 From: rakita Date: Wed, 10 Jul 2024 17:00:30 +0200 Subject: [PATCH 2/2] remove some TODOs for eofcreate nonce --- crates/revm/src/context/evm_context.rs | 32 ++++++++++++++------------ 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/crates/revm/src/context/evm_context.rs b/crates/revm/src/context/evm_context.rs index 0f2531f6c0..5ca0dbf299 100644 --- a/crates/revm/src/context/evm_context.rs +++ b/crates/revm/src/context/evm_context.rs @@ -101,7 +101,9 @@ impl EvmContext { #[inline] pub fn set_precompiles(&mut self, precompiles: ContextPrecompiles) { // set warm loaded addresses. - self.journaled_state.warm_preloaded_addresses = precompiles.addresses_set(); + self.journaled_state + .warm_preloaded_addresses + .extend(precompiles.addresses_set()); self.precompiles = precompiles; } @@ -364,18 +366,8 @@ impl EvmContext { initcode, input, created_address, - } => (input.clone(), initcode.clone(), *created_address), + } => (input.clone(), initcode.clone(), Some(*created_address)), EOFCreateKind::Tx { initdata } => { - // Use nonce from tx (if set) or from account (if not). - // Nonce for call is bumped in deduct_caller - // TODO(make this part of nonce increment code) - 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); @@ -386,7 +378,15 @@ impl EvmContext { return return_error(InstructionResult::InvalidEOFInitCode); } - (input, eof, self.env.tx.caller.create(nonce)) + // Use nonce from tx (if set) to calculate address. + // If not set, use the nonce from the account. + let nonce = self + .env + .tx + .nonce + .map(|nonce| self.env.tx.caller.create(nonce)); + + (input, eof, nonce) } }; @@ -404,10 +404,12 @@ impl EvmContext { } // Increase nonce of caller and check if it overflows - if self.journaled_state.inc_nonce(inputs.caller).is_none() { + let Some(nonce) = self.journaled_state.inc_nonce(inputs.caller) else { // can't happen on mainnet. return return_error(InstructionResult::Return); - } + }; + + let created_address = created_address.unwrap_or_else(|| inputs.caller.create(nonce)); // created address is not allowed to be a precompile. if self.precompiles.contains(&created_address) {