AntFleet

Anatomy · 57e5c9ae-1

AgentFloatHook._beforeSwap uses regular approve (not forceApprove) on USDT-style tokens

mediumbugclosed in 3c10efc
repo bf0d040b·PR #3·reviewed 1 month ago·closed 1 month ago

The vulnerable code

contracts/src/AgentFloatHook.sol:144-158

144 return (BaseHook.afterRemoveLiquidity.selector, delta);
145 }
146
147 /// @dev JIT recall — withdraw parked USDC if the price is returning into range
148 /// and the hook holds vault deposits from prior parks.
149 function _beforeSwap(
150 address sender,
151 PoolKey calldata key,
152 SwapParams calldata,
153 bytes calldata hookData
154 ) internal override returns (bytes4, BeforeSwapDelta, uint24) {
155 (, int24 currentTick,,) = poolManager.getSlot0(key.toId());
156
157 uint256 recallAmount = 100 * 10 ** 6;
158 if (hookData.length >= 32) {

The reasoning

Opus

AgentFloatHook._beforeSwap uses regular approve (not forceApprove) on USDT-style tokens

mediumbughigh
  • contracts/src/AgentFloatHook.sol:144-158
On mainnet USDT is the underlying. The README and other code paths consistently use forceApprove (zero-then-set) because USDT rejects non-zero → non-zero approve. The transient-storage branch of _beforeSwap uses plain `usdc.approve(address(poolManager), recallAmount)` (no SafeERC20 forceApprove), so the second+ JIT recall in a row will revert when the prior allowance to poolManager is non-zero. The fallback branch correctly uses forceApprove, which makes the inconsistency a real bug rather than intent.

Recommendation

Use `usdc.forceApprove(address(poolManager), recallAmount)` in both branches.

GPT-5

Inconsistent allowance handling in JIT recall: approve used in one branch and forceApprove in the other (USDT-style tokens may revert)

mediumbughigh
  • contracts/src/AgentFloatHook.sol:139-167
USDT-style tokens revert on non-zero to non-zero approve. The code uses approve (without zeroing) in the transient branch but forceApprove (zero-then-set) in the persistent branch. This inconsistency can cause recalls to revert depending on which branch executes.

Recommendation

Use SafeERC20.forceApprove (or zero-then-set approve) consistently in both branches when setting allowance for PoolManager.

The agreement

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

The fix

144 return (BaseHook.afterRemoveLiquidity.selector, delta);
145 }
146
147 /// @dev JIT recall — withdraw parked USDC if the price is returning into range
148 /// and the hook holds vault deposits from prior parks.
149 function _beforeSwap(
150 address sender,
151 PoolKey calldata key,
152 SwapParams calldata,
153 bytes calldata hookData
154 ) internal override returns (bytes4, BeforeSwapDelta, uint24) {
155 (, int24 currentTick,,) = poolManager.getSlot0(key.toId());
156
157 uint256 recallAmount = 100 * 10 ** 6;
158 if (hookData.length >= 32) {

Closure

Closed 1 month ago

SHA: 3c10efc6038bc5ab182e8b192224745b99bcf729

View closure receipt on GitHub →

Tweet thread template

tweet 1 of 8169 / 280

Two frontier models reviewed PR #3 on bf0d040b. Both found this bug: medium bug: AgentFloatHook._beforeSwap uses regular approve (not forceApprove) on USDT-style tokens

tweet 2 of 8126 / 280

The vulnerable code (contracts/src/AgentFloatHook.sol:144-158): (full snippet at https://www.antfleet.dev/anatomy/57e5c9ae-1)

tweet 3 of 8280 / 280

What Opus saw: "On mainnet USDT is the underlying. The README and other code paths consistently use forceApprove (zero-then-set) because USDT rejects non-zero → non-zero approve. The transient-storage branch of _beforeSwap uses plain `usdc.approve(address(poolManager), recallAm…

tweet 4 of 8280 / 280

What GPT-5 saw: "USDT-style tokens revert on non-zero to non-zero approve. The code uses approve (without zeroing) in the transient branch but forceApprove (zero-then-set) in the persistent branch. This inconsistency can cause recalls to revert depending on which branch execute…

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 3c10efc: (view diff at https://www.antfleet.dev/anatomy/57e5c9ae-1)

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/57e5c9ae-1

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