Skip to content

Commit 5679275

Browse files
authored
Merge pull request #154 from curvefi/feat/tricrypto-factory-burner
Add tricrypto factory burner
2 parents ef282e5 + 1c5f572 commit 5679275

File tree

2 files changed

+409
-0
lines changed

2 files changed

+409
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
# @version 0.3.7
2+
"""
3+
@title Tricrypto Factory LP Burner
4+
@notice Withdraws Tricrypto LP tokens
5+
"""
6+
7+
8+
interface ERC20:
9+
def transferFrom(_from: address, _to: address, _value: uint256) -> bool: nonpayable
10+
def balanceOf(_owner: address) -> uint256: view
11+
def decimals() -> uint256: view
12+
13+
interface CryptoSwap:
14+
def remove_liquidity_one_coin(token_amount: uint256, i: uint256, min_amount: uint256,
15+
use_eth: bool = False, receiver: address = msg.sender) -> uint256: nonpayable
16+
def coins(_i: uint256) -> address: view
17+
def price_oracle(_i: uint256) -> uint256: view
18+
def lp_price() -> uint256: view
19+
20+
interface PoolProxy:
21+
def burners(_coin: address) -> address: view
22+
23+
24+
interface LP:
25+
def minter() -> address: view
26+
27+
28+
BPS: constant(uint256) = 10000
29+
30+
slippage_of: public(HashMap[address, uint256])
31+
priority_of: public(HashMap[address, uint256])
32+
receiver_of: public(HashMap[address, address])
33+
is_token: public(HashMap[address, bool])
34+
35+
pool_proxy: public(address)
36+
slippage: public(uint256)
37+
receiver: public(address)
38+
recovery: public(address)
39+
40+
owner: public(address)
41+
future_owner: public(address)
42+
43+
44+
@external
45+
def __init__(_pool_proxy: address):
46+
"""
47+
@notice Contract constructor
48+
@param _pool_proxy Address of pool owner proxy
49+
"""
50+
self.pool_proxy = _pool_proxy
51+
self.receiver = _pool_proxy
52+
self.recovery = _pool_proxy
53+
self.owner = msg.sender
54+
55+
self.slippage = 100 # 1%
56+
self.priority_of[0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174] = 64 # USDC
57+
self.priority_of[0xE7a24EF0C5e95Ffb0f6684b813A78F2a3AD7D171] = 56 # am3crv
58+
self.priority_of[0xdAD97F7713Ae9437fa9249920eC8507e5FbB23d3] = 48 # tricrypto
59+
60+
61+
@internal
62+
def _burn(_coin: address, _amount: uint256):
63+
swap: address = _coin
64+
if self.is_token[_coin]:
65+
swap = LP(_coin).minter()
66+
coins: address[3] = [CryptoSwap(swap).coins(0), CryptoSwap(swap).coins(1), CryptoSwap(swap).coins(2)]
67+
priorities: uint256[3] = [self.priority_of[coins[0]], self.priority_of[coins[1]], self.priority_of[coins[2]]]
68+
assert priorities[0] > 0 or priorities[1] > 0 or priorities[2] > 0 # dev: unknown coins
69+
70+
i: uint256 = 0
71+
if priorities[1] > priorities[i]:
72+
i = 1
73+
if priorities[2] > priorities[i]:
74+
i = 2
75+
76+
min_amount: uint256 = _amount * CryptoSwap(swap).lp_price() / 10 ** 18
77+
if i > 0:
78+
min_amount = min_amount * 10 ** 18 / CryptoSwap(swap).price_oracle(i - 1)
79+
min_amount /= 10 ** (18 - ERC20(coins[i]).decimals())
80+
81+
slippage: uint256 = self.slippage_of[swap]
82+
if slippage == 0:
83+
slippage = self.slippage
84+
min_amount -= min_amount * slippage / BPS
85+
86+
receiver: address = self.receiver_of[coins[i]]
87+
if receiver == ZERO_ADDRESS:
88+
receiver = self.receiver
89+
CryptoSwap(swap).remove_liquidity_one_coin(_amount, i, min_amount, True, receiver)
90+
91+
92+
@external
93+
def burn(_coin: address) -> bool:
94+
"""
95+
@notice Convert `_coin` by removing liquidity
96+
@param _coin Address of the coin(swap) being converted
97+
@return bool success
98+
"""
99+
# transfer coins from caller
100+
amount: uint256 = ERC20(_coin).balanceOf(msg.sender)
101+
if amount != 0:
102+
ERC20(_coin).transferFrom(msg.sender, self, amount)
103+
104+
# get actual balance in case of pre-existing balance
105+
amount = ERC20(_coin).balanceOf(self)
106+
107+
if amount != 0:
108+
self._burn(_coin, amount)
109+
110+
return True
111+
112+
113+
@external
114+
def burn_amount(_coin: address, _amount_to_burn: uint256):
115+
"""
116+
@notice Burn a specific quantity of `_coin`
117+
@dev Useful when the total amount to burn is so large that it fails from slippage
118+
@param _coin Address of the coin being converted
119+
@param _amount_to_burn Amount of the coin to burn
120+
"""
121+
pool_proxy: address = self.pool_proxy
122+
amount: uint256 = ERC20(_coin).balanceOf(pool_proxy)
123+
if PoolProxy(pool_proxy).burners(_coin) == self and amount != 0:
124+
ERC20(_coin).transferFrom(pool_proxy, self, amount)
125+
126+
amount = ERC20(_coin).balanceOf(self)
127+
assert amount >= _amount_to_burn, "Insufficient balance"
128+
129+
self._burn(_coin, _amount_to_burn)
130+
131+
132+
@external
133+
def set_priority_of(_coin: address, _priority: uint256):
134+
"""
135+
@notice Set priority of a coin
136+
@dev Bigger value means higher priority
137+
@param _coin Token address
138+
@param _priority Token priority
139+
"""
140+
assert msg.sender == self.owner # dev: only owner
141+
self.priority_of[_coin] = _priority
142+
143+
144+
@external
145+
def set_many_priorities(_coins: address[8], _priorities: uint256[8]):
146+
"""
147+
@notice Set priority of many coins
148+
@dev Bigger value means higher priority
149+
@param _coins Token addresses
150+
@param _priorities Token priorities
151+
"""
152+
assert msg.sender == self.owner # dev: only owner
153+
for i in range(8):
154+
coin: address = _coins[i]
155+
if coin == ZERO_ADDRESS:
156+
break
157+
self.priority_of[coin] = _priorities[i]
158+
159+
160+
@external
161+
def set_slippage_of(_coin: address, _slippage: uint256):
162+
"""
163+
@notice Set custom slippage limit of a coin
164+
@dev Using self.slippage by default
165+
@param _coin Token address
166+
@param _slippage Slippage in bps for pool of token
167+
"""
168+
assert msg.sender == self.owner # dev: only owner
169+
assert _slippage <= BPS # dev: slippage too high
170+
self.slippage_of[_coin] = _slippage
171+
172+
173+
@external
174+
def set_many_slippages(_coins: address[8], _slippages: uint256[8]):
175+
"""
176+
@notice Set custom slippage limit of a coin
177+
@dev Using self.slippage by default
178+
@param _coins Token addresses
179+
@param _slippages Slippages in bps for each pool of token
180+
"""
181+
assert msg.sender == self.owner # dev: only owner
182+
for i in range(8):
183+
coin: address = _coins[i]
184+
if coin == ZERO_ADDRESS:
185+
break
186+
assert _slippages[i] <= BPS # dev: slippage too high
187+
self.slippage_of[coin] = _slippages[i]
188+
189+
190+
@external
191+
def set_slippage(_slippage: uint256):
192+
"""
193+
@notice Set default slippage parameter
194+
@param _slippage Slippage value in bps
195+
"""
196+
assert msg.sender == self.owner # dev: only owner
197+
assert _slippage <= BPS # dev: slippage too high
198+
self.slippage = _slippage
199+
200+
201+
@external
202+
def set_receiver_of(_coin: address, _receiver: address):
203+
"""
204+
@notice Set receiver of a coin
205+
@dev Using self.receiver by default
206+
@param _coin Token address
207+
@param _receiver Receiver of a token
208+
"""
209+
assert msg.sender == self.owner # dev: only owner
210+
self.receiver_of[_coin] = _receiver
211+
212+
213+
@external
214+
def set_many_receivers(_coins: address[8], _receivers: address[8]):
215+
"""
216+
@notice Set receivers of many coins
217+
@dev Using self.receiver by default
218+
@param _coins Token addresses
219+
@param _receivers Receivers of each token
220+
"""
221+
assert msg.sender == self.owner # dev: only owner
222+
for i in range(8):
223+
coin: address = _coins[i]
224+
if coin == ZERO_ADDRESS:
225+
break
226+
self.receiver_of[coin] = _receivers[i]
227+
228+
229+
@external
230+
def set_receiver(_receiver: address):
231+
"""
232+
@notice Set default receiver
233+
@param _receiver Address of default receiver
234+
"""
235+
assert msg.sender == self.owner # dev: only owner
236+
self.receiver = _receiver
237+
238+
239+
@external
240+
def set_token(_token: address, _is: bool):
241+
"""
242+
@notice Set LP tokens that are not pools
243+
@param _token Token address
244+
@param _is True if _token is LP
245+
"""
246+
assert msg.sender == self.owner # dev: only owner
247+
self.is_token[_token] = _is
248+
249+
250+
@external
251+
def recover_balance(_coin: address) -> bool:
252+
"""
253+
@notice Recover ERC20 tokens from this contract
254+
@dev Tokens are sent to the recovery address
255+
@param _coin Token address
256+
@return bool success
257+
"""
258+
assert msg.sender == self.owner # dev: only owner
259+
260+
amount: uint256 = ERC20(_coin).balanceOf(self)
261+
response: Bytes[32] = raw_call(
262+
_coin,
263+
_abi_encode(self.recovery, amount, method_id=method_id("transfer(address,uint256)")),
264+
max_outsize=32,
265+
)
266+
if len(response) != 0:
267+
assert convert(response, bool)
268+
269+
return True
270+
271+
272+
@external
273+
def set_recovery(_recovery: address) -> bool:
274+
"""
275+
@notice Set the token recovery address
276+
@param _recovery Token recovery address
277+
@return bool success
278+
"""
279+
assert msg.sender == self.owner # dev: only owner
280+
self.recovery = _recovery
281+
282+
return True
283+
284+
285+
@external
286+
def commit_transfer_ownership(_future_owner: address) -> bool:
287+
"""
288+
@notice Commit a transfer of ownership
289+
@dev Must be accepted by the new owner via `accept_transfer_ownership`
290+
@param _future_owner New owner address
291+
@return bool success
292+
"""
293+
assert msg.sender == self.owner # dev: only owner
294+
self.future_owner = _future_owner
295+
296+
return True
297+
298+
299+
@external
300+
def accept_transfer_ownership() -> bool:
301+
"""
302+
@notice Accept a transfer of ownership
303+
@return bool success
304+
"""
305+
assert msg.sender == self.future_owner # dev: only owner
306+
self.owner = msg.sender
307+
308+
return True

0 commit comments

Comments
 (0)