From 5800e6ad64751544f56bdf2ed2058bd85c20ac36 Mon Sep 17 00:00:00 2001 From: Linda Guiga <101227802+LindaGuiga@users.noreply.github.com> Date: Fri, 10 Nov 2023 19:19:00 -0500 Subject: [PATCH] Add run_syscall and tests for sload and sstore (#1344) * Add run_syscall and tests for sload and sstore * Replace panics with errors and address comments * Apply comments * Change last addr name in prepare_interpreter * Fix kernel_mode in tests * Minor cleanup --- evm/src/cpu/kernel/interpreter.rs | 99 +++++++++--- evm/src/cpu/kernel/tests/account_code.rs | 186 ++++++++++++++++++++++- 2 files changed, 260 insertions(+), 25 deletions(-) diff --git a/evm/src/cpu/kernel/interpreter.rs b/evm/src/cpu/kernel/interpreter.rs index 8cf22c19..928c2748 100644 --- a/evm/src/cpu/kernel/interpreter.rs +++ b/evm/src/cpu/kernel/interpreter.rs @@ -9,15 +9,18 @@ use ethereum_types::{U256, U512}; use keccak_hash::keccak; use plonky2::field::goldilocks_field::GoldilocksField; +use super::assembler::BYTES_PER_OFFSET; use crate::cpu::kernel::aggregator::KERNEL; use crate::cpu::kernel::constants::context_metadata::ContextMetadata; use crate::cpu::kernel::constants::global_metadata::GlobalMetadata; use crate::cpu::kernel::constants::txn_fields::NormalizedTxnField; +use crate::cpu::stack_bounds::MAX_USER_STACK_SIZE; use crate::extension_tower::BN_BASE; use crate::generation::prover_input::ProverInputFn; use crate::generation::state::GenerationState; use crate::generation::GenerationInputs; use crate::memory::segments::Segment; +use crate::util::u256_to_usize; use crate::witness::gas::gas_to_charge; use crate::witness::memory::{MemoryAddress, MemoryContextState, MemorySegmentState, MemoryState}; use crate::witness::operation::Operation; @@ -40,7 +43,7 @@ impl MemoryState { } pub struct Interpreter<'a> { - kernel_mode: bool, + pub(crate) kernel_mode: bool, jumpdests: Vec, pub(crate) context: usize, pub(crate) generation_state: GenerationState, @@ -156,7 +159,10 @@ impl<'a> Interpreter<'a> { } fn code(&self) -> &MemorySegmentState { - &self.generation_state.memory.contexts[self.context].segments[Segment::Code as usize] + // The context is 0 if we are in kernel mode. + &self.generation_state.memory.contexts + [(1 - self.generation_state.registers.is_kernel as usize) * self.context] + .segments[Segment::Code as usize] } fn code_slice(&self, n: usize) -> Vec { @@ -370,7 +376,7 @@ impl<'a> Interpreter<'a> { 0x20 => self.run_keccak256(), // "KECCAK256", 0x21 => self.run_keccak_general(), // "KECCAK_GENERAL", 0x30 => self.run_address(), // "ADDRESS", - 0x31 => todo!(), // "BALANCE", + 0x31 => self.run_syscall(opcode, 1, false)?, // "BALANCE", 0x32 => self.run_origin(), // "ORIGIN", 0x33 => self.run_caller(), // "CALLER", 0x34 => self.run_callvalue(), // "CALLVALUE", @@ -380,12 +386,12 @@ impl<'a> Interpreter<'a> { 0x38 => self.run_codesize(), // "CODESIZE", 0x39 => self.run_codecopy(), // "CODECOPY", 0x3a => self.run_gasprice(), // "GASPRICE", - 0x3b => todo!(), // "EXTCODESIZE", - 0x3c => todo!(), // "EXTCODECOPY", + 0x3b => self.run_syscall(opcode, 1, false)?, // "EXTCODESIZE", + 0x3c => self.run_syscall(opcode, 4, false)?, // "EXTCODECOPY", 0x3d => self.run_returndatasize(), // "RETURNDATASIZE", 0x3e => self.run_returndatacopy(), // "RETURNDATACOPY", - 0x3f => todo!(), // "EXTCODEHASH", - 0x40 => todo!(), // "BLOCKHASH", + 0x3f => self.run_syscall(opcode, 1, false)?, // "EXTCODEHASH", + 0x40 => self.run_syscall(opcode, 1, false)?, // "BLOCKHASH", 0x41 => self.run_coinbase(), // "COINBASE", 0x42 => self.run_timestamp(), // "TIMESTAMP", 0x43 => self.run_number(), // "NUMBER", @@ -398,44 +404,44 @@ impl<'a> Interpreter<'a> { 0x51 => self.run_mload(), // "MLOAD", 0x52 => self.run_mstore(), // "MSTORE", 0x53 => self.run_mstore8(), // "MSTORE8", - 0x54 => todo!(), // "SLOAD", - 0x55 => todo!(), // "SSTORE", + 0x54 => self.run_syscall(opcode, 1, false)?, // "SLOAD", + 0x55 => self.run_syscall(opcode, 2, false)?, // "SSTORE", 0x56 => self.run_jump(), // "JUMP", 0x57 => self.run_jumpi(), // "JUMPI", 0x58 => self.run_pc(), // "PC", 0x59 => self.run_msize(), // "MSIZE", - 0x5a => todo!(), // "GAS", + 0x5a => self.run_syscall(opcode, 0, true)?, // "GAS", 0x5b => self.run_jumpdest(), // "JUMPDEST", x if (0x5f..0x80).contains(&x) => self.run_push(x - 0x5f), // "PUSH" x if (0x80..0x90).contains(&x) => self.run_dup(x - 0x7f), // "DUP" x if (0x90..0xa0).contains(&x) => self.run_swap(x - 0x8f)?, // "SWAP" - 0xa0 => todo!(), // "LOG0", - 0xa1 => todo!(), // "LOG1", - 0xa2 => todo!(), // "LOG2", - 0xa3 => todo!(), // "LOG3", - 0xa4 => todo!(), // "LOG4", + 0xa0 => self.run_syscall(opcode, 2, false)?, // "LOG0", + 0xa1 => self.run_syscall(opcode, 3, false)?, // "LOG1", + 0xa2 => self.run_syscall(opcode, 4, false)?, // "LOG2", + 0xa3 => self.run_syscall(opcode, 5, false)?, // "LOG3", + 0xa4 => self.run_syscall(opcode, 6, false)?, // "LOG4", 0xa5 => bail!( "Executed PANIC, stack={:?}, memory={:?}", self.stack(), self.get_kernel_general_memory() ), // "PANIC", 0xee => self.run_mstore_32bytes(), // "MSTORE_32BYTES", - 0xf0 => todo!(), // "CREATE", - 0xf1 => todo!(), // "CALL", - 0xf2 => todo!(), // "CALLCODE", - 0xf3 => todo!(), // "RETURN", - 0xf4 => todo!(), // "DELEGATECALL", - 0xf5 => todo!(), // "CREATE2", + 0xf0 => self.run_syscall(opcode, 3, false)?, // "CREATE", + 0xf1 => self.run_syscall(opcode, 7, false)?, // "CALL", + 0xf2 => self.run_syscall(opcode, 7, false)?, // "CALLCODE", + 0xf3 => self.run_syscall(opcode, 2, false)?, // "RETURN", + 0xf4 => self.run_syscall(opcode, 6, false)?, // "DELEGATECALL", + 0xf5 => self.run_syscall(opcode, 4, false)?, // "CREATE2", 0xf6 => self.run_get_context(), // "GET_CONTEXT", 0xf7 => self.run_set_context(), // "SET_CONTEXT", 0xf8 => self.run_mload_32bytes(), // "MLOAD_32BYTES", 0xf9 => self.run_exit_kernel(), // "EXIT_KERNEL", - 0xfa => todo!(), // "STATICCALL", + 0xfa => self.run_syscall(opcode, 6, false)?, // "STATICCALL", 0xfb => self.run_mload_general(), // "MLOAD_GENERAL", 0xfc => self.run_mstore_general(), // "MSTORE_GENERAL", - 0xfd => todo!(), // "REVERT", + 0xfd => self.run_syscall(opcode, 2, false)?, // "REVERT", 0xfe => bail!("Executed INVALID"), // "INVALID", - 0xff => todo!(), // "SELFDESTRUCT", + 0xff => self.run_syscall(opcode, 1, false)?, // "SELFDESTRUCT", _ => bail!("Unrecognized opcode {}.", opcode), }; @@ -1002,6 +1008,50 @@ impl<'a> Interpreter<'a> { ); } + fn run_syscall( + &mut self, + opcode: u8, + stack_values_read: usize, + stack_len_increased: bool, + ) -> anyhow::Result<()> { + TryInto::::try_into(self.generation_state.registers.gas_used) + .map_err(|_| anyhow!("Gas overflow"))?; + if self.generation_state.registers.stack_len < stack_values_read { + return Err(anyhow!("Stack underflow")); + } + + if stack_len_increased + && !self.generation_state.registers.is_kernel + && self.generation_state.registers.stack_len >= MAX_USER_STACK_SIZE + { + return Err(anyhow!("Stack overflow")); + }; + + let handler_jumptable_addr = KERNEL.global_labels["syscall_jumptable"]; + let handler_addr = { + let offset = handler_jumptable_addr + (opcode as usize) * (BYTES_PER_OFFSET as usize); + self.get_memory_segment(Segment::Code)[offset..offset + 3] + .iter() + .fold(U256::from(0), |acc, &elt| acc * (1 << 8) + elt) + }; + + let new_program_counter = + u256_to_usize(handler_addr).map_err(|_| anyhow!("The program counter is too large"))?; + + let syscall_info = U256::from(self.generation_state.registers.program_counter + 1) + + U256::from((self.generation_state.registers.is_kernel as usize) << 32) + + (U256::from(self.generation_state.registers.gas_used) << 192); + self.generation_state.registers.program_counter = new_program_counter; + + self.generation_state.registers.is_kernel = true; + self.kernel_mode = true; + self.generation_state.registers.gas_used = 0; + self.push(syscall_info); + + self.run().map_err(|_| anyhow!("Syscall failed"))?; + Ok(()) + } + fn run_jump(&mut self) { let x = self.pop().as_usize(); self.jump_to(x); @@ -1149,6 +1199,7 @@ impl<'a> Interpreter<'a> { self.generation_state.registers.program_counter = program_counter; self.generation_state.registers.is_kernel = is_kernel_mode; + self.kernel_mode = is_kernel_mode; self.generation_state.registers.gas_used = gas_used_val; } diff --git a/evm/src/cpu/kernel/tests/account_code.rs b/evm/src/cpu/kernel/tests/account_code.rs index 27859378..b7b6debb 100644 --- a/evm/src/cpu/kernel/tests/account_code.rs +++ b/evm/src/cpu/kernel/tests/account_code.rs @@ -1,17 +1,20 @@ use std::collections::HashMap; use anyhow::{anyhow, Result}; +use eth_trie_utils::nibbles::Nibbles; use eth_trie_utils::partial_trie::{HashedPartialTrie, PartialTrie}; use ethereum_types::{Address, BigEndianHash, H256, U256}; +use hex_literal::hex; use keccak_hash::keccak; use rand::{thread_rng, Rng}; use crate::cpu::kernel::aggregator::KERNEL; -use crate::cpu::kernel::constants::context_metadata::ContextMetadata::GasLimit; +use crate::cpu::kernel::constants::context_metadata::ContextMetadata::{self, GasLimit}; use crate::cpu::kernel::constants::global_metadata::GlobalMetadata; use crate::cpu::kernel::interpreter::Interpreter; use crate::cpu::kernel::tests::mpt::nibbles_64; use crate::generation::mpt::{all_mpt_prover_inputs_reversed, AccountRlp}; +use crate::generation::TrieInputs; use crate::memory::segments::Segment; use crate::Node; @@ -192,3 +195,184 @@ fn test_extcodecopy() -> Result<()> { Ok(()) } + +/// Prepare the interpreter for storage tests by inserting all necessary accounts +/// in the state trie, adding the code we want to context 1 and switching the context. +fn prepare_interpreter_all_accounts( + interpreter: &mut Interpreter, + trie_inputs: TrieInputs, + addr: [u8; 20], + code: &[u8], +) -> Result<()> { + // Load all MPTs. + let load_all_mpts = KERNEL.global_labels["load_all_mpts"]; + + interpreter.generation_state.registers.program_counter = load_all_mpts; + interpreter.push(0xDEADBEEFu32.into()); + + interpreter.generation_state.mpt_prover_inputs = + all_mpt_prover_inputs_reversed(&trie_inputs) + .map_err(|err| anyhow!("Invalid MPT data: {:?}", err))?; + interpreter.run()?; + assert_eq!(interpreter.stack(), vec![]); + + // Switch context and initialize memory with the data we need for the tests. + interpreter.generation_state.registers.program_counter = 0; + interpreter.set_code(1, code.to_vec()); + interpreter.generation_state.memory.contexts[1].segments[Segment::ContextMetadata as usize] + .set( + ContextMetadata::Address as usize, + U256::from_big_endian(&addr), + ); + interpreter.generation_state.memory.contexts[1].segments[Segment::ContextMetadata as usize] + .set(ContextMetadata::GasLimit as usize, 100_000.into()); + interpreter.context = 1; + interpreter.generation_state.registers.context = 1; + interpreter.generation_state.registers.is_kernel = false; + interpreter.kernel_mode = false; + + Ok(()) +} + +/// Tests an SSTORE within a code similar to the contract code in add11_yml. +#[test] +fn sstore() -> Result<()> { + // We take the same `to` account as in add11_yml. + let addr = hex!("095e7baea6a6c7c4c2dfeb977efac326af552d87"); + + let addr_hashed = keccak(addr); + + let addr_nibbles = Nibbles::from_bytes_be(addr_hashed.as_bytes()).unwrap(); + + let code = [0x60, 0x01, 0x60, 0x01, 0x01, 0x60, 0x00, 0x55, 0x00]; + let code_hash = keccak(code); + + let account_before = AccountRlp { + balance: 0x0de0b6b3a7640000u64.into(), + code_hash, + ..AccountRlp::default() + }; + + let mut state_trie_before = HashedPartialTrie::from(Node::Empty); + + state_trie_before.insert(addr_nibbles, rlp::encode(&account_before).to_vec()); + + let trie_inputs = TrieInputs { + state_trie: state_trie_before.clone(), + transactions_trie: Node::Empty.into(), + receipts_trie: Node::Empty.into(), + storage_tries: vec![(addr_hashed, Node::Empty.into())], + }; + + let initial_stack = vec![]; + let mut interpreter = Interpreter::new_with_kernel(0, initial_stack); + + // Prepare the interpreter by inserting the account in the state trie. + prepare_interpreter_all_accounts(&mut interpreter, trie_inputs, addr, &code)?; + + interpreter.run()?; + + // The code should have added an element to the storage of `to_account`. We run + // `mpt_hash_state_trie` to check that. + let account_after = AccountRlp { + balance: 0x0de0b6b3a7640000u64.into(), + code_hash, + storage_root: HashedPartialTrie::from(Node::Leaf { + nibbles: Nibbles::from_h256_be(keccak([0u8; 32])), + value: vec![2], + }) + .hash(), + ..AccountRlp::default() + }; + // Now, execute mpt_hash_state_trie. + let mpt_hash_state_trie = KERNEL.global_labels["mpt_hash_state_trie"]; + interpreter.generation_state.registers.program_counter = mpt_hash_state_trie; + interpreter.context = 0; + interpreter.generation_state.registers.context = 0; + interpreter.generation_state.registers.is_kernel = true; + interpreter.kernel_mode = true; + interpreter.push(0xDEADBEEFu32.into()); + interpreter.run()?; + + assert_eq!( + interpreter.stack().len(), + 1, + "Expected 1 item on stack after hashing, found {:?}", + interpreter.stack() + ); + + let hash = H256::from_uint(&interpreter.stack()[0]); + + let mut expected_state_trie_after = HashedPartialTrie::from(Node::Empty); + expected_state_trie_after.insert(addr_nibbles, rlp::encode(&account_after).to_vec()); + + let expected_state_trie_hash = expected_state_trie_after.hash(); + assert_eq!(hash, expected_state_trie_hash); + Ok(()) +} + +/// Tests an SLOAD within a code similar to the contract code in add11_yml. +#[test] +fn sload() -> Result<()> { + // We take the same `to` account as in add11_yml. + let addr = hex!("095e7baea6a6c7c4c2dfeb977efac326af552d87"); + + let addr_hashed = keccak(addr); + + let addr_nibbles = Nibbles::from_bytes_be(addr_hashed.as_bytes()).unwrap(); + + // This code is similar to the one in add11_yml's contract, but we pop the added value + // and carry out an SLOAD instead of an SSTORE. + let code = [0x60, 0x01, 0x60, 0x01, 0x01, 0x50, 0x60, 0x00, 0x54, 0x00]; + let code_hash = keccak(code); + + let account_before = AccountRlp { + balance: 0x0de0b6b3a7640000u64.into(), + code_hash, + ..AccountRlp::default() + }; + + let mut state_trie_before = HashedPartialTrie::from(Node::Empty); + + state_trie_before.insert(addr_nibbles, rlp::encode(&account_before).to_vec()); + + let trie_inputs = TrieInputs { + state_trie: state_trie_before.clone(), + transactions_trie: Node::Empty.into(), + receipts_trie: Node::Empty.into(), + storage_tries: vec![(addr_hashed, Node::Empty.into())], + }; + + let initial_stack = vec![]; + let mut interpreter = Interpreter::new_with_kernel(0, initial_stack); + + // Prepare the interpreter by inserting the account in the state trie. + prepare_interpreter_all_accounts(&mut interpreter, trie_inputs, addr, &code)?; + + interpreter.run()?; + // We check that no value was found. + let value = interpreter.pop(); + assert_eq!(value, 0.into()); + // Now, execute mpt_hash_state_trie. We check that the state trie has not changed. + let mpt_hash_state_trie = KERNEL.global_labels["mpt_hash_state_trie"]; + interpreter.generation_state.registers.program_counter = mpt_hash_state_trie; + interpreter.context = 0; + interpreter.generation_state.registers.context = 0; + interpreter.generation_state.registers.is_kernel = true; + interpreter.kernel_mode = true; + interpreter.push(0xDEADBEEFu32.into()); + interpreter.run()?; + + assert_eq!( + interpreter.stack().len(), + 1, + "Expected 1 item on stack after hashing, found {:?}", + interpreter.stack() + ); + + let hash = H256::from_uint(&interpreter.stack()[0]); + + let expected_state_trie_hash = state_trie_before.hash(); + assert_eq!(hash, expected_state_trie_hash); + Ok(()) +}