-
Notifications
You must be signed in to change notification settings - Fork 3
s4 improve uniswapv3 swap precision #127
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
Conversation
and tests: testSwapExactInputAgainstFlowSwapV3, testSwapExactOutputAgainstFlowSwapV3
…d balance change check in tests
…uniswapv3-swap-precision
…uniswapv3-swap-precision # Conflicts: # cadence/contracts/connectors/evm/UniswapV3SwapConnectors.cdc
| @@ -0,0 +1,183 @@ | |||
| #test_fork(network: "mainnet", height: nil) | |||
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.
It's best that we pin to block heights for determinism in the fork tests (and CI speed/caching advantages that will come a future release)
|
|
||
| /// Executes exact output swap via router - user specifies desired output amount | ||
| /// Returns array of two vaults: [0] = output vault with exact amount, [1] = leftover input vault with unused tokens | ||
| access(self) fun _swapExactOut( |
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 this be moved to the section with the "helpers" below?
https://github.com/onflow/FlowActions/pull/127/changes#diff-d2b4391d7a409bed31ef788dc70777ad1419c96cee49add229a971a5bd0cc51eR711
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.
_swapExactOut, _swapExactIn are core swap implementation functions, not a utility helpers.
The helpers section below contains smaller utility functions (like _dryCall, _toCadenceOut, borrowCOA) that support these core implementations and work with members of struct Swapper
| /// @param quote: Required quote containing outAmount (exact output desired) and inAmount (max input allowed) | ||
| /// @param inVault: The input vault containing tokens to swap (must have balance >= quote.inAmount) | ||
| /// @return Array of two vaults: [0] = output vault with exact amount, [1] = leftover vault with unused input | ||
| access(all) fun swapExactOutput(quote: {DeFiActions.Quote}, inVault: @{FungibleToken.Vault}): @[{FungibleToken.Vault}] { |
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.
Is it necessary to add a new public function to only the UniswapV3SwapConnectors, outside of the generic Swapper interface conformance?
My understanding was that this could be implemented as part of the {Quote} returned by quoteOut (and/or quoteIn). If we’re able to make a breaking change, the cleanest option would probably be to add a mode (in/out) field to SwapConnectors.BasicQuote
If we can’t, we could keep BasicQuote as-is and introduce another struct, like ModeQuote, which carries this modality, then have swappers interpret it via casting.
e.g. Within the SwapConnectors contract, something like:
access(all) enum SwapMode {
access(all) case IN
access(all) case OUT
}
access(all) struct ModeQuote: Quote {
access(all) let mode: SwapMode
// ... rest
}And then within the UniswapV3SwapConnectors contract, something like:
access(all) fun normalizeQuote(quote: {DeFiActions.Quote}): SwapConnectors.ModeQuote {
if let mq = quote as? SwapConnectors.ModeQuote {
return mq
}
return SwapConnectors.ModeQuote(
mode: SwapMode.IN,
// ... rest of quote
)
}@nialexsan, maybe you have some more context on the original design philosophy 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.
To simplify and reduce possible errors in using these methods, I proposed two separate interfaces: swap and swapExactOut, because it's better to have two separate methods with different functionality than one method with functionality that depends on some input flags.
Also, UniswapV3 has two separate methods: swapExactInputSingle and swapExactOutputSingle;
and even FungibleToken has: withdraw(amount: UFix64) and withdrawAvailable(maxAmount: UFix64).
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 primary concern is that by deviating from the {Swapper} interface, systems interacting with the swapper will still experience the exact same precision loss that this PR intends to solve, unless they explicitly couple their code to the UniswapV3Connectors conrete implementation (undesirable), as swapExactOutput is not a part of the {Swapper} interface.
i.e. generally speaking a dev trying to swap for an exact output tokens would do the following
let swapper: {Swapper} = ...
swapper.swap(swapper.quoteOut(...), <-tokens)And this PR would not solve this issue. By conforming to a generic interface & respecting the existing code path, we allow all future {Swapper} implementations to provide this same functionality without changes to the downstream call sites.
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.
The fundamental issue is that the current Swapper interface:
access(all) fun swap(quote: {Quote}?, inVault: @{FungibleToken.Vault}): @{FungibleToken.Vault}
...cannot support swapExactOut because it needs to return BOTH the output vault AND the remaining/unused input tokens.
To properly solve this at the interface level (so all swappers benefit, not just UniswapV3), we need to change the signature to:
access(all) fun swap(quote: {Quote}?, inVault: @{FungibleToken.Vault}): @[{FungibleToken.Vault}]
Where [0] is the output vault and [1] is leftover input (empty vault for exactInput swaps).
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 it makes sense to add swapExactOutput to the interface and treat swap as swapExactIn, since it wouldn't require to return residual in token from the swap
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.
done
…uniswapv3-swap-precision
Add swapExactOut() method to DeFiActions.Swapper interface to support exact output swaps where users specify the desired output amount and receive any unconsumed input tokens back. Swapper interface: - Add swapExactOut(quote, inVault) returning [outVault, leftoverInVault] - Returns fixed-length array of 2 vaults for predictable handling - Includes pre/post conditions and Swapped event emission - Comprehensive documentation with parameter and return value details Implementation updates: - UniswapV3SwapConnectors: Full support via Uniswap V3 exactOutput - MultiSwapper: Routes to optimal inner swapper's swapExactOut - MockSwapper (test): Implements exact output with leftover calculation Unsupported (panic with clear messages): - SequentialSwapper: Complex to chain exact outputs - ERC4626SwapConnectors: No sync withdrawal support - UniswapV2SwapConnectors: Only supports exact input - IncrementFiSwapConnectors: Only supports exact input - IncrementFiPoolLiquidityConnectors.Zapper: LP zapping complexity
Closes #114
Summary
Tests