Case Study: DeFi Protocol Audit — Integer Truncation Allows Catastrophic Fee Overclaim on Ethereum
Executive Summary
This protocol distributes trading fees proportionally to NFT holders using a fee-per-share accumulator pattern. The core design is reasonable, but a type-mismatch in how the accumulator is stored introduces a catastrophic mathematical error: once enough fees have been collected, the truncation causes the math to wildly overflow, allowing some NFT holders to claim many times more than they are owed — draining all ETH in the contract.
Compounding this, the fee collection function lacks reentrancy protection and violates the checks-effects-interactions ordering pattern. An attacker can interrupt the fee distribution mid-execution to claim fees calculated on stale state. Flash-loan attacks can also exploit the fee accounting by briefly holding large share positions during the fee snapshot.
The race condition in the initial liquidity seeding function means the contract can record the wrong position ID if competing transactions occur at the same time.
Risk Assessment
This protocol has a critical bug that can drain all accumulated ETH through a routine fee claim. The reentrancy and flash-loan vectors make exploitation straightforward for a sophisticated attacker. The contract should not hold user funds until these issues are resolved.
Key Findings
uint128 Truncation of uint256 Fee Accumulators Causes Catastrophic Fee Miscalculation
What We Found
The contract uses a counting system to track how much each NFT share is owed in fees. The running total is stored in a large number format, but when it saves the 'starting point' for each share, it cuts the number in half. Once enough fees have been collected, that cut-off causes the math to go wildly wrong — some NFT holders could claim millions of times more than they're owed, draining all the ETH in the contract.
Why It Matters
Once the accumulator grows large enough, any holder can claim the entire ETH balance of the contract in a single transaction. The contract becomes permanently insolvent.
What Should Be Done
Store all fee accumulator checkpoints as uint256 to match the accumulator type. Audit every point where a uint256 value is cast to a smaller integer type.
pokeFees() Lacks nonReentrant Guard and Uses CEI-Violating Pattern
What We Found
The fee collection function can be interrupted mid-way by an attacker while it's waiting for an outside service to respond. During that interruption, the attacker can call the claim function and receive fee calculations based on outdated numbers.
Why It Matters
An attacker can re-enter the claim function before state is updated, receiving fee payouts calculated on stale balances. ETH can be drained beyond the intended amount.
What Should Be Done
Add OpenZeppelin's nonReentrant modifier to pokeFees() and all functions that call external contracts. Follow checks-effects-interactions strictly: update all state before any external call.
Flash-Loan MEV Attack: Share Acquisition Before pokeFees Captures Pro-Rata Fees
What We Found
An attacker can use a flash loan to briefly hold a large number of shares, collect a portion of fees that other users earned over time, then repay the loan — all in one action.
Why It Matters
Attackers can siphon accumulated fees owed to long-term holders in a single block. This is a griefing attack that persistently degrades the yield for legitimate participants.
What Should Be Done
Implement a share-weighted time-in-pool requirement for fee eligibility. Snapshot share balances at the start of each fee epoch rather than at the point of collection.
Race Condition in seed(): Position ID Set Before multicall Execution
What We Found
When the contract sets up its liquidity position, it tries to predict which ID number the position will get before it's actually created. If someone else creates their own position at the same moment, the contract records the wrong ID number.
Why It Matters
The contract may track the wrong liquidity position, causing all subsequent fee accounting and liquidity management to operate on a position it does not own.
What Should Be Done
Retrieve the actual position ID from the return value of the mint call rather than predicting it before the call. Never assume position IDs are sequential.
What a Secure Contract Should Have
Disclaimer: This case study is published for educational purposes only. It reflects the state of the analyzed contract at the time of audit and does not constitute investment advice, an endorsement, or a guarantee of security. Smart contracts may be modified after an audit. HyperAudit is not liable for losses arising from interaction with any contract referenced in this report. Project names and addresses have been removed to protect ongoing remediation efforts. For a full audit of your own contracts, visit hyperaudit.io.
Building a project?
Get the same depth of analysis on your own contracts before you deploy. Reports delivered in hours, not weeks.
Audit Your ContractThinking about buying a token?
Before you buy, find out if the contract is safe. We can audit any token on any EVM chain and tell you exactly what risks you're taking — in plain language you can act on.
Check a Token Before You Buy