Let's use this transaction as an example:
https://bscscan.com/tx/0xdd38b435d9840062db9106e23712873fa2da44c366fa737b17f7fc6d111ef076#eventlog
Here 172054109197
Zombie tokens were transferred to Zombie / BUSD LP in exchange for 6357581284196624466
BUSD tokens.
Here is the Swap event:
amount0In: 172054109197
amount1In: 0
amount0Out: 0
amount1Out: 6357581284196624466
If we look at both (Zombie, BUSD) contracts, they both use 18 decimals
, i.e. we can think of these values as:
0.000000172
Zombie traded for 6.3575
BUSD
To understand the price impact, we need to look at the Zombie / BUSD LP reserves before and after the transaction.
Before:
reserve0: 1203765031
reserve1: 6402195517789394391
After:
reserve0: 173257874228
reserve1: 44614233592769925
We know (by looking at the LP contract) that reserve0
represents Zombie token and reserve1
represents BUSD token.
As we traded Zombie for BUSD, we have to add the amount being traded in to the first reserve and deduct the amount being traded for from the second reserve, i.e.
reserve0: 1203765031+172054109197=173257874228
reserve1: 6402195517789394391+6357581284196624466=44614233592769925
This is how the Swap affected the reserves and therefore the price ratio.
TL;DR - Final formula is at the end. It would be great if someone can verify that I didn't make any mistakes.
If we exchange token x
for token y
, as per the constant product formula:
x * y = k
Let a
be the amount of x
we are exchanging to get b
amount of y
. Therefore:
(x + a) * (y - b) = k
The execution price of the trade, by definition, is just b / a
.
If our target execution price is e
, then b / a = e
=> b = ea
Therefore:
(x + a) * (y - ea) = k
But x * y
is also equal to k
, therefore:
x * y = (x + a) * (y - ea)
Now we can just rewrite the equation to get a
in terms of the other variables.
x * y = x * (y - ea) + a * (y - ea)
xy = xy - eax + ay - ea^2
- eax + ay - ea^2 = 0
-ea^2 + a(y - ex) = 0
We know a
is not zero, else the price would be undefined which is not possible. Because a
is not zero, we can safely divide across by a
:
-ea + y - ex = 0
Now a few more slight adjustments:
ea = y - ex
a = (y - ex) / e
a = (y / e) - x
So this is the final formula:
a = (y / e) - x
where a
is the maximum amount we can trade to get an execution price of e
or better, and x
and y
are the number of input and output tokens in the pool before the trade respectively.
TEST CASE - where 1 ETH is worth 2,000 USD
x = 100 ETH
y = 200,000 USD
e = 1,950
a = (y / e) - x
= (200,000 / 1,950) - 100
= 2.564 ETH
Best Answer
Let's assume a pool with X and Y assets (order is important). If the current price of X is lower than the target price, then some X assets must be bought from the pool. Otherwise if the price of X is higher than the target price, some Y assets must be bought from the pool.
The basic idea is to compute the amounts of X or Y to buy, with an algorithm similar to the swap algorithm in the Fig. 4, Uniswap v3 whitepaper.
Basic idea:
delta
variable to zero.delta
, and break the loop.delta
(find that by looking at its liquidity), and switch to the next tick range.The main logic is symmetrical for buying X and buying Y, but implementation details are slightly different depending on which direction the price must move.
Let's assume that these variables are already initialized:
contract
- the pool's contractsCurrentPrice
- sqrt of the current pricesPriceTarget
- sqrt of the target priceliquidity
- the liquidity in the current tick range of the pooltickLower
,tickUpper
- the min and max ticks of the current tick rangesPriceUpper
,sPriceUpper
- square roots of prices corresponding to the min and max ticks of the current rangetickSpacing
- the tick spacing in the pool.decimalsX
,decimalsY
- the number of decimals of the X and Y tokens, for printing the resultWhat follows is a proof-of-concept Python code.
Let's define some helper functions first:
The main code:
The main result is stored in the variable
deltaTokens
, as value with then can be used in theexactOutputSingle
function call.A caveat: the approach loops through all tick ranges until the target price is reached. This is fine in most situations, but this approach could be quite inefficient. Looking at the next initialized tick would be more efficient if the price difference is big and most of the potentially initialized ticks are not initialized. Another problem is that if there isn't enough liquidity in the pool, the loop would never stop, as there are no other stopping conditions at the moment.
Edit: for JavaScript and TypeScript apps, I suggest using the
SwapMath
library from the Uniswap v3 SDK as the basis for your code.