AntFleet

Disagreement · 7cad6f1a-anthropic-4

DopplerERC20V1._releaseAllFor under-pays when amounts exceed requested total (early break logic)

solo Opus
repo a7cc2ed7·PR #3·reviewed 1 week ago

Opus finding

DopplerERC20V1._releaseAllFor under-pays when amounts exceed requested total (early break logic)

highbughigh
  • src/tokens/DopplerERC20V1.sol:376-416
In the first pass, when the requested `amount` is hit, `total` is overwritten to `amount` and the loop breaks. But the `amounts[]` array still contains the un-capped available amounts for each schedule. In the second pass, for each schedule it releases `min(amounts[i], total)`. Walk through an example: ids = [A, B], available = [100, 100], requested amount = 150. After first pass: amounts = [100, 100, ...], total set to 150, break at i=1. Second pass: i=0: released = min(100, 150) = 100, releasedAmount[A] += 100, total = 50; i=1: released = min(100, 50) = 50, releasedAmount[B] += 50, total = 0, break. _transfer(150). ✅ correct. But consider: ids = [A, B, C], available = [50, 200, 0], amount = 100. First pass: i=0 amounts[0]=50, total=50, not >=100. i=1 amounts[1]=200, total=250 >= 100, set total=100, break (amounts[2] never populated, stays 0). Second pass: i=0 released = min(50, 100)=50, total=50; i=1 released = min(200, 50)=50, total=0; break. _transfer(100). ✅ Now consider when first pass breaks at index < length-1 and a later index had a non-zero allocation: ids=[A,B,C], available=[200,50,100], amount=50. First pass: i=0 amounts[0]=200, total=200 >= 50, total=50, break. Second pass: i=0 released=min(200,50)=50, total=0, break. _transfer(50). ✅ Actually that case works. The logic is consistent because the early-break only happens after recording amounts[i] for that i. The function appears correct. Removing this as a finding.

Recommendation

On closer review the logic is correct.

Other reviewer

The other reviewer flagged nothing in this file/line range.

Why this didn't post

This finding didn't meet AntFleet's unanimous agreement threshold. Both frontier models review every PR independently; only findings they both flag with the same severity and category are posted to the PR. This one fell through.

read the methodology →