Bytecode mismatch with optimizer_runs=999999 for constants using keccak256(...) - 1 pattern
- Dominant language
- C++
- Stars
- 25.7k
- Forks
- 6.2k
- Avg merge
- 2d 19h
- Merged PRs (30d)
- 29
Description
## Description
The Solidity 0.8.15 compiler produces different bytecode for the same source code when compiling with `optimizer_runs=999999`. This causes bytecode verification to fail when comparing artifacts with deployed contract bytecode.
Specifically, the compiled artifact shows 2563 bytes while test deployment produces 2473 bytes (90-byte difference). The constant value `type(uint256).max - 1` (used in storage slot calculations) is present in the artifact but missing from the deployed bytecode.
## Environment
- Compiler version: 0.8.15
- Compilation pipeline: legacy
- Target EVM version: cancun
- Framework/IDE: Foundry (forge 0.2.0+)
- EVM execution environment / backend / blockchain client: Foundry test environment
- Operating system: macOS
## Steps to Reproduce
### Repository Setup
```bash
git clone https://github.com/ethereum-optimism/optimism.git
cd optimism/packages/contracts-bedrock
```
### Reproduce the Bug (Bytecode Mismatch)
```bash
# Checkout commit before the fix
git checkout 9243bb0452efa3fd255556631688d1255723384a
# Run bytecode verification test
forge test --mc VerifyOPCM_Run_Test --mt test_run_succeeds -vv
```
**Expected output**: Test fails with bytecode mismatch
```
[FAIL: VerifyOPCM_Failed()] test_run_succeeds()
Checking Contract: protocolVersionsImpl
[FAIL] ERROR: Bytecode length mismatch for ProtocolVersions
Expected length: 2563
Actual length: 2473
Status: [FAIL] Verification failed for ProtocolVersions
```
### Verify the Fix
```bash
# Checkout commit with the fix
git checkout 82e928abc6b6d1405b3c2129c3661c552c785aaa
# Run the same test
forge test --mc VerifyOPCM_Run_Test --mt test_run_succeeds -vv
```
**Expected output**: Test passes
```
Checking Contract: protocolVersionsImpl
Status: [OK] Exact Match
Status: [OK] Verified ProtocolVersions
Overall Verification Status: SUCCESS
```
### The Fix
The fix reduces optimizer runs for ProtocolVersions from 999999 to 5000 via compilation restrictions:
```toml
# foundry.toml
compilation_restrictions = [
{ paths = "src/L1/ProtocolVersions.sol", optimizer_runs = 5000 }
]
```
## Source Code
The affected contract is `packages/contracts-bedrock/src/L1/ProtocolVersions.sol`:
```solidity
pragma solidity 0.8.15;
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { Storage } from "src/libraries/Storage.sol";
type ProtocolVersion is uint256;
contract ProtocolVersions is OwnableUpgradeable, ISemver {
uint256 public constant VERSION = 0;
// Storage slots using keccak256(...) - 1 pattern
bytes32 public constant REQUIRED_SLOT = bytes32(uint256(keccak256("protocolversion.required")) - 1);
bytes32 public constant RECOMMENDED_SLOT = bytes32(uint256(keccak256("protocolversion.recommended")) - 1);
string public constant version = "1.1.0";
constructor() {
_disableInitializers();
}
function initialize(
address _owner,
ProtocolVersion _required,
ProtocolVersion _recommended
) external initializer {
__Ownable_init();
transferOwnership(_owner);
_setRequired(_required);
_setRecommended(_recommended);
}
function required() external view returns (ProtocolVersion out_) {
out_ = ProtocolVersion.wrap(Storage.getUint(REQUIRED_SLOT));
}
function recommended() external view returns (ProtocolVersion out_) {
out_ = ProtocolVersion.wrap(Storage.getUint(RECOMMENDED_SLOT));
}
function setRequired(ProtocolVersion _required) external onlyOwner {
_setRequired(_required);
}
function setRecommended(ProtocolVersion _recommended) external onlyOwner {
_setRecommended(_recommended);
}
function _setRequired(ProtocolVersion _required) internal {
Storage.setUint(REQUIRED_SLOT, ProtocolVersion.unwrap(_required));
bytes memory data = abi.encode(_required);
emit ConfigUpdate(VERSION, UpdateType.REQUIRED_PROTOCOL_VERSION, data);
}
function _setRecommended(ProtocolVersion _recommended) internal {
Storage.setUint(RECOMMENDED_SLOT, ProtocolVersion.unwrap(_recommended));
bytes memory data = abi.encode(_recommended);
emit ConfigUpdate(VERSION, UpdateType.RECOMMENDED_PROTOCOL_VERSION, data);
}
}
```
## Observed Behavior
At `optimizer_runs=999999`, the compiler produces:
- Artifact bytecode: 2563 bytes
- Test deployment bytecode: 2473 bytes
- Missing constant: `0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe` (type(uint256).max - 1)
- Different jump addresses in function dispatcher
## Expected Behavior
The compiler should produce identical bytecode for identical source code with identical compiler settings. Bytecode should be deterministic across different compilation contexts (artifact generation vs test deployment).
## Additional Context
- The contracts are functionally equivalent (all tests pass)
- This is a determinism issue, not a correctness bug
- The pattern `bytes32(uint256(keccak256("...")) - 1)` appears to trigger the issue
- Affects any project using high optimizer runs with bytecode verification
- Workaround: Use `optimizer_runs <= 10000` for deterministic bytecode
## Commit References
- Bug present: `9243bb0452efa3fd255556631688d1255723384a`
- Fix applied: `82e928abc6b6d1405b3c2129c3661c552c785aaa`
- Repository: https://github.com/ethereum-optimism/optimism
- Affected file: `packages/contracts-bedrock/src/L1/ProtocolVersions.sol`
Contributor guide
Research direction
Start in packages/contracts-bedrock/src/L1/ProtocolVersions.sol and reproduce with forge test --mc VerifyOPCM_Run_Test --mt test_run_succeeds -vv at the bug-present commit. Compare artifact and deployment bytecode at optimizer_runs=999999, then inspect the compiler behavior behind the keccak256(...) - 1 constants. Done means identical bytecode and a passing verification test without relying on the optimizer restriction.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp, solidity
- Domain
- blockchain, compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100