Upgrading the proxy from ThunderLoan to ThunderLoanUpgraded causes a critical storage layout collision. Because the state variable s_feePrecision was removed and replaced with a constant (FEE_PRECISION), all subsequent state variables in ThunderLoanUpgraded shift down by one storage slot, corrupting s_flashLoanFee from 3e15 (0.3%) to 1e18 (100%).
In src/protocol/ThunderLoan.sol, the contract defines three sequential state variables after the inherited upgradeable base contracts:
In src/upgradedProtocol/ThunderLoanUpgraded.sol, s_feePrecision was replaced with uint256 public constant FEE_PRECISION = 1e18;. Because constant values are embedded directly in bytecode and do not occupy a storage slot, the storage layout in ThunderLoanUpgraded shifts:
Formal verification of upgrade storage-layout integrity (INV-10) on the ERC-1967 proxy proves that upgrading ThunderLoan to ThunderLoanUpgraded immediately corrupts getFee() without any state write:
You can also reproduce this in Foundry by adding the following test to test/unit/ThunderLoanTest.t.sol:
Immediately upon upgrading to ThunderLoanUpgraded, s_flashLoanFee reads the value 1e18 (100% fee) stored in s_feePrecision's old slot, and s_currentlyFlashLoaning shifts into s_flashLoanFee's old slot, completely breaking flash loan fee calculations and protocol state integrity.
Manual Review, Foundry, Neuro-Symbolic Formal Verification (Z3).
Preserve the exact storage layout order in ThunderLoanUpgraded.sol by keeping s_feePrecision (or an unused storage placeholder variable) in its original storage slot before s_flashLoanFee:
The contest is live. Earn rewards by submitting a finding.
Submissions are being reviewed by our AI judge. Results will be available in a few minutes.
View all submissionsThe contest is complete and the rewards are being distributed.