-
Notifications
You must be signed in to change notification settings - Fork 6.3k
Description
Description
When using the IR compilation pipeline with Solidity 0.8.33, adding a simple bool private storage variable for reentrancy protection causes deployment gas to double from ~10M to ~20M.
The only difference is ONE keyword: transient
// ❌ ~20M gas - deployment fails
bool private _isExecutingMechanism;
// ✅ ~10M gas - deployment succeeds
bool private transient _isExecutingMechanism;Expected behavior: Adding a single bool storage variable should not cause ~10M gas increase during deployment.
Actual behavior: Deployment gas doubles from ~9.9M to ~19.9M, exceeding typical block gas limits.
Additional paradox: The bytecode with the problematic bool private is actually SMALLER (42,163 bytes) than with transient (42,398 bytes), but gas is 2x higher!
Environment
- Compiler version: 0.8.33
- Compilation pipeline (legacy, IR, EOF): IR (
viaIR: true) - Target EVM version (as per compiler settings): default
- Framework/IDE (e.g. Foundry, Hardhat, Remix): Hardhat 3.1.0
- EVM execution environment / backend / blockchain client: Hardhat Network (EDR)
- Operating system: Windows 11
Steps to Reproduce
Minimal Solidity Code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.33;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract HAFToken is ERC20 {
// Many other storage variables (mappings, uints, addresses, etc.)
// ... approximately 30+ storage slots ...
// BUG: This single bool causes ~10M gas increase during deployment
// Change to `bool private transient` to fix
bool private _isExecutingMechanism;
constructor(
address _usdt,
address _factory,
address _router,
address _hashFiContract
) ERC20("HAF Token", "HAF") {
// Complex constructor with multiple external calls
// Creates Uniswap pair, sets up initial state, etc.
_mint(msg.sender, 210_000_000 * 10**18);
}
function _update(address from, address to, uint256 amount) internal virtual override {
_triggerLazyMechanisms();
super._update(from, to, amount);
}
function _triggerLazyMechanisms() internal {
// Simple reentrancy guard pattern
if (_isExecutingMechanism) return;
_isExecutingMechanism = true;
_tryDailyBurn();
_tryAutoBurn();
_isExecutingMechanism = false;
}
function _tryDailyBurn() internal {
// Complex burn logic that may trigger recursive _update calls
}
function _tryAutoBurn() internal {
// Complex auto-burn logic with potential swap operations
}
}Compiler Settings (hardhat.config.ts)
solidity: {
version: "0.8.33",
settings: {
optimizer: {
enabled: true,
runs: 1,
},
viaIR: true, // Required - bug only occurs with IR pipeline
},
}Full Reproduction Repository
git clone https://github.com/slwyts/HashFi.git
cd HashFi
git checkout HAFToken
npm installTest with transient (Working - ~10M gas):
npx hardhat clean
npx hardhat compile
npm run testnetReproduce the bug - Edit contracts/HAFToken.sol line 202:
// Change FROM:
bool private transient _isExecutingMechanism;
// Change TO:
bool private _isExecutingMechanism;Recompile and test (Failing - ~20M gas):
npx hardhat clean
npx hardhat compile
npm run testnetResults
| Code Version | Deployment Gas | Bytecode Size (init) | Result |
|---|---|---|---|
bool private _isExecutingMechanism |
19,945,987 | 42,163 bytes | ❌ Exceeds gas cap |
bool private transient _isExecutingMechanism |
9,923,481 | 42,398 bytes | ✅ Deploys |
Actual Logs
✅ With bool private transient (~10M gas):
Contract deployment: HashFi
From: 0xa4b76d7cae384c9a5fd5f573cef74bfdb980e966
Value: 0 ETH
Gas used: 9923481 of 12532761
Block #5: 0x18ab...
❌ With bool private (~20M gas):
Error HHE10409 in plugin hardhat-ignition: Gas estimation failed:
transaction gas limit (19945987) is greater than the cap (16777216)
Analysis
The issue appears related to how the IR pipeline handles:
- Storage variable SLOAD/SSTORE in functions called from
_updateoverride - Cross-function inlining -
_triggerLazyMechanisms()is called from_update(), which is called from many places - Control flow analysis for storage slots with read-modify-write patterns
The fact that transient storage (TLOAD/TSTORE) doesn't have this issue suggests different optimization paths for persistent vs transient storage in this specific pattern.
Relevant Commits
- One-line fix: slwyts/HashFi@7048055
- Test commit:
test: reproduce deployment gas overflow - bool private reentrancy lock causes gas from 10M to 20M - Fix commit:
fix: use transient storage for reentrancy lock to avoid viaIR compiler gas explosion