Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
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