Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/evm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ impl<CTX: ScrollContextTr, INSP>
Self(Evm {
ctx,
inspector,
instruction: ScrollInstructions::new_mainnet(spec),
instruction: ScrollInstructions::new_mainnet(),
precompiles: ScrollPrecompileProvider::new_with_spec(spec),
})
}
Expand Down
65 changes: 49 additions & 16 deletions src/instructions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<WIRE: InterpreterTypes, HOST> {
pub instruction_table: Rc<InstructionTable<WIRE, HOST>>,
Expand Down Expand Up @@ -46,8 +49,8 @@ where
WIRE: InterpreterTypes,
HOST: ScrollContextTr,
{
pub fn new_mainnet(spec: ScrollSpecId) -> Self {
Self::new(make_scroll_instruction_table::<WIRE, HOST>(spec))
pub fn new_mainnet() -> Self {
Self::new(make_scroll_instruction_table::<WIRE, HOST>())
}

pub fn new(base_table: InstructionTable<WIRE, HOST>) -> Self {
Expand All @@ -65,22 +68,17 @@ where
/// - `SELFDESTRUCT`
/// - `MCOPY`
pub fn make_scroll_instruction_table<WIRE: InterpreterTypes, HOST: ScrollContextTr>(
spec: ScrollSpecId,
) -> InstructionTable<WIRE, HOST> {
let mut table = instruction_table::<WIRE, HOST>();

// override the instructions
table[opcode::BLOCKHASH as usize] = blockhash::<WIRE, HOST>;
table[opcode::BASEFEE as usize] = basefee::<WIRE, HOST>;
table[opcode::TSTORE as usize] = tstore::<WIRE, HOST>;
table[opcode::TLOAD as usize] = tload::<WIRE, HOST>;
table[opcode::SELFDESTRUCT as usize] = selfdestruct::<WIRE, HOST>;
table[opcode::MCOPY as usize] = mcopy::<WIRE, HOST>;

// override blockhash opcode in pre-feynman blocks
if !spec.is_enabled_in(ScrollSpecId::FEYNMAN) {
table[opcode::BLOCKHASH as usize] = blockhash::<WIRE, HOST>;
}

table
}

Expand All @@ -89,10 +87,12 @@ pub fn make_scroll_instruction_table<WIRE: InterpreterTypes, HOST: ScrollContext

/// Computes the blockhash for the requested block number.
///
/// The blockhash is computed as the keccak256 hash of the chain id and the block number.
/// If the requested block number is the current block number, a future block number or a block
/// number older than `BLOCK_HASH_HISTORY` we return 0.
fn blockhash<WIRE: InterpreterTypes, H: Host>(interpreter: &mut Interpreter<WIRE>, host: &mut H) {
fn blockhash<WIRE: InterpreterTypes, H: ScrollContextTr>(
interpreter: &mut Interpreter<WIRE>,
host: &mut H,
) {
gas!(interpreter, gas::BLOCKHASH);
popn_top!([], requested_block_number, interpreter);

Expand All @@ -106,11 +106,31 @@ fn blockhash<WIRE: InterpreterTypes, H: Host>(interpreter: &mut Interpreter<WIRE
0 => 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
}
};
}

Expand Down Expand Up @@ -222,6 +242,7 @@ mod tests {

use crate::{
builder::{DefaultScrollContext, ScrollContext},
instructions::HISTORY_STORAGE_ADDRESS,
ScrollSpecId::*,
};

Expand All @@ -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);
Expand All @@ -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);
}
Expand Down
12 changes: 6 additions & 6 deletions src/l1block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());

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