forked from curvefi/curve-factory
-
Notifications
You must be signed in to change notification settings - Fork 0
/
DepositZapUSD.vy
308 lines (260 loc) · 12.1 KB
/
DepositZapUSD.vy
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
# @version 0.2.8
"""
@title "Zap" Depositer for permissionless USD metapools
@author Curve.Fi
@license Copyright (c) Curve.Fi, 2021 - all rights reserved
"""
interface ERC20:
def transfer(_receiver: address, _amount: uint256): nonpayable
def transferFrom(_sender: address, _receiver: address, _amount: uint256): nonpayable
def approve(_spender: address, _amount: uint256): nonpayable
def decimals() -> uint256: view
def balanceOf(_owner: address) -> uint256: view
interface CurveMeta:
def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256, _receiver: address) -> uint256: nonpayable
def remove_liquidity(_amount: uint256, min_amounts: uint256[N_COINS]) -> uint256[N_COINS]: nonpayable
def remove_liquidity_one_coin(_token_amount: uint256, i: int128, min_amount: uint256, _receiver: address) -> uint256: nonpayable
def remove_liquidity_imbalance(amounts: uint256[N_COINS], max_burn_amount: uint256) -> uint256: nonpayable
def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: view
def calc_token_amount(amounts: uint256[N_COINS], deposit: bool) -> uint256: view
def coins(i: uint256) -> address: view
interface CurveBase:
def add_liquidity(amounts: uint256[BASE_N_COINS], min_mint_amount: uint256): nonpayable
def remove_liquidity(_amount: uint256, min_amounts: uint256[BASE_N_COINS]): nonpayable
def remove_liquidity_one_coin(_token_amount: uint256, i: int128, min_amount: uint256): nonpayable
def remove_liquidity_imbalance(amounts: uint256[BASE_N_COINS], max_burn_amount: uint256): nonpayable
def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: view
def calc_token_amount(amounts: uint256[BASE_N_COINS], deposit: bool) -> uint256: view
def coins(i: uint256) -> address: view
def fee() -> uint256: view
N_COINS: constant(int128) = 2
MAX_COIN: constant(int128) = N_COINS-1
BASE_N_COINS: constant(int128) = 3
N_ALL_COINS: constant(int128) = N_COINS + BASE_N_COINS - 1
FEE_DENOMINATOR: constant(uint256) = 10 ** 10
FEE_IMPRECISION: constant(uint256) = 100 * 10 ** 8 # % of the fee
BASE_POOL: constant(address) = 0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7
BASE_LP_TOKEN: constant(address) = 0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490
BASE_COINS: constant(address[3]) = [
0x6B175474E89094C44Da98b954EedeAC495271d0F, # DAI
0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, # USDC
0xdAC17F958D2ee523a2206206994597C13D831ec7, # USDT
]
# coin -> pool -> is approved to transfer?
is_approved: HashMap[address, HashMap[address, bool]]
@external
def __init__():
"""
@notice Contract constructor
"""
base_coins: address[3] = BASE_COINS
for coin in base_coins:
ERC20(coin).approve(BASE_POOL, MAX_UINT256)
@external
def add_liquidity(
_pool: address,
_deposit_amounts: uint256[N_ALL_COINS],
_min_mint_amount: uint256,
_receiver: address = msg.sender,
) -> uint256:
"""
@notice Wrap underlying coins and deposit them into `_pool`
@param _pool Address of the pool to deposit into
@param _deposit_amounts List of amounts of underlying coins to deposit
@param _min_mint_amount Minimum amount of LP tokens to mint from the deposit
@param _receiver Address that receives the LP tokens
@return Amount of LP tokens received by depositing
"""
meta_amounts: uint256[N_COINS] = empty(uint256[N_COINS])
base_amounts: uint256[BASE_N_COINS] = empty(uint256[BASE_N_COINS])
deposit_base: bool = False
base_coins: address[3] = BASE_COINS
if _deposit_amounts[0] != 0:
coin: address = CurveMeta(_pool).coins(0)
if not self.is_approved[coin][_pool]:
ERC20(coin).approve(_pool, MAX_UINT256)
self.is_approved[coin][_pool] = True
ERC20(coin).transferFrom(msg.sender, self, _deposit_amounts[0])
meta_amounts[0] = _deposit_amounts[0]
for i in range(1, N_ALL_COINS):
amount: uint256 = _deposit_amounts[i]
if amount == 0:
continue
deposit_base = True
base_idx: uint256 = i - 1
coin: address = base_coins[base_idx]
ERC20(coin).transferFrom(msg.sender, self, amount)
# Handle potential Tether fees
if i == N_ALL_COINS - 1:
base_amounts[base_idx] = ERC20(coin).balanceOf(self)
else:
base_amounts[base_idx] = amount
# Deposit to the base pool
if deposit_base:
coin: address = BASE_LP_TOKEN
CurveBase(BASE_POOL).add_liquidity(base_amounts, 0)
meta_amounts[MAX_COIN] = ERC20(coin).balanceOf(self)
if not self.is_approved[coin][_pool]:
ERC20(coin).approve(_pool, MAX_UINT256)
self.is_approved[coin][_pool] = True
# Deposit to the meta pool
return CurveMeta(_pool).add_liquidity(meta_amounts, _min_mint_amount, _receiver)
@external
def remove_liquidity(
_pool: address,
_burn_amount: uint256,
_min_amounts: uint256[N_ALL_COINS],
_receiver: address = msg.sender
) -> uint256[N_ALL_COINS]:
"""
@notice Withdraw and unwrap coins from the pool
@dev Withdrawal amounts are based on current deposit ratios
@param _pool Address of the pool to deposit into
@param _burn_amount Quantity of LP tokens to burn in the withdrawal
@param _min_amounts Minimum amounts of underlying coins to receive
@param _receiver Address that receives the LP tokens
@return List of amounts of underlying coins that were withdrawn
"""
ERC20(_pool).transferFrom(msg.sender, self, _burn_amount)
min_amounts_base: uint256[BASE_N_COINS] = empty(uint256[BASE_N_COINS])
amounts: uint256[N_ALL_COINS] = empty(uint256[N_ALL_COINS])
# Withdraw from meta
meta_received: uint256[N_COINS] = CurveMeta(_pool).remove_liquidity(
_burn_amount,
[_min_amounts[0], convert(0, uint256)]
)
# Withdraw from base
for i in range(BASE_N_COINS):
min_amounts_base[i] = _min_amounts[MAX_COIN+i]
CurveBase(BASE_POOL).remove_liquidity(meta_received[1], min_amounts_base)
# Transfer all coins out
coin: address = CurveMeta(_pool).coins(0)
ERC20(coin).transfer(_receiver, meta_received[0])
amounts[0] = meta_received[0]
base_coins: address[BASE_N_COINS] = BASE_COINS
for i in range(1, N_ALL_COINS):
coin = base_coins[i-1]
amounts[i] = ERC20(coin).balanceOf(self)
ERC20(coin).transfer(_receiver, amounts[i])
return amounts
@external
def remove_liquidity_one_coin(
_pool: address,
_burn_amount: uint256,
i: int128,
_min_amount: uint256,
_receiver: address=msg.sender
) -> uint256:
"""
@notice Withdraw and unwrap a single coin from the pool
@param _pool Address of the pool to deposit into
@param _burn_amount Amount of LP tokens to burn in the withdrawal
@param i Index value of the coin to withdraw
@param _min_amount Minimum amount of underlying coin to receive
@param _receiver Address that receives the LP tokens
@return Amount of underlying coin received
"""
ERC20(_pool).transferFrom(msg.sender, self, _burn_amount)
coin_amount: uint256 = 0
if i == 0:
coin_amount = CurveMeta(_pool).remove_liquidity_one_coin(_burn_amount, i, _min_amount, _receiver)
else:
base_coins: address[BASE_N_COINS] = BASE_COINS
coin: address = base_coins[i - MAX_COIN]
# Withdraw a base pool coin
coin_amount = CurveMeta(_pool).remove_liquidity_one_coin(_burn_amount, MAX_COIN, 0, self)
CurveBase(BASE_POOL).remove_liquidity_one_coin(coin_amount, i-MAX_COIN, _min_amount)
coin_amount = ERC20(coin).balanceOf(self)
ERC20(coin).transfer(_receiver, coin_amount)
return coin_amount
@external
def remove_liquidity_imbalance(
_pool: address,
_amounts: uint256[N_ALL_COINS],
_max_burn_amount: uint256,
_receiver: address=msg.sender
) -> uint256:
"""
@notice Withdraw coins from the pool in an imbalanced amount
@param _pool Address of the pool to deposit into
@param _amounts List of amounts of underlying coins to withdraw
@param _max_burn_amount Maximum amount of LP token to burn in the withdrawal
@param _receiver Address that receives the LP tokens
@return Actual amount of the LP token burned in the withdrawal
"""
fee: uint256 = CurveBase(BASE_POOL).fee() * BASE_N_COINS / (4 * (BASE_N_COINS - 1))
fee += fee * FEE_IMPRECISION / FEE_DENOMINATOR # Overcharge to account for imprecision
# Transfer the LP token in
ERC20(_pool).transferFrom(msg.sender, self, _max_burn_amount)
withdraw_base: bool = False
amounts_base: uint256[BASE_N_COINS] = empty(uint256[BASE_N_COINS])
amounts_meta: uint256[N_COINS] = empty(uint256[N_COINS])
# determine amounts to withdraw from base pool
for i in range(BASE_N_COINS):
amount: uint256 = _amounts[MAX_COIN + i]
if amount != 0:
amounts_base[i] = amount
withdraw_base = True
# determine amounts to withdraw from metapool
amounts_meta[0] = _amounts[0]
if withdraw_base:
amounts_meta[MAX_COIN] = CurveBase(BASE_POOL).calc_token_amount(amounts_base, False)
amounts_meta[MAX_COIN] += amounts_meta[MAX_COIN] * fee / FEE_DENOMINATOR + 1
# withdraw from metapool and return the remaining LP tokens
burn_amount: uint256 = CurveMeta(_pool).remove_liquidity_imbalance(amounts_meta, _max_burn_amount)
ERC20(_pool).transfer(msg.sender, _max_burn_amount - burn_amount)
# withdraw from base pool
if withdraw_base:
CurveBase(BASE_POOL).remove_liquidity_imbalance(amounts_base, amounts_meta[MAX_COIN])
coin: address = BASE_LP_TOKEN
leftover: uint256 = ERC20(coin).balanceOf(self)
if leftover > 0:
# if some base pool LP tokens remain, re-deposit them for the caller
if not self.is_approved[coin][_pool]:
ERC20(coin).approve(_pool, MAX_UINT256)
self.is_approved[coin][_pool] = True
burn_amount -= CurveMeta(_pool).add_liquidity([convert(0, uint256), leftover], 0, msg.sender)
# transfer withdrawn base pool tokens to caller
base_coins: address[BASE_N_COINS] = BASE_COINS
for i in range(BASE_N_COINS):
ERC20(base_coins[i]).transfer(_receiver, amounts_base[i])
# transfer withdrawn metapool tokens to caller
if _amounts[0] > 0:
coin: address = CurveMeta(_pool).coins(0)
ERC20(coin).transfer(_receiver, _amounts[0])
return burn_amount
@view
@external
def calc_withdraw_one_coin(_pool: address, _token_amount: uint256, i: int128) -> uint256:
"""
@notice Calculate the amount received when withdrawing and unwrapping a single coin
@param _pool Address of the pool to deposit into
@param _token_amount Amount of LP tokens to burn in the withdrawal
@param i Index value of the underlying coin to withdraw
@return Amount of coin received
"""
if i < MAX_COIN:
return CurveMeta(_pool).calc_withdraw_one_coin(_token_amount, i)
else:
_base_tokens: uint256 = CurveMeta(_pool).calc_withdraw_one_coin(_token_amount, MAX_COIN)
return CurveBase(BASE_POOL).calc_withdraw_one_coin(_base_tokens, i-MAX_COIN)
@view
@external
def calc_token_amount(_pool: address, _amounts: uint256[N_ALL_COINS], _is_deposit: bool) -> uint256:
"""
@notice Calculate addition or reduction in token supply from a deposit or withdrawal
@dev This calculation accounts for slippage, but not fees.
Needed to prevent front-running, not for precise calculations!
@param _pool Address of the pool to deposit into
@param _amounts Amount of each underlying coin being deposited
@param _is_deposit set True for deposits, False for withdrawals
@return Expected amount of LP tokens received
"""
meta_amounts: uint256[N_COINS] = empty(uint256[N_COINS])
base_amounts: uint256[BASE_N_COINS] = empty(uint256[BASE_N_COINS])
meta_amounts[0] = _amounts[0]
for i in range(BASE_N_COINS):
base_amounts[i] = _amounts[i + MAX_COIN]
base_tokens: uint256 = CurveBase(BASE_POOL).calc_token_amount(base_amounts, _is_deposit)
meta_amounts[MAX_COIN] = base_tokens
return CurveMeta(_pool).calc_token_amount(meta_amounts, _is_deposit)