Skip to content

Commit

Permalink
feat(EOF): EIP-7698 eof creation transaction (#1467)
Browse files Browse the repository at this point in the history
* feat(EOF): eof tx create

* cleanup

* add box import

* fix(eof): add FrameResult if eof is in create tx

* Make returncontract a success, enabled by eof tx create

* fix build, add case
  • Loading branch information
rakita committed Jun 3, 2024
1 parent 916458c commit b7b92ae
Show file tree
Hide file tree
Showing 14 changed files with 198 additions and 95 deletions.
5 changes: 2 additions & 3 deletions crates/interpreter/src/instruction_result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ impl From<SuccessReason> for InstructionResult {
SuccessReason::Return => InstructionResult::Return,
SuccessReason::Stop => InstructionResult::Stop,
SuccessReason::SelfDestruct => InstructionResult::SelfDestruct,
SuccessReason::EofReturnContract => InstructionResult::ReturnContract,
}
}
}
Expand Down Expand Up @@ -269,9 +270,7 @@ impl From<InstructionResult> for SuccessOrHalt {
InstructionResult::FatalExternalError => Self::FatalExternalError,
InstructionResult::EOFOpcodeDisabledInLegacy => Self::Halt(HaltReason::OpcodeNotFound),
InstructionResult::EOFFunctionStackOverflow => Self::FatalExternalError,
InstructionResult::ReturnContract => {
panic!("Unexpected EOF internal Return Contract")
}
InstructionResult::ReturnContract => Self::Success(SuccessReason::EofReturnContract),
}
}
}
Expand Down
43 changes: 15 additions & 28 deletions crates/interpreter/src/instructions/contract.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
mod call_helpers;

pub use call_helpers::{
calc_call_gas, get_memory_input_and_out_ranges, resize_memory_and_return_range,
};
pub use call_helpers::{calc_call_gas, get_memory_input_and_out_ranges, resize_memory};
use revm_primitives::{keccak256, BerlinSpec};

