Skip to content

Commit

Permalink
Add run_syscall and tests for sload and sstore (#1344)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
LindaGuiga authored Nov 11, 2023
1 parent cc0cdd0 commit 5800e6a
Show file tree
Hide file tree
Showing 2 changed files with 260 additions and 25 deletions.
99 changes: 75 additions & 24 deletions evm/src/cpu/kernel/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -40,7 +43,7 @@ impl MemoryState {
}

pub struct Interpreter<'a> {
kernel_mode: bool,
pub(crate) kernel_mode: bool,
jumpdests: Vec<usize>,
pub(crate) context: usize,
pub(crate) generation_state: GenerationState<F>,
Expand Down Expand Up @@ -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<u8> {
Expand Down Expand Up @@ -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",
Expand All @@ -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",
Expand All @@ -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),
};

Expand Down Expand Up @@ -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::<u64>::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);
Expand Down Expand Up @@ -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;
}

Expand Down
186 changes: 185 additions & 1 deletion evm/src/cpu/kernel/tests/account_code.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -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(())
}

0 comments on commit 5800e6a

Please sign in to comment.