spotPriceAfterSwapfunctions for pools. This linearization does not work well with stable pools or other arbitrary pools.
tokenIn: the address of the token being sold by user
tokenOut: the address of the token being bought by user
'swapExactOut'>: the type of swap being done. The user can select the amount they want to sell (in
tokenIn) or the amount they want to buy (in
targetAmountSwap: the amount the user wants to buy OR sell, depending on
targetAmountSwapis the amount the user wants to sell. Conversely, if
targetAmountSwapis the amount the user wants to buy.
pools: is a dictionary that is loaded from subgraph with all the pools available on Balancer
paths: a path is a sequence of pools that enable the trade
tokenOutto happen. Paths can contain one hop (direct trades) or two hops. For example a trade of DAI for BAL could have direct trades with pools that contain both DAI and BAL and also multihop paths, for example DAI→WETH→BAL or DAI→USDC→BAL.
'BPT->token'>: This is something new to SOR v2. Join/Exit pools with a single token are considered by SOR as a swap just like a swap between two underlying tokens in a pool. This allows for cool use cases like swapping DAI for GUSD in two hops if there is a big stable pool (say pool A) with <DAI, USDT, USDC> and another stable pool with <BPTA, GUSD>, BPTA being the BPT of pool A. So if a hop involves joining a pool, i.e. DAI for BPTA, this
maxPools: is the maximum number of swaps the final solution of SOR can have. A multihop has two swaps, for example.
returnToken: is the token whose amount is unknown for the swap and SOR is calculating. Simply put, if
'swapExactIn'the user is inputting how much they want to sell (
tokenIn) and will get from SOR a
returnAmountwhich is how much they will get back in
tokenOut. Conversely, if
'swapExactOut', then the user is inputting how much they want to buy (
tokenOut) and will get from SOR a
returnAmountwhich is how much they have to pay in
costReturnToken: is how much an additional swap would add in gas costs in terms of
returnToken. For example, for a
'swapExactIn'swap of DAI for BAL,
costReturnTokencould be 0.1 BAL (BAL is the
returnToken). This is simply calculated by:
initialNumPaths) of the most liquid pools we need to be able to trade the
targetAmountSwapis much lower than the liquidity available in the largest pools so this is usually 1. Start with next step with
b = initialNumPathswhere b is the number of paths being considered.
bpaths and the best distribution of
targetAmountSwapwhich is a list called
bamounts to be swapped in each of the
bpaths. The sum of all amounts in
targetAmountSwap. This step also calculates the
returnAmountthe suggested swaps return.
returnAmountof previous step with
bestReturnAmount(which is the best return amount so far) considering the additional costs of adding an extra path (
costReturnToken * nSwapsInPath). If the
returnAmountis better than
band return to step 2). If not the algorithm has found the final solution. IMPORTANT: notice that if
'swapExactIn'having a better
returnAmountmeans that it is higher than
bestReturnAmount, i.e. the user is getting more
tokenOut. Conversely, if
'swapExactOut'having a better
returnAmountmeans that it is lower than
bestReturnAmount, i.e. the user is paying less
tokenInfor the desired amount (
tokenOutthey want to buy.
swapAmountslist based on the previous
bestSwapAmountsby adding another element to the list which is defined as
targetAmountSwap/band scale down all the elements of
1-1/bso that the sum still is equal to
targetAmountSwap. For example, if the previous
[150, 30]then the new initial
[100, 20, 60]
swapAmountsfrom above, find the best paths using
getBestPathIds()(see detailed information about this function in appendix below).
swapAmountsthat maximize the return for the given
swapAmountsand check if function
getBestPathIds()returns the same
bestPathIds. If yes, this means that we have converged to the best paths and swapAmounts and step 2) is finalized. If not, then go to step 2.3) again
swapAmountsthat will maximize the returns given an initial guess of
swapAmountsand the list of paths that should be used (
bestPathIds). This is all done in function
iterateSwapAmounts()which will be described below in its different steps.
iterateSwapAmountsApproximation()and which does a first iteration in approximating
swapAmountsto the optimal ones (this function is explained in detail in the appendix below). This function returns
priceswhich are the prices after swap for each of the paths considering their respective
swapAmountsas long as that
amountSwapis NOT at the limit of the path (i.e. equal to
path.limitAmount) AND is not zero.
pricesare the same within a given error margin. If this is true, then the objective of not leaving paths in a state that can be arbed has been achieved and the optimal distribution of swap amounts for these paths has been found. If prices are still not close enough to each other go back to step 2.3.1.
getBestPathIds()takes as inputs
swapAmountsand all available
pathsand is expected to find the paths that give the best return for the given
swapAmounts. The algorithm to do so is very simple: sort the
swapAmountsby descending order and starting with the largest
swapAmount, find the path that has the best return. Select this path and remove it from the list of available paths as we do not want to use the same path twice (as using it the first time would affect its price and slippage). The continue for each subsequent
swapAmountuntil we chose a path for each
iterateSwapAmountsApproximation()takes as inputs
bestPathIdsand is expected to find new
swapAmountsthat have spot prices after swap as close as possible.
targetSPwe need to use the derivatives of
targetSPit's easy to replace it in the equations above to find each
redistributeInputAmounts()does exactly that. Let's take a look at it into more detail below.
iterateSwapAmountsApproximation()above we end up with
targetAmountSwapas it should.
redistributeInputAmounts()might need to be calculated several times in a row because as the excesses are redistributed to paths that are viable, their swap amounts might go below zero or beyond the limit. We call
redistributeInputAmounts()iteratively until all swap amounts are above or equal to zero and below or equal to their path limit amounts.