Permalink
Cannot retrieve contributors at this time
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
649 lines (525 sloc)
20.8 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# (c) Curve.Fi, 2020 | |
import ERC20m as ERC20m | |
import yERC20 as yERC20 | |
from vyper.interfaces import ERC20 | |
# Tether transfer-only ABI | |
contract USDT: | |
def transfer(_to: address, _value: uint256): modifying | |
def transferFrom(_from: address, _to: address, _value: uint256): modifying | |
# Curve interface | |
contract Curve: | |
def get_virtual_price() -> uint256: constant | |
# This can (and needs to) be changed at compile time | |
N_COINS: constant(int128) = ___N_COINS___ # <- change | |
ZERO256: constant(uint256) = 0 # This hack is really bad XXX | |
ZEROS: constant(uint256[N_COINS]) = ___N_ZEROS___ # <- change | |
USE_LENDING: constant(bool[N_COINS]) = ___USE_LENDING___ | |
USE_CURVED: constant(bool[N_COINS]) = ___USE_CURVED___ | |
# Flag "ERC20s" which don't return from transfer() and transferFrom() | |
TETHERED: constant(bool[N_COINS]) = ___TETHERED___ | |
FEE_DENOMINATOR: constant(uint256) = 10 ** 10 | |
LENDING_PRECISION: constant(uint256) = 10 ** 18 | |
PRECISION: constant(uint256) = 10 ** 18 # The precision to convert to | |
PRECISION_MUL: constant(uint256[N_COINS]) = ___PRECISION_MUL___ | |
# PRECISION_MUL: constant(uint256[N_COINS]) = [ | |
# PRECISION / convert(PRECISION, uint256), # DAI | |
# PRECISION / convert(10 ** 6, uint256), # USDC | |
# PRECISION / convert(10 ** 6, uint256)] # USDT | |
admin_actions_delay: constant(uint256) = 3 * 86400 | |
# Events | |
TokenExchange: event({buyer: indexed(address), sold_id: int128, tokens_sold: uint256, bought_id: int128, tokens_bought: uint256}) | |
TokenExchangeUnderlying: event({buyer: indexed(address), sold_id: int128, tokens_sold: uint256, bought_id: int128, tokens_bought: uint256}) | |
AddLiquidity: event({provider: indexed(address), token_amounts: uint256[N_COINS], fees: uint256[N_COINS], invariant: uint256, token_supply: uint256}) | |
RemoveLiquidity: event({provider: indexed(address), token_amounts: uint256[N_COINS], fees: uint256[N_COINS], token_supply: uint256}) | |
RemoveLiquidityImbalance: event({provider: indexed(address), token_amounts: uint256[N_COINS], fees: uint256[N_COINS], invariant: uint256, token_supply: uint256}) | |
CommitNewAdmin: event({deadline: indexed(timestamp), admin: indexed(address)}) | |
NewAdmin: event({admin: indexed(address)}) | |
CommitNewParameters: event({deadline: indexed(timestamp), A: uint256, fee: uint256, admin_fee: uint256}) | |
NewParameters: event({A: uint256, fee: uint256, admin_fee: uint256}) | |
coins: public(address[N_COINS]) | |
coins_rates: public(address[N_COINS]) | |
underlying_coins: public(address[N_COINS]) | |
balances: public(uint256[N_COINS]) | |
A: public(uint256) # 2 x amplification coefficient | |
fee: public(uint256) # fee * 1e10 | |
admin_fee: public(uint256) # admin_fee * 1e10 | |
max_admin_fee: constant(uint256) = 5 * 10 ** 9 | |
max_fee: constant(uint256) = 5 * 10 ** 9 | |
max_A: constant(uint256) = 10 ** 6 | |
owner: public(address) | |
token: ERC20m | |
admin_actions_deadline: public(timestamp) | |
transfer_ownership_deadline: public(timestamp) | |
future_A: public(uint256) | |
future_fee: public(uint256) | |
future_admin_fee: public(uint256) | |
future_owner: public(address) | |
kill_deadline: timestamp | |
kill_deadline_dt: constant(uint256) = 2 * 30 * 86400 | |
is_killed: bool | |
@public | |
def __init__(_coins: address[N_COINS], _underlying_coins: address[N_COINS], | |
_coins_rates: address[N_COINS], _pool_token: address, | |
_A: uint256, _fee: uint256): | |
""" | |
_coins: Addresses of ERC20 conracts of coins (c-tokens) involved | |
_underlying_coins: Addresses of plain coins (ERC20) | |
_pool_token: Address of the token representing LP share | |
_A: Amplification coefficient multiplied by n * (n - 1) | |
_fee: Fee to charge for exchanges | |
""" | |
for i in range(N_COINS): | |
assert _coins[i] != ZERO_ADDRESS | |
assert _underlying_coins[i] != ZERO_ADDRESS | |
self.balances[i] = 0 | |
self.coins = _coins | |
self.coins_rates = _coins_rates | |
self.underlying_coins = _underlying_coins | |
self.A = _A | |
self.fee = _fee | |
self.admin_fee = 0 | |
self.owner = msg.sender | |
self.kill_deadline = block.timestamp + kill_deadline_dt | |
self.is_killed = False | |
self.token = ERC20m(_pool_token) | |
@private | |
@constant | |
def _current_rates() -> uint256[N_COINS]: | |
result: uint256[N_COINS] = PRECISION_MUL | |
use_lending: bool[N_COINS] = USE_LENDING | |
use_curved: bool[N_COINS] = USE_CURVED | |
for i in range(N_COINS): | |
rate: uint256 = LENDING_PRECISION # Used with no lending | |
if use_lending[i]: | |
rate = yERC20(self.coins[i]).getPricePerFullShare() | |
if use_curved[i]: | |
rate = Curve(self.coins_rates[i]).get_virtual_price() | |
result[i] *= rate | |
return result | |
@private | |
@constant | |
def _xp(rates: uint256[N_COINS]) -> uint256[N_COINS]: | |
result: uint256[N_COINS] = rates | |
for i in range(N_COINS): | |
result[i] = result[i] * self.balances[i] / PRECISION | |
return result | |
@private | |
@constant | |
def _xp_mem(rates: uint256[N_COINS], _balances: uint256[N_COINS]) -> uint256[N_COINS]: | |
result: uint256[N_COINS] = rates | |
for i in range(N_COINS): | |
result[i] = result[i] * _balances[i] / PRECISION | |
return result | |
@private | |
@constant | |
def get_D(xp: uint256[N_COINS]) -> uint256: | |
S: uint256 = 0 | |
for _x in xp: | |
S += _x | |
if S == 0: | |
return 0 | |
Dprev: uint256 = 0 | |
D: uint256 = S | |
Ann: uint256 = self.A * N_COINS | |
for _i in range(255): | |
D_P: uint256 = 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: | |
break | |
else: | |
if Dprev - D <= 1: | |
break | |
return D | |
@private | |
@constant | |
def get_D_mem(rates: uint256[N_COINS], _balances: uint256[N_COINS]) -> uint256: | |
return self.get_D(self._xp_mem(rates, _balances)) | |
@public | |
@constant | |
def get_virtual_price() -> uint256: | |
""" | |
Returns portfolio virtual price (for calculating profit) | |
scaled up by 1e18 | |
""" | |
D: uint256 = self.get_D(self._xp(self._current_rates())) | |
# D is in the units similar to DAI (e.g. converted to precision 1e18) | |
# When balanced, D = n * x_u - total virtual value of the portfolio | |
token_supply: uint256 = self.token.totalSupply() | |
return D * PRECISION / token_supply | |
@public | |
@constant | |
def calc_token_amount(amounts: uint256[N_COINS], deposit: bool) -> uint256: | |
""" | |
Simplified method to calculate addition or reduction in token supply at | |
deposit or withdrawal without taking fees into account (but looking at | |
slippage). | |
Needed to prevent front-running, not for precise calculations! | |
""" | |
_balances: uint256[N_COINS] = self.balances | |
rates: uint256[N_COINS] = self._current_rates() | |
D0: uint256 = self.get_D_mem(rates, _balances) | |
for i in range(N_COINS): | |
if deposit: | |
_balances[i] += amounts[i] | |
else: | |
_balances[i] -= amounts[i] | |
D1: uint256 = self.get_D_mem(rates, _balances) | |
token_amount: uint256 = self.token.totalSupply() | |
diff: uint256 = 0 | |
if deposit: | |
diff = D1 - D0 | |
else: | |
diff = D0 - D1 | |
return diff * token_amount / D0 | |
@public | |
@nonreentrant('lock') | |
def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256): | |
# Amounts is amounts of c-tokens | |
assert not self.is_killed | |
tethered: bool[N_COINS] = TETHERED | |
use_lending: bool[N_COINS] = USE_LENDING | |
fees: uint256[N_COINS] = ZEROS | |
_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) | |
_admin_fee: uint256 = self.admin_fee | |
token_supply: uint256 = self.token.totalSupply() | |
rates: uint256[N_COINS] = self._current_rates() | |
# Initial invariant | |
D0: uint256 = 0 | |
old_balances: uint256[N_COINS] = self.balances | |
if token_supply > 0: | |
D0 = self.get_D_mem(rates, old_balances) | |
new_balances: uint256[N_COINS] = old_balances | |
for i in range(N_COINS): | |
if token_supply == 0: | |
assert amounts[i] > 0 | |
# balances store amounts of c-tokens | |
new_balances[i] = old_balances[i] + amounts[i] | |
# Invariant after change | |
D1: uint256 = self.get_D_mem(rates, new_balances) | |
assert D1 > D0 | |
# We need to recalculate the invariant accounting for fees | |
# to calculate fair user's share | |
D2: uint256 = 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: uint256 = D1 * old_balances[i] / D0 | |
difference: uint256 = 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 | |
self.balances[i] = new_balances[i] - (fees[i] * _admin_fee / FEE_DENOMINATOR) | |
new_balances[i] -= fees[i] | |
D2 = self.get_D_mem(rates, new_balances) | |
else: | |
self.balances = new_balances | |
# Calculate, how much pool tokens to mint | |
mint_amount: uint256 = 0 | |
if token_supply == 0: | |
mint_amount = D1 # Take the dust if there was any | |
else: | |
mint_amount = token_supply * (D2 - D0) / D0 | |
assert mint_amount >= min_mint_amount, "Slippage screwed you" | |
# Take coins from the sender | |
for i in range(N_COINS): | |
if tethered[i] and not use_lending[i]: | |
USDT(self.coins[i]).transferFrom(msg.sender, self, amounts[i]) | |
else: | |
assert_modifiable( | |
ERC20(self.coins[i]).transferFrom(msg.sender, self, amounts[i])) | |
# Mint pool tokens | |
self.token.mint(msg.sender, mint_amount) | |
log.AddLiquidity(msg.sender, amounts, fees, D1, token_supply + mint_amount) | |
@private | |
@constant | |
def get_y(i: int128, j: int128, x: uint256, _xp: uint256[N_COINS]) -> uint256: | |
# 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: uint256 = self.get_D(_xp) | |
c: uint256 = D | |
S_: uint256 = 0 | |
Ann: uint256 = self.A * N_COINS | |
_x: uint256 = 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: uint256 = S_ + D / Ann # - D | |
y_prev: uint256 = 0 | |
y: uint256 = 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 | |
@public | |
@constant | |
def get_dy(i: int128, j: int128, dx: uint256) -> uint256: | |
# dx and dy in c-units | |
rates: uint256[N_COINS] = self._current_rates() | |
xp: uint256[N_COINS] = self._xp(rates) | |
x: uint256 = xp[i] + (dx * rates[i] / PRECISION) | |
y: uint256 = self.get_y(i, j, x, xp) | |
dy: uint256 = (xp[j] - y) * PRECISION / rates[j] | |
_fee: uint256 = self.fee * dy / FEE_DENOMINATOR | |
return dy - _fee | |
@public | |
@constant | |
def get_dx(i: int128, j: int128, dy: uint256) -> uint256: | |
# dx and dy in c-units | |
rates: uint256[N_COINS] = self._current_rates() | |
xp: uint256[N_COINS] = self._xp(rates) | |
y: uint256 = xp[j] - (dy * FEE_DENOMINATOR / (FEE_DENOMINATOR - self.fee)) * rates[j] / PRECISION | |
x: uint256 = self.get_y(j, i, y, xp) | |
dx: uint256 = (x - xp[i]) * PRECISION / rates[i] | |
return dx | |
@public | |
@constant | |
def get_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256: | |
# dx and dy in underlying units | |
rates: uint256[N_COINS] = self._current_rates() | |
xp: uint256[N_COINS] = self._xp(rates) | |
precisions: uint256[N_COINS] = PRECISION_MUL | |
x: uint256 = xp[i] + dx * precisions[i] | |
y: uint256 = self.get_y(i, j, x, xp) | |
dy: uint256 = (xp[j] - y) / precisions[j] | |
_fee: uint256 = self.fee * dy / FEE_DENOMINATOR | |
return dy - _fee | |
@public | |
@constant | |
def get_dx_underlying(i: int128, j: int128, dy: uint256) -> uint256: | |
# dx and dy in underlying units | |
rates: uint256[N_COINS] = self._current_rates() | |
xp: uint256[N_COINS] = self._xp(rates) | |
precisions: uint256[N_COINS] = PRECISION_MUL | |
y: uint256 = xp[j] - (dy * FEE_DENOMINATOR / (FEE_DENOMINATOR - self.fee)) * precisions[j] | |
x: uint256 = self.get_y(j, i, y, xp) | |
dx: uint256 = (x - xp[i]) / precisions[i] | |
return dx | |
@private | |
def _exchange(i: int128, j: int128, dx: uint256, rates: uint256[N_COINS]) -> uint256: | |
assert not self.is_killed | |
# dx and dy are in c-tokens | |
xp: uint256[N_COINS] = self._xp(rates) | |
x: uint256 = xp[i] + dx * rates[i] / PRECISION | |
y: uint256 = self.get_y(i, j, x, xp) | |
dy: uint256 = xp[j] - y | |
dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR | |
dy_admin_fee: uint256 = dy_fee * self.admin_fee / FEE_DENOMINATOR | |
self.balances[i] = x * PRECISION / rates[i] | |
self.balances[j] = (y + (dy_fee - dy_admin_fee)) * PRECISION / rates[j] | |
_dy: uint256 = (dy - dy_fee) * PRECISION / rates[j] | |
return _dy | |
@public | |
@nonreentrant('lock') | |
def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256): | |
rates: uint256[N_COINS] = self._current_rates() | |
dy: uint256 = self._exchange(i, j, dx, rates) | |
assert dy >= min_dy, "Exchange resulted in fewer coins than expected" | |
tethered: bool[N_COINS] = TETHERED | |
use_lending: bool[N_COINS] = USE_LENDING | |
if tethered[i] and not use_lending[i]: | |
USDT(self.coins[i]).transferFrom(msg.sender, self, dx) | |
else: | |
assert_modifiable(ERC20(self.coins[i]).transferFrom(msg.sender, self, dx)) | |
if tethered[j] and not use_lending[j]: | |
USDT(self.coins[j]).transfer(msg.sender, dy) | |
else: | |
assert_modifiable(ERC20(self.coins[j]).transfer(msg.sender, dy)) | |
log.TokenExchange(msg.sender, i, dx, j, dy) | |
@public | |
@nonreentrant('lock') | |
def exchange_underlying(i: int128, j: int128, dx: uint256, min_dy: uint256): | |
rates: uint256[N_COINS] = self._current_rates() | |
precisions: uint256[N_COINS] = PRECISION_MUL | |
rate_i: uint256 = rates[i] / precisions[i] | |
rate_j: uint256 = rates[j] / precisions[j] | |
dx_: uint256 = dx * PRECISION / rate_i | |
dy_: uint256 = self._exchange(i, j, dx_, rates) | |
dy: uint256 = dy_ * rate_j / PRECISION | |
assert dy >= min_dy, "Exchange resulted in fewer coins than expected" | |
use_lending: bool[N_COINS] = USE_LENDING | |
tethered: bool[N_COINS] = TETHERED | |
if tethered[i]: | |
USDT(self.underlying_coins[i]).transferFrom(msg.sender, self, dx) | |
else: | |
assert_modifiable(ERC20(self.underlying_coins[i])\ | |
.transferFrom(msg.sender, self, dx)) | |
if use_lending[i]: | |
ERC20(self.underlying_coins[i]).approve(self.coins[i], dx) | |
yERC20(self.coins[i]).deposit(dx) | |
if use_lending[j]: | |
yERC20(self.coins[j]).withdraw(dy_) | |
# y-tokens calculate imprecisely - use all available | |
dy = ERC20(self.underlying_coins[j]).balanceOf(self) | |
assert dy >= min_dy, "Exchange resulted in fewer coins than expected" | |
if tethered[j]: | |
USDT(self.underlying_coins[j]).transfer(msg.sender, dy) | |
else: | |
assert_modifiable(ERC20(self.underlying_coins[j])\ | |
.transfer(msg.sender, dy)) | |
log.TokenExchangeUnderlying(msg.sender, i, dx, j, dy) | |
@public | |
@nonreentrant('lock') | |
def remove_liquidity(_amount: uint256, min_amounts: uint256[N_COINS]): | |
total_supply: uint256 = self.token.totalSupply() | |
amounts: uint256[N_COINS] = ZEROS | |
fees: uint256[N_COINS] = ZEROS | |
tethered: bool[N_COINS] = TETHERED | |
use_lending: bool[N_COINS] = USE_LENDING | |
for i in range(N_COINS): | |
value: uint256 = self.balances[i] * _amount / total_supply | |
assert value >= min_amounts[i], "Withdrawal resulted in fewer coins than expected" | |
self.balances[i] -= value | |
amounts[i] = value | |
if tethered[i] and not use_lending[i]: | |
USDT(self.coins[i]).transfer(msg.sender, value) | |
else: | |
assert_modifiable(ERC20(self.coins[i]).transfer( | |
msg.sender, value)) | |
self.token.burnFrom(msg.sender, _amount) # Will raise if not enough | |
log.RemoveLiquidity(msg.sender, amounts, fees, total_supply - _amount) | |
@public | |
@nonreentrant('lock') | |
def remove_liquidity_imbalance(amounts: uint256[N_COINS], max_burn_amount: uint256): | |
assert not self.is_killed | |
tethered: bool[N_COINS] = TETHERED | |
use_lending: bool[N_COINS] = USE_LENDING | |
token_supply: uint256 = self.token.totalSupply() | |
assert token_supply > 0 | |
_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) | |
_admin_fee: uint256 = self.admin_fee | |
rates: uint256[N_COINS] = self._current_rates() | |
old_balances: uint256[N_COINS] = self.balances | |
new_balances: uint256[N_COINS] = old_balances | |
D0: uint256 = self.get_D_mem(rates, old_balances) | |
for i in range(N_COINS): | |
new_balances[i] -= amounts[i] | |
D1: uint256 = self.get_D_mem(rates, new_balances) | |
fees: uint256[N_COINS] = ZEROS | |
for i in range(N_COINS): | |
ideal_balance: uint256 = D1 * old_balances[i] / D0 | |
difference: uint256 = 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 | |
self.balances[i] = new_balances[i] - (fees[i] * _admin_fee / FEE_DENOMINATOR) | |
new_balances[i] -= fees[i] | |
D2: uint256 = self.get_D_mem(rates, new_balances) | |
token_amount: uint256 = (D0 - D2) * token_supply / D0 | |
assert token_amount > 0 | |
assert token_amount <= max_burn_amount, "Slippage screwed you" | |
for i in range(N_COINS): | |
if tethered[i] and not use_lending[i]: | |
USDT(self.coins[i]).transfer(msg.sender, amounts[i]) | |
else: | |
assert_modifiable(ERC20(self.coins[i]).transfer(msg.sender, amounts[i])) | |
self.token.burnFrom(msg.sender, token_amount) # Will raise if not enough | |
log.RemoveLiquidityImbalance(msg.sender, amounts, fees, D1, token_supply - token_amount) | |
@public | |
@nonreentrant('lock') | |
def donate_dust(amounts: uint256[N_COINS]): | |
for i in range(N_COINS): | |
amount: uint256 = amounts[i] | |
if amount > 0: | |
assert_modifiable( | |
ERC20(self.coins[i]).transferFrom(msg.sender, self, amount)) | |
self.balances[i] += amount | |
### Admin functions ### | |
@public | |
def commit_new_parameters(amplification: uint256, | |
new_fee: uint256, | |
new_admin_fee: uint256): | |
assert msg.sender == self.owner | |
assert self.admin_actions_deadline == 0 | |
assert new_admin_fee <= max_admin_fee | |
assert new_fee <= max_fee | |
assert amplification <= max_A | |
_deadline: timestamp = block.timestamp + admin_actions_delay | |
self.admin_actions_deadline = _deadline | |
self.future_A = amplification | |
self.future_fee = new_fee | |
self.future_admin_fee = new_admin_fee | |
log.CommitNewParameters(_deadline, amplification, new_fee, new_admin_fee) | |
@public | |
def apply_new_parameters(): | |
assert msg.sender == self.owner | |
assert self.admin_actions_deadline <= block.timestamp\ | |
and self.admin_actions_deadline > 0 | |
self.admin_actions_deadline = 0 | |
_A: uint256 = self.future_A | |
_fee: uint256 = self.future_fee | |
_admin_fee: uint256 = self.future_admin_fee | |
self.A = _A | |
self.fee = _fee | |
self.admin_fee = _admin_fee | |
log.NewParameters(_A, _fee, _admin_fee) | |
@public | |
def revert_new_parameters(): | |
assert msg.sender == self.owner | |
self.admin_actions_deadline = 0 | |
@public | |
def commit_transfer_ownership(_owner: address): | |
assert msg.sender == self.owner | |
assert self.transfer_ownership_deadline == 0 | |
_deadline: timestamp = block.timestamp + admin_actions_delay | |
self.transfer_ownership_deadline = _deadline | |
self.future_owner = _owner | |
log.CommitNewAdmin(_deadline, _owner) | |
@public | |
def apply_transfer_ownership(): | |
assert msg.sender == self.owner | |
assert block.timestamp >= self.transfer_ownership_deadline\ | |
and self.transfer_ownership_deadline > 0 | |
self.transfer_ownership_deadline = 0 | |
_owner: address = self.future_owner | |
self.owner = _owner | |
log.NewAdmin(_owner) | |
@public | |
def revert_transfer_ownership(): | |
assert msg.sender == self.owner | |
self.transfer_ownership_deadline = 0 | |
@public | |
def withdraw_admin_fees(): | |
assert msg.sender == self.owner | |
_precisions: uint256[N_COINS] = PRECISION_MUL | |
tethered: bool[N_COINS] = TETHERED | |
use_lending: bool[N_COINS] = USE_LENDING | |
for i in range(N_COINS): | |
c: address = self.coins[i] | |
value: uint256 = ERC20(c).balanceOf(self) - self.balances[i] | |
if value > 0: | |
if tethered[i] and not use_lending[i]: | |
USDT(c).transfer(msg.sender, value) | |
else: | |
assert_modifiable(ERC20(c).transfer(msg.sender, value)) | |
@public | |
def kill_me(): | |
assert msg.sender == self.owner | |
assert self.kill_deadline > block.timestamp | |
self.is_killed = True | |
@public | |
def unkill_me(): | |
assert msg.sender == self.owner | |
self.is_killed = False |