Skip to content

Commit ed2e388

Browse files
committed
feat: Implement EIP-8024 for Amsterdam
1 parent 2befb62 commit ed2e388

File tree

5 files changed

+191
-7
lines changed

5 files changed

+191
-7
lines changed

crates/bytecode/src/opcode.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -623,9 +623,9 @@ opcodes! {
623623
// 0xE3
624624
// 0xE4
625625
// 0xE5
626-
// 0xE6
627-
// 0xE7
628-
// 0xE8
626+
0xE6 => DUPN => stack_io(0, 1), immediate_size(1);
627+
0xE7 => SWAPN => stack_io(0, 0), immediate_size(1);
628+
0xE8 => EXCHANGE => stack_io(0, 0), immediate_size(1);
629629
// 0xE9
630630
// 0xEA
631631
// 0xEB
@@ -668,11 +668,15 @@ mod tests {
668668
#[test]
669669
fn test_immediate_size() {
670670
let mut expected = [0u8; 256];
671-
// PUSH opcodes
671+
672672
for push in PUSH1..=PUSH32 {
673673
expected[push as usize] = push - PUSH1 + 1;
674674
}
675675

676+
for stack_op in [DUPN, SWAPN, EXCHANGE] {
677+
expected[stack_op as usize] = 1;
678+
}
679+
676680
for (i, opcode) in OPCODE_INFO.iter().enumerate() {
677681
if let Some(opcode) = opcode {
678682
assert_eq!(
@@ -718,7 +722,7 @@ mod tests {
718722
for _ in OPCODE_INFO.into_iter().flatten() {
719723
opcode_num += 1;
720724
}
721-
assert_eq!(opcode_num, 150);
725+
assert_eq!(opcode_num, 153);
722726
}
723727

724728
#[test]

crates/interpreter/src/instruction_result.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ pub enum InstructionResult {
8181
CreateInitCodeSizeLimit,
8282
/// Fatal external error. Returned by database.
8383
FatalExternalError,
84+
/// Invalid encoding of an instruction's immediate operand.
85+
InvalidImmediateEncoding,
8486
}
8587

8688
impl From<TransferError> for InstructionResult {
@@ -190,6 +192,7 @@ macro_rules! return_error {
190192
| $crate::InstructionResult::CreateContractStartingWithEF
191193
| $crate::InstructionResult::CreateInitCodeSizeLimit
192194
| $crate::InstructionResult::FatalExternalError
195+
| $crate::InstructionResult::InvalidImmediateEncoding
193196
};
194197
}
195198

@@ -348,6 +351,9 @@ impl<HaltReasonTr: From<HaltReason>> From<InstructionResult> for SuccessOrHalt<H
348351
InstructionResult::InvalidExtDelegateCallTarget => {
349352
Self::Internal(InternalResult::InvalidExtDelegateCallTarget)
350353
}
354+
InstructionResult::InvalidImmediateEncoding => {
355+
Self::Halt(HaltReason::OpcodeNotFound.into())
356+
}
351357
}
352358
}
353359
}
@@ -419,4 +425,4 @@ mod tests {
419425
assert!(result.is_error());
420426
}
421427
}
422-
}
428+
}

crates/interpreter/src/instructions.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,10 @@ const fn instruction_table_impl<WIRE: InterpreterTypes, H: Host>() -> [Instructi
278278
table[SWAP15 as usize] = Instruction::new(stack::swap::<15, _, _>, 3);
279279
table[SWAP16 as usize] = Instruction::new(stack::swap::<16, _, _>, 3);
280280

281+
table[DUPN as usize] = Instruction::new(stack::dupn, 3);
282+
table[SWAPN as usize] = Instruction::new(stack::swapn, 3);
283+
table[EXCHANGE as usize] = Instruction::new(stack::exchange, 3);
284+
281285
table[LOG0 as usize] = Instruction::new(host::log::<0, _>, gas::LOG);
282286
table[LOG1 as usize] = Instruction::new(host::log::<1, _>, gas::LOG);
283287
table[LOG2 as usize] = Instruction::new(host::log::<2, _>, gas::LOG);

crates/interpreter/src/instructions/stack.rs

Lines changed: 170 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ pub fn push<const N: usize, WIRE: InterpreterTypes, H: ?Sized>(
3434
return;
3535
}
3636

37-
// Can ignore return. as relative N jump is safe operation
3837
context.interpreter.bytecode.relative_jump(N as isize);
3938
}
4039