use crate::{
Expand All @@ -12,29 +10,9 @@ use crate::{
CallInputs, CallScheme, CallValue, CreateInputs, CreateScheme, EOFCreateInput, Host,
InstructionResult, InterpreterAction, InterpreterResult, LoadAccountResult, MAX_INITCODE_SIZE,
};
use core::{cmp::max, ops::Range};
use core::cmp::max;
use std::boxed::Box;

/// Resize memory and return memory range if successful.
/// Return `None` if there is not enough gas. And if `len`
/// is zero return `Some(usize::MAX..usize::MAX)`.
pub fn resize_memory(
interpreter: &mut Interpreter,
offset: U256,
len: U256,
) -> Option<Range<usize>> {
let len = as_usize_or_fail_ret!(interpreter, len, None);
if len != 0 {
let offset = as_usize_or_fail_ret!(interpreter, offset, None);
resize_memory!(interpreter, offset, len, None);
// range is checked in resize_memory! macro and it is bounded by usize.
Some(offset..offset + len)
} else {
//unrealistic value so we are sure it is not used
Some(usize::MAX..usize::MAX)
}
}

/// EOF Create instruction
pub fn eofcreate<H: Host + ?Sized>(interpreter: &mut Interpreter, _host: &mut H) {
require_eof!(interpreter);
Expand All @@ -52,10 +30,20 @@ pub fn eofcreate<H: Host + ?Sized>(interpreter: &mut Interpreter, _host: &mut H)
.expect("EOF is checked");

// resize memory and get return range.
let Some(return_range) = resize_memory(interpreter, data_offset, data_size) else {
let Some(input_range) = resize_memory(interpreter, data_offset, data_size) else {
return;
};

let input = if !input_range.is_empty() {
interpreter
.shared_memory
.slice_range(input_range)
.to_vec()
.into()
} else {
Bytes::new()
};

let eof = Eof::decode(sub_container.clone()).expect("Subcontainer is verified");

if !eof.body.is_data_filled {
Expand Down Expand Up @@ -86,7 +74,7 @@ pub fn eofcreate<H: Host + ?Sized>(interpreter: &mut Interpreter, _host: &mut H)
value,
eof,
gas_limit,
return_range,
input,
)),
};

Expand Down Expand Up @@ -150,8 +138,7 @@ pub fn return_contract<H: Host + ?Sized>(interpreter: &mut Interpreter, _host: &
pub fn extcall_input(interpreter: &mut Interpreter) -> Option<Bytes> {
pop_ret!(interpreter, input_offset, input_size, None);

let return_memory_offset =
resize_memory_and_return_range(interpreter, input_offset, input_size)?;
let return_memory_offset = resize_memory(interpreter, input_offset, input_size)?;

Some(Bytes::copy_from_slice(
interpreter
Expand Down
6 changes: 3 additions & 3 deletions crates/interpreter/src/instructions/contract/call_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,21 @@ pub fn get_memory_input_and_out_ranges(
) -> Option<(Bytes, Range<usize>)> {
pop_ret!(interpreter, in_offset, in_len, out_offset, out_len, None);

let in_range = resize_memory_and_return_range(interpreter, in_offset, in_len)?;
let in_range = resize_memory(interpreter, in_offset, in_len)?;

let mut input = Bytes::new();
if !in_range.is_empty() {
input = Bytes::copy_from_slice(interpreter.shared_memory.slice_range(in_range));
}

let ret_range = resize_memory_and_return_range(interpreter, out_offset, out_len)?;
let ret_range = resize_memory(interpreter, out_offset, out_len)?;
Some((input, ret_range))
}

/// Resize memory and return range of memory.
/// If `len` is 0 dont touch memory and return `usize::MAX` as offset and 0 as length.
#[inline]
pub fn resize_memory_and_return_range(
pub fn resize_memory(
interpreter: &mut Interpreter,
offset: U256,
len: U256,
Expand Down
33 changes: 21 additions & 12 deletions crates/interpreter/src/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,18 +68,6 @@ impl Default for Interpreter {
}
}

/// The result of an interpreter operation.
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))]
pub struct InterpreterResult {
/// The result of the instruction execution.
pub result: InstructionResult,
/// The output of the instruction execution.
pub output: Bytes,
/// The gas usage information.
pub gas: Gas,
}

impl Interpreter {
/// Create new interpreter
pub fn new(contract: Contract, gas_limit: u64, is_static: bool) -> Self {
Expand Down Expand Up @@ -388,7 +376,28 @@ impl Interpreter {
}
}

/// The result of an interpreter operation.
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))]
pub struct InterpreterResult {
/// The result of the instruction execution.
pub result: InstructionResult,
/// The output of the instruction execution.
pub output: Bytes,
/// The gas usage information.
pub gas: Gas,
}

impl InterpreterResult {
/// Returns a new `InterpreterResult` with the given values.
pub fn new(result: InstructionResult, output: Bytes, gas: Gas) -> Self {
Self {
result,
output,
gas,
}
}

/// Returns whether the instruction result is a success.
#[inline]
pub const fn is_ok(&self) -> bool {
Expand Down
34 changes: 27 additions & 7 deletions crates/interpreter/src/interpreter_action/eof_create_inputs.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::primitives::{Address, Eof, U256};
use core::ops::Range;
use crate::primitives::{eof::EofDecodeError, Address, Bytes, Eof, TxEnv, U256};
use std::boxed::Box;

/// Inputs for EOF create call.
#[derive(Debug, Default, Clone, PartialEq, Eq)]
Expand All @@ -13,30 +13,50 @@ pub struct EOFCreateInput {
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,
/// Return memory range. If EOF creation Reverts it can return the
/// the memory range.
pub return_memory_range: Range<usize>,
}

impl EOFCreateInput {
/// Returns boxed EOFCreateInput or error.
/// Internally calls [`Self::new_tx`].
pub fn new_tx_boxed(tx: &TxEnv, nonce: u64) -> Result<Box<Self>, 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<Self, EofDecodeError> {
let (eof_init_code, input) = Eof::decode_dangling(tx.data.clone())?;
Ok(EOFCreateInput {
caller: tx.caller,
created_address: tx.caller.create(nonce),
value: tx.value,
eof_init_code,
gas_limit: tx.gas_limit,
input,
})
}

/// Returns a new instance of EOFCreateInput.
pub fn new(
caller: Address,
created_address: Address,
value: U256,
eof_init_code: Eof,
gas_limit: u64,
return_memory_range: Range<usize>,
input: Bytes,
) -> EOFCreateInput {
EOFCreateInput {
caller,
created_address,
value,
eof_init_code,
gas_limit,
return_memory_range,
input,
}
}
}
21 changes: 2 additions & 19 deletions crates/interpreter/src/interpreter_action/eof_create_outcome.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use core::ops::Range;

use crate::{Gas, InstructionResult, InterpreterResult};
use revm_primitives::{Address, Bytes};

Expand All @@ -14,8 +12,6 @@ pub struct EOFCreateOutcome {
pub result: InterpreterResult,
/// An optional address associated with the create operation.
pub address: Address,
/// Return memory range. If EOF creation Reverts it can return bytes from the memory.
pub return_memory_range: Range<usize>,
}

impl EOFCreateOutcome {
Expand All @@ -30,16 +26,8 @@ impl EOFCreateOutcome {
/// # Returns
///
/// A new [`EOFCreateOutcome`] instance.
pub fn new(
result: InterpreterResult,
address: Address,
return_memory_range: Range<usize>,
) -> Self {
Self {
result,
address,
return_memory_range,
}
pub fn new(result: InterpreterResult, address: Address) -> Self {
Self { result, address }
}

/// Retrieves a reference to the [`InstructionResult`] from the [`InterpreterResult`].
Expand Down Expand Up @@ -80,9 +68,4 @@ impl EOFCreateOutcome {
pub fn gas(&self) -> &Gas {
&self.result.gas
}

/// Returns the memory range that Revert bytes are going to be written.
pub fn return_range(&self) -> Range<usize> {
self.return_memory_range.clone()
}
}
56 changes: 56 additions & 0 deletions crates/primitives/src/bytecode/eof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,26 @@ impl Eof {
buffer.into()
}

/// Decode EOF that have additional dangling bytes.
/// Assume that data section is fully filled.
pub fn decode_dangling(mut eof: Bytes) -> Result<(Self, Bytes), EofDecodeError> {
let (header, _) = EofHeader::decode(&eof)?;
let eof_size = header.body_size() + header.size();
if eof_size > eof.len() {
return Err(EofDecodeError::MissingInput);
}
let dangling_data = eof.split_off(eof_size);
let body = EofBody::decode(&eof, &header)?;
Ok((
Self {
header,
body,
raw: eof,
},
dangling_data,
))
}

/// Decode EOF from raw bytes.
pub fn decode(raw: Bytes) -> Result<Self, EofDecodeError> {
let (header, _) = EofHeader::decode(&raw)?;
Expand Down Expand Up @@ -140,6 +160,42 @@ mod test {
assert_eq!(bytes, eof.encode_slow());
}

#[test]
fn decode_eof_dangling() {
let test_cases = [
(
bytes!("ef000101000402000100010400000000800000fe"),
bytes!("010203"),
false,
),
(
bytes!("ef000101000402000100010400000000800000fe"),
bytes!(""),
false,
),
(
bytes!("ef000101000402000100010400000000800000"),
bytes!(""),
true,
),
];

for (eof_bytes, dangling_data, is_err) in test_cases {
let mut raw = eof_bytes.to_vec();
raw.extend(&dangling_data);
let raw = Bytes::from(raw);

let result = Eof::decode_dangling(raw.clone());
assert_eq!(result.is_err(), is_err);
if is_err {
continue;
}
let (decoded_eof, decoded_dangling) = result.unwrap();
assert_eq!(eof_bytes, decoded_eof.encode_slow());
assert_eq!(decoded_dangling, dangling_data);
}
}

#[test]
fn data_slice() {
let bytes = bytes!("ef000101000402000100010400000000800000fe");
Expand Down
5 changes: 4 additions & 1 deletion crates/primitives/src/bytecode/eof/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,10 @@ impl EofHeader {

/// Returns body size. It is sum of code sizes, container sizes and data size.
pub fn body_size(&self) -> usize {
self.sum_code_sizes + self.sum_container_sizes + self.data_size as usize
self.types_size as usize
+ self.sum_code_sizes
+ self.sum_container_sizes
+ self.data_size as usize
}

/// Returns raw size of the EOF.
Expand Down
1 change: 1 addition & 0 deletions crates/primitives/src/result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,7 @@ pub enum SuccessReason {
Stop,
Return,
SelfDestruct,
EofReturnContract,
}

/// Indicates that the EVM has experienced an exceptional halt. This causes execution to
Expand Down
2 changes: 2 additions & 0 deletions crates/revm/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ where
}

impl<EXT, DB: Database> Host for Context<EXT, DB> {
/// Returns reference to Environment.
#[inline]
fn env(&self) -> &Env {
&self.evm.env
}
Expand Down
Loading

0 comments on commit b7b92ae

Please sign in to comment.