-
Notifications
You must be signed in to change notification settings - Fork 488
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Routing #58
Routing #58
Conversation
single hops passing on exactOut
contracts/V4Router.sol
Outdated
|
||
function _swapExactOutput(ExactOutputParams memory params, address msgSender) private { | ||
unchecked { | ||
for (uint256 i = params.path.length; i > 0; i--) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ugh i dislike that this is from n
to 1
not n-1
to 0
... but I see the issue.
I guess it could be
for (uint256 i = params.path.length-1; i < params.path.length; i--) {
but thats also ugly so i guess theres no good way 😂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dude yah, I actually searched all Uniswap for i--
to see how ppl deal with this type of thing. And I found ANOTHER POC v4 router that Pote wrote ages ago and he did the same thing baha
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like this is typical for for loops iterating backwards through the path though.. like doesn't seem too weird to me. Is there a reason we force it to be backwards though? I guess compatibility with how other routers work but there is no reason we couldn't just encode the path the other way also
contracts/V4Router.sol
Outdated
|
||
function _swapExactOutput(ExactOutputParams memory params, address msgSender) private { | ||
unchecked { | ||
for (uint256 i = params.path.length; i > 0; i--) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like this is typical for for loops iterating backwards through the path though.. like doesn't seem too weird to me. Is there a reason we force it to be backwards though? I guess compatibility with how other routers work but there is no reason we couldn't just encode the path the other way also
src/libraries/CalldataBytesLib.sol
Outdated
res.offset := offset | ||
} | ||
} | ||
|
||
/// @notice Performs the equivalent of `abi.decode(data, (uint256[], bytes[]))` in calldata | ||
/// @param _bytes The input bytes string to extract input arrays from | ||
/// @return actions The uint256 calldata array of actions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think decodeInCalldata is not memory-safe?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about it isnt memory safe?
if (delta > 0) revert InvalidDeltaForAction(); | ||
|
||
// TODO support address(this) paying too | ||
// TODO should it have a maxAmountOut added slippage protection? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
my thought is that slippage protection should be with the swap commands? why do we also need slippage protection with settle commands?
i think we can make settle just take an amount, and if you're ok with the full settle amount then you pass in a constant/sentinel
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Its to support split route trades. Lets say youre trading 1000 DAI for USDC, where you want at least 998 USDC
- 500 DAI -> USDT -> USDC
- 500 DAI -> USDC
Then you would do a command thats like "take all USDC, and check we're receiving at least 998".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We implement the same style of logic in UR using the sweep command e.g. here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For v3 split routes it trades both routes, with the recipient of the UR, and then calls SWEEP(USDC, 998)
which will revert the trade if 998 weren't received
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And we do it with the unwrapETH command to do the same check for ETH output trades e.g. here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but surely you're also checking slippage from USDT to USDC and from DAI to USDC? or are you ignoring those checks until the end? My concern is that we're doing lots of slippage checks and the ones in the middle then dont even matter?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My other thought process then also is just if posm & routers take/settle commands looks different now bc I was not planning on doing extra slippage checks on take/settle. (IK this is a future TODO but just adding my thoughts in this thread. Not blocking on getting this PR merged)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but surely you're also checking slippage from USDT to USDC and from DAI to USDC? or are you ignoring those checks until the end? My concern is that we're doing lots of slippage checks and the ones in the middle then dont even matter?
yeah the ones in the middle dont matter. the user doesnt care if one half of the trade gets hit with more slippage than the other - theres just an output amount that they want from the trade in total and thats it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for split route trades we pass in 0 as minAmountOut at each stage - and then do aggregated slippage checks on total output
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My other thought process then also is just if posm & routers take/settle commands looks different now
yeah i could do it as a different cmmand or something depending how the other commands end up
@@ -7,6 +7,9 @@ import {ImmutableState} from "./ImmutableState.sol"; | |||
/// @notice Abstract contract used to sync, send, and settle funds to the pool manager | |||
/// @dev Note that sync() is called before any erc-20 transfer in `settle`. | |||
abstract contract DeltaResolver is ImmutableState { | |||
/// @notice Emitted trying to settle a positive delta, or take a negative delta |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we make this type(uint256).max so its super explicit? Also matches other patterns like approving with type(uint256).max to mean my entire balance
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh weird.. on codespaces the code I was reviewing had the changes where you kept the flag for checking to use the contract balance.. I guess we can do that in a different PR!
// equivalent: abi.decode(params, (Currency, address)) | ||
Currency currency; | ||
address recipient; | ||
assembly ("memory-safe") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#199 just tagging that 199 should also put these in a lib so we can use in both contracts
src/V4Router.sol
Outdated
for (uint256 i = pathLength; i > 0; i--) { | ||
pathKey = params.path[i - 1]; | ||
(PoolKey memory poolKey, bool oneForZero) = pathKey.getPoolAndSwapDirection(currencyOut); | ||
amountIn = uint128(-_swap(poolKey, !oneForZero, int256(uint256(amountOut)), 0, pathKey.hookData)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like I commented at some point about documenting/adding a little comment about the return signs but can't find it anywhere.. Anyways I do think it would be nice explaining that the output is guaranteed to be negative for exactOutput in the reciprocal token.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
have actually added a safecast on this - i think with custom accounting hooks on low liquidity pools its technically not guaranteed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok looks good! I do think we should be adding some more tests (like split routes) but we can followup?
@@ -7,6 +7,9 @@ import {ImmutableState} from "./ImmutableState.sol"; | |||
/// @notice Abstract contract used to sync, send, and settle funds to the pool manager | |||
/// @dev Note that sync() is called before any erc-20 transfer in `settle`. | |||
abstract contract DeltaResolver is ImmutableState { | |||
/// @notice Emitted trying to settle a positive delta, or take a negative delta |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh weird.. on codespaces the code I was reviewing had the changes where you kept the flag for checking to use the contract balance.. I guess we can do that in a different PR!
|
||
function test_swapExactInputSingle_oneForZero() public { | ||
uint256 amountIn = 1 ether; | ||
uint256 expectedAmountOut = 992054607780215625; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
where is this number from? I think you could use deltas instead of hardcoding?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
gonna do this in the next PR - #221
|
||
/// @title Safe casting methods | ||
/// @notice Contains methods for safely casting between types | ||
library SafeCast { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not just import SafeCast from core?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this function doesnt exist in core rip
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
approving, but will need to move that func to core! (alice opening issue)
Probably of interest to note that foundry gas snapshots are not accounting for storage clear refunds, so before we get transient storage in here, gas is incredibly exaggerated.
Could cut down on bytecode by removing
Single
options