diff --git a/Makefile b/Makefile index 39440f534..39eb36b52 100644 --- a/Makefile +++ b/Makefile @@ -94,10 +94,10 @@ TEST_PATTERN ?= / # The endpoints tested may be limited by supplying a test pattern in the form "/endpoint_1|enpoint_2|..|enpoint_n" # For example, to run the rpc-compat suites for eth_chainId & eth_blockNumber you should run: # `make run-hive SIMULATION=ethereum/rpc-compat TEST_PATTERN="/eth_chainId|eth_blockNumber"` -run-hive: build-image setup-hive ## 🧪 Run Hive testing suite +run-hive: build-image ## 🧪 Run Hive testing suite cd hive && ./hive --sim $(SIMULATION) --client ethereumrust --sim.limit "$(TEST_PATTERN)" -run-hive-debug: build-image setup-hive ## 🐞 Run Hive testing suite in debug mode +run-hive-debug: build-image ## 🐞 Run Hive testing suite in debug mode cd hive && ./hive --sim $(SIMULATION) --client ethereumrust --sim.limit "$(TEST_PATTERN)" --docker.output clean-hive-logs: ## 🧹 Clean Hive logs diff --git a/crates/l2/Makefile b/crates/l2/Makefile index 3fe872317..23914ec55 100644 --- a/crates/l2/Makefile +++ b/crates/l2/Makefile @@ -13,7 +13,7 @@ down: down-local-l1 down-l2 ## 🛑 Shuts down the localnet clean: clean-contract-deps ## 🧹 Cleans the localnet -restart: restart-local-l1 restart-contract-deps deploy-l1 restart-l2 ## 🔄 Restarts the localnet +restart: restart-local-l1 deploy-l1 restart-l2 ## 🔄 Restarts the localnet cli: ## 🛠️ Installs the L2 Lambda Ethereum Rust CLI cargo install --path ${ETHEREUM_RUST_PATH}/cmd/ethereum_rust_l2/ --force 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 diff --git a/crates/vm/levm/src/constants.rs b/crates/vm/levm/src/constants.rs index c2f8ae1b7..c38f3f429 100644 --- a/crates/vm/levm/src/constants.rs +++ b/crates/vm/levm/src/constants.rs @@ -115,6 +115,8 @@ pub const TX_BASE_COST: U256 = U256([21000, 0, 0, 0]); pub const MAX_CODE_SIZE: usize = 0x6000; pub const MAX_CREATE_CODE_SIZE: usize = 2 * MAX_CODE_SIZE; +pub const INVALID_CONTRACT_PREFIX: u8 = 0xef; + // Costs in gas for init word and init code (in wei) pub const INIT_WORD_COST: i64 = 2; diff --git a/crates/vm/levm/src/errors.rs b/crates/vm/levm/src/errors.rs index 12c107da1..c3f372276 100644 --- a/crates/vm/levm/src/errors.rs +++ b/crates/vm/levm/src/errors.rs @@ -22,10 +22,14 @@ pub enum VMError { MissingBlobHashes, BlobHashIndexOutOfBounds, RevertOpcode, - SenderAccountDoesNotExist, + AddressDoesNotMatchAnAccount, SenderAccountShouldNotHaveBytecode, SenderBalanceShouldContainTransferValue, GasPriceIsLowerThanBaseFee, + AddressAlreadyOccupied, + ContractOutputTooBig, + InvalidInitialByte, + NonceOverflow, } pub enum OpcodeSuccess { diff --git a/crates/vm/levm/src/utils.rs b/crates/vm/levm/src/utils.rs index 599b3efea..c80aa682a 100644 --- a/crates/vm/levm/src/utils.rs +++ b/crates/vm/levm/src/utils.rs @@ -46,7 +46,7 @@ pub fn new_vm_with_ops_addr_bal(bytecode: Bytes, address: Address, balance: U256 ), ]; - let state = Db { + let mut state = Db { accounts: accounts.into(), block_hashes: Default::default(), }; @@ -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(), @@ -68,9 +68,12 @@ pub fn new_vm_with_ops_addr_bal(bytecode: Bytes, address: Address, balance: U256 U256::one(), Default::default(), Default::default(), - state, + &mut state, Default::default(), Default::default(), Default::default(), + Default::default(), + None, ) + .unwrap() } diff --git a/crates/vm/levm/src/vm.rs b/crates/vm/levm/src/vm.rs index ae305f6d9..952e1ba5a 100644 --- a/crates/vm/levm/src/vm.rs +++ b/crates/vm/levm/src/vm.rs @@ -201,9 +201,8 @@ pub fn word_to_address(word: U256) -> Address { } impl VM { - // TODO: Refactor this. #[allow(clippy::too_many_arguments)] - pub fn new( + fn call_type_transaction( to: Address, msg_sender: Address, value: U256, @@ -220,23 +219,13 @@ impl VM { block_blob_gas_used: Option, block_excess_blob_gas: Option, tx_blob_hashes: Option>, - ) -> Self { - // TODO: This handles only CALL transactions. + ) -> Result { 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, + to, None, bytecode, value, @@ -264,11 +253,204 @@ impl VM { tx_blob_hashes, }; - Self { + Ok(VM { call_frames: vec![initial_call_frame], db, env, accrued_substate: Substate::default(), + }) + } + + // 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 + #[allow(clippy::too_many_arguments)] + fn create_type_transaction( + sender: Address, + secret_key: H256, + db: &mut 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>, + salt: 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::OutOfGas); + } + } + .clone(); + + // (1) + if sender_account.balance < value { + return Err(VMError::OutOfGas); // Maybe a more personalized error + } + + sender_account.nonce = sender_account + .nonce + .checked_add(1) + .ok_or(VMError::NonceOverflow)?; + + // (2) + let new_contract_address = match salt { + Some(salt) => VM::calculate_create2_address(sender, &calldata, 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_contract_address) { + return Err(VMError::AddressAlreadyOccupied); + } + + // (3) + let mut created_contract = Account::new( + new_contract_address, + value, + calldata.clone(), + 1, + Default::default(), + ); + db_copy.add_account(new_contract_address, created_contract.clone()); + + // (4) + sender_account.balance -= value; + created_contract.balance += value; + + // (5) + let code: Bytes = calldata.clone(); + + // Call the contract + let mut vm = VM::new( + Some(created_contract.address), + sender, + value, + code, + sender_account.balance, + block_number, + coinbase, + timestamp, + prev_randao, + chain_id, + base_fee_per_gas, + gas_price, + &mut db_copy, + block_blob_gas_used, + block_excess_blob_gas, + tx_blob_hashes, + secret_key, + None, + )?; + + let res = vm.transact()?; + // Don't use a revert bc work with clones, so don't have to save previous state + + let contract_code = res.output; + + // (6) + if contract_code.len() > MAX_CODE_SIZE { + return Err(VMError::ContractOutputTooBig); + } + // Supposing contract code has contents + if contract_code[0] == INVALID_CONTRACT_PREFIX { + return Err(VMError::InvalidInitialByte); + } + + // If the initialization code completes successfully, a final contract-creation cost is paid, + // the code-deposit cost, c, proportional to the size of the created contract’s code + let creation_cost = 200 * contract_code.len(); + + sender_account.balance = sender_account + .balance + .checked_sub(U256::from(creation_cost)) + .ok_or(VMError::OutOfGas)?; + + created_contract.bytecode = contract_code; + + let mut acc = db_copy.accounts.get_mut(&sender).unwrap(); + *acc = sender_account; + acc = db_copy.accounts.get_mut(&new_contract_address).unwrap(); + *acc = created_contract; + + *db = db_copy; + Ok(vm) + } + + // TODO: Refactor this. + #[allow(clippy::too_many_arguments)] + pub fn new( + to: Option
, + 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: &mut Db, + block_blob_gas_used: Option, + block_excess_blob_gas: Option, + tx_blob_hashes: Option>, + secret_key: H256, + salt: Option, + ) -> Result { + // Maybe this desicion should be made in an upper layer + match to { + Some(address) => VM::call_type_transaction( + address, + msg_sender, + value, + calldata, + gas_limit, + block_number, + coinbase, + timestamp, + prev_randao, + chain_id, + base_fee_per_gas, + gas_price, + db.clone(), + block_blob_gas_used, + block_excess_blob_gas, + tx_blob_hashes, + ), + None => VM::create_type_transaction( + 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, + salt, + ), } } @@ -415,7 +597,6 @@ impl VM { } } - // let account = self.db.accounts.get(&self.env.origin).unwrap(); /// Based on Ethereum yellow paper's initial tests of intrinsic validity (Section 6). The last version is /// Shanghai, so there are probably missing Cancun validations. The intrinsic validations are: /// @@ -437,7 +618,9 @@ impl VM { // Validations (1), (2), (3), (5), and (8) are assumed done in upper layers. let sender_account = match self.db.accounts.get(&self.env.origin) { Some(acc) => acc, - None => return Err(VMError::SenderAccountDoesNotExist), + None => return Err(VMError::AddressDoesNotMatchAnAccount), + // This is a check for completeness. However if it were a none and + // it was not caught it would be caught in clause 6. }; // (4) if sender_account.has_code() { diff --git a/crates/vm/levm/tests/tests.rs b/crates/vm/levm/tests/tests.rs index 7aedbc923..fa22aad1d 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(), @@ -3857,11 +3857,14 @@ fn caller_op() { Default::default(), Default::default(), Default::default(), - db, + &mut db, Default::default(), Default::default(), Default::default(), - ); + Default::default(), + None, + ) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); @@ -3887,7 +3890,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(), @@ -3899,11 +3902,14 @@ fn origin_op() { Default::default(), Default::default(), Default::default(), - db, + &mut db, Default::default(), Default::default(), Default::default(), - ); + Default::default(), + None, + ) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); @@ -3953,7 +3959,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(), @@ -3965,11 +3971,14 @@ fn address_op() { Default::default(), Default::default(), Default::default(), - db, + &mut db, Default::default(), Default::default(), Default::default(), - ); + Default::default(), + None, + ) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); @@ -3997,7 +4006,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(), @@ -4009,11 +4018,14 @@ fn selfbalance_op() { Default::default(), Default::default(), Default::default(), - db, + &mut db, Default::default(), Default::default(), Default::default(), - ); + Default::default(), + None, + ) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); @@ -4037,7 +4049,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(), @@ -4049,11 +4061,14 @@ fn callvalue_op() { Default::default(), Default::default(), Default::default(), - db, + &mut db, Default::default(), Default::default(), Default::default(), - ); + Default::default(), + None, + ) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); @@ -4076,7 +4091,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(), @@ -4088,11 +4103,14 @@ fn codesize_op() { Default::default(), Default::default(), Default::default(), - db, + &mut db, Default::default(), Default::default(), Default::default(), - ); + Default::default(), + None, + ) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); @@ -4117,7 +4135,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(), @@ -4129,11 +4147,14 @@ fn gasprice_op() { Default::default(), Default::default(), U256::from(0x9876), - db, + &mut db, Default::default(), Default::default(), Default::default(), - ); + Default::default(), + None, + ) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); @@ -4175,7 +4196,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(), @@ -4187,11 +4208,14 @@ fn codecopy_op() { Default::default(), Default::default(), Default::default(), - db, + &mut db, Default::default(), Default::default(), Default::default(), - ); + Default::default(), + None, + ) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame);