Skip to content

Commit

Permalink
feat: returndatacopy (#500)
Browse files Browse the repository at this point in the history
closes #484
  • Loading branch information
Eikix authored Jan 21, 2025
1 parent 38c9abd commit 832a9b2
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 1 deletion.
72 changes: 71 additions & 1 deletion cairo/ethereum/cancun/vm/instructions/environment.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ from starkware.cairo.common.registers import get_fp_and_pc
from starkware.cairo.common.dict_access import DictAccess
from starkware.cairo.common.math import split_felt
from starkware.cairo.common.uint256 import uint256_lt
from starkware.cairo.common.math_cmp import is_le

from ethereum_types.bytes import Bytes32, Bytes32Struct
from ethereum_types.numeric import U256, U256Struct, Uint, UnionUintU256, UnionUintU256Enum
Expand All @@ -14,7 +15,7 @@ from ethereum_types.others import (
)
from ethereum.cancun.fork_types import Address, SetAddress, SetAddressStruct, SetAddressDictAccess
from ethereum.cancun.vm import Evm, EvmImpl, EnvImpl
from ethereum.cancun.vm.exceptions import ExceptionalHalt, OutOfGasError
from ethereum.cancun.vm.exceptions import ExceptionalHalt, OutOfGasError, OutOfBoundsRead
from ethereum.cancun.vm.gas import charge_gas, GasConstants, calculate_gas_extend_memory
from ethereum.cancun.vm.memory import buffer_read, memory_write, expand_by
from ethereum.cancun.vm.stack import Stack, push, pop
Expand Down Expand Up @@ -278,6 +279,75 @@ func returndatasize{range_check_ptr, evm: Evm}() -> ExceptionalHalt* {
return ok;
}

func returndatacopy{range_check_ptr: felt, evm: Evm}() -> ExceptionalHalt* {
alloc_locals;
// STACK
let stack = evm.value.stack;
with stack {
let (memory_start_index, err) = pop();
if (cast(err, felt) != 0) {
return err;
}
let (returndata_start_position, err) = pop();
if (cast(err, felt) != 0) {
return err;
}
let (size, err) = pop();
if (cast(err, felt) != 0) {
return err;
}
}
if (size.value.high != 0) {
tempvar err = new ExceptionalHalt(OutOfGasError);
return err;
}

let ceil32_size = ceil32(Uint(size.value.low));
let (words, _) = divmod(ceil32_size.value, 32);
let return_data_copy_gas_cost = GasConstants.GAS_RETURN_DATA_COPY * words;
// Calculate memory expansion cost
tempvar extensions_tuple = new TupleU256U256(new TupleU256U256Struct(memory_start_index, size));
tempvar extensions_list = ListTupleU256U256(new ListTupleU256U256Struct(extensions_tuple, 1));
let extend_memory = calculate_gas_extend_memory(evm.value.memory, extensions_list);
let err = charge_gas(
Uint(GasConstants.GAS_VERY_LOW + return_data_copy_gas_cost + extend_memory.value.cost.value)
);
if (cast(err, felt) != 0) {
return err;
}

let memory = evm.value.memory;
// Check if the read on return_data is in bounds
// If the start position is greater than 2 ** 128, then it is almost surely out of bounds
if (returndata_start_position.value.high != 0) {
tempvar err = new ExceptionalHalt(OutOfBoundsRead);
return err;
}
// Check if returndata_start_position and size are each less than 2**128, so that their
// sum is less than 2**129, which fits into a felt. We can then be sure that
// size.value.low + returndata_start_position.value.low won't wrap around the PRIME.
assert [range_check_ptr] = returndata_start_position.value.low;
let range_check_ptr = range_check_ptr + 1;
assert [range_check_ptr] = size.value.low;
let range_check_ptr = range_check_ptr + 1;
let is_in_bounds = is_le(
size.value.low + returndata_start_position.value.low, evm.value.return_data.value.len
);
if (is_in_bounds == 0) {
tempvar err = new ExceptionalHalt(OutOfBoundsRead);
return err;
}

with memory {
expand_by(extend_memory.value.expand_by);
let value = buffer_read(evm.value.return_data, returndata_start_position, size);
memory_write(memory_start_index, value);
}
EvmImpl.set_pc_stack_memory(Uint(evm.value.pc.value + 1), stack, memory);
let ok = cast(0, ExceptionalHalt*);
return ok;
}

// @notice Push the balance of the current address to the stack
func self_balance{range_check_ptr, poseidon_ptr: PoseidonBuiltin*, evm: Evm}() -> ExceptionalHalt* {
alloc_locals;
Expand Down
23 changes: 23 additions & 0 deletions cairo/tests/ethereum/cancun/vm/instructions/test_environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
codesize,
gasprice,
origin,
returndatacopy,
returndatasize,
self_balance,
)
Expand Down Expand Up @@ -56,6 +57,16 @@
EvmBuilder().with_gas_left().with_env(environment_empty_state).build()
)

evm_environment_strategy_with_return_data = (
EvmBuilder()
.with_memory()
.with_gas_left()
.with_env(environment_empty_state)
.with_return_data()
.with_capped_values_stack()
.build()
)

code_access_size_strategy = st.integers(min_value=0, max_value=MAX_CODE_SIZE).map(U256)
code_start_index_strategy = code_access_size_strategy

Expand Down Expand Up @@ -194,6 +205,18 @@ def test_returndatasize(self, cairo_run, evm: Evm):
returndatasize(evm)
assert evm == cairo_result

@given(evm=evm_environment_strategy_with_return_data)
def test_returndatacopy(self, cairo_run, evm: Evm):
try:
cairo_result = cairo_run("returndatacopy", evm)
except Exception as cairo_error:
with strict_raises(type(cairo_error)):
returndatacopy(evm)
return

returndatacopy(evm)
assert evm == cairo_result

@given(evm=evm_environment_strategy)
def test_self_balance(self, cairo_run, evm: Evm):
try:
Expand Down
12 changes: 12 additions & 0 deletions cairo/tests/utils/evm_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,14 @@ def with_stack(self, strategy=stack_strategy(Stack[U256])):
self._stack = strategy
return self

def with_capped_values_stack(self, max_value=2**8 - 1):
self._stack = st.lists(
st.integers(min_value=0, max_value=max_value).map(U256),
min_size=0,
max_size=1024,
).map(lambda x: Stack[U256](x))
return self

def with_memory(self, strategy=memory_lite):
self._memory = strategy
return self
Expand Down Expand Up @@ -132,6 +140,10 @@ def with_accessed_storage_keys(
self._accessed_storage_keys = strategy
return self

def with_return_data(self, strategy=st.binary(min_size=0, max_size=1024)):
self._return_data = strategy
return self

def build(self):
return st.builds(
Evm,
Expand Down

0 comments on commit 832a9b2

Please sign in to comment.