Security Audit Report
This report was generated by AI-assisted security tooling (x-ray pre-audit + 8 parallel audit agents with 4-gate validation). It is not a substitute for a manual expert audit.
All 15 findings have proof-of-concept tests. 18 tests across 3 contracts (
evm/*/test/security/AuditFindings.t.sol) reproduce each finding on-chain. Run with make docker-unit.
Scope: Obelisk.sol, BuyAndBurn.sol, PrelodeBurnRedeemer.sol | 1844 nSLOC | Commit f1911ef | 2026-04-06
Summary
The audit identified 15 findings and 6 leads across all three contracts. Overall verdict: FRAGILE — Unit tests exist with good test co-change rate (85.7%), but no stateful fuzz testing, formal verification, or fork tests. All admin actions are instant with no timelock or multisig.Findings Overview
| # | Confidence | Title | Contract |
|---|---|---|---|
| 1 | 95 | ERC20 allowance reused for destructive burn | Obelisk |
| 2 | 95 | Zero slippage on buy-and-burn swaps | BuyAndBurn |
| 3 | 90 | uint88 silent overflow corrupts fee accounting | BuyAndBurn |
| 4 | 90 | uint88 overflow DoS blocks all minting | Obelisk |
| 5 | 85 | uint88 overflow DoS blocks ETH distribution | Obelisk |
| 6 | 85 | Permissionless burnLPTokens enables griefing | Obelisk |
| 7 | 85 | Silent underflow permanently bricks buy-and-burn | BuyAndBurn |
| 8 | 85 | mintableLode uint96 truncation reduces rewards | Obelisk |
| 9 | 80 | Unbounded daily update loop can brick contract | Obelisk |
| 10 | 80 | Self-referral extracts protocol fees | Obelisk |
| 11 | 80 | Referrer griefing via ETH-rejecting contract | Obelisk |
| 12 | 80 | Nested loop can permanently lock ETH rewards | Obelisk |
| 13 | 75 | require(transfer) pattern in PrelodeBurnRedeemer | PrelodeBurnRedeemer |
| 14 | 75 | _burnMint overstates burn pool credit by 8% | Obelisk |
| 15 | 75 | Front-running root+rate updates | PrelodeBurnRedeemer |
High Confidence Findings
[95] 1. ERC20 Allowance Reused for Destructive Burn
Contract:Obelisk._burnLiquidLode | Detected by: 6 of 8 agents | PoC: test_finding01_allowanceReuseForBurn
_burnLiquidLode consumes the standard ERC20 allowance[user][msg.sender] for a destructive burn operation. The IObeliskOnBurn ERC165 check is a self-declaration — any contract can return true for any interface. Dedicated burn allowance mappings exist for mint and stake burns but not for liquid token burns.
Fix: Use a dedicated s_allowanceBurnLiquid mapping instead of the standard ERC20 allowance.
[95] 2. Zero Slippage Protection on Buy-and-Burn Swaps
Contract:BuyAndBurn._swapWETHForLode | Detected by: 3 of 8 agents | PoC: test_finding02_zeroSlippageConstants
The swap uses MIN_SQRT_RATIO + 1 / MAX_SQRT_RATIO - 1 as the Uniswap V3 price limit, accepting any execution price. Every buynBurn() call is a guaranteed sandwich opportunity. The function is permissionless and incentivized (0.33% fee), ensuring MEV bots will reliably target it.
Fix: Use a TWAP-derived price limit or accept a caller-supplied minAmountOut.
[90] 3. uint88 Silent Overflow Corrupts Fee Accounting
Contract:BuyAndBurn.collectFees | Detected by: 5 of 8 agents | PoC: test_finding03_uint88SilentOverflow
s_feesBuyAndBurn is uint88 under Solidity 0.7.6 (no checked arithmetic). When cumulative LP fees exceed ~309K ETH, the value silently wraps, permanently corrupting fee attribution and potentially allowing the global swap cap to be bypassed.
Fix: Widen s_feesBuyAndBurn to uint256.
[90] 4. uint88 Overflow DoS Blocks All Minting
Contract:Obelisk._protocolFees | Detected by: 3 of 8 agents | PoC: test_finding04_uint88OverflowDosMinting
s_undistributedETH is uint88. In Solidity 0.8+, += reverts on overflow. If accumulated fees approach the ~309K ETH cap without distributeETH() being called, all startMint and batchMint calls permanently revert.
Fix: Widen s_undistributedETH to uint256.
Medium Confidence Findings
[85] 5. uint88 Overflow DoS Blocks ETH Distribution
Contract:Obelisk._distributeETH | Detected by: 3 of 8 agents | PoC: test_finding05_cycleBurnRewardUint88
s_cycleBurnReward is uint88. The explicit cast silently truncates before +=, which then reverts on overflow, blocking all future distributeETH() and triggerPayouts().
Fix: Widen s_cycleBurnReward to uint256.
[85] 6. Permissionless burnLPTokens Enables Initialization Griefing
Contract:Obelisk.burnLPTokens | Detected by: 4 of 8 agents | PoC: test_finding06_permissionlessBurnLPTokens
No access control. Between mintLPTokens() and createInitialLiquidity(), an attacker can destroy the 100B LODE reserve for ~21K gas, permanently preventing pool initialization.
Fix: Add require(msg.sender == s_buyAndBurnAddress).
[85] 7. Silent Underflow Permanently Bricks Buy-and-Burn
Contract:BuyAndBurn.buynBurn | Detected by: 3 of 8 agents | PoC: test_finding07_silentUnderflow
Under Solidity 0.7.6, wethAmount - feeFunds silently underflows when rounding causes feeFunds to slightly exceed wethAmount, inflating s_totalWethBuyAndBurn to near-uint256.max and permanently DoS-ing all future swaps.
Fix: Replace if (wethAmount - feeFunds > 0) with if (wethAmount > feeFunds).
[85] 8. mintableLode uint96 Truncation Reduces User Rewards
Contract:MintInfo._startMint | Detected by: 1 of 8 agents | PoC: test_finding08_uint96Truncation
calculateMintReward can return values exceeding uint96.max (~7.9e28) for maximum parameters on day 1. The stored value is silently truncated, permanently reducing user rewards.
Fix: Widen UserMintInfo.mintableLode from uint96 to uint128.
[80] 9. Unbounded Daily Update Loop
Contract:GlobalInfo._dailyUpdate | Detected by: 1 of 8 agents | PoC: test_finding09_unboundedDailyUpdateLoop
Iterates once per missed day with no cap. Extended inactivity (~3 years) exceeds the block gas limit, bricking all contract functions.
[80] 10. Self-Referral Extracts Protocol Fees
Contract:ReferralInfo._setReferral | Detected by: 1 of 8 agents | PoC: test_finding10_selfReferral
No check prevents referring_user == user. A user can refer themselves to earn 5% ETH back + 5% LODE bonus on every mint.
[80] 11. Referrer Griefing via ETH-Rejecting Contract
Contract:Obelisk._protocolFees | Detected by: 1 of 8 agents | PoC: test_finding11_referrerGriefing
If a referrer address is a contract that reverts on ETH receipt, all mints by the referred user permanently revert.
[80] 12. Nested Loop Can Permanently Lock ETH Rewards
Contract:Obelisk._calculateUserCycleReward | Detected by: 1 of 8 agents | PoC: test_finding12_nestedLoopGasScaling
The O(cycles x shares) nested loop can exceed the block gas limit for users with many stakes and many unclaimed cycles, permanently locking their ETH rewards.
Low Confidence Findings
[75] 13. require(transfer) Pattern
Contract:PrelodeBurnRedeemer.claimLode | PoC: test_finding13_requireTransferPattern
Uses require(lodeToken.transfer(...)) which reverts on void-return tokens. Fragile if the LODE token address is ever changed.
[75] 14. _burnMint Overstates Burn Pool Credit
Contract:Obelisk._burnMint | PoC: test_finding14_burnMintOverstatesBurnCredit
Genesis fee (8%) is minted before the full amount is recorded as burn credit, overstating the user’s burn pool contribution.
[75] 15. Front-Running Root + Rate Updates
Contract:PrelodeBurnRedeemer.claimLode | PoC: test_finding15_frontRunRateDecrease
Users can front-run owner’s setExchangeRate reduction to claim at the old (higher) rate. No timelock or atomic update mechanism exists.
Leads
High-signal trails for manual investigation. Not scored — the full exploit path could not be completed in one analysis pass.
- getBuyAndBurnFunds view underflow — Unchecked subtraction in Solidity 0.7.6 returns near-
uint256.maxifs_feesBuyAndBurnexceeds WETH balance - Fee double-count toward global cap — Both WETH trackers may increment in the same call, exhausting the cap prematurely
- Owner rate manipulation — No timelock on
setExchangeRate; owner can slash unclaimed LODE entitlements - userBurnMint missing genesis fee — Direct burn path skips the 8% genesis mint that project-mediated path charges
- Swap callback pre-init confused deputy — No
s_lodeAddress != address(0)check inuniswapV3SwapCallback - collectFees sign-check dead code —
uint256(amount >= 0 ? amount : -amount)is a no-op
Recommendations
Critical (fix before deployment)
- Widen
uint88fields touint256(s_undistributedETH,s_cycleBurnReward,s_feesBuyAndBurn) - Add slippage protection to
_swapWETHForLode - Create dedicated burn-liquid allowance mapping
- Add access control to
burnLPTokens - Fix
wethAmount - feeFundsunderflow - Widen
mintableLodetouint128
High (fix before mainnet)
- Cap
_dailyUpdateloop iterations - Prevent self-referral
- Use pull-payment for referral fees
- Paginate payout claims
- Deploy all owner roles behind Gnosis Safe multisig
- Add timelock to
setMerkleRootandsetExchangeRate
Medium
- Use
SafeERC20.safeTransferin PrelodeBurnRedeemer - Correct burn credit in
_burnMint - Atomically update Merkle root and exchange rate
- Increase BuyAndBurn test coverage to ≥80%
- Add stateful fuzz tests for state machine invariants
Test Coverage
| Contract | Line Coverage | Branch Coverage | Tests |
|---|---|---|---|
| Obelisk | 84.2% | 80.5% | 204 functions |
| BuyAndBurn | 27.1% | 48.1% | 48 functions |
| PrelodeBurnRedeemer | 100% | 88.9% | 77 functions |
x-ray/ directory.