Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
2fa600b
User the new CLZ opcode
Amxx Dec 3, 2025
0608dc4
workaround edr bug by fixing the block gas limit
Amxx Dec 4, 2025
1f02c78
Update foundry.toml
Amxx Dec 4, 2025
6e174d4
fix lint
Amxx Dec 4, 2025
ecfffa4
Merge branch 'update/use-0.8.31-clz' of https://github.com/Amxx/openz…
Amxx Dec 4, 2025
c3e0817
Merge branch 'update/use-0.8.31-clz' of https://github.com/Amxx/openz…
Amxx Dec 4, 2025
9f8a13c
Merge branch 'update/use-0.8.31-clz' of https://github.com/Amxx/openz…
Amxx Dec 10, 2025
3660f16
Merge branch 'master' into update/use-0.8.31-clz
Amxx Dec 10, 2025
c01d333
minimize
Amxx Dec 10, 2025
6c4ac2b
cascade pragma bump
Amxx Dec 10, 2025
3606d19
Merge branch 'master' into update/use-0.8.31-clz
Amxx Dec 16, 2025
913adc8
Update to hardhat 2.28.0 and use pragma ^0.8.31
Amxx Dec 16, 2025
5860901
update pragma in templates
Amxx Dec 16, 2025
39bc90b
Target osaka by default
Amxx Dec 17, 2025
11747dc
fix deprecation warning
Amxx Dec 17, 2025
19d6dec
update foundry config to osaka
Amxx Dec 17, 2025
7450f76
fix foundry P256 tests: verify native is supported
Amxx Dec 17, 2025
9885011
remove manual activation of RIP7212
Amxx Dec 17, 2025
8750d03
use nightly foundry to support 0.8.31
Amxx Dec 17, 2025
a67de61
strip zero in RSA exponent to reduce gas cost of MODEXP precompile
Amxx Dec 18, 2025
f0b3133
Update .github/actions/setup/action.yml
Amxx Dec 22, 2025
255570a
Check foundry version in CI
Amxx Dec 22, 2025
4ea1971
Update checks.yml
Amxx Dec 22, 2025
8885af9
Merge branch 'hardfork/target-osaka' into update/use-0.8.31-clz
Amxx Dec 22, 2025
7996d40
Merge branch 'master' into update/use-0.8.31-clz
Amxx Dec 23, 2025
b9c5953
Merge branch 'hardfork/target-osaka' into update/use-0.8.31-clz
Amxx Dec 23, 2025
e5df974
npm run pragma
Amxx Dec 23, 2025
dae6b45
Merge branch 'master' into hardfork/target-osaka
Amxx Jan 5, 2026
450e1c2
Merge remote-tracking branch 'amxx/hardfork/target-osaka' into update…
Amxx Jan 8, 2026
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
72 changes: 14 additions & 58 deletions contracts/utils/math/Math.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.5.0) (utils/math/Math.sol)

pragma solidity ^0.8.20;
pragma solidity >0.8.30;

import {Panic} from "../Panic.sol";
import {SafeCast} from "./SafeCast.sol";
Expand Down Expand Up @@ -613,50 +613,22 @@ library Math {
}

