-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathsolver.py
405 lines (345 loc) · 12.1 KB
/
solver.py
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
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
N_COINS = 3
PRECISION_MUL = [1, 1000000000000, 1000000000000]
FEE_DENOMINATOR = 10 ** 10
PRECISION = 10 ** 18
MAX_ADMIN_FEE = 10 * 10 ** 9
MAX_FEE = 5 * 10 ** 9
MAX_A = 10 ** 6
MAX_A_CHANGE = 10
A_PRECISION = 100
amp = 2000
ADMIN_ACTIONS_DELAY = 3 * 86400
MIN_RAMP_TIME = 86400
def get_correct_D(xp, amp):
'''
xp = Balances in underlying tokens with increased precision = TokensPrecision unit
'''
S = 0
for _x in xp:
S += _x
if S == 0:
return 0
Dprev = 0
D = S
Ann = amp * N_COINS
for _i in range(255):
D_P = D
for _x in xp:
D_P = D_P * D / (_x * N_COINS + 1) # +1 is to prevent /0
Dprev = D
D = (Ann * S + D_P * N_COINS) * D / ((Ann - 1) * D + (N_COINS + 1) * D_P)
# Equality with the precision of 1
if D > Dprev:
if D - Dprev <= 0.000001:
return D
else:
if Dprev - D <= 0.00001:
return D
# convergence typically occurs in 4 rounds or less, this should be unreachable!
# if it does happen the pool is borked and LPs can withdraw via `remove_liquidity`
return D
def get_D(xp, amp):
'''
xp = Balances in underlying tokens with increased precision = TokensPrecision unit
'''
S = 0
for _x in xp:
S += _x
if S == 0:
return 0
Dprev = 0
D = S
Ann = amp * N_COINS
for _i in range(255):
D_P = D
for _x in xp:
D_P = D_P * D // (_x * N_COINS + 1) # +1 is to prevent /0
Dprev = D
D = (Ann * S + D_P * N_COINS) * D // ((Ann - 1) * D + (N_COINS + 1) * D_P)
# Equality with the precision of 1
if D > Dprev:
if D - Dprev <= 1:
return D
else:
if Dprev - D <= 1:
return D
# convergence typically occurs in 4 rounds or less, this should be unreachable!
# if it does happen the pool is borked and LPs can withdraw via `remove_liquidity`
return D
def get_I(xp, amp):
S = 0
for _x in xp:
S += _x
if S == 0:
return 0
Dprev = 0
D = S
Ann = amp * N_COINS
for _i in range(255):
D_P = D
for _x in xp:
D_P = D_P * D // (_x * N_COINS + 1) # +1 is to prevent /0
Dprev = D
D = (Ann * S + D_P * N_COINS) * D // ((Ann - 1) * D + (N_COINS + 1) * D_P)
# Equality with the precision of 1
if D > Dprev:
if D - Dprev <= 1:
return _i
else:
if Dprev - D <= 1:
return _i
# convergence typically occurs in 4 rounds or less, this should be unreachable!
# if it does happen the pool is borked and LPs can withdraw via `remove_liquidity`
return 1000
def _xp(current_balances, rates):
'''
Converts cTokens into Tokens by multiplying by their respective rates, read from the Compound contract
'''
result = rates.copy()
for i in range(N_COINS):
result[i] = result[i] * current_balances[i] // PRECISION
return result
def _xp_mem(rates, _balances):
result= rates.copy()
for i in range(N_COINS):
result[i] = result[i] * _balances[i] // PRECISION
return result
def USDTpool(xp, amp, D):
'''
Return f(D), takes xp in TokenPrecision units
'''
#amp is already A*n**(n-1)
Ann = amp*N_COINS
S = 0
for _x in xp:
S += _x
if S == 0:
return 0
P = 1 #product of xi
for _x in xp:
P*= _x
return Ann*S + (1-Ann)*D - (D**(N_COINS+1))/((N_COINS**N_COINS)*P)
def get_y(i, j, x, _xp, amp):
'''
i = position of the token that we put in
j = position of the token that we want to get out
x = new balance after adding the amount of tokens we put in
xp = current balances of the pool
amp = scaled amplification factor (A*n**(n-1))
'''
# x in the input is converted to the same price/precision
assert (i != j) and (i >= 0) and (j >= 0) and (i < N_COINS) and (j < N_COINS)
D = get_D(_xp, amp)
c = D
S_ = 0
Ann = amp * N_COINS
_x = 0
for _i in range(N_COINS):
if _i == i:
_x = x
elif _i != j:
_x = _xp[_i]
else:
continue
S_ += _x
c = c * D // (_x * N_COINS)
c = c * D // (Ann * N_COINS)
b = S_ + D // Ann # - D
y_prev = 0
y = D
for _i in range(255):
y_prev = y
y = (y*y + c) // (2 * y + b - D)
# Equality with the precision of 1
if y > y_prev:
if y - y_prev <= 1:
break
else:
if y_prev - y <= 1:
break
return y
def get_dy(i, j, xp, dx, amp, rates, fee):
'''
Slightly modified from the contracts, get the amount out from the amount given in. Removed precision stuff for now. See line 370. https://github.com/curvefi/curve-contract/blob/master/contracts/pools/usdt/StableSwapUSDT.vy
i = position of the token that we put in
j = position of the token that we want to get out
x = new balance after adding the amount of tokens we put in
xp = current balances of the pool
amp = scaled amplification factor (A*n**(n-1))
'''
# dx and dy in c-units
#rates: uint256[N_COINS] = self._stored_rates()
xp = _xp(xp, rates)
x = xp[i] + dx * rates[i] // PRECISION
y = get_y(i, j, x, xp, amp)
dy = (xp[j] - y) * PRECISION // rates[j]
_fee = fee * dy // FEE_DENOMINATOR
return dy - _fee
def _exchange(i, j, xp, dx, rates, fee, amp):
'''
See Curve USDT pool contract line 425
i = index of token in
j = index of token out
xp = current balances
dx = amount in (in cTokens)
rates = current exchange rate of cTokens to Tokens
returns: dy = amount out (in cTokens)
'''
# dx and dy are in c-tokens
xp = _xp(xp.copy(), rates)
x = xp[i] + dx * rates[i] // PRECISION
y = get_y(i, j, x, xp, amp)
dy = xp[j] - y
dy_fee = dy * fee // FEE_DENOMINATOR
#Updates the balances, not needed to look for an appropriate dx
# dy_admin_fee = dy_fee * admin_fee // FEE_DENOMINATOR
#self.balances[i] = x * PRECISION / rates[i]
#self.balances[j] = (y + (dy_fee - dy_admin_fee)) * PRECISION / rates[j]
_dy = (dy - dy_fee) * PRECISION // rates[j]
return _dy
def _exchangeWithUpdate(i, j, dx, rates, fee, amp, balances, admin_fee):
'''
See Curve USDT pool contract line 425
i = index of token in
j = index of token out
xp = current balances
dx = amount in (in cTokens)
rates = current exchange rate of cTokens to Tokens
returns: dy = amount out (in cTokens)
'''
# dx and dy are in c-tokens
xp = _xp(balances.copy(), rates)
x = xp[i] + dx * rates[i] // PRECISION
y = get_y(i, j, x, xp, amp)
dy = xp[j] - y
dy_fee = dy * fee // FEE_DENOMINATOR
#Updates the balances, not needed to look for an appropriate dx
dy_admin_fee = dy_fee * admin_fee // FEE_DENOMINATOR
new_balances = balances.copy()
new_balances[i] = x * PRECISION // rates[i]
new_balances[j] = (y + (dy_fee - dy_admin_fee)) * PRECISION // rates[j]
if(new_balances[i] < 0):
raise 'traded too much'
if(new_balances[j] < 0):
raise 'traded too much'
_dy = (dy - dy_fee) * PRECISION // rates[j]
return (new_balances,_dy)
def exchange(i, j, dx, rates, fee, amp, balances, ourFunds, admin_fee):
(new_balances,dy) = _exchangeWithUpdate(i, j, dx, rates, fee, amp, balances, admin_fee)
ourFunds[i] -= dx
if(ourFunds[i]<0):
raise 'traded too much'
ourFunds[j] += dy
return (ourFunds,new_balances)
def remove_liquidity(_amount, min_amounts, balances, total_supply):
amounts = [0,0,0]
new_balances = balances.copy()
for i in range(N_COINS):
value = balances[i] * _amount // total_supply
if(value < min_amounts[i]):
raise Exception("Withdrawal resulted in fewer coins than expected")
new_balances[i] -= value
amounts[i] = value
return(new_balances,amounts,total_supply-_amount)
def get_D_mem(rates, _balances,amp):
return get_D(_xp_mem(rates, _balances),amp)
def add_liquidity(amounts, totalSupply, balances_c_tokens,fee, rates, admin_fee, amp):
# Amounts is amounts of c-tokens
fees= [0,0,0]
_fee = fee * N_COINS // (4 * (N_COINS - 1))
_admin_fee = admin_fee
token_supply =totalSupply
# Initial invariant
D0 = 0
balances = balances_c_tokens.copy()
old_balances= balances_c_tokens.copy()
if token_supply > 0:
D0 = get_D_mem(rates.copy(), old_balances.copy(), amp)
new_balances = old_balances.copy()
for i in range(N_COINS):
if token_supply == 0:
if(amounts[i] <= 0):
raise Exception("assert amounts[i] > 0")
# balances store amounts of c-tokens
new_balances[i] = old_balances[i] + amounts[i]
# Invariant after change
D1 = get_D_mem(rates.copy(), new_balances.copy(), amp)
if(D1 <= D0):
raise Exception("assert D1 > D0")
# We need to recalculate the invariant accounting for fees
# to calculate fair user's share
D2 = D1
if token_supply > 0:
# Only account for fees if we are not the first to deposit
for i in range(N_COINS):
ideal_balance = D1 * old_balances[i] // D0
difference = 0
if ideal_balance > new_balances[i]:
difference = ideal_balance - new_balances[i]
else:
difference = new_balances[i] - ideal_balance
fees[i] = _fee * difference // FEE_DENOMINATOR
balances[i] = new_balances[i] - fees[i] * _admin_fee // FEE_DENOMINATOR
new_balances[i] -= fees[i]
D2 = get_D_mem(rates.copy(), new_balances.copy(), amp)
else:
balances = new_balances.copy()
# Calculate, how much pool tokens to mint
mint_amount = 0
if token_supply == 0:
mint_amount = D1 # Take the dust if there was any
else:
mint_amount = token_supply * (D2 - D0) // D0
# Mint pool tokens
return(amounts,fees,D1,token_supply + mint_amount,mint_amount,balances)
def remove_liquidity_imbalance(amounts, token_supply, fee, admin_fee, rates, balances):
assert token_supply > 0
_fee = fee * N_COINS // (4 * (N_COINS - 1))
_admin_fee = admin_fee
old_balances = balances.copy()
new_balances = old_balances.copy()
D0 = get_D_mem(rates, old_balances, amp)
for i in range(N_COINS):
new_balances[i] -= amounts[i]
D1 = get_D_mem(rates, new_balances, amp)
for i in range(N_COINS):
ideal_balance= D1 * old_balances[i] // D0
difference = 0
if ideal_balance > new_balances[i]:
difference = ideal_balance - new_balances[i]
else:
difference = new_balances[i] - ideal_balance
fees = _fee * difference // FEE_DENOMINATOR
balances[i] = new_balances[i] - fees * _admin_fee // FEE_DENOMINATOR
new_balances[i] -= fees
D2 = get_D_mem(rates, new_balances, amp)
token_amount = (D0 - D2) * token_supply // D0
assert token_amount > 0
return(token_amount,balances)
def USDTpool(xp, amp, D):
'''
Return f(D), takes xp in TokenPrecision units
'''
#amp is already A*n**(n-1)
Ann = amp*N_COINS
S = 0
for _x in xp:
S += _x
if S == 0:
return 0
P = 1 #product of xi
for _x in xp:
P*= _x
return Ann*S + (1-Ann)*D - (D**(N_COINS+1))/((N_COINS**N_COINS)*P)
rates = [ 210699862363827219553716955 , 215918944268682000000000000 , 1000000000000000000000000000000 ]
def CTokensToTokensIncreasedPrecision(amount, index):
return rates[index] * amount// PRECISION
def isInvalidD(ctokens, amp):
attack_balances_tokens_precision = [CTokensToTokensIncreasedPrecision(ctokens[0], 0), CTokensToTokensIncreasedPrecision(ctokens[1], 1), CTokensToTokensIncreasedPrecision(ctokens[2], 2)]
D = get_D(attack_balances_tokens_precision, amp)
u = USDTpool(attack_balances_tokens_precision, amp, D)
if abs(u) > 0:
return True
return False
def get_virtual_price(balances,rates,token_supply,amp):
return get_D(_xp(balances,rates),amp) * PRECISION // token_supply