Skip to content

Commit

Permalink
feat: contract account bytecode storage (#425)
Browse files Browse the repository at this point in the history
* feat: storage of bytecode (full bytes31 words)

* fix: renamed core_contracts to contracts
  • Loading branch information
enitrat authored Oct 17, 2023
1 parent 65fbf7d commit 772d2e0
Show file tree
Hide file tree
Showing 18 changed files with 413 additions and 20 deletions.
2 changes: 2 additions & 0 deletions crates/contracts/Scarb.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ version = "0.1.0"
starknet.workspace = true
evm = { path = "../evm" }
openzeppelin = { path = "../openzeppelin" }
utils = { path = "../utils" }
alexandria_storage = { git = "https://github.com/keep-starknet-strange/alexandria.git", rev = "3356bf0c5c1a089167d7d3c28d543e195325e596" }

[tool.fmt]
sort-module-level-items = true
48 changes: 48 additions & 0 deletions crates/contracts/src/contract_account.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//! Contract Account related functions, including bytecode storage

use alexandria_storage::list::{List, ListTrait};
use hash::{HashStateTrait, HashStateExTrait};
use poseidon::PoseidonTrait;
use starknet::{
StorageBaseAddress, storage_base_address_from_felt252, Store, EthAddress, SyscallResult,
storage_write_syscall, storage_address_from_base, storage_read_syscall,
storage_address_from_base_and_offset
};
use utils::helpers::{ByteArrayExTrait};
use utils::storage::{compute_storage_base_address};
use utils::traits::{StorageBaseAddressIntoFelt252, StoreBytes31};

/// Stores the EVM bytecode of a contract account in Kakarot Core's contract storage. The bytecode is first packed
/// into a ByteArray and then stored in the contract storage.
/// # Arguments
/// * `evm_address` - The EVM address of the contract account
/// * `bytecode` - The bytecode to store
fn store_bytecode(evm_address: EthAddress, bytecode: Span<u8>) {
let packed_bytecode: ByteArray = ByteArrayExTrait::from_bytes(bytecode);
// data_address is h(h(sn_keccak("contract_account_bytecode")), evm_address)
let data_address = compute_storage_base_address(
selector!("contract_account_bytecode"), array![evm_address.into()].span()
);
// We start storing the full 31-byte words of bytecode data at address
// `data_address`. The `pending_word` and `pending_word_len` are stored at
// address `data_address-2` and `data_address-1` respectively.
//TODO(eni) replace with ListTrait::new() once merged in alexandria
let mut stored_list: List<bytes31> = List {
address_domain: 0, base: data_address, len: 0, storage_size: Store::<bytes31>::size()
};
let pending_word_addr: felt252 = data_address.into() - 2;
let pending_word_len_addr: felt252 = pending_word_addr + 1;

// Store the `ByteArray` in the contract storage.
Store::<
felt252
>::write(0, storage_base_address_from_felt252(pending_word_addr), packed_bytecode.pending_word);
Store::<
usize
>::write(
0,
storage_base_address_from_felt252(pending_word_len_addr),
packed_bytecode.pending_word_len
);
stored_list.from_span(packed_bytecode.data.span());
}
3 changes: 3 additions & 0 deletions crates/contracts/src/lib.cairo
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
mod components;

// Contract account-related module
mod contract_account;

// Kakarot smart contract
mod kakarot_core;

Expand Down
3 changes: 3 additions & 0 deletions crates/contracts/src/tests.cairo
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#[cfg(test)]
mod test_contract_account;
#[cfg(test)]
mod test_kakarot_core;

#[cfg(test)]
mod test_ownable;

Expand Down
169 changes: 169 additions & 0 deletions crates/contracts/src/tests/test_contract_account.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
use alexandria_storage::list::{List, ListTrait};
use contracts::contract_account::{store_bytecode};
use contracts::tests::utils::constants::EVM_ADDRESS;
use starknet::{storage_base_address_from_felt252, Store};
use utils::storage::{compute_storage_base_address};
use utils::traits::{StoreBytes31, StorageBaseAddressIntoFelt252};

#[test]
#[available_gas(20000000)]
fn test_store_bytecode_word_not_full() {
let byte_array: Array<u8> = array![0x01, 0x02, 0x03, // 3 elements
];
let evm_address = EVM_ADDRESS();
store_bytecode(evm_address, byte_array.span());

// Address at which the bytecode should be stored
let data_addr = compute_storage_base_address(
selector!("contract_account_bytecode"), array![evm_address.into()].span()
);
let pending_word_addr = storage_base_address_from_felt252(data_addr.into() - 2_felt252);
let pending_word_len_addr = storage_base_address_from_felt252(data_addr.into() - 1_felt252);

let pending_word = Store::<felt252>::read(0, pending_word_addr).unwrap();
let pending_word_len = Store::<u32>::read(0, pending_word_len_addr).unwrap();
let list: List<bytes31> = Store::<List<bytes31>>::read(0, data_addr).unwrap();
let bytecode: ByteArray = ByteArray {
data: list.array(), pending_word: pending_word, pending_word_len: pending_word_len
};

assert(bytecode.pending_word_len == 3, 'pending word not 3');
assert(bytecode.pending_word == 0x010203, 'pending word not restituted');
assert(bytecode.data.len() == 0, 'data not empty');
}


#[test]
#[available_gas(20000000)]
fn test_store_bytecode_one_word() {
let byte_array: Array<u8> = array![
0x01,
0x02,
0x03,
0x04,
0x05,
0x06,
0x07,
0x08,
0x09,
0x0A,
0x0B,
0x0C,
0x0D,
0x0E,
0x0F,
0x10,
0x11,
0x12,
0x13,
0x14,
0x15,
0x16,
0x17,
0x18,
0x19,
0x1A,
0x1B,
0x1C,
0x1D,
0x1E,
0x1F, // 31 elements
];
let evm_address = EVM_ADDRESS();
store_bytecode(evm_address, byte_array.span());

// Address at which the bytecode should be stored
let data_addr = compute_storage_base_address(
selector!("contract_account_bytecode"), array![evm_address.into()].span()
);
let pending_word_addr = storage_base_address_from_felt252(data_addr.into() - 2_felt252);
let pending_word_len_addr = storage_base_address_from_felt252(data_addr.into() - 1_felt252);

let pending_word = Store::<felt252>::read(0, pending_word_addr).unwrap();
let pending_word_len = Store::<u32>::read(0, pending_word_len_addr).unwrap();
let list: List<bytes31> = Store::<List<bytes31>>::read(0, data_addr).unwrap();
let bytecode: ByteArray = ByteArray {
data: list.array(), pending_word: pending_word, pending_word_len: pending_word_len
};

assert(bytecode.pending_word_len == 0, 'pending word len not empty');
assert(bytecode.pending_word == 0, 'pending word not empty');
let mut i: u32 = 0;
loop {
if i == byte_array.len() {
break;
}
assert(bytecode[i] == *byte_array[i], 'stored bytecode error');
i += 1;
}
}

#[test]
#[available_gas(20000000)]
fn test_store_bytecode_one_word_pending() {
let byte_array: Array<u8> = array![
0x01,
0x02,
0x03,
0x04,
0x05,
0x06,
0x07,
0x08,
0x09,
0x0A,
0x0B,
0x0C,
0x0D,
0x0E,
0x0F,
0x10,
0x11,
0x12,
0x13,
0x14,
0x15,
0x16,
0x17,
0x18,
0x19,
0x1A,
0x1B,
0x1C,
0x1D,
0x1E,
0x1F,
0x20,
0x21 // 33 elements
];
let evm_address = EVM_ADDRESS();
store_bytecode(evm_address, byte_array.span());

// Address at which the bytecode should be stored
let data_addr = compute_storage_base_address(
selector!("contract_account_bytecode"), array![evm_address.into()].span()
);
let pending_word_addr = storage_base_address_from_felt252(data_addr.into() - 2_felt252);
let pending_word_len_addr = storage_base_address_from_felt252(data_addr.into() - 1_felt252);

let pending_word = Store::<felt252>::read(0, pending_word_addr).unwrap();
let pending_word_len = Store::<u32>::read(0, pending_word_len_addr).unwrap();
let list: List<bytes31> = Store::<List<bytes31>>::read(0, data_addr).unwrap();
let bytecode: ByteArray = ByteArray {
data: list.array(), pending_word: pending_word, pending_word_len: pending_word_len
};

assert(bytecode.pending_word_len == 2, 'pending word len not two');
assert(bytecode.pending_word == 0x2021, 'pending word not restituted');
let mut i: u32 = 0;
loop {
if i == byte_array.len() {
break;
}
assert(bytecode[i] == *byte_array[i], 'stored bytecode error');
i += 1;
}
}
//TODO add a test with huge amount of bytecode - using SNFoundry and loading data from txt


6 changes: 5 additions & 1 deletion crates/contracts/src/tests/utils.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ fn assert_no_events_left(address: ContractAddress) {
}

mod constants {
use starknet::{testing, contract_address_const, ContractAddress};
use starknet::{EthAddress, testing, contract_address_const, ContractAddress};
fn ZERO() -> ContractAddress {
contract_address_const::<0>()
}
Expand All @@ -49,6 +49,10 @@ mod constants {
contract_address_const::<0xe1145>()
}

fn EVM_ADDRESS() -> EthAddress {
0xc0ffee.try_into().unwrap()
}

fn ETH_BANK() -> ContractAddress {
contract_address_const::<0x777>()
}
Expand Down
2 changes: 1 addition & 1 deletion crates/evm/src/context.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use evm::model::Event;
use evm::stack::{Stack, StackTrait};
use starknet::get_caller_address;
use starknet::{EthAddress, ContractAddress};
use utils::helpers::{ArrayExtension, ArrayExtensionTrait};
use utils::helpers::{ArrayExtension, ArrayExtTrait};
use utils::traits::{SpanDefault, EthAddressDefault, ContractAddressDefault};

#[derive(Drop, Default, Copy, PartialEq)]
Expand Down
2 changes: 1 addition & 1 deletion crates/evm/src/instructions/sha3.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use evm::machine::Machine;
use evm::memory::MemoryTrait;
use evm::stack::StackTrait;
use keccak::{cairo_keccak, u128_split};
use utils::helpers::{ArrayExtensionTrait, U256Trait};
use utils::helpers::{ArrayExtTrait, U256Trait};

#[generate_trait]
impl Sha3Impl of Sha3Trait {
Expand Down
2 changes: 1 addition & 1 deletion crates/evm/src/memory.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use utils::constants::{
POW_2_72, POW_2_80, POW_2_88, POW_2_96, POW_2_104, POW_2_112, POW_2_120, POW_256_16
};
use utils::{
helpers, helpers::SpanExtensionTrait, helpers::ArrayExtensionTrait, math::Exponentiation,
helpers, helpers::SpanExtTrait, helpers::ArrayExtTrait, math::Exponentiation,
math::WrappingExponentiation, math::Bitshift
};

Expand Down
2 changes: 1 addition & 1 deletion crates/evm/src/storage_journal.cairo
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use nullable::{match_nullable, FromNullableResult};
use starknet::{StorageBaseAddress, Store, storage_base_address_from_felt252};
use utils::helpers::ArrayExtensionTrait;
use utils::helpers::ArrayExtTrait;
use utils::traits::{StorageBaseAddressPartialEq, StorageBaseAddressIntoFelt252};
/// The Journal tracks the changes applied to storage during the execution of a transaction.
/// Local changes tracks the changes applied inside a single execution context.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ use openzeppelin::token::erc20::interface::IERC20CamelDispatcherTrait;

use starknet::{EthAddressIntoFelt252, contract_address_const, testing::set_contract_address};
use utils::helpers::{
u256_to_bytes_array, load_word, ArrayExtension, ArrayExtensionTrait, SpanExtension,
SpanExtensionTrait
u256_to_bytes_array, load_word, ArrayExtension, ArrayExtTrait, SpanExtension, SpanExtTrait
};
use utils::traits::{EthAddressIntoU256};

Expand Down Expand Up @@ -706,9 +705,9 @@ fn test_returndata_copy(dest_offset: u32, offset: u32, mut size: u32) {
let result_span = u256_to_bytes_array(result).span();

if ((i + 1) * 32 > size) {
ArrayExtensionTrait::concat(ref results, result_span.slice(0, size - (i * 32)));
ArrayExtTrait::concat(ref results, result_span.slice(0, size - (i * 32)));
} else {
ArrayExtensionTrait::concat(ref results, result_span);
ArrayExtTrait::concat(ref results, result_span);
}

i += 1;
Expand Down
4 changes: 1 addition & 3 deletions crates/evm/src/tests/test_memory.cairo
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
use evm::memory::{MemoryTrait, InternalMemoryTrait, MemoryPrintTrait};
use integer::BoundedInt;
use utils::constants::{POW_2_8, POW_2_56, POW_2_64, POW_2_120};
use utils::{
math::Exponentiation, math::WrappingExponentiation, helpers, helpers::SpanExtensionTrait
};
use utils::{math::Exponentiation, math::WrappingExponentiation, helpers, helpers::SpanExtTrait};

mod internal {
use evm::memory::{MemoryTrait, InternalMemoryTrait, MemoryPrintTrait};
Expand Down
24 changes: 20 additions & 4 deletions crates/utils/src/helpers.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -335,8 +335,8 @@ fn pow2(pow: usize) -> u128 {
/// Splits a u256 into `len` bytes, big-endian, and appends the result to `dst`.
fn split_word(mut value: u256, mut len: usize, ref dst: Array<u8>) {
let word_le = split_word_le(value, len);
let word_be = ArrayExtensionTrait::reverse(word_le.span());
ArrayExtensionTrait::concat(ref dst, word_be.span());
let word_be = ArrayExtTrait::reverse(word_le.span());
ArrayExtTrait::concat(ref dst, word_be.span());
}

fn split_u128_le(ref dest: Array<u8>, mut value: u128, mut len: usize) {
Expand Down Expand Up @@ -442,7 +442,7 @@ fn u256_to_bytes_array(mut value: u256) -> Array<u8> {
}

#[generate_trait]
impl ArrayExtension<T, impl TCopy: Copy<T>, impl TDrop: Drop<T>> of ArrayExtensionTrait<T> {
impl ArrayExtension<T, impl TCopy: Copy<T>, impl TDrop: Drop<T>> of ArrayExtTrait<T> {
// Concatenates two arrays by adding the elements of arr2 to arr1.
fn concat(ref self: Array<T>, mut arr2: Span<T>) {
loop {
Expand Down Expand Up @@ -490,7 +490,7 @@ impl ArrayExtension<T, impl TCopy: Copy<T>, impl TDrop: Drop<T>> of ArrayExtensi
}

#[generate_trait]
impl SpanExtension<T, +Copy<T>, +Drop<T>> of SpanExtensionTrait<T> {
impl SpanExtension<T, +Copy<T>, +Drop<T>> of SpanExtTrait<T> {
// Returns true if the array contains an item.
fn contains<+PartialEq<T>>(mut self: Span<T>, value: T) -> bool {
loop {
Expand Down Expand Up @@ -523,3 +523,19 @@ impl U256Impl of U256Trait {
}
}

#[generate_trait]
impl ByteArrayExt of ByteArrayExTrait {
fn from_bytes(mut bytes: Span<u8>) -> ByteArray {
//TODO(eni): optimize deserialization of Span<u8> to ByteArray;
// we can just deserialize bytes by chunks of 31, skipping pending_word
// checks
let mut arr: ByteArray = Default::default();
loop {
match bytes.pop_front() {
Option::Some(byte) => { arr.append_byte(*byte); },
Option::None => { break; }
}
};
arr
}
}
1 change: 1 addition & 0 deletions crates/utils/src/lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mod eth_transaction;
mod rlp;
mod traits;
mod i256;
mod storage;

#[cfg(test)]
mod tests;
Loading

0 comments on commit 772d2e0

Please sign in to comment.