@@ -60,3 +59,173 @@ pub fn swap<const N: usize, WIRE: InterpreterTypes, H: ?Sized>(
6059
context.interpreter.halt(InstructionResult::StackOverflow);
6160
}
6261
}
62+
63+
/// Implements the DUPN instruction.
64+
///
65+
/// Duplicates the Nth stack item to the top of the stack, with N given by an immediate.
66+
pub fn dupn<WIRE: InterpreterTypes, H: ?Sized>(
67+
context: InstructionContext<'_, H, WIRE>,
68+
) {
69+
check!(context.interpreter, AMSTERDAM);
70+
let x: usize = context.interpreter.bytecode.read_u8().into();
71+
if let Some(n) = decode_single(x) {
72+
if !context.interpreter.stack.dup(n) {
73+
context.interpreter.halt(InstructionResult::StackOverflow);
74+
}
75+
context.interpreter.bytecode.relative_jump(1);
76+
} else {
77+
context.interpreter.halt(InstructionResult::InvalidImmediateEncoding);
78+
}
79+
}
80+
81+
/// Implements the SWAPN instruction.
82+
///
83+
/// Swaps the top stack item with the N+1th stack item, with N given by an immediate.
84+
pub fn swapn<WIRE: InterpreterTypes, H: ?Sized>(
85+
context: InstructionContext<'_, H, WIRE>,
86+
) {
87+
check!(context.interpreter, AMSTERDAM);
88+
let x: usize = context.interpreter.bytecode.read_u8().into();
89+
if let Some(n) = decode_single(x) {
90+
if !context.interpreter.stack.exchange(0, n) {
91+
context.interpreter.halt(InstructionResult::StackOverflow);
92+
}
93+
context.interpreter.bytecode.relative_jump(1);
94+
} else {
95+
context.interpreter.halt(InstructionResult::InvalidImmediateEncoding);
96+
}
97+
}
98+
99+
/// Implements the EXCHANGE instruction.
100+
///
101+
/// Swaps the N+1th stack item with the M+1th stack item, with N, M given by an immediate.
102+
pub fn exchange<WIRE: InterpreterTypes, H: ?Sized>(
103+
context: InstructionContext<'_, H, WIRE>,
104+
) {
105+
check!(context.interpreter, AMSTERDAM);
106+
let x: usize = context.interpreter.bytecode.read_u8().into();
107+
if let Some((n, m)) = decode_pair(x) {
108+
if !context.interpreter.stack.exchange(n, m - n) {
109+
context.interpreter.halt(InstructionResult::StackOverflow);
110+
}
111+
context.interpreter.bytecode.relative_jump(1);
112+
} else {
113+
context.interpreter.halt(InstructionResult::InvalidImmediateEncoding);
114+
}
115+
}
116+
117+
fn decode_single(x: usize) -> Option<usize> {
118+
if x <= 90 {
119+
Some(x + 17)
120+
} else if x >= 128 {
121+
Some(x - 20)
122+
} else {
123+
None
124+
}
125+
}
126+
127+
fn decode_pair(x: usize) -> Option<(usize, usize)> {
128+
let k = if x <= 79 {
129+
x
130+
} else if x >= 128 {
131+
x - 48
132+
} else {
133+
return None;
134+
};
135+
let q = k / 16;
136+
let r = k % 16;
137+
if q < r {
138+
Some((q + 1, r + 1))
139+
} else {
140+
Some((r + 1, 29 - q))
141+
}
142+
}
143+
144+
#[cfg(test)]
145+
mod tests {
146+
use crate::{
147+
gas::params::GasParams, host::DummyHost, instructions::instruction_table, interpreter::{EthInterpreter, ExtBytecode, InputsImpl, SharedMemory}, interpreter_types::LoopControl, Interpreter
148+
};
149+
use bytecode::Bytecode;
150+
use primitives::{hardfork::SpecId, Bytes, U256};
151+
152+
fn run_bytecode(code: &[u8]) -> Interpreter {
153+
let bytecode = Bytecode::new_raw(Bytes::copy_from_slice(code));
154+
let mut interpreter = Interpreter::<EthInterpreter>::new(
155+
SharedMemory::new(),
156+
ExtBytecode::new(bytecode),
157+
InputsImpl::default(),
158+
false,
159+
SpecId::OSAKA,
160+
u64::MAX,
161+
GasParams::default(),
162+
);
163+
let table = instruction_table::<EthInterpreter, DummyHost>();
164+
let mut host = DummyHost;
165+
interpreter.run_plain(&table, &mut host);
166+
interpreter
167+
}
168+
169+
#[test]
170+
fn test_dupn() {
171+
let interpreter = run_bytecode(&[
172+
0x60, 0x01, 0x60, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
173+
0x80, 0x80, 0x80, 0x80, 0x80, 0xe6, 0x00,
174+
]);
175+
assert_eq!(interpreter.stack.len(), 18);
176+
assert_eq!(interpreter.stack.data()[17], U256::from(1));
177+
assert_eq!(interpreter.stack.data()[0], U256::from(1));
178+
for i in 1..17 {
179+
assert_eq!(interpreter.stack.data()[i], U256::ZERO);
180+
}
181+
}
182+
183+
#[test]
184+
fn test_swapn() {
185+
let interpreter = run_bytecode(&[
186+
0x60, 0x01, 0x60, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
187+
0x80, 0x80, 0x80, 0x80, 0x80, 0x60, 0x02, 0xe7, 0x00,
188+
]);
189+
assert_eq!(interpreter.stack.len(), 18);
190+
assert_eq!(interpreter.stack.data()[17], U256::from(1));
191+
assert_eq!(interpreter.stack.data()[0], U256::from(2));
192+
for i in 1..17 {
193+
assert_eq!(interpreter.stack.data()[i], U256::ZERO);
194+
}
195+
}
196+
197+
#[test]
198+
fn test_exchange() {
199+
let interpreter = run_bytecode(&[0x60, 0x00, 0x60, 0x01, 0x60, 0x02, 0xe8, 0x01]);
200+
assert_eq!(interpreter.stack.len(), 3);
201+
assert_eq!(interpreter.stack.data()[2], U256::from(2));
202+
assert_eq!(interpreter.stack.data()[1], U256::from(0));
203+
assert_eq!(interpreter.stack.data()[0], U256::from(1));
204+
}
205+
206+
#[test]
207+
fn test_swapn_invalid_immediate() {
208+
let mut interpreter = run_bytecode(&[0xe7, 0x5b]);
209+
assert!(interpreter
210+
.bytecode
211+
.instruction_result()
212+
.is_none());
213+
}
214+
215+
#[test]
216+
fn test_jump_over_invalid_dupn() {
217+
let interpreter = run_bytecode(&[0x60, 0x04, 0x56, 0xe6, 0x5b]);
218+
assert!(interpreter
219+
.bytecode
220+
.is_not_end());
221+
}
222+
223+
#[test]
224+
fn test_exchange_with_iszero() {
225+
let interpreter = run_bytecode(&[0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0xe8, 0x01, 0x15]);
226+
assert_eq!(interpreter.stack.len(), 3);
227+
assert_eq!(interpreter.stack.data()[2], U256::from(1));
228+
assert_eq!(interpreter.stack.data()[1], U256::ZERO);
229+
assert_eq!(interpreter.stack.data()[0], U256::ZERO);
230+
}
231+
}

crates/interpreter/src/interpreter_types.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ pub trait StackTr {
227227
/// Exchanges two values on the stack.
228228
///
229229
/// Indexes are based from the top of the stack.
230+
/// `n` is the first index, and the second index is calculated as `n + m`.
230231
///
231232
/// Returns `true` if swap was successful, `false` if stack underflow.
232233
#[must_use]

0 commit comments

Comments
 (0)