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

feat: chain_id storage var #1571

Merged
merged 5 commits into from
Nov 6, 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
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,13 @@ on an underlying StarknetOS network.
It is **not** a description on how to deploy a solidity contract on the Kakarot
EVM.

Note that the chosen `chain_id` when deploying is important:

- To keep compatibility with metamask the max chain id is 4503599627370476 see
https://gist.github.com/rekmarks/a47bd5f2525936c4b8eee31a16345553
- To be compatible with ledger the chain id needs to be inferior to 4 bytes see
https://github.com/kkrt-labs/kakarot/issues/1530

The [deploy script](./kakarot_scripts/deploy_kakarot.py) relies on some env
variables defined in a `.env` file located at the root of the project and loaded
in the [constant file](./kakarot_scripts/constants.py). To get started, just
Expand Down
3 changes: 0 additions & 3 deletions cairo_zero/kakarot/constants.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@ namespace Constants {
const EMPTY_CODE_HASH_HIGH = 0xc5d2460186f7233c927e7db2dcc703c0;
const BURN_ADDRESS = 0xdead;

// See https://gist.github.com/rekmarks/a47bd5f2525936c4b8eee31a16345553
const MAX_SAFE_CHAIN_ID = 4503599627370476;

// PRECOMPILES

// Rollup precompiles
Expand Down
12 changes: 12 additions & 0 deletions cairo_zero/kakarot/kakarot.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,16 @@ func unpause{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}()
return ();
}

// @notice chain_id initializer
@external
func initialize_chain_id{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
chain_id: felt
) {
Ownable.assert_only_owner();
Kakarot.initialize_chain_id(chain_id);
return ();
}

