diff --git a/scripts/constants.py b/scripts/constants.py index 5d135b3dd..46103f130 100644 --- a/scripts/constants.py +++ b/scripts/constants.py @@ -157,7 +157,6 @@ class ArtifactType(Enum): {"contract_name": "Precompiles", "cairo_version": ArtifactType.cairo1}, ] -KAKAROT_CHAIN_ID = 1263227476 # KKRT (0x4b4b5254) in ASCII EVM_PRIVATE_KEY = os.getenv("EVM_PRIVATE_KEY") EVM_ADDRESS = ( EVM_PRIVATE_KEY diff --git a/scripts/deploy_kakarot.py b/scripts/deploy_kakarot.py index 1f12834f2..448eda3c5 100644 --- a/scripts/deploy_kakarot.py +++ b/scripts/deploy_kakarot.py @@ -59,6 +59,7 @@ async def main(): ETH_TOKEN_ADDRESS, # native_token_address_ class_hash["contract_account"], # contract_account_class_hash_ class_hash["proxy"], # account_proxy_class_hash + class_hash["Precompiles"], ) deployments["deployer_account"] = await deploy_starknet_account( class_hash["OpenzeppelinAccount"], private_key=DEPLOYER_ACCOUNT_PRIVATE_KEY diff --git a/scripts/utils/kakarot.py b/scripts/utils/kakarot.py index 06bf3ec1e..d5de4aa98 100644 --- a/scripts/utils/kakarot.py +++ b/scripts/utils/kakarot.py @@ -28,13 +28,7 @@ from web3.types import LogReceipt from scripts.artifacts import fetch_deployments -from scripts.constants import ( - EVM_ADDRESS, - EVM_PRIVATE_KEY, - KAKAROT_CHAIN_ID, - NETWORK, - RPC_CLIENT, -) +from scripts.constants import EVM_ADDRESS, EVM_PRIVATE_KEY, NETWORK, RPC_CLIENT from scripts.utils.starknet import call as _call_starknet from scripts.utils.starknet import fund_address as _fund_starknet_address from scripts.utils.starknet import get_contract as _get_starknet_contract @@ -296,7 +290,7 @@ async def eth_send_transaction( payload = { "type": 0x2, - "chainId": KAKAROT_CHAIN_ID, + "chainId": NETWORK["chain_id"], "nonce": nonce, "gas": gas, "maxPriorityFeePerGas": int(1e19), diff --git a/src/backend/starknet.cairo b/src/backend/starknet.cairo index d1b39ea51..83be39211 100644 --- a/src/backend/starknet.cairo +++ b/src/backend/starknet.cairo @@ -14,6 +14,7 @@ from starkware.starknet.common.syscalls import ( deploy as deploy_syscall, get_block_number, get_block_timestamp, + get_tx_info, ) from kakarot.account import Account @@ -99,6 +100,7 @@ namespace Starknet { alloc_locals; let (block_number) = get_block_number(); let (block_timestamp) = get_block_timestamp(); + let (tx_info) = get_tx_info(); let (block_hashes) = alloc(); // TODO: fix how blockhashes are retrieved memset(block_hashes, 0, 256 * 2); @@ -109,7 +111,7 @@ namespace Starknet { return new model.Environment( origin=origin, gas_price=gas_price, - chain_id=Constants.CHAIN_ID, + chain_id=tx_info.chain_id, prev_randao=Uint256(0, 0), block_number=block_number, block_gas_limit=Constants.BLOCK_GAS_LIMIT, diff --git a/src/kakarot/accounts/eoa/library.cairo b/src/kakarot/accounts/eoa/library.cairo index 0b70b7499..009d39379 100644 --- a/src/kakarot/accounts/eoa/library.cairo +++ b/src/kakarot/accounts/eoa/library.cairo @@ -109,6 +109,7 @@ namespace ExternallyOwnedAccount { EthTransaction.validate( address, tx_info.nonce, + tx_info.chain_id, r, s, v, diff --git a/src/kakarot/constants.cairo b/src/kakarot/constants.cairo index 73631d81e..d420d77e3 100644 --- a/src/kakarot/constants.cairo +++ b/src/kakarot/constants.cairo @@ -4,8 +4,6 @@ // @notice This file contains global constants. namespace Constants { // BLOCK - // CHAIN_ID = KKRT (0x4b4b5254) in ASCII - const CHAIN_ID = 1263227476; // Hardcode block gas limit to 20M const BLOCK_GAS_LIMIT = 20000000; diff --git a/src/kakarot/precompiles/precompiles.cairo b/src/kakarot/precompiles/precompiles.cairo index e1ddea767..c1cb3fbbb 100644 --- a/src/kakarot/precompiles/precompiles.cairo +++ b/src/kakarot/precompiles/precompiles.cairo @@ -131,6 +131,8 @@ namespace Precompiles { calldata_size=input_len + 2, calldata=calldata, ); - return (retdata_size, retdata, 0, 0); + // Precompiles always return a felt*, meaning that the first felt + // is the length of the output. + return (retdata[0], retdata + 1, 0, 0); } } diff --git a/src/utils/eth_transaction.cairo b/src/utils/eth_transaction.cairo index 4d715c78a..97897b79c 100644 --- a/src/utils/eth_transaction.cairo +++ b/src/utils/eth_transaction.cairo @@ -225,6 +225,7 @@ namespace EthTransaction { func validate{bitwise_ptr: BitwiseBuiltin*, range_check_ptr}( address: felt, account_nonce: felt, + chain_id: felt, r: Uint256, s: Uint256, v: felt, @@ -232,17 +233,17 @@ namespace EthTransaction { tx_data: felt*, ) { alloc_locals; - let (msg_hash, nonce, _gas_price, _gas_limit, _, _, chain_id, _, _) = decode( + let (msg_hash, nonce, _gas_price, _gas_limit, _, _, _chain_id, _, _) = decode( tx_data_len, tx_data ); assert nonce = account_nonce; - assert chain_id = Constants.CHAIN_ID; + assert chain_id = _chain_id; // Note: here, the validate process assumes an ECDSA signature, and r, s, v field // Technically, the transaction type can determine the signature scheme. let _is_legacy = is_legacy_tx(tx_data); if (_is_legacy != FALSE) { - tempvar y_parity = (v - 2 * Constants.CHAIN_ID - 35); + tempvar y_parity = (v - 2 * chain_id - 35); } else { tempvar y_parity = v; } diff --git a/tests/end_to_end/bytecodes.py b/tests/end_to_end/bytecodes.py index 52e9aa393..de470f51e 100644 --- a/tests/end_to_end/bytecodes.py +++ b/tests/end_to_end/bytecodes.py @@ -1,5 +1,7 @@ import pytest +from scripts.constants import NETWORK + test_cases = [ { "params": { @@ -734,7 +736,7 @@ "value": 0, "code": "600160024600", "calldata": "", - "stack": "1,2,1263227476", + "stack": f"1,2,{NETWORK['chain_id']}", "memory": "", "return_data": "", "success": 1, @@ -1901,7 +1903,6 @@ "marks": [ pytest.mark.SHA256, pytest.mark.Precompiles, - pytest.mark.xfail(reason="Hint is not whitelisted"), ], }, { diff --git a/tests/fixtures/EVM.cairo b/tests/fixtures/EVM.cairo index 0bd293436..41fb7c13f 100644 --- a/tests/fixtures/EVM.cairo +++ b/tests/fixtures/EVM.cairo @@ -21,6 +21,7 @@ from kakarot.storages import ( native_token_address, contract_account_class_hash, account_proxy_class_hash, + precompiles_class_hash, ) from backend.starknet import Starknet, Internals as StarknetInternals from utils.dict import dict_keys, dict_values @@ -28,11 +29,15 @@ from utils.dict import dict_keys, dict_values // Constructor @constructor func constructor{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - native_token_address_: felt, contract_account_class_hash_: felt, account_proxy_class_hash_: felt + native_token_address_: felt, + contract_account_class_hash_: felt, + account_proxy_class_hash_: felt, + precompiles_class_hash_: felt, ) { native_token_address.write(native_token_address_); contract_account_class_hash.write(contract_account_class_hash_); account_proxy_class_hash.write(account_proxy_class_hash_); + precompiles_class_hash.write(precompiles_class_hash_); return (); } diff --git a/tests/src/utils/test_eth_transaction.cairo b/tests/src/utils/test_eth_transaction.cairo index 945a389b6..828fcd281 100644 --- a/tests/src/utils/test_eth_transaction.cairo +++ b/tests/src/utils/test_eth_transaction.cairo @@ -47,6 +47,7 @@ func test__validate{bitwise_ptr: BitwiseBuiltin*, range_check_ptr}() { // Given tempvar address: felt; tempvar nonce: felt; + tempvar chain_id: felt; tempvar r: Uint256; tempvar s: Uint256; tempvar v: felt; @@ -55,6 +56,7 @@ func test__validate{bitwise_ptr: BitwiseBuiltin*, range_check_ptr}() { %{ ids.address = program_input["address"] ids.nonce = program_input["nonce"] + ids.chain_id = program_input["chain_id"] ids.r.low = program_input["r"][0] ids.r.high = program_input["r"][1] ids.s.low = program_input["s"][0] @@ -65,7 +67,7 @@ func test__validate{bitwise_ptr: BitwiseBuiltin*, range_check_ptr}() { %} // When - EthTransaction.validate(address, nonce, r, s, v, tx_data_len, tx_data); + EthTransaction.validate(address, nonce, chain_id, r, s, v, tx_data_len, tx_data); return (); } diff --git a/tests/src/utils/test_eth_transaction.py b/tests/src/utils/test_eth_transaction.py index 555e70eb8..d414ff8b3 100644 --- a/tests/src/utils/test_eth_transaction.py +++ b/tests/src/utils/test_eth_transaction.py @@ -36,6 +36,7 @@ async def test_should_pass_all_transactions_types( "test__validate", address=int(address, 16), nonce=transaction["nonce"], + chain_id=transaction["chainId"], r=int_to_uint256(signed.r), s=int_to_uint256(signed.s), v=signed["v"], @@ -46,7 +47,6 @@ async def test_should_pass_all_transactions_types( async def test_should_raise_with_wrong_chain_id(self, cairo_run, transaction): private_key = generate_random_private_key() address = private_key.public_key.to_checksum_address() - transaction = {**transaction, "chainId": 1} signed = Account.sign_transaction(transaction, private_key) encoded_unsigned_tx = rlp_encode_signed_data(transaction) @@ -56,6 +56,7 @@ async def test_should_raise_with_wrong_chain_id(self, cairo_run, transaction): "test__validate", address=int(address, 16), nonce=transaction["nonce"], + chain_id=transaction["chainId"] + 1, r=int_to_uint256(signed.r), s=int_to_uint256(signed.s), v=signed["v"], @@ -76,6 +77,7 @@ async def test_should_raise_with_wrong_address(self, cairo_run, transaction): "test__validate", address=int(address, 16), nonce=transaction["nonce"], + chain_id=transaction["chainId"], r=int_to_uint256(signed.r), s=int_to_uint256(signed.s), v=signed["v"], @@ -96,6 +98,7 @@ async def test_should_raise_with_wrong_nonce(self, cairo_run, transaction): "test__validate", address=int(address, 16), nonce=transaction["nonce"], + chain_id=transaction["chainId"], r=int_to_uint256(signed.r), s=int_to_uint256(signed.s), v=signed["v"], diff --git a/tests/utils/helpers.py b/tests/utils/helpers.py index ef81ec45e..4105d17c3 100644 --- a/tests/utils/helpers.py +++ b/tests/utils/helpers.py @@ -9,7 +9,7 @@ from eth_keys import keys from eth_utils import decode_hex, keccak, to_checksum_address -from tests.utils.constants import CHAIN_ID +from scripts.constants import NETWORK PERMIT_TYPEHASH = keccak( text="Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" @@ -83,7 +83,7 @@ def get_domain_separator(name: str, token_address: str) -> bytes: ), keccak(text=name), keccak(text="1"), - CHAIN_ID, + NETWORK["chain_id"], token_address, ], ) diff --git a/tests/utils/syscall_handler.py b/tests/utils/syscall_handler.py index 95206436a..20e2e31bc 100644 --- a/tests/utils/syscall_handler.py +++ b/tests/utils/syscall_handler.py @@ -1,4 +1,5 @@ import time +from collections import OrderedDict from contextlib import contextmanager from dataclasses import dataclass from typing import Optional, Union @@ -9,6 +10,8 @@ get_storage_var_address, ) +from tests.utils.constants import CHAIN_ID + @dataclass class SyscallHandler: @@ -20,6 +23,19 @@ class SyscallHandler: block_number: int = 0xABDE1 block_timestamp: int = int(time.time()) + tx_info = OrderedDict( + { + "version": 1, + "account_contract_address": 0xABDE1, + "max_fee": int(1e17), + # Signature len will be set later based on the signature. + "signature_len": None, + "signature": [], + "transaction_hash": 0xABDE1, + "chain_id": CHAIN_ID, + "nonce": 1, + } + ) contract_address: int = 0xABDE1 caller_address: int = 0xABDE1 patches = {} @@ -113,6 +129,35 @@ def get_block_timestamp(self, segments, syscall_ptr): """ segments.write_arg(syscall_ptr + 1, [self.block_timestamp]) + def get_tx_info(self, segments, syscall_ptr): + """ + Return a constant value for the get tx info system call. + + Syscall structure is: + struct GetTxInfoRequest { + selector: felt, + } + + struct GetTxInfoResponse { + tx_info: TxInfo*, + } + + struct GetTxInfo { + request: GetTxInfoRequest, + response: GetTxInfoResponse, + } + """ + signature_segment = segments.add() + segments.write_arg(signature_segment, self.tx_info["signature"]) + tx_info = { + **self.tx_info, + "signature_len": len(self.tx_info["signature"]), + "signature": signature_segment, + } + tx_info_segment = segments.add() + segments.write_arg(tx_info_segment, tx_info.values()) + segments.write_arg(syscall_ptr + 1, [tx_info_segment]) + def storage_read(self, segments, syscall_ptr): """ Return a constant value for the storage read system call.