Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(levm): add contract creation #990

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions crates/vm/levm/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
*.tar.gz

tests/ef_testcases
1 change: 1 addition & 0 deletions crates/vm/levm/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub enum VMError {
SenderAccountShouldNotHaveBytecode,
SenderBalanceShouldContainTransferValue,
GasPriceIsLowerThanBaseFee,
AddressAlreadyOccuped,
}

pub enum OpcodeSuccess {
Expand Down
4 changes: 3 additions & 1 deletion crates/vm/levm/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ pub fn new_vm_with_ops_addr_bal(bytecode: Bytes, address: Address, balance: U256
// add the account passed by parameter

VM::new(
Address::from_low_u64_be(42),
Some(Address::from_low_u64_be(42)),
address,
Default::default(),
Default::default(),
Expand All @@ -72,5 +72,7 @@ pub fn new_vm_with_ops_addr_bal(bytecode: Bytes, address: Address, balance: U256
Default::default(),
Default::default(),
Default::default(),
Default::default(),
)
.unwrap()
}
258 changes: 207 additions & 51 deletions crates/vm/levm/src/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::{
use ethereum_rust_rlp;
use ethereum_rust_rlp::encode::RLPEncode;
use ethereum_types::H160;
use keccak_hash::keccak;
use keccak_hash::{keccak, keccak256};
use sha3::{Digest, Keccak256};
use std::{
collections::{HashMap, HashSet},
Expand Down Expand Up @@ -200,11 +200,177 @@ pub fn word_to_address(word: U256) -> Address {
Address::from_slice(&bytes[12..])
}

#[allow(clippy::too_many_arguments)]
fn create_transaction(
to: Address,
msg_sender: Address,
value: U256,
calldata: Bytes,
gas_limit: U256,
block_number: U256,
coinbase: Address,
timestamp: U256,
prev_randao: Option<H256>,
chain_id: U256,
base_fee_per_gas: U256,
gas_price: U256,
db: Db,
block_blob_gas_used: Option<U256>,
block_excess_blob_gas: Option<U256>,
tx_blob_hashes: Option<Vec<H256>>,
) -> Result<VM, VMError> {
let bytecode = db.get_account_bytecode(&to);

let initial_call_frame = CallFrame::new(
msg_sender,
to,
to,
None,
bytecode,
value,
calldata.clone(),
false,
gas_limit,
TX_BASE_COST,
0,
);

let env = Environment {
consumed_gas: TX_BASE_COST,
origin: msg_sender,
refunded_gas: U256::zero(),
gas_limit,
block_number,
coinbase,
timestamp,
prev_randao,
chain_id,
base_fee_per_gas,
gas_price,
block_blob_gas_used,
block_excess_blob_gas,
tx_blob_hashes,
};

Ok(VM {
call_frames: vec![initial_call_frame],
db,
env,
accrued_substate: Substate::default(),
})
}

fn new_contract_address(sender: Address, nonce: u64) -> Address {
let mut addr = vec![];
addr.extend_from_slice(&sender.0);
addr.extend_from_slice(&nonce.to_be_bytes());

keccak256(&mut addr);
H160::from_slice(&addr[12..])
}

#[allow(clippy::too_many_arguments)]
fn create_contract(
sender: Address,
secret_key: H256,
db: Db,
value: U256,
calldata: Bytes,
block_number: U256,
coinbase: Address,
timestamp: U256,
prev_randao: Option<H256>,
chain_id: U256,
base_fee_per_gas: U256,
gas_price: U256,
block_blob_gas_used: Option<U256>,
block_excess_blob_gas: Option<U256>,
tx_blob_hashes: Option<Vec<H256>>,
) -> Result<VM, VMError> {
/*
Functionality should be:
1. Check whether caller has enough balance to make a transfer
2. Derive the new contract’s address from the caller’s address (passing in the creator account’s nonce)
3. Create the new contract account using the derived contract address (changing the “world state” StateDB)
4. Transfer the initial Ether endowment from caller to the new contract
5. Set input data as contract’s deploy code, then execute it with EVM. The ret variable is the returned contract code
6. Check for error. Or if the contract code is too big, fail. Charge the user gas then set the contract code
Source: https://medium.com/@hayeah/diving-into-the-ethereum-vm-part-5-the-smart-contract-creation-process-cb7b6133b855
*/
let mut db_copy = db.clone();
let mut sender_account = match db_copy.accounts.get(&sender) {
Some(acc) => acc,
None => {
return Err(VMError::SenderAccountDoesNotExist);
}
}
.clone();

// 1. Check whether caller has enough balance to make a transfer
if sender_account.balance < value {
return Err(VMError::OutOfGas); // Maybe a more personalized error
}

let new_nonce = sender_account.nonce + 1;

// Check for nonce errors?

sender_account.nonce = new_nonce;
sender_account.balance -= value;

// 2. Derive the new contract’s address from the caller’s address (passing in the creator account’s nonce)
let new_address = new_contract_address(sender, sender_account.nonce);
// If address is already in db, there's an error
if db_copy.accounts.contains_key(&new_address) {
return Err(VMError::AddressAlreadyOccuped); // Kinda this
}

// 3. Create the new contract account using the derived contract address (changing the “world state” StateDB)
let contract_address =
Account::new(new_address, value, calldata.clone(), 0, Default::default());
db_copy.add_account(new_address, contract_address.clone());

// 4. Transfer the initial Ether endowment from caller to the new contract

// 5. Set input data as contract’s deploy code, then execute it with EVM. The ret variable is the returned contract code
let code: Bytes = calldata.clone();

// Call the contract
let mut vm = VM::new(
Some(contract_address.address),
sender,
value,
code,
sender_account.balance,
block_number,
coinbase,
timestamp,
prev_randao,
chain_id,
base_fee_per_gas,
gas_price,
db_copy.clone(),
block_blob_gas_used,
block_excess_blob_gas,
tx_blob_hashes,
secret_key,
)?;

let res = vm.transact();

// The ret variable is the returned contract code ?????????
let _contract_code = res.unwrap().output;

//6. Check for error. Or if the contract code is too big, fail. Charge the user gas then set the contract code

Ok(vm)
}

impl VM {
// TODO: Refactor this.
#[allow(clippy::too_many_arguments)]
pub fn new(
to: Address,
to: Option<Address>,
msg_sender: Address,
value: U256,
calldata: Bytes,
Expand All @@ -220,55 +386,45 @@ impl VM {
block_blob_gas_used: Option<U256>,
block_excess_blob_gas: Option<U256>,
tx_blob_hashes: Option<Vec<H256>>,
) -> Self {
// TODO: This handles only CALL transactions.
let bytecode = db.get_account_bytecode(&to);

// TODO: This handles only CALL transactions.
// TODO: Remove this allow when CREATE is implemented.
#[allow(clippy::redundant_locals)]
let to = to;

// TODO: In CALL this is the `to`, in CREATE it is not.
let code_addr = to;

// TODO: this is mostly placeholder
let initial_call_frame = CallFrame::new(
msg_sender,
to,
code_addr,
None,
bytecode,
value,
calldata.clone(),
false,
gas_limit,
TX_BASE_COST,
0,
);

let env = Environment {
consumed_gas: TX_BASE_COST,
origin: msg_sender,
refunded_gas: U256::zero(),
gas_limit,
block_number,
coinbase,
timestamp,
prev_randao,
chain_id,
base_fee_per_gas,
gas_price,
block_blob_gas_used,
block_excess_blob_gas,
tx_blob_hashes,
};

Self {
call_frames: vec![initial_call_frame],
db,
env,
accrued_substate: Substate::default(),
secret_key: H256,
) -> Result<Self, VMError> {
// Maybe this desicion should be made in an upper layer
match to {
Some(add) => create_transaction(
add,
msg_sender,
value,
calldata,
gas_limit,
block_number,
coinbase,
timestamp,
prev_randao,
chain_id,
base_fee_per_gas,
gas_price,
db,
block_blob_gas_used,
block_excess_blob_gas,
tx_blob_hashes,
),
None => create_contract(
msg_sender,
secret_key,
db,
value,
calldata,
block_number,
coinbase,
timestamp,
prev_randao,
chain_id,
base_fee_per_gas,
gas_price,
block_blob_gas_used,
block_excess_blob_gas,
tx_blob_hashes,
),
}
}

Expand Down
Loading
Loading