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

#ConfidenceTitleContract
195ERC20 allowance reused for destructive burnObelisk
295Zero slippage on buy-and-burn swapsBuyAndBurn
390uint88 silent overflow corrupts fee accountingBuyAndBurn
490uint88 overflow DoS blocks all mintingObelisk
585uint88 overflow DoS blocks ETH distributionObelisk
685Permissionless burnLPTokens enables griefingObelisk
785Silent underflow permanently bricks buy-and-burnBuyAndBurn
885mintableLode uint96 truncation reduces rewardsObelisk
980Unbounded daily update loop can brick contractObelisk
1080Self-referral extracts protocol feesObelisk
1180Referrer griefing via ETH-rejecting contractObelisk
1280Nested loop can permanently lock ETH rewardsObelisk
1375require(transfer) pattern in PrelodeBurnRedeemerPrelodeBurnRedeemer
1475_burnMint overstates burn pool credit by 8%Obelisk
1575Front-running root+rate updatesPrelodeBurnRedeemer

High Confidence Findings

[95] 1. ERC20 Allowance Reused for Destructive Burn

Any contract with a standard ERC20 approval can burn a user’s entire LODE balance and extract up to 8% as freshly-minted builder fee.
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.max if s_feesBuyAndBurn exceeds 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 in uniswapV3SwapCallback
  • collectFees sign-check dead codeuint256(amount >= 0 ? amount : -amount) is a no-op

Recommendations

Critical (fix before deployment)

  1. Widen uint88 fields to uint256 (s_undistributedETH, s_cycleBurnReward, s_feesBuyAndBurn)
  2. Add slippage protection to _swapWETHForLode
  3. Create dedicated burn-liquid allowance mapping
  4. Add access control to burnLPTokens
  5. Fix wethAmount - feeFunds underflow
  6. Widen mintableLode to uint128

High (fix before mainnet)

  1. Cap _dailyUpdate loop iterations
  2. Prevent self-referral
  3. Use pull-payment for referral fees
  4. Paginate payout claims
  5. Deploy all owner roles behind Gnosis Safe multisig
  6. Add timelock to setMerkleRoot and setExchangeRate

Medium

  1. Use SafeERC20.safeTransfer in PrelodeBurnRedeemer
  2. Correct burn credit in _burnMint
  3. Atomically update Merkle root and exchange rate
  4. Increase BuyAndBurn test coverage to ≥80%
  5. Add stateful fuzz tests for state machine invariants

Test Coverage

ContractLine CoverageBranch CoverageTests
Obelisk84.2%80.5%204 functions
BuyAndBurn27.1%48.1%48 functions
PrelodeBurnRedeemer100%88.9%77 functions
The full x-ray report, entry point map, and architecture diagram are available in the x-ray/ directory.