diff --git a/cairo_zero/kakarot/gas.cairo b/cairo_zero/kakarot/gas.cairo index c4867f5ef..fe4f97c75 100644 --- a/cairo_zero/kakarot/gas.cairo +++ b/cairo_zero/kakarot/gas.cairo @@ -4,7 +4,7 @@ from starkware.cairo.common.bool import FALSE 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 @@ -142,19 +142,41 @@ namespace Gas { ) -> 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; + } + let max_offset_1 = [ap - 2]; + let range_check_ptr = [ap - 1]; + + 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; + } + 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; diff --git a/cairo_zero/tests/src/kakarot/test_gas.cairo b/cairo_zero/tests/src/kakarot/test_gas.cairo index 80b68d8fc..20e2da318 100644 --- a/cairo_zero/tests/src/kakarot/test_gas.cairo +++ b/cairo_zero/tests/src/kakarot/test_gas.cairo @@ -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 @@ -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); diff --git a/cairo_zero/tests/src/kakarot/test_gas.py b/cairo_zero/tests/src/kakarot/test_gas.py index 8b69d5173..272e86c11 100644 --- a/cairo_zero/tests/src/kakarot/test_gas.py +++ b/cairo_zero/tests/src/kakarot/test_gas.py @@ -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: @@ -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), @@ -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 diff --git a/cairo_zero/tests/src/utils/test_bytes.py b/cairo_zero/tests/src/utils/test_bytes.py index ae6025522..10c47ba6d 100644 --- a/cairo_zero/tests/src/utils/test_bytes.py +++ b/cairo_zero/tests/src/utils/test_bytes.py @@ -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: @@ -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) @@ -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)) @@ -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)) @@ -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))