-
Notifications
You must be signed in to change notification settings - Fork 972
feat: Implement EIP-8024 for Amsterdam #3223
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -623,9 +623,9 @@ opcodes! { | |
| // 0xE3 | ||
| // 0xE4 | ||
| // 0xE5 | ||
| // 0xE6 | ||
| // 0xE7 | ||
| // 0xE8 | ||
| 0xE6 => DUPN => stack_io(0, 1), immediate_size(1); | ||
| 0xE7 => SWAPN => stack_io(0, 0), immediate_size(1); | ||
| 0xE8 => EXCHANGE => stack_io(0, 0), immediate_size(1); | ||
|
Comment on lines
+626
to
+628
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
| // 0xE9 | ||
| // 0xEA | ||
| // 0xEB | ||
|
|
@@ -668,11 +668,15 @@ mod tests { | |
| #[test] | ||
| fn test_immediate_size() { | ||
| let mut expected = [0u8; 256]; | ||
| // PUSH opcodes | ||
|
|
||
| for push in PUSH1..=PUSH32 { | ||
| expected[push as usize] = push - PUSH1 + 1; | ||
| } | ||
|
|
||
| for stack_op in [DUPN, SWAPN, EXCHANGE] { | ||
| expected[stack_op as usize] = 1; | ||
| } | ||
|
|
||
| for (i, opcode) in OPCODE_INFO.iter().enumerate() { | ||
| if let Some(opcode) = opcode { | ||
| assert_eq!( | ||
|
|
@@ -718,7 +722,7 @@ mod tests { | |
| for _ in OPCODE_INFO.into_iter().flatten() { | ||
| opcode_num += 1; | ||
| } | ||
| assert_eq!(opcode_num, 150); | ||
| assert_eq!(opcode_num, 153); | ||
| } | ||
|
|
||
| #[test] | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -81,6 +81,8 @@ pub enum InstructionResult { | |
| CreateInitCodeSizeLimit, | ||
| /// Fatal external error. Returned by database. | ||
| FatalExternalError, | ||
| /// Invalid encoding of an instruction's immediate operand. | ||
| InvalidImmediateEncoding, | ||
| } | ||
|
|
||
| impl From<TransferError> for InstructionResult { | ||
|
|
@@ -190,6 +192,7 @@ macro_rules! return_error { | |
| | $crate::InstructionResult::CreateContractStartingWithEF | ||
| | $crate::InstructionResult::CreateInitCodeSizeLimit | ||
| | $crate::InstructionResult::FatalExternalError | ||
| | $crate::InstructionResult::InvalidImmediateEncoding | ||
| }; | ||
| } | ||
|
|
||
|
|
@@ -348,6 +351,9 @@ impl<HaltReasonTr: From<HaltReason>> From<InstructionResult> for SuccessOrHalt<H | |
| InstructionResult::InvalidExtDelegateCallTarget => { | ||
| Self::Internal(InternalResult::InvalidExtDelegateCallTarget) | ||
| } | ||
| InstructionResult::InvalidImmediateEncoding => { | ||
| Self::Halt(HaltReason::OpcodeNotFound.into()) | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't want to add a new |
||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -34,7 +34,6 @@ pub fn push<const N: usize, WIRE: InterpreterTypes, H: ?Sized>( | |
| return; | ||
| } | ||
|
|
||
| // Can ignore return. as relative N jump is safe operation | ||
| context.interpreter.bytecode.relative_jump(N as isize); | ||
| } | ||
|
|
||
|
|
@@ -60,3 +59,176 @@ pub fn swap<const N: usize, WIRE: InterpreterTypes, H: ?Sized>( | |
| context.interpreter.halt(InstructionResult::StackOverflow); | ||
| } | ||
| } | ||
|
|
||
| /// Implements the DUPN instruction. | ||
| /// | ||
| /// Duplicates the Nth stack item to the top of the stack, with N given by an immediate. | ||
| pub fn dupn<WIRE: InterpreterTypes, H: ?Sized>(context: InstructionContext<'_, H, WIRE>) { | ||
| check!(context.interpreter, AMSTERDAM); | ||
| let x: usize = context.interpreter.bytecode.read_u8().into(); | ||
| if let Some(n) = decode_single(x) { | ||
| if !context.interpreter.stack.dup(n) { | ||
| context.interpreter.halt(InstructionResult::StackOverflow); | ||
| } | ||
| context.interpreter.bytecode.relative_jump(1); | ||
| } else { | ||
| context | ||
| .interpreter | ||
| .halt(InstructionResult::InvalidImmediateEncoding); | ||
| } | ||
| } | ||
|
|
||
| /// Implements the SWAPN instruction. | ||
| /// | ||
| /// Swaps the top stack item with the N+1th stack item, with N given by an immediate. | ||
| pub fn swapn<WIRE: InterpreterTypes, H: ?Sized>(context: InstructionContext<'_, H, WIRE>) { | ||
| check!(context.interpreter, AMSTERDAM); | ||
| let x: usize = context.interpreter.bytecode.read_u8().into(); | ||
| if let Some(n) = decode_single(x) { | ||
| if !context.interpreter.stack.exchange(0, n) { | ||
| context.interpreter.halt(InstructionResult::StackOverflow); | ||
| } | ||
| context.interpreter.bytecode.relative_jump(1); | ||
| } else { | ||
| context | ||
| .interpreter | ||
| .halt(InstructionResult::InvalidImmediateEncoding); | ||
| } | ||
| } | ||
|
|
||
| /// Implements the EXCHANGE instruction. | ||
| /// | ||
| /// Swaps the N+1th stack item with the M+1th stack item, with N, M given by an immediate. | ||
| pub fn exchange<WIRE: InterpreterTypes, H: ?Sized>(context: InstructionContext<'_, H, WIRE>) { | ||
| check!(context.interpreter, AMSTERDAM); | ||
| let x: usize = context.interpreter.bytecode.read_u8().into(); | ||
| if let Some((n, m)) = decode_pair(x) { | ||
| if !context.interpreter.stack.exchange(n, m - n) { | ||
| context.interpreter.halt(InstructionResult::StackOverflow); | ||
| } | ||
| context.interpreter.bytecode.relative_jump(1); | ||
| } else { | ||
| context | ||
| .interpreter | ||
| .halt(InstructionResult::InvalidImmediateEncoding); | ||
| } | ||
| } | ||
|
|
||
| fn decode_single(x: usize) -> Option<usize> { | ||
| if x <= 90 { | ||
| Some(x + 17) | ||
| } else if x >= 128 { | ||
| Some(x - 20) | ||
| } else { | ||
| None | ||
| } | ||
| } | ||
|
|
||
| fn decode_pair(x: usize) -> Option<(usize, usize)> { | ||
| let k = if x <= 79 { | ||
| x | ||
| } else if x >= 128 { | ||
| x - 48 | ||
| } else { | ||
| return None; | ||
| }; | ||
| let q = k / 16; | ||
| let r = k % 16; | ||
| if q < r { | ||
| Some((q + 1, r + 1)) | ||
| } else { | ||
| Some((r + 1, 29 - q)) | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The test cases are the same listed in the EIP. |
||
| use crate::{ | ||
| gas::params::GasParams, | ||
| host::DummyHost, | ||
| instructions::instruction_table, | ||
| interpreter::{EthInterpreter, ExtBytecode, InputsImpl, SharedMemory}, | ||
| interpreter_types::LoopControl, | ||
| Interpreter, | ||
| }; | ||
| use bytecode::opcode::*; | ||
| use bytecode::Bytecode; | ||
| use primitives::{hardfork::SpecId, Bytes, U256}; | ||
|
|
||
| fn run_bytecode(code: &[u8]) -> Interpreter { | ||
| let bytecode = Bytecode::new_raw(Bytes::copy_from_slice(code)); | ||
| let mut interpreter = Interpreter::<EthInterpreter>::new( | ||
| SharedMemory::new(), | ||
| ExtBytecode::new(bytecode), | ||
| InputsImpl::default(), | ||
| false, | ||
| SpecId::AMSTERDAM, | ||
| u64::MAX, | ||
| GasParams::default(), | ||
| ); | ||
| let table = instruction_table::<EthInterpreter, DummyHost>(); | ||
| let mut host = DummyHost; | ||
| interpreter.run_plain(&table, &mut host); | ||
| interpreter | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_dupn() { | ||
| let interpreter = run_bytecode(&[ | ||
| PUSH1, 0x01, PUSH1, 0x00, DUP1, DUP1, DUP1, DUP1, DUP1, DUP1, DUP1, DUP1, DUP1, DUP1, | ||
| DUP1, DUP1, DUP1, DUP1, DUP1, DUPN, 0x00, | ||
| ]); | ||
| assert_eq!(interpreter.stack.len(), 18); | ||
| assert_eq!(interpreter.stack.data()[17], U256::from(1)); | ||
| assert_eq!(interpreter.stack.data()[0], U256::from(1)); | ||
| for i in 1..17 { | ||
| assert_eq!(interpreter.stack.data()[i], U256::ZERO); | ||
| } | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_swapn() { | ||
| let interpreter = run_bytecode(&[ | ||
| PUSH1, 0x01, PUSH1, 0x00, DUP1, DUP1, DUP1, DUP1, DUP1, DUP1, DUP1, DUP1, DUP1, DUP1, | ||
| DUP1, DUP1, DUP1, DUP1, DUP1, PUSH1, 0x02, SWAPN, 0x00, | ||
| ]); | ||
| assert_eq!(interpreter.stack.len(), 18); | ||
| assert_eq!(interpreter.stack.data()[17], U256::from(1)); | ||
| assert_eq!(interpreter.stack.data()[0], U256::from(2)); | ||
| for i in 1..17 { | ||
| assert_eq!(interpreter.stack.data()[i], U256::ZERO); | ||
| } | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_exchange() { | ||
| let interpreter = run_bytecode(&[PUSH1, 0x00, PUSH1, 0x01, PUSH1, 0x02, EXCHANGE, 0x01]); | ||
| assert_eq!(interpreter.stack.len(), 3); | ||
| assert_eq!(interpreter.stack.data()[2], U256::from(2)); | ||
| assert_eq!(interpreter.stack.data()[1], U256::from(0)); | ||
| assert_eq!(interpreter.stack.data()[0], U256::from(1)); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_swapn_invalid_immediate() { | ||
| let mut interpreter = run_bytecode(&[SWAPN, JUMPDEST]); | ||
| assert!(interpreter.bytecode.instruction_result().is_none()); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_jump_over_invalid_dupn() { | ||
| let interpreter = run_bytecode(&[PUSH1, 0x04, JUMP, DUPN, JUMPDEST]); | ||
| assert!(interpreter.bytecode.is_not_end()); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_exchange_with_iszero() { | ||
| let interpreter = run_bytecode(&[ | ||
| PUSH1, 0x00, PUSH1, 0x00, PUSH1, 0x00, EXCHANGE, 0x01, ISZERO, | ||
| ]); | ||
| assert_eq!(interpreter.stack.len(), 3); | ||
| assert_eq!(interpreter.stack.data()[2], U256::from(1)); | ||
| assert_eq!(interpreter.stack.data()[1], U256::ZERO); | ||
| assert_eq!(interpreter.stack.data()[0], U256::ZERO); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, this can generate a different jumptable for previous bytecode, as we are now jumping over one more immediate
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, will need to rework this so jump table is same as previous bytecodes.