Skip to content

Commit

Permalink
feat: sstore
Browse files Browse the repository at this point in the history
  • Loading branch information
enitrat committed Jan 16, 2025
1 parent 352e492 commit 3ecbad5
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 4 deletions.
2 changes: 1 addition & 1 deletion cairo/ethereum/cancun/vm.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ namespace EvmImpl {
return ();
}

func set_refund_counter{evm: Evm}(new_refund_counter: Uint) {
func set_refund_counter{evm: Evm}(new_refund_counter: felt) {
tempvar evm = Evm(
new EvmStruct(
pc=evm.value.pc,
Expand Down
129 changes: 127 additions & 2 deletions cairo/ethereum/cancun/vm/instructions/storage.cairo
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from ethereum.cancun.vm.stack import pop, push
from ethereum.cancun.vm import Evm, EvmImpl, Environment, EnvImpl
from ethereum.cancun.vm.exceptions import ExceptionalHalt
from ethereum.cancun.vm.exceptions import ExceptionalHalt, WriteInStaticContext, OutOfGasError
from ethereum.cancun.vm.gas import charge_gas, GasConstants
from ethereum.cancun.state import get_storage
from ethereum.cancun.state import get_storage, get_storage_original, set_storage
from ethereum.cancun.fork_types import (
SetTupleAddressBytes32,
SetTupleAddressBytes32DictAccess,
Expand All @@ -21,6 +21,7 @@ from starkware.cairo.common.cairo_builtins import PoseidonBuiltin, BitwiseBuilti
from starkware.cairo.lang.compiler.lib.registers import get_fp_and_pc
from starkware.cairo.common.alloc import alloc
from starkware.cairo.common.dict_access import DictAccess
from starkware.cairo.common.math_cmp import is_le

// @notice Loads to the stack the value corresponding to a certain key from the
// storage of the current account.
Expand Down Expand Up @@ -95,3 +96,127 @@ func sload{range_check_ptr, bitwise_ptr: BitwiseBuiltin*, poseidon_ptr: Poseidon
let ok = cast(0, ExceptionalHalt*);
return ok;
}

// @notice Stores a value at a certain key in the current context's storage.
func sstore{
range_check_ptr, bitwise_ptr: BitwiseBuiltin*, poseidon_ptr: PoseidonBuiltin*, evm: Evm
}() -> ExceptionalHalt* {
alloc_locals;
// STACK
let stack = evm.value.stack;
with stack {
let (key, err) = pop();
if (cast(err, felt) != 0) {
return err;
}
let (new_value, err) = pop();
if (cast(err, felt) != 0) {
return err;
}
}

let is_gas_left_not_enough = is_le(evm.value.gas_left.value, GasConstants.GAS_CALL_STIPEND);
if (is_gas_left_not_enough != 0) {
tempvar err = new ExceptionalHalt(OutOfGasError);
return err;
}

// Get storage values
let key_bytes32 = U256_to_be_bytes(key);
let state = evm.value.env.value.state;
let current_target = evm.value.message.value.current_target;
with state {
let original_value = get_storage_original(current_target, key_bytes32);
let current_value = get_storage(current_target, key_bytes32);
}

// Gas calculation
// Check accessed storage keys
tempvar accessed_tuple = TupleAddressBytes32(
new TupleAddressBytes32Struct(current_target, key_bytes32)
);
let (serialized_keys: felt*) = alloc();
assert serialized_keys[0] = accessed_tuple.value.address.value;
assert serialized_keys[1] = accessed_tuple.value.bytes32.value.low;
assert serialized_keys[2] = accessed_tuple.value.bytes32.value.high;
let dict_ptr = cast(evm.value.accessed_storage_keys.value.dict_ptr, DictAccess*);
with dict_ptr {
let (is_present) = hashdict_read(3, serialized_keys);
if (is_present == 0) {
hashdict_write(3, serialized_keys, 1);
tempvar gas_cost = GasConstants.GAS_COLD_SLOAD;
tempvar poseidon_ptr = poseidon_ptr;
tempvar dict_ptr = dict_ptr;
} else {
tempvar gas_cost = 0;
tempvar poseidon_ptr = poseidon_ptr;
tempvar dict_ptr = dict_ptr;
}
}
let gas_cost = [ap - 3];
let poseidon_ptr = cast([ap - 2], PoseidonBuiltin*);
let dict_ptr = cast([ap - 1], DictAccess*);

// Calculate storage gas cost
if (original_value.value == current_value.value) {
if (current_value.value != new_value.value) {
if (original_value.value == 0) {
tempvar gas_cost = gas_cost + GasConstants.GAS_STORAGE_SET;
} else {
tempvar gas_cost = gas_cost + (
GasConstants.GAS_STORAGE_UPDATE - GasConstants.GAS_COLD_SLOAD
);
}
}
} else {
tempvar gas_cost = GasConstants.GAS_WARM_ACCESS;
}
let gas_cost = [ap - 1];

tempvar refund_counter = evm.value.refund_counter;
// Refund calculation
if (current_value.value != new_value.value) {
if (original_value.value != 0 and current_value.value != 0 and new_value.value == 0) {
// Storage cleared for first time
refund_counter = refund_counter + GasConstants.GAS_STORAGE_CLEAR_REFUND;
}
if (original_value.value != 0 and current_value.value == 0) {
// Reverse previous refund
refund_counter = refund_counter - GasConstants.GAS_STORAGE_CLEAR_REFUND;
}
if (original_value.value == new_value.value) {
if (original_value.value == 0) {
refund_counter = refund_counter +
(GasConstants.GAS_STORAGE_SET - GasConstants.GAS_WARM_ACCESS);
} else {
refund_counter = refund_counter +
(GasConstants.GAS_STORAGE_UPDATE - GasConstants.GAS_COLD_SLOAD - GasConstants.GAS_WARM_ACCESS);
}
}
}

// Charge gas
let err = charge_gas(Uint(gas_cost));
if (cast(err, felt) != 0) {
return err;
}
// Check static call
if (evm.value.message.value.is_static.value != 0) {
tempvar err = new ExceptionalHalt(WriteInStaticContext);
return err;
}

// Set storage
with state {
set_storage(current_target, key_bytes32, new_value);
}

// Update EVM state
let env = evm.value.env;
EnvImpl.set_state{env=env}(state);
EvmImpl.set_env(env);
EvmImpl.set_pc_stack(Uint(evm.value.pc.value + 1), stack);
EvmImpl.set_refund_counter(refund_counter);
let ok = cast(0, ExceptionalHalt*);
return ok;
}
13 changes: 12 additions & 1 deletion cairo/tests/ethereum/cancun/vm/instructions/test_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from hypothesis import strategies as st
from hypothesis.strategies import composite

from ethereum.cancun.vm.instructions.storage import sload
from ethereum.cancun.vm.instructions.storage import sload, sstore
from tests.utils.args_gen import Evm
from tests.utils.evm_builder import EvmBuilder
from tests.utils.strategies import MAX_STORAGE_KEY_SET_SIZE
Expand Down Expand Up @@ -46,3 +46,14 @@ def test_sload(self, cairo_run, evm: Evm):

sload(evm)
assert evm == cairo_evm

@given(evm=evm_with_accessed_storage_keys())
def test_sstore(self, cairo_run, evm: Evm):
try:
cairo_evm = cairo_run("sstore", evm)
except Exception as cairo_error:
with pytest.raises(type(cairo_error)):
sstore(evm)
return
sstore(evm)
assert evm == cairo_evm

0 comments on commit 3ecbad5

Please sign in to comment.