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

feat: use Cairo1Helpers in AccountContract #1107

Merged
merged 11 commits into from
Apr 18, 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
1 change: 1 addition & 0 deletions kakarot_scripts/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ class ArtifactType(Enum):
{"contract_name": "EVM", "cairo_version": ArtifactType.cairo0},
{"contract_name": "OpenzeppelinAccount", "cairo_version": ArtifactType.cairo0},
{"contract_name": "Cairo1Helpers", "cairo_version": ArtifactType.cairo1},
{"contract_name": "Cairo1HelpersFixture", "cairo_version": ArtifactType.cairo1},
{"contract_name": "replace_class", "cairo_version": ArtifactType.cairo0},
]

Expand Down
56 changes: 39 additions & 17 deletions src/kakarot/accounts/account_contract.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ from starkware.cairo.common.cairo_builtins import HashBuiltin, BitwiseBuiltin, S
from starkware.cairo.common.uint256 import Uint256

// Local dependencies
from kakarot.accounts.library import AccountContract, Account_implementation
from kakarot.accounts.library import (
AccountContract,
Account_implementation,
Account_cairo1_helpers_class_hash,
)
from kakarot.accounts.model import CallArray
from kakarot.interfaces.interfaces import IKakarot, IAccount
from starkware.starknet.common.syscalls import get_tx_info, get_caller_address, replace_class
Expand Down Expand Up @@ -118,6 +122,40 @@ func __execute__{
response_len: felt, response: felt*
) {
alloc_locals;

// Upgrade flow
let (latest_account_class, latest_helpers_class) = AccountContract.get_latest_classes();
let (this_helpers_class) = Account_cairo1_helpers_class_hash.read();
if (latest_helpers_class != this_helpers_class) {
Account_cairo1_helpers_class_hash.write(latest_helpers_class);
tempvar syscall_ptr = syscall_ptr;
tempvar range_check_ptr = range_check_ptr;
tempvar pedersen_ptr = pedersen_ptr;
} else {
tempvar syscall_ptr = syscall_ptr;
tempvar range_check_ptr = range_check_ptr;
tempvar pedersen_ptr = pedersen_ptr;
}
let syscall_ptr = cast([ap - 3], felt*);
let range_check_ptr = [ap - 2];
let pedersen_ptr = cast([ap - 1], HashBuiltin*);

let (this_class) = Account_implementation.read();
if (latest_account_class != this_class) {
Account_implementation.write(latest_account_class);
// Library call to new class
let (response_len, response) = IAccount.library_call___execute__(
class_hash=latest_account_class,
call_array_len=call_array_len,
call_array=call_array,
calldata_len=calldata_len,
calldata=calldata,
);
replace_class(latest_account_class);
return (response_len, response);
}

// Execution flow
let (tx_info) = get_tx_info();
let version = tx_info.version;
with_attr error_message("EOA: deprecated tx version: {version}") {
Expand All @@ -133,22 +171,6 @@ func __execute__{
assert call_array_len = 1;
}

let latest_class = AccountContract.get_latest_class();
let (this_class) = Account_implementation.read();
if (latest_class != this_class) {
// Must be done before library_call, otherwise entering an infinite recursive loop.
Account_implementation.write(latest_class);
let (response_len, response) = IAccount.library_call___execute__(
class_hash=latest_class,
call_array_len=call_array_len,
call_array=call_array,
calldata_len=calldata_len,
calldata=calldata,
);
replace_class(latest_class);
return (response_len, response);
}

let (local response: felt*) = alloc();
let (response_len) = AccountContract.execute(
call_array_len=call_array_len,
Expand Down
87 changes: 81 additions & 6 deletions src/kakarot/accounts/library.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,13 @@ from starkware.starknet.common.syscalls import (
)
from starkware.cairo.common.memset import memset

from kakarot.interfaces.interfaces import IERC20, IKakarot
from kakarot.interfaces.interfaces import IERC20, IKakarot, ICairo1Helpers
from kakarot.accounts.model import CallArray
from kakarot.errors import Errors
from kakarot.constants import Constants
from utils.eth_transaction import EthTransaction
from utils.uint256 import uint256_add
from utils.bytes import bytes_to_bytes8_little_endian

// @dev: should always be zero for EOAs
@storage_var
Expand All @@ -57,6 +58,10 @@ func Account_implementation() -> (address: felt) {
func Account_evm_address() -> (evm_address: felt) {
}

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

@event
func transaction_executed(response_len: felt, response: felt*, success: felt, gas_used: felt) {
}
Expand Down Expand Up @@ -92,6 +97,10 @@ namespace AccountContract {
let infinite = Uint256(Constants.UINT128_MAX, Constants.UINT128_MAX);
IERC20.approve(native_token_address, kakarot_address, infinite);

// Write Cairo1Helpers class to storage
let (cairo1_helpers_class_hash) = IKakarot.get_cairo1_helpers_class_hash(kakarot_address);
Account_cairo1_helpers_class_hash.write(cairo1_helpers_class_hash);

// Register the account in the Kakarot mapping
IKakarot.register_account(kakarot_address, evm_address);
return ();
Expand Down Expand Up @@ -159,7 +168,7 @@ namespace AccountContract {
let v = tx_info.signature[4];
let (_, chain_id) = unsigned_div_rem(tx_info.chain_id, 2 ** 32);

EthTransaction.validate(
Internals.validate(
address,
tx_info.nonce,
chain_id,
Expand Down Expand Up @@ -408,11 +417,13 @@ namespace AccountContract {

// @notice Return the latest available implementation of the account contract.
// @returns The latest account class hash registered in Kakarot
func get_latest_class{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
) -> felt {
func get_latest_classes{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (
account_class: felt, cairo1_helpers_class: felt
) {
let (kakarot_address) = Ownable_owner.read();
let (latest_implementation) = IKakarot.get_account_contract_class_hash(kakarot_address);
return latest_implementation;
let (latest_account_class) = IKakarot.get_account_contract_class_hash(kakarot_address);
let (latest_cairo1_helpers_class) = IKakarot.get_cairo1_helpers_class_hash(kakarot_address);
return (latest_account_class, latest_cairo1_helpers_class);
}
}

Expand Down Expand Up @@ -592,4 +603,68 @@ namespace Internals {
jmp body if count != 0;
jmp read;
}

// @notice Validate an Ethereum transaction
// @dev This function validates an Ethereum transaction by checking if the transaction
// is correctly signed by the given address, and if the nonce in the transaction
// matches the nonce of the account. It decodes the transaction using the decode function,
// and then verifies the Ethereum signature on the transaction hash.
// @param address The address that is supposed to have signed the transaction
// @param account_nonce The nonce of the account
// @param tx_data_len The length of the raw transaction data
// @param tx_data The raw transaction data
func validate{
syscall_ptr: felt*,
pedersen_ptr: HashBuiltin*,
bitwise_ptr: BitwiseBuiltin*,
range_check_ptr,
}(
address: felt,
account_nonce: felt,
chain_id: felt,
r: Uint256,
s: Uint256,
v: felt,
tx_data_len: felt,
tx_data: felt*,
) {
alloc_locals;
let tx = EthTransaction.decode(tx_data_len, tx_data);
assert tx.signer_nonce = account_nonce;
assert tx.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 tx_type = EthTransaction.get_tx_type(tx_data);
local y_parity: felt;
if (tx_type == 0) {
assert y_parity = (v - 2 * chain_id - 35);
} else {
assert y_parity = v;
}

let (local words: felt*) = alloc();
let (words_len, last_word, last_word_num_bytes) = bytes_to_bytes8_little_endian(
words, tx_data_len, tx_data
);

let (helpers_class) = Account_cairo1_helpers_class_hash.read();
let (msg_hash) = ICairo1Helpers.library_call_keccak(
class_hash=helpers_class,
words_len=words_len,
words=words,
last_input_word=last_word,
last_input_num_bytes=last_word_num_bytes,
);

ICairo1Helpers.library_call_verify_eth_signature(
class_hash=helpers_class,
msg_hash=msg_hash,
r=r,
s=s,
y_parity=y_parity,
eth_address=address,
);
return ();
}
}
11 changes: 11 additions & 0 deletions src/kakarot/interfaces/interfaces.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,12 @@ namespace IKakarot {
func get_account_contract_class_hash() -> (account_contract_class_hash: felt) {
}

func set_cairo1_helpers_class_hash(cairo1_helpers_class_hash: felt) {
}

func get_cairo1_helpers_class_hash() -> (cairo1_helpers_class_hash: felt) {
}

func register_account(evm_address: felt) {
}

Expand Down Expand Up @@ -166,4 +172,9 @@ namespace ICairo1Helpers {
words_len: felt, words: felt*, last_input_word: felt, last_input_num_bytes: felt
) -> (hash: Uint256) {
}

func verify_eth_signature(
msg_hash: Uint256, r: Uint256, s: Uint256, y_parity: felt, eth_address: felt
) {
}
}
4 changes: 2 additions & 2 deletions src/kakarot/precompiles/ec_recover.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,9 @@ namespace EcRecoverHelpers {
let inputs_start = inputs;
keccak_add_uint256s{inputs=inputs}(n_elements=2, elements=elements, bigend=1);

let (implementation) = Kakarot_cairo1_helpers_class_hash.read();
let (helpers_class) = Kakarot_cairo1_helpers_class_hash.read();
let (point_hash) = ICairo1Helpers.library_call_keccak(
class_hash=implementation,
class_hash=helpers_class,
Comment on lines +115 to +117
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's just decide whether it's _class or _class_hash

words_len=8,
words=inputs_start,
last_input_word=0,
Expand Down
61 changes: 0 additions & 61 deletions src/utils/eth_transaction.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,14 @@
from starkware.cairo.common.alloc import alloc
from starkware.cairo.common.bool import TRUE, FALSE
from starkware.cairo.common.cairo_builtins import BitwiseBuiltin, HashBuiltin
from starkware.cairo.common.cairo_keccak.keccak import finalize_keccak, cairo_keccak_bigend
from starkware.cairo.common.cairo_secp.signature import verify_eth_signature_uint256
from starkware.cairo.common.math_cmp import is_not_zero, is_le
from starkware.cairo.common.memcpy import memcpy
from starkware.cairo.common.uint256 import Uint256

from kakarot.model import model
from kakarot.constants import Constants
from kakarot.interfaces.interfaces import IKakarot
from utils.rlp import RLP
from utils.utils import Helpers
from utils.bytes import bytes_to_bytes8_little_endian

// @title EthTransaction utils
// @notice This file contains utils for decoding eth transactions
Expand Down Expand Up @@ -199,63 +195,6 @@ namespace EthTransaction {
ret;
}

// @notice Validate an Ethereum transaction
// @dev This function validates an Ethereum transaction by checking if the transaction
// is correctly signed by the given address, and if the nonce in the transaction
// matches the nonce of the account. It decodes the transaction using the decode function,
// and then verifies the Ethereum signature on the transaction hash.
// @param address The address that is supposed to have signed the transaction
// @param account_nonce The nonce of the account
// @param tx_data_len The length of the raw transaction data
// @param tx_data The raw transaction data
func validate{bitwise_ptr: BitwiseBuiltin*, range_check_ptr}(
address: felt,
account_nonce: felt,
chain_id: felt,
r: Uint256,
s: Uint256,
v: felt,
tx_data_len: felt,
tx_data: felt*,
) {
alloc_locals;
let tx = decode(tx_data_len, tx_data);
assert tx.signer_nonce = account_nonce;
assert tx.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 tx_type = get_tx_type(tx_data);
local y_parity: felt;
if (tx_type == 0) {
assert y_parity = (v - 2 * chain_id - 35);
} else {
assert y_parity = v;
}

let (local words: felt*) = alloc();
let (words_len, last_word, last_word_num_bytes) = bytes_to_bytes8_little_endian(
words, tx_data_len, tx_data
);
assert [words + words_len] = last_word;
let words_len = words_len + 1;

let (keccak_ptr: felt*) = alloc();
let keccak_ptr_start = keccak_ptr;
with keccak_ptr {
// From keccak/cairo_keccak_bigend doc:
// > To use this function, split the input into words of 64 bits (little endian).
// > Same as keccak, but outputs the hash in big endian representation.
// > Note that the input is still treated as little endian.
let (msg_hash) = cairo_keccak_bigend(inputs=words, n_bytes=tx_data_len);
verify_eth_signature_uint256(
msg_hash=msg_hash, r=r, s=s, v=y_parity, eth_address=address
);
}
finalize_keccak(keccak_ptr_start, keccak_ptr);
return ();
}

// @notice Recursively parses the RLP-decoded access list.
// @dev the parsed format is [address, storage_keys_len, *[storage_keys], address, storage_keys_len, *[storage_keys]]
// where keys_len is the number of storage keys, and each storage key takes 2 felts.
Expand Down
20 changes: 0 additions & 20 deletions tests/end_to_end/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,26 +108,6 @@ def others(addresses):
return addresses[2:]


@pytest_asyncio.fixture(scope="function")
async def new_account(max_fee):
"""
Return a random funded new account.
"""
from kakarot_scripts.utils.kakarot import get_eoa
from kakarot_scripts.utils.starknet import fund_address
from tests.utils.helpers import generate_random_private_key

private_key = generate_random_private_key()
account = Wallet(
address=private_key.public_key.to_checksum_address(),
private_key=private_key,
# deploying an account with enough ETH to pass ~10 tx
starknet_contract=await get_eoa(private_key, amount=100 * max_fee / 1e18),
)
await fund_address(account.starknet_contract.address, 10)
return account


@pytest_asyncio.fixture(scope="session")
async def deployer() -> Account:
"""
Expand Down
Loading
Loading