Skip to content

Commit dda6247

Browse files
authored
Adds a contract to compute liquidity value after arbing to an observed price (#49)
* add contracts and some basic tests * fix lint * move to library, make gas tests * fix pragma * add a get liquidity value function, refactor swap to price example * couple more tests * bump the uniswap lib dependency for cheaper sqrt and FullMath.mulDiv * use safemath for the mul * add a test for large numbers
1 parent cb65bae commit dda6247

8 files changed

+694
-29
lines changed
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
pragma solidity =0.6.6;
2+
3+
import '../libraries/UniswapV2LiquidityMathLibrary.sol';
4+
5+
contract ExampleComputeLiquidityValue {
6+
using SafeMath for uint256;
7+
8+
address public immutable factory;
9+
10+
constructor(address factory_) public {
11+
factory = factory_;
12+
}
13+
14+
// see UniswapV2LiquidityMathLibrary#getReservesAfterArbitrage
15+
function getReservesAfterArbitrage(
16+
address tokenA,
17+
address tokenB,
18+
uint256 truePriceTokenA,
19+
uint256 truePriceTokenB
20+
) external view returns (uint256 reserveA, uint256 reserveB) {
21+
return UniswapV2LiquidityMathLibrary.getReservesAfterArbitrage(
22+
factory,
23+
tokenA,
24+
tokenB,
25+
truePriceTokenA,
26+
truePriceTokenB
27+
);
28+
}
29+
30+
// see UniswapV2LiquidityMathLibrary#getLiquidityValue
31+
function getLiquidityValue(
32+
address tokenA,
33+
address tokenB,
34+
uint256 liquidityAmount
35+
) external view returns (
36+
uint256 tokenAAmount,
37+
uint256 tokenBAmount
38+
) {
39+
return UniswapV2LiquidityMathLibrary.getLiquidityValue(
40+
factory,
41+
tokenA,
42+
tokenB,
43+
liquidityAmount
44+
);
45+
}
46+
47+
// see UniswapV2LiquidityMathLibrary#getLiquidityValueAfterArbitrageToPrice
48+
function getLiquidityValueAfterArbitrageToPrice(
49+
address tokenA,
50+
address tokenB,
51+
uint256 truePriceTokenA,
52+
uint256 truePriceTokenB,
53+
uint256 liquidityAmount
54+
) external view returns (
55+
uint256 tokenAAmount,
56+
uint256 tokenBAmount
57+
) {
58+
return UniswapV2LiquidityMathLibrary.getLiquidityValueAfterArbitrageToPrice(
59+
factory,
60+
tokenA,
61+
tokenB,
62+
truePriceTokenA,
63+
truePriceTokenB,
64+
liquidityAmount
65+
);
66+
}
67+
68+
// test function to measure the gas cost of the above function
69+
function getGasCostOfGetLiquidityValueAfterArbitrageToPrice(
70+
address tokenA,
71+
address tokenB,
72+
uint256 truePriceTokenA,
73+
uint256 truePriceTokenB,
74+
uint256 liquidityAmount
75+
) external view returns (
76+
uint256
77+
) {
78+
uint gasBefore = gasleft();
79+
UniswapV2LiquidityMathLibrary.getLiquidityValueAfterArbitrageToPrice(
80+
factory,
81+
tokenA,
82+
tokenB,
83+
truePriceTokenA,
84+
truePriceTokenB,
85+
liquidityAmount
86+
);
87+
uint gasAfter = gasleft();
88+
return gasBefore - gasAfter;
89+
}
90+
}

contracts/examples/ExampleSwapToPrice.sol

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol';
44
import '@uniswap/lib/contracts/libraries/Babylonian.sol';
55
import '@uniswap/lib/contracts/libraries/TransferHelper.sol';
66

7+
import '../libraries/UniswapV2LiquidityMathLibrary.sol';
78
import '../interfaces/IERC20.sol';
89
import '../interfaces/IUniswapV2Router01.sol';
910
import '../libraries/SafeMath.sol';
@@ -20,27 +21,6 @@ contract ExampleSwapToPrice {
2021
router = router_;
2122
}
2223

23-
// computes the direction and magnitude of the profit-maximizing trade
24-
function computeProfitMaximizingTrade(
25-
uint256 truePriceTokenA,
26-
uint256 truePriceTokenB,
27-
uint256 reserveA,
28-
uint256 reserveB
29-
) pure public returns (bool aToB, uint256 amountIn) {
30-
aToB = reserveA.mul(truePriceTokenB) / reserveB < truePriceTokenA;
31-
32-
uint256 invariant = reserveA.mul(reserveB);
33-
34-
uint256 leftSide = Babylonian.sqrt(
35-
invariant.mul(aToB ? truePriceTokenA : truePriceTokenB).mul(1000) /
36-
uint256(aToB ? truePriceTokenB : truePriceTokenA).mul(997)
37-
);
38-
uint256 rightSide = (aToB ? reserveA.mul(1000) : reserveB.mul(1000)) / 997;
39-
40-
// compute the amount that must be sent to move the price to the profit-maximizing price
41-
amountIn = leftSide.sub(rightSide);
42-
}
43-
4424
// swaps an amount of either token such that the trade is profit-maximizing, given an external true price
4525
// true price is expressed in the ratio of token A to token B
4626
// caller must approve this contract to spend whichever token is intended to be swapped
@@ -63,12 +43,14 @@ contract ExampleSwapToPrice {
6343
uint256 amountIn;
6444
{
6545
(uint256 reserveA, uint256 reserveB) = UniswapV2Library.getReserves(factory, tokenA, tokenB);
66-
(aToB, amountIn) = computeProfitMaximizingTrade(
46+
(aToB, amountIn) = UniswapV2LiquidityMathLibrary.computeProfitMaximizingTrade(
6747
truePriceTokenA, truePriceTokenB,
6848
reserveA, reserveB
6949
);
7050
}
7151

52+
require(amountIn > 0, 'ExampleSwapToPrice: ZERO_AMOUNT_IN');
53+
7254
// spend up to the allowance of the token in
7355
uint256 maxSpend = aToB ? maxSpendTokenA : maxSpendTokenB;
7456
if (amountIn > maxSpend) {
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
pragma solidity >=0.5.0;
2+
3+
import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol';
4+
import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol';
5+
import '@uniswap/lib/contracts/libraries/Babylonian.sol';
6+
import '@uniswap/lib/contracts/libraries/FullMath.sol';
7+
8+
import './SafeMath.sol';
9+
import './UniswapV2Library.sol';
10+
11+
// library containing some math for dealing with the liquidity shares of a pair, e.g. computing their exact value
12+
// in terms of the underlying tokens
13+
library UniswapV2LiquidityMathLibrary {
14+
using SafeMath for uint256;
15+
16+
// computes the direction and magnitude of the profit-maximizing trade
17+
function computeProfitMaximizingTrade(
18+
uint256 truePriceTokenA,
19+
uint256 truePriceTokenB,
20+
uint256 reserveA,
21+
uint256 reserveB
22+
) pure internal returns (bool aToB, uint256 amountIn) {
23+
aToB = FullMath.mulDiv(reserveA, truePriceTokenB, reserveB) < truePriceTokenA;
24+
25+
uint256 invariant = reserveA.mul(reserveB);
26+
27+
uint256 leftSide = Babylonian.sqrt(
28+
FullMath.mulDiv(
29+
invariant.mul(1000),
30+
aToB ? truePriceTokenA : truePriceTokenB,
31+
(aToB ? truePriceTokenB : truePriceTokenA).mul(997)
32+
)
33+
);
34+
uint256 rightSide = (aToB ? reserveA.mul(1000) : reserveB.mul(1000)) / 997;
35+
36+
if (leftSide < rightSide) return (false, 0);
37+
38+
// compute the amount that must be sent to move the price to the profit-maximizing price
39+
amountIn = leftSide.sub(rightSide);
40+
}
41+
42+
// gets the reserves after an arbitrage moves the price to the profit-maximizing ratio given an externally observed true price
43+
function getReservesAfterArbitrage(
44+
address factory,
45+
address tokenA,
46+
address tokenB,
47+
uint256 truePriceTokenA,
48+
uint256 truePriceTokenB
49+
) view internal returns (uint256 reserveA, uint256 reserveB) {
50+
// first get reserves before the swap
51+
(reserveA, reserveB) = UniswapV2Library.getReserves(factory, tokenA, tokenB);
52+
53+
require(reserveA > 0 && reserveB > 0, 'UniswapV2ArbitrageLibrary: ZERO_PAIR_RESERVES');
54+
55+
// then compute how much to swap to arb to the true price
56+
(bool aToB, uint256 amountIn) = computeProfitMaximizingTrade(truePriceTokenA, truePriceTokenB, reserveA, reserveB);
57+
58+
if (amountIn == 0) {
59+
return (reserveA, reserveB);
60+
}
61+
62+
// now affect the trade to the reserves
63+
if (aToB) {
64+
uint amountOut = UniswapV2Library.getAmountOut(amountIn, reserveA, reserveB);
65+
reserveA += amountIn;
66+
reserveB -= amountOut;
67+
} else {
68+
uint amountOut = UniswapV2Library.getAmountOut(amountIn, reserveB, reserveA);
69+
reserveB += amountIn;
70+
reserveA -= amountOut;
71+
}
72+
}
73+
74+
// computes liquidity value given all the parameters of the pair
75+
function computeLiquidityValue(
76+
uint256 reservesA,
77+
uint256 reservesB,
78+
uint256 totalSupply,
79+
uint256 liquidityAmount,
80+
bool feeOn,
81+
uint kLast
82+
) internal pure returns (uint256 tokenAAmount, uint256 tokenBAmount) {
83+
if (feeOn && kLast > 0) {
84+
uint rootK = Babylonian.sqrt(reservesA.mul(reservesB));
85+
uint rootKLast = Babylonian.sqrt(kLast);
86+
if (rootK > rootKLast) {
87+
uint numerator1 = totalSupply;
88+
uint numerator2 = rootK.sub(rootKLast);
89+
uint denominator = rootK.mul(5).add(rootKLast);
90+
uint feeLiquidity = FullMath.mulDiv(numerator1, numerator2, denominator);
91+
totalSupply = totalSupply.add(feeLiquidity);
92+
}
93+
}
94+
return (reservesA.mul(liquidityAmount) / totalSupply, reservesB.mul(liquidityAmount) / totalSupply);
95+
}
96+
97+
// get all current parameters from the pair and compute value of a liquidity amount
98+
// **note this is subject to manipulation, e.g. sandwich attacks**. prefer passing a manipulation resistant price to
99+
// #getLiquidityValueAfterArbitrageToPrice
100+
function getLiquidityValue(
101+
address factory,
102+
address tokenA,
103+
address tokenB,
104+
uint256 liquidityAmount
105+
) internal view returns (uint256 tokenAAmount, uint256 tokenBAmount) {
106+
(uint256 reservesA, uint256 reservesB) = UniswapV2Library.getReserves(factory, tokenA, tokenB);
107+
IUniswapV2Pair pair = IUniswapV2Pair(UniswapV2Library.pairFor(factory, tokenA, tokenB));
108+
bool feeOn = IUniswapV2Factory(factory).feeTo() != address(0);
109+
uint kLast = feeOn ? pair.kLast() : 0;
110+
uint totalSupply = pair.totalSupply();
111+
return computeLiquidityValue(reservesA, reservesB, totalSupply, liquidityAmount, feeOn, kLast);
112+
}
113+
114+
// given two tokens, tokenA and tokenB, and their "true price", i.e. the observed ratio of value of token A to token B,
115+
// and a liquidity amount, returns the value of the liquidity in terms of tokenA and tokenB
116+
function getLiquidityValueAfterArbitrageToPrice(
117+
address factory,
118+
address tokenA,
119+
address tokenB,
120+
uint256 truePriceTokenA,
121+
uint256 truePriceTokenB,
122+
uint256 liquidityAmount
123+
) internal view returns (
124+
uint256 tokenAAmount,
125+
uint256 tokenBAmount
126+
) {
127+
bool feeOn = IUniswapV2Factory(factory).feeTo() != address(0);
128+
IUniswapV2Pair pair = IUniswapV2Pair(UniswapV2Library.pairFor(factory, tokenA, tokenB));
129+
uint kLast = feeOn ? pair.kLast() : 0;
130+
uint totalSupply = pair.totalSupply();
131+
132+
// this also checks that totalSupply > 0
133+
require(totalSupply >= liquidityAmount && liquidityAmount > 0, 'ComputeLiquidityValue: LIQUIDITY_AMOUNT');
134+
135+
(uint reservesA, uint reservesB) = getReservesAfterArbitrage(factory, tokenA, tokenB, truePriceTokenA, truePriceTokenB);
136+
137+
return computeLiquidityValue(reservesA, reservesB, totalSupply, liquidityAmount, feeOn, kLast);
138+
}
139+
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"contracts"
1616
],
1717
"dependencies": {
18-
"@uniswap/lib": "1.1.1",
18+
"@uniswap/lib": "4.0.1-alpha",
1919
"@uniswap/v2-core": "1.0.0"
2020
},
2121
"devDependencies": {

0 commit comments

Comments
 (0)