// Constructor
@constructor
func constructor{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
Expand All @@ -79,6 +89,7 @@ func constructor{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr
uninitialized_account_class_hash: felt,
cairo1_helpers_class_hash: felt,
block_gas_limit: felt,
chain_id: felt,
) {
return Kakarot.constructor(
owner,
Expand All @@ -87,6 +98,7 @@ func constructor{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr
uninitialized_account_class_hash,
cairo1_helpers_class_hash,
block_gas_limit,
chain_id,
);
}

Expand Down
20 changes: 18 additions & 2 deletions cairo_zero/kakarot/library.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ from kakarot.storages import (
Kakarot_coinbase,
Kakarot_prev_randao,
Kakarot_block_gas_limit,
Kakarot_chain_id,
Kakarot_evm_to_starknet_address,
Kakarot_authorized_cairo_precompiles_callers,
Kakarot_l1_messaging_contract_address,
Expand All @@ -45,20 +46,23 @@ namespace Kakarot {
// @param uninitialized_account_class_hash The class hash of the uninitialized account used for deterministic address calculation.
// @param cairo1_helpers_class_hash The precompiles class hash for precompiles not implemented in Kakarot.
// @param block_gas_limit The block gas limit.
// @param chain_id The chain ID.
func constructor{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
owner: felt,
native_token_address,
account_contract_class_hash,
uninitialized_account_class_hash,
cairo1_helpers_class_hash,
block_gas_limit,
chain_id: felt,
) {
Ownable.initializer(owner);
Kakarot_native_token_address.write(native_token_address);
Kakarot_account_contract_class_hash.write(account_contract_class_hash);
Kakarot_uninitialized_account_class_hash.write(uninitialized_account_class_hash);
Kakarot_cairo1_helpers_class_hash.write(cairo1_helpers_class_hash);
Kakarot_block_gas_limit.write(block_gas_limit);
Kakarot_chain_id.write(chain_id);
return ();
}

Expand Down Expand Up @@ -125,8 +129,7 @@ namespace Kakarot {
func eth_chain_id{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (
chain_id: felt
) {
let (tx_info) = get_tx_info();
let (_, chain_id) = unsigned_div_rem(tx_info.chain_id, Constants.MAX_SAFE_CHAIN_ID);
let (chain_id) = Kakarot_chain_id.read();
return (chain_id=chain_id);
}

Expand Down Expand Up @@ -410,4 +413,17 @@ namespace Kakarot {
0, l1_sender, to, 2100000000, 1, value_u256, data_len, data, 0, access_list
);
}

// @notice Initialize the chain ID
// @param chain_id The chain ID
func initialize_chain_id{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
chain_id: felt
) {
with_attr error_message("Kakarot: chain_id already initialized") {
let (current_chain_id) = Kakarot_chain_id.read();
assert current_chain_id = 0;
}
Kakarot_chain_id.write(chain_id);
return ();
}
}
4 changes: 4 additions & 0 deletions cairo_zero/kakarot/storages.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ func Kakarot_prev_randao() -> (res: Uint256) {
func Kakarot_block_gas_limit() -> (res: felt) {
}

@storage_var
func Kakarot_chain_id() -> (res: felt) {
}

@storage_var
func Kakarot_authorized_cairo_precompiles_callers(address: felt) -> (res: felt) {
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class TestBlockInformation:
)
@SyscallHandler.patch("Kakarot_coinbase", COINBASE)
@SyscallHandler.patch("Kakarot_block_gas_limit", BLOCK_GAS_LIMIT)
@SyscallHandler.patch("Kakarot_chain_id", CHAIN_ID)
@patch.object(SyscallHandler, "block_timestamp", 0x1234)
def test__exec_block_information(self, cairo_run, opcode, expected_result):
output = cairo_run("test__exec_block_information", opcode=opcode)
Expand Down
8 changes: 8 additions & 0 deletions cairo_zero/tests/src/kakarot/test_kakarot.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ from kakarot.kakarot import (
handle_l1_message,
pause,
unpause,
initialize_chain_id,
)
from kakarot.model import model
from kakarot.account import Account
Expand Down Expand Up @@ -280,3 +281,10 @@ func test__unpause{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_p
unpause();
return ();
}

func test__initialize_chain_id{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() {
tempvar chain_id;
%{ ids.chain_id = program_input["chain_id"] %}
initialize_chain_id(chain_id);
return ();
}
55 changes: 41 additions & 14 deletions cairo_zero/tests/src/kakarot/test_kakarot.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,7 @@
from web3.exceptions import NoABIFunctionsFound

from kakarot_scripts.ef_tests.fetch import EF_TESTS_PARSED_DIR
from tests.utils.constants import (
CHAIN_ID,
MAX_SAFE_CHAIN_ID,
TRANSACTION_GAS_LIMIT,
TRANSACTIONS,
)
from tests.utils.constants import CHAIN_ID, TRANSACTION_GAS_LIMIT, TRANSACTIONS
from tests.utils.errors import cairo_error
from tests.utils.helpers import felt_to_signed_int, rlp_encode_signed_data
from tests.utils.syscall_handler import SyscallHandler, parse_state
Expand Down Expand Up @@ -97,7 +92,6 @@ def test_should_pause(self, cairo_run):
)

class TestUnpause:

@SyscallHandler.patch("Ownable_owner", 0xDEAD)
def test_should_assert_only_owner(self, cairo_run):
with cairo_error(message="Ownable: caller is not the owner"):
Expand Down Expand Up @@ -185,6 +179,31 @@ def test_should_set_prev_randao(self, cairo_run):
value=prev_randao,
)

class TestInitializeChainId:
@SyscallHandler.patch("Ownable_owner", 0xDEAD)
def test_should_assert_only_owner(self, cairo_run):
with cairo_error(message="Ownable: caller is not the owner"):
cairo_run("test__initialize_chain_id", chain_id=0xABC)

@SyscallHandler.patch("Ownable_owner", SyscallHandler.caller_address)
def test_should_initialize_chain_id(self, cairo_run):
chain_id = 0x123

cairo_run("test__initialize_chain_id", chain_id=chain_id)
SyscallHandler.mock_storage.assert_any_call(
address=get_storage_var_address("Kakarot_chain_id"),
value=chain_id,
)

@SyscallHandler.patch("Ownable_owner", SyscallHandler.caller_address)
def test_should_fail_initialize_chain_id_twice(self, cairo_run):
chain_id = 0x123
with (
cairo_error(message="Kakarot: chain_id already initialized"),
SyscallHandler.patch("Kakarot_chain_id", chain_id),
):
cairo_run("test__initialize_chain_id", chain_id=chain_id)

class TestBlockGasLimit:
@SyscallHandler.patch("Ownable_owner", 0xDEAD)
def test_should_assert_only_owner(self, cairo_run):
Expand Down Expand Up @@ -521,12 +540,13 @@ def test_create_tx_returndata_should_be_20_bytes_evm_address(self, cairo_run):

class TestEthChainIdEntrypoint:
@given(chain_id=integers(min_value=0, max_value=2**64 - 1))
def test_should_return_chain_id_modulo_max_safe_chain_id(
self, cairo_run, chain_id
):
with patch.dict(SyscallHandler.tx_info, {"chain_id": chain_id}):
def test_should_return_chain_id(self, cairo_run, chain_id):
with (
patch.dict(SyscallHandler.tx_info, {"chain_id": chain_id}),
SyscallHandler.patch("Kakarot_chain_id", chain_id),
):
res = cairo_run("test__eth_chain_id")
assert res == chain_id % MAX_SAFE_CHAIN_ID
assert res == chain_id

class TestEthSendRawTransactionEntrypoint:
@SyscallHandler.patch("Pausable_paused", 1)
Expand Down Expand Up @@ -559,6 +579,7 @@ def test_should_raise_invalid_chain_id_tx_type_different_from_0(
)

@SyscallHandler.patch("IAccount.get_nonce", lambda addr, data: [1])
@SyscallHandler.patch("Kakarot_chain_id", CHAIN_ID)
@pytest.mark.parametrize("tx", TRANSACTIONS)
def test_should_raise_invalid_nonce(self, cairo_run, tx):
# explicitly set the nonce in transaction to be different from the patch
Expand All @@ -571,8 +592,9 @@ def test_should_raise_invalid_nonce(self, cairo_run, tx):
tx_data=tx_data,
)

@given(gas_limit=integers(min_value=2**64, max_value=2**248 - 1))
@SyscallHandler.patch("Kakarot_chain_id", CHAIN_ID)
@SyscallHandler.patch("IAccount.get_nonce", lambda _, __: [34])
@given(gas_limit=integers(min_value=2**64, max_value=2**248 - 1))
def test_raise_gas_limit_too_high(self, cairo_run, gas_limit):
tx = {
"type": 2,
Expand All @@ -595,8 +617,9 @@ def test_raise_gas_limit_too_high(self, cairo_run, gas_limit):
tx_data=tx_data,
)

@given(maxFeePerGas=integers(min_value=2**128, max_value=2**248 - 1))
@SyscallHandler.patch("Kakarot_chain_id", CHAIN_ID)
@SyscallHandler.patch("IAccount.get_nonce", lambda _, __: [34])
@given(maxFeePerGas=integers(min_value=2**128, max_value=2**248 - 1))
def test_raise_max_fee_per_gas_too_high(self, cairo_run, maxFeePerGas):
tx = {
"type": 2,
Expand All @@ -619,6 +642,7 @@ def test_raise_max_fee_per_gas_too_high(self, cairo_run, maxFeePerGas):
tx_data=tx_data,
)

@SyscallHandler.patch("Kakarot_chain_id", CHAIN_ID)
@pytest.mark.parametrize("tx", TRANSACTIONS)
def test_raise_transaction_gas_limit_too_high(self, cairo_run, tx):
tx_data = list(rlp_encode_signed_data(tx))
Expand All @@ -635,6 +659,7 @@ def test_raise_transaction_gas_limit_too_high(self, cairo_run, tx):

@SyscallHandler.patch("Kakarot_block_gas_limit", TRANSACTION_GAS_LIMIT)
@SyscallHandler.patch("Kakarot_base_fee", TRANSACTION_GAS_LIMIT * 10**10)
@SyscallHandler.patch("Kakarot_chain_id", CHAIN_ID)
@pytest.mark.parametrize("tx", TRANSACTIONS)
def test_raise_max_fee_per_gas_too_low(self, cairo_run, tx):
tx_data = list(rlp_encode_signed_data(tx))
Expand All @@ -659,6 +684,7 @@ def max_priority_fee_too_high(draw):

@SyscallHandler.patch("Kakarot_block_gas_limit", TRANSACTION_GAS_LIMIT)
@SyscallHandler.patch("IAccount.get_nonce", lambda _, __: [34])
@SyscallHandler.patch("Kakarot_chain_id", CHAIN_ID)
@given(max_priority_fee_too_high())
def test_raise_max_priority_fee_too_high(
self, cairo_run, max_priority_fee_too_high
Expand Down Expand Up @@ -687,6 +713,7 @@ def test_raise_max_priority_fee_too_high(
@SyscallHandler.patch("IERC20.balanceOf", lambda _, __: [0, 0])
@SyscallHandler.patch("Kakarot_block_gas_limit", TRANSACTION_GAS_LIMIT)
@SyscallHandler.patch("IAccount.get_evm_address", lambda _, __: [0xABDE1])
@SyscallHandler.patch("Kakarot_chain_id", CHAIN_ID)
@pytest.mark.parametrize("tx", TRANSACTIONS)
def test_raise_not_enough_ETH_balance(self, cairo_run, tx):
tx_data = list(rlp_encode_signed_data(tx))
Expand Down
12 changes: 8 additions & 4 deletions kakarot_scripts/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@
BLOCK_GAS_LIMIT = 7_000_000
DEFAULT_GAS_PRICE = 1
BEACON_ROOT_ADDRESS = "0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02"
# see https://gist.github.com/rekmarks/a47bd5f2525936c4b8eee31a16345553
MAX_SAFE_CHAIN_ID = 4503599627370476

# See https://github.com/kkrt-labs/kakarot/issues/1530
MAX_LEDGER_CHAIN_ID = 2**32 - 1


class NetworkType(Enum):
Expand Down Expand Up @@ -206,7 +207,8 @@ class NetworkType(Enum):
if WEB3.is_connected():
chain_id = WEB3.eth.chain_id
else:
chain_id = starknet_chain_id % MAX_SAFE_CHAIN_ID
# Before making any changes to chain_id see https://github.com/kkrt-labs/kakarot/issues/1530
chain_id = starknet_chain_id % MAX_LEDGER_CHAIN_ID
except (
requests.exceptions.ConnectionError,
requests.exceptions.MissingSchema,
Expand All @@ -216,14 +218,16 @@ class NetworkType(Enum):
f"⚠️ Could not get chain Id from {NETWORK['rpc_url']}: {e}, defaulting to KKRT"
)
starknet_chain_id = int.from_bytes(b"KKRT", "big")
chain_id = starknet_chain_id % MAX_SAFE_CHAIN_ID
# Before making any changes to chain_id see https://github.com/kkrt-labs/kakarot/issues/1530
chain_id = starknet_chain_id % MAX_LEDGER_CHAIN_ID


class ChainId(IntEnum):
chain_id = chain_id
starknet_chain_id = starknet_chain_id


# Before making any changes to chain_id see https://github.com/kkrt-labs/kakarot/issues/1530
NETWORK["chain_id"] = ChainId.chain_id

ETH_TOKEN_ADDRESS = 0x49D36570D4E46F48E99674BD3FCC84644DDD6B96F7C741B1562B82F9E004DC7
Expand Down
1 change: 1 addition & 0 deletions kakarot_scripts/deployment/kakarot_deployment.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ async def deploy_or_upgrade_kakarot(owner):
class_hash["uninitialized_account"], # uninitialized_account_class_hash_
class_hash["Cairo1Helpers"],
BLOCK_GAS_LIMIT,
NETWORK["chain_id"],
)
await invoke(
"kakarot",
Expand Down
1 change: 1 addition & 0 deletions kakarot_scripts/deployment/starknet_deployments.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ async def deploy_starknet_contracts(account):
class_hash["uninitialized_account"],
class_hash["Cairo1Helpers"],
BLOCK_GAS_LIMIT,
NETWORK["chain_id"],
)
try:
coinbase = (
Expand Down
3 changes: 1 addition & 2 deletions tests/utils/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@

import pytest

from kakarot_scripts.constants import BLOCK_GAS_LIMIT, MAX_SAFE_CHAIN_ID
from kakarot_scripts.constants import BLOCK_GAS_LIMIT

BLOCK_GAS_LIMIT = BLOCK_GAS_LIMIT
MIN_BASE_FEE_PER_BLOB_GAS = 1

CHAIN_ID = int.from_bytes(b"KKRT", "big") # KKRT (0x4b4b5254) in ASCII
BIG_CHAIN_ID = int.from_bytes(b"SN_SEPOLIA", "big")
MAX_SAFE_CHAIN_ID = MAX_SAFE_CHAIN_ID

# Class hash of the cairo1 helpers
CAIRO1_HELPERS_CLASS_HASH = 0xDEADBEEFABDE1E11A5
Expand Down
Loading