Skip to content
This repository has been archived by the owner on Jan 9, 2025. It is now read-only.

fix: overflow check memory operations charge gas #1381

Merged
merged 10 commits into from
Sep 4, 2024
52 changes: 32 additions & 20 deletions src/kakarot/instructions/environmental_information.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -178,13 +178,17 @@ namespace EnvironmentalInformation {
// Any size upper than 2**128 will cause an OOG error, considering the maximum gas for a transaction.
let upper_bytes_bound = size.low + 31;
let (words, _) = unsigned_div_rem(upper_bytes_bound, 32);
let copy_gas_cost_low = words * Gas.COPY;
tempvar copy_gas_cost_high = is_not_zero(size.high) * 2 ** 128;
let copy_gas_cost = words * Gas.COPY;

local gas_to_charge: felt;
if (size.high == 0) {
assert gas_to_charge = memory_expansion.cost + copy_gas_cost;
} else {
assert gas_to_charge = Gas.MEMORY_COST_U128;
}

let evm = EVM.charge_gas(evm, gas_to_charge);

// static cost handled in jump table
let evm = EVM.charge_gas(
evm, memory_expansion.cost + copy_gas_cost_low + copy_gas_cost_high
);
if (evm.reverted != FALSE) {
return evm;
}
Expand Down Expand Up @@ -259,13 +263,17 @@ namespace EnvironmentalInformation {
// Any size upper than 2**128 will cause an OOG error, considering the maximum gas for a transaction.
let upper_bytes_bound = size.low + 31;
let (words, _) = unsigned_div_rem(upper_bytes_bound, 32);
let copy_gas_cost_low = words * Gas.COPY;
tempvar copy_gas_cost_high = is_not_zero(size.high) * 2 ** 128;
let copy_gas_cost = words * Gas.COPY;

local gas_to_charge: felt;
if (size.high == 0) {
assert gas_to_charge = memory_expansion.cost + copy_gas_cost;
} else {
assert gas_to_charge = Gas.MEMORY_COST_U128;
}

let evm = EVM.charge_gas(evm, gas_to_charge);

// static cost handled in jump table
let evm = EVM.charge_gas(
evm, memory_expansion.cost + copy_gas_cost_low + copy_gas_cost_high
);
if (evm.reverted != FALSE) {
return evm;
}
Expand Down Expand Up @@ -393,19 +401,23 @@ namespace EnvironmentalInformation {
tempvar access_gas_cost = is_warm * Gas.WARM_ACCESS + (1 - is_warm) *
Gas.COLD_ACCOUNT_ACCESS;

let memory_expansion = Gas.memory_expansion_cost_saturated(
memory.words_len, dest_offset, size
);
// Any size upper than 2**128 will cause an OOG error, considering the maximum gas for a transaction.
let upper_bytes_bound = size.low + 31;
let (words, _) = unsigned_div_rem(upper_bytes_bound, 32);
let copy_gas_cost_low = words * Gas.COPY;
tempvar copy_gas_cost_high = is_not_zero(size.high) * 2 ** 128;
let copy_gas_cost = words * Gas.COPY;

let memory_expansion = Gas.memory_expansion_cost_saturated(
memory.words_len, dest_offset, size
);
local gas_to_charge: felt;
if (size.high == 0) {
assert gas_to_charge = memory_expansion.cost + copy_gas_cost + access_gas_cost;
} else {
assert gas_to_charge = Gas.MEMORY_COST_U128;
}

let evm = EVM.charge_gas(evm, gas_to_charge);

let evm = EVM.charge_gas(
evm, access_gas_cost + copy_gas_cost_low + copy_gas_cost_high + memory_expansion.cost
);
if (evm.reverted != FALSE) {
return evm;
}
Expand Down
17 changes: 11 additions & 6 deletions src/kakarot/instructions/memory_operations.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from starkware.cairo.common.alloc import alloc
from starkware.cairo.common.bool import FALSE, TRUE
from starkware.cairo.common.cairo_builtins import HashBuiltin, BitwiseBuiltin
from starkware.cairo.common.uint256 import Uint256
from starkware.cairo.common.uint256 import Uint256, felt_to_uint256, uint256_add
obatirou marked this conversation as resolved.
Show resolved Hide resolved
from starkware.cairo.common.math_cmp import is_nn, is_not_zero

from kakarot.errors import Errors
Expand Down Expand Up @@ -110,12 +110,17 @@ namespace MemoryOperations {
// Any size upper than 2**128 will cause an OOG error, considering the maximum gas for a transaction.
let upper_bytes_bound = size.low + 31;
let (words, _) = unsigned_div_rem(upper_bytes_bound, 32);
let copy_gas_cost_low = words * Gas.COPY;
tempvar copy_gas_cost_high = is_not_zero(size.high) * 2 ** 128;
let copy_gas_cost = words * Gas.COPY;

local gas_to_charge: felt;
if (size.high == 0) {
assert gas_to_charge = memory_expansion.cost + copy_gas_cost;
} else {
assert gas_to_charge = Gas.MEMORY_COST_U128;
}

let evm = EVM.charge_gas(evm, gas_to_charge);

let evm = EVM.charge_gas(
evm, memory_expansion.cost + copy_gas_cost_low + copy_gas_cost_high
);
if (evm.reverted != FALSE) {
return evm;
}
Expand Down
23 changes: 19 additions & 4 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import pytest
from dotenv import load_dotenv
from hypothesis import Verbosity, settings
from hypothesis import Phase, Verbosity, settings
from starkware.cairo.lang.instances import LAYOUTS

load_dotenv()
Expand Down Expand Up @@ -59,8 +59,23 @@ def seed(request):

pytest_plugins = ["tests.fixtures.starknet"]

settings.register_profile("ci", deadline=None, max_examples=1000)
settings.register_profile("dev", deadline=None, max_examples=10)
settings.register_profile("debug", max_examples=10, verbosity=Verbosity.verbose)
settings.register_profile(
"ci",
deadline=None,
max_examples=1000,
phases=[Phase.explicit, Phase.reuse, Phase.generate, Phase.target],
)
settings.register_profile(
"dev",
deadline=None,
max_examples=10,
phases=[Phase.explicit, Phase.reuse, Phase.generate, Phase.target],
)
settings.register_profile(
"debug",
max_examples=10,
verbosity=Verbosity.verbose,
phases=[Phase.explicit, Phase.reuse, Phase.generate, Phase.target],
)
settings.load_profile(os.getenv("HYPOTHESIS_PROFILE", "default"))
logger.info(f"Using Hypothesis profile: {os.getenv('HYPOTHESIS_PROFILE', 'default')}")
37 changes: 36 additions & 1 deletion tests/src/kakarot/instructions/test_memory_operations.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,6 @@ func test__exec_mload_should_load_a_value_from_memory_with_offset_larger_than_ms
let (bytecode) = alloc();
let evm = TestHelpers.init_evm_with_bytecode(0, bytecode);
let test_offset = 684;
// Given
let stack = Stack.init();
let state = State.init();
let memory = Memory.init();
Expand All @@ -169,3 +168,39 @@ func test__exec_mload_should_load_a_value_from_memory_with_offset_larger_than_ms
assert memory.words_len = 23;
return ();
}

func test__exec_mcopy{
syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr, bitwise_ptr: BitwiseBuiltin*
}() -> (model.EVM*, model.Memory*) {
alloc_locals;
let (memory_init_state) = alloc();
local memory_init_state_len: felt;
let (size_mcopy_ptr) = alloc();
let (src_offset_mcopy_ptr) = alloc();
let (dst_offset_mcopy_ptr) = alloc();

%{
ids.memory_init_state_len = len(program_input["memory_init_state"])
segments.write_arg(ids.memory_init_state, program_input["memory_init_state"])
segments.write_arg(ids.size_mcopy_ptr, program_input["size_mcopy"])
segments.write_arg(ids.src_offset_mcopy_ptr, program_input["src_offset_mcopy"])
segments.write_arg(ids.dst_offset_mcopy_ptr, program_input["dst_offset_mcopy"])
%}

let size_mcopy = cast(size_mcopy_ptr, Uint256*);
let src_offset_mcopy = cast(src_offset_mcopy_ptr, Uint256*);
let dst_offset_mcopy = cast(dst_offset_mcopy_ptr, Uint256*);

let evm = TestHelpers.init_evm();
let stack = Stack.init();
let state = State.init();
let memory = TestHelpers.init_memory_with_values(memory_init_state_len, memory_init_state);

with stack, memory, state {
Stack.push(size_mcopy);
Stack.push(src_offset_mcopy);
Stack.push(dst_offset_mcopy);
let evm = MemoryOperations.exec_mcopy(evm);
}
return (evm, memory);
}
75 changes: 75 additions & 0 deletions tests/src/kakarot/instructions/test_memory_operations.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import pytest
from hypothesis import given
from hypothesis.strategies import integers, lists
from starkware.cairo.lang.cairo_constants import DEFAULT_PRIME

from kakarot_scripts.utils.uint256 import int_to_uint256


class TestMemoryOperations:
Expand Down Expand Up @@ -28,3 +33,73 @@ def test_should_load_a_value_from_memory_with_offset_larger_than_msize(
cairo_run(
"test__exec_mload_should_load_a_value_from_memory_with_offset_larger_than_msize"
)

class TestMcopy:

@given(
memory_init_state=lists(
integers(min_value=0, max_value=255), min_size=1, max_size=100
),
obatirou marked this conversation as resolved.
Show resolved Hide resolved
size_mcopy=integers(min_value=1, max_value=100),
src_offset_mcopy=integers(min_value=0, max_value=100),
dst_offset_mcopy=integers(min_value=0, max_value=100),
)
def test_should_copy_a_value_from_memory(
self,
cairo_run,
memory_init_state,
size_mcopy,
src_offset_mcopy,
dst_offset_mcopy,
):
(evm, memory) = cairo_run(
"test__exec_mcopy",
memory_init_state=memory_init_state,
size_mcopy=int_to_uint256(size_mcopy),
src_offset_mcopy=int_to_uint256(src_offset_mcopy),
dst_offset_mcopy=int_to_uint256(dst_offset_mcopy),
)
memory_init_state_expansion = memory_init_state + [0] * (
max(src_offset_mcopy, dst_offset_mcopy)
+ size_mcopy
- len(memory_init_state)
)
segment_to_copy = memory_init_state_expansion[
src_offset_mcopy : src_offset_mcopy + size_mcopy
]
expected_memory_state = (
memory_init_state_expansion[:dst_offset_mcopy]
+ segment_to_copy
+ memory_init_state_expansion[dst_offset_mcopy + size_mcopy :]
)
words_len = (len(expected_memory_state) + 31) // 32
expected_memory_state = "".join(
[f"{byte:02x}" for byte in expected_memory_state]
) + "00" * (words_len * 32 - len(expected_memory_state))
ClementWalter marked this conversation as resolved.
Show resolved Hide resolved
assert memory == expected_memory_state
obatirou marked this conversation as resolved.
Show resolved Hide resolved

@given(
memory_init_state=lists(
integers(min_value=0, max_value=255), min_size=1, max_size=100
),
obatirou marked this conversation as resolved.
Show resolved Hide resolved
size_mcopy=integers(min_value=2**128 - 31, max_value=DEFAULT_PRIME - 1),
src_offset_mcopy=integers(min_value=0, max_value=100),
dst_offset_mcopy=integers(min_value=0, max_value=100),
)
def test_should_fail_if_memory_expansion_to_large(
self,
cairo_run,
memory_init_state,
size_mcopy,
src_offset_mcopy,
dst_offset_mcopy,
):
(evm, memory) = cairo_run(
"test__exec_mcopy",
memory_init_state=memory_init_state,
size_mcopy=int_to_uint256(size_mcopy),
src_offset_mcopy=int_to_uint256(src_offset_mcopy),
dst_offset_mcopy=int_to_uint256(dst_offset_mcopy),
)
assert evm["reverted"] == 2
assert b"Kakarot: outOfGas left" in bytes(evm["return_data"])
Loading