Skip to content
Open
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
11 changes: 6 additions & 5 deletions crates/vm/levm/src/opcode_handlers/frame_tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -457,10 +457,11 @@ impl OpcodeHandler for OpFrameParamHandler {
/// SIGPARAM (0xB4) -- signature-scoped metadata and data copy (EIP-8141).
/// Metadata (params 0x00-0x03): stack `[param, signatureIndex]` with
/// `signatureIndex` on top; gas 2; returns one word (0x00 effective signer,
/// 0x01 scheme, 0x02 msg, 0x03 len(signature)). Copy (param 0x04): stack
/// `[memOffset, dataOffset, length, param, signatureIndex]` with `signatureIndex`
/// on top; CALLDATACOPY gas; copies an ARBITRARY signature's raw bytes into
/// memory (zero-filled past the end) and pushes nothing — any other scheme halts.
/// 0x01 scheme, 0x02 msg, 0x03 len(signature)). Copy (param 0x04): takes
/// `[signatureIndex, param, memOffset, dataOffset, length]` with `signatureIndex`
/// on top (popped first), matching `CALLDATACOPY`'s operand order; CALLDATACOPY
/// gas; copies an ARBITRARY signature's raw bytes into memory (zero-filled past
/// the end) and pushes nothing — any other scheme halts.
pub struct OpSigParamHandler;
impl OpcodeHandler for OpSigParamHandler {
#[inline(always)]
Expand All @@ -474,7 +475,7 @@ impl OpcodeHandler for OpSigParamHandler {
// 0x04: copy the referenced ARBITRARY signature's raw bytes into memory,
// CALLDATACOPY-style (see FRAMEDATACOPY). Pops three more operands.
if param == 0x04 {
let [length, data_offset, mem_offset] = *vm.current_call_frame.stack.pop()?;
let [mem_offset, data_offset, length] = *vm.current_call_frame.stack.pop()?;
let (length, mem_offset) = size_offset_to_usize(length, mem_offset)?;
let data_offset_opt = u256_to_offset(data_offset);

Expand Down
12 changes: 6 additions & 6 deletions docs/eip-8141.md
Original file line number Diff line number Diff line change
Expand Up @@ -316,12 +316,12 @@ signature-scoped metadata for `signatures[signatureIndex]`:
| 0x02 | msg (0 when empty — the canonical sig-hash case — else the 32-byte digest) |
| 0x03 | len(signature) |

**Copy (param 0x04)** — Stack input `[memOffset, dataOffset, length, param,
signatureIndex]` (signatureIndex on top); no stack output; gas cost matches
`CALLDATACOPY`. Copies `length` bytes of the referenced signature's raw
`signature`, starting at `dataOffset`, into memory at `memOffset`, zero-filling
past the end. Only valid for `ARBITRARY` signatures — any other scheme is an
exceptional halt.
**Copy (param 0x04)** — Stack input `[signatureIndex, param, memOffset, dataOffset,
length]`, listed top-first (signatureIndex on top, popped first), then the
`CALLDATACOPY` operand order; no stack output; gas cost matches `CALLDATACOPY`.
Copies `length` bytes of the referenced signature's raw `signature`, starting at
`dataOffset`, into memory at `memOffset`, zero-filling past the end. Only valid
for `ARBITRARY` signatures — any other scheme is an exceptional halt.

Any other `param`, or an out-of-bounds `signatureIndex`, is an exceptional halt.

Expand Down
39 changes: 29 additions & 10 deletions test/tests/levm/eip8141_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3287,26 +3287,26 @@ mod sigparam_execution_tests {
])
}

/// `SIGPARAM(0x04, index)` copying `length` bytes from `dataOffset` to memory
/// offset 0, then `SSTORE(0, MLOAD(0))`. The copy form takes
/// `[memOffset, dataOffset, length, param, signatureIndex]` with the index on
/// `SIGPARAM(0x04, index)` copying `length` bytes from `dataOffset` to
/// `memOffset`, then `SSTORE(0, MLOAD(memOffset))`. The copy form takes
/// `[signatureIndex, param, memOffset, dataOffset, length]` with the index on
/// top, so the pushes run in reverse.
fn sigparam_copy_code(index: u8, length: u8, data_offset: u8) -> Bytes {
fn sigparam_copy_code(index: u8, length: u8, data_offset: u8, mem_offset: u8) -> Bytes {
Bytes::from(vec![
0x60,
0x00, // PUSH1 memOffset
length, // PUSH1 length
0x60,
data_offset, // PUSH1 dataOffset
0x60,
length, // PUSH1 length
mem_offset, // PUSH1 memOffset
0x60,
0x04, // PUSH1 param (copy)
0x60,
index, // PUSH1 signatureIndex
0xB4, // SIGPARAM
0x60,
0x00, // PUSH1 0
0x51, // MLOAD
mem_offset, // PUSH1 memOffset
0x51, // MLOAD
0x60,
0x00, // PUSH1 0 (slot)
0x55, // SSTORE
Expand Down Expand Up @@ -3403,7 +3403,7 @@ mod sigparam_execution_tests {
// 4 payload bytes copied into memory at 0; MLOAD reads them left-aligned
// in the word, with the rest zero-filled.
let (result, db, reader) = run_reader(
sigparam_copy_code(0, 4, 0),
sigparam_copy_code(0, 4, 0, 0),
vec![arbitrary_sig(vec![0xDE, 0xAD, 0xBE, 0xEF])],
);
result.expect("valid tx");
Expand All @@ -3419,7 +3419,7 @@ mod sigparam_execution_tests {
fn sigparam_0x04_zero_fills_past_the_end() {
// Asking for 8 bytes of a 2-byte signature zero-fills the remainder.
let (result, db, reader) = run_reader(
sigparam_copy_code(0, 8, 0),
sigparam_copy_code(0, 8, 0, 0),
vec![arbitrary_sig(vec![0x11, 0x22])],
);
result.expect("valid tx");
Expand All @@ -3430,6 +3430,25 @@ mod sigparam_execution_tests {
U256::from_big_endian(&expected),
);
}

#[test]
fn sigparam_0x04_reads_operands_in_calldatacopy_order() {
// The copy operands follow `CALLDATACOPY`: memOffset above dataOffset
// above length. Distinct values for all three, and a destination past the
// first word, pin the order — reading them in any other order lands the
// bytes somewhere else (or copies a different count).
let (result, db, reader) = run_reader(
sigparam_copy_code(0, 3, 1, 0x20),
vec![arbitrary_sig(vec![0xAA, 0xBB, 0xCC, 0xDD])],
);
result.expect("valid tx");
let mut expected = [0u8; 32];
expected[..3].copy_from_slice(&[0xBB, 0xCC, 0xDD]);
assert_eq!(
storage_of(&db, reader, U256::zero()),
U256::from_big_endian(&expected),
);
}
}

// ==================== Default code: signature index by scope ====================
Expand Down
Loading