From d794db435d0a3abf10d9d68f357c2e40d5fd453f Mon Sep 17 00:00:00 2001 From: maximopalopoli Date: Fri, 25 Oct 2024 18:49:25 -0300 Subject: [PATCH 1/9] Handle Option Address in to: (new) --- crates/vm/levm/src/vm.rs | 233 ++++++++++++++++++++++++++++++--------- 1 file changed, 183 insertions(+), 50 deletions(-) diff --git a/crates/vm/levm/src/vm.rs b/crates/vm/levm/src/vm.rs index ae305f6d9..7a0fb468b 100644 --- a/crates/vm/levm/src/vm.rs +++ b/crates/vm/levm/src/vm.rs @@ -200,11 +200,154 @@ pub fn word_to_address(word: U256) -> Address { Address::from_slice(&bytes[12..]) } +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, + chain_id: U256, + base_fee_per_gas: U256, + gas_price: U256, + db: Db, + block_blob_gas_used: Option, + block_excess_blob_gas: Option, + tx_blob_hashes: Option>, +) -> Result { + 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 create_contract( + sender: Address, + secret_key: H256, + db: Db, + value: U256, + calldata: Bytes, + block_number: U256, + coinbase: Address, + timestamp: U256, + prev_randao: Option, + chain_id: U256, + base_fee_per_gas: U256, + gas_price: U256, + block_blob_gas_used: Option, + block_excess_blob_gas: Option, + tx_blob_hashes: Option>, +) -> Result { + 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(); + + 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; + + let code: Bytes = sender_account.bytecode.clone(); // It's not this but has to compile + + let new_address = match None { + // Fix + Some(salt) => VM::calculate_create2_address(sender, &code, salt), + None => VM::calculate_create_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 + } + + // Create the contract + let contract_address = Account::new(new_address, value, code.clone(), 0, Default::default()); + db_copy.add_account(new_address, contract_address.clone()); + + // Push address to stack? + /* current_call_frame + .stack + .push(address_to_word(new_address))?; + */ + + // Call the contract + let vm = VM::new( + Some(contract_address.address), + sender, + value, + calldata, + 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, + )?; + + Ok(vm) +} + impl VM { // TODO: Refactor this. #[allow(clippy::too_many_arguments)] pub fn new( - to: Address, + to: Option
, msg_sender: Address, value: U256, calldata: Bytes, @@ -220,55 +363,45 @@ impl VM { block_blob_gas_used: Option, block_excess_blob_gas: Option, tx_blob_hashes: Option>, - ) -> 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 { + // 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, + ), } } From 28e612dedde230b3b95a9b2af5cda5e13a217ede Mon Sep 17 00:00:00 2001 From: maximopalopoli Date: Fri, 25 Oct 2024 18:50:09 -0300 Subject: [PATCH 2/9] Add VMerror's (shouldn't be a VMErr) --- crates/vm/levm/src/errors.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/vm/levm/src/errors.rs b/crates/vm/levm/src/errors.rs index 12c107da1..bcd5e932c 100644 --- a/crates/vm/levm/src/errors.rs +++ b/crates/vm/levm/src/errors.rs @@ -26,6 +26,7 @@ pub enum VMError { SenderAccountShouldNotHaveBytecode, SenderBalanceShouldContainTransferValue, GasPriceIsLowerThanBaseFee, + AddressAlreadyOccuped, } pub enum OpcodeSuccess { From 9d25671ea56a9f6659f45ee4ff98e2de28301fce Mon Sep 17 00:00:00 2001 From: maximopalopoli Date: Fri, 25 Oct 2024 18:50:47 -0300 Subject: [PATCH 3/9] Change new func usages --- crates/vm/levm/src/utils.rs | 5 +++-- crates/vm/levm/tests/tests.rs | 40 +++++++++++++++++++++-------------- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/crates/vm/levm/src/utils.rs b/crates/vm/levm/src/utils.rs index 599b3efea..3199e454b 100644 --- a/crates/vm/levm/src/utils.rs +++ b/crates/vm/levm/src/utils.rs @@ -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(), @@ -72,5 +72,6 @@ pub fn new_vm_with_ops_addr_bal(bytecode: Bytes, address: Address, balance: U256 Default::default(), Default::default(), Default::default(), - ) + Default::default(), + ).unwrap() } diff --git a/crates/vm/levm/tests/tests.rs b/crates/vm/levm/tests/tests.rs index 4ddd2e8ee..df6017fec 100644 --- a/crates/vm/levm/tests/tests.rs +++ b/crates/vm/levm/tests/tests.rs @@ -3845,7 +3845,7 @@ fn caller_op() { ); let mut vm = VM::new( - address_that_has_the_code, + Some(address_that_has_the_code), caller, Default::default(), Default::default(), @@ -3861,7 +3861,8 @@ fn caller_op() { Default::default(), Default::default(), Default::default(), - ); + Default::default(), + ).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); @@ -3887,7 +3888,7 @@ fn origin_op() { ); let mut vm = VM::new( - address_that_has_the_code, + Some(address_that_has_the_code), msg_sender, Default::default(), Default::default(), @@ -3903,7 +3904,8 @@ fn origin_op() { Default::default(), Default::default(), Default::default(), - ); + Default::default(), + ).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); @@ -3955,7 +3957,7 @@ fn address_op() { ); let mut vm = VM::new( - address_that_has_the_code, + Some(address_that_has_the_code), Default::default(), Default::default(), Default::default(), @@ -3971,7 +3973,8 @@ fn address_op() { Default::default(), Default::default(), Default::default(), - ); + Default::default(), + ).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); @@ -3999,7 +4002,7 @@ fn selfbalance_op() { ); let mut vm = VM::new( - address_that_has_the_code, + Some(address_that_has_the_code), Default::default(), Default::default(), Default::default(), @@ -4015,7 +4018,8 @@ fn selfbalance_op() { Default::default(), Default::default(), Default::default(), - ); + Default::default(), + ).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); @@ -4039,7 +4043,7 @@ fn callvalue_op() { ); let mut vm = VM::new( - address_that_has_the_code, + Some(address_that_has_the_code), Default::default(), value, Default::default(), @@ -4055,7 +4059,8 @@ fn callvalue_op() { Default::default(), Default::default(), Default::default(), - ); + Default::default(), + ).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); @@ -4078,7 +4083,7 @@ fn codesize_op() { ); let mut vm = VM::new( - address_that_has_the_code, + Some(address_that_has_the_code), Default::default(), Default::default(), Default::default(), @@ -4094,7 +4099,8 @@ fn codesize_op() { Default::default(), Default::default(), Default::default(), - ); + Default::default(), + ).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); @@ -4119,7 +4125,7 @@ fn gasprice_op() { ); let mut vm = VM::new( - address_that_has_the_code, + Some(address_that_has_the_code), Default::default(), Default::default(), Default::default(), @@ -4135,7 +4141,8 @@ fn gasprice_op() { Default::default(), Default::default(), Default::default(), - ); + Default::default(), + ).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); @@ -4177,7 +4184,7 @@ fn codecopy_op() { ); let mut vm = VM::new( - address_that_has_the_code, + Some(address_that_has_the_code), Default::default(), Default::default(), Default::default(), @@ -4193,7 +4200,8 @@ fn codecopy_op() { Default::default(), Default::default(), Default::default(), - ); + Default::default(), + ).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); From 27e237e92b9c0106ad2d21634bcd208bfcaed993 Mon Sep 17 00:00:00 2001 From: maximopalopoli Date: Fri, 25 Oct 2024 18:51:42 -0300 Subject: [PATCH 4/9] Add tests folder to gitignore This is done because I've been working in the Ethereum Foundation tests and have them downloaded, and that difficults git work --- crates/vm/levm/.gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/vm/levm/.gitignore b/crates/vm/levm/.gitignore index 335ec9573..adb63666b 100644 --- a/crates/vm/levm/.gitignore +++ b/crates/vm/levm/.gitignore @@ -1 +1,3 @@ *.tar.gz + +tests/ef_testcases From e342259e14601f9534802abe3c2e4fca1c7d85f9 Mon Sep 17 00:00:00 2001 From: maximopalopoli Date: Fri, 25 Oct 2024 19:01:26 -0300 Subject: [PATCH 5/9] Avoid clippy errors --- crates/vm/levm/src/vm.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/vm/levm/src/vm.rs b/crates/vm/levm/src/vm.rs index 7a0fb468b..da4b984ac 100644 --- a/crates/vm/levm/src/vm.rs +++ b/crates/vm/levm/src/vm.rs @@ -200,6 +200,7 @@ 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, @@ -259,6 +260,7 @@ fn create_transaction( }) } +#[allow(clippy::too_many_arguments)] fn create_contract( sender: Address, secret_key: H256, From 0a82abe6dd3ba1b0bdd548fcac0db504a149e2b3 Mon Sep 17 00:00:00 2001 From: maximopalopoli Date: Fri, 25 Oct 2024 19:01:38 -0300 Subject: [PATCH 6/9] cargo fmt changes --- crates/vm/levm/src/utils.rs | 3 ++- crates/vm/levm/tests/tests.rs | 24 ++++++++++++++++-------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/crates/vm/levm/src/utils.rs b/crates/vm/levm/src/utils.rs index 3199e454b..21cabd1ee 100644 --- a/crates/vm/levm/src/utils.rs +++ b/crates/vm/levm/src/utils.rs @@ -73,5 +73,6 @@ pub fn new_vm_with_ops_addr_bal(bytecode: Bytes, address: Address, balance: U256 Default::default(), Default::default(), Default::default(), - ).unwrap() + ) + .unwrap() } diff --git a/crates/vm/levm/tests/tests.rs b/crates/vm/levm/tests/tests.rs index df6017fec..9bd33acdb 100644 --- a/crates/vm/levm/tests/tests.rs +++ b/crates/vm/levm/tests/tests.rs @@ -3862,7 +3862,8 @@ fn caller_op() { Default::default(), Default::default(), Default::default(), - ).unwrap(); + ) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); @@ -3905,7 +3906,8 @@ fn origin_op() { Default::default(), Default::default(), Default::default(), - ).unwrap(); + ) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); @@ -3974,7 +3976,8 @@ fn address_op() { Default::default(), Default::default(), Default::default(), - ).unwrap(); + ) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); @@ -4019,7 +4022,8 @@ fn selfbalance_op() { Default::default(), Default::default(), Default::default(), - ).unwrap(); + ) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); @@ -4060,7 +4064,8 @@ fn callvalue_op() { Default::default(), Default::default(), Default::default(), - ).unwrap(); + ) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); @@ -4100,7 +4105,8 @@ fn codesize_op() { Default::default(), Default::default(), Default::default(), - ).unwrap(); + ) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); @@ -4142,7 +4148,8 @@ fn gasprice_op() { Default::default(), Default::default(), Default::default(), - ).unwrap(); + ) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); @@ -4201,7 +4208,8 @@ fn codecopy_op() { Default::default(), Default::default(), Default::default(), - ).unwrap(); + ) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); From efbc3ea0c05b74d81a677c902924f9ba5b6103e3 Mon Sep 17 00:00:00 2001 From: maximopalopoli Date: Fri, 25 Oct 2024 19:23:41 -0300 Subject: [PATCH 7/9] Add documentation and reference --- crates/vm/levm/src/vm.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/vm/levm/src/vm.rs b/crates/vm/levm/src/vm.rs index da4b984ac..9ac5a2ce6 100644 --- a/crates/vm/levm/src/vm.rs +++ b/crates/vm/levm/src/vm.rs @@ -278,6 +278,16 @@ fn create_contract( block_excess_blob_gas: Option, tx_blob_hashes: Option>, ) -> Result { + /* + 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, From bca332b98d2bee89e95718cb07f40599c62f13c9 Mon Sep 17 00:00:00 2001 From: maximopalopoli Date: Fri, 25 Oct 2024 19:24:12 -0300 Subject: [PATCH 8/9] Improve create logic --- crates/vm/levm/src/vm.rs | 49 +++++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/crates/vm/levm/src/vm.rs b/crates/vm/levm/src/vm.rs index 9ac5a2ce6..7d2ee0d4a 100644 --- a/crates/vm/levm/src/vm.rs +++ b/crates/vm/levm/src/vm.rs @@ -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}, @@ -260,6 +260,15 @@ fn create_transaction( }) } +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, @@ -297,6 +306,7 @@ fn create_contract( } .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 } @@ -308,35 +318,30 @@ fn create_contract( sender_account.nonce = new_nonce; sender_account.balance -= value; - let code: Bytes = sender_account.bytecode.clone(); // It's not this but has to compile - - let new_address = match None { - // Fix - Some(salt) => VM::calculate_create2_address(sender, &code, salt), - None => VM::calculate_create_address(sender, sender_account.nonce), - }; - + // 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 } - // Create the contract - let contract_address = Account::new(new_address, value, code.clone(), 0, Default::default()); + + // 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()); - // Push address to stack? - /* current_call_frame - .stack - .push(address_to_word(new_address))?; - */ + // 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 vm = VM::new( + let mut vm = VM::new( Some(contract_address.address), sender, value, - calldata, + code, sender_account.balance, block_number, coinbase, @@ -352,6 +357,14 @@ fn create_contract( 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) } From 704b1355a8d80786f999a3cb3e10dfaf6ae78bcb Mon Sep 17 00:00:00 2001 From: maximopalopoli Date: Fri, 25 Oct 2024 19:25:26 -0300 Subject: [PATCH 9/9] clippy + fmt --- crates/vm/levm/src/vm.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/crates/vm/levm/src/vm.rs b/crates/vm/levm/src/vm.rs index 7d2ee0d4a..b2a9a6114 100644 --- a/crates/vm/levm/src/vm.rs +++ b/crates/vm/levm/src/vm.rs @@ -264,9 +264,9 @@ 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..]) + H160::from_slice(&addr[12..]) } #[allow(clippy::too_many_arguments)] @@ -325,14 +325,13 @@ fn create_contract( 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()); + 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(); @@ -360,11 +359,10 @@ fn create_contract( let res = vm.transact(); // The ret variable is the returned contract code ????????? - let contract_code = res.unwrap().output; + 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) }