/**
* @dev Return the log in base 2 of a positive value rounded towards zero.
* Returns 0 if given 0.
* @dev Counts the number of leading zero bits in a uint256.
*/
function log2(uint256 x) internal pure returns (uint256 r) {
// If value has upper 128 bits set, log2 result is at least 128
r = SafeCast.toUint(x > 0xffffffffffffffffffffffffffffffff) << 7;
// If upper 64 bits of 128-bit half set, add 64 to result
r |= SafeCast.toUint((x >> r) > 0xffffffffffffffff) << 6;
// If upper 32 bits of 64-bit half set, add 32 to result
r |= SafeCast.toUint((x >> r) > 0xffffffff) << 5;
// If upper 16 bits of 32-bit half set, add 16 to result
r |= SafeCast.toUint((x >> r) > 0xffff) << 4;
// If upper 8 bits of 16-bit half set, add 8 to result
r |= SafeCast.toUint((x >> r) > 0xff) << 3;
// If upper 4 bits of 8-bit half set, add 4 to result
r |= SafeCast.toUint((x >> r) > 0xf) << 2;

// Shifts value right by the current result and use it as an index into this lookup table:
//
// | x (4 bits) | index | table[index] = MSB position |
// |------------|---------|-----------------------------|
// | 0000 | 0 | table[0] = 0 |
// | 0001 | 1 | table[1] = 0 |
// | 0010 | 2 | table[2] = 1 |
// | 0011 | 3 | table[3] = 1 |
// | 0100 | 4 | table[4] = 2 |
// | 0101 | 5 | table[5] = 2 |
// | 0110 | 6 | table[6] = 2 |
// | 0111 | 7 | table[7] = 2 |
// | 1000 | 8 | table[8] = 3 |
// | 1001 | 9 | table[9] = 3 |
// | 1010 | 10 | table[10] = 3 |
// | 1011 | 11 | table[11] = 3 |
// | 1100 | 12 | table[12] = 3 |
// | 1101 | 13 | table[13] = 3 |
// | 1110 | 14 | table[14] = 3 |
// | 1111 | 15 | table[15] = 3 |
//
// The lookup table is represented as a 32-byte value with the MSB positions for 0-15 in the last 16 bytes.
function clz(uint256 value) internal pure returns (uint256 result) {
assembly ("memory-safe") {
r := or(r, byte(shr(r, x), 0x0000010102020202030303030303030300000000000000000000000000000000))
result := clz(value)
}
}

/**
* @dev Return the log in base 2 of a positive value rounded towards zero.
* Returns 0 if given 0.
*/
function log2(uint256 value) internal pure returns (uint256) {
return saturatingSub(255, clz(value));
}

/**
* @dev Return the log in base 2, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
Expand Down Expand Up @@ -723,17 +695,8 @@ library Math {
*
* Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
*/
function log256(uint256 x) internal pure returns (uint256 r) {
// If value has upper 128 bits set, log2 result is at least 128
r = SafeCast.toUint(x > 0xffffffffffffffffffffffffffffffff) << 7;
// If upper 64 bits of 128-bit half set, add 64 to result
r |= SafeCast.toUint((x >> r) > 0xffffffffffffffff) << 6;
// If upper 32 bits of 64-bit half set, add 32 to result
r |= SafeCast.toUint((x >> r) > 0xffffffff) << 5;
// If upper 16 bits of 32-bit half set, add 16 to result
r |= SafeCast.toUint((x >> r) > 0xffff) << 4;
// Add 1 if upper 8 bits of 16-bit half set, and divide accumulated result by 8
return (r >> 3) | SafeCast.toUint((x >> r) > 0xff);
function log256(uint256 value) internal pure returns (uint256) {
return log2(value) / 8;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note, DIV is 5 gas, shifts are 3 gas.

}

/**
Expand All @@ -753,11 +716,4 @@ library Math {
function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
return uint8(rounding) % 2 == 1;
}

/**
* @dev Counts the number of leading zero bits in a uint256.
*/
function clz(uint256 x) internal pure returns (uint256) {
return ternary(x == 0, 256, 255 - log2(x));
}
}
2 changes: 1 addition & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[profile.default]
solc_version = '0.8.27'
solc_version = '0.8.31'
evm_version = 'prague'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

CLZ opcode Ethereum hardfork prague osaka which version introduced

💡 Result:

The CLZ opcode was specified in EIP‑7939 and was included in the Fusaka network upgrade (not Prague). Test vectors/implementation work appear under Osaka testing, but mainnet activation is part of Fusaka. [1][2][3]

Sources:
[1] EIP‑7939 (Count leading zeros / CLZ).
[2] Fusaka mainnet announcement (lists EIP‑7939 as a Fusaka core EIP).
[3] Osaka execution-spec tests checklist referencing EIP‑7939.


Both Foundry and Hardhat EVM versions are too old for CLZ opcode support.

The solc_version is correctly updated to 0.8.31, but both evm_version settings are insufficient for the CLZ opcode. CLZ (Count Leading Zeros, EIP-7939) requires the Fusaka network upgrade for mainnet activation. Setting Foundry to 'prague' and Hardhat to 'osaka' will both fail when attempting to use the clz assembly instruction in Math.sol. Both configurations must be updated to 'fusaka' or later.

🤖 Prompt for AI Agents
In foundry.toml around lines 2-3, the EVM version is set to 'prague' which does
not support the CLZ opcode; update the evm_version to 'fusaka' (or a later
network upgrade) so EIP-7939 CLZ is available, keeping solc_version = '0.8.31'
as-is, and ensure the project's Hardhat config is also updated from 'osaka' to
'fusaka' or later to avoid runtime failures when using the clz assembly
instruction.

optimizer = true
optimizer-runs = 200
Expand Down
6 changes: 3 additions & 3 deletions hardhat.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/// ENVVAR
// - COMPILER: compiler version (default: 0.8.27)
// - COMPILER: compiler version (default: 0.8.31)
// - SRC: contracts folder to compile (default: contracts)
// - RUNS: number of optimization runs (default: 200)
// - IR: enable IR compilation (default: false)
Expand All @@ -18,7 +18,7 @@ const { argv } = require('yargs/yargs')()
compiler: {
alias: 'compileVersion',
type: 'string',
default: '0.8.27',
default: '0.8.31',
},
src: {
alias: 'source',
Expand All @@ -38,7 +38,7 @@ const { argv } = require('yargs/yargs')()
evm: {
alias: 'evmVersion',
type: 'string',
default: 'prague',
default: 'osaka',
},
// Extra modules
coverage: {
Expand Down
Loading
Loading