|
| 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