Audit contests are often framed as a numbers game — find more bugs, get higher severity, climb the leaderboard.

In reality, the biggest gains often come from the reports that get rejected.

This post breaks down a Denial of Service (DoS) issue I reported during a DeFi audit contest, why it was ultimately marked spam , and the key security lesson I took away from it.

The Report That Looked Right

The protocol exposed several batch claim functions used to claim rewards and bribes across multiple gauges in a single transaction :

  • claimRewards
  • claimBribes
  • claimAllRewards
  • claimAllBribes

Each of these functions :

1. Accepts an array of gauge addresses

2. Iterates through the array

3. Calls getReward(...) on each gauge

What stood out was that no validation was performed on the supplied addresses inside the loop.

In simplified form, the logic looked like this :

for (uint i = 0; i < _gauges.length; i++) {
    IGauge(_gauges[i]).getReward(msg.sender, _redeemType);
}

No checks for :

  • Whether the address is a registered gauge
  • Whether the gauge is alive
  • Whether the external call might revert

My Initial Reasoning

The concern seemed straightforward :

  • A user could pass a malicious or buggy gauge contract
  • That contract could revert inside getReward
  • One revert would cause the entire batch transaction to revert

From this, I concluded there was a Denial of Service risk affecting batch claims.

I even validated the behavior using a proof-of-concept :

  • A "good" gauge that succeeds
  • A "malicious" gauge that always reverts

As expected, the batch reverted when the malicious gauge was included. On paper, this looked like a classic batch DoS pattern.

Why the Report Was Rejected ?

Despite the technical correctness of the behavior, the issue was marked spam.

Here's why.

User-Induced Reverts Are Not Protocol DoS

The most important detail I missed initially :

The caller fully controls the input array.

If a user includes :

  • A malicious gauge
  • A non-gauge address
  • Any contract that reverts

Then the failure affects only that user and that transaction.

The protocol itself remains functional :

  • Other users can still claim rewards
  • Individual claims still work
  • No shared state is locked or corrupted

This is not a system-wide Denial of Service — it's self-inflicted failure.

Batch Functions Are UX Helpers, Not Core Logic

These batch claim functions exist for convenience, not as critical execution paths.

Users always have alternatives :

  • Call individual gauge claim functions
  • Exclude problematic gauges
  • Retry with a smaller set

A real DoS vulnerability would :

  • Block all users
  • Affect essential protocol operations
  • Have no viable workaround

None of those conditions applied here.

Missing Validation Was a Design Choice

At first glance, adding something like this felt reasonable :

require(isGauge[_gauges[i]], "Invalid gauge");

But in context, such checks :

  • Increase gas costs
  • Reduce composability
  • Add unnecessary coupling

The protocol intentionally assumes :

"If you pass bad input, the transaction may fail." That assumption is acceptable for user-initiated batch helpers.

The Key Lesson

This report taught me something more valuable than a validated finding :

Context matters more than patterns

Seeing an external call inside a loop is not enough. What matters is :

  • Who controls the input
  • Who is affected by failure
  • Whether the protocol is actually blocked

A checklist I now apply before reporting DoS issues :

  • Is the failure user-controlled or protocol-controlled?
  • Does it affect all users or only the caller?
  • Is this core logic or optional UX functionality?
  • Is there a clear alternative execution path?

Final Takeaway

Not every revert is a DoS. Not every missing check is a bug. Not every pattern applies in every context.

Audit contests reward thinking, not just findings. And sometimes, the most valuable lesson comes from the report that didn't make the cut.