mirror of
https://github.com/logos-storage/plonky2.git
synced 2026-01-08 00:33:06 +00:00
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
This commit is contained in:
parent
cc0cdd0783
commit
5800e6ad64
@ -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<usize>,
|
||||
pub(crate) context: usize,
|
||||
pub(crate) generation_state: GenerationState<F>,
|
||||
@ -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> {
|
||||
@ -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::<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);
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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(())
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user