-
Notifications
You must be signed in to change notification settings - Fork 2
/
ConstantProductAMM.sol
249 lines (192 loc) · 6.8 KB
/
ConstantProductAMM.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/**
* @title Constant Product AMM
* @dev A simple implementation of a constant product AMM
*/
contract CPAMM {
IERC20 public immutable token0;
IERC20 public immutable token1;
uint public reserve0;
uint public reserve1;
uint public totalSupply;
mapping(address => uint) public balanceOf;
constructor(address _token0, address _token1) {
token0 = IERC20(_token0);
token1 = IERC20(_token1);
}
function _mint(address _to, uint _amount) private {
balanceOf[_to] += _amount;
totalSupply += _amount;
}
function _burn(address _from, uint _amount) private {
balanceOf[_from] -= _amount;
totalSupply -= _amount;
}
function _update(uint _reserve0, uint _reserve1) private {
reserve0 = _reserve0;
reserve1 = _reserve1;
}
function swap(address _tokenIn, uint _amountIn) external returns (uint amountOut) {
require(
_tokenIn == address(token0) || _tokenIn == address(token1),
"invalid token"
);
require(_amountIn > 0, "amount in = 0");
bool isToken0 = _tokenIn == address(token0);
(IERC20 tokenIn, IERC20 tokenOut, uint reserveIn, uint reserveOut) = isToken0
? (token0, token1, reserve0, reserve1)
: (token1, token0, reserve1, reserve0);
tokenIn.transferFrom(msg.sender, address(this), _amountIn);
/*
How much dy for dx?
xy = k
(x + dx)(y - dy) = k
y - dy = k / (x + dx)
y - k / (x + dx) = dy
y - xy / (x + dx) = dy
(yx + ydx - xy) / (x + dx) = dy
ydx / (x + dx) = dy
*/
// 0.3% fee
uint amountInWithFee = (_amountIn * 997) / 1000;
amountOut = (reserveOut * amountInWithFee) / (reserveIn + amountInWithFee);
tokenOut.transfer(msg.sender, amountOut);
_update(token0.balanceOf(address(this)), token1.balanceOf(address(this)));
}
function addLiquidity(uint _amount0, uint _amount1) external returns (uint shares) {
token0.transferFrom(msg.sender, address(this), _amount0);
token1.transferFrom(msg.sender, address(this), _amount1);
/*
How much dx, dy to add?
xy = k
(x + dx)(y + dy) = k'
No price change, before and after adding liquidity
x / y = (x + dx) / (y + dy)
x(y + dy) = y(x + dx)
x * dy = y * dx
x / y = dx / dy
dy = y / x * dx
*/
if (reserve0 > 0 || reserve1 > 0) {
require(reserve0 * _amount1 == reserve1 * _amount0, "x / y != dx / dy");
}
/*
How much shares to mint?
f(x, y) = value of liquidity
We will define f(x, y) = sqrt(xy)
L0 = f(x, y)
L1 = f(x + dx, y + dy)
T = total shares
s = shares to mint
Total shares should increase proportional to increase in liquidity
L1 / L0 = (T + s) / T
L1 * T = L0 * (T + s)
(L1 - L0) * T / L0 = s
*/
/*
Claim
(L1 - L0) / L0 = dx / x = dy / y
Proof
--- Equation 1 ---
(L1 - L0) / L0 = (sqrt((x + dx)(y + dy)) - sqrt(xy)) / sqrt(xy)
dx / dy = x / y so replace dy = dx * y / x
--- Equation 2 ---
Equation 1 = (sqrt(xy + 2ydx + dx^2 * y / x) - sqrt(xy)) / sqrt(xy)
Multiply by sqrt(x) / sqrt(x)
Equation 2 = (sqrt(x^2y + 2xydx + dx^2 * y) - sqrt(x^2y)) / sqrt(x^2y)
= (sqrt(y)(sqrt(x^2 + 2xdx + dx^2) - sqrt(x^2)) / (sqrt(y)sqrt(x^2))
sqrt(y) on top and bottom cancels out
--- Equation 3 ---
Equation 2 = (sqrt(x^2 + 2xdx + dx^2) - sqrt(x^2)) / (sqrt(x^2)
= (sqrt((x + dx)^2) - sqrt(x^2)) / sqrt(x^2)
= ((x + dx) - x) / x
= dx / x
Since dx / dy = x / y,
dx / x = dy / y
Finally
(L1 - L0) / L0 = dx / x = dy / y
*/
if (totalSupply == 0) {
shares = _sqrt(_amount0 * _amount1);
} else {
shares = _min(
(_amount0 * totalSupply) / reserve0,
(_amount1 * totalSupply) / reserve1
);
}
require(shares > 0, "shares = 0");
_mint(msg.sender, shares);
_update(token0.balanceOf(address(this)), token1.balanceOf(address(this)));
}
function removeLiquidity(
uint _shares
) external returns (uint amount0, uint amount1) {
/*
Claim
dx, dy = amount of liquidity to remove
dx = s / T * x
dy = s / T * y
Proof
Let's find dx, dy such that
v / L = s / T
where
v = f(dx, dy) = sqrt(dxdy)
L = total liquidity = sqrt(xy)
s = shares
T = total supply
--- Equation 1 ---
v = s / T * L
sqrt(dxdy) = s / T * sqrt(xy)
Amount of liquidity to remove must not change price so
dx / dy = x / y
replace dy = dx * y / x
sqrt(dxdy) = sqrt(dx * dx * y / x) = dx * sqrt(y / x)
Divide both sides of Equation 1 with sqrt(y / x)
dx = s / T * sqrt(xy) / sqrt(y / x)
= s / T * sqrt(x^2) = s / T * x
Likewise
dy = s / T * y
*/
// bal0 >= reserve0
// bal1 >= reserve1
uint bal0 = token0.balanceOf(address(this));
uint bal1 = token1.balanceOf(address(this));
amount0 = (_shares * bal0) / totalSupply;
amount1 = (_shares * bal1) / totalSupply;
require(amount0 > 0 && amount1 > 0, "amount0 or amount1 = 0");
_burn(msg.sender, _shares);
_update(bal0 - amount0, bal1 - amount1);
token0.transfer(msg.sender, amount0);
token1.transfer(msg.sender, amount1);
}
function _sqrt(uint y) private pure returns (uint z) {
if (y > 3) {
z = y;
uint x = y / 2 + 1;
while (x < z) {
z = x;
x = (y / x + x) / 2;
}
} else if (y != 0) {
z = 1;
}
}
function _min(uint x, uint y) private pure returns (uint) {
return x <= y ? x : y;
}
}
interface IERC20 {
function totalSupply() external view returns (uint);
function balanceOf(address account) external view returns (uint);
function transfer(address recipient, uint amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint);
function approve(address spender, uint amount) external returns (bool);
function transferFrom(
address sender,
address recipient,
uint amount
) external returns (bool);
event Transfer(address indexed from, address indexed to, uint amount);
event Approval(address indexed owner, address indexed spender, uint amount);
}