From 1320f324ccd51784d6b2524a897fb685e755b9f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Walter?= Date: Sat, 11 Nov 2023 18:47:07 +0300 Subject: [PATCH 1/5] Put balance in Account instead of State --- src/kakarot/account.cairo | 60 ++++++++++++++++++++++-- src/kakarot/model.cairo | 4 +- src/kakarot/state.cairo | 99 +++++++++++++-------------------------- 3 files changed, 89 insertions(+), 74 deletions(-) diff --git a/src/kakarot/account.cairo b/src/kakarot/account.cairo index 8c38cdad2..bc984067f 100644 --- a/src/kakarot/account.cairo +++ b/src/kakarot/account.cairo @@ -28,7 +28,7 @@ from kakarot.storages import ( native_token_address, contract_account_class_hash, ) -from kakarot.interfaces.interfaces import IAccount, IContractAccount +from kakarot.interfaces.interfaces import IAccount, IContractAccount, IERC20 from kakarot.model import model from utils.dict import default_dict_copy from utils.utils import Helpers @@ -50,6 +50,7 @@ namespace Account { storage_start: DictAccess*, storage: DictAccess*, nonce: felt, + balance: Uint256*, selfdestruct: felt, } @@ -61,7 +62,9 @@ namespace Account { // @param nonce The initial nonce // @return The updated state // @return The account - func init(address: felt, code_len: felt, code: felt*, nonce: felt) -> model.Account* { + func init( + address: felt, code_len: felt, code: felt*, nonce: felt, balance: Uint256* + ) -> model.Account* { let (storage_start) = default_dict_new(0); return new model.Account( address=address, @@ -70,6 +73,7 @@ namespace Account { storage_start=storage_start, storage=storage_start, nonce=nonce, + balance=balance, selfdestruct=0, ); } @@ -85,6 +89,7 @@ namespace Account { storage_start=storage_start, storage=storage, nonce=self.nonce, + balance=self.balance, selfdestruct=self.selfdestruct, ); } @@ -101,6 +106,7 @@ namespace Account { storage_start=storage_start, storage=storage, nonce=self.nonce, + balance=self.balance, selfdestruct=self.selfdestruct, ); } @@ -177,10 +183,15 @@ namespace Account { alloc_locals; let starknet_account_exists = is_registered(address.evm); + let balance = read_balance(address); + tempvar balance_ptr = new Uint256(balance.low, balance.high); + // Case touching a non deployed account if (starknet_account_exists == 0) { let (bytecode: felt*) = alloc(); - let account = Account.init(address=address.evm, code_len=0, code=bytecode, nonce=0); + let account = Account.init( + address=address.evm, code_len=0, code=bytecode, nonce=0, balance=balance_ptr + ); return account; } @@ -191,7 +202,9 @@ namespace Account { // There is no way to access the nonce of an EOA currently // But putting 1 shouldn't have any impact and is safer than 0 // since has_code_or_nonce is used in some places to trigger collision - let account = Account.init(address=address.evm, code_len=0, code=bytecode, nonce=1); + let account = Account.init( + address=address.evm, code_len=0, code=bytecode, nonce=1, balance=balance_ptr + ); return account; } @@ -199,7 +212,11 @@ namespace Account { let (bytecode_len, bytecode) = IAccount.bytecode(contract_address=address.starknet); let (nonce) = IContractAccount.get_nonce(contract_address=address.starknet); let account = Account.init( - address=address.evm, code_len=bytecode_len, code=bytecode, nonce=nonce + address=address.evm, + code_len=bytecode_len, + code=bytecode, + nonce=nonce, + balance=balance_ptr, ); return account; } @@ -231,6 +248,7 @@ namespace Account { self.storage_start, storage, self.nonce, + self.balance, self.selfdestruct, ); return (self, value_ptr); @@ -264,6 +282,7 @@ namespace Account { self.storage_start, storage, self.nonce, + self.balance, self.selfdestruct, ); return (self, value_ptr); @@ -287,6 +306,7 @@ namespace Account { self.storage_start, storage, self.nonce, + self.balance, self.selfdestruct, ); return self; @@ -307,6 +327,7 @@ namespace Account { storage_start=self.storage_start, storage=self.storage, nonce=self.nonce, + balance=self.balance, selfdestruct=self.selfdestruct, ); } @@ -322,6 +343,34 @@ namespace Account { storage_start=self.storage_start, storage=self.storage, nonce=nonce, + balance=self.balance, + selfdestruct=self.selfdestruct, + ); + } + + // @notice Read the balance of an account without loading the Account + // @param address The address of the account + // @return the Uint256 balance + func read_balance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( + address: model.Address* + ) -> Uint256 { + let (native_token_address_) = native_token_address.read(); + let (balance) = IERC20.balanceOf(native_token_address_, address.starknet); + return balance; + } + + // @notice Set the balance of the Account + // @param self The pointer to the Account + // @param balance The new balance + func set_balance(self: model.Account*, balance: Uint256*) -> model.Account* { + return new model.Account( + address=self.address, + code_len=self.code_len, + code=self.code, + storage_start=self.storage_start, + storage=self.storage, + nonce=self.nonce, + balance=balance, selfdestruct=self.selfdestruct, ); } @@ -337,6 +386,7 @@ namespace Account { storage_start=self.storage_start, storage=self.storage, nonce=self.nonce, + balance=self.balance, selfdestruct=1, ); } diff --git a/src/kakarot/model.cairo b/src/kakarot/model.cairo index e31a31ea6..c6a078475 100644 --- a/src/kakarot/model.cairo +++ b/src/kakarot/model.cairo @@ -37,7 +37,6 @@ namespace model { // felt with inlined structs. Hence one has eventually // accounts := Dict // events := List - // balances := Dict // transfers := List // Unlike in standard EVM, we need to store the native token transfers as well since we use the // Starknet's ETH and can't just set the balances @@ -46,8 +45,6 @@ namespace model { accounts: DictAccess*, events_len: felt, events: Event*, - balances_start: DictAccess*, - balances: DictAccess*, transfers_len: felt, transfers: Transfer*, } @@ -62,6 +59,7 @@ namespace model { storage_start: DictAccess*, storage: DictAccess*, nonce: felt, + balance: Uint256*, selfdestruct: felt, } diff --git a/src/kakarot/state.cairo b/src/kakarot/state.cairo index 42e7a32cd..eb09ad183 100644 --- a/src/kakarot/state.cairo +++ b/src/kakarot/state.cairo @@ -30,8 +30,6 @@ namespace State { accounts: DictAccess*, events_len: felt, events: model.Event*, - balances_start: DictAccess*, - balances: DictAccess*, transfers_len: felt, transfers: model.Transfer*, } @@ -39,7 +37,6 @@ namespace State { // @dev Create a new empty State func init() -> model.State* { let (accounts_start) = default_dict_new(0); - let (balances_start) = default_dict_new(0); let (events: model.Event*) = alloc(); let (transfers: model.Transfer*) = alloc(); return new model.State( @@ -47,8 +44,6 @@ namespace State { accounts=accounts_start, events_len=0, events=events, - balances_start=balances_start, - balances=balances_start, transfers_len=0, transfers=transfers, ); @@ -63,9 +58,6 @@ namespace State { // for each account, storage is a new memory segment Internals._copy_accounts{accounts=accounts}(accounts_start, accounts); - // balances values are immutable so no need for an Internals._copy_balances - let (balances_start, balances) = default_dict_copy(self.balances_start, self.balances); - let (local events: felt*) = alloc(); memcpy(dst=events, src=self.events, len=self.events_len * model.Event.SIZE); @@ -77,8 +69,6 @@ namespace State { accounts=accounts, events_len=self.events_len, events=cast(events, model.Event*), - balances_start=balances_start, - balances=balances, transfers_len=self.transfers_len, transfers=cast(transfers, model.Transfer*), ); @@ -97,17 +87,11 @@ namespace State { // Squash again to keep only one Account.Summary per key let (local accounts_start, accounts) = default_dict_finalize(accounts_start, accounts, 0); - let (balances_start, balances) = default_dict_finalize( - self.balances_start, self.balances, 0 - ); - return new Summary( accounts_start=accounts_start, accounts=accounts, events_len=self.events_len, events=self.events, - balances_start=balances_start, - balances=balances, transfers_len=self.transfers_len, transfers=self.transfers, ); @@ -157,8 +141,6 @@ namespace State { accounts=accounts, events_len=self.events_len, events=self.events, - balances_start=self.balances_start, - balances=self.balances, transfers_len=self.transfers_len, transfers=self.transfers, ); @@ -173,8 +155,6 @@ namespace State { accounts=accounts, events_len=self.events_len, events=self.events, - balances_start=self.balances_start, - balances=self.balances, transfers_len=self.transfers_len, transfers=self.transfers, ); @@ -196,8 +176,6 @@ namespace State { accounts=accounts, events_len=self.events_len, events=self.events, - balances_start=self.balances_start, - balances=self.balances, transfers_len=self.transfers_len, transfers=self.transfers, ); @@ -249,8 +227,6 @@ namespace State { accounts=self.accounts, events_len=self.events_len + 1, events=self.events, - balances_start=self.balances_start, - balances=self.balances, transfers_len=self.transfers_len, transfers=self.transfers, ); @@ -272,35 +248,35 @@ namespace State { let fp_and_pc = get_fp_and_pc(); local __fp__: felt* = fp_and_pc.fp_val; - let (self, sender_balance_prev) = read_balance(self, transfer.sender); - let (success) = uint256_le(transfer.amount, sender_balance_prev); + let (self, sender) = get_account(self, transfer.sender); + let (success) = uint256_le(transfer.amount, [sender.balance]); if (success == 0) { return (self, success); } - let (self, recipient_balance_prev) = read_balance(self, transfer.recipient); - let (local sender_balance_new) = uint256_sub(sender_balance_prev, transfer.amount); + let (self, recipient) = get_account(self, transfer.recipient); + + let (local sender_balance_new) = uint256_sub([sender.balance], transfer.amount); let (local recipient_balance_new, carry) = uint256_add( - recipient_balance_prev, transfer.amount + [recipient.balance], transfer.amount ); - let balances = self.balances; - dict_write{dict_ptr=balances}( - key=transfer.sender.starknet, new_value=cast(&sender_balance_new, felt) - ); - dict_write{dict_ptr=balances}( - key=transfer.recipient.starknet, new_value=cast(&recipient_balance_new, felt) + let sender = Account.set_balance(sender, &sender_balance_new); + let recipient = Account.set_balance(recipient, &recipient_balance_new); + + let accounts = self.accounts; + dict_write{dict_ptr=accounts}(key=transfer.sender.starknet, new_value=cast(sender, felt)); + dict_write{dict_ptr=accounts}( + key=transfer.recipient.starknet, new_value=cast(recipient, felt) ); assert self.transfers[self.transfers_len] = transfer; tempvar state = new model.State( accounts_start=self.accounts_start, - accounts=self.accounts, + accounts=accounts, events_len=self.events_len, events=self.events, - balances_start=self.balances_start, - balances=balances, transfers_len=self.transfers_len + 1, transfers=self.transfers, ); @@ -314,36 +290,21 @@ namespace State { func read_balance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( self: model.State*, address: model.Address* ) -> (state: model.State*, balance: Uint256) { - let balances = self.balances; - let (pointer) = dict_read{dict_ptr=balances}(key=address.starknet); + let accounts = self.accounts; + let (pointer) = dict_read{dict_ptr=accounts}(key=address.starknet); + tempvar self = new model.State( + accounts_start=self.accounts_start, + accounts=accounts, + events_len=self.events_len, + events=self.events, + transfers_len=self.transfers_len, + transfers=self.transfers, + ); if (pointer != 0) { - let balance_ptr = cast(pointer, Uint256*); - tempvar self = new model.State( - accounts_start=self.accounts_start, - accounts=self.accounts, - events_len=self.events_len, - events=self.events, - balances_start=self.balances_start, - balances=balances, - transfers_len=self.transfers_len, - transfers=self.transfers, - ); - return (self, [balance_ptr]); + let account = cast(pointer, model.Account*); + return (self, [account.balance]); } else { - let (native_token_address_) = native_token_address.read(); - let (balance) = IERC20.balanceOf(native_token_address_, address.starknet); - tempvar balance_ptr = new Uint256(balance.low, balance.high); - dict_write{dict_ptr=balances}(key=address.starknet, new_value=cast(balance_ptr, felt)); - tempvar self = new model.State( - accounts_start=self.accounts_start, - accounts=self.accounts, - events_len=self.events_len, - events=self.events, - balances_start=self.balances_start, - balances=balances, - transfers_len=self.transfers_len, - transfers=self.transfers, - ); + let balance = Account.read_balance(address); return (self, balance); } } @@ -379,6 +340,12 @@ namespace Internals { return (); } + // Skip account if it has indeed never been fetched + // but only touched for balance read + if (accounts_start.new_value == 0) { + return _finalize_accounts(accounts_start + DictAccess.SIZE, accounts_end); + } + let account = cast(accounts_start.new_value, model.Account*); let account_summary = Account.finalize(account); dict_write{dict_ptr=accounts}( From 64ac28801e7c0be0502ba87a383bfc609c2b8239 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Walter?= Date: Tue, 21 Nov 2023 15:30:05 +0100 Subject: [PATCH 2/5] Move all syscalls to data_availability module --- src/data_availability/standalone.cairo | 0 src/data_availability/starknet.cairo | 230 ++++++++++++++++++ src/kakarot/account.cairo | 121 +-------- src/kakarot/events.cairo | 7 + src/kakarot/evm.cairo | 99 ++++++++ .../environmental_information.cairo | 1 - .../instructions/logging_operations.cairo | 1 - .../instructions/system_operations.cairo | 5 - src/kakarot/kakarot.cairo | 4 +- src/kakarot/library.cairo | 104 +------- src/kakarot/state.cairo | 109 +-------- src/kakarot/storages.cairo | 4 + tests/fixtures/EVM.cairo | 7 +- tests/src/kakarot/test_account.cairo | 26 +- tests/src/kakarot/test_account.py | 16 +- tests/src/kakarot/test_state.cairo | 24 +- 16 files changed, 402 insertions(+), 356 deletions(-) create mode 100644 src/data_availability/standalone.cairo create mode 100644 src/data_availability/starknet.cairo create mode 100644 src/kakarot/events.cairo diff --git a/src/data_availability/standalone.cairo b/src/data_availability/standalone.cairo new file mode 100644 index 000000000..e69de29bb diff --git a/src/data_availability/starknet.cairo b/src/data_availability/starknet.cairo new file mode 100644 index 000000000..294a300e4 --- /dev/null +++ b/src/data_availability/starknet.cairo @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: MIT + +%lang starknet + +from starkware.cairo.common.alloc import alloc +from starkware.cairo.common.bool import FALSE +from starkware.cairo.common.cairo_builtins import HashBuiltin +from starkware.cairo.common.dict_access import DictAccess +from starkware.cairo.common.hash import hash2 +from starkware.cairo.common.uint256 import Uint256 +from starkware.starknet.common.storage import normalize_address +from starkware.starknet.common.syscalls import ( + emit_event, + get_contract_address, + deploy as deploy_syscall, +) + +from kakarot.account import Account +from kakarot.events import evm_contract_deployed +from kakarot.interfaces.interfaces import IERC20, IContractAccount, IAccount +from kakarot.model import model +from kakarot.state import State +from kakarot.storages import ( + native_token_address, + contract_account_class_hash, + account_proxy_class_hash, + evm_to_starknet_address, +) + +namespace Starknet { + // @notice Commit the current state to the underlying data backend (here, Starknet) + // @dev Works on State.Summary to make sure only finalized states are committed. + // @param self The pointer to the State + func commit{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( + self: State.Summary* + ) { + // Accounts + Internals._commit_accounts(self.accounts_start, self.accounts); + + // Events + Internals._emit_events(self.events_len, self.events); + + // Transfers + let (native_token_address_) = native_token_address.read(); + Internals._transfer_eth(native_token_address_, self.transfers_len, self.transfers); + + return (); + } + + // @notice Deploy a new account proxy + // @dev Deploy an instance of an account + // @param evm_address The Ethereum address which will be controlling the account + // @param class_hash The hash of the implemented account (eoa/contract) + // @return account_address The Starknet Account Proxy address + func deploy{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( + class_hash: felt, evm_address: felt + ) -> (account_address: felt) { + alloc_locals; + let (kakarot_address: felt) = get_contract_address(); + let (_account_proxy_class_hash: felt) = account_proxy_class_hash.read(); + let (constructor_calldata: felt*) = alloc(); + let (starknet_address) = deploy_syscall( + _account_proxy_class_hash, + contract_address_salt=evm_address, + constructor_calldata_size=0, + constructor_calldata=constructor_calldata, + deploy_from_zero=0, + ); + assert constructor_calldata[0] = kakarot_address; + assert constructor_calldata[1] = evm_address; + IAccount.initialize(starknet_address, class_hash, 2, constructor_calldata); + evm_contract_deployed.emit(evm_address, starknet_address); + evm_to_starknet_address.write(evm_address, starknet_address); + return (account_address=starknet_address); + } +} + +namespace Internals { + // @notice Iterate through the accounts dict and commit them + // @dev Account is deployed here if it doesn't exist already + // @param accounts_start The dict start pointer + // @param accounts_end The dict end pointer + func _commit_accounts{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( + accounts_start: DictAccess*, accounts_end: DictAccess* + ) { + alloc_locals; + if (accounts_start == accounts_end) { + return (); + } + + let starknet_address = accounts_start.key; + let account = cast(accounts_start.new_value, Account.Summary*); + _commit_account(account, starknet_address); + + _commit_accounts(accounts_start + DictAccess.SIZE, accounts_end); + + return (); + } + + // @notice Commit the account to the storage backend at given address + // @dev Account is deployed here if it doesn't exist already + // @dev Works on Account.Summary to make sure only finalized accounts are committed. + // @param self The pointer to the Account + // @param starknet_address A starknet address to commit to + // @notice Iterate through the storage dict and update the Starknet storage + func _commit_account{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( + self: Account.Summary*, starknet_address: felt + ) { + alloc_locals; + + let starknet_account_exists = Account.is_registered(self.address); + + // Case new Account + if (starknet_account_exists == 0) { + // Just casting the Summary into an Account to apply has_code_or_nonce + // cf Summary note: like an Account, but frozen after squashing all dicts + // There is no reason to have has_code_or_nonce available in the public API + // for Account.Summary, but safe to use here + let code_or_nonce = Account.has_code_or_nonce(cast(self, model.Account*)); + + if (code_or_nonce != FALSE) { + // Deploy accounts + let (class_hash) = contract_account_class_hash.read(); + Starknet.deploy(class_hash, self.address); + // If SELFDESTRUCT, stops here to leave the account empty + if (self.selfdestruct != 0) { + return (); + } + + // Write bytecode + IContractAccount.write_bytecode(starknet_address, self.code_len, self.code); + // Set nonce + IContractAccount.set_nonce(starknet_address, self.nonce); + // Save storages + _save_storage(starknet_address, self.storage_start, self.storage); + return (); + } else { + // Touched an undeployed address in a CALL, do nothing + return (); + } + } + + // Case existing Account and SELFDESTRUCT + if (self.selfdestruct != 0) { + IContractAccount.selfdestruct(contract_address=starknet_address); + return (); + } + + let (account_type) = IAccount.account_type(contract_address=starknet_address); + if (account_type == 'EOA') { + return (); + } + + // Set nonce + IContractAccount.set_nonce(starknet_address, self.nonce); + // Save storages + Internals._save_storage(starknet_address, self.storage_start, self.storage); + + return (); + } + + // @notice Iterates through a list of events and emits them. + // @param events_len The length of the events array. + // @param events The array of Event structs that are emitted via the `emit_event` syscall. + func _emit_events{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( + events_len: felt, events: model.Event* + ) { + alloc_locals; + + if (events_len == 0) { + return (); + } + + let event: model.Event = [events]; + emit_event( + keys_len=event.topics_len, keys=event.topics, data_len=event.data_len, data=event.data + ); + + _emit_events(events_len - 1, events + model.Event.SIZE); + return (); + } + + // @notice Iterates through a list of Transfer and makes them + // @dev Transfers are made last so as to have all accounts created beforehand. + // @param transfers_len The length of the transfers array. + // @param transfers The array of Transfer. + func _transfer_eth{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( + token_address: felt, transfers_len: felt, transfers: model.Transfer* + ) { + if (transfers_len == 0) { + return (); + } + + let transfer = [transfers]; + IERC20.transferFrom( + token_address, transfer.sender.starknet, transfer.recipient.starknet, transfer.amount + ); + return _transfer_eth(token_address, transfers_len - 1, transfers + model.Transfer.SIZE); + } + + // @notice Iterates through the storage dict and update Contract Account storage. + // @param starknet_address The address of the Starknet account to save into. + // @param storage_start The dict start pointer + // @param storage_end The dict end pointer + func _save_storage{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( + starknet_address: felt, storage_start: DictAccess*, storage_end: DictAccess* + ) { + if (storage_start == storage_end) { + return (); + } + let value = cast(storage_start.new_value, Uint256*); + + IContractAccount.write_storage( + contract_address=starknet_address, storage_addr=storage_start.key, value=[value] + ); + + return _save_storage(starknet_address, storage_start + DictAccess.SIZE, storage_end); + } + + // @notice Compute the storage address of the given key when the storage var interface is + // storage_(key: Uint256) + // @dev Just the generated addr method when compiling the contract_account + func _storage_addr{pedersen_ptr: HashBuiltin*, range_check_ptr}(key: Uint256*) -> (res: felt) { + let res = 1510236440068827666686527023008568026372765124888307403567795291192307314167; + let (res) = hash2{hash_ptr=pedersen_ptr}(res, cast(key, felt*)[0]); + let (res) = hash2{hash_ptr=pedersen_ptr}(res, cast(key, felt*)[1]); + let (res) = normalize_address(addr=res); + return (res=res); + } +} diff --git a/src/kakarot/account.cairo b/src/kakarot/account.cairo index bc984067f..23ff4d03b 100644 --- a/src/kakarot/account.cairo +++ b/src/kakarot/account.cairo @@ -12,8 +12,6 @@ from starkware.cairo.common.hash import hash2 from starkware.cairo.common.math_cmp import is_not_zero from starkware.cairo.common.memcpy import memcpy from starkware.cairo.common.uint256 import Uint256 -from starkware.starknet.common.storage import normalize_address -from starkware.starknet.common.syscalls import deploy as deploy_syscall, get_contract_address from starkware.cairo.common.hash_state import ( hash_finalize, hash_init, @@ -21,6 +19,8 @@ from starkware.cairo.common.hash_state import ( hash_update_single, hash_update_with_hashchain, ) +from starkware.starknet.common.storage import normalize_address +from starkware.starknet.common.syscalls import get_contract_address from kakarot.constants import Constants from kakarot.storages import ( @@ -30,17 +30,10 @@ from kakarot.storages import ( ) from kakarot.interfaces.interfaces import IAccount, IContractAccount, IERC20 from kakarot.model import model +from kakarot.storages import evm_to_starknet_address from utils.dict import default_dict_copy from utils.utils import Helpers -@event -func evm_contract_deployed(evm_contract_address: felt, starknet_contract_address: felt) { -} - -@storage_var -func evm_to_starknet_address(evm_address: felt) -> (starknet_address: felt) { -} - namespace Account { // @dev Like an Account, but frozen after squashing all dicts struct Summary { @@ -111,68 +104,6 @@ namespace Account { ); } - // @notice Commit the account to the storage backend at given address - // @dev Account is deployed here if it doesn't exist already - // @dev Works on Account.Summary to make sure only finalized accounts are committed. - // @param self The pointer to the Account - // @param starknet_address A starknet address to commit to - // @notice Iterate through the storage dict and update the Starknet storage - func commit{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - self: Summary*, starknet_address: felt - ) { - alloc_locals; - - let starknet_account_exists = is_registered(self.address); - - // Case new Account - if (starknet_account_exists == 0) { - // Just casting the Summary into an Account to apply has_code_or_nonce - // cf Summary note: like an Account, but frozen after squashing all dicts - // There is no reason to have has_code_or_nonce available in the public API - // for Account.Summary, but safe to use here - let code_or_nonce = has_code_or_nonce(cast(self, model.Account*)); - - if (code_or_nonce != FALSE) { - // Deploy accounts - let (class_hash) = contract_account_class_hash.read(); - deploy(class_hash, self.address); - // If SELFDESTRUCT, stops here to leave the account empty - if (self.selfdestruct != 0) { - return (); - } - - // Write bytecode - IContractAccount.write_bytecode(starknet_address, self.code_len, self.code); - // Set nonce - IContractAccount.set_nonce(starknet_address, self.nonce); - // Save storages - Internals._save_storage(starknet_address, self.storage_start, self.storage); - return (); - } else { - // Touched an undeployed address in a CALL, do nothing - return (); - } - } - - // Case existing Account and SELFDESTRUCT - if (self.selfdestruct != 0) { - IContractAccount.selfdestruct(contract_address=starknet_address); - return (); - } - - let (account_type) = IAccount.account_type(contract_address=starknet_address); - if (account_type == 'EOA') { - return (); - } - - // Set nonce - IContractAccount.set_nonce(starknet_address, self.nonce); - // Save storages - Internals._save_storage(starknet_address, self.storage_start, self.storage); - - return (); - } - // @notice fetch an account from Starknet // @dev An non-deployed account is just an empty account. // @param address the pointer to the Address @@ -441,33 +372,6 @@ namespace Account { return (contract_address=contract_address); } - // @notice Deploy a new account proxy - // @dev Deploy an instance of an account - // @param evm_address The Ethereum address which will be controlling the account - // @param class_hash The hash of the implemented account (eoa/contract) - // @return account_address The Starknet Account Proxy address - func deploy{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - class_hash: felt, evm_address: felt - ) -> (account_address: felt) { - alloc_locals; - let (kakarot_address: felt) = get_contract_address(); - let (_account_proxy_class_hash: felt) = account_proxy_class_hash.read(); - let (constructor_calldata: felt*) = alloc(); - let (starknet_address) = deploy_syscall( - _account_proxy_class_hash, - contract_address_salt=evm_address, - constructor_calldata_size=0, - constructor_calldata=constructor_calldata, - deploy_from_zero=0, - ); - assert constructor_calldata[0] = kakarot_address; - assert constructor_calldata[1] = evm_address; - IAccount.initialize(starknet_address, class_hash, 2, constructor_calldata); - evm_contract_deployed.emit(evm_address, starknet_address); - evm_to_starknet_address.write(evm_address, starknet_address); - return (account_address=starknet_address); - } - // @notice Tells if an account has code_len > 0 or nonce > 0 // @dev See https://github.com/ethereum/execution-specs/blob/3fe6514f2d9d234e760d11af883a47c1263eff51/src/ethereum/shanghai/state.py#L352 // @param self The pointer to the Account @@ -493,25 +397,6 @@ namespace Account { } namespace Internals { - // @notice Iterates through the storage dict and update Contract Account storage. - // @param starknet_address The address of the Starknet account to save into. - // @param storage_start The dict start pointer - // @param storage_end The dict end pointer - func _save_storage{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - starknet_address: felt, storage_start: DictAccess*, storage_end: DictAccess* - ) { - if (storage_start == storage_end) { - return (); - } - let value = cast(storage_start.new_value, Uint256*); - - IContractAccount.write_storage( - contract_address=starknet_address, storage_addr=storage_start.key, value=[value] - ); - - return _save_storage(starknet_address, storage_start + DictAccess.SIZE, storage_end); - } - // @notice Compute the storage address of the given key when the storage var interface is // storage_(key: Uint256) // @dev Just the generated addr method when compiling the contract_account diff --git a/src/kakarot/events.cairo b/src/kakarot/events.cairo new file mode 100644 index 000000000..a16e23e10 --- /dev/null +++ b/src/kakarot/events.cairo @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT + +%lang starknet + +@event +func evm_contract_deployed(evm_contract_address: felt, starknet_contract_address: felt) { +} diff --git a/src/kakarot/evm.cairo b/src/kakarot/evm.cairo index 6e387ea54..39cca3096 100644 --- a/src/kakarot/evm.cairo +++ b/src/kakarot/evm.cairo @@ -3,6 +3,7 @@ %lang starknet // Starkware dependencies +from starkware.cairo.common.alloc import alloc from starkware.cairo.common.bool import FALSE, TRUE from starkware.cairo.common.cairo_builtins import HashBuiltin, BitwiseBuiltin from starkware.cairo.common.math_cmp import is_le @@ -669,6 +670,104 @@ namespace EVM { return run(ctx); } + // @notice Run the given bytecode with the given calldata and parameters + // @param address The target account address + // @param is_deploy_tx Whether the transaction is a deploy tx or not + // @param origin The caller EVM address + // @param bytecode_len The length of the bytecode + // @param bytecode The bytecode run + // @param calldata_len The length of the calldata + // @param calldata The calldata of the execution + // @param value The value of the execution + // @param gas_limit The gas limit of the execution + // @param gas_price The gas price for the execution + func execute{ + syscall_ptr: felt*, + pedersen_ptr: HashBuiltin*, + range_check_ptr, + bitwise_ptr: BitwiseBuiltin*, + }( + address: model.Address*, + is_deploy_tx: felt, + origin: model.Address*, + bytecode_len: felt, + bytecode: felt*, + calldata_len: felt, + calldata: felt*, + value: felt, + gas_limit: felt, + gas_price: felt, + ) -> Summary* { + alloc_locals; + + // If is_deploy_tx is TRUE, then + // bytecode is data and data is empty + // else, bytecode and data are kept as is + let bytecode_len = calldata_len * is_deploy_tx + bytecode_len * (1 - is_deploy_tx); + let calldata_len = calldata_len * (1 - is_deploy_tx); + if (is_deploy_tx != 0) { + let (empty: felt*) = alloc(); + tempvar bytecode = calldata; + tempvar calldata = empty; + } else { + tempvar bytecode = bytecode; + tempvar calldata = calldata; + } + + let root_context = ExecutionContext.init_empty(); + tempvar call_context = new model.CallContext( + bytecode=bytecode, + bytecode_len=bytecode_len, + calldata=calldata, + calldata_len=calldata_len, + value=value, + gas_limit=gas_limit, + gas_price=gas_price, + origin=origin, + calling_context=root_context, + address=address, + read_only=FALSE, + is_create=is_deploy_tx, + ); + + let ctx = ExecutionContext.init(call_context); + let ctx = ExecutionContext.add_intrinsic_gas_cost(ctx); + + let state = ctx.state; + // Handle value + let amount = Helpers.to_uint256(value); + let transfer = model.Transfer(origin, address, [amount]); + let (state, success) = State.add_transfer(state, transfer); + + // Check collision + let account = Account.fetch_or_create(address); + let code_or_nonce = Account.has_code_or_nonce(account); + let is_collision = code_or_nonce * is_deploy_tx; + // Nonce is set to 1 in case of deploy_tx + let nonce = account.nonce * (1 - is_deploy_tx) + is_deploy_tx; + let account = Account.set_nonce(account, nonce); + let state = State.set_account(state, address, account); + + let ctx = ExecutionContext.update_state(ctx, state); + + if (is_collision != 0) { + let (revert_reason_len, revert_reason) = Errors.addressCollision(); + tempvar ctx = ExecutionContext.stop(ctx, revert_reason_len, revert_reason, TRUE); + } else { + tempvar ctx = ctx; + } + + if (success == 0) { + let (revert_reason_len, revert_reason) = Errors.balanceError(); + tempvar ctx = ExecutionContext.stop(ctx, revert_reason_len, revert_reason, TRUE); + } else { + tempvar ctx = ctx; + } + + let summary = run(ctx); + return summary; + } + // @notice A placeholder for opcodes that don't exist // @dev Halts execution // @param ctx The pointer to the execution context diff --git a/src/kakarot/instructions/environmental_information.cairo b/src/kakarot/instructions/environmental_information.cairo index 8dbd105d8..da10ac849 100644 --- a/src/kakarot/instructions/environmental_information.cairo +++ b/src/kakarot/instructions/environmental_information.cairo @@ -7,7 +7,6 @@ from starkware.cairo.common.alloc import alloc from starkware.cairo.common.cairo_builtins import HashBuiltin, BitwiseBuiltin from starkware.cairo.common.cairo_keccak.keccak import cairo_keccak_bigend, finalize_keccak -from starkware.starknet.common.syscalls import get_caller_address, get_tx_info from starkware.cairo.common.uint256 import Uint256 from starkware.cairo.common.bool import TRUE, FALSE from starkware.cairo.common.math_cmp import is_le diff --git a/src/kakarot/instructions/logging_operations.cairo b/src/kakarot/instructions/logging_operations.cairo index 62e423c2f..6e3f275ed 100644 --- a/src/kakarot/instructions/logging_operations.cairo +++ b/src/kakarot/instructions/logging_operations.cairo @@ -6,7 +6,6 @@ from starkware.cairo.common.alloc import alloc from starkware.cairo.common.bool import FALSE, TRUE from starkware.cairo.common.cairo_builtins import HashBuiltin, BitwiseBuiltin -from starkware.starknet.common.syscalls import emit_event from starkware.cairo.common.math_cmp import is_le // Internal dependencies diff --git a/src/kakarot/instructions/system_operations.cairo b/src/kakarot/instructions/system_operations.cairo index f6fb1b515..66ac356e7 100644 --- a/src/kakarot/instructions/system_operations.cairo +++ b/src/kakarot/instructions/system_operations.cairo @@ -11,11 +11,6 @@ from starkware.cairo.common.math import split_felt, unsigned_div_rem from starkware.cairo.common.math_cmp import is_le, is_not_zero, is_nn from starkware.cairo.common.memcpy import memcpy from starkware.cairo.common.uint256 import Uint256, uint256_eq -from starkware.starknet.common.syscalls import ( - deploy as deploy_syscall, - get_contract_address, - get_tx_info, -) from starkware.cairo.common.registers import get_fp_and_pc // Internal dependencies diff --git a/src/kakarot/kakarot.cairo b/src/kakarot/kakarot.cairo index 37d4576a8..3bc9c4f6f 100644 --- a/src/kakarot/kakarot.cairo +++ b/src/kakarot/kakarot.cairo @@ -10,13 +10,13 @@ from starkware.cairo.common.uint256 import Uint256 from starkware.starknet.common.syscalls import get_caller_address // Local dependencies +from data_availability.starknet import Starknet from kakarot.account import Account from kakarot.evm import EVM from kakarot.library import Kakarot from kakarot.memory import Memory from kakarot.model import model from kakarot.stack import Stack -from kakarot.state import State from utils.utils import Helpers // Constructor @@ -191,7 +191,7 @@ func eth_send_transaction{ return result; } - State.commit(summary.state); + Starknet.commit(summary.state); if (to == 0) { // Overwrite return_data with deployed addresses diff --git a/src/kakarot/library.cairo b/src/kakarot/library.cairo index 40b42dc88..58cc583a4 100644 --- a/src/kakarot/library.cairo +++ b/src/kakarot/library.cairo @@ -7,10 +7,10 @@ from starkware.cairo.common.alloc import alloc from starkware.cairo.common.bool import FALSE, TRUE from starkware.cairo.common.cairo_builtins import HashBuiltin, BitwiseBuiltin from starkware.cairo.common.default_dict import default_dict_new -from starkware.starknet.common.syscalls import deploy as deploy_syscall from starkware.starknet.common.syscalls import get_caller_address, get_tx_info from starkware.cairo.common.math_cmp import is_not_zero +from data_availability.starknet import Starknet from kakarot.account import Account from kakarot.storages import ( account_proxy_class_hash, @@ -59,104 +59,6 @@ namespace Kakarot { return (); } - // @notice Run the given bytecode with the given calldata and parameters - // @param address The target account address - // @param is_deploy_tx Whether the transaction is a deploy tx or not - // @param origin The caller EVM address - // @param bytecode_len The length of the bytecode - // @param bytecode The bytecode run - // @param calldata_len The length of the calldata - // @param calldata The calldata of the execution - // @param value The value of the execution - // @param gas_limit The gas limit of the execution - // @param gas_price The gas price for the execution - func execute{ - syscall_ptr: felt*, - pedersen_ptr: HashBuiltin*, - range_check_ptr, - bitwise_ptr: BitwiseBuiltin*, - }( - address: model.Address*, - is_deploy_tx: felt, - origin: model.Address*, - bytecode_len: felt, - bytecode: felt*, - calldata_len: felt, - calldata: felt*, - value: felt, - gas_limit: felt, - gas_price: felt, - ) -> EVM.Summary* { - alloc_locals; - - // If is_deploy_tx is TRUE, then - // bytecode is data and data is empty - // else, bytecode and data are kept as is - let bytecode_len = calldata_len * is_deploy_tx + bytecode_len * (1 - is_deploy_tx); - let calldata_len = calldata_len * (1 - is_deploy_tx); - if (is_deploy_tx != 0) { - let (empty: felt*) = alloc(); - tempvar bytecode = calldata; - tempvar calldata = empty; - } else { - tempvar bytecode = bytecode; - tempvar calldata = calldata; - } - - let root_context = ExecutionContext.init_empty(); - tempvar call_context = new model.CallContext( - bytecode=bytecode, - bytecode_len=bytecode_len, - calldata=calldata, - calldata_len=calldata_len, - value=value, - gas_limit=gas_limit, - gas_price=gas_price, - origin=origin, - calling_context=root_context, - address=address, - read_only=FALSE, - is_create=is_deploy_tx, - ); - - let ctx = ExecutionContext.init(call_context); - let ctx = ExecutionContext.add_intrinsic_gas_cost(ctx); - - let state = ctx.state; - // Handle value - let amount = Helpers.to_uint256(value); - let transfer = model.Transfer(origin, address, [amount]); - let (state, success) = State.add_transfer(state, transfer); - - // Check collision - let account = Account.fetch_or_create(address); - let code_or_nonce = Account.has_code_or_nonce(account); - let is_collision = code_or_nonce * is_deploy_tx; - // Nonce is set to 1 in case of deploy_tx - let nonce = account.nonce * (1 - is_deploy_tx) + is_deploy_tx; - let account = Account.set_nonce(account, nonce); - let state = State.set_account(state, address, account); - - let ctx = ExecutionContext.update_state(ctx, state); - - if (is_collision != 0) { - let (revert_reason_len, revert_reason) = Errors.addressCollision(); - tempvar ctx = ExecutionContext.stop(ctx, revert_reason_len, revert_reason, TRUE); - } else { - tempvar ctx = ctx; - } - - if (success == 0) { - let (revert_reason_len, revert_reason) = Errors.balanceError(); - tempvar ctx = ExecutionContext.stop(ctx, revert_reason_len, revert_reason, TRUE); - } else { - tempvar ctx = ctx; - } - - let summary = EVM.run(ctx); - return summary; - } - // @notice The eth_call function as described in the RPC spec, see https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_call // @param origin The address the transaction is sent from. // @param to The address the transaction is directed to. @@ -193,7 +95,7 @@ namespace Kakarot { let is_deploy_tx = 1 - is_regular_tx; let account = Account.fetch_or_create(address); - let summary = execute( + let summary = EVM.execute( address, is_deploy_tx, origin_address, @@ -279,7 +181,7 @@ namespace Kakarot { alloc_locals; let (class_hash) = externally_owned_account_class_hash.read(); - let (starknet_contract_address) = Account.deploy(class_hash, evm_contract_address); + let (starknet_contract_address) = Starknet.deploy(class_hash, evm_contract_address); let (local native_token_address) = get_native_token(); let (local deploy_fee) = get_deploy_fee(); diff --git a/src/kakarot/state.cairo b/src/kakarot/state.cairo index eb09ad183..f5b435a57 100644 --- a/src/kakarot/state.cairo +++ b/src/kakarot/state.cairo @@ -3,22 +3,15 @@ %lang starknet from starkware.cairo.common.alloc import alloc -from starkware.cairo.common.cairo_builtins import HashBuiltin, BitwiseBuiltin +from starkware.cairo.common.cairo_builtins import HashBuiltin from starkware.cairo.common.default_dict import default_dict_new, default_dict_finalize from starkware.cairo.common.dict import dict_read, dict_write from starkware.cairo.common.dict_access import DictAccess -from starkware.cairo.common.hash_state import hash_finalize, hash_init, hash_update, hash_felts -from starkware.cairo.common.math import assert_not_zero from starkware.cairo.common.memcpy import memcpy from starkware.cairo.common.registers import get_fp_and_pc from starkware.cairo.common.uint256 import Uint256, uint256_add, uint256_sub, uint256_le -from starkware.starknet.common.storage import normalize_address -from starkware.starknet.common.syscalls import call_contract -from starkware.starknet.common.syscalls import emit_event from kakarot.account import Account -from kakarot.storages import native_token_address, contract_account_class_hash -from kakarot.interfaces.interfaces import IERC20 from kakarot.model import model from utils.dict import default_dict_copy from utils.utils import Helpers @@ -97,28 +90,6 @@ namespace State { ); } - // @notice Commit the current state to the underlying data backend (here, Starknet) - // @dev Works on State.Summary to make sure only finalized states are committed. - // @param self The pointer to the State - func commit{ - syscall_ptr: felt*, - pedersen_ptr: HashBuiltin*, - range_check_ptr, - bitwise_ptr: BitwiseBuiltin*, - }(self: Summary*) { - // Accounts - Internals._commit_accounts(self.accounts_start, self.accounts); - - // Events - Internals._emit_events(self.events_len, self.events); - - // Transfers - let (native_token_address_) = native_token_address.read(); - Internals._transfer_eth(native_token_address_, self.transfers_len, self.transfers); - - return (); - } - // @notice Get a given EVM Account // @dev Try to retrieve in the local Dict first, and if not already here // read the contract storage and cache the result. @@ -187,12 +158,9 @@ namespace State { // @param self The pointer to the execution State. // @param address The pointer to the Address. // @param key The pointer to the storage key - func read_storage{ - syscall_ptr: felt*, - pedersen_ptr: HashBuiltin*, - range_check_ptr, - bitwise_ptr: BitwiseBuiltin*, - }(self: model.State*, address: model.Address*, key: Uint256*) -> (model.State*, Uint256*) { + func read_storage{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( + self: model.State*, address: model.Address*, key: Uint256* + ) -> (model.State*, Uint256*) { alloc_locals; let (self, account) = get_account(self, address); let (account, value) = Account.read_storage(account, address, key); @@ -237,12 +205,9 @@ namespace State { // @param event The pointer to the Transfer // @return The updated State // @return The status of the transfer - func add_transfer{ - syscall_ptr: felt*, - pedersen_ptr: HashBuiltin*, - range_check_ptr, - bitwise_ptr: BitwiseBuiltin*, - }(self: model.State*, transfer: model.Transfer) -> (model.State*, felt) { + func add_transfer{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( + self: model.State*, transfer: model.Transfer + ) -> (model.State*, felt) { alloc_locals; // See https://docs.cairo-lang.org/0.12.0/how_cairo_works/functions.html#retrieving-registers let fp_and_pc = get_fp_and_pc(); @@ -354,64 +319,4 @@ namespace Internals { return _finalize_accounts(accounts_start + DictAccess.SIZE, accounts_end); } - - // @notice Iterate through the accounts dict and commit them - // @dev Account is deployed here if it doesn't exist already - // @param accounts_start The dict start pointer - // @param accounts_end The dict end pointer - func _commit_accounts{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - accounts_start: DictAccess*, accounts_end: DictAccess* - ) { - alloc_locals; - if (accounts_start == accounts_end) { - return (); - } - - let starknet_address = accounts_start.key; - let account = cast(accounts_start.new_value, Account.Summary*); - Account.commit(account, starknet_address); - - _commit_accounts(accounts_start + DictAccess.SIZE, accounts_end); - - return (); - } - - // @notice Iterates through a list of events and emits them. - // @param events_len The length of the events array. - // @param events The array of Event structs that are emitted via the `emit_event` syscall. - func _emit_events{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - events_len: felt, events: model.Event* - ) { - alloc_locals; - - if (events_len == 0) { - return (); - } - - let event: model.Event = [events]; - emit_event( - keys_len=event.topics_len, keys=event.topics, data_len=event.data_len, data=event.data - ); - - _emit_events(events_len - 1, events + model.Event.SIZE); - return (); - } - - // @notice Iterates through a list of Transfer and makes them - // @dev Transfers are made last so as to have all accounts created beforehand. - // @param transfers_len The length of the transfers array. - // @param transfers The array of Transfer. - func _transfer_eth{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - token_address: felt, transfers_len: felt, transfers: model.Transfer* - ) { - if (transfers_len == 0) { - return (); - } - - let transfer = [transfers]; - IERC20.transferFrom( - token_address, transfer.sender.starknet, transfer.recipient.starknet, transfer.amount - ); - return _transfer_eth(token_address, transfers_len - 1, transfers + model.Transfer.SIZE); - } } diff --git a/src/kakarot/storages.cairo b/src/kakarot/storages.cairo index 7209b9e38..4268382eb 100644 --- a/src/kakarot/storages.cairo +++ b/src/kakarot/storages.cairo @@ -25,3 +25,7 @@ func account_proxy_class_hash() -> (res: felt) { @storage_var func deploy_fee() -> (deploy_fee: felt) { } + +@storage_var +func evm_to_starknet_address(evm_address: felt) -> (starknet_address: felt) { +} diff --git a/tests/fixtures/EVM.cairo b/tests/fixtures/EVM.cairo index 08e7034e4..72336eae4 100644 --- a/tests/fixtures/EVM.cairo +++ b/tests/fixtures/EVM.cairo @@ -13,10 +13,8 @@ from starkware.starknet.common.syscalls import get_block_number, get_block_times // Local dependencies from kakarot.evm import EVM -from kakarot.library import Kakarot from kakarot.model import model from kakarot.stack import Stack -from kakarot.state import Internals as State from kakarot.constants import Constants from kakarot.storages import ( native_token_address, @@ -24,6 +22,7 @@ from kakarot.storages import ( blockhash_registry_address, account_proxy_class_hash, ) +from data_availability.starknet import Internals as Starknet from utils.dict import dict_keys, dict_values // Constructor @@ -55,7 +54,7 @@ func execute{ let fp_and_pc = get_fp_and_pc(); local __fp__: felt* = fp_and_pc.fp_val; tempvar address = new model.Address(1, 1); - let summary = Kakarot.execute( + let summary = EVM.execute( address=address, is_deploy_tx=0, origin=&origin, @@ -164,6 +163,6 @@ func evm_execute{ // We just emit the events as committing the accounts is out of the scope of these EVM // tests and requires a real CallContext.address (not Address(1, 1)) - State._emit_events(summary.state.events_len, summary.state.events); + Starknet._emit_events(summary.state.events_len, summary.state.events); return result; } diff --git a/tests/src/kakarot/test_account.cairo b/tests/src/kakarot/test_account.cairo index 8b4e07bc8..12ebc7550 100644 --- a/tests/src/kakarot/test_account.cairo +++ b/tests/src/kakarot/test_account.cairo @@ -18,14 +18,17 @@ from kakarot.account import Account @external func test__init__should_return_account_with_default_dict_as_storage{ syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr, bitwise_ptr: BitwiseBuiltin* -}(address: felt, code_len: felt, code: felt*, nonce: felt) { +}(address: felt, code_len: felt, code: felt*, nonce: felt, balance_low: felt) { // When - let account = Account.init(address, code_len, code, nonce); + tempvar balance = new Uint256(balance_low, 0); + let account = Account.init(address, code_len, code, nonce, balance); // Then assert account.address = address; assert account.code_len = code_len; assert account.nonce = nonce; + assert account.balance.low = balance_low; + assert account.balance.high = 0; assert account.selfdestruct = 0; let storage = account.storage; let (value) = dict_read{dict_ptr=storage}(0xdead); @@ -36,10 +39,11 @@ func test__init__should_return_account_with_default_dict_as_storage{ @external func test__copy__should_return_new_account_with_same_attributes{ syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr, bitwise_ptr: BitwiseBuiltin* -}(evm_address: felt, code_len: felt, code: felt*, nonce: felt) { +}(evm_address: felt, code_len: felt, code: felt*, nonce: felt, balance_low: felt) { alloc_locals; // Given - let account = Account.init(evm_address, code_len, code, nonce); + tempvar balance = new Uint256(balance_low, 0); + let account = Account.init(evm_address, code_len, code, nonce, balance); tempvar key = new Uint256(1, 2); tempvar value = new Uint256(3, 4); let account = Account.write_storage(account, key, value); @@ -53,6 +57,8 @@ func test__copy__should_return_new_account_with_same_attributes{ assert account.address = account_copy.address; assert account.code_len = account_copy.code_len; assert account.nonce = account_copy.nonce; + assert account.balance.low = balance_low; + assert account.balance.high = 0; assert account.selfdestruct = account_copy.selfdestruct; // Same storage @@ -78,7 +84,8 @@ func test__finalize__should_return_summary{ }(evm_address: felt, code_len: felt, code: felt*, nonce: felt) { // Given alloc_locals; - let account = Account.init(evm_address, code_len, code, nonce); + tempvar balance = new Uint256(0, 0); + let account = Account.init(evm_address, code_len, code, nonce, balance); tempvar key = new Uint256(1, 2); tempvar value = new Uint256(3, 4); tempvar address = new model.Address(0, evm_address); @@ -111,7 +118,8 @@ func test__finalize__should_return_summary_with_no_default_dict{ alloc_locals; tempvar key = new Uint256(1, 2); tempvar address = new model.Address(0, evm_address); - let account = Account.init(evm_address, code_len, code, nonce); + tempvar balance = new Uint256(0, 0); + let account = Account.init(evm_address, code_len, code, nonce, balance); // When let summary = Account.finalize(account); @@ -134,7 +142,8 @@ func test__write_storage__should_store_value_at_key{ local __fp__: felt* = fp_and_pc.fp_val; tempvar address = new model.Address(0, 0); let (local code: felt*) = alloc(); - let account = Account.init(0, 0, code, 0); + tempvar balance = new Uint256(0, 0); + let account = Account.init(0, 0, code, 0, balance); // When let account = Account.write_storage(account, &key, &value); @@ -153,7 +162,8 @@ func test__has_code_or_nonce{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, ran nonce: felt, code_len: felt, code: felt* ) -> (has_code_or_nonce: felt) { // Given - let account = Account.init(0, code_len, code, nonce); + tempvar balance = new Uint256(0, 0); + let account = Account.init(0, code_len, code, nonce, balance); // When let result = Account.has_code_or_nonce(account); diff --git a/tests/src/kakarot/test_account.py b/tests/src/kakarot/test_account.py index 24f4a2e0f..e3f6658e4 100644 --- a/tests/src/kakarot/test_account.py +++ b/tests/src/kakarot/test_account.py @@ -20,24 +20,26 @@ async def account(starknet: Starknet): class TestAccount: class TestInit: @pytest.mark.parametrize( - "address, code, nonce", [(0, [], 0), (2**160 - 1, [1, 2, 3], 1)] + "address, code, nonce, balance", + [(0, [], 0, 0), (2**160 - 1, [1, 2, 3], 1, 1)], ) async def test_should_return_account_with_default_dict_as_storage( - self, account, address, code, nonce + self, account, address, code, nonce, balance ): await account.test__init__should_return_account_with_default_dict_as_storage( - address, code, nonce + address, code, nonce, balance ).call() class TestCopy: @pytest.mark.parametrize( - "address, code, nonce", [(0, [], 0), (2**160 - 1, [1, 2, 3], 1)] + "address, code, nonce, balance", + [(0, [], 0, 0), (2**160 - 1, [1, 2, 3], 1, 1)], ) - async def test_should_return_account_with_default_dict_as_storage( - self, account, address, code, nonce + async def test_should_return_new_account_with_same_attributes( + self, account, address, code, nonce, balance ): await account.test__copy__should_return_new_account_with_same_attributes( - address, code, nonce + address, code, nonce, balance ).call() class TestFinalize: diff --git a/tests/src/kakarot/test_state.cairo b/tests/src/kakarot/test_state.cairo index 3051f16a0..5f21d86a3 100644 --- a/tests/src/kakarot/test_state.cairo +++ b/tests/src/kakarot/test_state.cairo @@ -10,11 +10,28 @@ from starkware.cairo.common.uint256 import Uint256, assert_uint256_eq from starkware.cairo.common.math import assert_not_equal from starkware.cairo.common.dict_access import DictAccess from starkware.cairo.common.registers import get_fp_and_pc +from starkware.starknet.common.syscalls import get_contract_address // Local dependencies from kakarot.model import model from kakarot.state import State from kakarot.account import Account +from kakarot.storages import native_token_address + +// Add a balanceOf for the accounts +@constructor +func constructor{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() { + let (contract_address) = get_contract_address(); + native_token_address.write(contract_address); + return (); +} + +@view +func balanceOf{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(account: felt) -> ( + balance: Uint256 +) { + return (Uint256(0, 0),); +} @external func test__init__should_return_state_with_default_dicts{ @@ -26,17 +43,12 @@ func test__init__should_return_state_with_default_dicts{ // Then assert state.accounts - state.accounts_start = 0; assert state.events_len = 0; - assert state.balances - state.balances_start = 0; assert state.transfers_len = 0; let accounts = state.accounts; let (value) = dict_read{dict_ptr=accounts}(0xdead); assert value = 0; - let balances = state.balances; - let (value) = dict_read{dict_ptr=balances}(0xdead); - assert value = 0; - return (); } @@ -76,8 +88,6 @@ func test__copy__should_return_new_state_with_same_attributes{ accounts=state.accounts, events_len=state.events_len, events=state.events, - balances_start=state.balances_start, - balances=state.balances, transfers_len=1, transfers=state.transfers, ); From c3d8a977ab74e559017aa4bc57821d0e281e9b86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Walter?= Date: Tue, 21 Nov 2023 18:14:18 +0100 Subject: [PATCH 3/5] Use evm address as key in state --- src/data_availability/starknet.cairo | 34 +++++++++++-------- src/kakarot/evm.cairo | 2 +- .../instructions/system_operations.cairo | 7 ++-- src/kakarot/library.cairo | 6 ++-- src/kakarot/state.cairo | 14 ++++---- .../test_environmental_information.cairo | 9 ++--- .../instructions/test_system_operations.cairo | 21 ++++++------ 7 files changed, 50 insertions(+), 43 deletions(-) diff --git a/src/data_availability/starknet.cairo b/src/data_availability/starknet.cairo index 294a300e4..da713d417 100644 --- a/src/data_availability/starknet.cairo +++ b/src/data_availability/starknet.cairo @@ -6,9 +6,7 @@ from starkware.cairo.common.alloc import alloc from starkware.cairo.common.bool import FALSE from starkware.cairo.common.cairo_builtins import HashBuiltin from starkware.cairo.common.dict_access import DictAccess -from starkware.cairo.common.hash import hash2 from starkware.cairo.common.uint256 import Uint256 -from starkware.starknet.common.storage import normalize_address from starkware.starknet.common.syscalls import ( emit_event, get_contract_address, @@ -73,6 +71,25 @@ namespace Starknet { evm_to_starknet_address.write(evm_address, starknet_address); return (account_address=starknet_address); } + + // @notice Return the bytecode of a given account + // @dev Return empty if the account is not deployed + // @param evm_address The address of the account + // @return bytecode_len The len of the bytecode + // @return bytecode The bytecode + func get_bytecode{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( + evm_address: felt + ) -> (bytecode_len: felt, bytecode: felt*) { + let (starknet_address) = evm_to_starknet_address.read(evm_address); + + if (starknet_address == 0) { + let (bytecode: felt*) = alloc(); + return (0, bytecode); + } + + let (bytecode_len, bytecode) = IAccount.bytecode(starknet_address); + return (bytecode_len, bytecode); + } } namespace Internals { @@ -88,7 +105,7 @@ namespace Internals { return (); } - let starknet_address = accounts_start.key; + let (starknet_address) = Account.compute_starknet_address(accounts_start.key); let account = cast(accounts_start.new_value, Account.Summary*); _commit_account(account, starknet_address); @@ -216,15 +233,4 @@ namespace Internals { return _save_storage(starknet_address, storage_start + DictAccess.SIZE, storage_end); } - - // @notice Compute the storage address of the given key when the storage var interface is - // storage_(key: Uint256) - // @dev Just the generated addr method when compiling the contract_account - func _storage_addr{pedersen_ptr: HashBuiltin*, range_check_ptr}(key: Uint256*) -> (res: felt) { - let res = 1510236440068827666686527023008568026372765124888307403567795291192307314167; - let (res) = hash2{hash_ptr=pedersen_ptr}(res, cast(key, felt*)[0]); - let (res) = hash2{hash_ptr=pedersen_ptr}(res, cast(key, felt*)[1]); - let (res) = normalize_address(addr=res); - return (res=res); - } } diff --git a/src/kakarot/evm.cairo b/src/kakarot/evm.cairo index 39cca3096..753dd22a6 100644 --- a/src/kakarot/evm.cairo +++ b/src/kakarot/evm.cairo @@ -740,7 +740,7 @@ namespace EVM { let (state, success) = State.add_transfer(state, transfer); // Check collision - let account = Account.fetch_or_create(address); + let (state, account) = State.get_account(state, address); let code_or_nonce = Account.has_code_or_nonce(account); let is_collision = code_or_nonce * is_deploy_tx; // Nonce is set to 1 in case of deploy_tx diff --git a/src/kakarot/instructions/system_operations.cairo b/src/kakarot/instructions/system_operations.cairo index 66ac356e7..45d275f21 100644 --- a/src/kakarot/instructions/system_operations.cairo +++ b/src/kakarot/instructions/system_operations.cairo @@ -766,7 +766,7 @@ namespace CreateHelper { self=ctx.memory, element_len=size.low, element=bytecode, offset=offset.low ); - // Get new account address + // Get target account let (state, evm_contract_address) = get_evm_address( ctx.state, ctx.call_context.address, popped_len, popped, size.low, bytecode ); @@ -774,9 +774,8 @@ namespace CreateHelper { tempvar address = new model.Address(starknet_contract_address, evm_contract_address); // Create Account with empty bytecode - let account = Account.fetch_or_create(address); + let (state, account) = State.get_account(state, address); let is_collision = Account.has_code_or_nonce(account); - let account = Account.set_nonce(account, 1); // Update calling context before creating sub context let ctx = ExecutionContext.update_memory(ctx, memory); @@ -790,6 +789,8 @@ namespace CreateHelper { // Create sub context with copied state let state = State.copy(ctx.state); + let (state, account) = State.get_account(state, address); + let account = Account.set_nonce(account, 1); let state = State.set_account(state, address, account); let (calldata: felt*) = alloc(); tempvar call_context: model.CallContext* = new model.CallContext( diff --git a/src/kakarot/library.cairo b/src/kakarot/library.cairo index 58cc583a4..6d1b87b3e 100644 --- a/src/kakarot/library.cairo +++ b/src/kakarot/library.cairo @@ -93,14 +93,14 @@ namespace Kakarot { let is_regular_tx = is_not_zero(to); let is_deploy_tx = 1 - is_regular_tx; - let account = Account.fetch_or_create(address); + let (bytecode_len, bytecode) = Starknet.get_bytecode(address.evm); let summary = EVM.execute( address, is_deploy_tx, origin_address, - account.code_len, - account.code, + bytecode_len, + bytecode, data_len, data, value, diff --git a/src/kakarot/state.cairo b/src/kakarot/state.cairo index f5b435a57..600e7262f 100644 --- a/src/kakarot/state.cairo +++ b/src/kakarot/state.cairo @@ -102,7 +102,7 @@ namespace State { ) -> (model.State*, model.Account*) { alloc_locals; let accounts = self.accounts; - let (pointer) = dict_read{dict_ptr=accounts}(key=address.starknet); + let (pointer) = dict_read{dict_ptr=accounts}(key=address.evm); // Return from local storage if found if (pointer != 0) { @@ -120,7 +120,7 @@ namespace State { // Otherwise read values from contract storage local accounts: DictAccess* = accounts; let account = Account.fetch_or_create(address); - dict_write{dict_ptr=accounts}(key=address.starknet, new_value=cast(account, felt)); + dict_write{dict_ptr=accounts}(key=address.evm, new_value=cast(account, felt)); tempvar state = new model.State( accounts_start=self.accounts_start, accounts=accounts, @@ -141,7 +141,7 @@ namespace State { self: model.State*, address: model.Address*, account: model.Account* ) -> model.State* { let accounts = self.accounts; - dict_write{dict_ptr=accounts}(key=address.starknet, new_value=cast(account, felt)); + dict_write{dict_ptr=accounts}(key=address.evm, new_value=cast(account, felt)); return new model.State( accounts_start=self.accounts_start, accounts=accounts, @@ -231,10 +231,8 @@ namespace State { let recipient = Account.set_balance(recipient, &recipient_balance_new); let accounts = self.accounts; - dict_write{dict_ptr=accounts}(key=transfer.sender.starknet, new_value=cast(sender, felt)); - dict_write{dict_ptr=accounts}( - key=transfer.recipient.starknet, new_value=cast(recipient, felt) - ); + dict_write{dict_ptr=accounts}(key=transfer.sender.evm, new_value=cast(sender, felt)); + dict_write{dict_ptr=accounts}(key=transfer.recipient.evm, new_value=cast(recipient, felt)); assert self.transfers[self.transfers_len] = transfer; tempvar state = new model.State( @@ -256,7 +254,7 @@ namespace State { self: model.State*, address: model.Address* ) -> (state: model.State*, balance: Uint256) { let accounts = self.accounts; - let (pointer) = dict_read{dict_ptr=accounts}(key=address.starknet); + let (pointer) = dict_read{dict_ptr=accounts}(key=address.evm); tempvar self = new model.State( accounts_start=self.accounts_start, accounts=accounts, diff --git a/tests/src/kakarot/instructions/test_environmental_information.cairo b/tests/src/kakarot/instructions/test_environmental_information.cairo index 1960ae406..b49f59d17 100644 --- a/tests/src/kakarot/instructions/test_environmental_information.cairo +++ b/tests/src/kakarot/instructions/test_environmental_information.cairo @@ -15,6 +15,7 @@ from starkware.starknet.common.syscalls import get_contract_address from openzeppelin.token.erc20.library import ERC20 // Local dependencies +from data_availability.starknet import Starknet from kakarot.account import Account from kakarot.constants import Constants from kakarot.storages import ( @@ -123,7 +124,7 @@ func test__exec_extcodesize__should_handle_address_with_no_code{ let (contract_account_class_hash_) = contract_account_class_hash.read(); let (evm_contract_address) = CreateHelper.get_create_address(0, 0); - let (local starknet_contract_address) = Account.deploy( + let (local starknet_contract_address) = Starknet.deploy( contract_account_class_hash_, evm_contract_address ); let address = Helpers.to_uint256(evm_contract_address); @@ -159,7 +160,7 @@ func test__exec_extcodecopy__should_handle_address_with_code{ let (contract_account_class_hash_) = contract_account_class_hash.read(); let (evm_contract_address) = CreateHelper.get_create_address(0, 0); - let (local starknet_contract_address) = Account.deploy( + let (local starknet_contract_address) = Starknet.deploy( contract_account_class_hash_, evm_contract_address ); IContractAccount.write_bytecode(starknet_contract_address, bytecode_len, bytecode); @@ -211,7 +212,7 @@ func test__exec_extcodecopy__should_handle_address_with_no_code{ let (contract_account_class_hash_) = contract_account_class_hash.read(); let (evm_contract_address) = CreateHelper.get_create_address(0, 0); - let (local starknet_contract_address) = Account.deploy( + let (local starknet_contract_address) = Starknet.deploy( contract_account_class_hash_, evm_contract_address ); let evm_contract_address_uint256 = Helpers.to_uint256(evm_contract_address); @@ -387,7 +388,7 @@ func test__exec_extcodehash__should_handle_address_with_code{ let (contract_account_class_hash_) = contract_account_class_hash.read(); let (evm_contract_address) = CreateHelper.get_create_address(0, 0); - let (local starknet_contract_address) = Account.deploy( + let (local starknet_contract_address) = Starknet.deploy( contract_account_class_hash_, evm_contract_address ); IContractAccount.write_bytecode(starknet_contract_address, bytecode_len, bytecode); diff --git a/tests/src/kakarot/instructions/test_system_operations.cairo b/tests/src/kakarot/instructions/test_system_operations.cairo index c3f481f9c..298380ab8 100644 --- a/tests/src/kakarot/instructions/test_system_operations.cairo +++ b/tests/src/kakarot/instructions/test_system_operations.cairo @@ -15,6 +15,7 @@ from starkware.starknet.common.syscalls import deploy, get_contract_address from openzeppelin.token.erc20.library import ERC20 // Local dependencies +from data_availability.starknet import Starknet from kakarot.constants import Constants from kakarot.storages import ( native_token_address, @@ -170,11 +171,11 @@ func test__exec_call__should_return_a_new_context_based_on_calling_ctx_stack{ let (contract_account_class_hash_) = contract_account_class_hash.read(); let (caller_evm_contract_address) = CreateHelper.get_create_address(0, 0); - let (caller_starknet_contract_address) = Account.deploy( + let (caller_starknet_contract_address) = Starknet.deploy( contract_account_class_hash_, caller_evm_contract_address ); let (callee_evm_contract_address) = CreateHelper.get_create_address(1, 0); - let (callee_starknet_contract_address) = Account.deploy( + let (callee_starknet_contract_address) = Starknet.deploy( contract_account_class_hash_, callee_evm_contract_address ); @@ -258,14 +259,14 @@ func test__exec_call__should_transfer_value{ let (contract_account_class_hash_) = contract_account_class_hash.read(); let (caller_evm_contract_address) = CreateHelper.get_create_address(0, 0); - let (caller_starknet_contract_address) = Account.deploy( + let (caller_starknet_contract_address) = Starknet.deploy( contract_account_class_hash_, caller_evm_contract_address ); tempvar caller_address = new model.Address( caller_starknet_contract_address, caller_evm_contract_address ); let (callee_evm_contract_address) = CreateHelper.get_create_address(1, 0); - let (callee_starknet_contract_address) = Account.deploy( + let (callee_starknet_contract_address) = Starknet.deploy( contract_account_class_hash_, callee_evm_contract_address ); tempvar callee_address = new model.Address( @@ -327,11 +328,11 @@ func test__exec_callcode__should_return_a_new_context_based_on_calling_ctx_stack let (contract_account_class_hash_) = contract_account_class_hash.read(); let (caller_evm_contract_address) = CreateHelper.get_create_address(0, 0); - let (caller_starknet_contract_address) = Account.deploy( + let (caller_starknet_contract_address) = Starknet.deploy( contract_account_class_hash_, caller_evm_contract_address ); let (callee_evm_contract_address) = CreateHelper.get_create_address(1, 0); - let (_) = Account.deploy(contract_account_class_hash_, callee_evm_contract_address); + let (_) = Starknet.deploy(contract_account_class_hash_, callee_evm_contract_address); // Fill the stack with input data let stack: model.Stack* = Stack.init(); @@ -407,14 +408,14 @@ func test__exec_callcode__should_transfer_value{ let (contract_account_class_hash_) = contract_account_class_hash.read(); let (caller_evm_contract_address) = CreateHelper.get_create_address(0, 0); - let (caller_starknet_contract_address) = Account.deploy( + let (caller_starknet_contract_address) = Starknet.deploy( contract_account_class_hash_, caller_evm_contract_address ); tempvar caller_address = new model.Address( caller_starknet_contract_address, caller_evm_contract_address ); let (callee_evm_contract_address) = CreateHelper.get_create_address(1, 0); - let (callee_starknet_contract_address) = Account.deploy( + let (callee_starknet_contract_address) = Starknet.deploy( contract_account_class_hash_, callee_evm_contract_address ); tempvar callee_address = new model.Address( @@ -476,7 +477,7 @@ func test__exec_staticcall__should_return_a_new_context_based_on_calling_ctx_sta let (contract_account_class_hash_) = contract_account_class_hash.read(); let (evm_contract_address) = CreateHelper.get_create_address(0, 0); - let (local starknet_contract_address) = Account.deploy( + let (local starknet_contract_address) = Starknet.deploy( contract_account_class_hash_, evm_contract_address ); @@ -550,7 +551,7 @@ func test__exec_delegatecall__should_return_a_new_context_based_on_calling_ctx_s let (contract_account_class_hash_) = contract_account_class_hash.read(); let (evm_contract_address) = CreateHelper.get_create_address(0, 0); - let (local starknet_contract_address) = Account.deploy( + let (local starknet_contract_address) = Starknet.deploy( contract_account_class_hash_, evm_contract_address ); From b597d611d6b20e7695a71069650ed026593c0b8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Walter?= Date: Wed, 22 Nov 2023 09:23:07 +0100 Subject: [PATCH 4/5] Fix tests --- src/data_availability/starknet.cairo | 6 ++ src/kakarot/state.cairo | 6 ++ .../instructions/test_system_operations.cairo | 42 +++++------- .../instructions/test_system_operations.py | 66 ++++++++++++------- 4 files changed, 72 insertions(+), 48 deletions(-) diff --git a/src/data_availability/starknet.cairo b/src/data_availability/starknet.cairo index da713d417..ad5fbd266 100644 --- a/src/data_availability/starknet.cairo +++ b/src/data_availability/starknet.cairo @@ -105,6 +105,12 @@ namespace Internals { return (); } + // Skip account if it has indeed never been fetched + // but only touched for balance read + if (accounts_start.new_value == 0) { + return _commit_accounts(accounts_start + DictAccess.SIZE, accounts_end); + } + let (starknet_address) = Account.compute_starknet_address(accounts_start.key); let account = cast(accounts_start.new_value, Account.Summary*); _commit_account(account, starknet_address); diff --git a/src/kakarot/state.cairo b/src/kakarot/state.cairo index 600e7262f..9d3eacf87 100644 --- a/src/kakarot/state.cairo +++ b/src/kakarot/state.cairo @@ -284,6 +284,12 @@ namespace Internals { return (); } + // Skip account if it has indeed never been fetched + // but only touched for balance read + if (accounts_start.new_value == 0) { + return _finalize_accounts(accounts_start + DictAccess.SIZE, accounts_end); + } + let account = cast(accounts_start.new_value, model.Account*); let account_summary = Account.copy(account); dict_write{dict_ptr=accounts}( diff --git a/tests/src/kakarot/instructions/test_system_operations.cairo b/tests/src/kakarot/instructions/test_system_operations.cairo index 298380ab8..7311b8118 100644 --- a/tests/src/kakarot/instructions/test_system_operations.cairo +++ b/tests/src/kakarot/instructions/test_system_operations.cairo @@ -6,13 +6,9 @@ from starkware.cairo.common.alloc import alloc from starkware.cairo.common.bool import TRUE, FALSE from starkware.cairo.common.cairo_builtins import HashBuiltin, BitwiseBuiltin -from starkware.cairo.common.default_dict import default_dict_new -from starkware.cairo.common.math import split_felt, assert_not_zero, assert_le +from starkware.cairo.common.math import split_felt, assert_not_zero from starkware.cairo.common.uint256 import Uint256, uint256_sub -from starkware.starknet.common.syscalls import deploy, get_contract_address - -// Third party dependencies -from openzeppelin.token.erc20.library import ERC20 +from starkware.starknet.common.syscalls import get_contract_address // Local dependencies from data_availability.starknet import Starknet @@ -25,8 +21,6 @@ from kakarot.storages import ( from kakarot.execution_context import ExecutionContext from kakarot.instructions.memory_operations import MemoryOperations from kakarot.instructions.system_operations import SystemOperations, CallHelper, CreateHelper -from kakarot.interfaces.interfaces import IContractAccount, IKakarot, IAccount, IERC20 -from kakarot.library import Kakarot from kakarot.account import Account from kakarot.model import model from kakarot.stack import Stack @@ -61,7 +55,8 @@ func constructor{ func get_native_token{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> ( native_token_address: felt ) { - return Kakarot.get_native_token(); + let (native_token) = native_token_address.read(); + return (native_token,); } // @dev mock function that returns the computed starknet address from an evm address @@ -196,9 +191,6 @@ func test__exec_call__should_return_a_new_context_based_on_calling_ctx_stack{ let stack = Stack.push(stack, value); let stack = Stack.push(stack, address); let stack = Stack.push(stack, gas); - // Put some value in memory as it is used for calldata with args_size and args_offset - // Word is 0x 11 22 33 44 55 66 77 88 00 00 ... 00 - // calldata should be 0x 44 55 66 77 tempvar memory_word = new Uint256(low=0, high=0x11223344556677880000000000000000); tempvar memory_offset = new Uint256(0, 0); let stack = Stack.push(stack, memory_word); @@ -262,16 +254,10 @@ func test__exec_call__should_transfer_value{ let (caller_starknet_contract_address) = Starknet.deploy( contract_account_class_hash_, caller_evm_contract_address ); - tempvar caller_address = new model.Address( - caller_starknet_contract_address, caller_evm_contract_address - ); let (callee_evm_contract_address) = CreateHelper.get_create_address(1, 0); let (callee_starknet_contract_address) = Starknet.deploy( contract_account_class_hash_, callee_evm_contract_address ); - tempvar callee_address = new model.Address( - callee_starknet_contract_address, callee_evm_contract_address - ); // Fill the stack with input data let stack: model.Stack* = Stack.init(); @@ -290,17 +276,20 @@ func test__exec_call__should_transfer_value{ let stack = Stack.push(stack, value); let stack = Stack.push(stack, address); let stack = Stack.push(stack, gas); - tempvar memory_word = new Uint256(low=0, high=22774453838368691922685013100469420032); + tempvar memory_word = new Uint256(low=0, high=0x11223344556677880000000000000000); tempvar memory_offset = new Uint256(0, 0); let stack = Stack.push(stack, memory_word); let stack = Stack.push(stack, memory_offset); let (bytecode) = alloc(); - local bytecode_len = 0; let ctx = TestHelpers.init_context_at_address_with_stack( - caller_starknet_contract_address, caller_evm_contract_address, bytecode_len, bytecode, stack + caller_starknet_contract_address, caller_evm_contract_address, 0, bytecode, stack ); let ctx = MemoryOperations.exec_mstore(ctx); + // Get the balance of caller pre-call + tempvar caller_address = new model.Address( + caller_starknet_contract_address, caller_evm_contract_address + ); let (state, caller_balance_prev) = State.read_balance(ctx.state, caller_address); let ctx = ExecutionContext.update_state(ctx, state); @@ -310,6 +299,9 @@ func test__exec_call__should_transfer_value{ // Then // get balances of caller and callee post-call let state = sub_ctx.state; + tempvar callee_address = new model.Address( + callee_starknet_contract_address, callee_evm_contract_address + ); let (state, callee_balance) = State.read_balance(state, callee_address); let (state, caller_balance_new) = State.read_balance(state, caller_address); let (caller_diff_balance) = uint256_sub(caller_balance_prev, caller_balance_new); @@ -354,7 +346,7 @@ func test__exec_callcode__should_return_a_new_context_based_on_calling_ctx_stack // Put some value in memory as it is used for calldata with args_size and args_offset // Word is 0x 11 22 33 44 55 66 77 88 00 00 ... 00 // calldata should be 0x 44 55 66 77 - tempvar memory_word = new Uint256(low=0, high=22774453838368691922685013100469420032); + tempvar memory_word = new Uint256(low=0, high=0x11223344556677880000000000000000); tempvar memory_offset = new Uint256(0, 0); let stack = Stack.push(stack, memory_word); let stack = Stack.push(stack, memory_offset); @@ -439,7 +431,7 @@ func test__exec_callcode__should_transfer_value{ let stack = Stack.push(stack, value); let stack = Stack.push(stack, address); let stack = Stack.push(stack, gas); - tempvar memory_word = new Uint256(low=0, high=22774453838368691922685013100469420032); + tempvar memory_word = new Uint256(low=0, high=0x11223344556677880000000000000000); tempvar memory_offset = new Uint256(0, 0); let stack = Stack.push(stack, memory_word); let stack = Stack.push(stack, memory_offset); @@ -499,7 +491,7 @@ func test__exec_staticcall__should_return_a_new_context_based_on_calling_ctx_sta // Put some value in memory as it is used for calldata with args_size and args_offset // Word is 0x 11 22 33 44 55 66 77 88 00 00 ... 00 // calldata should be 0x 44 55 66 77 - tempvar memory_word = new Uint256(low=0, high=22774453838368691922685013100469420032); + tempvar memory_word = new Uint256(low=0, high=0x11223344556677880000000000000000); tempvar memory_offset = new Uint256(0, 0); let stack = Stack.push(stack, memory_word); let stack = Stack.push(stack, memory_offset); @@ -573,7 +565,7 @@ func test__exec_delegatecall__should_return_a_new_context_based_on_calling_ctx_s // Put some value in memory as it is used for calldata with args_size and args_offset // Word is 0x 11 22 33 44 55 66 77 88 00 00 ... 00 // calldata should be 0x 44 55 66 77 - tempvar memory_word = new Uint256(low=0, high=22774453838368691922685013100469420032); + tempvar memory_word = new Uint256(low=0, high=0x11223344556677880000000000000000); tempvar memory_offset = new Uint256(0, 0); let stack = Stack.push(stack, memory_word); let stack = Stack.push(stack, memory_offset); diff --git a/tests/src/kakarot/instructions/test_system_operations.py b/tests/src/kakarot/instructions/test_system_operations.py index c353997af..be2aa821e 100644 --- a/tests/src/kakarot/instructions/test_system_operations.py +++ b/tests/src/kakarot/instructions/test_system_operations.py @@ -61,29 +61,49 @@ async def test_return(self, system_operations): 1000 ).call() - async def test_call(self, system_operations, mint): - await mint(ZERO_ACCOUNT, 2) - await system_operations.test__exec_call__should_return_a_new_context_based_on_calling_ctx_stack().call( - system_operations.contract_address - ) - - await system_operations.test__exec_callcode__should_return_a_new_context_based_on_calling_ctx_stack().call( - system_operations.contract_address - ) - - await system_operations.test__exec_staticcall__should_return_a_new_context_based_on_calling_ctx_stack().call() - - await system_operations.test__exec_delegatecall__should_return_a_new_context_based_on_calling_ctx_stack().call() - - async def test_call__should_transfer_value(self, system_operations, mint): - await mint(ZERO_ACCOUNT, 2) - await system_operations.test__exec_call__should_transfer_value().call( - system_operations.contract_address - ) - - await system_operations.test__exec_callcode__should_transfer_value().call( - system_operations.contract_address - ) + class TestCall: + async def test_should_return_a_new_context_based_on_calling_ctx_stack( + self, system_operations, mint + ): + await mint(ZERO_ACCOUNT, 2) + await system_operations.test__exec_call__should_return_a_new_context_based_on_calling_ctx_stack().call( + system_operations.contract_address + ) + + async def test_should_transfer_value(self, system_operations, mint): + await mint(ZERO_ACCOUNT, 2) + await system_operations.test__exec_call__should_transfer_value().call( + system_operations.contract_address + ) + + class TestCallcode: + async def test_should_return_a_new_context_based_on_calling_ctx_stack( + self, system_operations, mint + ): + await mint(ZERO_ACCOUNT, 2) + await system_operations.test__exec_callcode__should_return_a_new_context_based_on_calling_ctx_stack().call( + system_operations.contract_address + ) + + async def test_should_transfer_value(self, system_operations, mint): + await mint(ZERO_ACCOUNT, 2) + await system_operations.test__exec_callcode__should_transfer_value().call( + system_operations.contract_address + ) + + class TestStaticcall: + async def test_should_return_a_new_context_based_on_calling_ctx_stack( + self, system_operations, mint + ): + await mint(ZERO_ACCOUNT, 2) + await system_operations.test__exec_staticcall__should_return_a_new_context_based_on_calling_ctx_stack().call() + + class TestDelegatecall: + async def test_should_return_a_new_context_based_on_calling_ctx_stack( + self, system_operations, mint + ): + await mint(ZERO_ACCOUNT, 2) + await system_operations.test__exec_delegatecall__should_return_a_new_context_based_on_calling_ctx_stack().call() async def test_create(self, system_operations): salt = 0 From d3f3afb7e41a4ddc82fcb700a3802779c79a3c89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Walter?= Date: Wed, 22 Nov 2023 13:27:05 +0100 Subject: [PATCH 5/5] Remove State.read_balance --- src/data_availability/standalone.cairo | 0 src/data_availability/starknet.cairo | 6 --- .../instructions/block_information.cairo | 4 +- .../environmental_information.cairo | 8 ++-- .../instructions/system_operations.cairo | 4 +- src/kakarot/state.cairo | 38 ------------------- .../instructions/test_system_operations.cairo | 21 +++++----- 7 files changed, 18 insertions(+), 63 deletions(-) delete mode 100644 src/data_availability/standalone.cairo diff --git a/src/data_availability/standalone.cairo b/src/data_availability/standalone.cairo deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/data_availability/starknet.cairo b/src/data_availability/starknet.cairo index ad5fbd266..da713d417 100644 --- a/src/data_availability/starknet.cairo +++ b/src/data_availability/starknet.cairo @@ -105,12 +105,6 @@ namespace Internals { return (); } - // Skip account if it has indeed never been fetched - // but only touched for balance read - if (accounts_start.new_value == 0) { - return _commit_accounts(accounts_start + DictAccess.SIZE, accounts_end); - } - let (starknet_address) = Account.compute_starknet_address(accounts_start.key); let account = cast(accounts_start.new_value, Account.Summary*); _commit_account(account, starknet_address); diff --git a/src/kakarot/instructions/block_information.cairo b/src/kakarot/instructions/block_information.cairo index b238802e6..1e3d22b8b 100644 --- a/src/kakarot/instructions/block_information.cairo +++ b/src/kakarot/instructions/block_information.cairo @@ -181,8 +181,8 @@ namespace Internals { func selfbalance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( ctx: model.ExecutionContext* ) -> (model.ExecutionContext*, Uint256) { - let (state, balance) = State.read_balance(ctx.state, ctx.call_context.address); + let (state, account) = State.get_account(ctx.state, ctx.call_context.address); let ctx = ExecutionContext.update_state(ctx, state); - return (ctx, balance); + return (ctx, [account.balance]); } } diff --git a/src/kakarot/instructions/environmental_information.cairo b/src/kakarot/instructions/environmental_information.cairo index da10ac849..ebecdf191 100644 --- a/src/kakarot/instructions/environmental_information.cairo +++ b/src/kakarot/instructions/environmental_information.cairo @@ -71,8 +71,8 @@ namespace EnvironmentalInformation { let evm_address = Helpers.uint256_to_felt([address_uint256]); let (starknet_address) = Account.compute_starknet_address(evm_address); tempvar address = new model.Address(starknet_address, evm_address); - let (state, balance) = State.read_balance(ctx.state, address); - let stack = Stack.push_uint256(stack, balance); + let (state, account) = State.get_account(ctx.state, address); + let stack = Stack.push_uint256(stack, [account.balance]); let ctx = ExecutionContext.update_stack(ctx, stack); let ctx = ExecutionContext.update_state(ctx, state); @@ -565,8 +565,8 @@ namespace EnvironmentalInformation { let (state, account) = State.get_account(ctx.state, address); let has_code_or_nonce = Account.has_code_or_nonce(account); - let (state, balance) = State.read_balance(state, address); - let account_exists = has_code_or_nonce + balance.low; + let (state, account) = State.get_account(state, address); + let account_exists = has_code_or_nonce + account.balance.low; // Relevant cases: // https://github.com/ethereum/go-ethereum/blob/master/core/vm/instructions.go#L392 if (account_exists == 0) { diff --git a/src/kakarot/instructions/system_operations.cairo b/src/kakarot/instructions/system_operations.cairo index 45d275f21..777aac259 100644 --- a/src/kakarot/instructions/system_operations.cairo +++ b/src/kakarot/instructions/system_operations.cairo @@ -351,9 +351,9 @@ namespace SystemOperations { let (recipient_starknet_address) = Account.compute_starknet_address(recipient_evm_address); tempvar recipient = new model.Address(recipient_starknet_address, recipient_evm_address); - let (state, balance) = State.read_balance(ctx.state, ctx.call_context.address); + let (state, account) = State.get_account(ctx.state, ctx.call_context.address); let transfer = model.Transfer( - sender=ctx.call_context.address, recipient=recipient, amount=balance + sender=ctx.call_context.address, recipient=recipient, amount=[account.balance] ); let (state, success) = State.add_transfer(state, transfer); diff --git a/src/kakarot/state.cairo b/src/kakarot/state.cairo index 9d3eacf87..dc62dc927 100644 --- a/src/kakarot/state.cairo +++ b/src/kakarot/state.cairo @@ -245,32 +245,6 @@ namespace State { ); return (state, success); } - - // @notice Get the balance of a given address - // @dev Try to read from local dict, and read from ETH contract otherwise - // @param self The pointer to the State - // @param address The pointer to the Address - func read_balance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - self: model.State*, address: model.Address* - ) -> (state: model.State*, balance: Uint256) { - let accounts = self.accounts; - let (pointer) = dict_read{dict_ptr=accounts}(key=address.evm); - tempvar self = new model.State( - accounts_start=self.accounts_start, - accounts=accounts, - events_len=self.events_len, - events=self.events, - transfers_len=self.transfers_len, - transfers=self.transfers, - ); - if (pointer != 0) { - let account = cast(pointer, model.Account*); - return (self, [account.balance]); - } else { - let balance = Account.read_balance(address); - return (self, balance); - } - } } namespace Internals { @@ -284,12 +258,6 @@ namespace Internals { return (); } - // Skip account if it has indeed never been fetched - // but only touched for balance read - if (accounts_start.new_value == 0) { - return _finalize_accounts(accounts_start + DictAccess.SIZE, accounts_end); - } - let account = cast(accounts_start.new_value, model.Account*); let account_summary = Account.copy(account); dict_write{dict_ptr=accounts}( @@ -309,12 +277,6 @@ namespace Internals { return (); } - // Skip account if it has indeed never been fetched - // but only touched for balance read - if (accounts_start.new_value == 0) { - return _finalize_accounts(accounts_start + DictAccess.SIZE, accounts_end); - } - let account = cast(accounts_start.new_value, model.Account*); let account_summary = Account.finalize(account); dict_write{dict_ptr=accounts}( diff --git a/tests/src/kakarot/instructions/test_system_operations.cairo b/tests/src/kakarot/instructions/test_system_operations.cairo index 7311b8118..227a15a4e 100644 --- a/tests/src/kakarot/instructions/test_system_operations.cairo +++ b/tests/src/kakarot/instructions/test_system_operations.cairo @@ -290,7 +290,7 @@ func test__exec_call__should_transfer_value{ tempvar caller_address = new model.Address( caller_starknet_contract_address, caller_evm_contract_address ); - let (state, caller_balance_prev) = State.read_balance(ctx.state, caller_address); + let (state, caller_account_prev) = State.get_account(ctx.state, caller_address); let ctx = ExecutionContext.update_state(ctx, state); // When @@ -302,11 +302,12 @@ func test__exec_call__should_transfer_value{ tempvar callee_address = new model.Address( callee_starknet_contract_address, callee_evm_contract_address ); - let (state, callee_balance) = State.read_balance(state, callee_address); - let (state, caller_balance_new) = State.read_balance(state, caller_address); - let (caller_diff_balance) = uint256_sub(caller_balance_prev, caller_balance_new); - - assert callee_balance = Uint256(2, 0); + let (state, callee_account) = State.get_account(state, callee_address); + let (state, caller_account_new) = State.get_account(state, caller_address); + let (caller_diff_balance) = uint256_sub( + [caller_account_prev.balance], [caller_account_new.balance] + ); + assert [callee_account.balance] = Uint256(2, 0); assert caller_diff_balance = Uint256(2, 0); return (); } @@ -443,7 +444,7 @@ func test__exec_callcode__should_transfer_value{ let ctx = MemoryOperations.exec_mstore(ctx); // Get the balance of caller pre-call - let (state, caller_pre_balance) = State.read_balance(ctx.state, caller_address); + let (state, caller_pre_account) = State.get_account(ctx.state, caller_address); let ctx = ExecutionContext.update_state(ctx, state); // When @@ -452,11 +453,9 @@ func test__exec_callcode__should_transfer_value{ // Then // get balances of caller and callee post-call let state = sub_ctx.state; - let (state, caller_post_balance) = State.read_balance(state, caller_address); - let (state, callee_balance) = State.read_balance(state, callee_address); - let (caller_diff_balance) = uint256_sub(caller_pre_balance, caller_post_balance); + let (state, caller_post_account) = State.get_account(state, caller_address); - assert caller_post_balance = caller_pre_balance; + assert caller_post_account.balance = caller_pre_account.balance; return (); }