Blog/Case Study
EthereumDeFi Protocol with NFT Fee DistributionJuly 2026580 lines

Case Study: DeFi Protocol Audit — Integer Truncation Allows Catastrophic Fee Overclaim on Ethereum

37/100
Risk Score (D)
24
Total Findings
7
Critical + High
Severity Distribution

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

critical

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.

high

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.

high

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.

high

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

Fee accumulator checkpoints use the same integer type as the accumulator — no narrowing casts
All functions with external calls use nonReentrant guards and follow checks-effects-interactions
Fee eligibility uses time-weighted averaging to prevent flash-loan fee sniping
Position IDs are read from return values, not predicted ahead of the call
Comprehensive fuzz tests cover edge cases near uint128 and uint256 maximum values

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 Contract

Thinking 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