Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[KGA-87] fix: high offset memory expansion computations #1604

Merged
merged 3 commits into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 33 additions & 11 deletions cairo_zero/kakarot/gas.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from starkware.cairo.common.uint256 import Uint256, uint256_lt

from kakarot.model import model
from utils.uint256 import uint256_eq
from utils.uint256 import uint256_eq, uint256_add
from utils.utils import Helpers
from utils.maths import unsigned_div_rem

Expand Down Expand Up @@ -142,19 +142,41 @@
) -> model.MemoryExpansion {
alloc_locals;

let (is_zero_1) = uint256_eq([size_1], Uint256(0, 0));
let (is_zero_2) = uint256_eq([size_2], Uint256(0, 0));
tempvar both_zero = is_zero_1 * is_zero_2;
let (is_zero_size_1) = uint256_eq([size_1], Uint256(0, 0));
let (is_zero_size_2) = uint256_eq([size_2], Uint256(0, 0));
tempvar both_zero = is_zero_size_1 * is_zero_size_2;
jmp no_expansion if both_zero != 0;

tempvar is_not_saturated = Helpers.is_zero(offset_1.high) * Helpers.is_zero(size_1.high) *
Helpers.is_zero(offset_2.high) * Helpers.is_zero(size_2.high);
tempvar is_saturated = 1 - is_not_saturated;
tempvar range_check_ptr = range_check_ptr;
jmp expansion_cost_saturated if is_saturated != 0;
if (is_zero_size_1 == FALSE) {
let (max_offset_1_res, carry_1) = uint256_add([offset_1], [size_1]);
tempvar is_chunk_1_saturated = carry_1 + is_not_zero(max_offset_1_res.high);
tempvar max_offset_1 = max_offset_1_res.low;
tempvar range_check_ptr = range_check_ptr;

// Early jump if the chunk is saturated
jmp expansion_cost_saturated if is_chunk_1_saturated != 0;
} else {
tempvar max_offset_1 = 0;
tempvar range_check_ptr = range_check_ptr;

Check warning on line 160 in cairo_zero/kakarot/gas.cairo

View check run for this annotation

Codecov / codecov/patch

cairo_zero/kakarot/gas.cairo#L159-L160

Added lines #L159 - L160 were not covered by tests
}
let max_offset_1 = [ap - 2];
let range_check_ptr = [ap - 1];

Check warning on line 163 in cairo_zero/kakarot/gas.cairo

View check run for this annotation

Codecov / codecov/patch

cairo_zero/kakarot/gas.cairo#L163

Added line #L163 was not covered by tests

if (is_zero_size_2 == FALSE) {
let (max_offset_2_res, carry_2) = uint256_add([offset_2], [size_2]);
tempvar is_chunk_2_saturated = carry_2 + is_not_zero(max_offset_2_res.high);
tempvar max_offset_2 = max_offset_2_res.low;
tempvar range_check_ptr = range_check_ptr;

// Early jump if the chunk is saturated
jmp expansion_cost_saturated if is_chunk_2_saturated != 0;
} else {
tempvar max_offset_2 = 0;
tempvar range_check_ptr = range_check_ptr;

Check warning on line 175 in cairo_zero/kakarot/gas.cairo

View check run for this annotation

Codecov / codecov/patch

cairo_zero/kakarot/gas.cairo#L174-L175

Added lines #L174 - L175 were not covered by tests
}
let max_offset_2 = [ap - 2];
let range_check_ptr = [ap - 1];

let max_offset_1 = (1 - is_zero_1) * (offset_1.low + size_1.low);
let max_offset_2 = (1 - is_zero_2) * (offset_2.low + size_2.low);
let max_expansion_is_2 = is_le_felt(max_offset_1, max_offset_2);
let max_offset = max_offset_1 * (1 - max_expansion_is_2) + max_offset_2 *
max_expansion_is_2;
Expand Down
22 changes: 11 additions & 11 deletions cairo_zero/tests/src/kakarot/test_gas.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,14 @@ func test__max_memory_expansion_cost{range_check_ptr}() -> felt {
local size_2: Uint256;
%{
ids.words_len = program_input["words_len"];
ids.offset_1.low = program_input["offset_1"];
ids.offset_1.high = 0;
ids.size_1.low = program_input["size_1"];
ids.size_1.high = 0;
ids.offset_2.low = program_input["offset_2"];
ids.offset_2.high = 0;
ids.size_2.low = program_input["size_2"];
ids.size_2.high = 0;
ids.offset_1.low = program_input["offset_1"][0]
ids.offset_1.high = program_input["offset_1"][1]
ids.size_1.low = program_input["size_1"][0]
ids.size_1.high = program_input["size_1"][1]
ids.offset_2.low = program_input["offset_2"][0]
ids.offset_2.high = program_input["offset_2"][1]
ids.size_2.low = program_input["size_2"][0]
ids.size_2.high = program_input["size_2"][1]
%}
let memory_expansion = Gas.max_memory_expansion_cost(
words_len, &offset_1, &size_1, &offset_2, &size_2
Expand Down Expand Up @@ -74,9 +74,9 @@ func test__compute_message_call_gas{range_check_ptr}() -> felt {
tempvar gas_param: Uint256;
tempvar gas_left: felt;
%{
ids.gas_param.low = program_input["gas_param"];
ids.gas_param.high = 0;
ids.gas_left = program_input["gas_left"];
ids.gas_param.low = program_input["gas_param"][0]
ids.gas_param.high = program_input["gas_param"][1]
ids.gas_left = program_input["gas_left"]
%}
let gas = Gas.compute_message_call_gas(gas_param, gas_left);

Expand Down
46 changes: 28 additions & 18 deletions cairo_zero/tests/src/kakarot/test_gas.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
)
from hypothesis import given
from hypothesis.strategies import integers
from starkware.cairo.lang.cairo_constants import DEFAULT_PRIME

from kakarot_scripts.utils.uint256 import int_to_uint256


class TestGas:
Expand Down Expand Up @@ -33,32 +36,37 @@ def test_should_return_correct_expansion_cost(
assert diff == output

@given(
offset_1=integers(min_value=0, max_value=0xFFFFF),
size_1=integers(min_value=0, max_value=0xFFFFF),
offset_2=integers(min_value=0, max_value=0xFFFFF),
size_2=integers(min_value=0, max_value=0xFFFFF),
offset_1=integers(min_value=0, max_value=DEFAULT_PRIME - 1),
size_1=integers(min_value=0, max_value=DEFAULT_PRIME - 1),
offset_2=integers(min_value=0, max_value=DEFAULT_PRIME - 1),
size_2=integers(min_value=0, max_value=DEFAULT_PRIME - 1),
)
def test_should_return_max_expansion_cost(
self, cairo_run, offset_1, size_1, offset_2, size_2
):
memory_cost_u32 = calculate_memory_gas_cost(2**32 - 1)
output = cairo_run(
"test__max_memory_expansion_cost",
words_len=0,
offset_1=offset_1,
size_1=size_1,
offset_2=offset_2,
size_2=size_2,
offset_1=int_to_uint256(offset_1),
size_1=int_to_uint256(size_1),
offset_2=int_to_uint256(offset_2),
size_2=int_to_uint256(size_2),
)
expansion = calculate_gas_extend_memory(
b"",
[
(offset_1, size_1),
(offset_2, size_2),
],
)
assert (
output
== calculate_gas_extend_memory(
b"",
[
(offset_1, size_1),
(offset_2, size_2),
],
).cost

# If the memory expansion is greater than 2**27 words of 32 bytes
# We saturate it to the hardcoded value corresponding the the gas cost of a 2**32 memory size
expected_saturated = (
memory_cost_u32 if expansion.expand_by >= 2**32 else expansion.cost
)
assert output == expected_saturated

@given(
offset=integers(min_value=0, max_value=2**256 - 1),
Expand Down Expand Up @@ -94,6 +102,8 @@ def test_should_return_message_base_gas(
self, cairo_run, gas_param, gas_left, expected
):
output = cairo_run(
"test__compute_message_call_gas", gas_param=gas_param, gas_left=gas_left
"test__compute_message_call_gas",
gas_param=int_to_uint256(gas_param),
gas_left=gas_left,
)
assert output == expected
15 changes: 8 additions & 7 deletions cairo_zero/tests/src/utils/test_bytes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@
import pytest
from hypothesis import given
from hypothesis.strategies import integers
from starkware.cairo.lang.cairo_constants import DEFAULT_PRIME

from kakarot_scripts.utils.uint256 import int_to_uint256
from tests.utils.errors import cairo_error
from tests.utils.hints import patch_hint

PRIME = 0x800000000000011000000000000000000000000000000000000000000000001


class TestBytes:
class TestFeltToAscii:
Expand All @@ -29,7 +28,7 @@ def test_should_return_bytes(self, cairo_run, n):
)
assert expected == bytes(output)

@given(n=integers(min_value=2**248, max_value=PRIME - 1))
@given(n=integers(min_value=2**248, max_value=DEFAULT_PRIME - 1))
def test_should_raise_when_value_sup_31_bytes(self, cairo_run, n):
with cairo_error(message="felt_to_bytes_little: value >= 2**248"):
cairo_run("test__felt_to_bytes_little", n=n)
Expand Down Expand Up @@ -100,14 +99,16 @@ def test_should_return_bytes(self, cairo_run, n):
assert bytes.fromhex(f"{n:x}".rjust(len(res) * 2, "0")) == res

class TestFeltToBytes20:
@pytest.mark.parametrize("n", [0, 10, 1234, 0xFFFFFF, 2**128, PRIME - 1])
@pytest.mark.parametrize(
"n", [0, 10, 1234, 0xFFFFFF, 2**128, DEFAULT_PRIME - 1]
)
def test_should_return_bytes20(self, cairo_run, n):
output = cairo_run("test__felt_to_bytes20", n=n)
assert f"{n:064x}"[-40:] == bytes(output).hex()

class TestUint256ToBytesLittle:
@pytest.mark.parametrize(
"n", [0, 10, 1234, 0xFFFFFF, 2**128, PRIME - 1, 2**256 - 1]
"n", [0, 10, 1234, 0xFFFFFF, 2**128, DEFAULT_PRIME - 1, 2**256 - 1]
)
def test_should_return_bytes(self, cairo_run, n):
output = cairo_run("test__uint256_to_bytes_little", n=int_to_uint256(n))
Expand All @@ -116,7 +117,7 @@ def test_should_return_bytes(self, cairo_run, n):

class TestUint256ToBytes:
@pytest.mark.parametrize(
"n", [0, 10, 1234, 0xFFFFFF, 2**128, PRIME - 1, 2**256 - 1]
"n", [0, 10, 1234, 0xFFFFFF, 2**128, DEFAULT_PRIME - 1, 2**256 - 1]
)
def test_should_return_bytes(self, cairo_run, n):
output = cairo_run("test__uint256_to_bytes", n=int_to_uint256(n))
Expand All @@ -125,7 +126,7 @@ def test_should_return_bytes(self, cairo_run, n):

class TestUint256ToBytes32:
@pytest.mark.parametrize(
"n", [0, 10, 1234, 0xFFFFFF, 2**128, PRIME - 1, 2**256 - 1]
"n", [0, 10, 1234, 0xFFFFFF, 2**128, DEFAULT_PRIME - 1, 2**256 - 1]
)
def test_should_return_bytes(self, cairo_run, n):
output = cairo_run("test__uint256_to_bytes32", n=int_to_uint256(n))
Expand Down
Loading