-
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 all 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 |
|---|---|---|
|
|
@@ -43,9 +43,73 @@ pub fn sar(shift: U256, mut value: U256) -> U256 { | |
| } | ||
| } | ||
|
|
||
| /// Implements EIP-7939 `CLZ`, returning the number of leading zero bits in a 256-bit word. | ||
| /// `CLZ(0)` returns 256. | ||
| #[inline] | ||
| 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::{Output, execution::Machine, system::System}; | ||
| use crate::{Bytecode, EthAddress, ExecutionState}; | ||
| use fil_actors_runtime::test_utils::MockRuntime; | ||
| use fvm_shared::econ::TokenAmount; | ||
|
|
||
| mod test_vectors { | ||
| include!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/test_vectors.rs")); | ||
| } | ||
|
|
||
| #[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_eip7939_vectors_unit() { | ||
| for (input, expected) in test_vectors::clz_eip7939_test_vectors() { | ||
| assert_eq!(clz(input), expected); | ||
| } | ||
| } | ||
|
|
||
| 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_via_evm() { | ||
| for (input, expected) in test_vectors::clz_eip7939_test_vectors() { | ||
| assert_eq!(clz_via_evm(input), expected); | ||
| } | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_shl() { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| use fil_actor_evm::interpreter::opcodes; | ||
| use fil_actors_evm_shared::uints::U256; | ||
|
|
||
| mod test_vectors; | ||
| mod util; | ||
|
|
||
| fn initcode_for_runtime(runtime: &[u8]) -> Vec<u8> { | ||
| // Universal runtime constructor: | ||
| // https://github.com/wjmelements/evm/pull/87 | ||
| const CONSTRUCTOR: [u8; 11] = [ | ||
| opcodes::PUSH1, | ||
| 0x0b, | ||
| opcodes::CODESIZE, | ||
| opcodes::SUB, | ||
| opcodes::DUP1, | ||
| opcodes::PUSH1, | ||
| 0x0b, | ||
| opcodes::RETURNDATASIZE, | ||
| opcodes::CODECOPY, | ||
| opcodes::RETURNDATASIZE, | ||
| opcodes::RETURN, | ||
| ]; | ||
|
|
||
| let mut code = Vec::with_capacity(CONSTRUCTOR.len() + runtime.len()); | ||
| code.extend_from_slice(&CONSTRUCTOR); | ||
| 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::PUSH0, | ||
| opcodes::CALLDATALOAD, | ||
| opcodes::CLZ, | ||
| opcodes::PUSH0, | ||
| opcodes::MSTORE, | ||
| opcodes::MSIZE, | ||
| opcodes::PUSH0, | ||
| 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); | ||
|
|
||
| for (input, expected) in test_vectors::clz_eip7939_test_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)); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| use fil_actors_evm_shared::uints::U256; | ||
|
|
||
| /// EIP-7939 `CLZ` test vectors from https://eips.ethereum.org/EIPS/eip-7939 | ||
| #[allow(dead_code)] | ||
| pub fn clz_eip7939_test_vectors() -> [(U256, U256); 6] { | ||
| [ | ||
| (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)), | ||
| ] | ||
| } |
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.
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.
Applied this in spirit. I kept the doc comment a bit more explicit and also called out
CLZ(0) = 256.