Skip to content

Commit

Permalink
dev: deploy empty contract account (#591)
Browse files Browse the repository at this point in the history
* remove skipped tests

* split account creation and writing to starknet

* update runner

* log revert reason if any

* update print of reversion reason
  • Loading branch information
greged93 authored Nov 27, 2023
1 parent ba715e7 commit 08637cb
Show file tree
Hide file tree
Showing 6 changed files with 229 additions and 119 deletions.
23 changes: 0 additions & 23 deletions crates/build-utils/src/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,16 +100,6 @@ mod tests {
use super::*;
use std::path::Path;

#[test]
#[ignore]
fn test_filter_file() {
let filter = Filter::load_file("../../blockchain-tests-skip.yml").unwrap();
let path = PathWrapper::from(Path::new(
"../../ef-testing/ethereum-tests/BlockchainTests/GeneralStateTests/stCallCreateCallCodeTest/Call1024PreCalls.json",
).to_path_buf());
assert!(filter.is_skipped(&path, None));
}

#[test]
fn test_filter_regex() {
let filter = Filter::load_file("../../blockchain-tests-skip.yml").unwrap();
Expand All @@ -119,19 +109,6 @@ mod tests {
assert!(filter.is_skipped(&path, None));
}

#[test]
#[ignore]
fn test_filter_test() {
let filter = Filter::load_file("../../blockchain-tests-skip.yml").unwrap();
let path = PathWrapper::from(Path::new(
"../../ef-testing/ethereum-tests/BlockchainTests/GeneralStateTests/stTransactionTest/Opcodes_TransactionInit.json",
).to_path_buf());
assert!(filter.is_skipped(
&path,
Some("Opcodes_TransactionInit_d111g0v0_Shanghai".to_string())
));
}

#[test]
fn test_map_diff() {
// Given
Expand Down
126 changes: 126 additions & 0 deletions crates/ef-testing/src/evm_sequencer/account.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
use blockifier::abi::abi_utils::{get_storage_var_address, get_uint256_storage_var_addresses};
use reth_primitives::{Address, Bytes};
use revm_primitives::U256;
use starknet_api::{
core::{ContractAddress, Nonce},
hash::StarkFelt,
state::StorageKey,
StarknetApiError,
};

use crate::evm_sequencer::utils::split_u256;

use super::{
evm_state::KakarotConfig,
types::FeltSequencer,
utils::{compute_starknet_address, split_bytecode_to_starkfelt},
};

pub struct KakarotAccount {
pub(crate) starknet_address: ContractAddress,
pub(crate) evm_address: StarkFelt,
pub(crate) nonce: Nonce,
pub(crate) storage: Vec<(StorageKey, StarkFelt)>,
pub(crate) account_type: AccountType,
}

pub enum AccountType {
EOA,
Contract,
}

impl KakarotAccount {
pub fn new(
kakarot_config: &KakarotConfig,
evm_address: &Address,
code: &Bytes,
nonce: U256,
evm_storage: Vec<(U256, U256)>,
) -> Result<Self, StarknetApiError> {
let nonce = StarkFelt::from(TryInto::<u128>::try_into(nonce).map_err(|err| {
StarknetApiError::OutOfRange {
string: err.to_string(),
}
})?);

let starknet_address = compute_starknet_address(evm_address);
let starknet_address = ContractAddress::try_from(starknet_address)?;

let evm_address = TryInto::<FeltSequencer>::try_into(*evm_address)
.unwrap()
.into(); // infallible

let mut storage = vec![
(get_storage_var_address("evm_address", &[]), evm_address),
(
get_storage_var_address("is_initialized_", &[]),
StarkFelt::from(1u8),
),
(
get_storage_var_address("Ownable_owner", &[]),
kakarot_config.address,
),
(
get_storage_var_address("bytecode_len_", &[]),
StarkFelt::from(code.len() as u32),
),
(
get_storage_var_address("kakarot_address", &[]),
kakarot_config.address,
),
];

// Initialize the implementation and nonce based on account type.
// The account is an EOA if it has no bytecode and no storage (or all storage is zero).
let has_code_or_storage = !code.is_empty() || evm_storage.iter().any(|x| x.1 != U256::ZERO);
let account_type = if !has_code_or_storage {
storage.push((
get_storage_var_address("_implementation", &[]),
kakarot_config.eoa_class_hash,
));
AccountType::EOA
} else {
storage.append(&mut vec![
(get_storage_var_address("nonce", &[]), nonce),
(
get_storage_var_address("_implementation", &[]),
kakarot_config.contract_account_class_hash,
),
]);
AccountType::Contract
};

// Initialize the bytecode storage var.
let bytecode_storage = &mut split_bytecode_to_starkfelt(code)
.into_iter()
.enumerate()
.map(|(i, bytes)| {
(
get_storage_var_address("bytecode_", &[StarkFelt::from(i as u32)]),
bytes,
)
})
.collect();
storage.append(bytecode_storage);

// Initialize the storage vars.
let mut evm_storage_storage: Vec<(StorageKey, StarkFelt)> = evm_storage
.iter()
.flat_map(|(k, v)| {
let keys = split_u256(*k).map(Into::into);
let values = split_u256(*v).map(Into::<StarkFelt>::into);
let keys = get_uint256_storage_var_addresses("storage_", &keys).unwrap(); // safe unwrap: all vars are ASCII
vec![(keys.0, values[0]), (keys.1, values[1])]
})
.collect();
storage.append(&mut evm_storage_storage);

Ok(Self {
account_type,
storage,
starknet_address,
evm_address,
nonce: Nonce(nonce),
})
}
}
143 changes: 50 additions & 93 deletions crates/ef-testing/src/evm_sequencer/evm_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,37 @@ use blockifier::state::state_api::{State, StateReader, StateResult};
use reth_primitives::{Address, Bytes};
use revm_primitives::U256;
use starknet::core::types::FieldElement;
use starknet_api::core::Nonce;
use starknet_api::hash::StarkFelt;
use starknet_api::state::StorageKey;
use starknet_api::StarknetApiError;

use super::account::{AccountType, KakarotAccount};
use super::constants::{
CONTRACT_ACCOUNT_CLASS_HASH, EOA_CLASS_HASH, ETH_FEE_TOKEN_ADDRESS, KAKAROT_ADDRESS,
PROXY_CLASS_HASH,
};
use super::types::FeltSequencer;
use super::utils::{
compute_starknet_address, high_16_bytes_of_felt_to_bytes, split_bytecode_to_starkfelt,
split_u256,
};
use super::utils::{compute_starknet_address, high_16_bytes_of_felt_to_bytes, split_u256};
use super::KakarotSequencer;

pub struct KakarotConfig {
pub(crate) address: StarkFelt,
pub(crate) eoa_class_hash: StarkFelt,
pub(crate) contract_account_class_hash: StarkFelt,
}

impl Default for KakarotConfig {
fn default() -> Self {
Self {
address: *KAKAROT_ADDRESS.0.key(),
eoa_class_hash: EOA_CLASS_HASH.0,
contract_account_class_hash: CONTRACT_ACCOUNT_CLASS_HASH.0,
}
}
}

/// EVM state interface. Used to setup EOA and contract accounts,
/// fund them and get their state (balance, nonce, code, storage).
pub trait EvmState {
fn setup_account(
&mut self,
evm_address: &Address,
bytecode: &Bytes,
nonce: U256,
storage: Vec<(U256, U256)>,
) -> StateResult<()>;
fn setup_account(&mut self, account: KakarotAccount) -> StateResult<()>;

fn fund(&mut self, evm_address: &Address, balance: U256) -> StateResult<()>;

Expand All @@ -45,86 +50,25 @@ pub trait EvmState {

impl EvmState for KakarotSequencer {
/// Sets up an EOA or contract account. Writes nonce, code and storage to the sequencer storage.
fn setup_account(
&mut self,
evm_address: &Address,
bytecode: &Bytes,
nonce: U256,
evm_storage: Vec<(U256, U256)>,
) -> StateResult<()> {
let nonce = StarkFelt::from(TryInto::<u128>::try_into(nonce).map_err(|err| {
StarknetApiError::OutOfRange {
string: err.to_string(),
}
})?);
let starknet_address = compute_starknet_address(evm_address);
let evm_address = TryInto::<FeltSequencer>::try_into(*evm_address)
.unwrap()
.into(); // infallible

let mut storage = vec![
(("evm_address", vec![]), evm_address),
(("is_initialized_", vec![]), StarkFelt::from(1u8)),
(("Ownable_owner", vec![]), *KAKAROT_ADDRESS.0.key()),
(
("bytecode_len_", vec![]),
StarkFelt::from(bytecode.len() as u32),
),
(("kakarot_address", vec![]), *KAKAROT_ADDRESS.0.key()),
];

let starknet_address = starknet_address.try_into()?;
// Initialize the implementation and nonce based on account type.
// The account is an EOA if it has no bytecode and no storage (or all storage is zero).
if bytecode.is_empty() && evm_storage.iter().all(|x| x.1 == U256::ZERO) {
storage.push((("_implementation", vec![]), EOA_CLASS_HASH.0));
self.0.state.set_nonce(starknet_address, Nonce(nonce));
} else {
storage.append(&mut vec![
(("nonce", vec![]), nonce),
(("_implementation", vec![]), CONTRACT_ACCOUNT_CLASS_HASH.0),
]);
fn setup_account(&mut self, account: KakarotAccount) -> StateResult<()> {
if matches!(account.account_type, AccountType::EOA) {
self.0
.state
.set_nonce(account.starknet_address, account.nonce);
}

// Initialize the bytecode storage var.
let bytecode_storage = &mut split_bytecode_to_starkfelt(bytecode)
.into_iter()
.enumerate()
.map(|(i, bytes)| (("bytecode_", vec![StarkFelt::from(i as u32)]), bytes))
.collect();
storage.append(bytecode_storage);

// Initialize the storage vars.
let evm_storage_storage: Vec<(StorageKey, StarkFelt)> = evm_storage
.iter()
.flat_map(|(k, v)| {
let keys = split_u256(*k).map(Into::into);
let values = split_u256(*v).map(Into::<StarkFelt>::into);
let keys = get_uint256_storage_var_addresses("storage_", &keys).unwrap(); // safe unwrap: all vars are ASCII
vec![(keys.0, values[0]), (keys.1, values[1])]
})
.collect();
for (k, v) in evm_storage_storage {
(&mut self.0.state).set_storage_at(starknet_address, k, v);
}

// Write all the storage vars to the sequencer state.
for ((var, keys), v) in storage {
(&mut self.0.state).set_storage_at(
starknet_address,
get_storage_var_address(var, &keys),
v,
);
for (k, v) in account.storage {
(&mut self.0.state).set_storage_at(account.starknet_address, k, v);
}

// Set up the contract class hash.
(&mut self.0.state).set_class_hash_at(starknet_address, *PROXY_CLASS_HASH)?;
(&mut self.0.state).set_class_hash_at(account.starknet_address, *PROXY_CLASS_HASH)?;

// Add the address to the Kakarot evm to starknet mapping
(&mut self.0.state).set_storage_at(
*KAKAROT_ADDRESS,
get_storage_var_address("evm_to_starknet_address", &[evm_address]),
*starknet_address.0.key(),
get_storage_var_address("evm_to_starknet_address", &[account.evm_address]),
*account.starknet_address.0.key(),
);
Ok(())
}
Expand Down Expand Up @@ -309,16 +253,29 @@ mod tests {
let transaction = BroadcastedTransactionWrapper::new(transaction)
.try_into_execution_transaction(FieldElement::from(*CHAIN_ID))
.unwrap();

// When
let kakarot_config = KakarotConfig::default();
let bytecode = Bytes::from(vec![96, 1, 96, 0, 85]); // PUSH 01 PUSH 00 SSTORE
let nonce = U256::from(0);
sequencer
.setup_account(&TEST_CONTRACT_ADDRESS, &bytecode, nonce, vec![])
.unwrap();
sequencer
.setup_account(&PUBLIC_KEY, &Bytes::default(), U256::from(0), vec![])
.unwrap();

// When
let contract = KakarotAccount::new(
&kakarot_config,
&TEST_CONTRACT_ADDRESS,
&bytecode,
nonce,
vec![],
)
.unwrap();
let eoa = KakarotAccount::new(
&kakarot_config,
&PUBLIC_KEY,
&Bytes::default(),
nonce,
vec![],
)
.unwrap();
sequencer.setup_account(contract).unwrap();
sequencer.setup_account(eoa).unwrap();
sequencer.0.execute(transaction).unwrap();

// Then
Expand Down
1 change: 1 addition & 0 deletions crates/ef-testing/src/evm_sequencer/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod account;
pub mod constants;
pub mod evm_state;
pub mod types;
Expand Down
10 changes: 8 additions & 2 deletions crates/ef-testing/src/models/case.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
use super::{error::RunnerError, result::log_execution_result};
use crate::{
evm_sequencer::{
constants::CHAIN_ID, evm_state::EvmState, utils::to_broadcasted_starknet_transaction,
account::KakarotAccount,
constants::CHAIN_ID,
evm_state::{EvmState, KakarotConfig},
utils::to_broadcasted_starknet_transaction,
KakarotSequencer,
},
get_signed_rlp_encoded_transaction,
Expand Down Expand Up @@ -52,13 +55,16 @@ impl BlockchainTestCase {
}

fn handle_pre_state(&self, sequencer: &mut KakarotSequencer) -> Result<(), RunnerError> {
let kakarot_config = KakarotConfig::default();
for (address, account) in self.pre.iter() {
sequencer.setup_account(
let kakarot_account = KakarotAccount::new(
&kakarot_config,
address,
&account.code,
account.nonce.0,
account.storage.iter().map(|(k, v)| (k.0, v.0)).collect(),
)?;
sequencer.setup_account(kakarot_account)?;
sequencer.fund(address, account.balance.0)?;
}

Expand Down
Loading

0 comments on commit 08637cb

Please sign in to comment.