diff --git a/src/evm.rs b/src/evm.rs index 5d204ac..2783ad6 100644 --- a/src/evm.rs +++ b/src/evm.rs @@ -24,7 +24,7 @@ impl Self(Evm { ctx, inspector, - instruction: ScrollInstructions::new_mainnet(spec), + instruction: ScrollInstructions::new_mainnet(), precompiles: ScrollPrecompileProvider::new_with_spec(spec), }) } diff --git a/src/instructions.rs b/src/instructions.rs index 621c994..c85e0a6 100644 --- a/src/instructions.rs +++ b/src/instructions.rs @@ -10,10 +10,13 @@ use revm::{ popn, popn_top, push, require_non_staticcall, resize_memory, Host, InstructionResult, InstructionTable, Interpreter, InterpreterTypes, }, - primitives::{keccak256, BLOCK_HASH_HISTORY, U256}, + primitives::{address, keccak256, Address, BLOCK_HASH_HISTORY, U256}, }; use std::rc::Rc; +const HISTORY_STORAGE_ADDRESS: Address = address!("0x0000F90827F1C53a10cb7A02335B175320002935"); +const HISTORY_SERVE_WINDOW: u64 = 8191; + /// Holds the EVM instruction table for Scroll. pub struct ScrollInstructions { pub instruction_table: Rc>, @@ -46,8 +49,8 @@ where WIRE: InterpreterTypes, HOST: ScrollContextTr, { - pub fn new_mainnet(spec: ScrollSpecId) -> Self { - Self::new(make_scroll_instruction_table::(spec)) + pub fn new_mainnet() -> Self { + Self::new(make_scroll_instruction_table::()) } pub fn new(base_table: InstructionTable) -> Self { @@ -65,22 +68,17 @@ where /// - `SELFDESTRUCT` /// - `MCOPY` pub fn make_scroll_instruction_table( - spec: ScrollSpecId, ) -> InstructionTable { let mut table = instruction_table::(); // override the instructions + table[opcode::BLOCKHASH as usize] = blockhash::; table[opcode::BASEFEE as usize] = basefee::; table[opcode::TSTORE as usize] = tstore::; table[opcode::TLOAD as usize] = tload::; table[opcode::SELFDESTRUCT as usize] = selfdestruct::; table[opcode::MCOPY as usize] = mcopy::; - // override blockhash opcode in pre-feynman blocks - if !spec.is_enabled_in(ScrollSpecId::FEYNMAN) { - table[opcode::BLOCKHASH as usize] = blockhash::; - } - table } @@ -89,10 +87,12 @@ pub fn make_scroll_instruction_table(interpreter: &mut Interpreter, host: &mut H) { +fn blockhash( + interpreter: &mut Interpreter, + host: &mut H, +) { gas!(interpreter, gas::BLOCKHASH); popn_top!([], requested_block_number, interpreter); @@ -106,11 +106,31 @@ fn blockhash(interpreter: &mut Interpreter U256::ZERO, // blockhash requested for block older than BLOCK_HASH_HISTORY - return 0 x if x > BLOCK_HASH_HISTORY => U256::ZERO, - // blockhash requested for block in the history - return the hash - _ => { + // blockhash requested for block in the history (pre-Feynman) + // blockhash is computed as the keccak256 hash of the chain id and the block number + _ if !host.cfg().spec().is_enabled_in(ScrollSpecId::FEYNMAN) => { let chain_id = as_u64_saturated!(host.chain_id()); compute_block_hash(chain_id, as_u64_saturated!(requested_block_number)) } + // blockhash requested for block in the history (post-Feynman) + // blockhash is loaded from the EIP-2935 history storage system contract storage. + _ => { + // sload assumes that the account is present in the journal + if host.load_account_delegated(HISTORY_STORAGE_ADDRESS).is_none() { + interpreter.control.set_instruction_result(InstructionResult::FatalExternalError); + return; + }; + + // index in system contract ring buffer storage is block_number % HISTORY_SERVE_WINDOW + let index = requested_block_number_u64.wrapping_rem(HISTORY_SERVE_WINDOW); + + let Some(value) = host.sload(HISTORY_STORAGE_ADDRESS, U256::from(index)) else { + interpreter.control.set_instruction_result(InstructionResult::FatalExternalError); + return; + }; + + value.data + } }; } @@ -222,6 +242,7 @@ mod tests { use crate::{ builder::{DefaultScrollContext, ScrollContext}, + instructions::HISTORY_STORAGE_ADDRESS, ScrollSpecId::*, }; @@ -237,7 +258,7 @@ mod tests { context.modify_cfg(|cfg| cfg.chain_id = chain_id); context.modify_cfg(|cfg| cfg.spec = spec); - let instructions = make_scroll_instruction_table(spec); + let instructions = make_scroll_instruction_table(); let bytecode = Bytecode::new_legacy(Bytes::from(&[BLOCKHASH, STOP])); let mut interpreter = Interpreter::default().with_bytecode(bytecode); @@ -259,14 +280,26 @@ mod tests { context.modify_cfg(|cfg| cfg.chain_id = chain_id); context.modify_cfg(|cfg| cfg.spec = spec); - let instructions = make_scroll_instruction_table(spec); + // updating the history storage system contract is not part of revm, + // in this test we simply write the block hash to the contract storage. + let expected_block_hash = db.block_hash_ref(target_block).expect("db contains block hash"); + context.modify_db(|db| { + db.insert_account_storage( + HISTORY_STORAGE_ADDRESS, + U256::from(target_block), + expected_block_hash.into(), + ) + .expect("insert account should succeed") + }); + + let instructions = make_scroll_instruction_table(); let bytecode = Bytecode::new_legacy(Bytes::from(&[BLOCKHASH, STOP])); let mut interpreter = Interpreter::default().with_bytecode(bytecode); let _ = interpreter.stack.push(U256::from(target_block)); interpreter.run_plain(&instructions, &mut context); - let expected = db.block_hash_ref(target_block).expect("db contains block hash").into(); + let expected = expected_block_hash.into(); let actual = interpreter.stack.pop().expect("stack is not empty"); assert_eq!(actual, expected); } diff --git a/src/l1block.rs b/src/l1block.rs index 70ce446..a6d20c5 100644 --- a/src/l1block.rs +++ b/src/l1block.rs @@ -211,23 +211,23 @@ impl L1BlockInfo { let exec_scalar = self .l1_commit_scalar - .unwrap_or_else(|| panic!("missing exec scalar in spec_id={:?}", spec_id)); + .unwrap_or_else(|| panic!("missing exec scalar in spec_id={spec_id:?}")); let compressed_blob_scalar = self .l1_blob_scalar - .unwrap_or_else(|| panic!("missing l1 blob scalar in spec_id={:?}", spec_id)); + .unwrap_or_else(|| panic!("missing l1 blob scalar in spec_id={spec_id:?}")); let l1_blob_base_fee = self .l1_blob_base_fee - .unwrap_or_else(|| panic!("missing l1 blob base fee in spec_id={:?}", spec_id)); + .unwrap_or_else(|| panic!("missing l1 blob base fee in spec_id={spec_id:?}")); let penalty_threshold = self .penalty_threshold - .unwrap_or_else(|| panic!("missing penalty threshold in spec_id={:?}", spec_id)); + .unwrap_or_else(|| panic!("missing penalty threshold in spec_id={spec_id:?}")); let penalty_factor = self .penalty_factor - .unwrap_or_else(|| panic!("missing penalty factor in spec_id={:?}", spec_id)); + .unwrap_or_else(|| panic!("missing penalty factor in spec_id={spec_id:?}")); let tx_size = U256::from(input.len()); @@ -261,7 +261,7 @@ impl L1BlockInfo { self.calculate_tx_l1_cost_curie(input, spec_id) } else { let compression_ratio = compression_ratio.unwrap_or_else(|| { - panic!("compression ratio should be set in spec_id={:?}", spec_id) + panic!("compression ratio should be set in spec_id={spec_id:?}") }); self.calculate_tx_l1_cost_feynman(input, spec_id, compression_ratio) };