Commit d4a3f26
fix: tolerate hook-funded input on exact-output swaps
A v4 hook can pay a swap's input on the caller's behalf. When it covers the
cost in full, PoolManager hands the router the requested positive output
against an input delta of exactly zero. That is solvent -- the hook settles
the debt inside the unlock -- and core permits it explicitly: Hooks.sol:311
notes the caller "has to pay for (or receive) the hook's delta", and IHooks
documents a negative afterSwap return as the hook owing/sending currency.
But it is a shape no ordinary pool produces, where positive output always
implied strictly negative input. Exact output is the only direction that has
to DISCOVER its input by reading a delta, so it is the only one affected:
- The per-hop price guard divides output by input. A zero input panics 0x12,
even though the realized price is then infinite and clears every finite
bound it is checked against. Hit _swapExactOutputSingle and, because the
multihop guard is gated on array presence rather than each bound's value,
_swapExactOutput whenever minHopPriceX36 was non-empty.
- The multihop loop runs backwards, feeding each hop's input to the previous
hop as its required output. A funded hop propagated zero, and PoolManager
rejects amountSpecified == 0 with SwapAmountCannotBeZero.
Skip the division when the input is zero, and stop the backward loop when a
hop consumes nothing -- the upstream hops have nothing left to produce, and
_take/_settle already no-op on zero, so untouched currencies settle cleanly
and an amountIn of 0 trivially clears amountInMaximum.
Over-funding (a hook paying MORE than the cost, leaving a credit whose owner
is undefined in a route) is deliberately left unsupported and still reverts
SafeCastOverflow on the negation, as it does today. Naming that condition
would mean adding an error to IV4Router for no behavior change, so the
interface is untouched; the reasoning is recorded on _swapInput instead.
The fill check runs before the zero-input branch, so all-or-nothing still
holds: a funded hop that underfills reverts V4ExactOutputUnfilled as before.
Exact input is unaffected -- its divisor is the caller's own amountIn, never
a delta, so it can neither divide by zero nor read the sign that overflows.
Also corrects the "output delta will always be positive" comments, which
named a hook case the code does not actually handle: a hook taking more than
the whole output leaves a negative delta and the cast in _swapOutput rejects
it. Documented rather than fixed, since exact input is out of scope here.
Gas +26 single-hop, +54/hop multihop, +96 bytes. Snapshots regenerated
(isolate mode).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>1 parent 545a5d2 commit d4a3f26
4 files changed
Lines changed: 385 additions & 18 deletions
File tree
- snapshots
- src
- test
- mocks
- router
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | | - | |
| 2 | + | |
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
| |||
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
14 | | - | |
15 | | - | |
16 | | - | |
17 | | - | |
18 | | - | |
19 | | - | |
20 | | - | |
21 | | - | |
22 | | - | |
23 | | - | |
24 | | - | |
25 | | - | |
26 | | - | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
27 | 27 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
115 | 115 | | |
116 | 116 | | |
117 | 117 | | |
118 | | - | |
| 118 | + | |
| 119 | + | |
119 | 120 | | |
120 | 121 | | |
121 | 122 | | |
| |||
147 | 148 | | |
148 | 149 | | |
149 | 150 | | |
150 | | - | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
151 | 154 | | |
152 | 155 | | |
153 | 156 | | |
| |||
174 | 177 | | |
175 | 178 | | |
176 | 179 | | |
177 | | - | |
| 180 | + | |
| 181 | + | |
178 | 182 | | |
179 | 183 | | |
180 | 184 | | |
| |||
185 | 189 | | |
186 | 190 | | |
187 | 191 | | |
188 | | - | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
189 | 196 | | |
190 | 197 | | |
191 | 198 | | |
192 | 199 | | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
193 | 205 | | |
194 | 206 | | |
195 | 207 | | |
| |||
213 | 225 | | |
214 | 226 | | |
215 | 227 | | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
216 | 232 | | |
217 | 233 | | |
218 | 234 | | |
219 | 235 | | |
220 | 236 | | |
221 | 237 | | |
222 | 238 | | |
| 239 | + | |
| 240 | + | |
223 | 241 | | |
224 | 242 | | |
225 | 243 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
0 commit comments