Skip to content

Commit

Permalink
feat: integrate state (kkrt-labs#517)
Browse files Browse the repository at this point in the history
* feat: use state for account operations

tests: read balance from storage

tests: read_state_from_sn_storage

* fix: extcodehash and tests
fix: use deterministic CA addresses in state tests

* fix: access control fixes

* fix: fix rebase clashes

* fix: tests

* address pr review
  • Loading branch information
enitrat authored Nov 6, 2023
1 parent 963ca30 commit 12d431e
Show file tree
Hide file tree
Showing 16 changed files with 232 additions and 224 deletions.
21 changes: 1 addition & 20 deletions crates/contracts/src/kakarot_core/interface.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,7 @@ trait IKakarotCore<TContractState> {
/// particular EVM address and if so, returns its corresponding Starknet Address.
/// Otherwise, returns 0
fn address_registry(self: @TContractState, evm_address: EthAddress) -> StoredAccountType;
/// Maps an EVM address to a Starknet address
/// Triggerred when deployment of an EOA or CA is successful
fn set_address_registry(
ref self: TContractState, evm_address: EthAddress, account: StoredAccountType
);


/// Gets the nonce associated to a contract account
fn contract_account_nonce(self: @TContractState, evm_address: EthAddress) -> u64;
Expand All @@ -62,11 +58,6 @@ trait IKakarotCore<TContractState> {
/// Deploys an EOA for a particular EVM address
fn deploy_eoa(ref self: TContractState, evm_address: EthAddress) -> ContractAddress;

/// Deploys a Contract Account for a particular EVM address
fn deploy_ca(
ref self: TContractState, evm_address: EthAddress, bytecode: Span<u8>
) -> ContractAddress;

/// View entrypoint into the EVM
/// Performs view calls into the blockchain
/// It cannot modify the state of the chain
Expand Down Expand Up @@ -141,12 +132,6 @@ trait IExtendedKakarotCore<TContractState> {
/// Checks into KakarotCore storage if an EOA or a CA has been deployed for a
/// particular EVM address and if so, returns its corresponding Starknet Address
fn address_registry(self: @TContractState, evm_address: EthAddress) -> StoredAccountType;
/// Maps an EVM address to a Starknet address
/// Triggerred when deployment of an EOA or CA is successful
fn set_address_registry(
ref self: TContractState, evm_address: EthAddress, account: StoredAccountType
);


/// Gets the nonce associated to a contract account
fn contract_account_nonce(self: @TContractState, evm_address: EthAddress) -> u64;
Expand All @@ -171,10 +156,6 @@ trait IExtendedKakarotCore<TContractState> {

/// Deploys an EOA for a particular EVM address
fn deploy_eoa(ref self: TContractState, evm_address: EthAddress) -> ContractAddress;
/// Deploys a Contract Account for a particular EVM address
fn deploy_ca(
ref self: TContractState, evm_address: EthAddress, bytecode: Span<u8>
) -> ContractAddress;

/// View entrypoint into the EVM
/// Performs view calls into the blockchain
Expand Down
48 changes: 27 additions & 21 deletions crates/contracts/src/kakarot_core/kakarot.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -171,19 +171,17 @@ mod KakarotCore {
self.address_registry.read(evm_address)
}

fn set_address_registry(
ref self: ContractState, evm_address: EthAddress, account: StoredAccountType
) {
self.address_registry.write(evm_address, account);
}

fn contract_account_nonce(self: @ContractState, evm_address: EthAddress) -> u64 {
let ca = ContractAccountTrait::at(evm_address).unwrap().unwrap();
let ca = ContractAccountTrait::at(evm_address)
.expect('Fetching CA failed')
.expect('No CA found');
ca.nonce().unwrap()
}

fn account_balance(self: @ContractState, evm_address: EthAddress) -> u256 {
let maybe_account = AccountTrait::account_type_at(evm_address).unwrap();
let maybe_account = AccountTrait::account_type_at(evm_address)
.expect('Fetching account failed');
match maybe_account {
Option::Some(account) => account.balance().unwrap(),
Option::None => 0
Expand All @@ -193,30 +191,30 @@ mod KakarotCore {
fn contract_account_storage_at(
self: @ContractState, evm_address: EthAddress, key: u256
) -> u256 {
let ca = ContractAccountTrait::at(evm_address).unwrap().unwrap();
let ca = ContractAccountTrait::at(evm_address)
.expect('Fetching CA failed')
.expect('No CA found');
ca.storage_at(key).unwrap()
}

fn contract_account_bytecode(self: @ContractState, evm_address: EthAddress) -> Span<u8> {
let ca = ContractAccountTrait::at(evm_address).unwrap().unwrap();
let ca = ContractAccountTrait::at(evm_address)
.expect('Fetching CA failed')
.expect('No CA found');
ca.load_bytecode().unwrap()
}

fn contract_account_false_positive_jumpdest(
self: @ContractState, evm_address: EthAddress, offset: usize
) -> bool {
let ca = ContractAccountTrait::at(evm_address).unwrap().unwrap();
let ca = ContractAccountTrait::at(evm_address)
.expect('Fetching CA failed')
.expect('No CA found');
ca.is_false_positive_jumpdest(offset).unwrap()
}

fn deploy_eoa(ref self: ContractState, evm_address: EthAddress) -> ContractAddress {
EOATrait::deploy(evm_address).unwrap().starknet_address
}

fn deploy_ca(
ref self: ContractState, evm_address: EthAddress, bytecode: Span<u8>
) -> ContractAddress {
ContractAccountTrait::deploy(evm_address, bytecode).unwrap().starknet_address
EOATrait::deploy(evm_address).expect('EOA Deployment failed').starknet_address
}

fn eth_call(
Expand Down Expand Up @@ -264,6 +262,7 @@ mod KakarotCore {
}

fn set_eoa_class_hash(ref self: ContractState, new_class_hash: ClassHash) {
self.ownable.assert_only_owner();
let old_class_hash = self.eoa_class_hash.read();
self.eoa_class_hash.write(new_class_hash);
self.emit(EOAClassHashChange { old_class_hash, new_class_hash });
Expand All @@ -274,6 +273,7 @@ mod KakarotCore {
}

fn set_ca_class_hash(ref self: ContractState, new_class_hash: ClassHash) {
self.ownable.assert_only_owner();
let old_class_hash = self.ca_class_hash.read();
self.ca_class_hash.write(new_class_hash);
self.emit(CAClassHashChange { old_class_hash, new_class_hash });
Expand All @@ -284,6 +284,7 @@ mod KakarotCore {
}

fn set_account_class_hash(ref self: ContractState, new_class_hash: ClassHash) {
self.ownable.assert_only_owner();
let old_class_hash = self.account_class_hash.read();
self.account_class_hash.write(new_class_hash);
self.emit(AccountClassHashChange { old_class_hash, new_class_hash });
Expand All @@ -304,6 +305,14 @@ mod KakarotCore {
true
}

/// Maps an EVM address to a Starknet address
/// Triggerred when deployment of an EOA or CA is successful
fn set_address_registry(
ref self: ContractState, evm_address: EthAddress, account: StoredAccountType
) {
self.address_registry.write(evm_address, account);
}

fn handle_call(
self: @ContractState,
from: Address,
Expand All @@ -316,10 +325,7 @@ mod KakarotCore {
match to {
//TODO we can optimize this by doing this one step later, when we load the account from the state. This way we can avoid loading the account bytecode twice.
Option::Some(to) => {
let bytecode = match AccountTrait::account_type_at(to)? {
Option::Some(account) => account.bytecode()?,
Option::None => Default::default().span(),
};
let bytecode = AccountTrait::fetch_or_create(to)?.code;

let target_starknet_address = self.compute_starknet_address(to);
let to = Address { evm: to, starknet: target_starknet_address };
Expand Down
6 changes: 5 additions & 1 deletion crates/contracts/src/tests/test_kakarot_core.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,8 @@ fn test_kakarot_core_eoa_mapping() {

let another_sn_address: ContractAddress = 0xbeef.try_into().unwrap();

kakarot_core
let mut kakarot_state = KakarotCore::unsafe_new_contract_state();
kakarot_state
.set_address_registry(
test_utils::evm_address(), StoredAccountType::EOA(another_sn_address)
);
Expand Down Expand Up @@ -280,6 +281,7 @@ fn test_contract_account_class_hash() {
assert(class_hash == ContractAccount::TEST_CLASS_HASH.try_into().unwrap(), 'wrong class hash');

let new_class_hash: ClassHash = MockContractUpgradeableV1::TEST_CLASS_HASH.try_into().unwrap();
testing::set_contract_address(test_utils::other_starknet_address());
kakarot_core.set_ca_class_hash(new_class_hash);

assert(kakarot_core.ca_class_hash() == new_class_hash, 'wrong class hash');
Expand All @@ -306,6 +308,7 @@ fn test_account_class_hash() {
);

let new_class_hash: ClassHash = MockContractUpgradeableV1::TEST_CLASS_HASH.try_into().unwrap();
testing::set_contract_address(test_utils::other_starknet_address());
kakarot_core.set_account_class_hash(new_class_hash);

assert(kakarot_core.account_class_hash() == new_class_hash, 'wrong class hash');
Expand Down Expand Up @@ -334,6 +337,7 @@ fn test_eoa_class_hash() {
);

let new_class_hash: ClassHash = MockContractUpgradeableV1::TEST_CLASS_HASH.try_into().unwrap();
testing::set_contract_address(test_utils::other_starknet_address());
kakarot_core.set_eoa_class_hash(new_class_hash);

assert(kakarot_core.eoa_class_hash() == new_class_hash, 'wrong class hash');
Expand Down
1 change: 1 addition & 0 deletions crates/contracts/src/tests/test_utils.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use contracts::kakarot_core::{interface::IExtendedKakarotCoreDispatcher, Kakarot
use contracts::uninitialized_account::{
IUninitializedAccountDispatcher, IUninitializedAccountDispatcherTrait, UninitializedAccount
};

use evm::tests::test_utils::{deploy_fee, ca_address, other_starknet_address, chain_id};
use openzeppelin::token::erc20::ERC20;
use openzeppelin::token::erc20::interface::{IERC20CamelDispatcher, IERC20CamelDispatcherTrait};
Expand Down
6 changes: 1 addition & 5 deletions crates/evm/src/call_helpers.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,7 @@ impl MachineCallHelpersImpl of MachineCallHelpers {

// Case 2: `to` address is not a precompile
// We enter the standard flow
let maybe_account = AccountTrait::account_type_at(call_args.to.evm)?;
let bytecode = match maybe_account {
Option::Some(acc) => acc.bytecode()?,
Option::None => Default::default().span(),
};
let bytecode = self.state.get_account(call_args.to.evm)?.code;

// The caller in the subcontext is the current context's current address
let caller = self.address();
Expand Down
1 change: 0 additions & 1 deletion crates/evm/src/execution.cairo
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use core::result::ResultTrait;
use evm::context::{
CallContext, CallContextTrait, ExecutionContext, ExecutionContextType, ExecutionContextTrait,
Status
Expand Down
9 changes: 4 additions & 5 deletions crates/evm/src/instructions/block_information.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use evm::machine::{Machine, MachineCurrentContextTrait};
use evm::model::Account;
use evm::model::account::AccountTrait;
use evm::stack::StackTrait;
use evm::state::StateTrait;

// Corelib imports
use starknet::info::{get_block_number, get_block_timestamp, get_block_info};
Expand Down Expand Up @@ -103,11 +104,9 @@ impl BlockInformation of BlockInformationTrait {
/// Get balance of currently executing contract
/// # Specification: https://www.evm.codes/#47?fork=shanghai
fn exec_selfbalance(ref self: Machine) -> Result<(), EVMError> {
let maybe_account = AccountTrait::account_type_at(self.address().evm)?;
let balance: u256 = match maybe_account {
Option::Some(acc) => acc.balance()?,
Option::None => 0
};
let evm_address = self.address().evm;

let balance = self.state.read_balance(evm_address)?;

self.stack.push(balance)
}
Expand Down
98 changes: 28 additions & 70 deletions crates/evm/src/instructions/environmental_information.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ use evm::context::ExecutionContextTrait;
use evm::errors::{EVMError, RETURNDATA_OUT_OF_BOUNDS_ERROR, READ_SYSCALL_FAILED};
use evm::machine::{Machine, MachineCurrentContextTrait};
use evm::memory::MemoryTrait;
use evm::model::account::{AccountTrait};
use evm::model::{AccountType, ContractAccountTrait};
use evm::model::AccountTrait;
use evm::stack::StackTrait;
use evm::state::StateTrait;
use integer::u32_as_non_zero;
use integer::u32_overflowing_add;
use keccak::cairo_keccak;
Expand Down Expand Up @@ -35,13 +35,9 @@ impl EnvironmentInformationImpl of EnvironmentInformationTrait {
fn exec_balance(ref self: Machine) -> Result<(), EVMError> {
let evm_address = self.stack.pop_eth_address()?;

let maybe_account = AccountTrait::account_type_at(evm_address)?;
let balance: u256 = match maybe_account {
Option::Some(acc) => acc.balance()?,
Option::None => 0
};
let balance = self.state.read_balance(evm_address)?;

return self.stack.push(balance);
self.stack.push(balance)
}

/// 0x32 - ORIGIN
Expand Down Expand Up @@ -173,19 +169,8 @@ impl EnvironmentInformationImpl of EnvironmentInformationTrait {
fn exec_extcodesize(ref self: Machine) -> Result<(), EVMError> {
let evm_address = self.stack.pop_eth_address()?;

let maybe_account = AccountTrait::account_type_at(evm_address)?;
let account_type = match maybe_account {
Option::Some(account) => account,
Option::None => { return self.stack.push(0); },
};

match account_type {
AccountType::EOA(eoa) => { return self.stack.push(0); },
AccountType::ContractAccount(ca) => {
let mut bytecode = ca.load_bytecode()?;
self.stack.push(bytecode.len().into())
}
}
let account = self.state.get_account(evm_address)?;
self.stack.push(account.code.len().into())
}

/// 0x3C - EXTCODECOPY
Expand All @@ -197,35 +182,15 @@ impl EnvironmentInformationImpl of EnvironmentInformationTrait {
let offset = self.stack.pop_usize()?;
let size = self.stack.pop_usize()?;

let maybe_account = AccountTrait::account_type_at(evm_address)?;
let account_type = match maybe_account {
Option::Some(account) => account,
Option::None => {
self.memory.store_padded_segment(dest_offset, size, Default::default().span());
return Result::Ok(());
},
let bytecode = self.state.get_account(evm_address)?.code;
let bytecode_len = bytecode.len();
let bytecode_slice = if offset < bytecode_len {
bytecode.slice(offset, bytecode_len - offset)
} else {
Default::default().span()
};

match account_type {
AccountType::EOA(eoa) => {
self.memory.store_padded_segment(dest_offset, size, Default::default().span());
return Result::Ok(());
},
AccountType::ContractAccount(ca) => {
let mut bytecode = ca.load_bytecode()?;
// `cairo_keccak` takes in an array of little-endian u64s

let bytecode_len = bytecode.len();
let bytecode_slice = if offset < bytecode_len {
bytecode.slice(offset, bytecode_len - offset)
} else {
Default::default().span()
};

self.memory.store_padded_segment(dest_offset, size, bytecode_slice);
Result::Ok(())
}
}
self.memory.store_padded_segment(dest_offset, size, bytecode_slice);
Result::Ok(())
}

/// 0x3D - RETURNDATASIZE
Expand Down Expand Up @@ -273,28 +238,21 @@ impl EnvironmentInformationImpl of EnvironmentInformationTrait {
fn exec_extcodehash(ref self: Machine) -> Result<(), EVMError> {
let evm_address = self.stack.pop_eth_address()?;

let maybe_account = AccountTrait::account_type_at(evm_address)?;
let account_type = match maybe_account {
Option::Some(account) => account,
Option::None => { return self.stack.push(0); },
};

match account_type {
AccountType::EOA(eoa) => { return self.stack.push(EMPTY_KECCAK); },
AccountType::ContractAccount(ca) => {
let mut bytecode = ca.load_bytecode()?;
if bytecode.is_empty() {
return self.stack.push(EMPTY_KECCAK);
}

let mut bytecode: ByteArray = ByteArrayExTrait::from_bytes(bytecode);
let account = self.state.get_account(evm_address)?;
if (account.is_precompile() || (account.account_type.is_ca() && account.nonce == 0)) {
return self.stack.push(0);
}
let bytecode = account.code;

// `cairo_keccak` takes in an array of little-endian u64s
let (mut keccak_input, last_input_word, last_input_num_bytes) = bytecode
.to_u64_words();
let hash = cairo_keccak(ref keccak_input, :last_input_word, :last_input_num_bytes);
self.stack.push(hash.reverse_endianness())
}
if bytecode.is_empty() {
return self.stack.push(EMPTY_KECCAK);
}
//TODO optimize this by avoiding a ByteArray -> Span<u64> conversion
let mut bytecode: ByteArray = ByteArrayExTrait::from_bytes(bytecode);

// `cairo_keccak` takes in an array of little-endian u64s
let (mut keccak_input, last_input_word, last_input_num_bytes) = bytecode.to_u64_words();
let hash = cairo_keccak(ref keccak_input, :last_input_word, :last_input_num_bytes);
self.stack.push(hash.reverse_endianness())
}
}
Loading

0 comments on commit 12d431e

Please sign in to comment.