diff --git a/pyproject.toml b/pyproject.toml index 7400b2eff..2c15af560 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -102,6 +102,8 @@ markers = [ "MLOAD: Opcode Value 0x51 - Load word from memory", "MSTORE: Opcode Value 0x52 - Save word to memory", "MSTORE8: Opcode Value 0x53 - Save byte to memory", + "SLOAD: Opcode Value 0x54 - Load word from storage", + "SSTORE: Opcode Value 0x55 - Save word to storage", "JUMP: Opcode Value 0x56 - Alter the program counter", "JUMPI: Opcode Value 0x57 - Conditionally alter the program counter", "PC: Opcode Value 0x58 - Get the value of the program counter prior to the increment", diff --git a/scripts/utils/kakarot.py b/scripts/utils/kakarot.py index 2f067cef5..ade24067f 100644 --- a/scripts/utils/kakarot.py +++ b/scripts/utils/kakarot.py @@ -134,7 +134,7 @@ async def deploy( if success == 0: raise EvmTransactionError(response) - evm_address, starknet_address = response + starknet_address, evm_address = response contract.address = Web3.to_checksum_address(f"0x{evm_address:040x}") contract.starknet_address = starknet_address logger.info(f"✅ {contract_name} deployed at address {contract.address}") diff --git a/solidity_contracts/src/PlainOpcodes/PlainOpcodes.sol b/solidity_contracts/src/PlainOpcodes/PlainOpcodes.sol index 6a3b7075c..a7398ed1c 100644 --- a/solidity_contracts/src/PlainOpcodes/PlainOpcodes.sol +++ b/solidity_contracts/src/PlainOpcodes/PlainOpcodes.sol @@ -2,6 +2,7 @@ pragma solidity >=0.8.0; import "./RevertTestCases.sol"; +import "./Counter.sol"; interface ICounter { function count() external view returns (uint256); @@ -31,6 +32,11 @@ contract PlainOpcodes { event Log3(address indexed owner, address indexed spender, uint256 value); event Log4(address indexed owner, address indexed spender, uint256 indexed value); + event SentSome(address to, uint256 amount, bool success); + event NonceIncreased(uint256 nonce); + + mapping(address => uint256) public nonces; + /*////////////////////////////////////////////////////////////// CONSTRUCTOR //////////////////////////////////////////////////////////////*/ @@ -41,6 +47,10 @@ contract PlainOpcodes { /*////////////////////////////////////////////////////////////// FUNCTIONS FOR OPCODES //////////////////////////////////////////////////////////////*/ + function incrementMapping() public { + emit NonceIncreased(nonces[msg.sender]++); + } + function opcodeBlockHash(uint256 blockNumber) public view returns (bytes32 _blockhash) { return (blockhash(blockNumber)); } @@ -110,6 +120,24 @@ contract PlainOpcodes { emit Create2Address(_address); } + function createCounterAndCall() public returns (address counter_) { + bytes memory bytecode = type(Counter).creationCode; + assembly { + counter_ := create(0, add(bytecode, 32), mload(bytecode)) + } + ICounter(counter_).count(); + emit CreateAddress(counter_); + } + + function createCounterAndInvoke() public returns (address counter_) { + bytes memory bytecode = type(Counter).creationCode; + assembly { + counter_ := create(0, add(bytecode, 32), mload(bytecode)) + } + ICounter(counter_).inc(); + emit CreateAddress(counter_); + } + function requireNotZero(uint256 value) external pure { require(value != 0, "ZERO_VALUE"); } @@ -151,10 +179,13 @@ contract PlainOpcodes { function sendSome(address payable to, uint256 amount) public { bool success = to.send(amount); - require(success, "failed to send"); + emit SentSome(to, amount, success); } - function kill(address payable to) public { + function kill(address payable to) public payable { selfdestruct(to); } + + receive() external payable {} + fallback() external payable {} } diff --git a/solidity_contracts/src/PlainOpcodes/RevertTestCases.sol b/solidity_contracts/src/PlainOpcodes/RevertTestCases.sol index 50e2cc657..00bd2d553 100644 --- a/solidity_contracts/src/PlainOpcodes/RevertTestCases.sol +++ b/solidity_contracts/src/PlainOpcodes/RevertTestCases.sol @@ -1,22 +1,24 @@ pragma solidity >=0.8.0; + import "./Counter.sol"; contract ContractRevertsOnMethodCall { Counter public counter; - uint public value; - event PartyTime(bool shouldDance); - + uint256 public value; + + event PartyTime(bool shouldDance); + function triggerRevert() public { - counter = new Counter(); + counter = new Counter(); value = 1; - - emit PartyTime(true); + + emit PartyTime(true); revert("FAIL"); } } contract ContractRevertsOnConstruction { - uint public value; + uint256 public value; constructor() { value = 42; @@ -24,3 +26,26 @@ contract ContractRevertsOnConstruction { } } +contract ContractWithSelfdestructMethod { + uint256 public count; + + constructor() payable {} + + function inc() public { + count++; + } + + function kill() public { + selfdestruct(payable(msg.sender)); + } +} + +contract ContractRevertOnFallbackAndReceive { + fallback() external payable { + revert("reverted on fallback"); + } + + receive() external payable { + revert("reverted on receive"); + } +} diff --git a/solidity_contracts/tests/PlainOpcodes.t.sol b/solidity_contracts/tests/PlainOpcodes.t.sol index b0f832f8e..d0dba39af 100644 --- a/solidity_contracts/tests/PlainOpcodes.t.sol +++ b/solidity_contracts/tests/PlainOpcodes.t.sol @@ -4,7 +4,7 @@ import "forge-std/Test.sol"; import "forge-std/console.sol"; import {PlainOpcodes} from "../src/PlainOpcodes/PlainOpcodes.sol"; -import {ContractRevertsOnMethodCall} from "../src/PlainOpcodes/RevertTestCases.sol"; +import {ContractRevertsOnMethodCall, ContractWithSelfdestructMethod} from "../src/PlainOpcodes/RevertTestCases.sol"; import {Counter} from "../src/PlainOpcodes/Counter.sol"; contract PlainOpcodesTest is Test { @@ -63,6 +63,19 @@ contract PlainOpcodesTest is Test { assert(keccak256(bytes(errorMessage)) == keccak256("FAIL")); } + function testSelfDestructAndCreateAgain() public { + bytes memory bytecode = type(ContractWithSelfdestructMethod).creationCode; + uint256 salt = 1234; + address addr = plainOpcodes.create2(bytecode, salt); + ContractWithSelfdestructMethod contract_ = ContractWithSelfdestructMethod(addr); + contract_.inc(); + contract_.kill(); + plainOpcodes.create2(bytecode, salt); + contract_.inc(); + uint256 count = contract_.count(); + assertEq(count, 2); + } + function testCreate() public { uint256 count = 4; address[] memory addresses = plainOpcodes.create(type(Counter).creationCode, count); @@ -73,7 +86,23 @@ contract PlainOpcodesTest is Test { } function testSelfDestruct() public { + (bool success,) = address(plainOpcodes).call{value: 0.1 ether}(""); + assert(success == true); + + uint256 contractBalanceBefore = address(plainOpcodes).balance; + assert(contractBalanceBefore == 0.1 ether); + uint256 callerBalanceBefore = address(this).balance; + plainOpcodes.kill(payable(address(this))); + + uint256 contractBalanceAfter = address(plainOpcodes).balance; + assert(contractBalanceAfter == 0); + + // Balance is transferred immediately + uint256 callerBalanceAfter = address(this).balance; + assert(callerBalanceAfter - callerBalanceBefore == 0.1 ether); + + // Account is still callable until the end of the tx uint256 value = plainOpcodes.loop(10); assert(value == 10); } diff --git a/src/kakarot/account.cairo b/src/kakarot/account.cairo new file mode 100644 index 000000000..0aadfc52a --- /dev/null +++ b/src/kakarot/account.cairo @@ -0,0 +1,465 @@ +// SPDX-License-Identifier: MIT + +%lang starknet + +from starkware.cairo.common.alloc import alloc +from starkware.cairo.common.bool import FALSE, TRUE +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 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, + hash_update, + hash_update_single, + hash_update_with_hashchain, +) + +from kakarot.constants import ( + Constants, + account_proxy_class_hash, + native_token_address, + contract_account_class_hash, +) +from kakarot.interfaces.interfaces import IAccount, IContractAccount +from kakarot.model import model +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 { + address: felt, + code_len: felt, + code: felt*, + storage_start: DictAccess*, + storage: DictAccess*, + nonce: felt, + selfdestruct: felt, + } + + // @notice Create a new account + // @dev New contract accounts start at nonce=1. + // @param address The EVM address of the account + // @param code_len The length of the code + // @param code The pointer to the code + // @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* { + let (storage_start) = default_dict_new(0); + return new model.Account( + address=address, + code_len=code_len, + code=code, + storage_start=storage_start, + storage=storage_start, + nonce=nonce, + selfdestruct=0, + ); + } + + // @dev Copy the Account to safely mutate the storage + // @param self The pointer to the Account + func copy{range_check_ptr}(self: model.Account*) -> model.Account* { + let (storage_start, storage) = default_dict_copy(self.storage_start, self.storage); + return new model.Account( + address=self.address, + code_len=self.code_len, + code=self.code, + storage_start=storage_start, + storage=storage, + nonce=self.nonce, + selfdestruct=self.selfdestruct, + ); + } + + // @dev Squash dicts used internally + // @param self The pointer to the Account + // @return a Summary Account, frozen + func finalize{range_check_ptr}(self: model.Account*) -> Summary* { + let (storage_start, storage) = default_dict_finalize(self.storage_start, self.storage, 0); + return new Summary( + address=self.address, + code_len=self.code_len, + code=self.code, + storage_start=storage_start, + storage=storage, + nonce=self.nonce, + selfdestruct=self.selfdestruct, + ); + } + + // @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) { + // If SELFDESTRUCT, just do nothing + if (self.selfdestruct != 0) { + return (); + } + + // Deploy accounts + let (class_hash) = contract_account_class_hash.read(); + deploy(class_hash, self.address); + // 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 (); + } + + // 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 + // @return the account populated with Starknet data + func fetch_or_create{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( + address: model.Address* + ) -> model.Account* { + alloc_locals; + let starknet_account_exists = is_registered(address.evm); + + // 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); + return account; + } + + let (account_type) = IAccount.account_type(contract_address=address.starknet); + + if (account_type == 'EOA') { + let (bytecode: felt*) = alloc(); + // 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); + return account; + } + + // Case CA + 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 + ); + return account; + } + + // @notice Read a given storage + // @dev Try to retrieve in the local Dict first, if not already here + // read the contract storage and cache the result. + // @param self The pointer to the execution Account. + // @param address The pointer to the Address. + // @param key The pointer to the storage key + // @return The updated Account + // @return The read value + func read_storage{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( + self: model.Account*, address: model.Address*, key: Uint256 + ) -> (model.Account*, Uint256) { + alloc_locals; + let storage = self.storage; + let (local storage_addr) = Internals._storage_addr(key); + let (pointer) = dict_read{dict_ptr=storage}(key=storage_addr); + + // Case reading from local storage + if (pointer != 0) { + // Return from local storage if found + let value_ptr = cast(pointer, Uint256*); + tempvar self = new model.Account( + self.address, + self.code_len, + self.code, + self.storage_start, + storage, + self.nonce, + self.selfdestruct, + ); + return (self, [value_ptr]); + } + + // Case reading from Starknet storage + let starknet_account_exists = is_registered(address.evm); + if (starknet_account_exists != 0) { + let (value) = IContractAccount.storage( + contract_address=address.starknet, storage_addr=storage_addr + ); + tempvar value_ptr = new Uint256(value.low, value.high); + tempvar syscall_ptr = syscall_ptr; + tempvar pedersen_ptr = pedersen_ptr; + tempvar range_check_ptr = range_check_ptr; + // Otherwise returns 0 + } else { + tempvar value_ptr = new Uint256(0, 0); + tempvar syscall_ptr = syscall_ptr; + tempvar pedersen_ptr = pedersen_ptr; + tempvar range_check_ptr = range_check_ptr; + } + + // Cache for possible later use (almost free and can save a syscall later on) + dict_write{dict_ptr=storage}(key=storage_addr, new_value=cast(value_ptr, felt)); + + tempvar self = new model.Account( + self.address, + self.code_len, + self.code, + self.storage_start, + storage, + self.nonce, + self.selfdestruct, + ); + return (self, [value_ptr]); + } + + // @notice Update a storage key with the given value + // @param self The pointer to the Account. + // @param key The pointer to the Uint256 storage key + // @param value The pointer to the Uint256 value + func write_storage{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( + self: model.Account*, key: Uint256, value: Uint256* + ) -> model.Account* { + alloc_locals; + local storage: DictAccess* = self.storage; + let (storage_addr) = Internals._storage_addr(key); + dict_write{dict_ptr=storage}(key=storage_addr, new_value=cast(value, felt)); + tempvar self = new model.Account( + self.address, + self.code_len, + self.code, + self.storage_start, + storage, + self.nonce, + self.selfdestruct, + ); + return self; + } + + // @notice Set the code of the Account + // @dev The only reason to set code after creation is in deploy transaction where + // the account exists from the beginning for setting storages, but the + // deployed bytecode is known at the end (the return_data of the tx). + // @param self The pointer to the Account. + // @param code_len The len of the code + // @param code The code array + func set_code(self: model.Account*, code_len: felt, code: felt*) -> model.Account* { + assert self.code_len = 0; + return new model.Account( + address=self.address, + code_len=code_len, + code=code, + storage_start=self.storage_start, + storage=self.storage, + nonce=self.nonce, + selfdestruct=self.selfdestruct, + ); + } + + // @notice Set the nonce of the Account + // @param self The pointer to the Account + // @param nonce The new nonce + func set_nonce(self: model.Account*, nonce: felt) -> 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=nonce, + selfdestruct=self.selfdestruct, + ); + } + + // @notice Register an account for SELFDESTRUCT + // @dev True means that the account will be erased at the end of the transaction + // @return The pointer to the updated Account + func selfdestruct(self: model.Account*) -> 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, + selfdestruct=1, + ); + } + + // @dev Returns the registered starknet address for a given EVM address. Returns 0 if no contract is deployed for this + // EVM address. + // @param evm_address The EVM address to transform to a starknet address + // @return starknet_address The Starknet Account Contract address or 0 if not already deployed + func get_registered_starknet_address{ + syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr + }(evm_address: felt) -> (starknet_address: felt) { + return evm_to_starknet_address.read(evm_address); + } + + // @dev As contract addresses are deterministic we can know what will be the address of a starknet contract from its input EVM address + // @dev Adapted code from: https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/starknet/core/os/contract_address/contract_address.cairo + // @param evm_address The EVM address to transform to a starknet address + // @return contract_address The Starknet Account Contract address (not necessarily deployed) + func compute_starknet_address{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( + evm_address: felt + ) -> (contract_address: felt) { + alloc_locals; + let (_deployer_address: felt) = get_contract_address(); + let (_account_proxy_class_hash: felt) = account_proxy_class_hash.read(); + let (constructor_calldata: felt*) = alloc(); + let (hash_state_ptr) = hash_init(); + let (hash_state_ptr) = hash_update_single{hash_ptr=pedersen_ptr}( + hash_state_ptr=hash_state_ptr, item=Constants.CONTRACT_ADDRESS_PREFIX + ); + // hash deployer + let (hash_state_ptr) = hash_update_single{hash_ptr=pedersen_ptr}( + hash_state_ptr=hash_state_ptr, item=_deployer_address + ); + // hash salt + let (hash_state_ptr) = hash_update_single{hash_ptr=pedersen_ptr}( + hash_state_ptr=hash_state_ptr, item=evm_address + ); + // hash class hash + let (hash_state_ptr) = hash_update_single{hash_ptr=pedersen_ptr}( + hash_state_ptr=hash_state_ptr, item=_account_proxy_class_hash + ); + let (hash_state_ptr) = hash_update_with_hashchain{hash_ptr=pedersen_ptr}( + hash_state_ptr=hash_state_ptr, data_ptr=constructor_calldata, data_length=0 + ); + let (contract_address_before_modulo) = hash_finalize{hash_ptr=pedersen_ptr}( + hash_state_ptr=hash_state_ptr + ); + let (contract_address) = normalize_address{range_check_ptr=range_check_ptr}( + addr=contract_address_before_modulo + ); + + 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 + // @return TRUE is either nonce > 0 or code_len > 0, FALSE otherwise + func has_code_or_nonce(self: model.Account*) -> felt { + if (self.nonce + self.code_len != 0) { + return TRUE; + } + return FALSE; + } + + // @notice Tell if an account is already registered + // @param address the address (EVM) as felt + // @return true if the account is already registered + func is_registered{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( + address: felt + ) -> felt { + alloc_locals; + let (local registered_starknet_account) = get_registered_starknet_address(address); + let starknet_account_exists = is_not_zero(registered_starknet_account); + return starknet_account_exists; + } +} + +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 + 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/accounts/contract/contract_account.cairo b/src/kakarot/accounts/contract/contract_account.cairo index bb3b0d6e2..f6be54903 100644 --- a/src/kakarot/accounts/contract/contract_account.cairo +++ b/src/kakarot/accounts/contract/contract_account.cairo @@ -3,13 +3,13 @@ %lang starknet // Starkware dependencies +from openzeppelin.access.ownable.library import Ownable from starkware.cairo.common.cairo_builtins import HashBuiltin, BitwiseBuiltin from starkware.cairo.common.uint256 import Uint256 // Local dependencies from kakarot.accounts.contract.library import ContractAccount -from kakarot.accounts.library import Accounts -from openzeppelin.access.ownable.library import Ownable +from kakarot.account import Account // @title EVM smart contract account representation. @@ -61,23 +61,32 @@ func bytecode_len{ } // @notice Store a key-value pair. -// @param key The bytes32 storage key. +// @param key The storage address, with storage_var being storage_(key: Uint256) // @param value The bytes32 stored value. @external func write_storage{ syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr, bitwise_ptr: BitwiseBuiltin* -}(key: Uint256, value: Uint256) { - return ContractAccount.write_storage(key, value); +}(storage_addr: felt, value: Uint256) { + return ContractAccount.write_storage(storage_addr, value); +} + +// @notice Selfdestruct whatever can be +// @dev It's not possible to remove a contract in Starknet +@external +func selfdestruct{ + syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr, bitwise_ptr: BitwiseBuiltin* +}() { + return ContractAccount.selfdestruct(); } // @notice Read a given storage key -// @param key The bytes32 storage key. +// @param key The storage address, with storage_var being storage_(key: Uint256) // @return value The stored value if the key exists, 0 otherwise. @view func storage{ syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr, bitwise_ptr: BitwiseBuiltin* -}(key: Uint256) -> (value: Uint256) { - return ContractAccount.storage(key); +}(storage_addr: felt) -> (value: Uint256) { + return ContractAccount.storage(storage_addr); } // @notice This function checks if the account was initialized. @@ -96,8 +105,16 @@ func get_nonce{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( return ContractAccount.get_nonce(); } -// @notice This function increases the contract account nonce by 1 +// @notice This function set the contract account nonce @external -func increment_nonce{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() { - return ContractAccount.increment_nonce(); +func set_nonce{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(nonce: felt) { + return ContractAccount.set_nonce(nonce); +} + +// @notice Returns the account type +@view +func account_type{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> ( + type: felt +) { + return ('CA',); } diff --git a/src/kakarot/accounts/contract/library.cairo b/src/kakarot/accounts/contract/library.cairo index f9a8d7453..b2ab3c76c 100644 --- a/src/kakarot/accounts/contract/library.cairo +++ b/src/kakarot/accounts/contract/library.cairo @@ -3,7 +3,6 @@ %lang starknet // Starkware dependencies -from kakarot.interfaces.interfaces import IERC20, IKakarot from openzeppelin.access.ownable.library import Ownable from starkware.cairo.common.alloc import alloc from starkware.cairo.common.bool import FALSE @@ -11,6 +10,10 @@ from starkware.cairo.common.cairo_builtins import HashBuiltin, BitwiseBuiltin from starkware.cairo.common.math import unsigned_div_rem from starkware.cairo.common.registers import get_label_location from starkware.cairo.common.uint256 import Uint256, uint256_not +from starkware.starknet.common.syscalls import storage_read, storage_write +from starkware.cairo.common.memset import memset + +from kakarot.interfaces.interfaces import IERC20, IKakarot // Storage @@ -129,31 +132,63 @@ namespace ContractAccount { } // @notice This function is used to read the storage at a key. - // @param key The key to the stored value. + // @param key The storage key, which is hash_felts(cast(Uint256, felt*)) of the Uint256 storage key. // @return value The store value. func storage{ syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr, bitwise_ptr: BitwiseBuiltin*, - }(key: Uint256) -> (value: Uint256) { - let value = storage_.read(key); - return value; + }(storage_addr: felt) -> (value: Uint256) { + let (low) = storage_read(address=storage_addr + 0); + let (high) = storage_read(address=storage_addr + 1); + let value = Uint256(low, high); + return (value,); } // @notice This function is used to write to the storage of the account. - // @param key The key to the value to store. + // @param key The storage key, which is hash_felts(cast(Uint256, felt*)) of the Uint256 storage key. // @param value The value to store. func write_storage{ syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr, bitwise_ptr: BitwiseBuiltin*, - }(key: Uint256, value: Uint256) { + }(storage_addr: felt, value: Uint256) { // Access control check. Ownable.assert_only_owner(); // Write State - storage_.write(key, value); + storage_write(address=storage_addr + 0, value=value.low); + storage_write(address=storage_addr + 1, value=value.high); + return (); + } + + // @notice Selfdestruct whatever can be + // @dev It's not possible to remove a contract in Starknet + func selfdestruct{ + syscall_ptr: felt*, + pedersen_ptr: HashBuiltin*, + range_check_ptr, + bitwise_ptr: BitwiseBuiltin*, + }() { + alloc_locals; + // Access control check. + Ownable.assert_only_owner(); + nonce.write(0); + is_initialized_.write(0); + evm_address.write(0); + + // Bytecode could we erased more efficiently, there is no read to + // initialize a new memory segment. + let (bytecode_len) = bytecode_len_.read(); + let (local bytecode: felt*) = alloc(); + memset(bytecode, 0, bytecode_len); + write_bytecode(bytecode_len, bytecode); + + bytecode_len_.write(0); + + // TODO: clean also the storage + return (); } @@ -177,12 +212,13 @@ namespace ContractAccount { return nonce.read(); } - // @notice This function increases the account nonce by 1 - func increment_nonce{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() { + // @notice This function set the account nonce + func set_nonce{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( + new_nonce: felt + ) { // Access control check. Ownable.assert_only_owner(); - let (current_nonce: felt) = nonce.read(); - nonce.write(current_nonce + 1); + nonce.write(new_nonce); return (); } } diff --git a/src/kakarot/accounts/eoa/externally_owned_account.cairo b/src/kakarot/accounts/eoa/externally_owned_account.cairo index a504370b7..b559f9b89 100644 --- a/src/kakarot/accounts/eoa/externally_owned_account.cairo +++ b/src/kakarot/accounts/eoa/externally_owned_account.cairo @@ -8,7 +8,7 @@ from starkware.starknet.common.syscalls import get_tx_info, get_caller_address from starkware.cairo.common.math import assert_le from kakarot.accounts.eoa.library import ExternallyOwnedAccount -from kakarot.accounts.library import Accounts +from kakarot.account import Account // Externally Owned Account initializer @external @@ -136,3 +136,11 @@ func bytecode_len{ }() -> (len: felt) { return (len=0); } + +// @notice Returns the account type +@view +func account_type{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> ( + type: felt +) { + return ('EOA',); +} diff --git a/src/kakarot/accounts/eoa/library.cairo b/src/kakarot/accounts/eoa/library.cairo index c274484b2..c1541052d 100644 --- a/src/kakarot/accounts/eoa/library.cairo +++ b/src/kakarot/accounts/eoa/library.cairo @@ -7,7 +7,7 @@ from starkware.cairo.common.alloc import alloc from starkware.cairo.common.bool import TRUE, FALSE from starkware.cairo.common.memcpy import memcpy -from kakarot.accounts.library import Accounts +from kakarot.account import Account from kakarot.interfaces.interfaces import IERC20, IKakarot from utils.eth_transaction import EthTransaction from utils.utils import Helpers diff --git a/src/kakarot/accounts/library.cairo b/src/kakarot/accounts/library.cairo deleted file mode 100644 index 41f71de79..000000000 --- a/src/kakarot/accounts/library.cairo +++ /dev/null @@ -1,140 +0,0 @@ -// SPDX-License-Identifier: MIT - -%lang starknet - -from starkware.cairo.common.alloc import alloc -from starkware.cairo.common.cairo_builtins import HashBuiltin, BitwiseBuiltin -from starkware.cairo.common.bool import FALSE -from starkware.starknet.common.syscalls import deploy as deploy_syscall -from starkware.starknet.common.syscalls import get_contract_address -from starkware.starknet.common.storage import normalize_address -from starkware.cairo.common.hash_state import ( - hash_finalize, - hash_init, - hash_update, - hash_update_single, - hash_update_with_hashchain, -) - -from kakarot.constants import Constants, account_proxy_class_hash -from kakarot.interfaces.interfaces import IAccount - -@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 Accounts { - // @dev Returns the registered starknet address for a given EVM address. Returns 0 if no contract is deployed for this - // EVM address. - // @param evm_address The EVM address to transform to a starknet address - // @return starknet_address The Starknet Account Contract address or 0 if not already deployed - func get_starknet_address{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - evm_address: felt - ) -> (starknet_address: felt) { - return evm_to_starknet_address.read(evm_address); - } - - // @dev As contract addresses are deterministic we can know what will be the address of a starknet contract from its input EVM address - // @dev Adapted code from: https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/starknet/core/os/contract_address/contract_address.cairo - // @param evm_address The EVM address to transform to a starknet address - // @return contract_address The Starknet Account Contract address (not necessarily deployed) - func compute_starknet_address{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - evm_address: felt - ) -> (contract_address: felt) { - alloc_locals; - let (_deployer_address: felt) = get_contract_address(); - let (_account_proxy_class_hash: felt) = account_proxy_class_hash.read(); - let (constructor_calldata: felt*) = alloc(); - let (hash_state_ptr) = hash_init(); - let (hash_state_ptr) = hash_update_single{hash_ptr=pedersen_ptr}( - hash_state_ptr=hash_state_ptr, item=Constants.CONTRACT_ADDRESS_PREFIX - ); - // hash deployer - let (hash_state_ptr) = hash_update_single{hash_ptr=pedersen_ptr}( - hash_state_ptr=hash_state_ptr, item=_deployer_address - ); - // hash salt - let (hash_state_ptr) = hash_update_single{hash_ptr=pedersen_ptr}( - hash_state_ptr=hash_state_ptr, item=evm_address - ); - // hash class hash - let (hash_state_ptr) = hash_update_single{hash_ptr=pedersen_ptr}( - hash_state_ptr=hash_state_ptr, item=_account_proxy_class_hash - ); - let (hash_state_ptr) = hash_update_with_hashchain{hash_ptr=pedersen_ptr}( - hash_state_ptr=hash_state_ptr, data_ptr=constructor_calldata, data_length=0 - ); - let (contract_address_before_modulo) = hash_finalize{hash_ptr=pedersen_ptr}( - hash_state_ptr=hash_state_ptr - ); - let (contract_address) = normalize_address{range_check_ptr=range_check_ptr}( - addr=contract_address_before_modulo - ); - - 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 create{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 Returns the bytecode of a given EVM address - // @dev Returns an empty bytecode if the corresponding Starknet contract is not deployed - // as would eth_getCode do to any address - func get_bytecode{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - evm_address: felt - ) -> (bytecode_len: felt, bytecode: felt*) { - let (starknet_address) = get_starknet_address(evm_address); - - if (starknet_address == 0) { - let (bytecode: felt*) = alloc(); - return (0, bytecode); - } - - let (bytecode_len, bytecode) = IAccount.bytecode(contract_address=starknet_address); - return (bytecode_len, bytecode); - } - - // @notice Returns the bytecode_len of a given EVM address - // @dev Returns 0 if the corresponding Starknet contract is not deployed - // as would eth_getCode do to any address - func get_bytecode_len{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - evm_address: felt - ) -> (bytecode_len: felt) { - let (starknet_address) = get_starknet_address(evm_address); - - if (starknet_address == 0) { - return (bytecode_len=0); - } - - let (bytecode_len) = IAccount.bytecode_len(contract_address=starknet_address); - return (bytecode_len=bytecode_len); - } -} diff --git a/src/kakarot/errors.cairo b/src/kakarot/errors.cairo index cf25e5b93..d47a9470a 100644 --- a/src/kakarot/errors.cairo +++ b/src/kakarot/errors.cairo @@ -396,4 +396,83 @@ namespace Errors { assert [error + 34] = address; // return (35, error); } + + func balanceError() -> (error_len: felt, error: felt*) { + let (error) = get_label_location(balance_error_message); + return (40, error); + + balance_error_message: + dw 'K'; + dw 'a'; + dw 'k'; + dw 'a'; + dw 'r'; + dw 'o'; + dw 't'; + dw ':'; + dw ' '; + dw 't'; + dw 'r'; + dw 'a'; + dw 'n'; + dw 's'; + dw 'f'; + dw 'e'; + dw 'r'; + dw ' '; + dw 'a'; + dw 'm'; + dw 'o'; + dw 'u'; + dw 'n'; + dw 't'; + dw ' '; + dw 'e'; + dw 'x'; + dw 'c'; + dw 'e'; + dw 'e'; + dw 'd'; + dw 's'; + dw ' '; + dw 'b'; + dw 'a'; + dw 'l'; + dw 'a'; + dw 'n'; + dw 'c'; + dw 'e'; + } + + func addressCollision() -> (error_len: felt, error: felt*) { + let (error) = get_label_location(address_collision_error_message); + return (25, error); + + address_collision_error_message: + dw 'K'; + dw 'a'; + dw 'k'; + dw 'a'; + dw 'r'; + dw 'o'; + dw 't'; + dw ':'; + dw ' '; + dw 'a'; + dw 'd'; + dw 'd'; + dw 'r'; + dw 'e'; + dw 's'; + dw 's'; + dw 'C'; + dw 'o'; + dw 'l'; + dw 'l'; + dw 'i'; + dw 's'; + dw 'i'; + dw 'o'; + dw 'n'; + } } diff --git a/src/kakarot/evm.cairo b/src/kakarot/evm.cairo index 4f5b77437..e0dc8d81a 100644 --- a/src/kakarot/evm.cairo +++ b/src/kakarot/evm.cairo @@ -4,7 +4,7 @@ // Starkware dependencies from starkware.cairo.common.alloc import alloc -from starkware.cairo.common.bool import FALSE +from starkware.cairo.common.bool import FALSE, TRUE from starkware.cairo.common.cairo_builtins import HashBuiltin, BitwiseBuiltin from starkware.cairo.common.invoke import invoke from starkware.cairo.common.math import assert_nn @@ -15,6 +15,7 @@ from starkware.cairo.common.registers import get_label_location from starkware.cairo.common.uint256 import Uint256 // Internal dependencies +from kakarot.account import Account from kakarot.errors import Errors from kakarot.execution_context import ExecutionContext from kakarot.instructions.block_information import BlockInformation @@ -27,17 +28,12 @@ from kakarot.instructions.memory_operations import MemoryOperations from kakarot.instructions.push_operations import PushOperations from kakarot.instructions.sha3 import Sha3 from kakarot.instructions.stop_and_arithmetic_operations import StopAndArithmeticOperations -from kakarot.instructions.system_operations import ( - CallHelper, - CreateHelper, - SelfDestructHelper, - SystemOperations, -) -from kakarot.interfaces.interfaces import IAccount +from kakarot.instructions.system_operations import CallHelper, CreateHelper, SystemOperations from kakarot.memory import Memory from kakarot.model import model from kakarot.precompiles.precompiles import Precompiles from kakarot.stack import Stack +from kakarot.state import State from utils.utils import Helpers // @title EVM instructions processing. @@ -50,9 +46,11 @@ namespace EVM { return_data: felt*, return_data_len: felt, gas_used: felt, - starknet_contract_address: felt, - evm_contract_address: felt, + address: model.Address*, reverted: felt, + state: State.Summary*, + call_context: model.CallContext*, + program_counter: felt, } // @notice Decode the current opcode and execute associated function. @@ -620,44 +618,25 @@ namespace EVM { }(ctx: model.ExecutionContext*) -> Summary* { alloc_locals; - let ctx: model.ExecutionContext* = decode_and_execute(ctx=ctx); + if (ctx.stopped != FALSE) { + let ctx_summary = ExecutionContext.finalize(ctx); + let is_root: felt = ExecutionContext.is_empty(self=ctx_summary.calling_context); + if (is_root != FALSE) { + let evm_summary = finalize(ctx_summary); + return evm_summary; + } - if (ctx.stopped == FALSE) { - return run(ctx=ctx); + if (ctx_summary.call_context.is_create != 0) { + let ctx = CreateHelper.finalize_calling_context(ctx_summary); + return run(ctx); + } else { + let ctx = CallHelper.finalize_calling_context(ctx_summary); + return run(ctx); + } } - let summary = ExecutionContext.finalize(ctx); - let is_root: felt = ExecutionContext.is_empty(self=summary.calling_context); - if (is_root != FALSE) { - return finalize(summary); - } - - let is_precompile = Precompiles.is_precompile(address=summary.evm_contract_address); - if (is_precompile != FALSE) { - let ctx = CallHelper.finalize_calling_context(summary); - return run(ctx=ctx); - } - let (bytecode_len) = IAccount.bytecode_len( - contract_address=summary.starknet_contract_address - ); - - let has_return_data = is_not_zero(summary.return_data_len); - let has_empty_return_data = (1 - has_return_data); - - let is_eao = Helpers.is_address_caller(summary.starknet_contract_address); - - // If the starknet contract of the execution context has - // no bytecode, - // is not an EOA, - // and has return data - // then we treat it as at the end of a CREATE/CREATE2 opcode. - if (bytecode_len + is_eao + has_empty_return_data == 0) { - let ctx = CreateHelper.finalize_calling_context(summary); - return run(ctx=ctx); - } else { - let ctx = CallHelper.finalize_calling_context(summary); - return run(ctx=ctx); - } + let ctx = decode_and_execute(ctx); + return run(ctx); } // @notice A placeholder for opcodes that don't exist @@ -670,36 +649,63 @@ namespace EVM { bitwise_ptr: BitwiseBuiltin*, }(ctx: model.ExecutionContext*) -> model.ExecutionContext* { let (revert_reason_len, revert_reason) = Errors.unknownOpcode(); - let ctx = ExecutionContext.revert( - self=ctx, revert_reason=revert_reason, size=revert_reason_len - ); + let ctx = ExecutionContext.stop(ctx, revert_reason_len, revert_reason, TRUE); return ctx; } // @notice Finalizes a transaction. // @param ctx_summary The pointer to the execution context summary. // @return Summary The pointer to the transaction Summary. - func finalize{ - syscall_ptr: felt*, - pedersen_ptr: HashBuiltin*, - range_check_ptr, - bitwise_ptr: BitwiseBuiltin*, - }(ctx_summary: ExecutionContext.Summary*) -> Summary* { + func finalize{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( + ctx_summary: ExecutionContext.Summary* + ) -> Summary* { alloc_locals; - - Helpers.erase_contracts( - ctx_summary.selfdestruct_contracts_len, ctx_summary.selfdestruct_contracts - ); - - return new Summary( + let state_summary = Internals._get_state_summary(ctx_summary); + tempvar summary: Summary* = new Summary( memory=ctx_summary.memory, stack=ctx_summary.stack, return_data=ctx_summary.return_data, return_data_len=ctx_summary.return_data_len, gas_used=ctx_summary.gas_used, - starknet_contract_address=ctx_summary.starknet_contract_address, - evm_contract_address=ctx_summary.evm_contract_address, + address=ctx_summary.address, reverted=ctx_summary.reverted, + state=state_summary, + call_context=ctx_summary.call_context, + program_counter=ctx_summary.program_counter, ); + + return summary; + } +} + +namespace Internals { + func _get_state_summary{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( + ctx_summary: ExecutionContext.Summary* + ) -> State.Summary* { + alloc_locals; + // In case of a deploy tx, we need to store the return_data in the Account + if (ctx_summary.call_context.is_create != FALSE) { + let (state, account) = State.get_account(ctx_summary.state, ctx_summary.address); + let account = Account.set_code( + account, ctx_summary.return_data_len, ctx_summary.return_data + ); + let state = State.set_account(state, ctx_summary.address, account); + tempvar state = state; + tempvar syscall_ptr = syscall_ptr; + tempvar pedersen_ptr = pedersen_ptr; + tempvar range_check_ptr = range_check_ptr; + // Else the state is just the returned state of the ExecutionContext + } else { + tempvar state = ctx_summary.state; + tempvar syscall_ptr = syscall_ptr; + tempvar pedersen_ptr = pedersen_ptr; + tempvar range_check_ptr = range_check_ptr; + } + tempvar syscall_ptr = syscall_ptr; + tempvar pedersen_ptr = pedersen_ptr; + tempvar range_check_ptr = range_check_ptr; + + let state_summary = State.finalize(state); + return state_summary; } } diff --git a/src/kakarot/execution_context.cairo b/src/kakarot/execution_context.cairo index 5d6836df2..2aed60a81 100644 --- a/src/kakarot/execution_context.cairo +++ b/src/kakarot/execution_context.cairo @@ -6,23 +6,21 @@ 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, default_dict_finalize -from starkware.cairo.common.dict import DictAccess +from starkware.cairo.common.dict import DictAccess, dict_write, dict_read from starkware.cairo.common.math import assert_le, assert_nn 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.registers import get_label_location from starkware.cairo.common.uint256 import Uint256 -from starkware.starknet.common.syscalls import emit_event // Internal dependencies -from kakarot.accounts.library import Accounts +from kakarot.account import Account from kakarot.constants import Constants from kakarot.errors import Errors -from kakarot.interfaces.interfaces import IAccount, IContractAccount 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 // @title ExecutionContext related functions. @@ -32,15 +30,15 @@ namespace ExecutionContext { struct Summary { memory: Memory.Summary*, stack: Stack.Summary*, - return_data: felt*, return_data_len: felt, + return_data: felt*, gas_used: felt, - starknet_contract_address: felt, - evm_contract_address: felt, + address: model.Address*, reverted: felt, - selfdestruct_contracts_len: felt, - selfdestruct_contracts: felt*, + state: model.State*, calling_context: model.ExecutionContext*, + call_context: model.CallContext*, + program_counter: felt, } // @notice Initialize an empty context to act as a placeholder for root context. @@ -51,97 +49,44 @@ namespace ExecutionContext { return self; empty_context: + dw 0; // state dw 0; // call_context - dw 0; // program_counter - dw 1; // stopped - dw 0; // return_data - dw 0; // return_data_len dw 0; // stack dw 0; // memory + dw 0; // return_data_len + dw 0; // return_data + dw 0; // program_counter + dw 1; // stopped dw 0; // gas_used - dw 0; // gas_limit - dw 0; // gas_price - dw 0; // starknet_contract_address - dw 0; // evm_contract_address - dw 0; // origin - dw 0; // calling_context - dw 0; // selfdestruct_contracts_len - dw 0; // selfdestruct_contracts - dw 0; // events_len - dw 0; // events - dw 0; // create_contracts_len - dw 0; // create_contracts - dw 0; // revert_contract_state dw 0; // reverted - dw 0; // read only } // @notice Initialize the execution context. // @dev Initialize the execution context of a specific contract. // @param call_context The call_context (see model.CallContext) to be executed. - // @param starknet_contract_address The context starknet address. - // @param evm_contract_address The context corresponding evm address. - // @param origin The caller EVM address - // @param gas_limit The available gas for the ExecutionContext. - // @param gas_price The price per unit of gas. - // @param calling_context A reference to the context of the calling contract. This context stores the return data produced by the called contract in its memory. - // @param return_data_len The return_data length. - // @param return_data The region where returned data of the contract or precompile is written. - // @param read_only The boolean that determines whether state modifications can be executed from the sub-execution context. // @return ExecutionContext The initialized execution context. func init{ syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr, bitwise_ptr: BitwiseBuiltin*, - }( - call_context: model.CallContext*, - starknet_contract_address: felt, - evm_contract_address: felt, - origin: felt, - gas_limit: felt, - gas_price: felt, - calling_context: model.ExecutionContext*, - return_data_len: felt, - return_data: felt*, - read_only: felt, - ) -> model.ExecutionContext* { - alloc_locals; - let (empty_selfdestruct_contracts: felt*) = alloc(); - let (empty_events: model.Event*) = alloc(); - let (empty_create_contracts: felt*) = alloc(); - let (local revert_contract_state_dict_start) = default_dict_new(0); - tempvar revert_contract_state: model.RevertContractState* = new model.RevertContractState( - revert_contract_state_dict_start, revert_contract_state_dict_start - ); - - let stack: model.Stack* = Stack.init(); - let memory: model.Memory* = Memory.init(); + }(call_context: model.CallContext*) -> model.ExecutionContext* { + let stack = Stack.init(); + let memory = Memory.init(); + let state = State.init(); + let (return_data: felt*) = alloc(); return new model.ExecutionContext( + state=state, call_context=call_context, - program_counter=0, - stopped=FALSE, - return_data=return_data, - return_data_len=return_data_len, stack=stack, memory=memory, + return_data_len=0, + return_data=return_data, + program_counter=0, + stopped=FALSE, gas_used=0, - gas_limit=gas_limit, - gas_price=gas_price, - starknet_contract_address=starknet_contract_address, - evm_contract_address=evm_contract_address, - origin=origin, - calling_context=calling_context, - selfdestruct_contracts_len=0, - selfdestruct_contracts=empty_selfdestruct_contracts, - events_len=0, - events=empty_events, - create_addresses_len=0, - create_addresses=empty_create_contracts, - revert_contract_state=revert_contract_state, reverted=FALSE, - read_only=read_only, ); } @@ -149,14 +94,15 @@ namespace ExecutionContext { // @dev Computes with the intrinsic gas cost based on per transaction constant and cost of input data (16 gas per non-zero byte and 4 gas per zero byte). // @param self The execution context. // @return intrinsic gas cost. - func compute_intrinsic_gas_cost(self: model.ExecutionContext*) -> felt { + func add_intrinsic_gas_cost(self: model.ExecutionContext*) -> model.ExecutionContext* { let calldata = self.call_context.calldata; let calldata_len = self.call_context.calldata_len; let count = Helpers.count_nonzeroes(nonzeroes=0, idx=0, arr_len=calldata_len, arr=calldata); let zeroes = calldata_len - count.nonzeroes; let calldata_cost = zeroes * 4 + count.nonzeroes * 16; + let cost = Constants.TRANSACTION_INTRINSIC_GAS_COST + calldata_cost; - return (Constants.TRANSACTION_INTRINSIC_GAS_COST + calldata_cost); + return ExecutionContext.increment_gas_used(self, cost); } // @notice Return whether the current execution context is stopped. @@ -181,32 +127,24 @@ namespace ExecutionContext { // @notice Stop the current execution context. // @dev When the execution context is stopped, no more instructions can be executed. // @param self The pointer to the execution context. + // @param return_data_len The length of the return_data. + // @param return_data The pointer to the return_data array. + // @param reverted A boolean indicating whether the ExecutionContext is reverted or not. // @return ExecutionContext The pointer to the updated execution context. - func stop(self: model.ExecutionContext*) -> model.ExecutionContext* { + func stop( + self: model.ExecutionContext*, return_data_len: felt, return_data: felt*, reverted: felt + ) -> model.ExecutionContext* { return new model.ExecutionContext( + state=self.state, call_context=self.call_context, - program_counter=self.program_counter, - stopped=TRUE, - return_data=self.return_data, - return_data_len=self.return_data_len, stack=self.stack, memory=self.memory, + return_data_len=return_data_len, + return_data=return_data, + program_counter=self.program_counter, + stopped=TRUE, gas_used=self.gas_used, - gas_limit=self.gas_limit, - gas_price=self.gas_price, - starknet_contract_address=self.starknet_contract_address, - evm_contract_address=self.evm_contract_address, - origin=self.origin, - calling_context=self.calling_context, - selfdestruct_contracts_len=self.selfdestruct_contracts_len, - selfdestruct_contracts=self.selfdestruct_contracts, - events_len=self.events_len, - events=self.events, - create_addresses_len=self.create_addresses_len, - create_addresses=self.create_addresses, - revert_contract_state=self.revert_contract_state, - reverted=self.reverted, - read_only=self.read_only, + reverted=reverted, ); } @@ -218,42 +156,6 @@ namespace ExecutionContext { return self.reverted; } - // @notice Revert the current execution context. - // @dev When the execution context is reverted, no more instructions can be executed (it is stopped) and its contract creation and contract storage writes are reverted on its finalization. - // @param self The pointer to the execution context. - // @param revert_reason The byte array of the revert reason. - // @param size The size of the byte array. - // @return ExecutionContext The pointer to the updated execution context. - func revert( - self: model.ExecutionContext*, revert_reason: felt*, size: felt - ) -> model.ExecutionContext* { - return new model.ExecutionContext( - call_context=self.call_context, - program_counter=self.program_counter, - stopped=TRUE, - return_data=revert_reason, - return_data_len=size, - stack=self.stack, - memory=self.memory, - gas_used=self.gas_used, - gas_limit=self.gas_limit, - gas_price=self.gas_price, - starknet_contract_address=self.starknet_contract_address, - evm_contract_address=self.evm_contract_address, - origin=self.origin, - calling_context=self.calling_context, - selfdestruct_contracts_len=self.selfdestruct_contracts_len, - selfdestruct_contracts=self.selfdestruct_contracts, - events_len=self.events_len, - events=self.events, - create_addresses_len=self.create_addresses_len, - create_addresses=self.create_addresses, - revert_contract_state=self.revert_contract_state, - reverted=TRUE, - read_only=self.read_only, - ); - } - // @notice Finalizes the execution context. // @dev See https://www.cairo-lang.org/docs/reference/common_library.html#dictaccess // TL;DR: ensure that the prover used values that are consistent with the dictionary. @@ -268,20 +170,20 @@ namespace ExecutionContext { alloc_locals; let memory_summary = Memory.finalize(self.memory); let stack_summary = Stack.finalize(self.stack); - Internals._finalize_state_updates(self); + State.finalize(self.state); return new Summary( memory=memory_summary, stack=stack_summary, - return_data=self.return_data, return_data_len=self.return_data_len, + return_data=self.return_data, gas_used=self.gas_used, - starknet_contract_address=self.starknet_contract_address, - evm_contract_address=self.evm_contract_address, + address=self.call_context.address, reverted=self.reverted, - selfdestruct_contracts_len=self.selfdestruct_contracts_len, - selfdestruct_contracts=self.selfdestruct_contracts, - calling_context=self.calling_context, + state=self.state, + calling_context=self.call_context.calling_context, + call_context=self.call_context, + program_counter=self.program_counter, ); } @@ -311,32 +213,19 @@ namespace ExecutionContext { // @param stack The pointer to the new stack. // @return ExecutionContext The pointer to the updated execution context. func update_stack( - self: model.ExecutionContext*, new_stack: model.Stack* + self: model.ExecutionContext*, stack: model.Stack* ) -> model.ExecutionContext* { return new model.ExecutionContext( + state=self.state, call_context=self.call_context, + stack=stack, + memory=self.memory, + return_data_len=self.return_data_len, + return_data=self.return_data, program_counter=self.program_counter, stopped=self.stopped, - return_data=self.return_data, - return_data_len=self.return_data_len, - stack=new_stack, - memory=self.memory, gas_used=self.gas_used, - gas_limit=self.gas_limit, - gas_price=self.gas_price, - starknet_contract_address=self.starknet_contract_address, - evm_contract_address=self.evm_contract_address, - origin=self.origin, - calling_context=self.calling_context, - selfdestruct_contracts_len=self.selfdestruct_contracts_len, - selfdestruct_contracts=self.selfdestruct_contracts, - events_len=self.events_len, - events=self.events, - create_addresses_len=self.create_addresses_len, - create_addresses=self.create_addresses, - revert_contract_state=self.revert_contract_state, reverted=self.reverted, - read_only=self.read_only, ); } @@ -346,73 +235,19 @@ namespace ExecutionContext { // @param memory The pointer to the new memory. // @return ExecutionContext The pointer to the updated execution context. func update_memory( - self: model.ExecutionContext*, new_memory: model.Memory* + self: model.ExecutionContext*, memory: model.Memory* ) -> model.ExecutionContext* { return new model.ExecutionContext( + state=self.state, call_context=self.call_context, - program_counter=self.program_counter, - stopped=self.stopped, - return_data=self.return_data, - return_data_len=self.return_data_len, stack=self.stack, - memory=new_memory, - gas_used=self.gas_used, - gas_limit=self.gas_limit, - gas_price=self.gas_price, - starknet_contract_address=self.starknet_contract_address, - evm_contract_address=self.evm_contract_address, - origin=self.origin, - calling_context=self.calling_context, - selfdestruct_contracts_len=self.selfdestruct_contracts_len, - selfdestruct_contracts=self.selfdestruct_contracts, - events_len=self.events_len, - events=self.events, - create_addresses_len=self.create_addresses_len, - create_addresses=self.create_addresses, - revert_contract_state=self.revert_contract_state, - reverted=self.reverted, - read_only=self.read_only, - ); - } - - // @notice Update the return data of the current execution context. - // @dev The memory is updated with the given memory. - // @param self The pointer to the execution context. - // @param return_data_len The length of the return data array. - // @param return_data The return data array. - // @return ExecutionContext The pointer to the updated execution context. - func update_return_data{ - syscall_ptr: felt*, - pedersen_ptr: HashBuiltin*, - range_check_ptr, - bitwise_ptr: BitwiseBuiltin*, - }( - self: model.ExecutionContext*, return_data_len: felt, return_data: felt* - ) -> model.ExecutionContext* { - return new model.ExecutionContext( - call_context=self.call_context, + memory=memory, + return_data_len=self.return_data_len, + return_data=self.return_data, program_counter=self.program_counter, stopped=self.stopped, - return_data=return_data, - return_data_len=return_data_len, - stack=self.stack, - memory=self.memory, gas_used=self.gas_used, - gas_limit=self.gas_limit, - gas_price=self.gas_price, - starknet_contract_address=self.starknet_contract_address, - evm_contract_address=self.evm_contract_address, - origin=self.origin, - calling_context=self.calling_context, - selfdestruct_contracts_len=self.selfdestruct_contracts_len, - selfdestruct_contracts=self.selfdestruct_contracts, - events_len=self.events_len, - events=self.events, - create_addresses_len=self.create_addresses_len, - create_addresses=self.create_addresses, - revert_contract_state=self.revert_contract_state, reverted=self.reverted, - read_only=self.read_only, ); } @@ -425,29 +260,16 @@ namespace ExecutionContext { self: model.ExecutionContext*, inc_value: felt ) -> model.ExecutionContext* { return new model.ExecutionContext( + state=self.state, call_context=self.call_context, - program_counter=self.program_counter + inc_value, - stopped=self.stopped, - return_data=self.return_data, - return_data_len=self.return_data_len, stack=self.stack, memory=self.memory, + return_data_len=self.return_data_len, + return_data=self.return_data, + program_counter=self.program_counter + inc_value, + stopped=self.stopped, gas_used=self.gas_used, - gas_limit=self.gas_limit, - gas_price=self.gas_price, - starknet_contract_address=self.starknet_contract_address, - evm_contract_address=self.evm_contract_address, - origin=self.origin, - calling_context=self.calling_context, - selfdestruct_contracts_len=self.selfdestruct_contracts_len, - selfdestruct_contracts=self.selfdestruct_contracts, - events_len=self.events_len, - events=self.events, - create_addresses_len=self.create_addresses_len, - create_addresses=self.create_addresses, - revert_contract_state=self.revert_contract_state, reverted=self.reverted, - read_only=self.read_only, ); } @@ -460,254 +282,71 @@ namespace ExecutionContext { self: model.ExecutionContext*, inc_value: felt ) -> model.ExecutionContext* { return new model.ExecutionContext( + state=self.state, call_context=self.call_context, - program_counter=self.program_counter, - stopped=self.stopped, - return_data=self.return_data, - return_data_len=self.return_data_len, stack=self.stack, memory=self.memory, - gas_used=self.gas_used + inc_value, - gas_limit=self.gas_limit, - gas_price=self.gas_price, - starknet_contract_address=self.starknet_contract_address, - evm_contract_address=self.evm_contract_address, - origin=self.origin, - calling_context=self.calling_context, - selfdestruct_contracts_len=self.selfdestruct_contracts_len, - selfdestruct_contracts=self.selfdestruct_contracts, - events_len=self.events_len, - events=self.events, - create_addresses_len=self.create_addresses_len, - create_addresses=self.create_addresses, - revert_contract_state=self.revert_contract_state, - reverted=self.reverted, - read_only=self.read_only, - ); - } - - // @notice Update the starknet and evm contract addresses. - // @dev No check is made using the registry for these two addresses being actually linked. - // @param self The pointer to the execution context. - // @param starknet_contract_address The starknet_contract_address to use. - // @param evm_contract_address The evm_contract_address to use. - // @return ExecutionContext The pointer to the updated execution context. - func update_addresses( - self: model.ExecutionContext*, starknet_contract_address: felt, evm_contract_address: felt - ) -> model.ExecutionContext* { - return new model.ExecutionContext( - call_context=self.call_context, - program_counter=self.program_counter, - stopped=self.stopped, - return_data=self.return_data, return_data_len=self.return_data_len, - stack=self.stack, - memory=self.memory, - gas_used=self.gas_used, - gas_limit=self.gas_limit, - gas_price=self.gas_price, - starknet_contract_address=starknet_contract_address, - evm_contract_address=evm_contract_address, - origin=self.origin, - calling_context=self.calling_context, - selfdestruct_contracts_len=self.selfdestruct_contracts_len, - selfdestruct_contracts=self.selfdestruct_contracts, - events_len=self.events_len, - events=self.events, - create_addresses_len=self.create_addresses_len, - create_addresses=self.create_addresses, - revert_contract_state=self.revert_contract_state, - reverted=self.reverted, - read_only=self.read_only, - ); - } - - // @notice Update the array of contracts to destroy. - // @param self The pointer to the execution context. - // @param selfdestruct_contracts_len Array length of selfdestruct_contracts to add. - // @param selfdestruct_contracts The pointer to the new array of contracts to destroy. - // @return ExecutionContext The pointer to the updated execution context. - func push_to_selfdestruct_contracts( - self: model.ExecutionContext*, - selfdestruct_contracts_len: felt, - selfdestruct_contracts: felt*, - ) -> model.ExecutionContext* { - Helpers.fill_array( - fill_len=selfdestruct_contracts_len, - input_arr=selfdestruct_contracts, - output_arr=self.selfdestruct_contracts + self.selfdestruct_contracts_len, - ); - return new model.ExecutionContext( - call_context=self.call_context, + return_data=self.return_data, program_counter=self.program_counter, stopped=self.stopped, - return_data=self.return_data, - return_data_len=self.return_data_len, - stack=self.stack, - memory=self.memory, - gas_used=self.gas_used, - gas_limit=self.gas_limit, - gas_price=self.gas_price, - starknet_contract_address=self.starknet_contract_address, - evm_contract_address=self.evm_contract_address, - origin=self.origin, - calling_context=self.calling_context, - selfdestruct_contracts_len=self.selfdestruct_contracts_len + selfdestruct_contracts_len, - selfdestruct_contracts=self.selfdestruct_contracts, - events_len=self.events_len, - events=self.events, - create_addresses_len=self.create_addresses_len, - create_addresses=self.create_addresses, - revert_contract_state=self.revert_contract_state, + gas_used=self.gas_used + inc_value, reverted=self.reverted, - read_only=self.read_only, ); } - // @notice Update the array of events to emit in the case of a execution context successfully running to completion (see `LoggingHelper.finalize`). + // @notice Update the array of events to emit in the case of a execution context successfully running to completion (see `ExecutionContext.finalize`). // @param self The pointer to the execution context. - // @param selfdestruct_contracts_len Array length of events to add. - // @param selfdestruct_contracts The pointer to the new array of contracts to destroy. - func push_event_to_emit( + // @param topics_len The length of the topics + // @param topics The topics Uint256 array + // @param data_len The length of the data + // @param data The data bytes array + func push_event( self: model.ExecutionContext*, - event_keys_len: felt, - event_keys: Uint256*, - event_data_len: felt, - event_data: felt*, + topics_len: felt, + topics: Uint256*, + data_len: felt, + data: felt*, ) -> model.ExecutionContext* { - let event: model.Event = model.Event( - keys_len=event_keys_len, keys=event_keys, data_len=event_data_len, data=event_data - ); - assert [self.events + self.events_len * model.Event.SIZE] = event; - return new model.ExecutionContext( - call_context=self.call_context, - program_counter=self.program_counter, - stopped=self.stopped, - return_data=self.return_data, - return_data_len=self.return_data_len, - stack=self.stack, - memory=self.memory, - gas_used=self.gas_used, - gas_limit=self.gas_limit, - gas_price=self.gas_price, - starknet_contract_address=self.starknet_contract_address, - evm_contract_address=self.evm_contract_address, - origin=self.origin, - calling_context=self.calling_context, - selfdestruct_contracts_len=self.selfdestruct_contracts_len, - selfdestruct_contracts=self.selfdestruct_contracts, - events_len=self.events_len + 1, - events=self.events, - create_addresses_len=self.create_addresses_len, - create_addresses=self.create_addresses, - revert_contract_state=self.revert_contract_state, - reverted=self.reverted, - read_only=self.read_only, - ); - } + alloc_locals; - // @notice Add one contract to the array of create contracts to destroy in the case of the execution context reverting. - // @param self The pointer to the execution context. - // @param create_contract_address The address of the contract from the create(2) opcode called from the execution context. - func push_create_address( - self: model.ExecutionContext*, create_contract_address: felt - ) -> model.ExecutionContext* { - assert [self.create_addresses + self.create_addresses_len] = create_contract_address; - return new model.ExecutionContext( - call_context=self.call_context, - program_counter=self.program_counter, - stopped=self.stopped, - return_data=self.return_data, - return_data_len=self.return_data_len, - stack=self.stack, - memory=self.memory, - gas_used=self.gas_used, - gas_limit=self.gas_limit, - gas_price=self.gas_price, - starknet_contract_address=self.starknet_contract_address, - evm_contract_address=self.evm_contract_address, - origin=self.origin, - calling_context=self.calling_context, - selfdestruct_contracts_len=self.selfdestruct_contracts_len, - selfdestruct_contracts=self.selfdestruct_contracts, - events_len=self.events_len, - events=self.events, - create_addresses_len=self.create_addresses_len + 1, - create_addresses=self.create_addresses, - revert_contract_state=self.revert_contract_state, - reverted=self.reverted, - read_only=self.read_only, + // we add the operating evm_contract_address of the execution context + // as the first key of an event + // we track kakarot events as those emitted from the kkrt contract + // and map it to the corresponding EVM contract via this convention + // this looks a bit odd and may need to be reviewed + let (local topics_with_address: felt*) = alloc(); + assert [topics_with_address] = self.call_context.address.evm; + memcpy(dst=topics_with_address + 1, src=cast(topics, felt*), len=topics_len * Uint256.SIZE); + let event = model.Event( + topics_len=1 + topics_len * Uint256.SIZE, + topics=topics_with_address, + data_len=data_len, + data=data, ); - } - // @notice Add one contract to the array of contracts to destroy. - // @param self The pointer to the execution context. - // @param destroy_contract contract to destroy. - // @return ExecutionContext The pointer to the updated execution context. - func push_to_destroy_contract( - self: model.ExecutionContext*, destroy_contract: felt - ) -> model.ExecutionContext* { - assert [self.selfdestruct_contracts + self.selfdestruct_contracts_len] = destroy_contract; - return new model.ExecutionContext( - call_context=self.call_context, - program_counter=self.program_counter, - stopped=TRUE, - return_data=self.return_data, - return_data_len=self.return_data_len, - stack=self.stack, - memory=self.memory, - gas_used=self.gas_used, - gas_limit=self.gas_limit, - gas_price=self.gas_price, - starknet_contract_address=self.starknet_contract_address, - evm_contract_address=self.evm_contract_address, - origin=self.origin, - calling_context=self.calling_context, - selfdestruct_contracts_len=self.selfdestruct_contracts_len + 1, - selfdestruct_contracts=self.selfdestruct_contracts, - events_len=self.events_len, - events=self.events, - create_addresses_len=self.create_addresses_len, - create_addresses=self.create_addresses, - revert_contract_state=self.revert_contract_state, - reverted=self.reverted, - read_only=self.read_only, - ); + let state = State.add_event(self.state, event); + + return ExecutionContext.update_state(self, state); } // @notice Updates the dictionary that keeps track of the prior-to-first-write value of a contract storage key so it can be reverted to if the writing execution context reverts. // @param self The pointer to the execution context. - // @param dict_end The pointer to the updated end of the DictAccess array. - func update_revert_contract_state( - self: model.ExecutionContext*, dict_end: DictAccess* + // @param state The pointer to model.State + func update_state( + self: model.ExecutionContext*, state: model.State* ) -> model.ExecutionContext* { - tempvar revert_contract_state: model.RevertContractState* = new model.RevertContractState( - self.revert_contract_state.dict_start, dict_end - ); return new model.ExecutionContext( + state=state, call_context=self.call_context, - program_counter=self.program_counter, - stopped=FALSE, - return_data=self.return_data, - return_data_len=self.return_data_len, stack=self.stack, memory=self.memory, + return_data_len=self.return_data_len, + return_data=self.return_data, + program_counter=self.program_counter, + stopped=self.stopped, gas_used=self.gas_used, - gas_limit=self.gas_limit, - gas_price=self.gas_price, - starknet_contract_address=self.starknet_contract_address, - evm_contract_address=self.evm_contract_address, - origin=self.origin, - calling_context=self.calling_context, - selfdestruct_contracts_len=self.selfdestruct_contracts_len, - selfdestruct_contracts=self.selfdestruct_contracts, - events_len=self.events_len, - events=self.events, - create_addresses_len=self.create_addresses_len, - create_addresses=self.create_addresses, - revert_contract_state=revert_contract_state, reverted=self.reverted, - read_only=self.read_only, ); } @@ -725,135 +364,27 @@ namespace ExecutionContext { let is_le_bytecode_len = is_le(new_pc_offset, self.call_context.bytecode_len - 1); if (is_nn_pc + is_le_bytecode_len != 2) { let (revert_reason_len, revert_reason) = Errors.programCounterOutOfRange(); - let ctx = ExecutionContext.revert(self, revert_reason, revert_reason_len); + let ctx = ExecutionContext.stop(self, revert_reason_len, revert_reason, TRUE); return ctx; } if ([self.call_context.bytecode + new_pc_offset] != 0x5b) { let (revert_reason_len, revert_reason) = Errors.jumpToNonJumpdest(); - let ctx = ExecutionContext.revert(self, revert_reason, revert_reason_len); + let ctx = ExecutionContext.stop(self, revert_reason_len, revert_reason, TRUE); return ctx; } return new model.ExecutionContext( + state=self.state, call_context=self.call_context, - program_counter=new_pc_offset, - stopped=self.stopped, - return_data=self.return_data, - return_data_len=self.return_data_len, stack=self.stack, memory=self.memory, + return_data_len=self.return_data_len, + return_data=self.return_data, + program_counter=new_pc_offset, + stopped=self.stopped, gas_used=self.gas_used, - gas_limit=self.gas_limit, - gas_price=self.gas_price, - starknet_contract_address=self.starknet_contract_address, - evm_contract_address=self.evm_contract_address, - origin=self.origin, - calling_context=self.calling_context, - selfdestruct_contracts_len=self.selfdestruct_contracts_len, - selfdestruct_contracts=self.selfdestruct_contracts, - events_len=self.events_len, - events=self.events, - create_addresses_len=self.create_addresses_len, - create_addresses=self.create_addresses, - revert_contract_state=self.revert_contract_state, reverted=self.reverted, - read_only=self.read_only, - ); - } -} - -namespace Internals { - // @notice Iterates through a list of events and emits them on the case that a context ran successfully (stopped and not reverted). - // @param evm_contract_address The execution context's evm contract address. - // @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, - bitwise_ptr: BitwiseBuiltin*, - }(evm_contract_address: felt, events_len: felt, events: model.Event*) { - alloc_locals; - - if (events_len == 0) { - return (); - } - - let event: model.Event = [events]; - let event_keys_felt = cast(event.keys, felt*); - // we add the operating evm_contract_address of an execution context - // as the first key of an event - // we track kakarot events as those emitted from the kkrt contract - // and map it to the kkrt contract via this convention - let (event_keys: felt*) = alloc(); - memcpy(dst=event_keys + 1, src=event_keys_felt, len=event.keys_len); - assert [event_keys] = evm_contract_address; - let updated_event_len = event.keys_len + 1; - - emit_event( - keys_len=updated_event_len, keys=event_keys, data_len=event.data_len, data=event.data - ); - // we maintain the semantics of one event struct involves iterating a full event struct size recursively - _emit_events(evm_contract_address, events_len - 1, events + 1 * model.Event.SIZE); - return (); - } - - // @notice Finalize the state updates brought by this ExecutionContext: contract creation, balances, sstore and events. - // @param self The ExecutionContext. - func _finalize_state_updates{ - syscall_ptr: felt*, - pedersen_ptr: HashBuiltin*, - range_check_ptr, - bitwise_ptr: BitwiseBuiltin*, - }(self: model.ExecutionContext*) { - alloc_locals; - let (dict_start, dict_end) = default_dict_finalize( - self.revert_contract_state.dict_start, self.revert_contract_state.dict_end, 0 - ); - - if (self.reverted == 0) { - _emit_events(self.evm_contract_address, self.events_len, self.events); - return (); - } - - _finalize_storage_updates( - starknet_contract_address=self.starknet_contract_address, - dict_start=dict_start, - dict_end=dict_end, - ); - Helpers.erase_contracts(self.create_addresses_len, self.create_addresses); - return (); - } - - // @notice Iterates through the `revert_contract_state` dict and restores a contract state to what it was prior to the reverting execution context's writes. - // @param starknet_contract_address The contract address whose state is being restored to prior the execution contexts writes. - // @param reverted Whether the ExecutionContext was reverted or not - // @param dict_start The start of the `revert_contract_state` dict. - // @param dict_end The end of the `revert_contract_state` dict. - // @return ExecutionContext The pointer to the updated execution context. - func _finalize_storage_updates{ - syscall_ptr: felt*, - pedersen_ptr: HashBuiltin*, - range_check_ptr, - bitwise_ptr: BitwiseBuiltin*, - }(starknet_contract_address: felt, dict_start: DictAccess*, dict_end: DictAccess*) { - alloc_locals; - if (dict_start == dict_end) { - return (); - } - - let reverted_state = dict_start.new_value; - let casted_reverted_state = cast(reverted_state, model.KeyValue*); - IContractAccount.write_storage( - contract_address=starknet_contract_address, - key=casted_reverted_state.key, - value=casted_reverted_state.value, - ); - return _finalize_storage_updates( - starknet_contract_address=starknet_contract_address, - dict_start=dict_start + DictAccess.SIZE, - dict_end=dict_end, ); } } diff --git a/src/kakarot/instructions/block_information.cairo b/src/kakarot/instructions/block_information.cairo index ff90ea7af..02a620db4 100644 --- a/src/kakarot/instructions/block_information.cairo +++ b/src/kakarot/instructions/block_information.cairo @@ -11,12 +11,13 @@ from starkware.starknet.common.syscalls import get_block_number, get_block_times from starkware.cairo.common.uint256 import Uint256 // Internal dependencies -from kakarot.model import model -from utils.utils import Helpers +from kakarot.constants import Constants, native_token_address, blockhash_registry_address from kakarot.execution_context import ExecutionContext +from kakarot.interfaces.interfaces import IBlockhashRegistry +from kakarot.model import model from kakarot.stack import Stack -from kakarot.constants import Constants, native_token_address, blockhash_registry_address -from kakarot.interfaces.interfaces import IERC20, IBlockhashRegistry +from kakarot.state import State +from utils.utils import Helpers // @title BlockInformation information opcodes. // @notice This file contains the functions to execute for block information opcodes. @@ -65,7 +66,7 @@ namespace BlockInformation { // Update the execution context. // Update context stack. - let ctx = ExecutionContext.update_stack(self=ctx, new_stack=stack); + let ctx = ExecutionContext.update_stack(ctx, stack); // Increment gas used. let ctx = ExecutionContext.increment_gas_used(self=ctx, inc_value=GAS_COST_BLOCKHASH); return ctx; @@ -82,7 +83,7 @@ namespace BlockInformation { // Update the execution context. // Update context stack. - let ctx = ExecutionContext.update_stack(self=ctx, new_stack=stack); + let ctx = ExecutionContext.update_stack(ctx, stack); // Increment gas used. let ctx = ExecutionContext.increment_gas_used(self=ctx, inc_value=GAS_COST_BLOCKHASH); return ctx; @@ -110,7 +111,7 @@ namespace BlockInformation { // Update the execution context. // Update context stack. - let ctx = ExecutionContext.update_stack(self=ctx, new_stack=stack); + let ctx = ExecutionContext.update_stack(ctx, stack); // Increment gas used. let ctx = ExecutionContext.increment_gas_used(self=ctx, inc_value=GAS_COST_COINBASE); return ctx; @@ -141,7 +142,7 @@ namespace BlockInformation { // Update the execution context. // Update context stack. - let ctx = ExecutionContext.update_stack(self=ctx, new_stack=stack); + let ctx = ExecutionContext.update_stack(ctx, stack); // Increment gas used. let ctx = ExecutionContext.increment_gas_used(self=ctx, inc_value=GAS_COST_TIMESTAMP); return ctx; @@ -172,7 +173,7 @@ namespace BlockInformation { // Update the execution context. // Update context stack. - let ctx = ExecutionContext.update_stack(self=ctx, new_stack=stack); + let ctx = ExecutionContext.update_stack(ctx, stack); // Increment gas used. let ctx = ExecutionContext.increment_gas_used(self=ctx, inc_value=GAS_COST_NUMBER); return ctx; @@ -200,7 +201,7 @@ namespace BlockInformation { // Update the execution context. // Update context stack. - let ctx = ExecutionContext.update_stack(self=ctx, new_stack=stack); + let ctx = ExecutionContext.update_stack(ctx, stack); // Increment gas used. let ctx = ExecutionContext.increment_gas_used(self=ctx, inc_value=GAS_COST_DIFFICULTY); return ctx; @@ -222,12 +223,12 @@ namespace BlockInformation { bitwise_ptr: BitwiseBuiltin*, }(ctx: model.ExecutionContext*) -> model.ExecutionContext* { // Get the Gas Limit - let gas_limit = Helpers.to_uint256(val=ctx.gas_limit); + let gas_limit = Helpers.to_uint256(val=ctx.call_context.gas_limit); let stack: model.Stack* = Stack.push(self=ctx.stack, element=gas_limit); // Update the execution context. // Update context stack. - let ctx = ExecutionContext.update_stack(self=ctx, new_stack=stack); + let ctx = ExecutionContext.update_stack(ctx, stack); // Increment gas used. let ctx = ExecutionContext.increment_gas_used(self=ctx, inc_value=GAS_COST_GASLIMIT); return ctx; @@ -254,7 +255,7 @@ namespace BlockInformation { // Update the execution context. // Update context stack. - let ctx = ExecutionContext.update_stack(self=ctx, new_stack=stack); + let ctx = ExecutionContext.update_stack(ctx, stack); // Increment gas used. let ctx = ExecutionContext.increment_gas_used(self=ctx, inc_value=GAS_COST_CHAINID); return ctx; @@ -277,16 +278,12 @@ namespace BlockInformation { }(ctx: model.ExecutionContext*) -> model.ExecutionContext* { alloc_locals; // Get balance of current executing contract address balance and push to stack. - let (native_token_address_) = native_token_address.read(); - let (balance: Uint256) = IERC20.balanceOf( - contract_address=native_token_address_, account=ctx.starknet_contract_address - ); - let stack: model.Stack* = Stack.push(self=ctx.stack, element=balance); + let (state, balance) = State.read_balance(ctx.state, ctx.call_context.address); + let stack = Stack.push(self=ctx.stack, element=balance); // Update the execution context. - // Update context stack. - let ctx = ExecutionContext.update_stack(self=ctx, new_stack=stack); - // Increment gas used. + let ctx = ExecutionContext.update_stack(ctx, stack); + let ctx = ExecutionContext.update_state(ctx, state); let ctx = ExecutionContext.increment_gas_used(self=ctx, inc_value=GAS_COST_SELFBALANCE); return ctx; } @@ -313,7 +310,7 @@ namespace BlockInformation { // Update the execution context. // Update context stack. - let ctx = ExecutionContext.update_stack(self=ctx, new_stack=stack); + let ctx = ExecutionContext.update_stack(ctx, stack); // Increment gas used. let ctx = ExecutionContext.increment_gas_used(self=ctx, inc_value=GAS_COST_BASEFEE); return ctx; diff --git a/src/kakarot/instructions/comparison_operations.cairo b/src/kakarot/instructions/comparison_operations.cairo index ee6c8f196..c3b9a2d3a 100644 --- a/src/kakarot/instructions/comparison_operations.cairo +++ b/src/kakarot/instructions/comparison_operations.cairo @@ -81,7 +81,7 @@ namespace ComparisonOperations { let stack: model.Stack* = Stack.push(self=stack, element=Uint256(result, 0)); // Update context stack. - let ctx = ExecutionContext.update_stack(self=ctx, new_stack=stack); + let ctx = ExecutionContext.update_stack(ctx, stack); // Increment gas used. let ctx = ExecutionContext.increment_gas_used(self=ctx, inc_value=GAS_COST_LT); return ctx; @@ -121,7 +121,7 @@ namespace ComparisonOperations { let stack: model.Stack* = Stack.push(stack, Uint256(result, 0)); // Update context stack. - let ctx = ExecutionContext.update_stack(self=ctx, new_stack=stack); + let ctx = ExecutionContext.update_stack(ctx, stack); // Increment gas used. let ctx = ExecutionContext.increment_gas_used(self=ctx, inc_value=GAS_COST_GT); return ctx; @@ -161,7 +161,7 @@ namespace ComparisonOperations { let stack: model.Stack* = Stack.push(self=stack, element=Uint256(result, 0)); // Update context stack. - let ctx = ExecutionContext.update_stack(self=ctx, new_stack=stack); + let ctx = ExecutionContext.update_stack(ctx, stack); // Increment gas used. let ctx = ExecutionContext.increment_gas_used(self=ctx, inc_value=GAS_COST_SLT); return ctx; @@ -201,7 +201,7 @@ namespace ComparisonOperations { let stack: model.Stack* = Stack.push(self=stack, element=Uint256(result, 0)); // Update context stack. - let ctx = ExecutionContext.update_stack(self=ctx, new_stack=stack); + let ctx = ExecutionContext.update_stack(ctx, stack); // Increment gas used. let ctx = ExecutionContext.increment_gas_used(self=ctx, inc_value=GAS_COST_SGT); return ctx; @@ -241,7 +241,7 @@ namespace ComparisonOperations { let stack: model.Stack* = Stack.push(self=stack, element=Uint256(result, 0)); // Update context stack. - let ctx = ExecutionContext.update_stack(self=ctx, new_stack=stack); + let ctx = ExecutionContext.update_stack(ctx, stack); // Increment gas used. let ctx = ExecutionContext.increment_gas_used(self=ctx, inc_value=GAS_COST_EQ); return ctx; @@ -278,7 +278,7 @@ namespace ComparisonOperations { let stack: model.Stack* = Stack.push(self=stack, element=Uint256(result, 0)); // Update context stack. - let ctx = ExecutionContext.update_stack(self=ctx, new_stack=stack); + let ctx = ExecutionContext.update_stack(ctx, stack); // Increment gas used. let ctx = ExecutionContext.increment_gas_used(self=ctx, inc_value=GAS_COST_ISZERO); return ctx; @@ -318,7 +318,7 @@ namespace ComparisonOperations { let stack: model.Stack* = Stack.push(self=stack, element=result); // Update context stack. - let ctx = ExecutionContext.update_stack(self=ctx, new_stack=stack); + let ctx = ExecutionContext.update_stack(ctx, stack); // Increment gas used. let ctx = ExecutionContext.increment_gas_used(self=ctx, inc_value=GAS_COST_AND); return ctx; @@ -358,7 +358,7 @@ namespace ComparisonOperations { let stack: model.Stack* = Stack.push(self=stack, element=result); // Update context stack. - let ctx = ExecutionContext.update_stack(self=ctx, new_stack=stack); + let ctx = ExecutionContext.update_stack(ctx, stack); // Increment gas used. let ctx = ExecutionContext.increment_gas_used(self=ctx, inc_value=GAS_COST_OR); return ctx; @@ -396,7 +396,7 @@ namespace ComparisonOperations { let stack: model.Stack* = Stack.push(self=stack, element=result); // Update context stack. - let ctx = ExecutionContext.update_stack(self=ctx, new_stack=stack); + let ctx = ExecutionContext.update_stack(ctx, stack); // Increment gas used. let ctx = ExecutionContext.increment_gas_used(self=ctx, inc_value=GAS_COST_XOR); return ctx; @@ -437,7 +437,7 @@ namespace ComparisonOperations { let stack: model.Stack* = Stack.push(self=stack, element=result); // Update context stack. - let ctx = ExecutionContext.update_stack(self=ctx, new_stack=stack); + let ctx = ExecutionContext.update_stack(ctx, stack); // Increment gas used. let ctx = ExecutionContext.increment_gas_used(self=ctx, inc_value=GAS_COST_BYTE); return ctx; @@ -477,7 +477,7 @@ namespace ComparisonOperations { let stack: model.Stack* = Stack.push(self=stack, element=result); // Update context stack. - let ctx = ExecutionContext.update_stack(self=ctx, new_stack=stack); + let ctx = ExecutionContext.update_stack(ctx, stack); // Increment gas used. let ctx = ExecutionContext.increment_gas_used(self=ctx, inc_value=GAS_COST_SHL); return ctx; @@ -517,7 +517,7 @@ namespace ComparisonOperations { let stack: model.Stack* = Stack.push(self=stack, element=result); // Update context stack. - let ctx = ExecutionContext.update_stack(self=ctx, new_stack=stack); + let ctx = ExecutionContext.update_stack(ctx, stack); // Increment gas used. let ctx = ExecutionContext.increment_gas_used(self=ctx, inc_value=GAS_COST_SHR); return ctx; @@ -586,7 +586,7 @@ namespace ComparisonOperations { let stack: model.Stack* = Stack.push(self=stack, element=result); // Update context stack. - let ctx = ExecutionContext.update_stack(self=ctx, new_stack=stack); + let ctx = ExecutionContext.update_stack(ctx, stack); let ctx = ExecutionContext.increment_gas_used(self=ctx, inc_value=GAS_COST_SAR); return ctx; } @@ -622,7 +622,7 @@ namespace ComparisonOperations { let stack: model.Stack* = Stack.push(self=stack, element=result); // Update context stack. - let ctx = ExecutionContext.update_stack(self=ctx, new_stack=stack); + let ctx = ExecutionContext.update_stack(ctx, stack); // Increment gas used. let ctx = ExecutionContext.increment_gas_used(self=ctx, inc_value=GAS_COST_NOT); return ctx; diff --git a/src/kakarot/instructions/duplication_operations.cairo b/src/kakarot/instructions/duplication_operations.cairo index 0aca7bc7f..83bb5a39f 100644 --- a/src/kakarot/instructions/duplication_operations.cairo +++ b/src/kakarot/instructions/duplication_operations.cairo @@ -35,7 +35,7 @@ namespace DuplicationOperations { let stack = Stack.push(self=stack, element=element); // Update context stack. - let ctx = ExecutionContext.update_stack(self=ctx, new_stack=stack); + let ctx = ExecutionContext.update_stack(ctx, stack); // Increment gas used. let ctx = ExecutionContext.increment_gas_used(self=ctx, inc_value=GAS_COST_DUP); return ctx; diff --git a/src/kakarot/instructions/environmental_information.cairo b/src/kakarot/instructions/environmental_information.cairo index 1db15ab33..8ca689337 100644 --- a/src/kakarot/instructions/environmental_information.cairo +++ b/src/kakarot/instructions/environmental_information.cairo @@ -16,9 +16,8 @@ from utils.utils import Helpers from kakarot.execution_context import ExecutionContext from kakarot.stack import Stack from kakarot.memory import Memory -from kakarot.constants import native_token_address -from kakarot.interfaces.interfaces import IERC20, IAccount -from kakarot.accounts.library import Accounts +from kakarot.state import State +from kakarot.account import Account // @title Environmental information opcodes. // @notice This file contains the functions to execute for environmental information opcodes. @@ -59,11 +58,11 @@ namespace EnvironmentalInformation { alloc_locals; // Get the current execution contract from the context, - // convert to Uin256, and push to Stack. - let address = Helpers.to_uint256(ctx.evm_contract_address); - let stack: model.Stack* = Stack.push(self=ctx.stack, element=address); + // convert to Uint256, and push to Stack. + let address = Helpers.to_uint256(ctx.call_context.address.evm); + let stack: model.Stack* = Stack.push(ctx.stack, address); // Update the execution context. - let ctx = ExecutionContext.update_stack(self=ctx, new_stack=stack); + let ctx = ExecutionContext.update_stack(ctx, stack); // Increment gas used let ctx = ExecutionContext.increment_gas_used(self=ctx, inc_value=GAS_COST_ADDRESS); return ctx; @@ -86,25 +85,17 @@ namespace EnvironmentalInformation { }(ctx: model.ExecutionContext*) -> model.ExecutionContext* { alloc_locals; - // Get the evm address. - let (stack: model.Stack*, address: Uint256) = Stack.pop(ctx.stack); - - let address_felt = Helpers.uint256_to_felt(address); - // Get the starknet account address from the evm account address - let (starknet_contract_address) = Accounts.compute_starknet_address(address_felt); - // Get the number of native tokens owned by the given starknet account - let (native_token_address_) = native_token_address.read(); - let (balance: Uint256) = IERC20.balanceOf( - contract_address=native_token_address_, account=starknet_contract_address - ); + let (stack, address_uint256) = Stack.pop(ctx.stack); - let stack: model.Stack* = Stack.push(stack, balance); + 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(stack, balance); - // Update the execution context. - // Update context stack. - let ctx = ExecutionContext.update_stack(self=ctx, new_stack=stack); - // Increment gas used. - let ctx = ExecutionContext.increment_gas_used(self=ctx, inc_value=GAS_COST_BALANCE); + let ctx = ExecutionContext.update_stack(ctx, stack); + let ctx = ExecutionContext.update_state(ctx, state); + let ctx = ExecutionContext.increment_gas_used(ctx, GAS_COST_BALANCE); return ctx; } @@ -125,11 +116,11 @@ namespace EnvironmentalInformation { }(ctx: model.ExecutionContext*) -> model.ExecutionContext* { alloc_locals; - let origin_address = Helpers.to_uint256(ctx.origin); + let origin_address = Helpers.to_uint256(ctx.call_context.origin.evm); // Update Context stack let stack: model.Stack* = Stack.push(self=ctx.stack, element=origin_address); - let ctx = ExecutionContext.update_stack(self=ctx, new_stack=stack); + let ctx = ExecutionContext.update_stack(ctx, stack); // Increment gas used let ctx = ExecutionContext.increment_gas_used(self=ctx, inc_value=GAS_COST_ORIGIN); return ctx; @@ -151,15 +142,19 @@ namespace EnvironmentalInformation { bitwise_ptr: BitwiseBuiltin*, }(ctx: model.ExecutionContext*) -> model.ExecutionContext* { alloc_locals; - let is_root = ExecutionContext.is_empty(ctx.calling_context); - let caller = (1 - is_root) * ctx.calling_context.evm_contract_address + is_root * - ctx.origin; + let calling_context = ctx.call_context.calling_context; + let is_root = ExecutionContext.is_empty(calling_context); + if (is_root == 0) { + tempvar caller = calling_context.call_context.address.evm; + } else { + tempvar caller = ctx.call_context.origin.evm; + } let evm_address_uint256 = Helpers.to_uint256(caller); let stack: model.Stack* = Stack.push(self=ctx.stack, element=evm_address_uint256); // Update the execution context. // Update context stack. - let ctx = ExecutionContext.update_stack(self=ctx, new_stack=stack); + let ctx = ExecutionContext.update_stack(ctx, stack); // Increment gas used. let ctx = ExecutionContext.increment_gas_used(self=ctx, inc_value=GAS_COST_CALLER); return ctx; @@ -228,7 +223,7 @@ namespace EnvironmentalInformation { let stack: model.Stack* = Stack.push(self=stack, element=uint256_sliced_calldata); // Update context stack. - let ctx = ExecutionContext.update_stack(self=ctx, new_stack=stack); + let ctx = ExecutionContext.update_stack(ctx, stack); // Increment gas used. let ctx = ExecutionContext.increment_gas_used(self=ctx, inc_value=GAS_COST_CALLDATALOAD); return ctx; @@ -305,9 +300,9 @@ namespace EnvironmentalInformation { ); // Update context memory. - let ctx = ExecutionContext.update_memory(self=ctx, new_memory=memory); + let ctx = ExecutionContext.update_memory(ctx, memory); // Update context stack. - let ctx = ExecutionContext.update_stack(self=ctx, new_stack=stack); + let ctx = ExecutionContext.update_stack(ctx, stack); // Increment gas used. let ctx = ExecutionContext.increment_gas_used(self=ctx, inc_value=GAS_COST_CALLDATACOPY); return ctx; @@ -335,7 +330,7 @@ namespace EnvironmentalInformation { // Update the execution context. // Update context stack. - let ctx = ExecutionContext.update_stack(self=ctx, new_stack=stack); + let ctx = ExecutionContext.update_stack(ctx, stack); // Increment gas used. let ctx = ExecutionContext.increment_gas_used(self=ctx, inc_value=GAS_COST_CODESIZE); return ctx; @@ -411,7 +406,7 @@ namespace EnvironmentalInformation { alloc_locals; // Get the gasprice. - let gas_price_felt = ctx.gas_price; + let gas_price_felt = ctx.call_context.gas_price; let gas_price_uint256 = Helpers.to_uint256(gas_price_felt); let stack: model.Stack* = Stack.push(self=ctx.stack, element=gas_price_uint256); @@ -446,17 +441,19 @@ namespace EnvironmentalInformation { // Stack input: // 0 - address: 20-byte address of the contract to query. let (stack, address_uint256) = Stack.pop(self=stack); - let address_felt = Helpers.uint256_to_felt(address_uint256); - let (bytecode_len) = Accounts.get_bytecode_len(address_felt); + 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, account) = State.get_account(ctx.state, address); // bytecode_len cannot be greater than 24k in the EVM - let stack = Stack.push(stack, Uint256(low=bytecode_len, high=0)); + let stack = Stack.push(stack, Uint256(low=account.code_len, high=0)); - // Update context stack. - let ctx = ExecutionContext.update_stack(self=ctx, new_stack=stack); + let ctx = ExecutionContext.update_stack(ctx, stack); + let ctx = ExecutionContext.update_state(ctx, state); // TODO:distinction between warm and cold addresses determines dynamic cost - // for now we assume a cold address, which sets dynamic cost to 2600 + // for now we assume a cold address, which sets dynamic cost to 2600 // see: https://www.evm.codes/about#accesssets let ctx = ExecutionContext.increment_gas_used(self=ctx, inc_value=GAS_COST_EXTCODESIZE); @@ -493,8 +490,10 @@ namespace EnvironmentalInformation { let offset = popped[2]; let size = popped[3]; - let address_felt = Helpers.uint256_to_felt(address_uint256); - let (bytecode_len, bytecode) = Accounts.get_bytecode(address_felt); + 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, account) = State.get_account(ctx.state, address); // Get bytecode slice from offset to size // in the case were @@ -504,7 +503,7 @@ namespace EnvironmentalInformation { // with the requested `size` of zeroes let sliced_bytecode: felt* = Helpers.slice_data( - data_len=bytecode_len, data=bytecode, data_offset=offset.low, slice_len=size.low + data_len=account.code_len, data=account.code, data_offset=offset.low, slice_len=size.low ); // Write bytecode slice to memory at dest_offset @@ -515,10 +514,10 @@ namespace EnvironmentalInformation { self=ctx.memory, element_len=size.low, element=sliced_bytecode, offset=dest_offset.low ); - // Update context memory. - let ctx = ExecutionContext.update_memory(self=ctx, new_memory=memory); - // Update context stack. - let ctx = ExecutionContext.update_stack(self=ctx, new_stack=stack); + let ctx = ExecutionContext.update_memory(ctx, memory); + let ctx = ExecutionContext.update_stack(ctx, stack); + let ctx = ExecutionContext.update_state(ctx, state); + // Increment gas used. let (minimum_word_size) = Helpers.minimum_word_count(size.low); @@ -554,7 +553,7 @@ namespace EnvironmentalInformation { // Update the execution context. // Update context stack. - let ctx = ExecutionContext.update_stack(self=ctx, new_stack=stack); + let ctx = ExecutionContext.update_stack(ctx, stack); // Increment gas used. let ctx = ExecutionContext.increment_gas_used(self=ctx, inc_value=GAS_COST_RETURNDATASIZE); return ctx; @@ -603,9 +602,9 @@ namespace EnvironmentalInformation { ); // Update context memory. - let ctx = ExecutionContext.update_memory(self=ctx, new_memory=memory); + let ctx = ExecutionContext.update_memory(ctx, memory); // Update context stack. - let ctx = ExecutionContext.update_stack(self=ctx, new_stack=stack); + let ctx = ExecutionContext.update_stack(ctx, stack); // Increment gas used. let ctx = ExecutionContext.increment_gas_used(ctx, GAS_COST_CALLDATACOPY); return ctx; @@ -632,17 +631,19 @@ namespace EnvironmentalInformation { // Stack input: // 0 - address: 20-byte address of the contract to query. - let (stack, address_uint256) = Stack.pop(self=stack); - let address_felt = Helpers.uint256_to_felt(address_uint256); - let (bytecode_len, bytecode) = Accounts.get_bytecode(address_felt); + let (stack, address_uint256) = Stack.pop(stack); + 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, account) = State.get_account(ctx.state, address); let (local dest: felt*) = alloc(); // convert to little endian Helpers.bytes_to_bytes8_little_endian( - bytes_len=bytecode_len, - bytes=bytecode, + bytes_len=account.code_len, + bytes=account.code, index=0, - size=bytecode_len, + size=account.code_len, bytes8=0, bytes8_shift=0, dest=dest, @@ -653,14 +654,16 @@ namespace EnvironmentalInformation { local keccak_ptr_start: felt* = keccak_ptr; with keccak_ptr { - let (result) = cairo_keccak_bigend(inputs=dest, n_bytes=bytecode_len); + let (result) = cairo_keccak_bigend(inputs=dest, n_bytes=account.code_len); finalize_keccak(keccak_ptr_start=keccak_ptr_start, keccak_ptr_end=keccak_ptr); } - let stack: model.Stack* = Stack.push(self=stack, element=result); - // Update context stack + let stack = Stack.push(stack, result); + let ctx = ExecutionContext.update_stack(ctx, stack); + let ctx = ExecutionContext.update_state(ctx, state); + // Increment gas used (COLD ACCESS) // see: https://www.evm.codes/about#accesssets let ctx = ExecutionContext.increment_gas_used(self=ctx, inc_value=GAS_COST_EXTCODEHASH); diff --git a/src/kakarot/instructions/exchange_operations.cairo b/src/kakarot/instructions/exchange_operations.cairo index d2a034e7f..6378d015d 100644 --- a/src/kakarot/instructions/exchange_operations.cairo +++ b/src/kakarot/instructions/exchange_operations.cairo @@ -33,7 +33,7 @@ namespace ExchangeOperations { let stack = Stack.swap_i(self=stack, i=i + 1); // Update context stack. - let ctx = ExecutionContext.update_stack(self=ctx, new_stack=stack); + let ctx = ExecutionContext.update_stack(ctx, stack); // Increment gas used. let ctx = ExecutionContext.increment_gas_used(self=ctx, inc_value=GAS_COST_SWAP); return ctx; diff --git a/src/kakarot/instructions/logging_operations.cairo b/src/kakarot/instructions/logging_operations.cairo index fe14b1be9..4433dbf6a 100644 --- a/src/kakarot/instructions/logging_operations.cairo +++ b/src/kakarot/instructions/logging_operations.cairo @@ -4,7 +4,7 @@ // Starkware dependencies from starkware.cairo.common.alloc import alloc -from starkware.cairo.common.bool import FALSE +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 @@ -34,9 +34,9 @@ namespace LoggingOperations { ) -> model.ExecutionContext* { alloc_locals; - if (ctx.read_only != FALSE) { + if (ctx.call_context.read_only != FALSE) { let (revert_reason_len, revert_reason) = Errors.stateModificationError(); - let ctx = ExecutionContext.revert(ctx, revert_reason, revert_reason_len); + let ctx = ExecutionContext.stop(ctx, revert_reason_len, revert_reason, TRUE); return ctx; } @@ -47,25 +47,18 @@ namespace LoggingOperations { let (stack, popped) = Stack.pop_n(stack, topics_len + 2); let ctx = ExecutionContext.update_stack(ctx, stack); - let offset = popped[0]; - let size = popped[1]; - // Transform data + safety checks - let actual_size = Helpers.uint256_to_felt(size); - let actual_offset = Helpers.uint256_to_felt(offset); + let size = Helpers.uint256_to_felt(popped[1]); + let offset = Helpers.uint256_to_felt(popped[0]); // Log topics by emitting a starknet event let (data: felt*) = alloc(); let (memory, gas_cost) = Memory.load_n( - self=ctx.memory, element_len=actual_size, element=data, offset=actual_offset + self=ctx.memory, element_len=size, element=data, offset=offset ); - let ctx = ExecutionContext.push_event_to_emit( - self=ctx, - event_keys_len=topics_len * 2, - event_keys=popped + 4, - event_data_len=actual_size, - event_data=data, + let ctx = ExecutionContext.push_event( + self=ctx, topics_len=topics_len, topics=popped + 4, data_len=size, data=data ); // Update context stack. @@ -156,39 +149,3 @@ namespace LoggingOperations { return ctx; } } - -namespace LoggingHelper { - func _finalize_loop{ - syscall_ptr: felt*, - pedersen_ptr: HashBuiltin*, - range_check_ptr, - bitwise_ptr: BitwiseBuiltin*, - }(events_len: felt, events: model.Event*) -> felt* { - alloc_locals; - - if (events_len == 0) { - return (events); - } - - let event: model.Event = [events]; - - emit_event( - keys_len=event.keys_len, keys=event.keys, data_len=event.data_len, data=event.data - ); - // we maintain the semantics of one event struct involves iterating a full event struct size recursively - return _finalize_loop(events_len - 1, events + 1 * model.Event.SIZE); - } - - // @notice helper for log family opcodes. - // @notice emits all registered events when a calling context is finalized - // @return The pointer to the updated execution_context. - func finalize{ - syscall_ptr: felt*, - pedersen_ptr: HashBuiltin*, - range_check_ptr, - bitwise_ptr: BitwiseBuiltin*, - }(ctx: model.ExecutionContext*) -> model.ExecutionContext* { - _finalize_loop(ctx.events_len, ctx.events); - return ctx; - } -} diff --git a/src/kakarot/instructions/memory_operations.cairo b/src/kakarot/instructions/memory_operations.cairo index d28347a5c..daffb3429 100644 --- a/src/kakarot/instructions/memory_operations.cairo +++ b/src/kakarot/instructions/memory_operations.cairo @@ -6,23 +6,16 @@ from starkware.cairo.common.cairo_builtins import HashBuiltin, BitwiseBuiltin from starkware.cairo.common.uint256 import Uint256, uint256_eq, uint256_unsigned_div_rem from starkware.cairo.common.alloc import alloc -from starkware.cairo.common.bool import FALSE -from starkware.cairo.common.dict import ( - DictAccess, - dict_new, - dict_read, - dict_squash, - dict_update, - dict_write, -) +from starkware.cairo.common.bool import FALSE, TRUE +from starkware.cairo.common.dict import DictAccess, dict_new, dict_read, dict_write from starkware.cairo.common.registers import get_fp_and_pc from kakarot.errors import Errors from kakarot.execution_context import ExecutionContext -from kakarot.interfaces.interfaces import IContractAccount 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 // @title Exchange operations opcodes. @@ -349,74 +342,21 @@ namespace MemoryOperations { }(ctx: model.ExecutionContext*) -> model.ExecutionContext* { alloc_locals; - if (ctx.read_only != FALSE) { + if (ctx.call_context.read_only != FALSE) { let (revert_reason_len, revert_reason) = Errors.stateModificationError(); - let ctx = ExecutionContext.revert(ctx, revert_reason, revert_reason_len); + let ctx = ExecutionContext.stop(ctx, revert_reason_len, revert_reason, TRUE); return ctx; } - let stack = ctx.stack; - - // ------- 1. Get starknet address - let starknet_contract_address: felt = ctx.starknet_contract_address; + let (stack, popped) = Stack.pop_n(self=ctx.stack, n=2); - // ----- 2. Pop 2 values: key and value - - // Stack input: - // 0 - key: key of memory. - // 1 - value: value for given key. - let (stack, popped) = Stack.pop_n(self=stack, n=2); - // Update context stack. + let key = popped[0]; // Uint256* + let value = popped + Uint256.SIZE; // Uint256* + let state = State.write_storage(ctx.state, ctx.call_context.address, key, value); + let ctx = ExecutionContext.update_state(ctx, state); let ctx = ExecutionContext.update_stack(ctx, stack); - - let key = popped[0]; - let value = popped[1]; - - // 3. Call Write storage on contract with starknet address - with_attr error_message("Contract call failed") { - let (local prior_value: Uint256) = IContractAccount.storage( - contract_address=starknet_contract_address, key=key - ); - - IContractAccount.write_storage( - contract_address=starknet_contract_address, key=key, value=value - ); - } - - tempvar key_val = new model.KeyValue(key, prior_value); - let revert_contract_state_dict_end = ctx.revert_contract_state.dict_end; - - let (maybe_written) = dict_read{dict_ptr=revert_contract_state_dict_end}(key=key.low); - - // we only want to track the initial state of a written value relative to the beginning of the execution context, which is the very first write to the dictionary - // we initialize a default dictionary with the default value as zero. - // we check if return value of a read is zero to mark whether we want to write the prior value in this case or not. - if (maybe_written != 0) { - // if the value is not zero, then we treat it as a pointer to a keyvalue struct, - // meaning that the prior state was already written, so we do no writing - - // Increment gas used. - let ctx = ExecutionContext.increment_gas_used(ctx, GAS_COST_SSTORE); - let ctx = ExecutionContext.update_revert_contract_state( - ctx, revert_contract_state_dict_end - ); - return ctx; - } else { - // otherwise, there has been no write yet for this given context, - // so this prior is the prior to the entire execution context - // so we *do* write the keyvalue struct pointer to the dict - dict_write{dict_ptr=revert_contract_state_dict_end}( - key=key.low, new_value=cast(key_val, felt) - ); - - let ctx = ExecutionContext.update_revert_contract_state( - ctx, revert_contract_state_dict_end - ); - - // Increment gas used. - let ctx = ExecutionContext.increment_gas_used(ctx, GAS_COST_SSTORE); - return ctx; - } + let ctx = ExecutionContext.increment_gas_used(ctx, GAS_COST_SSTORE); + return ctx; } // @notice SLOAD operation @@ -435,29 +375,11 @@ namespace MemoryOperations { bitwise_ptr: BitwiseBuiltin*, }(ctx: model.ExecutionContext*) -> model.ExecutionContext* { alloc_locals; - let stack = ctx.stack; - - // ------- 1. Get starknet address - let starknet_contract_address: felt = ctx.starknet_contract_address; - - // ----- 2. Pop 1 value: key - - // Stack input: - // key: key of memory. - let (stack, local key) = Stack.pop(stack); - // local value: Uint256; - // 3. Get the data from storage - - let (local value: Uint256) = IContractAccount.storage( - contract_address=starknet_contract_address, key=key - ); - - let stack: model.Stack* = Stack.push(stack, value); - - // Update context stack. + let (stack, key) = Stack.pop(ctx.stack); + let (state, value) = State.read_storage(ctx.state, ctx.call_context.address, key); + let stack = Stack.push(stack, value); let ctx = ExecutionContext.update_stack(ctx, stack); - // Increment gas used. - let ctx = ExecutionContext.increment_gas_used(ctx, GAS_COST_SLOAD); + let ctx = ExecutionContext.update_state(ctx, state); return ctx; } @@ -481,7 +403,7 @@ namespace MemoryOperations { let stack: model.Stack* = ctx.stack; // Compute remaining gas. - let remaining_gas = ctx.gas_limit - ctx.gas_used - GAS_COST_GAS; + let remaining_gas = ctx.call_context.gas_limit - ctx.gas_used - GAS_COST_GAS; let stack: model.Stack* = Stack.push(ctx.stack, Uint256(remaining_gas, 0)); // Update context stack. diff --git a/src/kakarot/instructions/stop_and_arithmetic_operations.cairo b/src/kakarot/instructions/stop_and_arithmetic_operations.cairo index c27157b95..ce208215f 100644 --- a/src/kakarot/instructions/stop_and_arithmetic_operations.cairo +++ b/src/kakarot/instructions/stop_and_arithmetic_operations.cairo @@ -57,10 +57,7 @@ namespace StopAndArithmeticOperations { // execution context with *no* return data (unlike RETURN and REVERT). // hence we just clear the return_data and stop. let (return_data: felt*) = alloc(); - let ctx = ExecutionContext.update_return_data( - ctx, return_data_len=0, return_data=return_data - ); - let ctx = ExecutionContext.stop(ctx); + let ctx = ExecutionContext.stop(ctx, 0, return_data, FALSE); return ctx; } @@ -483,7 +480,7 @@ namespace StopAndArithmeticOperations { ctx: model.ExecutionContext*, stack: model.Stack*, gas_cost: felt ) -> model.ExecutionContext* { // Update context stack. - let ctx = ExecutionContext.update_stack(self=ctx, new_stack=stack); + let ctx = ExecutionContext.update_stack(ctx, stack); // Increment gas used. let ctx = ExecutionContext.increment_gas_used(self=ctx, inc_value=gas_cost); return ctx; diff --git a/src/kakarot/instructions/system_operations.cairo b/src/kakarot/instructions/system_operations.cairo index 313c4ee7c..3721d0cc5 100644 --- a/src/kakarot/instructions/system_operations.cairo +++ b/src/kakarot/instructions/system_operations.cairo @@ -7,16 +7,7 @@ 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.cairo_keccak.keccak import cairo_keccak_bigend, finalize_keccak -from starkware.cairo.common.dict import ( - DictAccess, - dict_new, - dict_read, - dict_squash, - dict_update, - dict_write, -) -from starkware.cairo.common.default_dict import default_dict_new, default_dict_finalize -from starkware.cairo.common.math import split_felt +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 @@ -25,17 +16,18 @@ from starkware.starknet.common.syscalls import ( get_contract_address, get_tx_info, ) +from starkware.cairo.common.registers import get_fp_and_pc // Internal dependencies -from kakarot.accounts.library import Accounts +from kakarot.account import Account from kakarot.constants import contract_account_class_hash, native_token_address, Constants from kakarot.errors import Errors from kakarot.execution_context import ExecutionContext -from kakarot.interfaces.interfaces import IContractAccount, IERC20, IAccount from kakarot.memory import Memory from kakarot.model import model from kakarot.precompiles.precompiles import Precompiles from kakarot.stack import Stack +from kakarot.state import State from utils.rlp import RLP from utils.utils import Helpers @@ -62,9 +54,9 @@ namespace SystemOperations { }(ctx: model.ExecutionContext*) -> model.ExecutionContext* { alloc_locals; - if (ctx.read_only != FALSE) { + if (ctx.call_context.read_only != FALSE) { let (revert_reason_len, revert_reason) = Errors.stateModificationError(); - let ctx = ExecutionContext.revert(ctx, revert_reason, revert_reason_len); + let ctx = ExecutionContext.stop(ctx, revert_reason_len, revert_reason, TRUE); return ctx; } @@ -73,8 +65,6 @@ namespace SystemOperations { // 1 - offset: byte offset in the memory in bytes (initialization code) // 2 - size: byte size to copy (size of initialization code) let (stack, popped) = Stack.pop_n(self=ctx.stack, n=3); - let ctx = ExecutionContext.update_stack(ctx, stack); - let size = popped[2]; // create dynamic gas: @@ -82,6 +72,8 @@ namespace SystemOperations { // -> ``memory_expansion_cost + deployment_code_execution_cost + code_deposit_cost`` is handled inside ``initialize_sub_context`` let (minimum_word_size) = Helpers.minimum_word_count(size.low); let word_size_gas = 6 * minimum_word_size; + + let ctx = ExecutionContext.update_stack(ctx, stack); let ctx = ExecutionContext.increment_gas_used(self=ctx, inc_value=word_size_gas); let sub_ctx = CreateHelper.initialize_sub_context(ctx=ctx, popped_len=3, popped=popped); @@ -104,9 +96,9 @@ namespace SystemOperations { }(ctx: model.ExecutionContext*) -> model.ExecutionContext* { alloc_locals; - if (ctx.read_only != FALSE) { + if (ctx.call_context.read_only != FALSE) { let (revert_reason_len, revert_reason) = Errors.stateModificationError(); - let ctx = ExecutionContext.revert(ctx, revert_reason, revert_reason_len); + let ctx = ExecutionContext.stop(ctx, revert_reason_len, revert_reason, TRUE); return ctx; } @@ -116,6 +108,7 @@ namespace SystemOperations { // 2 - size: byte size to copy (size of initialization code) // 3 - salt: salt for address generation let (stack, popped) = Stack.pop_n(self=ctx.stack, n=4); + let ctx = ExecutionContext.update_stack(ctx, stack); let sub_ctx = CreateHelper.initialize_sub_context(ctx=ctx, popped_len=4, popped=popped); @@ -142,7 +135,7 @@ namespace SystemOperations { // TODO: map the concept of consuming all the gas given to the context alloc_locals; let (revert_reason: felt*) = alloc(); - let ctx = ExecutionContext.revert(self=ctx, revert_reason=revert_reason, size=0); + let ctx = ExecutionContext.stop(ctx, 0, revert_reason, TRUE); return ctx; } @@ -162,21 +155,20 @@ namespace SystemOperations { }(ctx: model.ExecutionContext*) -> model.ExecutionContext* { alloc_locals; - let stack = ctx.stack; - let (stack, offset) = Stack.pop(stack); - let (stack, size) = Stack.pop(stack); - let ctx = ExecutionContext.update_stack(ctx, stack); + // Stack input: + // 0 - offset: byte offset in the memory in bytes + // 1 - size: byte size to copy + let (stack, popped) = Stack.pop_n(self=ctx.stack, n=2); + let offset = popped[0]; + let size = popped[1]; let (local return_data: felt*) = alloc(); - let (memory, gas_cost) = Memory.load_n( - self=ctx.memory, element_len=size.low, element=return_data, offset=offset.low - ); - let ctx = ExecutionContext.update_memory(self=ctx, new_memory=memory); + let (memory, gas_cost) = Memory.load_n(ctx.memory, size.low, return_data, offset.low); + + let ctx = ExecutionContext.update_stack(ctx, stack); + let ctx = ExecutionContext.update_memory(ctx, memory); let ctx = ExecutionContext.increment_gas_used(ctx, gas_cost); - let ctx = ExecutionContext.update_return_data( - ctx, return_data_len=size.low, return_data=return_data - ); - let ctx = ExecutionContext.stop(ctx); + let ctx = ExecutionContext.stop(ctx, size.low, return_data, FALSE); return ctx; } @@ -197,26 +189,21 @@ namespace SystemOperations { }(ctx: model.ExecutionContext*) -> model.ExecutionContext* { alloc_locals; - // Get stack and memory from context - let stack = ctx.stack; - let memory = ctx.memory; - // Stack input: // 0 - offset: byte offset in the memory in bytes // 1 - size: byte size to copy - let (stack, popped) = Stack.pop_n(self=stack, n=2); - + let (stack, popped) = Stack.pop_n(self=ctx.stack, n=2); let offset = popped[0]; let size = popped[1]; // Load revert reason from offset - let (revert_reason: felt*) = alloc(); - let (memory, gas_cost) = Memory.load_n(memory, size.low, revert_reason, offset.low); + let (return_data: felt*) = alloc(); + let (memory, gas_cost) = Memory.load_n(ctx.memory, size.low, return_data, offset.low); let ctx = ExecutionContext.update_stack(ctx, stack); - let ctx = ExecutionContext.revert(self=ctx, revert_reason=revert_reason, size=size.low); - let ctx = ExecutionContext.update_memory(self=ctx, new_memory=memory); + let ctx = ExecutionContext.update_memory(ctx, memory); let ctx = ExecutionContext.increment_gas_used(self=ctx, inc_value=gas_cost); + let ctx = ExecutionContext.stop(ctx, size.low, return_data, TRUE); return ctx; } @@ -234,32 +221,34 @@ namespace SystemOperations { range_check_ptr, bitwise_ptr: BitwiseBuiltin*, }(ctx: model.ExecutionContext*) -> model.ExecutionContext* { + // See https://docs.cairo-lang.org/0.12.0/how_cairo_works/functions.html#retrieving-registers + alloc_locals; + let fp_and_pc = get_fp_and_pc(); + local __fp__: felt* = fp_and_pc.fp_val; let sub_ctx = CallHelper.init_sub_context( - calling_ctx=ctx, with_value=TRUE, read_only=ctx.read_only + ctx=ctx, with_value=TRUE, read_only=ctx.call_context.read_only, self_call=FALSE ); - if (ctx.read_only * sub_ctx.call_context.value != FALSE) { + if (ctx.call_context.read_only * sub_ctx.call_context.value != FALSE) { let (revert_reason_len, revert_reason) = Errors.stateModificationError(); - let ctx = ExecutionContext.revert(ctx, revert_reason, revert_reason_len); + let ctx = ExecutionContext.stop(ctx, revert_reason_len, revert_reason, TRUE); return ctx; } - if (sub_ctx.call_context.value == 0) { - return sub_ctx; - } - - let (native_token_address_) = native_token_address.read(); - let amount_u256 = Helpers.to_uint256(sub_ctx.call_context.value); - let sender = ctx.starknet_contract_address; - let recipient = sub_ctx.starknet_contract_address; - let (success) = IERC20.transferFrom( - contract_address=native_token_address_, - sender=sender, - recipient=recipient, - amount=amount_u256, - ); - with_attr error_message( - "Kakarot: 0xF1: failed to transfer token from {sender} to {recipient}") { - assert success = TRUE; + let (value_high, value_low) = split_felt(sub_ctx.call_context.value); + tempvar value = Uint256(value_low, value_high); + let calling_context = sub_ctx.call_context.calling_context; + let transfer = model.Transfer( + calling_context.call_context.address, sub_ctx.call_context.address, value + ); + let (state, success) = State.add_transfer(sub_ctx.state, transfer); + let sub_ctx = ExecutionContext.update_state(sub_ctx, state); + if (success == 0) { + let (revert_reason_len, revert_reason) = Errors.balanceError(); + tempvar sub_ctx = ExecutionContext.stop( + sub_ctx, revert_reason_len, revert_reason, TRUE + ); + } else { + tempvar sub_ctx = sub_ctx; } return sub_ctx; @@ -280,7 +269,7 @@ namespace SystemOperations { bitwise_ptr: BitwiseBuiltin*, }(ctx: model.ExecutionContext*) -> model.ExecutionContext* { let sub_ctx = CallHelper.init_sub_context( - calling_ctx=ctx, with_value=FALSE, read_only=TRUE + ctx=ctx, with_value=FALSE, read_only=TRUE, self_call=FALSE ); return sub_ctx; } @@ -300,30 +289,8 @@ namespace SystemOperations { bitwise_ptr: BitwiseBuiltin*, }(ctx: model.ExecutionContext*) -> model.ExecutionContext* { let sub_ctx = CallHelper.init_sub_context( - calling_ctx=ctx, with_value=TRUE, read_only=ctx.read_only - ); - let recipient = sub_ctx.starknet_contract_address; - let sub_ctx = ExecutionContext.update_addresses( - sub_ctx, ctx.starknet_contract_address, ctx.evm_contract_address - ); - - if (sub_ctx.call_context.value == 0) { - return sub_ctx; - } - - let (native_token_address_) = native_token_address.read(); - let amount_u256 = Helpers.to_uint256(sub_ctx.call_context.value); - let sender = ctx.starknet_contract_address; - let (success) = IERC20.transferFrom( - contract_address=native_token_address_, - sender=sender, - recipient=recipient, - amount=amount_u256, + ctx=ctx, with_value=TRUE, read_only=ctx.call_context.read_only, self_call=TRUE ); - with_attr error_message( - "Kakarot: 0xF2: failed to transfer token from {sender} to {recipient}") { - assert success = TRUE; - } return sub_ctx; } @@ -343,10 +310,7 @@ namespace SystemOperations { bitwise_ptr: BitwiseBuiltin*, }(ctx: model.ExecutionContext*) -> model.ExecutionContext* { let sub_ctx = CallHelper.init_sub_context( - calling_ctx=ctx, with_value=FALSE, read_only=ctx.read_only - ); - let sub_ctx = ExecutionContext.update_addresses( - sub_ctx, ctx.starknet_contract_address, ctx.evm_contract_address + ctx=ctx, with_value=FALSE, read_only=ctx.call_context.read_only, self_call=TRUE ); return sub_ctx; @@ -367,44 +331,35 @@ namespace SystemOperations { }(ctx: model.ExecutionContext*) -> model.ExecutionContext* { alloc_locals; - if (ctx.read_only != FALSE) { + if (ctx.call_context.read_only != FALSE) { let (revert_reason_len, revert_reason) = Errors.stateModificationError(); - let ctx = ExecutionContext.revert(ctx, revert_reason, revert_reason_len); + let ctx = ExecutionContext.stop(ctx, revert_reason_len, revert_reason, TRUE); return ctx; } - // Get stack and memory from context - let stack = ctx.stack; - // Stack input: // 0 - address: account to send the current balance to - let (stack, address_uint256) = Stack.pop(stack); - let ctx = ExecutionContext.update_stack(ctx, stack); - let address_felt = Helpers.uint256_to_felt(address_uint256); - - // Get the number of native tokens owned by the given starknet - // account and transfer them to receiver - let (native_token_address_) = native_token_address.read(); - let (balance: Uint256) = IERC20.balanceOf( - contract_address=native_token_address_, account=ctx.starknet_contract_address - ); - let sender = ctx.starknet_contract_address; - let (success) = IERC20.transferFrom( - contract_address=native_token_address_, - sender=sender, - recipient=address_felt, - amount=balance, - ); - with_attr error_message( - "Kakarot: 0xFF: failed to transfer token from {sender} to {address_felt}") { - assert success = TRUE; - } - - // Save contract to be destroyed at the end of the transaction - let ctx = ExecutionContext.push_to_destroy_contract( - self=ctx, destroy_contract=ctx.starknet_contract_address - ); + // Transfer funds + let (stack, address) = Stack.pop(ctx.stack); + let (_, address_high) = unsigned_div_rem(address.high, 2 ** 32); + let address = Uint256(address_high, address.low); + let recipient_evm_address = Helpers.uint256_to_felt(address); + 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 transfer = model.Transfer( + sender=ctx.call_context.address, recipient=recipient, amount=balance + ); + let (state, success) = State.add_transfer(state, transfer); + + // Register for SELFDESTRUCT + let (state, account) = State.get_account(state, ctx.call_context.address); + let account = Account.selfdestruct(account); + let state = State.set_account(state, ctx.call_context.address, account); + + let ctx = ExecutionContext.update_state(ctx, state); + let ctx = ExecutionContext.update_stack(ctx, stack); return ctx; } @@ -422,6 +377,8 @@ namespace CallHelper { } // @dev: with_value arg lets specify if the call requires a value (CALL, CALLCODE) or not (STATICCALL, DELEGATECALL). + // @param ctx The pointer to the current ExecutionContext + // @param with_value Whether the call pops a value arg // @return ExecutionContext The pointer to the context and call args. func prepare_args{ syscall_ptr: felt*, @@ -435,12 +392,12 @@ namespace CallHelper { // Note: We don't pop ret_offset and ret_size here but at the end of the sub context // See finalize_calling_context let (stack, popped) = Stack.pop_n(self=ctx.stack, n=4 + with_value); - let ctx = ExecutionContext.update_stack(ctx, stack); let gas = 2 ** 128 * popped[0].high + popped[0].low; let address = 2 ** 128 * popped[1].high + popped[1].low; let stack_value = (2 ** 128 * popped[2].high + popped[2].low) * with_value; - // if the call op expects value to be on the stack, we return it, else the value is the calling call context value + // If the call op expects value to be on the stack, we return it + // Otherwise, the value is the calling call context value let value = with_value * stack_value + (1 - with_value) * ctx.call_context.value; let args_offset = 2 ** 128 * popped[2 + with_value].high + popped[2 + with_value].low; let args_size = 2 ** 128 * popped[3 + with_value].high + popped[3 + with_value].low; @@ -451,22 +408,11 @@ namespace CallHelper { self=ctx.memory, element_len=args_size, element=calldata, offset=args_offset ); - // TODO: account for value_to_empty_account_cost in dynamic gas - let value_nn = is_nn(value); - let value_not_zero = is_not_zero(value); - let value_is_positive = value_nn * value_not_zero; - let dynamic_gas = gas_cost + SystemOperations.GAS_COST_COLD_ADDRESS_ACCESS + - SystemOperations.GAS_COST_POSITIVE_VALUE * value_is_positive; - let ctx = ExecutionContext.increment_gas_used(self=ctx, inc_value=dynamic_gas); - - let remaining_gas = ctx.gas_limit - ctx.gas_used; - let (max_allowed_gas, _) = Helpers.div_rem(remaining_gas, 64); - let gas_limit = Helpers.min(gas, max_allowed_gas); - let call_args = CallArgs( - gas=gas_limit, address=address, value=value, args_size=args_size, calldata=calldata + gas=gas, address=address, value=value, args_size=args_size, calldata=calldata ); + let ctx = ExecutionContext.update_stack(ctx, stack); let ctx = ExecutionContext.update_memory(ctx, memory); return (ctx, call_args); @@ -476,6 +422,7 @@ namespace CallHelper { // @param calling_ctx The pointer to the calling execution context. // @param with_value The boolean that determines whether the sub-context's calling context has a value read from the calling context's stack or the calling context's calling context. // @param read_only The boolean that determines whether state modifications can be executed from the sub-execution context. + // @param self_call A boolean to indicate whether the account to message-call into is self (address of the current executing account) or the call argument's address (address of the call's target account) // @return ExecutionContext The pointer to the sub context. func init_sub_context{ syscall_ptr: felt*, @@ -483,50 +430,53 @@ namespace CallHelper { range_check_ptr, bitwise_ptr: BitwiseBuiltin*, }( - calling_ctx: model.ExecutionContext*, with_value: felt, read_only: felt + ctx: model.ExecutionContext*, with_value: felt, read_only: felt, self_call: felt ) -> model.ExecutionContext* { alloc_locals; - let (calling_ctx, local call_args) = CallHelper.prepare_args( - ctx=calling_ctx, with_value=with_value - ); + let (ctx, local call_args) = CallHelper.prepare_args(ctx, with_value); // Check if the called address is a precompiled contract let is_precompile = Precompiles.is_precompile(address=call_args.address); - if (is_precompile == FALSE) { - let (local return_data: felt*) = alloc(); - let (bytecode_len, bytecode) = Accounts.get_bytecode(call_args.address); - tempvar call_context = new model.CallContext( - bytecode=bytecode, - bytecode_len=bytecode_len, - calldata=call_args.calldata, + if (is_precompile != FALSE) { + let sub_ctx = Precompiles.run( + evm_address=call_args.address, calldata_len=call_args.args_size, + calldata=call_args.calldata, value=call_args.value, - ); - let (starknet_contract_address) = Accounts.compute_starknet_address(call_args.address); - let sub_ctx = ExecutionContext.init( - call_context=call_context, - starknet_contract_address=starknet_contract_address, - evm_contract_address=call_args.address, - origin=calling_ctx.origin, - gas_limit=call_args.gas, - gas_price=calling_ctx.gas_price, - calling_context=calling_ctx, - return_data_len=0, - return_data=return_data, - read_only=read_only, + calling_context=ctx, ); return sub_ctx; } - let sub_ctx = Precompiles.run( - address=call_args.address, - calldata_len=call_args.args_size, + let (starknet_contract_address) = Account.compute_starknet_address(call_args.address); + tempvar call_address = new model.Address(starknet_contract_address, call_args.address); + let (state, account) = State.get_account(ctx.state, call_address); + let ctx = ExecutionContext.update_state(ctx, state); + + if (self_call == FALSE) { + tempvar address = call_address; + } else { + tempvar address = ctx.call_context.address; + } + + tempvar call_context = new model.CallContext( + bytecode=account.code, + bytecode_len=account.code_len, calldata=call_args.calldata, + calldata_len=call_args.args_size, value=call_args.value, - calling_context=calling_ctx, - ); - + gas_limit=call_args.gas, + gas_price=ctx.call_context.gas_price, + origin=ctx.call_context.origin, + calling_context=ctx, + address=address, + read_only=read_only, + is_create=FALSE, + ); + let sub_ctx = ExecutionContext.init(call_context); + let state = State.copy(ctx.state); + let sub_ctx = ExecutionContext.update_state(sub_ctx, state); return sub_ctx; } @@ -560,41 +510,27 @@ namespace CallHelper { summary.calling_context.memory, ret_size, return_data, ret_offset ); - // Update SELFDESTROY contracts - Helpers.fill_array( - fill_len=summary.selfdestruct_contracts_len, - input_arr=summary.selfdestruct_contracts, - output_arr=summary.calling_context.selfdestruct_contracts + - summary.calling_context.selfdestruct_contracts_len, - ); - - // Return the updated calling context - return new model.ExecutionContext( + tempvar ctx = new model.ExecutionContext( + state=summary.calling_context.state, call_context=summary.calling_context.call_context, - program_counter=summary.calling_context.program_counter, - stopped=summary.calling_context.stopped, - return_data=summary.return_data, - return_data_len=summary.return_data_len, stack=stack, memory=memory, + return_data_len=summary.return_data_len, + return_data=summary.return_data, + program_counter=summary.calling_context.program_counter, + stopped=summary.calling_context.stopped, gas_used=summary.calling_context.gas_used + summary.gas_used, - gas_limit=summary.calling_context.gas_limit, - gas_price=summary.calling_context.gas_price, - starknet_contract_address=summary.calling_context.starknet_contract_address, - evm_contract_address=summary.calling_context.evm_contract_address, - origin=summary.calling_context.origin, - calling_context=summary.calling_context.calling_context, - selfdestruct_contracts_len=summary.calling_context.selfdestruct_contracts_len + - summary.selfdestruct_contracts_len, - selfdestruct_contracts=summary.calling_context.selfdestruct_contracts, - events_len=summary.calling_context.events_len, - events=summary.calling_context.events, - create_addresses_len=summary.calling_context.create_addresses_len, - create_addresses=summary.calling_context.create_addresses, - revert_contract_state=summary.calling_context.revert_contract_state, reverted=summary.calling_context.reverted, - read_only=summary.calling_context.read_only, ); + + // REVERTED, just update Stack and Memory + if (summary.reverted != FALSE) { + return ctx; + } + + let ctx = ExecutionContext.update_state(ctx, summary.state); + + return ctx; } } @@ -766,6 +702,40 @@ namespace CreateHelper { return (create2_address,); } + func get_evm_address{ + syscall_ptr: felt*, + pedersen_ptr: HashBuiltin*, + range_check_ptr, + bitwise_ptr: BitwiseBuiltin*, + }( + state: model.State*, + address: model.Address*, + popped_len: felt, + popped: Uint256*, + bytecode_len: felt, + bytecode: felt*, + ) -> (model.State*, felt) { + alloc_locals; + let (state, account) = State.get_account(state, address); + let nonce = account.nonce; + let account = Account.set_nonce(account, nonce + 1); + let state = State.set_account(state, address, account); + + // create2 context pops 4 off the stack, create pops 3 + // so we use popped_len to derive the way we should handle + // the creation of evm addresses + if (popped_len != 4) { + let (evm_contract_address) = CreateHelper.get_create_address(address.evm, nonce); + return (state, evm_contract_address); + } else { + let salt = popped[3]; + let (evm_contract_address) = CreateHelper.get_create2_address( + sender_address=address.evm, bytecode_len=bytecode_len, bytecode=bytecode, salt=salt + ); + return (state, evm_contract_address); + } + } + // @notice Deploy a new Contract account and initialize a sub context at these addresses // with bytecode from calling context memory. // @param ctx The pointer to the calling context. @@ -780,130 +750,63 @@ namespace CreateHelper { }(ctx: model.ExecutionContext*, popped_len: felt, popped: Uint256*) -> model.ExecutionContext* { alloc_locals; - // Load bytecode code from memory + // Load CallContext bytecode code from memory let value = popped[0]; let offset = popped[1]; - let size: Uint256 = popped[2]; + let size = popped[2]; let (bytecode: felt*) = alloc(); let (memory, gas_cost) = Memory.load_n( self=ctx.memory, element_len=size.low, element=bytecode, offset=offset.low ); - let ctx = ExecutionContext.update_memory(ctx, memory); + // Get new account address + let (state, evm_contract_address) = get_evm_address( + ctx.state, ctx.call_context.address, popped_len, popped, size.low, bytecode + ); + let (starknet_contract_address) = Account.compute_starknet_address(evm_contract_address); + tempvar address = new model.Address(starknet_contract_address, evm_contract_address); + + // Create Account with empty bytecode + let account = Account.fetch_or_create(address); + let is_collision = Account.has_code_or_nonce(account); + let account = Account.set_nonce(account, 1); + + if (is_collision != 0) { + let (revert_reason_len, revert_reason) = Errors.addressCollision(); + tempvar ctx = ExecutionContext.stop(ctx, revert_reason_len, revert_reason, TRUE); + return ctx; + } + + // Update calling context before creating sub context + let ctx = ExecutionContext.update_memory(ctx, memory); let ctx = ExecutionContext.increment_gas_used( - self=ctx, inc_value=gas_cost + SystemOperations.GAS_COST_CREATE + ctx, gas_cost + SystemOperations.GAS_COST_CREATE ); + let ctx = ExecutionContext.update_state(ctx, state); - // Prepare execution context - let (empty_array: felt*) = alloc(); + // Create sub context with copied state + let state = State.copy(ctx.state); + let state = State.set_account(state, address, account); + let (calldata: felt*) = alloc(); tempvar call_context: model.CallContext* = new model.CallContext( bytecode=bytecode, bytecode_len=size.low, - calldata=empty_array, + calldata=calldata, calldata_len=0, value=value.low, + gas_limit=ctx.call_context.gas_limit, + gas_price=ctx.call_context.gas_price, + origin=ctx.call_context.origin, + calling_context=ctx, + address=address, + read_only=FALSE, + is_create=TRUE, ); - let (local return_data: felt*) = alloc(); - let (empty_selfdestruct_contracts: felt*) = alloc(); - let (empty_create_addresses: felt*) = alloc(); - let (empty_events: model.Event*) = alloc(); - let stack = Stack.init(); - let memory = Memory.init(); - let empty_context = ExecutionContext.init_empty(); - - // create2 context pops 4 off the stack, create pops 3 - // so we use popped_len to derive the way we should handle - // the creation of evm addresses - if (popped_len != 4) { - let (nonce) = IContractAccount.get_nonce(ctx.starknet_contract_address); - - let (evm_contract_address) = CreateHelper.get_create_address( - ctx.evm_contract_address, nonce - ); - - let (contract_account_class_hash_) = contract_account_class_hash.read(); - IContractAccount.increment_nonce(ctx.starknet_contract_address); - let (starknet_contract_address) = Accounts.create( - contract_account_class_hash_, evm_contract_address - ); - let ctx = ExecutionContext.push_create_address(ctx, starknet_contract_address); - let (local revert_contract_state_dict_start) = default_dict_new(0); - tempvar revert_contract_state: model.RevertContractState* = new model.RevertContractState( - revert_contract_state_dict_start, revert_contract_state_dict_start); - tempvar sub_ctx = new model.ExecutionContext( - call_context=call_context, - program_counter=0, - stopped=FALSE, - return_data=return_data, - return_data_len=0, - stack=stack, - memory=memory, - gas_used=0, - gas_limit=0, - gas_price=0, - starknet_contract_address=starknet_contract_address, - evm_contract_address=evm_contract_address, - origin=ctx.origin, - calling_context=ctx, - selfdestruct_contracts_len=0, - selfdestruct_contracts=empty_selfdestruct_contracts, - events_len=0, - events=empty_events, - create_addresses_len=0, - create_addresses=empty_create_addresses, - revert_contract_state=revert_contract_state, - reverted=FALSE, - read_only=FALSE, - ); - - return sub_ctx; - } else { - let _nonce = popped[3]; - let (evm_contract_address) = CreateHelper.get_create2_address( - sender_address=ctx.evm_contract_address, - bytecode_len=size.low, - bytecode=bytecode, - salt=_nonce, - ); - - let (contract_account_class_hash_) = contract_account_class_hash.read(); - IContractAccount.increment_nonce(ctx.starknet_contract_address); - let (starknet_contract_address) = Accounts.create( - contract_account_class_hash_, evm_contract_address - ); - let ctx = ExecutionContext.push_create_address(ctx, starknet_contract_address); - let (local revert_contract_state_dict_start) = default_dict_new(0); - tempvar revert_contract_state: model.RevertContractState* = new model.RevertContractState( - revert_contract_state_dict_start, revert_contract_state_dict_start); - tempvar sub_ctx = new model.ExecutionContext( - call_context=call_context, - program_counter=0, - stopped=FALSE, - return_data=return_data, - return_data_len=0, - stack=stack, - memory=memory, - gas_used=0, - gas_limit=0, - gas_price=0, - starknet_contract_address=starknet_contract_address, - evm_contract_address=evm_contract_address, - origin=ctx.origin, - calling_context=ctx, - selfdestruct_contracts_len=0, - selfdestruct_contracts=empty_selfdestruct_contracts, - events_len=0, - events=empty_events, - create_addresses_len=0, - create_addresses=empty_create_addresses, - revert_contract_state=revert_contract_state, - reverted=FALSE, - read_only=FALSE, - ); + let sub_ctx = ExecutionContext.init(call_context); + let sub_ctx = ExecutionContext.update_state(sub_ctx, state); - return sub_ctx; - } + return sub_ctx; } // @notice At the end of a sub-context initiated with CREATE or CREATE2, the calling context's stack is updated. @@ -925,110 +828,37 @@ namespace CreateHelper { ); // Stack output: the address of the deployed contract, 0 if the deployment failed. - let (address_high, address_low) = split_felt( - summary.evm_contract_address * (1 - summary.reverted) - ); + let (address_high, address_low) = split_felt(summary.address.evm * (1 - summary.reverted)); let stack = Stack.push( summary.calling_context.stack, Uint256(low=address_low, high=address_high) ); - // Update SELFDESTROY contracts - Helpers.fill_array( - fill_len=summary.selfdestruct_contracts_len, - input_arr=summary.selfdestruct_contracts, - output_arr=summary.calling_context.selfdestruct_contracts + - summary.calling_context.selfdestruct_contracts_len, - ); - - // Return the updated calling context - tempvar calling_context = new model.ExecutionContext( + // Re-create the calling context with updated stack and return_data + tempvar ctx = new model.ExecutionContext( + state=summary.calling_context.state, call_context=summary.calling_context.call_context, - program_counter=summary.calling_context.program_counter, - stopped=summary.calling_context.stopped, - return_data=summary.return_data, - return_data_len=summary.return_data_len, stack=stack, memory=summary.calling_context.memory, - gas_used=summary.calling_context.gas_used + gas, - gas_limit=summary.calling_context.gas_limit, - gas_price=summary.calling_context.gas_price, - starknet_contract_address=summary.calling_context.starknet_contract_address, - evm_contract_address=summary.calling_context.evm_contract_address, - origin=summary.calling_context.origin, - calling_context=summary.calling_context.calling_context, - selfdestruct_contracts_len=summary.calling_context.selfdestruct_contracts_len + - summary.selfdestruct_contracts_len, - selfdestruct_contracts=summary.calling_context.selfdestruct_contracts, - events_len=summary.calling_context.events_len, - events=summary.calling_context.events, - create_addresses_len=summary.calling_context.create_addresses_len, - create_addresses=summary.calling_context.create_addresses, - revert_contract_state=summary.calling_context.revert_contract_state, + return_data_len=summary.return_data_len, + return_data=summary.return_data, + program_counter=summary.calling_context.program_counter, + stopped=summary.calling_context.stopped, + gas_used=summary.calling_context.gas_used + summary.gas_used, reverted=summary.calling_context.reverted, - read_only=summary.calling_context.read_only, ); - if (summary.reverted != 0) { - return calling_context; - } else { - IContractAccount.write_bytecode( - contract_address=summary.starknet_contract_address, - bytecode_len=summary.return_data_len, - bytecode=summary.return_data, - ); - return calling_context; + // REVERTED, just returns + if (summary.reverted != FALSE) { + return ctx; } - } -} -namespace SelfDestructHelper { - // @notice A helper for SELFDESTRUCT operation. - // It overwrites contract account bytecode with 0s - // remove contract from registry - // @param ctx The pointer to the calling context. - // @return ExecutionContext The pointer to the updated execution_context. - func finalize{ - syscall_ptr: felt*, - pedersen_ptr: HashBuiltin*, - range_check_ptr, - bitwise_ptr: BitwiseBuiltin*, - }(ctx: model.ExecutionContext*) -> model.ExecutionContext* { - alloc_locals; + // Write bytecode to Account + let (state, account) = State.get_account(summary.state, summary.address); + let account = Account.set_code(account, summary.return_data_len, summary.return_data); + let state = State.set_account(state, summary.address, account); - let empty_selfdestruct_contracts = Helpers.erase_contracts( - ctx.selfdestruct_contracts_len, ctx.selfdestruct_contracts - ); - let (empty_create_addresses: felt*) = alloc(); - let (empty_events: model.Event*) = alloc(); - let (revert_contract_state_dict_start) = default_dict_new(0); - tempvar revert_contract_state: model.RevertContractState* = new model.RevertContractState( - revert_contract_state_dict_start, revert_contract_state_dict_start - ); + let ctx = ExecutionContext.update_state(ctx, state); - return new model.ExecutionContext( - call_context=ctx.call_context, - program_counter=ctx.program_counter, - stopped=ctx.stopped, - return_data=ctx.return_data, - return_data_len=ctx.return_data_len, - stack=ctx.stack, - memory=ctx.memory, - gas_used=ctx.gas_used, - gas_limit=ctx.gas_limit, - gas_price=ctx.gas_price, - starknet_contract_address=ctx.starknet_contract_address, - evm_contract_address=ctx.evm_contract_address, - origin=ctx.origin, - calling_context=ctx.calling_context, - selfdestruct_contracts_len=0, - selfdestruct_contracts=empty_selfdestruct_contracts, - events_len=0, - events=empty_events, - create_addresses_len=0, - create_addresses=empty_create_addresses, - revert_contract_state=revert_contract_state, - reverted=FALSE, - read_only=FALSE, - ); + return ctx; } } diff --git a/src/kakarot/interfaces/interfaces.cairo b/src/kakarot/interfaces/interfaces.cairo index 68873d47e..6c226bbf7 100644 --- a/src/kakarot/interfaces/interfaces.cairo +++ b/src/kakarot/interfaces/interfaces.cairo @@ -56,6 +56,9 @@ namespace IAccount { func bytecode() -> (bytecode_len: felt, bytecode: felt*) { } + + func account_type() -> (type: felt) { + } } @contract_interface @@ -63,16 +66,19 @@ namespace IContractAccount { func write_bytecode(bytecode_len: felt, bytecode: felt*) { } - func storage(key: Uint256) -> (value: Uint256) { + func storage(storage_addr: felt) -> (value: Uint256) { } - func write_storage(key: Uint256, value: Uint256) { + func write_storage(storage_addr: felt, value: Uint256) { } func get_nonce() -> (nonce: felt) { } - func increment_nonce() { + func set_nonce(nonce: felt) { + } + + func selfdestruct() { } } diff --git a/src/kakarot/kakarot.cairo b/src/kakarot/kakarot.cairo index c6eb31063..1453fbecf 100644 --- a/src/kakarot/kakarot.cairo +++ b/src/kakarot/kakarot.cairo @@ -3,18 +3,21 @@ %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.uint256 import Uint256 -from starkware.cairo.common.alloc import alloc from starkware.starknet.common.syscalls import get_caller_address // Local dependencies +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.memory import Memory -from kakarot.accounts.library import Accounts -from kakarot.interfaces.interfaces import IAccount +from kakarot.state import State +from utils.utils import Helpers // Constructor @constructor @@ -102,7 +105,7 @@ func get_deploy_fee{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ func compute_starknet_address{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( evm_address: felt ) -> (contract_address: felt) { - return Accounts.compute_starknet_address(evm_address); + return Account.compute_starknet_address(evm_address); } // @notice Returns the registered starknet address for a given EVM address. @@ -113,7 +116,7 @@ func compute_starknet_address{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, ra func get_starknet_address{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( evm_address: felt ) -> (starknet_address: felt) { - return Accounts.get_starknet_address(evm_address); + return Account.get_registered_starknet_address(evm_address); } // @notice Deploy a new externally owned account. @@ -151,8 +154,13 @@ func eth_call{ data_len: felt, data: felt*, ) -> (return_data_len: felt, return_data: felt*, success: felt) { - Kakarot.assert_view(); - return Kakarot.eth_call(origin, to, gas_limit, gas_price, value, data_len, data); + let summary = Kakarot.eth_call(origin, to, gas_limit, gas_price, value, data_len, data); + let result = (summary.return_data_len, summary.return_data, 1 - summary.reverted); + if (to == 0) { + return (2, cast(summary.address, felt*), 1 - summary.reverted); + } else { + return result; + } } // @notice The eth_send_transaction function as described in the spec, @@ -176,5 +184,19 @@ func eth_send_transaction{ alloc_locals; let (local starknet_caller_address) = get_caller_address(); let (local origin) = Kakarot.safe_get_evm_address(starknet_caller_address); - return Kakarot.eth_call(origin, to, gas_limit, gas_price, value, data_len, data); + let summary = Kakarot.eth_call(origin, to, gas_limit, gas_price, value, data_len, data); + let result = (summary.return_data_len, summary.return_data, 1 - summary.reverted); + + if (summary.reverted != FALSE) { + return result; + } + + State.commit(summary.state); + + if (to == 0) { + // Overwrite return_data with deployed addresses + return (2, cast(summary.address, felt*), 1 - summary.reverted); + } + + return result; } diff --git a/src/kakarot/library.cairo b/src/kakarot/library.cairo index cb4c0aaf7..5aae82306 100644 --- a/src/kakarot/library.cairo +++ b/src/kakarot/library.cairo @@ -9,8 +9,9 @@ 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 kakarot.accounts.library import Accounts +from kakarot.account import Account from kakarot.constants import ( account_proxy_class_hash, blockhash_registry_address, @@ -19,13 +20,16 @@ from kakarot.constants import ( externally_owned_account_class_hash, native_token_address, ) +from kakarot.constants import Constants +from kakarot.errors import Errors from kakarot.evm import EVM from kakarot.execution_context import ExecutionContext from kakarot.instructions.system_operations import CreateHelper -from kakarot.interfaces.interfaces import IAccount, IContractAccount, IERC20 +from kakarot.interfaces.interfaces import IAccount, IERC20 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 // @title Kakarot main library file. @@ -56,13 +60,8 @@ namespace Kakarot { } // @notice Run the given bytecode with the given calldata and parameters - // @dev starknet_contract_address and evm_contract_address can be set to 0 if - // there is no notion of deployed contract in the bytecode. Otherwise, - // they should match (ie. that compute_starknet_address(IAccount.get_evm_address(starknet_contract_address)) - // should equal starknet_contract_address. In a future version, either one or the - // other will be removed - // @param starknet_contract_address The starknet contract address of the called contract - // @param evm_contract_address The corresponding EVM contract address of the called contract + // @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 @@ -71,16 +70,15 @@ namespace Kakarot { // @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 - // @param reverted Whether the transaction is reverted or not func execute{ syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr, bitwise_ptr: BitwiseBuiltin*, }( - starknet_contract_address: felt, - evm_contract_address: felt, - origin: felt, + address: model.Address*, + is_deploy_tx: felt, + origin: model.Address*, bytecode_len: felt, bytecode: felt*, calldata_len: felt, @@ -88,70 +86,127 @@ namespace Kakarot { value: felt, gas_limit: felt, gas_price: felt, - ) -> ( - stack_accesses_len: felt, - stack_accesses: felt*, - stack_len: felt, - memory_accesses_len: felt, - memory_accesses: felt*, - memory_bytes_len: felt, - starknet_contract_address: felt, - evm_contract_address: felt, - return_data_len: felt, - return_data: felt*, - gas_used: felt, - reverted: felt, - ) { + ) -> EVM.Summary* { alloc_locals; - // Prepare execution context - let root_context = ExecutionContext.init_empty(); - let return_data: felt* = alloc(); + // 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, - ); - - let ctx = ExecutionContext.init( - call_context=call_context, - starknet_contract_address=starknet_contract_address, - evm_contract_address=evm_contract_address, - origin=origin, gas_limit=gas_limit, gas_price=gas_price, + origin=origin, calling_context=root_context, - return_data_len=0, - return_data=return_data, + address=address, read_only=FALSE, + is_create=is_deploy_tx, ); - // Compute intrinsic gas cost and update gas used - let cost = ExecutionContext.compute_intrinsic_gas_cost(ctx); - let ctx = ExecutionContext.increment_gas_used(self=ctx, inc_value=cost); + 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 is_registered = Account.is_registered(address.evm); + let is_collision = is_registered * is_deploy_tx; + + // Nonce is set to 1 in case of deploy_tx + let account = Account.fetch_or_create(address); + 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; + } - let memory_accesses_len = summary.memory.squashed_end - summary.memory.squashed_start; - let stack_accesses_len = summary.stack.squashed_end - summary.stack.squashed_start; - - return ( - stack_accesses_len=stack_accesses_len, - stack_accesses=summary.stack.squashed_start, - stack_len=summary.stack.len_16bytes, - memory_accesses_len=memory_accesses_len, - memory_accesses=summary.memory.squashed_start, - memory_bytes_len=summary.memory.bytes_len, - starknet_contract_address=summary.starknet_contract_address, - evm_contract_address=summary.evm_contract_address, - return_data_len=summary.return_data_len, - return_data=summary.return_data, - gas_used=summary.gas_used, - reverted=summary.reverted, + // @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. + // @param gas_limit Integer of the gas provided for the transaction execution + // @param gas_price Integer of the gas price used for each paid gas + // @param value Integer of the value sent with this transaction + // @param data_len The length of the data + // @param data Hash of the method signature and encoded parameters. For details see Ethereum Contract ABI in the Solidity documentation + // @return return_data_len The length of the returned bytes + // @return return_data The returned bytes array + // @return success A boolean TRUE if the transaction succeeded, FALSE if it's reverted + func eth_call{ + syscall_ptr: felt*, + pedersen_ptr: HashBuiltin*, + range_check_ptr, + bitwise_ptr: BitwiseBuiltin*, + }( + origin: felt, + to: felt, + gas_limit: felt, + gas_price: felt, + value: felt, + data_len: felt, + data: felt*, + ) -> EVM.Summary* { + alloc_locals; + let evm_contract_address = resolve_to(to, origin); + let (starknet_contract_address) = Account.compute_starknet_address(evm_contract_address); + tempvar address = new model.Address(starknet_contract_address, evm_contract_address); + let (starknet_origin_address) = Account.compute_starknet_address(origin); + tempvar origin_address = new model.Address(starknet_origin_address, origin); + + let is_regular_tx = is_not_zero(to); + let is_deploy_tx = 1 - is_regular_tx; + let account = Account.fetch_or_create(address); + + let summary = execute( + address, + is_deploy_tx, + origin_address, + account.code_len, + account.code, + data_len, + data, + value, + gas_limit, + gas_price, ); + return summary; } // @notice The Blockhash registry is used by the BLOCKHASH opcode @@ -216,92 +271,6 @@ namespace Kakarot { return (deploy_fee_,); } - // @notice Transfer "value" native tokens from "origin" to "to" - // @param origin The sender address. - // @param to_ The address the transaction is directed to. - // @param value Integer of the value sent with this transaction - // @return success Boolean to indicate success or failure of transfer - func transfer{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - origin: felt, to_: felt, value: felt - ) -> (success: felt) { - alloc_locals; - if (value == 0) { - return (success=TRUE); - } - let (local native_token_address) = get_native_token(); - let (sender) = Accounts.compute_starknet_address(origin); - let (recipient) = Accounts.compute_starknet_address(to_); - let amount = Helpers.to_uint256(value); - let (success) = IERC20.transferFrom( - contract_address=native_token_address, sender=sender, recipient=recipient, amount=amount - ); - return (success=success); - } - - // @notice Deploy contract account. - // @dev First deploy a contract_account with no bytecode, then run the calldata as bytecode with the new address, - // then set the bytecode with the result of the initial run. - // @param origin The origin for the transaction - // @param evm_contract_address The evm address of the contract to be deployed - // @param value The value to be transferred as part of this deploy call - // @param bytecode_len The deploy bytecode length. - // @param bytecode The deploy bytecode. - // @return starknet_contract_address The newly deployed starknet contract address. - func deploy_contract_account{ - syscall_ptr: felt*, - pedersen_ptr: HashBuiltin*, - range_check_ptr, - bitwise_ptr: BitwiseBuiltin*, - }( - origin: felt, evm_contract_address: felt, value: felt, bytecode_len: felt, bytecode: felt* - ) -> (starknet_contract_address: felt, reverted: felt) { - alloc_locals; - - let (class_hash) = contract_account_class_hash.read(); - let (starknet_contract_address) = Accounts.create(class_hash, evm_contract_address); - let (empty_array: felt*) = alloc(); - - let ( - stack_accesses_len, - stack_accesses, - stack_len, - memory_accesses_len, - memory_accesses, - memory_bytes_len, - starknet_contract_address, - evm_contract_address, - return_data_len, - return_data, - gas_used, - reverted, - ) = execute( - starknet_contract_address=starknet_contract_address, - evm_contract_address=evm_contract_address, - origin=origin, - bytecode_len=bytecode_len, - bytecode=bytecode, - calldata_len=0, - calldata=empty_array, - value=value, - gas_limit=0, - gas_price=0, - ); - - if (reverted == 0) { - // Update contract bytecode with execution result - IContractAccount.write_bytecode( - contract_address=starknet_contract_address, - bytecode_len=return_data_len, - bytecode=return_data, - ); - return (starknet_contract_address=starknet_contract_address, reverted=reverted); - } else { - // Just do nothing, the deployed account at starknet_contract_address is empty - // and will not be targeted again because the address depends on the caller nonce - return (starknet_contract_address=starknet_contract_address, reverted=reverted); - } - } - // @notice Deploy a new externally owned account. // @param evm_contract_address The evm address that is mapped to the newly deployed starknet contract address. // @return starknet_contract_address The newly deployed starknet contract address. @@ -314,7 +283,7 @@ namespace Kakarot { alloc_locals; let (class_hash) = externally_owned_account_class_hash.read(); - let (starknet_contract_address) = Accounts.create(class_hash, evm_contract_address); + let (starknet_contract_address) = Account.deploy(class_hash, evm_contract_address); let (local native_token_address) = get_native_token(); let (local deploy_fee) = get_deploy_fee(); @@ -331,78 +300,26 @@ namespace Kakarot { return (starknet_contract_address=starknet_contract_address); } - // @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. - // @param gas_limit Integer of the gas provided for the transaction execution - // @param gas_price Integer of the gas price used for each paid gas - // @param value Integer of the value sent with this transaction - // @param data_len The length of the data - // @param data Hash of the method signature and encoded parameters. For details see Ethereum Contract ABI in the Solidity documentation - // @return return_data_len The length of the returned bytes - // @return return_data The returned bytes array - // @return success A boolean TRUE if the transaction succeeded, FALSE if it's reverted - func eth_call{ + // @notice Get the ExecutionContext address from the transaction + // @dev When to=0, it's a deploy tx so we first compute the target address + // @param to The transaction to parameter + // @param origin The transaction origin parameter + // @return the target evm address + func resolve_to{ syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr, bitwise_ptr: BitwiseBuiltin*, - }( - origin: felt, - to: felt, - gas_limit: felt, - gas_price: felt, - value: felt, - data_len: felt, - data: felt*, - ) -> (return_data_len: felt, return_data: felt*, success: felt) { + }(to: felt, origin: felt) -> felt { alloc_locals; - - if (to == 0) { - // TODO: read the nonce from the provided origin address, otherwise in view mode this will - // TODO: always use a 0 nonce - let (tx_info) = get_tx_info(); - let (evm_contract_address) = CreateHelper.get_create_address(origin, tx_info.nonce); - - let (success) = transfer(origin, evm_contract_address, value); - with_attr error_message( - "Kakarot: eth_call: failed to transfer {value} tokens to {evm_contract_address}") { - assert success = TRUE; - } - - let (starknet_contract_address, reverted) = deploy_contract_account( - origin=origin, - evm_contract_address=evm_contract_address, - value=value, - bytecode_len=data_len, - bytecode=data, - ); - let (return_data) = alloc(); - assert [return_data] = evm_contract_address; - assert [return_data + 1] = starknet_contract_address; - return (2, return_data, 1 - reverted); - } else { - let (success) = transfer(origin, to, value); - with_attr error_message( - "Kakarot: eth_call: failed to transfer {value} tokens to {to}") { - assert success = TRUE; - } - let (local starknet_contract_address) = Accounts.compute_starknet_address(to); - let (bytecode_len, bytecode) = Accounts.get_bytecode(to); - let summary = execute( - starknet_contract_address, - to, - origin, - bytecode_len, - bytecode, - data_len, - data, - value, - gas_limit, - gas_price, - ); - return (summary.return_data_len, summary.return_data, 1 - summary.reverted); + if (to != 0) { + return to; } + // TODO: read the nonce from the provided origin address, otherwise in view mode this will + // TODO: always use a 0 nonce + let (tx_info) = get_tx_info(); + let (local evm_contract_address) = CreateHelper.get_create_address(origin, tx_info.nonce); + return evm_contract_address; } // @notice returns the EVM address associated to a Starknet account deployed by kakarot. @@ -418,7 +335,7 @@ namespace Kakarot { }(starknet_address: felt) -> (evm_address: felt) { alloc_locals; let (local evm_address) = IAccount.get_evm_address(starknet_address); - let (local computed_starknet_address) = Accounts.compute_starknet_address(evm_address); + let (local computed_starknet_address) = Account.compute_starknet_address(evm_address); with_attr error_message("Kakarot: caller contract is not a Kakarot Account") { assert computed_starknet_address = starknet_address; @@ -426,21 +343,4 @@ namespace Kakarot { return (evm_address=evm_address); } - - // @notice Since it's possible in starknet to send a transcation to a @view entrypoint, this - // ensures that there is no ongoing transaction (so it's really a view call). - // @dev Raise if tx_info.account_contract_address is not 0 - func assert_view{ - syscall_ptr: felt*, - pedersen_ptr: HashBuiltin*, - range_check_ptr, - bitwise_ptr: BitwiseBuiltin*, - }() { - let (tx_info) = get_tx_info(); - with_attr error_message("Kakarot: entrypoint should only be called in view mode") { - assert tx_info.account_contract_address = 0; - } - - return (); - } } diff --git a/src/kakarot/model.cairo b/src/kakarot/model.cairo index e9b753e8f..9fad874d2 100644 --- a/src/kakarot/model.cairo +++ b/src/kakarot/model.cairo @@ -32,47 +32,91 @@ namespace model { bytes_len: felt, } + // @dev In Cairo Zero, dict are list of DictAccess, ie that they can contain only felts. For having + // dict of structs, we store in the dict pointers to the struct. List of structs are just list of + // 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 + struct State { + accounts_start: DictAccess*, + accounts: DictAccess*, + events_len: felt, + events: Event*, + balances_start: DictAccess*, + balances: DictAccess*, + transfers_len: felt, + transfers: Transfer*, + } + + // @notice The struct representing an EVM account. + // @dev We don't put the balance here to avoid loading the whole Account just for sending ETH + // @dev The address here is consequently an EVM address + struct Account { + address: felt, + code_len: felt, + code: felt*, + storage_start: DictAccess*, + storage: DictAccess*, + nonce: felt, + selfdestruct: felt, + } + + // @notice The struct representing an EVM event. + // @dev The topics are indeed a first felt for the emitting EVM account, followed by a list of Uint256 + struct Event { + topics_len: felt, + topics: felt*, + data_len: felt, + data: felt*, + } + + // @dev A struct to save Starknet native ETH transfers to be made when finalizing a tx + struct Transfer { + sender: Address*, + recipient: Address*, + amount: Uint256, + } + + // @dev Though one of the two address is enough, we store both to save on steps and simplify the usage. + struct Address { + starknet: felt, + evm: felt, + } + // @notice info: https://www.evm.codes/about#calldata // @notice Struct storing data related to a call. + // @dev All CallContext fields are constant during a given call. // @param bytecode The executed bytecode. // @param bytecode_len The length of bytecode. // @param calldata byte The space where the data parameter of a transaction or call is held. // @param calldata_len The length of calldata. // @param value The amount of native token to transfer. + // @param gas_limit The gas limit for the call. + // @param gas_price The gas price for the call. + // @param origin The origin of the transaction. + // @param calling_context The parent context of the current execution context, can be empty when context + // is root context | see ExecutionContext.is_empty(ctx). + // @param address The address of the current EVM account. Note that the bytecode may not be the one + // of the account in case of a CALLCODE or DELEGATECALL + // @param read_only if set to true, context cannot do any state modifying instructions or send ETH in the sub context. + // @param is_create if set to true, the call context is a CREATEs or deploy execution struct CallContext { bytecode: felt*, bytecode_len: felt, calldata: felt*, calldata_len: felt, value: felt, - } - - // @notice A dictionary that keeps track of the prior-to-first-write-of-operating-execution-context value of a contract storage key so it can be reverted to if the writing execution context reverts. - // @param dict_start pointer to a DictAccess used to store the revert contract states's value at a contract storage key. - // @param dict_start The pointer to the end of the DictAccess array. - // @param dict_end The pointer to the end of the DictAccess array. - struct RevertContractState { - dict_start: DictAccess*, - dict_end: DictAccess*, - } - - // @notice The prior-to-first-write-of-operating-execution-context value of a contract storage key in `RevertContractState` - // @param key The key of memory of contract storage (see `MemoryOperations.exec_sstore`). - // @param value The value of memory of contract storage (see `MemoryOperations.exec_sstore`). - struct KeyValue { - key: Uint256, - value: Uint256, - } - - // TODO: possible to just import `EmitEvent` struct from `starkware.starknet.common.syscalls` - // @notice info: https://www.evm.codes/about#calldata - // @notice Struct storing data related to an event emitting, as in when calling `emit_event` - // @notice conveying the data as a struct is necessary because we want to delay the actual emitting until an execution context is completed and not reverted - struct Event { - keys_len: felt, - keys: Uint256*, - data_len: felt, - data: felt*, + gas_limit: felt, + gas_price: felt, + origin: Address*, + calling_context: ExecutionContext*, + address: Address*, + read_only: felt, + is_create: felt, } // @dev Stores all data relevant to the current execution context. @@ -84,43 +128,17 @@ namespace model { // @param stack The current execution context stack. // @param memory The current execution context memory. // @param gas_used The gas consumed by the current state of the execution. - // @param gas_limit The maximum amount of gas for the execution. - // @param gas_price The amount to pay per unit of gas. - // @param starknet_contract_address The starknet address of the contract interacted with. - // @param evm_contract_address The evm address of the contract interacted with. - // @param calling_context The parent context of the current execution context, can be empty when context - // is root context | see ExecutionContext.is_empty(ctx). - // @param selfdestruct_contracts_len The destroy_contract length. - // @param selfdestruct_contracts The array of contracts to destroy at the end of the transaction. - // @param events_len The events length. - // @param events The events to be emitted upon a non-reverted stopped execution context. - // @param create_addresses_len The create_addresses length. - // @param create_addresses The addresses of contracts initialized by the create(2) opcodes that are deleted if the creating context is reverted. - // @param revert_contract_state A dictionary that keeps track of the prior-to-first-write value of a contract storage key so it can be reverted to if the writing execution context reverts. - // @param read_only if set to true, context cannot do any state modifying instructions or send ETH in the sub context. + // @param state The current journal of state updates. struct ExecutionContext { + state: State*, call_context: CallContext*, - program_counter: felt, - stopped: felt, - return_data: felt*, - return_data_len: felt, stack: Stack*, memory: Memory*, + return_data_len: felt, + return_data: felt*, + program_counter: felt, + stopped: felt, gas_used: felt, - gas_limit: felt, - gas_price: felt, - starknet_contract_address: felt, - evm_contract_address: felt, - origin: felt, - calling_context: ExecutionContext*, - selfdestruct_contracts_len: felt, - selfdestruct_contracts: felt*, - events_len: felt, - events: Event*, - create_addresses_len: felt, - create_addresses: felt*, - revert_contract_state: RevertContractState*, reverted: felt, - read_only: felt, } } diff --git a/src/kakarot/precompiles/precompiles.cairo b/src/kakarot/precompiles/precompiles.cairo index 31a9f6e21..f8be9c433 100644 --- a/src/kakarot/precompiles/precompiles.cairo +++ b/src/kakarot/precompiles/precompiles.cairo @@ -28,7 +28,7 @@ from kakarot.stack import Stack namespace Precompiles { // @notice Executes a precompile at a given precompile address // @dev Associates gas used and precompile return values to a execution subcontext - // @param address The precompile address to be executed + // @param evm_address The precompile evm_address to be executed // @param calldata_len The calldata length // @param calldata The calldata. // @param value The value. @@ -40,7 +40,7 @@ namespace Precompiles { range_check_ptr, bitwise_ptr: BitwiseBuiltin*, }( - address: felt, + evm_address: felt, calldata_len: felt, calldata: felt*, value: felt, @@ -48,42 +48,31 @@ namespace Precompiles { ) -> model.ExecutionContext* { alloc_locals; - // Execute the precompile at a given address - let (output_len, output, gas_used) = _exec_precompile(address, calldata_len, calldata); - - let (local revert_contract_state_dict_start) = default_dict_new(0); - tempvar revert_contract_state: model.RevertContractState* = new model.RevertContractState( - revert_contract_state_dict_start, revert_contract_state_dict_start - ); + // Execute the precompile at a given evm_address + let (output_len, output, gas_used) = _exec_precompile(evm_address, calldata_len, calldata); // Build returned execution context let stack = Stack.init(); let memory = Memory.init(); - local sub_ctx: model.ExecutionContext* = new model.ExecutionContext( - call_context=cast(0, model.CallContext*), - program_counter=0, - stopped=TRUE, - return_data=output, - return_data_len=output_len, - stack=stack, - memory=memory, - gas_used=gas_used, - gas_limit=0, + tempvar address = new model.Address(0, evm_address); + tempvar call_context = new model.CallContext( + bytecode=cast(0, felt*), + bytecode_len=0, + calldata=cast(0, felt*), + calldata_len=0, + value=0, + gas_limit=Constants.TRANSACTION_GAS_LIMIT, gas_price=0, - starknet_contract_address=0, - evm_contract_address=address, - origin=calling_context.origin, + origin=calling_context.call_context.origin, calling_context=calling_context, - selfdestruct_contracts_len=0, - selfdestruct_contracts=cast(0, felt*), - events_len=0, - events=cast(0, model.Event*), - create_addresses_len=0, - create_addresses=cast(0, felt*), - revert_contract_state=revert_contract_state, - reverted=FALSE, + address=address, read_only=FALSE, + is_create=FALSE, ); + let sub_ctx = ExecutionContext.init(call_context); + let sub_ctx = ExecutionContext.update_state(sub_ctx, calling_context.state); + let sub_ctx = ExecutionContext.stop(sub_ctx, output_len, output, FALSE); + let sub_ctx = ExecutionContext.increment_gas_used(sub_ctx, gas_used); return sub_ctx; } @@ -92,9 +81,9 @@ namespace Precompiles { return is_not_zero(address) * is_le(address, Constants.LAST_PRECOMPILE_ADDRESS); } - // @notice Executes associated function of precompiled address. + // @notice Executes associated function of precompiled evm_address. // @dev This function uses an internal jump table to execute the corresponding precompile impmentation. - // @param address The precompile address. + // @param evm_address The precompile evm_address. // @param input_len The length of the input array. // @param input The input array. // @return output_len The output length. @@ -105,23 +94,23 @@ namespace Precompiles { pedersen_ptr: HashBuiltin*, range_check_ptr, bitwise_ptr: BitwiseBuiltin*, - }(address: felt, input_len: felt, input: felt*) -> ( + }(evm_address: felt, input_len: felt, input: felt*) -> ( output_len: felt, output: felt*, gas_used: felt ) { // Compute the corresponding offset in the jump table: - // count 1 for "next line" and 3 steps per precompile address: call, precompile, ret - tempvar offset = 1 + 3 * address; + // count 1 for "next line" and 3 steps per precompile evm_address: call, precompile, ret + tempvar offset = 1 + 3 * evm_address; // Prepare arguments [ap] = syscall_ptr, ap++; [ap] = pedersen_ptr, ap++; [ap] = range_check_ptr, ap++; [ap] = bitwise_ptr, ap++; - [ap] = address, ap++; + [ap] = evm_address, ap++; [ap] = input_len, ap++; [ap] = input, ap++; - // call precompile address + // call precompile evm_address jmp rel offset; call unknown_precompile; // 0x0 ret; @@ -147,7 +136,7 @@ namespace Precompiles { // @notice A placeholder for precompile that are not implemented yet. // @dev Halts execution. - // @param address The address. + // @param evm_address The evm_address. // @param _input_len The length of the input array. // @param _input The input array. func unknown_precompile{ @@ -155,8 +144,8 @@ namespace Precompiles { pedersen_ptr: HashBuiltin*, range_check_ptr, bitwise_ptr: BitwiseBuiltin*, - }(address: felt, _input_len: felt, _input: felt*) { - with_attr error_message("Kakarot: UnknownPrecompile {address}") { + }(evm_address: felt, _input_len: felt, _input: felt*) { + with_attr error_message("Kakarot: UnknownPrecompile {evm_address}") { assert 0 = 1; } return (); @@ -164,7 +153,7 @@ namespace Precompiles { // @notice A placeholder for precompile that are not implemented yet. // @dev Halts execution. - // @param address The address. + // @param evm_address The evm_address. // @param _input_len The length of the input array. // @param _input The input array. func not_implemented_precompile{ @@ -172,8 +161,8 @@ namespace Precompiles { pedersen_ptr: HashBuiltin*, range_check_ptr, bitwise_ptr: BitwiseBuiltin*, - }(address: felt, _input_len: felt, _input: felt*) { - with_attr error_message("Kakarot: NotImplementedPrecompile {address}") { + }(evm_address: felt, _input_len: felt, _input: felt*) { + with_attr error_message("Kakarot: NotImplementedPrecompile {evm_address}") { assert 0 = 1; } return (); @@ -181,7 +170,7 @@ namespace Precompiles { // @notice A placeholder for precompile that are not implemented yet. // @dev Halts execution. - // @param address The address. + // @param evm_address The evm_address. // @param _input_len The length of the input array. // @param _input The input array. func not_whitelisted_precompile{ @@ -189,8 +178,8 @@ namespace Precompiles { pedersen_ptr: HashBuiltin*, range_check_ptr, bitwise_ptr: BitwiseBuiltin*, - }(address: felt, _input_len: felt, _input: felt*) { - with_attr error_message("Kakarot: NotWhitelistedPrecompile {address}") { + }(evm_address: felt, _input_len: felt, _input: felt*) { + with_attr error_message("Kakarot: NotWhitelistedPrecompile {evm_address}") { assert 0 = 1; } return (); diff --git a/src/kakarot/state.cairo b/src/kakarot/state.cairo new file mode 100644 index 000000000..d0aa91cb8 --- /dev/null +++ b/src/kakarot/state.cairo @@ -0,0 +1,454 @@ +// SPDX-License-Identifier: MIT + +%lang starknet + +from starkware.cairo.common.alloc import alloc +from starkware.cairo.common.cairo_builtins import HashBuiltin, BitwiseBuiltin +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.constants 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 + +namespace State { + // @dev Like an State, but frozen after squashing all dicts + struct Summary { + accounts_start: DictAccess*, + accounts: DictAccess*, + events_len: felt, + events: model.Event*, + balances_start: DictAccess*, + balances: DictAccess*, + transfers_len: felt, + transfers: model.Transfer*, + } + + // @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( + accounts_start=accounts_start, + accounts=accounts_start, + events_len=0, + events=events, + balances_start=balances_start, + balances=balances_start, + transfers_len=0, + transfers=transfers, + ); + } + + // @dev Deep copy of the state, creating new memory segments + // @param self The pointer to the State + func copy{range_check_ptr}(self: model.State*) -> model.State* { + alloc_locals; + // accounts are a new memory segment + let (accounts_start, accounts) = default_dict_copy(self.accounts_start, self.accounts); + // 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); + + let (local transfers: felt*) = alloc(); + memcpy(dst=transfers, src=self.transfers, len=self.transfers_len * model.Transfer.SIZE); + + return new model.State( + accounts_start=accounts_start, + 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*), + ); + } + + // @dev Squash dicts used internally + // @param self The pointer to the State + func finalize{range_check_ptr}(self: model.State*) -> Summary* { + alloc_locals; + // First squash to get only one account per key + let (local accounts_start, accounts) = default_dict_finalize( + self.accounts_start, self.accounts, 0 + ); + // Finalizing the accounts create another entry per account + Internals._finalize_accounts{accounts=accounts}(accounts_start, accounts); + // 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, + ); + } + + // @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. + // @param self The pointer to the State. + // @param key The pointer to the address + // @return The updated state + // @return The account + func get_account{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( + self: model.State*, address: model.Address* + ) -> (model.State*, model.Account*) { + alloc_locals; + let accounts = self.accounts; + let (pointer) = dict_read{dict_ptr=accounts}(key=address.starknet); + + // Return from local storage if found + if (pointer != 0) { + let account = cast(pointer, model.Account*); + tempvar state = new model.State( + accounts_start=self.accounts_start, + 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, + ); + return (state, account); + } else { + // 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)); + tempvar state = new model.State( + accounts_start=self.accounts_start, + 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, + ); + return (state, account); + } + } + + // @notice Set the Account at the given address + // @param self The pointer to the State. + // @param address The address of the Account + // @param account The new account + func set_account( + 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)); + return new model.State( + accounts_start=self.accounts_start, + 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, + ); + } + + // @notice Read a given storage + // @dev Try to retrieve in the local Dict first, if not already here + // read the contract storage and cache the result. + // @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) { + alloc_locals; + let (self, account) = get_account(self, address); + let (account, value) = Account.read_storage(account, address, key); + let self = set_account(self, address, account); + return (self, value); + } + + // @notice Update a storage key with the given value + // @param self The pointer to the State. + // @param address The pointer to the Account address + // @param key The pointer to the Uint256 storage key + // @param value The pointer to the Uint256 value + func write_storage{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( + self: model.State*, address: model.Address*, key: Uint256, value: Uint256* + ) -> model.State* { + alloc_locals; + let (self, account) = get_account(self, address); + let account = Account.write_storage(account, key, value); + let self = set_account(self, address, account); + return self; + } + + // @notice Add an event to the Event* array + // @param self The pointer to the State + // @param event The pointer to the Event + // @return The updated State + func add_event(self: model.State*, event: model.Event) -> model.State* { + assert self.events[self.events_len] = event; + + return new model.State( + accounts_start=self.accounts_start, + 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, + ); + } + + // @notice Add a transfer to the Transfer* array + // @param self The pointer to the 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) { + 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(); + 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); + + 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 (local recipient_balance_new, carry) = uint256_add( + recipient_balance_prev, 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) + ); + assert self.transfers[self.transfers_len] = transfer; + + tempvar state = 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 + 1, + transfers=self.transfers, + ); + 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, + bitwise_ptr: BitwiseBuiltin*, + }(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); + 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]); + } 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, + ); + return (self, balance); + } + } +} + +namespace Internals { + // @notice Iterate through the accounts dict and copy them + // @param accounts_start The dict start pointer + // @param accounts_end The dict end pointer + func _copy_accounts{range_check_ptr, accounts: DictAccess*}( + accounts_start: DictAccess*, accounts_end: DictAccess* + ) { + if (accounts_start == accounts_end) { + return (); + } + + let account = cast(accounts_start.new_value, model.Account*); + let account_summary = Account.copy(account); + dict_write{dict_ptr=accounts}( + key=accounts_start.key, new_value=cast(account_summary, felt) + ); + + return _copy_accounts(accounts_start + DictAccess.SIZE, accounts_end); + } + + // @notice Iterate through the accounts dict and finalize them + // @param accounts_start The dict start pointer + // @param accounts_end The dict end pointer + func _finalize_accounts{range_check_ptr, accounts: DictAccess*}( + accounts_start: DictAccess*, accounts_end: DictAccess* + ) { + if (accounts_start == accounts_end) { + return (); + } + + let account = cast(accounts_start.new_value, model.Account*); + let account_summary = Account.finalize(account); + dict_write{dict_ptr=accounts}( + key=accounts_start.key, new_value=cast(account_summary, felt) + ); + + 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. + // Kakarot is not authorized for accounts that are created and SELDESTRUCT in the same transaction + // @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/utils/dict.cairo b/src/utils/dict.cairo new file mode 100644 index 000000000..2b7fde255 --- /dev/null +++ b/src/utils/dict.cairo @@ -0,0 +1,101 @@ +%lang starknet + +from starkware.cairo.common.dict_access import DictAccess +from starkware.cairo.common.default_dict import default_dict_new +from starkware.cairo.common.dict import dict_write, dict_squash +from starkware.cairo.common.math_cmp import is_not_zero +from starkware.cairo.common.alloc import alloc +from starkware.cairo.common.math import unsigned_div_rem + +// @dev Copy a default_dict +// @param start The pointer to the beginning of the dict +// @param self The pointer to the end of the dict +func default_dict_copy{range_check_ptr}(start: DictAccess*, end: DictAccess*) -> ( + DictAccess*, DictAccess* +) { + alloc_locals; + let (squashed_start, squashed_end) = dict_squash(start, end); + let dict_len = squashed_end - squashed_start; + + if (dict_len == 0) { + tempvar default_value = 0; + } else { + tempvar default_value = squashed_start.prev_value; + } + + let (local new_start) = default_dict_new(default_value); + let new_ptr = new_start; + + if (dict_len == 0) { + return (new_start, new_ptr); + } + + tempvar range_check_ptr = range_check_ptr; + tempvar squashed_start = squashed_start; + tempvar dict_len = dict_len; + tempvar new_ptr = new_ptr; + + loop: + let range_check_ptr = [ap - 4]; + let squashed_start = cast([ap - 3], DictAccess*); + let dict_len = [ap - 2]; + let new_ptr = cast([ap - 1], DictAccess*); + + let key = [squashed_start].key; + let new_value = [squashed_start].new_value; + + dict_write{dict_ptr=new_ptr}(key=key, new_value=new_value); + + tempvar range_check_ptr = range_check_ptr; + tempvar squashed_start = squashed_start + DictAccess.SIZE; + tempvar dict_len = dict_len - DictAccess.SIZE; + tempvar new_ptr = new_ptr; + + static_assert range_check_ptr == [ap - 4]; + static_assert squashed_start == [ap - 3]; + static_assert dict_len == [ap - 2]; + static_assert new_ptr == [ap - 1]; + + jmp loop if dict_len != 0; + + return (new_start, new_ptr); +} + +func dict_keys{range_check_ptr}(dict_start: DictAccess*, dict_end: DictAccess*) -> ( + keys_len: felt, keys: felt* +) { + alloc_locals; + let (local keys_start: felt*) = alloc(); + let dict_len = dict_end - dict_start; + let (local keys_len, _) = unsigned_div_rem(dict_len, DictAccess.SIZE); + + if (dict_len == 0) { + return (keys_len, keys_start); + } + + tempvar range_check_ptr = range_check_ptr; + tempvar keys = keys_start; + tempvar len = keys_len; + tempvar dict = dict_start; + + loop: + let range_check_ptr = [ap - 4]; + let keys = cast([ap - 3], felt*); + let len = [ap - 2]; + let dict = cast([ap - 1], DictAccess*); + + assert [keys] = dict.key; + tempvar range_check_ptr = range_check_ptr; + tempvar keys = keys + 1; + tempvar len = len - 1; + tempvar dict = dict + DictAccess.SIZE; + + static_assert range_check_ptr == [ap - 4]; + static_assert keys == [ap - 3]; + static_assert len == [ap - 2]; + static_assert dict == [ap - 1]; + + jmp loop if len != 0; + + return (keys_len, keys_start); +} diff --git a/src/utils/utils.cairo b/src/utils/utils.cairo index a0b3f81b6..37281a5f4 100644 --- a/src/utils/utils.cairo +++ b/src/utils/utils.cairo @@ -26,9 +26,7 @@ from starkware.cairo.common.cairo_secp.bigint import BigInt3, bigint_to_uint256, from starkware.cairo.common.bool import FALSE from starkware.cairo.common.cairo_builtins import HashBuiltin, BitwiseBuiltin from starkware.starknet.common.syscalls import get_caller_address - -// Internal dependencies -from kakarot.interfaces.interfaces import IAccount, IContractAccount +from starkware.cairo.common.hash_state import hash_finalize, hash_init, hash_update // @title Helper Functions // @notice This file contains a selection of helper function that simplify tasks such as type conversion and bit manipulation @@ -204,29 +202,6 @@ namespace Helpers { return res; } - func erase_contracts{ - syscall_ptr: felt*, - pedersen_ptr: HashBuiltin*, - range_check_ptr, - bitwise_ptr: BitwiseBuiltin*, - }(contracts_len: felt, contracts_arr: felt*) -> felt* { - alloc_locals; - - if (contracts_len == 0) { - return (contracts_arr); - } - - let starknet_contract_address = [contracts_arr]; - let (bytecode_len) = IAccount.bytecode_len(contract_address=starknet_contract_address); - let (erase_data) = alloc(); - fill(bytecode_len, erase_data, 0); - IContractAccount.write_bytecode( - contract_address=starknet_contract_address, bytecode_len=0, bytecode=erase_data - ); - - return erase_contracts(contracts_len - 1, contracts_arr + 1); - } - // @notice This function is used to make an arbitrary length array of same elements. // @param arr: pointer to the first element // @param value: value to place diff --git a/tests/end_to_end/PlainOpcodes/conftest.py b/tests/end_to_end/PlainOpcodes/conftest.py index a33e16d5a..a40162af2 100644 --- a/tests/end_to_end/PlainOpcodes/conftest.py +++ b/tests/end_to_end/PlainOpcodes/conftest.py @@ -27,3 +27,12 @@ async def plain_opcodes(deploy_solidity_contract, counter, owner): counter.address, caller_eoa=owner.starknet_contract, ) + + +@pytest_asyncio.fixture(scope="package") +async def revert_on_fallbacks(deploy_solidity_contract, owner): + return await deploy_solidity_contract( + "PlainOpcodes", + "ContractRevertOnFallbackAndReceive", + caller_eoa=owner.starknet_contract, + ) diff --git a/tests/end_to_end/PlainOpcodes/test_plain_opcodes.py b/tests/end_to_end/PlainOpcodes/test_plain_opcodes.py index aa44e0e7b..22fc57ded 100644 --- a/tests/end_to_end/PlainOpcodes/test_plain_opcodes.py +++ b/tests/end_to_end/PlainOpcodes/test_plain_opcodes.py @@ -166,13 +166,6 @@ async def test_should_create_counters( ).nonce assert nonce_final == nonce_initial + count - @pytest.mark.xfail( - reason=""" - TODO: need to fix when there is no return data from the bytecode execution, - it calls CallHelper instead of CreateHelper when finalizing calling context - https://github.com/kkrt-labs/kakarot/issues/726 - """ - ) @pytest.mark.parametrize("bytecode", ["0x", "0x6000600155600160015500"]) async def test_should_create_empty_contract_when_creation_code_has_no_return( self, @@ -190,14 +183,35 @@ async def test_should_create_empty_contract_when_creation_code_has_no_return( events = plain_opcodes.events.parse_starknet_events(receipt.events) assert len(events["CreateAddress"]) == 1 - starknet_address = compute_starknet_address( + starknet_address = await compute_starknet_address( events["CreateAddress"][0]["_address"] ) contract_account = await get_contract( "contract_account", address=starknet_address ) - actual_bytecode = (await contract_account.bytecode().call()).result.bytecode - assert actual_bytecode == [] + assert [] == (await contract_account.functions["bytecode"].call()).bytecode + + async def test_should_create_counter_and_call_in_the_same_tx( + self, + plain_opcodes, + get_solidity_contract, + ): + receipt = await plain_opcodes.createCounterAndCall() + events = plain_opcodes.events.parse_starknet_events(receipt.events) + address = events["CreateAddress"][0]["_address"] + counter = get_solidity_contract("PlainOpcodes", "Counter", address=address) + assert await counter.count() == 0 + + async def test_should_create_counter_and_invoke_in_the_same_tx( + self, + plain_opcodes, + get_solidity_contract, + ): + receipt = await plain_opcodes.createCounterAndInvoke() + events = plain_opcodes.events.parse_starknet_events(receipt.events) + address = events["CreateAddress"][0]["_address"] + counter = get_solidity_contract("PlainOpcodes", "Counter", address=address) + assert await counter.count() == 1 class TestCreate2: async def test_should_deploy_bytecode_at_address( @@ -328,3 +342,57 @@ async def test_send_some_should_send( assert receiver_balance_after - receiver_balance_before == amount assert sender_balance_before - sender_balance_after == amount + + async def test_send_some_should_revert_when_amount_exceed_balance( + self, plain_opcodes, fund_starknet_address, eth_balance_of, owner, other + ): + amount = 1 + await fund_starknet_address(plain_opcodes.starknet_address, amount) + + sender_balance_before = await eth_balance_of(plain_opcodes.starknet_address) + receipt = await plain_opcodes.sendSome( + other.address, sender_balance_before + 1, caller_eoa=owner + ) + events = plain_opcodes.events.parse_starknet_events(receipt.events) + assert events["SentSome"] == [ + { + "to": other.address, + "amount": sender_balance_before + 1, + "success": False, + } + ] + assert sender_balance_before == await eth_balance_of( + plain_opcodes.starknet_address + ) + + class TestMapping: + async def test_should_emit_event_and_increase_nonce(self, plain_opcodes): + receipt = await plain_opcodes.incrementMapping() + events = plain_opcodes.events.parse_starknet_events(receipt.events) + prev_nonce = events["NonceIncreased"][0]["nonce"] + receipt = await plain_opcodes.incrementMapping() + events = plain_opcodes.events.parse_starknet_events(receipt.events) + assert events["NonceIncreased"][0]["nonce"] - prev_nonce == 1 + + class TestFallbackFunctions: + @pytest.mark.parametrize( + "data,value,message", (("", 1234, "receive"), ("0x00", 0, "fallback")) + ) + async def test_should_revert_on_fallbacks( + self, + revert_on_fallbacks, + eth_send_transaction, + data, + value, + message, + addresses, + ): + receipt, response, success = await eth_send_transaction( + to=revert_on_fallbacks.address, + gas=0, + data=data, + value=value, + caller_eoa=addresses[2].starknet_contract, + ) + assert not success + assert f"reverted on {message}".encode() in bytes(response) diff --git a/tests/end_to_end/Solmate/test_erc20.py b/tests/end_to_end/Solmate/test_erc20.py index b4a44837f..fe80826df 100644 --- a/tests/end_to_end/Solmate/test_erc20.py +++ b/tests/end_to_end/Solmate/test_erc20.py @@ -168,10 +168,9 @@ async def test_transfer_from_should_fail_when_insufficient_balance( ) class TestPermit: - async def test_should_permit(self, block_with_tx_hashes, erc_20, owner, other): + async def test_should_permit(self, erc_20, owner, other): nonce = await erc_20.nonces(owner.address) - pending_timestamp = block_with_tx_hashes("pending")["timestamp"] - deadline = pending_timestamp + 1 + deadline = 2**256 - 1 digest = get_approval_digest( "Kakarot Token", erc_20.address, @@ -184,7 +183,7 @@ async def test_should_permit(self, block_with_tx_hashes, erc_20, owner, other): deadline, ) v, r, s = ec_sign(digest, owner.private_key) - await erc_20.permit( + receipt = await erc_20.permit( owner.address, other.address, TEST_SUPPLY, @@ -194,7 +193,15 @@ async def test_should_permit(self, block_with_tx_hashes, erc_20, owner, other): s, caller_eoa=owner, ) + events = erc_20.events.parse_starknet_events(receipt.events) + assert events["Approval"] == [ + { + "owner": owner.address, + "spender": other.address, + "amount": TEST_SUPPLY, + } + ] assert await erc_20.allowance(owner.address, other.address) == TEST_SUPPLY assert await erc_20.nonces(owner.address) == 1 diff --git a/tests/end_to_end/bytecodes.py b/tests/end_to_end/bytecodes.py index 362d7d7a4..59694110c 100644 --- a/tests/end_to_end/bytecodes.py +++ b/tests/end_to_end/bytecodes.py @@ -8,7 +8,8 @@ "calldata": "", "stack": "", "memory": "0000000000000000000000000000000000000000000000000000000000000042", - "return_value": "0000000000000000000000000000000000000000000000000000000000000042", + "return_data": "0000000000000000000000000000000000000000000000000000000000000042", + "success": 1, }, "id": "return", "marks": [pytest.mark.RETURN, pytest.mark.SystemOperations], @@ -20,7 +21,8 @@ "calldata": "", "stack": "", "memory": "0000", - "return_value": "00", + "return_data": "00", + "success": 1, }, "id": "return2", "marks": [pytest.mark.RETURN, pytest.mark.SystemOperations], @@ -32,7 +34,8 @@ "calldata": "", "stack": "", "memory": "0360003900000000000000000000000000000000000000000000000000000000", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "codecopy", "marks": [pytest.mark.CODECOPY, pytest.mark.EnvironmentalInformation], @@ -44,7 +47,8 @@ "calldata": "", "stack": "1766847064778384329583297500742918515827483896875618958121606201292619775", "memory": "6008601f60003900000000000000000000000000000000000000000000000000", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "codecopy2", "marks": [pytest.mark.CODECOPY, pytest.mark.EnvironmentalInformation], @@ -56,7 +60,8 @@ "calldata": "", "stack": "16", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Arithmetic operations", "marks": [ @@ -81,7 +86,8 @@ "calldata": "", "stack": "1,1,3,4,2,5,3", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Duplication operations", "marks": [pytest.mark.DUP, pytest.mark.DuplicationOperations], @@ -93,7 +99,8 @@ "calldata": "", "stack": "1", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - SAR", "marks": [pytest.mark.SAR, pytest.mark.ComparisonBitwiseLogicOperations], @@ -105,7 +112,8 @@ "calldata": "", "stack": "115792089237316195423570985008687907853269984665640564039457584007913129639935", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - SAR", "marks": [pytest.mark.SAR, pytest.mark.ComparisonBitwiseLogicOperations], @@ -117,7 +125,8 @@ "calldata": "", "stack": "0", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - SAR", "marks": [pytest.mark.SAR, pytest.mark.ComparisonBitwiseLogicOperations], @@ -129,7 +138,8 @@ "calldata": "", "stack": "1", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - SAR", "marks": [pytest.mark.SAR, pytest.mark.ComparisonBitwiseLogicOperations], @@ -141,7 +151,8 @@ "calldata": "", "stack": "127", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - SAR", "marks": [pytest.mark.SAR, pytest.mark.ComparisonBitwiseLogicOperations], @@ -153,7 +164,8 @@ "calldata": "", "stack": "1", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - SAR", "marks": [pytest.mark.SAR, pytest.mark.ComparisonBitwiseLogicOperations], @@ -165,7 +177,8 @@ "calldata": "", "stack": "0", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - SAR", "marks": [pytest.mark.SAR, pytest.mark.ComparisonBitwiseLogicOperations], @@ -177,7 +190,8 @@ "calldata": "", "stack": "0", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - SAR", "marks": [pytest.mark.SAR, pytest.mark.ComparisonBitwiseLogicOperations], @@ -189,7 +203,8 @@ "calldata": "", "stack": "0", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - SAR", "marks": [pytest.mark.SAR, pytest.mark.ComparisonBitwiseLogicOperations], @@ -201,7 +216,8 @@ "calldata": "", "stack": "86844066927987146567678238756515930889952488499230423029593188005934847229952", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - SHR", "marks": [pytest.mark.SAR, pytest.mark.ComparisonBitwiseLogicOperations], @@ -213,7 +229,8 @@ "calldata": "", "stack": "115792089237316195423570985008687907853269984665640564039457584007913129639935", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - SAR", "marks": [pytest.mark.SAR, pytest.mark.ComparisonBitwiseLogicOperations], @@ -225,7 +242,8 @@ "calldata": "", "stack": "115792089237316195423570985008687907853269984665640564039457584007913129639935", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - SAR", "marks": [pytest.mark.SAR, pytest.mark.ComparisonBitwiseLogicOperations], @@ -237,7 +255,8 @@ "calldata": "", "stack": "115792089237316195423570985008687907853269984665640564039457584007913129639935", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - SAR", "marks": [pytest.mark.SAR, pytest.mark.ComparisonBitwiseLogicOperations], @@ -249,7 +268,8 @@ "calldata": "", "stack": "115792089237316195423570985008687907853269984665640564039457584007913129639935", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - SAR", "marks": [pytest.mark.SAR, pytest.mark.ComparisonBitwiseLogicOperations], @@ -261,7 +281,8 @@ "calldata": "", "stack": "115792089237316195423570985008687907853269984665640564039457584007913129639935", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - SAR", "marks": [pytest.mark.SAR, pytest.mark.ComparisonBitwiseLogicOperations], @@ -273,7 +294,8 @@ "calldata": "", "stack": "115792089237316195423570985008687907853269984665640564039457584007913129639935", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - SAR", "marks": [pytest.mark.SAR, pytest.mark.ComparisonBitwiseLogicOperations], @@ -285,7 +307,8 @@ "calldata": "", "stack": "1", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - SHL", "marks": [pytest.mark.SHL, pytest.mark.ComparisonBitwiseLogicOperations], @@ -297,7 +320,8 @@ "calldata": "", "stack": "0", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - SHL", "marks": [pytest.mark.SHL, pytest.mark.ComparisonBitwiseLogicOperations], @@ -309,7 +333,8 @@ "calldata": "", "stack": "115792089237316195423570985008687907853269984665640564039457584007913129639934", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - SHL", "marks": [pytest.mark.SHL, pytest.mark.ComparisonBitwiseLogicOperations], @@ -321,7 +346,8 @@ "calldata": "", "stack": "2", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - SHL", "marks": [pytest.mark.SHL, pytest.mark.ComparisonBitwiseLogicOperations], @@ -333,7 +359,8 @@ "calldata": "", "stack": "57896044618658097711785492504343953926634992332820282019728792003956564819968", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - SHL", "marks": [pytest.mark.SHL, pytest.mark.ComparisonBitwiseLogicOperations], @@ -345,7 +372,8 @@ "calldata": "", "stack": "0", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - SHL", "marks": [pytest.mark.SHL, pytest.mark.ComparisonBitwiseLogicOperations], @@ -357,7 +385,8 @@ "calldata": "", "stack": "0", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - SHL", "marks": [pytest.mark.SHL, pytest.mark.ComparisonBitwiseLogicOperations], @@ -369,7 +398,8 @@ "calldata": "", "stack": "115792089237316195423570985008687907853269984665640564039457584007913129639935", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - SHL", "marks": [pytest.mark.SHL, pytest.mark.ComparisonBitwiseLogicOperations], @@ -381,7 +411,8 @@ "calldata": "", "stack": "115792089237316195423570985008687907853269984665640564039457584007913129639934", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - SHL", "marks": [pytest.mark.SHL, pytest.mark.ComparisonBitwiseLogicOperations], @@ -393,7 +424,8 @@ "calldata": "", "stack": "57896044618658097711785492504343953926634992332820282019728792003956564819968", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - SHL", "marks": [pytest.mark.SHL, pytest.mark.ComparisonBitwiseLogicOperations], @@ -405,7 +437,8 @@ "calldata": "", "stack": "0", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - SHL", "marks": [pytest.mark.SHL, pytest.mark.ComparisonBitwiseLogicOperations], @@ -417,7 +450,8 @@ "calldata": "", "stack": "1", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - SHR", "marks": [pytest.mark.SHR, pytest.mark.ComparisonBitwiseLogicOperations], @@ -429,7 +463,8 @@ "calldata": "", "stack": "0", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - SHR", "marks": [pytest.mark.SHR, pytest.mark.ComparisonBitwiseLogicOperations], @@ -441,7 +476,8 @@ "calldata": "", "stack": "0", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - SHR", "marks": [pytest.mark.SHR, pytest.mark.ComparisonBitwiseLogicOperations], @@ -453,7 +489,8 @@ "calldata": "", "stack": "0", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - SHR", "marks": [pytest.mark.SHR, pytest.mark.ComparisonBitwiseLogicOperations], @@ -465,7 +502,8 @@ "calldata": "", "stack": "28948022309329048855892746252171976963317496166410141009864396001978282409984", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - SHR", "marks": [pytest.mark.SHR, pytest.mark.ComparisonBitwiseLogicOperations], @@ -477,7 +515,8 @@ "calldata": "", "stack": "1", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - SHR", "marks": [pytest.mark.SHR, pytest.mark.ComparisonBitwiseLogicOperations], @@ -489,7 +528,8 @@ "calldata": "", "stack": "0", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - SHR", "marks": [pytest.mark.SHR, pytest.mark.ComparisonBitwiseLogicOperations], @@ -501,7 +541,8 @@ "calldata": "", "stack": "0", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - SHR", "marks": [pytest.mark.SHR, pytest.mark.ComparisonBitwiseLogicOperations], @@ -513,7 +554,8 @@ "calldata": "", "stack": "115792089237316195423570985008687907853269984665640564039457584007913129639935", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - SHR", "marks": [pytest.mark.SHR, pytest.mark.ComparisonBitwiseLogicOperations], @@ -525,7 +567,8 @@ "calldata": "", "stack": "57896044618658097711785492504343953926634992332820282019728792003956564819967", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - SHR", "marks": [pytest.mark.SHR, pytest.mark.ComparisonBitwiseLogicOperations], @@ -537,7 +580,8 @@ "calldata": "", "stack": "1", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - SHR", "marks": [pytest.mark.SHR, pytest.mark.ComparisonBitwiseLogicOperations], @@ -549,7 +593,8 @@ "calldata": "", "stack": "5", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - AND", "marks": [pytest.mark.AND, pytest.mark.ComparisonBitwiseLogicOperations], @@ -561,7 +606,8 @@ "calldata": "", "stack": "1,0", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - EQ", "marks": [pytest.mark.EQ, pytest.mark.ComparisonBitwiseLogicOperations], @@ -573,7 +619,8 @@ "calldata": "", "stack": "0,1", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - GT", "marks": [pytest.mark.GT, pytest.mark.ComparisonBitwiseLogicOperations], @@ -585,7 +632,8 @@ "calldata": "", "stack": "1", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - ISZERO", "marks": [pytest.mark.ISZERO, pytest.mark.ComparisonBitwiseLogicOperations], @@ -597,7 +645,8 @@ "calldata": "", "stack": "1,0", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - LT", "marks": [pytest.mark.LT, pytest.mark.ComparisonBitwiseLogicOperations], @@ -609,7 +658,8 @@ "calldata": "", "stack": "115792089237316195423570985008687907853269984665640564039457584007913129639935", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - NOT", "marks": [pytest.mark.NOT, pytest.mark.ComparisonBitwiseLogicOperations], @@ -621,7 +671,8 @@ "calldata": "", "stack": "7", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - OR", "marks": [pytest.mark.OR, pytest.mark.ComparisonBitwiseLogicOperations], @@ -633,7 +684,8 @@ "calldata": "", "stack": "0", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - SGT", "marks": [pytest.mark.SGT, pytest.mark.ComparisonBitwiseLogicOperations], @@ -645,7 +697,8 @@ "calldata": "", "stack": "1", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Comparison & bitwise logic operations - SLT", "marks": [pytest.mark.SLT, pytest.mark.ComparisonBitwiseLogicOperations], @@ -657,7 +710,8 @@ "calldata": "", "stack": "1,2,3,4", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Exchange operations", "marks": [pytest.mark.SWAP, pytest.mark.ExchangeOperations], @@ -669,7 +723,8 @@ "calldata": "", "stack": "1,258,7", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Environmental information", "marks": [pytest.mark.CODESIZE, pytest.mark.EnvironmentalInformation], @@ -681,7 +736,8 @@ "calldata": "", "stack": "1,2,1263227476", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Block information CHAINID", "marks": [pytest.mark.CHAINID, pytest.mark.BlockInformation], @@ -693,7 +749,8 @@ "calldata": "", "stack": "1598625851760128517552627854997699631064626954749952456622017584404508471300", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Block information COINBASE", "marks": [pytest.mark.COINBASE, pytest.mark.BlockInformation], @@ -705,10 +762,11 @@ "calldata": "", "stack": "1,2", "memory": "", - "return_value": "", + "return_data": "", + "success": 0, }, "id": "System operations INVALID", - "marks": [pytest.mark.INVALID, pytest.mark.SystemOperations, pytest.mark.xfail], + "marks": [pytest.mark.INVALID, pytest.mark.SystemOperations], }, { "params": { @@ -717,7 +775,8 @@ "calldata": "", "stack": "{block_number}", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Block information NUMBER", "marks": [pytest.mark.NUMBER, pytest.mark.BlockInformation], @@ -729,7 +788,8 @@ "calldata": "", "stack": "{timestamp}", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Block information TIMESTAMP", "marks": [pytest.mark.TIMESTAMP, pytest.mark.BlockInformation], @@ -741,7 +801,8 @@ "calldata": "", "stack": "0000000000000000000000000000000000000000000000000000000000000000", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Get balance of currently executing contract - 0x47 SELFBALANCE", "marks": [pytest.mark.SELFBALANCE, pytest.mark.BlockInformation], @@ -753,7 +814,8 @@ "calldata": "", "stack": "{account_address}", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Origin Address", "marks": [pytest.mark.ORIGIN, pytest.mark.EnvironmentalInformation], @@ -765,7 +827,8 @@ "calldata": "", "stack": "{account_address}", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Caller Address", "marks": [pytest.mark.CALLER, pytest.mark.EnvironmentalInformation], @@ -777,7 +840,8 @@ "calldata": "", "stack": "31605475728638136284098257830937953109142906242585568807375082376557418698875", "memory": "0000000000000000000000000000000000000000000000000000000000000100", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Hash 32 bytes", "marks": [pytest.mark.SHA3], @@ -789,7 +853,8 @@ "calldata": "", "stack": "68071607937700842810429351077030899797510977729217708600998965445571406158526", "memory": "0000000000000000000000000000000000000000000000000000000000000010", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Hash 1 byte with offset 1f", "marks": [pytest.mark.SHA3], @@ -801,7 +866,8 @@ "calldata": "", "stack": "85131057757245807317576516368191972321038229705283732634690444270750521936266", "memory": "0000000000000000000000000000000000000000000000000000000000000010", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Hash 1 byte no offset", "marks": [pytest.mark.SHA3], @@ -813,7 +879,8 @@ "calldata": "", "stack": "101225983456080153511598605893998939348063346639131267901574990367534118792751", "memory": "0000000000000000000000000000000000000000000000000000000000000010", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Hash 7 bytes", "marks": [pytest.mark.SHA3], @@ -825,7 +892,8 @@ "calldata": "", "stack": "500549258012437878224561338362079327067368301550791134293299473726337612750", "memory": "0000000000000000000000000000000000000000000000000000000000000010", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Hash 8 bytes", "marks": [pytest.mark.SHA3], @@ -837,7 +905,8 @@ "calldata": "", "stack": "78337347954576241567341556127836028920764967266964912349540464394612926403441", "memory": "0000000000000000000000000000000000000000000000000000000000000010", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Hash 9 bytes", "marks": [pytest.mark.SHA3], @@ -849,7 +918,8 @@ "calldata": "", "stack": "41382199742381387985558122494590197322490258008471162768551975289239028668781", "memory": "0000000000000000000000000000000000000000000000000000000000000010", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Hash 17 bytes", "marks": [pytest.mark.SHA3], @@ -861,7 +931,8 @@ "calldata": "", "stack": "1000000", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Gas Limit", "marks": [pytest.mark.GASLIMIT, pytest.mark.BlockInformation], @@ -873,7 +944,8 @@ "calldata": "", "stack": "0", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Get the size of return data - 0x3d RETURNDATASIZE", "marks": [pytest.mark.RETURNDATASIZE, pytest.mark.EnvironmentalInformation], @@ -885,7 +957,8 @@ "calldata": "", "stack": "10,0", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Load Word from Memory", "marks": [pytest.mark.DIFFICULTY, pytest.mark.BlockInformation], @@ -897,7 +970,8 @@ "calldata": "", "stack": "0", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Get baseFee", "marks": [ @@ -909,10 +983,11 @@ "params": { "code": "34", "calldata": "", - "value": 9000000000, - "stack": "9000000000", + "value": 90, + "stack": "90", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Get deposited value by the instruction/transaction responsible for this execution - 0x34 CALLVALUE", "marks": [pytest.mark.CALLVALUE, pytest.mark.EnvironmentalInformation], @@ -924,7 +999,8 @@ "calldata": "000000000000000000000000000000000000000000000000000000000000000a", "stack": "10", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Load CallData onto the Stack - 0x35 CALLDATALOAD", "marks": [pytest.mark.CALLDATALOAD, pytest.mark.EnvironmentalInformation], @@ -936,7 +1012,8 @@ "calldata": "", "stack": "0", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Get the size of calldata when empty calldata - 0x36 CALLDATASIZE", "marks": [pytest.mark.CALLDATASIZE, pytest.mark.EnvironmentalInformation], @@ -948,7 +1025,8 @@ "calldata": "ff", "stack": "1", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Get the size of calldata when non empty calldata - 0x36 CALLDATASIZE", "marks": [pytest.mark.CALLDATASIZE, pytest.mark.EnvironmentalInformation], @@ -960,7 +1038,8 @@ "calldata": "", "stack": "0", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Balance", "marks": [pytest.mark.BALANCE, pytest.mark.EnvironmentalInformation], @@ -972,7 +1051,8 @@ "calldata": "", "stack": "", "memory": "000000000000000000000000000000000000000000000000000000000000000a", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Memory operations", "marks": [pytest.mark.MSTORE, pytest.mark.StackMemoryStorageFlowOperations], @@ -984,7 +1064,8 @@ "calldata": "", "stack": "", "memory": "00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Memory operations", "marks": [pytest.mark.MSTORE, pytest.mark.StackMemoryStorageFlowOperations], @@ -996,7 +1077,8 @@ "calldata": "", "stack": "", "memory": "000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000000000fa00000000000000000000000000000000000000000000000000000000", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Memory operations", "marks": [ @@ -1011,7 +1093,8 @@ "calldata": "", "stack": "0,1,3", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Memory operation - PC", "marks": [pytest.mark.PC, pytest.mark.StackMemoryStorageFlowOperations], @@ -1023,7 +1106,8 @@ "calldata": "", "stack": "0", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Get Memory Size", "marks": [pytest.mark.MSIZE, pytest.mark.StackMemoryStorageFlowOperations], @@ -1035,7 +1119,8 @@ "calldata": "", "stack": "10", "memory": "000000000000000000000000000000000000000000000000000000000000000a", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Load Word from Memory", "marks": [ @@ -1051,7 +1136,8 @@ "calldata": "", "stack": "0,1,1,1,8", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Jumpdest opcode", "marks": [ @@ -1066,7 +1152,8 @@ "calldata": "", "stack": "11", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "JUMP opcode", "marks": [ @@ -1081,7 +1168,8 @@ "calldata": "", "stack": "20", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "JUMP if condition is met", "marks": [ @@ -1096,7 +1184,8 @@ "calldata": "", "stack": "", "memory": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Memory operations - Check very large offsets", "marks": [ @@ -1111,7 +1200,8 @@ "calldata": "", "stack": "", "memory": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Memory operations - Check Colliding offsets", "marks": [ @@ -1126,7 +1216,8 @@ "calldata": "", "stack": "", "memory": "0000111111111111111111111111111111111111111111111111111111111111", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Memory operations - Check saving memory with 30 bytes", "marks": [ @@ -1141,7 +1232,8 @@ "calldata": "", "stack": "", "memory": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011ffffffffffffffffffffff", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Memory operations - Check saving memory in between an already saved memory location", "marks": [ @@ -1156,7 +1248,8 @@ "calldata": "", "stack": "", "memory": "0000002200000000000000000000000000000000000000000000000000000000", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Memory operations - Check saving memory in between an already saved memory location", "marks": [ @@ -1171,7 +1264,8 @@ "calldata": "", "stack": "", "memory": "1111111111221111111111111111111111111111111111111111111111111111", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Memory operations - Check saving memory in between an already saved memory location", "marks": [ @@ -1186,7 +1280,8 @@ "calldata": "00112233445566778899aabbcceeddff", "stack": "", "memory": "0000000000005566778899aabbcceeddff0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "calldatacopy", "marks": [ @@ -1201,7 +1296,8 @@ "calldata": "11111111111111111111111111111111111111111111111111111111111111111111", "stack": "", "memory": "0011221111111111111111111111111111111133445566778899aabbccddeeff", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "calldatacopy1", "marks": [ @@ -1216,7 +1312,8 @@ "calldata": "00112233445566778899aabbcceeddff00112233445566778899aabbccddeeff", "stack": "", "memory": "5566778899aabbcceeddff00112233445566778899aabbccddeeff00000000000000000000000000000000000000000000000000000000000000000000000000", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "calldatacopy2", "marks": [ @@ -1231,7 +1328,8 @@ "calldata": "00112233445566778899aabbcceeddff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff", "stack": "", "memory": "5566778899aabbcceeddff00112233445566778899aabbccddeeff0011223344", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "calldatacopy3", "marks": [ @@ -1246,7 +1344,8 @@ "calldata": "00112233445566778899aabbcceeddff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff", "stack": "", "memory": "33445566778899aabbcceeddff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00000000000000000000000000000000000000", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "calldatacopy4", "marks": [ @@ -1261,7 +1360,8 @@ "calldata": "00112233445566778899aabbcceeddff00112233445566778899aabbccddeeff", "stack": "", "memory": "5566778899aabbcceeddff001122334400000000000000000000000000000000", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "calldatacopy5", "marks": [ @@ -1276,7 +1376,8 @@ "calldata": "", "stack": "", "memory": "0000000000000000000000000000000000000000000000000000000000000010", - "return_value": "", + "return_data": "", + "success": 1, "events": [[[], [0x10]]], }, "id": "PRElog0", @@ -1292,7 +1393,8 @@ "calldata": "", "stack": "", "memory": "0000000000000000000000000000000000000000000000000000000000000010000000", - "return_value": "", + "return_data": "", + "success": 1, "events": [[[], [0x00]]], }, "id": "PRElog0-1", @@ -1308,7 +1410,8 @@ "calldata": "", "stack": "", "memory": "0000000000000000000000000000000000000000000000000000000000000010", - "return_value": "", + "return_data": "", + "success": 1, "events": [ [ [ @@ -1332,7 +1435,8 @@ "calldata": "", "stack": "", "memory": "0000000000000000000000000000000000000000000000000000000000000010000000", - "return_value": "", + "return_data": "", + "success": 1, "events": [[[0xFF, 0x00], [0x00]]], }, "id": "PRElog1-1", @@ -1348,7 +1452,8 @@ "calldata": "", "stack": "", "memory": "0000000000000000000000000000000000000000000000000000000000000010", - "return_value": "", + "return_data": "", + "success": 1, "events": [ [ [ @@ -1374,7 +1479,8 @@ "calldata": "", "stack": "", "memory": "0000000000000000000000000000000000000000000000000000000000000010000000", - "return_value": "", + "return_data": "", + "success": 1, "events": [[[0xFF, 0x00, 0x00, 0x00], [0x00]]], }, "id": "PRElog2-1", @@ -1390,7 +1496,8 @@ "calldata": "", "stack": "", "memory": "0000000000000000000000000000000000000000000000000000000000000010", - "return_value": "", + "return_data": "", + "success": 1, "events": [ [ [ @@ -1418,7 +1525,8 @@ "calldata": "", "stack": "", "memory": "0000000000000000000000000000000000000000000000000000000000000010000000", - "return_value": "", + "return_data": "", + "success": 1, "events": [[[0xFF, 0x00, 0x00, 0x00, 0xAB, 0x00], [0x00]]], }, "id": "PRElog3-1", @@ -1434,7 +1542,8 @@ "calldata": "", "stack": "", "memory": "0000000000000000000000000000000000000000000000000000000000000010", - "return_value": "", + "return_data": "", + "success": 1, "events": [ [ [ @@ -1464,7 +1573,8 @@ "calldata": "", "stack": "", "memory": "0000000000000000000000000000000000000000000000000000000000000010000000", - "return_value": "", + "return_data": "", + "success": 1, "events": [[[0xFF, 0x00, 0x00, 0x00, 0xAB, 0x00, 0x08, 0x00], [0x00]]], }, "id": "PRElog4-1", @@ -1476,7 +1586,8 @@ "calldata": "", "stack": "", "memory": "6002000000000000000000000000000000000000000000000000000000000000", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Environment Information - CODECOPY (0x39) - code slice within bounds, memory offset > len with tail padding", "marks": [ @@ -1491,7 +1602,8 @@ "calldata": "", "stack": "", "memory": "002233445566778899778899aabbccddeeff00112233445566778899aabbccdd", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Environmental Information - CODECOPY (0x39) - code slice within bounds, memory copy within bounds", "marks": [ @@ -1506,7 +1618,8 @@ "calldata": "", "stack": "", "memory": "002233445566778899aabbccddeeff00112233445566778899aabbccdd6000526000000000000000000000000000000000000000000000000000000000000000", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Environmental Information - CODECOPY (0x39) - code slice within bounds, memory offset < len < offset + size", "marks": [ @@ -1521,7 +1634,8 @@ "calldata": "", "stack": "", "memory": "00000060026003390000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Environmental Information - CODECOPY (0x39) - code with padding + memory offset > len ", "marks": [ @@ -1536,7 +1650,8 @@ "calldata": "", "stack": "", "memory": "000000110000000000778899aabbccddeeff00112233445566778899aabbccdd", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Environmental Information - CODECOPY (0x39) - code offset > len, memory offset + size < len", "marks": [ @@ -1551,7 +1666,8 @@ "calldata": "", "stack": "", "memory": "7dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Environment Information - CODECOPY (0x39) - evmcode example 1", "marks": [ @@ -1566,7 +1682,8 @@ "calldata": "", "stack": "", "memory": "7f00000000000000ffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Environment Information - CODECOPY (0x39) - evmcode example 1+2", "marks": [ @@ -1579,9 +1696,10 @@ "value": 0, "code": "3000", "calldata": "", - "stack": "0", + "stack": "1", "memory": "", - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Get address of currently executing account - 0x30 ADDRESS", "marks": [pytest.mark.ADDRESS, pytest.mark.EnvironmentalInformation], @@ -1599,7 +1717,8 @@ "4f8ae3bd7535248d0bd448298cc2e2071e56992d0774dc340c368ae950852ada" "0000000000000000000000007156526fbd7a3c72969b54f64e42c10fbb768c8a" ), - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Precompiles - EC_RECOVER - playground test case", "marks": [pytest.mark.EC_RECOVER, pytest.mark.Precompiles], @@ -1614,7 +1733,8 @@ "00000000000000000000000000000000000000000000000000000000000000ff" "0000000000000000000000002c0c45d3ecab80fe060e5f1d7057cd2f8de5e557" ), - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Precompiles - RIPEMD160 - playground test case", "marks": [pytest.mark.RIPEMD160, pytest.mark.Precompiles], @@ -1633,7 +1753,8 @@ "030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd3" "15ed738c0e0a7c92e7845f96b2ae9c0a68a6a449e3538fc7ff3ebf7a5a18a2c4" ), - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Precompiles - EC_ADD - playground test case", "marks": [ @@ -1655,7 +1776,8 @@ "08090a0000000000000000000000000000000000000000000000000000000000" "0000000000000000000000000000000000000000000000000000000000000008" ), - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Precompiles - ModExp - playground test case", "marks": [ @@ -1677,7 +1799,8 @@ "030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd3" "15ed738c0e0a7c92e7845f96b2ae9c0a68a6a449e3538fc7ff3ebf7a5a18a2c4" ), - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Precompiles - EC_MUL - playground test case", "marks": [ @@ -1701,7 +1824,8 @@ "0000000000000000000000000000000000000000000000000000000000000000" "0000000003000000000000000000000000000000010000000000000000000000" ), - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Precompiles - BLAKE2F - playground test case", "marks": [pytest.mark.BLAKE2F, pytest.mark.Precompiles], @@ -1718,7 +1842,8 @@ "00000000000000000000000000000000000000000000000000000000000000ff" "a8100ae6aa1940d0b663bb31cd466142ebbdbd5187131b92d93818987832eb89" ), - "return_value": "", + "return_data": "", + "success": 1, }, "id": "Precompiles - SHA2-256 - playground test case", "marks": [ @@ -1727,4 +1852,30 @@ pytest.mark.xfail(reason="Hint is not whitelisted"), ], }, + { + "params": { + "value": 0, + "code": "60ff60aa55", + "calldata": "", + "stack": "", + "memory": "", + "return_data": "", + "success": 1, + }, + "id": "SSTORE 0xff at key 0xaa", + "marks": [pytest.mark.SSTORE], + }, + { + "params": { + "value": 0, + "code": "60ff60aa5560aa54", + "calldata": "", + "stack": f"{0xff}", + "memory": "", + "return_data": "", + "success": 1, + }, + "id": "SSTORE 0xff at key 0xaa, then SLOAD 0xaa", + "marks": [pytest.mark.SSTORE, pytest.mark.SLOAD], + }, ] diff --git a/tests/end_to_end/conftest.py b/tests/end_to_end/conftest.py index bc296fd27..393c42027 100644 --- a/tests/end_to_end/conftest.py +++ b/tests/end_to_end/conftest.py @@ -34,7 +34,7 @@ def max_fee(): Return max fee hardcoded to 1 ETH to make sure tx passes it is not used per se in the test. """ - return int(1e18) + return int(5e17) @pytest.fixture(scope="session", autouse=True) @@ -72,9 +72,9 @@ async def addresses(max_fee) -> List[Wallet]: Wallet( address=private_key.public_key.to_checksum_address(), private_key=private_key, - # deploying an account with enough ETH to pass ~30 tx + # deploying an account with enough ETH to pass ~1000 tx starknet_contract=await get_eoa( - private_key, amount=30 * max_fee / 1e18 + private_key, amount=100 * max_fee / 1e18 ), ) ) @@ -311,3 +311,13 @@ async def _factory(evm_address: Union[str, int]): return False return _factory + + +@pytest.fixture +def eth_send_transaction(max_fee): + """ + Send a decoded transaction to Kakarot. + """ + from scripts.utils.kakarot import eth_send_transaction + + return partial(eth_send_transaction, max_fee=max_fee) diff --git a/tests/end_to_end/test_kakarot.py b/tests/end_to_end/test_kakarot.py index 65090fd20..cebd17f2b 100644 --- a/tests/end_to_end/test_kakarot.py +++ b/tests/end_to_end/test_kakarot.py @@ -32,64 +32,78 @@ async def evm(): @pytest.mark.asyncio class TestKakarot: - @pytest.mark.parametrize( - "params", - params_execute, - ) - async def test_execute( - self, - starknet: FullNodeClient, - eth: Contract, - wait_for_transaction, - params: dict, - request, - evm: Contract, - addresses, - max_fee, - ): - call = evm.functions["execute"].prepare( - origin=int(addresses[0].address, 16), - value=int(params["value"]), - bytecode=hex_string_to_bytes_array(params["code"]), - calldata=hex_string_to_bytes_array(params["calldata"]), + class TestEVM: + @pytest.mark.parametrize( + "params", + params_execute, ) - with traceit.context(request.node.callspec.id): - result = await call.call() - stack_result = extract_stack_from_execute(result) - memory_result = extract_memory_from_execute(result) - - assert stack_result == ( - [ - int(x) - for x in params["stack"] - .format( - account_address=int(addresses[0].address, 16), - timestamp=result.block_timestamp, - block_number=result.block_number, + async def test_execute( + self, + starknet: FullNodeClient, + eth: Contract, + wait_for_transaction, + params: dict, + request, + evm: Contract, + addresses, + max_fee, + ): + with traceit.context(request.node.callspec.id): + result = await evm.functions["evm_call"].call( + origin={ + "starknet": addresses[0].starknet_contract.address, + "evm": int(addresses[0].address, 16), + }, + value=int(params["value"]), + bytecode=hex_string_to_bytes_array(params["code"]), + calldata=hex_string_to_bytes_array(params["calldata"]), ) - .split(",") - ] - if params["stack"] - else [] - ) - assert memory_result == hex_string_to_bytes_array(params["memory"]) - - events = params.get("events") - if events: - # Events only show up in a transaction, thus we run the same call, but in a tx - tx = await call.invoke(max_fee=max_fee) - status = await wait_for_transaction(tx.hash) - assert status == TransactionStatus.ACCEPTED_ON_L2 - receipt = await starknet.get_transaction_receipt(tx.hash) - assert [ + stack_result = extract_stack_from_execute(result) + memory_result = extract_memory_from_execute(result) + + assert result.success == params["success"] + assert stack_result == ( [ - # we remove the key that is used to convey the emitting kakarot evm contract - event.keys[1:], - event.data, + int(x) + for x in params["stack"] + .format( + account_address=int(addresses[0].address, 16), + timestamp=result.block_timestamp, + block_number=result.block_number, + ) + .split(",") ] - for event in receipt.events - if event.from_address != eth.address - ] == events + if params["stack"] + else [] + ) + assert memory_result == hex_string_to_bytes_array(params["memory"]) + assert bytes(result.return_data).hex() == params["return_data"] + + events = params.get("events") + if events: + # Events only show up in a transaction, thus we run the same call, but in a tx + tx = await evm.functions["evm_execute"].invoke( + origin={ + "starknet": addresses[0].starknet_contract.address, + "evm": int(addresses[0].address, 16), + }, + value=int(params["value"]), + bytecode=hex_string_to_bytes_array(params["code"]), + calldata=hex_string_to_bytes_array(params["calldata"]), + max_fee=max_fee, + ) + status = await wait_for_transaction(tx.hash) + assert status == TransactionStatus.ACCEPTED_ON_L2 + receipt = await starknet.get_transaction_receipt(tx.hash) + assert [ + [ + # we remove the key that is used to convey the emitting kakarot evm contract + event.keys[1:], + event.data, + ] + for event in receipt.events + if event.from_address != eth.address + ] == events class TestComputeStarknetAddress: async def test_should_return_same_as_deployed_address( diff --git a/tests/fixtures/EVM.cairo b/tests/fixtures/EVM.cairo index 9960480c9..b66db9dc0 100644 --- a/tests/fixtures/EVM.cairo +++ b/tests/fixtures/EVM.cairo @@ -4,16 +4,19 @@ %lang starknet // Starkware dependencies +from starkware.cairo.common.alloc import alloc +from starkware.cairo.common.bool import FALSE from starkware.cairo.common.cairo_builtins import HashBuiltin, BitwiseBuiltin +from starkware.cairo.common.registers import get_fp_and_pc from starkware.cairo.common.uint256 import Uint256 -from starkware.cairo.common.alloc import alloc from starkware.starknet.common.syscalls import get_block_number, get_block_timestamp // 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 ( native_token_address, contract_account_class_hash, @@ -21,6 +24,7 @@ from kakarot.constants import ( account_proxy_class_hash, Constants, ) +from utils.dict import dict_keys // Constructor @constructor @@ -37,11 +41,40 @@ func constructor{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr return (); } -@external func execute{ syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr, bitwise_ptr: BitwiseBuiltin* }( - origin: felt, + origin: model.Address, + value: felt, + bytecode_len: felt, + bytecode: felt*, + calldata_len: felt, + calldata: felt*, +) -> EVM.Summary* { + alloc_locals; + 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( + address=address, + is_deploy_tx=0, + origin=&origin, + bytecode_len=bytecode_len, + bytecode=bytecode, + calldata_len=calldata_len, + calldata=calldata, + value=value, + gas_limit=Constants.TRANSACTION_GAS_LIMIT, + gas_price=0, + ); + return summary; +} + +@view +func evm_call{ + syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr, bitwise_ptr: BitwiseBuiltin* +}( + origin: model.Address, value: felt, bytecode_len: felt, bytecode: felt*, @@ -56,55 +89,71 @@ func execute{ memory_accesses_len: felt, memory_accesses: felt*, memory_bytes_len: felt, + account_addresses_len: felt, + account_addresses: felt*, starknet_contract_address: felt, evm_contract_address: felt, return_data_len: felt, return_data: felt*, gas_used: felt, success: felt, + program_counter: felt, ) { alloc_locals; let (local block_number) = get_block_number(); let (local block_timestamp) = get_block_timestamp(); - let ( - stack_accesses_len, - stack_accesses, - stack_len, - memory_accesses_len, - memory_accesses, - memory_bytes_len, - starknet_contract_address, - evm_contract_address, - return_data_len, - return_data, - gas_used, - reverted, - ) = Kakarot.execute( - starknet_contract_address=0, - evm_contract_address=0, - origin=origin, - bytecode_len=bytecode_len, - bytecode=bytecode, - calldata_len=calldata_len, - calldata=calldata, - value=value, - gas_limit=Constants.TRANSACTION_GAS_LIMIT, - gas_price=0, + let summary = execute(origin, value, bytecode_len, bytecode, calldata_len, calldata); + + let stack_accesses_len = summary.stack.squashed_end - summary.stack.squashed_start; + let memory_accesses_len = summary.memory.squashed_end - summary.memory.squashed_start; + + // Return only accounts keys, ie. touched starknet addresses + let (account_addresses_len, account_addresses) = dict_keys( + summary.state.accounts_start, summary.state.accounts ); + return ( - block_number, - block_timestamp, - stack_accesses_len, - stack_accesses, - stack_len, - memory_accesses_len, - memory_accesses, - memory_bytes_len, - starknet_contract_address, - evm_contract_address, - return_data_len, - return_data, - gas_used, - 1 - reverted, + block_number=block_number, + block_timestamp=block_timestamp, + stack_accesses_len=stack_accesses_len, + stack_accesses=summary.stack.squashed_start, + stack_len=summary.stack.len_16bytes, + memory_accesses_len=memory_accesses_len, + memory_accesses=summary.memory.squashed_start, + memory_bytes_len=summary.memory.bytes_len, + account_addresses_len=account_addresses_len, + account_addresses=account_addresses, + starknet_contract_address=summary.address.starknet, + evm_contract_address=summary.address.evm, + return_data_len=summary.return_data_len, + return_data=summary.return_data, + gas_used=summary.gas_used, + success=1 - summary.reverted, + program_counter=summary.program_counter, ); } + +@external +func evm_execute{ + syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr, bitwise_ptr: BitwiseBuiltin* +}( + origin: model.Address, + value: felt, + bytecode_len: felt, + bytecode: felt*, + calldata_len: felt, + calldata: felt*, +) -> (return_data_len: felt, return_data: felt*, success: felt) { + alloc_locals; + let summary = execute(origin, value, bytecode_len, bytecode, calldata_len, calldata); + let result = (summary.return_data_len, summary.return_data, 1 - summary.reverted); + + if (summary.reverted != FALSE) { + return result; + } + + // 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); + return result; +} diff --git a/tests/integration/accounts/test_contract_account.py b/tests/integration/accounts/test_contract_account.py index 812533cd5..d7bac96b8 100644 --- a/tests/integration/accounts/test_contract_account.py +++ b/tests/integration/accounts/test_contract_account.py @@ -117,7 +117,7 @@ async def test_should_increment_nonce( initial_nonce = (await contract_account.get_nonce().call()).result.nonce # Increment nonce - await contract_account.increment_nonce().execute( + await contract_account.set_nonce(initial_nonce + 1).execute( caller_address=kakarot.contract_address ) @@ -131,6 +131,6 @@ async def test_should_raise_when_caller_is_not_kakarot( self, contract_account: StarknetContract, kakarot ): with cairo_error(): - await contract_account.increment_nonce().execute( + await contract_account.set_nonce(0).execute( caller_address=kakarot.contract_address + 1 ) diff --git a/tests/integration/ef_tests/test_ef_blockchain_tests.py b/tests/integration/ef_tests/test_ef_blockchain_tests.py index 77d7aad2d..1dd6dbe60 100644 --- a/tests/integration/ef_tests/test_ef_blockchain_tests.py +++ b/tests/integration/ef_tests/test_ef_blockchain_tests.py @@ -210,7 +210,7 @@ async def test_case( starknet_address = get_starknet_address(int(transaction["sender"], 16)) await kakarot.eth_send_transaction( - to=int(transaction["to"], 16), + to=int(transaction["to"] or "0", 16), gas_limit=int(transaction["gasLimit"], 16), gas_price=int(transaction["gasPrice"], 16), value=int(transaction["value"], 16), diff --git a/tests/src/kakarot/accounts/eoa/mock_externally_owned_account.cairo b/tests/src/kakarot/accounts/eoa/mock_externally_owned_account.cairo index e158be598..784afd804 100644 --- a/tests/src/kakarot/accounts/eoa/mock_externally_owned_account.cairo +++ b/tests/src/kakarot/accounts/eoa/mock_externally_owned_account.cairo @@ -6,7 +6,7 @@ from starkware.cairo.common.cairo_builtins import HashBuiltin, BitwiseBuiltin from starkware.cairo.common.alloc import alloc from kakarot.accounts.eoa.library import ExternallyOwnedAccount -from kakarot.accounts.library import Accounts +from kakarot.account import Account // Externally Owned Account initializer @external diff --git a/tests/src/kakarot/accounts/eoa/mock_kakarot.cairo b/tests/src/kakarot/accounts/eoa/mock_kakarot.cairo index 8e9510484..a93d47ed4 100644 --- a/tests/src/kakarot/accounts/eoa/mock_kakarot.cairo +++ b/tests/src/kakarot/accounts/eoa/mock_kakarot.cairo @@ -7,7 +7,7 @@ from starkware.cairo.common.memcpy import memcpy from starkware.cairo.common.alloc import alloc from starkware.starknet.common.syscalls import get_caller_address -from kakarot.accounts.library import Accounts +from kakarot.account import Account from kakarot.constants import account_proxy_class_hash, externally_owned_account_class_hash from kakarot.library import native_token_address, Kakarot from kakarot.interfaces.interfaces import IAccount @@ -38,7 +38,7 @@ func get_native_token{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_chec func compute_starknet_address{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( evm_address: felt ) -> (contract_address: felt) { - let (contract_address_) = Accounts.compute_starknet_address(evm_address); + let (contract_address_) = Account.compute_starknet_address(evm_address); return (contract_address=contract_address_); } @@ -47,7 +47,7 @@ func compute_starknet_address{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, ra func get_starknet_address{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( evm_address: felt ) -> (starknet_address: felt) { - return Accounts.get_starknet_address(evm_address); + return Account.get_registered_starknet_address(evm_address); } // @notice Deploy a new externally owned account. @@ -73,8 +73,6 @@ func eth_call{ data: felt*, ) -> (return_data_len: felt, return_data: felt*, success: felt) { alloc_locals; - // Do the transfer - Kakarot.transfer(origin, to, value); // Mock only the execution part let (local return_data) = alloc(); diff --git a/tests/src/kakarot/accounts/eoa/test_library.py b/tests/src/kakarot/accounts/eoa/test_library.py index ed995fddd..fdec841d8 100644 --- a/tests/src/kakarot/accounts/eoa/test_library.py +++ b/tests/src/kakarot/accounts/eoa/test_library.py @@ -1,5 +1,3 @@ -from collections import Counter - import pytest import pytest_asyncio from starkware.starknet.testing.contract import DeclaredClass, StarknetContract @@ -85,49 +83,3 @@ async def test_execute_should_make_all_calls_and_return_concat_results( assert ( await mock_externally_owned_account.execute(calls, list(calldata)).call() ).result.response == expected_result - - async def test_should_transfer_value_to_destination_address( - self, mock_kakarot, mock_externally_owned_account, eth, private_key - ): - txs = [t for t in TRANSACTIONS if t["to"]] - (calls, calldata, _) = get_multicall_from_evm_txs(txs, private_key) - total_transferred_value = sum([x["value"] for x in txs]) - - evm_to_starknet_address = dict() - expected_balances = Counter() - - # Storing initial balance, as eth storage persists across tests. - initial_balance = ( - await eth.balanceOf(mock_externally_owned_account.contract_address).call() - ).result.balance.low - - # Mint tokens to the EOA - await eth.mint( - mock_externally_owned_account.contract_address, (total_transferred_value, 0) - ).execute() - - for transaction in txs: - # Update expected balances - evm_address = int(transaction["to"], 16) - expected_balances[evm_address] += transaction["value"] - - # Update address mapping - if evm_address not in evm_to_starknet_address: - starknet_address = ( - await mock_kakarot.compute_starknet_address(evm_address).call() - ).result.contract_address - evm_to_starknet_address[evm_address] = starknet_address - - # execute the multicall - await mock_externally_owned_account.execute(calls, list(calldata)).execute() - - # verify the value was transferred - for evm_address, amount in expected_balances.items(): - assert ( - await eth.balanceOf(evm_to_starknet_address[evm_address]).call() - ).result.balance.low == amount - - # verify EOA has used all its recently minted balance - assert ( - await eth.balanceOf(mock_externally_owned_account.contract_address).call() - ).result.balance.low == initial_balance diff --git a/tests/src/kakarot/instructions/test_environmental_information.cairo b/tests/src/kakarot/instructions/test_environmental_information.cairo index 94724e617..1a318cb4c 100644 --- a/tests/src/kakarot/instructions/test_environmental_information.cairo +++ b/tests/src/kakarot/instructions/test_environmental_information.cairo @@ -31,7 +31,7 @@ from kakarot.instructions.memory_operations import MemoryOperations from kakarot.instructions.environmental_information import EnvironmentalInformation from tests.utils.helpers import TestHelpers from kakarot.library import Kakarot -from kakarot.accounts.library import Accounts +from kakarot.account import Account from kakarot.instructions.system_operations import CreateHelper @constructor @@ -74,49 +74,25 @@ func init_context{ tempvar bytecode_len = 1; let (calldata) = alloc(); assert [calldata] = ''; - local call_context: model.CallContext* = new model.CallContext( - bytecode=bytecode, bytecode_len=bytecode_len, calldata=calldata, calldata_len=1, value=0 - ); - - // Initialize ExecutionContext - let (empty_return_data: felt*) = alloc(); - let (empty_selfdestruct_contracts: felt*) = alloc(); - let (empty_events: model.Event*) = alloc(); - let stack: model.Stack* = Stack.init(); - let memory: model.Memory* = Memory.init(); - let gas_limit = Constants.TRANSACTION_GAS_LIMIT; let calling_context = ExecutionContext.init_empty(); - - let (local revert_contract_state_dict_start) = default_dict_new(0); - tempvar revert_contract_state: model.RevertContractState* = new model.RevertContractState( - revert_contract_state_dict_start, revert_contract_state_dict_start - ); - - local ctx: model.ExecutionContext* = new model.ExecutionContext( - call_context=call_context, - program_counter=0, - stopped=FALSE, - return_data=empty_return_data, - return_data_len=0, - stack=stack, - memory=memory, - gas_used=0, - gas_limit=gas_limit, + tempvar address = new model.Address(0, 420); + local call_context: model.CallContext* = new model.CallContext( + bytecode=bytecode, + bytecode_len=bytecode_len, + calldata=calldata, + calldata_len=1, + value=0, + gas_limit=Constants.TRANSACTION_GAS_LIMIT, gas_price=0, - starknet_contract_address=0, - evm_contract_address=420, - origin=100, + origin=address, calling_context=calling_context, - selfdestruct_contracts_len=0, - selfdestruct_contracts=empty_selfdestruct_contracts, - events_len=0, - events=empty_events, - create_addresses_len=0, - create_addresses=cast(0, felt*), - revert_contract_state=revert_contract_state, - reverted=FALSE, + address=address, read_only=FALSE, + is_create=FALSE, ); + + // Initialize ExecutionContext + let ctx = ExecutionContext.init(call_context); return ctx; } @@ -149,7 +125,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) = Accounts.create( + let (local starknet_contract_address) = Account.deploy( contract_account_class_hash_, evm_contract_address ); let evm_contract_address_uint256 = Helpers.to_uint256(evm_contract_address); @@ -191,7 +167,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) = Accounts.create( + let (local starknet_contract_address) = Account.deploy( contract_account_class_hash_, evm_contract_address ); IContractAccount.write_bytecode(starknet_contract_address, bytecode_len, bytecode); @@ -240,7 +216,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) = Accounts.create( + let (local starknet_contract_address) = Account.deploy( contract_account_class_hash_, evm_contract_address ); let evm_contract_address_uint256 = Helpers.to_uint256(evm_contract_address); @@ -294,7 +270,7 @@ func test__exec_gasprice{ bytecode_len, bytecode, stack ); - let expected_gas_price_uint256 = Helpers.to_uint256(ctx.gas_price); + let expected_gas_price_uint256 = Helpers.to_uint256(ctx.call_context.gas_price); let result = EnvironmentalInformation.exec_gasprice(ctx); let (stack, gasprice) = Stack.peek(result.stack, 0); @@ -406,7 +382,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) = Accounts.create( + let (local starknet_contract_address) = Account.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 c5756a74f..95f40e498 100644 --- a/tests/src/kakarot/instructions/test_system_operations.cairo +++ b/tests/src/kakarot/instructions/test_system_operations.cairo @@ -4,11 +4,12 @@ // Starkware dependencies 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.uint256 import Uint256, uint256_sub from starkware.starknet.common.syscalls import deploy, get_contract_address -from starkware.cairo.common.math import split_felt, assert_not_zero, assert_le // Third party dependencies from openzeppelin.token.erc20.library import ERC20 @@ -22,18 +23,14 @@ from kakarot.constants import ( ) from kakarot.execution_context import ExecutionContext from kakarot.instructions.memory_operations import MemoryOperations -from kakarot.instructions.system_operations import ( - SystemOperations, - CallHelper, - CreateHelper, - SelfDestructHelper, -) +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.accounts.library import Accounts +from kakarot.account import Account from kakarot.model import model from kakarot.stack import Stack from kakarot.memory import Memory +from kakarot.state import State from tests.utils.helpers import TestHelpers from utils.utils import Helpers @@ -71,7 +68,7 @@ func get_native_token{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_chec func compute_starknet_address{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( evm_address: felt ) -> (contract_address: felt) { - let (contract_address_) = Accounts.compute_starknet_address(evm_address); + let (contract_address_) = Account.compute_starknet_address(evm_address); return (contract_address=contract_address_); } @@ -173,11 +170,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) = Accounts.create( + let (caller_starknet_contract_address) = Account.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) = Accounts.create( + let (callee_starknet_contract_address) = Account.deploy( contract_account_class_hash_, callee_evm_contract_address ); @@ -228,19 +225,17 @@ func test__exec_call__should_return_a_new_context_based_on_calling_ctx_stack{ assert sub_ctx.stopped = 0; assert sub_ctx.return_data_len = 0; assert sub_ctx.gas_used = 0; - let (gas_felt, _) = Helpers.div_rem(Constants.TRANSACTION_GAS_LIMIT, 64); - assert_le(sub_ctx.gas_limit, gas_felt); - assert sub_ctx.gas_price = 0; - assert sub_ctx.starknet_contract_address = callee_starknet_contract_address; - assert sub_ctx.evm_contract_address = callee_evm_contract_address; - TestHelpers.assert_execution_context_equal(sub_ctx.calling_context, ctx); + assert sub_ctx.call_context.gas_price = 0; + assert sub_ctx.call_context.address.starknet = callee_starknet_contract_address; + assert sub_ctx.call_context.address.evm = callee_evm_contract_address; + TestHelpers.assert_execution_context_equal(sub_ctx.call_context.calling_context, ctx); // Fake a RETURN in sub_ctx then teardow, see note in evm.codes: // If the size of the return data is not known, it can also be retrieved after the call with // the instructions RETURNDATASIZE and RETURNDATACOPY (since the Byzantium fork). let (local return_data: felt*) = alloc(); assert [return_data] = 0x11; - let sub_ctx = ExecutionContext.update_return_data(sub_ctx, 1, return_data); + let sub_ctx = ExecutionContext.stop(sub_ctx, 1, return_data, FALSE); let summary = ExecutionContext.finalize(sub_ctx); let ctx = CallHelper.finalize_calling_context(summary); @@ -263,18 +258,18 @@ 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) = Accounts.create( + let (caller_starknet_contract_address) = Account.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) = Accounts.create( + let (callee_starknet_contract_address) = Account.deploy( contract_account_class_hash_, callee_evm_contract_address ); - - // Get the balance of caller pre-call - let (native_token_address_) = native_token_address.read(); - let (caller_pre_balance) = IERC20.balanceOf( - contract_address=native_token_address_, account=caller_starknet_contract_address + tempvar callee_address = new model.Address( + callee_starknet_contract_address, callee_evm_contract_address ); // Fill the stack with input data @@ -304,19 +299,19 @@ func test__exec_call__should_transfer_value{ caller_starknet_contract_address, caller_evm_contract_address, bytecode_len, bytecode, stack ); let ctx = MemoryOperations.exec_mstore(ctx); + // Get the balance of caller pre-call + let (state, caller_balance_prev) = State.read_balance(ctx.state, caller_address); + let ctx = ExecutionContext.update_state(ctx, state); // When let sub_ctx = SystemOperations.exec_call(ctx); // Then // get balances of caller and callee post-call - let (callee_balance) = IERC20.balanceOf( - contract_address=native_token_address_, account=callee_starknet_contract_address - ); - let (caller_post_balance) = IERC20.balanceOf( - contract_address=native_token_address_, account=caller_starknet_contract_address - ); - let (caller_diff_balance) = uint256_sub(caller_pre_balance, caller_post_balance); + let state = sub_ctx.state; + 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); assert caller_diff_balance = Uint256(2, 0); @@ -332,11 +327,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) = Accounts.create( + let (caller_starknet_contract_address) = Account.deploy( contract_account_class_hash_, caller_evm_contract_address ); let (callee_evm_contract_address) = CreateHelper.get_create_address(1, 0); - let (_) = Accounts.create(contract_account_class_hash_, callee_evm_contract_address); + let (_) = Account.deploy(contract_account_class_hash_, callee_evm_contract_address); // Fill the stack with input data let stack: model.Stack* = Stack.init(); @@ -383,18 +378,16 @@ func test__exec_callcode__should_return_a_new_context_based_on_calling_ctx_stack assert sub_ctx.program_counter = 0; assert sub_ctx.stopped = 0; assert sub_ctx.gas_used = 0; - let (gas_felt, _) = Helpers.div_rem(Constants.TRANSACTION_GAS_LIMIT, 64); - assert_le(sub_ctx.gas_limit, gas_felt); - assert sub_ctx.gas_price = 0; - assert sub_ctx.starknet_contract_address = caller_starknet_contract_address; - assert sub_ctx.evm_contract_address = caller_evm_contract_address; - TestHelpers.assert_execution_context_equal(sub_ctx.calling_context, ctx); + assert sub_ctx.call_context.gas_price = 0; + assert sub_ctx.call_context.address.starknet = caller_starknet_contract_address; + assert sub_ctx.call_context.address.evm = caller_evm_contract_address; + TestHelpers.assert_execution_context_equal(sub_ctx.call_context.calling_context, ctx); // Fake a RETURN in sub_ctx then teardow, see note in evm.codes: // If the size of the return data is not known, it can also be retrieved after the call with // the instructions RETURNDATASIZE and RETURNDATACOPY (since the Byzantium fork). // So it's expected that the RETURN of the sub_ctx does set proper values for return_data_len and return_data - let sub_ctx = ExecutionContext.update_return_data(sub_ctx, 0, sub_ctx.return_data); + let sub_ctx = ExecutionContext.stop(sub_ctx, 0, sub_ctx.return_data, FALSE); let summary = ExecutionContext.finalize(sub_ctx); let ctx = CallHelper.finalize_calling_context(summary); @@ -414,18 +407,18 @@ 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) = Accounts.create( + let (caller_starknet_contract_address) = Account.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) = Accounts.create( + let (callee_starknet_contract_address) = Account.deploy( contract_account_class_hash_, callee_evm_contract_address ); - - // Get the balance of caller pre-call - let (native_token_address_) = native_token_address.read(); - let (caller_pre_balance) = IERC20.balanceOf( - contract_address=native_token_address_, account=caller_starknet_contract_address + tempvar callee_address = new model.Address( + callee_starknet_contract_address, callee_evm_contract_address ); // Fill the stack with input data @@ -456,21 +449,21 @@ 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 ctx = ExecutionContext.update_state(ctx, state); + // When let sub_ctx = SystemOperations.exec_callcode(ctx); // Then // get balances of caller and callee post-call - let (callee_balance) = IERC20.balanceOf( - contract_address=native_token_address_, account=callee_starknet_contract_address - ); - let (caller_post_balance) = IERC20.balanceOf( - contract_address=native_token_address_, account=caller_starknet_contract_address - ); + 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); - assert callee_balance = Uint256(2, 0); - assert caller_diff_balance = Uint256(2, 0); + assert caller_post_balance = caller_pre_balance; return (); } @@ -483,7 +476,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) = Accounts.create( + let (local starknet_contract_address) = Account.deploy( contract_account_class_hash_, evm_contract_address ); @@ -528,18 +521,16 @@ func test__exec_staticcall__should_return_a_new_context_based_on_calling_ctx_sta assert sub_ctx.program_counter = 0; assert sub_ctx.stopped = 0; assert sub_ctx.gas_used = 0; - let (gas_felt, _) = Helpers.div_rem(Constants.TRANSACTION_GAS_LIMIT, 64); - assert_le(sub_ctx.gas_limit, gas_felt); - assert sub_ctx.gas_price = 0; - assert sub_ctx.starknet_contract_address = starknet_contract_address; - assert sub_ctx.evm_contract_address = evm_contract_address; - TestHelpers.assert_execution_context_equal(sub_ctx.calling_context, ctx); + assert sub_ctx.call_context.gas_price = 0; + assert sub_ctx.call_context.address.starknet = starknet_contract_address; + assert sub_ctx.call_context.address.evm = evm_contract_address; + TestHelpers.assert_execution_context_equal(sub_ctx.call_context.calling_context, ctx); // Fake a RETURN in sub_ctx then teardow, see note in evm.codes: // If the size of the return data is not known, it can also be retrieved after the call with // the instructions RETURNDATASIZE and RETURNDATACOPY (since the Byzantium fork). // So it's expected that the RETURN of the sub_ctx does set proper values for return_data_len and return_data - let sub_ctx = ExecutionContext.update_return_data(sub_ctx, 0, sub_ctx.return_data); + let sub_ctx = ExecutionContext.stop(sub_ctx, 0, sub_ctx.return_data, FALSE); let summary = ExecutionContext.finalize(sub_ctx); let ctx = CallHelper.finalize_calling_context(summary); @@ -559,7 +550,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) = Accounts.create( + let (local starknet_contract_address) = Account.deploy( contract_account_class_hash_, evm_contract_address ); @@ -604,18 +595,16 @@ func test__exec_delegatecall__should_return_a_new_context_based_on_calling_ctx_s assert sub_ctx.program_counter = 0; assert sub_ctx.stopped = 0; assert sub_ctx.gas_used = 0; - let (gas_felt, _) = Helpers.div_rem(Constants.TRANSACTION_GAS_LIMIT, 64); - assert_le(sub_ctx.gas_limit, gas_felt); - assert sub_ctx.gas_price = 0; - assert sub_ctx.starknet_contract_address = ctx.starknet_contract_address; - assert sub_ctx.evm_contract_address = ctx.evm_contract_address; - TestHelpers.assert_execution_context_equal(sub_ctx.calling_context, ctx); + assert sub_ctx.call_context.gas_price = 0; + assert sub_ctx.call_context.address.starknet = ctx.call_context.address.starknet; + assert sub_ctx.call_context.address.evm = ctx.call_context.address.evm; + TestHelpers.assert_execution_context_equal(sub_ctx.call_context.calling_context, ctx); // Fake a RETURN in sub_ctx then teardow, see note in evm.codes: // If the size of the return data is not known, it can also be retrieved after the call with // the instructions RETURNDATASIZE and RETURNDATACOPY (since the Byzantium fork). // So it's expected that the RETURN of the sub_ctx does set proper values for return_data_len and return_data - let sub_ctx = ExecutionContext.update_return_data(sub_ctx, 0, sub_ctx.return_data); + let sub_ctx = ExecutionContext.stop(sub_ctx, 0, sub_ctx.return_data, FALSE); let summary = ExecutionContext.finalize(sub_ctx); let ctx = CallHelper.finalize_calling_context(summary); @@ -676,38 +665,27 @@ func test__exec_create__should_return_a_new_context_with_bytecode_from_memory_at assert sub_ctx.stopped = 0; assert sub_ctx.return_data_len = 0; assert sub_ctx.gas_used = 0; - assert sub_ctx.gas_limit = 0; - assert sub_ctx.gas_price = 0; - assert_not_zero(sub_ctx.starknet_contract_address); - assert_not_zero(sub_ctx.evm_contract_address); - let (sub_ctx_contract_stored_bytecode) = IAccount.bytecode_len( - sub_ctx.starknet_contract_address - ); - assert sub_ctx_contract_stored_bytecode = 0; - TestHelpers.assert_execution_context_equal(ctx, sub_ctx.calling_context); + assert sub_ctx.call_context.gas_limit = ctx.call_context.gas_limit; + assert sub_ctx.call_context.gas_price = ctx.call_context.gas_price; + assert_not_zero(sub_ctx.call_context.address.starknet); + assert_not_zero(sub_ctx.call_context.address.evm); + TestHelpers.assert_execution_context_equal(ctx, sub_ctx.call_context.calling_context); // Fake a RETURN in sub_ctx then finalize let return_data_len = 65; TestHelpers.array_fill(sub_ctx.return_data, return_data_len, 0xff); - let sub_ctx = ExecutionContext.update_return_data( - sub_ctx, return_data_len, sub_ctx.return_data - ); + let sub_ctx = ExecutionContext.stop(sub_ctx, return_data_len, sub_ctx.return_data, FALSE); let summary = ExecutionContext.finalize(sub_ctx); let ctx = CreateHelper.finalize_calling_context(summary); // Then let (stack, address) = Stack.peek(ctx.stack, 0); let evm_contract_address = Helpers.uint256_to_felt(address); - assert evm_contract_address = sub_ctx.evm_contract_address; - assert sub_ctx.evm_contract_address = expected_create_address; - let (created_contract_bytecode_len, created_contract_bytecode) = IAccount.bytecode( - sub_ctx.starknet_contract_address - ); + assert evm_contract_address = sub_ctx.call_context.address.evm; + assert sub_ctx.call_context.address.evm = expected_create_address; + let (state, account) = State.get_account(ctx.state, sub_ctx.call_context.address); TestHelpers.assert_array_equal( - created_contract_bytecode_len, - created_contract_bytecode, - return_data_len, - sub_ctx.return_data, + account.code_len, account.code, return_data_len, sub_ctx.return_data ); return (); @@ -759,7 +737,7 @@ func test__exec_create2__should_return_a_new_context_with_bytecode_from_memory_a contract_address, evm_caller_address, bytecode_len, bytecode, stack ); - assert ctx.evm_contract_address = evm_caller_address; + assert ctx.call_context.address.evm = evm_caller_address; let ctx = MemoryOperations.exec_mstore(ctx); // When @@ -777,120 +755,105 @@ func test__exec_create2__should_return_a_new_context_with_bytecode_from_memory_a assert sub_ctx.stopped = 0; assert sub_ctx.return_data_len = 0; assert sub_ctx.gas_used = 0; - assert sub_ctx.gas_limit = 0; - assert sub_ctx.gas_price = 0; - assert_not_zero(sub_ctx.starknet_contract_address); - assert_not_zero(sub_ctx.evm_contract_address); - let (sub_ctx_contract_stored_bytecode) = IAccount.bytecode_len( - sub_ctx.starknet_contract_address - ); - assert sub_ctx_contract_stored_bytecode = 0; - TestHelpers.assert_execution_context_equal(ctx, sub_ctx.calling_context); + assert sub_ctx.call_context.gas_limit = ctx.call_context.gas_limit; + assert sub_ctx.call_context.gas_price = ctx.call_context.gas_price; + assert_not_zero(sub_ctx.call_context.address.starknet); + assert_not_zero(sub_ctx.call_context.address.evm); + TestHelpers.assert_execution_context_equal(ctx, sub_ctx.call_context.calling_context); // Fake a RETURN in sub_ctx then finalize let return_data_len = 65; TestHelpers.array_fill(sub_ctx.return_data, return_data_len, 0xff); - let sub_ctx = ExecutionContext.update_return_data( - sub_ctx, return_data_len, sub_ctx.return_data - ); + let sub_ctx = ExecutionContext.stop(sub_ctx, return_data_len, sub_ctx.return_data, FALSE); let summary = ExecutionContext.finalize(sub_ctx); let ctx = CreateHelper.finalize_calling_context(summary); // Then let (stack, address) = Stack.peek(ctx.stack, 0); let evm_contract_address = Helpers.uint256_to_felt(address); - assert evm_contract_address = sub_ctx.evm_contract_address; - assert sub_ctx.evm_contract_address = expected_create2_address; - let (created_contract_bytecode_len, created_contract_bytecode) = IAccount.bytecode( - sub_ctx.starknet_contract_address - ); + assert evm_contract_address = sub_ctx.call_context.address.evm; + assert sub_ctx.call_context.address.evm = expected_create2_address; + let state = ctx.state; + let (state, account) = State.get_account(state, sub_ctx.call_context.address); TestHelpers.assert_array_equal( - created_contract_bytecode_len, - created_contract_bytecode, - return_data_len, - sub_ctx.return_data, + account.code_len, account.code, return_data_len, sub_ctx.return_data ); return (); } -@external -func test__exec_selfdestruct__should_delete_account_bytecode{ - syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr, bitwise_ptr: BitwiseBuiltin* -}() { - alloc_locals; - - // Create sub_ctx writing directly in memory because need to update calling_context - let (bytecode) = alloc(); - let stack: model.Stack* = Stack.init(); - let memory: model.Memory* = Memory.init(); - let (bytecode) = alloc(); - let (return_data) = alloc(); - assert [return_data] = 0; - assert [return_data + 1] = 10; - let (selfdestruct_contracts) = alloc(); - let (calldata) = alloc(); - assert [calldata] = ''; - local call_context: model.CallContext* = new model.CallContext( - bytecode=bytecode, bytecode_len=0, calldata=calldata, calldata_len=1, value=0 - ); - let stack = Stack.push(stack, Uint256(10, 0)); - let (local revert_contract_state_dict_start) = default_dict_new(0); - tempvar revert_contract_state: model.RevertContractState* = new model.RevertContractState( - revert_contract_state_dict_start, revert_contract_state_dict_start - ); - - // Simulate contract creation - 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) = Accounts.create( - contract_account_class_hash_, evm_contract_address - ); - - // Fill contract bytecode - let (bytecode) = alloc(); - assert [bytecode] = 1907; - IContractAccount.write_bytecode( - contract_address=starknet_contract_address, bytecode_len=1, bytecode=bytecode - ); - - // Create context - let (sub_ctx: felt*) = alloc(); - let sub_ctx_object: model.ExecutionContext* = cast(sub_ctx, model.ExecutionContext*); - - assert [sub_ctx + 0] = cast(call_context, felt); // call_context - assert [sub_ctx + 1] = 0; // program_counter - assert [sub_ctx + 2] = 0; // stopped - assert [sub_ctx + 3] = cast(return_data + 1, felt); // return_data - assert [sub_ctx + 4] = 1; // return_data_len - assert [sub_ctx + 5] = cast(stack, felt); // stack - assert [sub_ctx + 6] = cast(memory, felt); // memory - assert [sub_ctx + 7] = 0; // gas_used - assert [sub_ctx + 8] = 0; // gas_limit - assert [sub_ctx + 9] = 0; // intrinsic_gas_cost - assert [sub_ctx + 10] = starknet_contract_address; // starknet_contract_address - assert [sub_ctx + 11] = evm_contract_address; // evm_contract_address - assert [sub_ctx + 12] = 0; // origin - assert [sub_ctx + 13] = 0; // calling_context - assert [sub_ctx + 14] = 0; // selfdestruct_contracts_len - assert [sub_ctx + 15] = cast(selfdestruct_contracts, felt); // selfdestruct_contracts - assert [sub_ctx + 16] = 0; // events_len - assert [sub_ctx + 17] = cast(0, felt); // events - assert [sub_ctx + 18] = 0; // create_addresses_len - assert [sub_ctx + 19] = cast(0, felt); // create_addresses - assert [sub_ctx + 20] = cast(revert_contract_state, felt); // revert_contract_state - assert [sub_ctx + 21] = 0; // reverted - assert [sub_ctx + 22] = 0; // read only - - // When - let sub_ctx_object: model.ExecutionContext* = SystemOperations.exec_selfdestruct( - sub_ctx_object - ); - SelfDestructHelper.finalize(sub_ctx_object); - - // Then - let (evm_contract_byte_len) = IAccount.bytecode_len(contract_address=starknet_contract_address); - - assert evm_contract_byte_len = 0; - return (); -} +// @external +// func test__exec_selfdestruct__should_add_account_to_state{ +// syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr, bitwise_ptr: BitwiseBuiltin* +// }() { +// alloc_locals; + +// // Create sub_ctx writing directly in memory because need to update calling_context +// let (bytecode) = alloc(); +// let stack: model.Stack* = Stack.init(); +// let memory: model.Memory* = Memory.init(); +// let (bytecode) = alloc(); +// let (return_data) = alloc(); +// assert [return_data] = 0; +// assert [return_data + 1] = 10; +// let (selfdestructs) = alloc(); +// let (calldata) = alloc(); +// assert [calldata] = ''; +// local call_context: model.CallContext* = new model.CallContext( +// bytecode=bytecode, bytecode_len=0, calldata=calldata, calldata_len=1, value=0 +// ); +// let stack = Stack.push(stack, Uint256(10, 0)); +// let (local revert_contract_state_dict_start) = default_dict_new(0); +// tempvar revert_contract_state: model.RevertContractState* = new model.RevertContractState( +// revert_contract_state_dict_start, revert_contract_state_dict_start +// ); + +// // Simulate contract creation +// 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( +// contract_account_class_hash_, evm_contract_address +// ); + +// // Fill contract bytecode +// let (bytecode) = alloc(); +// assert [bytecode] = 1907; +// IContractAccount.write_bytecode( +// contract_address=starknet_contract_address, bytecode_len=1, bytecode=bytecode +// ); + +// tempvar address = new model.Address(starknet_contract_address, evm_contract_address); +// // Create context +// let (sub_ctx: felt*) = alloc(); +// let sub_ctx_object: model.ExecutionContext* = cast(sub_ctx, model.ExecutionContext*); + +// assert [sub_ctx + 0] = cast(call_context, felt); // call_context +// assert [sub_ctx + 1] = 0; // program_counter +// assert [sub_ctx + 2] = 0; // stopped +// assert [sub_ctx + 3] = cast(return_data + 1, felt); // return_data +// assert [sub_ctx + 4] = 1; // return_data_len +// assert [sub_ctx + 5] = cast(stack, felt); // stack +// assert [sub_ctx + 6] = cast(memory, felt); // memory +// assert [sub_ctx + 7] = 0; // gas_used +// assert [sub_ctx + 8] = 0; // gas_limit +// assert [sub_ctx + 9] = 0; // intrinsic_gas_cost +// assert [sub_ctx + 10] = cast(address, felt); // address +// assert [sub_ctx + 12] = 0; // origin +// assert [sub_ctx + 13] = 0; // calling_context +// assert [sub_ctx + 14] = 0; // selfdestructs_len +// assert [sub_ctx + 15] = cast(selfdestructs, felt); // selfdestructs +// assert [sub_ctx + 17] = cast(0, felt); // state +// assert [sub_ctx + 21] = 0; // reverted +// assert [sub_ctx + 22] = 0; // read only + +// // When +// let sub_ctx_object: model.ExecutionContext* = SystemOperations.exec_selfdestruct( +// sub_ctx_object +// ); + +// // Then +// assert_non_zero(sub_ctx.selfdestructs_len); +// let address = [sub_ctx.selfdestructs + sub_ctx.selfdestructs_len - 1]; +// assert address = sub_ctx_object.address.starknet; +// return (); +// } diff --git a/tests/src/kakarot/instructions/test_system_operations.py b/tests/src/kakarot/instructions/test_system_operations.py index c759e341e..7ea6af36a 100644 --- a/tests/src/kakarot/instructions/test_system_operations.py +++ b/tests/src/kakarot/instructions/test_system_operations.py @@ -140,8 +140,3 @@ async def test_create2(self, system_operations): (0, memory_word), int(expected_create2_addr, 16), ).call() - - async def test_selfdestruct(self, system_operations): - await system_operations.test__exec_selfdestruct__should_delete_account_bytecode().call( - system_operations.contract_address - ) diff --git a/tests/src/kakarot/precompiles/test_precompiles.cairo b/tests/src/kakarot/precompiles/test_precompiles.cairo index 225a5c335..c009d24d8 100644 --- a/tests/src/kakarot/precompiles/test_precompiles.cairo +++ b/tests/src/kakarot/precompiles/test_precompiles.cairo @@ -25,7 +25,7 @@ func test__precompiles_should_throw_on_out_of_bounds{ }(address: felt) { // When let result = Precompiles.run( - address=address, + evm_address=address, calldata_len=0, calldata=cast(0, felt*), value=0, @@ -46,7 +46,7 @@ func test__not_implemented_precompile_should_raise_with_detailed_error_message{ }(address: felt) { // When let result = Precompiles.not_implemented_precompile( - address=address, _input_len=0, _input=cast(0, felt*) + evm_address=address, _input_len=0, _input=cast(0, felt*) ); return (); @@ -57,9 +57,23 @@ func test__run_should_return_a_stopped_execution_context{ syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr, bitwise_ptr: BitwiseBuiltin* }(address: felt) { // When - let calling_context = ExecutionContext.init_empty(); + tempvar call_context = new model.CallContext( + bytecode=cast(0, felt*), + bytecode_len=0, + calldata=cast(0, felt*), + calldata_len=0, + value=0, + gas_limit=0, + gas_price=0, + origin=cast(0, model.Address*), + calling_context=cast(0, model.ExecutionContext*), + address=cast(0, model.Address*), + read_only=0, + is_create=0, + ); + let calling_context = ExecutionContext.init(call_context); let result = Precompiles.run( - address=address, + evm_address=address, calldata_len=0, calldata=cast(0, felt*), value=0, diff --git a/tests/src/kakarot/test_account.cairo b/tests/src/kakarot/test_account.cairo new file mode 100644 index 000000000..e7bc1ec49 --- /dev/null +++ b/tests/src/kakarot/test_account.cairo @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: MIT + +%lang starknet + +// Starkware dependencies +from starkware.cairo.common.alloc import alloc +from starkware.cairo.common.cairo_builtins import HashBuiltin, BitwiseBuiltin +from starkware.cairo.common.dict import dict_read +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 + +// Local dependencies +from kakarot.model import model +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) { + // When + let account = Account.init(address, code_len, code, nonce); + + // Then + assert account.address = address; + assert account.code_len = code_len; + assert account.nonce = nonce; + assert account.selfdestruct = 0; + let storage = account.storage; + let (value) = dict_read{dict_ptr=storage}(0xdead); + assert value = 0; + return (); +} + +@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) { + alloc_locals; + // Given + let account = Account.init(evm_address, code_len, code, nonce); + let key = Uint256(1, 2); + tempvar value = new Uint256(3, 4); + let account = Account.write_storage(account, key, value); + + // When + let account_copy = Account.copy(account); + + // Then + + // Same immutable attributes + assert account.address = account_copy.address; + assert account.code_len = account_copy.code_len; + assert account.nonce = account_copy.nonce; + assert account.selfdestruct = account_copy.selfdestruct; + + // Same storage + let storage_len = account.storage - account.storage_start; + let storage_copy_len = account_copy.storage - account_copy.storage_start; + assert storage_len = storage_copy_len; + tempvar address = new model.Address(0, evm_address); + let (account_copy, value_copy) = Account.read_storage(account_copy, address, key); + assert_uint256_eq([value], value_copy); + + // Updating copy doesn't update original + tempvar new_value = new Uint256(5, 6); + let account_copy = Account.write_storage(account_copy, key, new_value); + let (account, value_original) = Account.read_storage(account, address, key); + assert_uint256_eq([value], value_original); + + return (); +} + +@external +func test__finalize__should_return_summary{ + syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr +}(evm_address: felt, code_len: felt, code: felt*, nonce: felt) { + // Given + alloc_locals; + let account = Account.init(evm_address, code_len, code, nonce); + let key = Uint256(1, 2); + tempvar value = new Uint256(3, 4); + tempvar address = new model.Address(0, evm_address); + let account = Account.write_storage(account, key, value); + let (account, value_read) = Account.read_storage(account, address, key); + let (account, value_read) = Account.read_storage(account, address, key); + let (account, value_read) = Account.read_storage(account, address, key); + + // When + let summary = Account.finalize(account); + + // Then + let account_storage_len = account.storage - account.storage_start; + assert account_storage_len = 4 * DictAccess.SIZE; + let summary_storage_len = summary.storage - summary.storage_start; + assert summary_storage_len = 1 * DictAccess.SIZE; + let (account, value_summary) = Account.read_storage( + cast(summary, model.Account*), address, key + ); + assert_uint256_eq(value_read, value_summary); + + return (); +} + +@external +func test__finalize__should_return_summary_with_no_default_dict{ + syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr +}(evm_address: felt, code_len: felt, code: felt*, nonce: felt) { + // Given + alloc_locals; + tempvar key = Uint256(1, 2); + tempvar address = new model.Address(0, evm_address); + let account = Account.init(evm_address, code_len, code, nonce); + + // When + let summary = Account.finalize(account); + + // Then + with_attr error_message("KeyError") { + Account.read_storage(cast(summary, model.Account*), address, key); + } + + return (); +} + +@external +func test__write_storage__should_store_value_at_key{ + syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr +}(key: Uint256, value: Uint256) { + // Given + alloc_locals; + let fp_and_pc = get_fp_and_pc(); + 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); + + // When + let account = Account.write_storage(account, key, &value); + + // Then + let storage_len = account.storage - account.storage_start; + assert storage_len = DictAccess.SIZE; + let (account, value_read) = Account.read_storage(account, address, key); + assert_uint256_eq(value_read, value); + + return (); +} + +@external +func test__has_code_or_nonce{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( + nonce: felt, code_len: felt, code: felt* +) -> (has_code_or_nonce: felt) { + // Given + let account = Account.init(0, code_len, code, nonce); + + // When + let result = Account.has_code_or_nonce(account); + + // Then + return (result,); +} diff --git a/tests/src/kakarot/test_account.py b/tests/src/kakarot/test_account.py new file mode 100644 index 000000000..24f4a2e0f --- /dev/null +++ b/tests/src/kakarot/test_account.py @@ -0,0 +1,86 @@ +import pytest +import pytest_asyncio +from starkware.starknet.testing.starknet import Starknet + +from tests.utils.errors import cairo_error +from tests.utils.uint256 import int_to_uint256 + + +@pytest_asyncio.fixture +async def account(starknet: Starknet): + class_hash = await starknet.deprecated_declare( + source="./tests/src/kakarot/test_account.cairo", + cairo_path=["src"], + disable_hint_validation=True, + ) + return await starknet.deploy(class_hash=class_hash.class_hash) + + +@pytest.mark.asyncio +class TestAccount: + class TestInit: + @pytest.mark.parametrize( + "address, code, nonce", [(0, [], 0), (2**160 - 1, [1, 2, 3], 1)] + ) + async def test_should_return_account_with_default_dict_as_storage( + self, account, address, code, nonce + ): + await account.test__init__should_return_account_with_default_dict_as_storage( + address, code, nonce + ).call() + + class TestCopy: + @pytest.mark.parametrize( + "address, code, nonce", [(0, [], 0), (2**160 - 1, [1, 2, 3], 1)] + ) + async def test_should_return_account_with_default_dict_as_storage( + self, account, address, code, nonce + ): + await account.test__copy__should_return_new_account_with_same_attributes( + address, code, nonce + ).call() + + class TestFinalize: + @pytest.mark.parametrize( + "address, code, nonce", [(0, [], 0), (2**160 - 1, [1, 2, 3], 1)] + ) + async def test_should_return_summary(self, account, address, code, nonce): + await account.test__finalize__should_return_summary( + address, code, nonce + ).call() + + @pytest.mark.parametrize( + "address, code, nonce", [(0, [], 0), (2**160 - 1, [1, 2, 3], 1)] + ) + async def test_should_return_summary_with_no_default_dict( + self, account, address, code, nonce + ): + with cairo_error("KeyError"): + await account.test__finalize__should_return_summary_with_no_default_dict( + address, code, nonce + ).call() + + class TestWriteStorage: + @pytest.mark.parametrize("key, value", [(0, 0), (2**256 - 1, 2**256 - 1)]) + async def test_should_store_value_at_key(self, account, key, value): + await account.test__write_storage__should_store_value_at_key( + int_to_uint256(key), int_to_uint256(value) + ).call() + + class TestHasCodeOrNonce: + @pytest.mark.parametrize( + "nonce,code,expected_result", + ( + (0, [], False), + (1, [], True), + (0, [1], True), + (1, [1], True), + ), + ) + async def test_should_return_true_when_nonce( + self, account, nonce, code, expected_result + ): + result = ( + await account.test__has_code_or_nonce(nonce, code).call() + ).result.has_code_or_nonce + assert result == expected_result diff --git a/tests/src/kakarot/test_execution_context.cairo b/tests/src/kakarot/test_execution_context.cairo index 0bf9560bf..ebc44fa9c 100644 --- a/tests/src/kakarot/test_execution_context.cairo +++ b/tests/src/kakarot/test_execution_context.cairo @@ -28,24 +28,25 @@ func test__init__should_return_an_empty_execution_context{ assert [calldata] = ''; // When - local call_context: model.CallContext* = new model.CallContext( - bytecode=bytecode, bytecode_len=bytecode_len, calldata=calldata, calldata_len=1, value=0 - ); + tempvar address = new model.Address(0, 0); let calling_ctx = ExecutionContext.init_empty(); - let (return_data: felt*) = alloc(); - let result: model.ExecutionContext* = ExecutionContext.init( - call_context, - starknet_contract_address=0, - evm_contract_address=0, - origin=0, + local call_context: model.CallContext* = new model.CallContext( + bytecode=bytecode, + bytecode_len=bytecode_len, + calldata=calldata, + calldata_len=1, + value=0, gas_limit=Constants.TRANSACTION_GAS_LIMIT, gas_price=0, + origin=address, calling_context=calling_ctx, - return_data_len=0, - return_data=return_data, + address=address, read_only=0, + is_create=0, ); + let result: model.ExecutionContext* = ExecutionContext.init(call_context); + // Then assert result.call_context.bytecode = bytecode; assert result.call_context.bytecode_len = 1; @@ -55,8 +56,8 @@ func test__init__should_return_an_empty_execution_context{ assert result.stack.len_16bytes = 0; assert result.memory.bytes_len = 0; assert result.gas_used = 0; - assert result.gas_limit = Constants.TRANSACTION_GAS_LIMIT; // TODO: Add support for gas limit - assert result.gas_price = 0; + assert result.call_context.gas_limit = Constants.TRANSACTION_GAS_LIMIT; // TODO: Add support for gas limit + assert result.call_context.gas_price = 0; return (); } diff --git a/tests/src/kakarot/test_state.cairo b/tests/src/kakarot/test_state.cairo new file mode 100644 index 000000000..16756488f --- /dev/null +++ b/tests/src/kakarot/test_state.cairo @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: MIT + +%lang starknet + +// Starkware dependencies +from starkware.cairo.common.alloc import alloc +from starkware.cairo.common.cairo_builtins import HashBuiltin, BitwiseBuiltin +from starkware.cairo.common.dict import dict_read +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 + +// Local dependencies +from kakarot.model import model +from kakarot.state import State +from kakarot.account import Account + +@external +func test__init__should_return_state_with_default_dicts{ + syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr, bitwise_ptr: BitwiseBuiltin* +}() { + // When + let state = State.init(); + + // 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 (); +} + +@external +func test__copy__should_return_new_state_with_same_attributes{ + syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr, bitwise_ptr: BitwiseBuiltin* +}() { + alloc_locals; + // Given + + // 1. Create empty State + let state = State.init(); + + // 2. Put two accounts with some storage + tempvar address_0 = new model.Address(1, 2); + tempvar address_1 = new model.Address(3, 4); + tempvar key_0 = Uint256(1, 2); + tempvar key_1 = Uint256(3, 4); + tempvar value = new Uint256(3, 4); + let state = State.write_storage(state, address_0, key_0, value); + let state = State.write_storage(state, address_1, key_0, value); + let state = State.write_storage(state, address_1, key_1, value); + + // 3. Put some events + let (local topics: felt*) = alloc(); + let (local data: felt*) = alloc(); + let event = model.Event(topics_len=0, topics=topics, data_len=0, data=data); + let state = State.add_event(state, event); + + // 4. Add transfers + // State.add_transfer requires a native token contract deployed so we just push. + let amount = Uint256(0xa, 0xb); + tempvar transfer = model.Transfer(address_0, address_1, amount); + assert state.transfers[0] = transfer; + tempvar state = new model.State( + accounts_start=state.accounts_start, + 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, + ); + + // When + let state_copy = State.copy(state); + + // Then + + // Storage + let (state_copy, value_copy) = State.read_storage(state_copy, address_0, key_0); + assert_uint256_eq([value], value_copy); + let (state_copy, value_copy) = State.read_storage(state_copy, address_1, key_0); + assert_uint256_eq([value], value_copy); + let (state_copy, value_copy) = State.read_storage(state_copy, address_1, key_1); + assert_uint256_eq([value], value_copy); + + // Events + assert state_copy.events_len = state.events_len; + + // Transfers + assert state_copy.transfers_len = state.transfers_len; + let transfer_copy = state_copy.transfers; + assert transfer.sender.starknet = transfer_copy.sender.starknet; + assert transfer.sender.evm = transfer_copy.sender.evm; + assert transfer.recipient.starknet = transfer_copy.recipient.starknet; + assert transfer.recipient.evm = transfer_copy.recipient.evm; + assert_uint256_eq(transfer.amount, transfer_copy.amount); + + return (); +} diff --git a/tests/src/kakarot/test_state.py b/tests/src/kakarot/test_state.py new file mode 100644 index 000000000..894b5e21e --- /dev/null +++ b/tests/src/kakarot/test_state.py @@ -0,0 +1,24 @@ +import pytest +import pytest_asyncio +from starkware.starknet.testing.starknet import Starknet + + +@pytest_asyncio.fixture +async def state(starknet: Starknet): + class_hash = await starknet.deprecated_declare( + source="./tests/src/kakarot/test_state.cairo", + cairo_path=["src"], + disable_hint_validation=True, + ) + return await starknet.deploy(class_hash=class_hash.class_hash) + + +@pytest.mark.asyncio +class TestState: + class TestInit: + async def test_should_return_state_with_default_dicts(self, state): + await state.test__init__should_return_state_with_default_dicts().call() + + class TestCopy: + async def test_should_return_new_state_with_same_attributes(self, state): + await state.test__copy__should_return_new_state_with_same_attributes().call() diff --git a/tests/src/utils/test_dict.cairo b/tests/src/utils/test_dict.cairo new file mode 100644 index 000000000..140cfdae2 --- /dev/null +++ b/tests/src/utils/test_dict.cairo @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: MIT + +%lang starknet + +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_write, dict_read +from starkware.cairo.common.dict_access import DictAccess + +from utils.dict import dict_keys, default_dict_copy + +@external +func test__dict_keys__should_return_keys{ + syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr +}() { + alloc_locals; + let (local dict_start) = default_dict_new(0); + let dict_ptr = dict_start; + + with dict_ptr { + dict_write(0xa, 2); + dict_write(0xb, 3); + dict_write(0xb, 4); + dict_read(0xb); + dict_write(0xc, 5); + } + + let (keys_len, keys) = dict_keys(dict_start, dict_ptr); + + assert keys_len = 5; + assert [keys + 0] = 0xa; + assert [keys + 1] = 0xb; + assert [keys + 2] = 0xb; + assert [keys + 3] = 0xb; + assert [keys + 4] = 0xc; + + let (squashed_start, squashed_end) = default_dict_finalize(dict_start, dict_ptr, 0); + + let (keys_len, keys) = dict_keys(squashed_start, squashed_end); + + assert keys_len = 3; + assert [keys + 0] = 0xa; + assert [keys + 1] = 0xb; + assert [keys + 2] = 0xc; + + return (); +} + +@external +func test__default_dict_copy__should_return_copied_dict{range_check_ptr}() { + let default_value = 0xdead; + let (dict_ptr_start) = default_dict_new(default_value); + let dict_ptr = dict_ptr_start; + let key = 0x7e1; + with dict_ptr { + let (value) = dict_read(key); + assert value = default_value; + dict_write(key, 0xff); + let (value) = dict_read(key); + assert value = 0xff; + dict_write(key + 1, 0xff + 1); + dict_write(key + 2, 0xff + 2); + dict_write(key + 3, 0xff + 3); + dict_write(key + 4, 0xff + 4); + } + let (new_start, new_ptr) = default_dict_copy(dict_ptr_start, dict_ptr); + + assert new_ptr - new_start = DictAccess.SIZE * 5; + + let dict_ptr = new_ptr; + with dict_ptr { + let (value) = dict_read(key); + assert value = 0xff; + let (value) = dict_read(key + 1); + assert value = 0xff + 1; + let (value) = dict_read(key + 2); + assert value = 0xff + 2; + let (value) = dict_read(key + 3); + assert value = 0xff + 3; + let (value) = dict_read(key + 4); + assert value = 0xff + 4; + let (value) = dict_read(key + 10); + assert value = default_value; + } + + return (); +} diff --git a/tests/src/utils/test_dict.py b/tests/src/utils/test_dict.py new file mode 100644 index 000000000..635de20d3 --- /dev/null +++ b/tests/src/utils/test_dict.py @@ -0,0 +1,24 @@ +import pytest +import pytest_asyncio +from starkware.starknet.testing.starknet import Starknet + + +@pytest_asyncio.fixture +async def dict_(starknet: Starknet): + class_hash = await starknet.deprecated_declare( + source="./tests/src/utils/test_dict.cairo", + cairo_path=["src"], + disable_hint_validation=True, + ) + return await starknet.deploy(class_hash=class_hash.class_hash) + + +@pytest.mark.asyncio +class TestDict: + class TestDictKeys: + async def test_should_return_keys(self, dict_): + await dict_.test__dict_keys__should_return_keys().call() + + class TestDefaultDictCopy: + async def test_should_return_copied_dict(self, dict_): + await dict_.test__default_dict_copy__should_return_copied_dict().call() diff --git a/tests/utils/helpers.cairo b/tests/utils/helpers.cairo index 11360e7e5..b4566bfc0 100644 --- a/tests/utils/helpers.cairo +++ b/tests/utils/helpers.cairo @@ -40,23 +40,24 @@ namespace TestHelpers { let (calldata) = alloc(); assert [calldata] = ''; - local call_context: model.CallContext* = new model.CallContext( - bytecode=bytecode, bytecode_len=bytecode_len, calldata=calldata, calldata_len=1, value=0 - ); let root_context = ExecutionContext.init_empty(); - let return_data: felt* = alloc(); - let ctx: model.ExecutionContext* = ExecutionContext.init( - call_context, - starknet_contract_address=starknet_contract_address, - evm_contract_address=evm_contract_address, - origin=0, + tempvar address = new model.Address(starknet_contract_address, evm_contract_address); + tempvar origin = new model.Address(0, 0); + local call_context: model.CallContext* = new model.CallContext( + bytecode=bytecode, + bytecode_len=bytecode_len, + calldata=calldata, + calldata_len=1, + value=0, gas_limit=Constants.TRANSACTION_GAS_LIMIT, gas_price=0, + origin=origin, calling_context=root_context, - return_data_len=0, - return_data=return_data, + address=address, read_only=FALSE, + is_create=FALSE, ); + let ctx: model.ExecutionContext* = ExecutionContext.init(call_context); return ctx; } @@ -123,7 +124,7 @@ namespace TestHelpers { bytecode_len: felt, bytecode: felt*, return_data_len: felt, return_data: felt* ) -> model.ExecutionContext* { let ctx: model.ExecutionContext* = init_context(bytecode_len, bytecode); - let ctx = ExecutionContext.update_return_data(ctx, return_data_len, return_data); + let ctx = ExecutionContext.stop(ctx, return_data_len, return_data, FALSE); return ctx; } @@ -188,30 +189,24 @@ namespace TestHelpers { return assert_array_equal(array_0_len - 1, array_0 + 1, array_1_len - 1, array_1 + 1); } - func assert_call_context_equal( - call_context_0: model.CallContext*, call_context_1: model.CallContext* - ) { - assert call_context_0.value = call_context_1.value; - assert_array_equal( - call_context_0.bytecode_len, - call_context_0.bytecode, - call_context_1.bytecode_len, - call_context_1.bytecode, - ); - assert_array_equal( - call_context_0.calldata_len, - call_context_0.calldata, - call_context_1.calldata_len, - call_context_1.calldata, - ); + func assert_call_context_equal(ctx_0: model.CallContext*, ctx_1: model.CallContext*) { + assert ctx_0.value = ctx_1.value; + assert_array_equal(ctx_0.bytecode_len, ctx_0.bytecode, ctx_1.bytecode_len, ctx_1.bytecode); + assert_array_equal(ctx_0.calldata_len, ctx_0.calldata, ctx_1.calldata_len, ctx_1.calldata); + + assert ctx_0.gas_limit = ctx_1.gas_limit; + assert ctx_0.address.starknet = ctx_1.address.starknet; + assert ctx_0.gas_price = ctx_1.gas_price; + assert ctx_0.address.evm = ctx_1.address.evm; + assert_execution_context_equal(ctx_0.calling_context, ctx_1.calling_context); return (); } func assert_execution_context_equal( ctx_0: model.ExecutionContext*, ctx_1: model.ExecutionContext* ) { - let is_context_0_root = ExecutionContext.is_empty(ctx_0.calling_context); - let is_context_1_root = ExecutionContext.is_empty(ctx_1.calling_context); + let is_context_0_root = ExecutionContext.is_empty(ctx_0.call_context.calling_context); + let is_context_1_root = ExecutionContext.is_empty(ctx_1.call_context.calling_context); assert is_context_0_root = is_context_1_root; if (is_context_0_root != FALSE) { return (); @@ -228,12 +223,7 @@ namespace TestHelpers { // TODO: Implement assert_dict_access_equal and finalize this helper once Stack and Memory are stabilized // assert ctx_0.stack = ctx_1.stack; // assert ctx_0.memory = ctx_1.memory; - - assert ctx_0.gas_limit = ctx_1.gas_limit; - assert ctx_0.gas_price = ctx_1.gas_price; - assert ctx_0.starknet_contract_address = ctx_1.starknet_contract_address; - assert ctx_0.evm_contract_address = ctx_1.evm_contract_address; - return assert_execution_context_equal(ctx_0.calling_context, ctx_1.calling_context); + return (); } func print_uint256(val: Uint256) { @@ -281,7 +271,7 @@ namespace TestHelpers { print(f"{ids.execution_context.gas_limit=}") print(f"{ids.execution_context.gas_price}") print(f"{ids.execution_context.starknet_contract_address=}") - print(f"{ids.execution_context.evm_contract_address=}") + print(f"{ids.execution_context.address.evm=}") %} return (); }