-
Notifications
You must be signed in to change notification settings - Fork 88
feat(evm): add EIP-7939 CLZ opcode (0x1e) #1709
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
base: master
Are you sure you want to change the base?
Changes from 3 commits
fb766d4
bfce3e4
299153c
1a92c7f
775f332
270bfcd
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 |
|---|---|---|
|
|
@@ -125,6 +125,7 @@ pub mod opcodes { | |
| 0x1b: SHL, | ||
| 0x1c: SHR, | ||
| 0x1d: SAR, | ||
| 0x1e: CLZ, | ||
| 0x20: KECCAK256, | ||
| 0x30: ADDRESS, | ||
| 0x31: BALANCE, | ||
|
|
@@ -292,6 +293,11 @@ pub fn execute( | |
| mod tests { | ||
| use crate::evm_unit_test; | ||
|
|
||
| #[test] | ||
| fn test_clz_opcode_value() { | ||
| assert_eq!(super::opcodes::CLZ, 0x1e); | ||
| } | ||
|
|
||
| macro_rules! check_underflow_err { | ||
| ($($ins:ident,)*) => { | ||
| $(do_check_underflow_err!($ins);)* | ||
|
|
@@ -415,6 +421,7 @@ mod tests { | |
| CODECOPY, | ||
| CREATE, | ||
| CREATE2, | ||
| CLZ, | ||
|
||
| RETURN, | ||
| REVERT, | ||
| SELFDESTRUCT, | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -43,9 +43,81 @@ pub fn sar(shift: U256, mut value: U256) -> U256 { | |||||||
| } | ||||||||
| } | ||||||||
|
|
||||||||
| #[inline] | ||||||||
|
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.
Suggested change
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. Applied this in spirit. I kept the doc comment a bit more explicit and also called out |
||||||||
| pub fn clz(value: U256) -> U256 { | ||||||||
| U256::from(value.leading_zeros()) | ||||||||
| } | ||||||||
|
Comment on lines
+48
to
+51
Contributor
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. nit: it might be obvious to some, but my brain doesn't immediately click that 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. +1 on this. Try I see that this is also okay in a way that EIP calling it "CLZ", so should be fine.
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. Added a |
||||||||
|
|
||||||||
| #[cfg(test)] | ||||||||
| mod tests { | ||||||||
| use super::*; | ||||||||
| use crate::interpreter::opcodes; | ||||||||
| use crate::interpreter::{execution::Machine, system::System, Output}; | ||||||||
| use crate::{Bytecode, EthAddress, ExecutionState}; | ||||||||
| use fil_actors_runtime::test_utils::MockRuntime; | ||||||||
| use fvm_shared::econ::TokenAmount; | ||||||||
|
|
||||||||
| #[test] | ||||||||
| fn test_clz_eip7939_vectors_unit() { | ||||||||
| // Directly matches the EIP-7939 test cases. | ||||||||
|
||||||||
| assert_eq!(clz(U256::ZERO), U256::from(256)); | ||||||||
| assert_eq!(clz(U256::ONE << 255), U256::ZERO); | ||||||||
| assert_eq!(clz(U256::MAX), U256::ZERO); | ||||||||
| assert_eq!(clz(U256::ONE << 254), U256::ONE); | ||||||||
| assert_eq!(clz((U256::ONE << 255) - U256::ONE), U256::ONE); | ||||||||
| assert_eq!(clz(U256::ONE), U256::from(255)); | ||||||||
| } | ||||||||
|
|
||||||||
| #[test] | ||||||||
|
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. Not entirely sure of this test. Either we introduce a wide range of tests, or a fuzzy test here, or none at all. This seems quite arbitrary.
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. Removed the extra |
||||||||
| fn test_clz_misc_unit() { | ||||||||
| // 2 is 10 binary, so 256 - 2 = 254 | ||||||||
| assert_eq!(clz(U256::from(2)), U256::from(254)); | ||||||||
| } | ||||||||
|
|
||||||||
| fn clz_via_evm(value: U256) -> U256 { | ||||||||
| let rt = MockRuntime::default(); | ||||||||
| rt.in_call.replace(true); | ||||||||
|
|
||||||||
| let mut state = ExecutionState::new( | ||||||||
| EthAddress::from_id(1000), | ||||||||
| EthAddress::from_id(1000), | ||||||||
| TokenAmount::from_atto(0), | ||||||||
| Vec::new(), | ||||||||
| ); | ||||||||
|
|
||||||||
| let mut imm = [0u8; 32]; | ||||||||
| value.write_as_big_endian(&mut imm); | ||||||||
|
|
||||||||
| let mut code = Vec::with_capacity(1 + 32 + 1); | ||||||||
| code.push(opcodes::PUSH32); | ||||||||
| code.extend_from_slice(&imm); | ||||||||
| code.push(opcodes::CLZ); | ||||||||
|
|
||||||||
| let mut system = System::new(&rt, false); | ||||||||
| let bytecode = Bytecode::new(code); | ||||||||
| let mut machine = Machine { | ||||||||
| system: &mut system, | ||||||||
| state: &mut state, | ||||||||
| bytecode: &bytecode, | ||||||||
| pc: 0, | ||||||||
| output: Output::default(), | ||||||||
| }; | ||||||||
|
|
||||||||
| machine.step().expect("PUSH32 step failed"); | ||||||||
| machine.step().expect("CLZ step failed"); | ||||||||
| machine.state.stack.pop_many::<1>().expect("missing CLZ result")[0] | ||||||||
| } | ||||||||
|
|
||||||||
| #[test] | ||||||||
| fn test_clz_eip7939_vectors() { | ||||||||
|
||||||||
| fn test_clz_eip7939_vectors() { | |
| fn test_clz_eip7939_vectors_via_evm() { |
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.
Kept the machine-level coverage, renamed it to test_clz_eip7939_vectors_via_evm, and switched it to the shared vectors so it's no longer a copy/paste duplicate.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| use fil_actor_evm::interpreter::opcodes; | ||
| use fil_actors_evm_shared::uints::U256; | ||
|
|
||
| mod util; | ||
|
|
||
| fn initcode_for_runtime(runtime: &[u8]) -> Vec<u8> { | ||
| let len: u16 = runtime.len().try_into().expect("runtime too large"); | ||
|
|
||
| // initcode: | ||
| // PUSH2 len | ||
| // PUSH2 offset | ||
| // PUSH1 0x00 | ||
| // CODECOPY | ||
| // PUSH2 len | ||
| // PUSH1 0x00 | ||
| // RETURN | ||
| // <runtime bytes> | ||
|
||
| let init_len: u16 = 15; | ||
| let offset = init_len; | ||
|
|
||
| let [len_hi, len_lo] = len.to_be_bytes(); | ||
| let [off_hi, off_lo] = offset.to_be_bytes(); | ||
|
|
||
| let mut code = vec![ | ||
| opcodes::PUSH2, | ||
| len_hi, | ||
| len_lo, | ||
| opcodes::PUSH2, | ||
| off_hi, | ||
| off_lo, | ||
| opcodes::PUSH1, | ||
| 0x00, | ||
| opcodes::CODECOPY, | ||
| opcodes::PUSH2, | ||
| len_hi, | ||
| len_lo, | ||
| opcodes::PUSH1, | ||
| 0x00, | ||
| opcodes::RETURN, | ||
| ]; | ||
| code.extend_from_slice(runtime); | ||
| code | ||
| } | ||
|
|
||
| fn clz_runtime() -> Vec<u8> { | ||
| // Reads a 32-byte word from calldata[0..32], computes CLZ, and returns the 32-byte result. | ||
| vec![ | ||
| opcodes::PUSH1, | ||
| 0x00, | ||
|
||
| opcodes::CALLDATALOAD, | ||
| opcodes::CLZ, | ||
| opcodes::PUSH1, | ||
| 0x00, | ||
| opcodes::MSTORE, | ||
| opcodes::PUSH1, | ||
| 0x20, | ||
|
||
| opcodes::PUSH1, | ||
| 0x00, | ||
| opcodes::RETURN, | ||
| ] | ||
| } | ||
|
|
||
| fn u256_be_bytes(value: U256) -> [u8; 32] { | ||
| let mut out = [0u8; 32]; | ||
| value.write_as_big_endian(&mut out); | ||
| out | ||
| } | ||
|
|
||
| #[test] | ||
| fn eip7939_clz_vectors_end_to_end() { | ||
| let initcode = initcode_for_runtime(&clz_runtime()); | ||
| let rt = util::construct_and_verify(initcode); | ||
|
|
||
| let vectors = [ | ||
| (U256::ZERO, U256::from(256)), | ||
|
||
| (U256::ONE << 255, U256::ZERO), | ||
| (U256::MAX, U256::ZERO), | ||
| (U256::ONE << 254, U256::ONE), | ||
| ((U256::ONE << 255) - U256::ONE, U256::ONE), | ||
| (U256::ONE, U256::from(255)), | ||
| ]; | ||
|
|
||
| for (input, expected) in vectors { | ||
| let ret = util::invoke_contract(&rt, &u256_be_bytes(input)); | ||
| assert_eq!(ret.len(), 32); | ||
| assert_eq!(ret.as_slice(), &u256_be_bytes(expected)); | ||
| } | ||
| } | ||
|
|
||
snissn marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
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.
I don't think it's a valuable test. If we want to check the
def_opcodesmacro works, lets write a test to for it entirely.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.
I second this. Introduce this only if other opcodes do it as well.
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.
Removed the standalone
test_clz_opcode_valuecheck. The execution underflow coverage still exercisesCLZalongside the other opcodes.