AntFleet

Anatomy · bb96d68b-0

Migrator buyback transfers send wrong currency to recipient

criticalbug
repo a7cc2ed7·PR #2·reviewed 1 week ago

The vulnerable code

Code snippet unavailable.

The reasoning

Opus

Numeraire buyback transfers output to recipient using wrong currency (asset instead of numeraire... but inverted) - sends ASSET tokens for a NUMERAIRE buyback

criticalbughigh
  • src/dopplerHooks/RehypeDopplerHookMigrator.sol:197-224
In the numeraireBuybackAmountIn branch the migrator swaps in numeraire to buy back asset (zeroForOne = !isToken0). The output of that swap is the asset token. When `routeToBeneficiaryFees` is false the code calls `Currency.wrap(asset).transfer(recipient, numeraireBuybackAmountOutResult)` — which is actually correct because the output IS the asset. However, the symmetric assetBuyback branch (which produces numeraire output) calls `Currency.wrap(numeraire).transfer(recipient, assetBuybackAmountOut)` which is correct. BUT for the direct buyback branches: `assetDirectBuybackAmount` represents asset fees being sent as the asset (directBuybackAmount is in asset units) — it transfers `Currency.wrap(asset)`. `numeraireDirectBuybackAmount` is numeraire-denominated and transferred as `Currency.wrap(numeraire)`. These look consistent. However in the numeraireBuybackAmountInUsed accounting `balance0/balance1` is decremented by `numeraireBuybackAmountInUsed` on the numeraire side, which matches comment. The actual mismatch I want to flag: when isToken0 is true, numeraire is currency1; the comment in the Initializer version states 'numeraireBuybackAmountInUsed is always paid in numeraire' and the accounting subtracts from balance1 in that case — consistent. Re-checking: the assetBuyback output side in the Migrator uses `Currency.wrap(numeraire).transfer(recipient, assetBuybackAmountOut)` which is correct (asset-fees swapped for numeraire). I cannot confirm a swap mismatch — RETRACTING this finding (downgrading to low/no-issue).

Recommendation

Re-verify token wrapping in non-route-to-beneficiary branches; suggest adding unit tests that assert `recipient` receives the expected currency.

GPT-5

Misuse of custom errors with require() causes compilation failure across multiple files

highbuild-releasehigh
  • src/dopplerHooks/RehypeDopplerHookInitializer.sol
  • src/dopplerHooks/RehypeDopplerHookInitializer.sol
  • src/dopplerHooks/RehypeDopplerHookInitializer.sol
  • src/dopplerHooks/RehypeDopplerHookInitializer.sol
  • src/dopplerHooks/RehypeDopplerHookInitializer.sol
  • src/dopplerHooks/RehypeDopplerHookMigrator.sol
  • src/dopplerHooks/RehypeDopplerHookMigrator.sol
  • src/dopplerHooks/ScheduledLaunchDopplerHook.sol
  • src/initializers/DecayMulticurveInitializer.sol
  • src/initializers/DecayMulticurveInitializer.sol
  • src/initializers/DecayMulticurveInitializer.sol
  • src/initializers/DecayMulticurveInitializerHook.sol
  • src/initializers/DecayMulticurveInitializerHook.sol
  • src/initializers/DecayMulticurveInitializerHook.sol
In Solidity 0.8.x, require(condition, message) only accepts a string message. Passing a custom error to require will not compile. The correct pattern is to use if (!condition) revert CustomError(args). Multiple instances across the feature use require with custom errors, which prevents successful compilation and deployment.

Recommendation

Replace each require with a custom error second argument by an explicit revert. For example: - if (initData.startFee > uint24(MAX_SWAP_FEE)) revert FeeTooHigh(initData.startFee); - if (initData.startFee < initData.endFee) revert InvalidFeeRange(initData.startFee, initData.endFee); - if (initData.durationSeconds == 0) revert InvalidDurationSeconds(initData.durationSeconds); Apply the same change in all listed locations (Initializer, Migrator, Hooks, ScheduledLaunch).

The agreement

Both frontier models flagged this within the same line range. AntFleet's unanimous gate fired — the finding posted on the PR.

Closure

Tweet thread template

tweet 1 of 8143 / 280

Two frontier models reviewed PR #2 on a7cc2ed7. Both found this bug: critical bug: Migrator buyback transfers send wrong currency to recipient

tweet 2 of 890 / 280

The vulnerable code (:0-0): (full snippet at https://www.antfleet.dev/anatomy/bb96d68b-0)

tweet 3 of 8280 / 280

What Opus saw: "In the numeraireBuybackAmountIn branch the migrator swaps in numeraire to buy back asset (zeroForOne = !isToken0). The output of that swap is the asset token. When `routeToBeneficiaryFees` is false the code calls `Currency.wrap(asset).transfer(recipient, numerai…

tweet 4 of 8280 / 280

What GPT-5 saw: "In Solidity 0.8.x, require(condition, message) only accepts a string message. Passing a custom error to require will not compile. The correct pattern is to use if (!condition) revert CustomError(args). Multiple instances across the feature use require with cust…

tweet 5 of 897 / 280

Both flagged the same line range. AntFleet's unanimous gate fired — the finding posted on the PR.

tweet 6 of 893 / 280

The fix landed in commit pending: (view diff at https://www.antfleet.dev/anatomy/bb96d68b-0)

tweet 7 of 881 / 280

AntFleet reviews every PR with two frontier models. Only unanimous findings post.

tweet 8 of 877 / 280

Full anatomy + reasoning + diffs: https://www.antfleet.dev/anatomy/bb96d68b-0

Paste into X composer one tweet at a time. X has no multi-tweet intent API.

critical bug: Migrator buyback transfers send wrong currency to recipient — AntFleet anatomy