-
Notifications
You must be signed in to change notification settings - Fork 16
/
Copy pathUniswapV4.sol
390 lines (357 loc) · 17.6 KB
/
UniswapV4.sol
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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;
import {IERC20} from "@forge-std/interfaces/IERC20.sol";
import {ISignatureTransfer} from "@permit2/interfaces/ISignatureTransfer.sol";
import {SafeTransferLib} from "../vendor/SafeTransferLib.sol";
import {SettlerAbstract} from "../SettlerAbstract.sol";
import {Panic} from "../utils/Panic.sol";
import {UnsafeMath} from "../utils/UnsafeMath.sol";
import {TooMuchSlippage, DeltaNotPositive, DeltaNotNegative, ZeroSellAmount} from "./SettlerErrors.sol";
import {
BalanceDelta, IHooks, IPoolManager, UnsafePoolManager, IUnlockCallback
} from "./UniswapV4Types.sol";
import {Encoder, NotesLib, StateLib, Decoder, Take} from "./FlashAccountingCommon.sol";
library CreditDebt {
using UnsafeMath for int256;
function asCredit(int256 delta, IERC20 token) internal pure returns (uint256) {
if (delta < 0) {
revert DeltaNotPositive(token);
}
return uint256(delta);
}
function asDebt(int256 delta, IERC20 token) internal pure returns (uint256) {
if (delta > 0) {
revert DeltaNotNegative(token);
}
return uint256(delta.unsafeNeg());
}
}
abstract contract UniswapV4 is SettlerAbstract {
using SafeTransferLib for IERC20;
using UnsafeMath for uint256;
using UnsafeMath for int256;
using CreditDebt for int256;
using UnsafePoolManager for IPoolManager;
using NotesLib for NotesLib.Note;
using NotesLib for NotesLib.Note[];
using StateLib for StateLib.State;
constructor() {
assert(BASIS == Encoder.BASIS);
assert(BASIS == Decoder.BASIS);
assert(ETH_ADDRESS == Decoder.ETH_ADDRESS);
}
function _POOL_MANAGER() internal view virtual returns (IPoolManager);
//// These two functions are the entrypoints to this set of actions. Because UniV4 has a
//// mandatory callback, and the vast majority of the business logic has to be executed inside
//// the callback, they're pretty minimal. Both end up inside the last function in this file
//// `unlockCallback`, which is where most of the business logic lives. Primarily, these
//// functions are concerned with correctly encoding the argument to
//// `POOL_MANAGER.unlock(...)`. Pay special attention to the `payer` field, which is what
//// signals to the callback whether we should be spending a coupon.
//// How to generate `fills` for UniV4:
////
//// Linearize your DAG of fills by doing a topological sort on the tokens involved. In the
//// topological sort of tokens, when there is a choice of the next token, break ties by
//// preferring a token if it is the lexicographically largest token that is bought among fills
//// with sell token equal to the previous token in the topological sort. Then sort the fills
//// belonging to each sell token by their buy token. This technique isn't *quite* optimal, but
//// it's pretty close. The buy token of the final fill is special-cased. It is the token that
//// will be transferred to `recipient` and have its slippage checked against `amountOutMin`. In
//// the event that you are encoding a series of fills with more than one output token, ensure
//// that at least one of the global buy token's fills is positioned appropriately.
////
//// Now that you have a list of fills, encode each fill as follows.
//// First encode the `bps` for the fill as 2 bytes. Remember that this `bps` is relative to the
//// running balance at the moment that the fill is settled.
//// Second, encode the packing key for that fill as 1 byte. The packing key byte depends on the
//// tokens involved in the previous fill. The packing key for the first fill must be 1;
//// i.e. encode only the buy token for the first fill.
//// 0 -> sell and buy tokens remain unchanged from the previous fill (pure multiplex)
//// 1 -> sell token remains unchanged from the previous fill, buy token is encoded (diamond multiplex)
//// 2 -> sell token becomes the buy token from the previous fill, new buy token is encoded (multihop)
//// 3 -> both sell and buy token are encoded
//// Obviously, after encoding the packing key, you encode 0, 1, or 2 tokens (each as 20 bytes),
//// as appropriate.
//// The remaining fields of the fill are mandatory.
//// Third, encode the pool fee as 3 bytes, and the pool tick spacing as 3 bytes.
//// Fourth, encode the hook address as 20 bytes.
//// Fifth, encode the hook data for the fill. Encode the length of the hook data as 3 bytes,
//// then append the hook data itself.
////
//// Repeat the process for each fill and concatenate the results without padding.
function sellToUniswapV4(
address recipient,
IERC20 sellToken,
uint256 bps,
bool feeOnTransfer,
uint256 hashMul,
uint256 hashMod,
bytes memory fills,
uint256 amountOutMin
) internal returns (uint256 buyAmount) {
bytes memory data = Encoder.encode(
uint32(IPoolManager.unlock.selector),
recipient,
sellToken,
bps,
feeOnTransfer,
hashMul,
hashMod,
fills,
amountOutMin
);
bytes memory encodedBuyAmount = _setOperatorAndCall(
address(_POOL_MANAGER()), data, uint32(IUnlockCallback.unlockCallback.selector), _uniV4Callback
);
// buyAmount = abi.decode(abi.decode(encodedBuyAmount, (bytes)), (uint256));
assembly ("memory-safe") {
// We can skip all the checks performed by `abi.decode` because we know that this is the
// verbatim result from `unlockCallback` and that `unlockCallback` encoded the buy
// amount correctly.
buyAmount := mload(add(0x60, encodedBuyAmount))
}
}
function sellToUniswapV4VIP(
address recipient,
bool feeOnTransfer,
uint256 hashMul,
uint256 hashMod,
bytes memory fills,
ISignatureTransfer.PermitTransferFrom memory permit,
bytes memory sig,
uint256 amountOutMin
) internal returns (uint256 buyAmount) {
bytes memory data = Encoder.encodeVIP(
uint32(IPoolManager.unlock.selector),
recipient,
feeOnTransfer,
hashMul,
hashMod,
fills,
permit,
sig,
_isForwarded(),
amountOutMin
);
bytes memory encodedBuyAmount = _setOperatorAndCall(
address(_POOL_MANAGER()), data, uint32(IUnlockCallback.unlockCallback.selector), _uniV4Callback
);
// buyAmount = abi.decode(abi.decode(encodedBuyAmount, (bytes)), (uint256));
assembly ("memory-safe") {
// We can skip all the checks performed by `abi.decode` because we know that this is the
// verbatim result from `unlockCallback` and that `unlockCallback` encoded the buy
// amount correctly.
buyAmount := mload(add(0x60, encodedBuyAmount))
}
}
function _uniV4Callback(bytes calldata data) private returns (bytes memory) {
// We know that our calldata is well-formed. Therefore, the first slot is 0x20 and the
// second slot is the length of the strict ABIEncoded payload
assembly ("memory-safe") {
data.length := calldataload(add(0x20, data.offset))
data.offset := add(0x40, data.offset)
}
return unlockCallback(data);
}
//// The following functions are the helper functions for `unlockCallback`. They abstract much
//// of the complexity of tracking which tokens need to be zeroed out at the end of the
//// callback.
////
//// The two major pieces of state that are maintained through the callback are `Note[] memory
//// notes` and `State memory state`
////
//// `notes` keeps track of the list of the tokens that have been touched throughout the
//// callback that have nonzero credit. At the end of the fills, all tokens with credit will be
//// swept back to Settler. These are the global buy token (against which slippage is checked)
//// and any other multiplex-out tokens. Only the global sell token is allowed to have debt, but
//// it is accounted slightly differently from the other tokens. The function `_take` is
//// responsible for iterating over the list of tokens and withdrawing any credit to the
//// appropriate recipient.
////
//// `state` exists to reduce stack pressure and to simplify/gas-optimize the process of
//// swapping. By keeping track of the sell and buy token on each hop, we're able to compress
//// the representation of the fills required to satisfy the swap. Most often in a swap, the
//// tokens in adjacent fills are somewhat in common. By caching, we avoid having them appear
//// multiple times in the calldata.
// the mandatory fields are
// 2 - sell bps
// 1 - pool key tokens case
// 3 - pool fee
// 3 - pool tick spacing
// 20 - pool hooks
// 3 - hook data length
uint256 private constant _HOP_DATA_LENGTH = 32;
uint256 private constant _ADDRESS_MASK = 0x00ffffffffffffffffffffffffffffffffffffffff;
/// Decode a `PoolKey` from its packed representation in `bytes` and the token information in
/// `state`. Returns the `zeroForOne` flag and the suffix of the bytes that are not consumed in
/// the decoding process.
function _setPoolKey(IPoolManager.PoolKey memory key, StateLib.State memory state, bytes calldata data)
private
pure
returns (bool, bytes calldata)
{
(IERC20 sellToken, IERC20 buyToken) = (state.sell.token, state.buy.token);
bool zeroForOne;
assembly ("memory-safe") {
sellToken := and(_ADDRESS_MASK, sellToken)
buyToken := and(_ADDRESS_MASK, buyToken)
zeroForOne :=
or(
eq(sellToken, 0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee),
and(iszero(eq(buyToken, 0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee)), lt(sellToken, buyToken))
)
}
(key.token0, key.token1) = zeroForOne ? (sellToken, buyToken) : (buyToken, sellToken);
uint256 packed;
assembly ("memory-safe") {
packed := shr(0x30, calldataload(data.offset))
data.offset := add(0x1a, data.offset)
data.length := sub(data.length, 0x1a)
// we don't check for array out-of-bounds here; we will check it later in `Decoder.overflowCheck`
}
key.fee = uint24(packed >> 184);
key.tickSpacing = int24(uint24(packed >> 160));
key.hooks = IHooks.wrap(address(uint160(packed)));
return (zeroForOne, data);
}
function _pay(
IERC20 sellToken,
address payer,
uint256 sellAmount,
ISignatureTransfer.PermitTransferFrom calldata permit,
bool isForwarded,
bytes calldata sig
) private returns (uint256) {
IPoolManager(msg.sender).unsafeSync(sellToken);
if (payer == address(this)) {
sellToken.safeTransfer(msg.sender, sellAmount);
} else {
// assert(payer == address(0));
ISignatureTransfer.SignatureTransferDetails memory transferDetails =
ISignatureTransfer.SignatureTransferDetails({to: msg.sender, requestedAmount: sellAmount});
_transferFrom(permit, transferDetails, sig, isForwarded);
}
return IPoolManager(msg.sender).unsafeSettle();
}
function unlockCallback(bytes calldata data) private returns (bytes memory) {
address recipient;
uint256 minBuyAmount;
uint256 hashMul;
uint256 hashMod;
bool feeOnTransfer;
address payer;
(data, recipient, minBuyAmount, hashMul, hashMod, feeOnTransfer, payer) = Decoder.decodeHeader(data);
// Set up `state` and `notes`. The other values are ancillary and might be used when we need
// to settle global sell token debt at the end of swapping.
(
bytes calldata newData,
StateLib.State memory state,
NotesLib.Note[] memory notes,
ISignatureTransfer.PermitTransferFrom calldata permit,
bool isForwarded,
bytes calldata sig
) = Decoder.initialize(data, hashMul, hashMod, payer);
if (payer != address(this)) {
state.globalSell.amount = _permitToSellAmountCalldata(permit);
}
if (feeOnTransfer) {
state.globalSell.amount =
_pay(state.globalSell.token, payer, state.globalSell.amount, permit, isForwarded, sig);
}
if (state.globalSell.amount == 0) {
revert ZeroSellAmount(state.globalSell.token);
}
state.globalSellAmount = state.globalSell.amount;
data = newData;
// Now that we've unpacked and decoded the header, we can begin decoding the array of swaps
// and executing them.
IPoolManager.PoolKey memory key;
IPoolManager.SwapParams memory params;
while (data.length >= _HOP_DATA_LENGTH) {
uint16 bps;
assembly ("memory-safe") {
bps := shr(0xf0, calldataload(data.offset))
data.offset := add(0x02, data.offset)
data.length := sub(data.length, 0x02)
// we don't check for array out-of-bounds here; we will check it later in `Decoder.overflowCheck`
}
data = Decoder.updateState(state, notes, data);
bool zeroForOne;
(zeroForOne, data) = _setPoolKey(key, state, data);
bytes calldata hookData;
(data, hookData) = Decoder.decodeBytes(data);
Decoder.overflowCheck(data);
params.zeroForOne = zeroForOne;
unchecked {
params.amountSpecified = int256((state.sell.amount * bps).unsafeDiv(BASIS)).unsafeNeg();
}
// TODO: price limits
params.sqrtPriceLimitX96 = zeroForOne ? 4295128740 : 1461446703485210103287273052203988822378723970341;
BalanceDelta delta = IPoolManager(msg.sender).unsafeSwap(key, params, hookData);
{
(int256 settledSellAmount, int256 settledBuyAmount) =
zeroForOne ? (delta.amount0(), delta.amount1()) : (delta.amount1(), delta.amount0());
// Some insane hooks may increase the sell amount; obviously this may result in
// unavoidable reverts in some cases. But we still need to make sure that we don't
// underflow to avoid wildly unexpected behavior. The pool manager enforces that the
// settled sell amount cannot be positive
state.sell.amount -= uint256(settledSellAmount.unsafeNeg());
// If `state.buy.amount()` overflows an `int128`, we'll get a revert inside the pool
// manager later. We cannot overflow a `uint256`.
unchecked {
state.buy.amount += settledBuyAmount.asCredit(state.buy.token);
}
}
}
// `data` has been consumed. All that remains is to settle out the net result of all the
// swaps. Any credit in any token other than `state.buy.token` will be swept to
// Settler. `state.buy.token` will be sent to `recipient`.
{
(IERC20 globalSellToken, uint256 globalSellAmount) = (state.globalSell.token, state.globalSell.amount);
uint256 globalBuyAmount =
Take.take(state, notes, uint32(IPoolManager.take.selector), recipient, minBuyAmount);
if (feeOnTransfer) {
// We've already transferred the sell token to the pool manager and
// `settle`'d. `globalSellAmount` is the verbatim credit in that token stored by the
// pool manager. We only need to handle the case of incomplete filling.
if (globalSellAmount != 0) {
Take._callSelector(
uint32(IPoolManager.take.selector),
globalSellToken,
payer == address(this) ? address(this) : _msgSender(),
globalSellAmount
);
}
} else {
// While `notes` records a credit value, the pool manager actually records a debt
// for the global sell token. We recover the exact amount of that debt and then pay
// it.
// `globalSellAmount` is _usually_ zero, but if it isn't it represents a partial
// fill. This subtraction recovers the actual debt recorded in the pool manager.
uint256 debt;
unchecked {
debt = state.globalSellAmount - globalSellAmount;
}
if (debt == 0) {
revert ZeroSellAmount(globalSellToken);
}
if (globalSellToken == ETH_ADDRESS) {
IPoolManager(msg.sender).unsafeSync(IERC20(address(0)));
IPoolManager(msg.sender).unsafeSettle(debt);
} else {
_pay(globalSellToken, payer, debt, permit, isForwarded, sig);
}
}
bytes memory returndata;
assembly ("memory-safe") {
returndata := mload(0x40)
mstore(returndata, 0x60)
mstore(add(0x20, returndata), 0x20)
mstore(add(0x40, returndata), 0x20)
mstore(add(0x60, returndata), globalBuyAmount)
mstore(0x40, add(0x80, returndata))
}
return returndata;
}
}
}