diff --git a/.gitignore b/.gitignore index fbdad4b7..971a72c4 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ reports/ .venv/ venv/ .vscode/ +.env \ No newline at end of file diff --git a/brownie-config.yaml b/brownie-config.yaml index 19e11340..89b5cf03 100644 --- a/brownie-config.yaml +++ b/brownie-config.yaml @@ -1,5 +1,10 @@ +dotenv: .env # default balance of 10,000 ether to simplify testing of ETH pools networks: + athens: + host: https://zetachain-testnet-archive.allthatnode.com:8545 # Example RPC URL + chainId: 7001 + development: cmd_settings: default_balance: 10000 diff --git a/contracts/pools/3pool/README.md b/contracts/pools/3pool/README.md deleted file mode 100644 index 7d7845a1..00000000 --- a/contracts/pools/3pool/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# curve-contract/contracts/pools/3pool - -[Curve tri-pool](https://www.curve.fi/3pool). - -## Contracts - -* [`StableSwap3Pool`](StableSwap3Pool.vy): Curve stablecoin AMM contract - -## Deployments - -* [`CurveContractV2`](../../tokens/CurveTokenV2.vy): [0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490](https://etherscan.io/address/0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490) -* [`LiquidityGauge`](../../gauges/LiquidityGauge.vy): [0xbFcF63294aD7105dEa65aA58F8AE5BE2D9d0952A](https://etherscan.io/address/0xbfcf63294ad7105dea65aa58f8ae5be2d9d0952a) -* [`StableSwap3Pool`](StableSwap3Pool.vy): [0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7](https://etherscan.io/address/0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7) - -## Stablecoins - -Curve tri-pool supports swaps between the following stablecoins: - -* `DAI`: [0x6b175474e89094c44da98b954eedeac495271d0f](https://etherscan.io/token/0x6b175474e89094c44da98b954eedeac495271d0f) -* `USDC`: [0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48](https://etherscan.io/token/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48) -* `USDT`: [0xdac17f958d2ee523a2206206994597c13d831ec7](https://etherscan.io/address/0xdac17f958d2ee523a2206206994597c13d831ec7) diff --git a/contracts/pools/3pool/StableSwap3Pool.vy b/contracts/pools/3pool/StableSwap3Pool.vy deleted file mode 100644 index 04d17018..00000000 --- a/contracts/pools/3pool/StableSwap3Pool.vy +++ /dev/null @@ -1,847 +0,0 @@ -# @version 0.2.4 -# (c) Curve.Fi, 2020 -# Pool for DAI/USDC/USDT - -from vyper.interfaces import ERC20 - -interface CurveToken: - def totalSupply() -> uint256: view - def mint(_to: address, _value: uint256) -> bool: nonpayable - def burnFrom(_to: address, _value: uint256) -> bool: nonpayable - - -# Events -event TokenExchange: - buyer: indexed(address) - sold_id: int128 - tokens_sold: uint256 - bought_id: int128 - tokens_bought: uint256 - - -event AddLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event RemoveLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - token_supply: uint256 - -event RemoveLiquidityOne: - provider: indexed(address) - token_amount: uint256 - coin_amount: uint256 - -event RemoveLiquidityImbalance: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event CommitNewAdmin: - deadline: indexed(uint256) - admin: indexed(address) - -event NewAdmin: - admin: indexed(address) - - -event CommitNewFee: - deadline: indexed(uint256) - fee: uint256 - admin_fee: uint256 - -event NewFee: - fee: uint256 - admin_fee: uint256 - -event RampA: - old_A: uint256 - new_A: uint256 - initial_time: uint256 - future_time: uint256 - -event StopRampA: - A: uint256 - t: uint256 - - -# This can (and needs to) be changed at compile time -N_COINS: constant(int128) = 3 # <- change - -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]) = [1, 1000000000000, 1000000000000] -RATES: constant(uint256[N_COINS]) = [1000000000000000000, 1000000000000000000000000000000, 1000000000000000000000000000000] -FEE_INDEX: constant(int128) = 2 # Which coin may potentially have fees (USDT) - -MAX_ADMIN_FEE: constant(uint256) = 10 * 10 ** 9 -MAX_FEE: constant(uint256) = 5 * 10 ** 9 -MAX_A: constant(uint256) = 10 ** 6 -MAX_A_CHANGE: constant(uint256) = 10 - -ADMIN_ACTIONS_DELAY: constant(uint256) = 3 * 86400 -MIN_RAMP_TIME: constant(uint256) = 86400 - -coins: public(address[N_COINS]) -balances: public(uint256[N_COINS]) -fee: public(uint256) # fee * 1e10 -admin_fee: public(uint256) # admin_fee * 1e10 - -owner: public(address) -token: CurveToken - -initial_A: public(uint256) -future_A: public(uint256) -initial_A_time: public(uint256) -future_A_time: public(uint256) - -admin_actions_deadline: public(uint256) -transfer_ownership_deadline: public(uint256) -future_fee: public(uint256) -future_admin_fee: public(uint256) -future_owner: public(address) - -is_killed: bool -kill_deadline: uint256 -KILL_DEADLINE_DT: constant(uint256) = 2 * 30 * 86400 - - -@external -def __init__( - _owner: address, - _coins: address[N_COINS], - _pool_token: address, - _A: uint256, - _fee: uint256, - _admin_fee: uint256 -): - """ - @notice Contract constructor - @param _owner Contract owner address - @param _coins Addresses of ERC20 conracts of coins - @param _pool_token Address of the token representing LP share - @param _A Amplification coefficient multiplied by n * (n - 1) - @param _fee Fee to charge for exchanges - @param _admin_fee Admin fee - """ - for i in range(N_COINS): - assert _coins[i] != ZERO_ADDRESS - self.coins = _coins - self.initial_A = _A - self.future_A = _A - self.fee = _fee - self.admin_fee = _admin_fee - self.owner = _owner - self.kill_deadline = block.timestamp + KILL_DEADLINE_DT - self.token = CurveToken(_pool_token) - - -@view -@internal -def _A() -> uint256: - """ - Handle ramping A up or down - """ - t1: uint256 = self.future_A_time - A1: uint256 = self.future_A - - if block.timestamp < t1: - A0: uint256 = self.initial_A - t0: uint256 = self.initial_A_time - # Expressions in uint256 cannot have negative numbers, thus "if" - if A1 > A0: - return A0 + (A1 - A0) * (block.timestamp - t0) / (t1 - t0) - else: - return A0 - (A0 - A1) * (block.timestamp - t0) / (t1 - t0) - - else: # when t1 == 0 or block.timestamp >= t1 - return A1 - - -@view -@external -def A() -> uint256: - return self._A() - - -@view -@internal -def _xp() -> uint256[N_COINS]: - result: uint256[N_COINS] = RATES - for i in range(N_COINS): - result[i] = result[i] * self.balances[i] / LENDING_PRECISION - return result - - -@pure -@internal -def _xp_mem(_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 - - -@pure -@internal -def get_D(xp: uint256[N_COINS], amp: uint256) -> uint256: - S: uint256 = 0 - for _x in xp: - S += _x - if S == 0: - return 0 - - Dprev: uint256 = 0 - D: uint256 = S - Ann: uint256 = amp * N_COINS - for _i in range(255): - D_P: uint256 = D - for _x in xp: - D_P = D_P * D / (_x * N_COINS) # If division by 0, this will be borked: only withdrawal will work. And that is good - 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 - - -@view -@internal -def get_D_mem(_balances: uint256[N_COINS], amp: uint256) -> uint256: - return self.get_D(self._xp_mem(_balances), amp) - - -@view -@external -def get_virtual_price() -> uint256: - """ - Returns portfolio virtual price (for calculating profit) - scaled up by 1e18 - """ - D: uint256 = self.get_D(self._xp(), self._A()) - # 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 - - -@view -@external -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 - amp: uint256 = self._A() - D0: uint256 = self.get_D_mem(_balances, amp) - for i in range(N_COINS): - if deposit: - _balances[i] += amounts[i] - else: - _balances[i] -= amounts[i] - D1: uint256 = self.get_D_mem(_balances, amp) - token_amount: uint256 = self.token.totalSupply() - diff: uint256 = 0 - if deposit: - diff = D1 - D0 - else: - diff = D0 - D1 - return diff * token_amount / D0 - - -@external -@nonreentrant('lock') -def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256): - assert not self.is_killed # dev: is killed - - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - _admin_fee: uint256 = self.admin_fee - amp: uint256 = self._A() - - token_supply: uint256 = self.token.totalSupply() - # Initial invariant - D0: uint256 = 0 - old_balances: uint256[N_COINS] = self.balances - if token_supply > 0: - D0 = self.get_D_mem(old_balances, amp) - new_balances: uint256[N_COINS] = old_balances - - for i in range(N_COINS): - in_amount: uint256 = amounts[i] - if token_supply == 0: - assert in_amount > 0 # dev: initial deposit requires all coins - in_coin: address = self.coins[i] - - # Take coins from the sender - if in_amount > 0: - if i == FEE_INDEX: - in_amount = ERC20(in_coin).balanceOf(self) - - # "safeTransferFrom" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - in_coin, - concat( - method_id("transferFrom(address,address,uint256)"), - convert(msg.sender, bytes32), - convert(self, bytes32), - convert(amounts[i], bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - - if i == FEE_INDEX: - in_amount = ERC20(in_coin).balanceOf(self) - in_amount - - new_balances[i] = old_balances[i] + in_amount - - # Invariant after change - D1: uint256 = self.get_D_mem(new_balances, amp) - 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(new_balances, amp) - 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" - - # Mint pool tokens - self.token.mint(msg.sender, mint_amount) - - log AddLiquidity(msg.sender, amounts, fees, D1, token_supply + mint_amount) - - -@view -@internal -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 # dev: same coin - assert j >= 0 # dev: j below zero - assert j < N_COINS # dev: j above N_COINS - - # should be unreachable, but good for safety - assert i >= 0 - assert i < N_COINS - - amp: uint256 = self._A() - D: uint256 = self.get_D(xp_, amp) - c: uint256 = D - S_: uint256 = 0 - Ann: uint256 = amp * 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 - - -@view -@external -def get_dy(i: int128, j: int128, dx: uint256) -> uint256: - # dx and dy in c-units - rates: uint256[N_COINS] = RATES - xp: uint256[N_COINS] = self._xp() - - x: uint256 = xp[i] + (dx * rates[i] / PRECISION) - y: uint256 = self.get_y(i, j, x, xp) - dy: uint256 = (xp[j] - y - 1) * PRECISION / rates[j] - _fee: uint256 = self.fee * dy / FEE_DENOMINATOR - return dy - _fee - - -@view -@external -def get_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256: - # dx and dy in underlying units - xp: uint256[N_COINS] = self._xp() - 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 - 1) / precisions[j] - _fee: uint256 = self.fee * dy / FEE_DENOMINATOR - return dy - _fee - - - -@external -@nonreentrant('lock') -def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256): - assert not self.is_killed # dev: is killed - rates: uint256[N_COINS] = RATES - - old_balances: uint256[N_COINS] = self.balances - xp: uint256[N_COINS] = self._xp_mem(old_balances) - - # Handling an unexpected charge of a fee on transfer (USDT, PAXG) - dx_w_fee: uint256 = dx - input_coin: address = self.coins[i] - - if i == FEE_INDEX: - dx_w_fee = ERC20(input_coin).balanceOf(self) - - # "safeTransferFrom" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - input_coin, - concat( - method_id("transferFrom(address,address,uint256)"), - convert(msg.sender, bytes32), - convert(self, bytes32), - convert(dx, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - - if i == FEE_INDEX: - dx_w_fee = ERC20(input_coin).balanceOf(self) - dx_w_fee - - x: uint256 = xp[i] + dx_w_fee * rates[i] / PRECISION - y: uint256 = self.get_y(i, j, x, xp) - - dy: uint256 = xp[j] - y - 1 # -1 just in case there were some rounding errors - dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR - - # Convert all to real units - dy = (dy - dy_fee) * PRECISION / rates[j] - assert dy >= min_dy, "Exchange resulted in fewer coins than expected" - - dy_admin_fee: uint256 = dy_fee * self.admin_fee / FEE_DENOMINATOR - dy_admin_fee = dy_admin_fee * PRECISION / rates[j] - - # Change balances exactly in same way as we change actual ERC20 coin amounts - self.balances[i] = old_balances[i] + dx_w_fee - # When rounding errors happen, we undercharge admin fee in favor of LP - self.balances[j] = old_balances[j] - dy - dy_admin_fee - - # "safeTransfer" which works for ERC20s which return bool or not - _response = raw_call( - self.coins[j], - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(dy, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - - log TokenExchange(msg.sender, i, dx, j, dy) - - -@external -@nonreentrant('lock') -def remove_liquidity(_amount: uint256, min_amounts: uint256[N_COINS]): - total_supply: uint256 = self.token.totalSupply() - amounts: uint256[N_COINS] = empty(uint256[N_COINS]) - fees: uint256[N_COINS] = empty(uint256[N_COINS]) # Fees are unused but we've got them historically in event - - 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 - - # "safeTransfer" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - self.coins[i], - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(value, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - - self.token.burnFrom(msg.sender, _amount) # dev: insufficient funds - - log RemoveLiquidity(msg.sender, amounts, fees, total_supply - _amount) - - -@external -@nonreentrant('lock') -def remove_liquidity_imbalance(amounts: uint256[N_COINS], max_burn_amount: uint256): - assert not self.is_killed # dev: is killed - - token_supply: uint256 = self.token.totalSupply() - assert token_supply != 0 # dev: zero total supply - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - _admin_fee: uint256 = self.admin_fee - amp: uint256 = self._A() - - old_balances: uint256[N_COINS] = self.balances - new_balances: uint256[N_COINS] = old_balances - D0: uint256 = self.get_D_mem(old_balances, amp) - for i in range(N_COINS): - new_balances[i] -= amounts[i] - D1: uint256 = self.get_D_mem(new_balances, amp) - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - 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(new_balances, amp) - - token_amount: uint256 = (D0 - D2) * token_supply / D0 - assert token_amount != 0 # dev: zero tokens burned - token_amount += 1 # In case of rounding errors - make it unfavorable for the "attacker" - assert token_amount <= max_burn_amount, "Slippage screwed you" - - self.token.burnFrom(msg.sender, token_amount) # dev: insufficient funds - for i in range(N_COINS): - if amounts[i] != 0: - - # "safeTransfer" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - self.coins[i], - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(amounts[i], bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - - log RemoveLiquidityImbalance(msg.sender, amounts, fees, D1, token_supply - token_amount) - - -@view -@internal -def get_y_D(A_: uint256, i: int128, xp: uint256[N_COINS], D: uint256) -> uint256: - """ - Calculate x[i] if one reduces D from being calculated for xp to D - - Done by solving quadratic equation iteratively. - x_1**2 + x1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) - x_1**2 + b*x_1 = c - - x_1 = (x_1**2 + c) / (2*x_1 + b) - """ - # x in the input is converted to the same price/precision - - assert i >= 0 # dev: i below zero - assert i < N_COINS # dev: i above N_COINS - - c: uint256 = D - S_: uint256 = 0 - Ann: uint256 = A_ * N_COINS - - _x: uint256 = 0 - for _i in range(N_COINS): - if _i != i: - _x = xp[_i] - else: - continue - S_ += _x - c = c * D / (_x * N_COINS) - c = c * D / (Ann * N_COINS) - b: uint256 = S_ + D / Ann - 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 - - -@view -@internal -def _calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> (uint256, uint256): - # First, need to calculate - # * Get current D - # * Solve Eqn against y_i for D - _token_amount - amp: uint256 = self._A() - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - precisions: uint256[N_COINS] = PRECISION_MUL - total_supply: uint256 = self.token.totalSupply() - - xp: uint256[N_COINS] = self._xp() - - D0: uint256 = self.get_D(xp, amp) - D1: uint256 = D0 - _token_amount * D0 / total_supply - xp_reduced: uint256[N_COINS] = xp - - new_y: uint256 = self.get_y_D(amp, i, xp, D1) - dy_0: uint256 = (xp[i] - new_y) / precisions[i] # w/o fees - - for j in range(N_COINS): - dx_expected: uint256 = 0 - if j == i: - dx_expected = xp[j] * D1 / D0 - new_y - else: - dx_expected = xp[j] - xp[j] * D1 / D0 - xp_reduced[j] -= _fee * dx_expected / FEE_DENOMINATOR - - dy: uint256 = xp_reduced[i] - self.get_y_D(amp, i, xp_reduced, D1) - dy = (dy - 1) / precisions[i] # Withdraw less to account for rounding errors - - return dy, dy_0 - dy - - -@view -@external -def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - return self._calc_withdraw_one_coin(_token_amount, i)[0] - - -@external -@nonreentrant('lock') -def remove_liquidity_one_coin(_token_amount: uint256, i: int128, min_amount: uint256): - """ - Remove _amount of liquidity all in a form of coin i - """ - assert not self.is_killed # dev: is killed - - dy: uint256 = 0 - dy_fee: uint256 = 0 - dy, dy_fee = self._calc_withdraw_one_coin(_token_amount, i) - assert dy >= min_amount, "Not enough coins removed" - - self.balances[i] -= (dy + dy_fee * self.admin_fee / FEE_DENOMINATOR) - self.token.burnFrom(msg.sender, _token_amount) # dev: insufficient funds - - # "safeTransfer" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - self.coins[i], - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(dy, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - - log RemoveLiquidityOne(msg.sender, _token_amount, dy) - - -### Admin functions ### -@external -def ramp_A(_future_A: uint256, _future_time: uint256): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.initial_A_time + MIN_RAMP_TIME - assert _future_time >= block.timestamp + MIN_RAMP_TIME # dev: insufficient time - - _initial_A: uint256 = self._A() - assert (_future_A > 0) and (_future_A < MAX_A) - assert ((_future_A >= _initial_A) and (_future_A <= _initial_A * MAX_A_CHANGE)) or\ - ((_future_A < _initial_A) and (_future_A * MAX_A_CHANGE >= _initial_A)) - self.initial_A = _initial_A - self.future_A = _future_A - self.initial_A_time = block.timestamp - self.future_A_time = _future_time - - log RampA(_initial_A, _future_A, block.timestamp, _future_time) - - -@external -def stop_ramp_A(): - assert msg.sender == self.owner # dev: only owner - - current_A: uint256 = self._A() - self.initial_A = current_A - self.future_A = current_A - self.initial_A_time = block.timestamp - self.future_A_time = block.timestamp - # now (block.timestamp < t1) is always False, so we return saved A - - log StopRampA(current_A, block.timestamp) - - -@external -def commit_new_fee(new_fee: uint256, new_admin_fee: uint256): - assert msg.sender == self.owner # dev: only owner - assert self.admin_actions_deadline == 0 # dev: active action - assert new_fee <= MAX_FEE # dev: fee exceeds maximum - assert new_admin_fee <= MAX_ADMIN_FEE # dev: admin fee exceeds maximum - - _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.admin_actions_deadline = _deadline - self.future_fee = new_fee - self.future_admin_fee = new_admin_fee - - log CommitNewFee(_deadline, new_fee, new_admin_fee) - - -@external -def apply_new_fee(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.admin_actions_deadline # dev: insufficient time - assert self.admin_actions_deadline != 0 # dev: no active action - - self.admin_actions_deadline = 0 - _fee: uint256 = self.future_fee - _admin_fee: uint256 = self.future_admin_fee - self.fee = _fee - self.admin_fee = _admin_fee - - log NewFee(_fee, _admin_fee) - - -@external -def revert_new_parameters(): - assert msg.sender == self.owner # dev: only owner - - self.admin_actions_deadline = 0 - - -@external -def commit_transfer_ownership(_owner: address): - assert msg.sender == self.owner # dev: only owner - assert self.transfer_ownership_deadline == 0 # dev: active transfer - - _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.transfer_ownership_deadline = _deadline - self.future_owner = _owner - - log CommitNewAdmin(_deadline, _owner) - - -@external -def apply_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.transfer_ownership_deadline # dev: insufficient time - assert self.transfer_ownership_deadline != 0 # dev: no active transfer - - self.transfer_ownership_deadline = 0 - _owner: address = self.future_owner - self.owner = _owner - - log NewAdmin(_owner) - - -@external -def revert_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - - self.transfer_ownership_deadline = 0 - - -@view -@external -def admin_balances(i: uint256) -> uint256: - return ERC20(self.coins[i]).balanceOf(self) - self.balances[i] - - -@external -def withdraw_admin_fees(): - assert msg.sender == self.owner # dev: only owner - - for i in range(N_COINS): - c: address = self.coins[i] - value: uint256 = ERC20(c).balanceOf(self) - self.balances[i] - if value > 0: - # "safeTransfer" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - c, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(value, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - - -@external -def donate_admin_fees(): - assert msg.sender == self.owner # dev: only owner - for i in range(N_COINS): - self.balances[i] = ERC20(self.coins[i]).balanceOf(self) - - -@external -def kill_me(): - assert msg.sender == self.owner # dev: only owner - assert self.kill_deadline > block.timestamp # dev: deadline has passed - self.is_killed = True - - -@external -def unkill_me(): - assert msg.sender == self.owner # dev: only owner - self.is_killed = False diff --git a/contracts/pools/3pool/pooldata.json b/contracts/pools/3pool/pooldata.json deleted file mode 100644 index bb31eaff..00000000 --- a/contracts/pools/3pool/pooldata.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "lp_contract": "CurveTokenV2", - "swap_address": "0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7", - "lp_token_address": "0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490", - "gauge_addresses": ["0xbFcF63294aD7105dEa65aA58F8AE5BE2D9d0952A"], - "lp_constructor": { - "name": "Curve.fi DAI/USDC/USDT", - "symbol": "3Crv" - }, - "coins": [ - { - "name": "DAI", - "decimals": 18, - "tethered": false, - "underlying_address": "0x6b175474e89094c44da98b954eedeac495271d0f" - }, - { - "name": "USDC", - "decimals": 6, - "tethered": false, - "underlying_address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" - - }, - { - "name": "USDT", - "decimals": 6, - "tethered": true, - "underlying_address": "0xdac17f958d2ee523a2206206994597c13d831ec7" - } - ] -} diff --git a/contracts/pools/pax/README.md b/contracts/pools/ZRC20/README.md similarity index 100% rename from contracts/pools/pax/README.md rename to contracts/pools/ZRC20/README.md diff --git a/contracts/pools/link/StableSwapLINK.vy b/contracts/pools/ZRC20/StableSwapZRC20.vy similarity index 81% rename from contracts/pools/link/StableSwapLINK.vy rename to contracts/pools/ZRC20/StableSwapZRC20.vy index fc71a513..efd0684b 100644 --- a/contracts/pools/link/StableSwapLINK.vy +++ b/contracts/pools/ZRC20/StableSwapZRC20.vy @@ -1,10 +1,14 @@ -# @version 0.2.8 +# @version ^0.2.8 """ @title StableSwap @author Curve.Fi @license Copyright (c) Curve.Fi, 2020 - all rights reserved -@notice Minimal pool implementation with no lending -@dev Swaps between LINK and sLINK +@notice Curve ETH pool implementation +@dev This contract is only a template, pool-specific constants + must be set prior to compiling. Note that this template + contains optimizations that depend upon every token within + the pool using 18 decimal places and should therefore be + dealt with accordingly. """ from vyper.interfaces import ERC20 @@ -77,7 +81,7 @@ event StopRampA: # These constants must be set prior to compiling -N_COINS: constant(int128) = 2 +N_COINS: constant(int128) = 3 # fixed constants FEE_DENOMINATOR: constant(uint256) = 10 ** 10 @@ -96,9 +100,6 @@ balances: public(uint256[N_COINS]) fee: public(uint256) # fee * 1e10 admin_fee: public(uint256) # admin_fee * 1e10 -previous_balances: public(uint256[N_COINS]) -block_timestamp_last: public(uint256) - owner: public(address) lp_token: public(address) @@ -183,17 +184,6 @@ def A_precise() -> uint256: return self._A() -@internal -def _update(): - """ - Commits pre-change balances for the previous block - Can be used to compare against current values for flash loan checks - """ - if block.timestamp > self.block_timestamp_last: - self.previous_balances = self.balances - self.block_timestamp_last = block.timestamp - - @pure @internal def _get_D(_xp: uint256[N_COINS], _amp: uint256) -> uint256: @@ -234,12 +224,6 @@ def _get_D(_xp: uint256[N_COINS], _amp: uint256) -> uint256: raise -@view -@internal -def _get_D_mem(_balances: uint256[N_COINS], _amp: uint256) -> uint256: - return self._get_D(_balances, _amp) - - @view @external def get_virtual_price() -> uint256: @@ -268,13 +252,13 @@ def calc_token_amount(_amounts: uint256[N_COINS], _is_deposit: bool) -> uint256: """ amp: uint256 = self._A() balances: uint256[N_COINS] = self.balances - D0: uint256 = self._get_D_mem(balances, amp) + D0: uint256 = self._get_D(balances, amp) for i in range(N_COINS): if _is_deposit: balances[i] += _amounts[i] else: balances[i] -= _amounts[i] - D1: uint256 = self._get_D_mem(balances, amp) + D1: uint256 = self._get_D(balances, amp) token_amount: uint256 = CurveToken(self.lp_token).totalSupply() diff: uint256 = 0 if _is_deposit: @@ -284,6 +268,7 @@ def calc_token_amount(_amounts: uint256[N_COINS], _is_deposit: bool) -> uint256: return diff * token_amount / D0 +@payable @external @nonreentrant('lock') def add_liquidity(_amounts: uint256[N_COINS], _min_mint_amount: uint256) -> uint256: @@ -293,33 +278,31 @@ def add_liquidity(_amounts: uint256[N_COINS], _min_mint_amount: uint256) -> uint @param _min_mint_amount Minimum amount of LP tokens to mint from the deposit @return Amount of LP tokens received by depositing """ - self._update() assert not self.is_killed # dev: is killed amp: uint256 = self._A() old_balances: uint256[N_COINS] = self.balances - + # Initial invariant - D0: uint256 = self._get_D_mem(old_balances, amp) + D0: uint256 = self._get_D(old_balances, amp) lp_token: address = self.lp_token - token_supply: uint256 = CurveToken(lp_token).totalSupply() - new_balances: uint256[N_COINS] = old_balances + token_supply: uint256 = ERC20(lp_token).totalSupply() + new_balances: uint256[N_COINS] = empty(uint256[N_COINS]) for i in range(N_COINS): if token_supply == 0: assert _amounts[i] > 0 # dev: initial deposit requires all coins - # balances store amounts of c-tokens - new_balances[i] += _amounts[i] + new_balances[i] = old_balances[i] + _amounts[i] # Invariant after change - D1: uint256 = self._get_D_mem(new_balances, amp) + D1: uint256 = self._get_D(new_balances, amp) assert D1 > D0 # We need to recalculate the invariant accounting for fees # to calculate fair user's share - D2: uint256 = D1 fees: uint256[N_COINS] = empty(uint256[N_COINS]) mint_amount: uint256 = 0 + D2: uint256 = D1 if token_supply > 0: # Only account for fees if we are not the first to deposit fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) @@ -327,32 +310,36 @@ def add_liquidity(_amounts: uint256[N_COINS], _min_mint_amount: uint256) -> uint for i in range(N_COINS): ideal_balance: uint256 = D1 * old_balances[i] / D0 difference: uint256 = 0 - new_balance: uint256 = new_balances[i] - if ideal_balance > new_balance: - difference = ideal_balance - new_balance + if ideal_balance > new_balances[i]: + difference = ideal_balance - new_balances[i] else: - difference = new_balance - ideal_balance + difference = new_balances[i] - ideal_balance fees[i] = fee * difference / FEE_DENOMINATOR - self.balances[i] = new_balance - (fees[i] * admin_fee / FEE_DENOMINATOR) + self.balances[i] = new_balances[i] - (fees[i] * admin_fee / FEE_DENOMINATOR) new_balances[i] -= fees[i] - D2 = self._get_D_mem(new_balances, amp) + D2 = self._get_D(new_balances, amp) mint_amount = token_supply * (D2 - D0) / D0 else: self.balances = new_balances mint_amount = D1 # Take the dust if there was any + assert mint_amount >= _min_mint_amount, "Slippage screwed you" # Take coins from the sender for i in range(N_COINS): - if _amounts[i] > 0: + coin: address = self.coins[i] + amount: uint256 = _amounts[i] + if coin == 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE: + assert msg.value == amount + elif amount > 0: # "safeTransferFrom" which works for ERC20s which return bool or not _response: Bytes[32] = raw_call( - self.coins[i], + coin, concat( method_id("transferFrom(address,address,uint256)"), convert(msg.sender, bytes32), convert(self, bytes32), - convert(_amounts[i], bytes32), + convert(amount, bytes32), ), max_outsize=32, ) @@ -427,7 +414,6 @@ def _get_y(i: int128, j: int128, x: uint256, _xp: uint256[N_COINS]) -> uint256: @external def get_dy(i: int128, j: int128, _dx: uint256) -> uint256: xp: uint256[N_COINS] = self.balances - x: uint256 = xp[i] + _dx y: uint256 = self._get_y(i, j, x, xp) dy: uint256 = xp[j] - y - 1 @@ -435,6 +421,7 @@ def get_dy(i: int128, j: int128, _dx: uint256) -> uint256: return dy - fee +@payable @external @nonreentrant('lock') def exchange(i: int128, j: int128, _dx: uint256, _min_dy: uint256) -> uint256: @@ -448,19 +435,17 @@ def exchange(i: int128, j: int128, _dx: uint256, _min_dy: uint256) -> uint256: @return Actual amount of `j` received """ assert not self.is_killed # dev: is killed - self._update() old_balances: uint256[N_COINS] = self.balances - xp: uint256[N_COINS] = old_balances - x: uint256 = xp[i] + _dx - y: uint256 = self._get_y(i, j, x, xp) + x: uint256 = old_balances[i] + _dx + y: uint256 = self._get_y(i, j, x, old_balances) - dy: uint256 = xp[j] - y - 1 # -1 just in case there were some rounding errors + dy: uint256 = old_balances[j] - y - 1 # -1 just in case there were some rounding errors dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR # Convert all to real units - dy -= dy_fee + dy = dy - dy_fee assert dy >= _min_dy, "Exchange resulted in fewer coins than expected" dy_admin_fee: uint256 = dy_fee * self.admin_fee / FEE_DENOMINATOR @@ -470,30 +455,39 @@ def exchange(i: int128, j: int128, _dx: uint256, _min_dy: uint256) -> uint256: # When rounding errors happen, we undercharge admin fee in favor of LP self.balances[j] = old_balances[j] - dy - dy_admin_fee - _response: Bytes[32] = raw_call( - self.coins[i], - concat( - method_id("transferFrom(address,address,uint256)"), - convert(msg.sender, bytes32), - convert(self, bytes32), - convert(_dx, bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) - - _response = raw_call( - self.coins[j], - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(dy, bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) + coin: address = self.coins[i] + if coin == 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE: + assert msg.value == _dx + else: + assert msg.value == 0 + _response: Bytes[32] = raw_call( + coin, + concat( + method_id("transferFrom(address,address,uint256)"), + convert(msg.sender, bytes32), + convert(self, bytes32), + convert(_dx, bytes32), + ), + max_outsize=32, + ) + if len(_response) > 0: + assert convert(_response, bool) + + coin = self.coins[j] + if coin == 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE: + raw_call(msg.sender, b"", value=dy) + else: + _response: Bytes[32] = raw_call( + coin, + concat( + method_id("transfer(address,uint256)"), + convert(msg.sender, bytes32), + convert(dy, bytes32), + ), + max_outsize=32, + ) + if len(_response) > 0: + assert convert(_response, bool) log TokenExchange(msg.sender, i, _dx, j, dy) @@ -510,11 +504,9 @@ def remove_liquidity(_amount: uint256, _min_amounts: uint256[N_COINS]) -> uint25 @param _min_amounts Minimum amounts of underlying coins to receive @return List of amounts of coins that were withdrawn """ - self._update() lp_token: address = self.lp_token total_supply: uint256 = CurveToken(lp_token).totalSupply() amounts: uint256[N_COINS] = empty(uint256[N_COINS]) - fees: uint256[N_COINS] = empty(uint256[N_COINS]) # Fees are unused but we've got them historically in event for i in range(N_COINS): old_balance: uint256 = self.balances[i] @@ -522,21 +514,25 @@ def remove_liquidity(_amount: uint256, _min_amounts: uint256[N_COINS]) -> uint25 assert value >= _min_amounts[i], "Withdrawal resulted in fewer coins than expected" self.balances[i] = old_balance - value amounts[i] = value - _response: Bytes[32] = raw_call( - self.coins[i], - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(value, bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) + coin: address = self.coins[i] + if coin == 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE: + raw_call(msg.sender, b"", value=value) + else: + _response: Bytes[32] = raw_call( + coin, + concat( + method_id("transfer(address,uint256)"), + convert(msg.sender, bytes32), + convert(value, bytes32), + ), + max_outsize=32, + ) + if len(_response) > 0: + assert convert(_response, bool) CurveToken(lp_token).burnFrom(msg.sender, _amount) # dev: insufficient funds - log RemoveLiquidity(msg.sender, amounts, fees, total_supply - _amount) + log RemoveLiquidity(msg.sender, amounts, empty(uint256[N_COINS]), total_supply - _amount) return amounts @@ -551,19 +547,18 @@ def remove_liquidity_imbalance(_amounts: uint256[N_COINS], _max_burn_amount: uin @return Actual amount of the LP token burned in the withdrawal """ assert not self.is_killed # dev: is killed - self._update() amp: uint256 = self._A() old_balances: uint256[N_COINS] = self.balances - D0: uint256 = self._get_D_mem(old_balances, amp) - new_balances: uint256[N_COINS] = old_balances + D0: uint256 = self._get_D(old_balances, amp) + new_balances: uint256[N_COINS] = empty(uint256[N_COINS]) for i in range(N_COINS): - new_balances[i] -= _amounts[i] - D1: uint256 = self._get_D_mem(new_balances, amp) + new_balances[i] = old_balances[i] - _amounts[i] + D1: uint256 = self._get_D(new_balances, amp) + fees: uint256[N_COINS] = empty(uint256[N_COINS]) fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) admin_fee: uint256 = self.admin_fee - fees: uint256[N_COINS] = empty(uint256[N_COINS]) for i in range(N_COINS): new_balance: uint256 = new_balances[i] ideal_balance: uint256 = D1 * old_balances[i] / D0 @@ -575,7 +570,7 @@ def remove_liquidity_imbalance(_amounts: uint256[N_COINS], _max_burn_amount: uin fees[i] = fee * difference / FEE_DENOMINATOR self.balances[i] = new_balance - (fees[i] * admin_fee / FEE_DENOMINATOR) new_balances[i] = new_balance - fees[i] - D2: uint256 = self._get_D_mem(new_balances, amp) + D2: uint256 = self._get_D(new_balances, amp) lp_token: address = self.lp_token token_supply: uint256 = CurveToken(lp_token).totalSupply() @@ -586,18 +581,23 @@ def remove_liquidity_imbalance(_amounts: uint256[N_COINS], _max_burn_amount: uin CurveToken(lp_token).burnFrom(msg.sender, token_amount) # dev: insufficient funds for i in range(N_COINS): - if _amounts[i] != 0: - _response: Bytes[32] = raw_call( - self.coins[i], - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(_amounts[i], bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) + amount: uint256 = _amounts[i] + if amount != 0: + coin: address = self.coins[i] + if coin == 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE: + raw_call(msg.sender, b"", value=amount) + else: + _response: Bytes[32] = raw_call( + coin, + concat( + method_id("transfer(address,uint256)"), + convert(msg.sender, bytes32), + convert(amount, bytes32), + ), + max_outsize=32, + ) + if len(_response) > 0: + assert convert(_response, bool) log RemoveLiquidityImbalance(msg.sender, _amounts, fees, D1, token_supply - token_amount) @@ -626,7 +626,7 @@ def _get_y_D(A: uint256, i: int128, _xp: uint256[N_COINS], D: uint256) -> uint25 S: uint256 = 0 _x: uint256 = 0 y_prev: uint256 = 0 - + for _i in range(N_COINS): if _i != i: _x = _xp[_i] @@ -660,12 +660,11 @@ def _calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> (uint256, uint amp: uint256 = self._A() xp: uint256[N_COINS] = self.balances D0: uint256 = self._get_D(xp, amp) - total_supply: uint256 = CurveToken(self.lp_token).totalSupply() D1: uint256 = D0 - _token_amount * D0 / total_supply new_y: uint256 = self._get_y_D(amp, i, xp, D1) - xp_reduced: uint256[N_COINS] = xp fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) + xp_reduced: uint256[N_COINS] = xp for j in range(N_COINS): dx_expected: uint256 = 0 if j == i: @@ -675,6 +674,7 @@ def _calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> (uint256, uint xp_reduced[j] -= fee * dx_expected / FEE_DENOMINATOR dy: uint256 = xp_reduced[i] - self._get_y_D(amp, i, xp_reduced, D1) + dy -= 1 # Withdraw less to account for rounding errors dy_0: uint256 = xp[i] - new_y # w/o fees @@ -704,7 +704,6 @@ def remove_liquidity_one_coin(_token_amount: uint256, i: int128, _min_amount: ui @return Amount of coin received """ assert not self.is_killed # dev: is killed - self._update() dy: uint256 = 0 dy_fee: uint256 = 0 @@ -715,17 +714,21 @@ def remove_liquidity_one_coin(_token_amount: uint256, i: int128, _min_amount: ui self.balances[i] -= (dy + dy_fee * self.admin_fee / FEE_DENOMINATOR) CurveToken(self.lp_token).burnFrom(msg.sender, _token_amount) # dev: insufficient funds - _response: Bytes[32] = raw_call( - self.coins[i], - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(dy, bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) + coin: address = self.coins[i] + if coin == 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE: + raw_call(msg.sender, b"", value=dy) + else: + _response: Bytes[32] = raw_call( + coin, + concat( + method_id("transfer(address,uint256)"), + convert(msg.sender, bytes32), + convert(dy, bytes32), + ), + max_outsize=32, + ) + if len(_response) > 0: + assert convert(_response, bool) log RemoveLiquidityOne(msg.sender, _token_amount, dy, total_supply - _token_amount) @@ -842,7 +845,11 @@ def revert_transfer_ownership(): @view @external def admin_balances(i: uint256) -> uint256: - return ERC20(self.coins[i]).balanceOf(self) - self.balances[i] + coin: address = self.coins[i] + if coin == 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE: + return self.balance - self.balances[i] + else: + return ERC20(coin).balanceOf(self) - self.balances[i] @external @@ -851,26 +858,35 @@ def withdraw_admin_fees(): for i in range(N_COINS): coin: address = self.coins[i] - value: uint256 = ERC20(coin).balanceOf(self) - self.balances[i] - if value > 0: - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(value, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) + if coin == 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE: + value: uint256 = self.balance - self.balances[i] + if value > 0: + raw_call(msg.sender, b"", value=value) + else: + value: uint256 = ERC20(coin).balanceOf(self) - self.balances[i] + if value > 0: + _response: Bytes[32] = raw_call( + coin, + concat( + method_id("transfer(address,uint256)"), + convert(msg.sender, bytes32), + convert(value, bytes32), + ), + max_outsize=32, + ) # dev: failed transfer + if len(_response) > 0: + assert convert(_response, bool) @external def donate_admin_fees(): assert msg.sender == self.owner # dev: only owner for i in range(N_COINS): - self.balances[i] = ERC20(self.coins[i]).balanceOf(self) + coin: address = self.coins[i] + if coin == 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE: + self.balances[i] = self.balance + else: + self.balances[i] = ERC20(coin).balanceOf(self) @external diff --git a/contracts/pools/ZRC20/pooldata.json b/contracts/pools/ZRC20/pooldata.json new file mode 100644 index 00000000..91c85575 --- /dev/null +++ b/contracts/pools/ZRC20/pooldata.json @@ -0,0 +1,38 @@ +{ + "lp_contract": "CurveTokenV1", + "pool_types": [ + "eth" + ], + "swap_constructor": { + "_A": 200, + "_fee": 4000000, + "_admin_fee": 0 + }, + "lp_constructor": { + "name": "Curve.fi ETH/BNB/ZETA", + "symbol": "yZRC20" + }, + "coins": [ + { + "name": "ZETA", + "decimals": 18, + "tethered": false, + "wrapped": false, + "underlying_address": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" + }, + { + "name": "ycBNB.BSC", + "decimals": 18, + "tethered": false, + "wrapped": false, + "underlying_address": "0xd97B1de3619ed2c6BEb3860147E30cA8A7dC9891" + }, + { + "name": "ycETH.ETH", + "decimals": 18, + "tethered": false, + "wrapped": false, + "underlying_address": "0x13A0c5930C028511Dc02665E7285134B6d11A5f4" + } + ] +} \ No newline at end of file diff --git a/contracts/pools/aave/README.md b/contracts/pools/aave/README.md deleted file mode 100644 index d49f6f6e..00000000 --- a/contracts/pools/aave/README.md +++ /dev/null @@ -1,29 +0,0 @@ -# curve-contract/contracts/pools/aave - -[Curve Aave pool](https://www.curve.fi/aave), with lending on [Aave](https://aave.com/). - -## Contracts - -* [`StableSwapAave`](StableSwapAave.vy): Curve stablecoin AMM contract - -## Deployments - -* [`CurveContractV3`](../../tokens/CurveTokenV3.vy): [0xFd2a8fA60Abd58Efe3EeE34dd494cD491dC14900](https://etherscan.io/address/0xFd2a8fA60Abd58Efe3EeE34dd494cD491dC14900) -* [`LiquidityGaugeV2`](https://github.com/curvefi/curve-dao-contracts/blob/master/contracts/gauges/LiquidityGaugeV2.vy): [0xd662908ADA2Ea1916B3318327A97eB18aD588b5d](https://etherscan.io/address/0xd662908ADA2Ea1916B3318327A97eB18aD588b5d) -* [`StableSwapAave`](StableSwapAave.vy): [0xDeBF20617708857ebe4F679508E7b7863a8A8EeE](https://etherscan.io/address/0xDeBF20617708857ebe4F679508E7b7863a8A8EeE) - -## Stablecoins - -Curve Aave pool supports swaps between the following stablecoins: - -### Wrapped - -* `aDAI`: [0x028171bCA77440897B824Ca71D1c56caC55b68A3](https://etherscan.io/address/0x028171bCA77440897B824Ca71D1c56caC55b68A3) -* `aUSDC`: [0xBcca60bB61934080951369a648Fb03DF4F96263C](https://etherscan.io/address/0xBcca60bB61934080951369a648Fb03DF4F96263C) -* `aUSDT`: [0x3Ed3B47Dd13EC9a98b44e6204A523E766B225811](https://etherscan.io/address/0x3Ed3B47Dd13EC9a98b44e6204A523E766B225811) - -### Underlying - -* `DAI`: [0x6b175474e89094c44da98b954eedeac495271d0f](https://etherscan.io/token/0x6b175474e89094c44da98b954eedeac495271d0f) -* `USDC`: [0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48](https://etherscan.io/token/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48) -* `USDT`: [0xdac17f958d2ee523a2206206994597c13d831ec7](https://etherscan.io/address/0xdac17f958d2ee523a2206206994597c13d831ec7) diff --git a/contracts/pools/aave/StableSwapAave.vy b/contracts/pools/aave/StableSwapAave.vy deleted file mode 100644 index 21af5217..00000000 --- a/contracts/pools/aave/StableSwapAave.vy +++ /dev/null @@ -1,1053 +0,0 @@ -# @version 0.2.8 -""" -@title Curve aPool -@author Curve.Fi -@license Copyright (c) Curve.Fi, 2020 - all rights reserved -@notice Pool implementation with aToken-style lending -""" - -from vyper.interfaces import ERC20 - - -interface LendingPool: - def withdraw(_underlying_asset: address, _amount: uint256, _receiver: address): nonpayable - -interface CurveToken: - def mint(_to: address, _value: uint256) -> bool: nonpayable - def burnFrom(_to: address, _value: uint256) -> bool: nonpayable - - -# Events -event TokenExchange: - buyer: indexed(address) - sold_id: int128 - tokens_sold: uint256 - bought_id: int128 - tokens_bought: uint256 - -event TokenExchangeUnderlying: - buyer: indexed(address) - sold_id: int128 - tokens_sold: uint256 - bought_id: int128 - tokens_bought: uint256 - -event AddLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event RemoveLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - token_supply: uint256 - -event RemoveLiquidityOne: - provider: indexed(address) - token_amount: uint256 - coin_amount: uint256 - -event RemoveLiquidityImbalance: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event CommitNewAdmin: - deadline: indexed(uint256) - admin: indexed(address) - -event NewAdmin: - admin: indexed(address) - -event CommitNewFee: - deadline: indexed(uint256) - fee: uint256 - admin_fee: uint256 - offpeg_fee_multiplier: uint256 - -event NewFee: - fee: uint256 - admin_fee: uint256 - offpeg_fee_multiplier: uint256 - -event RampA: - old_A: uint256 - new_A: uint256 - initial_time: uint256 - future_time: uint256 - -event StopRampA: - A: uint256 - t: uint256 - - -# These constants must be set prior to compiling -N_COINS: constant(int128) = 3 -PRECISION_MUL: constant(uint256[N_COINS]) = [1, 1000000000000, 1000000000000] - -# fixed constants -FEE_DENOMINATOR: constant(uint256) = 10 ** 10 -PRECISION: constant(uint256) = 10 ** 18 # The precision to convert to - -MAX_ADMIN_FEE: constant(uint256) = 10 * 10 ** 9 -MAX_FEE: constant(uint256) = 5 * 10 ** 9 - -MAX_A: constant(uint256) = 10 ** 6 -MAX_A_CHANGE: constant(uint256) = 10 -A_PRECISION: constant(uint256) = 100 - -ADMIN_ACTIONS_DELAY: constant(uint256) = 3 * 86400 -MIN_RAMP_TIME: constant(uint256) = 86400 - -coins: public(address[N_COINS]) -underlying_coins: public(address[N_COINS]) -admin_balances: public(uint256[N_COINS]) - -fee: public(uint256) # fee * 1e10 -offpeg_fee_multiplier: public(uint256) # * 1e10 -admin_fee: public(uint256) # admin_fee * 1e10 - -owner: public(address) -lp_token: public(address) - -aave_lending_pool: address -aave_referral: uint256 - -initial_A: public(uint256) -future_A: public(uint256) -initial_A_time: public(uint256) -future_A_time: public(uint256) - -admin_actions_deadline: public(uint256) -transfer_ownership_deadline: public(uint256) -future_fee: public(uint256) -future_admin_fee: public(uint256) -future_offpeg_fee_multiplier: public(uint256) # * 1e10 -future_owner: public(address) - -is_killed: bool -kill_deadline: uint256 -KILL_DEADLINE_DT: constant(uint256) = 2 * 30 * 86400 - - -@external -def __init__( - _coins: address[N_COINS], - _underlying_coins: address[N_COINS], - _pool_token: address, - _aave_lending_pool: address, - _A: uint256, - _fee: uint256, - _admin_fee: uint256, - _offpeg_fee_multiplier: uint256, -): - """ - @notice Contract constructor - @param _coins List of wrapped coin addresses - @param _underlying_coins List of underlying coin addresses - @param _pool_token Pool LP token address - @param _aave_lending_pool Aave lending pool address - @param _A Amplification coefficient multiplied by n * (n - 1) - @param _fee Swap fee expressed as an integer with 1e10 precision - @param _admin_fee Percentage of fee taken as an admin fee, - expressed as an integer with 1e10 precision - @param _offpeg_fee_multiplier Offpeg fee multiplier - """ - for i in range(N_COINS): - assert _coins[i] != ZERO_ADDRESS - assert _underlying_coins[i] != ZERO_ADDRESS - - self.coins = _coins - self.underlying_coins = _underlying_coins - self.initial_A = _A * A_PRECISION - self.future_A = _A * A_PRECISION - self.fee = _fee - self.admin_fee = _admin_fee - self.offpeg_fee_multiplier = _offpeg_fee_multiplier - self.owner = msg.sender - self.kill_deadline = block.timestamp + KILL_DEADLINE_DT - self.lp_token = _pool_token - self.aave_lending_pool = _aave_lending_pool - - # approve transfer of underlying coin to aave lending pool - for coin in _underlying_coins: - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("approve(address,uint256)"), - convert(_aave_lending_pool, bytes32), - convert(MAX_UINT256, bytes32) - ), - max_outsize=32 - ) - if len(_response) != 0: - assert convert(_response, bool) - - - -@view -@internal -def _A() -> uint256: - t1: uint256 = self.future_A_time - A1: uint256 = self.future_A - - if block.timestamp < t1: - # handle ramping up and down of A - A0: uint256 = self.initial_A - t0: uint256 = self.initial_A_time - # Expressions in uint256 cannot have negative numbers, thus "if" - if A1 > A0: - return A0 + (A1 - A0) * (block.timestamp - t0) / (t1 - t0) - else: - return A0 - (A0 - A1) * (block.timestamp - t0) / (t1 - t0) - - else: # when t1 == 0 or block.timestamp >= t1 - return A1 - - -@view -@external -def A() -> uint256: - return self._A() / A_PRECISION - - -@view -@external -def A_precise() -> uint256: - return self._A() - - -@pure -@internal -def _dynamic_fee(xpi: uint256, xpj: uint256, _fee: uint256, _feemul: uint256) -> uint256: - if _feemul <= FEE_DENOMINATOR: - return _fee - else: - xps2: uint256 = (xpi + xpj) - xps2 *= xps2 # Doing just ** 2 can overflow apparently - return (_feemul * _fee) / ( - (_feemul - FEE_DENOMINATOR) * 4 * xpi * xpj / xps2 + \ - FEE_DENOMINATOR) - - -@view -@external -def dynamic_fee(i: int128, j: int128) -> uint256: - """ - @notice Return the fee for swapping between `i` and `j` - @param i Index value for the coin to send - @param j Index value of the coin to recieve - @return Swap fee expressed as an integer with 1e10 precision - """ - precisions: uint256[N_COINS] = PRECISION_MUL - xpi: uint256 = (ERC20(self.coins[i]).balanceOf(self) - self.admin_balances[i]) * precisions[i] - xpj: uint256 = (ERC20(self.coins[j]).balanceOf(self) - self.admin_balances[j]) * precisions[j] - return self._dynamic_fee(xpi, xpj, self.fee, self.offpeg_fee_multiplier) - - -@view -@external -def balances(i: uint256) -> uint256: - """ - @notice Get the current balance of a coin within the - pool, less the accrued admin fees - @param i Index value for the coin to query balance of - @return Token balance - """ - return ERC20(self.coins[i]).balanceOf(self) - self.admin_balances[i] - - -@view -@internal -def _balances() -> uint256[N_COINS]: - result: uint256[N_COINS] = empty(uint256[N_COINS]) - for i in range(N_COINS): - result[i] = ERC20(self.coins[i]).balanceOf(self) - self.admin_balances[i] - return result - - -@pure -@internal -def get_D(xp: uint256[N_COINS], amp: uint256) -> uint256: - """ - D invariant calculation in non-overflowing integer operations - iteratively - - A * sum(x_i) * n**n + D = A * D * n**n + D**(n+1) / (n**n * prod(x_i)) - - Converging solution: - D[j+1] = (A * n**n * sum(x_i) - D[j]**(n+1) / (n**n prod(x_i))) / (A * n**n - 1) - """ - S: uint256 = 0 - - for _x in xp: - S += _x - if S == 0: - return 0 - - Dprev: uint256 = 0 - D: uint256 = S - Ann: uint256 = amp * 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 / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (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` - raise - - - -@view -@internal -def get_D_precisions(coin_balances: uint256[N_COINS], amp: uint256) -> uint256: - xp: uint256[N_COINS] = PRECISION_MUL - for i in range(N_COINS): - xp[i] *= coin_balances[i] - return self.get_D(xp, amp) - - -@view -@external -def get_virtual_price() -> uint256: - """ - @notice The current virtual price of the pool LP token - @dev Useful for calculating profits - @return LP token virtual price normalized to 1e18 - """ - D: uint256 = self.get_D_precisions(self._balances(), self._A()) - # 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 = ERC20(self.lp_token).totalSupply() - return D * PRECISION / token_supply - - -@view -@external -def calc_token_amount(_amounts: uint256[N_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 _amounts Amount of each coin being deposited - @param is_deposit set True for deposits, False for withdrawals - @return Expected amount of LP tokens received - """ - coin_balances: uint256[N_COINS] = self._balances() - amp: uint256 = self._A() - D0: uint256 = self.get_D_precisions(coin_balances, amp) - for i in range(N_COINS): - if is_deposit: - coin_balances[i] += _amounts[i] - else: - coin_balances[i] -= _amounts[i] - D1: uint256 = self.get_D_precisions(coin_balances, amp) - token_amount: uint256 = ERC20(self.lp_token).totalSupply() - diff: uint256 = 0 - if is_deposit: - diff = D1 - D0 - else: - diff = D0 - D1 - return diff * token_amount / D0 - - -@external -@nonreentrant('lock') -def add_liquidity(_amounts: uint256[N_COINS], _min_mint_amount: uint256, _use_underlying: bool = False) -> uint256: - """ - @notice Deposit coins into the pool - @param _amounts List of amounts of coins to deposit - @param _min_mint_amount Minimum amount of LP tokens to mint from the deposit - @param _use_underlying If True, deposit underlying assets instead of aTokens - @return Amount of LP tokens received by depositing - """ - - assert not self.is_killed # dev: is killed - - # Initial invariant - amp: uint256 = self._A() - old_balances: uint256[N_COINS] = self._balances() - lp_token: address = self.lp_token - token_supply: uint256 = ERC20(lp_token).totalSupply() - D0: uint256 = 0 - if token_supply != 0: - D0 = self.get_D_precisions(old_balances, amp) - - new_balances: uint256[N_COINS] = old_balances - for i in range(N_COINS): - if token_supply == 0: - assert _amounts[i] != 0 # dev: initial deposit requires all coins - new_balances[i] += _amounts[i] - - # Invariant after change - D1: uint256 = self.get_D_precisions(new_balances, amp) - assert D1 > D0 - - # We need to recalculate the invariant accounting for fees - # to calculate fair user's share - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - mint_amount: uint256 = 0 - if token_supply != 0: - # Only account for fees if we are not the first to deposit - ys: uint256 = (D0 + D1) / N_COINS - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - _feemul: uint256 = self.offpeg_fee_multiplier - _admin_fee: uint256 = self.admin_fee - difference: uint256 = 0 - for i in range(N_COINS): - ideal_balance: uint256 = D1 * old_balances[i] / D0 - new_balance: uint256 = new_balances[i] - if ideal_balance > new_balance: - difference = ideal_balance - new_balance - else: - difference = new_balance - ideal_balance - xs: uint256 = old_balances[i] + new_balance - fees[i] = self._dynamic_fee(xs, ys, _fee, _feemul) * difference / FEE_DENOMINATOR - if _admin_fee != 0: - self.admin_balances[i] += fees[i] * _admin_fee / FEE_DENOMINATOR - new_balances[i] = new_balance - fees[i] - D2: uint256 = self.get_D_precisions(new_balances, amp) - mint_amount = token_supply * (D2 - D0) / D0 - else: - mint_amount = D1 # Take the dust if there was any - - assert mint_amount >= _min_mint_amount, "Slippage screwed you" - - # Take coins from the sender - if _use_underlying: - lending_pool: address = self.aave_lending_pool - aave_referral: bytes32 = convert(self.aave_referral, bytes32) - - # Take coins from the sender - for i in range(N_COINS): - amount: uint256 = _amounts[i] - if amount != 0: - coin: address = self.underlying_coins[i] - # transfer underlying coin from msg.sender to self - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transferFrom(address,address,uint256)"), - convert(msg.sender, bytes32), - convert(self, bytes32), - convert(amount, bytes32) - ), - max_outsize=32 - ) - if len(_response) != 0: - assert convert(_response, bool) - - # deposit to aave lending pool - raw_call( - lending_pool, - concat( - method_id("deposit(address,uint256,address,uint16)"), - convert(coin, bytes32), - convert(amount, bytes32), - convert(self, bytes32), - aave_referral, - ) - ) - else: - for i in range(N_COINS): - amount: uint256 = _amounts[i] - if amount != 0: - assert ERC20(self.coins[i]).transferFrom(msg.sender, self, amount) # dev: failed transfer - - # Mint pool tokens - CurveToken(lp_token).mint(msg.sender, mint_amount) - - log AddLiquidity(msg.sender, _amounts, fees, D1, token_supply + mint_amount) - - return mint_amount - - -@view -@internal -def get_y(i: int128, j: int128, x: uint256, xp: uint256[N_COINS]) -> uint256: - """ - Calculate x[j] if one makes x[i] = x - - Done by solving quadratic equation iteratively. - x_1**2 + x1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) - x_1**2 + b*x_1 = c - - x_1 = (x_1**2 + c) / (2*x_1 + b) - """ - # x in the input is converted to the same price/precision - - assert i != j # dev: same coin - assert j >= 0 # dev: j below zero - assert j < N_COINS # dev: j above N_COINS - - # should be unreachable, but good for safety - assert i >= 0 - assert i < N_COINS - - amp: uint256 = self._A() - D: uint256 = self.get_D(xp, amp) - Ann: uint256 = amp * N_COINS - c: uint256 = D - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: 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 * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann # - D - 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: - return y - else: - if y_prev - y <= 1: - return y - raise - - -@view -@internal -def _get_dy(i: int128, j: int128, dx: uint256) -> uint256: - xp: uint256[N_COINS] = self._balances() - precisions: uint256[N_COINS] = PRECISION_MUL - for k in range(N_COINS): - xp[k] *= precisions[k] - - 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._dynamic_fee( - (xp[i] + x) / 2, (xp[j] + y) / 2, self.fee, self.offpeg_fee_multiplier - ) * dy / FEE_DENOMINATOR - return dy - _fee - - -@view -@external -def get_dy(i: int128, j: int128, dx: uint256) -> uint256: - return self._get_dy(i, j, dx) - - -@view -@external -def get_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256: - return self._get_dy(i, j, dx) - - -@internal -def _exchange(i: int128, j: int128, dx: uint256) -> uint256: - assert not self.is_killed # dev: is killed - # dx and dy are in aTokens - - xp: uint256[N_COINS] = self._balances() - precisions: uint256[N_COINS] = PRECISION_MUL - for k in range(N_COINS): - xp[k] *= precisions[k] - - x: uint256 = xp[i] + dx * precisions[i] - y: uint256 = self.get_y(i, j, x, xp) - dy: uint256 = xp[j] - y - dy_fee: uint256 = dy * self._dynamic_fee( - (xp[i] + x) / 2, (xp[j] + y) / 2, self.fee, self.offpeg_fee_multiplier - ) / FEE_DENOMINATOR - - admin_fee: uint256 = self.admin_fee - if admin_fee != 0: - dy_admin_fee: uint256 = dy_fee * admin_fee / FEE_DENOMINATOR - if dy_admin_fee != 0: - self.admin_balances[j] += dy_admin_fee / precisions[j] - - return (dy - dy_fee) / precisions[j] - - -@external -@nonreentrant('lock') -def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256) -> uint256: - """ - @notice Perform an exchange between two coins - @dev Index values can be found via the `coins` public getter method - @param i Index value for the coin to send - @param j Index valie of the coin to recieve - @param dx Amount of `i` being exchanged - @param min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - dy: uint256 = self._exchange(i, j, dx) - assert dy >= min_dy, "Exchange resulted in fewer coins than expected" - - assert ERC20(self.coins[i]).transferFrom(msg.sender, self, dx) - assert ERC20(self.coins[j]).transfer(msg.sender, dy) - - log TokenExchange(msg.sender, i, dx, j, dy) - - return dy - - -@external -@nonreentrant('lock') -def exchange_underlying(i: int128, j: int128, dx: uint256, min_dy: uint256) -> uint256: - """ - @notice Perform an exchange between two underlying coins - @dev Index values can be found via the `underlying_coins` public getter method - @param i Index value for the underlying coin to send - @param j Index valie of the underlying coin to recieve - @param dx Amount of `i` being exchanged - @param min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - dy: uint256 = self._exchange(i, j, dx) - assert dy >= min_dy, "Exchange resulted in fewer coins than expected" - - u_coin_i: address = self.underlying_coins[i] - lending_pool: address = self.aave_lending_pool - - # transfer underlying coin from msg.sender to self - _response: Bytes[32] = raw_call( - u_coin_i, - concat( - method_id("transferFrom(address,address,uint256)"), - convert(msg.sender, bytes32), - convert(self, bytes32), - convert(dx, bytes32) - ), - max_outsize=32 - ) - if len(_response) != 0: - assert convert(_response, bool) - - # deposit to aave lending pool - raw_call( - lending_pool, - concat( - method_id("deposit(address,uint256,address,uint16)"), - convert(u_coin_i, bytes32), - convert(dx, bytes32), - convert(self, bytes32), - convert(self.aave_referral, bytes32), - ) - ) - # withdraw `j` underlying from lending pool and transfer to caller - LendingPool(lending_pool).withdraw(self.underlying_coins[j], dy, msg.sender) - - log TokenExchangeUnderlying(msg.sender, i, dx, j, dy) - - return dy - - -@external -@nonreentrant('lock') -def remove_liquidity( - _amount: uint256, - _min_amounts: uint256[N_COINS], - _use_underlying: bool = False, -) -> uint256[N_COINS]: - """ - @notice Withdraw coins from the pool - @dev Withdrawal amounts are based on current deposit ratios - @param _amount Quantity of LP tokens to burn in the withdrawal - @param _min_amounts Minimum amounts of underlying coins to receive - @param _use_underlying If True, withdraw underlying assets instead of aTokens - @return List of amounts of coins that were withdrawn - """ - amounts: uint256[N_COINS] = self._balances() - lp_token: address = self.lp_token - total_supply: uint256 = ERC20(lp_token).totalSupply() - CurveToken(lp_token).burnFrom(msg.sender, _amount) # dev: insufficient funds - - lending_pool: address = ZERO_ADDRESS - if _use_underlying: - lending_pool = self.aave_lending_pool - - for i in range(N_COINS): - value: uint256 = amounts[i] * _amount / total_supply - assert value >= _min_amounts[i], "Withdrawal resulted in fewer coins than expected" - amounts[i] = value - if _use_underlying: - LendingPool(lending_pool).withdraw(self.underlying_coins[i], value, msg.sender) - else: - assert ERC20(self.coins[i]).transfer(msg.sender, value) - - log RemoveLiquidity(msg.sender, amounts, empty(uint256[N_COINS]), total_supply - _amount) - - return amounts - - -@external -@nonreentrant('lock') -def remove_liquidity_imbalance( - _amounts: uint256[N_COINS], - _max_burn_amount: uint256, - _use_underlying: bool = False -) -> uint256: - """ - @notice Withdraw coins from the pool in an imbalanced amount - @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 _use_underlying If True, withdraw underlying assets instead of aTokens - @return Actual amount of the LP token burned in the withdrawal - """ - assert not self.is_killed # dev: is killed - - amp: uint256 = self._A() - old_balances: uint256[N_COINS] = self._balances() - D0: uint256 = self.get_D_precisions(old_balances, amp) - new_balances: uint256[N_COINS] = old_balances - for i in range(N_COINS): - new_balances[i] -= _amounts[i] - D1: uint256 = self.get_D_precisions(new_balances, amp) - ys: uint256 = (D0 + D1) / N_COINS - - lp_token: address = self.lp_token - token_supply: uint256 = ERC20(lp_token).totalSupply() - assert token_supply != 0 # dev: zero total supply - - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - _feemul: uint256 = self.offpeg_fee_multiplier - _admin_fee: uint256 = self.admin_fee - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - for i in range(N_COINS): - ideal_balance: uint256 = D1 * old_balances[i] / D0 - new_balance: uint256 = new_balances[i] - difference: uint256 = 0 - if ideal_balance > new_balance: - difference = ideal_balance - new_balance - else: - difference = new_balance - ideal_balance - xs: uint256 = new_balance + old_balances[i] - fees[i] = self._dynamic_fee(xs, ys, _fee, _feemul) * difference / FEE_DENOMINATOR - if _admin_fee != 0: - self.admin_balances[i] += fees[i] * _admin_fee / FEE_DENOMINATOR - new_balances[i] -= fees[i] - D2: uint256 = self.get_D_precisions(new_balances, amp) - - token_amount: uint256 = (D0 - D2) * token_supply / D0 - assert token_amount != 0 # dev: zero tokens burned - assert token_amount <= _max_burn_amount, "Slippage screwed you" - - CurveToken(lp_token).burnFrom(msg.sender, token_amount) # dev: insufficient funds - - lending_pool: address = ZERO_ADDRESS - if _use_underlying: - lending_pool = self.aave_lending_pool - - for i in range(N_COINS): - amount: uint256 = _amounts[i] - if amount != 0: - if _use_underlying: - LendingPool(lending_pool).withdraw(self.underlying_coins[i], amount, msg.sender) - else: - assert ERC20(self.coins[i]).transfer(msg.sender, amount) - - log RemoveLiquidityImbalance(msg.sender, _amounts, fees, D1, token_supply - token_amount) - - return token_amount - - -@pure -@internal -def get_y_D(A_: uint256, i: int128, xp: uint256[N_COINS], D: uint256) -> uint256: - """ - Calculate x[i] if one reduces D from being calculated for xp to D - - Done by solving quadratic equation iteratively. - x_1**2 + x1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) - x_1**2 + b*x_1 = c - - x_1 = (x_1**2 + c) / (2*x_1 + b) - """ - # x in the input is converted to the same price/precision - - assert i >= 0 # dev: i below zero - assert i < N_COINS # dev: i above N_COINS - - Ann: uint256 = A_ * N_COINS - c: uint256 = D - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: uint256 = 0 - - for _i in range(N_COINS): - if _i != i: - _x = xp[_i] - else: - continue - S_ += _x - c = c * D / (_x * N_COINS) - c = c * D * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann - 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: - return y - else: - if y_prev - y <= 1: - return y - raise - - -@view -@internal -def _calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - # First, need to calculate - # * Get current D - # * Solve Eqn against y_i for D - _token_amount - amp: uint256 = self._A() - xp: uint256[N_COINS] = self._balances() - precisions: uint256[N_COINS] = PRECISION_MUL - - for j in range(N_COINS): - xp[j] *= precisions[j] - - D0: uint256 = self.get_D(xp, amp) - D1: uint256 = D0 - _token_amount * D0 / ERC20(self.lp_token).totalSupply() - new_y: uint256 = self.get_y_D(amp, i, xp, D1) - - xp_reduced: uint256[N_COINS] = xp - ys: uint256 = (D0 + D1) / (2 * N_COINS) - - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - feemul: uint256 = self.offpeg_fee_multiplier - for j in range(N_COINS): - dx_expected: uint256 = 0 - xavg: uint256 = 0 - if j == i: - dx_expected = xp[j] * D1 / D0 - new_y - xavg = (xp[j] + new_y) / 2 - else: - dx_expected = xp[j] - xp[j] * D1 / D0 - xavg = xp[j] - xp_reduced[j] -= self._dynamic_fee(xavg, ys, _fee, feemul) * dx_expected / FEE_DENOMINATOR - - dy: uint256 = xp_reduced[i] - self.get_y_D(amp, i, xp_reduced, D1) - - return (dy - 1) / precisions[i] - - -@view -@external -def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - """ - @notice Calculate the amount received when withdrawing a single coin - @dev Result is the same for underlying or wrapped asset withdrawals - @param _token_amount Amount of LP tokens to burn in the withdrawal - @param i Index value of the coin to withdraw - @return Amount of coin received - """ - return self._calc_withdraw_one_coin(_token_amount, i) - - -@external -@nonreentrant('lock') -def remove_liquidity_one_coin( - _token_amount: uint256, - i: int128, - _min_amount: uint256, - _use_underlying: bool = False -) -> uint256: - """ - @notice Withdraw a single coin from the pool - @param _token_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 coin to receive - @param _use_underlying If True, withdraw underlying assets instead of aTokens - @return Amount of coin received - """ - assert not self.is_killed # dev: is killed - - dy: uint256 = self._calc_withdraw_one_coin(_token_amount, i) - assert dy >= _min_amount, "Not enough coins removed" - - CurveToken(self.lp_token).burnFrom(msg.sender, _token_amount) # dev: insufficient funds - - if _use_underlying: - LendingPool(self.aave_lending_pool).withdraw(self.underlying_coins[i], dy, msg.sender) - else: - assert ERC20(self.coins[i]).transfer(msg.sender, dy) - - log RemoveLiquidityOne(msg.sender, _token_amount, dy) - - return dy - - -### Admin functions ### - -@external -def ramp_A(_future_A: uint256, _future_time: uint256): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.initial_A_time + MIN_RAMP_TIME - assert _future_time >= block.timestamp + MIN_RAMP_TIME # dev: insufficient time - - _initial_A: uint256 = self._A() - _future_A_p: uint256 = _future_A * A_PRECISION - - assert _future_A > 0 and _future_A < MAX_A - if _future_A_p < _initial_A: - assert _future_A_p * MAX_A_CHANGE >= _initial_A - else: - assert _future_A_p <= _initial_A * MAX_A_CHANGE - - self.initial_A = _initial_A - self.future_A = _future_A_p - self.initial_A_time = block.timestamp - self.future_A_time = _future_time - - log RampA(_initial_A, _future_A_p, block.timestamp, _future_time) - - -@external -def stop_ramp_A(): - assert msg.sender == self.owner # dev: only owner - - current_A: uint256 = self._A() - self.initial_A = current_A - self.future_A = current_A - self.initial_A_time = block.timestamp - self.future_A_time = block.timestamp - # now (block.timestamp < t1) is always False, so we return saved A - - log StopRampA(current_A, block.timestamp) - - -@external -def commit_new_fee(new_fee: uint256, new_admin_fee: uint256, new_offpeg_fee_multiplier: uint256): - assert msg.sender == self.owner # dev: only owner - assert self.admin_actions_deadline == 0 # dev: active action - assert new_fee <= MAX_FEE # dev: fee exceeds maximum - assert new_admin_fee <= MAX_ADMIN_FEE # dev: admin fee exceeds maximum - assert new_offpeg_fee_multiplier * new_fee <= MAX_FEE * FEE_DENOMINATOR # dev: offpeg multiplier exceeds maximum - - _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.admin_actions_deadline = _deadline - self.future_fee = new_fee - self.future_admin_fee = new_admin_fee - self.future_offpeg_fee_multiplier = new_offpeg_fee_multiplier - - log CommitNewFee(_deadline, new_fee, new_admin_fee, new_offpeg_fee_multiplier) - - -@external -def apply_new_fee(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.admin_actions_deadline # dev: insufficient time - assert self.admin_actions_deadline != 0 # dev: no active action - - self.admin_actions_deadline = 0 - _fee: uint256 = self.future_fee - _admin_fee: uint256 = self.future_admin_fee - _fml: uint256 = self.future_offpeg_fee_multiplier - self.fee = _fee - self.admin_fee = _admin_fee - self.offpeg_fee_multiplier = _fml - - log NewFee(_fee, _admin_fee, _fml) - - -@external -def revert_new_parameters(): - assert msg.sender == self.owner # dev: only owner - - self.admin_actions_deadline = 0 - - -@external -def commit_transfer_ownership(_owner: address): - assert msg.sender == self.owner # dev: only owner - assert self.transfer_ownership_deadline == 0 # dev: active transfer - - _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.transfer_ownership_deadline = _deadline - self.future_owner = _owner - - log CommitNewAdmin(_deadline, _owner) - - -@external -def apply_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.transfer_ownership_deadline # dev: insufficient time - assert self.transfer_ownership_deadline != 0 # dev: no active transfer - - self.transfer_ownership_deadline = 0 - _owner: address = self.future_owner - self.owner = _owner - - log NewAdmin(_owner) - - -@external -def revert_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - - self.transfer_ownership_deadline = 0 - - -@external -def withdraw_admin_fees(): - assert msg.sender == self.owner # dev: only owner - - for i in range(N_COINS): - value: uint256 = self.admin_balances[i] - if value != 0: - assert ERC20(self.coins[i]).transfer(msg.sender, value) - self.admin_balances[i] = 0 - - -@external -def donate_admin_fees(): - """ - Just in case admin balances somehow become higher than total (rounding error?) - this can be used to fix the state, too - """ - assert msg.sender == self.owner # dev: only owner - self.admin_balances = empty(uint256[N_COINS]) - - -@external -def kill_me(): - assert msg.sender == self.owner # dev: only owner - assert self.kill_deadline > block.timestamp # dev: deadline has passed - self.is_killed = True - - -@external -def unkill_me(): - assert msg.sender == self.owner # dev: only owner - self.is_killed = False - - -@external -def set_aave_referral(referral_code: uint256): - assert msg.sender == self.owner # dev: only owner - assert referral_code < 2 ** 16 # dev: uint16 overflow - self.aave_referral = referral_code diff --git a/contracts/pools/aave/pooldata.json b/contracts/pools/aave/pooldata.json deleted file mode 100644 index 5b4d9633..00000000 --- a/contracts/pools/aave/pooldata.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "lp_contract": "CurveTokenV3", - "pool_types": ["arate"], - "wrapped_contract": "ATokenMock", - "swap_address": "0xDeBF20617708857ebe4F679508E7b7863a8A8EeE", - "lp_token_address": "0xFd2a8fA60Abd58Efe3EeE34dd494cD491dC14900", - "gauge_addresses": ["0xd662908ADA2Ea1916B3318327A97eB18aD588b5d"], - "lp_constructor": { - "symbol": "a3CRV", - "name": "Curve.fi aDAI/aUSDC/aUSDT" - }, - "swap_constructor": { - "_A": 200, - "_fee": 4000000, - "_admin_fee": 5000000000, - "_offpeg_fee_multiplier": 20000000000, - "_aave_lending_pool": "0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9" - }, - "coins": [ - { - "name": "aDAI", - "underlying_name": "DAI", - "decimals": 18, - "wrapped_decimals": 18, - "tethered": false, - "underlying_address": "0x6b175474e89094c44da98b954eedeac495271d0f", - "wrapped_address": "0x028171bCA77440897B824Ca71D1c56caC55b68A3" - }, - { - "name": "aUSDC", - "underlying_name": "USDC", - "decimals": 6, - "wrapped_decimals": 6, - "tethered": false, - "underlying_address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", - "wrapped_address": "0xBcca60bB61934080951369a648Fb03DF4F96263C" - }, - { - "name": "aUSDT", - "underlying_name": "USDT", - "decimals": 6, - "wrapped_decimals": 6, - "tethered": true, - "underlying_address": "0xdac17f958d2ee523a2206206994597c13d831ec7", - "wrapped_address": "0x3Ed3B47Dd13EC9a98b44e6204A523E766B225811" - } - ] -} diff --git a/contracts/pools/aeth/README.md b/contracts/pools/aeth/README.md deleted file mode 100644 index 3834562e..00000000 --- a/contracts/pools/aeth/README.md +++ /dev/null @@ -1,20 +0,0 @@ -# curve-contract/contracts/pools/aeth - -[Curve ankrETH]() - -## Contracts - -- [`StableSwapAETH`](StableSwapAETH.vy): Curve stablecoin AMM contract - -## Deployments - -- [`CurveContractV3`](../../tokens/CurveTokenV3.vy): -- [`LiquidityGaugeV2`](https://github.com/curvefi/curve-dao-contracts/blob/master/contracts/gauges/LiquidityGaugeV2.vy): -- [`StableSwapAETH`](StableSwapAETH.vy): - -## Stablecoins - -Curve aETH pool supports swaps between ETH and [`Ankr`](https://github.com/Ankr-network) staked ETH (aETH): - -- `ETH`: represented in the pool as `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE` -- `aETH`: [0xE95A203B1a91a908F9B9CE46459d101078c2c3cb](https://etherscan.io/address/0xE95A203B1a91a908F9B9CE46459d101078c2c3cb#code) diff --git a/contracts/pools/aeth/RateCalculatorAETH.vy b/contracts/pools/aeth/RateCalculatorAETH.vy deleted file mode 100644 index 1edfb083..00000000 --- a/contracts/pools/aeth/RateCalculatorAETH.vy +++ /dev/null @@ -1,22 +0,0 @@ -# @version 0.2.11 -""" -@title Curve aETH Pool Rate Calculator -@author Curve.Fi -@license Copyright (c) Curve.Fi, 2021 - all rights reserved -@notice Logic for calculating exchange rate between aETH -> ETH -""" - -interface aETH: - def ratio() -> uint256: view - - -@view -@external -def get_rate(_coin: address) -> uint256: - """ - @notice Calculate the exchange rate for 1 aETH -> ETH - @param _coin The aETH contract address - @return The exchange rate of 1 aETH in ETH - """ - result: uint256 = aETH(_coin).ratio() - return 10 ** 36 / result diff --git a/contracts/pools/aeth/StableSwapAETH.vy b/contracts/pools/aeth/StableSwapAETH.vy deleted file mode 100644 index d99870b1..00000000 --- a/contracts/pools/aeth/StableSwapAETH.vy +++ /dev/null @@ -1,843 +0,0 @@ - -# @version 0.2.8 -""" -@title ETH/aETH StableSwap -@author Curve.Fi -@license Copyright (c) Curve.Fi, 2020 - all rights reserved -""" - -from vyper.interfaces import ERC20 - -# External Contracts -interface aETH: - def ratio() -> uint256: view - - -interface CurveToken: - def mint(_to: address, _value: uint256) -> bool: nonpayable - def burnFrom(_to: address, _value: uint256) -> bool: nonpayable - - -# Events -event TokenExchange: - buyer: indexed(address) - sold_id: int128 - tokens_sold: uint256 - bought_id: int128 - tokens_bought: uint256 - -event AddLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event RemoveLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - token_supply: uint256 - -event RemoveLiquidityOne: - provider: indexed(address) - token_amount: uint256 - coin_amount: uint256 - -event RemoveLiquidityImbalance: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event CommitNewAdmin: - deadline: indexed(uint256) - admin: indexed(address) - -event NewAdmin: - admin: indexed(address) - -event CommitNewFee: - deadline: indexed(uint256) - fee: uint256 - admin_fee: uint256 - -event NewFee: - fee: uint256 - admin_fee: uint256 - -event RampA: - old_A: uint256 - new_A: uint256 - initial_time: uint256 - future_time: uint256 - -event StopRampA: - A: uint256 - t: uint256 - - -# These constants must be set prior to compiling -N_COINS: constant(int128) = 2 - -# fixed constants -FEE_DENOMINATOR: constant(uint256) = 10 ** 10 -LENDING_PRECISION: constant(uint256) = 10 ** 18 -PRECISION: constant(uint256) = 10 ** 18 # The precision to convert to - -MAX_ADMIN_FEE: constant(uint256) = 10 * 10 ** 9 -MAX_FEE: constant(uint256) = 5 * 10 ** 9 -MAX_A: constant(uint256) = 10 ** 6 -MAX_A_CHANGE: constant(uint256) = 10 - -ADMIN_ACTIONS_DELAY: constant(uint256) = 3 * 86400 -MIN_RAMP_TIME: constant(uint256) = 86400 - -coins: public(address[N_COINS]) -balances: public(uint256[N_COINS]) - -fee: public(uint256) # fee * 1e10 -admin_fee: public(uint256) # admin_fee * 1e10 - -owner: public(address) -lp_token: public(address) - -A_PRECISION: constant(uint256) = 100 -initial_A: public(uint256) -future_A: public(uint256) -initial_A_time: public(uint256) -future_A_time: public(uint256) - -admin_actions_deadline: public(uint256) -transfer_ownership_deadline: public(uint256) -future_fee: public(uint256) -future_admin_fee: public(uint256) -future_owner: public(address) - -is_killed: bool -kill_deadline: uint256 -KILL_DEADLINE_DT: constant(uint256) = 2 * 30 * 86400 - - -@external -def __init__( - _owner: address, - _coins: address[N_COINS], - _pool_token: address, - _A: uint256, - _fee: uint256, - _admin_fee: uint256, -): - """ - @notice Contract constructor - @param _owner Contract owner address - @param _coins Addresses of ERC20 contracts of wrapped coins - @param _pool_token Address of the token representing LP share - @param _A Amplification coefficient multiplied by n * (n - 1) - @param _fee Fee to charge for exchanges - @param _admin_fee Admin fee - """ - - assert _coins[0] == 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE - assert _coins[1] != ZERO_ADDRESS - - self.coins = _coins - self.initial_A = _A * A_PRECISION - self.future_A = _A * A_PRECISION - self.fee = _fee - self.admin_fee = _admin_fee - self.owner = _owner - self.kill_deadline = block.timestamp + KILL_DEADLINE_DT - self.lp_token = _pool_token - - -@view -@internal -def _A() -> uint256: - """ - Handle ramping A up or down - """ - t1: uint256 = self.future_A_time - A1: uint256 = self.future_A - - if block.timestamp < t1: - A0: uint256 = self.initial_A - t0: uint256 = self.initial_A_time - # Expressions in uint256 cannot have negative numbers, thus "if" - if A1 > A0: - return A0 + (A1 - A0) * (block.timestamp - t0) / (t1 - t0) - else: - return A0 - (A0 - A1) * (block.timestamp - t0) / (t1 - t0) - - else: # when t1 == 0 or block.timestamp >= t1 - return A1 - - -@view -@external -def A() -> uint256: - return self._A() / A_PRECISION - - -@view -@external -def A_precise() -> uint256: - return self._A() - - -@view -@internal -def _stored_rates() -> uint256[N_COINS]: - return [ - convert(PRECISION, uint256), - PRECISION * LENDING_PRECISION / aETH(self.coins[1]).ratio() - ] - - -@view -@internal -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 - - -@internal -@view -def get_D(xp: uint256[N_COINS], amp: uint256) -> uint256: - S: uint256 = 0 - Dprev: uint256 = 0 - - for _x in xp: - S += _x - if S == 0: - return 0 - - D: uint256 = S - Ann: uint256 = amp * N_COINS - for _i in range(255): - D_P: uint256 = D - for _x in xp: - D_P = D_P * D / (_x * N_COINS) # If division by 0, this will be borked: only withdrawal will work. And that is good - Dprev = D - D = (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (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` - raise - - -@view -@internal -def get_D_mem(rates: uint256[N_COINS], _balances: uint256[N_COINS], amp: uint256) -> uint256: - result: uint256[N_COINS] = rates - for i in range(N_COINS): - result[i] = result[i] * _balances[i] / PRECISION - return self.get_D(result, amp) - - -@view -@external -def get_virtual_price() -> uint256: - """ - @notice The current virtual price of the pool LP token - @dev Useful for calculating profits - @return LP token virtual price normalized to 1e18 - """ - D: uint256 = self.get_D(self._xp(self._stored_rates()), self._A()) - # 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 = ERC20(self.lp_token).totalSupply() - return D * PRECISION / token_supply - - -@view -@external -def calc_token_amount(amounts: uint256[N_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 amounts Amount of each coin being deposited - @param is_deposit set True for deposits, False for withdrawals - @return Expected amount of LP tokens received - """ - amp: uint256 = self._A() - rates: uint256[N_COINS] = self._stored_rates() - _balances: uint256[N_COINS] = self.balances - D0: uint256 = self.get_D_mem(rates, _balances, amp) - for i in range(N_COINS): - _amount: uint256 = amounts[i] - if is_deposit: - _balances[i] += _amount - else: - _balances[i] -= _amount - D1: uint256 = self.get_D_mem(rates, _balances, amp) - token_amount: uint256 = ERC20(self.lp_token).totalSupply() - diff: uint256 = 0 - if is_deposit: - diff = D1 - D0 - else: - diff = D0 - D1 - return diff * token_amount / D0 - -@payable -@external -@nonreentrant('lock') -def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256) -> uint256: - """ - @notice Deposit coins into the pool - @param amounts List of amounts of coins to deposit - @param min_mint_amount Minimum amount of LP tokens to mint from the deposit - @return Amount of LP tokens received by depositing - """ - assert not self.is_killed - amp: uint256 = self._A() - rates: uint256[N_COINS] = self._stored_rates() - _lp_token: address = self.lp_token - token_supply: uint256 = ERC20(_lp_token).totalSupply() - - # Initial invariant - D0: uint256 = 0 - old_balances: uint256[N_COINS] = self.balances - if token_supply != 0: - D0 = self.get_D_mem(rates, old_balances, amp) - - new_balances: uint256[N_COINS] = old_balances - for i in range(N_COINS): - if token_supply == 0: - assert amounts[i] > 0 - new_balances[i] += amounts[i] - - # Invariant after change - D1: uint256 = self.get_D_mem(rates, new_balances, amp) - assert D1 > D0 - - # We need to recalculate the invariant accounting for fees - # to calculate fair user's share - D2: uint256 = D1 - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - mint_amount: uint256 = 0 - if token_supply != 0: - # Only account for fees if we are not the first to deposit - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - _admin_fee: uint256 = self.admin_fee - 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, amp) - mint_amount = token_supply * (D2 - D0) / D0 - else: - self.balances = new_balances - mint_amount = D1 # Take the dust if there was any - - assert mint_amount >= min_mint_amount, "Slippage screwed you" - - # Take coins from the sender - assert msg.value == amounts[0] - if amounts[1] > 0: - assert ERC20(self.coins[1]).transferFrom(msg.sender, self, amounts[1]) - - # Mint pool tokens - CurveToken(_lp_token).mint(msg.sender, mint_amount) - - log AddLiquidity(msg.sender, amounts, fees, D1, token_supply + mint_amount) - - return mint_amount - - -@view -@internal -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 # dev: same coin - assert j >= 0 # dev: j below zero - assert j < N_COINS # dev: j above N_COINS - - # should be unreachable, but good for safety - assert i >= 0 - assert i < N_COINS - - A_: uint256 = self._A() - D: uint256 = self.get_D(xp_, A_) - Ann: uint256 = A_ * N_COINS - c: uint256 = D - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: 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 * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann # - D - 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: - return y - else: - if y_prev - y <= 1: - return y - raise - - -@view -@external -def get_dy(i: int128, j: int128, dx: uint256) -> uint256: - # dx and dy in c-units - rates: uint256[N_COINS] = self._stored_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 - _fee: uint256 = self.fee * dy / FEE_DENOMINATOR - return (dy - _fee) * PRECISION / rates[j] - - -@view -@external -def get_dx(i: int128, j: int128, dy: uint256) -> uint256: - # dx and dy in c-units - rates: uint256[N_COINS] = self._stored_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 - - -@payable -@external -@nonreentrant('lock') -def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256) -> uint256: - """ - @notice Perform an exchange between two coins - @dev Index values can be found via the `coins` public getter method - @param i Index value for the coin to send - @param j Index valie of the coin to recieve - @param dx Amount of `i` being exchanged - @param min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - assert not self.is_killed # dev: is killed - - rates: uint256[N_COINS] = self._stored_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 - 1 # -1 just in case there were some rounding errors - 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 = (dy - dy_fee) * PRECISION / rates[j] - assert dy >= min_dy, "Exchange resulted in fewer coins than expected" - - coin: address = self.coins[1] - if i == 0: - assert msg.value == dx - assert ERC20(coin).transfer(msg.sender, dy) - else: - assert msg.value == 0 - assert ERC20(coin).transferFrom(msg.sender, self, dx) - raw_call(msg.sender, b"", value=dy) - - log TokenExchange(msg.sender, i, dx, j, dy) - - return dy - - -@external -@nonreentrant('lock') -def remove_liquidity(_amount: uint256, min_amounts: uint256[N_COINS]) -> uint256[N_COINS]: - """ - @notice Withdraw coins from the pool - @dev Withdrawal amounts are based on current deposit ratios - @param _amount Quantity of LP tokens to burn in the withdrawal - @param min_amounts Minimum amounts of underlying coins to receive - @return List of amounts of coins that were withdrawn - """ - _lp_token: address = self.lp_token - total_supply: uint256 = ERC20(_lp_token).totalSupply() - amounts: uint256[N_COINS] = empty(uint256[N_COINS]) - - for i in range(N_COINS): - _balance: uint256 = self.balances[i] - value: uint256 = _balance * _amount / total_supply - assert value >= min_amounts[i], "Withdrawal resulted in fewer coins than expected" - self.balances[i] = _balance - value - amounts[i] = value - if i == 0: - raw_call(msg.sender, b"", value=value) - else: - assert ERC20(self.coins[1]).transfer(msg.sender, value) - - CurveToken(_lp_token).burnFrom(msg.sender, _amount) # Will raise if not enough - - log RemoveLiquidity(msg.sender, amounts, empty(uint256[N_COINS]), total_supply - _amount) - - return amounts - - -@external -@nonreentrant('lock') -def remove_liquidity_imbalance(amounts: uint256[N_COINS], max_burn_amount: uint256) -> uint256: - """ - @notice Withdraw coins from the pool in an imbalanced amount - @param amounts List of amounts of underlying coins to withdraw - @param max_burn_amount Maximum amount of LP token to burn in the withdrawal - @return Actual amount of the LP token burned in the withdrawal - """ - assert not self.is_killed - amp: uint256 = self._A() - rates: uint256[N_COINS] = self._stored_rates() - old_balances: uint256[N_COINS] = self.balances - D0: uint256 = self.get_D_mem(rates, old_balances, amp) - - new_balances: uint256[N_COINS] = old_balances - for i in range(N_COINS): - new_balances[i] -= amounts[i] - D1: uint256 = self.get_D_mem(rates, new_balances, amp) - - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - _admin_fee: uint256 = self.admin_fee - for i in range(N_COINS): - ideal_balance: uint256 = D1 * old_balances[i] / D0 - new_balance: uint256 = new_balances[i] - difference: uint256 = 0 - if ideal_balance > new_balance: - difference = ideal_balance - new_balance - else: - difference = new_balance - ideal_balance - fees[i] = _fee * difference / FEE_DENOMINATOR - self.balances[i] = new_balance - (fees[i] * _admin_fee / FEE_DENOMINATOR) - new_balances[i] = new_balance - fees[i] - D2: uint256 = self.get_D_mem(rates, new_balances, amp) - - lp_token: address = self.lp_token - token_supply: uint256 = ERC20(lp_token).totalSupply() - token_amount: uint256 = (D0 - D2) * token_supply / D0 - assert token_amount != 0 - assert token_amount <= max_burn_amount, "Slippage screwed you" - - CurveToken(lp_token).burnFrom(msg.sender, token_amount) # dev: insufficient funds - - if amounts[0] != 0: - raw_call(msg.sender, b"", value=amounts[0]) - if amounts[1] != 0: - assert ERC20(self.coins[1]).transfer(msg.sender, amounts[1]) - - log RemoveLiquidityImbalance(msg.sender, amounts, fees, D1, token_supply - token_amount) - - return token_amount - - -@pure -@internal -def get_y_D(A_: uint256, i: int128, xp: uint256[N_COINS], D: uint256) -> uint256: - """ - Calculate x[i] if one reduces D from being calculated for xp to D - - Done by solving quadratic equation iteratively. - x_1**2 + x_1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) - x_1**2 + b*x_1 = c - - x_1 = (x_1**2 + c) / (2*x_1 + b) - """ - # x in the input is converted to the same price/precision - - assert i >= 0 # dev: i below zero - assert i < N_COINS # dev: i above N_COINS - - Ann: uint256 = A_ * N_COINS - c: uint256 = D - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: uint256 = 0 - - for _i in range(N_COINS): - if _i != i: - _x = xp[_i] - else: - continue - S_ += _x - c = c * D / (_x * N_COINS) - c = c * D * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann - 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: - return y - else: - if y_prev - y <= 1: - return y - raise - - -@view -@internal -def _calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> (uint256, uint256): - # First, need to calculate - # * Get current D - # * Solve Eqn against y_i for D - _token_amount - amp: uint256 = self._A() - rates: uint256[N_COINS] = self._stored_rates() - xp: uint256[N_COINS] = self._xp(rates) - D0: uint256 = self.get_D(xp, amp) - - total_supply: uint256 = ERC20(self.lp_token).totalSupply() - - D1: uint256 = D0 - _token_amount * D0 / total_supply - new_y: uint256 = self.get_y_D(amp, i, xp, D1) - - xp_reduced: uint256[N_COINS] = xp - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - - for j in range(N_COINS): - dx_expected: uint256 = 0 - xp_j: uint256 = xp[j] - if j == i: - dx_expected = xp_j * D1 / D0 - new_y - else: - dx_expected = xp_j - xp_j * D1 / D0 - xp_reduced[j] -= _fee * dx_expected / FEE_DENOMINATOR - - dy: uint256 = xp_reduced[i] - self.get_y_D(amp, i, xp_reduced, D1) - rate: uint256 = rates[i] - dy = (dy - 1) * PRECISION / rate # Withdraw less to account for rounding errors - dy_0: uint256 = (xp[i] - new_y) * PRECISION / rate # w/o fees - - return dy, dy_0 - dy - - -@view -@external -def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - """ - @notice Calculate the amount received when withdrawing a single coin - @param _token_amount Amount of LP tokens to burn in the withdrawal - @param i Index value of the coin to withdraw - @return Amount of coin received - """ - return self._calc_withdraw_one_coin(_token_amount, i)[0] - - -@external -@nonreentrant('lock') -def remove_liquidity_one_coin(_token_amount: uint256, i: int128, _min_amount: uint256) -> uint256: - """ - @notice Withdraw a single coin from the pool - @param _token_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 coin to receive - @return Amount of coin received - """ - assert not self.is_killed # dev: is killed - - dy: uint256 = 0 - dy_fee: uint256 = 0 - dy, dy_fee = self._calc_withdraw_one_coin(_token_amount, i) - assert dy >= _min_amount, "Not enough coins removed" - - self.balances[i] -= (dy + dy_fee * self.admin_fee / FEE_DENOMINATOR) - CurveToken(self.lp_token).burnFrom(msg.sender, _token_amount) # dev: insufficient funds - - if i == 0: - raw_call(msg.sender, b"", value=dy) - else: - assert ERC20(self.coins[1]).transfer(msg.sender, dy) - - log RemoveLiquidityOne(msg.sender, _token_amount, dy) - - return dy - - -### Admin functions ### -@external -def ramp_A(_future_A: uint256, _future_time: uint256): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.initial_A_time + MIN_RAMP_TIME - assert _future_time >= block.timestamp + MIN_RAMP_TIME # dev: insufficient time - - _initial_A: uint256 = self._A() - _future_A_p: uint256 = _future_A * A_PRECISION - - assert _future_A > 0 and _future_A < MAX_A - if _future_A_p < _initial_A: - assert _future_A_p * MAX_A_CHANGE >= _initial_A - else: - assert _future_A_p <= _initial_A * MAX_A_CHANGE - - self.initial_A = _initial_A - self.future_A = _future_A_p - self.initial_A_time = block.timestamp - self.future_A_time = _future_time - - log RampA(_initial_A, _future_A_p, block.timestamp, _future_time) - - -@external -def stop_ramp_A(): - assert msg.sender == self.owner # dev: only owner - - current_A: uint256 = self._A() - self.initial_A = current_A - self.future_A = current_A - self.initial_A_time = block.timestamp - self.future_A_time = block.timestamp - # now (block.timestamp < t1) is always False, so we return saved A - - log StopRampA(current_A, block.timestamp) - - -@external -def commit_new_fee(new_fee: uint256, new_admin_fee: uint256): - assert msg.sender == self.owner # dev: only owner - assert self.admin_actions_deadline == 0 # dev: active action - assert new_fee <= MAX_FEE # dev: fee exceeds maximum - assert new_admin_fee <= MAX_ADMIN_FEE # dev: admin fee exceeds maximum - - _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.admin_actions_deadline = _deadline - self.future_fee = new_fee - self.future_admin_fee = new_admin_fee - - log CommitNewFee(_deadline, new_fee, new_admin_fee) - - -@external -@nonreentrant('lock') -def apply_new_fee(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.admin_actions_deadline # dev: insufficient time - assert self.admin_actions_deadline != 0 # dev: no active action - - self.admin_actions_deadline = 0 - _fee: uint256 = self.future_fee - _admin_fee: uint256 = self.future_admin_fee - self.fee = _fee - self.admin_fee = _admin_fee - - log NewFee(_fee, _admin_fee) - - -@external -def revert_new_parameters(): - assert msg.sender == self.owner # dev: only owner - - self.admin_actions_deadline = 0 - - -@external -def commit_transfer_ownership(_owner: address): - assert msg.sender == self.owner # dev: only owner - assert self.transfer_ownership_deadline == 0 # dev: active transfer - - _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.transfer_ownership_deadline = _deadline - self.future_owner = _owner - - log CommitNewAdmin(_deadline, _owner) - - -@external -@nonreentrant('lock') -def apply_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.transfer_ownership_deadline # dev: insufficient time - assert self.transfer_ownership_deadline != 0 # dev: no active transfer - - self.transfer_ownership_deadline = 0 - _owner: address = self.future_owner - self.owner = _owner - - log NewAdmin(_owner) - - -@external -def revert_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - self.transfer_ownership_deadline = 0 - - -@view -@external -def admin_balances(i: uint256) -> uint256: - if i == 0: - return self.balance - self.balances[0] - return ERC20(self.coins[i]).balanceOf(self) - self.balances[i] - - -@external -@nonreentrant('lock') -def withdraw_admin_fees(): - assert msg.sender == self.owner # dev: only owner - - amount: uint256 = self.balance - self.balances[0] - if amount != 0: - raw_call(msg.sender, b"", value=amount) - - amount = ERC20(self.coins[1]).balanceOf(self) - self.balances[1] - if amount != 0: - assert ERC20(self.coins[1]).transfer(msg.sender, amount) - - -@external -@nonreentrant('lock') -def donate_admin_fees(): - assert msg.sender == self.owner # dev: only owner - for i in range(N_COINS): - if i == 0: - self.balances[0] = self.balance - else: - self.balances[i] = ERC20(self.coins[i]).balanceOf(self) - - -@external -def kill_me(): - assert msg.sender == self.owner # dev: only owner - assert self.kill_deadline > block.timestamp # dev: deadline has passed - self.is_killed = True - - -@external -def unkill_me(): - assert msg.sender == self.owner # dev: only owner - self.is_killed = False diff --git a/contracts/pools/aeth/pooldata.json b/contracts/pools/aeth/pooldata.json deleted file mode 100644 index 189fae59..00000000 --- a/contracts/pools/aeth/pooldata.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "lp_contract": "CurveTokenV3", - "pool_types": ["eth", "crate"], - "wrapped_contract": "aETH", - "swap_address": "0xA96A65c051bF88B4095Ee1f2451C2A9d43F53Ae2", - "lp_token_address": "0xaA17A236F2bAdc98DDc0Cf999AbB47D47Fc0A6Cf", - "gauge_addresses": ["0x6d10ed2cF043E6fcf51A0e7b4C2Af3Fa06695707"], - "lp_constructor": { - "symbol": "ankrCRV", - "name": "Curve.fi ETH/aETH" - }, - "swap_constructor": { - "_A": 10, - "_fee": 4000000, - "_admin_fee": 5000000000 - }, - "coins": [ - { - "name": "ETH", - "decimals": 18, - "tethered": false, - "underlying_address": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" - }, - { - "name": "ankrETH", - "underlying_name": "ETH", - "decimals": 18, - "wrapped_decimals": 18, - "tethered": false, - "wrapped_address": "0xE95A203B1a91a908F9B9CE46459d101078c2c3cb" - } - ], - "testing": { - "initial_amount": 10000 - }, - "rate_calculator_address": "0x1B0D3406F49ED85ACb907F0554e787dEB6cEac33" -} diff --git a/contracts/pools/bbtc/DepositBBTC.vy b/contracts/pools/bbtc/DepositBBTC.vy deleted file mode 100644 index 433c5f3d..00000000 --- a/contracts/pools/bbtc/DepositBBTC.vy +++ /dev/null @@ -1,366 +0,0 @@ -# @version 0.2.8 -""" -@title "Zap" Depositer for Curve bBTC pool -@author Curve.Fi -@license Copyright (c) Curve.Fi, 2020 - all rights reserved -""" - -from vyper.interfaces import ERC20 - - -interface CurveMeta: - def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256) -> 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) -> 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 base_pool() -> address: 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: int128) -> 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 - - -pool: public(address) -token: public(address) -base_pool: public(address) - -coins: public(address[N_COINS]) -base_coins: public(address[BASE_N_COINS]) - - -@external -def __init__(_pool: address, _token: address): - """ - @notice Contract constructor - @param _pool Metapool address - @param _token Pool LP token address - """ - self.pool = _pool - self.token = _token - _base_pool: address = CurveMeta(_pool).base_pool() - self.base_pool = _base_pool - - for i in range(N_COINS): - coin: address = CurveMeta(_pool).coins(i) - self.coins[i] = coin - # approve coins for infinite transfers - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("approve(address,uint256)"), - convert(_pool, bytes32), - convert(MAX_UINT256, bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) - - for i in range(BASE_N_COINS): - coin: address = CurveBase(_base_pool).coins(i) - self.base_coins[i] = coin - # approve underlying coins for infinite transfers - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("approve(address,uint256)"), - convert(_base_pool, bytes32), - convert(MAX_UINT256, bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) - - -@external -def add_liquidity(amounts: uint256[N_ALL_COINS], min_mint_amount: uint256) -> uint256: - """ - @notice Wrap underlying coins and deposit them in the pool - @param amounts List of amounts of underlying coins to deposit - @param min_mint_amount Minimum amount of LP tokens to mint from the deposit - @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 - - # Transfer all coins in - for i in range(N_ALL_COINS): - amount: uint256 = amounts[i] - if amount == 0: - continue - coin: address = ZERO_ADDRESS - if i < MAX_COIN: - coin = self.coins[i] - meta_amounts[i] = amount - else: - x: int128 = i - MAX_COIN - coin = self.base_coins[x] - base_amounts[x] = amount - deposit_base = True - # "safeTransferFrom" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transferFrom(address,address,uint256)"), - convert(msg.sender, bytes32), - convert(self, bytes32), - convert(amount, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransferFrom" - - # Deposit to the base pool - if deposit_base: - CurveBase(self.base_pool).add_liquidity(base_amounts, 0) - meta_amounts[MAX_COIN] = ERC20(self.coins[MAX_COIN]).balanceOf(self) - - # Deposit to the meta pool - CurveMeta(self.pool).add_liquidity(meta_amounts, min_mint_amount) - - # Transfer meta token back - _lp_token: address = self.token - _lp_amount: uint256 = ERC20(_lp_token).balanceOf(self) - assert ERC20(_lp_token).transfer(msg.sender, _lp_amount) - - return _lp_amount - - -@external -def remove_liquidity(_amount: uint256, min_amounts: uint256[N_ALL_COINS]) -> uint256[N_ALL_COINS]: - """ - @notice Withdraw and unwrap coins from the pool - @dev Withdrawal amounts are based on current deposit ratios - @param _amount Quantity of LP tokens to burn in the withdrawal - @param min_amounts Minimum amounts of underlying coins to receive - @return List of amounts of underlying coins that were withdrawn - """ - _token: address = self.token - assert ERC20(_token).transferFrom(msg.sender, self, _amount) - - min_amounts_meta: uint256[N_COINS] = empty(uint256[N_COINS]) - 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 - for i in range(MAX_COIN): - min_amounts_meta[i] = min_amounts[i] - CurveMeta(self.pool).remove_liquidity(_amount, min_amounts_meta) - - # Withdraw from base - _base_amount: uint256 = ERC20(self.coins[MAX_COIN]).balanceOf(self) - for i in range(BASE_N_COINS): - min_amounts_base[i] = min_amounts[MAX_COIN+i] - CurveBase(self.base_pool).remove_liquidity(_base_amount, min_amounts_base) - - # Transfer all coins out - for i in range(N_ALL_COINS): - coin: address = ZERO_ADDRESS - if i < MAX_COIN: - coin = self.coins[i] - else: - coin = self.base_coins[i - MAX_COIN] - amounts[i] = ERC20(coin).balanceOf(self) - # "safeTransfer" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(amounts[i], bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - return amounts - - -@external -def remove_liquidity_one_coin(_token_amount: uint256, i: int128, _min_amount: uint256) -> uint256: - """ - @notice Withdraw and unwrap a single coin from the pool - @param _token_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 - @return Amount of underlying coin received - """ - assert ERC20(self.token).transferFrom(msg.sender, self, _token_amount) - - coin: address = ZERO_ADDRESS - if i < MAX_COIN: - coin = self.coins[i] - # Withdraw a metapool coin - CurveMeta(self.pool).remove_liquidity_one_coin(_token_amount, i, _min_amount) - else: - coin = self.base_coins[i - MAX_COIN] - # Withdraw a base pool coin - CurveMeta(self.pool).remove_liquidity_one_coin(_token_amount, MAX_COIN, 0) - CurveBase(self.base_pool).remove_liquidity_one_coin( - ERC20(self.coins[MAX_COIN]).balanceOf(self), i-MAX_COIN, _min_amount - ) - - # Tranfer the coin out - coin_amount: uint256 = ERC20(coin).balanceOf(self) - # "safeTransfer" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(coin_amount, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - return coin_amount - - -@external -def remove_liquidity_imbalance(amounts: uint256[N_ALL_COINS], max_burn_amount: uint256) -> uint256: - """ - @notice Withdraw coins from the pool in an imbalanced amount - @param amounts List of amounts of underlying coins to withdraw - @param max_burn_amount Maximum amount of LP token to burn in the withdrawal. - This value cannot exceed the caller's LP token balance. - @return Actual amount of the LP token burned in the withdrawal - """ - _base_pool: address = self.base_pool - _meta_pool: address = self.pool - _base_coins: address[BASE_N_COINS] = self.base_coins - _meta_coins: address[N_COINS] = self.coins - _lp_token: address = self.token - - 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 - assert ERC20(_lp_token).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]) - leftover_amounts: uint256[N_COINS] = empty(uint256[N_COINS]) - - # Prepare quantities - for i in range(MAX_COIN): - amounts_meta[i] = amounts[i] - - for i in range(BASE_N_COINS): - amount: uint256 = amounts[MAX_COIN + i] - if amount != 0: - amounts_base[i] = amount - withdraw_base = True - - if withdraw_base: - amounts_meta[MAX_COIN] = CurveBase(self.base_pool).calc_token_amount(amounts_base, False) - amounts_meta[MAX_COIN] += amounts_meta[MAX_COIN] * fee / FEE_DENOMINATOR + 1 - - # Remove liquidity and deposit leftovers back - CurveMeta(_meta_pool).remove_liquidity_imbalance(amounts_meta, max_burn_amount) - if withdraw_base: - CurveBase(_base_pool).remove_liquidity_imbalance(amounts_base, amounts_meta[MAX_COIN]) - leftover_amounts[MAX_COIN] = ERC20(_meta_coins[MAX_COIN]).balanceOf(self) - if leftover_amounts[MAX_COIN] > 0: - CurveMeta(_meta_pool).add_liquidity(leftover_amounts, 0) - - # Transfer all coins out - for i in range(N_ALL_COINS): - coin: address = ZERO_ADDRESS - amount: uint256 = 0 - if i < MAX_COIN: - coin = _meta_coins[i] - amount = amounts_meta[i] - else: - coin = _base_coins[i - MAX_COIN] - amount = amounts_base[i - MAX_COIN] - # "safeTransfer" which works for ERC20s which return bool or not - if amount > 0: - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(amount, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - # Transfer the leftover LP token out - leftover: uint256 = ERC20(_lp_token).balanceOf(self) - if leftover > 0: - assert ERC20(_lp_token).transfer(msg.sender, leftover) - - return max_burn_amount - leftover - - -@view -@external -def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - """ - @notice Calculate the amount received when withdrawing and unwrapping a single coin - @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(self.pool).calc_withdraw_one_coin(_token_amount, i) - else: - _base_tokens: uint256 = CurveMeta(self.pool).calc_withdraw_one_coin(_token_amount, MAX_COIN) - return CurveBase(self.base_pool).calc_withdraw_one_coin(_base_tokens, i-MAX_COIN) - - -@view -@external -def calc_token_amount(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 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]) - - for i in range(MAX_COIN): - meta_amounts[i] = amounts[i] - - for i in range(BASE_N_COINS): - base_amounts[i] = amounts[i + MAX_COIN] - - _base_tokens: uint256 = CurveBase(self.base_pool).calc_token_amount(base_amounts, is_deposit) - meta_amounts[MAX_COIN] = _base_tokens - - return CurveMeta(self.pool).calc_token_amount(meta_amounts, is_deposit) diff --git a/contracts/pools/bbtc/README.md b/contracts/pools/bbtc/README.md deleted file mode 100644 index 8480c528..00000000 --- a/contracts/pools/bbtc/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# curve-contract/contracts/pools/bbtc - -[Curve bBTC metapool](https://www.curve.fi/bbtc), allowing swaps via the Curve [sBTC pool](../sbtc). - -## Contracts - -* [`DepositBBTC`](DepositBBTC.vy): Depositor contract, used to wrap underlying tokens prior to depositing them into the pool -* [`StableSwapBBTC`](StableSwapBBTC.vy): Curve stablecoin AMM contract - -## Deployments - -* [`CurveContractV2`](../../tokens/CurveTokenV3.vy): [0x410e3E86ef427e30B9235497143881f717d93c2A](https://etherscan.io/address/0x410e3E86ef427e30B9235497143881f717d93c2A) -* [`DepositBBTC`](DepositBBTC.vy): [0xC45b2EEe6e09cA176Ca3bB5f7eEe7C47bF93c756](https://etherscan.io/address/0xC45b2EEe6e09cA176Ca3bB5f7eEe7C47bF93c756) -* [`LiquidityGaugeV2`](https://github.com/curvefi/curve-dao-contracts/blob/master/contracts/gauges/LiquidityGaugeV2.vy): [0xdFc7AdFa664b08767b735dE28f9E84cd30492aeE](https://etherscan.io/address/0xdFc7AdFa664b08767b735dE28f9E84cd30492aeE) -* [`StableSwapBBTC`](StableSwapBBTC.vy): [0x071c661B4DeefB59E2a3DdB20Db036821eeE8F4b](https://etherscan.io/address/0x071c661B4DeefB59E2a3DdB20Db036821eeE8F4b) - -## Stablecoins - -Curve bBTC metapool utilizes the supports swaps between the following assets: - -## Direct swaps - -Direct swaps are possible between bBTC and the Curve sBTC LP token. - -* `bBTC`: [0x9be89d2a4cd102d8fecc6bf9da793be995c22541](https://etherscan.io/address/0x9be89d2a4cd102d8fecc6bf9da793be995c22541) -* `sbtcCRV`: [0x075b1bb99792c9E1041bA13afEf80C91a1e70fB3](https://etherscan.io/address/0x075b1bb99792c9E1041bA13afEf80C91a1e70fB3) - -## Base Pool coins - -The sBTC LP token may be wrapped or unwrapped to provide swaps between bBTC and the following coins: - -* `renBTC`: [0xeb4c2781e4eba804ce9a9803c67d0893436bb27d](https://etherscan.io/address/0xeb4c2781e4eba804ce9a9803c67d0893436bb27d) -* `wBTC`: [0x2260fac5e5542a773aa44fbcfedf7c193bc2c599](https://etherscan.io/address/0x2260fac5e5542a773aa44fbcfedf7c193bc2c599) -* `sBTC`: [0xfe18be6b3bd88a2d2a7f928d00292e7a9963cfc6](https://etherscan.io/address/0xfe18be6b3bd88a2d2a7f928d00292e7a9963cfc6) diff --git a/contracts/pools/bbtc/StableSwapBBTC.vy b/contracts/pools/bbtc/StableSwapBBTC.vy deleted file mode 100644 index 68cb359f..00000000 --- a/contracts/pools/bbtc/StableSwapBBTC.vy +++ /dev/null @@ -1,1076 +0,0 @@ -# @version 0.2.8 -""" -@title Curve bBTC Metapool -@author Curve.Fi -@license Copyright (c) Curve.Fi, 2020 - all rights reserved -@dev Utilizes sBTC pool to allow swaps between bBTC / rebBTC / wBTC / sBTC -""" - -from vyper.interfaces import ERC20 - -interface CurveToken: - def totalSupply() -> uint256: view - def mint(_to: address, _value: uint256) -> bool: nonpayable - def burnFrom(_to: address, _value: uint256) -> bool: nonpayable - -interface Curve: - def coins(i: int128) -> address: view - def get_virtual_price() -> uint256: view - def calc_token_amount(amounts: uint256[BASE_N_COINS], deposit: bool) -> uint256: view - def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: view - def fee() -> uint256: view - def get_dy(i: int128, j: int128, dx: uint256) -> uint256: view - def get_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256: view - def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256): nonpayable - def add_liquidity(amounts: uint256[BASE_N_COINS], min_mint_amount: uint256): nonpayable - def remove_liquidity_one_coin(_token_amount: uint256, i: int128, min_amount: uint256): nonpayable - - -# Events -event TokenExchange: - buyer: indexed(address) - sold_id: int128 - tokens_sold: uint256 - bought_id: int128 - tokens_bought: uint256 - -event TokenExchangeUnderlying: - buyer: indexed(address) - sold_id: int128 - tokens_sold: uint256 - bought_id: int128 - tokens_bought: uint256 - -event AddLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event RemoveLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - token_supply: uint256 - -event RemoveLiquidityOne: - provider: indexed(address) - token_amount: uint256 - coin_amount: uint256 - token_supply: uint256 - -event RemoveLiquidityImbalance: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event CommitNewAdmin: - deadline: indexed(uint256) - admin: indexed(address) - -event NewAdmin: - admin: indexed(address) - -event CommitNewFee: - deadline: indexed(uint256) - fee: uint256 - admin_fee: uint256 - -event NewFee: - fee: uint256 - admin_fee: uint256 - -event RampA: - old_A: uint256 - new_A: uint256 - initial_time: uint256 - future_time: uint256 - -event StopRampA: - A: uint256 - t: uint256 - - -N_COINS: constant(int128) = 2 -MAX_COIN: constant(int128) = N_COINS - 1 - -FEE_DENOMINATOR: constant(uint256) = 10 ** 10 -PRECISION: constant(uint256) = 10 ** 18 # The precision to convert to -PRECISION_MUL: constant(uint256[N_COINS]) = [10000000000, 1] -RATES: constant(uint256[N_COINS]) = [10000000000000000000000000000, 1000000000000000000] -BASE_N_COINS: constant(int128) = 3 - -# An asset which may have a transfer fee (renBTC) -FEE_ASSET: constant(address) = 0xEB4C2781e4ebA804CE9a9803C67d0893436bB27D - -MAX_ADMIN_FEE: constant(uint256) = 10 * 10 ** 9 -MAX_FEE: constant(uint256) = 5 * 10 ** 9 -MAX_A: constant(uint256) = 10 ** 6 -MAX_A_CHANGE: constant(uint256) = 10 - -ADMIN_ACTIONS_DELAY: constant(uint256) = 3 * 86400 -MIN_RAMP_TIME: constant(uint256) = 86400 - -coins: public(address[N_COINS]) -balances: public(uint256[N_COINS]) -fee: public(uint256) # fee * 1e10 -admin_fee: public(uint256) # admin_fee * 1e10 - -owner: public(address) -token: public(CurveToken) - -BASE_CACHE_EXPIRES: constant(int128) = 10 * 60 # 10 min -base_pool: public(address) -base_virtual_price: public(uint256) -base_cache_updated: public(uint256) -base_coins: public(address[BASE_N_COINS]) - -A_PRECISION: constant(uint256) = 100 -initial_A: public(uint256) -future_A: public(uint256) -initial_A_time: public(uint256) -future_A_time: public(uint256) - -admin_actions_deadline: public(uint256) -transfer_ownership_deadline: public(uint256) -future_fee: public(uint256) -future_admin_fee: public(uint256) -future_owner: public(address) - -is_killed: bool -kill_deadline: uint256 -KILL_DEADLINE_DT: constant(uint256) = 2 * 30 * 86400 - - -@external -def __init__( - _owner: address, - _coins: address[N_COINS], - _pool_token: address, - _base_pool: address, - _A: uint256, - _fee: uint256, - _admin_fee: uint256 -): - """ - @notice Contract constructor - @param _owner Contract owner address - @param _coins Addresses of ERC20 conracts of coins - @param _pool_token Address of the token representing LP share - @param _base_pool Address of the base pool (which will have a virtual price) - @param _A Amplification coefficient multiplied by n * (n - 1) - @param _fee Fee to charge for exchanges - @param _admin_fee Admin fee - """ - for i in range(N_COINS): - assert _coins[i] != ZERO_ADDRESS - self.coins = _coins - self.initial_A = _A * A_PRECISION - self.future_A = _A * A_PRECISION - self.fee = _fee - self.admin_fee = _admin_fee - self.owner = _owner - self.kill_deadline = block.timestamp + KILL_DEADLINE_DT - self.token = CurveToken(_pool_token) - - self.base_pool = _base_pool - self.base_virtual_price = Curve(_base_pool).get_virtual_price() - self.base_cache_updated = block.timestamp - for i in range(BASE_N_COINS): - _base_coin: address = Curve(_base_pool).coins(i) - self.base_coins[i] = _base_coin - - # approve underlying coins for infinite transfers - _response: Bytes[32] = raw_call( - _base_coin, - concat( - method_id("approve(address,uint256)"), - convert(_base_pool, bytes32), - convert(MAX_UINT256, bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) - - -@view -@internal -def _A() -> uint256: - """ - Handle ramping A up or down - """ - t1: uint256 = self.future_A_time - A1: uint256 = self.future_A - - if block.timestamp < t1: - A0: uint256 = self.initial_A - t0: uint256 = self.initial_A_time - # Expressions in uint256 cannot have negative numbers, thus "if" - if A1 > A0: - return A0 + (A1 - A0) * (block.timestamp - t0) / (t1 - t0) - else: - return A0 - (A0 - A1) * (block.timestamp - t0) / (t1 - t0) - - else: # when t1 == 0 or block.timestamp >= t1 - return A1 - - -@view -@external -def A() -> uint256: - return self._A() / A_PRECISION - - -@view -@external -def A_precise() -> uint256: - return self._A() - - -@view -@internal -def _xp(vp_rate: uint256) -> uint256[N_COINS]: - result: uint256[N_COINS] = RATES - result[MAX_COIN] = vp_rate # virtual price for the metacurrency - for i in range(N_COINS): - result[i] = result[i] * self.balances[i] / PRECISION - return result - - -@pure -@internal -def _xp_mem(vp_rate: uint256, _balances: uint256[N_COINS]) -> uint256[N_COINS]: - result: uint256[N_COINS] = RATES - result[MAX_COIN] = vp_rate # virtual price for the metacurrency - for i in range(N_COINS): - result[i] = result[i] * _balances[i] / PRECISION - return result - - -@internal -def _vp_rate() -> uint256: - if block.timestamp > self.base_cache_updated + BASE_CACHE_EXPIRES: - vprice: uint256 = Curve(self.base_pool).get_virtual_price() - self.base_virtual_price = vprice - self.base_cache_updated = block.timestamp - return vprice - else: - return self.base_virtual_price - - -@internal -@view -def _vp_rate_ro() -> uint256: - if block.timestamp > self.base_cache_updated + BASE_CACHE_EXPIRES: - return Curve(self.base_pool).get_virtual_price() - else: - return self.base_virtual_price - - -@pure -@internal -def get_D(xp: uint256[N_COINS], amp: uint256) -> uint256: - S: uint256 = 0 - Dprev: uint256 = 0 - for _x in xp: - S += _x - if S == 0: - return 0 - - D: uint256 = S - Ann: uint256 = amp * N_COINS - for _i in range(255): - D_P: uint256 = D - for _x in xp: - D_P = D_P * D / (_x * N_COINS) # If division by 0, this will be borked: only withdrawal will work. And that is good - Dprev = D - D = (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (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 - - -@view -@internal -def get_D_mem(vp_rate: uint256, _balances: uint256[N_COINS], amp: uint256) -> uint256: - xp: uint256[N_COINS] = self._xp_mem(vp_rate, _balances) - return self.get_D(xp, amp) - - -@view -@external -def get_virtual_price() -> uint256: - """ - @notice The current virtual price of the pool LP token - @dev Useful for calculating profits - @return LP token virtual price normalized to 1e18 - """ - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate_ro() - xp: uint256[N_COINS] = self._xp(vp_rate) - D: uint256 = self.get_D(xp, amp) - # 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 - - -@view -@external -def calc_token_amount(amounts: uint256[N_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 amounts Amount of each coin being deposited - @param is_deposit set True for deposits, False for withdrawals - @return Expected amount of LP tokens received - """ - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate_ro() - _balances: uint256[N_COINS] = self.balances - D0: uint256 = self.get_D_mem(vp_rate, _balances, amp) - for i in range(N_COINS): - if is_deposit: - _balances[i] += amounts[i] - else: - _balances[i] -= amounts[i] - D1: uint256 = self.get_D_mem(vp_rate, _balances, amp) - token_amount: uint256 = self.token.totalSupply() - diff: uint256 = 0 - if is_deposit: - diff = D1 - D0 - else: - diff = D0 - D1 - return diff * token_amount / D0 - - -@external -@nonreentrant('lock') -def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256) -> uint256: - """ - @notice Deposit coins into the pool - @param amounts List of amounts of coins to deposit - @param min_mint_amount Minimum amount of LP tokens to mint from the deposit - @return Amount of LP tokens received by depositing - """ - assert not self.is_killed # dev: is killed - - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate() - token_supply: uint256 = self.token.totalSupply() - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - _admin_fee: uint256 = self.admin_fee - - # Initial invariant - D0: uint256 = 0 - old_balances: uint256[N_COINS] = self.balances - if token_supply > 0: - D0 = self.get_D_mem(vp_rate, old_balances, amp) - new_balances: uint256[N_COINS] = old_balances - - for i in range(N_COINS): - if token_supply == 0: - assert amounts[i] > 0 # dev: initial deposit requires all coins - # balances store amounts of c-tokens - new_balances[i] = old_balances[i] + amounts[i] - - # Invariant after change - D1: uint256 = self.get_D_mem(vp_rate, new_balances, amp) - assert D1 > D0 - - # We need to recalculate the invariant accounting for fees - # to calculate fair user's share - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - 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(vp_rate, new_balances, amp) - 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 amounts[i] > 0: - assert ERC20(self.coins[i]).transferFrom(msg.sender, self, amounts[i]) # dev: failed transfer - - # Mint pool tokens - self.token.mint(msg.sender, mint_amount) - - log AddLiquidity(msg.sender, amounts, fees, D1, token_supply + mint_amount) - - return mint_amount - - -@view -@internal -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 # dev: same coin - assert j >= 0 # dev: j below zero - assert j < N_COINS # dev: j above N_COINS - - # should be unreachable, but good for safety - assert i >= 0 - assert i < N_COINS - - amp: uint256 = self._A() - D: uint256 = self.get_D(xp_, amp) - - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: uint256 = 0 - c: uint256 = D - Ann: uint256 = amp * N_COINS - - 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 * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann # - D - 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 - - -@view -@external -def get_dy(i: int128, j: int128, dx: uint256) -> uint256: - # dx and dy in c-units - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = self._vp_rate_ro() - xp: uint256[N_COINS] = self._xp(rates[MAX_COIN]) - - x: uint256 = xp[i] + (dx * rates[i] / PRECISION) - y: uint256 = self.get_y(i, j, x, xp) - dy: uint256 = xp[j] - y - 1 - _fee: uint256 = self.fee * dy / FEE_DENOMINATOR - return (dy - _fee) * PRECISION / rates[j] - - -@view -@external -def get_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256: - # dx and dy in underlying units - vp_rate: uint256 = self._vp_rate_ro() - xp: uint256[N_COINS] = self._xp(vp_rate) - precisions: uint256[N_COINS] = PRECISION_MUL - _base_pool: address = self.base_pool - - # Use base_i or base_j if they are >= 0 - base_i: int128 = i - MAX_COIN - base_j: int128 = j - MAX_COIN - meta_i: int128 = MAX_COIN - meta_j: int128 = MAX_COIN - if base_i < 0: - meta_i = i - if base_j < 0: - meta_j = j - - x: uint256 = 0 - if base_i < 0: - x = xp[i] + dx * precisions[i] - else: - if base_j < 0: - # i is from BasePool - # At first, get the amount of pool tokens - base_inputs: uint256[BASE_N_COINS] = empty(uint256[BASE_N_COINS]) - base_inputs[base_i] = dx - # Token amount transformed to underlying "dollars" - x = Curve(_base_pool).calc_token_amount(base_inputs, True) * vp_rate / PRECISION - # Accounting for deposit/withdraw fees approximately - x -= x * Curve(_base_pool).fee() / (2 * FEE_DENOMINATOR) - # Adding number of pool tokens - x += xp[MAX_COIN] - else: - # If both are from the base pool - return Curve(_base_pool).get_dy(base_i, base_j, dx) - - # This pool is involved only when in-pool assets are used - y: uint256 = self.get_y(meta_i, meta_j, x, xp) - dy: uint256 = xp[meta_j] - y - 1 - dy = (dy - self.fee * dy / FEE_DENOMINATOR) - - # If output is going via the metapool - if base_j < 0: - dy /= precisions[meta_j] - else: - # j is from BasePool - # The fee is already accounted for - dy = Curve(_base_pool).calc_withdraw_one_coin(dy * PRECISION / vp_rate, base_j) - - return dy - - -@external -@nonreentrant('lock') -def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256) -> uint256: - """ - @notice Perform an exchange between two coins - @dev Index values can be found via the `coins` public getter method - @param i Index value for the coin to send - @param j Index valie of the coin to recieve - @param dx Amount of `i` being exchanged - @param min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - assert not self.is_killed # dev: is killed - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = self._vp_rate() - - old_balances: uint256[N_COINS] = self.balances - xp: uint256[N_COINS] = self._xp_mem(rates[MAX_COIN], old_balances) - - x: uint256 = xp[i] + dx * rates[i] / PRECISION - y: uint256 = self.get_y(i, j, x, xp) - - dy: uint256 = xp[j] - y - 1 # -1 just in case there were some rounding errors - dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR - - # Convert all to real units - dy = (dy - dy_fee) * PRECISION / rates[j] - assert dy >= min_dy, "Too few coins in result" - - dy_admin_fee: uint256 = dy_fee * self.admin_fee / FEE_DENOMINATOR - dy_admin_fee = dy_admin_fee * PRECISION / rates[j] - - # Change balances exactly in same way as we change actual ERC20 coin amounts - self.balances[i] = old_balances[i] + dx - # When rounding errors happen, we undercharge admin fee in favor of LP - self.balances[j] = old_balances[j] - dy - dy_admin_fee - - assert ERC20(self.coins[i]).transferFrom(msg.sender, self, dx) - assert ERC20(self.coins[j]).transfer(msg.sender, dy) - - log TokenExchange(msg.sender, i, dx, j, dy) - - return dy - - -@external -@nonreentrant('lock') -def exchange_underlying(i: int128, j: int128, dx: uint256, min_dy: uint256) -> uint256: - """ - @notice Perform an exchange between two underlying coins - @dev Index values can be found via the `underlying_coins` public getter method - @param i Index value for the underlying coin to send - @param j Index valie of the underlying coin to recieve - @param dx Amount of `i` being exchanged - @param min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - assert not self.is_killed # dev: is killed - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = self._vp_rate() - _base_pool: address = self.base_pool - - # Use base_i or base_j if they are >= 0 - base_i: int128 = i - MAX_COIN - base_j: int128 = j - MAX_COIN - meta_i: int128 = MAX_COIN - meta_j: int128 = MAX_COIN - if base_i < 0: - meta_i = i - if base_j < 0: - meta_j = j - dy: uint256 = 0 - - # Addresses for input and output coins - input_coin: address = ZERO_ADDRESS - if base_i < 0: - input_coin = self.coins[i] - else: - input_coin = self.base_coins[base_i] - output_coin: address = ZERO_ADDRESS - if base_j < 0: - output_coin = self.coins[j] - else: - output_coin = self.base_coins[base_j] - - # Handle potential Tether fees - dx_w_fee: uint256 = dx - if input_coin == FEE_ASSET: - dx_w_fee = ERC20(FEE_ASSET).balanceOf(self) - # "safeTransferFrom" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - input_coin, - concat( - method_id("transferFrom(address,address,uint256)"), - convert(msg.sender, bytes32), - convert(self, bytes32), - convert(dx, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransferFrom" - # Handle potential Tether fees - if input_coin == FEE_ASSET: - dx_w_fee = ERC20(FEE_ASSET).balanceOf(self) - dx_w_fee - - if base_i < 0 or base_j < 0: - old_balances: uint256[N_COINS] = self.balances - xp: uint256[N_COINS] = self._xp_mem(rates[MAX_COIN], old_balances) - - x: uint256 = 0 - if base_i < 0: - x = xp[i] + dx_w_fee * rates[i] / PRECISION - else: - # i is from BasePool - # At first, get the amount of pool tokens - base_inputs: uint256[BASE_N_COINS] = empty(uint256[BASE_N_COINS]) - base_inputs[base_i] = dx_w_fee - coin_i: address = self.coins[MAX_COIN] - # Deposit and measure delta - x = ERC20(coin_i).balanceOf(self) - Curve(_base_pool).add_liquidity(base_inputs, 0) - # Need to convert pool token to "virtual" units using rates - # dx is also different now - dx_w_fee = ERC20(coin_i).balanceOf(self) - x - x = dx_w_fee * rates[MAX_COIN] / PRECISION - # Adding number of pool tokens - x += xp[MAX_COIN] - - y: uint256 = self.get_y(meta_i, meta_j, x, xp) - - # Either a real coin or token - dy = xp[meta_j] - y - 1 # -1 just in case there were some rounding errors - dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR - - # Convert all to real units - # Works for both pool coins and real coins - dy = (dy - dy_fee) * PRECISION / rates[meta_j] - - dy_admin_fee: uint256 = dy_fee * self.admin_fee / FEE_DENOMINATOR - dy_admin_fee = dy_admin_fee * PRECISION / rates[meta_j] - - # Change balances exactly in same way as we change actual ERC20 coin amounts - self.balances[meta_i] = old_balances[meta_i] + dx_w_fee - # When rounding errors happen, we undercharge admin fee in favor of LP - self.balances[meta_j] = old_balances[meta_j] - dy - dy_admin_fee - - # Withdraw from the base pool if needed - if base_j >= 0: - out_amount: uint256 = ERC20(output_coin).balanceOf(self) - Curve(_base_pool).remove_liquidity_one_coin(dy, base_j, 0) - dy = ERC20(output_coin).balanceOf(self) - out_amount - - assert dy >= min_dy, "Too few coins in result" - - else: - # If both are from the base pool - dy = ERC20(output_coin).balanceOf(self) - Curve(_base_pool).exchange(base_i, base_j, dx_w_fee, min_dy) - dy = ERC20(output_coin).balanceOf(self) - dy - - # "safeTransfer" which works for ERC20s which return bool or not - _response = raw_call( - output_coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(dy, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - log TokenExchangeUnderlying(msg.sender, i, dx, j, dy) - - return dy - - -@external -@nonreentrant('lock') -def remove_liquidity(_amount: uint256, min_amounts: uint256[N_COINS]) -> uint256[N_COINS]: - """ - @notice Withdraw coins from the pool - @dev Withdrawal amounts are based on current deposit ratios - @param _amount Quantity of LP tokens to burn in the withdrawal - @param min_amounts Minimum amounts of underlying coins to receive - @return List of amounts of coins that were withdrawn - """ - total_supply: uint256 = self.token.totalSupply() - amounts: uint256[N_COINS] = empty(uint256[N_COINS]) - fees: uint256[N_COINS] = empty(uint256[N_COINS]) # Fees are unused but we've got them historically in event - - for i in range(N_COINS): - value: uint256 = self.balances[i] * _amount / total_supply - assert value >= min_amounts[i], "Too few coins in result" - self.balances[i] -= value - amounts[i] = value - assert ERC20(self.coins[i]).transfer(msg.sender, value) - - self.token.burnFrom(msg.sender, _amount) # dev: insufficient funds - - log RemoveLiquidity(msg.sender, amounts, fees, total_supply - _amount) - - return amounts - - -@external -@nonreentrant('lock') -def remove_liquidity_imbalance(amounts: uint256[N_COINS], max_burn_amount: uint256) -> uint256: - """ - @notice Withdraw coins from the pool in an imbalanced amount - @param amounts List of amounts of underlying coins to withdraw - @param max_burn_amount Maximum amount of LP token to burn in the withdrawal - @return Actual amount of the LP token burned in the withdrawal - """ - assert not self.is_killed # dev: is killed - - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate() - - token_supply: uint256 = self.token.totalSupply() - assert token_supply != 0 # dev: zero total supply - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - _admin_fee: uint256 = self.admin_fee - - old_balances: uint256[N_COINS] = self.balances - new_balances: uint256[N_COINS] = old_balances - D0: uint256 = self.get_D_mem(vp_rate, old_balances, amp) - for i in range(N_COINS): - new_balances[i] -= amounts[i] - D1: uint256 = self.get_D_mem(vp_rate, new_balances, amp) - - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - 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(vp_rate, new_balances, amp) - - token_amount: uint256 = (D0 - D2) * token_supply / D0 - assert token_amount != 0 # dev: zero tokens burned - token_amount += 1 # In case of rounding errors - make it unfavorable for the "attacker" - assert token_amount <= max_burn_amount, "Slippage screwed you" - - self.token.burnFrom(msg.sender, token_amount) # dev: insufficient funds - for i in range(N_COINS): - if amounts[i] != 0: - assert ERC20(self.coins[i]).transfer(msg.sender, amounts[i]) - - log RemoveLiquidityImbalance(msg.sender, amounts, fees, D1, token_supply - token_amount) - - return token_amount - - -@view -@internal -def get_y_D(A_: uint256, i: int128, xp: uint256[N_COINS], D: uint256) -> uint256: - """ - Calculate x[i] if one reduces D from being calculated for xp to D - - Done by solving quadratic equation iteratively. - x_1**2 + x1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) - x_1**2 + b*x_1 = c - - x_1 = (x_1**2 + c) / (2*x_1 + b) - """ - # x in the input is converted to the same price/precision - - assert i >= 0 # dev: i below zero - assert i < N_COINS # dev: i above N_COINS - - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: uint256 = 0 - - c: uint256 = D - Ann: uint256 = A_ * N_COINS - - for _i in range(N_COINS): - if _i != i: - _x = xp[_i] - else: - continue - S_ += _x - c = c * D / (_x * N_COINS) - c = c * D * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann - 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 - - -@view -@internal -def _calc_withdraw_one_coin(_token_amount: uint256, i: int128, vp_rate: uint256) -> (uint256, uint256, uint256): - # First, need to calculate - # * Get current D - # * Solve Eqn against y_i for D - _token_amount - amp: uint256 = self._A() - xp: uint256[N_COINS] = self._xp(vp_rate) - D0: uint256 = self.get_D(xp, amp) - - total_supply: uint256 = self.token.totalSupply() - D1: uint256 = D0 - _token_amount * D0 / total_supply - new_y: uint256 = self.get_y_D(amp, i, xp, D1) - - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = vp_rate - - xp_reduced: uint256[N_COINS] = xp - dy_0: uint256 = (xp[i] - new_y) * PRECISION / rates[i] # w/o fees - - for j in range(N_COINS): - dx_expected: uint256 = 0 - if j == i: - dx_expected = xp[j] * D1 / D0 - new_y - else: - dx_expected = xp[j] - xp[j] * D1 / D0 - xp_reduced[j] -= _fee * dx_expected / FEE_DENOMINATOR - - dy: uint256 = xp_reduced[i] - self.get_y_D(amp, i, xp_reduced, D1) - dy = (dy - 1) * PRECISION / rates[i] # Withdraw less to account for rounding errors - - return dy, dy_0 - dy, total_supply - - -@view -@external -def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - """ - @notice Calculate the amount received when withdrawing a single coin - @param _token_amount Amount of LP tokens to burn in the withdrawal - @param i Index value of the coin to withdraw - @return Amount of coin received - """ - vp_rate: uint256 = self._vp_rate_ro() - return self._calc_withdraw_one_coin(_token_amount, i, vp_rate)[0] - - -@external -@nonreentrant('lock') -def remove_liquidity_one_coin(_token_amount: uint256, i: int128, _min_amount: uint256) -> uint256: - """ - @notice Withdraw a single coin from the pool - @param _token_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 coin to receive - @return Amount of coin received - """ - assert not self.is_killed # dev: is killed - - vp_rate: uint256 = self._vp_rate() - dy: uint256 = 0 - dy_fee: uint256 = 0 - total_supply: uint256 = 0 - dy, dy_fee, total_supply = self._calc_withdraw_one_coin(_token_amount, i, vp_rate) - assert dy >= _min_amount, "Not enough coins removed" - - self.balances[i] -= (dy + dy_fee * self.admin_fee / FEE_DENOMINATOR) - self.token.burnFrom(msg.sender, _token_amount) # dev: insufficient funds - assert ERC20(self.coins[i]).transfer(msg.sender, dy) - - log RemoveLiquidityOne(msg.sender, _token_amount, dy, total_supply - _token_amount) - - return dy - - -### Admin functions ### -@external -def ramp_A(_future_A: uint256, _future_time: uint256): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.initial_A_time + MIN_RAMP_TIME - assert _future_time >= block.timestamp + MIN_RAMP_TIME # dev: insufficient time - - _initial_A: uint256 = self._A() - _future_A_p: uint256 = _future_A * A_PRECISION - - assert _future_A > 0 and _future_A < MAX_A - if _future_A_p < _initial_A: - assert _future_A_p * MAX_A_CHANGE >= _initial_A - else: - assert _future_A_p <= _initial_A * MAX_A_CHANGE - - self.initial_A = _initial_A - self.future_A = _future_A_p - self.initial_A_time = block.timestamp - self.future_A_time = _future_time - - log RampA(_initial_A, _future_A_p, block.timestamp, _future_time) - - -@external -def stop_ramp_A(): - assert msg.sender == self.owner # dev: only owner - - current_A: uint256 = self._A() - self.initial_A = current_A - self.future_A = current_A - self.initial_A_time = block.timestamp - self.future_A_time = block.timestamp - # now (block.timestamp < t1) is always False, so we return saved A - - log StopRampA(current_A, block.timestamp) - - -@external -def commit_new_fee(new_fee: uint256, new_admin_fee: uint256): - assert msg.sender == self.owner # dev: only owner - assert self.admin_actions_deadline == 0 # dev: active action - assert new_fee <= MAX_FEE # dev: fee exceeds maximum - assert new_admin_fee <= MAX_ADMIN_FEE # dev: admin fee exceeds maximum - - _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.admin_actions_deadline = _deadline - self.future_fee = new_fee - self.future_admin_fee = new_admin_fee - - log CommitNewFee(_deadline, new_fee, new_admin_fee) - - -@external -def apply_new_fee(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.admin_actions_deadline # dev: insufficient time - assert self.admin_actions_deadline != 0 # dev: no active action - - self.admin_actions_deadline = 0 - _fee: uint256 = self.future_fee - _admin_fee: uint256 = self.future_admin_fee - self.fee = _fee - self.admin_fee = _admin_fee - - log NewFee(_fee, _admin_fee) - - -@external -def revert_new_parameters(): - assert msg.sender == self.owner # dev: only owner - - self.admin_actions_deadline = 0 - - -@external -def commit_transfer_ownership(_owner: address): - assert msg.sender == self.owner # dev: only owner - assert self.transfer_ownership_deadline == 0 # dev: active transfer - - _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.transfer_ownership_deadline = _deadline - self.future_owner = _owner - - log CommitNewAdmin(_deadline, _owner) - - -@external -def apply_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.transfer_ownership_deadline # dev: insufficient time - assert self.transfer_ownership_deadline != 0 # dev: no active transfer - - self.transfer_ownership_deadline = 0 - _owner: address = self.future_owner - self.owner = _owner - - log NewAdmin(_owner) - - -@external -def revert_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - - self.transfer_ownership_deadline = 0 - - -@view -@external -def admin_balances(i: uint256) -> uint256: - return ERC20(self.coins[i]).balanceOf(self) - self.balances[i] - - -@external -def withdraw_admin_fees(): - assert msg.sender == self.owner # dev: only owner - - for i in range(N_COINS): - c: address = self.coins[i] - value: uint256 = ERC20(c).balanceOf(self) - self.balances[i] - if value > 0: - assert ERC20(c).transfer(msg.sender, value) - - -@external -def donate_admin_fees(): - assert msg.sender == self.owner # dev: only owner - for i in range(N_COINS): - self.balances[i] = ERC20(self.coins[i]).balanceOf(self) - - -@external -def kill_me(): - assert msg.sender == self.owner # dev: only owner - assert self.kill_deadline > block.timestamp # dev: deadline has passed - self.is_killed = True - - -@external -def unkill_me(): - assert msg.sender == self.owner # dev: only owner - self.is_killed = False diff --git a/contracts/pools/bbtc/pooldata.json b/contracts/pools/bbtc/pooldata.json deleted file mode 100644 index 10b2ebb8..00000000 --- a/contracts/pools/bbtc/pooldata.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "base_pool": "sbtc", - "pool_types": ["meta"], - "swap_address": "0x071c661B4DeefB59E2a3DdB20Db036821eeE8F4b", - "lp_token_address": "0x410e3E86ef427e30B9235497143881f717d93c2A", - "zap_address": "0xC45b2EEe6e09cA176Ca3bB5f7eEe7C47bF93c756", - "gauge_addresses": ["0xdFc7AdFa664b08767b735dE28f9E84cd30492aeE"], - "lp_contract": "CurveTokenV3", - "lp_constructor": { - "symbol": "bBTC/sbtcCRV", - "name": "Curve.fi bBTC/sbtcCRV" - }, - "swap_constructor": { - "_A": 200, - "_fee": 4000000, - "_admin_fee": 5000000000 - }, - "coins": [ - { - "name": "bBTC", - "decimals": 8, - "tethered": false, - "wrapped": false, - "underlying_address": "0x9be89d2a4cd102d8fecc6bf9da793be995c22541" - }, - { - "name": "sbtcCRV", - "decimals": 18, - "wrapped": false, - "base_pool_token": true, - "underlying_address": "0x075b1bb99792c9E1041bA13afEf80C91a1e70fB3" - } - ], - "testing": { - "initial_amount": 100 - } -} diff --git a/contracts/pools/busd/DepositBUSD.vy b/contracts/pools/busd/DepositBUSD.vy deleted file mode 100644 index 5b8972a4..00000000 --- a/contracts/pools/busd/DepositBUSD.vy +++ /dev/null @@ -1,338 +0,0 @@ -# @version 0.1.0b17 -# A "zap" to deposit/withdraw Curve contract without too many transactions -# (c) Curve.Fi, 2020 -from vyper.interfaces import ERC20 - -# External Contracts -contract yERC20: - def totalSupply() -> uint256: constant - def allowance(_owner: address, _spender: address) -> uint256: constant - def transfer(_to: address, _value: uint256) -> bool: modifying - def transferFrom(_from: address, _to: address, _value: uint256) -> bool: modifying - def approve(_spender: address, _value: uint256) -> bool: modifying - def name() -> string[64]: constant - def symbol() -> string[32]: constant - def decimals() -> uint256: constant - def balanceOf(arg0: address) -> uint256: constant - def deposit(depositAmount: uint256): modifying - def withdraw(withdrawTokens: uint256): modifying - def getPricePerFullShare() -> uint256: constant - - - - -# Tether transfer-only ABI -contract USDT: - def transfer(_to: address, _value: uint256): modifying - def transferFrom(_from: address, _to: address, _value: uint256): modifying - - -contract Curve: - def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256): modifying - def remove_liquidity(_amount: uint256, min_amounts: uint256[N_COINS]): modifying - def remove_liquidity_imbalance(amounts: uint256[N_COINS], max_burn_amount: uint256): modifying - def balances(i: int128) -> uint256: constant - def A() -> uint256: constant - def fee() -> uint256: constant - def owner() -> address: constant - - -N_COINS: constant(int128) = 4 -TETHERED: constant(bool[N_COINS]) = [False, False, True, False] -ZERO256: constant(uint256) = 0 # This hack is really bad XXX -ZEROS: constant(uint256[N_COINS]) = [ZERO256, ZERO256, ZERO256, ZERO256] # <- change -LENDING_PRECISION: constant(uint256) = 10 ** 18 -PRECISION: constant(uint256) = 10 ** 18 -PRECISION_MUL: constant(uint256[N_COINS]) = [convert(1, uint256), convert(1000000000000, uint256), convert(1000000000000, uint256), convert(1, uint256)] -FEE_DENOMINATOR: constant(uint256) = 10 ** 10 -FEE_IMPRECISION: constant(uint256) = 25 * 10 ** 8 # % of the fee - -coins: public(address[N_COINS]) -underlying_coins: public(address[N_COINS]) -curve: public(address) -token: public(address) - - -@public -def __init__(_coins: address[N_COINS], _underlying_coins: address[N_COINS], - _curve: address, _token: address): - self.coins = _coins - self.underlying_coins = _underlying_coins - self.curve = _curve - self.token = _token - - -@public -@nonreentrant('lock') -def add_liquidity(uamounts: uint256[N_COINS], min_mint_amount: uint256): - tethered: bool[N_COINS] = TETHERED - amounts: uint256[N_COINS] = ZEROS - - for i in range(N_COINS): - uamount: uint256 = uamounts[i] - - if uamount > 0: - # Transfer the underlying coin from owner - if tethered[i]: - USDT(self.underlying_coins[i]).transferFrom( - msg.sender, self, uamount) - else: - assert_modifiable(ERC20(self.underlying_coins[i])\ - .transferFrom(msg.sender, self, uamount)) - - # Mint if needed - ERC20(self.underlying_coins[i]).approve(self.coins[i], uamount) - yERC20(self.coins[i]).deposit(uamount) - amounts[i] = yERC20(self.coins[i]).balanceOf(self) - ERC20(self.coins[i]).approve(self.curve, amounts[i]) - - Curve(self.curve).add_liquidity(amounts, min_mint_amount) - - tokens: uint256 = ERC20(self.token).balanceOf(self) - assert_modifiable(ERC20(self.token).transfer(msg.sender, tokens)) - - -@private -def _send_all(_addr: address, min_uamounts: uint256[N_COINS], one: int128): - tethered: bool[N_COINS] = TETHERED - - for i in range(N_COINS): - if (one < 0) or (i == one): - _coin: address = self.coins[i] - _balance: uint256 = yERC20(_coin).balanceOf(self) - if _balance == 0: # Do nothing for 0 coins - continue - yERC20(_coin).withdraw(_balance) - - _ucoin: address = self.underlying_coins[i] - _uamount: uint256 = ERC20(_ucoin).balanceOf(self) - assert _uamount >= min_uamounts[i], "Not enough coins withdrawn" - - if tethered[i]: - USDT(_ucoin).transfer(_addr, _uamount) - else: - assert_modifiable(ERC20(_ucoin).transfer(_addr, _uamount)) - - -@public -@nonreentrant('lock') -def remove_liquidity(_amount: uint256, min_uamounts: uint256[N_COINS]): - zeros: uint256[N_COINS] = ZEROS - - assert_modifiable(ERC20(self.token).transferFrom(msg.sender, self, _amount)) - Curve(self.curve).remove_liquidity(_amount, zeros) - - self._send_all(msg.sender, min_uamounts, -1) - - -@public -@nonreentrant('lock') -def remove_liquidity_imbalance(uamounts: uint256[N_COINS], max_burn_amount: uint256): - """ - Get max_burn_amount in, remove requested liquidity and transfer back what is left - """ - tethered: bool[N_COINS] = TETHERED - _token: address = self.token - - amounts: uint256[N_COINS] = uamounts - for i in range(N_COINS): - if amounts[i] > 0: - rate: uint256 = yERC20(self.coins[i]).getPricePerFullShare() - amounts[i] = amounts[i] * LENDING_PRECISION / rate - - # Transfrer max tokens in - _tokens: uint256 = ERC20(_token).balanceOf(msg.sender) - if _tokens > max_burn_amount: - _tokens = max_burn_amount - assert_modifiable(ERC20(_token).transferFrom(msg.sender, self, _tokens)) - - Curve(self.curve).remove_liquidity_imbalance(amounts, max_burn_amount) - - # Transfer unused tokens back - _tokens = ERC20(_token).balanceOf(self) - assert_modifiable(ERC20(_token).transfer(msg.sender, _tokens)) - - # Unwrap and transfer all the coins we've got - self._send_all(msg.sender, ZEROS, -1) - - -@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(A: uint256, 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 = 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_y(A: uint256, i: int128, _xp: uint256[N_COINS], D: uint256) -> uint256: - """ - Calculate x[i] if one reduces D from being calculated for _xp to D - - Done by solving quadratic equation iteratively. - x_1**2 + x1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) - x_1**2 + b*x_1 = c - - x_1 = (x_1**2 + c) / (2*x_1 + b) - """ - # x in the input is converted to the same price/precision - - assert (i >= 0) and (i < N_COINS) - - c: uint256 = D - S_: uint256 = 0 - Ann: uint256 = A * N_COINS - - _x: uint256 = 0 - for _i in range(N_COINS): - if _i != i: - _x = _xp[_i] - else: - continue - S_ += _x - c = c * D / (_x * N_COINS) - c = c * D / (Ann * N_COINS) - b: uint256 = S_ + D / Ann - 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 - - -@private -@constant -def _calc_withdraw_one_coin(_token_amount: uint256, i: int128, rates: uint256[N_COINS]) -> uint256: - # First, need to calculate - # * Get current D - # * Solve Eqn against y_i for D - _token_amount - crv: address = self.curve - A: uint256 = Curve(crv).A() - fee: uint256 = Curve(crv).fee() * N_COINS / (4 * (N_COINS - 1)) - fee += fee * FEE_IMPRECISION / FEE_DENOMINATOR # Overcharge to account for imprecision - precisions: uint256[N_COINS] = PRECISION_MUL - total_supply: uint256 = ERC20(self.token).totalSupply() - - xp: uint256[N_COINS] = PRECISION_MUL - S: uint256 = 0 - for j in range(N_COINS): - xp[j] *= Curve(crv).balances(j) - xp[j] = xp[j] * rates[j] / LENDING_PRECISION - S += xp[j] - - D0: uint256 = self.get_D(A, xp) - D1: uint256 = D0 - _token_amount * D0 / total_supply - xp_reduced: uint256[N_COINS] = xp - - # xp = xp - fee * | xp * D1 / D0 - (xp - S * dD / D0 * (0, ... 1, ..0))| - for j in range(N_COINS): - dx_expected: uint256 = 0 - b_ideal: uint256 = xp[j] * D1 / D0 - b_expected: uint256 = xp[j] - if j == i: - b_expected -= S * (D0 - D1) / D0 - if b_ideal >= b_expected: - dx_expected += (b_ideal - b_expected) - else: - dx_expected += (b_expected - b_ideal) - xp_reduced[j] -= fee * dx_expected / FEE_DENOMINATOR - - dy: uint256 = xp_reduced[i] - self.get_y(A, i, xp_reduced, D1) - dy = dy / precisions[i] - - return dy - - -@public -@constant -def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - rates: uint256[N_COINS] = ZEROS - - for j in range(N_COINS): - rates[j] = yERC20(self.coins[j]).getPricePerFullShare() - - return self._calc_withdraw_one_coin(_token_amount, i, rates) - - -@public -@nonreentrant('lock') -def remove_liquidity_one_coin(_token_amount: uint256, i: int128, min_uamount: uint256, donate_dust: bool = False): - """ - Remove _amount of liquidity all in a form of coin i - """ - rates: uint256[N_COINS] = ZEROS - _token: address = self.token - - for j in range(N_COINS): - rates[j] = yERC20(self.coins[j]).getPricePerFullShare() - - dy: uint256 = self._calc_withdraw_one_coin(_token_amount, i, rates) - assert dy >= min_uamount, "Not enough coins removed" - - assert_modifiable( - ERC20(self.token).transferFrom(msg.sender, self, _token_amount)) - - amounts: uint256[N_COINS] = ZEROS - amounts[i] = dy * LENDING_PRECISION / rates[i] - token_amount_before: uint256 = ERC20(_token).balanceOf(self) - Curve(self.curve).remove_liquidity_imbalance(amounts, _token_amount) - - # Unwrap and transfer all the coins we've got - self._send_all(msg.sender, ZEROS, i) - - if not donate_dust: - # Transfer unused tokens back - token_amount_after: uint256 = ERC20(_token).balanceOf(self) - if token_amount_after > token_amount_before: - assert_modifiable(ERC20(_token).transfer( - msg.sender, token_amount_after - token_amount_before) - ) - - -@public -@nonreentrant('lock') -def withdraw_donated_dust(): - owner: address = Curve(self.curve).owner() - assert msg.sender == owner - - _token: address = self.token - assert_modifiable( - ERC20(_token).transfer(owner, ERC20(_token).balanceOf(self))) diff --git a/contracts/pools/busd/README.md b/contracts/pools/busd/README.md deleted file mode 100644 index a2256c5f..00000000 --- a/contracts/pools/busd/README.md +++ /dev/null @@ -1,33 +0,0 @@ -# curve-contract/contracts/pools/busd - -[Curve BUSD pool](https://www.curve.fi/busd), with lending on [yearn.finance](https://yearn.finance/). - -## Contracts - -* [`DepositBUSD`](DepositBUSD.vy): Depositor contract, used to wrap underlying tokens prior to depositing them into the pool -* [`StableSwapBUSD`](StableSwapBUSD.vy): Curve stablecoin AMM contract - -## Deployments - -* [`CurveContractV1`](../../tokens/CurveTokenV1.vy): [0x3B3Ac5386837Dc563660FB6a0937DFAa5924333B](https://etherscan.io/address/0x3B3Ac5386837Dc563660FB6a0937DFAa5924333B) -* [`DepositBUSD`](DepositBUSD.vy): [0xb6c057591e073249f2d9d88ba59a46cfc9b59edb](https://etherscan.io/address/0xb6c057591e073249f2d9d88ba59a46cfc9b59edb) -* [`LiquidityGauge`](../../gauges/LiquidityGauge.vy): [0x69Fb7c45726cfE2baDeE8317005d3F94bE838840](https://etherscan.io/address/0x69fb7c45726cfe2badee8317005d3f94be838840) -* [`StableSwapBUSD`](StableSwapBUSD.vy): [0x79a8C46DeA5aDa233ABaFFD40F3A0A2B1e5A4F27](https://etherscan.io/address/0x79a8C46DeA5aDa233ABaFFD40F3A0A2B1e5A4F27) - -## Stablecoins - -Curve BUSD pool supports swaps between the following stablecoins: - -### Wrapped - -* `yDAI`: [0xc2cb1040220768554cf699b0d863a3cd4324ce32](https://etherscan.io/address/0xc2cb1040220768554cf699b0d863a3cd4324ce32) -* `yUSDC`: [0x26ea744e5b887e5205727f55dfbe8685e3b21951](https://etherscan.io/address/0x26ea744e5b887e5205727f55dfbe8685e3b21951) -* `yUSDT`: [0xe6354ed5bc4b393a5aad09f21c46e101e692d447](https://etherscan.io/address/0xe6354ed5bc4b393a5aad09f21c46e101e692d447) -* `yBUSD`: [0x04bc0ab673d88ae9dbc9da2380cb6b79c4bca9ae](https://etherscan.io/address/0x04bc0ab673d88ae9dbc9da2380cb6b79c4bca9ae) - -### Underlying - -* `DAI`: [0x6b175474e89094c44da98b954eedeac495271d0f](https://etherscan.io/token/0x6b175474e89094c44da98b954eedeac495271d0f) -* `USDC`: [0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48](https://etherscan.io/token/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48) -* `USDT`: [0xdac17f958d2ee523a2206206994597c13d831ec7](https://etherscan.io/address/0xdac17f958d2ee523a2206206994597c13d831ec7) -* `BUSD`: [0x4fabb145d64652a948d72533023f6e7a623c7c53](https://etherscan.io/address/0x4fabb145d64652a948d72533023f6e7a623c7c53) diff --git a/contracts/pools/busd/StableSwapBUSD.vy b/contracts/pools/busd/StableSwapBUSD.vy deleted file mode 100644 index 14d20ed4..00000000 --- a/contracts/pools/busd/StableSwapBUSD.vy +++ /dev/null @@ -1,624 +0,0 @@ -# @version 0.1.0b16 -# (c) Curve.Fi, 2020 - - -# External Contracts -contract ERC20m: - def totalSupply() -> uint256: constant - def allowance(_owner: address, _spender: address) -> uint256: constant - def transfer(_to: address, _value: uint256) -> bool: modifying - def transferFrom(_from: address, _to: address, _value: uint256) -> bool: modifying - def approve(_spender: address, _value: uint256) -> bool: modifying - def mint(_to: address, _value: uint256): modifying - def burn(_value: uint256): modifying - def burnFrom(_to: address, _value: uint256): modifying - def name() -> string[64]: constant - def symbol() -> string[32]: constant - def decimals() -> uint256: constant - def balanceOf(arg0: address) -> uint256: constant - def set_minter(_minter: address): modifying - - - -# External Contracts -contract yERC20: - def totalSupply() -> uint256: constant - def allowance(_owner: address, _spender: address) -> uint256: constant - def transfer(_to: address, _value: uint256) -> bool: modifying - def transferFrom(_from: address, _to: address, _value: uint256) -> bool: modifying - def approve(_spender: address, _value: uint256) -> bool: modifying - def name() -> string[64]: constant - def symbol() -> string[32]: constant - def decimals() -> uint256: constant - def balanceOf(arg0: address) -> uint256: constant - def deposit(depositAmount: uint256): modifying - def withdraw(withdrawTokens: uint256): modifying - def getPricePerFullShare() -> uint256: constant - - -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 - -# This can (and needs to) be changed at compile time -N_COINS: constant(int128) = 4 # <- change - -ZERO256: constant(uint256) = 0 # This hack is really bad XXX -ZEROS: constant(uint256[N_COINS]) = [ZERO256, ZERO256, ZERO256, ZERO256] # <- change - -TETHERED: constant(bool[N_COINS]) = [False, False, True, False] - -FEE_DENOMINATOR: constant(uint256) = 10 ** 10 -PRECISION: constant(uint256) = 10 ** 18 # The precision to convert to -PRECISION_MUL: constant(uint256[N_COINS]) = [convert(1, uint256), convert(1000000000000, uint256), convert(1000000000000, uint256), convert(1, uint256)] -# PRECISION_MUL: constant(uint256[N_COINS]) = [ -# PRECISION / convert(10 ** 18, uint256), # DAI -# PRECISION / convert(10 ** 6, uint256), # USDC -# PRECISION / convert(10 ** 6, uint256), # USDT -# PRECISION / convert(10 ** 18, uint256)] # TUSD - -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]) -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], - _pool_token: address, - _A: uint256, _fee: uint256): - """ - _coins: Addresses of ERC20 contracts of coins (y-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.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 _stored_rates() -> uint256[N_COINS]: - result: uint256[N_COINS] = PRECISION_MUL - for i in range(N_COINS): - result[i] *= yERC20(self.coins[i]).getPricePerFullShare() - 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._stored_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._stored_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 - - 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._stored_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): - assert_modifiable( - yERC20(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._stored_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._stored_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._stored_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._stored_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._stored_rates() - dy: uint256 = self._exchange(i, j, dx, rates) - assert dy >= min_dy, "Exchange resulted in fewer coins than expected" - - assert_modifiable(yERC20(self.coins[i]).transferFrom(msg.sender, self, dx)) - - assert_modifiable(yERC20(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._stored_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" - 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)) - ERC20(self.underlying_coins[i]).approve(self.coins[i], dx) - yERC20(self.coins[i]).deposit(dx) - 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 - - 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 - assert_modifiable(yERC20(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 - - 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._stored_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): - assert_modifiable(yERC20(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) - - -### 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 - - for i in range(N_COINS): - c: address = self.coins[i] - value: uint256 = yERC20(c).balanceOf(self) - self.balances[i] - if value > 0: - assert_modifiable(yERC20(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 diff --git a/contracts/pools/busd/pooldata.json b/contracts/pools/busd/pooldata.json deleted file mode 100644 index 25bc5810..00000000 --- a/contracts/pools/busd/pooldata.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "lp_contract": "CurveTokenV1", - "wrapped_contract": "yERC20", - "swap_address": "0x79a8C46DeA5aDa233ABaFFD40F3A0A2B1e5A4F27", - "lp_token_address": "0x3B3Ac5386837Dc563660FB6a0937DFAa5924333B", - "zap_address": "0xb6c057591e073249f2d9d88ba59a46cfc9b59edb", - "gauge_addresses": ["0x69Fb7c45726cfE2baDeE8317005d3F94bE838840"], - "lp_constructor": { - "name": "Curve.fi yDAI/yUSDC/yUSDT/yBUSD", - "symbol": "yDAI+yUSDC+yUSDT+yBUSD" - }, - "coins": [ - { - "name": "yDAI", - "underlying_name": "DAI", - "decimals": 18, - "tethered": false, - "wrapped_decimals": 18, - "underlying_address": "0x6b175474e89094c44da98b954eedeac495271d0f", - "wrapped_address": "0xc2cb1040220768554cf699b0d863a3cd4324ce32" - }, - { - "name": "yUSDC", - "underlying_name": "USDC", - "decimals": 6, - "tethered": false, - "wrapped_decimals": 6, - "underlying_address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", - "wrapped_address": "0x26ea744e5b887e5205727f55dfbe8685e3b21951" - }, - { - "name": "yUSDT", - "underlying_name": "USDT", - "decimals": 6, - "tethered": true, - "wrapped_decimals": 6, - "underlying_address": "0xdac17f958d2ee523a2206206994597c13d831ec7", - "wrapped_address": "0xe6354ed5bc4b393a5aad09f21c46e101e692d447" - }, - { - "name": "yBUSD", - "underlying_name": "BUSD", - "decimals": 18, - "tethered": false, - "wrapped_decimals": 18, - "underlying_address": "0x4fabb145d64652a948d72533023f6e7a623c7c53", - "wrapped_address": "0x04bc0ab673d88ae9dbc9da2380cb6b79c4bca9ae" - } - ] -} diff --git a/contracts/pools/compound/DepositCompound.vy b/contracts/pools/compound/DepositCompound.vy deleted file mode 100644 index 821a2e7e..00000000 --- a/contracts/pools/compound/DepositCompound.vy +++ /dev/null @@ -1,371 +0,0 @@ -# @version 0.1.0b16 -# A "zap" to deposit/withdraw Curve contract without too many transactions -# (c) Curve.Fi, 2020 -from vyper.interfaces import ERC20 - -# External Contracts -contract cERC20: - def totalSupply() -> uint256: constant - def allowance(_owner: address, _spender: address) -> uint256: constant - def transfer(_to: address, _value: uint256) -> bool: modifying - def transferFrom(_from: address, _to: address, _value: uint256) -> bool: modifying - def approve(_spender: address, _value: uint256) -> bool: modifying - def burn(_value: uint256): modifying - def burnFrom(_to: address, _value: uint256): modifying - def name() -> string[64]: constant - def symbol() -> string[32]: constant - def decimals() -> uint256: constant - def balanceOf(arg0: address) -> uint256: constant - def mint(mintAmount: uint256) -> uint256: modifying - def redeem(redeemTokens: uint256) -> uint256: modifying - def redeemUnderlying(redeemAmount: uint256) -> uint256: modifying - def exchangeRateStored() -> uint256: constant - def exchangeRateCurrent() -> uint256: modifying - def supplyRatePerBlock() -> uint256: constant - def accrualBlockNumber() -> uint256: constant - - -# Tether transfer-only ABI -contract USDT: - def transfer(_to: address, _value: uint256): modifying - def transferFrom(_from: address, _to: address, _value: uint256): modifying - - -contract Curve: - def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256): modifying - def remove_liquidity(_amount: uint256, min_amounts: uint256[N_COINS]): modifying - def remove_liquidity_imbalance(amounts: uint256[N_COINS], max_burn_amount: uint256): modifying - def balances(i: int128) -> uint256: constant - def A() -> uint256: constant - def fee() -> uint256: constant - def owner() -> address: constant - - -N_COINS: constant(int128) = 2 -TETHERED: constant(bool[N_COINS]) = [False, False] -USE_LENDING: constant(bool[N_COINS]) = [True, True] -ZERO256: constant(uint256) = 0 # This hack is really bad XXX -ZEROS: constant(uint256[N_COINS]) = [ZERO256, ZERO256] # <- change -LENDING_PRECISION: constant(uint256) = 10 ** 18 -PRECISION: constant(uint256) = 10 ** 18 -PRECISION_MUL: constant(uint256[N_COINS]) = [convert(1, uint256), convert(1000000000000, uint256)] -FEE_DENOMINATOR: constant(uint256) = 10 ** 10 -FEE_IMPRECISION: constant(uint256) = 25 * 10 ** 8 # % of the fee - -coins: public(address[N_COINS]) -underlying_coins: public(address[N_COINS]) -curve: public(address) -token: public(address) - - -@public -def __init__(_coins: address[N_COINS], _underlying_coins: address[N_COINS], - _curve: address, _token: address): - self.coins = _coins - self.underlying_coins = _underlying_coins - self.curve = _curve - self.token = _token - - -@public -@nonreentrant('lock') -def add_liquidity(uamounts: uint256[N_COINS], min_mint_amount: uint256): - use_lending: bool[N_COINS] = USE_LENDING - tethered: bool[N_COINS] = TETHERED - amounts: uint256[N_COINS] = ZEROS - - for i in range(N_COINS): - uamount: uint256 = uamounts[i] - - if uamount > 0: - # Transfer the underlying coin from owner - if tethered[i]: - USDT(self.underlying_coins[i]).transferFrom( - msg.sender, self, uamount) - else: - assert_modifiable(ERC20(self.underlying_coins[i])\ - .transferFrom(msg.sender, self, uamount)) - - # Mint if needed - if use_lending[i]: - ERC20(self.underlying_coins[i]).approve(self.coins[i], uamount) - ok: uint256 = cERC20(self.coins[i]).mint(uamount) - if ok > 0: - raise "Could not mint coin" - amounts[i] = cERC20(self.coins[i]).balanceOf(self) - ERC20(self.coins[i]).approve(self.curve, amounts[i]) - else: - amounts[i] = uamount - ERC20(self.underlying_coins[i]).approve(self.curve, uamount) - - Curve(self.curve).add_liquidity(amounts, min_mint_amount) - - tokens: uint256 = ERC20(self.token).balanceOf(self) - assert_modifiable(ERC20(self.token).transfer(msg.sender, tokens)) - - -@private -def _send_all(_addr: address, min_uamounts: uint256[N_COINS], one: int128): - use_lending: bool[N_COINS] = USE_LENDING - tethered: bool[N_COINS] = TETHERED - - for i in range(N_COINS): - if (one < 0) or (i == one): - if use_lending[i]: - _coin: address = self.coins[i] - _balance: uint256 = cERC20(_coin).balanceOf(self) - if _balance == 0: # Do nothing if there are 0 coins - continue - ok: uint256 = cERC20(_coin).redeem(_balance) - if ok > 0: - raise "Could not redeem coin" - - _ucoin: address = self.underlying_coins[i] - _uamount: uint256 = ERC20(_ucoin).balanceOf(self) - assert _uamount >= min_uamounts[i], "Not enough coins withdrawn" - - # Send only if we have something to send - if _uamount >= 0: - if tethered[i]: - USDT(_ucoin).transfer(_addr, _uamount) - else: - assert_modifiable(ERC20(_ucoin).transfer(_addr, _uamount)) - - -@public -@nonreentrant('lock') -def remove_liquidity(_amount: uint256, min_uamounts: uint256[N_COINS]): - zeros: uint256[N_COINS] = ZEROS - - assert_modifiable(ERC20(self.token).transferFrom(msg.sender, self, _amount)) - Curve(self.curve).remove_liquidity(_amount, zeros) - - self._send_all(msg.sender, min_uamounts, -1) - - -@public -@nonreentrant('lock') -def remove_liquidity_imbalance(uamounts: uint256[N_COINS], max_burn_amount: uint256): - """ - Get max_burn_amount in, remove requested liquidity and transfer back what is left - """ - use_lending: bool[N_COINS] = USE_LENDING - tethered: bool[N_COINS] = TETHERED - _token: address = self.token - - amounts: uint256[N_COINS] = uamounts - for i in range(N_COINS): - if use_lending[i] and amounts[i] > 0: - rate: uint256 = cERC20(self.coins[i]).exchangeRateCurrent() - amounts[i] = amounts[i] * LENDING_PRECISION / rate - # if not use_lending - all good already - - # Transfrer max tokens in - _tokens: uint256 = ERC20(_token).balanceOf(msg.sender) - if _tokens > max_burn_amount: - _tokens = max_burn_amount - assert_modifiable(ERC20(_token).transferFrom(msg.sender, self, _tokens)) - - Curve(self.curve).remove_liquidity_imbalance(amounts, max_burn_amount) - - # Transfer unused tokens back - _tokens = ERC20(_token).balanceOf(self) - assert_modifiable(ERC20(_token).transfer(msg.sender, _tokens)) - - # Unwrap and transfer all the coins we've got - self._send_all(msg.sender, ZEROS, -1) - - -@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(A: uint256, 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 = 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_y(A: uint256, i: int128, _xp: uint256[N_COINS], D: uint256) -> uint256: - """ - Calculate x[i] if one reduces D from being calculated for _xp to D - - Done by solving quadratic equation iteratively. - x_1**2 + x1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) - x_1**2 + b*x_1 = c - - x_1 = (x_1**2 + c) / (2*x_1 + b) - """ - # x in the input is converted to the same price/precision - - assert (i >= 0) and (i < N_COINS) - - c: uint256 = D - S_: uint256 = 0 - Ann: uint256 = A * N_COINS - - _x: uint256 = 0 - for _i in range(N_COINS): - if _i != i: - _x = _xp[_i] - else: - continue - S_ += _x - c = c * D / (_x * N_COINS) - c = c * D / (Ann * N_COINS) - b: uint256 = S_ + D / Ann - 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 - - -@private -@constant -def _calc_withdraw_one_coin(_token_amount: uint256, i: int128, rates: uint256[N_COINS]) -> uint256: - # First, need to calculate - # * Get current D - # * Solve Eqn against y_i for D - _token_amount - use_lending: bool[N_COINS] = USE_LENDING - # tethered: bool[N_COINS] = TETHERED - crv: address = self.curve - A: uint256 = Curve(crv).A() - fee: uint256 = Curve(crv).fee() * N_COINS / (4 * (N_COINS - 1)) - fee += fee * FEE_IMPRECISION / FEE_DENOMINATOR # Overcharge to account for imprecision - precisions: uint256[N_COINS] = PRECISION_MUL - total_supply: uint256 = ERC20(self.token).totalSupply() - - xp: uint256[N_COINS] = PRECISION_MUL - S: uint256 = 0 - for j in range(N_COINS): - xp[j] *= Curve(crv).balances(j) - if use_lending[j]: - # Use stored rate b/c we have imprecision anyway - xp[j] = xp[j] * rates[j] / LENDING_PRECISION - S += xp[j] - # if not use_lending - all good already - - D0: uint256 = self.get_D(A, xp) - D1: uint256 = D0 - _token_amount * D0 / total_supply - xp_reduced: uint256[N_COINS] = xp - - # xp = xp - fee * | xp * D1 / D0 - (xp - S * dD / D0 * (0, ... 1, ..0))| - for j in range(N_COINS): - dx_expected: uint256 = 0 - b_ideal: uint256 = xp[j] * D1 / D0 - b_expected: uint256 = xp[j] - if j == i: - b_expected -= S * (D0 - D1) / D0 - if b_ideal >= b_expected: - dx_expected = (b_ideal - b_expected) - else: - dx_expected = (b_expected - b_ideal) - xp_reduced[j] -= fee * dx_expected / FEE_DENOMINATOR - - dy: uint256 = xp_reduced[i] - self.get_y(A, i, xp_reduced, D1) - dy = dy / precisions[i] - - return dy - - -@public -@constant -def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - rates: uint256[N_COINS] = ZEROS - use_lending: bool[N_COINS] = USE_LENDING - - for j in range(N_COINS): - if use_lending[j]: - rates[j] = cERC20(self.coins[j]).exchangeRateStored() - else: - rates[j] = 10 ** 18 - - return self._calc_withdraw_one_coin(_token_amount, i, rates) - - -@public -@nonreentrant('lock') -def remove_liquidity_one_coin(_token_amount: uint256, i: int128, min_uamount: uint256, donate_dust: bool = False): - """ - Remove _amount of liquidity all in a form of coin i - """ - use_lending: bool[N_COINS] = USE_LENDING - rates: uint256[N_COINS] = ZEROS - _token: address = self.token - - for j in range(N_COINS): - if use_lending[j]: - rates[j] = cERC20(self.coins[j]).exchangeRateCurrent() - else: - rates[j] = LENDING_PRECISION - - dy: uint256 = self._calc_withdraw_one_coin(_token_amount, i, rates) - assert dy >= min_uamount, "Not enough coins removed" - - assert_modifiable( - ERC20(self.token).transferFrom(msg.sender, self, _token_amount)) - - amounts: uint256[N_COINS] = ZEROS - amounts[i] = dy * LENDING_PRECISION / rates[i] - token_amount_before: uint256 = ERC20(_token).balanceOf(self) - Curve(self.curve).remove_liquidity_imbalance(amounts, _token_amount) - - # Unwrap and transfer all the coins we've got - self._send_all(msg.sender, ZEROS, i) - - if not donate_dust: - # Transfer unused tokens back - token_amount_after: uint256 = ERC20(_token).balanceOf(self) - if token_amount_after > token_amount_before: - assert_modifiable(ERC20(_token).transfer( - msg.sender, token_amount_after - token_amount_before) - ) - - -@public -@nonreentrant('lock') -def withdraw_donated_dust(): - owner: address = Curve(self.curve).owner() - assert msg.sender == owner - - _token: address = self.token - assert_modifiable( - ERC20(_token).transfer(owner, ERC20(_token).balanceOf(self))) diff --git a/contracts/pools/compound/README.md b/contracts/pools/compound/README.md deleted file mode 100644 index 73defd9f..00000000 --- a/contracts/pools/compound/README.md +++ /dev/null @@ -1,29 +0,0 @@ -# curve-contract/contracts/pools/compound - -[Curve Compound pool](https://www.curve.fi/compound), with lending on [Compound](https://compound.finance/). - -## Contracts - -* [`DepositCompound`](DepositCompound.vy): Depositor contract, used to wrap underlying tokens prior to depositing them into the pool -* [`StableSwapCompound`](StableSwapCompound.vy): Curve stablecoin AMM contract - -## Deployments - -* [`CurveContractV1`](../../tokens/CurveTokenV1.vy): [0x845838DF265Dcd2c412A1Dc9e959c7d08537f8a2](https://etherscan.io/address/0x845838DF265Dcd2c412A1Dc9e959c7d08537f8a2) -* [`DepositCompound`](DepositCompound.vy): [0xeb21209ae4c2c9ff2a86aca31e123764a3b6bc06](https://etherscan.io/address/0xeb21209ae4c2c9ff2a86aca31e123764a3b6bc06) -* [`LiquidityGauge`](../../gauges/LiquidityGauge.vy): [0x7ca5b0a2910B33e9759DC7dDB0413949071D7575](https://etherscan.io/address/0x7ca5b0a2910b33e9759dc7ddb0413949071d7575) -* [`StableSwapCompound`](StableSwapCompound.vy): [0xA2B47E3D5c44877cca798226B7B8118F9BFb7A56](https://etherscan.io/address/0xA2B47E3D5c44877cca798226B7B8118F9BFb7A56) - -## Stablecoins - -Curve Compound pool supports swaps between the following stablecoins: - -### Wrapped - -* `cDAI`: [0x5d3a536e4d6dbd6114cc1ead35777bab948e3643](https://etherscan.io/token/0x5d3a536e4d6dbd6114cc1ead35777bab948e3643) -* `cUSDC`: [0x39aa39c021dfbae8fac545936693ac917d5e7563](https://etherscan.io/token/0x39aa39c021dfbae8fac545936693ac917d5e7563) - -### Underlying - -* `DAI`: [0x6b175474e89094c44da98b954eedeac495271d0f](https://etherscan.io/token/0x6b175474e89094c44da98b954eedeac495271d0f) -* `USDC`: [0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48](https://etherscan.io/token/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48) diff --git a/contracts/pools/compound/StableSwapCompound.vy b/contracts/pools/compound/StableSwapCompound.vy deleted file mode 100644 index 37b8c0e3..00000000 --- a/contracts/pools/compound/StableSwapCompound.vy +++ /dev/null @@ -1,685 +0,0 @@ -# @version 0.1.0b16 -# (c) Curve.Fi, 2020 - - -# External Contracts -contract ERC20m: - def totalSupply() -> uint256: constant - def allowance(_owner: address, _spender: address) -> uint256: constant - def transfer(_to: address, _value: uint256) -> bool: modifying - def transferFrom(_from: address, _to: address, _value: uint256) -> bool: modifying - def approve(_spender: address, _value: uint256) -> bool: modifying - def mint(_to: address, _value: uint256): modifying - def burn(_value: uint256): modifying - def burnFrom(_to: address, _value: uint256): modifying - def name() -> string[64]: constant - def symbol() -> string[32]: constant - def decimals() -> uint256: constant - def balanceOf(arg0: address) -> uint256: constant - def set_minter(_minter: address): modifying - - - -# External Contracts -contract cERC20: - def totalSupply() -> uint256: constant - def allowance(_owner: address, _spender: address) -> uint256: constant - def transfer(_to: address, _value: uint256) -> bool: modifying - def transferFrom(_from: address, _to: address, _value: uint256) -> bool: modifying - def approve(_spender: address, _value: uint256) -> bool: modifying - def burn(_value: uint256): modifying - def burnFrom(_to: address, _value: uint256): modifying - def name() -> string[64]: constant - def symbol() -> string[32]: constant - def decimals() -> uint256: constant - def balanceOf(arg0: address) -> uint256: constant - def mint(mintAmount: uint256) -> uint256: modifying - def redeem(redeemTokens: uint256) -> uint256: modifying - def redeemUnderlying(redeemAmount: uint256) -> uint256: modifying - def exchangeRateStored() -> uint256: constant - def exchangeRateCurrent() -> uint256: modifying - def supplyRatePerBlock() -> uint256: constant - def accrualBlockNumber() -> uint256: constant - - -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 - - -# This can (and needs to) be changed at compile time -N_COINS: constant(int128) = 2 # <- change - -ZERO256: constant(uint256) = 0 # This hack is really bad XXX -ZEROS: constant(uint256[N_COINS]) = [ZERO256, ZERO256] # <- change - -USE_LENDING: constant(bool[N_COINS]) = [True, True] - -# Flag "ERC20s" which don't return from transfer() and transferFrom() -TETHERED: constant(bool[N_COINS]) = [False, False] - -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]) = [convert(1, uint256), convert(1000000000000, uint256)] -# 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]) -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], - _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.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 _stored_rates() -> uint256[N_COINS]: - # exchangeRateStored * (1 + supplyRatePerBlock * (getBlockNumber - accrualBlockNumber) / 1e18) - result: uint256[N_COINS] = PRECISION_MUL - use_lending: bool[N_COINS] = USE_LENDING - for i in range(N_COINS): - rate: uint256 = LENDING_PRECISION # Used with no lending - if use_lending[i]: - rate = cERC20(self.coins[i]).exchangeRateStored() - supply_rate: uint256 = cERC20(self.coins[i]).supplyRatePerBlock() - old_block: uint256 = cERC20(self.coins[i]).accrualBlockNumber() - rate += rate * supply_rate * (block.number - old_block) / LENDING_PRECISION - result[i] *= rate - return result - - -@private -def _current_rates() -> uint256[N_COINS]: - result: uint256[N_COINS] = PRECISION_MUL - use_lending: bool[N_COINS] = USE_LENDING - for i in range(N_COINS): - rate: uint256 = LENDING_PRECISION # Used with no lending - if use_lending[i]: - rate = cERC20(self.coins[i]).exchangeRateCurrent() - 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._stored_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._stored_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( - cERC20(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._stored_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._stored_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._stored_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._stored_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(cERC20(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(cERC20(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 - - ok: uint256 = 0 - 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) - ok = cERC20(self.coins[i]).mint(dx) - if ok > 0: - raise "Could not mint coin" - if use_lending[j]: - ok = cERC20(self.coins[j]).redeem(dy_) - if ok > 0: - raise "Could not redeem coin" - 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(cERC20(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(cERC20(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) - - -### 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 = cERC20(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(cERC20(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 diff --git a/contracts/pools/compound/pooldata.json b/contracts/pools/compound/pooldata.json deleted file mode 100644 index 9a1e15bc..00000000 --- a/contracts/pools/compound/pooldata.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "lp_contract": "CurveTokenV1", - "pool_types": ["crate"], - "wrapped_contract": "cERC20", - "swap_address": "0xA2B47E3D5c44877cca798226B7B8118F9BFb7A56", - "lp_token_address": "0x845838DF265Dcd2c412A1Dc9e959c7d08537f8a2", - "zap_address": "0xeb21209ae4c2c9ff2a86aca31e123764a3b6bc06", - "gauge_addresses": ["0x7ca5b0a2910B33e9759DC7dDB0413949071D7575"], - "lp_constructor": { - "name": "Curve.fi cDAI/cUSDC", - "symbol": "cDAI+cUSDC" - }, - "coins": [ - { - "name": "cDAI", - "underlying_name": "DAI", - "decimals": 18, - "tethered": false, - "wrapped_decimals": 8, - "underlying_address": "0x6b175474e89094c44da98b954eedeac495271d0f", - "wrapped_address": "0x5d3a536e4d6dbd6114cc1ead35777bab948e3643" - }, - { - "name": "cUSDC", - "underlying_name": "USDC", - "decimals": 6, - "tethered": false, - "wrapped_decimals": 8, - "underlying_address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", - "wrapped_address": "0x39aa39c021dfbae8fac545936693ac917d5e7563" - } - ] -} diff --git a/contracts/pools/dusd/DepositDUSD.vy b/contracts/pools/dusd/DepositDUSD.vy deleted file mode 100644 index 3cbcdaa2..00000000 --- a/contracts/pools/dusd/DepositDUSD.vy +++ /dev/null @@ -1,376 +0,0 @@ -# @version 0.2.7 -""" -@title "Zap" Depositer for Curve DUSD pool -@author Curve.Fi -@license Copyright (c) Curve.Fi, 2020 - all rights reserved -""" - -from vyper.interfaces import ERC20 - - -interface CurveMeta: - def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256) -> 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) -> 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 base_pool() -> address: 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 - -# An asset which may have a transfer fee (USDT) -FEE_ASSET: constant(address) = 0xdAC17F958D2ee523a2206206994597C13D831ec7 - -FEE_DENOMINATOR: constant(uint256) = 10 ** 10 -FEE_IMPRECISION: constant(uint256) = 100 * 10 ** 8 # % of the fee - - -pool: public(address) -token: public(address) -base_pool: public(address) - -coins: public(address[N_COINS]) -base_coins: public(address[BASE_N_COINS]) - - -@external -def __init__(_pool: address, _token: address): - """ - @notice Contract constructor - @param _pool Metapool address - @param _token Pool LP token address - """ - self.pool = _pool - self.token = _token - _base_pool: address = CurveMeta(_pool).base_pool() - self.base_pool = _base_pool - - for i in range(N_COINS): - coin: address = CurveMeta(_pool).coins(convert(i, uint256)) - self.coins[i] = coin - # approve coins for infinite transfers - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("approve(address,uint256)"), - convert(_pool, bytes32), - convert(MAX_UINT256, bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) - - for i in range(BASE_N_COINS): - coin: address = CurveBase(_base_pool).coins(convert(i, uint256)) - self.base_coins[i] = coin - # approve underlying coins for infinite transfers - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("approve(address,uint256)"), - convert(_base_pool, bytes32), - convert(MAX_UINT256, bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) - - -@external -def add_liquidity(amounts: uint256[N_ALL_COINS], min_mint_amount: uint256) -> uint256: - """ - @notice Wrap underlying coins and deposit them in the pool - @param amounts List of amounts of underlying coins to deposit - @param min_mint_amount Minimum amount of LP tokens to mint from the deposit - @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 - - # Transfer all coins in - for i in range(N_ALL_COINS): - amount: uint256 = amounts[i] - if amount == 0: - continue - coin: address = ZERO_ADDRESS - if i < MAX_COIN: - coin = self.coins[i] - meta_amounts[i] = amount - else: - x: int128 = i - MAX_COIN - coin = self.base_coins[x] - base_amounts[x] = amount - deposit_base = True - # "safeTransferFrom" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transferFrom(address,address,uint256)"), - convert(msg.sender, bytes32), - convert(self, bytes32), - convert(amount, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransferFrom" - # Handle potential Tether fees - if coin == FEE_ASSET: - amount = ERC20(FEE_ASSET).balanceOf(self) - if i < MAX_COIN: - meta_amounts[i] = amount - else: - base_amounts[i - MAX_COIN] = amount - - # Deposit to the base pool - if deposit_base: - CurveBase(self.base_pool).add_liquidity(base_amounts, 0) - meta_amounts[MAX_COIN] = ERC20(self.coins[MAX_COIN]).balanceOf(self) - - # Deposit to the meta pool - CurveMeta(self.pool).add_liquidity(meta_amounts, min_mint_amount) - - # Transfer meta token back - _lp_token: address = self.token - _lp_amount: uint256 = ERC20(_lp_token).balanceOf(self) - assert ERC20(_lp_token).transfer(msg.sender, _lp_amount) - - return _lp_amount - - -@external -def remove_liquidity(_amount: uint256, min_amounts: uint256[N_ALL_COINS]) -> uint256[N_ALL_COINS]: - """ - @notice Withdraw and unwrap coins from the pool - @dev Withdrawal amounts are based on current deposit ratios - @param _amount Quantity of LP tokens to burn in the withdrawal - @param min_amounts Minimum amounts of underlying coins to receive - @return List of amounts of underlying coins that were withdrawn - """ - _token: address = self.token - assert ERC20(_token).transferFrom(msg.sender, self, _amount) - - min_amounts_meta: uint256[N_COINS] = empty(uint256[N_COINS]) - 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 - for i in range(MAX_COIN): - min_amounts_meta[i] = min_amounts[i] - CurveMeta(self.pool).remove_liquidity(_amount, min_amounts_meta) - - # Withdraw from base - _base_amount: uint256 = ERC20(self.coins[MAX_COIN]).balanceOf(self) - for i in range(BASE_N_COINS): - min_amounts_base[i] = min_amounts[MAX_COIN+i] - CurveBase(self.base_pool).remove_liquidity(_base_amount, min_amounts_base) - - # Transfer all coins out - for i in range(N_ALL_COINS): - coin: address = ZERO_ADDRESS - if i < MAX_COIN: - coin = self.coins[i] - else: - coin = self.base_coins[i - MAX_COIN] - amounts[i] = ERC20(coin).balanceOf(self) - # "safeTransfer" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(amounts[i], bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - return amounts - - -@external -def remove_liquidity_one_coin(_token_amount: uint256, i: int128, _min_amount: uint256) -> uint256: - """ - @notice Withdraw and unwrap a single coin from the pool - @param _token_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 - @return Amount of underlying coin received - """ - assert ERC20(self.token).transferFrom(msg.sender, self, _token_amount) - - coin: address = ZERO_ADDRESS - if i < MAX_COIN: - coin = self.coins[i] - # Withdraw a metapool coin - CurveMeta(self.pool).remove_liquidity_one_coin(_token_amount, i, _min_amount) - else: - coin = self.base_coins[i - MAX_COIN] - # Withdraw a base pool coin - CurveMeta(self.pool).remove_liquidity_one_coin(_token_amount, MAX_COIN, 0) - CurveBase(self.base_pool).remove_liquidity_one_coin( - ERC20(self.coins[MAX_COIN]).balanceOf(self), i-MAX_COIN, _min_amount - ) - - # Tranfer the coin out - coin_amount: uint256 = ERC20(coin).balanceOf(self) - # "safeTransfer" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(coin_amount, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - return coin_amount - - -@external -def remove_liquidity_imbalance(amounts: uint256[N_ALL_COINS], max_burn_amount: uint256) -> uint256: - """ - @notice Withdraw coins from the pool in an imbalanced amount - @param amounts List of amounts of underlying coins to withdraw - @param max_burn_amount Maximum amount of LP token to burn in the withdrawal. - This value cannot exceed the caller's LP token balance. - @return Actual amount of the LP token burned in the withdrawal - """ - _base_pool: address = self.base_pool - _meta_pool: address = self.pool - _base_coins: address[BASE_N_COINS] = self.base_coins - _meta_coins: address[N_COINS] = self.coins - _lp_token: address = self.token - - 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 - assert ERC20(_lp_token).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]) - leftover_amounts: uint256[N_COINS] = empty(uint256[N_COINS]) - - # Prepare quantities - for i in range(MAX_COIN): - amounts_meta[i] = amounts[i] - - for i in range(BASE_N_COINS): - amount: uint256 = amounts[MAX_COIN + i] - if amount != 0: - amounts_base[i] = amount - withdraw_base = True - - if withdraw_base: - amounts_meta[MAX_COIN] = CurveBase(self.base_pool).calc_token_amount(amounts_base, False) - amounts_meta[MAX_COIN] += amounts_meta[MAX_COIN] * fee / FEE_DENOMINATOR + 1 - - # Remove liquidity and deposit leftovers back - CurveMeta(_meta_pool).remove_liquidity_imbalance(amounts_meta, max_burn_amount) - if withdraw_base: - CurveBase(_base_pool).remove_liquidity_imbalance(amounts_base, amounts_meta[MAX_COIN]) - leftover_amounts[MAX_COIN] = ERC20(_meta_coins[MAX_COIN]).balanceOf(self) - if leftover_amounts[MAX_COIN] > 0: - CurveMeta(_meta_pool).add_liquidity(leftover_amounts, 0) - - # Transfer all coins out - for i in range(N_ALL_COINS): - coin: address = ZERO_ADDRESS - amount: uint256 = 0 - if i < MAX_COIN: - coin = _meta_coins[i] - amount = amounts_meta[i] - else: - coin = _base_coins[i - MAX_COIN] - amount = amounts_base[i - MAX_COIN] - # "safeTransfer" which works for ERC20s which return bool or not - if amount > 0: - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(amount, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - # Transfer the leftover LP token out - leftover: uint256 = ERC20(_lp_token).balanceOf(self) - if leftover > 0: - assert ERC20(_lp_token).transfer(msg.sender, leftover) - - return max_burn_amount - leftover - - -@view -@external -def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - """ - @notice Calculate the amount received when withdrawing and unwrapping a single coin - @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(self.pool).calc_withdraw_one_coin(_token_amount, i) - else: - _base_tokens: uint256 = CurveMeta(self.pool).calc_withdraw_one_coin(_token_amount, MAX_COIN) - return CurveBase(self.base_pool).calc_withdraw_one_coin(_base_tokens, i-MAX_COIN) - - -@view -@external -def calc_token_amount(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 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]) - - for i in range(MAX_COIN): - meta_amounts[i] = amounts[i] - - for i in range(BASE_N_COINS): - base_amounts[i] = amounts[i + MAX_COIN] - - _base_tokens: uint256 = CurveBase(self.base_pool).calc_token_amount(base_amounts, is_deposit) - meta_amounts[MAX_COIN] = _base_tokens - - return CurveMeta(self.pool).calc_token_amount(meta_amounts, is_deposit) diff --git a/contracts/pools/dusd/README.md b/contracts/pools/dusd/README.md deleted file mode 100644 index 1a36dcf2..00000000 --- a/contracts/pools/dusd/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# curve-contract/contracts/pools/dusd - -[Curve DUSD metapool](https://www.curve.fi/dusd), allowing swaps via the Curve [tri-pool](../3pool). - -## Contracts - -* [`DepositDUSD`](DepositDUSD.vy): Depositor contract, used to wrap underlying tokens prior to depositing them into the pool -* [`StableSwapDUSD`](StableSwapDUSD.vy): Curve stablecoin AMM contract - -## Deployments - -* [`CurveContractV2`](../../tokens/CurveTokenV2.vy): [0x3a664Ab939FD8482048609f652f9a0B0677337B9](https://etherscan.io/address/0x3a664Ab939FD8482048609f652f9a0B0677337B9) -* [`DepositDUSD`](DepositDUSD.vy): [0x61E10659fe3aa93d036d099405224E4Ac24996d0](https://etherscan.io/address/0x61E10659fe3aa93d036d099405224E4Ac24996d0) -* [`LiquidityGaugeReward`](../../gauges/LiquidityGaugeReward.vy): [0xAEA6c312f4b3E04D752946d329693F7293bC2e6D](https://etherscan.io/address/0xAEA6c312f4b3E04D752946d329693F7293bC2e6D) -* [`StableSwapDUSD`](StableSwapDUSD.vy): [0x8038C01A0390a8c547446a0b2c18fc9aEFEcc10c](https://etherscan.io/address/0x8038C01A0390a8c547446a0b2c18fc9aEFEcc10c) - -## Stablecoins - -Curve DUSD metapool utilizes the supports swaps between the following assets: - -## Direct swaps - -Direct swaps are possible between DUSD and the Curve tri-pool LP token. - -* `DUSD`: [0x5bc25f649fc4e26069ddf4cf4010f9f706c23831](https://etherscan.io/address/0x5bc25f649fc4e26069ddf4cf4010f9f706c23831) -* `3CRV`: [0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490](https://etherscan.io/address/0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490) - -## Base Pool coins - -The tri-pool LP token may be wrapped or unwrapped to provide swaps between DUSD and the following stablecoins: - -* `DAI`: [0x6b175474e89094c44da98b954eedeac495271d0f](https://etherscan.io/address/0x6b175474e89094c44da98b954eedeac495271d0f) -* `USDC`: [0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48](https://etherscan.io/address/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48) -* `USDT`: [0xdac17f958d2ee523a2206206994597c13d831ec7](https://etherscan.io/address/0xdac17f958d2ee523a2206206994597c13d831ec7) diff --git a/contracts/pools/dusd/StableSwapDUSD.vy b/contracts/pools/dusd/StableSwapDUSD.vy deleted file mode 100644 index 61960363..00000000 --- a/contracts/pools/dusd/StableSwapDUSD.vy +++ /dev/null @@ -1,1083 +0,0 @@ -# @version 0.2.7 -""" -@title Curve DUSD Metapool -@author Curve.Fi -@license Copyright (c) Curve.Fi, 2020 - all rights reserved -@dev Utilizes 3Pool to allow swaps between DUSD / DAI / USDC / USDT -""" - -from vyper.interfaces import ERC20 - -interface CurveToken: - def totalSupply() -> uint256: view - def mint(_to: address, _value: uint256) -> bool: nonpayable - def burnFrom(_to: address, _value: uint256) -> bool: nonpayable - - -interface Curve: - def coins(i: uint256) -> address: view - def get_virtual_price() -> uint256: view - def calc_token_amount(amounts: uint256[BASE_N_COINS], deposit: bool) -> uint256: view - def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: view - def fee() -> uint256: view - def get_dy(i: int128, j: int128, dx: uint256) -> uint256: view - def get_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256: view - def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256): nonpayable - def add_liquidity(amounts: uint256[BASE_N_COINS], min_mint_amount: uint256): nonpayable - def remove_liquidity_one_coin(_token_amount: uint256, i: int128, min_amount: uint256): nonpayable - - -# Events -event TokenExchange: - buyer: indexed(address) - sold_id: int128 - tokens_sold: uint256 - bought_id: int128 - tokens_bought: uint256 - -event TokenExchangeUnderlying: - buyer: indexed(address) - sold_id: int128 - tokens_sold: uint256 - bought_id: int128 - tokens_bought: uint256 - -event AddLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event RemoveLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - token_supply: uint256 - -event RemoveLiquidityOne: - provider: indexed(address) - token_amount: uint256 - coin_amount: uint256 - token_supply: uint256 - -event RemoveLiquidityImbalance: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event CommitNewAdmin: - deadline: indexed(uint256) - admin: indexed(address) - -event NewAdmin: - admin: indexed(address) - -event CommitNewFee: - deadline: indexed(uint256) - fee: uint256 - admin_fee: uint256 - -event NewFee: - fee: uint256 - admin_fee: uint256 - -event RampA: - old_A: uint256 - new_A: uint256 - initial_time: uint256 - future_time: uint256 - -event StopRampA: - A: uint256 - t: uint256 - - -N_COINS: constant(int128) = 2 -MAX_COIN: constant(int128) = N_COINS - 1 - -FEE_DENOMINATOR: constant(uint256) = 10 ** 10 -PRECISION: constant(uint256) = 10 ** 18 # The precision to convert to -PRECISION_MUL: constant(uint256[N_COINS]) = [1, 1] -RATES: constant(uint256[N_COINS]) = [1000000000000000000, 1000000000000000000] - -BASE_N_COINS: constant(int128) = 3 -N_ALL_COINS: constant(int128) = N_COINS + BASE_N_COINS - 1 -BASE_PRECISION_MUL: constant(uint256[BASE_N_COINS]) = [1, 1000000000000, 1000000000000] -BASE_RATES: constant(uint256[BASE_N_COINS]) = [1000000000000000000, 1000000000000000000000000000000, 1000000000000000000000000000000] - -# An asset which may have a transfer fee (USDT) -FEE_ASSET: constant(address) = 0xdAC17F958D2ee523a2206206994597C13D831ec7 - -MAX_ADMIN_FEE: constant(uint256) = 10 * 10 ** 9 -MAX_FEE: constant(uint256) = 5 * 10 ** 9 -MAX_A: constant(uint256) = 10 ** 6 -MAX_A_CHANGE: constant(uint256) = 10 - -ADMIN_ACTIONS_DELAY: constant(uint256) = 3 * 86400 -MIN_RAMP_TIME: constant(uint256) = 86400 - -coins: public(address[N_COINS]) -balances: public(uint256[N_COINS]) -fee: public(uint256) # fee * 1e10 -admin_fee: public(uint256) # admin_fee * 1e10 - -owner: public(address) -token: CurveToken - -# Token corresponding to the pool is always the last one -BASE_POOL_COINS: constant(int128) = 3 -BASE_CACHE_EXPIRES: constant(int128) = 10 * 60 # 10 min -base_pool: public(address) -base_virtual_price: public(uint256) -base_cache_updated: public(uint256) -base_coins: public(address[BASE_POOL_COINS]) - -A_PRECISION: constant(uint256) = 100 -initial_A: public(uint256) -future_A: public(uint256) -initial_A_time: public(uint256) -future_A_time: public(uint256) - -admin_actions_deadline: public(uint256) -transfer_ownership_deadline: public(uint256) -future_fee: public(uint256) -future_admin_fee: public(uint256) -future_owner: public(address) - -is_killed: bool -kill_deadline: uint256 -KILL_DEADLINE_DT: constant(uint256) = 2 * 30 * 86400 - - -@external -def __init__( - _owner: address, - _coins: address[N_COINS], - _pool_token: address, - _base_pool: address, - _A: uint256, - _fee: uint256, - _admin_fee: uint256 -): - """ - @notice Contract constructor - @param _owner Contract owner address - @param _coins Addresses of ERC20 conracts of coins - @param _pool_token Address of the token representing LP share - @param _base_pool Address of the base pool (which will have a virtual price) - @param _A Amplification coefficient multiplied by n * (n - 1) - @param _fee Fee to charge for exchanges - @param _admin_fee Admin fee - """ - for i in range(N_COINS): - assert _coins[i] != ZERO_ADDRESS - self.coins = _coins - self.initial_A = _A * A_PRECISION - self.future_A = _A * A_PRECISION - self.fee = _fee - self.admin_fee = _admin_fee - self.owner = _owner - self.kill_deadline = block.timestamp + KILL_DEADLINE_DT - self.token = CurveToken(_pool_token) - - self.base_pool = _base_pool - self.base_virtual_price = Curve(_base_pool).get_virtual_price() - self.base_cache_updated = block.timestamp - for i in range(BASE_POOL_COINS): - _base_coin: address = Curve(_base_pool).coins(convert(i, uint256)) - self.base_coins[i] = _base_coin - - # approve underlying coins for infinite transfers - _response: Bytes[32] = raw_call( - _base_coin, - concat( - method_id("approve(address,uint256)"), - convert(_base_pool, bytes32), - convert(MAX_UINT256, bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) - - -@view -@internal -def _A() -> uint256: - """ - Handle ramping A up or down - """ - t1: uint256 = self.future_A_time - A1: uint256 = self.future_A - - if block.timestamp < t1: - A0: uint256 = self.initial_A - t0: uint256 = self.initial_A_time - # Expressions in uint256 cannot have negative numbers, thus "if" - if A1 > A0: - return A0 + (A1 - A0) * (block.timestamp - t0) / (t1 - t0) - else: - return A0 - (A0 - A1) * (block.timestamp - t0) / (t1 - t0) - - else: # when t1 == 0 or block.timestamp >= t1 - return A1 - - -@view -@external -def A() -> uint256: - return self._A() / A_PRECISION - - -@view -@external -def A_precise() -> uint256: - return self._A() - - -@view -@internal -def _xp(vp_rate: uint256) -> uint256[N_COINS]: - result: uint256[N_COINS] = RATES - result[MAX_COIN] = vp_rate # virtual price for the metacurrency - for i in range(N_COINS): - result[i] = result[i] * self.balances[i] / PRECISION - return result - - -@pure -@internal -def _xp_mem(vp_rate: uint256, _balances: uint256[N_COINS]) -> uint256[N_COINS]: - result: uint256[N_COINS] = RATES - result[MAX_COIN] = vp_rate # virtual price for the metacurrency - for i in range(N_COINS): - result[i] = result[i] * _balances[i] / PRECISION - return result - - -@internal -def _vp_rate() -> uint256: - if block.timestamp > self.base_cache_updated + BASE_CACHE_EXPIRES: - vprice: uint256 = Curve(self.base_pool).get_virtual_price() - self.base_virtual_price = vprice - self.base_cache_updated = block.timestamp - return vprice - else: - return self.base_virtual_price - - -@internal -@view -def _vp_rate_ro() -> uint256: - if block.timestamp > self.base_cache_updated + BASE_CACHE_EXPIRES: - return Curve(self.base_pool).get_virtual_price() - else: - return self.base_virtual_price - - -@pure -@internal -def get_D(xp: uint256[N_COINS], amp: uint256) -> uint256: - S: uint256 = 0 - Dprev: uint256 = 0 - for _x in xp: - S += _x - if S == 0: - return 0 - - D: uint256 = S - Ann: uint256 = amp * N_COINS - for _i in range(255): - D_P: uint256 = D - for _x in xp: - D_P = D_P * D / (_x * N_COINS) # If division by 0, this will be borked: only withdrawal will work. And that is good - Dprev = D - D = (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (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 - - -@view -@internal -def get_D_mem(vp_rate: uint256, _balances: uint256[N_COINS], amp: uint256) -> uint256: - xp: uint256[N_COINS] = self._xp_mem(vp_rate, _balances) - return self.get_D(xp, amp) - - -@view -@external -def get_virtual_price() -> uint256: - """ - @notice The current virtual price of the pool LP token - @dev Useful for calculating profits - @return LP token virtual price normalized to 1e18 - """ - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate_ro() - xp: uint256[N_COINS] = self._xp(vp_rate) - D: uint256 = self.get_D(xp, amp) - # 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 - - -@view -@external -def calc_token_amount(amounts: uint256[N_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 amounts Amount of each coin being deposited - @param is_deposit set True for deposits, False for withdrawals - @return Expected amount of LP tokens received - """ - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate_ro() - _balances: uint256[N_COINS] = self.balances - D0: uint256 = self.get_D_mem(vp_rate, _balances, amp) - for i in range(N_COINS): - if is_deposit: - _balances[i] += amounts[i] - else: - _balances[i] -= amounts[i] - D1: uint256 = self.get_D_mem(vp_rate, _balances, amp) - token_amount: uint256 = self.token.totalSupply() - diff: uint256 = 0 - if is_deposit: - diff = D1 - D0 - else: - diff = D0 - D1 - return diff * token_amount / D0 - - -@external -@nonreentrant('lock') -def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256) -> uint256: - """ - @notice Deposit coins into the pool - @param amounts List of amounts of coins to deposit - @param min_mint_amount Minimum amount of LP tokens to mint from the deposit - @return Amount of LP tokens received by depositing - """ - assert not self.is_killed # dev: is killed - - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate() - token_supply: uint256 = self.token.totalSupply() - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - _admin_fee: uint256 = self.admin_fee - - # Initial invariant - D0: uint256 = 0 - old_balances: uint256[N_COINS] = self.balances - if token_supply > 0: - D0 = self.get_D_mem(vp_rate, old_balances, amp) - new_balances: uint256[N_COINS] = old_balances - - for i in range(N_COINS): - if token_supply == 0: - assert amounts[i] > 0 # dev: initial deposit requires all coins - # balances store amounts of c-tokens - new_balances[i] = old_balances[i] + amounts[i] - - # Invariant after change - D1: uint256 = self.get_D_mem(vp_rate, new_balances, amp) - assert D1 > D0 - - # We need to recalculate the invariant accounting for fees - # to calculate fair user's share - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - 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(vp_rate, new_balances, amp) - 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 amounts[i] > 0: - assert ERC20(self.coins[i]).transferFrom(msg.sender, self, amounts[i]) # dev: failed transfer - - # Mint pool tokens - self.token.mint(msg.sender, mint_amount) - - log AddLiquidity(msg.sender, amounts, fees, D1, token_supply + mint_amount) - - return mint_amount - - -@view -@internal -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 # dev: same coin - assert j >= 0 # dev: j below zero - assert j < N_COINS # dev: j above N_COINS - - # should be unreachable, but good for safety - assert i >= 0 - assert i < N_COINS - - amp: uint256 = self._A() - D: uint256 = self.get_D(xp_, amp) - - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: uint256 = 0 - c: uint256 = D - Ann: uint256 = amp * N_COINS - - 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 * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann # - D - 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 - - -@view -@external -def get_dy(i: int128, j: int128, dx: uint256) -> uint256: - # dx and dy in c-units - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = self._vp_rate_ro() - xp: uint256[N_COINS] = self._xp(rates[MAX_COIN]) - - x: uint256 = xp[i] + (dx * rates[i] / PRECISION) - y: uint256 = self.get_y(i, j, x, xp) - dy: uint256 = xp[j] - y - 1 - _fee: uint256 = self.fee * dy / FEE_DENOMINATOR - return (dy - _fee) * PRECISION / rates[j] - - -@view -@external -def get_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256: - # dx and dy in underlying units - vp_rate: uint256 = self._vp_rate_ro() - xp: uint256[N_COINS] = self._xp(vp_rate) - precisions: uint256[N_COINS] = PRECISION_MUL - _base_pool: address = self.base_pool - - # Use base_i or base_j if they are >= 0 - base_i: int128 = i - MAX_COIN - base_j: int128 = j - MAX_COIN - meta_i: int128 = MAX_COIN - meta_j: int128 = MAX_COIN - if base_i < 0: - meta_i = i - if base_j < 0: - meta_j = j - - x: uint256 = 0 - if base_i < 0: - x = xp[i] + dx * precisions[i] - else: - if base_j < 0: - # i is from BasePool - # At first, get the amount of pool tokens - base_inputs: uint256[BASE_N_COINS] = empty(uint256[BASE_N_COINS]) - base_inputs[base_i] = dx - # Token amount transformed to underlying "dollars" - x = Curve(_base_pool).calc_token_amount(base_inputs, True) * vp_rate / PRECISION - # Accounting for deposit/withdraw fees approximately - x -= x * Curve(_base_pool).fee() / (2 * FEE_DENOMINATOR) - # Adding number of pool tokens - x += xp[MAX_COIN] - else: - # If both are from the base pool - return Curve(_base_pool).get_dy(base_i, base_j, dx) - - # This pool is involved only when in-pool assets are used - y: uint256 = self.get_y(meta_i, meta_j, x, xp) - dy: uint256 = xp[meta_j] - y - 1 - dy = (dy - self.fee * dy / FEE_DENOMINATOR) - - # If output is going via the metapool - if base_j < 0: - dy /= precisions[meta_j] - else: - # j is from BasePool - # The fee is already accounted for - dy = Curve(_base_pool).calc_withdraw_one_coin(dy * PRECISION / vp_rate, base_j) - - return dy - - -@external -@nonreentrant('lock') -def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256) -> uint256: - """ - @notice Perform an exchange between two coins - @dev Index values can be found via the `coins` public getter method - @param i Index value for the coin to send - @param j Index valie of the coin to recieve - @param dx Amount of `i` being exchanged - @param min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - assert not self.is_killed # dev: is killed - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = self._vp_rate() - - old_balances: uint256[N_COINS] = self.balances - xp: uint256[N_COINS] = self._xp_mem(rates[MAX_COIN], old_balances) - - x: uint256 = xp[i] + dx * rates[i] / PRECISION - y: uint256 = self.get_y(i, j, x, xp) - - dy: uint256 = xp[j] - y - 1 # -1 just in case there were some rounding errors - dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR - - # Convert all to real units - dy = (dy - dy_fee) * PRECISION / rates[j] - assert dy >= min_dy, "Too few coins in result" - - dy_admin_fee: uint256 = dy_fee * self.admin_fee / FEE_DENOMINATOR - dy_admin_fee = dy_admin_fee * PRECISION / rates[j] - - # Change balances exactly in same way as we change actual ERC20 coin amounts - self.balances[i] = old_balances[i] + dx - # When rounding errors happen, we undercharge admin fee in favor of LP - self.balances[j] = old_balances[j] - dy - dy_admin_fee - - assert ERC20(self.coins[i]).transferFrom(msg.sender, self, dx) - assert ERC20(self.coins[j]).transfer(msg.sender, dy) - - log TokenExchange(msg.sender, i, dx, j, dy) - - return dy - - -@external -@nonreentrant('lock') -def exchange_underlying(i: int128, j: int128, dx: uint256, min_dy: uint256) -> uint256: - """ - @notice Perform an exchange between two underlying coins - @dev Index values can be found via the `underlying_coins` public getter method - @param i Index value for the underlying coin to send - @param j Index valie of the underlying coin to recieve - @param dx Amount of `i` being exchanged - @param min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - assert not self.is_killed # dev: is killed - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = self._vp_rate() - _base_pool: address = self.base_pool - - # Use base_i or base_j if they are >= 0 - base_i: int128 = i - MAX_COIN - base_j: int128 = j - MAX_COIN - meta_i: int128 = MAX_COIN - meta_j: int128 = MAX_COIN - if base_i < 0: - meta_i = i - if base_j < 0: - meta_j = j - dy: uint256 = 0 - - # Addresses for input and output coins - input_coin: address = ZERO_ADDRESS - if base_i < 0: - input_coin = self.coins[i] - else: - input_coin = self.base_coins[base_i] - output_coin: address = ZERO_ADDRESS - if base_j < 0: - output_coin = self.coins[j] - else: - output_coin = self.base_coins[base_j] - - # Handle potential Tether fees - dx_w_fee: uint256 = dx - if input_coin == FEE_ASSET: - dx_w_fee = ERC20(FEE_ASSET).balanceOf(self) - # "safeTransferFrom" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - input_coin, - concat( - method_id("transferFrom(address,address,uint256)"), - convert(msg.sender, bytes32), - convert(self, bytes32), - convert(dx, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransferFrom" - # Handle potential Tether fees - if input_coin == FEE_ASSET: - dx_w_fee = ERC20(FEE_ASSET).balanceOf(self) - dx_w_fee - - if base_i < 0 or base_j < 0: - old_balances: uint256[N_COINS] = self.balances - xp: uint256[N_COINS] = self._xp_mem(rates[MAX_COIN], old_balances) - - x: uint256 = 0 - if base_i < 0: - x = xp[i] + dx_w_fee * rates[i] / PRECISION - else: - # i is from BasePool - # At first, get the amount of pool tokens - base_inputs: uint256[BASE_N_COINS] = empty(uint256[BASE_N_COINS]) - base_inputs[base_i] = dx_w_fee - coin_i: address = self.coins[MAX_COIN] - # Deposit and measure delta - x = ERC20(coin_i).balanceOf(self) - Curve(_base_pool).add_liquidity(base_inputs, 0) - # Need to convert pool token to "virtual" units using rates - # dx is also different now - dx_w_fee = ERC20(coin_i).balanceOf(self) - x - x = dx_w_fee * rates[MAX_COIN] / PRECISION - # Adding number of pool tokens - x += xp[MAX_COIN] - - y: uint256 = self.get_y(meta_i, meta_j, x, xp) - - # Either a real coin or token - dy = xp[meta_j] - y - 1 # -1 just in case there were some rounding errors - dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR - - # Convert all to real units - # Works for both pool coins and real coins - dy = (dy - dy_fee) * PRECISION / rates[meta_j] - - dy_admin_fee: uint256 = dy_fee * self.admin_fee / FEE_DENOMINATOR - dy_admin_fee = dy_admin_fee * PRECISION / rates[meta_j] - - # Change balances exactly in same way as we change actual ERC20 coin amounts - self.balances[meta_i] = old_balances[meta_i] + dx_w_fee - # When rounding errors happen, we undercharge admin fee in favor of LP - self.balances[meta_j] = old_balances[meta_j] - dy - dy_admin_fee - - # Withdraw from the base pool if needed - if base_j >= 0: - out_amount: uint256 = ERC20(output_coin).balanceOf(self) - Curve(_base_pool).remove_liquidity_one_coin(dy, base_j, 0) - dy = ERC20(output_coin).balanceOf(self) - out_amount - - assert dy >= min_dy, "Too few coins in result" - - else: - # If both are from the base pool - dy = ERC20(output_coin).balanceOf(self) - Curve(_base_pool).exchange(base_i, base_j, dx_w_fee, min_dy) - dy = ERC20(output_coin).balanceOf(self) - dy - - # "safeTransfer" which works for ERC20s which return bool or not - _response = raw_call( - output_coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(dy, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - log TokenExchangeUnderlying(msg.sender, i, dx, j, dy) - - return dy - - -@external -@nonreentrant('lock') -def remove_liquidity(_amount: uint256, min_amounts: uint256[N_COINS]) -> uint256[N_COINS]: - """ - @notice Withdraw coins from the pool - @dev Withdrawal amounts are based on current deposit ratios - @param _amount Quantity of LP tokens to burn in the withdrawal - @param min_amounts Minimum amounts of underlying coins to receive - @return List of amounts of coins that were withdrawn - """ - total_supply: uint256 = self.token.totalSupply() - amounts: uint256[N_COINS] = empty(uint256[N_COINS]) - fees: uint256[N_COINS] = empty(uint256[N_COINS]) # Fees are unused but we've got them historically in event - - for i in range(N_COINS): - value: uint256 = self.balances[i] * _amount / total_supply - assert value >= min_amounts[i], "Too few coins in result" - self.balances[i] -= value - amounts[i] = value - assert ERC20(self.coins[i]).transfer(msg.sender, value) - - self.token.burnFrom(msg.sender, _amount) # dev: insufficient funds - - log RemoveLiquidity(msg.sender, amounts, fees, total_supply - _amount) - - return amounts - - -@external -@nonreentrant('lock') -def remove_liquidity_imbalance(amounts: uint256[N_COINS], max_burn_amount: uint256) -> uint256: - """ - @notice Withdraw coins from the pool in an imbalanced amount - @param amounts List of amounts of underlying coins to withdraw - @param max_burn_amount Maximum amount of LP token to burn in the withdrawal - @return Actual amount of the LP token burned in the withdrawal - """ - assert not self.is_killed # dev: is killed - - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate() - - token_supply: uint256 = self.token.totalSupply() - assert token_supply != 0 # dev: zero total supply - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - _admin_fee: uint256 = self.admin_fee - - old_balances: uint256[N_COINS] = self.balances - new_balances: uint256[N_COINS] = old_balances - D0: uint256 = self.get_D_mem(vp_rate, old_balances, amp) - for i in range(N_COINS): - new_balances[i] -= amounts[i] - D1: uint256 = self.get_D_mem(vp_rate, new_balances, amp) - - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - 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(vp_rate, new_balances, amp) - - token_amount: uint256 = (D0 - D2) * token_supply / D0 - assert token_amount != 0 # dev: zero tokens burned - token_amount += 1 # In case of rounding errors - make it unfavorable for the "attacker" - assert token_amount <= max_burn_amount, "Slippage screwed you" - - self.token.burnFrom(msg.sender, token_amount) # dev: insufficient funds - for i in range(N_COINS): - if amounts[i] != 0: - assert ERC20(self.coins[i]).transfer(msg.sender, amounts[i]) - - log RemoveLiquidityImbalance(msg.sender, amounts, fees, D1, token_supply - token_amount) - - return token_amount - - -@view -@internal -def get_y_D(A_: uint256, i: int128, xp: uint256[N_COINS], D: uint256) -> uint256: - """ - Calculate x[i] if one reduces D from being calculated for xp to D - - Done by solving quadratic equation iteratively. - x_1**2 + x1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) - x_1**2 + b*x_1 = c - - x_1 = (x_1**2 + c) / (2*x_1 + b) - """ - # x in the input is converted to the same price/precision - - assert i >= 0 # dev: i below zero - assert i < N_COINS # dev: i above N_COINS - - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: uint256 = 0 - - c: uint256 = D - Ann: uint256 = A_ * N_COINS - - for _i in range(N_COINS): - if _i != i: - _x = xp[_i] - else: - continue - S_ += _x - c = c * D / (_x * N_COINS) - c = c * D * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann - 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 - - -@view -@internal -def _calc_withdraw_one_coin(_token_amount: uint256, i: int128, vp_rate: uint256) -> (uint256, uint256, uint256): - # First, need to calculate - # * Get current D - # * Solve Eqn against y_i for D - _token_amount - amp: uint256 = self._A() - xp: uint256[N_COINS] = self._xp(vp_rate) - D0: uint256 = self.get_D(xp, amp) - - total_supply: uint256 = self.token.totalSupply() - D1: uint256 = D0 - _token_amount * D0 / total_supply - new_y: uint256 = self.get_y_D(amp, i, xp, D1) - - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = vp_rate - - xp_reduced: uint256[N_COINS] = xp - dy_0: uint256 = (xp[i] - new_y) * PRECISION / rates[i] # w/o fees - - for j in range(N_COINS): - dx_expected: uint256 = 0 - if j == i: - dx_expected = xp[j] * D1 / D0 - new_y - else: - dx_expected = xp[j] - xp[j] * D1 / D0 - xp_reduced[j] -= _fee * dx_expected / FEE_DENOMINATOR - - dy: uint256 = xp_reduced[i] - self.get_y_D(amp, i, xp_reduced, D1) - dy = (dy - 1) * PRECISION / rates[i] # Withdraw less to account for rounding errors - - return dy, dy_0 - dy, total_supply - - -@view -@external -def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - """ - @notice Calculate the amount received when withdrawing a single coin - @param _token_amount Amount of LP tokens to burn in the withdrawal - @param i Index value of the coin to withdraw - @return Amount of coin received - """ - vp_rate: uint256 = self._vp_rate_ro() - return self._calc_withdraw_one_coin(_token_amount, i, vp_rate)[0] - - -@external -@nonreentrant('lock') -def remove_liquidity_one_coin(_token_amount: uint256, i: int128, _min_amount: uint256) -> uint256: - """ - @notice Withdraw a single coin from the pool - @param _token_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 coin to receive - @return Amount of coin received - """ - assert not self.is_killed # dev: is killed - - vp_rate: uint256 = self._vp_rate() - dy: uint256 = 0 - dy_fee: uint256 = 0 - total_supply: uint256 = 0 - dy, dy_fee, total_supply = self._calc_withdraw_one_coin(_token_amount, i, vp_rate) - assert dy >= _min_amount, "Not enough coins removed" - - self.balances[i] -= (dy + dy_fee * self.admin_fee / FEE_DENOMINATOR) - self.token.burnFrom(msg.sender, _token_amount) # dev: insufficient funds - assert ERC20(self.coins[i]).transfer(msg.sender, dy) - - log RemoveLiquidityOne(msg.sender, _token_amount, dy, total_supply - _token_amount) - - return dy - - -### Admin functions ### -@external -def ramp_A(_future_A: uint256, _future_time: uint256): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.initial_A_time + MIN_RAMP_TIME - assert _future_time >= block.timestamp + MIN_RAMP_TIME # dev: insufficient time - - _initial_A: uint256 = self._A() - _future_A_p: uint256 = _future_A * A_PRECISION - - assert _future_A > 0 and _future_A < MAX_A - if _future_A_p < _initial_A: - assert _future_A_p * MAX_A_CHANGE >= _initial_A - else: - assert _future_A_p <= _initial_A * MAX_A_CHANGE - - self.initial_A = _initial_A - self.future_A = _future_A_p - self.initial_A_time = block.timestamp - self.future_A_time = _future_time - - log RampA(_initial_A, _future_A_p, block.timestamp, _future_time) - - -@external -def stop_ramp_A(): - assert msg.sender == self.owner # dev: only owner - - current_A: uint256 = self._A() - self.initial_A = current_A - self.future_A = current_A - self.initial_A_time = block.timestamp - self.future_A_time = block.timestamp - # now (block.timestamp < t1) is always False, so we return saved A - - log StopRampA(current_A, block.timestamp) - - -@external -def commit_new_fee(new_fee: uint256, new_admin_fee: uint256): - assert msg.sender == self.owner # dev: only owner - assert self.admin_actions_deadline == 0 # dev: active action - assert new_fee <= MAX_FEE # dev: fee exceeds maximum - assert new_admin_fee <= MAX_ADMIN_FEE # dev: admin fee exceeds maximum - - _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.admin_actions_deadline = _deadline - self.future_fee = new_fee - self.future_admin_fee = new_admin_fee - - log CommitNewFee(_deadline, new_fee, new_admin_fee) - - -@external -def apply_new_fee(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.admin_actions_deadline # dev: insufficient time - assert self.admin_actions_deadline != 0 # dev: no active action - - self.admin_actions_deadline = 0 - _fee: uint256 = self.future_fee - _admin_fee: uint256 = self.future_admin_fee - self.fee = _fee - self.admin_fee = _admin_fee - - log NewFee(_fee, _admin_fee) - - -@external -def revert_new_parameters(): - assert msg.sender == self.owner # dev: only owner - - self.admin_actions_deadline = 0 - - -@external -def commit_transfer_ownership(_owner: address): - assert msg.sender == self.owner # dev: only owner - assert self.transfer_ownership_deadline == 0 # dev: active transfer - - _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.transfer_ownership_deadline = _deadline - self.future_owner = _owner - - log CommitNewAdmin(_deadline, _owner) - - -@external -def apply_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.transfer_ownership_deadline # dev: insufficient time - assert self.transfer_ownership_deadline != 0 # dev: no active transfer - - self.transfer_ownership_deadline = 0 - _owner: address = self.future_owner - self.owner = _owner - - log NewAdmin(_owner) - - -@external -def revert_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - - self.transfer_ownership_deadline = 0 - - -@view -@external -def admin_balances(i: uint256) -> uint256: - return ERC20(self.coins[i]).balanceOf(self) - self.balances[i] - - -@external -def withdraw_admin_fees(): - assert msg.sender == self.owner # dev: only owner - - for i in range(N_COINS): - c: address = self.coins[i] - value: uint256 = ERC20(c).balanceOf(self) - self.balances[i] - if value > 0: - assert ERC20(c).transfer(msg.sender, value) - - -@external -def donate_admin_fees(): - assert msg.sender == self.owner # dev: only owner - for i in range(N_COINS): - self.balances[i] = ERC20(self.coins[i]).balanceOf(self) - - -@external -def kill_me(): - assert msg.sender == self.owner # dev: only owner - assert self.kill_deadline > block.timestamp # dev: deadline has passed - self.is_killed = True - - -@external -def unkill_me(): - assert msg.sender == self.owner # dev: only owner - self.is_killed = False diff --git a/contracts/pools/dusd/pooldata.json b/contracts/pools/dusd/pooldata.json deleted file mode 100644 index a917d717..00000000 --- a/contracts/pools/dusd/pooldata.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "base_pool": "3pool", - "pool_types": ["meta"], - "swap_address": "0x8038C01A0390a8c547446a0b2c18fc9aEFEcc10c", - "lp_token_address": "0x3a664Ab939FD8482048609f652f9a0B0677337B9", - "zap_address": "0x61E10659fe3aa93d036d099405224E4Ac24996d0", - "gauge_addresses": ["0xAEA6c312f4b3E04D752946d329693F7293bC2e6D"], - "lp_contract": "CurveTokenV2", - "lp_constructor": { - "symbol": "dusd3CRV", - "name": "Curve.fi DUSD/3Crv" - }, - "swap_constructor": { - "_A": 200, - "_fee": 4000000, - "_admin_fee": 0 - }, - "coins": [ - { - "name": "DUSD", - "decimals": 18, - "tethered": false, - "underlying_address": "0x5bc25f649fc4e26069ddf4cf4010f9f706c23831" - }, - { - "name": "3CRV", - "decimals": 18, - "base_pool_token": true, - "underlying_address": "0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490" - } - ] -} diff --git a/contracts/pools/eurs/README.md b/contracts/pools/eurs/README.md deleted file mode 100644 index 80f7edc7..00000000 --- a/contracts/pools/eurs/README.md +++ /dev/null @@ -1,20 +0,0 @@ -# curve-contract/contracts/pools/eurs - -[Curve EURS pool](https://www.curve.fi/eurs). This is a no-lending pool. - -## Contracts - -* [`StableSwapEURS`](StableSwapEURS.vy): Curve stablecoin AMM contract - -## Deployments - -* [`CurveContractV3`](../../tokens/CurveTokenV3.vy): [0x194eBd173F6cDacE046C53eACcE9B953F28411d1](https://etherscan.io/address/0x194eBd173F6cDacE046C53eACcE9B953F28411d1) -* [`LiquidityGaugeV2`](https://github.com/curvefi/curve-dao-contracts/blob/master/contracts/gauges/LiquidityGaugeV2.vy): [0x90Bb609649E0451E5aD952683D64BD2d1f245840](https://etherscan.io/address/0x90Bb609649E0451E5aD952683D64BD2d1f245840) -* [`StableSwapEURS`](StableSwapEURS.vy): [0x0Ce6a5fF5217e38315f87032CF90686C96627CAA](https://etherscan.io/address/0x0Ce6a5fF5217e38315f87032CF90686C96627CAA) - -## Stablecoins - -Curve EURS pool supports swaps between the following stablecoins: - -* `EURS`: [0xdB25f211AB05b1c97D595516F45794528a807ad8](https://etherscan.io/address/0xdB25f211AB05b1c97D595516F45794528a807ad8) -* `sEUR`: [0xD71eCFF9342A5Ced620049e616c5035F1dB98620](https://etherscan.io/address/0xD71eCFF9342A5Ced620049e616c5035F1dB98620) diff --git a/contracts/pools/eurs/StableSwapEURS.vy b/contracts/pools/eurs/StableSwapEURS.vy deleted file mode 100644 index b8b49ee9..00000000 --- a/contracts/pools/eurs/StableSwapEURS.vy +++ /dev/null @@ -1,884 +0,0 @@ -# @version 0.2.8 -""" -@title StableSwap -@author Curve.Fi -@license Copyright (c) Curve.Fi, 2020 - all rights reserved -@notice Pool for swapping between Euro-denominated stablecoins -@dev Swaps between EURS and sEUR -""" - -from vyper.interfaces import ERC20 - -interface CurveToken: - def mint(_to: address, _value: uint256) -> bool: nonpayable - def burnFrom(_to: address, _value: uint256) -> bool: nonpayable - - -# Events -event TokenExchange: - buyer: indexed(address) - sold_id: int128 - tokens_sold: uint256 - bought_id: int128 - tokens_bought: uint256 - -event AddLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event RemoveLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - token_supply: uint256 - -event RemoveLiquidityOne: - provider: indexed(address) - token_amount: uint256 - coin_amount: uint256 - token_supply: uint256 - -event RemoveLiquidityImbalance: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event CommitNewAdmin: - deadline: indexed(uint256) - admin: indexed(address) - -event NewAdmin: - admin: indexed(address) - -event CommitNewFee: - deadline: indexed(uint256) - fee: uint256 - admin_fee: uint256 - -event NewFee: - fee: uint256 - admin_fee: uint256 - -event RampA: - old_A: uint256 - new_A: uint256 - initial_time: uint256 - future_time: uint256 - -event StopRampA: - A: uint256 - t: uint256 - - -# These constants must be set prior to compiling -N_COINS: constant(int128) = 2 -PRECISION_MUL: constant(uint256[N_COINS]) = [10000000000000000, 1] -RATES: constant(uint256[N_COINS]) = [10000000000000000000000000000000000, 1000000000000000000] - -# fixed constants -FEE_DENOMINATOR: constant(uint256) = 10 ** 10 -PRECISION: constant(uint256) = 10 ** 18 # The precision to convert to - -MAX_ADMIN_FEE: constant(uint256) = 10 * 10 ** 9 -MAX_FEE: constant(uint256) = 5 * 10 ** 9 -MAX_A: constant(uint256) = 10 ** 6 -MAX_A_CHANGE: constant(uint256) = 10 - -ADMIN_ACTIONS_DELAY: constant(uint256) = 3 * 86400 -MIN_RAMP_TIME: constant(uint256) = 86400 - -coins: public(address[N_COINS]) -balances: public(uint256[N_COINS]) -fee: public(uint256) # fee * 1e10 -admin_fee: public(uint256) # admin_fee * 1e10 - -owner: public(address) -lp_token: public(address) - -A_PRECISION: constant(uint256) = 100 -initial_A: public(uint256) -future_A: public(uint256) -initial_A_time: public(uint256) -future_A_time: public(uint256) - -admin_actions_deadline: public(uint256) -transfer_ownership_deadline: public(uint256) -future_fee: public(uint256) -future_admin_fee: public(uint256) -future_owner: public(address) - -is_killed: bool -kill_deadline: uint256 -KILL_DEADLINE_DT: constant(uint256) = 2 * 30 * 86400 - - -@external -def __init__( - _owner: address, - _coins: address[N_COINS], - _pool_token: address, - _A: uint256, - _fee: uint256, - _admin_fee: uint256 -): - """ - @notice Contract constructor - @param _owner Contract owner address - @param _coins Addresses of ERC20 conracts of coins - @param _pool_token Address of the token representing LP share - @param _A Amplification coefficient multiplied by n * (n - 1) - @param _fee Fee to charge for exchanges - @param _admin_fee Admin fee - """ - for i in range(N_COINS): - assert _coins[i] != ZERO_ADDRESS - self.coins = _coins - self.initial_A = _A * A_PRECISION - self.future_A = _A * A_PRECISION - self.fee = _fee - self.admin_fee = _admin_fee - self.owner = _owner - self.kill_deadline = block.timestamp + KILL_DEADLINE_DT - self.lp_token = _pool_token - - -@view -@internal -def _A() -> uint256: - """ - Handle ramping A up or down - """ - t1: uint256 = self.future_A_time - A1: uint256 = self.future_A - - if block.timestamp < t1: - A0: uint256 = self.initial_A - t0: uint256 = self.initial_A_time - # Expressions in uint256 cannot have negative numbers, thus "if" - if A1 > A0: - return A0 + (A1 - A0) * (block.timestamp - t0) / (t1 - t0) - else: - return A0 - (A0 - A1) * (block.timestamp - t0) / (t1 - t0) - - else: # when t1 == 0 or block.timestamp >= t1 - return A1 - - -@view -@external -def A() -> uint256: - return self._A() / A_PRECISION - - -@view -@external -def A_precise() -> uint256: - return self._A() - - -@view -@internal -def _xp() -> uint256[N_COINS]: - result: uint256[N_COINS] = RATES - for i in range(N_COINS): - result[i] = result[i] * self.balances[i] / PRECISION - return result - - -@pure -@internal -def _xp_mem(_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 - - -@pure -@internal -def get_D(xp: uint256[N_COINS], amp: uint256) -> uint256: - S: uint256 = 0 - Dprev: uint256 = 0 - - for _x in xp: - S += _x - if S == 0: - return 0 - - D: uint256 = S - Ann: uint256 = amp * N_COINS - for _i in range(255): - D_P: uint256 = D - for _x in xp: - D_P = D_P * D / (_x * N_COINS) # If division by 0, this will be borked: only withdrawal will work. And that is good - Dprev = D - D = (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (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` - raise - - -@view -@internal -def get_D_mem(_balances: uint256[N_COINS], amp: uint256) -> uint256: - return self.get_D(self._xp_mem(_balances), amp) - - -@view -@external -def get_virtual_price() -> uint256: - """ - @notice The current virtual price of the pool LP token - @dev Useful for calculating profits - @return LP token virtual price normalized to 1e18 - """ - D: uint256 = self.get_D(self._xp(), self._A()) - # 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 = ERC20(self.lp_token).totalSupply() - return D * PRECISION / token_supply - - -@view -@external -def calc_token_amount(amounts: uint256[N_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 amounts Amount of each coin being deposited - @param is_deposit set True for deposits, False for withdrawals - @return Expected amount of LP tokens received - """ - amp: uint256 = self._A() - _balances: uint256[N_COINS] = self.balances - D0: uint256 = self.get_D_mem(_balances, amp) - for i in range(N_COINS): - if is_deposit: - _balances[i] += amounts[i] - else: - _balances[i] -= amounts[i] - D1: uint256 = self.get_D_mem(_balances, amp) - token_amount: uint256 = ERC20(self.lp_token).totalSupply() - diff: uint256 = 0 - if is_deposit: - diff = D1 - D0 - else: - diff = D0 - D1 - return diff * token_amount / D0 - - -@external -@nonreentrant('lock') -def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256) -> uint256: - """ - @notice Deposit coins into the pool - @param amounts List of amounts of coins to deposit - @param min_mint_amount Minimum amount of LP tokens to mint from the deposit - @return Amount of LP tokens received by depositing - """ - assert not self.is_killed # dev: is killed - - amp: uint256 = self._A() - - _lp_token: address = self.lp_token - token_supply: uint256 = ERC20(_lp_token).totalSupply() - # Initial invariant - D0: uint256 = 0 - old_balances: uint256[N_COINS] = self.balances - if token_supply > 0: - D0 = self.get_D_mem(old_balances, amp) - new_balances: uint256[N_COINS] = old_balances - - for i in range(N_COINS): - if token_supply == 0: - assert amounts[i] > 0 # dev: initial deposit requires all coins - # balances store amounts of c-tokens - new_balances[i] = old_balances[i] + amounts[i] - - # Invariant after change - D1: uint256 = self.get_D_mem(new_balances, amp) - assert D1 > D0 - - # We need to recalculate the invariant accounting for fees - # to calculate fair user's share - D2: uint256 = D1 - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - - if token_supply > 0: - # Only account for fees if we are not the first to deposit - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - _admin_fee: uint256 = self.admin_fee - 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(new_balances, amp) - 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 amounts[i] > 0: - # "safeTransferFrom" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - self.coins[i], - concat( - method_id("transferFrom(address,address,uint256)"), - convert(msg.sender, bytes32), - convert(self, bytes32), - convert(amounts[i], bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) - - # Mint pool tokens - CurveToken(_lp_token).mint(msg.sender, mint_amount) - - log AddLiquidity(msg.sender, amounts, fees, D1, token_supply + mint_amount) - - return mint_amount - - -@view -@internal -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 # dev: same coin - assert j >= 0 # dev: j below zero - assert j < N_COINS # dev: j above N_COINS - - # should be unreachable, but good for safety - assert i >= 0 - assert i < N_COINS - - amp: uint256 = self._A() - D: uint256 = self.get_D(xp_, amp) - Ann: uint256 = amp * N_COINS - c: uint256 = D - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: 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 * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann # - D - 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: - return y - else: - if y_prev - y <= 1: - return y - raise - - -@view -@external -def get_dy(i: int128, j: int128, dx: uint256) -> uint256: - xp: uint256[N_COINS] = self._xp() - rates: uint256[N_COINS] = RATES - - x: uint256 = xp[i] + (dx * rates[i] / PRECISION) - y: uint256 = self.get_y(i, j, x, xp) - dy: uint256 = (xp[j] - y - 1) - _fee: uint256 = self.fee * dy / FEE_DENOMINATOR - return (dy - _fee) * PRECISION / rates[j] - - -@external -@nonreentrant('lock') -def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256) -> uint256: - """ - @notice Perform an exchange between two coins - @dev Index values can be found via the `coins` public getter method - @param i Index value for the coin to send - @param j Index valie of the coin to recieve - @param dx Amount of `i` being exchanged - @param min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - assert not self.is_killed # dev: is killed - - old_balances: uint256[N_COINS] = self.balances - xp: uint256[N_COINS] = self._xp_mem(old_balances) - - rates: uint256[N_COINS] = RATES - x: uint256 = xp[i] + dx * rates[i] / PRECISION - y: uint256 = self.get_y(i, j, x, xp) - - dy: uint256 = xp[j] - y - 1 # -1 just in case there were some rounding errors - dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR - - # Convert all to real units - dy = (dy - dy_fee) * PRECISION / rates[j] - assert dy >= min_dy, "Exchange resulted in fewer coins than expected" - - dy_admin_fee: uint256 = dy_fee * self.admin_fee / FEE_DENOMINATOR - dy_admin_fee = dy_admin_fee * PRECISION / rates[j] - - # Change balances exactly in same way as we change actual ERC20 coin amounts - self.balances[i] = old_balances[i] + dx - # When rounding errors happen, we undercharge admin fee in favor of LP - self.balances[j] = old_balances[j] - dy - dy_admin_fee - - # "safeTransferFrom" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - self.coins[i], - concat( - method_id("transferFrom(address,address,uint256)"), - convert(msg.sender, bytes32), - convert(self, bytes32), - convert(dx, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) - - _response = raw_call( - self.coins[j], - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(dy, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) - - log TokenExchange(msg.sender, i, dx, j, dy) - - return dy - - -@external -@nonreentrant('lock') -def remove_liquidity(_amount: uint256, min_amounts: uint256[N_COINS]) -> uint256[N_COINS]: - """ - @notice Withdraw coins from the pool - @dev Withdrawal amounts are based on current deposit ratios - @param _amount Quantity of LP tokens to burn in the withdrawal - @param min_amounts Minimum amounts of underlying coins to receive - @return List of amounts of coins that were withdrawn - """ - _lp_token: address = self.lp_token - total_supply: uint256 = ERC20(_lp_token).totalSupply() - amounts: uint256[N_COINS] = empty(uint256[N_COINS]) - fees: uint256[N_COINS] = empty(uint256[N_COINS]) # Fees are unused but we've got them historically in event - - 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 - _response: Bytes[32] = raw_call( - self.coins[i], - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(value, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) - - CurveToken(_lp_token).burnFrom(msg.sender, _amount) # dev: insufficient funds - - log RemoveLiquidity(msg.sender, amounts, fees, total_supply - _amount) - - return amounts - - -@external -@nonreentrant('lock') -def remove_liquidity_imbalance(amounts: uint256[N_COINS], max_burn_amount: uint256) -> uint256: - """ - @notice Withdraw coins from the pool in an imbalanced amount - @param amounts List of amounts of underlying coins to withdraw - @param max_burn_amount Maximum amount of LP token to burn in the withdrawal - @return Actual amount of the LP token burned in the withdrawal - """ - assert not self.is_killed # dev: is killed - - amp: uint256 = self._A() - - old_balances: uint256[N_COINS] = self.balances - new_balances: uint256[N_COINS] = old_balances - D0: uint256 = self.get_D_mem(old_balances, amp) - for i in range(N_COINS): - new_balances[i] -= amounts[i] - D1: uint256 = self.get_D_mem(new_balances, amp) - - _lp_token: address = self.lp_token - token_supply: uint256 = ERC20(_lp_token).totalSupply() - assert token_supply != 0 # dev: zero total supply - - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - _admin_fee: uint256 = self.admin_fee - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - 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(new_balances, amp) - - token_amount: uint256 = (D0 - D2) * token_supply / D0 - assert token_amount != 0 # dev: zero tokens burned - token_amount += 1 # In case of rounding errors - make it unfavorable for the "attacker" - assert token_amount <= max_burn_amount, "Slippage screwed you" - - CurveToken(_lp_token).burnFrom(msg.sender, token_amount) # dev: insufficient funds - for i in range(N_COINS): - if amounts[i] != 0: - _response: Bytes[32] = raw_call( - self.coins[i], - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(amounts[i], bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) - - - log RemoveLiquidityImbalance(msg.sender, amounts, fees, D1, token_supply - token_amount) - - return token_amount - - -@view -@internal -def get_y_D(A_: uint256, i: int128, xp: uint256[N_COINS], D: uint256) -> uint256: - """ - Calculate x[i] if one reduces D from being calculated for xp to D - - Done by solving quadratic equation iteratively. - x_1**2 + x1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) - x_1**2 + b*x_1 = c - - x_1 = (x_1**2 + c) / (2*x_1 + b) - """ - # x in the input is converted to the same price/precision - - assert i >= 0 # dev: i below zero - assert i < N_COINS # dev: i above N_COINS - - Ann: uint256 = A_ * N_COINS - c: uint256 = D - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: uint256 = 0 - - for _i in range(N_COINS): - if _i != i: - _x = xp[_i] - else: - continue - S_ += _x - c = c * D / (_x * N_COINS) - c = c * D * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann - - 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: - return y - else: - if y_prev - y <= 1: - return y - raise - - -@view -@internal -def _calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> (uint256, uint256, uint256): - # First, need to calculate - # * Get current D - # * Solve Eqn against y_i for D - _token_amount - amp: uint256 = self._A() - xp: uint256[N_COINS] = self._xp() - D0: uint256 = self.get_D(xp, amp) - - total_supply: uint256 = ERC20(self.lp_token).totalSupply() - D1: uint256 = D0 - _token_amount * D0 / total_supply - new_y: uint256 = self.get_y_D(amp, i, xp, D1) - xp_reduced: uint256[N_COINS] = xp - - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - for j in range(N_COINS): - dx_expected: uint256 = 0 - if j == i: - dx_expected = xp[j] * D1 / D0 - new_y - else: - dx_expected = xp[j] - xp[j] * D1 / D0 - xp_reduced[j] -= _fee * dx_expected / FEE_DENOMINATOR - - dy: uint256 = xp_reduced[i] - self.get_y_D(amp, i, xp_reduced, D1) - precisions: uint256[N_COINS] = PRECISION_MUL - dy = (dy - 1) / precisions[i] # Withdraw less to account for rounding errors - dy_0: uint256 = (xp[i] - new_y) / precisions[i] # w/o fees - - return dy, dy_0 - dy, total_supply - - -@view -@external -def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - """ - @notice Calculate the amount received when withdrawing a single coin - @param _token_amount Amount of LP tokens to burn in the withdrawal - @param i Index value of the coin to withdraw - @return Amount of coin received - """ - return self._calc_withdraw_one_coin(_token_amount, i)[0] - - -@external -@nonreentrant('lock') -def remove_liquidity_one_coin(_token_amount: uint256, i: int128, _min_amount: uint256) -> uint256: - """ - @notice Withdraw a single coin from the pool - @param _token_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 coin to receive - @return Amount of coin received - """ - assert not self.is_killed # dev: is killed - - dy: uint256 = 0 - dy_fee: uint256 = 0 - total_supply: uint256 = 0 - dy, dy_fee, total_supply = self._calc_withdraw_one_coin(_token_amount, i) - assert dy >= _min_amount, "Not enough coins removed" - - self.balances[i] -= (dy + dy_fee * self.admin_fee / FEE_DENOMINATOR) - CurveToken(self.lp_token).burnFrom(msg.sender, _token_amount) # dev: insufficient funds - - - _response: Bytes[32] = raw_call( - self.coins[i], - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(dy, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) - - log RemoveLiquidityOne(msg.sender, _token_amount, dy, total_supply - _token_amount) - - return dy - - -### Admin functions ### -@external -def ramp_A(_future_A: uint256, _future_time: uint256): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.initial_A_time + MIN_RAMP_TIME - assert _future_time >= block.timestamp + MIN_RAMP_TIME # dev: insufficient time - - _initial_A: uint256 = self._A() - _future_A_p: uint256 = _future_A * A_PRECISION - - assert _future_A > 0 and _future_A < MAX_A - if _future_A_p < _initial_A: - assert _future_A_p * MAX_A_CHANGE >= _initial_A - else: - assert _future_A_p <= _initial_A * MAX_A_CHANGE - - self.initial_A = _initial_A - self.future_A = _future_A_p - self.initial_A_time = block.timestamp - self.future_A_time = _future_time - - log RampA(_initial_A, _future_A_p, block.timestamp, _future_time) - - -@external -def stop_ramp_A(): - assert msg.sender == self.owner # dev: only owner - - current_A: uint256 = self._A() - self.initial_A = current_A - self.future_A = current_A - self.initial_A_time = block.timestamp - self.future_A_time = block.timestamp - # now (block.timestamp < t1) is always False, so we return saved A - - log StopRampA(current_A, block.timestamp) - - -@external -def commit_new_fee(new_fee: uint256, new_admin_fee: uint256): - assert msg.sender == self.owner # dev: only owner - assert self.admin_actions_deadline == 0 # dev: active action - assert new_fee <= MAX_FEE # dev: fee exceeds maximum - assert new_admin_fee <= MAX_ADMIN_FEE # dev: admin fee exceeds maximum - - _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.admin_actions_deadline = _deadline - self.future_fee = new_fee - self.future_admin_fee = new_admin_fee - - log CommitNewFee(_deadline, new_fee, new_admin_fee) - - -@external -def apply_new_fee(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.admin_actions_deadline # dev: insufficient time - assert self.admin_actions_deadline != 0 # dev: no active action - - self.admin_actions_deadline = 0 - _fee: uint256 = self.future_fee - _admin_fee: uint256 = self.future_admin_fee - self.fee = _fee - self.admin_fee = _admin_fee - - log NewFee(_fee, _admin_fee) - - -@external -def revert_new_parameters(): - assert msg.sender == self.owner # dev: only owner - - self.admin_actions_deadline = 0 - - -@external -def commit_transfer_ownership(_owner: address): - assert msg.sender == self.owner # dev: only owner - assert self.transfer_ownership_deadline == 0 # dev: active transfer - - _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.transfer_ownership_deadline = _deadline - self.future_owner = _owner - - log CommitNewAdmin(_deadline, _owner) - - -@external -def apply_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.transfer_ownership_deadline # dev: insufficient time - assert self.transfer_ownership_deadline != 0 # dev: no active transfer - - self.transfer_ownership_deadline = 0 - _owner: address = self.future_owner - self.owner = _owner - - log NewAdmin(_owner) - - -@external -def revert_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - - self.transfer_ownership_deadline = 0 - - -@view -@external -def admin_balances(i: uint256) -> uint256: - return ERC20(self.coins[i]).balanceOf(self) - self.balances[i] - - -@external -def withdraw_admin_fees(): - assert msg.sender == self.owner # dev: only owner - - for i in range(N_COINS): - coin: address = self.coins[i] - value: uint256 = ERC20(coin).balanceOf(self) - self.balances[i] - if value > 0: - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(value, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) - - -@external -def donate_admin_fees(): - assert msg.sender == self.owner # dev: only owner - for i in range(N_COINS): - self.balances[i] = ERC20(self.coins[i]).balanceOf(self) - - -@external -def kill_me(): - assert msg.sender == self.owner # dev: only owner - assert self.kill_deadline > block.timestamp # dev: deadline has passed - self.is_killed = True - - -@external -def unkill_me(): - assert msg.sender == self.owner # dev: only owner - self.is_killed = False diff --git a/contracts/pools/eurs/pooldata.json b/contracts/pools/eurs/pooldata.json deleted file mode 100644 index aa452890..00000000 --- a/contracts/pools/eurs/pooldata.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "lp_contract": "CurveTokenV3", - "swap_address": "0x0Ce6a5fF5217e38315f87032CF90686C96627CAA", - "lp_token_address": "0x194eBd173F6cDacE046C53eACcE9B953F28411d1", - "gauge_addresses": ["0x90Bb609649E0451E5aD952683D64BD2d1f245840"], - "lp_constructor": { - "symbol": "eursCRV", - "name": "Curve.fi EURS/sEUR" - }, - "swap_constructor": { - "_A": 100, - "_fee": 4000000, - "_admin_fee": 5000000000 - }, - "coins": [ - { - "name": "EURS", - "decimals": 2, - "tethered": false, - "underlying_address": "0xdB25f211AB05b1c97D595516F45794528a807ad8" - }, - { - "name": "sEUR", - "decimals": 18, - "tethered": false, - "underlying_address": "0xD71eCFF9342A5Ced620049e616c5035F1dB98620" - } - ], - "testing": { - "initial_amount": 10000 - } -} diff --git a/contracts/pools/gusd/DepositGUSD.vy b/contracts/pools/gusd/DepositGUSD.vy deleted file mode 100644 index 17660467..00000000 --- a/contracts/pools/gusd/DepositGUSD.vy +++ /dev/null @@ -1,376 +0,0 @@ -# @version 0.2.7 -""" -@title "Zap" Depositer for Curve GUSD pool -@author Curve.Fi -@license Copyright (c) Curve.Fi, 2020 - all rights reserved -""" - -from vyper.interfaces import ERC20 - - -interface CurveMeta: - def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256) -> 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) -> 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 base_pool() -> address: 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 - -# An asset which may have a transfer fee (USDT) -FEE_ASSET: constant(address) = 0xdAC17F958D2ee523a2206206994597C13D831ec7 - -FEE_DENOMINATOR: constant(uint256) = 10 ** 10 -FEE_IMPRECISION: constant(uint256) = 100 * 10 ** 8 # % of the fee - - -pool: public(address) -token: public(address) -base_pool: public(address) - -coins: public(address[N_COINS]) -base_coins: public(address[BASE_N_COINS]) - - -@external -def __init__(_pool: address, _token: address): - """ - @notice Contract constructor - @param _pool Metapool address - @param _token Pool LP token address - """ - self.pool = _pool - self.token = _token - _base_pool: address = CurveMeta(_pool).base_pool() - self.base_pool = _base_pool - - for i in range(N_COINS): - coin: address = CurveMeta(_pool).coins(convert(i, uint256)) - self.coins[i] = coin - # approve coins for infinite transfers - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("approve(address,uint256)"), - convert(_pool, bytes32), - convert(MAX_UINT256, bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) - - for i in range(BASE_N_COINS): - coin: address = CurveBase(_base_pool).coins(convert(i, uint256)) - self.base_coins[i] = coin - # approve underlying coins for infinite transfers - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("approve(address,uint256)"), - convert(_base_pool, bytes32), - convert(MAX_UINT256, bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) - - -@external -def add_liquidity(amounts: uint256[N_ALL_COINS], min_mint_amount: uint256) -> uint256: - """ - @notice Wrap underlying coins and deposit them in the pool - @param amounts List of amounts of underlying coins to deposit - @param min_mint_amount Minimum amount of LP tokens to mint from the deposit - @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 - - # Transfer all coins in - for i in range(N_ALL_COINS): - amount: uint256 = amounts[i] - if amount == 0: - continue - coin: address = ZERO_ADDRESS - if i < MAX_COIN: - coin = self.coins[i] - meta_amounts[i] = amount - else: - x: int128 = i - MAX_COIN - coin = self.base_coins[x] - base_amounts[x] = amount - deposit_base = True - # "safeTransferFrom" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transferFrom(address,address,uint256)"), - convert(msg.sender, bytes32), - convert(self, bytes32), - convert(amount, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransferFrom" - # Handle potential Tether fees - if coin == FEE_ASSET: - amount = ERC20(FEE_ASSET).balanceOf(self) - if i < MAX_COIN: - meta_amounts[i] = amount - else: - base_amounts[i - MAX_COIN] = amount - - # Deposit to the base pool - if deposit_base: - CurveBase(self.base_pool).add_liquidity(base_amounts, 0) - meta_amounts[MAX_COIN] = ERC20(self.coins[MAX_COIN]).balanceOf(self) - - # Deposit to the meta pool - CurveMeta(self.pool).add_liquidity(meta_amounts, min_mint_amount) - - # Transfer meta token back - _lp_token: address = self.token - _lp_amount: uint256 = ERC20(_lp_token).balanceOf(self) - assert ERC20(_lp_token).transfer(msg.sender, _lp_amount) - - return _lp_amount - - -@external -def remove_liquidity(_amount: uint256, min_amounts: uint256[N_ALL_COINS]) -> uint256[N_ALL_COINS]: - """ - @notice Withdraw and unwrap coins from the pool - @dev Withdrawal amounts are based on current deposit ratios - @param _amount Quantity of LP tokens to burn in the withdrawal - @param min_amounts Minimum amounts of underlying coins to receive - @return List of amounts of underlying coins that were withdrawn - """ - _token: address = self.token - assert ERC20(_token).transferFrom(msg.sender, self, _amount) - - min_amounts_meta: uint256[N_COINS] = empty(uint256[N_COINS]) - 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 - for i in range(MAX_COIN): - min_amounts_meta[i] = min_amounts[i] - CurveMeta(self.pool).remove_liquidity(_amount, min_amounts_meta) - - # Withdraw from base - _base_amount: uint256 = ERC20(self.coins[MAX_COIN]).balanceOf(self) - for i in range(BASE_N_COINS): - min_amounts_base[i] = min_amounts[MAX_COIN+i] - CurveBase(self.base_pool).remove_liquidity(_base_amount, min_amounts_base) - - # Transfer all coins out - for i in range(N_ALL_COINS): - coin: address = ZERO_ADDRESS - if i < MAX_COIN: - coin = self.coins[i] - else: - coin = self.base_coins[i - MAX_COIN] - amounts[i] = ERC20(coin).balanceOf(self) - # "safeTransfer" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(amounts[i], bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - return amounts - - -@external -def remove_liquidity_one_coin(_token_amount: uint256, i: int128, _min_amount: uint256) -> uint256: - """ - @notice Withdraw and unwrap a single coin from the pool - @param _token_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 - @return Amount of underlying coin received - """ - assert ERC20(self.token).transferFrom(msg.sender, self, _token_amount) - - coin: address = ZERO_ADDRESS - if i < MAX_COIN: - coin = self.coins[i] - # Withdraw a metapool coin - CurveMeta(self.pool).remove_liquidity_one_coin(_token_amount, i, _min_amount) - else: - coin = self.base_coins[i - MAX_COIN] - # Withdraw a base pool coin - CurveMeta(self.pool).remove_liquidity_one_coin(_token_amount, MAX_COIN, 0) - CurveBase(self.base_pool).remove_liquidity_one_coin( - ERC20(self.coins[MAX_COIN]).balanceOf(self), i-MAX_COIN, _min_amount - ) - - # Tranfer the coin out - coin_amount: uint256 = ERC20(coin).balanceOf(self) - # "safeTransfer" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(coin_amount, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - return coin_amount - - -@external -def remove_liquidity_imbalance(amounts: uint256[N_ALL_COINS], max_burn_amount: uint256) -> uint256: - """ - @notice Withdraw coins from the pool in an imbalanced amount - @param amounts List of amounts of underlying coins to withdraw - @param max_burn_amount Maximum amount of LP token to burn in the withdrawal. - This value cannot exceed the caller's LP token balance. - @return Actual amount of the LP token burned in the withdrawal - """ - _base_pool: address = self.base_pool - _meta_pool: address = self.pool - _base_coins: address[BASE_N_COINS] = self.base_coins - _meta_coins: address[N_COINS] = self.coins - _lp_token: address = self.token - - 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 - assert ERC20(_lp_token).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]) - leftover_amounts: uint256[N_COINS] = empty(uint256[N_COINS]) - - # Prepare quantities - for i in range(MAX_COIN): - amounts_meta[i] = amounts[i] - - for i in range(BASE_N_COINS): - amount: uint256 = amounts[MAX_COIN + i] - if amount != 0: - amounts_base[i] = amount - withdraw_base = True - - if withdraw_base: - amounts_meta[MAX_COIN] = CurveBase(self.base_pool).calc_token_amount(amounts_base, False) - amounts_meta[MAX_COIN] += amounts_meta[MAX_COIN] * fee / FEE_DENOMINATOR + 1 - - # Remove liquidity and deposit leftovers back - CurveMeta(_meta_pool).remove_liquidity_imbalance(amounts_meta, max_burn_amount) - if withdraw_base: - CurveBase(_base_pool).remove_liquidity_imbalance(amounts_base, amounts_meta[MAX_COIN]) - leftover_amounts[MAX_COIN] = ERC20(_meta_coins[MAX_COIN]).balanceOf(self) - if leftover_amounts[MAX_COIN] > 0: - CurveMeta(_meta_pool).add_liquidity(leftover_amounts, 0) - - # Transfer all coins out - for i in range(N_ALL_COINS): - coin: address = ZERO_ADDRESS - amount: uint256 = 0 - if i < MAX_COIN: - coin = _meta_coins[i] - amount = amounts_meta[i] - else: - coin = _base_coins[i - MAX_COIN] - amount = amounts_base[i - MAX_COIN] - # "safeTransfer" which works for ERC20s which return bool or not - if amount > 0: - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(amount, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - # Transfer the leftover LP token out - leftover: uint256 = ERC20(_lp_token).balanceOf(self) - if leftover > 0: - assert ERC20(_lp_token).transfer(msg.sender, leftover) - - return max_burn_amount - leftover - - -@view -@external -def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - """ - @notice Calculate the amount received when withdrawing and unwrapping a single coin - @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(self.pool).calc_withdraw_one_coin(_token_amount, i) - else: - _base_tokens: uint256 = CurveMeta(self.pool).calc_withdraw_one_coin(_token_amount, MAX_COIN) - return CurveBase(self.base_pool).calc_withdraw_one_coin(_base_tokens, i-MAX_COIN) - - -@view -@external -def calc_token_amount(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 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]) - - for i in range(MAX_COIN): - meta_amounts[i] = amounts[i] - - for i in range(BASE_N_COINS): - base_amounts[i] = amounts[i + MAX_COIN] - - _base_tokens: uint256 = CurveBase(self.base_pool).calc_token_amount(base_amounts, is_deposit) - meta_amounts[MAX_COIN] = _base_tokens - - return CurveMeta(self.pool).calc_token_amount(meta_amounts, is_deposit) diff --git a/contracts/pools/gusd/README.md b/contracts/pools/gusd/README.md deleted file mode 100644 index ca7bf748..00000000 --- a/contracts/pools/gusd/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# curve-contract/contracts/pools/gusd - -[Curve GUSD metapool](https://www.curve.fi/gusd), allowing swaps via the Curve [tri-pool](../3pool). - -## Contracts - -* [`DepositGUSD`](DepositGUSD.vy): Depositor contract, used to wrap underlying tokens prior to depositing them into the pool -* [`StableSwapGUSD`](StableSwapGUSD.vy): Curve stablecoin AMM contract - -## Deployments - -* [`CurveContractV2`](../../tokens/CurveTokenV2.vy): [0xD2967f45c4f384DEEa880F807Be904762a3DeA07](https://etherscan.io/address/0xD2967f45c4f384DEEa880F807Be904762a3DeA07) -* [`DepositGUSD`](DepositGUSD.vy): [0x64448B78561690B70E17CBE8029a3e5c1bB7136e](https://etherscan.io/address/0x64448B78561690B70E17CBE8029a3e5c1bB7136e) -* [`LiquidityGauge`](../../gauges/LiquidityGauge.vy): [0xC5cfaDA84E902aD92DD40194f0883ad49639b023](https://etherscan.io/address/0xC5cfaDA84E902aD92DD40194f0883ad49639b023) -* [`StableSwapGUSD`](StableSwapGUSD.vy): [0x4f062658EaAF2C1ccf8C8e36D6824CDf41167956](https://etherscan.io/address/0x4f062658EaAF2C1ccf8C8e36D6824CDf41167956) - -## Stablecoins - -Curve GUSD metapool utilizes the supports swaps between the following assets: - -## Direct swaps - -Direct swaps are possible between GUSD and the Curve tri-pool LP token. - -* `GUSD`: [0x056fd409e1d7a124bd7017459dfea2f387b6d5cd](https://etherscan.io/address/0x056fd409e1d7a124bd7017459dfea2f387b6d5cd) -* `3CRV`: [0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490](https://etherscan.io/address/0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490) - -## Base Pool coins - -The tri-pool LP token may be wrapped or unwrapped to provide swaps between GUSD and the following stablecoins: - -* `DAI`: [0x6b175474e89094c44da98b954eedeac495271d0f](https://etherscan.io/address/0x6b175474e89094c44da98b954eedeac495271d0f) -* `USDC`: [0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48](https://etherscan.io/address/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48) -* `USDT`: [0xdac17f958d2ee523a2206206994597c13d831ec7](https://etherscan.io/address/0xdac17f958d2ee523a2206206994597c13d831ec7) diff --git a/contracts/pools/gusd/StableSwapGUSD.vy b/contracts/pools/gusd/StableSwapGUSD.vy deleted file mode 100644 index 5c845820..00000000 --- a/contracts/pools/gusd/StableSwapGUSD.vy +++ /dev/null @@ -1,1082 +0,0 @@ -# @version 0.2.5 -""" -@title Curve GUSD Metapool -@author Curve.Fi -@license Copyright (c) Curve.Fi, 2020 - all rights reserved -@dev Utilizes 3Pool to allow swaps between GUSD / DAI / USDC / USDT -""" - -from vyper.interfaces import ERC20 - -interface CurveToken: - def totalSupply() -> uint256: view - def mint(_to: address, _value: uint256) -> bool: nonpayable - def burnFrom(_to: address, _value: uint256) -> bool: nonpayable - -interface Curve: - def coins(i: uint256) -> address: view - def get_virtual_price() -> uint256: view - def calc_token_amount(amounts: uint256[BASE_N_COINS], deposit: bool) -> uint256: view - def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: view - def fee() -> uint256: view - def get_dy(i: int128, j: int128, dx: uint256) -> uint256: view - def get_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256: view - def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256): nonpayable - def add_liquidity(amounts: uint256[BASE_N_COINS], min_mint_amount: uint256): nonpayable - def remove_liquidity_one_coin(_token_amount: uint256, i: int128, min_amount: uint256): nonpayable - - -# Events -event TokenExchange: - buyer: indexed(address) - sold_id: int128 - tokens_sold: uint256 - bought_id: int128 - tokens_bought: uint256 - -event TokenExchangeUnderlying: - buyer: indexed(address) - sold_id: int128 - tokens_sold: uint256 - bought_id: int128 - tokens_bought: uint256 - -event AddLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event RemoveLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - token_supply: uint256 - -event RemoveLiquidityOne: - provider: indexed(address) - token_amount: uint256 - coin_amount: uint256 - token_supply: uint256 - -event RemoveLiquidityImbalance: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event CommitNewAdmin: - deadline: indexed(uint256) - admin: indexed(address) - -event NewAdmin: - admin: indexed(address) - -event CommitNewFee: - deadline: indexed(uint256) - fee: uint256 - admin_fee: uint256 - -event NewFee: - fee: uint256 - admin_fee: uint256 - -event RampA: - old_A: uint256 - new_A: uint256 - initial_time: uint256 - future_time: uint256 - -event StopRampA: - A: uint256 - t: uint256 - - -N_COINS: constant(int128) = 2 -MAX_COIN: constant(int128) = N_COINS - 1 - -FEE_DENOMINATOR: constant(uint256) = 10 ** 10 -PRECISION: constant(uint256) = 10 ** 18 # The precision to convert to -PRECISION_MUL: constant(uint256[N_COINS]) = [10000000000000000, 1] -RATES: constant(uint256[N_COINS]) = [10000000000000000000000000000000000, 1000000000000000000] - -BASE_N_COINS: constant(int128) = 3 -N_ALL_COINS: constant(int128) = N_COINS + BASE_N_COINS - 1 -BASE_PRECISION_MUL: constant(uint256[BASE_N_COINS]) = [1, 1000000000000, 1000000000000] -BASE_RATES: constant(uint256[BASE_N_COINS]) = [1000000000000000000, 1000000000000000000000000000000, 1000000000000000000000000000000] - -# An asset which may have a transfer fee (USDT) -FEE_ASSET: constant(address) = 0xdAC17F958D2ee523a2206206994597C13D831ec7 - -MAX_ADMIN_FEE: constant(uint256) = 10 * 10 ** 9 -MAX_FEE: constant(uint256) = 5 * 10 ** 9 -MAX_A: constant(uint256) = 10 ** 6 -MAX_A_CHANGE: constant(uint256) = 10 - -ADMIN_ACTIONS_DELAY: constant(uint256) = 3 * 86400 -MIN_RAMP_TIME: constant(uint256) = 86400 - -coins: public(address[N_COINS]) -balances: public(uint256[N_COINS]) -fee: public(uint256) # fee * 1e10 -admin_fee: public(uint256) # admin_fee * 1e10 - -owner: public(address) -token: CurveToken - -# Token corresponding to the pool is always the last one -BASE_POOL_COINS: constant(int128) = 3 -BASE_CACHE_EXPIRES: constant(int128) = 10 * 60 # 10 min -base_pool: public(address) -base_virtual_price: public(uint256) -base_cache_updated: public(uint256) -base_coins: public(address[BASE_POOL_COINS]) - -A_PRECISION: constant(uint256) = 100 -initial_A: public(uint256) -future_A: public(uint256) -initial_A_time: public(uint256) -future_A_time: public(uint256) - -admin_actions_deadline: public(uint256) -transfer_ownership_deadline: public(uint256) -future_fee: public(uint256) -future_admin_fee: public(uint256) -future_owner: public(address) - -is_killed: bool -kill_deadline: uint256 -KILL_DEADLINE_DT: constant(uint256) = 2 * 30 * 86400 - - -@external -def __init__( - _owner: address, - _coins: address[N_COINS], - _pool_token: address, - _base_pool: address, - _A: uint256, - _fee: uint256, - _admin_fee: uint256 -): - """ - @notice Contract constructor - @param _owner Contract owner address - @param _coins Addresses of ERC20 conracts of coins - @param _pool_token Address of the token representing LP share - @param _base_pool Address of the base pool (which will have a virtual price) - @param _A Amplification coefficient multiplied by n * (n - 1) - @param _fee Fee to charge for exchanges - @param _admin_fee Admin fee - """ - for i in range(N_COINS): - assert _coins[i] != ZERO_ADDRESS - self.coins = _coins - self.initial_A = _A * A_PRECISION - self.future_A = _A * A_PRECISION - self.fee = _fee - self.admin_fee = _admin_fee - self.owner = _owner - self.kill_deadline = block.timestamp + KILL_DEADLINE_DT - self.token = CurveToken(_pool_token) - - self.base_pool = _base_pool - self.base_virtual_price = Curve(_base_pool).get_virtual_price() - self.base_cache_updated = block.timestamp - for i in range(BASE_POOL_COINS): - _base_coin: address = Curve(_base_pool).coins(convert(i, uint256)) - self.base_coins[i] = _base_coin - - # approve underlying coins for infinite transfers - _response: Bytes[32] = raw_call( - _base_coin, - concat( - method_id("approve(address,uint256)"), - convert(_base_pool, bytes32), - convert(MAX_UINT256, bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) - - -@view -@internal -def _A() -> uint256: - """ - Handle ramping A up or down - """ - t1: uint256 = self.future_A_time - A1: uint256 = self.future_A - - if block.timestamp < t1: - A0: uint256 = self.initial_A - t0: uint256 = self.initial_A_time - # Expressions in uint256 cannot have negative numbers, thus "if" - if A1 > A0: - return A0 + (A1 - A0) * (block.timestamp - t0) / (t1 - t0) - else: - return A0 - (A0 - A1) * (block.timestamp - t0) / (t1 - t0) - - else: # when t1 == 0 or block.timestamp >= t1 - return A1 - - -@view -@external -def A() -> uint256: - return self._A() / A_PRECISION - - -@view -@external -def A_precise() -> uint256: - return self._A() - - -@view -@internal -def _xp(vp_rate: uint256) -> uint256[N_COINS]: - result: uint256[N_COINS] = RATES - result[MAX_COIN] = vp_rate # virtual price for the metacurrency - for i in range(N_COINS): - result[i] = result[i] * self.balances[i] / PRECISION - return result - - -@pure -@internal -def _xp_mem(vp_rate: uint256, _balances: uint256[N_COINS]) -> uint256[N_COINS]: - result: uint256[N_COINS] = RATES - result[MAX_COIN] = vp_rate # virtual price for the metacurrency - for i in range(N_COINS): - result[i] = result[i] * _balances[i] / PRECISION - return result - - -@internal -def _vp_rate() -> uint256: - if block.timestamp > self.base_cache_updated + BASE_CACHE_EXPIRES: - vprice: uint256 = Curve(self.base_pool).get_virtual_price() - self.base_virtual_price = vprice - self.base_cache_updated = block.timestamp - return vprice - else: - return self.base_virtual_price - - -@internal -@view -def _vp_rate_ro() -> uint256: - if block.timestamp > self.base_cache_updated + BASE_CACHE_EXPIRES: - return Curve(self.base_pool).get_virtual_price() - else: - return self.base_virtual_price - - -@pure -@internal -def get_D(xp: uint256[N_COINS], amp: uint256) -> uint256: - S: uint256 = 0 - Dprev: uint256 = 0 - for _x in xp: - S += _x - if S == 0: - return 0 - - D: uint256 = S - Ann: uint256 = amp * N_COINS - for _i in range(255): - D_P: uint256 = D - for _x in xp: - D_P = D_P * D / (_x * N_COINS) # If division by 0, this will be borked: only withdrawal will work. And that is good - Dprev = D - D = (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (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 - - -@view -@internal -def get_D_mem(vp_rate: uint256, _balances: uint256[N_COINS], amp: uint256) -> uint256: - xp: uint256[N_COINS] = self._xp_mem(vp_rate, _balances) - return self.get_D(xp, amp) - - -@view -@external -def get_virtual_price() -> uint256: - """ - @notice The current virtual price of the pool LP token - @dev Useful for calculating profits - @return LP token virtual price normalized to 1e18 - """ - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate_ro() - xp: uint256[N_COINS] = self._xp(vp_rate) - D: uint256 = self.get_D(xp, amp) - # 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 - - -@view -@external -def calc_token_amount(amounts: uint256[N_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 amounts Amount of each coin being deposited - @param is_deposit set True for deposits, False for withdrawals - @return Expected amount of LP tokens received - """ - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate_ro() - _balances: uint256[N_COINS] = self.balances - D0: uint256 = self.get_D_mem(vp_rate, _balances, amp) - for i in range(N_COINS): - if is_deposit: - _balances[i] += amounts[i] - else: - _balances[i] -= amounts[i] - D1: uint256 = self.get_D_mem(vp_rate, _balances, amp) - token_amount: uint256 = self.token.totalSupply() - diff: uint256 = 0 - if is_deposit: - diff = D1 - D0 - else: - diff = D0 - D1 - return diff * token_amount / D0 - - -@external -@nonreentrant('lock') -def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256) -> uint256: - """ - @notice Deposit coins into the pool - @param amounts List of amounts of coins to deposit - @param min_mint_amount Minimum amount of LP tokens to mint from the deposit - @return Amount of LP tokens received by depositing - """ - assert not self.is_killed # dev: is killed - - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate() - token_supply: uint256 = self.token.totalSupply() - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - _admin_fee: uint256 = self.admin_fee - - # Initial invariant - D0: uint256 = 0 - old_balances: uint256[N_COINS] = self.balances - if token_supply > 0: - D0 = self.get_D_mem(vp_rate, old_balances, amp) - new_balances: uint256[N_COINS] = old_balances - - for i in range(N_COINS): - if token_supply == 0: - assert amounts[i] > 0 # dev: initial deposit requires all coins - # balances store amounts of c-tokens - new_balances[i] = old_balances[i] + amounts[i] - - # Invariant after change - D1: uint256 = self.get_D_mem(vp_rate, new_balances, amp) - assert D1 > D0 - - # We need to recalculate the invariant accounting for fees - # to calculate fair user's share - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - 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(vp_rate, new_balances, amp) - 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 amounts[i] > 0: - assert ERC20(self.coins[i]).transferFrom(msg.sender, self, amounts[i]) # dev: failed transfer - - # Mint pool tokens - self.token.mint(msg.sender, mint_amount) - - log AddLiquidity(msg.sender, amounts, fees, D1, token_supply + mint_amount) - - return mint_amount - - -@view -@internal -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 # dev: same coin - assert j >= 0 # dev: j below zero - assert j < N_COINS # dev: j above N_COINS - - # should be unreachable, but good for safety - assert i >= 0 - assert i < N_COINS - - amp: uint256 = self._A() - D: uint256 = self.get_D(xp_, amp) - - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: uint256 = 0 - c: uint256 = D - Ann: uint256 = amp * N_COINS - - 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 * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann # - D - 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 - - -@view -@external -def get_dy(i: int128, j: int128, dx: uint256) -> uint256: - # dx and dy in c-units - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = self._vp_rate_ro() - xp: uint256[N_COINS] = self._xp(rates[MAX_COIN]) - - x: uint256 = xp[i] + (dx * rates[i] / PRECISION) - y: uint256 = self.get_y(i, j, x, xp) - dy: uint256 = xp[j] - y - 1 - _fee: uint256 = self.fee * dy / FEE_DENOMINATOR - return (dy - _fee) * PRECISION / rates[j] - - -@view -@external -def get_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256: - # dx and dy in underlying units - vp_rate: uint256 = self._vp_rate_ro() - xp: uint256[N_COINS] = self._xp(vp_rate) - precisions: uint256[N_COINS] = PRECISION_MUL - _base_pool: address = self.base_pool - - # Use base_i or base_j if they are >= 0 - base_i: int128 = i - MAX_COIN - base_j: int128 = j - MAX_COIN - meta_i: int128 = MAX_COIN - meta_j: int128 = MAX_COIN - if base_i < 0: - meta_i = i - if base_j < 0: - meta_j = j - - x: uint256 = 0 - if base_i < 0: - x = xp[i] + dx * precisions[i] - else: - if base_j < 0: - # i is from BasePool - # At first, get the amount of pool tokens - base_inputs: uint256[BASE_N_COINS] = empty(uint256[BASE_N_COINS]) - base_inputs[base_i] = dx - # Token amount transformed to underlying "dollars" - x = Curve(_base_pool).calc_token_amount(base_inputs, True) * vp_rate / PRECISION - # Accounting for deposit/withdraw fees approximately - x -= x * Curve(_base_pool).fee() / (2 * FEE_DENOMINATOR) - # Adding number of pool tokens - x += xp[MAX_COIN] - else: - # If both are from the base pool - return Curve(_base_pool).get_dy(base_i, base_j, dx) - - # This pool is involved only when in-pool assets are used - y: uint256 = self.get_y(meta_i, meta_j, x, xp) - dy: uint256 = xp[meta_j] - y - 1 - dy = (dy - self.fee * dy / FEE_DENOMINATOR) - - # If output is going via the metapool - if base_j < 0: - dy /= precisions[meta_j] - else: - # j is from BasePool - # The fee is already accounted for - dy = Curve(_base_pool).calc_withdraw_one_coin(dy * PRECISION / vp_rate, base_j) - - return dy - - -@external -@nonreentrant('lock') -def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256) -> uint256: - """ - @notice Perform an exchange between two coins - @dev Index values can be found via the `coins` public getter method - @param i Index value for the coin to send - @param j Index valie of the coin to recieve - @param dx Amount of `i` being exchanged - @param min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - assert not self.is_killed # dev: is killed - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = self._vp_rate() - - old_balances: uint256[N_COINS] = self.balances - xp: uint256[N_COINS] = self._xp_mem(rates[MAX_COIN], old_balances) - - x: uint256 = xp[i] + dx * rates[i] / PRECISION - y: uint256 = self.get_y(i, j, x, xp) - - dy: uint256 = xp[j] - y - 1 # -1 just in case there were some rounding errors - dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR - - # Convert all to real units - dy = (dy - dy_fee) * PRECISION / rates[j] - assert dy >= min_dy, "Too few coins in result" - - dy_admin_fee: uint256 = dy_fee * self.admin_fee / FEE_DENOMINATOR - dy_admin_fee = dy_admin_fee * PRECISION / rates[j] - - # Change balances exactly in same way as we change actual ERC20 coin amounts - self.balances[i] = old_balances[i] + dx - # When rounding errors happen, we undercharge admin fee in favor of LP - self.balances[j] = old_balances[j] - dy - dy_admin_fee - - assert ERC20(self.coins[i]).transferFrom(msg.sender, self, dx) - assert ERC20(self.coins[j]).transfer(msg.sender, dy) - - log TokenExchange(msg.sender, i, dx, j, dy) - - return dy - - -@external -@nonreentrant('lock') -def exchange_underlying(i: int128, j: int128, dx: uint256, min_dy: uint256) -> uint256: - """ - @notice Perform an exchange between two underlying coins - @dev Index values can be found via the `underlying_coins` public getter method - @param i Index value for the underlying coin to send - @param j Index valie of the underlying coin to recieve - @param dx Amount of `i` being exchanged - @param min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - assert not self.is_killed # dev: is killed - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = self._vp_rate() - _base_pool: address = self.base_pool - - # Use base_i or base_j if they are >= 0 - base_i: int128 = i - MAX_COIN - base_j: int128 = j - MAX_COIN - meta_i: int128 = MAX_COIN - meta_j: int128 = MAX_COIN - if base_i < 0: - meta_i = i - if base_j < 0: - meta_j = j - dy: uint256 = 0 - - # Addresses for input and output coins - input_coin: address = ZERO_ADDRESS - if base_i < 0: - input_coin = self.coins[i] - else: - input_coin = self.base_coins[base_i] - output_coin: address = ZERO_ADDRESS - if base_j < 0: - output_coin = self.coins[j] - else: - output_coin = self.base_coins[base_j] - - # Handle potential Tether fees - dx_w_fee: uint256 = dx - if input_coin == FEE_ASSET: - dx_w_fee = ERC20(FEE_ASSET).balanceOf(self) - # "safeTransferFrom" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - input_coin, - concat( - method_id("transferFrom(address,address,uint256)"), - convert(msg.sender, bytes32), - convert(self, bytes32), - convert(dx, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransferFrom" - # Handle potential Tether fees - if input_coin == FEE_ASSET: - dx_w_fee = ERC20(FEE_ASSET).balanceOf(self) - dx_w_fee - - if base_i < 0 or base_j < 0: - old_balances: uint256[N_COINS] = self.balances - xp: uint256[N_COINS] = self._xp_mem(rates[MAX_COIN], old_balances) - - x: uint256 = 0 - if base_i < 0: - x = xp[i] + dx_w_fee * rates[i] / PRECISION - else: - # i is from BasePool - # At first, get the amount of pool tokens - base_inputs: uint256[BASE_N_COINS] = empty(uint256[BASE_N_COINS]) - base_inputs[base_i] = dx_w_fee - coin_i: address = self.coins[MAX_COIN] - # Deposit and measure delta - x = ERC20(coin_i).balanceOf(self) - Curve(_base_pool).add_liquidity(base_inputs, 0) - # Need to convert pool token to "virtual" units using rates - # dx is also different now - dx_w_fee = ERC20(coin_i).balanceOf(self) - x - x = dx_w_fee * rates[MAX_COIN] / PRECISION - # Adding number of pool tokens - x += xp[MAX_COIN] - - y: uint256 = self.get_y(meta_i, meta_j, x, xp) - - # Either a real coin or token - dy = xp[meta_j] - y - 1 # -1 just in case there were some rounding errors - dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR - - # Convert all to real units - # Works for both pool coins and real coins - dy = (dy - dy_fee) * PRECISION / rates[meta_j] - - dy_admin_fee: uint256 = dy_fee * self.admin_fee / FEE_DENOMINATOR - dy_admin_fee = dy_admin_fee * PRECISION / rates[meta_j] - - # Change balances exactly in same way as we change actual ERC20 coin amounts - self.balances[meta_i] = old_balances[meta_i] + dx_w_fee - # When rounding errors happen, we undercharge admin fee in favor of LP - self.balances[meta_j] = old_balances[meta_j] - dy - dy_admin_fee - - # Withdraw from the base pool if needed - if base_j >= 0: - out_amount: uint256 = ERC20(output_coin).balanceOf(self) - Curve(_base_pool).remove_liquidity_one_coin(dy, base_j, 0) - dy = ERC20(output_coin).balanceOf(self) - out_amount - - assert dy >= min_dy, "Too few coins in result" - - else: - # If both are from the base pool - dy = ERC20(output_coin).balanceOf(self) - Curve(_base_pool).exchange(base_i, base_j, dx_w_fee, min_dy) - dy = ERC20(output_coin).balanceOf(self) - dy - - # "safeTransfer" which works for ERC20s which return bool or not - _response = raw_call( - output_coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(dy, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - log TokenExchangeUnderlying(msg.sender, i, dx, j, dy) - - return dy - - -@external -@nonreentrant('lock') -def remove_liquidity(_amount: uint256, min_amounts: uint256[N_COINS]) -> uint256[N_COINS]: - """ - @notice Withdraw coins from the pool - @dev Withdrawal amounts are based on current deposit ratios - @param _amount Quantity of LP tokens to burn in the withdrawal - @param min_amounts Minimum amounts of underlying coins to receive - @return List of amounts of coins that were withdrawn - """ - total_supply: uint256 = self.token.totalSupply() - amounts: uint256[N_COINS] = empty(uint256[N_COINS]) - fees: uint256[N_COINS] = empty(uint256[N_COINS]) # Fees are unused but we've got them historically in event - - for i in range(N_COINS): - value: uint256 = self.balances[i] * _amount / total_supply - assert value >= min_amounts[i], "Too few coins in result" - self.balances[i] -= value - amounts[i] = value - assert ERC20(self.coins[i]).transfer(msg.sender, value) - - self.token.burnFrom(msg.sender, _amount) # dev: insufficient funds - - log RemoveLiquidity(msg.sender, amounts, fees, total_supply - _amount) - - return amounts - - -@external -@nonreentrant('lock') -def remove_liquidity_imbalance(amounts: uint256[N_COINS], max_burn_amount: uint256) -> uint256: - """ - @notice Withdraw coins from the pool in an imbalanced amount - @param amounts List of amounts of underlying coins to withdraw - @param max_burn_amount Maximum amount of LP token to burn in the withdrawal - @return Actual amount of the LP token burned in the withdrawal - """ - assert not self.is_killed # dev: is killed - - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate() - - token_supply: uint256 = self.token.totalSupply() - assert token_supply != 0 # dev: zero total supply - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - _admin_fee: uint256 = self.admin_fee - - old_balances: uint256[N_COINS] = self.balances - new_balances: uint256[N_COINS] = old_balances - D0: uint256 = self.get_D_mem(vp_rate, old_balances, amp) - for i in range(N_COINS): - new_balances[i] -= amounts[i] - D1: uint256 = self.get_D_mem(vp_rate, new_balances, amp) - - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - 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(vp_rate, new_balances, amp) - - token_amount: uint256 = (D0 - D2) * token_supply / D0 - assert token_amount != 0 # dev: zero tokens burned - token_amount += 1 # In case of rounding errors - make it unfavorable for the "attacker" - assert token_amount <= max_burn_amount, "Slippage screwed you" - - self.token.burnFrom(msg.sender, token_amount) # dev: insufficient funds - for i in range(N_COINS): - if amounts[i] != 0: - assert ERC20(self.coins[i]).transfer(msg.sender, amounts[i]) - - log RemoveLiquidityImbalance(msg.sender, amounts, fees, D1, token_supply - token_amount) - - return token_amount - - -@view -@internal -def get_y_D(A_: uint256, i: int128, xp: uint256[N_COINS], D: uint256) -> uint256: - """ - Calculate x[i] if one reduces D from being calculated for xp to D - - Done by solving quadratic equation iteratively. - x_1**2 + x1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) - x_1**2 + b*x_1 = c - - x_1 = (x_1**2 + c) / (2*x_1 + b) - """ - # x in the input is converted to the same price/precision - - assert i >= 0 # dev: i below zero - assert i < N_COINS # dev: i above N_COINS - - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: uint256 = 0 - - c: uint256 = D - Ann: uint256 = A_ * N_COINS - - for _i in range(N_COINS): - if _i != i: - _x = xp[_i] - else: - continue - S_ += _x - c = c * D / (_x * N_COINS) - c = c * D * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann - 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 - - -@view -@internal -def _calc_withdraw_one_coin(_token_amount: uint256, i: int128, vp_rate: uint256) -> (uint256, uint256, uint256): - # First, need to calculate - # * Get current D - # * Solve Eqn against y_i for D - _token_amount - amp: uint256 = self._A() - xp: uint256[N_COINS] = self._xp(vp_rate) - D0: uint256 = self.get_D(xp, amp) - - total_supply: uint256 = self.token.totalSupply() - D1: uint256 = D0 - _token_amount * D0 / total_supply - new_y: uint256 = self.get_y_D(amp, i, xp, D1) - - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = vp_rate - - xp_reduced: uint256[N_COINS] = xp - dy_0: uint256 = (xp[i] - new_y) * PRECISION / rates[i] # w/o fees - - for j in range(N_COINS): - dx_expected: uint256 = 0 - if j == i: - dx_expected = xp[j] * D1 / D0 - new_y - else: - dx_expected = xp[j] - xp[j] * D1 / D0 - xp_reduced[j] -= _fee * dx_expected / FEE_DENOMINATOR - - dy: uint256 = xp_reduced[i] - self.get_y_D(amp, i, xp_reduced, D1) - dy = (dy - 1) * PRECISION / rates[i] # Withdraw less to account for rounding errors - - return dy, dy_0 - dy, total_supply - - -@view -@external -def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - """ - @notice Calculate the amount received when withdrawing a single coin - @param _token_amount Amount of LP tokens to burn in the withdrawal - @param i Index value of the coin to withdraw - @return Amount of coin received - """ - vp_rate: uint256 = self._vp_rate_ro() - return self._calc_withdraw_one_coin(_token_amount, i, vp_rate)[0] - - -@external -@nonreentrant('lock') -def remove_liquidity_one_coin(_token_amount: uint256, i: int128, _min_amount: uint256) -> uint256: - """ - @notice Withdraw a single coin from the pool - @param _token_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 coin to receive - @return Amount of coin received - """ - assert not self.is_killed # dev: is killed - - vp_rate: uint256 = self._vp_rate() - dy: uint256 = 0 - dy_fee: uint256 = 0 - total_supply: uint256 = 0 - dy, dy_fee, total_supply = self._calc_withdraw_one_coin(_token_amount, i, vp_rate) - assert dy >= _min_amount, "Not enough coins removed" - - self.balances[i] -= (dy + dy_fee * self.admin_fee / FEE_DENOMINATOR) - self.token.burnFrom(msg.sender, _token_amount) # dev: insufficient funds - assert ERC20(self.coins[i]).transfer(msg.sender, dy) - - log RemoveLiquidityOne(msg.sender, _token_amount, dy, total_supply - _token_amount) - - return dy - - -### Admin functions ### -@external -def ramp_A(_future_A: uint256, _future_time: uint256): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.initial_A_time + MIN_RAMP_TIME - assert _future_time >= block.timestamp + MIN_RAMP_TIME # dev: insufficient time - - _initial_A: uint256 = self._A() - _future_A_p: uint256 = _future_A * A_PRECISION - - assert _future_A > 0 and _future_A < MAX_A - if _future_A_p < _initial_A: - assert _future_A_p * MAX_A_CHANGE >= _initial_A - else: - assert _future_A_p <= _initial_A * MAX_A_CHANGE - - self.initial_A = _initial_A - self.future_A = _future_A_p - self.initial_A_time = block.timestamp - self.future_A_time = _future_time - - log RampA(_initial_A, _future_A_p, block.timestamp, _future_time) - - -@external -def stop_ramp_A(): - assert msg.sender == self.owner # dev: only owner - - current_A: uint256 = self._A() - self.initial_A = current_A - self.future_A = current_A - self.initial_A_time = block.timestamp - self.future_A_time = block.timestamp - # now (block.timestamp < t1) is always False, so we return saved A - - log StopRampA(current_A, block.timestamp) - - -@external -def commit_new_fee(new_fee: uint256, new_admin_fee: uint256): - assert msg.sender == self.owner # dev: only owner - assert self.admin_actions_deadline == 0 # dev: active action - assert new_fee <= MAX_FEE # dev: fee exceeds maximum - assert new_admin_fee <= MAX_ADMIN_FEE # dev: admin fee exceeds maximum - - _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.admin_actions_deadline = _deadline - self.future_fee = new_fee - self.future_admin_fee = new_admin_fee - - log CommitNewFee(_deadline, new_fee, new_admin_fee) - - -@external -def apply_new_fee(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.admin_actions_deadline # dev: insufficient time - assert self.admin_actions_deadline != 0 # dev: no active action - - self.admin_actions_deadline = 0 - _fee: uint256 = self.future_fee - _admin_fee: uint256 = self.future_admin_fee - self.fee = _fee - self.admin_fee = _admin_fee - - log NewFee(_fee, _admin_fee) - - -@external -def revert_new_parameters(): - assert msg.sender == self.owner # dev: only owner - - self.admin_actions_deadline = 0 - - -@external -def commit_transfer_ownership(_owner: address): - assert msg.sender == self.owner # dev: only owner - assert self.transfer_ownership_deadline == 0 # dev: active transfer - - _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.transfer_ownership_deadline = _deadline - self.future_owner = _owner - - log CommitNewAdmin(_deadline, _owner) - - -@external -def apply_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.transfer_ownership_deadline # dev: insufficient time - assert self.transfer_ownership_deadline != 0 # dev: no active transfer - - self.transfer_ownership_deadline = 0 - _owner: address = self.future_owner - self.owner = _owner - - log NewAdmin(_owner) - - -@external -def revert_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - - self.transfer_ownership_deadline = 0 - - -@view -@external -def admin_balances(i: uint256) -> uint256: - return ERC20(self.coins[i]).balanceOf(self) - self.balances[i] - - -@external -def withdraw_admin_fees(): - assert msg.sender == self.owner # dev: only owner - - for i in range(N_COINS): - c: address = self.coins[i] - value: uint256 = ERC20(c).balanceOf(self) - self.balances[i] - if value > 0: - assert ERC20(c).transfer(msg.sender, value) - - -@external -def donate_admin_fees(): - assert msg.sender == self.owner # dev: only owner - for i in range(N_COINS): - self.balances[i] = ERC20(self.coins[i]).balanceOf(self) - - -@external -def kill_me(): - assert msg.sender == self.owner # dev: only owner - assert self.kill_deadline > block.timestamp # dev: deadline has passed - self.is_killed = True - - -@external -def unkill_me(): - assert msg.sender == self.owner # dev: only owner - self.is_killed = False diff --git a/contracts/pools/gusd/pooldata.json b/contracts/pools/gusd/pooldata.json deleted file mode 100644 index 8927d9ac..00000000 --- a/contracts/pools/gusd/pooldata.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "base_pool": "3pool", - "pool_types": ["meta"], - "lp_contract": "CurveTokenV2", - "swap_address": "0x4f062658EaAF2C1ccf8C8e36D6824CDf41167956", - "lp_token_address": "0xD2967f45c4f384DEEa880F807Be904762a3DeA07", - "zap_address": "0x64448B78561690B70E17CBE8029a3e5c1bB7136e", - "gauge_addresses": ["0xC5cfaDA84E902aD92DD40194f0883ad49639b023"], - "lp_constructor": { - "symbol": "gusd3CRV", - "name": "Curve.fi GUSD/3Crv" - }, - "swap_constructor": { - "_A": 200, - "_fee": 4000000, - "_admin_fee": 0 - }, - "coins": [ - { - "name": "GUSD", - "decimals": 2, - "tethered": false, - "underlying_address": "0x056fd409e1d7a124bd7017459dfea2f387b6d5cd" - }, - { - "name": "3CRV", - "decimals": 18, - "base_pool_token": true, - "underlying_address": "0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490" - } - ] -} diff --git a/contracts/pools/hbtc/README.md b/contracts/pools/hbtc/README.md deleted file mode 100644 index 7f02c535..00000000 --- a/contracts/pools/hbtc/README.md +++ /dev/null @@ -1,20 +0,0 @@ -# curve-contract/contracts/pools/hbtc - -[Curve hBTC pool](https://www.curve.fi/hbtc). This is a no-lending pool. - -## Contracts - -* [`StableSwapHBTC`](StableSwapHBTC.vy): Curve stablecoin AMM contract - -## Deployments - -* [`CurveContractV2`](../../tokens/CurveTokenV2.vy): [0xb19059ebb43466C323583928285a49f558E572Fd](https://etherscan.io/address/0xb19059ebb43466C323583928285a49f558E572Fd) -* [`LiquidityGauge`](../../gauges/LiquidityGauge.vy): [0x4c18E409Dc8619bFb6a1cB56D114C3f592E0aE79](https://etherscan.io/address/0x4c18E409Dc8619bFb6a1cB56D114C3f592E0aE79) -* [`StableSwapHBTC`](StableSwapHBTC.vy): [0x4CA9b3063Ec5866A4B82E437059D2C43d1be596F](https://etherscan.io/address/0x4CA9b3063Ec5866A4B82E437059D2C43d1be596F) - -## Stablecoins - -Curve hBTC pool supports swaps between the following stablecoins: - -* `hBTC`: [0x0316EB71485b0Ab14103307bf65a021042c6d380](https://etherscan.io/address/0x0316EB71485b0Ab14103307bf65a021042c6d380) -* `wBTC`: [0x2260fac5e5542a773aa44fbcfedf7c193bc2c599](https://etherscan.io/address/0x2260fac5e5542a773aa44fbcfedf7c193bc2c599) diff --git a/contracts/pools/hbtc/StableSwapHBTC.vy b/contracts/pools/hbtc/StableSwapHBTC.vy deleted file mode 100644 index 7406cc66..00000000 --- a/contracts/pools/hbtc/StableSwapHBTC.vy +++ /dev/null @@ -1,756 +0,0 @@ -# @version 0.2.4 -# (c) Curve.Fi, 2020 -# Pool for hBTC/wBTC - -from vyper.interfaces import ERC20 - -interface CurveToken: - def set_minter(_minter: address): nonpayable - def set_name(_name: String[64], _symbol: String[32]): nonpayable - def totalSupply() -> uint256: view - def allowance(_owner: address, _spender: address) -> uint256: view - def transfer(_to: address, _value: uint256) -> bool: nonpayable - def transferFrom(_from: address, _to: address, _value: uint256) -> bool: nonpayable - def approve(_spender: address, _value: uint256) -> bool: nonpayable - def mint(_to: address, _value: uint256) -> bool: nonpayable - def burnFrom(_to: address, _value: uint256) -> bool: nonpayable - def name() -> String[64]: view - def symbol() -> String[32]: view - def decimals() -> uint256: view - def balanceOf(arg0: address) -> uint256: view - - -# Events -event TokenExchange: - buyer: indexed(address) - sold_id: int128 - tokens_sold: uint256 - bought_id: int128 - tokens_bought: uint256 - - -event AddLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event RemoveLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - token_supply: uint256 - -event RemoveLiquidityOne: - provider: indexed(address) - token_amount: uint256 - coin_amount: uint256 - -event RemoveLiquidityImbalance: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event CommitNewAdmin: - deadline: indexed(uint256) - admin: indexed(address) - -event NewAdmin: - admin: indexed(address) - - -event CommitNewFee: - deadline: indexed(uint256) - fee: uint256 - admin_fee: uint256 - -event NewFee: - fee: uint256 - admin_fee: uint256 - -event RampA: - old_A: uint256 - new_A: uint256 - initial_time: uint256 - future_time: uint256 - -event StopRampA: - A: uint256 - t: uint256 - - -# This can (and needs to) be changed at compile time -N_COINS: constant(int128) = 2 # <- change - -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]) = [1, 10000000000] -RATES: constant(uint256[N_COINS]) = [1000000000000000000, 10000000000000000000000000000] - -MAX_ADMIN_FEE: constant(uint256) = 10 * 10 ** 9 -MAX_FEE: constant(uint256) = 5 * 10 ** 9 -MAX_A: constant(uint256) = 10 ** 6 -MAX_A_CHANGE: constant(uint256) = 10 - -ADMIN_ACTIONS_DELAY: constant(uint256) = 3 * 86400 -MIN_RAMP_TIME: constant(uint256) = 86400 - -coins: public(address[N_COINS]) -balances: public(uint256[N_COINS]) -fee: public(uint256) # fee * 1e10 -admin_fee: public(uint256) # admin_fee * 1e10 - -owner: public(address) -token: CurveToken - -initial_A: public(uint256) -future_A: public(uint256) -initial_A_time: public(uint256) -future_A_time: public(uint256) - -admin_actions_deadline: public(uint256) -transfer_ownership_deadline: public(uint256) -future_fee: public(uint256) -future_admin_fee: public(uint256) -future_owner: public(address) - -is_killed: bool -kill_deadline: uint256 -KILL_DEADLINE_DT: constant(uint256) = 2 * 30 * 86400 - - -@external -def __init__( - _owner: address, - _coins: address[N_COINS], - _pool_token: address, - _A: uint256, - _fee: uint256, - _admin_fee: uint256 -): - """ - @notice Contract constructor - @param _owner Contract owner address - @param _coins Addresses of ERC20 conracts of coins - @param _pool_token Address of the token representing LP share - @param _A Amplification coefficient multiplied by n * (n - 1) - @param _fee Fee to charge for exchanges - @param _admin_fee Admin fee - """ - for i in range(N_COINS): - assert _coins[i] != ZERO_ADDRESS - self.coins = _coins - self.initial_A = _A - self.future_A = _A - self.fee = _fee - self.admin_fee = _admin_fee - self.owner = _owner - self.kill_deadline = block.timestamp + KILL_DEADLINE_DT - self.token = CurveToken(_pool_token) - - -@view -@internal -def _A() -> uint256: - """ - Handle ramping A up or down - """ - t1: uint256 = self.future_A_time - A1: uint256 = self.future_A - - if block.timestamp < t1: - A0: uint256 = self.initial_A - t0: uint256 = self.initial_A_time - # Expressions in uint256 cannot have negative numbers, thus "if" - if A1 > A0: - return A0 + (A1 - A0) * (block.timestamp - t0) / (t1 - t0) - else: - return A0 - (A0 - A1) * (block.timestamp - t0) / (t1 - t0) - - else: # when t1 == 0 or block.timestamp >= t1 - return A1 - - -@view -@external -def A() -> uint256: - return self._A() - - -@view -@internal -def _xp() -> uint256[N_COINS]: - result: uint256[N_COINS] = RATES - for i in range(N_COINS): - result[i] = result[i] * self.balances[i] / LENDING_PRECISION - return result - - -@pure -@internal -def _xp_mem(_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 - - -@pure -@internal -def get_D(xp: uint256[N_COINS], amp: uint256) -> uint256: - S: uint256 = 0 - for _x in xp: - S += _x - if S == 0: - return 0 - - Dprev: uint256 = 0 - D: uint256 = S - Ann: uint256 = amp * N_COINS - for _i in range(255): - D_P: uint256 = D - for _x in xp: - D_P = D_P * D / (_x * N_COINS) # If division by 0, this will be borked: only withdrawal will work. And that is good - 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 - - -@view -@internal -def get_D_mem(_balances: uint256[N_COINS], amp: uint256) -> uint256: - return self.get_D(self._xp_mem(_balances), amp) - - -@view -@external -def get_virtual_price() -> uint256: - """ - Returns portfolio virtual price (for calculating profit) - scaled up by 1e18 - """ - D: uint256 = self.get_D(self._xp(), self._A()) - # 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 - - -@view -@external -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 - amp: uint256 = self._A() - D0: uint256 = self.get_D_mem(_balances, amp) - for i in range(N_COINS): - if deposit: - _balances[i] += amounts[i] - else: - _balances[i] -= amounts[i] - D1: uint256 = self.get_D_mem(_balances, amp) - token_amount: uint256 = self.token.totalSupply() - diff: uint256 = 0 - if deposit: - diff = D1 - D0 - else: - diff = D0 - D1 - return diff * token_amount / D0 - - -@external -@nonreentrant('lock') -def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256): - assert not self.is_killed # dev: is killed - - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - _admin_fee: uint256 = self.admin_fee - amp: uint256 = self._A() - - token_supply: uint256 = self.token.totalSupply() - # Initial invariant - D0: uint256 = 0 - old_balances: uint256[N_COINS] = self.balances - if token_supply > 0: - D0 = self.get_D_mem(old_balances, amp) - new_balances: uint256[N_COINS] = old_balances - - for i in range(N_COINS): - if token_supply == 0: - assert amounts[i] > 0 # dev: initial deposit requires all coins - # balances store amounts of c-tokens - new_balances[i] = old_balances[i] + amounts[i] - - # Invariant after change - D1: uint256 = self.get_D_mem(new_balances, amp) - 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(new_balances, amp) - 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 amounts[i] > 0: - assert ERC20(self.coins[i]).transferFrom(msg.sender, self, amounts[i]) # dev: failed transfer - - # Mint pool tokens - self.token.mint(msg.sender, mint_amount) - - log AddLiquidity(msg.sender, amounts, fees, D1, token_supply + mint_amount) - - -@view -@internal -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 # dev: same coin - assert j >= 0 # dev: j below zero - assert j < N_COINS # dev: j above N_COINS - - # should be unreachable, but good for safety - assert i >= 0 - assert i < N_COINS - - amp: uint256 = self._A() - D: uint256 = self.get_D(xp_, amp) - c: uint256 = D - S_: uint256 = 0 - Ann: uint256 = amp * 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 - - -@view -@external -def get_dy(i: int128, j: int128, dx: uint256) -> uint256: - # dx and dy in c-units - rates: uint256[N_COINS] = RATES - xp: uint256[N_COINS] = self._xp() - - x: uint256 = xp[i] + (dx * rates[i] / PRECISION) - y: uint256 = self.get_y(i, j, x, xp) - dy: uint256 = (xp[j] - y - 1) * PRECISION / rates[j] - _fee: uint256 = self.fee * dy / FEE_DENOMINATOR - return dy - _fee - - -@view -@external -def get_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256: - # dx and dy in underlying units - xp: uint256[N_COINS] = self._xp() - 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 - 1) / precisions[j] - _fee: uint256 = self.fee * dy / FEE_DENOMINATOR - return dy - _fee - - - -@external -@nonreentrant('lock') -def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256): - assert not self.is_killed # dev: is killed - rates: uint256[N_COINS] = RATES - - old_balances: uint256[N_COINS] = self.balances - xp: uint256[N_COINS] = self._xp_mem(old_balances) - - x: uint256 = xp[i] + dx * rates[i] / PRECISION - y: uint256 = self.get_y(i, j, x, xp) - - dy: uint256 = xp[j] - y - 1 # -1 just in case there were some rounding errors - dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR - - # Convert all to real units - dy = (dy - dy_fee) * PRECISION / rates[j] - assert dy >= min_dy, "Exchange resulted in fewer coins than expected" - - dy_admin_fee: uint256 = dy_fee * self.admin_fee / FEE_DENOMINATOR - dy_admin_fee = dy_admin_fee * PRECISION / rates[j] - - # Change balances exactly in same way as we change actual ERC20 coin amounts - self.balances[i] = old_balances[i] + dx - # When rounding errors happen, we undercharge admin fee in favor of LP - self.balances[j] = old_balances[j] - dy - dy_admin_fee - - assert ERC20(self.coins[i]).transferFrom(msg.sender, self, dx) - assert ERC20(self.coins[j]).transfer(msg.sender, dy) - - log TokenExchange(msg.sender, i, dx, j, dy) - - -@external -@nonreentrant('lock') -def remove_liquidity(_amount: uint256, min_amounts: uint256[N_COINS]): - total_supply: uint256 = self.token.totalSupply() - amounts: uint256[N_COINS] = empty(uint256[N_COINS]) - fees: uint256[N_COINS] = empty(uint256[N_COINS]) # Fees are unused but we've got them historically in event - - 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 - assert ERC20(self.coins[i]).transfer(msg.sender, value) - - self.token.burnFrom(msg.sender, _amount) # dev: insufficient funds - - log RemoveLiquidity(msg.sender, amounts, fees, total_supply - _amount) - - -@external -@nonreentrant('lock') -def remove_liquidity_imbalance(amounts: uint256[N_COINS], max_burn_amount: uint256): - assert not self.is_killed # dev: is killed - - token_supply: uint256 = self.token.totalSupply() - assert token_supply != 0 # dev: zero total supply - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - _admin_fee: uint256 = self.admin_fee - amp: uint256 = self._A() - - old_balances: uint256[N_COINS] = self.balances - new_balances: uint256[N_COINS] = old_balances - D0: uint256 = self.get_D_mem(old_balances, amp) - for i in range(N_COINS): - new_balances[i] -= amounts[i] - D1: uint256 = self.get_D_mem(new_balances, amp) - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - 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(new_balances, amp) - - token_amount: uint256 = (D0 - D2) * token_supply / D0 - assert token_amount != 0 # dev: zero tokens burned - token_amount += 1 # In case of rounding errors - make it unfavorable for the "attacker" - assert token_amount <= max_burn_amount, "Slippage screwed you" - - self.token.burnFrom(msg.sender, token_amount) # dev: insufficient funds - for i in range(N_COINS): - if amounts[i] != 0: - assert ERC20(self.coins[i]).transfer(msg.sender, amounts[i]) - - log RemoveLiquidityImbalance(msg.sender, amounts, fees, D1, token_supply - token_amount) - - -@view -@internal -def get_y_D(A_: uint256, i: int128, xp: uint256[N_COINS], D: uint256) -> uint256: - """ - Calculate x[i] if one reduces D from being calculated for xp to D - - Done by solving quadratic equation iteratively. - x_1**2 + x1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) - x_1**2 + b*x_1 = c - - x_1 = (x_1**2 + c) / (2*x_1 + b) - """ - # x in the input is converted to the same price/precision - - assert i >= 0 # dev: i below zero - assert i < N_COINS # dev: i above N_COINS - - c: uint256 = D - S_: uint256 = 0 - Ann: uint256 = A_ * N_COINS - - _x: uint256 = 0 - for _i in range(N_COINS): - if _i != i: - _x = xp[_i] - else: - continue - S_ += _x - c = c * D / (_x * N_COINS) - c = c * D / (Ann * N_COINS) - b: uint256 = S_ + D / Ann - 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 - - -@view -@internal -def _calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> (uint256, uint256): - # First, need to calculate - # * Get current D - # * Solve Eqn against y_i for D - _token_amount - amp: uint256 = self._A() - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - precisions: uint256[N_COINS] = PRECISION_MUL - total_supply: uint256 = self.token.totalSupply() - - xp: uint256[N_COINS] = self._xp() - - D0: uint256 = self.get_D(xp, amp) - D1: uint256 = D0 - _token_amount * D0 / total_supply - xp_reduced: uint256[N_COINS] = xp - - new_y: uint256 = self.get_y_D(amp, i, xp, D1) - dy_0: uint256 = (xp[i] - new_y) / precisions[i] # w/o fees - - for j in range(N_COINS): - dx_expected: uint256 = 0 - if j == i: - dx_expected = xp[j] * D1 / D0 - new_y - else: - dx_expected = xp[j] - xp[j] * D1 / D0 - xp_reduced[j] -= _fee * dx_expected / FEE_DENOMINATOR - - dy: uint256 = xp_reduced[i] - self.get_y_D(amp, i, xp_reduced, D1) - dy = (dy - 1) / precisions[i] # Withdraw less to account for rounding errors - - return dy, dy_0 - dy - - -@view -@external -def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - return self._calc_withdraw_one_coin(_token_amount, i)[0] - - -@external -@nonreentrant('lock') -def remove_liquidity_one_coin(_token_amount: uint256, i: int128, min_amount: uint256): - """ - Remove _amount of liquidity all in a form of coin i - """ - assert not self.is_killed # dev: is killed - - dy: uint256 = 0 - dy_fee: uint256 = 0 - dy, dy_fee = self._calc_withdraw_one_coin(_token_amount, i) - assert dy >= min_amount, "Not enough coins removed" - - self.balances[i] -= (dy + dy_fee * self.admin_fee / FEE_DENOMINATOR) - self.token.burnFrom(msg.sender, _token_amount) # dev: insufficient funds - assert ERC20(self.coins[i]).transfer(msg.sender, dy) - - log RemoveLiquidityOne(msg.sender, _token_amount, dy) - - -### Admin functions ### -@external -def ramp_A(_future_A: uint256, _future_time: uint256): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.initial_A_time + MIN_RAMP_TIME - assert _future_time >= block.timestamp + MIN_RAMP_TIME # dev: insufficient time - - _initial_A: uint256 = self._A() - assert (_future_A > 0) and (_future_A < MAX_A) - assert ((_future_A >= _initial_A) and (_future_A <= _initial_A * MAX_A_CHANGE)) or\ - ((_future_A < _initial_A) and (_future_A * MAX_A_CHANGE >= _initial_A)) - self.initial_A = _initial_A - self.future_A = _future_A - self.initial_A_time = block.timestamp - self.future_A_time = _future_time - - log RampA(_initial_A, _future_A, block.timestamp, _future_time) - - -@external -def stop_ramp_A(): - assert msg.sender == self.owner # dev: only owner - - current_A: uint256 = self._A() - self.initial_A = current_A - self.future_A = current_A - self.initial_A_time = block.timestamp - self.future_A_time = block.timestamp - # now (block.timestamp < t1) is always False, so we return saved A - - log StopRampA(current_A, block.timestamp) - - -@external -def commit_new_fee(new_fee: uint256, new_admin_fee: uint256): - assert msg.sender == self.owner # dev: only owner - assert self.admin_actions_deadline == 0 # dev: active action - assert new_fee <= MAX_FEE # dev: fee exceeds maximum - assert new_admin_fee <= MAX_ADMIN_FEE # dev: admin fee exceeds maximum - - _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.admin_actions_deadline = _deadline - self.future_fee = new_fee - self.future_admin_fee = new_admin_fee - - log CommitNewFee(_deadline, new_fee, new_admin_fee) - - -@external -def apply_new_fee(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.admin_actions_deadline # dev: insufficient time - assert self.admin_actions_deadline != 0 # dev: no active action - - self.admin_actions_deadline = 0 - _fee: uint256 = self.future_fee - _admin_fee: uint256 = self.future_admin_fee - self.fee = _fee - self.admin_fee = _admin_fee - - log NewFee(_fee, _admin_fee) - - -@external -def revert_new_parameters(): - assert msg.sender == self.owner # dev: only owner - - self.admin_actions_deadline = 0 - - -@external -def commit_transfer_ownership(_owner: address): - assert msg.sender == self.owner # dev: only owner - assert self.transfer_ownership_deadline == 0 # dev: active transfer - - _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.transfer_ownership_deadline = _deadline - self.future_owner = _owner - - log CommitNewAdmin(_deadline, _owner) - - -@external -def apply_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.transfer_ownership_deadline # dev: insufficient time - assert self.transfer_ownership_deadline != 0 # dev: no active transfer - - self.transfer_ownership_deadline = 0 - _owner: address = self.future_owner - self.owner = _owner - - log NewAdmin(_owner) - - -@external -def revert_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - - self.transfer_ownership_deadline = 0 - - -@view -@external -def admin_balances(i: uint256) -> uint256: - return ERC20(self.coins[i]).balanceOf(self) - self.balances[i] - - -@external -def withdraw_admin_fees(): - assert msg.sender == self.owner # dev: only owner - - for i in range(N_COINS): - c: address = self.coins[i] - value: uint256 = ERC20(c).balanceOf(self) - self.balances[i] - if value > 0: - assert ERC20(c).transfer(msg.sender, value) - - -@external -def donate_admin_fees(): - assert msg.sender == self.owner # dev: only owner - for i in range(N_COINS): - self.balances[i] = ERC20(self.coins[i]).balanceOf(self) - - -@external -def kill_me(): - assert msg.sender == self.owner # dev: only owner - assert self.kill_deadline > block.timestamp # dev: deadline has passed - self.is_killed = True - - -@external -def unkill_me(): - assert msg.sender == self.owner # dev: only owner - self.is_killed = False diff --git a/contracts/pools/hbtc/pooldata.json b/contracts/pools/hbtc/pooldata.json deleted file mode 100644 index 9d8c8bc1..00000000 --- a/contracts/pools/hbtc/pooldata.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "lp_contract": "CurveTokenV2", - "swap_address": "0x4CA9b3063Ec5866A4B82E437059D2C43d1be596F", - "lp_token_address": "0xb19059ebb43466C323583928285a49f558E572Fd", - "gauge_addresses": ["0x4c18E409Dc8619bFb6a1cB56D114C3f592E0aE79"], - "lp_constructor": { - "name": "Curve.fi hBTC/wBTC", - "symbol": "hCRV" - }, - "coins": [ - { - "name": "hBTC", - "decimals": 18, - "tethered": false, - "underlying_address": "0x0316EB71485b0Ab14103307bf65a021042c6d380" - }, - { - "name": "wBTC", - "decimals": 8, - "tethered": false, - "underlying_address": "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599" - } - ] -} diff --git a/contracts/pools/husd/DepositHUSD.vy b/contracts/pools/husd/DepositHUSD.vy deleted file mode 100644 index ac7053d0..00000000 --- a/contracts/pools/husd/DepositHUSD.vy +++ /dev/null @@ -1,376 +0,0 @@ -# @version 0.2.7 -""" -@title "Zap" Depositer for Curve HUSD pool -@author Curve.Fi -@license Copyright (c) Curve.Fi, 2020 - all rights reserved -""" - -from vyper.interfaces import ERC20 - - -interface CurveMeta: - def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256) -> 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) -> 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 base_pool() -> address: 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 - -# An asset which may have a transfer fee (USDT) -FEE_ASSET: constant(address) = 0xdAC17F958D2ee523a2206206994597C13D831ec7 - -FEE_DENOMINATOR: constant(uint256) = 10 ** 10 -FEE_IMPRECISION: constant(uint256) = 100 * 10 ** 8 # % of the fee - - -pool: public(address) -token: public(address) -base_pool: public(address) - -coins: public(address[N_COINS]) -base_coins: public(address[BASE_N_COINS]) - - -@external -def __init__(_pool: address, _token: address): - """ - @notice Contract constructor - @param _pool Metapool address - @param _token Pool LP token address - """ - self.pool = _pool - self.token = _token - _base_pool: address = CurveMeta(_pool).base_pool() - self.base_pool = _base_pool - - for i in range(N_COINS): - coin: address = CurveMeta(_pool).coins(convert(i, uint256)) - self.coins[i] = coin - # approve coins for infinite transfers - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("approve(address,uint256)"), - convert(_pool, bytes32), - convert(MAX_UINT256, bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) - - for i in range(BASE_N_COINS): - coin: address = CurveBase(_base_pool).coins(convert(i, uint256)) - self.base_coins[i] = coin - # approve underlying coins for infinite transfers - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("approve(address,uint256)"), - convert(_base_pool, bytes32), - convert(MAX_UINT256, bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) - - -@external -def add_liquidity(amounts: uint256[N_ALL_COINS], min_mint_amount: uint256) -> uint256: - """ - @notice Wrap underlying coins and deposit them in the pool - @param amounts List of amounts of underlying coins to deposit - @param min_mint_amount Minimum amount of LP tokens to mint from the deposit - @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 - - # Transfer all coins in - for i in range(N_ALL_COINS): - amount: uint256 = amounts[i] - if amount == 0: - continue - coin: address = ZERO_ADDRESS - if i < MAX_COIN: - coin = self.coins[i] - meta_amounts[i] = amount - else: - x: int128 = i - MAX_COIN - coin = self.base_coins[x] - base_amounts[x] = amount - deposit_base = True - # "safeTransferFrom" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transferFrom(address,address,uint256)"), - convert(msg.sender, bytes32), - convert(self, bytes32), - convert(amount, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransferFrom" - # Handle potential Tether fees - if coin == FEE_ASSET: - amount = ERC20(FEE_ASSET).balanceOf(self) - if i < MAX_COIN: - meta_amounts[i] = amount - else: - base_amounts[i - MAX_COIN] = amount - - # Deposit to the base pool - if deposit_base: - CurveBase(self.base_pool).add_liquidity(base_amounts, 0) - meta_amounts[MAX_COIN] = ERC20(self.coins[MAX_COIN]).balanceOf(self) - - # Deposit to the meta pool - CurveMeta(self.pool).add_liquidity(meta_amounts, min_mint_amount) - - # Transfer meta token back - _lp_token: address = self.token - _lp_amount: uint256 = ERC20(_lp_token).balanceOf(self) - assert ERC20(_lp_token).transfer(msg.sender, _lp_amount) - - return _lp_amount - - -@external -def remove_liquidity(_amount: uint256, min_amounts: uint256[N_ALL_COINS]) -> uint256[N_ALL_COINS]: - """ - @notice Withdraw and unwrap coins from the pool - @dev Withdrawal amounts are based on current deposit ratios - @param _amount Quantity of LP tokens to burn in the withdrawal - @param min_amounts Minimum amounts of underlying coins to receive - @return List of amounts of underlying coins that were withdrawn - """ - _token: address = self.token - assert ERC20(_token).transferFrom(msg.sender, self, _amount) - - min_amounts_meta: uint256[N_COINS] = empty(uint256[N_COINS]) - 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 - for i in range(MAX_COIN): - min_amounts_meta[i] = min_amounts[i] - CurveMeta(self.pool).remove_liquidity(_amount, min_amounts_meta) - - # Withdraw from base - _base_amount: uint256 = ERC20(self.coins[MAX_COIN]).balanceOf(self) - for i in range(BASE_N_COINS): - min_amounts_base[i] = min_amounts[MAX_COIN+i] - CurveBase(self.base_pool).remove_liquidity(_base_amount, min_amounts_base) - - # Transfer all coins out - for i in range(N_ALL_COINS): - coin: address = ZERO_ADDRESS - if i < MAX_COIN: - coin = self.coins[i] - else: - coin = self.base_coins[i - MAX_COIN] - amounts[i] = ERC20(coin).balanceOf(self) - # "safeTransfer" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(amounts[i], bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - return amounts - - -@external -def remove_liquidity_one_coin(_token_amount: uint256, i: int128, _min_amount: uint256) -> uint256: - """ - @notice Withdraw and unwrap a single coin from the pool - @param _token_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 - @return Amount of underlying coin received - """ - assert ERC20(self.token).transferFrom(msg.sender, self, _token_amount) - - coin: address = ZERO_ADDRESS - if i < MAX_COIN: - coin = self.coins[i] - # Withdraw a metapool coin - CurveMeta(self.pool).remove_liquidity_one_coin(_token_amount, i, _min_amount) - else: - coin = self.base_coins[i - MAX_COIN] - # Withdraw a base pool coin - CurveMeta(self.pool).remove_liquidity_one_coin(_token_amount, MAX_COIN, 0) - CurveBase(self.base_pool).remove_liquidity_one_coin( - ERC20(self.coins[MAX_COIN]).balanceOf(self), i-MAX_COIN, _min_amount - ) - - # Tranfer the coin out - coin_amount: uint256 = ERC20(coin).balanceOf(self) - # "safeTransfer" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(coin_amount, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - return coin_amount - - -@external -def remove_liquidity_imbalance(amounts: uint256[N_ALL_COINS], max_burn_amount: uint256) -> uint256: - """ - @notice Withdraw coins from the pool in an imbalanced amount - @param amounts List of amounts of underlying coins to withdraw - @param max_burn_amount Maximum amount of LP token to burn in the withdrawal. - This value cannot exceed the caller's LP token balance. - @return Actual amount of the LP token burned in the withdrawal - """ - _base_pool: address = self.base_pool - _meta_pool: address = self.pool - _base_coins: address[BASE_N_COINS] = self.base_coins - _meta_coins: address[N_COINS] = self.coins - _lp_token: address = self.token - - 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 - assert ERC20(_lp_token).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]) - leftover_amounts: uint256[N_COINS] = empty(uint256[N_COINS]) - - # Prepare quantities - for i in range(MAX_COIN): - amounts_meta[i] = amounts[i] - - for i in range(BASE_N_COINS): - amount: uint256 = amounts[MAX_COIN + i] - if amount != 0: - amounts_base[i] = amount - withdraw_base = True - - if withdraw_base: - amounts_meta[MAX_COIN] = CurveBase(self.base_pool).calc_token_amount(amounts_base, False) - amounts_meta[MAX_COIN] += amounts_meta[MAX_COIN] * fee / FEE_DENOMINATOR + 1 - - # Remove liquidity and deposit leftovers back - CurveMeta(_meta_pool).remove_liquidity_imbalance(amounts_meta, max_burn_amount) - if withdraw_base: - CurveBase(_base_pool).remove_liquidity_imbalance(amounts_base, amounts_meta[MAX_COIN]) - leftover_amounts[MAX_COIN] = ERC20(_meta_coins[MAX_COIN]).balanceOf(self) - if leftover_amounts[MAX_COIN] > 0: - CurveMeta(_meta_pool).add_liquidity(leftover_amounts, 0) - - # Transfer all coins out - for i in range(N_ALL_COINS): - coin: address = ZERO_ADDRESS - amount: uint256 = 0 - if i < MAX_COIN: - coin = _meta_coins[i] - amount = amounts_meta[i] - else: - coin = _base_coins[i - MAX_COIN] - amount = amounts_base[i - MAX_COIN] - # "safeTransfer" which works for ERC20s which return bool or not - if amount > 0: - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(amount, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - # Transfer the leftover LP token out - leftover: uint256 = ERC20(_lp_token).balanceOf(self) - if leftover > 0: - assert ERC20(_lp_token).transfer(msg.sender, leftover) - - return max_burn_amount - leftover - - -@view -@external -def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - """ - @notice Calculate the amount received when withdrawing and unwrapping a single coin - @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(self.pool).calc_withdraw_one_coin(_token_amount, i) - else: - _base_tokens: uint256 = CurveMeta(self.pool).calc_withdraw_one_coin(_token_amount, MAX_COIN) - return CurveBase(self.base_pool).calc_withdraw_one_coin(_base_tokens, i-MAX_COIN) - - -@view -@external -def calc_token_amount(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 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]) - - for i in range(MAX_COIN): - meta_amounts[i] = amounts[i] - - for i in range(BASE_N_COINS): - base_amounts[i] = amounts[i + MAX_COIN] - - _base_tokens: uint256 = CurveBase(self.base_pool).calc_token_amount(base_amounts, is_deposit) - meta_amounts[MAX_COIN] = _base_tokens - - return CurveMeta(self.pool).calc_token_amount(meta_amounts, is_deposit) diff --git a/contracts/pools/husd/README.md b/contracts/pools/husd/README.md deleted file mode 100644 index c41fcee7..00000000 --- a/contracts/pools/husd/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# curve-contract/contracts/pools/husd - -[Curve HUSD metapool](https://www.curve.fi/husd), allowing swaps via the Curve [tri-pool](../3pool). - -## Contracts - -* [`DepositHUSD`](DepositHUSD.vy): Depositor contract, used to wrap underlying tokens prior to depositing them into the pool -* [`StableSwapHUSD`](StableSwapHUSD.vy): Curve stablecoin AMM contract - -## Deployments - -* [`CurveContractV2`](../../tokens/CurveTokenV2.vy): [0x5B5CFE992AdAC0C9D48E05854B2d91C73a003858](https://etherscan.io/address/0x5B5CFE992AdAC0C9D48E05854B2d91C73a003858) -* [`DepositHUSD`](DepositHUSD.vy): [0x09672362833d8f703D5395ef3252D4Bfa51c15ca](https://etherscan.io/address/0x09672362833d8f703D5395ef3252D4Bfa51c15ca) -* [`LiquidityGauge`](../../gauges/LiquidityGauge.vy): [0x2db0E83599a91b508Ac268a6197b8B14F5e72840](https://etherscan.io/address/0x2db0E83599a91b508Ac268a6197b8B14F5e72840) -* [`StableSwapHUSD`](StableSwapHUSD.vy): [0x3eF6A01A0f81D6046290f3e2A8c5b843e738E604](https://etherscan.io/address/0x3eF6A01A0f81D6046290f3e2A8c5b843e738E604) - -## Stablecoins - -Curve HUSD metapool utilizes the supports swaps between the following assets: - -## Direct swaps - -Direct swaps are possible between HUSD and the Curve tri-pool LP token. - -* `HUSD`: [0xdf574c24545e5ffecb9a659c229253d4111d87e1](https://etherscan.io/address/0xdf574c24545e5ffecb9a659c229253d4111d87e1) -* `3CRV`: [0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490](https://etherscan.io/address/0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490) - -## Base Pool coins - -The tri-pool LP token may be wrapped or unwrapped to provide swaps between HUSD and the following stablecoins: - -* `DAI`: [0x6b175474e89094c44da98b954eedeac495271d0f](https://etherscan.io/address/0x6b175474e89094c44da98b954eedeac495271d0f) -* `USDC`: [0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48](https://etherscan.io/address/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48) -* `USDT`: [0xdac17f958d2ee523a2206206994597c13d831ec7](https://etherscan.io/address/0xdac17f958d2ee523a2206206994597c13d831ec7) diff --git a/contracts/pools/husd/StableSwapHUSD.vy b/contracts/pools/husd/StableSwapHUSD.vy deleted file mode 100644 index c6f84544..00000000 --- a/contracts/pools/husd/StableSwapHUSD.vy +++ /dev/null @@ -1,1083 +0,0 @@ -# @version 0.2.5 -""" -@title Curve HUSD Metapool -@author Curve.Fi -@license Copyright (c) Curve.Fi, 2020 - all rights reserved -@dev Utilizes 3Pool to allow swaps between HUSD / DAI / USDC / USDT -""" - -from vyper.interfaces import ERC20 - -interface CurveToken: - def totalSupply() -> uint256: view - def mint(_to: address, _value: uint256) -> bool: nonpayable - def burnFrom(_to: address, _value: uint256) -> bool: nonpayable - - -interface Curve: - def coins(i: uint256) -> address: view - def get_virtual_price() -> uint256: view - def calc_token_amount(amounts: uint256[BASE_N_COINS], deposit: bool) -> uint256: view - def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: view - def fee() -> uint256: view - def get_dy(i: int128, j: int128, dx: uint256) -> uint256: view - def get_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256: view - def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256): nonpayable - def add_liquidity(amounts: uint256[BASE_N_COINS], min_mint_amount: uint256): nonpayable - def remove_liquidity_one_coin(_token_amount: uint256, i: int128, min_amount: uint256): nonpayable - - -# Events -event TokenExchange: - buyer: indexed(address) - sold_id: int128 - tokens_sold: uint256 - bought_id: int128 - tokens_bought: uint256 - -event TokenExchangeUnderlying: - buyer: indexed(address) - sold_id: int128 - tokens_sold: uint256 - bought_id: int128 - tokens_bought: uint256 - -event AddLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event RemoveLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - token_supply: uint256 - -event RemoveLiquidityOne: - provider: indexed(address) - token_amount: uint256 - coin_amount: uint256 - token_supply: uint256 - -event RemoveLiquidityImbalance: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event CommitNewAdmin: - deadline: indexed(uint256) - admin: indexed(address) - -event NewAdmin: - admin: indexed(address) - -event CommitNewFee: - deadline: indexed(uint256) - fee: uint256 - admin_fee: uint256 - -event NewFee: - fee: uint256 - admin_fee: uint256 - -event RampA: - old_A: uint256 - new_A: uint256 - initial_time: uint256 - future_time: uint256 - -event StopRampA: - A: uint256 - t: uint256 - - -N_COINS: constant(int128) = 2 -MAX_COIN: constant(int128) = N_COINS - 1 - -FEE_DENOMINATOR: constant(uint256) = 10 ** 10 -PRECISION: constant(uint256) = 10 ** 18 # The precision to convert to -PRECISION_MUL: constant(uint256[N_COINS]) = [10000000000, 1] -RATES: constant(uint256[N_COINS]) = [10000000000000000000000000000, 1000000000000000000] - -BASE_N_COINS: constant(int128) = 3 -N_ALL_COINS: constant(int128) = N_COINS + BASE_N_COINS - 1 -BASE_PRECISION_MUL: constant(uint256[BASE_N_COINS]) = [1, 1000000000000, 1000000000000] -BASE_RATES: constant(uint256[BASE_N_COINS]) = [1000000000000000000, 1000000000000000000000000000000, 1000000000000000000000000000000] - -# An asset which may have a transfer fee (USDT) -FEE_ASSET: constant(address) = 0xdAC17F958D2ee523a2206206994597C13D831ec7 - -MAX_ADMIN_FEE: constant(uint256) = 10 * 10 ** 9 -MAX_FEE: constant(uint256) = 5 * 10 ** 9 -MAX_A: constant(uint256) = 10 ** 6 -MAX_A_CHANGE: constant(uint256) = 10 - -ADMIN_ACTIONS_DELAY: constant(uint256) = 3 * 86400 -MIN_RAMP_TIME: constant(uint256) = 86400 - -coins: public(address[N_COINS]) -balances: public(uint256[N_COINS]) -fee: public(uint256) # fee * 1e10 -admin_fee: public(uint256) # admin_fee * 1e10 - -owner: public(address) -token: CurveToken - -# Token corresponding to the pool is always the last one -BASE_POOL_COINS: constant(int128) = 3 -BASE_CACHE_EXPIRES: constant(int128) = 10 * 60 # 10 min -base_pool: public(address) -base_virtual_price: public(uint256) -base_cache_updated: public(uint256) -base_coins: public(address[BASE_POOL_COINS]) - -A_PRECISION: constant(uint256) = 100 -initial_A: public(uint256) -future_A: public(uint256) -initial_A_time: public(uint256) -future_A_time: public(uint256) - -admin_actions_deadline: public(uint256) -transfer_ownership_deadline: public(uint256) -future_fee: public(uint256) -future_admin_fee: public(uint256) -future_owner: public(address) - -is_killed: bool -kill_deadline: uint256 -KILL_DEADLINE_DT: constant(uint256) = 2 * 30 * 86400 - - -@external -def __init__( - _owner: address, - _coins: address[N_COINS], - _pool_token: address, - _base_pool: address, - _A: uint256, - _fee: uint256, - _admin_fee: uint256 -): - """ - @notice Contract constructor - @param _owner Contract owner address - @param _coins Addresses of ERC20 conracts of coins - @param _pool_token Address of the token representing LP share - @param _base_pool Address of the base pool (which will have a virtual price) - @param _A Amplification coefficient multiplied by n * (n - 1) - @param _fee Fee to charge for exchanges - @param _admin_fee Admin fee - """ - for i in range(N_COINS): - assert _coins[i] != ZERO_ADDRESS - self.coins = _coins - self.initial_A = _A * A_PRECISION - self.future_A = _A * A_PRECISION - self.fee = _fee - self.admin_fee = _admin_fee - self.owner = _owner - self.kill_deadline = block.timestamp + KILL_DEADLINE_DT - self.token = CurveToken(_pool_token) - - self.base_pool = _base_pool - self.base_virtual_price = Curve(_base_pool).get_virtual_price() - self.base_cache_updated = block.timestamp - for i in range(BASE_POOL_COINS): - _base_coin: address = Curve(_base_pool).coins(convert(i, uint256)) - self.base_coins[i] = _base_coin - - # approve underlying coins for infinite transfers - _response: Bytes[32] = raw_call( - _base_coin, - concat( - method_id("approve(address,uint256)"), - convert(_base_pool, bytes32), - convert(MAX_UINT256, bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) - - -@view -@internal -def _A() -> uint256: - """ - Handle ramping A up or down - """ - t1: uint256 = self.future_A_time - A1: uint256 = self.future_A - - if block.timestamp < t1: - A0: uint256 = self.initial_A - t0: uint256 = self.initial_A_time - # Expressions in uint256 cannot have negative numbers, thus "if" - if A1 > A0: - return A0 + (A1 - A0) * (block.timestamp - t0) / (t1 - t0) - else: - return A0 - (A0 - A1) * (block.timestamp - t0) / (t1 - t0) - - else: # when t1 == 0 or block.timestamp >= t1 - return A1 - - -@view -@external -def A() -> uint256: - return self._A() / A_PRECISION - - -@view -@external -def A_precise() -> uint256: - return self._A() - - -@view -@internal -def _xp(vp_rate: uint256) -> uint256[N_COINS]: - result: uint256[N_COINS] = RATES - result[MAX_COIN] = vp_rate # virtual price for the metacurrency - for i in range(N_COINS): - result[i] = result[i] * self.balances[i] / PRECISION - return result - - -@pure -@internal -def _xp_mem(vp_rate: uint256, _balances: uint256[N_COINS]) -> uint256[N_COINS]: - result: uint256[N_COINS] = RATES - result[MAX_COIN] = vp_rate # virtual price for the metacurrency - for i in range(N_COINS): - result[i] = result[i] * _balances[i] / PRECISION - return result - - -@internal -def _vp_rate() -> uint256: - if block.timestamp > self.base_cache_updated + BASE_CACHE_EXPIRES: - vprice: uint256 = Curve(self.base_pool).get_virtual_price() - self.base_virtual_price = vprice - self.base_cache_updated = block.timestamp - return vprice - else: - return self.base_virtual_price - - -@internal -@view -def _vp_rate_ro() -> uint256: - if block.timestamp > self.base_cache_updated + BASE_CACHE_EXPIRES: - return Curve(self.base_pool).get_virtual_price() - else: - return self.base_virtual_price - - -@pure -@internal -def get_D(xp: uint256[N_COINS], amp: uint256) -> uint256: - S: uint256 = 0 - Dprev: uint256 = 0 - for _x in xp: - S += _x - if S == 0: - return 0 - - D: uint256 = S - Ann: uint256 = amp * N_COINS - for _i in range(255): - D_P: uint256 = D - for _x in xp: - D_P = D_P * D / (_x * N_COINS) # If division by 0, this will be borked: only withdrawal will work. And that is good - Dprev = D - D = (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (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 - - -@view -@internal -def get_D_mem(vp_rate: uint256, _balances: uint256[N_COINS], amp: uint256) -> uint256: - xp: uint256[N_COINS] = self._xp_mem(vp_rate, _balances) - return self.get_D(xp, amp) - - -@view -@external -def get_virtual_price() -> uint256: - """ - @notice The current virtual price of the pool LP token - @dev Useful for calculating profits - @return LP token virtual price normalized to 1e18 - """ - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate_ro() - xp: uint256[N_COINS] = self._xp(vp_rate) - D: uint256 = self.get_D(xp, amp) - # 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 - - -@view -@external -def calc_token_amount(amounts: uint256[N_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 amounts Amount of each coin being deposited - @param is_deposit set True for deposits, False for withdrawals - @return Expected amount of LP tokens received - """ - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate_ro() - _balances: uint256[N_COINS] = self.balances - D0: uint256 = self.get_D_mem(vp_rate, _balances, amp) - for i in range(N_COINS): - if is_deposit: - _balances[i] += amounts[i] - else: - _balances[i] -= amounts[i] - D1: uint256 = self.get_D_mem(vp_rate, _balances, amp) - token_amount: uint256 = self.token.totalSupply() - diff: uint256 = 0 - if is_deposit: - diff = D1 - D0 - else: - diff = D0 - D1 - return diff * token_amount / D0 - - -@external -@nonreentrant('lock') -def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256) -> uint256: - """ - @notice Deposit coins into the pool - @param amounts List of amounts of coins to deposit - @param min_mint_amount Minimum amount of LP tokens to mint from the deposit - @return Amount of LP tokens received by depositing - """ - assert not self.is_killed # dev: is killed - - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate() - token_supply: uint256 = self.token.totalSupply() - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - _admin_fee: uint256 = self.admin_fee - - # Initial invariant - D0: uint256 = 0 - old_balances: uint256[N_COINS] = self.balances - if token_supply > 0: - D0 = self.get_D_mem(vp_rate, old_balances, amp) - new_balances: uint256[N_COINS] = old_balances - - for i in range(N_COINS): - if token_supply == 0: - assert amounts[i] > 0 # dev: initial deposit requires all coins - # balances store amounts of c-tokens - new_balances[i] = old_balances[i] + amounts[i] - - # Invariant after change - D1: uint256 = self.get_D_mem(vp_rate, new_balances, amp) - assert D1 > D0 - - # We need to recalculate the invariant accounting for fees - # to calculate fair user's share - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - 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(vp_rate, new_balances, amp) - 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 amounts[i] > 0: - assert ERC20(self.coins[i]).transferFrom(msg.sender, self, amounts[i]) # dev: failed transfer - - # Mint pool tokens - self.token.mint(msg.sender, mint_amount) - - log AddLiquidity(msg.sender, amounts, fees, D1, token_supply + mint_amount) - - return mint_amount - - -@view -@internal -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 # dev: same coin - assert j >= 0 # dev: j below zero - assert j < N_COINS # dev: j above N_COINS - - # should be unreachable, but good for safety - assert i >= 0 - assert i < N_COINS - - amp: uint256 = self._A() - D: uint256 = self.get_D(xp_, amp) - - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: uint256 = 0 - c: uint256 = D - Ann: uint256 = amp * N_COINS - - 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 * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann # - D - 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 - - -@view -@external -def get_dy(i: int128, j: int128, dx: uint256) -> uint256: - # dx and dy in c-units - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = self._vp_rate_ro() - xp: uint256[N_COINS] = self._xp(rates[MAX_COIN]) - - x: uint256 = xp[i] + (dx * rates[i] / PRECISION) - y: uint256 = self.get_y(i, j, x, xp) - dy: uint256 = xp[j] - y - 1 - _fee: uint256 = self.fee * dy / FEE_DENOMINATOR - return (dy - _fee) * PRECISION / rates[j] - - -@view -@external -def get_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256: - # dx and dy in underlying units - vp_rate: uint256 = self._vp_rate_ro() - xp: uint256[N_COINS] = self._xp(vp_rate) - precisions: uint256[N_COINS] = PRECISION_MUL - _base_pool: address = self.base_pool - - # Use base_i or base_j if they are >= 0 - base_i: int128 = i - MAX_COIN - base_j: int128 = j - MAX_COIN - meta_i: int128 = MAX_COIN - meta_j: int128 = MAX_COIN - if base_i < 0: - meta_i = i - if base_j < 0: - meta_j = j - - x: uint256 = 0 - if base_i < 0: - x = xp[i] + dx * precisions[i] - else: - if base_j < 0: - # i is from BasePool - # At first, get the amount of pool tokens - base_inputs: uint256[BASE_N_COINS] = empty(uint256[BASE_N_COINS]) - base_inputs[base_i] = dx - # Token amount transformed to underlying "dollars" - x = Curve(_base_pool).calc_token_amount(base_inputs, True) * vp_rate / PRECISION - # Accounting for deposit/withdraw fees approximately - x -= x * Curve(_base_pool).fee() / (2 * FEE_DENOMINATOR) - # Adding number of pool tokens - x += xp[MAX_COIN] - else: - # If both are from the base pool - return Curve(_base_pool).get_dy(base_i, base_j, dx) - - # This pool is involved only when in-pool assets are used - y: uint256 = self.get_y(meta_i, meta_j, x, xp) - dy: uint256 = xp[meta_j] - y - 1 - dy = (dy - self.fee * dy / FEE_DENOMINATOR) - - # If output is going via the metapool - if base_j < 0: - dy /= precisions[meta_j] - else: - # j is from BasePool - # The fee is already accounted for - dy = Curve(_base_pool).calc_withdraw_one_coin(dy * PRECISION / vp_rate, base_j) - - return dy - - -@external -@nonreentrant('lock') -def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256) -> uint256: - """ - @notice Perform an exchange between two coins - @dev Index values can be found via the `coins` public getter method - @param i Index value for the coin to send - @param j Index valie of the coin to recieve - @param dx Amount of `i` being exchanged - @param min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - assert not self.is_killed # dev: is killed - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = self._vp_rate() - - old_balances: uint256[N_COINS] = self.balances - xp: uint256[N_COINS] = self._xp_mem(rates[MAX_COIN], old_balances) - - x: uint256 = xp[i] + dx * rates[i] / PRECISION - y: uint256 = self.get_y(i, j, x, xp) - - dy: uint256 = xp[j] - y - 1 # -1 just in case there were some rounding errors - dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR - - # Convert all to real units - dy = (dy - dy_fee) * PRECISION / rates[j] - assert dy >= min_dy, "Too few coins in result" - - dy_admin_fee: uint256 = dy_fee * self.admin_fee / FEE_DENOMINATOR - dy_admin_fee = dy_admin_fee * PRECISION / rates[j] - - # Change balances exactly in same way as we change actual ERC20 coin amounts - self.balances[i] = old_balances[i] + dx - # When rounding errors happen, we undercharge admin fee in favor of LP - self.balances[j] = old_balances[j] - dy - dy_admin_fee - - assert ERC20(self.coins[i]).transferFrom(msg.sender, self, dx) - assert ERC20(self.coins[j]).transfer(msg.sender, dy) - - log TokenExchange(msg.sender, i, dx, j, dy) - - return dy - - -@external -@nonreentrant('lock') -def exchange_underlying(i: int128, j: int128, dx: uint256, min_dy: uint256) -> uint256: - """ - @notice Perform an exchange between two underlying coins - @dev Index values can be found via the `underlying_coins` public getter method - @param i Index value for the underlying coin to send - @param j Index valie of the underlying coin to recieve - @param dx Amount of `i` being exchanged - @param min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - assert not self.is_killed # dev: is killed - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = self._vp_rate() - _base_pool: address = self.base_pool - - # Use base_i or base_j if they are >= 0 - base_i: int128 = i - MAX_COIN - base_j: int128 = j - MAX_COIN - meta_i: int128 = MAX_COIN - meta_j: int128 = MAX_COIN - if base_i < 0: - meta_i = i - if base_j < 0: - meta_j = j - dy: uint256 = 0 - - # Addresses for input and output coins - input_coin: address = ZERO_ADDRESS - if base_i < 0: - input_coin = self.coins[i] - else: - input_coin = self.base_coins[base_i] - output_coin: address = ZERO_ADDRESS - if base_j < 0: - output_coin = self.coins[j] - else: - output_coin = self.base_coins[base_j] - - # Handle potential Tether fees - dx_w_fee: uint256 = dx - if input_coin == FEE_ASSET: - dx_w_fee = ERC20(FEE_ASSET).balanceOf(self) - # "safeTransferFrom" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - input_coin, - concat( - method_id("transferFrom(address,address,uint256)"), - convert(msg.sender, bytes32), - convert(self, bytes32), - convert(dx, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransferFrom" - # Handle potential Tether fees - if input_coin == FEE_ASSET: - dx_w_fee = ERC20(FEE_ASSET).balanceOf(self) - dx_w_fee - - if base_i < 0 or base_j < 0: - old_balances: uint256[N_COINS] = self.balances - xp: uint256[N_COINS] = self._xp_mem(rates[MAX_COIN], old_balances) - - x: uint256 = 0 - if base_i < 0: - x = xp[i] + dx_w_fee * rates[i] / PRECISION - else: - # i is from BasePool - # At first, get the amount of pool tokens - base_inputs: uint256[BASE_N_COINS] = empty(uint256[BASE_N_COINS]) - base_inputs[base_i] = dx_w_fee - coin_i: address = self.coins[MAX_COIN] - # Deposit and measure delta - x = ERC20(coin_i).balanceOf(self) - Curve(_base_pool).add_liquidity(base_inputs, 0) - # Need to convert pool token to "virtual" units using rates - # dx is also different now - dx_w_fee = ERC20(coin_i).balanceOf(self) - x - x = dx_w_fee * rates[MAX_COIN] / PRECISION - # Adding number of pool tokens - x += xp[MAX_COIN] - - y: uint256 = self.get_y(meta_i, meta_j, x, xp) - - # Either a real coin or token - dy = xp[meta_j] - y - 1 # -1 just in case there were some rounding errors - dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR - - # Convert all to real units - # Works for both pool coins and real coins - dy = (dy - dy_fee) * PRECISION / rates[meta_j] - - dy_admin_fee: uint256 = dy_fee * self.admin_fee / FEE_DENOMINATOR - dy_admin_fee = dy_admin_fee * PRECISION / rates[meta_j] - - # Change balances exactly in same way as we change actual ERC20 coin amounts - self.balances[meta_i] = old_balances[meta_i] + dx_w_fee - # When rounding errors happen, we undercharge admin fee in favor of LP - self.balances[meta_j] = old_balances[meta_j] - dy - dy_admin_fee - - # Withdraw from the base pool if needed - if base_j >= 0: - out_amount: uint256 = ERC20(output_coin).balanceOf(self) - Curve(_base_pool).remove_liquidity_one_coin(dy, base_j, 0) - dy = ERC20(output_coin).balanceOf(self) - out_amount - - assert dy >= min_dy, "Too few coins in result" - - else: - # If both are from the base pool - dy = ERC20(output_coin).balanceOf(self) - Curve(_base_pool).exchange(base_i, base_j, dx_w_fee, min_dy) - dy = ERC20(output_coin).balanceOf(self) - dy - - # "safeTransfer" which works for ERC20s which return bool or not - _response = raw_call( - output_coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(dy, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - log TokenExchangeUnderlying(msg.sender, i, dx, j, dy) - - return dy - - -@external -@nonreentrant('lock') -def remove_liquidity(_amount: uint256, min_amounts: uint256[N_COINS]) -> uint256[N_COINS]: - """ - @notice Withdraw coins from the pool - @dev Withdrawal amounts are based on current deposit ratios - @param _amount Quantity of LP tokens to burn in the withdrawal - @param min_amounts Minimum amounts of underlying coins to receive - @return List of amounts of coins that were withdrawn - """ - total_supply: uint256 = self.token.totalSupply() - amounts: uint256[N_COINS] = empty(uint256[N_COINS]) - fees: uint256[N_COINS] = empty(uint256[N_COINS]) # Fees are unused but we've got them historically in event - - for i in range(N_COINS): - value: uint256 = self.balances[i] * _amount / total_supply - assert value >= min_amounts[i], "Too few coins in result" - self.balances[i] -= value - amounts[i] = value - assert ERC20(self.coins[i]).transfer(msg.sender, value) - - self.token.burnFrom(msg.sender, _amount) # dev: insufficient funds - - log RemoveLiquidity(msg.sender, amounts, fees, total_supply - _amount) - - return amounts - - -@external -@nonreentrant('lock') -def remove_liquidity_imbalance(amounts: uint256[N_COINS], max_burn_amount: uint256) -> uint256: - """ - @notice Withdraw coins from the pool in an imbalanced amount - @param amounts List of amounts of underlying coins to withdraw - @param max_burn_amount Maximum amount of LP token to burn in the withdrawal - @return Actual amount of the LP token burned in the withdrawal - """ - assert not self.is_killed # dev: is killed - - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate() - - token_supply: uint256 = self.token.totalSupply() - assert token_supply != 0 # dev: zero total supply - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - _admin_fee: uint256 = self.admin_fee - - old_balances: uint256[N_COINS] = self.balances - new_balances: uint256[N_COINS] = old_balances - D0: uint256 = self.get_D_mem(vp_rate, old_balances, amp) - for i in range(N_COINS): - new_balances[i] -= amounts[i] - D1: uint256 = self.get_D_mem(vp_rate, new_balances, amp) - - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - 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(vp_rate, new_balances, amp) - - token_amount: uint256 = (D0 - D2) * token_supply / D0 - assert token_amount != 0 # dev: zero tokens burned - token_amount += 1 # In case of rounding errors - make it unfavorable for the "attacker" - assert token_amount <= max_burn_amount, "Slippage screwed you" - - self.token.burnFrom(msg.sender, token_amount) # dev: insufficient funds - for i in range(N_COINS): - if amounts[i] != 0: - assert ERC20(self.coins[i]).transfer(msg.sender, amounts[i]) - - log RemoveLiquidityImbalance(msg.sender, amounts, fees, D1, token_supply - token_amount) - - return token_amount - - -@view -@internal -def get_y_D(A_: uint256, i: int128, xp: uint256[N_COINS], D: uint256) -> uint256: - """ - Calculate x[i] if one reduces D from being calculated for xp to D - - Done by solving quadratic equation iteratively. - x_1**2 + x1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) - x_1**2 + b*x_1 = c - - x_1 = (x_1**2 + c) / (2*x_1 + b) - """ - # x in the input is converted to the same price/precision - - assert i >= 0 # dev: i below zero - assert i < N_COINS # dev: i above N_COINS - - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: uint256 = 0 - - c: uint256 = D - Ann: uint256 = A_ * N_COINS - - for _i in range(N_COINS): - if _i != i: - _x = xp[_i] - else: - continue - S_ += _x - c = c * D / (_x * N_COINS) - c = c * D * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann - 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 - - -@view -@internal -def _calc_withdraw_one_coin(_token_amount: uint256, i: int128, vp_rate: uint256) -> (uint256, uint256, uint256): - # First, need to calculate - # * Get current D - # * Solve Eqn against y_i for D - _token_amount - amp: uint256 = self._A() - xp: uint256[N_COINS] = self._xp(vp_rate) - D0: uint256 = self.get_D(xp, amp) - - total_supply: uint256 = self.token.totalSupply() - D1: uint256 = D0 - _token_amount * D0 / total_supply - new_y: uint256 = self.get_y_D(amp, i, xp, D1) - - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = vp_rate - - xp_reduced: uint256[N_COINS] = xp - dy_0: uint256 = (xp[i] - new_y) * PRECISION / rates[i] # w/o fees - - for j in range(N_COINS): - dx_expected: uint256 = 0 - if j == i: - dx_expected = xp[j] * D1 / D0 - new_y - else: - dx_expected = xp[j] - xp[j] * D1 / D0 - xp_reduced[j] -= _fee * dx_expected / FEE_DENOMINATOR - - dy: uint256 = xp_reduced[i] - self.get_y_D(amp, i, xp_reduced, D1) - dy = (dy - 1) * PRECISION / rates[i] # Withdraw less to account for rounding errors - - return dy, dy_0 - dy, total_supply - - -@view -@external -def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - """ - @notice Calculate the amount received when withdrawing a single coin - @param _token_amount Amount of LP tokens to burn in the withdrawal - @param i Index value of the coin to withdraw - @return Amount of coin received - """ - vp_rate: uint256 = self._vp_rate_ro() - return self._calc_withdraw_one_coin(_token_amount, i, vp_rate)[0] - - -@external -@nonreentrant('lock') -def remove_liquidity_one_coin(_token_amount: uint256, i: int128, _min_amount: uint256) -> uint256: - """ - @notice Withdraw a single coin from the pool - @param _token_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 coin to receive - @return Amount of coin received - """ - assert not self.is_killed # dev: is killed - - vp_rate: uint256 = self._vp_rate() - dy: uint256 = 0 - dy_fee: uint256 = 0 - total_supply: uint256 = 0 - dy, dy_fee, total_supply = self._calc_withdraw_one_coin(_token_amount, i, vp_rate) - assert dy >= _min_amount, "Not enough coins removed" - - self.balances[i] -= (dy + dy_fee * self.admin_fee / FEE_DENOMINATOR) - self.token.burnFrom(msg.sender, _token_amount) # dev: insufficient funds - assert ERC20(self.coins[i]).transfer(msg.sender, dy) - - log RemoveLiquidityOne(msg.sender, _token_amount, dy, total_supply - _token_amount) - - return dy - - -### Admin functions ### -@external -def ramp_A(_future_A: uint256, _future_time: uint256): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.initial_A_time + MIN_RAMP_TIME - assert _future_time >= block.timestamp + MIN_RAMP_TIME # dev: insufficient time - - _initial_A: uint256 = self._A() - _future_A_p: uint256 = _future_A * A_PRECISION - - assert _future_A > 0 and _future_A < MAX_A - if _future_A_p < _initial_A: - assert _future_A_p * MAX_A_CHANGE >= _initial_A - else: - assert _future_A_p <= _initial_A * MAX_A_CHANGE - - self.initial_A = _initial_A - self.future_A = _future_A_p - self.initial_A_time = block.timestamp - self.future_A_time = _future_time - - log RampA(_initial_A, _future_A_p, block.timestamp, _future_time) - - -@external -def stop_ramp_A(): - assert msg.sender == self.owner # dev: only owner - - current_A: uint256 = self._A() - self.initial_A = current_A - self.future_A = current_A - self.initial_A_time = block.timestamp - self.future_A_time = block.timestamp - # now (block.timestamp < t1) is always False, so we return saved A - - log StopRampA(current_A, block.timestamp) - - -@external -def commit_new_fee(new_fee: uint256, new_admin_fee: uint256): - assert msg.sender == self.owner # dev: only owner - assert self.admin_actions_deadline == 0 # dev: active action - assert new_fee <= MAX_FEE # dev: fee exceeds maximum - assert new_admin_fee <= MAX_ADMIN_FEE # dev: admin fee exceeds maximum - - _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.admin_actions_deadline = _deadline - self.future_fee = new_fee - self.future_admin_fee = new_admin_fee - - log CommitNewFee(_deadline, new_fee, new_admin_fee) - - -@external -def apply_new_fee(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.admin_actions_deadline # dev: insufficient time - assert self.admin_actions_deadline != 0 # dev: no active action - - self.admin_actions_deadline = 0 - _fee: uint256 = self.future_fee - _admin_fee: uint256 = self.future_admin_fee - self.fee = _fee - self.admin_fee = _admin_fee - - log NewFee(_fee, _admin_fee) - - -@external -def revert_new_parameters(): - assert msg.sender == self.owner # dev: only owner - - self.admin_actions_deadline = 0 - - -@external -def commit_transfer_ownership(_owner: address): - assert msg.sender == self.owner # dev: only owner - assert self.transfer_ownership_deadline == 0 # dev: active transfer - - _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.transfer_ownership_deadline = _deadline - self.future_owner = _owner - - log CommitNewAdmin(_deadline, _owner) - - -@external -def apply_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.transfer_ownership_deadline # dev: insufficient time - assert self.transfer_ownership_deadline != 0 # dev: no active transfer - - self.transfer_ownership_deadline = 0 - _owner: address = self.future_owner - self.owner = _owner - - log NewAdmin(_owner) - - -@external -def revert_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - - self.transfer_ownership_deadline = 0 - - -@view -@external -def admin_balances(i: uint256) -> uint256: - return ERC20(self.coins[i]).balanceOf(self) - self.balances[i] - - -@external -def withdraw_admin_fees(): - assert msg.sender == self.owner # dev: only owner - - for i in range(N_COINS): - c: address = self.coins[i] - value: uint256 = ERC20(c).balanceOf(self) - self.balances[i] - if value > 0: - assert ERC20(c).transfer(msg.sender, value) - - -@external -def donate_admin_fees(): - assert msg.sender == self.owner # dev: only owner - for i in range(N_COINS): - self.balances[i] = ERC20(self.coins[i]).balanceOf(self) - - -@external -def kill_me(): - assert msg.sender == self.owner # dev: only owner - assert self.kill_deadline > block.timestamp # dev: deadline has passed - self.is_killed = True - - -@external -def unkill_me(): - assert msg.sender == self.owner # dev: only owner - self.is_killed = False diff --git a/contracts/pools/husd/pooldata.json b/contracts/pools/husd/pooldata.json deleted file mode 100644 index 3b6bcaff..00000000 --- a/contracts/pools/husd/pooldata.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "base_pool": "3pool", - "pool_types": ["meta"], - "lp_contract": "CurveTokenV2", - "swap_address": "0x3eF6A01A0f81D6046290f3e2A8c5b843e738E604", - "lp_token_address": "0x5B5CFE992AdAC0C9D48E05854B2d91C73a003858", - "zap_address": "0x09672362833d8f703D5395ef3252D4Bfa51c15ca", - "gauge_addresses": ["0x2db0E83599a91b508Ac268a6197b8B14F5e72840"], - "lp_constructor": { - "symbol": "husd3CRV", - "name": "Curve.fi HUSD/3Crv" - }, - "swap_constructor": { - "_A": 200, - "_fee": 4000000, - "_admin_fee": 0 - }, - "coins": [ - { - "name": "HUSD", - "decimals": 8, - "tethered": false, - "underlying_address": "0xdf574c24545e5ffecb9a659c229253d4111d87e1" - }, - { - "name": "3CRV", - "decimals": 18, - "base_pool_token": true, - "underlying_address": "0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490" - } - ] -} diff --git a/contracts/pools/ib/README.md b/contracts/pools/ib/README.md deleted file mode 100644 index a1a3ea4e..00000000 --- a/contracts/pools/ib/README.md +++ /dev/null @@ -1,29 +0,0 @@ -# curve-contract/contracts/pools/ib - -[Curve Iron Bank pool](https://www.curve.fi/ib), with lending on [Cream](https://v1.yearn.finance/lending). - -## Contracts - -* [`StableSwapIB`](StableSwapIB.vy): Curve stablecoin AMM contract - -## Deployments - -* [`CurveContractV3`](../../tokens/CurveTokenV3.vy): [0x5282a4eF67D9C33135340fB3289cc1711c13638C](https://etherscan.io/address/0x5282a4eF67D9C33135340fB3289cc1711c13638C) -* [`LiquidityGaugeV2`](https://github.com/curvefi/curve-dao-contracts/blob/master/contracts/gauges/LiquidityGaugeV2.vy): [0xF5194c3325202F456c95c1Cf0cA36f8475C1949F](https://etherscan.io/address/0xF5194c3325202F456c95c1Cf0cA36f8475C1949F) -* [`StableSwapIB`](StableSwapIB.vy): [0x2dded6Da1BF5DBdF597C45fcFaa3194e53EcfeAF](https://etherscan.io/address/0x2dded6Da1BF5DBdF597C45fcFaa3194e53EcfeAF) - -## Stablecoins - -Curve Iron Bank pool supports swaps between the following stablecoins: - -### Wrapped - -* `cyDAI`: [0x8e595470ed749b85c6f7669de83eae304c2ec68f](https://etherscan.io/address/0x8e595470ed749b85c6f7669de83eae304c2ec68f) -* `aUSDC`: [0x76eb2fe28b36b3ee97f3adae0c69606eedb2a37c](https://etherscan.io/address/0x76eb2fe28b36b3ee97f3adae0c69606eedb2a37c) -* `cyUSDT`: [0x48759f220ed983db51fa7a8c0d2aab8f3ce4166a](https://etherscan.io/address/0x48759f220ed983db51fa7a8c0d2aab8f3ce4166a) - -### Underlying - -* `DAI`: [0x6b175474e89094c44da98b954eedeac495271d0f](https://etherscan.io/token/0x6b175474e89094c44da98b954eedeac495271d0f) -* `USDC`: [0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48](https://etherscan.io/token/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48) -* `USDT`: [0xdac17f958d2ee523a2206206994597c13d831ec7](https://etherscan.io/address/0xdac17f958d2ee523a2206206994597c13d831ec7) diff --git a/contracts/pools/ib/StableSwapIB.vy b/contracts/pools/ib/StableSwapIB.vy deleted file mode 100644 index 82cbd2fa..00000000 --- a/contracts/pools/ib/StableSwapIB.vy +++ /dev/null @@ -1,1006 +0,0 @@ -# @version 0.2.8 -""" -@title Curve IronBank Pool -@author Curve.Fi -@license Copyright (c) Curve.Fi, 2021 - all rights reserved -@notice Pool for swapping between cyTokens (cyDAI, cyUSDC, cyUSDT) -""" - -# External Contracts -interface cyToken: - def transfer(_to: address, _value: uint256) -> bool: nonpayable - def transferFrom(_from: address, _to: address, _value: uint256) -> bool: nonpayable - def mint(mintAmount: uint256) -> uint256: nonpayable - def redeem(redeemTokens: uint256) -> uint256: nonpayable - def exchangeRateStored() -> uint256: view - def exchangeRateCurrent() -> uint256: nonpayable - def supplyRatePerBlock() -> uint256: view - def accrualBlockNumber() -> uint256: view - -interface CurveToken: - def mint(_to: address, _value: uint256) -> bool: nonpayable - def burnFrom(_to: address, _value: uint256) -> bool: nonpayable - -interface ERC20: - def transfer(_to: address, _value: uint256): nonpayable - def transferFrom(_from: address, _to: address, _value: uint256): nonpayable - def totalSupply() -> uint256: view - def balanceOf(_addr: address) -> uint256: view - -# Events -event TokenExchange: - buyer: indexed(address) - sold_id: int128 - tokens_sold: uint256 - bought_id: int128 - tokens_bought: uint256 - -event TokenExchangeUnderlying: - buyer: indexed(address) - sold_id: int128 - tokens_sold: uint256 - bought_id: int128 - tokens_bought: uint256 - -event AddLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event RemoveLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - token_supply: uint256 - -event RemoveLiquidityOne: - provider: indexed(address) - token_amount: uint256 - coin_amount: uint256 - -event RemoveLiquidityImbalance: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event CommitNewAdmin: - deadline: indexed(uint256) - admin: indexed(address) - -event NewAdmin: - admin: indexed(address) - -event CommitNewFee: - deadline: indexed(uint256) - fee: uint256 - admin_fee: uint256 - -event NewFee: - fee: uint256 - admin_fee: uint256 - -event RampA: - old_A: uint256 - new_A: uint256 - initial_time: uint256 - future_time: uint256 - -event StopRampA: - A: uint256 - t: uint256 - - -# These constants must be set prior to compiling -N_COINS: constant(int128) = 3 -PRECISION_MUL: constant(uint256[N_COINS]) = [1, 1000000000000, 1000000000000] - -# fixed constants -FEE_DENOMINATOR: constant(uint256) = 10 ** 10 -PRECISION: constant(uint256) = 10 ** 18 # The precision to convert to - -MAX_ADMIN_FEE: constant(uint256) = 10 * 10 ** 9 -MAX_FEE: constant(uint256) = 5 * 10 ** 9 -MAX_A: constant(uint256) = 10 ** 6 -MAX_A_CHANGE: constant(uint256) = 10 - -ADMIN_ACTIONS_DELAY: constant(uint256) = 3 * 86400 -MIN_RAMP_TIME: constant(uint256) = 86400 - -coins: public(address[N_COINS]) -underlying_coins: public(address[N_COINS]) -balances: public(uint256[N_COINS]) - -previous_balances: public(uint256[N_COINS]) -block_timestamp_last: public(uint256) - -fee: public(uint256) # fee * 1e10 -admin_fee: public(uint256) # admin_fee * 1e10 - -owner: public(address) -lp_token: public(address) - -A_PRECISION: constant(uint256) = 100 -initial_A: public(uint256) -future_A: public(uint256) -initial_A_time: public(uint256) -future_A_time: public(uint256) - -admin_actions_deadline: public(uint256) -transfer_ownership_deadline: public(uint256) -future_fee: public(uint256) -future_admin_fee: public(uint256) -future_owner: public(address) - -is_killed: bool -kill_deadline: uint256 -KILL_DEADLINE_DT: constant(uint256) = 2 * 30 * 86400 - -@external -def __init__( - _owner: address, - _coins: address[N_COINS], - _underlying_coins: address[N_COINS], - _pool_token: address, - _A: uint256, - _fee: uint256, - _admin_fee: uint256, -): - """ - @notice Contract constructor - @param _owner Contract owner address - @param _coins Addresses of ERC20 contracts of wrapped coins - @param _underlying_coins Addresses of ERC20 contracts of underlying coins - @param _pool_token Address of the token representing LP share - @param _A Amplification coefficient multiplied by n * (n - 1) - @param _fee Fee to charge for exchanges - @param _admin_fee Admin fee - """ - for i in range(N_COINS): - assert _coins[i] != ZERO_ADDRESS - assert _underlying_coins[i] != ZERO_ADDRESS - - # approve underlying coins for infinite transfers - _response: Bytes[32] = raw_call( - _underlying_coins[i], - concat( - method_id("approve(address,uint256)"), - convert(_coins[i], bytes32), - convert(MAX_UINT256, bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) - - self.coins = _coins - self.underlying_coins = _underlying_coins - self.initial_A = _A * A_PRECISION - self.future_A = _A * A_PRECISION - self.fee = _fee - self.admin_fee = _admin_fee - self.owner = _owner - self.kill_deadline = block.timestamp + KILL_DEADLINE_DT - self.lp_token = _pool_token - - -@view -@internal -def _A() -> uint256: - """ - Handle ramping A up or down - """ - t1: uint256 = self.future_A_time - A1: uint256 = self.future_A - - if block.timestamp < t1: - A0: uint256 = self.initial_A - t0: uint256 = self.initial_A_time - # Expressions in uint256 cannot have negative numbers, thus "if" - if A1 > A0: - return A0 + (A1 - A0) * (block.timestamp - t0) / (t1 - t0) - else: - return A0 - (A0 - A1) * (block.timestamp - t0) / (t1 - t0) - - else: # when t1 == 0 or block.timestamp >= t1 - return A1 - - -@view -@external -def A() -> uint256: - return self._A() / A_PRECISION - - -@view -@external -def A_precise() -> uint256: - return self._A() - - -@view -@internal -def _stored_rates() -> uint256[N_COINS]: - # exchangeRateStored * (1 + supplyRatePerBlock * (getBlockNumber - accrualBlockNumber) / 1e18) - result: uint256[N_COINS] = PRECISION_MUL - for i in range(N_COINS): - coin: address = self.coins[i] - rate: uint256 = cyToken(coin).exchangeRateStored() - rate += rate * cyToken(coin).supplyRatePerBlock() * (block.number - cyToken(coin).accrualBlockNumber()) / PRECISION - result[i] *= rate - return result - - -@internal -def _update(): - """ - Commits pre-change balances for the previous block - Can be used to compare against current values for flash loan checks - """ - if block.timestamp > self.block_timestamp_last: - self.previous_balances = self.balances - self.block_timestamp_last = block.timestamp - - -@internal -def _current_rates() -> uint256[N_COINS]: - self._update() - result: uint256[N_COINS] = PRECISION_MUL - for i in range(N_COINS): - result[i] *= cyToken(self.coins[i]).exchangeRateCurrent() - return result - - -@view -@internal -def _xp(rates: uint256[N_COINS]) -> uint256[N_COINS]: - result: uint256[N_COINS] = empty(uint256[N_COINS]) - for i in range(N_COINS): - result[i] = rates[i] * self.balances[i] / PRECISION - return result - -@pure -@internal -def get_D(xp: uint256[N_COINS], amp: uint256) -> uint256: - S: uint256 = 0 - Dprev: uint256 = 0 - - for _x in xp: - S += _x - if S == 0: - return 0 - - D: uint256 = S - Ann: uint256 = amp * N_COINS - for _i in range(255): - D_P: uint256 = D - for _x in xp: - D_P = D_P * D / (_x * N_COINS) # If division by 0, this will be borked: only withdrawal will work. And that is good - Dprev = D - D = (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (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` - raise - - -@view -@internal -def get_D_mem(rates: uint256[N_COINS], _balances: uint256[N_COINS], _amp: uint256) -> uint256: - xp: uint256[N_COINS] = empty(uint256[N_COINS]) - for i in range(N_COINS): - xp[i] = rates[i] * _balances[i] / PRECISION - - return self.get_D(xp, _amp) - - -@view -@external -def get_virtual_price() -> uint256: - """ - @notice The current virtual price of the pool LP token - @dev Useful for calculating profits - @return LP token virtual price normalized to 1e18 - """ - D: uint256 = self.get_D(self._xp(self._stored_rates()), self._A()) - # 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 - return D * PRECISION / ERC20(self.lp_token).totalSupply() - - -@view -@external -def calc_token_amount(amounts: uint256[N_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 amounts Amount of each coin being deposited - @param is_deposit set True for deposits, False for withdrawals - @return Expected amount of LP tokens received - """ - amp: uint256 = self._A() - rates: uint256[N_COINS] = self._stored_rates() - _balances: uint256[N_COINS] = self.balances - D0: uint256 = self.get_D_mem(rates, _balances, amp) - for i in range(N_COINS): - _amount: uint256 = amounts[i] - if is_deposit: - _balances[i] += _amount - else: - _balances[i] -= _amount - D1: uint256 = self.get_D_mem(rates, _balances, amp) - token_amount: uint256 = ERC20(self.lp_token).totalSupply() - diff: uint256 = 0 - if is_deposit: - diff = D1 - D0 - else: - diff = D0 - D1 - return diff * token_amount / D0 - - -@external -@nonreentrant('lock') -def add_liquidity( - _amounts: uint256[N_COINS], - _min_mint_amount: uint256, - _use_underlying: bool = False -) -> uint256: - """ - @notice Deposit coins into the pool - @param _amounts List of amounts of coins to deposit - @param _min_mint_amount Minimum amount of LP tokens to mint from the deposit - @param _use_underlying If True, deposit underlying assets instead of cyTokens - @return Amount of LP tokens received by depositing - """ - assert not self.is_killed - - amp: uint256 = self._A() - rates: uint256[N_COINS] = self._current_rates() - _lp_token: address = self.lp_token - token_supply: uint256 = ERC20(_lp_token).totalSupply() - - # Initial invariant - old_balances: uint256[N_COINS] = self.balances - D0: uint256 = self.get_D_mem(rates, old_balances, amp) - - # Take coins from the sender - new_balances: uint256[N_COINS] = old_balances - amounts: uint256[N_COINS] = empty(uint256[N_COINS]) - for i in range(N_COINS): - amount: uint256 = _amounts[i] - - if amount == 0: - assert token_supply > 0 - else: - coin: address = self.coins[i] - if _use_underlying: - ERC20(self.underlying_coins[i]).transferFrom(msg.sender, self, amount) - before: uint256 = ERC20(coin).balanceOf(self) - assert cyToken(coin).mint(amount) == 0 - amount = ERC20(coin).balanceOf(self) - before - else: - assert cyToken(coin).transferFrom(msg.sender, self, amount) - amounts[i] = amount - new_balances[i] += amount - - # Invariant after change - D1: uint256 = self.get_D_mem(rates, new_balances, amp) - assert D1 > D0 - - # We need to recalculate the invariant accounting for fees - # to calculate fair user's share - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - mint_amount: uint256 = 0 - if token_supply != 0: - # Only account for fees if we are not the first to deposit - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - _admin_fee: uint256 = self.admin_fee - difference: uint256 = 0 - for i in range(N_COINS): - new_balance: uint256 = new_balances[i] - ideal_balance: uint256 = D1 * old_balances[i] / D0 - if ideal_balance > new_balance: - difference = ideal_balance - new_balance - else: - difference = new_balance - ideal_balance - fees[i] = _fee * difference / FEE_DENOMINATOR - self.balances[i] = new_balance - (fees[i] * _admin_fee / FEE_DENOMINATOR) - new_balances[i] -= fees[i] - D2: uint256 = self.get_D_mem(rates, new_balances, amp) - mint_amount = token_supply * (D2 - D0) / D0 - else: - self.balances = new_balances - mint_amount = D1 # Take the dust if there was any - - assert mint_amount >= _min_mint_amount, "Slippage screwed you" - - # Mint pool tokens - CurveToken(_lp_token).mint(msg.sender, mint_amount) - - log AddLiquidity(msg.sender, amounts, fees, D1, token_supply + mint_amount) - - return mint_amount - - -@view -@internal -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 # dev: same coin - assert j >= 0 # dev: j below zero - assert j < N_COINS # dev: j above N_COINS - - # should be unreachable, but good for safety - assert i >= 0 - assert i < N_COINS - - A_: uint256 = self._A() - D: uint256 = self.get_D(xp_, A_) - Ann: uint256 = A_ * N_COINS - c: uint256 = D - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: 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 * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann # - D - 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: - return y - else: - if y_prev - y <= 1: - return y - raise - - -@view -@external -def get_dy(i: int128, j: int128, dx: uint256) -> uint256: - # dx and dy in c-units - rates: uint256[N_COINS] = self._stored_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 - 1 - return (dy - (self.fee * dy / FEE_DENOMINATOR)) * PRECISION / rates[j] - - -@view -@external -def get_dx(i: int128, j: int128, dy: uint256) -> uint256: - # dx and dy in c-units - rates: uint256[N_COINS] = self._stored_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) - return (x - xp[i]) * PRECISION / rates[i] - - -@view -@external -def get_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256: - # dx and dy in underlying units - rates: uint256[N_COINS] = self._stored_rates() - xp: uint256[N_COINS] = self._xp(rates) - precisions: uint256[N_COINS] = PRECISION_MUL - - x: uint256 = xp[i] + dx * precisions[i] - dy: uint256 = xp[j] - self.get_y(i, j, x, xp) - 1 - _fee: uint256 = self.fee * dy / FEE_DENOMINATOR - return (dy - _fee) / precisions[j] - - -@external -@view -def get_dx_underlying(i: int128, j: int128, dy: uint256) -> uint256: - # dx and dy in underlying units - rates: uint256[N_COINS] = self._stored_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] - return (self.get_y(j, i, y, xp) - xp[i]) / precisions[i] - -@internal -def _exchange(i: int128, j: int128, dx: uint256) -> uint256: - assert not self.is_killed - # dx and dy are in cy tokens - - rates: uint256[N_COINS] = self._current_rates() - old_balances: uint256[N_COINS] = self.balances - - xp: uint256[N_COINS] = empty(uint256[N_COINS]) - for k in range(N_COINS): - xp[k] = rates[k] * old_balances[k] / PRECISION - - x: uint256 = xp[i] + dx * rates[i] / PRECISION - dy: uint256 = xp[j] - self.get_y(i, j, x, xp) - 1 # -1 just in case there were some rounding errors - dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR - - dy = (dy - dy_fee) * PRECISION / rates[j] - dy_admin_fee: uint256 = dy_fee * self.admin_fee / FEE_DENOMINATOR - dy_admin_fee = dy_admin_fee * PRECISION / rates[j] - - self.balances[i] = old_balances[i] + dx - self.balances[j] = old_balances[j] - dy - dy_admin_fee - - return dy - - -@external -@nonreentrant('lock') -def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256) -> uint256: - """ - @notice Perform an exchange between two coins - @dev Index values can be found via the `coins` public getter method - @param i Index value for the coin to send - @param j Index valie of the coin to recieve - @param dx Amount of `i` being exchanged - @param min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - dy: uint256 = self._exchange(i, j, dx) - assert dy >= min_dy, "Too few coins in result" - - assert cyToken(self.coins[i]).transferFrom(msg.sender, self, dx) - assert cyToken(self.coins[j]).transfer(msg.sender, dy) - - log TokenExchange(msg.sender, i, dx, j, dy) - - return dy - - -@external -@nonreentrant('lock') -def exchange_underlying(i: int128, j: int128, dx: uint256, min_dy: uint256) -> uint256: - """ - @notice Perform an exchange between two underlying coins - @dev Index values can be found via the `underlying_coins` public getter method - @param i Index value for the underlying coin to send - @param j Index valie of the underlying coin to recieve - @param dx Amount of `i` being exchanged - @param min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - - ERC20(self.underlying_coins[i]).transferFrom(msg.sender, self, dx) - - coin: address = self.coins[i] - - dx_: uint256 = ERC20(coin).balanceOf(self) - assert cyToken(coin).mint(dx) == 0 - dx_ = ERC20(coin).balanceOf(self) - dx_ - dy_: uint256 = self._exchange(i, j, dx_) - - assert cyToken(self.coins[j]).redeem(dy_) == 0 - - underlying: address = self.underlying_coins[j] - - dy: uint256 = ERC20(underlying).balanceOf(self) - assert dy >= min_dy, "Too few coins in result" - - ERC20(underlying).transfer(msg.sender, dy) - - log TokenExchangeUnderlying(msg.sender, i, dx, j, dy) - - return dy - - -@external -@nonreentrant('lock') -def remove_liquidity( - _amount: uint256, - _min_amounts: uint256[N_COINS], - _use_underlying: bool = False -) -> uint256[N_COINS]: - """ - @notice Withdraw coins from the pool - @dev Withdrawal amounts are based on current deposit ratios - @param _amount Quantity of LP tokens to burn in the withdrawal - @param _min_amounts Minimum amounts of underlying coins to receive - @param _use_underlying If True, withdraw underlying assets instead of cyTokens - @return List of amounts of coins that were withdrawn - """ - self._update() - _lp_token: address = self.lp_token - total_supply: uint256 = ERC20(_lp_token).totalSupply() - amounts: uint256[N_COINS] = empty(uint256[N_COINS]) - - for i in range(N_COINS): - _balance: uint256 = self.balances[i] - value: uint256 = _balance * _amount / total_supply - self.balances[i] = _balance - value - amounts[i] = value - - coin: address = self.coins[i] - if _use_underlying: - assert cyToken(coin).redeem(value) == 0 - underlying: address = self.underlying_coins[i] - value = ERC20(underlying).balanceOf(self) - ERC20(underlying).transfer(msg.sender, value) - else: - assert cyToken(coin).transfer(msg.sender, value) - - assert value >= _min_amounts[i] - - CurveToken(_lp_token).burnFrom(msg.sender, _amount) # Will raise if not enough - - log RemoveLiquidity(msg.sender, amounts, empty(uint256[N_COINS]), total_supply - _amount) - - return amounts - - -@external -@nonreentrant('lock') -def remove_liquidity_imbalance( - _amounts: uint256[N_COINS], - _max_burn_amount: uint256, - _use_underlying: bool = False -) -> uint256: - """ - @notice Withdraw coins from the pool in an imbalanced amount - @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 _use_underlying If True, withdraw underlying assets instead of cyTokens - @return Actual amount of the LP token burned in the withdrawal - """ - assert not self.is_killed - - amp: uint256 = self._A() - rates: uint256[N_COINS] = self._current_rates() - old_balances: uint256[N_COINS] = self.balances - D0: uint256 = self.get_D_mem(rates, old_balances, amp) - - new_balances: uint256[N_COINS] = old_balances - amounts: uint256[N_COINS] = _amounts - - precisions: uint256[N_COINS] = PRECISION_MUL - for i in range(N_COINS): - amount: uint256 = amounts[i] - if amount > 0: - if _use_underlying: - amount = amount * precisions[i] * PRECISION / rates[i] - amounts[i] = amount - new_balances[i] -= amount - - D1: uint256 = self.get_D_mem(rates, new_balances, amp) - - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - _admin_fee: uint256 = self.admin_fee - for i in range(N_COINS): - ideal_balance: uint256 = D1 * old_balances[i] / D0 - new_balance: uint256 = new_balances[i] - difference: uint256 = 0 - if ideal_balance > new_balance: - difference = ideal_balance - new_balance - else: - difference = new_balance - ideal_balance - coin_fee: uint256 = _fee * difference / FEE_DENOMINATOR - self.balances[i] = new_balance - (coin_fee * _admin_fee / FEE_DENOMINATOR) - new_balances[i] -= coin_fee - fees[i] = coin_fee - D2: uint256 = self.get_D_mem(rates, new_balances, amp) - - lp_token: address = self.lp_token - token_supply: uint256 = ERC20(lp_token).totalSupply() - token_amount: uint256 = (D0 - D2) * token_supply / D0 - assert token_amount != 0 - assert token_amount <= _max_burn_amount, "Slippage screwed you" - - CurveToken(lp_token).burnFrom(msg.sender, token_amount) # dev: insufficient funds - for i in range(N_COINS): - amount: uint256 = amounts[i] - if amount != 0: - coin: address = self.coins[i] - if _use_underlying: - assert cyToken(coin).redeem(amount) == 0 - underlying: address = self.underlying_coins[i] - ERC20(underlying).transfer(msg.sender, ERC20(underlying).balanceOf(self)) - else: - assert cyToken(coin).transfer(msg.sender, amount) - - log RemoveLiquidityImbalance(msg.sender, amounts, fees, D1, token_supply - token_amount) - - return token_amount - - -@pure -@internal -def get_y_D(A_: uint256, i: int128, xp: uint256[N_COINS], D: uint256) -> uint256: - """ - Calculate x[i] if one reduces D from being calculated for xp to D - - Done by solving quadratic equation iteratively. - x_1**2 + x1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) - x_1**2 + b*x_1 = c - - x_1 = (x_1**2 + c) / (2*x_1 + b) - """ - # x in the input is converted to the same price/precision - - assert i >= 0 # dev: i below zero - assert i < N_COINS # dev: i above N_COINS - - Ann: uint256 = A_ * N_COINS - c: uint256 = D - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: uint256 = 0 - - for _i in range(N_COINS): - if _i != i: - _x = xp[_i] - else: - continue - S_ += _x - c = c * D / (_x * N_COINS) - c = c * D * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann - 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: - return y - else: - if y_prev - y <= 1: - return y - raise - - -@view -@internal -def _calc_withdraw_one_coin(_token_amount: uint256, i: int128, _use_underlying: bool, _rates: uint256[N_COINS]) -> uint256[2]: - # First, need to calculate - # * Get current D - # * Solve Eqn against y_i for D - _token_amount - amp: uint256 = self._A() - xp: uint256[N_COINS] = self._xp(_rates) - D0: uint256 = self.get_D(xp, amp) - - total_supply: uint256 = ERC20(self.lp_token).totalSupply() - - D1: uint256 = D0 - _token_amount * D0 / total_supply - new_y: uint256 = self.get_y_D(amp, i, xp, D1) - - xp_reduced: uint256[N_COINS] = xp - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - rate: uint256 = _rates[i] - - for j in range(N_COINS): - dx_expected: uint256 = 0 - xp_j: uint256 = xp[j] - if j == i: - dx_expected = xp_j * D1 / D0 - new_y - else: - dx_expected = xp_j - xp_j * D1 / D0 - xp_reduced[j] -= _fee * dx_expected / FEE_DENOMINATOR - - dy: uint256 = xp_reduced[i] - self.get_y_D(amp, i, xp_reduced, D1) - dy = (dy - 1) * PRECISION / rate # Withdraw less to account for rounding errors - dy_fee: uint256 = ((xp[i] - new_y) * PRECISION / rate) - dy - if _use_underlying: - # this branch is only reachable when called via `calc_withdraw_one_coin`, which - # only needs `dy` - so we don't bother converting `dy_fee` to the underlying - precisions: uint256[N_COINS] = PRECISION_MUL - dy = dy * rate / precisions[i] / PRECISION - - return [dy, dy_fee] - - -@view -@external -def calc_withdraw_one_coin(_token_amount: uint256, i: int128, _use_underlying: bool = False) -> uint256: - """ - @notice Calculate the amount received when withdrawing a single coin - @param _token_amount Amount of LP tokens to burn in the withdrawal - @param i Index value of the coin to withdraw - @return Amount of coin received - """ - return self._calc_withdraw_one_coin(_token_amount, i, _use_underlying, self._stored_rates())[0] - - -@external -@nonreentrant('lock') -def remove_liquidity_one_coin( - _token_amount: uint256, - i: int128, - _min_amount: uint256, - _use_underlying: bool = False -) -> uint256: - """ - @notice Withdraw a single coin from the pool - @param _token_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 coin to receive - @param _use_underlying If True, withdraw underlying assets instead of cyTokens - @return Amount of coin received - """ - assert not self.is_killed # dev: is killed - - dy: uint256[2] = self._calc_withdraw_one_coin(_token_amount, i, False, self._current_rates()) - amount: uint256 = dy[0] - - self.balances[i] -= (dy[0] + dy[1] * self.admin_fee / FEE_DENOMINATOR) - CurveToken(self.lp_token).burnFrom(msg.sender, _token_amount) # dev: insufficient funds - coin: address = self.coins[i] - if _use_underlying: - assert cyToken(coin).redeem(dy[0]) == 0 - underlying: address = self.underlying_coins[i] - amount = ERC20(underlying).balanceOf(self) - ERC20(underlying).transfer(msg.sender, amount) - else: - assert cyToken(coin).transfer(msg.sender, amount) - - assert amount >= _min_amount, "Not enough coins removed" - log RemoveLiquidityOne(msg.sender, _token_amount, dy[0]) - - return dy[0] - - -### Admin functions ### -@external -def ramp_A(_future_A: uint256, _future_time: uint256): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.initial_A_time + MIN_RAMP_TIME - assert _future_time >= block.timestamp + MIN_RAMP_TIME # dev: insufficient time - - _initial_A: uint256 = self._A() - _future_A_p: uint256 = _future_A * A_PRECISION - - assert _future_A > 0 and _future_A < MAX_A - if _future_A_p < _initial_A: - assert _future_A_p * MAX_A_CHANGE >= _initial_A - else: - assert _future_A_p <= _initial_A * MAX_A_CHANGE - - self.initial_A = _initial_A - self.future_A = _future_A_p - self.initial_A_time = block.timestamp - self.future_A_time = _future_time - - log RampA(_initial_A, _future_A_p, block.timestamp, _future_time) - - -@external -def stop_ramp_A(): - assert msg.sender == self.owner # dev: only owner - - current_A: uint256 = self._A() - self.initial_A = current_A - self.future_A = current_A - self.initial_A_time = block.timestamp - self.future_A_time = block.timestamp - # now (block.timestamp < t1) is always False, so we return saved A - - log StopRampA(current_A, block.timestamp) - - -@external -def commit_new_fee(new_fee: uint256, new_admin_fee: uint256): - assert msg.sender == self.owner # dev: only owner - assert self.admin_actions_deadline == 0 # dev: active action - assert new_fee <= MAX_FEE # dev: fee exceeds maximum - assert new_admin_fee <= MAX_ADMIN_FEE # dev: admin fee exceeds maximum - - _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.admin_actions_deadline = _deadline - self.future_fee = new_fee - self.future_admin_fee = new_admin_fee - - log CommitNewFee(_deadline, new_fee, new_admin_fee) - - -@external -def apply_new_fee(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.admin_actions_deadline # dev: insufficient time - assert self.admin_actions_deadline != 0 # dev: no active action - - self.admin_actions_deadline = 0 - _fee: uint256 = self.future_fee - _admin_fee: uint256 = self.future_admin_fee - self.fee = _fee - self.admin_fee = _admin_fee - - log NewFee(_fee, _admin_fee) - - -@external -def revert_new_parameters(): - assert msg.sender == self.owner # dev: only owner - - self.admin_actions_deadline = 0 - - -@external -def commit_transfer_ownership(_owner: address): - assert msg.sender == self.owner # dev: only owner - assert self.transfer_ownership_deadline == 0 # dev: active transfer - - _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.transfer_ownership_deadline = _deadline - self.future_owner = _owner - - log CommitNewAdmin(_deadline, _owner) - - -@external -def apply_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.transfer_ownership_deadline # dev: insufficient time - assert self.transfer_ownership_deadline != 0 # dev: no active transfer - - self.transfer_ownership_deadline = 0 - _owner: address = self.future_owner - self.owner = _owner - - log NewAdmin(_owner) - - -@external -def revert_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - - self.transfer_ownership_deadline = 0 - - -@view -@external -def admin_balances(i: uint256) -> uint256: - return ERC20(self.coins[i]).balanceOf(self) - self.balances[i] - - -@external -def withdraw_admin_fees(): - assert msg.sender == self.owner # dev: only owner - - for i in range(N_COINS): - coin: address = self.coins[i] - value: uint256 = ERC20(coin).balanceOf(self) - self.balances[i] - if value > 0: - assert cyToken(coin).transfer(msg.sender, value) - - -@external -def kill_me(): - assert msg.sender == self.owner # dev: only owner - assert self.kill_deadline > block.timestamp # dev: deadline has passed - self.is_killed = True - - -@external -def unkill_me(): - assert msg.sender == self.owner # dev: only owner - self.is_killed = False diff --git a/contracts/pools/ib/pooldata.json b/contracts/pools/ib/pooldata.json deleted file mode 100644 index 35291d29..00000000 --- a/contracts/pools/ib/pooldata.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "lp_contract": "CurveTokenV3", - "pool_types": ["crate"], - "wrapped_contract": "cERC20", - "swap_address": "0x2dded6Da1BF5DBdF597C45fcFaa3194e53EcfeAF", - "lp_token_address": "0x5282a4eF67D9C33135340fB3289cc1711c13638C", - "gauge_addresses": ["0xF5194c3325202F456c95c1Cf0cA36f8475C1949F"], - "lp_constructor": { - "symbol": "ib3CRV", - "name": "Curve.fi cyDAI/cyUSDC/cyUSDT" - }, - "swap_constructor": { - "_A": 600, - "_fee": 4000000, - "_admin_fee": 5000000000 - }, - "coins": [ - { - "name": "cyDAI", - "underlying_name": "DAI", - "decimals": 18, - "wrapped_decimals": 8, - "tethered": false, - "underlying_address": "0x6b175474e89094c44da98b954eedeac495271d0f", - "wrapped_address": "0x8e595470ed749b85c6f7669de83eae304c2ec68f" - }, - { - "name": "cyUSDC", - "underlying_name": "USDC", - "decimals": 6, - "wrapped_decimals": 8, - "tethered": false, - "underlying_address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", - "wrapped_address": "0x76eb2fe28b36b3ee97f3adae0c69606eedb2a37c" - - }, - { - "name": "cyUSDT", - "underlying_name": "USDT", - "decimals": 6, - "wrapped_decimals": 8, - "tethered": true, - "underlying_address": "0xdac17f958d2ee523a2206206994597c13d831ec7", - "wrapped_address": "0x48759f220ed983db51fa7a8c0d2aab8f3ce4166a" - } - ] -} diff --git a/contracts/pools/link/README.md b/contracts/pools/link/README.md deleted file mode 100644 index 3f58bb32..00000000 --- a/contracts/pools/link/README.md +++ /dev/null @@ -1,20 +0,0 @@ -# curve-contract/contracts/pools/link - -[Curve LINK pool](https://www.curve.fi/link). This is a no-lending pool. - -## Contracts - -* [`StableSwapLINK`](StableSwapLINK.vy): Curve stablecoin AMM contract - -## Deployments - -* [`CurveContractV3`](../../tokens/CurveTokenV3.vy): [0xcee60cfa923170e4f8204ae08b4fa6a3f5656f3a](https://etherscan.io/address/0xcee60cfa923170e4f8204ae08b4fa6a3f5656f3a) -* [`LiquidityGaugeV2`](https://github.com/curvefi/curve-dao-contracts/blob/master/contracts/gauges/LiquidityGaugeV2.vy): [0xFD4D8a17df4C27c1dD245d153ccf4499e806C87D](https://etherscan.io/address/0xFD4D8a17df4C27c1dD245d153ccf4499e806C87D) -* [`StableSwapLINK`](StableSwapLINK.vy): [0xf178c0b5bb7e7abf4e12a4838c7b7c5ba2c623c0](https://etherscan.io/address/0xf178c0b5bb7e7abf4e12a4838c7b7c5ba2c623c0) - -## Stablecoins - -Curve LINK pool supports swaps between the following stablecoins: - -* `LINK`: [0x514910771AF9Ca656af840dff83E8264EcF986CA](https://etherscan.io/address/0x514910771AF9Ca656af840dff83E8264EcF986CA) -* `sLINK`: [0xbBC455cb4F1B9e4bFC4B73970d360c8f032EfEE6](https://etherscan.io/address/0xbBC455cb4F1B9e4bFC4B73970d360c8f032EfEE6) diff --git a/contracts/pools/link/pooldata.json b/contracts/pools/link/pooldata.json deleted file mode 100644 index 05b90c4c..00000000 --- a/contracts/pools/link/pooldata.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "lp_contract": "CurveTokenV3", - "swap_address": "0xf178c0b5bb7e7abf4e12a4838c7b7c5ba2c623c0", - "lp_token_address": "0xcee60cfa923170e4f8204ae08b4fa6a3f5656f3a", - "gauge_addresses": ["0xFD4D8a17df4C27c1dD245d153ccf4499e806C87D"], - "lp_constructor": { - "symbol": "linkCRV", - "name": "Curve.fi LINK/sLINK" - }, - "swap_constructor": { - "_A": 100, - "_fee": 4000000, - "_admin_fee": 5000000000 - }, - "coins": [ - { - "name": "LINK", - "decimals": 18, - "tethered": false, - "underlying_address": "0x514910771AF9Ca656af840dff83E8264EcF986CA" - }, - { - "name": "sLINK", - "decimals": 18, - "tethered": false, - "underlying_address": "0xbBC455cb4F1B9e4bFC4B73970d360c8f032EfEE6" - } - ], - "testing": { - "initial_amount": 10000 - } -} diff --git a/contracts/pools/linkusd/DepositLinkUSD.vy b/contracts/pools/linkusd/DepositLinkUSD.vy deleted file mode 100644 index 1a417490..00000000 --- a/contracts/pools/linkusd/DepositLinkUSD.vy +++ /dev/null @@ -1,376 +0,0 @@ -# @version 0.2.7 -""" -@title "Zap" Depositer for Curve LinkUSD pool -@author Curve.Fi -@license Copyright (c) Curve.Fi, 2020 - all rights reserved -""" - -from vyper.interfaces import ERC20 - - -interface CurveMeta: - def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256) -> 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) -> 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 base_pool() -> address: 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 - -# An asset which may have a transfer fee (USDT) -FEE_ASSET: constant(address) = 0xdAC17F958D2ee523a2206206994597C13D831ec7 - -FEE_DENOMINATOR: constant(uint256) = 10 ** 10 -FEE_IMPRECISION: constant(uint256) = 100 * 10 ** 8 # % of the fee - - -pool: public(address) -token: public(address) -base_pool: public(address) - -coins: public(address[N_COINS]) -base_coins: public(address[BASE_N_COINS]) - - -@external -def __init__(_pool: address, _token: address): - """ - @notice Contract constructor - @param _pool Metapool address - @param _token Pool LP token address - """ - self.pool = _pool - self.token = _token - _base_pool: address = CurveMeta(_pool).base_pool() - self.base_pool = _base_pool - - for i in range(N_COINS): - coin: address = CurveMeta(_pool).coins(convert(i, uint256)) - self.coins[i] = coin - # approve coins for infinite transfers - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("approve(address,uint256)"), - convert(_pool, bytes32), - convert(MAX_UINT256, bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) - - for i in range(BASE_N_COINS): - coin: address = CurveBase(_base_pool).coins(convert(i, uint256)) - self.base_coins[i] = coin - # approve underlying coins for infinite transfers - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("approve(address,uint256)"), - convert(_base_pool, bytes32), - convert(MAX_UINT256, bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) - - -@external -def add_liquidity(amounts: uint256[N_ALL_COINS], min_mint_amount: uint256) -> uint256: - """ - @notice Wrap underlying coins and deposit them in the pool - @param amounts List of amounts of underlying coins to deposit - @param min_mint_amount Minimum amount of LP tokens to mint from the deposit - @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 - - # Transfer all coins in - for i in range(N_ALL_COINS): - amount: uint256 = amounts[i] - if amount == 0: - continue - coin: address = ZERO_ADDRESS - if i < MAX_COIN: - coin = self.coins[i] - meta_amounts[i] = amount - else: - x: int128 = i - MAX_COIN - coin = self.base_coins[x] - base_amounts[x] = amount - deposit_base = True - # "safeTransferFrom" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transferFrom(address,address,uint256)"), - convert(msg.sender, bytes32), - convert(self, bytes32), - convert(amount, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransferFrom" - # Handle potential Tether fees - if coin == FEE_ASSET: - amount = ERC20(FEE_ASSET).balanceOf(self) - if i < MAX_COIN: - meta_amounts[i] = amount - else: - base_amounts[i - MAX_COIN] = amount - - # Deposit to the base pool - if deposit_base: - CurveBase(self.base_pool).add_liquidity(base_amounts, 0) - meta_amounts[MAX_COIN] = ERC20(self.coins[MAX_COIN]).balanceOf(self) - - # Deposit to the meta pool - CurveMeta(self.pool).add_liquidity(meta_amounts, min_mint_amount) - - # Transfer meta token back - _lp_token: address = self.token - _lp_amount: uint256 = ERC20(_lp_token).balanceOf(self) - assert ERC20(_lp_token).transfer(msg.sender, _lp_amount) - - return _lp_amount - - -@external -def remove_liquidity(_amount: uint256, min_amounts: uint256[N_ALL_COINS]) -> uint256[N_ALL_COINS]: - """ - @notice Withdraw and unwrap coins from the pool - @dev Withdrawal amounts are based on current deposit ratios - @param _amount Quantity of LP tokens to burn in the withdrawal - @param min_amounts Minimum amounts of underlying coins to receive - @return List of amounts of underlying coins that were withdrawn - """ - _token: address = self.token - assert ERC20(_token).transferFrom(msg.sender, self, _amount) - - min_amounts_meta: uint256[N_COINS] = empty(uint256[N_COINS]) - 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 - for i in range(MAX_COIN): - min_amounts_meta[i] = min_amounts[i] - CurveMeta(self.pool).remove_liquidity(_amount, min_amounts_meta) - - # Withdraw from base - _base_amount: uint256 = ERC20(self.coins[MAX_COIN]).balanceOf(self) - for i in range(BASE_N_COINS): - min_amounts_base[i] = min_amounts[MAX_COIN+i] - CurveBase(self.base_pool).remove_liquidity(_base_amount, min_amounts_base) - - # Transfer all coins out - for i in range(N_ALL_COINS): - coin: address = ZERO_ADDRESS - if i < MAX_COIN: - coin = self.coins[i] - else: - coin = self.base_coins[i - MAX_COIN] - amounts[i] = ERC20(coin).balanceOf(self) - # "safeTransfer" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(amounts[i], bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - return amounts - - -@external -def remove_liquidity_one_coin(_token_amount: uint256, i: int128, _min_amount: uint256) -> uint256: - """ - @notice Withdraw and unwrap a single coin from the pool - @param _token_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 - @return Amount of underlying coin received - """ - assert ERC20(self.token).transferFrom(msg.sender, self, _token_amount) - - coin: address = ZERO_ADDRESS - if i < MAX_COIN: - coin = self.coins[i] - # Withdraw a metapool coin - CurveMeta(self.pool).remove_liquidity_one_coin(_token_amount, i, _min_amount) - else: - coin = self.base_coins[i - MAX_COIN] - # Withdraw a base pool coin - CurveMeta(self.pool).remove_liquidity_one_coin(_token_amount, MAX_COIN, 0) - CurveBase(self.base_pool).remove_liquidity_one_coin( - ERC20(self.coins[MAX_COIN]).balanceOf(self), i-MAX_COIN, _min_amount - ) - - # Tranfer the coin out - coin_amount: uint256 = ERC20(coin).balanceOf(self) - # "safeTransfer" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(coin_amount, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - return coin_amount - - -@external -def remove_liquidity_imbalance(amounts: uint256[N_ALL_COINS], max_burn_amount: uint256) -> uint256: - """ - @notice Withdraw coins from the pool in an imbalanced amount - @param amounts List of amounts of underlying coins to withdraw - @param max_burn_amount Maximum amount of LP token to burn in the withdrawal. - This value cannot exceed the caller's LP token balance. - @return Actual amount of the LP token burned in the withdrawal - """ - _base_pool: address = self.base_pool - _meta_pool: address = self.pool - _base_coins: address[BASE_N_COINS] = self.base_coins - _meta_coins: address[N_COINS] = self.coins - _lp_token: address = self.token - - 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 - assert ERC20(_lp_token).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]) - leftover_amounts: uint256[N_COINS] = empty(uint256[N_COINS]) - - # Prepare quantities - for i in range(MAX_COIN): - amounts_meta[i] = amounts[i] - - for i in range(BASE_N_COINS): - amount: uint256 = amounts[MAX_COIN + i] - if amount != 0: - amounts_base[i] = amount - withdraw_base = True - - if withdraw_base: - amounts_meta[MAX_COIN] = CurveBase(self.base_pool).calc_token_amount(amounts_base, False) - amounts_meta[MAX_COIN] += amounts_meta[MAX_COIN] * fee / FEE_DENOMINATOR + 1 - - # Remove liquidity and deposit leftovers back - CurveMeta(_meta_pool).remove_liquidity_imbalance(amounts_meta, max_burn_amount) - if withdraw_base: - CurveBase(_base_pool).remove_liquidity_imbalance(amounts_base, amounts_meta[MAX_COIN]) - leftover_amounts[MAX_COIN] = ERC20(_meta_coins[MAX_COIN]).balanceOf(self) - if leftover_amounts[MAX_COIN] > 0: - CurveMeta(_meta_pool).add_liquidity(leftover_amounts, 0) - - # Transfer all coins out - for i in range(N_ALL_COINS): - coin: address = ZERO_ADDRESS - amount: uint256 = 0 - if i < MAX_COIN: - coin = _meta_coins[i] - amount = amounts_meta[i] - else: - coin = _base_coins[i - MAX_COIN] - amount = amounts_base[i - MAX_COIN] - # "safeTransfer" which works for ERC20s which return bool or not - if amount > 0: - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(amount, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - # Transfer the leftover LP token out - leftover: uint256 = ERC20(_lp_token).balanceOf(self) - if leftover > 0: - assert ERC20(_lp_token).transfer(msg.sender, leftover) - - return max_burn_amount - leftover - - -@view -@external -def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - """ - @notice Calculate the amount received when withdrawing and unwrapping a single coin - @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(self.pool).calc_withdraw_one_coin(_token_amount, i) - else: - _base_tokens: uint256 = CurveMeta(self.pool).calc_withdraw_one_coin(_token_amount, MAX_COIN) - return CurveBase(self.base_pool).calc_withdraw_one_coin(_base_tokens, i-MAX_COIN) - - -@view -@external -def calc_token_amount(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 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]) - - for i in range(MAX_COIN): - meta_amounts[i] = amounts[i] - - for i in range(BASE_N_COINS): - base_amounts[i] = amounts[i + MAX_COIN] - - _base_tokens: uint256 = CurveBase(self.base_pool).calc_token_amount(base_amounts, is_deposit) - meta_amounts[MAX_COIN] = _base_tokens - - return CurveMeta(self.pool).calc_token_amount(meta_amounts, is_deposit) diff --git a/contracts/pools/linkusd/README.md b/contracts/pools/linkusd/README.md deleted file mode 100644 index 692e9c3d..00000000 --- a/contracts/pools/linkusd/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# curve-contract/contracts/pools/linkusd - -[Curve LINKUSD metapool](https://www.curve.fi/linkusd), allowing swaps via the Curve [tri-pool](../3pool). - -## Contracts - -* [`DepositLinkUSD`](DepositLinkUSD.vy): Depositor contract, used to wrap underlying tokens prior to depositing them into the pool -* [`StableSwapLinkUSD`](StableSwapLinkUSD.vy): Curve stablecoin AMM contract - -## Deployments - -* [`CurveContractV2`](../../tokens/CurveTokenV2.vy): [0x6D65b498cb23deAba52db31c93Da9BFFb340FB8F](https://etherscan.io/address/0x6D65b498cb23deAba52db31c93Da9BFFb340FB8F) -* [`DepositLinkUSD`](DepositGUSD.vy): [0x1de7f0866e2c4adAC7b457c58Cc25c8688CDa1f2](https://etherscan.io/address/0x1de7f0866e2c4adAC7b457c58Cc25c8688CDa1f2) -* [`LiquidityGauge`](../../gauges/LiquidityGauge.vy): [](https://etherscan.io/address/) -* [`StableSwapLinkUSD`](StableSwapUSDT.vy): [0xE7a24EF0C5e95Ffb0f6684b813A78F2a3AD7D171](https://etherscan.io/address/0xE7a24EF0C5e95Ffb0f6684b813A78F2a3AD7D171) - -## Stablecoins - -Curve LINKUSD metapool utilizes the supports swaps between the following assets: - -## Direct swaps - -Direct swaps are possible between LINKUSD and the Curve tri-pool LP token. - -* `LINKUSD`: [0x0E2EC54fC0B509F445631Bf4b91AB8168230C752](https://etherscan.io/address/0x0E2EC54fC0B509F445631Bf4b91AB8168230C752) -* `3CRV`: [0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490](https://etherscan.io/address/0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490) - -## Base Pool coins - -The tri-pool LP token may be wrapped or unwrapped to provide swaps between LINKUSD and the following stablecoins: - -* `DAI`: [0x6b175474e89094c44da98b954eedeac495271d0f](https://etherscan.io/address/0x6b175474e89094c44da98b954eedeac495271d0f) -* `USDC`: [0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48](https://etherscan.io/address/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48) -* `USDT`: [0xdac17f958d2ee523a2206206994597c13d831ec7](https://etherscan.io/address/0xdac17f958d2ee523a2206206994597c13d831ec7) diff --git a/contracts/pools/linkusd/StableSwapLinkUSD.vy b/contracts/pools/linkusd/StableSwapLinkUSD.vy deleted file mode 100644 index ccf78353..00000000 --- a/contracts/pools/linkusd/StableSwapLinkUSD.vy +++ /dev/null @@ -1,1082 +0,0 @@ -# @version 0.2.5 -""" -@title Curve LINKUSD Metapool -@author Curve.Fi -@license Copyright (c) Curve.Fi, 2020 - all rights reserved -@dev Utilizes 3Pool to allow swaps between LINKUSD / DAI / USDC / USDT -""" - -from vyper.interfaces import ERC20 - -interface CurveToken: - def totalSupply() -> uint256: view - def mint(_to: address, _value: uint256) -> bool: nonpayable - def burnFrom(_to: address, _value: uint256) -> bool: nonpayable - -interface Curve: - def coins(i: uint256) -> address: view - def get_virtual_price() -> uint256: view - def calc_token_amount(amounts: uint256[BASE_N_COINS], deposit: bool) -> uint256: view - def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: view - def fee() -> uint256: view - def get_dy(i: int128, j: int128, dx: uint256) -> uint256: view - def get_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256: view - def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256): nonpayable - def add_liquidity(amounts: uint256[BASE_N_COINS], min_mint_amount: uint256): nonpayable - def remove_liquidity_one_coin(_token_amount: uint256, i: int128, min_amount: uint256): nonpayable - - -# Events -event TokenExchange: - buyer: indexed(address) - sold_id: int128 - tokens_sold: uint256 - bought_id: int128 - tokens_bought: uint256 - -event TokenExchangeUnderlying: - buyer: indexed(address) - sold_id: int128 - tokens_sold: uint256 - bought_id: int128 - tokens_bought: uint256 - -event AddLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event RemoveLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - token_supply: uint256 - -event RemoveLiquidityOne: - provider: indexed(address) - token_amount: uint256 - coin_amount: uint256 - token_supply: uint256 - -event RemoveLiquidityImbalance: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event CommitNewAdmin: - deadline: indexed(uint256) - admin: indexed(address) - -event NewAdmin: - admin: indexed(address) - -event CommitNewFee: - deadline: indexed(uint256) - fee: uint256 - admin_fee: uint256 - -event NewFee: - fee: uint256 - admin_fee: uint256 - -event RampA: - old_A: uint256 - new_A: uint256 - initial_time: uint256 - future_time: uint256 - -event StopRampA: - A: uint256 - t: uint256 - - -N_COINS: constant(int128) = 2 -MAX_COIN: constant(int128) = N_COINS - 1 - -FEE_DENOMINATOR: constant(uint256) = 10 ** 10 -PRECISION: constant(uint256) = 10 ** 18 # The precision to convert to -PRECISION_MUL: constant(uint256[N_COINS]) = [1, 1] -RATES: constant(uint256[N_COINS]) = [1000000000000000000, 1000000000000000000] - -BASE_N_COINS: constant(int128) = 3 -N_ALL_COINS: constant(int128) = N_COINS + BASE_N_COINS - 1 -BASE_PRECISION_MUL: constant(uint256[BASE_N_COINS]) = [1, 1000000000000, 1000000000000] -BASE_RATES: constant(uint256[BASE_N_COINS]) = [1000000000000000000, 1000000000000000000000000000000, 1000000000000000000000000000000] - -# An asset which may have a transfer fee (USDT) -FEE_ASSET: constant(address) = 0xdAC17F958D2ee523a2206206994597C13D831ec7 - -MAX_ADMIN_FEE: constant(uint256) = 10 * 10 ** 9 -MAX_FEE: constant(uint256) = 5 * 10 ** 9 -MAX_A: constant(uint256) = 10 ** 6 -MAX_A_CHANGE: constant(uint256) = 10 - -ADMIN_ACTIONS_DELAY: constant(uint256) = 3 * 86400 -MIN_RAMP_TIME: constant(uint256) = 86400 - -coins: public(address[N_COINS]) -balances: public(uint256[N_COINS]) -fee: public(uint256) # fee * 1e10 -admin_fee: public(uint256) # admin_fee * 1e10 - -owner: public(address) -token: CurveToken - -# Token corresponding to the pool is always the last one -BASE_POOL_COINS: constant(int128) = 3 -BASE_CACHE_EXPIRES: constant(int128) = 10 * 60 # 10 min -base_pool: public(address) -base_virtual_price: public(uint256) -base_cache_updated: public(uint256) -base_coins: public(address[BASE_POOL_COINS]) - -A_PRECISION: constant(uint256) = 100 -initial_A: public(uint256) -future_A: public(uint256) -initial_A_time: public(uint256) -future_A_time: public(uint256) - -admin_actions_deadline: public(uint256) -transfer_ownership_deadline: public(uint256) -future_fee: public(uint256) -future_admin_fee: public(uint256) -future_owner: public(address) - -is_killed: bool -kill_deadline: uint256 -KILL_DEADLINE_DT: constant(uint256) = 2 * 30 * 86400 - - -@external -def __init__( - _owner: address, - _coins: address[N_COINS], - _pool_token: address, - _base_pool: address, - _A: uint256, - _fee: uint256, - _admin_fee: uint256 -): - """ - @notice Contract constructor - @param _owner Contract owner address - @param _coins Addresses of ERC20 conracts of coins - @param _pool_token Address of the token representing LP share - @param _base_pool Address of the base pool (which will have a virtual price) - @param _A Amplification coefficient multiplied by n * (n - 1) - @param _fee Fee to charge for exchanges - @param _admin_fee Admin fee - """ - for i in range(N_COINS): - assert _coins[i] != ZERO_ADDRESS - self.coins = _coins - self.initial_A = _A * A_PRECISION - self.future_A = _A * A_PRECISION - self.fee = _fee - self.admin_fee = _admin_fee - self.owner = _owner - self.kill_deadline = block.timestamp + KILL_DEADLINE_DT - self.token = CurveToken(_pool_token) - - self.base_pool = _base_pool - self.base_virtual_price = Curve(_base_pool).get_virtual_price() - self.base_cache_updated = block.timestamp - for i in range(BASE_POOL_COINS): - _base_coin: address = Curve(_base_pool).coins(convert(i, uint256)) - self.base_coins[i] = _base_coin - - # approve underlying coins for infinite transfers - _response: Bytes[32] = raw_call( - _base_coin, - concat( - method_id("approve(address,uint256)"), - convert(_base_pool, bytes32), - convert(MAX_UINT256, bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) - - -@view -@internal -def _A() -> uint256: - """ - Handle ramping A up or down - """ - t1: uint256 = self.future_A_time - A1: uint256 = self.future_A - - if block.timestamp < t1: - A0: uint256 = self.initial_A - t0: uint256 = self.initial_A_time - # Expressions in uint256 cannot have negative numbers, thus "if" - if A1 > A0: - return A0 + (A1 - A0) * (block.timestamp - t0) / (t1 - t0) - else: - return A0 - (A0 - A1) * (block.timestamp - t0) / (t1 - t0) - - else: # when t1 == 0 or block.timestamp >= t1 - return A1 - - -@view -@external -def A() -> uint256: - return self._A() / A_PRECISION - - -@view -@external -def A_precise() -> uint256: - return self._A() - - -@view -@internal -def _xp(vp_rate: uint256) -> uint256[N_COINS]: - result: uint256[N_COINS] = RATES - result[MAX_COIN] = vp_rate # virtual price for the metacurrency - for i in range(N_COINS): - result[i] = result[i] * self.balances[i] / PRECISION - return result - - -@pure -@internal -def _xp_mem(vp_rate: uint256, _balances: uint256[N_COINS]) -> uint256[N_COINS]: - result: uint256[N_COINS] = RATES - result[MAX_COIN] = vp_rate # virtual price for the metacurrency - for i in range(N_COINS): - result[i] = result[i] * _balances[i] / PRECISION - return result - - -@internal -def _vp_rate() -> uint256: - if block.timestamp > self.base_cache_updated + BASE_CACHE_EXPIRES: - vprice: uint256 = Curve(self.base_pool).get_virtual_price() - self.base_virtual_price = vprice - self.base_cache_updated = block.timestamp - return vprice - else: - return self.base_virtual_price - - -@internal -@view -def _vp_rate_ro() -> uint256: - if block.timestamp > self.base_cache_updated + BASE_CACHE_EXPIRES: - return Curve(self.base_pool).get_virtual_price() - else: - return self.base_virtual_price - - -@pure -@internal -def get_D(xp: uint256[N_COINS], amp: uint256) -> uint256: - S: uint256 = 0 - Dprev: uint256 = 0 - for _x in xp: - S += _x - if S == 0: - return 0 - - D: uint256 = S - Ann: uint256 = amp * N_COINS - for _i in range(255): - D_P: uint256 = D - for _x in xp: - D_P = D_P * D / (_x * N_COINS) # If division by 0, this will be borked: only withdrawal will work. And that is good - Dprev = D - D = (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (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 - - -@view -@internal -def get_D_mem(vp_rate: uint256, _balances: uint256[N_COINS], amp: uint256) -> uint256: - xp: uint256[N_COINS] = self._xp_mem(vp_rate, _balances) - return self.get_D(xp, amp) - - -@view -@external -def get_virtual_price() -> uint256: - """ - @notice The current virtual price of the pool LP token - @dev Useful for calculating profits - @return LP token virtual price normalized to 1e18 - """ - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate_ro() - xp: uint256[N_COINS] = self._xp(vp_rate) - D: uint256 = self.get_D(xp, amp) - # 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 - - -@view -@external -def calc_token_amount(amounts: uint256[N_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 amounts Amount of each coin being deposited - @param is_deposit set True for deposits, False for withdrawals - @return Expected amount of LP tokens received - """ - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate_ro() - _balances: uint256[N_COINS] = self.balances - D0: uint256 = self.get_D_mem(vp_rate, _balances, amp) - for i in range(N_COINS): - if is_deposit: - _balances[i] += amounts[i] - else: - _balances[i] -= amounts[i] - D1: uint256 = self.get_D_mem(vp_rate, _balances, amp) - token_amount: uint256 = self.token.totalSupply() - diff: uint256 = 0 - if is_deposit: - diff = D1 - D0 - else: - diff = D0 - D1 - return diff * token_amount / D0 - - -@external -@nonreentrant('lock') -def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256) -> uint256: - """ - @notice Deposit coins into the pool - @param amounts List of amounts of coins to deposit - @param min_mint_amount Minimum amount of LP tokens to mint from the deposit - @return Amount of LP tokens received by depositing - """ - assert not self.is_killed # dev: is killed - - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate() - token_supply: uint256 = self.token.totalSupply() - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - _admin_fee: uint256 = self.admin_fee - - # Initial invariant - D0: uint256 = 0 - old_balances: uint256[N_COINS] = self.balances - if token_supply > 0: - D0 = self.get_D_mem(vp_rate, old_balances, amp) - new_balances: uint256[N_COINS] = old_balances - - for i in range(N_COINS): - if token_supply == 0: - assert amounts[i] > 0 # dev: initial deposit requires all coins - # balances store amounts of c-tokens - new_balances[i] = old_balances[i] + amounts[i] - - # Invariant after change - D1: uint256 = self.get_D_mem(vp_rate, new_balances, amp) - assert D1 > D0 - - # We need to recalculate the invariant accounting for fees - # to calculate fair user's share - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - 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(vp_rate, new_balances, amp) - 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 amounts[i] > 0: - assert ERC20(self.coins[i]).transferFrom(msg.sender, self, amounts[i]) # dev: failed transfer - - # Mint pool tokens - self.token.mint(msg.sender, mint_amount) - - log AddLiquidity(msg.sender, amounts, fees, D1, token_supply + mint_amount) - - return mint_amount - - -@view -@internal -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 # dev: same coin - assert j >= 0 # dev: j below zero - assert j < N_COINS # dev: j above N_COINS - - # should be unreachable, but good for safety - assert i >= 0 - assert i < N_COINS - - amp: uint256 = self._A() - D: uint256 = self.get_D(xp_, amp) - - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: uint256 = 0 - c: uint256 = D - Ann: uint256 = amp * N_COINS - - 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 * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann # - D - 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 - - -@view -@external -def get_dy(i: int128, j: int128, dx: uint256) -> uint256: - # dx and dy in c-units - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = self._vp_rate_ro() - xp: uint256[N_COINS] = self._xp(rates[MAX_COIN]) - - x: uint256 = xp[i] + (dx * rates[i] / PRECISION) - y: uint256 = self.get_y(i, j, x, xp) - dy: uint256 = xp[j] - y - 1 - _fee: uint256 = self.fee * dy / FEE_DENOMINATOR - return (dy - _fee) * PRECISION / rates[j] - - -@view -@external -def get_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256: - # dx and dy in underlying units - vp_rate: uint256 = self._vp_rate_ro() - xp: uint256[N_COINS] = self._xp(vp_rate) - precisions: uint256[N_COINS] = PRECISION_MUL - _base_pool: address = self.base_pool - - # Use base_i or base_j if they are >= 0 - base_i: int128 = i - MAX_COIN - base_j: int128 = j - MAX_COIN - meta_i: int128 = MAX_COIN - meta_j: int128 = MAX_COIN - if base_i < 0: - meta_i = i - if base_j < 0: - meta_j = j - - x: uint256 = 0 - if base_i < 0: - x = xp[i] + dx * precisions[i] - else: - if base_j < 0: - # i is from BasePool - # At first, get the amount of pool tokens - base_inputs: uint256[BASE_N_COINS] = empty(uint256[BASE_N_COINS]) - base_inputs[base_i] = dx - # Token amount transformed to underlying "dollars" - x = Curve(_base_pool).calc_token_amount(base_inputs, True) * vp_rate / PRECISION - # Accounting for deposit/withdraw fees approximately - x -= x * Curve(_base_pool).fee() / (2 * FEE_DENOMINATOR) - # Adding number of pool tokens - x += xp[MAX_COIN] - else: - # If both are from the base pool - return Curve(_base_pool).get_dy(base_i, base_j, dx) - - # This pool is involved only when in-pool assets are used - y: uint256 = self.get_y(meta_i, meta_j, x, xp) - dy: uint256 = xp[meta_j] - y - 1 - dy = (dy - self.fee * dy / FEE_DENOMINATOR) - - # If output is going via the metapool - if base_j < 0: - dy /= precisions[meta_j] - else: - # j is from BasePool - # The fee is already accounted for - dy = Curve(_base_pool).calc_withdraw_one_coin(dy * PRECISION / vp_rate, base_j) - - return dy - - -@external -@nonreentrant('lock') -def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256) -> uint256: - """ - @notice Perform an exchange between two coins - @dev Index values can be found via the `coins` public getter method - @param i Index value for the coin to send - @param j Index valie of the coin to recieve - @param dx Amount of `i` being exchanged - @param min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - assert not self.is_killed # dev: is killed - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = self._vp_rate() - - old_balances: uint256[N_COINS] = self.balances - xp: uint256[N_COINS] = self._xp_mem(rates[MAX_COIN], old_balances) - - x: uint256 = xp[i] + dx * rates[i] / PRECISION - y: uint256 = self.get_y(i, j, x, xp) - - dy: uint256 = xp[j] - y - 1 # -1 just in case there were some rounding errors - dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR - - # Convert all to real units - dy = (dy - dy_fee) * PRECISION / rates[j] - assert dy >= min_dy, "Too few coins in result" - - dy_admin_fee: uint256 = dy_fee * self.admin_fee / FEE_DENOMINATOR - dy_admin_fee = dy_admin_fee * PRECISION / rates[j] - - # Change balances exactly in same way as we change actual ERC20 coin amounts - self.balances[i] = old_balances[i] + dx - # When rounding errors happen, we undercharge admin fee in favor of LP - self.balances[j] = old_balances[j] - dy - dy_admin_fee - - assert ERC20(self.coins[i]).transferFrom(msg.sender, self, dx) - assert ERC20(self.coins[j]).transfer(msg.sender, dy) - - log TokenExchange(msg.sender, i, dx, j, dy) - - return dy - - -@external -@nonreentrant('lock') -def exchange_underlying(i: int128, j: int128, dx: uint256, min_dy: uint256) -> uint256: - """ - @notice Perform an exchange between two underlying coins - @dev Index values can be found via the `underlying_coins` public getter method - @param i Index value for the underlying coin to send - @param j Index valie of the underlying coin to recieve - @param dx Amount of `i` being exchanged - @param min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - assert not self.is_killed # dev: is killed - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = self._vp_rate() - _base_pool: address = self.base_pool - - # Use base_i or base_j if they are >= 0 - base_i: int128 = i - MAX_COIN - base_j: int128 = j - MAX_COIN - meta_i: int128 = MAX_COIN - meta_j: int128 = MAX_COIN - if base_i < 0: - meta_i = i - if base_j < 0: - meta_j = j - dy: uint256 = 0 - - # Addresses for input and output coins - input_coin: address = ZERO_ADDRESS - if base_i < 0: - input_coin = self.coins[i] - else: - input_coin = self.base_coins[base_i] - output_coin: address = ZERO_ADDRESS - if base_j < 0: - output_coin = self.coins[j] - else: - output_coin = self.base_coins[base_j] - - # Handle potential Tether fees - dx_w_fee: uint256 = dx - if input_coin == FEE_ASSET: - dx_w_fee = ERC20(FEE_ASSET).balanceOf(self) - # "safeTransferFrom" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - input_coin, - concat( - method_id("transferFrom(address,address,uint256)"), - convert(msg.sender, bytes32), - convert(self, bytes32), - convert(dx, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransferFrom" - # Handle potential Tether fees - if input_coin == FEE_ASSET: - dx_w_fee = ERC20(FEE_ASSET).balanceOf(self) - dx_w_fee - - if base_i < 0 or base_j < 0: - old_balances: uint256[N_COINS] = self.balances - xp: uint256[N_COINS] = self._xp_mem(rates[MAX_COIN], old_balances) - - x: uint256 = 0 - if base_i < 0: - x = xp[i] + dx_w_fee * rates[i] / PRECISION - else: - # i is from BasePool - # At first, get the amount of pool tokens - base_inputs: uint256[BASE_N_COINS] = empty(uint256[BASE_N_COINS]) - base_inputs[base_i] = dx_w_fee - coin_i: address = self.coins[MAX_COIN] - # Deposit and measure delta - x = ERC20(coin_i).balanceOf(self) - Curve(_base_pool).add_liquidity(base_inputs, 0) - # Need to convert pool token to "virtual" units using rates - # dx is also different now - dx_w_fee = ERC20(coin_i).balanceOf(self) - x - x = dx_w_fee * rates[MAX_COIN] / PRECISION - # Adding number of pool tokens - x += xp[MAX_COIN] - - y: uint256 = self.get_y(meta_i, meta_j, x, xp) - - # Either a real coin or token - dy = xp[meta_j] - y - 1 # -1 just in case there were some rounding errors - dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR - - # Convert all to real units - # Works for both pool coins and real coins - dy = (dy - dy_fee) * PRECISION / rates[meta_j] - - dy_admin_fee: uint256 = dy_fee * self.admin_fee / FEE_DENOMINATOR - dy_admin_fee = dy_admin_fee * PRECISION / rates[meta_j] - - # Change balances exactly in same way as we change actual ERC20 coin amounts - self.balances[meta_i] = old_balances[meta_i] + dx_w_fee - # When rounding errors happen, we undercharge admin fee in favor of LP - self.balances[meta_j] = old_balances[meta_j] - dy - dy_admin_fee - - # Withdraw from the base pool if needed - if base_j >= 0: - out_amount: uint256 = ERC20(output_coin).balanceOf(self) - Curve(_base_pool).remove_liquidity_one_coin(dy, base_j, 0) - dy = ERC20(output_coin).balanceOf(self) - out_amount - - assert dy >= min_dy, "Too few coins in result" - - else: - # If both are from the base pool - dy = ERC20(output_coin).balanceOf(self) - Curve(_base_pool).exchange(base_i, base_j, dx_w_fee, min_dy) - dy = ERC20(output_coin).balanceOf(self) - dy - - # "safeTransfer" which works for ERC20s which return bool or not - _response = raw_call( - output_coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(dy, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - log TokenExchangeUnderlying(msg.sender, i, dx, j, dy) - - return dy - - -@external -@nonreentrant('lock') -def remove_liquidity(_amount: uint256, min_amounts: uint256[N_COINS]) -> uint256[N_COINS]: - """ - @notice Withdraw coins from the pool - @dev Withdrawal amounts are based on current deposit ratios - @param _amount Quantity of LP tokens to burn in the withdrawal - @param min_amounts Minimum amounts of underlying coins to receive - @return List of amounts of coins that were withdrawn - """ - total_supply: uint256 = self.token.totalSupply() - amounts: uint256[N_COINS] = empty(uint256[N_COINS]) - fees: uint256[N_COINS] = empty(uint256[N_COINS]) # Fees are unused but we've got them historically in event - - for i in range(N_COINS): - value: uint256 = self.balances[i] * _amount / total_supply - assert value >= min_amounts[i], "Too few coins in result" - self.balances[i] -= value - amounts[i] = value - assert ERC20(self.coins[i]).transfer(msg.sender, value) - - self.token.burnFrom(msg.sender, _amount) # dev: insufficient funds - - log RemoveLiquidity(msg.sender, amounts, fees, total_supply - _amount) - - return amounts - - -@external -@nonreentrant('lock') -def remove_liquidity_imbalance(amounts: uint256[N_COINS], max_burn_amount: uint256) -> uint256: - """ - @notice Withdraw coins from the pool in an imbalanced amount - @param amounts List of amounts of underlying coins to withdraw - @param max_burn_amount Maximum amount of LP token to burn in the withdrawal - @return Actual amount of the LP token burned in the withdrawal - """ - assert not self.is_killed # dev: is killed - - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate() - - token_supply: uint256 = self.token.totalSupply() - assert token_supply != 0 # dev: zero total supply - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - _admin_fee: uint256 = self.admin_fee - - old_balances: uint256[N_COINS] = self.balances - new_balances: uint256[N_COINS] = old_balances - D0: uint256 = self.get_D_mem(vp_rate, old_balances, amp) - for i in range(N_COINS): - new_balances[i] -= amounts[i] - D1: uint256 = self.get_D_mem(vp_rate, new_balances, amp) - - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - 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(vp_rate, new_balances, amp) - - token_amount: uint256 = (D0 - D2) * token_supply / D0 - assert token_amount != 0 # dev: zero tokens burned - token_amount += 1 # In case of rounding errors - make it unfavorable for the "attacker" - assert token_amount <= max_burn_amount, "Slippage screwed you" - - self.token.burnFrom(msg.sender, token_amount) # dev: insufficient funds - for i in range(N_COINS): - if amounts[i] != 0: - assert ERC20(self.coins[i]).transfer(msg.sender, amounts[i]) - - log RemoveLiquidityImbalance(msg.sender, amounts, fees, D1, token_supply - token_amount) - - return token_amount - - -@view -@internal -def get_y_D(A_: uint256, i: int128, xp: uint256[N_COINS], D: uint256) -> uint256: - """ - Calculate x[i] if one reduces D from being calculated for xp to D - - Done by solving quadratic equation iteratively. - x_1**2 + x1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) - x_1**2 + b*x_1 = c - - x_1 = (x_1**2 + c) / (2*x_1 + b) - """ - # x in the input is converted to the same price/precision - - assert i >= 0 # dev: i below zero - assert i < N_COINS # dev: i above N_COINS - - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: uint256 = 0 - - c: uint256 = D - Ann: uint256 = A_ * N_COINS - - for _i in range(N_COINS): - if _i != i: - _x = xp[_i] - else: - continue - S_ += _x - c = c * D / (_x * N_COINS) - c = c * D * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann - 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 - - -@view -@internal -def _calc_withdraw_one_coin(_token_amount: uint256, i: int128, vp_rate: uint256) -> (uint256, uint256, uint256): - # First, need to calculate - # * Get current D - # * Solve Eqn against y_i for D - _token_amount - amp: uint256 = self._A() - xp: uint256[N_COINS] = self._xp(vp_rate) - D0: uint256 = self.get_D(xp, amp) - - total_supply: uint256 = self.token.totalSupply() - D1: uint256 = D0 - _token_amount * D0 / total_supply - new_y: uint256 = self.get_y_D(amp, i, xp, D1) - - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = vp_rate - - xp_reduced: uint256[N_COINS] = xp - dy_0: uint256 = (xp[i] - new_y) * PRECISION / rates[i] # w/o fees - - for j in range(N_COINS): - dx_expected: uint256 = 0 - if j == i: - dx_expected = xp[j] * D1 / D0 - new_y - else: - dx_expected = xp[j] - xp[j] * D1 / D0 - xp_reduced[j] -= _fee * dx_expected / FEE_DENOMINATOR - - dy: uint256 = xp_reduced[i] - self.get_y_D(amp, i, xp_reduced, D1) - dy = (dy - 1) * PRECISION / rates[i] # Withdraw less to account for rounding errors - - return dy, dy_0 - dy, total_supply - - -@view -@external -def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - """ - @notice Calculate the amount received when withdrawing a single coin - @param _token_amount Amount of LP tokens to burn in the withdrawal - @param i Index value of the coin to withdraw - @return Amount of coin received - """ - vp_rate: uint256 = self._vp_rate_ro() - return self._calc_withdraw_one_coin(_token_amount, i, vp_rate)[0] - - -@external -@nonreentrant('lock') -def remove_liquidity_one_coin(_token_amount: uint256, i: int128, _min_amount: uint256) -> uint256: - """ - @notice Withdraw a single coin from the pool - @param _token_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 coin to receive - @return Amount of coin received - """ - assert not self.is_killed # dev: is killed - - vp_rate: uint256 = self._vp_rate() - dy: uint256 = 0 - dy_fee: uint256 = 0 - total_supply: uint256 = 0 - dy, dy_fee, total_supply = self._calc_withdraw_one_coin(_token_amount, i, vp_rate) - assert dy >= _min_amount, "Not enough coins removed" - - self.balances[i] -= (dy + dy_fee * self.admin_fee / FEE_DENOMINATOR) - self.token.burnFrom(msg.sender, _token_amount) # dev: insufficient funds - assert ERC20(self.coins[i]).transfer(msg.sender, dy) - - log RemoveLiquidityOne(msg.sender, _token_amount, dy, total_supply - _token_amount) - - return dy - - -### Admin functions ### -@external -def ramp_A(_future_A: uint256, _future_time: uint256): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.initial_A_time + MIN_RAMP_TIME - assert _future_time >= block.timestamp + MIN_RAMP_TIME # dev: insufficient time - - _initial_A: uint256 = self._A() - _future_A_p: uint256 = _future_A * A_PRECISION - - assert _future_A > 0 and _future_A < MAX_A - if _future_A_p < _initial_A: - assert _future_A_p * MAX_A_CHANGE >= _initial_A - else: - assert _future_A_p <= _initial_A * MAX_A_CHANGE - - self.initial_A = _initial_A - self.future_A = _future_A_p - self.initial_A_time = block.timestamp - self.future_A_time = _future_time - - log RampA(_initial_A, _future_A_p, block.timestamp, _future_time) - - -@external -def stop_ramp_A(): - assert msg.sender == self.owner # dev: only owner - - current_A: uint256 = self._A() - self.initial_A = current_A - self.future_A = current_A - self.initial_A_time = block.timestamp - self.future_A_time = block.timestamp - # now (block.timestamp < t1) is always False, so we return saved A - - log StopRampA(current_A, block.timestamp) - - -@external -def commit_new_fee(new_fee: uint256, new_admin_fee: uint256): - assert msg.sender == self.owner # dev: only owner - assert self.admin_actions_deadline == 0 # dev: active action - assert new_fee <= MAX_FEE # dev: fee exceeds maximum - assert new_admin_fee <= MAX_ADMIN_FEE # dev: admin fee exceeds maximum - - _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.admin_actions_deadline = _deadline - self.future_fee = new_fee - self.future_admin_fee = new_admin_fee - - log CommitNewFee(_deadline, new_fee, new_admin_fee) - - -@external -def apply_new_fee(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.admin_actions_deadline # dev: insufficient time - assert self.admin_actions_deadline != 0 # dev: no active action - - self.admin_actions_deadline = 0 - _fee: uint256 = self.future_fee - _admin_fee: uint256 = self.future_admin_fee - self.fee = _fee - self.admin_fee = _admin_fee - - log NewFee(_fee, _admin_fee) - - -@external -def revert_new_parameters(): - assert msg.sender == self.owner # dev: only owner - - self.admin_actions_deadline = 0 - - -@external -def commit_transfer_ownership(_owner: address): - assert msg.sender == self.owner # dev: only owner - assert self.transfer_ownership_deadline == 0 # dev: active transfer - - _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.transfer_ownership_deadline = _deadline - self.future_owner = _owner - - log CommitNewAdmin(_deadline, _owner) - - -@external -def apply_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.transfer_ownership_deadline # dev: insufficient time - assert self.transfer_ownership_deadline != 0 # dev: no active transfer - - self.transfer_ownership_deadline = 0 - _owner: address = self.future_owner - self.owner = _owner - - log NewAdmin(_owner) - - -@external -def revert_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - - self.transfer_ownership_deadline = 0 - - -@view -@external -def admin_balances(i: uint256) -> uint256: - return ERC20(self.coins[i]).balanceOf(self) - self.balances[i] - - -@external -def withdraw_admin_fees(): - assert msg.sender == self.owner # dev: only owner - - for i in range(N_COINS): - c: address = self.coins[i] - value: uint256 = ERC20(c).balanceOf(self) - self.balances[i] - if value > 0: - assert ERC20(c).transfer(msg.sender, value) - - -@external -def donate_admin_fees(): - assert msg.sender == self.owner # dev: only owner - for i in range(N_COINS): - self.balances[i] = ERC20(self.coins[i]).balanceOf(self) - - -@external -def kill_me(): - assert msg.sender == self.owner # dev: only owner - assert self.kill_deadline > block.timestamp # dev: deadline has passed - self.is_killed = True - - -@external -def unkill_me(): - assert msg.sender == self.owner # dev: only owner - self.is_killed = False diff --git a/contracts/pools/linkusd/pooldata.json b/contracts/pools/linkusd/pooldata.json deleted file mode 100644 index c913dad2..00000000 --- a/contracts/pools/linkusd/pooldata.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "base_pool": "3pool", - "pool_types": ["meta"], - "swap_address": "0xE7a24EF0C5e95Ffb0f6684b813A78F2a3AD7D171", - "lp_token_address": "0x6D65b498cb23deAba52db31c93Da9BFFb340FB8F", - "zap_address": "0x1de7f0866e2c4adAC7b457c58Cc25c8688CDa1f2", - "gauge_addresses": [], - "lp_contract": "CurveTokenV2", - "lp_constructor": { - "symbol": "LinkUSD3CRV", - "name": "Curve.fi LinkUSD/3Crv" - }, - "swap_constructor": { - "_A": 5, - "_fee": 15000000, - "_admin_fee": 0 - }, - "coins": [ - { - "name": "LINKUSD", - "decimals": 18, - "tethered": false, - "underlying_address": "0x0E2EC54fC0B509F445631Bf4b91AB8168230C752" - }, - { - "name": "3CRV", - "decimals": 18, - "base_pool_token": true, - "underlying_address": "0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490" - } - ] -} diff --git a/contracts/pools/musd/DepositMUSD.vy b/contracts/pools/musd/DepositMUSD.vy deleted file mode 100644 index f171d35f..00000000 --- a/contracts/pools/musd/DepositMUSD.vy +++ /dev/null @@ -1,376 +0,0 @@ -# @version 0.2.7 -""" -@title "Zap" Depositer for Curve MUSD pool -@author Curve.Fi -@license Copyright (c) Curve.Fi, 2020 - all rights reserved -""" - -from vyper.interfaces import ERC20 - - -interface CurveMeta: - def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256) -> 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) -> 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 base_pool() -> address: 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 - -# An asset which may have a transfer fee (USDT) -FEE_ASSET: constant(address) = 0xdAC17F958D2ee523a2206206994597C13D831ec7 - -FEE_DENOMINATOR: constant(uint256) = 10 ** 10 -FEE_IMPRECISION: constant(uint256) = 100 * 10 ** 8 # % of the fee - - -pool: public(address) -token: public(address) -base_pool: public(address) - -coins: public(address[N_COINS]) -base_coins: public(address[BASE_N_COINS]) - - -@external -def __init__(_pool: address, _token: address): - """ - @notice Contract constructor - @param _pool Metapool address - @param _token Pool LP token address - """ - self.pool = _pool - self.token = _token - _base_pool: address = CurveMeta(_pool).base_pool() - self.base_pool = _base_pool - - for i in range(N_COINS): - coin: address = CurveMeta(_pool).coins(convert(i, uint256)) - self.coins[i] = coin - # approve coins for infinite transfers - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("approve(address,uint256)"), - convert(_pool, bytes32), - convert(MAX_UINT256, bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) - - for i in range(BASE_N_COINS): - coin: address = CurveBase(_base_pool).coins(convert(i, uint256)) - self.base_coins[i] = coin - # approve underlying coins for infinite transfers - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("approve(address,uint256)"), - convert(_base_pool, bytes32), - convert(MAX_UINT256, bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) - - -@external -def add_liquidity(amounts: uint256[N_ALL_COINS], min_mint_amount: uint256) -> uint256: - """ - @notice Wrap underlying coins and deposit them in the pool - @param amounts List of amounts of underlying coins to deposit - @param min_mint_amount Minimum amount of LP tokens to mint from the deposit - @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 - - # Transfer all coins in - for i in range(N_ALL_COINS): - amount: uint256 = amounts[i] - if amount == 0: - continue - coin: address = ZERO_ADDRESS - if i < MAX_COIN: - coin = self.coins[i] - meta_amounts[i] = amount - else: - x: int128 = i - MAX_COIN - coin = self.base_coins[x] - base_amounts[x] = amount - deposit_base = True - # "safeTransferFrom" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transferFrom(address,address,uint256)"), - convert(msg.sender, bytes32), - convert(self, bytes32), - convert(amount, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransferFrom" - # Handle potential Tether fees - if coin == FEE_ASSET: - amount = ERC20(FEE_ASSET).balanceOf(self) - if i < MAX_COIN: - meta_amounts[i] = amount - else: - base_amounts[i - MAX_COIN] = amount - - # Deposit to the base pool - if deposit_base: - CurveBase(self.base_pool).add_liquidity(base_amounts, 0) - meta_amounts[MAX_COIN] = ERC20(self.coins[MAX_COIN]).balanceOf(self) - - # Deposit to the meta pool - CurveMeta(self.pool).add_liquidity(meta_amounts, min_mint_amount) - - # Transfer meta token back - _lp_token: address = self.token - _lp_amount: uint256 = ERC20(_lp_token).balanceOf(self) - assert ERC20(_lp_token).transfer(msg.sender, _lp_amount) - - return _lp_amount - - -@external -def remove_liquidity(_amount: uint256, min_amounts: uint256[N_ALL_COINS]) -> uint256[N_ALL_COINS]: - """ - @notice Withdraw and unwrap coins from the pool - @dev Withdrawal amounts are based on current deposit ratios - @param _amount Quantity of LP tokens to burn in the withdrawal - @param min_amounts Minimum amounts of underlying coins to receive - @return List of amounts of underlying coins that were withdrawn - """ - _token: address = self.token - assert ERC20(_token).transferFrom(msg.sender, self, _amount) - - min_amounts_meta: uint256[N_COINS] = empty(uint256[N_COINS]) - 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 - for i in range(MAX_COIN): - min_amounts_meta[i] = min_amounts[i] - CurveMeta(self.pool).remove_liquidity(_amount, min_amounts_meta) - - # Withdraw from base - _base_amount: uint256 = ERC20(self.coins[MAX_COIN]).balanceOf(self) - for i in range(BASE_N_COINS): - min_amounts_base[i] = min_amounts[MAX_COIN+i] - CurveBase(self.base_pool).remove_liquidity(_base_amount, min_amounts_base) - - # Transfer all coins out - for i in range(N_ALL_COINS): - coin: address = ZERO_ADDRESS - if i < MAX_COIN: - coin = self.coins[i] - else: - coin = self.base_coins[i - MAX_COIN] - amounts[i] = ERC20(coin).balanceOf(self) - # "safeTransfer" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(amounts[i], bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - return amounts - - -@external -def remove_liquidity_one_coin(_token_amount: uint256, i: int128, _min_amount: uint256) -> uint256: - """ - @notice Withdraw and unwrap a single coin from the pool - @param _token_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 - @return Amount of underlying coin received - """ - assert ERC20(self.token).transferFrom(msg.sender, self, _token_amount) - - coin: address = ZERO_ADDRESS - if i < MAX_COIN: - coin = self.coins[i] - # Withdraw a metapool coin - CurveMeta(self.pool).remove_liquidity_one_coin(_token_amount, i, _min_amount) - else: - coin = self.base_coins[i - MAX_COIN] - # Withdraw a base pool coin - CurveMeta(self.pool).remove_liquidity_one_coin(_token_amount, MAX_COIN, 0) - CurveBase(self.base_pool).remove_liquidity_one_coin( - ERC20(self.coins[MAX_COIN]).balanceOf(self), i-MAX_COIN, _min_amount - ) - - # Tranfer the coin out - coin_amount: uint256 = ERC20(coin).balanceOf(self) - # "safeTransfer" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(coin_amount, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - return coin_amount - - -@external -def remove_liquidity_imbalance(amounts: uint256[N_ALL_COINS], max_burn_amount: uint256) -> uint256: - """ - @notice Withdraw coins from the pool in an imbalanced amount - @param amounts List of amounts of underlying coins to withdraw - @param max_burn_amount Maximum amount of LP token to burn in the withdrawal. - This value cannot exceed the caller's LP token balance. - @return Actual amount of the LP token burned in the withdrawal - """ - _base_pool: address = self.base_pool - _meta_pool: address = self.pool - _base_coins: address[BASE_N_COINS] = self.base_coins - _meta_coins: address[N_COINS] = self.coins - _lp_token: address = self.token - - 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 - assert ERC20(_lp_token).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]) - leftover_amounts: uint256[N_COINS] = empty(uint256[N_COINS]) - - # Prepare quantities - for i in range(MAX_COIN): - amounts_meta[i] = amounts[i] - - for i in range(BASE_N_COINS): - amount: uint256 = amounts[MAX_COIN + i] - if amount != 0: - amounts_base[i] = amount - withdraw_base = True - - if withdraw_base: - amounts_meta[MAX_COIN] = CurveBase(self.base_pool).calc_token_amount(amounts_base, False) - amounts_meta[MAX_COIN] += amounts_meta[MAX_COIN] * fee / FEE_DENOMINATOR + 1 - - # Remove liquidity and deposit leftovers back - CurveMeta(_meta_pool).remove_liquidity_imbalance(amounts_meta, max_burn_amount) - if withdraw_base: - CurveBase(_base_pool).remove_liquidity_imbalance(amounts_base, amounts_meta[MAX_COIN]) - leftover_amounts[MAX_COIN] = ERC20(_meta_coins[MAX_COIN]).balanceOf(self) - if leftover_amounts[MAX_COIN] > 0: - CurveMeta(_meta_pool).add_liquidity(leftover_amounts, 0) - - # Transfer all coins out - for i in range(N_ALL_COINS): - coin: address = ZERO_ADDRESS - amount: uint256 = 0 - if i < MAX_COIN: - coin = _meta_coins[i] - amount = amounts_meta[i] - else: - coin = _base_coins[i - MAX_COIN] - amount = amounts_base[i - MAX_COIN] - # "safeTransfer" which works for ERC20s which return bool or not - if amount > 0: - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(amount, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - # Transfer the leftover LP token out - leftover: uint256 = ERC20(_lp_token).balanceOf(self) - if leftover > 0: - assert ERC20(_lp_token).transfer(msg.sender, leftover) - - return max_burn_amount - leftover - - -@view -@external -def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - """ - @notice Calculate the amount received when withdrawing and unwrapping a single coin - @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(self.pool).calc_withdraw_one_coin(_token_amount, i) - else: - _base_tokens: uint256 = CurveMeta(self.pool).calc_withdraw_one_coin(_token_amount, MAX_COIN) - return CurveBase(self.base_pool).calc_withdraw_one_coin(_base_tokens, i-MAX_COIN) - - -@view -@external -def calc_token_amount(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 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]) - - for i in range(MAX_COIN): - meta_amounts[i] = amounts[i] - - for i in range(BASE_N_COINS): - base_amounts[i] = amounts[i + MAX_COIN] - - _base_tokens: uint256 = CurveBase(self.base_pool).calc_token_amount(base_amounts, is_deposit) - meta_amounts[MAX_COIN] = _base_tokens - - return CurveMeta(self.pool).calc_token_amount(meta_amounts, is_deposit) diff --git a/contracts/pools/musd/README.md b/contracts/pools/musd/README.md deleted file mode 100644 index 36c823c0..00000000 --- a/contracts/pools/musd/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# curve-contract/contracts/pools/musd - -[Curve MUSD metapool](https://www.curve.fi/musd), allowing swaps via the Curve [tri-pool](../3pool). - -## Contracts - -* [`DepositMUSD`](DepositMUSD.vy): Depositor contract, used to wrap underlying tokens prior to depositing them into the pool -* [`StableSwapMUSD`](StableSwapMUSD.vy): Curve stablecoin AMM contract - -## Deployments - -* [`CurveContractV2`](../../tokens/CurveTokenV2.vy): [0x1AEf73d49Dedc4b1778d0706583995958Dc862e6](https://etherscan.io/address/0x1AEf73d49Dedc4b1778d0706583995958Dc862e6) -* [`DepositMUSD`](DepositMUSD.vy): [0x803A2B40c5a9BB2B86DD630B274Fa2A9202874C2](https://etherscan.io/address/0x803A2B40c5a9BB2B86DD630B274Fa2A9202874C2) -* [`LiquidityGaugeReward`](LiquidityGaugeReward): [0x5f626c30EC1215f4EdCc9982265E8b1F411D1352](https://etherscan.io/address/0x5f626c30EC1215f4EdCc9982265E8b1F411D1352) -* [`StableSwapMUSD`](StableSwapMUSD.vy): [0x8474DdbE98F5aA3179B3B3F5942D724aFcdec9f6](https://etherscan.io/address/0x8474DdbE98F5aA3179B3B3F5942D724aFcdec9f6) - -## Stablecoins - -Curve MUSD metapool utilizes the supports swaps between the following assets: - -## Direct swaps - -Direct swaps are possible between MUSD and the Curve tri-pool LP token. - -* `MUSD`: [0xe2f2a5C287993345a840Db3B0845fbC70f5935a5](https://etherscan.io/address/0xe2f2a5C287993345a840Db3B0845fbC70f5935a5) -* `3CRV`: [0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490](https://etherscan.io/address/0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490) - -## Base Pool coins - -The tri-pool LP token may be wrapped or unwrapped to provide swaps between MUSD and the following stablecoins: - -* `DAI`: [0x6b175474e89094c44da98b954eedeac495271d0f](https://etherscan.io/address/0x6b175474e89094c44da98b954eedeac495271d0f) -* `USDC`: [0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48](https://etherscan.io/address/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48) -* `USDT`: [0xdac17f958d2ee523a2206206994597c13d831ec7](https://etherscan.io/address/0xdac17f958d2ee523a2206206994597c13d831ec7) diff --git a/contracts/pools/musd/StableSwapMUSD.vy b/contracts/pools/musd/StableSwapMUSD.vy deleted file mode 100644 index 3c205c1f..00000000 --- a/contracts/pools/musd/StableSwapMUSD.vy +++ /dev/null @@ -1,1083 +0,0 @@ -# @version 0.2.5 -""" -@title Curve MUSD Metapool -@author Curve.Fi -@license Copyright (c) Curve.Fi, 2020 - all rights reserved -@dev Utilizes 3Pool to allow swaps between MUSD / DAI / USDC / USDT -""" - -from vyper.interfaces import ERC20 - -interface CurveToken: - def totalSupply() -> uint256: view - def mint(_to: address, _value: uint256) -> bool: nonpayable - def burnFrom(_to: address, _value: uint256) -> bool: nonpayable - - -interface Curve: - def coins(i: uint256) -> address: view - def get_virtual_price() -> uint256: view - def calc_token_amount(amounts: uint256[BASE_N_COINS], deposit: bool) -> uint256: view - def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: view - def fee() -> uint256: view - def get_dy(i: int128, j: int128, dx: uint256) -> uint256: view - def get_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256: view - def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256): nonpayable - def add_liquidity(amounts: uint256[BASE_N_COINS], min_mint_amount: uint256): nonpayable - def remove_liquidity_one_coin(_token_amount: uint256, i: int128, min_amount: uint256): nonpayable - - -# Events -event TokenExchange: - buyer: indexed(address) - sold_id: int128 - tokens_sold: uint256 - bought_id: int128 - tokens_bought: uint256 - -event TokenExchangeUnderlying: - buyer: indexed(address) - sold_id: int128 - tokens_sold: uint256 - bought_id: int128 - tokens_bought: uint256 - -event AddLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event RemoveLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - token_supply: uint256 - -event RemoveLiquidityOne: - provider: indexed(address) - token_amount: uint256 - coin_amount: uint256 - token_supply: uint256 - -event RemoveLiquidityImbalance: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event CommitNewAdmin: - deadline: indexed(uint256) - admin: indexed(address) - -event NewAdmin: - admin: indexed(address) - -event CommitNewFee: - deadline: indexed(uint256) - fee: uint256 - admin_fee: uint256 - -event NewFee: - fee: uint256 - admin_fee: uint256 - -event RampA: - old_A: uint256 - new_A: uint256 - initial_time: uint256 - future_time: uint256 - -event StopRampA: - A: uint256 - t: uint256 - - -N_COINS: constant(int128) = 2 -MAX_COIN: constant(int128) = N_COINS - 1 - -FEE_DENOMINATOR: constant(uint256) = 10 ** 10 -PRECISION: constant(uint256) = 10 ** 18 # The precision to convert to -PRECISION_MUL: constant(uint256[N_COINS]) = [1, 1] -RATES: constant(uint256[N_COINS]) = [1000000000000000000, 1000000000000000000] - -BASE_N_COINS: constant(int128) = 3 -N_ALL_COINS: constant(int128) = N_COINS + BASE_N_COINS - 1 -BASE_PRECISION_MUL: constant(uint256[BASE_N_COINS]) = [1, 1000000000000, 1000000000000] -BASE_RATES: constant(uint256[BASE_N_COINS]) = [1000000000000000000, 1000000000000000000000000000000, 1000000000000000000000000000000] - -# An asset which may have a transfer fee (USDT) -FEE_ASSET: constant(address) = 0xdAC17F958D2ee523a2206206994597C13D831ec7 - -MAX_ADMIN_FEE: constant(uint256) = 10 * 10 ** 9 -MAX_FEE: constant(uint256) = 5 * 10 ** 9 -MAX_A: constant(uint256) = 10 ** 6 -MAX_A_CHANGE: constant(uint256) = 10 - -ADMIN_ACTIONS_DELAY: constant(uint256) = 3 * 86400 -MIN_RAMP_TIME: constant(uint256) = 86400 - -coins: public(address[N_COINS]) -balances: public(uint256[N_COINS]) -fee: public(uint256) # fee * 1e10 -admin_fee: public(uint256) # admin_fee * 1e10 - -owner: public(address) -token: CurveToken - -# Token corresponding to the pool is always the last one -BASE_POOL_COINS: constant(int128) = 3 -BASE_CACHE_EXPIRES: constant(int128) = 10 * 60 # 10 min -base_pool: public(address) -base_virtual_price: public(uint256) -base_cache_updated: public(uint256) -base_coins: public(address[BASE_POOL_COINS]) - -A_PRECISION: constant(uint256) = 100 -initial_A: public(uint256) -future_A: public(uint256) -initial_A_time: public(uint256) -future_A_time: public(uint256) - -admin_actions_deadline: public(uint256) -transfer_ownership_deadline: public(uint256) -future_fee: public(uint256) -future_admin_fee: public(uint256) -future_owner: public(address) - -is_killed: bool -kill_deadline: uint256 -KILL_DEADLINE_DT: constant(uint256) = 2 * 30 * 86400 - - -@external -def __init__( - _owner: address, - _coins: address[N_COINS], - _pool_token: address, - _base_pool: address, - _A: uint256, - _fee: uint256, - _admin_fee: uint256 -): - """ - @notice Contract constructor - @param _owner Contract owner address - @param _coins Addresses of ERC20 conracts of coins - @param _pool_token Address of the token representing LP share - @param _base_pool Address of the base pool (which will have a virtual price) - @param _A Amplification coefficient multiplied by n * (n - 1) - @param _fee Fee to charge for exchanges - @param _admin_fee Admin fee - """ - for i in range(N_COINS): - assert _coins[i] != ZERO_ADDRESS - self.coins = _coins - self.initial_A = _A * A_PRECISION - self.future_A = _A * A_PRECISION - self.fee = _fee - self.admin_fee = _admin_fee - self.owner = _owner - self.kill_deadline = block.timestamp + KILL_DEADLINE_DT - self.token = CurveToken(_pool_token) - - self.base_pool = _base_pool - self.base_virtual_price = Curve(_base_pool).get_virtual_price() - self.base_cache_updated = block.timestamp - for i in range(BASE_POOL_COINS): - _base_coin: address = Curve(_base_pool).coins(convert(i, uint256)) - self.base_coins[i] = _base_coin - - # approve underlying coins for infinite transfers - _response: Bytes[32] = raw_call( - _base_coin, - concat( - method_id("approve(address,uint256)"), - convert(_base_pool, bytes32), - convert(MAX_UINT256, bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) - - -@view -@internal -def _A() -> uint256: - """ - Handle ramping A up or down - """ - t1: uint256 = self.future_A_time - A1: uint256 = self.future_A - - if block.timestamp < t1: - A0: uint256 = self.initial_A - t0: uint256 = self.initial_A_time - # Expressions in uint256 cannot have negative numbers, thus "if" - if A1 > A0: - return A0 + (A1 - A0) * (block.timestamp - t0) / (t1 - t0) - else: - return A0 - (A0 - A1) * (block.timestamp - t0) / (t1 - t0) - - else: # when t1 == 0 or block.timestamp >= t1 - return A1 - - -@view -@external -def A() -> uint256: - return self._A() / A_PRECISION - - -@view -@external -def A_precise() -> uint256: - return self._A() - - -@view -@internal -def _xp(vp_rate: uint256) -> uint256[N_COINS]: - result: uint256[N_COINS] = RATES - result[MAX_COIN] = vp_rate # virtual price for the metacurrency - for i in range(N_COINS): - result[i] = result[i] * self.balances[i] / PRECISION - return result - - -@pure -@internal -def _xp_mem(vp_rate: uint256, _balances: uint256[N_COINS]) -> uint256[N_COINS]: - result: uint256[N_COINS] = RATES - result[MAX_COIN] = vp_rate # virtual price for the metacurrency - for i in range(N_COINS): - result[i] = result[i] * _balances[i] / PRECISION - return result - - -@internal -def _vp_rate() -> uint256: - if block.timestamp > self.base_cache_updated + BASE_CACHE_EXPIRES: - vprice: uint256 = Curve(self.base_pool).get_virtual_price() - self.base_virtual_price = vprice - self.base_cache_updated = block.timestamp - return vprice - else: - return self.base_virtual_price - - -@internal -@view -def _vp_rate_ro() -> uint256: - if block.timestamp > self.base_cache_updated + BASE_CACHE_EXPIRES: - return Curve(self.base_pool).get_virtual_price() - else: - return self.base_virtual_price - - -@pure -@internal -def get_D(xp: uint256[N_COINS], amp: uint256) -> uint256: - S: uint256 = 0 - Dprev: uint256 = 0 - for _x in xp: - S += _x - if S == 0: - return 0 - - D: uint256 = S - Ann: uint256 = amp * N_COINS - for _i in range(255): - D_P: uint256 = D - for _x in xp: - D_P = D_P * D / (_x * N_COINS) # If division by 0, this will be borked: only withdrawal will work. And that is good - Dprev = D - D = (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (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 - - -@view -@internal -def get_D_mem(vp_rate: uint256, _balances: uint256[N_COINS], amp: uint256) -> uint256: - xp: uint256[N_COINS] = self._xp_mem(vp_rate, _balances) - return self.get_D(xp, amp) - - -@view -@external -def get_virtual_price() -> uint256: - """ - @notice The current virtual price of the pool LP token - @dev Useful for calculating profits - @return LP token virtual price normalized to 1e18 - """ - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate_ro() - xp: uint256[N_COINS] = self._xp(vp_rate) - D: uint256 = self.get_D(xp, amp) - # 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 - - -@view -@external -def calc_token_amount(amounts: uint256[N_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 amounts Amount of each coin being deposited - @param is_deposit set True for deposits, False for withdrawals - @return Expected amount of LP tokens received - """ - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate_ro() - _balances: uint256[N_COINS] = self.balances - D0: uint256 = self.get_D_mem(vp_rate, _balances, amp) - for i in range(N_COINS): - if is_deposit: - _balances[i] += amounts[i] - else: - _balances[i] -= amounts[i] - D1: uint256 = self.get_D_mem(vp_rate, _balances, amp) - token_amount: uint256 = self.token.totalSupply() - diff: uint256 = 0 - if is_deposit: - diff = D1 - D0 - else: - diff = D0 - D1 - return diff * token_amount / D0 - - -@external -@nonreentrant('lock') -def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256) -> uint256: - """ - @notice Deposit coins into the pool - @param amounts List of amounts of coins to deposit - @param min_mint_amount Minimum amount of LP tokens to mint from the deposit - @return Amount of LP tokens received by depositing - """ - assert not self.is_killed # dev: is killed - - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate() - token_supply: uint256 = self.token.totalSupply() - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - _admin_fee: uint256 = self.admin_fee - - # Initial invariant - D0: uint256 = 0 - old_balances: uint256[N_COINS] = self.balances - if token_supply > 0: - D0 = self.get_D_mem(vp_rate, old_balances, amp) - new_balances: uint256[N_COINS] = old_balances - - for i in range(N_COINS): - if token_supply == 0: - assert amounts[i] > 0 # dev: initial deposit requires all coins - # balances store amounts of c-tokens - new_balances[i] = old_balances[i] + amounts[i] - - # Invariant after change - D1: uint256 = self.get_D_mem(vp_rate, new_balances, amp) - assert D1 > D0 - - # We need to recalculate the invariant accounting for fees - # to calculate fair user's share - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - 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(vp_rate, new_balances, amp) - 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 amounts[i] > 0: - assert ERC20(self.coins[i]).transferFrom(msg.sender, self, amounts[i]) # dev: failed transfer - - # Mint pool tokens - self.token.mint(msg.sender, mint_amount) - - log AddLiquidity(msg.sender, amounts, fees, D1, token_supply + mint_amount) - - return mint_amount - - -@view -@internal -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 # dev: same coin - assert j >= 0 # dev: j below zero - assert j < N_COINS # dev: j above N_COINS - - # should be unreachable, but good for safety - assert i >= 0 - assert i < N_COINS - - amp: uint256 = self._A() - D: uint256 = self.get_D(xp_, amp) - - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: uint256 = 0 - c: uint256 = D - Ann: uint256 = amp * N_COINS - - 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 * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann # - D - 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 - - -@view -@external -def get_dy(i: int128, j: int128, dx: uint256) -> uint256: - # dx and dy in c-units - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = self._vp_rate_ro() - xp: uint256[N_COINS] = self._xp(rates[MAX_COIN]) - - x: uint256 = xp[i] + (dx * rates[i] / PRECISION) - y: uint256 = self.get_y(i, j, x, xp) - dy: uint256 = xp[j] - y - 1 - _fee: uint256 = self.fee * dy / FEE_DENOMINATOR - return (dy - _fee) * PRECISION / rates[j] - - -@view -@external -def get_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256: - # dx and dy in underlying units - vp_rate: uint256 = self._vp_rate_ro() - xp: uint256[N_COINS] = self._xp(vp_rate) - precisions: uint256[N_COINS] = PRECISION_MUL - _base_pool: address = self.base_pool - - # Use base_i or base_j if they are >= 0 - base_i: int128 = i - MAX_COIN - base_j: int128 = j - MAX_COIN - meta_i: int128 = MAX_COIN - meta_j: int128 = MAX_COIN - if base_i < 0: - meta_i = i - if base_j < 0: - meta_j = j - - x: uint256 = 0 - if base_i < 0: - x = xp[i] + dx * precisions[i] - else: - if base_j < 0: - # i is from BasePool - # At first, get the amount of pool tokens - base_inputs: uint256[BASE_N_COINS] = empty(uint256[BASE_N_COINS]) - base_inputs[base_i] = dx - # Token amount transformed to underlying "dollars" - x = Curve(_base_pool).calc_token_amount(base_inputs, True) * vp_rate / PRECISION - # Accounting for deposit/withdraw fees approximately - x -= x * Curve(_base_pool).fee() / (2 * FEE_DENOMINATOR) - # Adding number of pool tokens - x += xp[MAX_COIN] - else: - # If both are from the base pool - return Curve(_base_pool).get_dy(base_i, base_j, dx) - - # This pool is involved only when in-pool assets are used - y: uint256 = self.get_y(meta_i, meta_j, x, xp) - dy: uint256 = xp[meta_j] - y - 1 - dy = (dy - self.fee * dy / FEE_DENOMINATOR) - - # If output is going via the metapool - if base_j < 0: - dy /= precisions[meta_j] - else: - # j is from BasePool - # The fee is already accounted for - dy = Curve(_base_pool).calc_withdraw_one_coin(dy * PRECISION / vp_rate, base_j) - - return dy - - -@external -@nonreentrant('lock') -def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256) -> uint256: - """ - @notice Perform an exchange between two coins - @dev Index values can be found via the `coins` public getter method - @param i Index value for the coin to send - @param j Index valie of the coin to recieve - @param dx Amount of `i` being exchanged - @param min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - assert not self.is_killed # dev: is killed - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = self._vp_rate() - - old_balances: uint256[N_COINS] = self.balances - xp: uint256[N_COINS] = self._xp_mem(rates[MAX_COIN], old_balances) - - x: uint256 = xp[i] + dx * rates[i] / PRECISION - y: uint256 = self.get_y(i, j, x, xp) - - dy: uint256 = xp[j] - y - 1 # -1 just in case there were some rounding errors - dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR - - # Convert all to real units - dy = (dy - dy_fee) * PRECISION / rates[j] - assert dy >= min_dy, "Too few coins in result" - - dy_admin_fee: uint256 = dy_fee * self.admin_fee / FEE_DENOMINATOR - dy_admin_fee = dy_admin_fee * PRECISION / rates[j] - - # Change balances exactly in same way as we change actual ERC20 coin amounts - self.balances[i] = old_balances[i] + dx - # When rounding errors happen, we undercharge admin fee in favor of LP - self.balances[j] = old_balances[j] - dy - dy_admin_fee - - assert ERC20(self.coins[i]).transferFrom(msg.sender, self, dx) - assert ERC20(self.coins[j]).transfer(msg.sender, dy) - - log TokenExchange(msg.sender, i, dx, j, dy) - - return dy - - -@external -@nonreentrant('lock') -def exchange_underlying(i: int128, j: int128, dx: uint256, min_dy: uint256) -> uint256: - """ - @notice Perform an exchange between two underlying coins - @dev Index values can be found via the `underlying_coins` public getter method - @param i Index value for the underlying coin to send - @param j Index valie of the underlying coin to recieve - @param dx Amount of `i` being exchanged - @param min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - assert not self.is_killed # dev: is killed - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = self._vp_rate() - _base_pool: address = self.base_pool - - # Use base_i or base_j if they are >= 0 - base_i: int128 = i - MAX_COIN - base_j: int128 = j - MAX_COIN - meta_i: int128 = MAX_COIN - meta_j: int128 = MAX_COIN - if base_i < 0: - meta_i = i - if base_j < 0: - meta_j = j - dy: uint256 = 0 - - # Addresses for input and output coins - input_coin: address = ZERO_ADDRESS - if base_i < 0: - input_coin = self.coins[i] - else: - input_coin = self.base_coins[base_i] - output_coin: address = ZERO_ADDRESS - if base_j < 0: - output_coin = self.coins[j] - else: - output_coin = self.base_coins[base_j] - - # Handle potential Tether fees - dx_w_fee: uint256 = dx - if input_coin == FEE_ASSET: - dx_w_fee = ERC20(FEE_ASSET).balanceOf(self) - # "safeTransferFrom" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - input_coin, - concat( - method_id("transferFrom(address,address,uint256)"), - convert(msg.sender, bytes32), - convert(self, bytes32), - convert(dx, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransferFrom" - # Handle potential Tether fees - if input_coin == FEE_ASSET: - dx_w_fee = ERC20(FEE_ASSET).balanceOf(self) - dx_w_fee - - if base_i < 0 or base_j < 0: - old_balances: uint256[N_COINS] = self.balances - xp: uint256[N_COINS] = self._xp_mem(rates[MAX_COIN], old_balances) - - x: uint256 = 0 - if base_i < 0: - x = xp[i] + dx_w_fee * rates[i] / PRECISION - else: - # i is from BasePool - # At first, get the amount of pool tokens - base_inputs: uint256[BASE_N_COINS] = empty(uint256[BASE_N_COINS]) - base_inputs[base_i] = dx_w_fee - coin_i: address = self.coins[MAX_COIN] - # Deposit and measure delta - x = ERC20(coin_i).balanceOf(self) - Curve(_base_pool).add_liquidity(base_inputs, 0) - # Need to convert pool token to "virtual" units using rates - # dx is also different now - dx_w_fee = ERC20(coin_i).balanceOf(self) - x - x = dx_w_fee * rates[MAX_COIN] / PRECISION - # Adding number of pool tokens - x += xp[MAX_COIN] - - y: uint256 = self.get_y(meta_i, meta_j, x, xp) - - # Either a real coin or token - dy = xp[meta_j] - y - 1 # -1 just in case there were some rounding errors - dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR - - # Convert all to real units - # Works for both pool coins and real coins - dy = (dy - dy_fee) * PRECISION / rates[meta_j] - - dy_admin_fee: uint256 = dy_fee * self.admin_fee / FEE_DENOMINATOR - dy_admin_fee = dy_admin_fee * PRECISION / rates[meta_j] - - # Change balances exactly in same way as we change actual ERC20 coin amounts - self.balances[meta_i] = old_balances[meta_i] + dx_w_fee - # When rounding errors happen, we undercharge admin fee in favor of LP - self.balances[meta_j] = old_balances[meta_j] - dy - dy_admin_fee - - # Withdraw from the base pool if needed - if base_j >= 0: - out_amount: uint256 = ERC20(output_coin).balanceOf(self) - Curve(_base_pool).remove_liquidity_one_coin(dy, base_j, 0) - dy = ERC20(output_coin).balanceOf(self) - out_amount - - assert dy >= min_dy, "Too few coins in result" - - else: - # If both are from the base pool - dy = ERC20(output_coin).balanceOf(self) - Curve(_base_pool).exchange(base_i, base_j, dx_w_fee, min_dy) - dy = ERC20(output_coin).balanceOf(self) - dy - - # "safeTransfer" which works for ERC20s which return bool or not - _response = raw_call( - output_coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(dy, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - log TokenExchangeUnderlying(msg.sender, i, dx, j, dy) - - return dy - - -@external -@nonreentrant('lock') -def remove_liquidity(_amount: uint256, min_amounts: uint256[N_COINS]) -> uint256[N_COINS]: - """ - @notice Withdraw coins from the pool - @dev Withdrawal amounts are based on current deposit ratios - @param _amount Quantity of LP tokens to burn in the withdrawal - @param min_amounts Minimum amounts of underlying coins to receive - @return List of amounts of coins that were withdrawn - """ - total_supply: uint256 = self.token.totalSupply() - amounts: uint256[N_COINS] = empty(uint256[N_COINS]) - fees: uint256[N_COINS] = empty(uint256[N_COINS]) # Fees are unused but we've got them historically in event - - for i in range(N_COINS): - value: uint256 = self.balances[i] * _amount / total_supply - assert value >= min_amounts[i], "Too few coins in result" - self.balances[i] -= value - amounts[i] = value - assert ERC20(self.coins[i]).transfer(msg.sender, value) - - self.token.burnFrom(msg.sender, _amount) # dev: insufficient funds - - log RemoveLiquidity(msg.sender, amounts, fees, total_supply - _amount) - - return amounts - - -@external -@nonreentrant('lock') -def remove_liquidity_imbalance(amounts: uint256[N_COINS], max_burn_amount: uint256) -> uint256: - """ - @notice Withdraw coins from the pool in an imbalanced amount - @param amounts List of amounts of underlying coins to withdraw - @param max_burn_amount Maximum amount of LP token to burn in the withdrawal - @return Actual amount of the LP token burned in the withdrawal - """ - assert not self.is_killed # dev: is killed - - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate() - - token_supply: uint256 = self.token.totalSupply() - assert token_supply != 0 # dev: zero total supply - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - _admin_fee: uint256 = self.admin_fee - - old_balances: uint256[N_COINS] = self.balances - new_balances: uint256[N_COINS] = old_balances - D0: uint256 = self.get_D_mem(vp_rate, old_balances, amp) - for i in range(N_COINS): - new_balances[i] -= amounts[i] - D1: uint256 = self.get_D_mem(vp_rate, new_balances, amp) - - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - 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(vp_rate, new_balances, amp) - - token_amount: uint256 = (D0 - D2) * token_supply / D0 - assert token_amount != 0 # dev: zero tokens burned - token_amount += 1 # In case of rounding errors - make it unfavorable for the "attacker" - assert token_amount <= max_burn_amount, "Slippage screwed you" - - self.token.burnFrom(msg.sender, token_amount) # dev: insufficient funds - for i in range(N_COINS): - if amounts[i] != 0: - assert ERC20(self.coins[i]).transfer(msg.sender, amounts[i]) - - log RemoveLiquidityImbalance(msg.sender, amounts, fees, D1, token_supply - token_amount) - - return token_amount - - -@view -@internal -def get_y_D(A_: uint256, i: int128, xp: uint256[N_COINS], D: uint256) -> uint256: - """ - Calculate x[i] if one reduces D from being calculated for xp to D - - Done by solving quadratic equation iteratively. - x_1**2 + x1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) - x_1**2 + b*x_1 = c - - x_1 = (x_1**2 + c) / (2*x_1 + b) - """ - # x in the input is converted to the same price/precision - - assert i >= 0 # dev: i below zero - assert i < N_COINS # dev: i above N_COINS - - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: uint256 = 0 - - c: uint256 = D - Ann: uint256 = A_ * N_COINS - - for _i in range(N_COINS): - if _i != i: - _x = xp[_i] - else: - continue - S_ += _x - c = c * D / (_x * N_COINS) - c = c * D * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann - 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 - - -@view -@internal -def _calc_withdraw_one_coin(_token_amount: uint256, i: int128, vp_rate: uint256) -> (uint256, uint256, uint256): - # First, need to calculate - # * Get current D - # * Solve Eqn against y_i for D - _token_amount - amp: uint256 = self._A() - xp: uint256[N_COINS] = self._xp(vp_rate) - D0: uint256 = self.get_D(xp, amp) - - total_supply: uint256 = self.token.totalSupply() - D1: uint256 = D0 - _token_amount * D0 / total_supply - new_y: uint256 = self.get_y_D(amp, i, xp, D1) - - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = vp_rate - - xp_reduced: uint256[N_COINS] = xp - dy_0: uint256 = (xp[i] - new_y) * PRECISION / rates[i] # w/o fees - - for j in range(N_COINS): - dx_expected: uint256 = 0 - if j == i: - dx_expected = xp[j] * D1 / D0 - new_y - else: - dx_expected = xp[j] - xp[j] * D1 / D0 - xp_reduced[j] -= _fee * dx_expected / FEE_DENOMINATOR - - dy: uint256 = xp_reduced[i] - self.get_y_D(amp, i, xp_reduced, D1) - dy = (dy - 1) * PRECISION / rates[i] # Withdraw less to account for rounding errors - - return dy, dy_0 - dy, total_supply - - -@view -@external -def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - """ - @notice Calculate the amount received when withdrawing a single coin - @param _token_amount Amount of LP tokens to burn in the withdrawal - @param i Index value of the coin to withdraw - @return Amount of coin received - """ - vp_rate: uint256 = self._vp_rate_ro() - return self._calc_withdraw_one_coin(_token_amount, i, vp_rate)[0] - - -@external -@nonreentrant('lock') -def remove_liquidity_one_coin(_token_amount: uint256, i: int128, _min_amount: uint256) -> uint256: - """ - @notice Withdraw a single coin from the pool - @param _token_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 coin to receive - @return Amount of coin received - """ - assert not self.is_killed # dev: is killed - - vp_rate: uint256 = self._vp_rate() - dy: uint256 = 0 - dy_fee: uint256 = 0 - total_supply: uint256 = 0 - dy, dy_fee, total_supply = self._calc_withdraw_one_coin(_token_amount, i, vp_rate) - assert dy >= _min_amount, "Not enough coins removed" - - self.balances[i] -= (dy + dy_fee * self.admin_fee / FEE_DENOMINATOR) - self.token.burnFrom(msg.sender, _token_amount) # dev: insufficient funds - assert ERC20(self.coins[i]).transfer(msg.sender, dy) - - log RemoveLiquidityOne(msg.sender, _token_amount, dy, total_supply - _token_amount) - - return dy - - -### Admin functions ### -@external -def ramp_A(_future_A: uint256, _future_time: uint256): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.initial_A_time + MIN_RAMP_TIME - assert _future_time >= block.timestamp + MIN_RAMP_TIME # dev: insufficient time - - _initial_A: uint256 = self._A() - _future_A_p: uint256 = _future_A * A_PRECISION - - assert _future_A > 0 and _future_A < MAX_A - if _future_A_p < _initial_A: - assert _future_A_p * MAX_A_CHANGE >= _initial_A - else: - assert _future_A_p <= _initial_A * MAX_A_CHANGE - - self.initial_A = _initial_A - self.future_A = _future_A_p - self.initial_A_time = block.timestamp - self.future_A_time = _future_time - - log RampA(_initial_A, _future_A_p, block.timestamp, _future_time) - - -@external -def stop_ramp_A(): - assert msg.sender == self.owner # dev: only owner - - current_A: uint256 = self._A() - self.initial_A = current_A - self.future_A = current_A - self.initial_A_time = block.timestamp - self.future_A_time = block.timestamp - # now (block.timestamp < t1) is always False, so we return saved A - - log StopRampA(current_A, block.timestamp) - - -@external -def commit_new_fee(new_fee: uint256, new_admin_fee: uint256): - assert msg.sender == self.owner # dev: only owner - assert self.admin_actions_deadline == 0 # dev: active action - assert new_fee <= MAX_FEE # dev: fee exceeds maximum - assert new_admin_fee <= MAX_ADMIN_FEE # dev: admin fee exceeds maximum - - _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.admin_actions_deadline = _deadline - self.future_fee = new_fee - self.future_admin_fee = new_admin_fee - - log CommitNewFee(_deadline, new_fee, new_admin_fee) - - -@external -def apply_new_fee(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.admin_actions_deadline # dev: insufficient time - assert self.admin_actions_deadline != 0 # dev: no active action - - self.admin_actions_deadline = 0 - _fee: uint256 = self.future_fee - _admin_fee: uint256 = self.future_admin_fee - self.fee = _fee - self.admin_fee = _admin_fee - - log NewFee(_fee, _admin_fee) - - -@external -def revert_new_parameters(): - assert msg.sender == self.owner # dev: only owner - - self.admin_actions_deadline = 0 - - -@external -def commit_transfer_ownership(_owner: address): - assert msg.sender == self.owner # dev: only owner - assert self.transfer_ownership_deadline == 0 # dev: active transfer - - _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.transfer_ownership_deadline = _deadline - self.future_owner = _owner - - log CommitNewAdmin(_deadline, _owner) - - -@external -def apply_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.transfer_ownership_deadline # dev: insufficient time - assert self.transfer_ownership_deadline != 0 # dev: no active transfer - - self.transfer_ownership_deadline = 0 - _owner: address = self.future_owner - self.owner = _owner - - log NewAdmin(_owner) - - -@external -def revert_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - - self.transfer_ownership_deadline = 0 - - -@view -@external -def admin_balances(i: uint256) -> uint256: - return ERC20(self.coins[i]).balanceOf(self) - self.balances[i] - - -@external -def withdraw_admin_fees(): - assert msg.sender == self.owner # dev: only owner - - for i in range(N_COINS): - c: address = self.coins[i] - value: uint256 = ERC20(c).balanceOf(self) - self.balances[i] - if value > 0: - assert ERC20(c).transfer(msg.sender, value) - - -@external -def donate_admin_fees(): - assert msg.sender == self.owner # dev: only owner - for i in range(N_COINS): - self.balances[i] = ERC20(self.coins[i]).balanceOf(self) - - -@external -def kill_me(): - assert msg.sender == self.owner # dev: only owner - assert self.kill_deadline > block.timestamp # dev: deadline has passed - self.is_killed = True - - -@external -def unkill_me(): - assert msg.sender == self.owner # dev: only owner - self.is_killed = False diff --git a/contracts/pools/musd/pooldata.json b/contracts/pools/musd/pooldata.json deleted file mode 100644 index 7c898219..00000000 --- a/contracts/pools/musd/pooldata.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "base_pool": "3pool", - "pool_types": ["meta"], - "lp_contract": "CurveTokenV2", - "swap_address": "0x8474DdbE98F5aA3179B3B3F5942D724aFcdec9f6", - "lp_token_address": "0x1AEf73d49Dedc4b1778d0706583995958Dc862e6", - "zap_address": "0x803A2B40c5a9BB2B86DD630B274Fa2A9202874C2", - "gauge_addresses": ["0x5f626c30EC1215f4EdCc9982265E8b1F411D1352"], - "lp_constructor": { - "symbol": "musd3CRV", - "name": "Curve.fi MUSD/3Crv" - }, - "swap_constructor": { - "_A": 200, - "_fee": 4000000, - "_admin_fee": 0 - }, - "coins": [ - { - "name": "MUSD", - "decimals": 18, - "tethered": false, - "underlying_address": "0xe2f2a5C287993345a840Db3B0845fbC70f5935a5" - }, - { - "name": "3CRV", - "decimals": 18, - "base_pool_token": true, - "underlying_address": "0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490" - } - ] -} diff --git a/contracts/pools/obtc/DepositOBTC.vy b/contracts/pools/obtc/DepositOBTC.vy deleted file mode 100644 index aad1db30..00000000 --- a/contracts/pools/obtc/DepositOBTC.vy +++ /dev/null @@ -1,366 +0,0 @@ -# @version 0.2.8 -""" -@title "Zap" Depositer for Curve oBTC pool -@author Curve.Fi -@license Copyright (c) Curve.Fi, 2020 - all rights reserved -""" - -from vyper.interfaces import ERC20 - - -interface CurveMeta: - def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256) -> 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) -> 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 base_pool() -> address: 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: int128) -> 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 - - -pool: public(address) -token: public(address) -base_pool: public(address) - -coins: public(address[N_COINS]) -base_coins: public(address[BASE_N_COINS]) - - -@external -def __init__(_pool: address, _token: address): - """ - @notice Contract constructor - @param _pool Metapool address - @param _token Pool LP token address - """ - self.pool = _pool - self.token = _token - _base_pool: address = CurveMeta(_pool).base_pool() - self.base_pool = _base_pool - - for i in range(N_COINS): - coin: address = CurveMeta(_pool).coins(i) - self.coins[i] = coin - # approve coins for infinite transfers - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("approve(address,uint256)"), - convert(_pool, bytes32), - convert(MAX_UINT256, bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) - - for i in range(BASE_N_COINS): - coin: address = CurveBase(_base_pool).coins(i) - self.base_coins[i] = coin - # approve underlying coins for infinite transfers - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("approve(address,uint256)"), - convert(_base_pool, bytes32), - convert(MAX_UINT256, bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) - - -@external -def add_liquidity(amounts: uint256[N_ALL_COINS], min_mint_amount: uint256) -> uint256: - """ - @notice Wrap underlying coins and deposit them in the pool - @param amounts List of amounts of underlying coins to deposit - @param min_mint_amount Minimum amount of LP tokens to mint from the deposit - @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 - - # Transfer all coins in - for i in range(N_ALL_COINS): - amount: uint256 = amounts[i] - if amount == 0: - continue - coin: address = ZERO_ADDRESS - if i < MAX_COIN: - coin = self.coins[i] - meta_amounts[i] = amount - else: - x: int128 = i - MAX_COIN - coin = self.base_coins[x] - base_amounts[x] = amount - deposit_base = True - # "safeTransferFrom" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transferFrom(address,address,uint256)"), - convert(msg.sender, bytes32), - convert(self, bytes32), - convert(amount, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransferFrom" - - # Deposit to the base pool - if deposit_base: - CurveBase(self.base_pool).add_liquidity(base_amounts, 0) - meta_amounts[MAX_COIN] = ERC20(self.coins[MAX_COIN]).balanceOf(self) - - # Deposit to the meta pool - CurveMeta(self.pool).add_liquidity(meta_amounts, min_mint_amount) - - # Transfer meta token back - _lp_token: address = self.token - _lp_amount: uint256 = ERC20(_lp_token).balanceOf(self) - assert ERC20(_lp_token).transfer(msg.sender, _lp_amount) - - return _lp_amount - - -@external -def remove_liquidity(_amount: uint256, min_amounts: uint256[N_ALL_COINS]) -> uint256[N_ALL_COINS]: - """ - @notice Withdraw and unwrap coins from the pool - @dev Withdrawal amounts are based on current deposit ratios - @param _amount Quantity of LP tokens to burn in the withdrawal - @param min_amounts Minimum amounts of underlying coins to receive - @return List of amounts of underlying coins that were withdrawn - """ - _token: address = self.token - assert ERC20(_token).transferFrom(msg.sender, self, _amount) - - min_amounts_meta: uint256[N_COINS] = empty(uint256[N_COINS]) - 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 - for i in range(MAX_COIN): - min_amounts_meta[i] = min_amounts[i] - CurveMeta(self.pool).remove_liquidity(_amount, min_amounts_meta) - - # Withdraw from base - _base_amount: uint256 = ERC20(self.coins[MAX_COIN]).balanceOf(self) - for i in range(BASE_N_COINS): - min_amounts_base[i] = min_amounts[MAX_COIN+i] - CurveBase(self.base_pool).remove_liquidity(_base_amount, min_amounts_base) - - # Transfer all coins out - for i in range(N_ALL_COINS): - coin: address = ZERO_ADDRESS - if i < MAX_COIN: - coin = self.coins[i] - else: - coin = self.base_coins[i - MAX_COIN] - amounts[i] = ERC20(coin).balanceOf(self) - # "safeTransfer" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(amounts[i], bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - return amounts - - -@external -def remove_liquidity_one_coin(_token_amount: uint256, i: int128, _min_amount: uint256) -> uint256: - """ - @notice Withdraw and unwrap a single coin from the pool - @param _token_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 - @return Amount of underlying coin received - """ - assert ERC20(self.token).transferFrom(msg.sender, self, _token_amount) - - coin: address = ZERO_ADDRESS - if i < MAX_COIN: - coin = self.coins[i] - # Withdraw a metapool coin - CurveMeta(self.pool).remove_liquidity_one_coin(_token_amount, i, _min_amount) - else: - coin = self.base_coins[i - MAX_COIN] - # Withdraw a base pool coin - CurveMeta(self.pool).remove_liquidity_one_coin(_token_amount, MAX_COIN, 0) - CurveBase(self.base_pool).remove_liquidity_one_coin( - ERC20(self.coins[MAX_COIN]).balanceOf(self), i-MAX_COIN, _min_amount - ) - - # Tranfer the coin out - coin_amount: uint256 = ERC20(coin).balanceOf(self) - # "safeTransfer" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(coin_amount, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - return coin_amount - - -@external -def remove_liquidity_imbalance(amounts: uint256[N_ALL_COINS], max_burn_amount: uint256) -> uint256: - """ - @notice Withdraw coins from the pool in an imbalanced amount - @param amounts List of amounts of underlying coins to withdraw - @param max_burn_amount Maximum amount of LP token to burn in the withdrawal. - This value cannot exceed the caller's LP token balance. - @return Actual amount of the LP token burned in the withdrawal - """ - _base_pool: address = self.base_pool - _meta_pool: address = self.pool - _base_coins: address[BASE_N_COINS] = self.base_coins - _meta_coins: address[N_COINS] = self.coins - _lp_token: address = self.token - - 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 - assert ERC20(_lp_token).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]) - leftover_amounts: uint256[N_COINS] = empty(uint256[N_COINS]) - - # Prepare quantities - for i in range(MAX_COIN): - amounts_meta[i] = amounts[i] - - for i in range(BASE_N_COINS): - amount: uint256 = amounts[MAX_COIN + i] - if amount != 0: - amounts_base[i] = amount - withdraw_base = True - - if withdraw_base: - amounts_meta[MAX_COIN] = CurveBase(self.base_pool).calc_token_amount(amounts_base, False) - amounts_meta[MAX_COIN] += amounts_meta[MAX_COIN] * fee / FEE_DENOMINATOR + 1 - - # Remove liquidity and deposit leftovers back - CurveMeta(_meta_pool).remove_liquidity_imbalance(amounts_meta, max_burn_amount) - if withdraw_base: - CurveBase(_base_pool).remove_liquidity_imbalance(amounts_base, amounts_meta[MAX_COIN]) - leftover_amounts[MAX_COIN] = ERC20(_meta_coins[MAX_COIN]).balanceOf(self) - if leftover_amounts[MAX_COIN] > 0: - CurveMeta(_meta_pool).add_liquidity(leftover_amounts, 0) - - # Transfer all coins out - for i in range(N_ALL_COINS): - coin: address = ZERO_ADDRESS - amount: uint256 = 0 - if i < MAX_COIN: - coin = _meta_coins[i] - amount = amounts_meta[i] - else: - coin = _base_coins[i - MAX_COIN] - amount = amounts_base[i - MAX_COIN] - # "safeTransfer" which works for ERC20s which return bool or not - if amount > 0: - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(amount, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - # Transfer the leftover LP token out - leftover: uint256 = ERC20(_lp_token).balanceOf(self) - if leftover > 0: - assert ERC20(_lp_token).transfer(msg.sender, leftover) - - return max_burn_amount - leftover - - -@view -@external -def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - """ - @notice Calculate the amount received when withdrawing and unwrapping a single coin - @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(self.pool).calc_withdraw_one_coin(_token_amount, i) - else: - _base_tokens: uint256 = CurveMeta(self.pool).calc_withdraw_one_coin(_token_amount, MAX_COIN) - return CurveBase(self.base_pool).calc_withdraw_one_coin(_base_tokens, i-MAX_COIN) - - -@view -@external -def calc_token_amount(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 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]) - - for i in range(MAX_COIN): - meta_amounts[i] = amounts[i] - - for i in range(BASE_N_COINS): - base_amounts[i] = amounts[i + MAX_COIN] - - _base_tokens: uint256 = CurveBase(self.base_pool).calc_token_amount(base_amounts, is_deposit) - meta_amounts[MAX_COIN] = _base_tokens - - return CurveMeta(self.pool).calc_token_amount(meta_amounts, is_deposit) diff --git a/contracts/pools/obtc/README.md b/contracts/pools/obtc/README.md deleted file mode 100644 index a72b88c6..00000000 --- a/contracts/pools/obtc/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# curve-contract/contracts/pools/obtc - -[Curve oBTC metapool](https://www.curve.fi/obtc), allowing swaps via the Curve [sBTC pool](../sbtc). - -## Contracts - -* [`DepositOBTC`](DepositOBTC.vy): Depositor contract, used to wrap underlying tokens prior to depositing them into the pool -* [`StableSwapOBTC`](StableSwapOBTC.vy): Curve stablecoin AMM contract - -## Deployments - -[`CurveContractV3`](../../tokens/CurveTokenV3.vy): [0x2fE94ea3d5d4a175184081439753DE15AeF9d614](https://etherscan.io/address/0x2fE94ea3d5d4a175184081439753DE15AeF9d614) -* [`DepositOBTC`](DepositOBTC.vy): [0xd5BCf53e2C81e1991570f33Fa881c49EEa570C8D](https://etherscan.io/address/0xd5BCf53e2C81e1991570f33Fa881c49EEa570C8D) -* [`LiquidityGaugeV2`](../../gauges/LiquidityGaugeV2.vy): [0x11137B10C210b579405c21A07489e28F3c040AB1](https://etherscan.io/address/0x11137B10C210b579405c21A07489e28F3c040AB1) -* [`StableSwapOBTC`](StableSwapOBTC.vy): [0xd81dA8D904b52208541Bade1bD6595D8a251F8dd](https://etherscan.io/address/0xd81dA8D904b52208541Bade1bD6595D8a251F8dd) - -## Stablecoins - -Curve oBTC metapool utilizes the supports swaps between the following assets: - -## Direct swaps - -Direct swaps are possible between oBTC and the Curve sBTC LP token. - -* `oBTC`: [0x8064d9Ae6cDf087b1bcd5BDf3531bD5d8C537a68](https://etherscan.io/address/0x8064d9Ae6cDf087b1bcd5BDf3531bD5d8C537a68) -* `sbtcCRV`: [0x075b1bb99792c9E1041bA13afEf80C91a1e70fB3](https://etherscan.io/address/0x075b1bb99792c9E1041bA13afEf80C91a1e70fB3) - -## Base Pool coins - -The sBTC LP token may be wrapped or unwrapped to provide swaps between oBTC and the following coins: - -* `renBTC`: [0xeb4c2781e4eba804ce9a9803c67d0893436bb27d](https://etherscan.io/address/0xeb4c2781e4eba804ce9a9803c67d0893436bb27d) -* `wBTC`: [0x2260fac5e5542a773aa44fbcfedf7c193bc2c599](https://etherscan.io/address/0x2260fac5e5542a773aa44fbcfedf7c193bc2c599) -* `sBTC`: [0xfe18be6b3bd88a2d2a7f928d00292e7a9963cfc6](https://etherscan.io/address/0xfe18be6b3bd88a2d2a7f928d00292e7a9963cfc6) diff --git a/contracts/pools/obtc/StableSwapOBTC.vy b/contracts/pools/obtc/StableSwapOBTC.vy deleted file mode 100644 index 09df2329..00000000 --- a/contracts/pools/obtc/StableSwapOBTC.vy +++ /dev/null @@ -1,1076 +0,0 @@ -# @version 0.2.8 -""" -@title Curve oBTC Metapool -@author Curve.Fi -@license Copyright (c) Curve.Fi, 2020 - all rights reserved -@dev Utilizes sBTC pool to allow swaps between oBTC / rebBTC / wBTC / sBTC -""" - -from vyper.interfaces import ERC20 - -interface CurveToken: - def totalSupply() -> uint256: view - def mint(_to: address, _value: uint256) -> bool: nonpayable - def burnFrom(_to: address, _value: uint256) -> bool: nonpayable - -interface Curve: - def coins(i: int128) -> address: view - def get_virtual_price() -> uint256: view - def calc_token_amount(amounts: uint256[BASE_N_COINS], deposit: bool) -> uint256: view - def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: view - def fee() -> uint256: view - def get_dy(i: int128, j: int128, dx: uint256) -> uint256: view - def get_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256: view - def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256): nonpayable - def add_liquidity(amounts: uint256[BASE_N_COINS], min_mint_amount: uint256): nonpayable - def remove_liquidity_one_coin(_token_amount: uint256, i: int128, min_amount: uint256): nonpayable - - -# Events -event TokenExchange: - buyer: indexed(address) - sold_id: int128 - tokens_sold: uint256 - bought_id: int128 - tokens_bought: uint256 - -event TokenExchangeUnderlying: - buyer: indexed(address) - sold_id: int128 - tokens_sold: uint256 - bought_id: int128 - tokens_bought: uint256 - -event AddLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event RemoveLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - token_supply: uint256 - -event RemoveLiquidityOne: - provider: indexed(address) - token_amount: uint256 - coin_amount: uint256 - token_supply: uint256 - -event RemoveLiquidityImbalance: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event CommitNewAdmin: - deadline: indexed(uint256) - admin: indexed(address) - -event NewAdmin: - admin: indexed(address) - -event CommitNewFee: - deadline: indexed(uint256) - fee: uint256 - admin_fee: uint256 - -event NewFee: - fee: uint256 - admin_fee: uint256 - -event RampA: - old_A: uint256 - new_A: uint256 - initial_time: uint256 - future_time: uint256 - -event StopRampA: - A: uint256 - t: uint256 - - -N_COINS: constant(int128) = 2 -MAX_COIN: constant(int128) = N_COINS - 1 - -FEE_DENOMINATOR: constant(uint256) = 10 ** 10 -PRECISION: constant(uint256) = 10 ** 18 # The precision to convert to -PRECISION_MUL: constant(uint256[N_COINS]) = [1, 1] -RATES: constant(uint256[N_COINS]) = [1000000000000000000, 1000000000000000000] -BASE_N_COINS: constant(int128) = 3 - -# An asset which may have a transfer fee (renBTC) -FEE_ASSET: constant(address) = 0xEB4C2781e4ebA804CE9a9803C67d0893436bB27D - -MAX_ADMIN_FEE: constant(uint256) = 10 * 10 ** 9 -MAX_FEE: constant(uint256) = 5 * 10 ** 9 -MAX_A: constant(uint256) = 10 ** 6 -MAX_A_CHANGE: constant(uint256) = 10 - -ADMIN_ACTIONS_DELAY: constant(uint256) = 3 * 86400 -MIN_RAMP_TIME: constant(uint256) = 86400 - -coins: public(address[N_COINS]) -balances: public(uint256[N_COINS]) -fee: public(uint256) # fee * 1e10 -admin_fee: public(uint256) # admin_fee * 1e10 - -owner: public(address) -token: public(CurveToken) - -BASE_CACHE_EXPIRES: constant(int128) = 10 * 60 # 10 min -base_pool: public(address) -base_virtual_price: public(uint256) -base_cache_updated: public(uint256) -base_coins: public(address[BASE_N_COINS]) - -A_PRECISION: constant(uint256) = 100 -initial_A: public(uint256) -future_A: public(uint256) -initial_A_time: public(uint256) -future_A_time: public(uint256) - -admin_actions_deadline: public(uint256) -transfer_ownership_deadline: public(uint256) -future_fee: public(uint256) -future_admin_fee: public(uint256) -future_owner: public(address) - -is_killed: bool -kill_deadline: uint256 -KILL_DEADLINE_DT: constant(uint256) = 2 * 30 * 86400 - - -@external -def __init__( - _owner: address, - _coins: address[N_COINS], - _pool_token: address, - _base_pool: address, - _A: uint256, - _fee: uint256, - _admin_fee: uint256 -): - """ - @notice Contract constructor - @param _owner Contract owner address - @param _coins Addresses of ERC20 conracts of coins - @param _pool_token Address of the token representing LP share - @param _base_pool Address of the base pool (which will have a virtual price) - @param _A Amplification coefficient multiplied by n * (n - 1) - @param _fee Fee to charge for exchanges - @param _admin_fee Admin fee - """ - for i in range(N_COINS): - assert _coins[i] != ZERO_ADDRESS - self.coins = _coins - self.initial_A = _A * A_PRECISION - self.future_A = _A * A_PRECISION - self.fee = _fee - self.admin_fee = _admin_fee - self.owner = _owner - self.kill_deadline = block.timestamp + KILL_DEADLINE_DT - self.token = CurveToken(_pool_token) - - self.base_pool = _base_pool - self.base_virtual_price = Curve(_base_pool).get_virtual_price() - self.base_cache_updated = block.timestamp - for i in range(BASE_N_COINS): - _base_coin: address = Curve(_base_pool).coins(i) - self.base_coins[i] = _base_coin - - # approve underlying coins for infinite transfers - _response: Bytes[32] = raw_call( - _base_coin, - concat( - method_id("approve(address,uint256)"), - convert(_base_pool, bytes32), - convert(MAX_UINT256, bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) - - -@view -@internal -def _A() -> uint256: - """ - Handle ramping A up or down - """ - t1: uint256 = self.future_A_time - A1: uint256 = self.future_A - - if block.timestamp < t1: - A0: uint256 = self.initial_A - t0: uint256 = self.initial_A_time - # Expressions in uint256 cannot have negative numbers, thus "if" - if A1 > A0: - return A0 + (A1 - A0) * (block.timestamp - t0) / (t1 - t0) - else: - return A0 - (A0 - A1) * (block.timestamp - t0) / (t1 - t0) - - else: # when t1 == 0 or block.timestamp >= t1 - return A1 - - -@view -@external -def A() -> uint256: - return self._A() / A_PRECISION - - -@view -@external -def A_precise() -> uint256: - return self._A() - - -@view -@internal -def _xp(vp_rate: uint256) -> uint256[N_COINS]: - result: uint256[N_COINS] = RATES - result[MAX_COIN] = vp_rate # virtual price for the metacurrency - for i in range(N_COINS): - result[i] = result[i] * self.balances[i] / PRECISION - return result - - -@pure -@internal -def _xp_mem(vp_rate: uint256, _balances: uint256[N_COINS]) -> uint256[N_COINS]: - result: uint256[N_COINS] = RATES - result[MAX_COIN] = vp_rate # virtual price for the metacurrency - for i in range(N_COINS): - result[i] = result[i] * _balances[i] / PRECISION - return result - - -@internal -def _vp_rate() -> uint256: - if block.timestamp > self.base_cache_updated + BASE_CACHE_EXPIRES: - vprice: uint256 = Curve(self.base_pool).get_virtual_price() - self.base_virtual_price = vprice - self.base_cache_updated = block.timestamp - return vprice - else: - return self.base_virtual_price - - -@internal -@view -def _vp_rate_ro() -> uint256: - if block.timestamp > self.base_cache_updated + BASE_CACHE_EXPIRES: - return Curve(self.base_pool).get_virtual_price() - else: - return self.base_virtual_price - - -@pure -@internal -def get_D(xp: uint256[N_COINS], amp: uint256) -> uint256: - S: uint256 = 0 - Dprev: uint256 = 0 - for _x in xp: - S += _x - if S == 0: - return 0 - - D: uint256 = S - Ann: uint256 = amp * N_COINS - for _i in range(255): - D_P: uint256 = D - for _x in xp: - D_P = D_P * D / (_x * N_COINS) # If division by 0, this will be borked: only withdrawal will work. And that is good - Dprev = D - D = (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (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 - - -@view -@internal -def get_D_mem(vp_rate: uint256, _balances: uint256[N_COINS], amp: uint256) -> uint256: - xp: uint256[N_COINS] = self._xp_mem(vp_rate, _balances) - return self.get_D(xp, amp) - - -@view -@external -def get_virtual_price() -> uint256: - """ - @notice The current virtual price of the pool LP token - @dev Useful for calculating profits - @return LP token virtual price normalized to 1e18 - """ - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate_ro() - xp: uint256[N_COINS] = self._xp(vp_rate) - D: uint256 = self.get_D(xp, amp) - # 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 - - -@view -@external -def calc_token_amount(amounts: uint256[N_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 amounts Amount of each coin being deposited - @param is_deposit set True for deposits, False for withdrawals - @return Expected amount of LP tokens received - """ - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate_ro() - _balances: uint256[N_COINS] = self.balances - D0: uint256 = self.get_D_mem(vp_rate, _balances, amp) - for i in range(N_COINS): - if is_deposit: - _balances[i] += amounts[i] - else: - _balances[i] -= amounts[i] - D1: uint256 = self.get_D_mem(vp_rate, _balances, amp) - token_amount: uint256 = self.token.totalSupply() - diff: uint256 = 0 - if is_deposit: - diff = D1 - D0 - else: - diff = D0 - D1 - return diff * token_amount / D0 - - -@external -@nonreentrant('lock') -def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256) -> uint256: - """ - @notice Deposit coins into the pool - @param amounts List of amounts of coins to deposit - @param min_mint_amount Minimum amount of LP tokens to mint from the deposit - @return Amount of LP tokens received by depositing - """ - assert not self.is_killed # dev: is killed - - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate() - token_supply: uint256 = self.token.totalSupply() - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - _admin_fee: uint256 = self.admin_fee - - # Initial invariant - D0: uint256 = 0 - old_balances: uint256[N_COINS] = self.balances - if token_supply > 0: - D0 = self.get_D_mem(vp_rate, old_balances, amp) - new_balances: uint256[N_COINS] = old_balances - - for i in range(N_COINS): - if token_supply == 0: - assert amounts[i] > 0 # dev: initial deposit requires all coins - # balances store amounts of c-tokens - new_balances[i] = old_balances[i] + amounts[i] - - # Invariant after change - D1: uint256 = self.get_D_mem(vp_rate, new_balances, amp) - assert D1 > D0 - - # We need to recalculate the invariant accounting for fees - # to calculate fair user's share - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - 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(vp_rate, new_balances, amp) - 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 amounts[i] > 0: - assert ERC20(self.coins[i]).transferFrom(msg.sender, self, amounts[i]) # dev: failed transfer - - # Mint pool tokens - self.token.mint(msg.sender, mint_amount) - - log AddLiquidity(msg.sender, amounts, fees, D1, token_supply + mint_amount) - - return mint_amount - - -@view -@internal -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 # dev: same coin - assert j >= 0 # dev: j below zero - assert j < N_COINS # dev: j above N_COINS - - # should be unreachable, but good for safety - assert i >= 0 - assert i < N_COINS - - amp: uint256 = self._A() - D: uint256 = self.get_D(xp_, amp) - - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: uint256 = 0 - c: uint256 = D - Ann: uint256 = amp * N_COINS - - 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 * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann # - D - 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 - - -@view -@external -def get_dy(i: int128, j: int128, dx: uint256) -> uint256: - # dx and dy in c-units - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = self._vp_rate_ro() - xp: uint256[N_COINS] = self._xp(rates[MAX_COIN]) - - x: uint256 = xp[i] + (dx * rates[i] / PRECISION) - y: uint256 = self.get_y(i, j, x, xp) - dy: uint256 = xp[j] - y - 1 - _fee: uint256 = self.fee * dy / FEE_DENOMINATOR - return (dy - _fee) * PRECISION / rates[j] - - -@view -@external -def get_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256: - # dx and dy in underlying units - vp_rate: uint256 = self._vp_rate_ro() - xp: uint256[N_COINS] = self._xp(vp_rate) - precisions: uint256[N_COINS] = PRECISION_MUL - _base_pool: address = self.base_pool - - # Use base_i or base_j if they are >= 0 - base_i: int128 = i - MAX_COIN - base_j: int128 = j - MAX_COIN - meta_i: int128 = MAX_COIN - meta_j: int128 = MAX_COIN - if base_i < 0: - meta_i = i - if base_j < 0: - meta_j = j - - x: uint256 = 0 - if base_i < 0: - x = xp[i] + dx * precisions[i] - else: - if base_j < 0: - # i is from BasePool - # At first, get the amount of pool tokens - base_inputs: uint256[BASE_N_COINS] = empty(uint256[BASE_N_COINS]) - base_inputs[base_i] = dx - # Token amount transformed to underlying "dollars" - x = Curve(_base_pool).calc_token_amount(base_inputs, True) * vp_rate / PRECISION - # Accounting for deposit/withdraw fees approximately - x -= x * Curve(_base_pool).fee() / (2 * FEE_DENOMINATOR) - # Adding number of pool tokens - x += xp[MAX_COIN] - else: - # If both are from the base pool - return Curve(_base_pool).get_dy(base_i, base_j, dx) - - # This pool is involved only when in-pool assets are used - y: uint256 = self.get_y(meta_i, meta_j, x, xp) - dy: uint256 = xp[meta_j] - y - 1 - dy = (dy - self.fee * dy / FEE_DENOMINATOR) - - # If output is going via the metapool - if base_j < 0: - dy /= precisions[meta_j] - else: - # j is from BasePool - # The fee is already accounted for - dy = Curve(_base_pool).calc_withdraw_one_coin(dy * PRECISION / vp_rate, base_j) - - return dy - - -@external -@nonreentrant('lock') -def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256) -> uint256: - """ - @notice Perform an exchange between two coins - @dev Index values can be found via the `coins` public getter method - @param i Index value for the coin to send - @param j Index valie of the coin to recieve - @param dx Amount of `i` being exchanged - @param min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - assert not self.is_killed # dev: is killed - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = self._vp_rate() - - old_balances: uint256[N_COINS] = self.balances - xp: uint256[N_COINS] = self._xp_mem(rates[MAX_COIN], old_balances) - - x: uint256 = xp[i] + dx * rates[i] / PRECISION - y: uint256 = self.get_y(i, j, x, xp) - - dy: uint256 = xp[j] - y - 1 # -1 just in case there were some rounding errors - dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR - - # Convert all to real units - dy = (dy - dy_fee) * PRECISION / rates[j] - assert dy >= min_dy, "Too few coins in result" - - dy_admin_fee: uint256 = dy_fee * self.admin_fee / FEE_DENOMINATOR - dy_admin_fee = dy_admin_fee * PRECISION / rates[j] - - # Change balances exactly in same way as we change actual ERC20 coin amounts - self.balances[i] = old_balances[i] + dx - # When rounding errors happen, we undercharge admin fee in favor of LP - self.balances[j] = old_balances[j] - dy - dy_admin_fee - - assert ERC20(self.coins[i]).transferFrom(msg.sender, self, dx) - assert ERC20(self.coins[j]).transfer(msg.sender, dy) - - log TokenExchange(msg.sender, i, dx, j, dy) - - return dy - - -@external -@nonreentrant('lock') -def exchange_underlying(i: int128, j: int128, dx: uint256, min_dy: uint256) -> uint256: - """ - @notice Perform an exchange between two underlying coins - @dev Index values can be found via the `underlying_coins` public getter method - @param i Index value for the underlying coin to send - @param j Index valie of the underlying coin to recieve - @param dx Amount of `i` being exchanged - @param min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - assert not self.is_killed # dev: is killed - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = self._vp_rate() - _base_pool: address = self.base_pool - - # Use base_i or base_j if they are >= 0 - base_i: int128 = i - MAX_COIN - base_j: int128 = j - MAX_COIN - meta_i: int128 = MAX_COIN - meta_j: int128 = MAX_COIN - if base_i < 0: - meta_i = i - if base_j < 0: - meta_j = j - dy: uint256 = 0 - - # Addresses for input and output coins - input_coin: address = ZERO_ADDRESS - if base_i < 0: - input_coin = self.coins[i] - else: - input_coin = self.base_coins[base_i] - output_coin: address = ZERO_ADDRESS - if base_j < 0: - output_coin = self.coins[j] - else: - output_coin = self.base_coins[base_j] - - # Handle potential Tether fees - dx_w_fee: uint256 = dx - if input_coin == FEE_ASSET: - dx_w_fee = ERC20(FEE_ASSET).balanceOf(self) - # "safeTransferFrom" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - input_coin, - concat( - method_id("transferFrom(address,address,uint256)"), - convert(msg.sender, bytes32), - convert(self, bytes32), - convert(dx, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransferFrom" - # Handle potential Tether fees - if input_coin == FEE_ASSET: - dx_w_fee = ERC20(FEE_ASSET).balanceOf(self) - dx_w_fee - - if base_i < 0 or base_j < 0: - old_balances: uint256[N_COINS] = self.balances - xp: uint256[N_COINS] = self._xp_mem(rates[MAX_COIN], old_balances) - - x: uint256 = 0 - if base_i < 0: - x = xp[i] + dx_w_fee * rates[i] / PRECISION - else: - # i is from BasePool - # At first, get the amount of pool tokens - base_inputs: uint256[BASE_N_COINS] = empty(uint256[BASE_N_COINS]) - base_inputs[base_i] = dx_w_fee - coin_i: address = self.coins[MAX_COIN] - # Deposit and measure delta - x = ERC20(coin_i).balanceOf(self) - Curve(_base_pool).add_liquidity(base_inputs, 0) - # Need to convert pool token to "virtual" units using rates - # dx is also different now - dx_w_fee = ERC20(coin_i).balanceOf(self) - x - x = dx_w_fee * rates[MAX_COIN] / PRECISION - # Adding number of pool tokens - x += xp[MAX_COIN] - - y: uint256 = self.get_y(meta_i, meta_j, x, xp) - - # Either a real coin or token - dy = xp[meta_j] - y - 1 # -1 just in case there were some rounding errors - dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR - - # Convert all to real units - # Works for both pool coins and real coins - dy = (dy - dy_fee) * PRECISION / rates[meta_j] - - dy_admin_fee: uint256 = dy_fee * self.admin_fee / FEE_DENOMINATOR - dy_admin_fee = dy_admin_fee * PRECISION / rates[meta_j] - - # Change balances exactly in same way as we change actual ERC20 coin amounts - self.balances[meta_i] = old_balances[meta_i] + dx_w_fee - # When rounding errors happen, we undercharge admin fee in favor of LP - self.balances[meta_j] = old_balances[meta_j] - dy - dy_admin_fee - - # Withdraw from the base pool if needed - if base_j >= 0: - out_amount: uint256 = ERC20(output_coin).balanceOf(self) - Curve(_base_pool).remove_liquidity_one_coin(dy, base_j, 0) - dy = ERC20(output_coin).balanceOf(self) - out_amount - - assert dy >= min_dy, "Too few coins in result" - - else: - # If both are from the base pool - dy = ERC20(output_coin).balanceOf(self) - Curve(_base_pool).exchange(base_i, base_j, dx_w_fee, min_dy) - dy = ERC20(output_coin).balanceOf(self) - dy - - # "safeTransfer" which works for ERC20s which return bool or not - _response = raw_call( - output_coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(dy, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - log TokenExchangeUnderlying(msg.sender, i, dx, j, dy) - - return dy - - -@external -@nonreentrant('lock') -def remove_liquidity(_amount: uint256, min_amounts: uint256[N_COINS]) -> uint256[N_COINS]: - """ - @notice Withdraw coins from the pool - @dev Withdrawal amounts are based on current deposit ratios - @param _amount Quantity of LP tokens to burn in the withdrawal - @param min_amounts Minimum amounts of underlying coins to receive - @return List of amounts of coins that were withdrawn - """ - total_supply: uint256 = self.token.totalSupply() - amounts: uint256[N_COINS] = empty(uint256[N_COINS]) - fees: uint256[N_COINS] = empty(uint256[N_COINS]) # Fees are unused but we've got them historically in event - - for i in range(N_COINS): - value: uint256 = self.balances[i] * _amount / total_supply - assert value >= min_amounts[i], "Too few coins in result" - self.balances[i] -= value - amounts[i] = value - assert ERC20(self.coins[i]).transfer(msg.sender, value) - - self.token.burnFrom(msg.sender, _amount) # dev: insufficient funds - - log RemoveLiquidity(msg.sender, amounts, fees, total_supply - _amount) - - return amounts - - -@external -@nonreentrant('lock') -def remove_liquidity_imbalance(amounts: uint256[N_COINS], max_burn_amount: uint256) -> uint256: - """ - @notice Withdraw coins from the pool in an imbalanced amount - @param amounts List of amounts of underlying coins to withdraw - @param max_burn_amount Maximum amount of LP token to burn in the withdrawal - @return Actual amount of the LP token burned in the withdrawal - """ - assert not self.is_killed # dev: is killed - - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate() - - token_supply: uint256 = self.token.totalSupply() - assert token_supply != 0 # dev: zero total supply - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - _admin_fee: uint256 = self.admin_fee - - old_balances: uint256[N_COINS] = self.balances - new_balances: uint256[N_COINS] = old_balances - D0: uint256 = self.get_D_mem(vp_rate, old_balances, amp) - for i in range(N_COINS): - new_balances[i] -= amounts[i] - D1: uint256 = self.get_D_mem(vp_rate, new_balances, amp) - - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - 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(vp_rate, new_balances, amp) - - token_amount: uint256 = (D0 - D2) * token_supply / D0 - assert token_amount != 0 # dev: zero tokens burned - token_amount += 1 # In case of rounding errors - make it unfavorable for the "attacker" - assert token_amount <= max_burn_amount, "Slippage screwed you" - - self.token.burnFrom(msg.sender, token_amount) # dev: insufficient funds - for i in range(N_COINS): - if amounts[i] != 0: - assert ERC20(self.coins[i]).transfer(msg.sender, amounts[i]) - - log RemoveLiquidityImbalance(msg.sender, amounts, fees, D1, token_supply - token_amount) - - return token_amount - - -@view -@internal -def get_y_D(A_: uint256, i: int128, xp: uint256[N_COINS], D: uint256) -> uint256: - """ - Calculate x[i] if one reduces D from being calculated for xp to D - - Done by solving quadratic equation iteratively. - x_1**2 + x1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) - x_1**2 + b*x_1 = c - - x_1 = (x_1**2 + c) / (2*x_1 + b) - """ - # x in the input is converted to the same price/precision - - assert i >= 0 # dev: i below zero - assert i < N_COINS # dev: i above N_COINS - - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: uint256 = 0 - - c: uint256 = D - Ann: uint256 = A_ * N_COINS - - for _i in range(N_COINS): - if _i != i: - _x = xp[_i] - else: - continue - S_ += _x - c = c * D / (_x * N_COINS) - c = c * D * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann - 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 - - -@view -@internal -def _calc_withdraw_one_coin(_token_amount: uint256, i: int128, vp_rate: uint256) -> (uint256, uint256, uint256): - # First, need to calculate - # * Get current D - # * Solve Eqn against y_i for D - _token_amount - amp: uint256 = self._A() - xp: uint256[N_COINS] = self._xp(vp_rate) - D0: uint256 = self.get_D(xp, amp) - - total_supply: uint256 = self.token.totalSupply() - D1: uint256 = D0 - _token_amount * D0 / total_supply - new_y: uint256 = self.get_y_D(amp, i, xp, D1) - - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = vp_rate - - xp_reduced: uint256[N_COINS] = xp - dy_0: uint256 = (xp[i] - new_y) * PRECISION / rates[i] # w/o fees - - for j in range(N_COINS): - dx_expected: uint256 = 0 - if j == i: - dx_expected = xp[j] * D1 / D0 - new_y - else: - dx_expected = xp[j] - xp[j] * D1 / D0 - xp_reduced[j] -= _fee * dx_expected / FEE_DENOMINATOR - - dy: uint256 = xp_reduced[i] - self.get_y_D(amp, i, xp_reduced, D1) - dy = (dy - 1) * PRECISION / rates[i] # Withdraw less to account for rounding errors - - return dy, dy_0 - dy, total_supply - - -@view -@external -def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - """ - @notice Calculate the amount received when withdrawing a single coin - @param _token_amount Amount of LP tokens to burn in the withdrawal - @param i Index value of the coin to withdraw - @return Amount of coin received - """ - vp_rate: uint256 = self._vp_rate_ro() - return self._calc_withdraw_one_coin(_token_amount, i, vp_rate)[0] - - -@external -@nonreentrant('lock') -def remove_liquidity_one_coin(_token_amount: uint256, i: int128, _min_amount: uint256) -> uint256: - """ - @notice Withdraw a single coin from the pool - @param _token_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 coin to receive - @return Amount of coin received - """ - assert not self.is_killed # dev: is killed - - vp_rate: uint256 = self._vp_rate() - dy: uint256 = 0 - dy_fee: uint256 = 0 - total_supply: uint256 = 0 - dy, dy_fee, total_supply = self._calc_withdraw_one_coin(_token_amount, i, vp_rate) - assert dy >= _min_amount, "Not enough coins removed" - - self.balances[i] -= (dy + dy_fee * self.admin_fee / FEE_DENOMINATOR) - self.token.burnFrom(msg.sender, _token_amount) # dev: insufficient funds - assert ERC20(self.coins[i]).transfer(msg.sender, dy) - - log RemoveLiquidityOne(msg.sender, _token_amount, dy, total_supply - _token_amount) - - return dy - - -### Admin functions ### -@external -def ramp_A(_future_A: uint256, _future_time: uint256): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.initial_A_time + MIN_RAMP_TIME - assert _future_time >= block.timestamp + MIN_RAMP_TIME # dev: insufficient time - - _initial_A: uint256 = self._A() - _future_A_p: uint256 = _future_A * A_PRECISION - - assert _future_A > 0 and _future_A < MAX_A - if _future_A_p < _initial_A: - assert _future_A_p * MAX_A_CHANGE >= _initial_A - else: - assert _future_A_p <= _initial_A * MAX_A_CHANGE - - self.initial_A = _initial_A - self.future_A = _future_A_p - self.initial_A_time = block.timestamp - self.future_A_time = _future_time - - log RampA(_initial_A, _future_A_p, block.timestamp, _future_time) - - -@external -def stop_ramp_A(): - assert msg.sender == self.owner # dev: only owner - - current_A: uint256 = self._A() - self.initial_A = current_A - self.future_A = current_A - self.initial_A_time = block.timestamp - self.future_A_time = block.timestamp - # now (block.timestamp < t1) is always False, so we return saved A - - log StopRampA(current_A, block.timestamp) - - -@external -def commit_new_fee(new_fee: uint256, new_admin_fee: uint256): - assert msg.sender == self.owner # dev: only owner - assert self.admin_actions_deadline == 0 # dev: active action - assert new_fee <= MAX_FEE # dev: fee exceeds maximum - assert new_admin_fee <= MAX_ADMIN_FEE # dev: admin fee exceeds maximum - - _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.admin_actions_deadline = _deadline - self.future_fee = new_fee - self.future_admin_fee = new_admin_fee - - log CommitNewFee(_deadline, new_fee, new_admin_fee) - - -@external -def apply_new_fee(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.admin_actions_deadline # dev: insufficient time - assert self.admin_actions_deadline != 0 # dev: no active action - - self.admin_actions_deadline = 0 - _fee: uint256 = self.future_fee - _admin_fee: uint256 = self.future_admin_fee - self.fee = _fee - self.admin_fee = _admin_fee - - log NewFee(_fee, _admin_fee) - - -@external -def revert_new_parameters(): - assert msg.sender == self.owner # dev: only owner - - self.admin_actions_deadline = 0 - - -@external -def commit_transfer_ownership(_owner: address): - assert msg.sender == self.owner # dev: only owner - assert self.transfer_ownership_deadline == 0 # dev: active transfer - - _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.transfer_ownership_deadline = _deadline - self.future_owner = _owner - - log CommitNewAdmin(_deadline, _owner) - - -@external -def apply_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.transfer_ownership_deadline # dev: insufficient time - assert self.transfer_ownership_deadline != 0 # dev: no active transfer - - self.transfer_ownership_deadline = 0 - _owner: address = self.future_owner - self.owner = _owner - - log NewAdmin(_owner) - - -@external -def revert_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - - self.transfer_ownership_deadline = 0 - - -@view -@external -def admin_balances(i: uint256) -> uint256: - return ERC20(self.coins[i]).balanceOf(self) - self.balances[i] - - -@external -def withdraw_admin_fees(): - assert msg.sender == self.owner # dev: only owner - - for i in range(N_COINS): - c: address = self.coins[i] - value: uint256 = ERC20(c).balanceOf(self) - self.balances[i] - if value > 0: - assert ERC20(c).transfer(msg.sender, value) - - -@external -def donate_admin_fees(): - assert msg.sender == self.owner # dev: only owner - for i in range(N_COINS): - self.balances[i] = ERC20(self.coins[i]).balanceOf(self) - - -@external -def kill_me(): - assert msg.sender == self.owner # dev: only owner - assert self.kill_deadline > block.timestamp # dev: deadline has passed - self.is_killed = True - - -@external -def unkill_me(): - assert msg.sender == self.owner # dev: only owner - self.is_killed = False diff --git a/contracts/pools/obtc/pooldata.json b/contracts/pools/obtc/pooldata.json deleted file mode 100644 index 1cfcc970..00000000 --- a/contracts/pools/obtc/pooldata.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "base_pool": "sbtc", - "pool_types": ["meta"], - "swap_address": "0xd81dA8D904b52208541Bade1bD6595D8a251F8dd", - "lp_token_address": "0x2fE94ea3d5d4a175184081439753DE15AeF9d614", - "zap_address": "0xd5BCf53e2C81e1991570f33Fa881c49EEa570C8D", - "gauge_addresses": ["0x11137B10C210b579405c21A07489e28F3c040AB1"], - "lp_contract": "CurveTokenV3", - "lp_constructor": { - "symbol": "oBTC/sbtcCRV", - "name": "Curve.fi oBTC/sbtcCRV" - }, - "swap_constructor": { - "_A": 200, - "_fee": 4000000, - "_admin_fee": 5000000000 - }, - "coins": [ - { - "name": "oBTC", - "decimals": 18, - "tethered": false, - "wrapped": false, - "underlying_address": "0x8064d9Ae6cDf087b1bcd5BDf3531bD5d8C537a68" - }, - { - "name": "sbtcCRV", - "decimals": 18, - "wrapped": false, - "base_pool_token": true, - "underlying_address": "0x075b1bb99792c9E1041bA13afEf80C91a1e70fB3" - } - ], - "testing": { - "initial_amount": 100 - } -} diff --git a/contracts/pools/pax/DepositPax.vy b/contracts/pools/pax/DepositPax.vy deleted file mode 100644 index a4057ef5..00000000 --- a/contracts/pools/pax/DepositPax.vy +++ /dev/null @@ -1,363 +0,0 @@ -# @version 0.1.0b17 -# A "zap" to deposit/withdraw Curve contract without too many transactions -# (c) Curve.Fi, 2020 -from vyper.interfaces import ERC20 - -# External Contracts -contract yERC20: - def totalSupply() -> uint256: constant - def allowance(_owner: address, _spender: address) -> uint256: constant - def transfer(_to: address, _value: uint256) -> bool: modifying - def transferFrom(_from: address, _to: address, _value: uint256) -> bool: modifying - def approve(_spender: address, _value: uint256) -> bool: modifying - def name() -> string[64]: constant - def symbol() -> string[32]: constant - def decimals() -> uint256: constant - def balanceOf(arg0: address) -> uint256: constant - def deposit(depositAmount: uint256): modifying - def withdraw(withdrawTokens: uint256): modifying - def getPricePerFullShare() -> uint256: constant - - - - -# Tether transfer-only ABI -contract USDT: - def transfer(_to: address, _value: uint256): modifying - def transferFrom(_from: address, _to: address, _value: uint256): modifying - - -contract Curve: - def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256): modifying - def remove_liquidity(_amount: uint256, min_amounts: uint256[N_COINS]): modifying - def remove_liquidity_imbalance(amounts: uint256[N_COINS], max_burn_amount: uint256): modifying - def balances(i: int128) -> uint256: constant - def A() -> uint256: constant - def fee() -> uint256: constant - def owner() -> address: constant - - -N_COINS: constant(int128) = 4 -TETHERED: constant(bool[N_COINS]) = [False, False, True, False] -USE_LENDING: constant(bool[N_COINS]) = [True, True, True, False] -ZERO256: constant(uint256) = 0 # This hack is really bad XXX -ZEROS: constant(uint256[N_COINS]) = [ZERO256, ZERO256, ZERO256, ZERO256] # <- change -LENDING_PRECISION: constant(uint256) = 10 ** 18 -PRECISION: constant(uint256) = 10 ** 18 -PRECISION_MUL: constant(uint256[N_COINS]) = [convert(1, uint256), convert(1000000000000, uint256), convert(1000000000000, uint256), convert(1, uint256)] -FEE_DENOMINATOR: constant(uint256) = 10 ** 10 -FEE_IMPRECISION: constant(uint256) = 25 * 10 ** 8 # % of the fee - -coins: public(address[N_COINS]) -underlying_coins: public(address[N_COINS]) -curve: public(address) -token: public(address) - - -@public -def __init__(_coins: address[N_COINS], _underlying_coins: address[N_COINS], - _curve: address, _token: address): - self.coins = _coins - self.underlying_coins = _underlying_coins - self.curve = _curve - self.token = _token - - -@public -@nonreentrant('lock') -def add_liquidity(uamounts: uint256[N_COINS], min_mint_amount: uint256): - use_lending: bool[N_COINS] = USE_LENDING - tethered: bool[N_COINS] = TETHERED - amounts: uint256[N_COINS] = ZEROS - - for i in range(N_COINS): - uamount: uint256 = uamounts[i] - - if uamount > 0: - # Transfer the underlying coin from owner - if tethered[i]: - USDT(self.underlying_coins[i]).transferFrom( - msg.sender, self, uamount) - else: - assert_modifiable(ERC20(self.underlying_coins[i])\ - .transferFrom(msg.sender, self, uamount)) - - # Mint if needed - if use_lending[i]: - ERC20(self.underlying_coins[i]).approve(self.coins[i], uamount) - yERC20(self.coins[i]).deposit(uamount) - amounts[i] = yERC20(self.coins[i]).balanceOf(self) - ERC20(self.coins[i]).approve(self.curve, amounts[i]) - else: - amounts[i] = uamount - ERC20(self.underlying_coins[i]).approve(self.curve, uamount) - - Curve(self.curve).add_liquidity(amounts, min_mint_amount) - - tokens: uint256 = ERC20(self.token).balanceOf(self) - assert_modifiable(ERC20(self.token).transfer(msg.sender, tokens)) - - -@private -def _send_all(_addr: address, min_uamounts: uint256[N_COINS], one: int128): - use_lending: bool[N_COINS] = USE_LENDING - tethered: bool[N_COINS] = TETHERED - - for i in range(N_COINS): - if (one < 0) or (i == one): - if use_lending[i]: - _coin: address = self.coins[i] - _balance: uint256 = yERC20(_coin).balanceOf(self) - if _balance == 0: # Do nothing if there are 0 coins - continue - yERC20(_coin).withdraw(_balance) - - _ucoin: address = self.underlying_coins[i] - _uamount: uint256 = ERC20(_ucoin).balanceOf(self) - assert _uamount >= min_uamounts[i], "Not enough coins withdrawn" - - # Send only if we have something to send - if _uamount >= 0: - if tethered[i]: - USDT(_ucoin).transfer(_addr, _uamount) - else: - assert_modifiable(ERC20(_ucoin).transfer(_addr, _uamount)) - - -@public -@nonreentrant('lock') -def remove_liquidity(_amount: uint256, min_uamounts: uint256[N_COINS]): - zeros: uint256[N_COINS] = ZEROS - - assert_modifiable(ERC20(self.token).transferFrom(msg.sender, self, _amount)) - Curve(self.curve).remove_liquidity(_amount, zeros) - - self._send_all(msg.sender, min_uamounts, -1) - - -@public -@nonreentrant('lock') -def remove_liquidity_imbalance(uamounts: uint256[N_COINS], max_burn_amount: uint256): - """ - Get max_burn_amount in, remove requested liquidity and transfer back what is left - """ - use_lending: bool[N_COINS] = USE_LENDING - tethered: bool[N_COINS] = TETHERED - _token: address = self.token - - amounts: uint256[N_COINS] = uamounts - for i in range(N_COINS): - if use_lending[i] and amounts[i] > 0: - rate: uint256 = yERC20(self.coins[i]).getPricePerFullShare() - amounts[i] = amounts[i] * LENDING_PRECISION / rate - # if not use_lending - all good already - - # Transfrer max tokens in - _tokens: uint256 = ERC20(_token).balanceOf(msg.sender) - if _tokens > max_burn_amount: - _tokens = max_burn_amount - assert_modifiable(ERC20(_token).transferFrom(msg.sender, self, _tokens)) - - Curve(self.curve).remove_liquidity_imbalance(amounts, max_burn_amount) - - # Transfer unused tokens back - _tokens = ERC20(_token).balanceOf(self) - assert_modifiable(ERC20(_token).transfer(msg.sender, _tokens)) - - # Unwrap and transfer all the coins we've got - self._send_all(msg.sender, ZEROS, -1) - - -@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(A: uint256, 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 = 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_y(A: uint256, i: int128, _xp: uint256[N_COINS], D: uint256) -> uint256: - """ - Calculate x[i] if one reduces D from being calculated for _xp to D - - Done by solving quadratic equation iteratively. - x_1**2 + x1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) - x_1**2 + b*x_1 = c - - x_1 = (x_1**2 + c) / (2*x_1 + b) - """ - # x in the input is converted to the same price/precision - - assert (i >= 0) and (i < N_COINS) - - c: uint256 = D - S_: uint256 = 0 - Ann: uint256 = A * N_COINS - - _x: uint256 = 0 - for _i in range(N_COINS): - if _i != i: - _x = _xp[_i] - else: - continue - S_ += _x - c = c * D / (_x * N_COINS) - c = c * D / (Ann * N_COINS) - b: uint256 = S_ + D / Ann - 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 - - -@private -@constant -def _calc_withdraw_one_coin(_token_amount: uint256, i: int128, rates: uint256[N_COINS]) -> uint256: - # First, need to calculate - # * Get current D - # * Solve Eqn against y_i for D - _token_amount - use_lending: bool[N_COINS] = USE_LENDING - # tethered: bool[N_COINS] = TETHERED - crv: address = self.curve - A: uint256 = Curve(crv).A() - fee: uint256 = Curve(crv).fee() * N_COINS / (4 * (N_COINS - 1)) - fee += fee * FEE_IMPRECISION / FEE_DENOMINATOR # Overcharge to account for imprecision - precisions: uint256[N_COINS] = PRECISION_MUL - total_supply: uint256 = ERC20(self.token).totalSupply() - - xp: uint256[N_COINS] = PRECISION_MUL - S: uint256 = 0 - for j in range(N_COINS): - xp[j] *= Curve(crv).balances(j) - if use_lending[j]: - # Use stored rate b/c we have imprecision anyway - xp[j] = xp[j] * rates[j] / LENDING_PRECISION - S += xp[j] - # if not use_lending - all good already - - D0: uint256 = self.get_D(A, xp) - D1: uint256 = D0 - _token_amount * D0 / total_supply - xp_reduced: uint256[N_COINS] = xp - - # xp = xp - fee * | xp * D1 / D0 - (xp - S * dD / D0 * (0, ... 1, ..0))| - for j in range(N_COINS): - dx_expected: uint256 = 0 - b_ideal: uint256 = xp[j] * D1 / D0 - b_expected: uint256 = xp[j] - if j == i: - b_expected -= S * (D0 - D1) / D0 - if b_ideal >= b_expected: - dx_expected = (b_ideal - b_expected) - else: - dx_expected = (b_expected - b_ideal) - xp_reduced[j] -= fee * dx_expected / FEE_DENOMINATOR - - dy: uint256 = xp_reduced[i] - self.get_y(A, i, xp_reduced, D1) - dy = dy / precisions[i] - - return dy - - -@public -@constant -def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - rates: uint256[N_COINS] = ZEROS - use_lending: bool[N_COINS] = USE_LENDING - - for j in range(N_COINS): - if use_lending[j]: - rates[j] = yERC20(self.coins[j]).getPricePerFullShare() - else: - rates[j] = 10 ** 18 - - return self._calc_withdraw_one_coin(_token_amount, i, rates) - - -@public -@nonreentrant('lock') -def remove_liquidity_one_coin(_token_amount: uint256, i: int128, min_uamount: uint256, donate_dust: bool = False): - """ - Remove _amount of liquidity all in a form of coin i - """ - use_lending: bool[N_COINS] = USE_LENDING - rates: uint256[N_COINS] = ZEROS - _token: address = self.token - - for j in range(N_COINS): - if use_lending[j]: - rates[j] = yERC20(self.coins[j]).getPricePerFullShare() - else: - rates[j] = LENDING_PRECISION - - dy: uint256 = self._calc_withdraw_one_coin(_token_amount, i, rates) - assert dy >= min_uamount, "Not enough coins removed" - - assert_modifiable( - ERC20(self.token).transferFrom(msg.sender, self, _token_amount)) - - amounts: uint256[N_COINS] = ZEROS - amounts[i] = dy * LENDING_PRECISION / rates[i] - token_amount_before: uint256 = ERC20(_token).balanceOf(self) - Curve(self.curve).remove_liquidity_imbalance(amounts, _token_amount) - - # Unwrap and transfer all the coins we've got - self._send_all(msg.sender, ZEROS, i) - - if not donate_dust: - # Transfer unused tokens back - token_amount_after: uint256 = ERC20(_token).balanceOf(self) - if token_amount_after > token_amount_before: - assert_modifiable(ERC20(_token).transfer( - msg.sender, token_amount_after - token_amount_before) - ) - - -@public -@nonreentrant('lock') -def withdraw_donated_dust(): - owner: address = Curve(self.curve).owner() - assert msg.sender == owner - - _token: address = self.token - assert_modifiable( - ERC20(_token).transfer(owner, ERC20(_token).balanceOf(self))) diff --git a/contracts/pools/pax/StableSwapPax.vy b/contracts/pools/pax/StableSwapPax.vy deleted file mode 100644 index e7b36f61..00000000 --- a/contracts/pools/pax/StableSwapPax.vy +++ /dev/null @@ -1,707 +0,0 @@ -# @version 0.1.0b17 -# (c) Curve.Fi, 2020 - - -# External Contracts -contract ERC20m: - def totalSupply() -> uint256: constant - def allowance(_owner: address, _spender: address) -> uint256: constant - def transfer(_to: address, _value: uint256) -> bool: modifying - def transferFrom(_from: address, _to: address, _value: uint256) -> bool: modifying - def approve(_spender: address, _value: uint256) -> bool: modifying - def mint(_to: address, _value: uint256): modifying - def burn(_value: uint256): modifying - def burnFrom(_to: address, _value: uint256): modifying - def name() -> string[64]: constant - def symbol() -> string[32]: constant - def decimals() -> uint256: constant - def balanceOf(arg0: address) -> uint256: constant - def set_minter(_minter: address): modifying - - - -# External Contracts -contract yERC20: - def totalSupply() -> uint256: constant - def allowance(_owner: address, _spender: address) -> uint256: constant - def transfer(_to: address, _value: uint256) -> bool: modifying - def transferFrom(_from: address, _to: address, _value: uint256) -> bool: modifying - def approve(_spender: address, _value: uint256) -> bool: modifying - def name() -> string[64]: constant - def symbol() -> string[32]: constant - def decimals() -> uint256: constant - def balanceOf(arg0: address) -> uint256: constant - def deposit(depositAmount: uint256): modifying - def withdraw(withdrawTokens: uint256): modifying - def getPricePerFullShare() -> uint256: constant - - -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 - - -# This can (and needs to) be changed at compile time -N_COINS: constant(int128) = 4 # <- change - -ZERO256: constant(uint256) = 0 # This hack is really bad XXX -ZEROS: constant(uint256[N_COINS]) = [ZERO256, ZERO256, ZERO256, ZERO256] # <- change - -USE_LENDING: constant(bool[N_COINS]) = [True, True, True, False] - -# Flag "ERC20s" which don't return from transfer() and transferFrom() -TETHERED: constant(bool[N_COINS]) = [False, False, True, False] - -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]) = [convert(1, uint256), convert(1000000000000, uint256), convert(1000000000000, uint256), convert(1, uint256)] -# 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 -min_ramp_time: constant(uint256) = 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)}) - -CommitNewFee: event({deadline: indexed(timestamp), fee: uint256, admin_fee: uint256}) -NewFee: event({fee: uint256, admin_fee: uint256}) -RampA: event({old_A: uint256, new_A: uint256, initial_time: timestamp, future_time: timestamp}) -StopRampA: event({A: uint256, t: timestamp}) - -coins: public(address[N_COINS]) -underlying_coins: public(address[N_COINS]) -balances: public(uint256[N_COINS]) -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 -max_A_change: constant(uint256) = 10 - -owner: public(address) -token: ERC20m - -initial_A: public(uint256) -future_A: public(uint256) -initial_A_time: public(timestamp) -future_A_time: public(timestamp) - -admin_actions_deadline: public(timestamp) -transfer_ownership_deadline: public(timestamp) -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], - _pool_token: address, - _A: uint256, _fee: uint256): - """ - _coins: Addresses of ERC20 conracts of coins (y-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.underlying_coins = _underlying_coins - self.initial_A = _A - self.future_A = _A - self.initial_A_time = 0 - self.future_A_time = 0 - 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) - - -@constant -@private -def _A() -> uint256: - """ - Handle ramping A up or down - """ - t1: timestamp = self.future_A_time - A1: uint256 = self.future_A - - if block.timestamp < t1: - A0: uint256 = self.initial_A - t0: timestamp = self.initial_A_time - # Expressions in uint256 cannot have negative numbers, thus "if" - if A1 > A0: - return A0 + (A1 - A0) * (block.timestamp - t0) / (t1 - t0) - else: - return A0 - (A0 - A1) * (block.timestamp - t0) / (t1 - t0) - - else: # when t1 == 0 or block.timestamp >= t1 - return A1 - - -@constant -@public -def A() -> uint256: - return self._A() - - -@private -@constant -def _rates() -> uint256[N_COINS]: - # exchangeRateStored * (1 + supplyRatePerBlock * (getBlockNumber - accrualBlockNumber) / 1e18) - result: uint256[N_COINS] = PRECISION_MUL - use_lending: bool[N_COINS] = USE_LENDING - for i in range(N_COINS): - rate: uint256 = LENDING_PRECISION # Used with no lending - if use_lending[i]: - rate = yERC20(self.coins[i]).getPricePerFullShare() - 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], amp: uint256) -> uint256: - S: uint256 = 0 - for _x in xp: - S += _x - if S == 0: - return 0 - - Dprev: uint256 = 0 - D: uint256 = S - Ann: uint256 = amp * N_COINS - for _i in range(255): - D_P: uint256 = D - for _x in xp: - D_P = D_P * D / (_x * N_COINS) # If division by 0, this will be borked: only withdrawal will work. And that is good - 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], amp: uint256) -> uint256: - return self.get_D(self._xp_mem(rates, _balances), amp) - - -@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._rates()), self._A()) - # 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._rates() - amp: uint256 = self._A() - D0: uint256 = self.get_D_mem(rates, _balances, amp) - 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, amp) - 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 - amp: uint256 = self._A() - - token_supply: uint256 = self.token.totalSupply() - rates: uint256[N_COINS] = self._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, amp) - 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, amp) - 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, amp) - 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 amounts[i] > 0: - if tethered[i] and not use_lending[i]: - USDT(self.coins[i]).transferFrom(msg.sender, self, amounts[i]) - else: - assert_modifiable( - yERC20(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) - - amp: uint256 = self._A() - D: uint256 = self.get_D(_xp, amp) - c: uint256 = D - S_: uint256 = 0 - Ann: uint256 = amp * 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._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 - 1) * PRECISION / rates[j] - _fee: uint256 = self.fee * dy / FEE_DENOMINATOR - return dy - _fee - - -@public -@constant -def get_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256: - # dx and dy in underlying units - rates: uint256[N_COINS] = self._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 - 1) / precisions[j] - _fee: uint256 = self.fee * dy / FEE_DENOMINATOR - return dy - _fee - - -@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._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(yERC20(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(yERC20(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._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(yERC20(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._rates() - amp: uint256 = self._A() - - old_balances: uint256[N_COINS] = self.balances - new_balances: uint256[N_COINS] = old_balances - D0: uint256 = self.get_D_mem(rates, old_balances, amp) - for i in range(N_COINS): - new_balances[i] -= amounts[i] - D1: uint256 = self.get_D_mem(rates, new_balances, amp) - 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, amp) - - token_amount: uint256 = (D0 - D2) * token_supply / D0 + 1 - assert token_amount <= max_burn_amount, "Slippage screwed you" - - for i in range(N_COINS): - if amounts[i] > 0: - if tethered[i] and not use_lending[i]: - USDT(self.coins[i]).transfer(msg.sender, amounts[i]) - else: - assert_modifiable(yERC20(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) - - -### Admin functions ### -@public -def ramp_A(_future_A: uint256, _future_time: timestamp): - assert msg.sender == self.owner - assert block.timestamp >= self.initial_A_time + min_ramp_time - assert _future_time >= block.timestamp + min_ramp_time - - _initial_A: uint256 = self._A() - assert (_future_A > 0) and (_future_A < max_A) - assert ((_future_A >= _initial_A) and (_future_A <= _initial_A * max_A_change)) or\ - ((_future_A < _initial_A) and (_future_A * max_A_change >= _initial_A)) - self.initial_A = _initial_A - self.future_A = _future_A - self.initial_A_time = block.timestamp - self.future_A_time = _future_time - - log.RampA(_initial_A, _future_A, block.timestamp, _future_time) - - -@public -def stop_ramp_A(): - assert msg.sender == self.owner - - current_A: uint256 = self._A() - self.initial_A = current_A - self.future_A = current_A - self.initial_A_time = block.timestamp - self.future_A_time = block.timestamp - # now (block.timestamp < t1) is always False, so we return saved A - - log.StopRampA(current_A, block.timestamp) - - -@public -def commit_new_fee(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 - - _deadline: timestamp = block.timestamp + admin_actions_delay - self.admin_actions_deadline = _deadline - self.future_fee = new_fee - self.future_admin_fee = new_admin_fee - - log.CommitNewFee(_deadline, new_fee, new_admin_fee) - - -@public -def apply_new_fee(): - assert msg.sender == self.owner - assert self.admin_actions_deadline <= block.timestamp\ - and self.admin_actions_deadline > 0 - - self.admin_actions_deadline = 0 - _fee: uint256 = self.future_fee - _admin_fee: uint256 = self.future_admin_fee - self.fee = _fee - self.admin_fee = _admin_fee - - log.NewFee(_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 = yERC20(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(yERC20(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 diff --git a/contracts/pools/pax/pooldata.json b/contracts/pools/pax/pooldata.json deleted file mode 100644 index e5328384..00000000 --- a/contracts/pools/pax/pooldata.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "lp_contract": "CurveTokenV1", - "wrapped_contract": "yERC20", - "swap_address": "0x06364f10B501e868329afBc005b3492902d6C763", - "lp_token_address": "0xD905e2eaeBe188fc92179b6350807D8bd91Db0D8", - "zap_address": "0xa50ccc70b6a011cffddf45057e39679379187287", - "gauge_addresses": ["0x64E3C23bfc40722d3B649844055F1D51c1ac041d"], - "lp_constructor": { - "name": "Curve.fi DAI/USDC/USDT/PAX", - "symbol": "ypaxCrv" - }, - "coins": [ - { - "name": "ycDAI", - "underlying_name": "DAI", - "decimals": 18, - "tethered": false, - "wrapped_decimals": 18, - "underlying_address": "0x6b175474e89094c44da98b954eedeac495271d0f", - "wrapped_address": "0x99d1fa417f94dcd62bfe781a1213c092a47041bc" - }, - { - "name": "ycUSDC", - "underlying_name": "USDC", - "decimals": 6, - "tethered": false, - "wrapped_decimals": 6, - "underlying_address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", - "wrapped_address": "0x9777d7e2b60bb01759d0e2f8be2095df444cb07e" - }, - { - "name": "ycUSDT", - "underlying_name": "USDT", - "decimals": 6, - "tethered": true, - "wrapped_decimals": 6, - "underlying_address": "0xdac17f958d2ee523a2206206994597c13d831ec7", - "wrapped_address": "0x1be5d71f2da660bfdee8012ddc58d024448a0a59" - }, - { - "name": "PAX", - "decimals": 18, - "tethered": false, - "underlying_address": "0x8e870d67f660d95d5be530380d0ec0bd388289e1" - } - ] -} diff --git a/contracts/pools/pbtc/DepositPBTC.vy b/contracts/pools/pbtc/DepositPBTC.vy deleted file mode 100644 index b9e7c588..00000000 --- a/contracts/pools/pbtc/DepositPBTC.vy +++ /dev/null @@ -1,366 +0,0 @@ -# @version 0.2.8 -""" -@title "Zap" Depositer for Curve pBTC pool -@author Curve.Fi -@license Copyright (c) Curve.Fi, 2020 - all rights reserved -""" - -from vyper.interfaces import ERC20 - - -interface CurveMeta: - def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256) -> 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) -> 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 base_pool() -> address: 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: int128) -> 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 - - -pool: public(address) -token: public(address) -base_pool: public(address) - -coins: public(address[N_COINS]) -base_coins: public(address[BASE_N_COINS]) - - -@external -def __init__(_pool: address, _token: address): - """ - @notice Contract constructor - @param _pool Metapool address - @param _token Pool LP token address - """ - self.pool = _pool - self.token = _token - _base_pool: address = CurveMeta(_pool).base_pool() - self.base_pool = _base_pool - - for i in range(N_COINS): - coin: address = CurveMeta(_pool).coins(i) - self.coins[i] = coin - # approve coins for infinite transfers - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("approve(address,uint256)"), - convert(_pool, bytes32), - convert(MAX_UINT256, bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) - - for i in range(BASE_N_COINS): - coin: address = CurveBase(_base_pool).coins(i) - self.base_coins[i] = coin - # approve underlying coins for infinite transfers - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("approve(address,uint256)"), - convert(_base_pool, bytes32), - convert(MAX_UINT256, bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) - - -@external -def add_liquidity(amounts: uint256[N_ALL_COINS], min_mint_amount: uint256) -> uint256: - """ - @notice Wrap underlying coins and deposit them in the pool - @param amounts List of amounts of underlying coins to deposit - @param min_mint_amount Minimum amount of LP tokens to mint from the deposit - @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 - - # Transfer all coins in - for i in range(N_ALL_COINS): - amount: uint256 = amounts[i] - if amount == 0: - continue - coin: address = ZERO_ADDRESS - if i < MAX_COIN: - coin = self.coins[i] - meta_amounts[i] = amount - else: - x: int128 = i - MAX_COIN - coin = self.base_coins[x] - base_amounts[x] = amount - deposit_base = True - # "safeTransferFrom" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transferFrom(address,address,uint256)"), - convert(msg.sender, bytes32), - convert(self, bytes32), - convert(amount, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransferFrom" - - # Deposit to the base pool - if deposit_base: - CurveBase(self.base_pool).add_liquidity(base_amounts, 0) - meta_amounts[MAX_COIN] = ERC20(self.coins[MAX_COIN]).balanceOf(self) - - # Deposit to the meta pool - CurveMeta(self.pool).add_liquidity(meta_amounts, min_mint_amount) - - # Transfer meta token back - _lp_token: address = self.token - _lp_amount: uint256 = ERC20(_lp_token).balanceOf(self) - assert ERC20(_lp_token).transfer(msg.sender, _lp_amount) - - return _lp_amount - - -@external -def remove_liquidity(_amount: uint256, min_amounts: uint256[N_ALL_COINS]) -> uint256[N_ALL_COINS]: - """ - @notice Withdraw and unwrap coins from the pool - @dev Withdrawal amounts are based on current deposit ratios - @param _amount Quantity of LP tokens to burn in the withdrawal - @param min_amounts Minimum amounts of underlying coins to receive - @return List of amounts of underlying coins that were withdrawn - """ - _token: address = self.token - assert ERC20(_token).transferFrom(msg.sender, self, _amount) - - min_amounts_meta: uint256[N_COINS] = empty(uint256[N_COINS]) - 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 - for i in range(MAX_COIN): - min_amounts_meta[i] = min_amounts[i] - CurveMeta(self.pool).remove_liquidity(_amount, min_amounts_meta) - - # Withdraw from base - _base_amount: uint256 = ERC20(self.coins[MAX_COIN]).balanceOf(self) - for i in range(BASE_N_COINS): - min_amounts_base[i] = min_amounts[MAX_COIN+i] - CurveBase(self.base_pool).remove_liquidity(_base_amount, min_amounts_base) - - # Transfer all coins out - for i in range(N_ALL_COINS): - coin: address = ZERO_ADDRESS - if i < MAX_COIN: - coin = self.coins[i] - else: - coin = self.base_coins[i - MAX_COIN] - amounts[i] = ERC20(coin).balanceOf(self) - # "safeTransfer" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(amounts[i], bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - return amounts - - -@external -def remove_liquidity_one_coin(_token_amount: uint256, i: int128, _min_amount: uint256) -> uint256: - """ - @notice Withdraw and unwrap a single coin from the pool - @param _token_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 - @return Amount of underlying coin received - """ - assert ERC20(self.token).transferFrom(msg.sender, self, _token_amount) - - coin: address = ZERO_ADDRESS - if i < MAX_COIN: - coin = self.coins[i] - # Withdraw a metapool coin - CurveMeta(self.pool).remove_liquidity_one_coin(_token_amount, i, _min_amount) - else: - coin = self.base_coins[i - MAX_COIN] - # Withdraw a base pool coin - CurveMeta(self.pool).remove_liquidity_one_coin(_token_amount, MAX_COIN, 0) - CurveBase(self.base_pool).remove_liquidity_one_coin( - ERC20(self.coins[MAX_COIN]).balanceOf(self), i-MAX_COIN, _min_amount - ) - - # Tranfer the coin out - coin_amount: uint256 = ERC20(coin).balanceOf(self) - # "safeTransfer" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(coin_amount, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - return coin_amount - - -@external -def remove_liquidity_imbalance(amounts: uint256[N_ALL_COINS], max_burn_amount: uint256) -> uint256: - """ - @notice Withdraw coins from the pool in an imbalanced amount - @param amounts List of amounts of underlying coins to withdraw - @param max_burn_amount Maximum amount of LP token to burn in the withdrawal. - This value cannot exceed the caller's LP token balance. - @return Actual amount of the LP token burned in the withdrawal - """ - _base_pool: address = self.base_pool - _meta_pool: address = self.pool - _base_coins: address[BASE_N_COINS] = self.base_coins - _meta_coins: address[N_COINS] = self.coins - _lp_token: address = self.token - - 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 - assert ERC20(_lp_token).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]) - leftover_amounts: uint256[N_COINS] = empty(uint256[N_COINS]) - - # Prepare quantities - for i in range(MAX_COIN): - amounts_meta[i] = amounts[i] - - for i in range(BASE_N_COINS): - amount: uint256 = amounts[MAX_COIN + i] - if amount != 0: - amounts_base[i] = amount - withdraw_base = True - - if withdraw_base: - amounts_meta[MAX_COIN] = CurveBase(self.base_pool).calc_token_amount(amounts_base, False) - amounts_meta[MAX_COIN] += amounts_meta[MAX_COIN] * fee / FEE_DENOMINATOR + 1 - - # Remove liquidity and deposit leftovers back - CurveMeta(_meta_pool).remove_liquidity_imbalance(amounts_meta, max_burn_amount) - if withdraw_base: - CurveBase(_base_pool).remove_liquidity_imbalance(amounts_base, amounts_meta[MAX_COIN]) - leftover_amounts[MAX_COIN] = ERC20(_meta_coins[MAX_COIN]).balanceOf(self) - if leftover_amounts[MAX_COIN] > 0: - CurveMeta(_meta_pool).add_liquidity(leftover_amounts, 0) - - # Transfer all coins out - for i in range(N_ALL_COINS): - coin: address = ZERO_ADDRESS - amount: uint256 = 0 - if i < MAX_COIN: - coin = _meta_coins[i] - amount = amounts_meta[i] - else: - coin = _base_coins[i - MAX_COIN] - amount = amounts_base[i - MAX_COIN] - # "safeTransfer" which works for ERC20s which return bool or not - if amount > 0: - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(amount, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - # Transfer the leftover LP token out - leftover: uint256 = ERC20(_lp_token).balanceOf(self) - if leftover > 0: - assert ERC20(_lp_token).transfer(msg.sender, leftover) - - return max_burn_amount - leftover - - -@view -@external -def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - """ - @notice Calculate the amount received when withdrawing and unwrapping a single coin - @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(self.pool).calc_withdraw_one_coin(_token_amount, i) - else: - _base_tokens: uint256 = CurveMeta(self.pool).calc_withdraw_one_coin(_token_amount, MAX_COIN) - return CurveBase(self.base_pool).calc_withdraw_one_coin(_base_tokens, i-MAX_COIN) - - -@view -@external -def calc_token_amount(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 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]) - - for i in range(MAX_COIN): - meta_amounts[i] = amounts[i] - - for i in range(BASE_N_COINS): - base_amounts[i] = amounts[i + MAX_COIN] - - _base_tokens: uint256 = CurveBase(self.base_pool).calc_token_amount(base_amounts, is_deposit) - meta_amounts[MAX_COIN] = _base_tokens - - return CurveMeta(self.pool).calc_token_amount(meta_amounts, is_deposit) diff --git a/contracts/pools/pbtc/README.md b/contracts/pools/pbtc/README.md deleted file mode 100644 index c25102a7..00000000 --- a/contracts/pools/pbtc/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# curve-contract/contracts/pools/pbtc - -[Curve pBTC metapool](https://www.curve.fi/pbtc), allowing swaps via the Curve [sBTC pool](../sbtc). - -## Contracts - -* [`DepositPBTC`](DepositPBTC.vy): Depositor contract, used to wrap underlying tokens prior to depositing them into the pool -* [`StableSwapPBTC`](StableSwapPBTC.vy): Curve stablecoin AMM contract - -## Deployments - -[`CurveContractV2`](../../tokens/CurveTokenV2.vy): [0xDE5331AC4B3630f94853Ff322B66407e0D6331E8](https://etherscan.io/address/0xDE5331AC4B3630f94853Ff322B66407e0D6331E8) -* [`DepositPBTC`](DepositPBTC.vy): [0x11F419AdAbbFF8d595E7d5b223eee3863Bb3902C](https://etherscan.io/address/0x11F419AdAbbFF8d595E7d5b223eee3863Bb3902C) -* [`LiquidityGaugeV2`](https://github.com/curvefi/curve-dao-contracts/blob/master/contracts/gauges/LiquidityGaugeV2.vy): [0xd7d147c6Bb90A718c3De8C0568F9B560C79fa416](https://etherscan.io/address/0xd7d147c6Bb90A718c3De8C0568F9B560C79fa416) -* [`StableSwapPBTC`](StableSwapPBTC.vy): [0x7F55DDe206dbAD629C080068923b36fe9D6bDBeF](https://etherscan.io/address/0x7F55DDe206dbAD629C080068923b36fe9D6bDBeF) - -## Stablecoins - -Curve pBTC metapool utilizes the supports swaps between the following assets: - -## Direct swaps - -Direct swaps are possible between pBTC and the Curve sBTC LP token. - -* `pBTC`: [0x5228a22e72ccC52d415EcFd199F99D0665E7733b](https://etherscan.io/address/0x5228a22e72ccC52d415EcFd199F99D0665E7733b) -* `sbtcCRV`: [0x075b1bb99792c9E1041bA13afEf80C91a1e70fB3](https://etherscan.io/address/0x075b1bb99792c9E1041bA13afEf80C91a1e70fB3) - -## Base Pool coins - -The sBTC LP token may be wrapped or unwrapped to provide swaps between pBTC and the following coins: - -* `renBTC`: [0xeb4c2781e4eba804ce9a9803c67d0893436bb27d](https://etherscan.io/address/0xeb4c2781e4eba804ce9a9803c67d0893436bb27d) -* `wBTC`: [0x2260fac5e5542a773aa44fbcfedf7c193bc2c599](https://etherscan.io/address/0x2260fac5e5542a773aa44fbcfedf7c193bc2c599) -* `sBTC`: [0xfe18be6b3bd88a2d2a7f928d00292e7a9963cfc6](https://etherscan.io/address/0xfe18be6b3bd88a2d2a7f928d00292e7a9963cfc6) diff --git a/contracts/pools/pbtc/StableSwapPBTC.vy b/contracts/pools/pbtc/StableSwapPBTC.vy deleted file mode 100644 index d72826d3..00000000 --- a/contracts/pools/pbtc/StableSwapPBTC.vy +++ /dev/null @@ -1,1076 +0,0 @@ -# @version 0.2.8 -""" -@title Curve pBTC Metapool -@author Curve.Fi -@license Copyright (c) Curve.Fi, 2020 - all rights reserved -@dev Utilizes sBTC pool to allow swaps between pBTC / rebBTC / wBTC / sBTC -""" - -from vyper.interfaces import ERC20 - -interface CurveToken: - def totalSupply() -> uint256: view - def mint(_to: address, _value: uint256) -> bool: nonpayable - def burnFrom(_to: address, _value: uint256) -> bool: nonpayable - -interface Curve: - def coins(i: int128) -> address: view - def get_virtual_price() -> uint256: view - def calc_token_amount(amounts: uint256[BASE_N_COINS], deposit: bool) -> uint256: view - def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: view - def fee() -> uint256: view - def get_dy(i: int128, j: int128, dx: uint256) -> uint256: view - def get_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256: view - def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256): nonpayable - def add_liquidity(amounts: uint256[BASE_N_COINS], min_mint_amount: uint256): nonpayable - def remove_liquidity_one_coin(_token_amount: uint256, i: int128, min_amount: uint256): nonpayable - - -# Events -event TokenExchange: - buyer: indexed(address) - sold_id: int128 - tokens_sold: uint256 - bought_id: int128 - tokens_bought: uint256 - -event TokenExchangeUnderlying: - buyer: indexed(address) - sold_id: int128 - tokens_sold: uint256 - bought_id: int128 - tokens_bought: uint256 - -event AddLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event RemoveLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - token_supply: uint256 - -event RemoveLiquidityOne: - provider: indexed(address) - token_amount: uint256 - coin_amount: uint256 - token_supply: uint256 - -event RemoveLiquidityImbalance: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event CommitNewAdmin: - deadline: indexed(uint256) - admin: indexed(address) - -event NewAdmin: - admin: indexed(address) - -event CommitNewFee: - deadline: indexed(uint256) - fee: uint256 - admin_fee: uint256 - -event NewFee: - fee: uint256 - admin_fee: uint256 - -event RampA: - old_A: uint256 - new_A: uint256 - initial_time: uint256 - future_time: uint256 - -event StopRampA: - A: uint256 - t: uint256 - - -N_COINS: constant(int128) = 2 -MAX_COIN: constant(int128) = N_COINS - 1 - -FEE_DENOMINATOR: constant(uint256) = 10 ** 10 -PRECISION: constant(uint256) = 10 ** 18 # The precision to convert to -PRECISION_MUL: constant(uint256[N_COINS]) = [1, 1] -RATES: constant(uint256[N_COINS]) = [1000000000000000000, 1000000000000000000] -BASE_N_COINS: constant(int128) = 3 - -# An asset which may have a transfer fee (renBTC) -FEE_ASSET: constant(address) = 0xEB4C2781e4ebA804CE9a9803C67d0893436bB27D - -MAX_ADMIN_FEE: constant(uint256) = 10 * 10 ** 9 -MAX_FEE: constant(uint256) = 5 * 10 ** 9 -MAX_A: constant(uint256) = 10 ** 6 -MAX_A_CHANGE: constant(uint256) = 10 - -ADMIN_ACTIONS_DELAY: constant(uint256) = 3 * 86400 -MIN_RAMP_TIME: constant(uint256) = 86400 - -coins: public(address[N_COINS]) -balances: public(uint256[N_COINS]) -fee: public(uint256) # fee * 1e10 -admin_fee: public(uint256) # admin_fee * 1e10 - -owner: public(address) -token: public(CurveToken) - -BASE_CACHE_EXPIRES: constant(int128) = 10 * 60 # 10 min -base_pool: public(address) -base_virtual_price: public(uint256) -base_cache_updated: public(uint256) -base_coins: public(address[BASE_N_COINS]) - -A_PRECISION: constant(uint256) = 100 -initial_A: public(uint256) -future_A: public(uint256) -initial_A_time: public(uint256) -future_A_time: public(uint256) - -admin_actions_deadline: public(uint256) -transfer_ownership_deadline: public(uint256) -future_fee: public(uint256) -future_admin_fee: public(uint256) -future_owner: public(address) - -is_killed: bool -kill_deadline: uint256 -KILL_DEADLINE_DT: constant(uint256) = 2 * 30 * 86400 - - -@external -def __init__( - _owner: address, - _coins: address[N_COINS], - _pool_token: address, - _base_pool: address, - _A: uint256, - _fee: uint256, - _admin_fee: uint256 -): - """ - @notice Contract constructor - @param _owner Contract owner address - @param _coins Addresses of ERC20 conracts of coins - @param _pool_token Address of the token representing LP share - @param _base_pool Address of the base pool (which will have a virtual price) - @param _A Amplification coefficient multiplied by n * (n - 1) - @param _fee Fee to charge for exchanges - @param _admin_fee Admin fee - """ - for i in range(N_COINS): - assert _coins[i] != ZERO_ADDRESS - self.coins = _coins - self.initial_A = _A * A_PRECISION - self.future_A = _A * A_PRECISION - self.fee = _fee - self.admin_fee = _admin_fee - self.owner = _owner - self.kill_deadline = block.timestamp + KILL_DEADLINE_DT - self.token = CurveToken(_pool_token) - - self.base_pool = _base_pool - self.base_virtual_price = Curve(_base_pool).get_virtual_price() - self.base_cache_updated = block.timestamp - for i in range(BASE_N_COINS): - _base_coin: address = Curve(_base_pool).coins(i) - self.base_coins[i] = _base_coin - - # approve underlying coins for infinite transfers - _response: Bytes[32] = raw_call( - _base_coin, - concat( - method_id("approve(address,uint256)"), - convert(_base_pool, bytes32), - convert(MAX_UINT256, bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) - - -@view -@internal -def _A() -> uint256: - """ - Handle ramping A up or down - """ - t1: uint256 = self.future_A_time - A1: uint256 = self.future_A - - if block.timestamp < t1: - A0: uint256 = self.initial_A - t0: uint256 = self.initial_A_time - # Expressions in uint256 cannot have negative numbers, thus "if" - if A1 > A0: - return A0 + (A1 - A0) * (block.timestamp - t0) / (t1 - t0) - else: - return A0 - (A0 - A1) * (block.timestamp - t0) / (t1 - t0) - - else: # when t1 == 0 or block.timestamp >= t1 - return A1 - - -@view -@external -def A() -> uint256: - return self._A() / A_PRECISION - - -@view -@external -def A_precise() -> uint256: - return self._A() - - -@view -@internal -def _xp(vp_rate: uint256) -> uint256[N_COINS]: - result: uint256[N_COINS] = RATES - result[MAX_COIN] = vp_rate # virtual price for the metacurrency - for i in range(N_COINS): - result[i] = result[i] * self.balances[i] / PRECISION - return result - - -@pure -@internal -def _xp_mem(vp_rate: uint256, _balances: uint256[N_COINS]) -> uint256[N_COINS]: - result: uint256[N_COINS] = RATES - result[MAX_COIN] = vp_rate # virtual price for the metacurrency - for i in range(N_COINS): - result[i] = result[i] * _balances[i] / PRECISION - return result - - -@internal -def _vp_rate() -> uint256: - if block.timestamp > self.base_cache_updated + BASE_CACHE_EXPIRES: - vprice: uint256 = Curve(self.base_pool).get_virtual_price() - self.base_virtual_price = vprice - self.base_cache_updated = block.timestamp - return vprice - else: - return self.base_virtual_price - - -@internal -@view -def _vp_rate_ro() -> uint256: - if block.timestamp > self.base_cache_updated + BASE_CACHE_EXPIRES: - return Curve(self.base_pool).get_virtual_price() - else: - return self.base_virtual_price - - -@pure -@internal -def get_D(xp: uint256[N_COINS], amp: uint256) -> uint256: - S: uint256 = 0 - Dprev: uint256 = 0 - for _x in xp: - S += _x - if S == 0: - return 0 - - D: uint256 = S - Ann: uint256 = amp * N_COINS - for _i in range(255): - D_P: uint256 = D - for _x in xp: - D_P = D_P * D / (_x * N_COINS) # If division by 0, this will be borked: only withdrawal will work. And that is good - Dprev = D - D = (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (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 - - -@view -@internal -def get_D_mem(vp_rate: uint256, _balances: uint256[N_COINS], amp: uint256) -> uint256: - xp: uint256[N_COINS] = self._xp_mem(vp_rate, _balances) - return self.get_D(xp, amp) - - -@view -@external -def get_virtual_price() -> uint256: - """ - @notice The current virtual price of the pool LP token - @dev Useful for calculating profits - @return LP token virtual price normalized to 1e18 - """ - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate_ro() - xp: uint256[N_COINS] = self._xp(vp_rate) - D: uint256 = self.get_D(xp, amp) - # 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 - - -@view -@external -def calc_token_amount(amounts: uint256[N_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 amounts Amount of each coin being deposited - @param is_deposit set True for deposits, False for withdrawals - @return Expected amount of LP tokens received - """ - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate_ro() - _balances: uint256[N_COINS] = self.balances - D0: uint256 = self.get_D_mem(vp_rate, _balances, amp) - for i in range(N_COINS): - if is_deposit: - _balances[i] += amounts[i] - else: - _balances[i] -= amounts[i] - D1: uint256 = self.get_D_mem(vp_rate, _balances, amp) - token_amount: uint256 = self.token.totalSupply() - diff: uint256 = 0 - if is_deposit: - diff = D1 - D0 - else: - diff = D0 - D1 - return diff * token_amount / D0 - - -@external -@nonreentrant('lock') -def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256) -> uint256: - """ - @notice Deposit coins into the pool - @param amounts List of amounts of coins to deposit - @param min_mint_amount Minimum amount of LP tokens to mint from the deposit - @return Amount of LP tokens received by depositing - """ - assert not self.is_killed # dev: is killed - - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate() - token_supply: uint256 = self.token.totalSupply() - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - _admin_fee: uint256 = self.admin_fee - - # Initial invariant - D0: uint256 = 0 - old_balances: uint256[N_COINS] = self.balances - if token_supply > 0: - D0 = self.get_D_mem(vp_rate, old_balances, amp) - new_balances: uint256[N_COINS] = old_balances - - for i in range(N_COINS): - if token_supply == 0: - assert amounts[i] > 0 # dev: initial deposit requires all coins - # balances store amounts of c-tokens - new_balances[i] = old_balances[i] + amounts[i] - - # Invariant after change - D1: uint256 = self.get_D_mem(vp_rate, new_balances, amp) - assert D1 > D0 - - # We need to recalculate the invariant accounting for fees - # to calculate fair user's share - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - 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(vp_rate, new_balances, amp) - 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 amounts[i] > 0: - assert ERC20(self.coins[i]).transferFrom(msg.sender, self, amounts[i]) # dev: failed transfer - - # Mint pool tokens - self.token.mint(msg.sender, mint_amount) - - log AddLiquidity(msg.sender, amounts, fees, D1, token_supply + mint_amount) - - return mint_amount - - -@view -@internal -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 # dev: same coin - assert j >= 0 # dev: j below zero - assert j < N_COINS # dev: j above N_COINS - - # should be unreachable, but good for safety - assert i >= 0 - assert i < N_COINS - - amp: uint256 = self._A() - D: uint256 = self.get_D(xp_, amp) - - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: uint256 = 0 - c: uint256 = D - Ann: uint256 = amp * N_COINS - - 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 * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann # - D - 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 - - -@view -@external -def get_dy(i: int128, j: int128, dx: uint256) -> uint256: - # dx and dy in c-units - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = self._vp_rate_ro() - xp: uint256[N_COINS] = self._xp(rates[MAX_COIN]) - - x: uint256 = xp[i] + (dx * rates[i] / PRECISION) - y: uint256 = self.get_y(i, j, x, xp) - dy: uint256 = xp[j] - y - 1 - _fee: uint256 = self.fee * dy / FEE_DENOMINATOR - return (dy - _fee) * PRECISION / rates[j] - - -@view -@external -def get_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256: - # dx and dy in underlying units - vp_rate: uint256 = self._vp_rate_ro() - xp: uint256[N_COINS] = self._xp(vp_rate) - precisions: uint256[N_COINS] = PRECISION_MUL - _base_pool: address = self.base_pool - - # Use base_i or base_j if they are >= 0 - base_i: int128 = i - MAX_COIN - base_j: int128 = j - MAX_COIN - meta_i: int128 = MAX_COIN - meta_j: int128 = MAX_COIN - if base_i < 0: - meta_i = i - if base_j < 0: - meta_j = j - - x: uint256 = 0 - if base_i < 0: - x = xp[i] + dx * precisions[i] - else: - if base_j < 0: - # i is from BasePool - # At first, get the amount of pool tokens - base_inputs: uint256[BASE_N_COINS] = empty(uint256[BASE_N_COINS]) - base_inputs[base_i] = dx - # Token amount transformed to underlying "dollars" - x = Curve(_base_pool).calc_token_amount(base_inputs, True) * vp_rate / PRECISION - # Accounting for deposit/withdraw fees approximately - x -= x * Curve(_base_pool).fee() / (2 * FEE_DENOMINATOR) - # Adding number of pool tokens - x += xp[MAX_COIN] - else: - # If both are from the base pool - return Curve(_base_pool).get_dy(base_i, base_j, dx) - - # This pool is involved only when in-pool assets are used - y: uint256 = self.get_y(meta_i, meta_j, x, xp) - dy: uint256 = xp[meta_j] - y - 1 - dy = (dy - self.fee * dy / FEE_DENOMINATOR) - - # If output is going via the metapool - if base_j < 0: - dy /= precisions[meta_j] - else: - # j is from BasePool - # The fee is already accounted for - dy = Curve(_base_pool).calc_withdraw_one_coin(dy * PRECISION / vp_rate, base_j) - - return dy - - -@external -@nonreentrant('lock') -def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256) -> uint256: - """ - @notice Perform an exchange between two coins - @dev Index values can be found via the `coins` public getter method - @param i Index value for the coin to send - @param j Index valie of the coin to recieve - @param dx Amount of `i` being exchanged - @param min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - assert not self.is_killed # dev: is killed - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = self._vp_rate() - - old_balances: uint256[N_COINS] = self.balances - xp: uint256[N_COINS] = self._xp_mem(rates[MAX_COIN], old_balances) - - x: uint256 = xp[i] + dx * rates[i] / PRECISION - y: uint256 = self.get_y(i, j, x, xp) - - dy: uint256 = xp[j] - y - 1 # -1 just in case there were some rounding errors - dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR - - # Convert all to real units - dy = (dy - dy_fee) * PRECISION / rates[j] - assert dy >= min_dy, "Too few coins in result" - - dy_admin_fee: uint256 = dy_fee * self.admin_fee / FEE_DENOMINATOR - dy_admin_fee = dy_admin_fee * PRECISION / rates[j] - - # Change balances exactly in same way as we change actual ERC20 coin amounts - self.balances[i] = old_balances[i] + dx - # When rounding errors happen, we undercharge admin fee in favor of LP - self.balances[j] = old_balances[j] - dy - dy_admin_fee - - assert ERC20(self.coins[i]).transferFrom(msg.sender, self, dx) - assert ERC20(self.coins[j]).transfer(msg.sender, dy) - - log TokenExchange(msg.sender, i, dx, j, dy) - - return dy - - -@external -@nonreentrant('lock') -def exchange_underlying(i: int128, j: int128, dx: uint256, min_dy: uint256) -> uint256: - """ - @notice Perform an exchange between two underlying coins - @dev Index values can be found via the `underlying_coins` public getter method - @param i Index value for the underlying coin to send - @param j Index valie of the underlying coin to recieve - @param dx Amount of `i` being exchanged - @param min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - assert not self.is_killed # dev: is killed - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = self._vp_rate() - _base_pool: address = self.base_pool - - # Use base_i or base_j if they are >= 0 - base_i: int128 = i - MAX_COIN - base_j: int128 = j - MAX_COIN - meta_i: int128 = MAX_COIN - meta_j: int128 = MAX_COIN - if base_i < 0: - meta_i = i - if base_j < 0: - meta_j = j - dy: uint256 = 0 - - # Addresses for input and output coins - input_coin: address = ZERO_ADDRESS - if base_i < 0: - input_coin = self.coins[i] - else: - input_coin = self.base_coins[base_i] - output_coin: address = ZERO_ADDRESS - if base_j < 0: - output_coin = self.coins[j] - else: - output_coin = self.base_coins[base_j] - - # Handle potential Tether fees - dx_w_fee: uint256 = dx - if input_coin == FEE_ASSET: - dx_w_fee = ERC20(FEE_ASSET).balanceOf(self) - # "safeTransferFrom" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - input_coin, - concat( - method_id("transferFrom(address,address,uint256)"), - convert(msg.sender, bytes32), - convert(self, bytes32), - convert(dx, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransferFrom" - # Handle potential Tether fees - if input_coin == FEE_ASSET: - dx_w_fee = ERC20(FEE_ASSET).balanceOf(self) - dx_w_fee - - if base_i < 0 or base_j < 0: - old_balances: uint256[N_COINS] = self.balances - xp: uint256[N_COINS] = self._xp_mem(rates[MAX_COIN], old_balances) - - x: uint256 = 0 - if base_i < 0: - x = xp[i] + dx_w_fee * rates[i] / PRECISION - else: - # i is from BasePool - # At first, get the amount of pool tokens - base_inputs: uint256[BASE_N_COINS] = empty(uint256[BASE_N_COINS]) - base_inputs[base_i] = dx_w_fee - coin_i: address = self.coins[MAX_COIN] - # Deposit and measure delta - x = ERC20(coin_i).balanceOf(self) - Curve(_base_pool).add_liquidity(base_inputs, 0) - # Need to convert pool token to "virtual" units using rates - # dx is also different now - dx_w_fee = ERC20(coin_i).balanceOf(self) - x - x = dx_w_fee * rates[MAX_COIN] / PRECISION - # Adding number of pool tokens - x += xp[MAX_COIN] - - y: uint256 = self.get_y(meta_i, meta_j, x, xp) - - # Either a real coin or token - dy = xp[meta_j] - y - 1 # -1 just in case there were some rounding errors - dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR - - # Convert all to real units - # Works for both pool coins and real coins - dy = (dy - dy_fee) * PRECISION / rates[meta_j] - - dy_admin_fee: uint256 = dy_fee * self.admin_fee / FEE_DENOMINATOR - dy_admin_fee = dy_admin_fee * PRECISION / rates[meta_j] - - # Change balances exactly in same way as we change actual ERC20 coin amounts - self.balances[meta_i] = old_balances[meta_i] + dx_w_fee - # When rounding errors happen, we undercharge admin fee in favor of LP - self.balances[meta_j] = old_balances[meta_j] - dy - dy_admin_fee - - # Withdraw from the base pool if needed - if base_j >= 0: - out_amount: uint256 = ERC20(output_coin).balanceOf(self) - Curve(_base_pool).remove_liquidity_one_coin(dy, base_j, 0) - dy = ERC20(output_coin).balanceOf(self) - out_amount - - assert dy >= min_dy, "Too few coins in result" - - else: - # If both are from the base pool - dy = ERC20(output_coin).balanceOf(self) - Curve(_base_pool).exchange(base_i, base_j, dx_w_fee, min_dy) - dy = ERC20(output_coin).balanceOf(self) - dy - - # "safeTransfer" which works for ERC20s which return bool or not - _response = raw_call( - output_coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(dy, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - log TokenExchangeUnderlying(msg.sender, i, dx, j, dy) - - return dy - - -@external -@nonreentrant('lock') -def remove_liquidity(_amount: uint256, min_amounts: uint256[N_COINS]) -> uint256[N_COINS]: - """ - @notice Withdraw coins from the pool - @dev Withdrawal amounts are based on current deposit ratios - @param _amount Quantity of LP tokens to burn in the withdrawal - @param min_amounts Minimum amounts of underlying coins to receive - @return List of amounts of coins that were withdrawn - """ - total_supply: uint256 = self.token.totalSupply() - amounts: uint256[N_COINS] = empty(uint256[N_COINS]) - fees: uint256[N_COINS] = empty(uint256[N_COINS]) # Fees are unused but we've got them historically in event - - for i in range(N_COINS): - value: uint256 = self.balances[i] * _amount / total_supply - assert value >= min_amounts[i], "Too few coins in result" - self.balances[i] -= value - amounts[i] = value - assert ERC20(self.coins[i]).transfer(msg.sender, value) - - self.token.burnFrom(msg.sender, _amount) # dev: insufficient funds - - log RemoveLiquidity(msg.sender, amounts, fees, total_supply - _amount) - - return amounts - - -@external -@nonreentrant('lock') -def remove_liquidity_imbalance(amounts: uint256[N_COINS], max_burn_amount: uint256) -> uint256: - """ - @notice Withdraw coins from the pool in an imbalanced amount - @param amounts List of amounts of underlying coins to withdraw - @param max_burn_amount Maximum amount of LP token to burn in the withdrawal - @return Actual amount of the LP token burned in the withdrawal - """ - assert not self.is_killed # dev: is killed - - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate() - - token_supply: uint256 = self.token.totalSupply() - assert token_supply != 0 # dev: zero total supply - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - _admin_fee: uint256 = self.admin_fee - - old_balances: uint256[N_COINS] = self.balances - new_balances: uint256[N_COINS] = old_balances - D0: uint256 = self.get_D_mem(vp_rate, old_balances, amp) - for i in range(N_COINS): - new_balances[i] -= amounts[i] - D1: uint256 = self.get_D_mem(vp_rate, new_balances, amp) - - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - 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(vp_rate, new_balances, amp) - - token_amount: uint256 = (D0 - D2) * token_supply / D0 - assert token_amount != 0 # dev: zero tokens burned - token_amount += 1 # In case of rounding errors - make it unfavorable for the "attacker" - assert token_amount <= max_burn_amount, "Slippage screwed you" - - self.token.burnFrom(msg.sender, token_amount) # dev: insufficient funds - for i in range(N_COINS): - if amounts[i] != 0: - assert ERC20(self.coins[i]).transfer(msg.sender, amounts[i]) - - log RemoveLiquidityImbalance(msg.sender, amounts, fees, D1, token_supply - token_amount) - - return token_amount - - -@view -@internal -def get_y_D(A_: uint256, i: int128, xp: uint256[N_COINS], D: uint256) -> uint256: - """ - Calculate x[i] if one reduces D from being calculated for xp to D - - Done by solving quadratic equation iteratively. - x_1**2 + x1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) - x_1**2 + b*x_1 = c - - x_1 = (x_1**2 + c) / (2*x_1 + b) - """ - # x in the input is converted to the same price/precision - - assert i >= 0 # dev: i below zero - assert i < N_COINS # dev: i above N_COINS - - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: uint256 = 0 - - c: uint256 = D - Ann: uint256 = A_ * N_COINS - - for _i in range(N_COINS): - if _i != i: - _x = xp[_i] - else: - continue - S_ += _x - c = c * D / (_x * N_COINS) - c = c * D * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann - 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 - - -@view -@internal -def _calc_withdraw_one_coin(_token_amount: uint256, i: int128, vp_rate: uint256) -> (uint256, uint256, uint256): - # First, need to calculate - # * Get current D - # * Solve Eqn against y_i for D - _token_amount - amp: uint256 = self._A() - xp: uint256[N_COINS] = self._xp(vp_rate) - D0: uint256 = self.get_D(xp, amp) - - total_supply: uint256 = self.token.totalSupply() - D1: uint256 = D0 - _token_amount * D0 / total_supply - new_y: uint256 = self.get_y_D(amp, i, xp, D1) - - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = vp_rate - - xp_reduced: uint256[N_COINS] = xp - dy_0: uint256 = (xp[i] - new_y) * PRECISION / rates[i] # w/o fees - - for j in range(N_COINS): - dx_expected: uint256 = 0 - if j == i: - dx_expected = xp[j] * D1 / D0 - new_y - else: - dx_expected = xp[j] - xp[j] * D1 / D0 - xp_reduced[j] -= _fee * dx_expected / FEE_DENOMINATOR - - dy: uint256 = xp_reduced[i] - self.get_y_D(amp, i, xp_reduced, D1) - dy = (dy - 1) * PRECISION / rates[i] # Withdraw less to account for rounding errors - - return dy, dy_0 - dy, total_supply - - -@view -@external -def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - """ - @notice Calculate the amount received when withdrawing a single coin - @param _token_amount Amount of LP tokens to burn in the withdrawal - @param i Index value of the coin to withdraw - @return Amount of coin received - """ - vp_rate: uint256 = self._vp_rate_ro() - return self._calc_withdraw_one_coin(_token_amount, i, vp_rate)[0] - - -@external -@nonreentrant('lock') -def remove_liquidity_one_coin(_token_amount: uint256, i: int128, _min_amount: uint256) -> uint256: - """ - @notice Withdraw a single coin from the pool - @param _token_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 coin to receive - @return Amount of coin received - """ - assert not self.is_killed # dev: is killed - - vp_rate: uint256 = self._vp_rate() - dy: uint256 = 0 - dy_fee: uint256 = 0 - total_supply: uint256 = 0 - dy, dy_fee, total_supply = self._calc_withdraw_one_coin(_token_amount, i, vp_rate) - assert dy >= _min_amount, "Not enough coins removed" - - self.balances[i] -= (dy + dy_fee * self.admin_fee / FEE_DENOMINATOR) - self.token.burnFrom(msg.sender, _token_amount) # dev: insufficient funds - assert ERC20(self.coins[i]).transfer(msg.sender, dy) - - log RemoveLiquidityOne(msg.sender, _token_amount, dy, total_supply - _token_amount) - - return dy - - -### Admin functions ### -@external -def ramp_A(_future_A: uint256, _future_time: uint256): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.initial_A_time + MIN_RAMP_TIME - assert _future_time >= block.timestamp + MIN_RAMP_TIME # dev: insufficient time - - _initial_A: uint256 = self._A() - _future_A_p: uint256 = _future_A * A_PRECISION - - assert _future_A > 0 and _future_A < MAX_A - if _future_A_p < _initial_A: - assert _future_A_p * MAX_A_CHANGE >= _initial_A - else: - assert _future_A_p <= _initial_A * MAX_A_CHANGE - - self.initial_A = _initial_A - self.future_A = _future_A_p - self.initial_A_time = block.timestamp - self.future_A_time = _future_time - - log RampA(_initial_A, _future_A_p, block.timestamp, _future_time) - - -@external -def stop_ramp_A(): - assert msg.sender == self.owner # dev: only owner - - current_A: uint256 = self._A() - self.initial_A = current_A - self.future_A = current_A - self.initial_A_time = block.timestamp - self.future_A_time = block.timestamp - # now (block.timestamp < t1) is always False, so we return saved A - - log StopRampA(current_A, block.timestamp) - - -@external -def commit_new_fee(new_fee: uint256, new_admin_fee: uint256): - assert msg.sender == self.owner # dev: only owner - assert self.admin_actions_deadline == 0 # dev: active action - assert new_fee <= MAX_FEE # dev: fee exceeds maximum - assert new_admin_fee <= MAX_ADMIN_FEE # dev: admin fee exceeds maximum - - _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.admin_actions_deadline = _deadline - self.future_fee = new_fee - self.future_admin_fee = new_admin_fee - - log CommitNewFee(_deadline, new_fee, new_admin_fee) - - -@external -def apply_new_fee(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.admin_actions_deadline # dev: insufficient time - assert self.admin_actions_deadline != 0 # dev: no active action - - self.admin_actions_deadline = 0 - _fee: uint256 = self.future_fee - _admin_fee: uint256 = self.future_admin_fee - self.fee = _fee - self.admin_fee = _admin_fee - - log NewFee(_fee, _admin_fee) - - -@external -def revert_new_parameters(): - assert msg.sender == self.owner # dev: only owner - - self.admin_actions_deadline = 0 - - -@external -def commit_transfer_ownership(_owner: address): - assert msg.sender == self.owner # dev: only owner - assert self.transfer_ownership_deadline == 0 # dev: active transfer - - _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.transfer_ownership_deadline = _deadline - self.future_owner = _owner - - log CommitNewAdmin(_deadline, _owner) - - -@external -def apply_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.transfer_ownership_deadline # dev: insufficient time - assert self.transfer_ownership_deadline != 0 # dev: no active transfer - - self.transfer_ownership_deadline = 0 - _owner: address = self.future_owner - self.owner = _owner - - log NewAdmin(_owner) - - -@external -def revert_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - - self.transfer_ownership_deadline = 0 - - -@view -@external -def admin_balances(i: uint256) -> uint256: - return ERC20(self.coins[i]).balanceOf(self) - self.balances[i] - - -@external -def withdraw_admin_fees(): - assert msg.sender == self.owner # dev: only owner - - for i in range(N_COINS): - c: address = self.coins[i] - value: uint256 = ERC20(c).balanceOf(self) - self.balances[i] - if value > 0: - assert ERC20(c).transfer(msg.sender, value) - - -@external -def donate_admin_fees(): - assert msg.sender == self.owner # dev: only owner - for i in range(N_COINS): - self.balances[i] = ERC20(self.coins[i]).balanceOf(self) - - -@external -def kill_me(): - assert msg.sender == self.owner # dev: only owner - assert self.kill_deadline > block.timestamp # dev: deadline has passed - self.is_killed = True - - -@external -def unkill_me(): - assert msg.sender == self.owner # dev: only owner - self.is_killed = False diff --git a/contracts/pools/pbtc/pooldata.json b/contracts/pools/pbtc/pooldata.json deleted file mode 100644 index 001902cd..00000000 --- a/contracts/pools/pbtc/pooldata.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "base_pool": "sbtc", - "pool_types": ["meta"], - "swap_address": "0x7F55DDe206dbAD629C080068923b36fe9D6bDBeF", - "lp_token_address": "0xDE5331AC4B3630f94853Ff322B66407e0D6331E8", - "zap_address": "0x11F419AdAbbFF8d595E7d5b223eee3863Bb3902C", - "gauge_addresses": ["0xd7d147c6Bb90A718c3De8C0568F9B560C79fa416"], - "lp_contract": "CurveTokenV2", - "lp_constructor": { - "symbol": "pBTC/sbtcCRV", - "name": "Curve.fi pBTC/sbtcCRV" - }, - "swap_constructor": { - "_A": 200, - "_fee": 4000000, - "_admin_fee": 5000000000 - }, - "coins": [ - { - "name": "pBTC", - "decimals": 18, - "tethered": false, - "wrapped": false, - "underlying_address": "0x5228a22e72ccC52d415EcFd199F99D0665E7733b" - }, - { - "name": "sbtcCRV", - "decimals": 18, - "wrapped": false, - "base_pool_token": true, - "underlying_address": "0x075b1bb99792c9E1041bA13afEf80C91a1e70fB3" - } - ], - "testing": { - "initial_amount": 100 - } -} diff --git a/contracts/pools/ren/README.md b/contracts/pools/ren/README.md deleted file mode 100644 index e56b483f..00000000 --- a/contracts/pools/ren/README.md +++ /dev/null @@ -1,20 +0,0 @@ -# curve-contract/contracts/pools/ren - -[Curve RenBTC pool](https://www.curve.fi/ren). This is a no-lending pool. - -## Contracts - -* [`StableSwapRen`](StableSwapRen.vy): Curve stablecoin AMM contract - -## Deployments - -* [`CurveContractV1`](../../tokens/CurveTokenV1.vy): [0x49849C98ae39Fff122806C06791Fa73784FB3675](https://etherscan.io/address/0x49849C98ae39Fff122806C06791Fa73784FB3675) -* [`LiquidityGauge`](../../gauges/LiquidityGauge.vy): [0xB1F2cdeC61db658F091671F5f199635aEF202CAC](https://etherscan.io/address/0xB1F2cdeC61db658F091671F5f199635aEF202CAC) -* [`StableSwapRen`](StableSwapRen.vy): [0x93054188d876f558f4a66B2EF1d97d16eDf0895B](https://etherscan.io/address/0x93054188d876f558f4a66B2EF1d97d16eDf0895B) - -## Stablecoins - -Curve RenBTC pool supports swaps between the following stablecoins: - -* `renBTC`: [0xeb4c2781e4eba804ce9a9803c67d0893436bb27d](https://etherscan.io/address/0xeb4c2781e4eba804ce9a9803c67d0893436bb27d) -* `wBTC`: [0x2260fac5e5542a773aa44fbcfedf7c193bc2c599](https://etherscan.io/address/0x2260fac5e5542a773aa44fbcfedf7c193bc2c599) diff --git a/contracts/pools/ren/StableSwapRen.vy b/contracts/pools/ren/StableSwapRen.vy deleted file mode 100644 index 3cd1ee18..00000000 --- a/contracts/pools/ren/StableSwapRen.vy +++ /dev/null @@ -1,743 +0,0 @@ -# @version 0.1.0b17 -# (c) Curve.Fi, 2020 -# Pools for renBTC/wBTC. Ren can potentially change amount of underlying bitcoins - - -# External Contracts -contract ERC20m: - def totalSupply() -> uint256: constant - def allowance(_owner: address, _spender: address) -> uint256: constant - def transfer(_to: address, _value: uint256) -> bool: modifying - def transferFrom(_from: address, _to: address, _value: uint256) -> bool: modifying - def approve(_spender: address, _value: uint256) -> bool: modifying - def mint(_to: address, _value: uint256): modifying - def burn(_value: uint256): modifying - def burnFrom(_to: address, _value: uint256): modifying - def name() -> string[64]: constant - def symbol() -> string[32]: constant - def decimals() -> uint256: constant - def balanceOf(arg0: address) -> uint256: constant - def set_minter(_minter: address): modifying - - - -# External Contracts -contract cERC20: - def totalSupply() -> uint256: constant - def allowance(_owner: address, _spender: address) -> uint256: constant - def transfer(_to: address, _value: uint256) -> bool: modifying - def transferFrom(_from: address, _to: address, _value: uint256) -> bool: modifying - def approve(_spender: address, _value: uint256) -> bool: modifying - def burn(_value: uint256): modifying - def burnFrom(_to: address, _value: uint256): modifying - def name() -> string[64]: constant - def symbol() -> string[32]: constant - def decimals() -> uint256: constant - def balanceOf(arg0: address) -> uint256: constant - def mint(mintAmount: uint256) -> uint256: modifying - def redeem(redeemTokens: uint256) -> uint256: modifying - def redeemUnderlying(redeemAmount: uint256) -> uint256: modifying - def exchangeRateStored() -> uint256: constant - def exchangeRateCurrent() -> uint256: constant - def supplyRatePerBlock() -> uint256: constant - def accrualBlockNumber() -> uint256: constant - - -from vyper.interfaces import ERC20 - - -# This can (and needs to) be changed at compile time -N_COINS: constant(int128) = 2 # <- change - -ZERO256: constant(uint256) = 0 # This hack is really bad XXX -ZEROS: constant(uint256[N_COINS]) = [ZERO256, ZERO256] # <- change - -USE_LENDING: constant(bool[N_COINS]) = [True, False] - -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]) = [convert(10000000000, uint256), convert(10000000000, uint256)] -# 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 -min_ramp_time: constant(uint256) = 86400 - -# Events -TokenExchange: 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}) -RemoveLiquidityOne: event({provider: indexed(address), token_amount: uint256, coin_amount: 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)}) - -CommitNewFee: event({deadline: indexed(timestamp), fee: uint256, admin_fee: uint256}) -NewFee: event({fee: uint256, admin_fee: uint256}) -RampA: event({old_A: uint256, new_A: uint256, initial_time: timestamp, future_time: timestamp}) -StopRampA: event({A: uint256, t: timestamp}) - -coins: public(address[N_COINS]) -balances: public(uint256[N_COINS]) -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 -max_A_change: constant(uint256) = 10 - -owner: public(address) -token: ERC20m - -initial_A: public(uint256) -future_A: public(uint256) -initial_A_time: public(timestamp) -future_A_time: public(timestamp) - -admin_actions_deadline: public(timestamp) -transfer_ownership_deadline: public(timestamp) -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], - _pool_token: address, - _A: uint256, _fee: uint256): - """ - _coins: Addresses of ERC20 conracts of coins - _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 - self.balances[i] = 0 - self.coins = _coins - self.initial_A = _A - self.future_A = _A - self.fee = _fee - self.owner = msg.sender - self.kill_deadline = block.timestamp + kill_deadline_dt - self.is_killed = False - self.token = ERC20m(_pool_token) - - -@constant -@private -def _A() -> uint256: - """ - Handle ramping A up or down - """ - t1: timestamp = self.future_A_time - A1: uint256 = self.future_A - - if block.timestamp < t1: - A0: uint256 = self.initial_A - t0: timestamp = self.initial_A_time - # Expressions in uint256 cannot have negative numbers, thus "if" - if A1 > A0: - return A0 + (A1 - A0) * (block.timestamp - t0) / (t1 - t0) - else: - return A0 - (A0 - A1) * (block.timestamp - t0) / (t1 - t0) - - else: # when t1 == 0 or block.timestamp >= t1 - return A1 - - -@constant -@public -def A() -> uint256: - return self._A() - - -@private -@constant -def _rates() -> uint256[N_COINS]: - result: uint256[N_COINS] = PRECISION_MUL - use_lending: bool[N_COINS] = USE_LENDING - for i in range(N_COINS): - rate: uint256 = LENDING_PRECISION # Used with no lending - if use_lending[i]: - rate = cERC20(self.coins[i]).exchangeRateCurrent() - 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] / LENDING_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], amp: uint256) -> uint256: - S: uint256 = 0 - for _x in xp: - S += _x - if S == 0: - return 0 - - Dprev: uint256 = 0 - D: uint256 = S - Ann: uint256 = amp * N_COINS - for _i in range(255): - D_P: uint256 = D - for _x in xp: - D_P = D_P * D / (_x * N_COINS) # If division by 0, this will be borked: only withdrawal will work. And that is good - 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], amp: uint256) -> uint256: - return self.get_D(self._xp_mem(rates, _balances), amp) - - -@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._rates()), self._A()) - # 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._rates() - amp: uint256 = self._A() - D0: uint256 = self.get_D_mem(rates, _balances, amp) - 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, amp) - 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 - - 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 - amp: uint256 = self._A() - - token_supply: uint256 = self.token.totalSupply() - rates: uint256[N_COINS] = self._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, amp) - 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, amp) - 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, amp) - 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 amounts[i] > 0: - assert_modifiable( - cERC20(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) - - amp: uint256 = self._A() - D: uint256 = self.get_D(_xp, amp) - c: uint256 = D - S_: uint256 = 0 - Ann: uint256 = amp * 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._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 - 1) * PRECISION / rates[j] - _fee: uint256 = self.fee * dy / FEE_DENOMINATOR - return dy - _fee - - -@public -@constant -def get_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256: - # dx and dy in underlying units - rates: uint256[N_COINS] = self._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 - 1) / precisions[j] - _fee: uint256 = self.fee * dy / FEE_DENOMINATOR - return dy - _fee - - -@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 - - old_balances: uint256[N_COINS] = self.balances - xp: uint256[N_COINS] = self._xp_mem(rates, old_balances) - - x: uint256 = xp[i] + dx * rates[i] / PRECISION - y: uint256 = self.get_y(i, j, x, xp) - - dy: uint256 = xp[j] - y - 1 # -1 just in case there were some rounding errors - dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR - dy_admin_fee: uint256 = dy_fee * self.admin_fee / FEE_DENOMINATOR - - # Convert all to real units - dy = (dy - dy_fee) * PRECISION / rates[j] - dy_admin_fee = dy_admin_fee * PRECISION / rates[j] - - # Change balances exactly in same way as we change actual ERC20 coin amounts - self.balances[i] = old_balances[i] + dx - # When rounding errors happen, we undercharge admin fee in favor of LP - self.balances[j] = old_balances[j] - dy - dy_admin_fee - - return dy - - -@public -@nonreentrant('lock') -def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256): - rates: uint256[N_COINS] = self._rates() - dy: uint256 = self._exchange(i, j, dx, rates) - assert dy >= min_dy, "Exchange resulted in fewer coins than expected" - use_lending: bool[N_COINS] = USE_LENDING - - assert_modifiable(cERC20(self.coins[i]).transferFrom(msg.sender, self, dx)) - assert_modifiable(cERC20(self.coins[j]).transfer(msg.sender, dy)) - - log.TokenExchange(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 # Fees are unused but we've got them historically in event - 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 - assert_modifiable(cERC20(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 - 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._rates() - amp: uint256 = self._A() - - old_balances: uint256[N_COINS] = self.balances - new_balances: uint256[N_COINS] = old_balances - D0: uint256 = self.get_D_mem(rates, old_balances, amp) - for i in range(N_COINS): - new_balances[i] -= amounts[i] - D1: uint256 = self.get_D_mem(rates, new_balances, amp) - 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, amp) - - token_amount: uint256 = (D0 - D2) * token_supply / D0 + 1 - assert token_amount <= max_burn_amount, "Slippage screwed you" - - for i in range(N_COINS): - if amounts[i] > 0: - assert_modifiable(cERC20(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) - - -@private -@constant -def get_y_D(A: uint256, i: int128, xp: uint256[N_COINS], D: uint256) -> uint256: - """ - Calculate x[i] if one reduces D from being calculated for xp to D - - Done by solving quadratic equation iteratively. - x_1**2 + x1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) - x_1**2 + b*x_1 = c - - x_1 = (x_1**2 + c) / (2*x_1 + b) - """ - # x in the input is converted to the same price/precision - - assert (i >= 0) and (i < N_COINS) - - c: uint256 = D - S_: uint256 = 0 - Ann: uint256 = A * N_COINS - - _x: uint256 = 0 - for _i in range(N_COINS): - if _i != i: - _x = xp[_i] - else: - continue - S_ += _x - c = c * D / (_x * N_COINS) - c = c * D / (Ann * N_COINS) - b: uint256 = S_ + D / Ann - 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 - - -@private -@constant -def _calc_withdraw_one_coin(_token_amount: uint256, i: int128, rates: uint256[N_COINS]) -> (uint256, uint256): - # First, need to calculate - # * Get current D - # * Solve Eqn against y_i for D - _token_amount - amp: uint256 = self._A() - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - precisions: uint256[N_COINS] = PRECISION_MUL - total_supply: uint256 = self.token.totalSupply() - - xp: uint256[N_COINS] = self._xp(rates) - - D0: uint256 = self.get_D(xp, amp) - D1: uint256 = D0 - _token_amount * D0 / total_supply - xp_reduced: uint256[N_COINS] = xp - - new_y: uint256 = self.get_y_D(amp, i, xp, D1) - dy_0: uint256 = (xp[i] - new_y) / precisions[i] # w/o fees - - for j in range(N_COINS): - dx_expected: uint256 = 0 - if j == i: - dx_expected = xp[j] * D1 / D0 - new_y - else: - dx_expected = xp[j] - xp[j] * D1 / D0 - xp_reduced[j] -= _fee * dx_expected / FEE_DENOMINATOR - - dy: uint256 = xp_reduced[i] - self.get_y_D(amp, i, xp_reduced, D1) - dy = (dy - 1) / precisions[i] # Withdraw less to account for rounding errors - - return dy, dy_0 - dy - - -@public -@constant -def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - rates: uint256[N_COINS] = self._rates() - return self._calc_withdraw_one_coin(_token_amount, i, rates)[0] - - -@public -@nonreentrant('lock') -def remove_liquidity_one_coin(_token_amount: uint256, i: int128, min_amount: uint256): - """ - Remove _amount of liquidity all in a form of coin i - """ - dy: uint256 = 0 - dy_fee: uint256 = 0 - rates: uint256[N_COINS] = self._rates() - dy, dy_fee = self._calc_withdraw_one_coin(_token_amount, i, rates) - assert dy >= min_amount, "Not enough coins removed" - - self.balances[i] -= (dy + dy_fee * self.admin_fee / FEE_DENOMINATOR) - self.token.burnFrom(msg.sender, _token_amount) - assert_modifiable(ERC20(self.coins[i]).transfer(msg.sender, dy)) - - log.RemoveLiquidityOne(msg.sender, _token_amount, dy) - - -### Admin functions ### -@public -def ramp_A(_future_A: uint256, _future_time: timestamp): - assert msg.sender == self.owner - assert block.timestamp >= self.initial_A_time + min_ramp_time - assert _future_time >= block.timestamp + min_ramp_time - - _initial_A: uint256 = self._A() - assert (_future_A > 0) and (_future_A < max_A) - assert ((_future_A >= _initial_A) and (_future_A <= _initial_A * max_A_change)) or\ - ((_future_A < _initial_A) and (_future_A * max_A_change >= _initial_A)) - self.initial_A = _initial_A - self.future_A = _future_A - self.initial_A_time = block.timestamp - self.future_A_time = _future_time - - log.RampA(_initial_A, _future_A, block.timestamp, _future_time) - - -@public -def stop_ramp_A(): - assert msg.sender == self.owner - - current_A: uint256 = self._A() - self.initial_A = current_A - self.future_A = current_A - self.initial_A_time = block.timestamp - self.future_A_time = block.timestamp - # now (block.timestamp < t1) is always False, so we return saved A - - log.StopRampA(current_A, block.timestamp) - - -@public -def commit_new_fee(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 - - _deadline: timestamp = block.timestamp + admin_actions_delay - self.admin_actions_deadline = _deadline - self.future_fee = new_fee - self.future_admin_fee = new_admin_fee - - log.CommitNewFee(_deadline, new_fee, new_admin_fee) - - -@public -def apply_new_fee(): - assert msg.sender == self.owner - assert self.admin_actions_deadline <= block.timestamp\ - and self.admin_actions_deadline > 0 - - self.admin_actions_deadline = 0 - _fee: uint256 = self.future_fee - _admin_fee: uint256 = self.future_admin_fee - self.fee = _fee - self.admin_fee = _admin_fee - - log.NewFee(_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 - - for i in range(N_COINS): - c: address = self.coins[i] - value: uint256 = cERC20(c).balanceOf(self) - self.balances[i] - if value > 0: - assert_modifiable(cERC20(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 diff --git a/contracts/pools/ren/pooldata.json b/contracts/pools/ren/pooldata.json deleted file mode 100644 index 441f86c1..00000000 --- a/contracts/pools/ren/pooldata.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "lp_contract": "CurveTokenV1", - "wrapped_contract": "renERC20", - "swap_address": "0x93054188d876f558f4a66B2EF1d97d16eDf0895B", - "lp_token_address": "0x49849C98ae39Fff122806C06791Fa73784FB3675", - "gauge_addresses": ["0xB1F2cdeC61db658F091671F5f199635aEF202CAC"], - "lp_constructor": { - "name": "Curve.fi renBTC/wBTC", - "symbol": "crvRenWBTC" - }, - "coins": [ - { - "name": "renBTC", - "tethered": false, - "wrapped_decimals": 8, - "underlying_address": "0xeb4c2781e4eba804ce9a9803c67d0893436bb27d", - "wrapped_address": "0xeb4c2781e4eba804ce9a9803c67d0893436bb27d" - }, - { - "name": "wBTC", - "decimals": 8, - "tethered": false, - "underlying_address": "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599" - } - ], - "testing": { - "initial_amount": 1000 - } -} diff --git a/contracts/pools/reth/README.md b/contracts/pools/reth/README.md deleted file mode 100644 index 0bede453..00000000 --- a/contracts/pools/reth/README.md +++ /dev/null @@ -1,20 +0,0 @@ -# curve-contract/contracts/pools/reth - -[Curve rETH]() - -## Contracts - -- [`StableSwaprETH`](StableSwapRETH.vy): Curve stablecoin AMM contract - -## Deployments - -- [`CurveContractV3`](../../tokens/CurveTokenV3.vy): [0x53a901d48795C58f485cBB38df08FA96a24669D5](https://etherscan.io/address/0x53a901d48795C58f485cBB38df08FA96a24669D5) -- [`LiquidityGaugeV3`](https://github.com/curvefi/curve-dao-contracts/blob/master/contracts/gauges/LiquidityGaugeV3.vy): [0x824F13f1a2F29cFEEa81154b46C0fc820677A637](https://etherscan.io/address/0x824F13f1a2F29cFEEa81154b46C0fc820677A637) -- [`StableSwapRETH`](StableSwapRETH.vy): [0xF9440930043eb3997fc70e1339dBb11F341de7A8](https://etherscan.io/address/0xF9440930043eb3997fc70e1339dBb11F341de7A8) - -## Stablecoins - -Curve rETH pool supports swaps between ETH and [`rETH`](https://github.com/stafiprotocol/) staked ETH (rETH): - -- `ETH`: represented in the pool as `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE` -- `rETH`: [0x9559Aaa82d9649C7A7b220E7c461d2E74c9a3593](https://etherscan.io/token/0x9559aaa82d9649c7a7b220e7c461d2e74c9a3593#code) diff --git a/contracts/pools/reth/StableSwapRETH.vy b/contracts/pools/reth/StableSwapRETH.vy deleted file mode 100644 index 3d8911f1..00000000 --- a/contracts/pools/reth/StableSwapRETH.vy +++ /dev/null @@ -1,843 +0,0 @@ - -# @version 0.2.12 -""" -@title ETH/rETH StableSwap -@author Curve.Fi -@license Copyright (c) Curve.Fi, 2021 - all rights reserved -""" - -from vyper.interfaces import ERC20 - -# External Contracts -interface rETH: - def getExchangeRate() -> uint256: view - - -interface CurveToken: - def mint(_to: address, _value: uint256) -> bool: nonpayable - def burnFrom(_to: address, _value: uint256) -> bool: nonpayable - - -# Events -event TokenExchange: - buyer: indexed(address) - sold_id: int128 - tokens_sold: uint256 - bought_id: int128 - tokens_bought: uint256 - -event AddLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event RemoveLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - token_supply: uint256 - -event RemoveLiquidityOne: - provider: indexed(address) - token_amount: uint256 - coin_amount: uint256 - -event RemoveLiquidityImbalance: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event CommitNewAdmin: - deadline: indexed(uint256) - admin: indexed(address) - -event NewAdmin: - admin: indexed(address) - -event CommitNewFee: - deadline: indexed(uint256) - fee: uint256 - admin_fee: uint256 - -event NewFee: - fee: uint256 - admin_fee: uint256 - -event RampA: - old_A: uint256 - new_A: uint256 - initial_time: uint256 - future_time: uint256 - -event StopRampA: - A: uint256 - t: uint256 - - -# These constants must be set prior to compiling -N_COINS: constant(int128) = 2 - -# fixed constants -FEE_DENOMINATOR: constant(uint256) = 10 ** 10 -LENDING_PRECISION: constant(uint256) = 10 ** 18 -PRECISION: constant(uint256) = 10 ** 18 # The precision to convert to - -MAX_ADMIN_FEE: constant(uint256) = 10 * 10 ** 9 -MAX_FEE: constant(uint256) = 5 * 10 ** 9 -MAX_A: constant(uint256) = 10 ** 6 -MAX_A_CHANGE: constant(uint256) = 10 - -ADMIN_ACTIONS_DELAY: constant(uint256) = 3 * 86400 -MIN_RAMP_TIME: constant(uint256) = 86400 - -coins: public(address[N_COINS]) -balances: public(uint256[N_COINS]) - -fee: public(uint256) # fee * 1e10 -admin_fee: public(uint256) # admin_fee * 1e10 - -owner: public(address) -lp_token: public(address) - -A_PRECISION: constant(uint256) = 100 -initial_A: public(uint256) -future_A: public(uint256) -initial_A_time: public(uint256) -future_A_time: public(uint256) - -admin_actions_deadline: public(uint256) -transfer_ownership_deadline: public(uint256) -future_fee: public(uint256) -future_admin_fee: public(uint256) -future_owner: public(address) - -is_killed: bool -kill_deadline: uint256 -KILL_DEADLINE_DT: constant(uint256) = 2 * 30 * 86400 - - -@external -def __init__( - _owner: address, - _coins: address[N_COINS], - _pool_token: address, - _A: uint256, - _fee: uint256, - _admin_fee: uint256, -): - """ - @notice Contract constructor - @param _owner Contract owner address - @param _coins Addresses of ERC20 contracts of wrapped coins - @param _pool_token Address of the token representing LP share - @param _A Amplification coefficient multiplied by n * (n - 1) - @param _fee Fee to charge for exchanges - @param _admin_fee Admin fee - """ - - assert _coins[0] == 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE - assert _coins[1] != ZERO_ADDRESS - - self.coins = _coins - self.initial_A = _A * A_PRECISION - self.future_A = _A * A_PRECISION - self.fee = _fee - self.admin_fee = _admin_fee - self.owner = _owner - self.kill_deadline = block.timestamp + KILL_DEADLINE_DT - self.lp_token = _pool_token - - -@view -@internal -def _A() -> uint256: - """ - Handle ramping A up or down - """ - t1: uint256 = self.future_A_time - A1: uint256 = self.future_A - - if block.timestamp < t1: - A0: uint256 = self.initial_A - t0: uint256 = self.initial_A_time - # Expressions in uint256 cannot have negative numbers, thus "if" - if A1 > A0: - return A0 + (A1 - A0) * (block.timestamp - t0) / (t1 - t0) - else: - return A0 - (A0 - A1) * (block.timestamp - t0) / (t1 - t0) - - else: # when t1 == 0 or block.timestamp >= t1 - return A1 - - -@view -@external -def A() -> uint256: - return self._A() / A_PRECISION - - -@view -@external -def A_precise() -> uint256: - return self._A() - - -@view -@internal -def _stored_rates() -> uint256[N_COINS]: - return [ - convert(PRECISION, uint256), - rETH(self.coins[1]).getExchangeRate() - ] - - -@view -@internal -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 - - -@internal -@pure -def get_D(xp: uint256[N_COINS], amp: uint256) -> uint256: - S: uint256 = 0 - Dprev: uint256 = 0 - - for _x in xp: - S += _x - if S == 0: - return 0 - - D: uint256 = S - Ann: uint256 = amp * N_COINS - for _i in range(255): - D_P: uint256 = D - for _x in xp: - D_P = D_P * D / (_x * N_COINS) # If division by 0, this will be borked: only withdrawal will work. And that is good - Dprev = D - D = (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (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` - raise - - -@view -@internal -def get_D_mem(rates: uint256[N_COINS], _balances: uint256[N_COINS], amp: uint256) -> uint256: - result: uint256[N_COINS] = rates - for i in range(N_COINS): - result[i] = result[i] * _balances[i] / PRECISION - return self.get_D(result, amp) - - -@view -@external -def get_virtual_price() -> uint256: - """ - @notice The current virtual price of the pool LP token - @dev Useful for calculating profits - @return LP token virtual price normalized to 1e18 - """ - D: uint256 = self.get_D(self._xp(self._stored_rates()), self._A()) - # 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 = ERC20(self.lp_token).totalSupply() - return D * PRECISION / token_supply - - -@view -@external -def calc_token_amount(amounts: uint256[N_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 amounts Amount of each coin being deposited - @param is_deposit set True for deposits, False for withdrawals - @return Expected amount of LP tokens received - """ - amp: uint256 = self._A() - rates: uint256[N_COINS] = self._stored_rates() - _balances: uint256[N_COINS] = self.balances - D0: uint256 = self.get_D_mem(rates, _balances, amp) - for i in range(N_COINS): - _amount: uint256 = amounts[i] - if is_deposit: - _balances[i] += _amount - else: - _balances[i] -= _amount - D1: uint256 = self.get_D_mem(rates, _balances, amp) - token_amount: uint256 = ERC20(self.lp_token).totalSupply() - diff: uint256 = 0 - if is_deposit: - diff = D1 - D0 - else: - diff = D0 - D1 - return diff * token_amount / D0 - -@payable -@external -@nonreentrant('lock') -def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256) -> uint256: - """ - @notice Deposit coins into the pool - @param amounts List of amounts of coins to deposit - @param min_mint_amount Minimum amount of LP tokens to mint from the deposit - @return Amount of LP tokens received by depositing - """ - assert not self.is_killed - amp: uint256 = self._A() - rates: uint256[N_COINS] = self._stored_rates() - _lp_token: address = self.lp_token - token_supply: uint256 = ERC20(_lp_token).totalSupply() - - # Initial invariant - D0: uint256 = 0 - old_balances: uint256[N_COINS] = self.balances - if token_supply != 0: - D0 = self.get_D_mem(rates, old_balances, amp) - - new_balances: uint256[N_COINS] = old_balances - for i in range(N_COINS): - if token_supply == 0: - assert amounts[i] > 0 - new_balances[i] += amounts[i] - - # Invariant after change - D1: uint256 = self.get_D_mem(rates, new_balances, amp) - assert D1 > D0 - - # We need to recalculate the invariant accounting for fees - # to calculate fair user's share - D2: uint256 = D1 - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - mint_amount: uint256 = 0 - if token_supply != 0: - # Only account for fees if we are not the first to deposit - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - _admin_fee: uint256 = self.admin_fee - 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, amp) - mint_amount = token_supply * (D2 - D0) / D0 - else: - self.balances = new_balances - mint_amount = D1 # Take the dust if there was any - - assert mint_amount >= min_mint_amount, "Slippage screwed you" - - # Take coins from the sender - assert msg.value == amounts[0] - if amounts[1] > 0: - assert ERC20(self.coins[1]).transferFrom(msg.sender, self, amounts[1]) - - # Mint pool tokens - CurveToken(_lp_token).mint(msg.sender, mint_amount) - - log AddLiquidity(msg.sender, amounts, fees, D1, token_supply + mint_amount) - - return mint_amount - - -@view -@internal -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 # dev: same coin - assert j >= 0 # dev: j below zero - assert j < N_COINS # dev: j above N_COINS - - # should be unreachable, but good for safety - assert i >= 0 - assert i < N_COINS - - A_: uint256 = self._A() - D: uint256 = self.get_D(xp_, A_) - Ann: uint256 = A_ * N_COINS - c: uint256 = D - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: 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 * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann # - D - 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: - return y - else: - if y_prev - y <= 1: - return y - raise - - -@view -@external -def get_dy(i: int128, j: int128, dx: uint256) -> uint256: - # dx and dy in c-units - rates: uint256[N_COINS] = self._stored_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 - _fee: uint256 = self.fee * dy / FEE_DENOMINATOR - return (dy - _fee) * PRECISION / rates[j] - - -@view -@external -def get_dx(i: int128, j: int128, dy: uint256) -> uint256: - # dx and dy in c-units - rates: uint256[N_COINS] = self._stored_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 - - -@payable -@external -@nonreentrant('lock') -def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256) -> uint256: - """ - @notice Perform an exchange between two coins - @dev Index values can be found via the `coins` public getter method - @param i Index value for the coin to send - @param j Index valie of the coin to recieve - @param dx Amount of `i` being exchanged - @param min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - assert not self.is_killed # dev: is killed - - rates: uint256[N_COINS] = self._stored_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 - 1 # -1 just in case there were some rounding errors - 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 = (dy - dy_fee) * PRECISION / rates[j] - assert dy >= min_dy, "Exchange resulted in fewer coins than expected" - - coin: address = self.coins[1] - if i == 0: - assert msg.value == dx - assert ERC20(coin).transfer(msg.sender, dy) - else: - assert msg.value == 0 - assert ERC20(coin).transferFrom(msg.sender, self, dx) - raw_call(msg.sender, b"", value=dy) - - log TokenExchange(msg.sender, i, dx, j, dy) - - return dy - - -@external -@nonreentrant('lock') -def remove_liquidity(_amount: uint256, min_amounts: uint256[N_COINS]) -> uint256[N_COINS]: - """ - @notice Withdraw coins from the pool - @dev Withdrawal amounts are based on current deposit ratios - @param _amount Quantity of LP tokens to burn in the withdrawal - @param min_amounts Minimum amounts of underlying coins to receive - @return List of amounts of coins that were withdrawn - """ - _lp_token: address = self.lp_token - total_supply: uint256 = ERC20(_lp_token).totalSupply() - amounts: uint256[N_COINS] = empty(uint256[N_COINS]) - - for i in range(N_COINS): - _balance: uint256 = self.balances[i] - value: uint256 = _balance * _amount / total_supply - assert value >= min_amounts[i], "Withdrawal resulted in fewer coins than expected" - self.balances[i] = _balance - value - amounts[i] = value - if i == 0: - raw_call(msg.sender, b"", value=value) - else: - assert ERC20(self.coins[1]).transfer(msg.sender, value) - - CurveToken(_lp_token).burnFrom(msg.sender, _amount) # Will raise if not enough - - log RemoveLiquidity(msg.sender, amounts, empty(uint256[N_COINS]), total_supply - _amount) - - return amounts - - -@external -@nonreentrant('lock') -def remove_liquidity_imbalance(amounts: uint256[N_COINS], max_burn_amount: uint256) -> uint256: - """ - @notice Withdraw coins from the pool in an imbalanced amount - @param amounts List of amounts of underlying coins to withdraw - @param max_burn_amount Maximum amount of LP token to burn in the withdrawal - @return Actual amount of the LP token burned in the withdrawal - """ - assert not self.is_killed - amp: uint256 = self._A() - rates: uint256[N_COINS] = self._stored_rates() - old_balances: uint256[N_COINS] = self.balances - D0: uint256 = self.get_D_mem(rates, old_balances, amp) - - new_balances: uint256[N_COINS] = old_balances - for i in range(N_COINS): - new_balances[i] -= amounts[i] - D1: uint256 = self.get_D_mem(rates, new_balances, amp) - - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - _admin_fee: uint256 = self.admin_fee - for i in range(N_COINS): - ideal_balance: uint256 = D1 * old_balances[i] / D0 - new_balance: uint256 = new_balances[i] - difference: uint256 = 0 - if ideal_balance > new_balance: - difference = ideal_balance - new_balance - else: - difference = new_balance - ideal_balance - fees[i] = _fee * difference / FEE_DENOMINATOR - self.balances[i] = new_balance - (fees[i] * _admin_fee / FEE_DENOMINATOR) - new_balances[i] = new_balance - fees[i] - D2: uint256 = self.get_D_mem(rates, new_balances, amp) - - lp_token: address = self.lp_token - token_supply: uint256 = ERC20(lp_token).totalSupply() - token_amount: uint256 = (D0 - D2) * token_supply / D0 - assert token_amount != 0 - assert token_amount <= max_burn_amount, "Slippage screwed you" - - CurveToken(lp_token).burnFrom(msg.sender, token_amount) # dev: insufficient funds - - if amounts[0] != 0: - raw_call(msg.sender, b"", value=amounts[0]) - if amounts[1] != 0: - assert ERC20(self.coins[1]).transfer(msg.sender, amounts[1]) - - log RemoveLiquidityImbalance(msg.sender, amounts, fees, D1, token_supply - token_amount) - - return token_amount - - -@pure -@internal -def get_y_D(A_: uint256, i: int128, xp: uint256[N_COINS], D: uint256) -> uint256: - """ - Calculate x[i] if one reduces D from being calculated for xp to D - - Done by solving quadratic equation iteratively. - x_1**2 + x_1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) - x_1**2 + b*x_1 = c - - x_1 = (x_1**2 + c) / (2*x_1 + b) - """ - # x in the input is converted to the same price/precision - - assert i >= 0 # dev: i below zero - assert i < N_COINS # dev: i above N_COINS - - Ann: uint256 = A_ * N_COINS - c: uint256 = D - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: uint256 = 0 - - for _i in range(N_COINS): - if _i != i: - _x = xp[_i] - else: - continue - S_ += _x - c = c * D / (_x * N_COINS) - c = c * D * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann - 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: - return y - else: - if y_prev - y <= 1: - return y - raise - - -@view -@internal -def _calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> (uint256, uint256): - # First, need to calculate - # * Get current D - # * Solve Eqn against y_i for D - _token_amount - amp: uint256 = self._A() - rates: uint256[N_COINS] = self._stored_rates() - xp: uint256[N_COINS] = self._xp(rates) - D0: uint256 = self.get_D(xp, amp) - - total_supply: uint256 = ERC20(self.lp_token).totalSupply() - - D1: uint256 = D0 - _token_amount * D0 / total_supply - new_y: uint256 = self.get_y_D(amp, i, xp, D1) - - xp_reduced: uint256[N_COINS] = xp - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - - for j in range(N_COINS): - dx_expected: uint256 = 0 - xp_j: uint256 = xp[j] - if j == i: - dx_expected = xp_j * D1 / D0 - new_y - else: - dx_expected = xp_j - xp_j * D1 / D0 - xp_reduced[j] -= _fee * dx_expected / FEE_DENOMINATOR - - dy: uint256 = xp_reduced[i] - self.get_y_D(amp, i, xp_reduced, D1) - rate: uint256 = rates[i] - dy = (dy - 1) * PRECISION / rate # Withdraw less to account for rounding errors - dy_0: uint256 = (xp[i] - new_y) * PRECISION / rate # w/o fees - - return dy, dy_0 - dy - - -@view -@external -def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - """ - @notice Calculate the amount received when withdrawing a single coin - @param _token_amount Amount of LP tokens to burn in the withdrawal - @param i Index value of the coin to withdraw - @return Amount of coin received - """ - return self._calc_withdraw_one_coin(_token_amount, i)[0] - - -@external -@nonreentrant('lock') -def remove_liquidity_one_coin(_token_amount: uint256, i: int128, _min_amount: uint256) -> uint256: - """ - @notice Withdraw a single coin from the pool - @param _token_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 coin to receive - @return Amount of coin received - """ - assert not self.is_killed # dev: is killed - - dy: uint256 = 0 - dy_fee: uint256 = 0 - dy, dy_fee = self._calc_withdraw_one_coin(_token_amount, i) - assert dy >= _min_amount, "Not enough coins removed" - - self.balances[i] -= (dy + dy_fee * self.admin_fee / FEE_DENOMINATOR) - CurveToken(self.lp_token).burnFrom(msg.sender, _token_amount) # dev: insufficient funds - - if i == 0: - raw_call(msg.sender, b"", value=dy) - else: - assert ERC20(self.coins[1]).transfer(msg.sender, dy) - - log RemoveLiquidityOne(msg.sender, _token_amount, dy) - - return dy - - -### Admin functions ### -@external -def ramp_A(_future_A: uint256, _future_time: uint256): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.initial_A_time + MIN_RAMP_TIME - assert _future_time >= block.timestamp + MIN_RAMP_TIME # dev: insufficient time - - _initial_A: uint256 = self._A() - _future_A_p: uint256 = _future_A * A_PRECISION - - assert _future_A > 0 and _future_A < MAX_A - if _future_A_p < _initial_A: - assert _future_A_p * MAX_A_CHANGE >= _initial_A - else: - assert _future_A_p <= _initial_A * MAX_A_CHANGE - - self.initial_A = _initial_A - self.future_A = _future_A_p - self.initial_A_time = block.timestamp - self.future_A_time = _future_time - - log RampA(_initial_A, _future_A_p, block.timestamp, _future_time) - - -@external -def stop_ramp_A(): - assert msg.sender == self.owner # dev: only owner - - current_A: uint256 = self._A() - self.initial_A = current_A - self.future_A = current_A - self.initial_A_time = block.timestamp - self.future_A_time = block.timestamp - # now (block.timestamp < t1) is always False, so we return saved A - - log StopRampA(current_A, block.timestamp) - - -@external -def commit_new_fee(new_fee: uint256, new_admin_fee: uint256): - assert msg.sender == self.owner # dev: only owner - assert self.admin_actions_deadline == 0 # dev: active action - assert new_fee <= MAX_FEE # dev: fee exceeds maximum - assert new_admin_fee <= MAX_ADMIN_FEE # dev: admin fee exceeds maximum - - _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.admin_actions_deadline = _deadline - self.future_fee = new_fee - self.future_admin_fee = new_admin_fee - - log CommitNewFee(_deadline, new_fee, new_admin_fee) - - -@external -@nonreentrant('lock') -def apply_new_fee(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.admin_actions_deadline # dev: insufficient time - assert self.admin_actions_deadline != 0 # dev: no active action - - self.admin_actions_deadline = 0 - _fee: uint256 = self.future_fee - _admin_fee: uint256 = self.future_admin_fee - self.fee = _fee - self.admin_fee = _admin_fee - - log NewFee(_fee, _admin_fee) - - -@external -def revert_new_parameters(): - assert msg.sender == self.owner # dev: only owner - - self.admin_actions_deadline = 0 - - -@external -def commit_transfer_ownership(_owner: address): - assert msg.sender == self.owner # dev: only owner - assert self.transfer_ownership_deadline == 0 # dev: active transfer - - _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.transfer_ownership_deadline = _deadline - self.future_owner = _owner - - log CommitNewAdmin(_deadline, _owner) - - -@external -@nonreentrant('lock') -def apply_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.transfer_ownership_deadline # dev: insufficient time - assert self.transfer_ownership_deadline != 0 # dev: no active transfer - - self.transfer_ownership_deadline = 0 - _owner: address = self.future_owner - self.owner = _owner - - log NewAdmin(_owner) - - -@external -def revert_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - self.transfer_ownership_deadline = 0 - - -@view -@external -def admin_balances(i: uint256) -> uint256: - if i == 0: - return self.balance - self.balances[0] - return ERC20(self.coins[i]).balanceOf(self) - self.balances[i] - - -@external -@nonreentrant('lock') -def withdraw_admin_fees(): - assert msg.sender == self.owner # dev: only owner - - amount: uint256 = self.balance - self.balances[0] - if amount != 0: - raw_call(msg.sender, b"", value=amount) - - amount = ERC20(self.coins[1]).balanceOf(self) - self.balances[1] - if amount != 0: - assert ERC20(self.coins[1]).transfer(msg.sender, amount) - - -@external -@nonreentrant('lock') -def donate_admin_fees(): - assert msg.sender == self.owner # dev: only owner - for i in range(N_COINS): - if i == 0: - self.balances[0] = self.balance - else: - self.balances[i] = ERC20(self.coins[i]).balanceOf(self) - - -@external -def kill_me(): - assert msg.sender == self.owner # dev: only owner - assert self.kill_deadline > block.timestamp # dev: deadline has passed - self.is_killed = True - - -@external -def unkill_me(): - assert msg.sender == self.owner # dev: only owner - self.is_killed = False diff --git a/contracts/pools/reth/pooldata.json b/contracts/pools/reth/pooldata.json deleted file mode 100644 index 31d5673f..00000000 --- a/contracts/pools/reth/pooldata.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "lp_contract": "CurveTokenV3", - "pool_types": ["eth", "crate"], - "wrapped_contract": "rETH", - "swap_address": "0xF9440930043eb3997fc70e1339dBb11F341de7A8", - "lp_token_address": "0x53a901d48795C58f485cBB38df08FA96a24669D5", - "gauge_addresses": ["0x824F13f1a2F29cFEEa81154b46C0fc820677A637"], - "lp_constructor": { - "symbol": "rCRV", - "name": "Curve.fi ETH/rETH" - }, - "swap_constructor": { - "_A": 10, - "_fee": 4000000, - "_admin_fee": 5000000000 - }, - "coins": [ - { - "name": "ETH", - "decimals": 18, - "tethered": false, - "underlying_address": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" - }, - { - "name": "rETH", - "decimals": 18, - "wrapped_decimals": 18, - "tethered": false, - "wrapped_address": "0x9559Aaa82d9649C7A7b220E7c461d2E74c9a3593" - } - ], - "testing": { - "initial_amount": 10000 - } -} diff --git a/contracts/pools/rsv/DepositRSV.vy b/contracts/pools/rsv/DepositRSV.vy deleted file mode 100644 index 7acc8511..00000000 --- a/contracts/pools/rsv/DepositRSV.vy +++ /dev/null @@ -1,376 +0,0 @@ -# @version 0.2.7 -""" -@title "Zap" Depositer for Curve RSV pool -@author Curve.Fi -@license Copyright (c) Curve.Fi, 2020 - all rights reserved -""" - -from vyper.interfaces import ERC20 - - -interface CurveMeta: - def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256) -> 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) -> 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 base_pool() -> address: 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 - -# An asset which may have a transfer fee (USDT) -FEE_ASSET: constant(address) = 0xdAC17F958D2ee523a2206206994597C13D831ec7 - -FEE_DENOMINATOR: constant(uint256) = 10 ** 10 -FEE_IMPRECISION: constant(uint256) = 100 * 10 ** 8 # % of the fee - - -pool: public(address) -token: public(address) -base_pool: public(address) - -coins: public(address[N_COINS]) -base_coins: public(address[BASE_N_COINS]) - - -@external -def __init__(_pool: address, _token: address): - """ - @notice Contract constructor - @param _pool Metapool address - @param _token Pool LP token address - """ - self.pool = _pool - self.token = _token - _base_pool: address = CurveMeta(_pool).base_pool() - self.base_pool = _base_pool - - for i in range(N_COINS): - coin: address = CurveMeta(_pool).coins(convert(i, uint256)) - self.coins[i] = coin - # approve coins for infinite transfers - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("approve(address,uint256)"), - convert(_pool, bytes32), - convert(MAX_UINT256, bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) - - for i in range(BASE_N_COINS): - coin: address = CurveBase(_base_pool).coins(convert(i, uint256)) - self.base_coins[i] = coin - # approve underlying coins for infinite transfers - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("approve(address,uint256)"), - convert(_base_pool, bytes32), - convert(MAX_UINT256, bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) - - -@external -def add_liquidity(amounts: uint256[N_ALL_COINS], min_mint_amount: uint256) -> uint256: - """ - @notice Wrap underlying coins and deposit them in the pool - @param amounts List of amounts of underlying coins to deposit - @param min_mint_amount Minimum amount of LP tokens to mint from the deposit - @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 - - # Transfer all coins in - for i in range(N_ALL_COINS): - amount: uint256 = amounts[i] - if amount == 0: - continue - coin: address = ZERO_ADDRESS - if i < MAX_COIN: - coin = self.coins[i] - meta_amounts[i] = amount - else: - x: int128 = i - MAX_COIN - coin = self.base_coins[x] - base_amounts[x] = amount - deposit_base = True - # "safeTransferFrom" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transferFrom(address,address,uint256)"), - convert(msg.sender, bytes32), - convert(self, bytes32), - convert(amount, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransferFrom" - # Handle potential Tether fees - if coin == FEE_ASSET: - amount = ERC20(FEE_ASSET).balanceOf(self) - if i < MAX_COIN: - meta_amounts[i] = amount - else: - base_amounts[i - MAX_COIN] = amount - - # Deposit to the base pool - if deposit_base: - CurveBase(self.base_pool).add_liquidity(base_amounts, 0) - meta_amounts[MAX_COIN] = ERC20(self.coins[MAX_COIN]).balanceOf(self) - - # Deposit to the meta pool - CurveMeta(self.pool).add_liquidity(meta_amounts, min_mint_amount) - - # Transfer meta token back - _lp_token: address = self.token - _lp_amount: uint256 = ERC20(_lp_token).balanceOf(self) - assert ERC20(_lp_token).transfer(msg.sender, _lp_amount) - - return _lp_amount - - -@external -def remove_liquidity(_amount: uint256, min_amounts: uint256[N_ALL_COINS]) -> uint256[N_ALL_COINS]: - """ - @notice Withdraw and unwrap coins from the pool - @dev Withdrawal amounts are based on current deposit ratios - @param _amount Quantity of LP tokens to burn in the withdrawal - @param min_amounts Minimum amounts of underlying coins to receive - @return List of amounts of underlying coins that were withdrawn - """ - _token: address = self.token - assert ERC20(_token).transferFrom(msg.sender, self, _amount) - - min_amounts_meta: uint256[N_COINS] = empty(uint256[N_COINS]) - 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 - for i in range(MAX_COIN): - min_amounts_meta[i] = min_amounts[i] - CurveMeta(self.pool).remove_liquidity(_amount, min_amounts_meta) - - # Withdraw from base - _base_amount: uint256 = ERC20(self.coins[MAX_COIN]).balanceOf(self) - for i in range(BASE_N_COINS): - min_amounts_base[i] = min_amounts[MAX_COIN+i] - CurveBase(self.base_pool).remove_liquidity(_base_amount, min_amounts_base) - - # Transfer all coins out - for i in range(N_ALL_COINS): - coin: address = ZERO_ADDRESS - if i < MAX_COIN: - coin = self.coins[i] - else: - coin = self.base_coins[i - MAX_COIN] - amounts[i] = ERC20(coin).balanceOf(self) - # "safeTransfer" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(amounts[i], bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - return amounts - - -@external -def remove_liquidity_one_coin(_token_amount: uint256, i: int128, _min_amount: uint256) -> uint256: - """ - @notice Withdraw and unwrap a single coin from the pool - @param _token_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 - @return Amount of underlying coin received - """ - assert ERC20(self.token).transferFrom(msg.sender, self, _token_amount) - - coin: address = ZERO_ADDRESS - if i < MAX_COIN: - coin = self.coins[i] - # Withdraw a metapool coin - CurveMeta(self.pool).remove_liquidity_one_coin(_token_amount, i, _min_amount) - else: - coin = self.base_coins[i - MAX_COIN] - # Withdraw a base pool coin - CurveMeta(self.pool).remove_liquidity_one_coin(_token_amount, MAX_COIN, 0) - CurveBase(self.base_pool).remove_liquidity_one_coin( - ERC20(self.coins[MAX_COIN]).balanceOf(self), i-MAX_COIN, _min_amount - ) - - # Tranfer the coin out - coin_amount: uint256 = ERC20(coin).balanceOf(self) - # "safeTransfer" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(coin_amount, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - return coin_amount - - -@external -def remove_liquidity_imbalance(amounts: uint256[N_ALL_COINS], max_burn_amount: uint256) -> uint256: - """ - @notice Withdraw coins from the pool in an imbalanced amount - @param amounts List of amounts of underlying coins to withdraw - @param max_burn_amount Maximum amount of LP token to burn in the withdrawal. - This value cannot exceed the caller's LP token balance. - @return Actual amount of the LP token burned in the withdrawal - """ - _base_pool: address = self.base_pool - _meta_pool: address = self.pool - _base_coins: address[BASE_N_COINS] = self.base_coins - _meta_coins: address[N_COINS] = self.coins - _lp_token: address = self.token - - 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 - assert ERC20(_lp_token).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]) - leftover_amounts: uint256[N_COINS] = empty(uint256[N_COINS]) - - # Prepare quantities - for i in range(MAX_COIN): - amounts_meta[i] = amounts[i] - - for i in range(BASE_N_COINS): - amount: uint256 = amounts[MAX_COIN + i] - if amount != 0: - amounts_base[i] = amount - withdraw_base = True - - if withdraw_base: - amounts_meta[MAX_COIN] = CurveBase(self.base_pool).calc_token_amount(amounts_base, False) - amounts_meta[MAX_COIN] += amounts_meta[MAX_COIN] * fee / FEE_DENOMINATOR + 1 - - # Remove liquidity and deposit leftovers back - CurveMeta(_meta_pool).remove_liquidity_imbalance(amounts_meta, max_burn_amount) - if withdraw_base: - CurveBase(_base_pool).remove_liquidity_imbalance(amounts_base, amounts_meta[MAX_COIN]) - leftover_amounts[MAX_COIN] = ERC20(_meta_coins[MAX_COIN]).balanceOf(self) - if leftover_amounts[MAX_COIN] > 0: - CurveMeta(_meta_pool).add_liquidity(leftover_amounts, 0) - - # Transfer all coins out - for i in range(N_ALL_COINS): - coin: address = ZERO_ADDRESS - amount: uint256 = 0 - if i < MAX_COIN: - coin = _meta_coins[i] - amount = amounts_meta[i] - else: - coin = _base_coins[i - MAX_COIN] - amount = amounts_base[i - MAX_COIN] - # "safeTransfer" which works for ERC20s which return bool or not - if amount > 0: - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(amount, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - # Transfer the leftover LP token out - leftover: uint256 = ERC20(_lp_token).balanceOf(self) - if leftover > 0: - assert ERC20(_lp_token).transfer(msg.sender, leftover) - - return max_burn_amount - leftover - - -@view -@external -def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - """ - @notice Calculate the amount received when withdrawing and unwrapping a single coin - @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(self.pool).calc_withdraw_one_coin(_token_amount, i) - else: - _base_tokens: uint256 = CurveMeta(self.pool).calc_withdraw_one_coin(_token_amount, MAX_COIN) - return CurveBase(self.base_pool).calc_withdraw_one_coin(_base_tokens, i-MAX_COIN) - - -@view -@external -def calc_token_amount(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 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]) - - for i in range(MAX_COIN): - meta_amounts[i] = amounts[i] - - for i in range(BASE_N_COINS): - base_amounts[i] = amounts[i + MAX_COIN] - - _base_tokens: uint256 = CurveBase(self.base_pool).calc_token_amount(base_amounts, is_deposit) - meta_amounts[MAX_COIN] = _base_tokens - - return CurveMeta(self.pool).calc_token_amount(meta_amounts, is_deposit) diff --git a/contracts/pools/rsv/README.md b/contracts/pools/rsv/README.md deleted file mode 100644 index 1d0b04b6..00000000 --- a/contracts/pools/rsv/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# curve-contract/contracts/pools/rsv - -[Curve RSV metapool](https://www.curve.fi/rsv), allowing swaps via the Curve [tri-pool](../3pool). - -## Contracts - -* [`DepositRSV`](DepositRSV.vy): Depositor contract, used to wrap underlying tokens prior to depositing them into the pool -* [`StableSwapRSV`](StableSwapRSV.vy): Curve stablecoin AMM contract - -## Deployments - -* [`CurveContractV2`](../../tokens/CurveTokenV2.vy): [0xC2Ee6b0334C261ED60C72f6054450b61B8f18E35](https://etherscan.io/address/0xC2Ee6b0334C261ED60C72f6054450b61B8f18E35) -* [`DepositRSV`](DepositRSV.vy): [0xBE175115BF33E12348ff77CcfEE4726866A0Fbd5](https://etherscan.io/address/0xBE175115BF33E12348ff77CcfEE4726866A0Fbd5) -* [`LiquidityGaugeReward`](../../gauges/LiquidityGaugeReward.vy): [0x4dC4A289a8E33600D8bD4cf5F6313E43a37adec7](https://etherscan.io/address/0x4dC4A289a8E33600D8bD4cf5F6313E43a37adec7) -* [`StableSwapRSV`](StableSwapRSV.vy): [0xC18cC39da8b11dA8c3541C598eE022258F9744da](https://etherscan.io/address/0xC18cC39da8b11dA8c3541C598eE022258F9744da) - -## Stablecoins - -Curve RSV metapool utilizes the supports swaps between the following assets: - -## Direct swaps - -Direct swaps are possible between RSV and the Curve tri-pool LP token. - -* `RSV`: [0x196f4727526eA7FB1e17b2071B3d8eAA38486988](https://etherscan.io/address/0x196f4727526eA7FB1e17b2071B3d8eAA38486988) -* `3CRV`: [0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490](https://etherscan.io/address/0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490) - -## Base Pool coins - -The tri-pool LP token may be wrapped or unwrapped to provide swaps between RSV and the following stablecoins: - -* `DAI`: [0x6b175474e89094c44da98b954eedeac495271d0f](https://etherscan.io/address/0x6b175474e89094c44da98b954eedeac495271d0f) -* `USDC`: [0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48](https://etherscan.io/address/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48) -* `USDT`: [0xdac17f958d2ee523a2206206994597c13d831ec7](https://etherscan.io/address/0xdac17f958d2ee523a2206206994597c13d831ec7) diff --git a/contracts/pools/rsv/StableSwapRSV.vy b/contracts/pools/rsv/StableSwapRSV.vy deleted file mode 100644 index 3d3bd012..00000000 --- a/contracts/pools/rsv/StableSwapRSV.vy +++ /dev/null @@ -1,1083 +0,0 @@ -# @version 0.2.5 -""" -@title Curve RSV Metapool -@author Curve.Fi -@license Copyright (c) Curve.Fi, 2020 - all rights reserved -@dev Utilizes 3Pool to allow swaps between RSV / DAI / USDC / USDT -""" - -from vyper.interfaces import ERC20 - -interface CurveToken: - def totalSupply() -> uint256: view - def mint(_to: address, _value: uint256) -> bool: nonpayable - def burnFrom(_to: address, _value: uint256) -> bool: nonpayable - - -interface Curve: - def coins(i: uint256) -> address: view - def get_virtual_price() -> uint256: view - def calc_token_amount(amounts: uint256[BASE_N_COINS], deposit: bool) -> uint256: view - def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: view - def fee() -> uint256: view - def get_dy(i: int128, j: int128, dx: uint256) -> uint256: view - def get_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256: view - def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256): nonpayable - def add_liquidity(amounts: uint256[BASE_N_COINS], min_mint_amount: uint256): nonpayable - def remove_liquidity_one_coin(_token_amount: uint256, i: int128, min_amount: uint256): nonpayable - - -# Events -event TokenExchange: - buyer: indexed(address) - sold_id: int128 - tokens_sold: uint256 - bought_id: int128 - tokens_bought: uint256 - -event TokenExchangeUnderlying: - buyer: indexed(address) - sold_id: int128 - tokens_sold: uint256 - bought_id: int128 - tokens_bought: uint256 - -event AddLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event RemoveLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - token_supply: uint256 - -event RemoveLiquidityOne: - provider: indexed(address) - token_amount: uint256 - coin_amount: uint256 - token_supply: uint256 - -event RemoveLiquidityImbalance: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event CommitNewAdmin: - deadline: indexed(uint256) - admin: indexed(address) - -event NewAdmin: - admin: indexed(address) - -event CommitNewFee: - deadline: indexed(uint256) - fee: uint256 - admin_fee: uint256 - -event NewFee: - fee: uint256 - admin_fee: uint256 - -event RampA: - old_A: uint256 - new_A: uint256 - initial_time: uint256 - future_time: uint256 - -event StopRampA: - A: uint256 - t: uint256 - - -N_COINS: constant(int128) = 2 -MAX_COIN: constant(int128) = N_COINS - 1 - -FEE_DENOMINATOR: constant(uint256) = 10 ** 10 -PRECISION: constant(uint256) = 10 ** 18 # The precision to convert to -PRECISION_MUL: constant(uint256[N_COINS]) = [1, 1] -RATES: constant(uint256[N_COINS]) = [1000000000000000000, 1000000000000000000] - -BASE_N_COINS: constant(int128) = 3 -N_ALL_COINS: constant(int128) = N_COINS + BASE_N_COINS - 1 -BASE_PRECISION_MUL: constant(uint256[BASE_N_COINS]) = [1, 1000000000000, 1000000000000] -BASE_RATES: constant(uint256[BASE_N_COINS]) = [1000000000000000000, 1000000000000000000000000000000, 1000000000000000000000000000000] - -# An asset which may have a transfer fee (USDT) -FEE_ASSET: constant(address) = 0xdAC17F958D2ee523a2206206994597C13D831ec7 - -MAX_ADMIN_FEE: constant(uint256) = 10 * 10 ** 9 -MAX_FEE: constant(uint256) = 5 * 10 ** 9 -MAX_A: constant(uint256) = 10 ** 6 -MAX_A_CHANGE: constant(uint256) = 10 - -ADMIN_ACTIONS_DELAY: constant(uint256) = 3 * 86400 -MIN_RAMP_TIME: constant(uint256) = 86400 - -coins: public(address[N_COINS]) -balances: public(uint256[N_COINS]) -fee: public(uint256) # fee * 1e10 -admin_fee: public(uint256) # admin_fee * 1e10 - -owner: public(address) -token: CurveToken - -# Token corresponding to the pool is always the last one -BASE_POOL_COINS: constant(int128) = 3 -BASE_CACHE_EXPIRES: constant(int128) = 10 * 60 # 10 min -base_pool: public(address) -base_virtual_price: public(uint256) -base_cache_updated: public(uint256) -base_coins: public(address[BASE_POOL_COINS]) - -A_PRECISION: constant(uint256) = 100 -initial_A: public(uint256) -future_A: public(uint256) -initial_A_time: public(uint256) -future_A_time: public(uint256) - -admin_actions_deadline: public(uint256) -transfer_ownership_deadline: public(uint256) -future_fee: public(uint256) -future_admin_fee: public(uint256) -future_owner: public(address) - -is_killed: bool -kill_deadline: uint256 -KILL_DEADLINE_DT: constant(uint256) = 2 * 30 * 86400 - - -@external -def __init__( - _owner: address, - _coins: address[N_COINS], - _pool_token: address, - _base_pool: address, - _A: uint256, - _fee: uint256, - _admin_fee: uint256 -): - """ - @notice Contract constructor - @param _owner Contract owner address - @param _coins Addresses of ERC20 conracts of coins - @param _pool_token Address of the token representing LP share - @param _base_pool Address of the base pool (which will have a virtual price) - @param _A Amplification coefficient multiplied by n * (n - 1) - @param _fee Fee to charge for exchanges - @param _admin_fee Admin fee - """ - for i in range(N_COINS): - assert _coins[i] != ZERO_ADDRESS - self.coins = _coins - self.initial_A = _A * A_PRECISION - self.future_A = _A * A_PRECISION - self.fee = _fee - self.admin_fee = _admin_fee - self.owner = _owner - self.kill_deadline = block.timestamp + KILL_DEADLINE_DT - self.token = CurveToken(_pool_token) - - self.base_pool = _base_pool - self.base_virtual_price = Curve(_base_pool).get_virtual_price() - self.base_cache_updated = block.timestamp - for i in range(BASE_POOL_COINS): - _base_coin: address = Curve(_base_pool).coins(convert(i, uint256)) - self.base_coins[i] = _base_coin - - # approve underlying coins for infinite transfers - _response: Bytes[32] = raw_call( - _base_coin, - concat( - method_id("approve(address,uint256)"), - convert(_base_pool, bytes32), - convert(MAX_UINT256, bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) - - -@view -@internal -def _A() -> uint256: - """ - Handle ramping A up or down - """ - t1: uint256 = self.future_A_time - A1: uint256 = self.future_A - - if block.timestamp < t1: - A0: uint256 = self.initial_A - t0: uint256 = self.initial_A_time - # Expressions in uint256 cannot have negative numbers, thus "if" - if A1 > A0: - return A0 + (A1 - A0) * (block.timestamp - t0) / (t1 - t0) - else: - return A0 - (A0 - A1) * (block.timestamp - t0) / (t1 - t0) - - else: # when t1 == 0 or block.timestamp >= t1 - return A1 - - -@view -@external -def A() -> uint256: - return self._A() / A_PRECISION - - -@view -@external -def A_precise() -> uint256: - return self._A() - - -@view -@internal -def _xp(vp_rate: uint256) -> uint256[N_COINS]: - result: uint256[N_COINS] = RATES - result[MAX_COIN] = vp_rate # virtual price for the metacurrency - for i in range(N_COINS): - result[i] = result[i] * self.balances[i] / PRECISION - return result - - -@pure -@internal -def _xp_mem(vp_rate: uint256, _balances: uint256[N_COINS]) -> uint256[N_COINS]: - result: uint256[N_COINS] = RATES - result[MAX_COIN] = vp_rate # virtual price for the metacurrency - for i in range(N_COINS): - result[i] = result[i] * _balances[i] / PRECISION - return result - - -@internal -def _vp_rate() -> uint256: - if block.timestamp > self.base_cache_updated + BASE_CACHE_EXPIRES: - vprice: uint256 = Curve(self.base_pool).get_virtual_price() - self.base_virtual_price = vprice - self.base_cache_updated = block.timestamp - return vprice - else: - return self.base_virtual_price - - -@internal -@view -def _vp_rate_ro() -> uint256: - if block.timestamp > self.base_cache_updated + BASE_CACHE_EXPIRES: - return Curve(self.base_pool).get_virtual_price() - else: - return self.base_virtual_price - - -@pure -@internal -def get_D(xp: uint256[N_COINS], amp: uint256) -> uint256: - S: uint256 = 0 - Dprev: uint256 = 0 - for _x in xp: - S += _x - if S == 0: - return 0 - - D: uint256 = S - Ann: uint256 = amp * N_COINS - for _i in range(255): - D_P: uint256 = D - for _x in xp: - D_P = D_P * D / (_x * N_COINS) # If division by 0, this will be borked: only withdrawal will work. And that is good - Dprev = D - D = (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (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 - - -@view -@internal -def get_D_mem(vp_rate: uint256, _balances: uint256[N_COINS], amp: uint256) -> uint256: - xp: uint256[N_COINS] = self._xp_mem(vp_rate, _balances) - return self.get_D(xp, amp) - - -@view -@external -def get_virtual_price() -> uint256: - """ - @notice The current virtual price of the pool LP token - @dev Useful for calculating profits - @return LP token virtual price normalized to 1e18 - """ - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate_ro() - xp: uint256[N_COINS] = self._xp(vp_rate) - D: uint256 = self.get_D(xp, amp) - # 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 - - -@view -@external -def calc_token_amount(amounts: uint256[N_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 amounts Amount of each coin being deposited - @param is_deposit set True for deposits, False for withdrawals - @return Expected amount of LP tokens received - """ - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate_ro() - _balances: uint256[N_COINS] = self.balances - D0: uint256 = self.get_D_mem(vp_rate, _balances, amp) - for i in range(N_COINS): - if is_deposit: - _balances[i] += amounts[i] - else: - _balances[i] -= amounts[i] - D1: uint256 = self.get_D_mem(vp_rate, _balances, amp) - token_amount: uint256 = self.token.totalSupply() - diff: uint256 = 0 - if is_deposit: - diff = D1 - D0 - else: - diff = D0 - D1 - return diff * token_amount / D0 - - -@external -@nonreentrant('lock') -def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256) -> uint256: - """ - @notice Deposit coins into the pool - @param amounts List of amounts of coins to deposit - @param min_mint_amount Minimum amount of LP tokens to mint from the deposit - @return Amount of LP tokens received by depositing - """ - assert not self.is_killed # dev: is killed - - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate() - token_supply: uint256 = self.token.totalSupply() - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - _admin_fee: uint256 = self.admin_fee - - # Initial invariant - D0: uint256 = 0 - old_balances: uint256[N_COINS] = self.balances - if token_supply > 0: - D0 = self.get_D_mem(vp_rate, old_balances, amp) - new_balances: uint256[N_COINS] = old_balances - - for i in range(N_COINS): - if token_supply == 0: - assert amounts[i] > 0 # dev: initial deposit requires all coins - # balances store amounts of c-tokens - new_balances[i] = old_balances[i] + amounts[i] - - # Invariant after change - D1: uint256 = self.get_D_mem(vp_rate, new_balances, amp) - assert D1 > D0 - - # We need to recalculate the invariant accounting for fees - # to calculate fair user's share - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - 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(vp_rate, new_balances, amp) - 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 amounts[i] > 0: - assert ERC20(self.coins[i]).transferFrom(msg.sender, self, amounts[i]) # dev: failed transfer - - # Mint pool tokens - self.token.mint(msg.sender, mint_amount) - - log AddLiquidity(msg.sender, amounts, fees, D1, token_supply + mint_amount) - - return mint_amount - - -@view -@internal -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 # dev: same coin - assert j >= 0 # dev: j below zero - assert j < N_COINS # dev: j above N_COINS - - # should be unreachable, but good for safety - assert i >= 0 - assert i < N_COINS - - amp: uint256 = self._A() - D: uint256 = self.get_D(xp_, amp) - - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: uint256 = 0 - c: uint256 = D - Ann: uint256 = amp * N_COINS - - 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 * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann # - D - 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 - - -@view -@external -def get_dy(i: int128, j: int128, dx: uint256) -> uint256: - # dx and dy in c-units - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = self._vp_rate_ro() - xp: uint256[N_COINS] = self._xp(rates[MAX_COIN]) - - x: uint256 = xp[i] + (dx * rates[i] / PRECISION) - y: uint256 = self.get_y(i, j, x, xp) - dy: uint256 = xp[j] - y - 1 - _fee: uint256 = self.fee * dy / FEE_DENOMINATOR - return (dy - _fee) * PRECISION / rates[j] - - -@view -@external -def get_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256: - # dx and dy in underlying units - vp_rate: uint256 = self._vp_rate_ro() - xp: uint256[N_COINS] = self._xp(vp_rate) - precisions: uint256[N_COINS] = PRECISION_MUL - _base_pool: address = self.base_pool - - # Use base_i or base_j if they are >= 0 - base_i: int128 = i - MAX_COIN - base_j: int128 = j - MAX_COIN - meta_i: int128 = MAX_COIN - meta_j: int128 = MAX_COIN - if base_i < 0: - meta_i = i - if base_j < 0: - meta_j = j - - x: uint256 = 0 - if base_i < 0: - x = xp[i] + dx * precisions[i] - else: - if base_j < 0: - # i is from BasePool - # At first, get the amount of pool tokens - base_inputs: uint256[BASE_N_COINS] = empty(uint256[BASE_N_COINS]) - base_inputs[base_i] = dx - # Token amount transformed to underlying "dollars" - x = Curve(_base_pool).calc_token_amount(base_inputs, True) * vp_rate / PRECISION - # Accounting for deposit/withdraw fees approximately - x -= x * Curve(_base_pool).fee() / (2 * FEE_DENOMINATOR) - # Adding number of pool tokens - x += xp[MAX_COIN] - else: - # If both are from the base pool - return Curve(_base_pool).get_dy(base_i, base_j, dx) - - # This pool is involved only when in-pool assets are used - y: uint256 = self.get_y(meta_i, meta_j, x, xp) - dy: uint256 = xp[meta_j] - y - 1 - dy = (dy - self.fee * dy / FEE_DENOMINATOR) - - # If output is going via the metapool - if base_j < 0: - dy /= precisions[meta_j] - else: - # j is from BasePool - # The fee is already accounted for - dy = Curve(_base_pool).calc_withdraw_one_coin(dy * PRECISION / vp_rate, base_j) - - return dy - - -@external -@nonreentrant('lock') -def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256) -> uint256: - """ - @notice Perform an exchange between two coins - @dev Index values can be found via the `coins` public getter method - @param i Index value for the coin to send - @param j Index valie of the coin to recieve - @param dx Amount of `i` being exchanged - @param min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - assert not self.is_killed # dev: is killed - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = self._vp_rate() - - old_balances: uint256[N_COINS] = self.balances - xp: uint256[N_COINS] = self._xp_mem(rates[MAX_COIN], old_balances) - - x: uint256 = xp[i] + dx * rates[i] / PRECISION - y: uint256 = self.get_y(i, j, x, xp) - - dy: uint256 = xp[j] - y - 1 # -1 just in case there were some rounding errors - dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR - - # Convert all to real units - dy = (dy - dy_fee) * PRECISION / rates[j] - assert dy >= min_dy, "Too few coins in result" - - dy_admin_fee: uint256 = dy_fee * self.admin_fee / FEE_DENOMINATOR - dy_admin_fee = dy_admin_fee * PRECISION / rates[j] - - # Change balances exactly in same way as we change actual ERC20 coin amounts - self.balances[i] = old_balances[i] + dx - # When rounding errors happen, we undercharge admin fee in favor of LP - self.balances[j] = old_balances[j] - dy - dy_admin_fee - - assert ERC20(self.coins[i]).transferFrom(msg.sender, self, dx) - assert ERC20(self.coins[j]).transfer(msg.sender, dy) - - log TokenExchange(msg.sender, i, dx, j, dy) - - return dy - - -@external -@nonreentrant('lock') -def exchange_underlying(i: int128, j: int128, dx: uint256, min_dy: uint256) -> uint256: - """ - @notice Perform an exchange between two underlying coins - @dev Index values can be found via the `underlying_coins` public getter method - @param i Index value for the underlying coin to send - @param j Index valie of the underlying coin to recieve - @param dx Amount of `i` being exchanged - @param min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - assert not self.is_killed # dev: is killed - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = self._vp_rate() - _base_pool: address = self.base_pool - - # Use base_i or base_j if they are >= 0 - base_i: int128 = i - MAX_COIN - base_j: int128 = j - MAX_COIN - meta_i: int128 = MAX_COIN - meta_j: int128 = MAX_COIN - if base_i < 0: - meta_i = i - if base_j < 0: - meta_j = j - dy: uint256 = 0 - - # Addresses for input and output coins - input_coin: address = ZERO_ADDRESS - if base_i < 0: - input_coin = self.coins[i] - else: - input_coin = self.base_coins[base_i] - output_coin: address = ZERO_ADDRESS - if base_j < 0: - output_coin = self.coins[j] - else: - output_coin = self.base_coins[base_j] - - # Handle potential Tether fees - dx_w_fee: uint256 = dx - if input_coin == FEE_ASSET: - dx_w_fee = ERC20(FEE_ASSET).balanceOf(self) - # "safeTransferFrom" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - input_coin, - concat( - method_id("transferFrom(address,address,uint256)"), - convert(msg.sender, bytes32), - convert(self, bytes32), - convert(dx, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransferFrom" - # Handle potential Tether fees - if input_coin == FEE_ASSET: - dx_w_fee = ERC20(FEE_ASSET).balanceOf(self) - dx_w_fee - - if base_i < 0 or base_j < 0: - old_balances: uint256[N_COINS] = self.balances - xp: uint256[N_COINS] = self._xp_mem(rates[MAX_COIN], old_balances) - - x: uint256 = 0 - if base_i < 0: - x = xp[i] + dx_w_fee * rates[i] / PRECISION - else: - # i is from BasePool - # At first, get the amount of pool tokens - base_inputs: uint256[BASE_N_COINS] = empty(uint256[BASE_N_COINS]) - base_inputs[base_i] = dx_w_fee - coin_i: address = self.coins[MAX_COIN] - # Deposit and measure delta - x = ERC20(coin_i).balanceOf(self) - Curve(_base_pool).add_liquidity(base_inputs, 0) - # Need to convert pool token to "virtual" units using rates - # dx is also different now - dx_w_fee = ERC20(coin_i).balanceOf(self) - x - x = dx_w_fee * rates[MAX_COIN] / PRECISION - # Adding number of pool tokens - x += xp[MAX_COIN] - - y: uint256 = self.get_y(meta_i, meta_j, x, xp) - - # Either a real coin or token - dy = xp[meta_j] - y - 1 # -1 just in case there were some rounding errors - dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR - - # Convert all to real units - # Works for both pool coins and real coins - dy = (dy - dy_fee) * PRECISION / rates[meta_j] - - dy_admin_fee: uint256 = dy_fee * self.admin_fee / FEE_DENOMINATOR - dy_admin_fee = dy_admin_fee * PRECISION / rates[meta_j] - - # Change balances exactly in same way as we change actual ERC20 coin amounts - self.balances[meta_i] = old_balances[meta_i] + dx_w_fee - # When rounding errors happen, we undercharge admin fee in favor of LP - self.balances[meta_j] = old_balances[meta_j] - dy - dy_admin_fee - - # Withdraw from the base pool if needed - if base_j >= 0: - out_amount: uint256 = ERC20(output_coin).balanceOf(self) - Curve(_base_pool).remove_liquidity_one_coin(dy, base_j, 0) - dy = ERC20(output_coin).balanceOf(self) - out_amount - - assert dy >= min_dy, "Too few coins in result" - - else: - # If both are from the base pool - dy = ERC20(output_coin).balanceOf(self) - Curve(_base_pool).exchange(base_i, base_j, dx_w_fee, min_dy) - dy = ERC20(output_coin).balanceOf(self) - dy - - # "safeTransfer" which works for ERC20s which return bool or not - _response = raw_call( - output_coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(dy, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - log TokenExchangeUnderlying(msg.sender, i, dx, j, dy) - - return dy - - -@external -@nonreentrant('lock') -def remove_liquidity(_amount: uint256, min_amounts: uint256[N_COINS]) -> uint256[N_COINS]: - """ - @notice Withdraw coins from the pool - @dev Withdrawal amounts are based on current deposit ratios - @param _amount Quantity of LP tokens to burn in the withdrawal - @param min_amounts Minimum amounts of underlying coins to receive - @return List of amounts of coins that were withdrawn - """ - total_supply: uint256 = self.token.totalSupply() - amounts: uint256[N_COINS] = empty(uint256[N_COINS]) - fees: uint256[N_COINS] = empty(uint256[N_COINS]) # Fees are unused but we've got them historically in event - - for i in range(N_COINS): - value: uint256 = self.balances[i] * _amount / total_supply - assert value >= min_amounts[i], "Too few coins in result" - self.balances[i] -= value - amounts[i] = value - assert ERC20(self.coins[i]).transfer(msg.sender, value) - - self.token.burnFrom(msg.sender, _amount) # dev: insufficient funds - - log RemoveLiquidity(msg.sender, amounts, fees, total_supply - _amount) - - return amounts - - -@external -@nonreentrant('lock') -def remove_liquidity_imbalance(amounts: uint256[N_COINS], max_burn_amount: uint256) -> uint256: - """ - @notice Withdraw coins from the pool in an imbalanced amount - @param amounts List of amounts of underlying coins to withdraw - @param max_burn_amount Maximum amount of LP token to burn in the withdrawal - @return Actual amount of the LP token burned in the withdrawal - """ - assert not self.is_killed # dev: is killed - - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate() - - token_supply: uint256 = self.token.totalSupply() - assert token_supply != 0 # dev: zero total supply - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - _admin_fee: uint256 = self.admin_fee - - old_balances: uint256[N_COINS] = self.balances - new_balances: uint256[N_COINS] = old_balances - D0: uint256 = self.get_D_mem(vp_rate, old_balances, amp) - for i in range(N_COINS): - new_balances[i] -= amounts[i] - D1: uint256 = self.get_D_mem(vp_rate, new_balances, amp) - - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - 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(vp_rate, new_balances, amp) - - token_amount: uint256 = (D0 - D2) * token_supply / D0 - assert token_amount != 0 # dev: zero tokens burned - token_amount += 1 # In case of rounding errors - make it unfavorable for the "attacker" - assert token_amount <= max_burn_amount, "Slippage screwed you" - - self.token.burnFrom(msg.sender, token_amount) # dev: insufficient funds - for i in range(N_COINS): - if amounts[i] != 0: - assert ERC20(self.coins[i]).transfer(msg.sender, amounts[i]) - - log RemoveLiquidityImbalance(msg.sender, amounts, fees, D1, token_supply - token_amount) - - return token_amount - - -@view -@internal -def get_y_D(A_: uint256, i: int128, xp: uint256[N_COINS], D: uint256) -> uint256: - """ - Calculate x[i] if one reduces D from being calculated for xp to D - - Done by solving quadratic equation iteratively. - x_1**2 + x1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) - x_1**2 + b*x_1 = c - - x_1 = (x_1**2 + c) / (2*x_1 + b) - """ - # x in the input is converted to the same price/precision - - assert i >= 0 # dev: i below zero - assert i < N_COINS # dev: i above N_COINS - - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: uint256 = 0 - - c: uint256 = D - Ann: uint256 = A_ * N_COINS - - for _i in range(N_COINS): - if _i != i: - _x = xp[_i] - else: - continue - S_ += _x - c = c * D / (_x * N_COINS) - c = c * D * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann - 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 - - -@view -@internal -def _calc_withdraw_one_coin(_token_amount: uint256, i: int128, vp_rate: uint256) -> (uint256, uint256, uint256): - # First, need to calculate - # * Get current D - # * Solve Eqn against y_i for D - _token_amount - amp: uint256 = self._A() - xp: uint256[N_COINS] = self._xp(vp_rate) - D0: uint256 = self.get_D(xp, amp) - - total_supply: uint256 = self.token.totalSupply() - D1: uint256 = D0 - _token_amount * D0 / total_supply - new_y: uint256 = self.get_y_D(amp, i, xp, D1) - - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = vp_rate - - xp_reduced: uint256[N_COINS] = xp - dy_0: uint256 = (xp[i] - new_y) * PRECISION / rates[i] # w/o fees - - for j in range(N_COINS): - dx_expected: uint256 = 0 - if j == i: - dx_expected = xp[j] * D1 / D0 - new_y - else: - dx_expected = xp[j] - xp[j] * D1 / D0 - xp_reduced[j] -= _fee * dx_expected / FEE_DENOMINATOR - - dy: uint256 = xp_reduced[i] - self.get_y_D(amp, i, xp_reduced, D1) - dy = (dy - 1) * PRECISION / rates[i] # Withdraw less to account for rounding errors - - return dy, dy_0 - dy, total_supply - - -@view -@external -def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - """ - @notice Calculate the amount received when withdrawing a single coin - @param _token_amount Amount of LP tokens to burn in the withdrawal - @param i Index value of the coin to withdraw - @return Amount of coin received - """ - vp_rate: uint256 = self._vp_rate_ro() - return self._calc_withdraw_one_coin(_token_amount, i, vp_rate)[0] - - -@external -@nonreentrant('lock') -def remove_liquidity_one_coin(_token_amount: uint256, i: int128, _min_amount: uint256) -> uint256: - """ - @notice Withdraw a single coin from the pool - @param _token_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 coin to receive - @return Amount of coin received - """ - assert not self.is_killed # dev: is killed - - vp_rate: uint256 = self._vp_rate() - dy: uint256 = 0 - dy_fee: uint256 = 0 - total_supply: uint256 = 0 - dy, dy_fee, total_supply = self._calc_withdraw_one_coin(_token_amount, i, vp_rate) - assert dy >= _min_amount, "Not enough coins removed" - - self.balances[i] -= (dy + dy_fee * self.admin_fee / FEE_DENOMINATOR) - self.token.burnFrom(msg.sender, _token_amount) # dev: insufficient funds - assert ERC20(self.coins[i]).transfer(msg.sender, dy) - - log RemoveLiquidityOne(msg.sender, _token_amount, dy, total_supply - _token_amount) - - return dy - - -### Admin functions ### -@external -def ramp_A(_future_A: uint256, _future_time: uint256): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.initial_A_time + MIN_RAMP_TIME - assert _future_time >= block.timestamp + MIN_RAMP_TIME # dev: insufficient time - - _initial_A: uint256 = self._A() - _future_A_p: uint256 = _future_A * A_PRECISION - - assert _future_A > 0 and _future_A < MAX_A - if _future_A_p < _initial_A: - assert _future_A_p * MAX_A_CHANGE >= _initial_A - else: - assert _future_A_p <= _initial_A * MAX_A_CHANGE - - self.initial_A = _initial_A - self.future_A = _future_A_p - self.initial_A_time = block.timestamp - self.future_A_time = _future_time - - log RampA(_initial_A, _future_A_p, block.timestamp, _future_time) - - -@external -def stop_ramp_A(): - assert msg.sender == self.owner # dev: only owner - - current_A: uint256 = self._A() - self.initial_A = current_A - self.future_A = current_A - self.initial_A_time = block.timestamp - self.future_A_time = block.timestamp - # now (block.timestamp < t1) is always False, so we return saved A - - log StopRampA(current_A, block.timestamp) - - -@external -def commit_new_fee(new_fee: uint256, new_admin_fee: uint256): - assert msg.sender == self.owner # dev: only owner - assert self.admin_actions_deadline == 0 # dev: active action - assert new_fee <= MAX_FEE # dev: fee exceeds maximum - assert new_admin_fee <= MAX_ADMIN_FEE # dev: admin fee exceeds maximum - - _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.admin_actions_deadline = _deadline - self.future_fee = new_fee - self.future_admin_fee = new_admin_fee - - log CommitNewFee(_deadline, new_fee, new_admin_fee) - - -@external -def apply_new_fee(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.admin_actions_deadline # dev: insufficient time - assert self.admin_actions_deadline != 0 # dev: no active action - - self.admin_actions_deadline = 0 - _fee: uint256 = self.future_fee - _admin_fee: uint256 = self.future_admin_fee - self.fee = _fee - self.admin_fee = _admin_fee - - log NewFee(_fee, _admin_fee) - - -@external -def revert_new_parameters(): - assert msg.sender == self.owner # dev: only owner - - self.admin_actions_deadline = 0 - - -@external -def commit_transfer_ownership(_owner: address): - assert msg.sender == self.owner # dev: only owner - assert self.transfer_ownership_deadline == 0 # dev: active transfer - - _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.transfer_ownership_deadline = _deadline - self.future_owner = _owner - - log CommitNewAdmin(_deadline, _owner) - - -@external -def apply_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.transfer_ownership_deadline # dev: insufficient time - assert self.transfer_ownership_deadline != 0 # dev: no active transfer - - self.transfer_ownership_deadline = 0 - _owner: address = self.future_owner - self.owner = _owner - - log NewAdmin(_owner) - - -@external -def revert_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - - self.transfer_ownership_deadline = 0 - - -@view -@external -def admin_balances(i: uint256) -> uint256: - return ERC20(self.coins[i]).balanceOf(self) - self.balances[i] - - -@external -def withdraw_admin_fees(): - assert msg.sender == self.owner # dev: only owner - - for i in range(N_COINS): - c: address = self.coins[i] - value: uint256 = ERC20(c).balanceOf(self) - self.balances[i] - if value > 0: - assert ERC20(c).transfer(msg.sender, value) - - -@external -def donate_admin_fees(): - assert msg.sender == self.owner # dev: only owner - for i in range(N_COINS): - self.balances[i] = ERC20(self.coins[i]).balanceOf(self) - - -@external -def kill_me(): - assert msg.sender == self.owner # dev: only owner - assert self.kill_deadline > block.timestamp # dev: deadline has passed - self.is_killed = True - - -@external -def unkill_me(): - assert msg.sender == self.owner # dev: only owner - self.is_killed = False diff --git a/contracts/pools/rsv/pooldata.json b/contracts/pools/rsv/pooldata.json deleted file mode 100644 index 443086a1..00000000 --- a/contracts/pools/rsv/pooldata.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "base_pool": "3pool", - "pool_types": ["meta"], - "swap_address": "0xC18cC39da8b11dA8c3541C598eE022258F9744da", - "lp_token_address": "0xC2Ee6b0334C261ED60C72f6054450b61B8f18E35", - "zap_address": "0xBE175115BF33E12348ff77CcfEE4726866A0Fbd5", - "gauge_addresses": ["0x4dC4A289a8E33600D8bD4cf5F6313E43a37adec7"], - "lp_contract": "CurveTokenV2", - "lp_constructor": { - "symbol": "rsv3CRV", - "name": "Curve.fi RSV/3Crv" - }, - "swap_constructor": { - "_A": 200, - "_fee": 4000000, - "_admin_fee": 0 - }, - "coins": [ - { - "name": "RSV", - "decimals": 18, - "tethered": false, - "underlying_address": "0x196f4727526eA7FB1e17b2071B3d8eAA38486988" - }, - { - "name": "3CRV", - "decimals": 18, - "base_pool_token": true, - "underlying_address": "0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490" - } - ] -} diff --git a/contracts/pools/saave/README.md b/contracts/pools/saave/README.md deleted file mode 100644 index e82cdc37..00000000 --- a/contracts/pools/saave/README.md +++ /dev/null @@ -1,27 +0,0 @@ -# curve-contract/contracts/pools/saave - -[Curve SAAVE pool](https://www.curve.fi/saave), with lending on [Aave](https://aave.com/). - -## Contracts - -* [`StableSwapSAAVE`](StableSwapSAAVE.vy): Curve stablecoin AMM contract - -## Deployments - -* [`CurveContractV3`](../../tokens/CurveTokenV3.vy): [0x02d341CcB60fAaf662bC0554d13778015d1b285C](https://etherscan.io/address/0x02d341CcB60fAaf662bC0554d13778015d1b285C) -* [`LiquidityGaugeV2`](https://github.com/curvefi/curve-dao-contracts/blob/master/contracts/gauges/LiquidityGaugeV2.vy): [0x462253b8F74B72304c145DB0e4Eebd326B22ca39](https://etherscan.io/address/0x462253b8F74B72304c145DB0e4Eebd326B22ca39) -* [`StableSwapSAAVE`](StableSwapSAAVE.vy): [0xeb16ae0052ed37f479f7fe63849198df1765a733](https://etherscan.io/address/0xeb16ae0052ed37f479f7fe63849198df1765a733) - -## Stablecoins - -Curve SAAVE pool supports swaps between the following stablecoins: - -### Wrapped - -* `aDAI`: [0x028171bCA77440897B824Ca71D1c56caC55b68A3](https://etherscan.io/address/0x028171bCA77440897B824Ca71D1c56caC55b68A3) -* `aSUSD`: [0x6c5024cd4f8a59110119c56f8933403a539555eb](https://etherscan.io/address/0x6c5024cd4f8a59110119c56f8933403a539555eb) - -### Underlying - -* `DAI`: [0x6b175474e89094c44da98b954eedeac495271d0f](https://etherscan.io/token/0x6b175474e89094c44da98b954eedeac495271d0f) -* `sUSD`: [0x57ab1ec28d129707052df4df418d58a2d46d5f51](https://etherscan.io/token/0x57ab1ec28d129707052df4df418d58a2d46d5f51) diff --git a/contracts/pools/saave/StableSwapSAAVE.vy b/contracts/pools/saave/StableSwapSAAVE.vy deleted file mode 100644 index 87207385..00000000 --- a/contracts/pools/saave/StableSwapSAAVE.vy +++ /dev/null @@ -1,997 +0,0 @@ -# @version 0.2.8 -""" -@title Curve saPool -@author Curve.Fi -@license Copyright (c) Curve.Fi, 2020 - all rights reserved -@notice Pool implementation with aToken-style lending -""" - -from vyper.interfaces import ERC20 - - -interface LendingPool: - def withdraw(_underlying_asset: address, _amount: uint256, _receiver: address): nonpayable - -interface CurveToken: - def mint(_to: address, _value: uint256) -> bool: nonpayable - def burnFrom(_to: address, _value: uint256) -> bool: nonpayable - - -# Events -event TokenExchange: - buyer: indexed(address) - sold_id: int128 - tokens_sold: uint256 - bought_id: int128 - tokens_bought: uint256 - -event TokenExchangeUnderlying: - buyer: indexed(address) - sold_id: int128 - tokens_sold: uint256 - bought_id: int128 - tokens_bought: uint256 - -event AddLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event RemoveLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - token_supply: uint256 - -event RemoveLiquidityOne: - provider: indexed(address) - token_amount: uint256 - coin_amount: uint256 - -event RemoveLiquidityImbalance: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event CommitNewAdmin: - deadline: indexed(uint256) - admin: indexed(address) - -event NewAdmin: - admin: indexed(address) - -event CommitNewFee: - deadline: indexed(uint256) - fee: uint256 - admin_fee: uint256 - offpeg_fee_multiplier: uint256 - -event NewFee: - fee: uint256 - admin_fee: uint256 - offpeg_fee_multiplier: uint256 - -event RampA: - old_A: uint256 - new_A: uint256 - initial_time: uint256 - future_time: uint256 - -event StopRampA: - A: uint256 - t: uint256 - - -N_COINS: constant(int128) = 2 - -# fixed constants -FEE_DENOMINATOR: constant(uint256) = 10 ** 10 -PRECISION: constant(uint256) = 10 ** 18 - -MAX_ADMIN_FEE: constant(uint256) = 10 * 10 ** 9 -MAX_FEE: constant(uint256) = 5 * 10 ** 9 - -MAX_A: constant(uint256) = 10 ** 6 -MAX_A_CHANGE: constant(uint256) = 10 -A_PRECISION: constant(uint256) = 100 - -ADMIN_ACTIONS_DELAY: constant(uint256) = 3 * 86400 -MIN_RAMP_TIME: constant(uint256) = 86400 - -coins: public(address[N_COINS]) -underlying_coins: public(address[N_COINS]) -admin_balances: public(uint256[N_COINS]) - -fee: public(uint256) # fee * 1e10 -offpeg_fee_multiplier: public(uint256) # * 1e10 -admin_fee: public(uint256) # admin_fee * 1e10 - -owner: public(address) -lp_token: public(address) - -aave_lending_pool: address -aave_referral: uint256 - -initial_A: public(uint256) -future_A: public(uint256) -initial_A_time: public(uint256) -future_A_time: public(uint256) - -admin_actions_deadline: public(uint256) -transfer_ownership_deadline: public(uint256) -future_fee: public(uint256) -future_admin_fee: public(uint256) -future_offpeg_fee_multiplier: public(uint256) # * 1e10 -future_owner: public(address) - -is_killed: bool -kill_deadline: uint256 -KILL_DEADLINE_DT: constant(uint256) = 2 * 30 * 86400 - - -@external -def __init__( - _coins: address[N_COINS], - _underlying_coins: address[N_COINS], - _pool_token: address, - _aave_lending_pool: address, - _A: uint256, - _fee: uint256, - _admin_fee: uint256, - _offpeg_fee_multiplier: uint256, -): - """ - @notice Contract constructor - @param _coins List of wrapped coin addresses - @param _underlying_coins List of underlying coin addresses - @param _pool_token Pool LP token address - @param _aave_lending_pool Aave lending pool address - @param _A Amplification coefficient multiplied by n * (n - 1) - @param _fee Swap fee expressed as an integer with 1e10 precision - @param _admin_fee Percentage of fee taken as an admin fee, - expressed as an integer with 1e10 precision - @param _offpeg_fee_multiplier Offpeg fee multiplier - """ - for i in range(N_COINS): - assert _coins[i] != ZERO_ADDRESS - assert _underlying_coins[i] != ZERO_ADDRESS - - self.coins = _coins - self.underlying_coins = _underlying_coins - self.initial_A = _A * A_PRECISION - self.future_A = _A * A_PRECISION - self.fee = _fee - self.admin_fee = _admin_fee - self.offpeg_fee_multiplier = _offpeg_fee_multiplier - self.owner = msg.sender - self.kill_deadline = block.timestamp + KILL_DEADLINE_DT - self.lp_token = _pool_token - self.aave_lending_pool = _aave_lending_pool - - # approve transfer of underlying coin to aave lending pool - for coin in _underlying_coins: - assert ERC20(coin).approve(_aave_lending_pool, MAX_UINT256) - - -@view -@internal -def _A() -> uint256: - t1: uint256 = self.future_A_time - A1: uint256 = self.future_A - - if block.timestamp < t1: - # handle ramping up and down of A - A0: uint256 = self.initial_A - t0: uint256 = self.initial_A_time - # Expressions in uint256 cannot have negative numbers, thus "if" - if A1 > A0: - return A0 + (A1 - A0) * (block.timestamp - t0) / (t1 - t0) - else: - return A0 - (A0 - A1) * (block.timestamp - t0) / (t1 - t0) - - else: # when t1 == 0 or block.timestamp >= t1 - return A1 - - -@view -@external -def A() -> uint256: - return self._A() / A_PRECISION - - -@view -@external -def A_precise() -> uint256: - return self._A() - - -@pure -@internal -def _dynamic_fee(xpi: uint256, xpj: uint256, _fee: uint256, _feemul: uint256) -> uint256: - if _feemul <= FEE_DENOMINATOR: - return _fee - else: - xps2: uint256 = (xpi + xpj) - xps2 *= xps2 # Doing just ** 2 can overflow apparently - return (_feemul * _fee) / ( - (_feemul - FEE_DENOMINATOR) * 4 * xpi * xpj / xps2 + \ - FEE_DENOMINATOR) - - -@view -@external -def dynamic_fee(i: int128, j: int128) -> uint256: - """ - @notice Return the fee for swapping between `i` and `j` - @param i Index value for the coin to send - @param j Index value of the coin to recieve - @return Swap fee expressed as an integer with 1e10 precision - """ - xpi: uint256 = (ERC20(self.coins[i]).balanceOf(self) - self.admin_balances[i]) - xpj: uint256 = (ERC20(self.coins[j]).balanceOf(self) - self.admin_balances[j]) - return self._dynamic_fee(xpi, xpj, self.fee, self.offpeg_fee_multiplier) - - -@view -@external -def balances(i: uint256) -> uint256: - """ - @notice Get the current balance of a coin within the - pool, less the accrued admin fees - @param i Index value for the coin to query balance of - @return Token balance - """ - return ERC20(self.coins[i]).balanceOf(self) - self.admin_balances[i] - - -@view -@internal -def _balances() -> uint256[N_COINS]: - result: uint256[N_COINS] = empty(uint256[N_COINS]) - for i in range(N_COINS): - result[i] = ERC20(self.coins[i]).balanceOf(self) - self.admin_balances[i] - return result - - -@pure -@internal -def get_D(xp: uint256[N_COINS], amp: uint256) -> uint256: - """ - D invariant calculation in non-overflowing integer operations - iteratively - - A * sum(x_i) * n**n + D = A * D * n**n + D**(n+1) / (n**n * prod(x_i)) - - Converging solution: - D[j+1] = (A * n**n * sum(x_i) - D[j]**(n+1) / (n**n prod(x_i))) / (A * n**n - 1) - """ - S: uint256 = 0 - - for _x in xp: - S += _x - if S == 0: - return 0 - - Dprev: uint256 = 0 - D: uint256 = S - Ann: uint256 = amp * 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 / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (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` - raise - - -@view -@external -def get_virtual_price() -> uint256: - """ - @notice The current virtual price of the pool LP token - @dev Useful for calculating profits - @return LP token virtual price normalized to 1e18 - """ - D: uint256 = self.get_D(self._balances(), self._A()) - # 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 = ERC20(self.lp_token).totalSupply() - return D * PRECISION / token_supply - - -@view -@external -def calc_token_amount(_amounts: uint256[N_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 _amounts Amount of each coin being deposited - @param is_deposit set True for deposits, False for withdrawals - @return Expected amount of LP tokens received - """ - coin_balances: uint256[N_COINS] = self._balances() - amp: uint256 = self._A() - D0: uint256 = self.get_D(coin_balances, amp) - for i in range(N_COINS): - if is_deposit: - coin_balances[i] += _amounts[i] - else: - coin_balances[i] -= _amounts[i] - D1: uint256 = self.get_D(coin_balances, amp) - token_amount: uint256 = ERC20(self.lp_token).totalSupply() - diff: uint256 = 0 - if is_deposit: - diff = D1 - D0 - else: - diff = D0 - D1 - return diff * token_amount / D0 - - -@external -@nonreentrant('lock') -def add_liquidity(_amounts: uint256[N_COINS], _min_mint_amount: uint256, _use_underlying: bool = False) -> uint256: - """ - @notice Deposit coins into the pool - @param _amounts List of amounts of coins to deposit - @param _min_mint_amount Minimum amount of LP tokens to mint from the deposit - @param _use_underlying If True, deposit underlying assets instead of aTokens - @return Amount of LP tokens received by depositing - """ - - assert not self.is_killed # dev: is killed - - # Initial invariant - amp: uint256 = self._A() - old_balances: uint256[N_COINS] = self._balances() - lp_token: address = self.lp_token - token_supply: uint256 = ERC20(lp_token).totalSupply() - D0: uint256 = 0 - if token_supply != 0: - D0 = self.get_D(old_balances, amp) - - new_balances: uint256[N_COINS] = old_balances - for i in range(N_COINS): - if token_supply == 0: - assert _amounts[i] != 0 # dev: initial deposit requires all coins - new_balances[i] += _amounts[i] - - # Invariant after change - D1: uint256 = self.get_D(new_balances, amp) - assert D1 > D0 - - # We need to recalculate the invariant accounting for fees - # to calculate fair user's share - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - mint_amount: uint256 = 0 - if token_supply != 0: - # Only account for fees if we are not the first to deposit - ys: uint256 = (D0 + D1) / N_COINS - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - _feemul: uint256 = self.offpeg_fee_multiplier - _admin_fee: uint256 = self.admin_fee - difference: uint256 = 0 - for i in range(N_COINS): - ideal_balance: uint256 = D1 * old_balances[i] / D0 - new_balance: uint256 = new_balances[i] - if ideal_balance > new_balance: - difference = ideal_balance - new_balance - else: - difference = new_balance - ideal_balance - xs: uint256 = old_balances[i] + new_balance - fees[i] = self._dynamic_fee(xs, ys, _fee, _feemul) * difference / FEE_DENOMINATOR - if _admin_fee != 0: - self.admin_balances[i] += fees[i] * _admin_fee / FEE_DENOMINATOR - new_balances[i] = new_balance - fees[i] - D2: uint256 = self.get_D(new_balances, amp) - mint_amount = token_supply * (D2 - D0) / D0 - else: - mint_amount = D1 # Take the dust if there was any - - assert mint_amount >= _min_mint_amount, "Slippage screwed you" - - # Take coins from the sender - if _use_underlying: - lending_pool: address = self.aave_lending_pool - aave_referral: bytes32 = convert(self.aave_referral, bytes32) - - # Take coins from the sender - for i in range(N_COINS): - amount: uint256 = _amounts[i] - if amount != 0: - coin: address = self.underlying_coins[i] - # transfer underlying coin from msg.sender to self - assert ERC20(coin).transferFrom(msg.sender, self, amount) - - # deposit to aave lending pool - raw_call( - lending_pool, - concat( - method_id("deposit(address,uint256,address,uint16)"), - convert(coin, bytes32), - convert(amount, bytes32), - convert(self, bytes32), - aave_referral, - ) - ) - else: - for i in range(N_COINS): - amount: uint256 = _amounts[i] - if amount != 0: - assert ERC20(self.coins[i]).transferFrom(msg.sender, self, amount) # dev: failed transfer - - # Mint pool tokens - CurveToken(lp_token).mint(msg.sender, mint_amount) - - log AddLiquidity(msg.sender, _amounts, fees, D1, token_supply + mint_amount) - - return mint_amount - - -@view -@internal -def get_y(i: int128, j: int128, x: uint256, xp: uint256[N_COINS]) -> uint256: - """ - Calculate x[j] if one makes x[i] = x - - Done by solving quadratic equation iteratively. - x_1**2 + x1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) - x_1**2 + b*x_1 = c - - x_1 = (x_1**2 + c) / (2*x_1 + b) - """ - # x in the input is converted to the same price/precision - - assert i != j # dev: same coin - assert j >= 0 # dev: j below zero - assert j < N_COINS # dev: j above N_COINS - - # should be unreachable, but good for safety - assert i >= 0 - assert i < N_COINS - - amp: uint256 = self._A() - D: uint256 = self.get_D(xp, amp) - Ann: uint256 = amp * N_COINS - c: uint256 = D - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: 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 * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann # - D - 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: - return y - else: - if y_prev - y <= 1: - return y - raise - - -@view -@internal -def _get_dy(i: int128, j: int128, dx: uint256) -> uint256: - xp: uint256[N_COINS] = self._balances() - - x: uint256 = xp[i] + dx - y: uint256 = self.get_y(i, j, x, xp) - dy: uint256 = (xp[j] - y) - _fee: uint256 = self._dynamic_fee( - (xp[i] + x) / 2, (xp[j] + y) / 2, self.fee, self.offpeg_fee_multiplier - ) * dy / FEE_DENOMINATOR - return dy - _fee - - -@view -@external -def get_dy(i: int128, j: int128, dx: uint256) -> uint256: - return self._get_dy(i, j, dx) - - -@view -@external -def get_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256: - return self._get_dy(i, j, dx) - - -@internal -def _exchange(i: int128, j: int128, dx: uint256) -> uint256: - assert not self.is_killed # dev: is killed - # dx and dy are in aTokens - - xp: uint256[N_COINS] = self._balances() - - x: uint256 = xp[i] + dx - y: uint256 = self.get_y(i, j, x, xp) - dy: uint256 = xp[j] - y - dy_fee: uint256 = dy * self._dynamic_fee( - (xp[i] + x) / 2, (xp[j] + y) / 2, self.fee, self.offpeg_fee_multiplier - ) / FEE_DENOMINATOR - - admin_fee: uint256 = self.admin_fee - if admin_fee != 0: - dy_admin_fee: uint256 = dy_fee * admin_fee / FEE_DENOMINATOR - if dy_admin_fee != 0: - self.admin_balances[j] += dy_admin_fee - - return dy - dy_fee - - -@external -@nonreentrant('lock') -def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256) -> uint256: - """ - @notice Perform an exchange between two coins - @dev Index values can be found via the `coins` public getter method - @param i Index value for the coin to send - @param j Index valie of the coin to recieve - @param dx Amount of `i` being exchanged - @param min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - dy: uint256 = self._exchange(i, j, dx) - assert dy >= min_dy, "Exchange resulted in fewer coins than expected" - - assert ERC20(self.coins[i]).transferFrom(msg.sender, self, dx) - assert ERC20(self.coins[j]).transfer(msg.sender, dy) - - log TokenExchange(msg.sender, i, dx, j, dy) - - return dy - - -@external -@nonreentrant('lock') -def exchange_underlying(i: int128, j: int128, dx: uint256, min_dy: uint256) -> uint256: - """ - @notice Perform an exchange between two underlying coins - @dev Index values can be found via the `underlying_coins` public getter method - @param i Index value for the underlying coin to send - @param j Index valie of the underlying coin to recieve - @param dx Amount of `i` being exchanged - @param min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - dy: uint256 = self._exchange(i, j, dx) - assert dy >= min_dy, "Exchange resulted in fewer coins than expected" - - u_coin_i: address = self.underlying_coins[i] - lending_pool: address = self.aave_lending_pool - - # transfer underlying coin from msg.sender to self - assert ERC20(u_coin_i).transferFrom(msg.sender, self, dx) - - # deposit to aave lending pool - raw_call( - lending_pool, - concat( - method_id("deposit(address,uint256,address,uint16)"), - convert(u_coin_i, bytes32), - convert(dx, bytes32), - convert(self, bytes32), - convert(self.aave_referral, bytes32), - ) - ) - # withdraw `j` underlying from lending pool and transfer to caller - LendingPool(lending_pool).withdraw(self.underlying_coins[j], dy, msg.sender) - - log TokenExchangeUnderlying(msg.sender, i, dx, j, dy) - - return dy - - -@external -@nonreentrant('lock') -def remove_liquidity( - _amount: uint256, - _min_amounts: uint256[N_COINS], - _use_underlying: bool = False, -) -> uint256[N_COINS]: - """ - @notice Withdraw coins from the pool - @dev Withdrawal amounts are based on current deposit ratios - @param _amount Quantity of LP tokens to burn in the withdrawal - @param _min_amounts Minimum amounts of underlying coins to receive - @param _use_underlying If True, withdraw underlying assets instead of aTokens - @return List of amounts of coins that were withdrawn - """ - amounts: uint256[N_COINS] = self._balances() - lp_token: address = self.lp_token - total_supply: uint256 = ERC20(lp_token).totalSupply() - CurveToken(lp_token).burnFrom(msg.sender, _amount) # dev: insufficient funds - - lending_pool: address = ZERO_ADDRESS - if _use_underlying: - lending_pool = self.aave_lending_pool - - for i in range(N_COINS): - value: uint256 = amounts[i] * _amount / total_supply - assert value >= _min_amounts[i], "Withdrawal resulted in fewer coins than expected" - amounts[i] = value - if _use_underlying: - LendingPool(lending_pool).withdraw(self.underlying_coins[i], value, msg.sender) - else: - assert ERC20(self.coins[i]).transfer(msg.sender, value) - - log RemoveLiquidity(msg.sender, amounts, empty(uint256[N_COINS]), total_supply - _amount) - - return amounts - - -@external -@nonreentrant('lock') -def remove_liquidity_imbalance( - _amounts: uint256[N_COINS], - _max_burn_amount: uint256, - _use_underlying: bool = False -) -> uint256: - """ - @notice Withdraw coins from the pool in an imbalanced amount - @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 _use_underlying If True, withdraw underlying assets instead of aTokens - @return Actual amount of the LP token burned in the withdrawal - """ - assert not self.is_killed # dev: is killed - - amp: uint256 = self._A() - old_balances: uint256[N_COINS] = self._balances() - D0: uint256 = self.get_D(old_balances, amp) - new_balances: uint256[N_COINS] = old_balances - for i in range(N_COINS): - new_balances[i] -= _amounts[i] - D1: uint256 = self.get_D(new_balances, amp) - ys: uint256 = (D0 + D1) / N_COINS - - lp_token: address = self.lp_token - token_supply: uint256 = ERC20(lp_token).totalSupply() - assert token_supply != 0 # dev: zero total supply - - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - _feemul: uint256 = self.offpeg_fee_multiplier - _admin_fee: uint256 = self.admin_fee - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - for i in range(N_COINS): - ideal_balance: uint256 = D1 * old_balances[i] / D0 - new_balance: uint256 = new_balances[i] - difference: uint256 = 0 - if ideal_balance > new_balance: - difference = ideal_balance - new_balance - else: - difference = new_balance - ideal_balance - xs: uint256 = new_balance + old_balances[i] - fees[i] = self._dynamic_fee(xs, ys, _fee, _feemul) * difference / FEE_DENOMINATOR - if _admin_fee != 0: - self.admin_balances[i] += fees[i] * _admin_fee / FEE_DENOMINATOR - new_balances[i] -= fees[i] - D2: uint256 = self.get_D(new_balances, amp) - - token_amount: uint256 = (D0 - D2) * token_supply / D0 - assert token_amount != 0 # dev: zero tokens burned - assert token_amount <= _max_burn_amount, "Slippage screwed you" - - CurveToken(lp_token).burnFrom(msg.sender, token_amount) # dev: insufficient funds - - lending_pool: address = ZERO_ADDRESS - if _use_underlying: - lending_pool = self.aave_lending_pool - - for i in range(N_COINS): - amount: uint256 = _amounts[i] - if amount != 0: - if _use_underlying: - LendingPool(lending_pool).withdraw(self.underlying_coins[i], amount, msg.sender) - else: - assert ERC20(self.coins[i]).transfer(msg.sender, amount) - - log RemoveLiquidityImbalance(msg.sender, _amounts, fees, D1, token_supply - token_amount) - - return token_amount - - -@pure -@internal -def get_y_D(A_: uint256, i: int128, xp: uint256[N_COINS], D: uint256) -> uint256: - """ - Calculate x[i] if one reduces D from being calculated for xp to D - - Done by solving quadratic equation iteratively. - x_1**2 + x1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) - x_1**2 + b*x_1 = c - - x_1 = (x_1**2 + c) / (2*x_1 + b) - """ - # x in the input is converted to the same price/precision - - assert i >= 0 # dev: i below zero - assert i < N_COINS # dev: i above N_COINS - - Ann: uint256 = A_ * N_COINS - c: uint256 = D - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: uint256 = 0 - - for _i in range(N_COINS): - if _i != i: - _x = xp[_i] - else: - continue - S_ += _x - c = c * D / (_x * N_COINS) - c = c * D * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann - 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: - return y - else: - if y_prev - y <= 1: - return y - raise - - -@view -@internal -def _calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - # First, need to calculate - # * Get current D - # * Solve Eqn against y_i for D - _token_amount - amp: uint256 = self._A() - xp: uint256[N_COINS] = self._balances() - - D0: uint256 = self.get_D(xp, amp) - D1: uint256 = D0 - _token_amount * D0 / ERC20(self.lp_token).totalSupply() - new_y: uint256 = self.get_y_D(amp, i, xp, D1) - - xp_reduced: uint256[N_COINS] = xp - ys: uint256 = (D0 + D1) / (2 * N_COINS) - - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - feemul: uint256 = self.offpeg_fee_multiplier - for j in range(N_COINS): - dx_expected: uint256 = 0 - xavg: uint256 = 0 - if j == i: - dx_expected = xp[j] * D1 / D0 - new_y - xavg = (xp[j] + new_y) / 2 - else: - dx_expected = xp[j] - xp[j] * D1 / D0 - xavg = xp[j] - xp_reduced[j] -= self._dynamic_fee(xavg, ys, _fee, feemul) * dx_expected / FEE_DENOMINATOR - - dy: uint256 = xp_reduced[i] - self.get_y_D(amp, i, xp_reduced, D1) - - return dy - 1 - - -@view -@external -def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - """ - @notice Calculate the amount received when withdrawing a single coin - @dev Result is the same for underlying or wrapped asset withdrawals - @param _token_amount Amount of LP tokens to burn in the withdrawal - @param i Index value of the coin to withdraw - @return Amount of coin received - """ - return self._calc_withdraw_one_coin(_token_amount, i) - - -@external -@nonreentrant('lock') -def remove_liquidity_one_coin( - _token_amount: uint256, - i: int128, - _min_amount: uint256, - _use_underlying: bool = False -) -> uint256: - """ - @notice Withdraw a single coin from the pool - @param _token_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 coin to receive - @param _use_underlying If True, withdraw underlying assets instead of aTokens - @return Amount of coin received - """ - assert not self.is_killed # dev: is killed - - dy: uint256 = self._calc_withdraw_one_coin(_token_amount, i) - assert dy >= _min_amount, "Not enough coins removed" - - CurveToken(self.lp_token).burnFrom(msg.sender, _token_amount) # dev: insufficient funds - - if _use_underlying: - LendingPool(self.aave_lending_pool).withdraw(self.underlying_coins[i], dy, msg.sender) - else: - assert ERC20(self.coins[i]).transfer(msg.sender, dy) - - log RemoveLiquidityOne(msg.sender, _token_amount, dy) - - return dy - - -### Admin functions ### - -@external -def ramp_A(_future_A: uint256, _future_time: uint256): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.initial_A_time + MIN_RAMP_TIME - assert _future_time >= block.timestamp + MIN_RAMP_TIME # dev: insufficient time - - _initial_A: uint256 = self._A() - _future_A_p: uint256 = _future_A * A_PRECISION - - assert _future_A > 0 and _future_A < MAX_A - if _future_A_p < _initial_A: - assert _future_A_p * MAX_A_CHANGE >= _initial_A - else: - assert _future_A_p <= _initial_A * MAX_A_CHANGE - - self.initial_A = _initial_A - self.future_A = _future_A_p - self.initial_A_time = block.timestamp - self.future_A_time = _future_time - - log RampA(_initial_A, _future_A_p, block.timestamp, _future_time) - - -@external -def stop_ramp_A(): - assert msg.sender == self.owner # dev: only owner - - current_A: uint256 = self._A() - self.initial_A = current_A - self.future_A = current_A - self.initial_A_time = block.timestamp - self.future_A_time = block.timestamp - # now (block.timestamp < t1) is always False, so we return saved A - - log StopRampA(current_A, block.timestamp) - - -@external -def commit_new_fee(new_fee: uint256, new_admin_fee: uint256, new_offpeg_fee_multiplier: uint256): - assert msg.sender == self.owner # dev: only owner - assert self.admin_actions_deadline == 0 # dev: active action - assert new_fee <= MAX_FEE # dev: fee exceeds maximum - assert new_admin_fee <= MAX_ADMIN_FEE # dev: admin fee exceeds maximum - assert new_offpeg_fee_multiplier * new_fee <= MAX_FEE * FEE_DENOMINATOR # dev: offpeg multiplier exceeds maximum - - _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.admin_actions_deadline = _deadline - self.future_fee = new_fee - self.future_admin_fee = new_admin_fee - self.future_offpeg_fee_multiplier = new_offpeg_fee_multiplier - - log CommitNewFee(_deadline, new_fee, new_admin_fee, new_offpeg_fee_multiplier) - - -@external -def apply_new_fee(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.admin_actions_deadline # dev: insufficient time - assert self.admin_actions_deadline != 0 # dev: no active action - - self.admin_actions_deadline = 0 - _fee: uint256 = self.future_fee - _admin_fee: uint256 = self.future_admin_fee - _fml: uint256 = self.future_offpeg_fee_multiplier - self.fee = _fee - self.admin_fee = _admin_fee - self.offpeg_fee_multiplier = _fml - - log NewFee(_fee, _admin_fee, _fml) - - -@external -def revert_new_parameters(): - assert msg.sender == self.owner # dev: only owner - - self.admin_actions_deadline = 0 - - -@external -def commit_transfer_ownership(_owner: address): - assert msg.sender == self.owner # dev: only owner - assert self.transfer_ownership_deadline == 0 # dev: active transfer - - _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.transfer_ownership_deadline = _deadline - self.future_owner = _owner - - log CommitNewAdmin(_deadline, _owner) - - -@external -def apply_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.transfer_ownership_deadline # dev: insufficient time - assert self.transfer_ownership_deadline != 0 # dev: no active transfer - - self.transfer_ownership_deadline = 0 - _owner: address = self.future_owner - self.owner = _owner - - log NewAdmin(_owner) - - -@external -def revert_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - - self.transfer_ownership_deadline = 0 - - -@external -def withdraw_admin_fees(): - assert msg.sender == self.owner # dev: only owner - - for i in range(N_COINS): - value: uint256 = self.admin_balances[i] - if value != 0: - assert ERC20(self.coins[i]).transfer(msg.sender, value) - self.admin_balances[i] = 0 - - -@external -def donate_admin_fees(): - """ - Just in case admin balances somehow become higher than total (rounding error?) - this can be used to fix the state, too - """ - assert msg.sender == self.owner # dev: only owner - self.admin_balances = empty(uint256[N_COINS]) - - -@external -def kill_me(): - assert msg.sender == self.owner # dev: only owner - assert self.kill_deadline > block.timestamp # dev: deadline has passed - self.is_killed = True - - -@external -def unkill_me(): - assert msg.sender == self.owner # dev: only owner - self.is_killed = False - - -@external -def set_aave_referral(referral_code: uint256): - assert msg.sender == self.owner # dev: only owner - assert referral_code < 2 ** 16 # dev: uint16 overflow - self.aave_referral = referral_code diff --git a/contracts/pools/saave/pooldata.json b/contracts/pools/saave/pooldata.json deleted file mode 100644 index a12ca61e..00000000 --- a/contracts/pools/saave/pooldata.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "lp_contract": "CurveTokenV3", - "pool_types": ["arate"], - "wrapped_contract": "ATokenMock", - "swap_address": "0xeb16ae0052ed37f479f7fe63849198df1765a733", - "lp_token_address": "0x02d341CcB60fAaf662bC0554d13778015d1b285C", - "gauge_addresses": ["0x462253b8F74B72304c145DB0e4Eebd326B22ca39"], - "lp_constructor": { - "symbol": "saCRV", - "name": "Curve.fi aDAI/aSUSD" - }, - "swap_constructor": { - "_A": 100, - "_fee": 4000000, - "_admin_fee": 5000000000, - "_offpeg_fee_multiplier": 20000000000, - "_aave_lending_pool": "0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9" - }, - "coins": [ - { - "name": "aDAI", - "underlying_name": "DAI", - "decimals": 18, - "wrapped_decimals": 18, - "tethered": false, - "underlying_address": "0x6b175474e89094c44da98b954eedeac495271d0f", - "wrapped_address": "0x028171bCA77440897B824Ca71D1c56caC55b68A3" - }, - { - "name": "aSUSD", - "underlying_name": "SUSD", - "decimals": 18, - "wrapped_decimals": 18, - "tethered": false, - "underlying_address": "0x57ab1ec28d129707052df4df418d58a2d46d5f51", - "wrapped_address": "0x6c5024cd4f8a59110119c56f8933403a539555eb" - } - ] -} diff --git a/contracts/pools/sbtc/README.md b/contracts/pools/sbtc/README.md deleted file mode 100644 index 2e7ef610..00000000 --- a/contracts/pools/sbtc/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# curve-contract/contracts/pools/sbtc - -[Curve sBTC pool](https://www.curve.fi/sbtc). This is a no-lending pool. - -## Contracts - -* [`StableSwapSBTC`](StableSwapSBTC.vy): Curve stablecoin AMM contract - -## Deployments - -* [`CurveContractV1`](../../tokens/CurveTokenV1.vy): [0x075b1bb99792c9E1041bA13afEf80C91a1e70fB3](https://etherscan.io/address/0x075b1bb99792c9E1041bA13afEf80C91a1e70fB3) -* [`LiquidityGaugeReward`](../../gauges/LiquidityGaugeReward.vy): [0x705350c4BcD35c9441419DdD5d2f097d7a55410F](https://etherscan.io/address/0x705350c4BcD35c9441419DdD5d2f097d7a55410F) -* [`StableSwapSBTC`](StableSwapSBTC.vy): [0x7fC77b5c7614E1533320Ea6DDc2Eb61fa00A9714](https://etherscan.io/address/0x7fC77b5c7614E1533320Ea6DDc2Eb61fa00A9714) - -## Stablecoins - -Curve sBTC pool supports swaps between the following stablecoins: - -* `renBTC`: [0xeb4c2781e4eba804ce9a9803c67d0893436bb27d](https://etherscan.io/address/0xeb4c2781e4eba804ce9a9803c67d0893436bb27d) -* `wBTC`: [0x2260fac5e5542a773aa44fbcfedf7c193bc2c599](https://etherscan.io/address/0x2260fac5e5542a773aa44fbcfedf7c193bc2c599) -* `sBTC`: [0xfe18be6b3bd88a2d2a7f928d00292e7a9963cfc6](https://etherscan.io/address/0xfe18be6b3bd88a2d2a7f928d00292e7a9963cfc6) diff --git a/contracts/pools/sbtc/StableSwapSBTC.vy b/contracts/pools/sbtc/StableSwapSBTC.vy deleted file mode 100644 index 5bafcfb7..00000000 --- a/contracts/pools/sbtc/StableSwapSBTC.vy +++ /dev/null @@ -1,743 +0,0 @@ -# @version 0.1.0b17 -# (c) Curve.Fi, 2020 -# Pools for renBTC/wBTC. Ren can potentially change amount of underlying bitcoins - - -# External Contracts -contract ERC20m: - def totalSupply() -> uint256: constant - def allowance(_owner: address, _spender: address) -> uint256: constant - def transfer(_to: address, _value: uint256) -> bool: modifying - def transferFrom(_from: address, _to: address, _value: uint256) -> bool: modifying - def approve(_spender: address, _value: uint256) -> bool: modifying - def mint(_to: address, _value: uint256): modifying - def burn(_value: uint256): modifying - def burnFrom(_to: address, _value: uint256): modifying - def name() -> string[64]: constant - def symbol() -> string[32]: constant - def decimals() -> uint256: constant - def balanceOf(arg0: address) -> uint256: constant - def set_minter(_minter: address): modifying - - - -# External Contracts -contract cERC20: - def totalSupply() -> uint256: constant - def allowance(_owner: address, _spender: address) -> uint256: constant - def transfer(_to: address, _value: uint256) -> bool: modifying - def transferFrom(_from: address, _to: address, _value: uint256) -> bool: modifying - def approve(_spender: address, _value: uint256) -> bool: modifying - def burn(_value: uint256): modifying - def burnFrom(_to: address, _value: uint256): modifying - def name() -> string[64]: constant - def symbol() -> string[32]: constant - def decimals() -> uint256: constant - def balanceOf(arg0: address) -> uint256: constant - def mint(mintAmount: uint256) -> uint256: modifying - def redeem(redeemTokens: uint256) -> uint256: modifying - def redeemUnderlying(redeemAmount: uint256) -> uint256: modifying - def exchangeRateStored() -> uint256: constant - def exchangeRateCurrent() -> uint256: constant - def supplyRatePerBlock() -> uint256: constant - def accrualBlockNumber() -> uint256: constant - - -from vyper.interfaces import ERC20 - - -# This can (and needs to) be changed at compile time -N_COINS: constant(int128) = 3 # <- change - -ZERO256: constant(uint256) = 0 # This hack is really bad XXX -ZEROS: constant(uint256[N_COINS]) = [ZERO256, ZERO256, ZERO256] # <- change - -USE_LENDING: constant(bool[N_COINS]) = [True, False, False] - -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]) = [convert(10000000000, uint256), convert(10000000000, uint256), convert(1, uint256)] -# 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 -min_ramp_time: constant(uint256) = 86400 - -# Events -TokenExchange: 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}) -RemoveLiquidityOne: event({provider: indexed(address), token_amount: uint256, coin_amount: 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)}) - -CommitNewFee: event({deadline: indexed(timestamp), fee: uint256, admin_fee: uint256}) -NewFee: event({fee: uint256, admin_fee: uint256}) -RampA: event({old_A: uint256, new_A: uint256, initial_time: timestamp, future_time: timestamp}) -StopRampA: event({A: uint256, t: timestamp}) - -coins: public(address[N_COINS]) -balances: public(uint256[N_COINS]) -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 -max_A_change: constant(uint256) = 10 - -owner: public(address) -token: ERC20m - -initial_A: public(uint256) -future_A: public(uint256) -initial_A_time: public(timestamp) -future_A_time: public(timestamp) - -admin_actions_deadline: public(timestamp) -transfer_ownership_deadline: public(timestamp) -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], - _pool_token: address, - _A: uint256, _fee: uint256): - """ - _coins: Addresses of ERC20 conracts of coins - _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 - self.balances[i] = 0 - self.coins = _coins - self.initial_A = _A - self.future_A = _A - self.fee = _fee - self.owner = msg.sender - self.kill_deadline = block.timestamp + kill_deadline_dt - self.is_killed = False - self.token = ERC20m(_pool_token) - - -@constant -@private -def _A() -> uint256: - """ - Handle ramping A up or down - """ - t1: timestamp = self.future_A_time - A1: uint256 = self.future_A - - if block.timestamp < t1: - A0: uint256 = self.initial_A - t0: timestamp = self.initial_A_time - # Expressions in uint256 cannot have negative numbers, thus "if" - if A1 > A0: - return A0 + (A1 - A0) * (block.timestamp - t0) / (t1 - t0) - else: - return A0 - (A0 - A1) * (block.timestamp - t0) / (t1 - t0) - - else: # when t1 == 0 or block.timestamp >= t1 - return A1 - - -@constant -@public -def A() -> uint256: - return self._A() - - -@private -@constant -def _rates() -> uint256[N_COINS]: - result: uint256[N_COINS] = PRECISION_MUL - use_lending: bool[N_COINS] = USE_LENDING - for i in range(N_COINS): - rate: uint256 = LENDING_PRECISION # Used with no lending - if use_lending[i]: - rate = cERC20(self.coins[i]).exchangeRateCurrent() - 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] / LENDING_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], amp: uint256) -> uint256: - S: uint256 = 0 - for _x in xp: - S += _x - if S == 0: - return 0 - - Dprev: uint256 = 0 - D: uint256 = S - Ann: uint256 = amp * N_COINS - for _i in range(255): - D_P: uint256 = D - for _x in xp: - D_P = D_P * D / (_x * N_COINS) # If division by 0, this will be borked: only withdrawal will work. And that is good - 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], amp: uint256) -> uint256: - return self.get_D(self._xp_mem(rates, _balances), amp) - - -@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._rates()), self._A()) - # 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._rates() - amp: uint256 = self._A() - D0: uint256 = self.get_D_mem(rates, _balances, amp) - 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, amp) - 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 - - 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 - amp: uint256 = self._A() - - token_supply: uint256 = self.token.totalSupply() - rates: uint256[N_COINS] = self._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, amp) - 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, amp) - 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, amp) - 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 amounts[i] > 0: - assert_modifiable( - cERC20(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) - - amp: uint256 = self._A() - D: uint256 = self.get_D(_xp, amp) - c: uint256 = D - S_: uint256 = 0 - Ann: uint256 = amp * 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._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 - 1) * PRECISION / rates[j] - _fee: uint256 = self.fee * dy / FEE_DENOMINATOR - return dy - _fee - - -@public -@constant -def get_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256: - # dx and dy in underlying units - rates: uint256[N_COINS] = self._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 - 1) / precisions[j] - _fee: uint256 = self.fee * dy / FEE_DENOMINATOR - return dy - _fee - - -@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 - - old_balances: uint256[N_COINS] = self.balances - xp: uint256[N_COINS] = self._xp_mem(rates, old_balances) - - x: uint256 = xp[i] + dx * rates[i] / PRECISION - y: uint256 = self.get_y(i, j, x, xp) - - dy: uint256 = xp[j] - y - 1 # -1 just in case there were some rounding errors - dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR - dy_admin_fee: uint256 = dy_fee * self.admin_fee / FEE_DENOMINATOR - - # Convert all to real units - dy = (dy - dy_fee) * PRECISION / rates[j] - dy_admin_fee = dy_admin_fee * PRECISION / rates[j] - - # Change balances exactly in same way as we change actual ERC20 coin amounts - self.balances[i] = old_balances[i] + dx - # When rounding errors happen, we undercharge admin fee in favor of LP - self.balances[j] = old_balances[j] - dy - dy_admin_fee - - return dy - - -@public -@nonreentrant('lock') -def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256): - rates: uint256[N_COINS] = self._rates() - dy: uint256 = self._exchange(i, j, dx, rates) - assert dy >= min_dy, "Exchange resulted in fewer coins than expected" - use_lending: bool[N_COINS] = USE_LENDING - - assert_modifiable(cERC20(self.coins[i]).transferFrom(msg.sender, self, dx)) - assert_modifiable(cERC20(self.coins[j]).transfer(msg.sender, dy)) - - log.TokenExchange(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 # Fees are unused but we've got them historically in event - 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 - assert_modifiable(cERC20(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 - 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._rates() - amp: uint256 = self._A() - - old_balances: uint256[N_COINS] = self.balances - new_balances: uint256[N_COINS] = old_balances - D0: uint256 = self.get_D_mem(rates, old_balances, amp) - for i in range(N_COINS): - new_balances[i] -= amounts[i] - D1: uint256 = self.get_D_mem(rates, new_balances, amp) - 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, amp) - - token_amount: uint256 = (D0 - D2) * token_supply / D0 + 1 - assert token_amount <= max_burn_amount, "Slippage screwed you" - - for i in range(N_COINS): - if amounts[i] > 0: - assert_modifiable(cERC20(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) - - -@private -@constant -def get_y_D(A: uint256, i: int128, xp: uint256[N_COINS], D: uint256) -> uint256: - """ - Calculate x[i] if one reduces D from being calculated for xp to D - - Done by solving quadratic equation iteratively. - x_1**2 + x1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) - x_1**2 + b*x_1 = c - - x_1 = (x_1**2 + c) / (2*x_1 + b) - """ - # x in the input is converted to the same price/precision - - assert (i >= 0) and (i < N_COINS) - - c: uint256 = D - S_: uint256 = 0 - Ann: uint256 = A * N_COINS - - _x: uint256 = 0 - for _i in range(N_COINS): - if _i != i: - _x = xp[_i] - else: - continue - S_ += _x - c = c * D / (_x * N_COINS) - c = c * D / (Ann * N_COINS) - b: uint256 = S_ + D / Ann - 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 - - -@private -@constant -def _calc_withdraw_one_coin(_token_amount: uint256, i: int128, rates: uint256[N_COINS]) -> (uint256, uint256): - # First, need to calculate - # * Get current D - # * Solve Eqn against y_i for D - _token_amount - amp: uint256 = self._A() - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - precisions: uint256[N_COINS] = PRECISION_MUL - total_supply: uint256 = self.token.totalSupply() - - xp: uint256[N_COINS] = self._xp(rates) - - D0: uint256 = self.get_D(xp, amp) - D1: uint256 = D0 - _token_amount * D0 / total_supply - xp_reduced: uint256[N_COINS] = xp - - new_y: uint256 = self.get_y_D(amp, i, xp, D1) - dy_0: uint256 = (xp[i] - new_y) / precisions[i] # w/o fees - - for j in range(N_COINS): - dx_expected: uint256 = 0 - if j == i: - dx_expected = xp[j] * D1 / D0 - new_y - else: - dx_expected = xp[j] - xp[j] * D1 / D0 - xp_reduced[j] -= _fee * dx_expected / FEE_DENOMINATOR - - dy: uint256 = xp_reduced[i] - self.get_y_D(amp, i, xp_reduced, D1) - dy = (dy - 1) / precisions[i] # Withdraw less to account for rounding errors - - return dy, dy_0 - dy - - -@public -@constant -def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - rates: uint256[N_COINS] = self._rates() - return self._calc_withdraw_one_coin(_token_amount, i, rates)[0] - - -@public -@nonreentrant('lock') -def remove_liquidity_one_coin(_token_amount: uint256, i: int128, min_amount: uint256): - """ - Remove _amount of liquidity all in a form of coin i - """ - dy: uint256 = 0 - dy_fee: uint256 = 0 - rates: uint256[N_COINS] = self._rates() - dy, dy_fee = self._calc_withdraw_one_coin(_token_amount, i, rates) - assert dy >= min_amount, "Not enough coins removed" - - self.balances[i] -= (dy + dy_fee * self.admin_fee / FEE_DENOMINATOR) - self.token.burnFrom(msg.sender, _token_amount) - assert_modifiable(ERC20(self.coins[i]).transfer(msg.sender, dy)) - - log.RemoveLiquidityOne(msg.sender, _token_amount, dy) - - -### Admin functions ### -@public -def ramp_A(_future_A: uint256, _future_time: timestamp): - assert msg.sender == self.owner - assert block.timestamp >= self.initial_A_time + min_ramp_time - assert _future_time >= block.timestamp + min_ramp_time - - _initial_A: uint256 = self._A() - assert (_future_A > 0) and (_future_A < max_A) - assert ((_future_A >= _initial_A) and (_future_A <= _initial_A * max_A_change)) or\ - ((_future_A < _initial_A) and (_future_A * max_A_change >= _initial_A)) - self.initial_A = _initial_A - self.future_A = _future_A - self.initial_A_time = block.timestamp - self.future_A_time = _future_time - - log.RampA(_initial_A, _future_A, block.timestamp, _future_time) - - -@public -def stop_ramp_A(): - assert msg.sender == self.owner - - current_A: uint256 = self._A() - self.initial_A = current_A - self.future_A = current_A - self.initial_A_time = block.timestamp - self.future_A_time = block.timestamp - # now (block.timestamp < t1) is always False, so we return saved A - - log.StopRampA(current_A, block.timestamp) - - -@public -def commit_new_fee(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 - - _deadline: timestamp = block.timestamp + admin_actions_delay - self.admin_actions_deadline = _deadline - self.future_fee = new_fee - self.future_admin_fee = new_admin_fee - - log.CommitNewFee(_deadline, new_fee, new_admin_fee) - - -@public -def apply_new_fee(): - assert msg.sender == self.owner - assert self.admin_actions_deadline <= block.timestamp\ - and self.admin_actions_deadline > 0 - - self.admin_actions_deadline = 0 - _fee: uint256 = self.future_fee - _admin_fee: uint256 = self.future_admin_fee - self.fee = _fee - self.admin_fee = _admin_fee - - log.NewFee(_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 - - for i in range(N_COINS): - c: address = self.coins[i] - value: uint256 = cERC20(c).balanceOf(self) - self.balances[i] - if value > 0: - assert_modifiable(cERC20(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 diff --git a/contracts/pools/sbtc/pooldata.json b/contracts/pools/sbtc/pooldata.json deleted file mode 100644 index d6e4e7ed..00000000 --- a/contracts/pools/sbtc/pooldata.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "lp_contract": "CurveTokenV1", - "wrapped_contract": "renERC20", - "swap_address": "0x7fC77b5c7614E1533320Ea6DDc2Eb61fa00A9714", - "lp_token_address": "0x075b1bb99792c9E1041bA13afEf80C91a1e70fB3", - "gauge_addresses": ["0x705350c4BcD35c9441419DdD5d2f097d7a55410F"], - "lp_constructor": { - "name": "Curve.fi renBTC/wBTC/sBTC", - "symbol": "crvRenWSBTC" - }, - "coins": [ - { - "name": "renBTC", - "tethered": false, - "wrapped_decimals": 8, - "underlying_address": "0xeb4c2781e4eba804ce9a9803c67d0893436bb27d", - "wrapped_address": "0xeb4c2781e4eba804ce9a9803c67d0893436bb27d" - }, - { - "name": "wBTC", - "decimals": 8, - "tethered": false, - "underlying_address": "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599" - }, - { - "name": "sBTC", - "decimals": 18, - "tethered": false, - "underlying_address": "0xfe18be6b3bd88a2d2a7f928d00292e7a9963cfc6" - } - ], - "testing": { - "initial_amount": 1000 - } -} diff --git a/contracts/pools/seth/README.md b/contracts/pools/seth/README.md deleted file mode 100644 index 6c722be6..00000000 --- a/contracts/pools/seth/README.md +++ /dev/null @@ -1,20 +0,0 @@ -# curve-contract/contracts/pools/seth - -[Curve sETH pool](https://www.curve.fi/seth). This is a no-lending pool. - -## Contracts - -* [`StableSwapSETH`](StableSwapSETH.vy): Curve stablecoin AMM contract - -## Deployments - -* [`CurveContractV3`](../../tokens/CurveTokenV3.vy): [0xA3D87FffcE63B53E0d54fAa1cc983B7eB0b74A9c](https://etherscan.io/address/0xA3D87FffcE63B53E0d54fAa1cc983B7eB0b74A9c) -* [`LiquidityGaugeV2`](https://github.com/curvefi/curve-dao-contracts/blob/master/contracts/gauges/LiquidityGaugeV2.vy): [](https://etherscan.io/address/0x3C0FFFF15EA30C35d7A85B85c0782D6c94e1d238) -* [`StableSwapSETH`](StableSwapSETH.vy): [0xc5424b857f758e906013f3555dad202e4bdb4567](https://etherscan.io/address/0xc5424b857f758e906013f3555dad202e4bdb4567) - -## Stablecoins - -Curve ETH pool supports swaps between Ether and Synthetix sETH. - -* `ETH`: represented in the pool as `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE` -* `sETH`: [0x5e74C9036fb86BD7eCdcb084a0673EFc32eA31cb](https://etherscan.io/address/0x5e74C9036fb86BD7eCdcb084a0673EFc32eA31cb) diff --git a/contracts/pools/seth/StableSwapSETH.vy b/contracts/pools/seth/StableSwapSETH.vy deleted file mode 100644 index 4ec8fedd..00000000 --- a/contracts/pools/seth/StableSwapSETH.vy +++ /dev/null @@ -1,883 +0,0 @@ -# @version 0.2.8 -""" -@title Curve ETH/sETH StableSwap -@author Curve.Fi -@license Copyright (c) Curve.Fi, 2020 - all rights reserved -""" - -from vyper.interfaces import ERC20 - -interface CurveToken: - def mint(_to: address, _value: uint256) -> bool: nonpayable - def burnFrom(_to: address, _value: uint256) -> bool: nonpayable - - -# Events -event TokenExchange: - buyer: indexed(address) - sold_id: int128 - tokens_sold: uint256 - bought_id: int128 - tokens_bought: uint256 - -event AddLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event RemoveLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - token_supply: uint256 - -event RemoveLiquidityOne: - provider: indexed(address) - token_amount: uint256 - coin_amount: uint256 - -event RemoveLiquidityImbalance: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event CommitNewAdmin: - deadline: indexed(uint256) - admin: indexed(address) - -event NewAdmin: - admin: indexed(address) - -event CommitNewFee: - deadline: indexed(uint256) - fee: uint256 - admin_fee: uint256 - -event NewFee: - fee: uint256 - admin_fee: uint256 - -event RampA: - old_A: uint256 - new_A: uint256 - initial_time: uint256 - future_time: uint256 - -event StopRampA: - A: uint256 - t: uint256 - - -# These constants must be set prior to compiling -N_COINS: constant(int128) = 2 - -# fixed constants -FEE_DENOMINATOR: constant(uint256) = 10 ** 10 -PRECISION: constant(uint256) = 10 ** 18 # The precision to convert to - -MAX_ADMIN_FEE: constant(uint256) = 10 * 10 ** 9 -MAX_FEE: constant(uint256) = 5 * 10 ** 9 -MAX_A: constant(uint256) = 10 ** 6 -MAX_A_CHANGE: constant(uint256) = 10 - -ADMIN_ACTIONS_DELAY: constant(uint256) = 3 * 86400 -MIN_RAMP_TIME: constant(uint256) = 86400 - -coins: public(address[N_COINS]) -balances: public(uint256[N_COINS]) -fee: public(uint256) # fee * 1e10 -admin_fee: public(uint256) # admin_fee * 1e10 - -owner: public(address) -lp_token: address - -A_PRECISION: constant(uint256) = 100 -initial_A: public(uint256) -future_A: public(uint256) -initial_A_time: public(uint256) -future_A_time: public(uint256) - -admin_actions_deadline: public(uint256) -transfer_ownership_deadline: public(uint256) -future_fee: public(uint256) -future_admin_fee: public(uint256) -future_owner: public(address) - -is_killed: bool -kill_deadline: uint256 -KILL_DEADLINE_DT: constant(uint256) = 2 * 30 * 86400 - - -@external -def __init__( - _owner: address, - _coins: address[N_COINS], - _pool_token: address, - _A: uint256, - _fee: uint256, - _admin_fee: uint256 -): - """ - @notice Contract constructor - @param _owner Contract owner address - @param _coins Addresses of ERC20 conracts of coins - @param _pool_token Address of the token representing LP share - @param _A Amplification coefficient multiplied by n * (n - 1) - @param _fee Fee to charge for exchanges - @param _admin_fee Admin fee - """ - for i in range(N_COINS): - assert _coins[i] != ZERO_ADDRESS - self.coins = _coins - self.initial_A = _A * A_PRECISION - self.future_A = _A * A_PRECISION - self.fee = _fee - self.admin_fee = _admin_fee - self.owner = _owner - self.kill_deadline = block.timestamp + KILL_DEADLINE_DT - self.lp_token = _pool_token - - -@view -@internal -def _A() -> uint256: - """ - Handle ramping A up or down - """ - t1: uint256 = self.future_A_time - A1: uint256 = self.future_A - - if block.timestamp < t1: - A0: uint256 = self.initial_A - t0: uint256 = self.initial_A_time - # Expressions in uint256 cannot have negative numbers, thus "if" - if A1 > A0: - return A0 + (A1 - A0) * (block.timestamp - t0) / (t1 - t0) - else: - return A0 - (A0 - A1) * (block.timestamp - t0) / (t1 - t0) - - else: # when t1 == 0 or block.timestamp >= t1 - return A1 - - -@view -@external -def A() -> uint256: - return self._A() / A_PRECISION - - -@view -@external -def A_precise() -> uint256: - return self._A() - - -@pure -@internal -def get_D(xp: uint256[N_COINS], amp: uint256) -> uint256: - S: uint256 = 0 - Dprev: uint256 = 0 - for _x in xp: - S += _x - if S == 0: - return 0 - - D: uint256 = S - Ann: uint256 = amp * N_COINS - for _i in range(255): - D_P: uint256 = D - for _x in xp: - D_P = D_P * D / (_x * N_COINS) # If division by 0, this will be borked: only withdrawal will work. And that is good - Dprev = D - D = (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (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 - raise - - -@view -@external -def get_virtual_price() -> uint256: - """ - @notice The current virtual price of the pool LP token - @dev Useful for calculating profits - @return LP token virtual price normalized to 1e18 - """ - D: uint256 = self.get_D(self.balances, self._A()) - # 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 = ERC20(self.lp_token).totalSupply() - return D * PRECISION / token_supply - - -@view -@external -def calc_token_amount(amounts: uint256[N_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 amounts Amount of each coin being deposited - @param is_deposit set True for deposits, False for withdrawals - @return Expected amount of LP tokens received - """ - amp: uint256 = self._A() - _balances: uint256[N_COINS] = self.balances - D0: uint256 = self.get_D(_balances, amp) - for i in range(N_COINS): - if is_deposit: - _balances[i] += amounts[i] - else: - _balances[i] -= amounts[i] - D1: uint256 = self.get_D(_balances, amp) - token_amount: uint256 = ERC20(self.lp_token).totalSupply() - diff: uint256 = 0 - if is_deposit: - diff = D1 - D0 - else: - diff = D0 - D1 - return diff * token_amount / D0 - - -@payable -@external -@nonreentrant('lock') -def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256) -> uint256: - """ - @notice Deposit coins into the pool - @param amounts List of amounts of coins to deposit - @param min_mint_amount Minimum amount of LP tokens to mint from the deposit - @return Amount of LP tokens received by depositing - """ - assert not self.is_killed # dev: is killed - - # Initial invariant - amp: uint256 = self._A() - old_balances: uint256[N_COINS] = self.balances - D0: uint256 = self.get_D(old_balances, amp) - - lp_token: address = self.lp_token - token_supply: uint256 = ERC20(lp_token).totalSupply() - new_balances: uint256[N_COINS] = empty(uint256[N_COINS]) - for i in range(N_COINS): - if token_supply == 0: - assert amounts[i] > 0 # dev: initial deposit requires all coins - new_balances[i] = old_balances[i] + amounts[i] - - # Invariant after change - D1: uint256 = self.get_D(new_balances, amp) - assert D1 > D0 - - # We need to recalculate the invariant accounting for fees - # to calculate fair user's share - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - mint_amount: uint256 = 0 - D2: uint256 = D1 - if token_supply > 0: - # Only account for fees if we are not the first to deposit - fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - admin_fee: uint256 = self.admin_fee - 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(new_balances, amp) - mint_amount = token_supply * (D2 - D0) / D0 - else: - self.balances = new_balances - mint_amount = D1 # Take the dust if there was any - - assert mint_amount >= min_mint_amount, "Slippage screwed you" - - # Take coins from the sender - for i in range(N_COINS): - _coin: address = self.coins[i] - _amount: uint256 = amounts[i] - if _coin == 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE: - assert msg.value == _amount - elif _amount > 0: - # "safeTransferFrom" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - _coin, - concat( - method_id("transferFrom(address,address,uint256)"), - convert(msg.sender, bytes32), - convert(self, bytes32), - convert(_amount, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) - - # Mint pool tokens - CurveToken(lp_token).mint(msg.sender, mint_amount) - - log AddLiquidity(msg.sender, amounts, fees, D1, token_supply + mint_amount) - - return mint_amount - - -@view -@internal -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 # dev: same coin - assert j >= 0 # dev: j below zero - assert j < N_COINS # dev: j above N_COINS - - # should be unreachable, but good for safety - assert i >= 0 - assert i < N_COINS - - amp: uint256 = self._A() - D: uint256 = self.get_D(xp_, amp) - c: uint256 = D - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: uint256 = 0 - Ann: uint256 = amp * N_COINS - - 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 * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann # - D - 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: - return y - else: - if y_prev - y <= 1: - return y - raise - - -@view -@external -def get_dy(i: int128, j: int128, dx: uint256) -> uint256: - xp: uint256[N_COINS] = self.balances - x: uint256 = xp[i] + dx - y: uint256 = self.get_y(i, j, x, xp) - dy: uint256 = xp[j] - y - 1 - _fee: uint256 = self.fee * dy / FEE_DENOMINATOR - return dy - _fee - - -@payable -@external -@nonreentrant('lock') -def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256) -> uint256: - """ - @notice Perform an exchange between two coins - @dev Index values can be found via the `coins` public getter method - @param i Index value for the coin to send - @param j Index valie of the coin to recieve - @param dx Amount of `i` being exchanged - @param min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - assert not self.is_killed # dev: is killed - - old_balances: uint256[N_COINS] = self.balances - - x: uint256 = old_balances[i] + dx - y: uint256 = self.get_y(i, j, x, old_balances) - - dy: uint256 = old_balances[j] - y - 1 # -1 just in case there were some rounding errors - dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR - - # Convert all to real units - dy = dy - dy_fee - assert dy >= min_dy, "Exchange resulted in fewer coins than expected" - - dy_admin_fee: uint256 = dy_fee * self.admin_fee / FEE_DENOMINATOR - - # Change balances exactly in same way as we change actual ERC20 coin amounts - self.balances[i] = old_balances[i] + dx - # When rounding errors happen, we undercharge admin fee in favor of LP - self.balances[j] = old_balances[j] - dy - dy_admin_fee - - # "safeTransferFrom" which works for ERC20s which return bool or not - _coin: address = self.coins[i] - if _coin == 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE: - assert msg.value == dx - else: - assert msg.value == 0 - _response: Bytes[32] = raw_call( - _coin, - concat( - method_id("transferFrom(address,address,uint256)"), - convert(msg.sender, bytes32), - convert(self, bytes32), - convert(dx, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) - - _coin = self.coins[j] - if _coin == 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE: - raw_call(msg.sender, b"", value=dy) - else: - _response: Bytes[32] = raw_call( - _coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(dy, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) - - log TokenExchange(msg.sender, i, dx, j, dy) - - return dy - - -@external -@nonreentrant('lock') -def remove_liquidity(_amount: uint256, min_amounts: uint256[N_COINS]) -> uint256[N_COINS]: - """ - @notice Withdraw coins from the pool - @dev Withdrawal amounts are based on current deposit ratios - @param _amount Quantity of LP tokens to burn in the withdrawal - @param min_amounts Minimum amounts of underlying coins to receive - @return List of amounts of coins that were withdrawn - """ - lp_token: address = self.lp_token - total_supply: uint256 = ERC20(lp_token).totalSupply() - amounts: uint256[N_COINS] = empty(uint256[N_COINS]) - - CurveToken(lp_token).burnFrom(msg.sender, _amount) # dev: insufficient funds - - for i in range(N_COINS): - old_balance: uint256 = self.balances[i] - value: uint256 = old_balance * _amount / total_supply - assert value >= min_amounts[i], "Withdrawal resulted in fewer coins than expected" - - self.balances[i] = old_balance - value - amounts[i] = value - _coin: address = self.coins[i] - if _coin == 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE: - raw_call(msg.sender, b"", value=value) - else: - _response: Bytes[32] = raw_call( - _coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(value, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) - - log RemoveLiquidity(msg.sender, amounts, empty(uint256[N_COINS]), total_supply - _amount) - - return amounts - - -@external -@nonreentrant('lock') -def remove_liquidity_imbalance(amounts: uint256[N_COINS], max_burn_amount: uint256) -> uint256: - """ - @notice Withdraw coins from the pool in an imbalanced amount - @param amounts List of amounts of underlying coins to withdraw - @param max_burn_amount Maximum amount of LP token to burn in the withdrawal - @return Actual amount of the LP token burned in the withdrawal - """ - assert not self.is_killed # dev: is killed - - amp: uint256 = self._A() - lp_token: address = self.lp_token - token_supply: uint256 = ERC20(lp_token).totalSupply() - assert token_supply != 0 # dev: zero total supply - - old_balances: uint256[N_COINS] = self.balances - D0: uint256 = self.get_D(old_balances, amp) - - new_balances: uint256[N_COINS] = empty(uint256[N_COINS]) - for i in range(N_COINS): - new_balances[i] = old_balances[i] - amounts[i] - D1: uint256 = self.get_D(new_balances, amp) - - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - difference: uint256 = 0 - fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - admin_fee: uint256 = self.admin_fee - for i in range(N_COINS): - new_balance: uint256 = new_balances[i] - ideal_balance: uint256 = D1 * old_balances[i] / D0 - if ideal_balance > new_balance: - difference = ideal_balance - new_balance - else: - difference = new_balance - ideal_balance - fees[i] = fee * difference / FEE_DENOMINATOR - self.balances[i] = new_balance - (fees[i] * admin_fee / FEE_DENOMINATOR) - new_balances[i] = new_balance - fees[i] - D2: uint256 = self.get_D(new_balances, amp) - - token_amount: uint256 = (D0 - D2) * token_supply / D0 - assert token_amount != 0 # dev: zero tokens burned - token_amount += 1 # In case of rounding errors - make it unfavorable for the "attacker" - assert token_amount <= max_burn_amount, "Slippage screwed you" - - CurveToken(lp_token).burnFrom(msg.sender, token_amount) # dev: insufficient funds - - for i in range(N_COINS): - amount: uint256 = amounts[i] - if amount != 0: - coin: address = self.coins[i] - if coin == 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE: - raw_call(msg.sender, b"", value=amount) - else: - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(amount, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) - - log RemoveLiquidityImbalance(msg.sender, amounts, fees, D1, token_supply - token_amount) - - return token_amount - - -@view -@internal -def get_y_D(A_: uint256, i: int128, xp: uint256[N_COINS], D: uint256) -> uint256: - """ - Calculate x[i] if one reduces D from being calculated for xp to D - - Done by solving quadratic equation iteratively. - x_1**2 + x_1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) - x_1**2 + b*x_1 = c - - x_1 = (x_1**2 + c) / (2*x_1 + b) - """ - # x in the input is converted to the same price/precision - - assert i >= 0 # dev: i below zero - assert i < N_COINS # dev: i above N_COINS - - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: uint256 = 0 - c: uint256 = D - Ann: uint256 = A_ * N_COINS - - for _i in range(N_COINS): - if _i != i: - _x = xp[_i] - else: - continue - S_ += _x - c = c * D / (_x * N_COINS) - c = c * D * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann - - 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: - return y - else: - if y_prev - y <= 1: - return y - raise - - -@view -@internal -def _calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> (uint256, uint256): - # First, need to calculate - # * Get current D - # * Solve Eqn against y_i for D - _token_amount - amp: uint256 = self._A() - xp: uint256[N_COINS] = self.balances - D0: uint256 = self.get_D(xp, amp) - total_supply: uint256 = ERC20(self.lp_token).totalSupply() - D1: uint256 = D0 - _token_amount * D0 / total_supply - new_y: uint256 = self.get_y_D(amp, i, xp, D1) - - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - xp_reduced: uint256[N_COINS] = xp - for j in range(N_COINS): - dx_expected: uint256 = 0 - if j == i: - dx_expected = xp[j] * D1 / D0 - new_y - else: - dx_expected = xp[j] - xp[j] * D1 / D0 - xp_reduced[j] -= _fee * dx_expected / FEE_DENOMINATOR - - dy: uint256 = xp_reduced[i] - self.get_y_D(amp, i, xp_reduced, D1) - - dy -= 1 # Withdraw less to account for rounding errors - dy_0: uint256 = xp[i] - new_y # w/o fees - - return dy, dy_0 - dy - - -@view -@external -def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - """ - @notice Calculate the amount received when withdrawing a single coin - @param _token_amount Amount of LP tokens to burn in the withdrawal - @param i Index value of the coin to withdraw - @return Amount of coin received - """ - return self._calc_withdraw_one_coin(_token_amount, i)[0] - - -@external -@nonreentrant('lock') -def remove_liquidity_one_coin(_token_amount: uint256, i: int128, _min_amount: uint256) -> uint256: - """ - @notice Withdraw a single coin from the pool - @param _token_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 coin to receive - @return Amount of coin received - """ - assert not self.is_killed # dev: is killed - - dy: uint256 = 0 - dy_fee: uint256 = 0 - dy, dy_fee = self._calc_withdraw_one_coin(_token_amount, i) - assert dy >= _min_amount, "Not enough coins removed" - - self.balances[i] -= (dy + dy_fee * self.admin_fee / FEE_DENOMINATOR) - CurveToken(self.lp_token).burnFrom(msg.sender, _token_amount) # dev: insufficient funds - - _coin: address = self.coins[i] - if _coin == 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE: - raw_call(msg.sender, b"", value=dy) - else: - _response: Bytes[32] = raw_call( - _coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(dy, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) - - log RemoveLiquidityOne(msg.sender, _token_amount, dy) - - return dy - - -### Admin functions ### -@external -def ramp_A(_future_A: uint256, _future_time: uint256): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.initial_A_time + MIN_RAMP_TIME - assert _future_time >= block.timestamp + MIN_RAMP_TIME # dev: insufficient time - - _initial_A: uint256 = self._A() - _future_A_p: uint256 = _future_A * A_PRECISION - - assert _future_A > 0 and _future_A < MAX_A - if _future_A_p < _initial_A: - assert _future_A_p * MAX_A_CHANGE >= _initial_A - else: - assert _future_A_p <= _initial_A * MAX_A_CHANGE - - self.initial_A = _initial_A - self.future_A = _future_A_p - self.initial_A_time = block.timestamp - self.future_A_time = _future_time - - log RampA(_initial_A, _future_A_p, block.timestamp, _future_time) - - -@external -def stop_ramp_A(): - assert msg.sender == self.owner # dev: only owner - - current_A: uint256 = self._A() - self.initial_A = current_A - self.future_A = current_A - self.initial_A_time = block.timestamp - self.future_A_time = block.timestamp - # now (block.timestamp < t1) is always False, so we return saved A - - log StopRampA(current_A, block.timestamp) - - -@external -def commit_new_fee(new_fee: uint256, new_admin_fee: uint256): - assert msg.sender == self.owner # dev: only owner - assert self.admin_actions_deadline == 0 # dev: active action - assert new_fee <= MAX_FEE # dev: fee exceeds maximum - assert new_admin_fee <= MAX_ADMIN_FEE # dev: admin fee exceeds maximum - - _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.admin_actions_deadline = _deadline - self.future_fee = new_fee - self.future_admin_fee = new_admin_fee - - log CommitNewFee(_deadline, new_fee, new_admin_fee) - - -@external -@nonreentrant('lock') -def apply_new_fee(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.admin_actions_deadline # dev: insufficient time - assert self.admin_actions_deadline != 0 # dev: no active action - - self.admin_actions_deadline = 0 - _fee: uint256 = self.future_fee - _admin_fee: uint256 = self.future_admin_fee - self.fee = _fee - self.admin_fee = _admin_fee - - log NewFee(_fee, _admin_fee) - - -@external -def revert_new_parameters(): - assert msg.sender == self.owner # dev: only owner - - self.admin_actions_deadline = 0 - - -@external -def commit_transfer_ownership(_owner: address): - assert msg.sender == self.owner # dev: only owner - assert self.transfer_ownership_deadline == 0 # dev: active transfer - - _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.transfer_ownership_deadline = _deadline - self.future_owner = _owner - - log CommitNewAdmin(_deadline, _owner) - - -@external -@nonreentrant('lock') -def apply_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.transfer_ownership_deadline # dev: insufficient time - assert self.transfer_ownership_deadline != 0 # dev: no active transfer - - self.transfer_ownership_deadline = 0 - _owner: address = self.future_owner - self.owner = _owner - - log NewAdmin(_owner) - - -@external -def revert_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - - self.transfer_ownership_deadline = 0 - - -@view -@external -def admin_balances(i: uint256) -> uint256: - coin: address = self.coins[i] - if coin == 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE: - return self.balance - self.balances[i] - else: - return ERC20(coin).balanceOf(self) - self.balances[i] - - -@external -@nonreentrant('lock') -def withdraw_admin_fees(): - assert msg.sender == self.owner # dev: only owner - - for i in range(N_COINS): - coin: address = self.coins[i] - if coin == 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE: - value: uint256 = self.balance - self.balances[i] - if value > 0: - raw_call(msg.sender, b"", value=value) - else: - value: uint256 = ERC20(coin).balanceOf(self) - self.balances[i] - if value > 0: - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(value, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) - - - -@external -@nonreentrant('lock') -def donate_admin_fees(): - assert msg.sender == self.owner # dev: only owner - for i in range(N_COINS): - coin: address = self.coins[i] - if coin == 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE: - self.balances[i] = self.balance - else: - self.balances[i] = ERC20(coin).balanceOf(self) - - -@external -def kill_me(): - assert msg.sender == self.owner # dev: only owner - assert self.kill_deadline > block.timestamp # dev: deadline has passed - self.is_killed = True - - -@external -def unkill_me(): - assert msg.sender == self.owner # dev: only owner - self.is_killed = False diff --git a/contracts/pools/seth/pooldata.json b/contracts/pools/seth/pooldata.json deleted file mode 100644 index 4ceedf11..00000000 --- a/contracts/pools/seth/pooldata.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "lp_contract": "CurveTokenV3", - "pool_types": ["eth"], - "swap_address": "0xc5424b857f758e906013f3555dad202e4bdb4567", - "lp_token_address": "0xA3D87FffcE63B53E0d54fAa1cc983B7eB0b74A9c", - "gauge_addresses": ["0x3C0FFFF15EA30C35d7A85B85c0782D6c94e1d238"], - "lp_constructor": { - "symbol": "eCRV", - "name": "Curve.fi ETH/sETH" - }, - "swap_constructor": { - "_A": 100, - "_fee": 4000000, - "_admin_fee": 5000000000 - }, - "coins": [ - { - "name": "ETH", - "decimals": 18, - "tethered": false, - "underlying_address": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" - }, - { - "name": "sETH", - "decimals": 18, - "tethered": false, - "underlying_address": "0x5e74C9036fb86BD7eCdcb084a0673EFc32eA31cb" - } - ], - "testing": { - "initial_amount": 10000 - } -} diff --git a/contracts/pools/steth/README.md b/contracts/pools/steth/README.md deleted file mode 100644 index 4acc8a2f..00000000 --- a/contracts/pools/steth/README.md +++ /dev/null @@ -1,20 +0,0 @@ -# curve-contract/contracts/pools/steth - -[Curve stETH pool](https://www.curve.fi/steth). This is a no-lending pool. - -## Contracts - -* [`StableSwapSTETH`](StableSwapSTETH.vy): Curve stablecoin AMM contract - -## Deployments - -* [`CurveContractV3`](../../tokens/CurveTokenV3.vy): [0x06325440D014e39736583c165C2963BA99fAf14E](https://etherscan.io/address/0x06325440D014e39736583c165C2963BA99fAf14E) -* [`LiquidityGaugeV2`](https://github.com/curvefi/curve-dao-contracts/blob/master/contracts/gauges/LiquidityGaugeV2.vy): [0x182B723a58739a9c974cFDB385ceaDb237453c28](https://etherscan.io/address/0x182B723a58739a9c974cFDB385ceaDb237453c28) -* [`StableSwapSTETH`](StableSwapSTETH.vy): [0xDC24316b9AE028F1497c275EB9192a3Ea0f67022](https://etherscan.io/address/0xDC24316b9AE028F1497c275EB9192a3Ea0f67022) - -## Stablecoins - -Curve stETH pool supports swaps between Ether and Lido staked ETH. - -* `ETH`: represented in the pool as `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE` -* `stETH`: [0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84](https://etherscan.io/address/0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84) diff --git a/contracts/pools/steth/StableSwapSTETH.vy b/contracts/pools/steth/StableSwapSTETH.vy deleted file mode 100644 index b4c7ba48..00000000 --- a/contracts/pools/steth/StableSwapSTETH.vy +++ /dev/null @@ -1,839 +0,0 @@ -# @version 0.2.8 -""" -@title Curve ETH/stETH StableSwap -@author Curve.Fi -@license Copyright (c) Curve.Fi, 2020 - all rights reserved -""" - -from vyper.interfaces import ERC20 - - -interface CurveToken: - def mint(_to: address, _value: uint256) -> bool: nonpayable - def burnFrom(_to: address, _value: uint256) -> bool: nonpayable - - -# Events -event TokenExchange: - buyer: indexed(address) - sold_id: int128 - tokens_sold: uint256 - bought_id: int128 - tokens_bought: uint256 - -event TokenExchangeUnderlying: - buyer: indexed(address) - sold_id: int128 - tokens_sold: uint256 - bought_id: int128 - tokens_bought: uint256 - -event AddLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event RemoveLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - token_supply: uint256 - -event RemoveLiquidityOne: - provider: indexed(address) - token_amount: uint256 - coin_amount: uint256 - -event RemoveLiquidityImbalance: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event CommitNewAdmin: - deadline: indexed(uint256) - admin: indexed(address) - -event NewAdmin: - admin: indexed(address) - -event CommitNewFee: - deadline: indexed(uint256) - fee: uint256 - admin_fee: uint256 - -event NewFee: - fee: uint256 - admin_fee: uint256 - -event RampA: - old_A: uint256 - new_A: uint256 - initial_time: uint256 - future_time: uint256 - -event StopRampA: - A: uint256 - t: uint256 - - -# These constants must be set prior to compiling -N_COINS: constant(int128) = 2 - -# fixed constants -FEE_DENOMINATOR: constant(uint256) = 10 ** 10 -PRECISION: constant(uint256) = 10 ** 18 # The precision to convert to - -MAX_ADMIN_FEE: constant(uint256) = 10 * 10 ** 9 -MAX_FEE: constant(uint256) = 5 * 10 ** 9 - -MAX_A: constant(uint256) = 10 ** 6 -MAX_A_CHANGE: constant(uint256) = 10 -A_PRECISION: constant(uint256) = 100 - -ADMIN_ACTIONS_DELAY: constant(uint256) = 3 * 86400 -MIN_RAMP_TIME: constant(uint256) = 86400 - -coins: public(address[N_COINS]) -admin_balances: public(uint256[N_COINS]) - -fee: public(uint256) # fee * 1e10 -admin_fee: public(uint256) # admin_fee * 1e10 - -owner: public(address) -lp_token: public(address) - -initial_A: public(uint256) -future_A: public(uint256) -initial_A_time: public(uint256) -future_A_time: public(uint256) - -admin_actions_deadline: public(uint256) -transfer_ownership_deadline: public(uint256) -future_fee: public(uint256) -future_admin_fee: public(uint256) -future_owner: public(address) - -is_killed: bool -kill_deadline: uint256 -KILL_DEADLINE_DT: constant(uint256) = 2 * 30 * 86400 - - -@external -def __init__( - _owner: address, - _coins: address[N_COINS], - _pool_token: address, - _A: uint256, - _fee: uint256, - _admin_fee: uint256 -): - """ - @notice Contract constructor - @param _owner Contract owner address - @param _coins Addresses of ERC20 conracts of coins - @param _pool_token Address of the token representing LP share - @param _A Amplification coefficient multiplied by n * (n - 1) - @param _fee Fee to charge for exchanges - @param _admin_fee Admin fee - """ - assert _coins[0] == 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE - assert _coins[1] != ZERO_ADDRESS - - self.coins = _coins - self.initial_A = _A * A_PRECISION - self.future_A = _A * A_PRECISION - self.fee = _fee - self.admin_fee = _admin_fee - self.owner = _owner - self.kill_deadline = block.timestamp + KILL_DEADLINE_DT - self.lp_token = _pool_token - - -@view -@internal -def _A() -> uint256: - t1: uint256 = self.future_A_time - A1: uint256 = self.future_A - - if block.timestamp < t1: - # handle ramping up and down of A - A0: uint256 = self.initial_A - t0: uint256 = self.initial_A_time - # Expressions in uint256 cannot have negative numbers, thus "if" - if A1 > A0: - return A0 + (A1 - A0) * (block.timestamp - t0) / (t1 - t0) - else: - return A0 - (A0 - A1) * (block.timestamp - t0) / (t1 - t0) - - else: # when t1 == 0 or block.timestamp >= t1 - return A1 - - -@view -@external -def A() -> uint256: - return self._A() / A_PRECISION - - -@view -@external -def A_precise() -> uint256: - return self._A() - - -@view -@internal -def _balances(_value: uint256 = 0) -> uint256[N_COINS]: - return [ - self.balance - self.admin_balances[0] - _value, - ERC20(self.coins[1]).balanceOf(self) - self.admin_balances[1] - ] - - -@view -@external -def balances(i: uint256) -> uint256: - """ - @notice Get the current balance of a coin within the - pool, less the accrued admin fees - @param i Index value for the coin to query balance of - @return Token balance - """ - return self._balances()[i] - - -@pure -@internal -def get_D(xp: uint256[N_COINS], amp: uint256) -> uint256: - """ - D invariant calculation in non-overflowing integer operations - iteratively - - A * sum(x_i) * n**n + D = A * D * n**n + D**(n+1) / (n**n * prod(x_i)) - - Converging solution: - D[j+1] = (A * n**n * sum(x_i) - D[j]**(n+1) / (n**n prod(x_i))) / (A * n**n - 1) - """ - S: uint256 = 0 - Dprev: uint256 = 0 - - for _x in xp: - S += _x - if S == 0: - return 0 - - D: uint256 = S - Ann: uint256 = amp * 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 / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (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` - raise - - -@view -@external -def get_virtual_price() -> uint256: - """ - @notice The current virtual price of the pool LP token - @dev Useful for calculating profits - @return LP token virtual price normalized to 1e18 - """ - D: uint256 = self.get_D(self._balances(), self._A()) - # 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 = ERC20(self.lp_token).totalSupply() - return D * PRECISION / token_supply - - -@view -@external -def calc_token_amount(amounts: uint256[N_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 amounts Amount of each coin being deposited - @param is_deposit set True for deposits, False for withdrawals - @return Expected amount of LP tokens received - """ - amp: uint256 = self._A() - balances: uint256[N_COINS] = self._balances() - D0: uint256 = self.get_D(balances, amp) - for i in range(N_COINS): - if is_deposit: - balances[i] += amounts[i] - else: - balances[i] -= amounts[i] - D1: uint256 = self.get_D(balances, amp) - token_amount: uint256 = ERC20(self.lp_token).totalSupply() - diff: uint256 = 0 - if is_deposit: - diff = D1 - D0 - else: - diff = D0 - D1 - return diff * token_amount / D0 - - -@payable -@external -@nonreentrant('lock') -def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256) -> uint256: - """ - @notice Deposit coins into the pool - @param amounts List of amounts of coins to deposit - @param min_mint_amount Minimum amount of LP tokens to mint from the deposit - @return Amount of LP tokens received by depositing - """ - assert not self.is_killed # dev: is killed - - # Initial invariant - amp: uint256 = self._A() - old_balances: uint256[N_COINS] = self._balances(msg.value) - D0: uint256 = self.get_D(old_balances, amp) - - lp_token: address = self.lp_token - token_supply: uint256 = ERC20(lp_token).totalSupply() - new_balances: uint256[N_COINS] = old_balances - for i in range(N_COINS): - if token_supply == 0: - assert amounts[i] > 0 # dev: initial deposit requires all coins - new_balances[i] += amounts[i] - - # Invariant after change - D1: uint256 = self.get_D(new_balances, amp) - assert D1 > D0 - - # We need to recalculate the invariant accounting for fees - # to calculate fair user's share - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - mint_amount: uint256 = 0 - D2: uint256 = 0 - if token_supply > 0: - # Only account for fees if we are not the first to deposit - fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - admin_fee: uint256 = self.admin_fee - 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 - if admin_fee != 0: - self.admin_balances[i] += fees[i] * admin_fee / FEE_DENOMINATOR - new_balances[i] -= fees[i] - D2 = self.get_D(new_balances, amp) - mint_amount = token_supply * (D2 - D0) / D0 - else: - mint_amount = D1 # Take the dust if there was any - - assert mint_amount >= min_mint_amount, "Slippage screwed you" - - # Take coins from the sender - assert msg.value == amounts[0] - if amounts[1] > 0: - assert ERC20(self.coins[1]).transferFrom(msg.sender, self, amounts[1]) - - # Mint pool tokens - CurveToken(lp_token).mint(msg.sender, mint_amount) - - log AddLiquidity(msg.sender, amounts, fees, D1, token_supply + mint_amount) - - return mint_amount - - -@view -@internal -def get_y(i: int128, j: int128, x: uint256, xp: uint256[N_COINS]) -> uint256: - """ - Calculate x[j] if one makes x[i] = x - - Done by solving quadratic equation iteratively. - x_1**2 + x1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) - x_1**2 + b*x_1 = c - - x_1 = (x_1**2 + c) / (2*x_1 + b) - """ - # x in the input is converted to the same price/precision - - assert i != j # dev: same coin - assert j >= 0 # dev: j below zero - assert j < N_COINS # dev: j above N_COINS - - # should be unreachable, but good for safety - assert i >= 0 - assert i < N_COINS - - amp: uint256 = self._A() - D: uint256 = self.get_D(xp, amp) - Ann: uint256 = amp * N_COINS - c: uint256 = D - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: 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 * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann # - D - 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: - return y - else: - if y_prev - y <= 1: - return y - raise - - -@view -@external -def get_dy(i: int128, j: int128, dx: uint256) -> uint256: - xp: uint256[N_COINS] = self._balances() - x: uint256 = xp[i] + dx - y: uint256 = self.get_y(i, j, x, xp) - dy: uint256 = xp[j] - y - 1 - fee: uint256 = self.fee * dy / FEE_DENOMINATOR - return dy - fee - - -@payable -@external -@nonreentrant('lock') -def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256) -> uint256: - """ - @notice Perform an exchange between two coins - @dev Index values can be found via the `coins` public getter method - @param i Index value for the coin to send - @param j Index valie of the coin to recieve - @param dx Amount of `i` being exchanged - @param min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - assert not self.is_killed # dev: is killed - # dx and dy are in aTokens - - xp: uint256[N_COINS] = self._balances(msg.value) - - x: uint256 = xp[i] + dx - y: uint256 = self.get_y(i, j, x, xp) - dy: uint256 = xp[j] - y - 1 - dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR - - # Convert all to real units - dy = dy - dy_fee - assert dy >= min_dy, "Exchange resulted in fewer coins than expected" - - admin_fee: uint256 = self.admin_fee - if admin_fee != 0: - dy_admin_fee: uint256 = dy_fee * admin_fee / FEE_DENOMINATOR - if dy_admin_fee != 0: - self.admin_balances[j] += dy_admin_fee - - coin: address = self.coins[1] - if i == 0: - assert msg.value == dx - assert ERC20(coin).transfer(msg.sender, dy) - else: - assert msg.value == 0 - assert ERC20(coin).transferFrom(msg.sender, self, dx) - raw_call(msg.sender, b"", value=dy) - - log TokenExchange(msg.sender, i, dx, j, dy) - - return dy - - -@external -@nonreentrant('lock') -def remove_liquidity( - _amount: uint256, - _min_amounts: uint256[N_COINS], -) -> uint256[N_COINS]: - """ - @notice Withdraw coins from the pool - @dev Withdrawal amounts are based on current deposit ratios - @param _amount Quantity of LP tokens to burn in the withdrawal - @param _min_amounts Minimum amounts of underlying coins to receive - @return List of amounts of coins that were withdrawn - """ - amounts: uint256[N_COINS] = self._balances() - lp_token: address = self.lp_token - total_supply: uint256 = ERC20(lp_token).totalSupply() - CurveToken(lp_token).burnFrom(msg.sender, _amount) # dev: insufficient funds - - for i in range(N_COINS): - value: uint256 = amounts[i] * _amount / total_supply - assert value >= _min_amounts[i], "Withdrawal resulted in fewer coins than expected" - - amounts[i] = value - if i == 0: - raw_call(msg.sender, b"", value=value) - else: - assert ERC20(self.coins[1]).transfer(msg.sender, value) - - log RemoveLiquidity(msg.sender, amounts, empty(uint256[N_COINS]), total_supply - _amount) - - return amounts - - -@external -@nonreentrant('lock') -def remove_liquidity_imbalance( - _amounts: uint256[N_COINS], - _max_burn_amount: uint256 -) -> uint256: - """ - @notice Withdraw coins from the pool in an imbalanced amount - @param _amounts List of amounts of underlying coins to withdraw - @param _max_burn_amount Maximum amount of LP token to burn in the withdrawal - @return Actual amount of the LP token burned in the withdrawal - """ - assert not self.is_killed # dev: is killed - - amp: uint256 = self._A() - old_balances: uint256[N_COINS] = self._balances() - D0: uint256 = self.get_D(old_balances, amp) - new_balances: uint256[N_COINS] = old_balances - for i in range(N_COINS): - new_balances[i] -= _amounts[i] - D1: uint256 = self.get_D(new_balances, amp) - - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - admin_fee: uint256 = self.admin_fee - for i in range(N_COINS): - ideal_balance: uint256 = D1 * old_balances[i] / D0 - new_balance: uint256 = new_balances[i] - difference: uint256 = 0 - if ideal_balance > new_balance: - difference = ideal_balance - new_balance - else: - difference = new_balance - ideal_balance - fees[i] = fee * difference / FEE_DENOMINATOR - if admin_fee != 0: - self.admin_balances[i] += fees[i] * admin_fee / FEE_DENOMINATOR - new_balances[i] -= fees[i] - D2: uint256 = self.get_D(new_balances, amp) - - lp_token: address = self.lp_token - token_supply: uint256 = ERC20(lp_token).totalSupply() - token_amount: uint256 = (D0 - D2) * token_supply / D0 - - assert token_amount != 0 # dev: zero tokens burned - assert token_amount <= _max_burn_amount, "Slippage screwed you" - - CurveToken(lp_token).burnFrom(msg.sender, token_amount) # dev: insufficient funds - - if _amounts[0] != 0: - raw_call(msg.sender, b"", value=_amounts[0]) - if _amounts[1] != 0: - assert ERC20(self.coins[1]).transfer(msg.sender, _amounts[1]) - - log RemoveLiquidityImbalance(msg.sender, _amounts, fees, D1, token_supply - token_amount) - - return token_amount - - -@pure -@internal -def get_y_D(A_: uint256, i: int128, xp: uint256[N_COINS], D: uint256) -> uint256: - """ - Calculate x[i] if one reduces D from being calculated for xp to D - - Done by solving quadratic equation iteratively. - x_1**2 + x1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) - x_1**2 + b*x_1 = c - - x_1 = (x_1**2 + c) / (2*x_1 + b) - """ - # x in the input is converted to the same price/precision - - assert i >= 0 # dev: i below zero - assert i < N_COINS # dev: i above N_COINS - - Ann: uint256 = A_ * N_COINS - c: uint256 = D - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: uint256 = 0 - - for _i in range(N_COINS): - if _i != i: - _x = xp[_i] - else: - continue - S_ += _x - c = c * D / (_x * N_COINS) - c = c * D * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann - 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: - return y - else: - if y_prev - y <= 1: - return y - raise - - -@view -@internal -def _calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> (uint256, uint256): - # First, need to calculate - # * Get current D - # * Solve Eqn against y_i for D - _token_amount - amp: uint256 = self._A() - xp: uint256[N_COINS] = self._balances() - D0: uint256 = self.get_D(xp, amp) - total_supply: uint256 = ERC20(self.lp_token).totalSupply() - D1: uint256 = D0 - _token_amount * D0 / total_supply - new_y: uint256 = self.get_y_D(amp, i, xp, D1) - - fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - xp_reduced: uint256[N_COINS] = xp - for j in range(N_COINS): - dx_expected: uint256 = 0 - if j == i: - dx_expected = xp[j] * D1 / D0 - new_y - else: - dx_expected = xp[j] - xp[j] * D1 / D0 - xp_reduced[j] -= fee * dx_expected / FEE_DENOMINATOR - - dy: uint256 = xp_reduced[i] - self.get_y_D(amp, i, xp_reduced, D1) - - dy -= 1 # Withdraw less to account for rounding errors - dy_0: uint256 = xp[i] - new_y # w/o fees - - return dy, dy_0 - dy - - -@view -@external -def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - """ - @notice Calculate the amount received when withdrawing a single coin - @dev Result is the same for underlying or wrapped asset withdrawals - @param _token_amount Amount of LP tokens to burn in the withdrawal - @param i Index value of the coin to withdraw - @return Amount of coin received - """ - return self._calc_withdraw_one_coin(_token_amount, i)[0] - - -@external -@nonreentrant('lock') -def remove_liquidity_one_coin( - _token_amount: uint256, - i: int128, - _min_amount: uint256 -) -> uint256: - """ - @notice Withdraw a single coin from the pool - @param _token_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 coin to receive - @return Amount of coin received - """ - assert not self.is_killed # dev: is killed - - dy: uint256 = 0 - dy_fee: uint256 = 0 - dy, dy_fee = self._calc_withdraw_one_coin(_token_amount, i) - - assert dy >= _min_amount, "Not enough coins removed" - - self.admin_balances[i] += dy_fee * self.admin_fee / FEE_DENOMINATOR - - CurveToken(self.lp_token).burnFrom(msg.sender, _token_amount) # dev: insufficient funds - - if i == 0: - raw_call(msg.sender, b"", value=dy) - else: - assert ERC20(self.coins[1]).transfer(msg.sender, dy) - - log RemoveLiquidityOne(msg.sender, _token_amount, dy) - - return dy - - -### Admin functions ### - -@external -def ramp_A(_future_A: uint256, _future_time: uint256): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.initial_A_time + MIN_RAMP_TIME - assert _future_time >= block.timestamp + MIN_RAMP_TIME # dev: insufficient time - - _initial_A: uint256 = self._A() - _future_A_p: uint256 = _future_A * A_PRECISION - - assert _future_A > 0 and _future_A < MAX_A - if _future_A_p < _initial_A: - assert _future_A_p * MAX_A_CHANGE >= _initial_A - else: - assert _future_A_p <= _initial_A * MAX_A_CHANGE - - self.initial_A = _initial_A - self.future_A = _future_A_p - self.initial_A_time = block.timestamp - self.future_A_time = _future_time - - log RampA(_initial_A, _future_A_p, block.timestamp, _future_time) - - -@external -def stop_ramp_A(): - assert msg.sender == self.owner # dev: only owner - - current_A: uint256 = self._A() - self.initial_A = current_A - self.future_A = current_A - self.initial_A_time = block.timestamp - self.future_A_time = block.timestamp - # now (block.timestamp < t1) is always False, so we return saved A - - log StopRampA(current_A, block.timestamp) - - -@external -def commit_new_fee(new_fee: uint256, new_admin_fee: uint256): - assert msg.sender == self.owner # dev: only owner - assert self.admin_actions_deadline == 0 # dev: active action - assert new_fee <= MAX_FEE # dev: fee exceeds maximum - assert new_admin_fee <= MAX_ADMIN_FEE # dev: admin fee exceeds maximum - - _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.admin_actions_deadline = _deadline - self.future_fee = new_fee - self.future_admin_fee = new_admin_fee - - log CommitNewFee(_deadline, new_fee, new_admin_fee) - - -@external -@nonreentrant('lock') -def apply_new_fee(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.admin_actions_deadline # dev: insufficient time - assert self.admin_actions_deadline != 0 # dev: no active action - - self.admin_actions_deadline = 0 - _fee: uint256 = self.future_fee - _admin_fee: uint256 = self.future_admin_fee - self.fee = _fee - self.admin_fee = _admin_fee - - log NewFee(_fee, _admin_fee) - - -@external -def revert_new_parameters(): - assert msg.sender == self.owner # dev: only owner - - self.admin_actions_deadline = 0 - - -@external -def commit_transfer_ownership(_owner: address): - assert msg.sender == self.owner # dev: only owner - assert self.transfer_ownership_deadline == 0 # dev: active transfer - - _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.transfer_ownership_deadline = _deadline - self.future_owner = _owner - - log CommitNewAdmin(_deadline, _owner) - - -@external -@nonreentrant('lock') -def apply_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.transfer_ownership_deadline # dev: insufficient time - assert self.transfer_ownership_deadline != 0 # dev: no active transfer - - self.transfer_ownership_deadline = 0 - _owner: address = self.future_owner - self.owner = _owner - - log NewAdmin(_owner) - - -@external -def revert_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - - self.transfer_ownership_deadline = 0 - - -@external -@nonreentrant('lock') -def withdraw_admin_fees(): - assert msg.sender == self.owner # dev: only owner - - amount: uint256 = self.admin_balances[0] - if amount != 0: - raw_call(msg.sender, b"", value=amount) - - amount = self.admin_balances[1] - if amount != 0: - assert ERC20(self.coins[1]).transfer(msg.sender, amount) - - self.admin_balances = empty(uint256[N_COINS]) - - -@external -def donate_admin_fees(): - """ - Just in case admin balances somehow become higher than total (rounding error?) - this can be used to fix the state, too - """ - assert msg.sender == self.owner # dev: only owner - self.admin_balances = empty(uint256[N_COINS]) - - -@external -def kill_me(): - assert msg.sender == self.owner # dev: only owner - assert self.kill_deadline > block.timestamp # dev: deadline has passed - self.is_killed = True - - -@external -def unkill_me(): - assert msg.sender == self.owner # dev: only owner - self.is_killed = False diff --git a/contracts/pools/steth/pooldata.json b/contracts/pools/steth/pooldata.json deleted file mode 100644 index f1d77185..00000000 --- a/contracts/pools/steth/pooldata.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "lp_contract": "CurveTokenV3", - "pool_types": ["eth", "arate"], - "swap_address": "0xDC24316b9AE028F1497c275EB9192a3Ea0f67022", - "lp_token_address": "0x06325440D014e39736583c165C2963BA99fAf14E", - "gauge_addresses": ["0x182B723a58739a9c974cFDB385ceaDb237453c28"], - "lp_constructor": { - "symbol": "steCRV", - "name": "Curve.fi ETH/stETH" - }, - "swap_constructor": { - "_A": 5, - "_fee": 4000000, - "_admin_fee": 5000000000 - }, - "coins": [ - { - "name": "ETH", - "decimals": 18, - "tethered": false, - "underlying_address": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" - }, - { - "name": "stETH", - "decimals": 18, - "tethered": false, - "underlying_address": "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84" - } - ], - "testing": { - "initial_amount": 10000 - } -} diff --git a/contracts/pools/susd/DepositSUSD.vy b/contracts/pools/susd/DepositSUSD.vy deleted file mode 100644 index 26786d4e..00000000 --- a/contracts/pools/susd/DepositSUSD.vy +++ /dev/null @@ -1,373 +0,0 @@ -# @version 0.1.0b17 -# A "zap" to deposit/withdraw Curve contract without too many transactions -# (c) Curve.Fi, 2020 -from vyper.interfaces import ERC20 - -# External Contracts -contract cERC20: - def totalSupply() -> uint256: constant - def allowance(_owner: address, _spender: address) -> uint256: constant - def transfer(_to: address, _value: uint256) -> bool: modifying - def transferFrom(_from: address, _to: address, _value: uint256) -> bool: modifying - def approve(_spender: address, _value: uint256) -> bool: modifying - def burn(_value: uint256): modifying - def burnFrom(_to: address, _value: uint256): modifying - def name() -> string[64]: constant - def symbol() -> string[32]: constant - def decimals() -> uint256: constant - def balanceOf(arg0: address) -> uint256: constant - def mint(mintAmount: uint256) -> uint256: modifying - def redeem(redeemTokens: uint256) -> uint256: modifying - def redeemUnderlying(redeemAmount: uint256) -> uint256: modifying - def exchangeRateStored() -> uint256: constant - def exchangeRateCurrent() -> uint256: modifying - def supplyRatePerBlock() -> uint256: constant - def accrualBlockNumber() -> uint256: constant - - - - -# Tether transfer-only ABI -contract USDT: - def transfer(_to: address, _value: uint256): modifying - def transferFrom(_from: address, _to: address, _value: uint256): modifying - - -contract Curve: - def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256): modifying - def remove_liquidity(_amount: uint256, min_amounts: uint256[N_COINS]): modifying - def remove_liquidity_imbalance(amounts: uint256[N_COINS], max_burn_amount: uint256): modifying - def balances(i: int128) -> uint256: constant - def A() -> uint256: constant - def fee() -> uint256: constant - def owner() -> address: constant - - -N_COINS: constant(int128) = 4 -TETHERED: constant(bool[N_COINS]) = [False, False, True, False] -USE_LENDING: constant(bool[N_COINS]) = [False, False, False, False] -ZERO256: constant(uint256) = 0 # This hack is really bad XXX -ZEROS: constant(uint256[N_COINS]) = [ZERO256, ZERO256, ZERO256, ZERO256] # <- change -LENDING_PRECISION: constant(uint256) = 10 ** 18 -PRECISION: constant(uint256) = 10 ** 18 -PRECISION_MUL: constant(uint256[N_COINS]) = [convert(1, uint256), convert(1000000000000, uint256), convert(1000000000000, uint256), convert(1, uint256)] -FEE_DENOMINATOR: constant(uint256) = 10 ** 10 -FEE_IMPRECISION: constant(uint256) = 25 * 10 ** 8 # % of the fee - -coins: public(address[N_COINS]) -underlying_coins: public(address[N_COINS]) -curve: public(address) -token: public(address) - - -@public -def __init__(_coins: address[N_COINS], _underlying_coins: address[N_COINS], - _curve: address, _token: address): - self.coins = _coins - self.underlying_coins = _underlying_coins - self.curve = _curve - self.token = _token - - -@public -@nonreentrant('lock') -def add_liquidity(uamounts: uint256[N_COINS], min_mint_amount: uint256): - use_lending: bool[N_COINS] = USE_LENDING - tethered: bool[N_COINS] = TETHERED - amounts: uint256[N_COINS] = ZEROS - - for i in range(N_COINS): - uamount: uint256 = uamounts[i] - - if uamount > 0: - # Transfer the underlying coin from owner - if tethered[i]: - USDT(self.underlying_coins[i]).transferFrom( - msg.sender, self, uamount) - else: - assert_modifiable(ERC20(self.underlying_coins[i])\ - .transferFrom(msg.sender, self, uamount)) - - # Mint if needed - if use_lending[i]: - ERC20(self.underlying_coins[i]).approve(self.coins[i], uamount) - ok: uint256 = cERC20(self.coins[i]).mint(uamount) - if ok > 0: - raise "Could not mint coin" - amounts[i] = cERC20(self.coins[i]).balanceOf(self) - ERC20(self.coins[i]).approve(self.curve, amounts[i]) - else: - amounts[i] = uamount - ERC20(self.underlying_coins[i]).approve(self.curve, uamount) - - Curve(self.curve).add_liquidity(amounts, min_mint_amount) - - tokens: uint256 = ERC20(self.token).balanceOf(self) - assert_modifiable(ERC20(self.token).transfer(msg.sender, tokens)) - - -@private -def _send_all(_addr: address, min_uamounts: uint256[N_COINS], one: int128): - use_lending: bool[N_COINS] = USE_LENDING - tethered: bool[N_COINS] = TETHERED - - for i in range(N_COINS): - if (one < 0) or (i == one): - if use_lending[i]: - _coin: address = self.coins[i] - _balance: uint256 = cERC20(_coin).balanceOf(self) - if _balance == 0: # Do nothing if there are 0 coins - continue - ok: uint256 = cERC20(_coin).redeem(_balance) - if ok > 0: - raise "Could not redeem coin" - - _ucoin: address = self.underlying_coins[i] - _uamount: uint256 = ERC20(_ucoin).balanceOf(self) - assert _uamount >= min_uamounts[i], "Not enough coins withdrawn" - - # Send only if we have something to send - if _uamount >= 0: - if tethered[i]: - USDT(_ucoin).transfer(_addr, _uamount) - else: - assert_modifiable(ERC20(_ucoin).transfer(_addr, _uamount)) - - -@public -@nonreentrant('lock') -def remove_liquidity(_amount: uint256, min_uamounts: uint256[N_COINS]): - zeros: uint256[N_COINS] = ZEROS - - assert_modifiable(ERC20(self.token).transferFrom(msg.sender, self, _amount)) - Curve(self.curve).remove_liquidity(_amount, zeros) - - self._send_all(msg.sender, min_uamounts, -1) - - -@public -@nonreentrant('lock') -def remove_liquidity_imbalance(uamounts: uint256[N_COINS], max_burn_amount: uint256): - """ - Get max_burn_amount in, remove requested liquidity and transfer back what is left - """ - use_lending: bool[N_COINS] = USE_LENDING - tethered: bool[N_COINS] = TETHERED - _token: address = self.token - - amounts: uint256[N_COINS] = uamounts - for i in range(N_COINS): - if use_lending[i] and amounts[i] > 0: - rate: uint256 = cERC20(self.coins[i]).exchangeRateCurrent() - amounts[i] = amounts[i] * LENDING_PRECISION / rate - # if not use_lending - all good already - - # Transfrer max tokens in - _tokens: uint256 = ERC20(_token).balanceOf(msg.sender) - if _tokens > max_burn_amount: - _tokens = max_burn_amount - assert_modifiable(ERC20(_token).transferFrom(msg.sender, self, _tokens)) - - Curve(self.curve).remove_liquidity_imbalance(amounts, max_burn_amount) - - # Transfer unused tokens back - _tokens = ERC20(_token).balanceOf(self) - assert_modifiable(ERC20(_token).transfer(msg.sender, _tokens)) - - # Unwrap and transfer all the coins we've got - self._send_all(msg.sender, ZEROS, -1) - - -@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(A: uint256, 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 = 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_y(A: uint256, i: int128, _xp: uint256[N_COINS], D: uint256) -> uint256: - """ - Calculate x[i] if one reduces D from being calculated for _xp to D - - Done by solving quadratic equation iteratively. - x_1**2 + x1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) - x_1**2 + b*x_1 = c - - x_1 = (x_1**2 + c) / (2*x_1 + b) - """ - # x in the input is converted to the same price/precision - - assert (i >= 0) and (i < N_COINS) - - c: uint256 = D - S_: uint256 = 0 - Ann: uint256 = A * N_COINS - - _x: uint256 = 0 - for _i in range(N_COINS): - if _i != i: - _x = _xp[_i] - else: - continue - S_ += _x - c = c * D / (_x * N_COINS) - c = c * D / (Ann * N_COINS) - b: uint256 = S_ + D / Ann - 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 - - -@private -@constant -def _calc_withdraw_one_coin(_token_amount: uint256, i: int128, rates: uint256[N_COINS]) -> uint256: - # First, need to calculate - # * Get current D - # * Solve Eqn against y_i for D - _token_amount - use_lending: bool[N_COINS] = USE_LENDING - # tethered: bool[N_COINS] = TETHERED - crv: address = self.curve - A: uint256 = Curve(crv).A() - fee: uint256 = Curve(crv).fee() * N_COINS / (4 * (N_COINS - 1)) - fee += fee * FEE_IMPRECISION / FEE_DENOMINATOR # Overcharge to account for imprecision - precisions: uint256[N_COINS] = PRECISION_MUL - total_supply: uint256 = ERC20(self.token).totalSupply() - - xp: uint256[N_COINS] = PRECISION_MUL - S: uint256 = 0 - for j in range(N_COINS): - xp[j] *= Curve(crv).balances(j) - if use_lending[j]: - # Use stored rate b/c we have imprecision anyway - xp[j] = xp[j] * rates[j] / LENDING_PRECISION - S += xp[j] - # if not use_lending - all good already - - D0: uint256 = self.get_D(A, xp) - D1: uint256 = D0 - _token_amount * D0 / total_supply - xp_reduced: uint256[N_COINS] = xp - - # xp = xp - fee * | xp * D1 / D0 - (xp - S * dD / D0 * (0, ... 1, ..0))| - for j in range(N_COINS): - dx_expected: uint256 = 0 - b_ideal: uint256 = xp[j] * D1 / D0 - b_expected: uint256 = xp[j] - if j == i: - b_expected -= S * (D0 - D1) / D0 - if b_ideal >= b_expected: - dx_expected = (b_ideal - b_expected) - else: - dx_expected = (b_expected - b_ideal) - xp_reduced[j] -= fee * dx_expected / FEE_DENOMINATOR - - dy: uint256 = xp_reduced[i] - self.get_y(A, i, xp_reduced, D1) - dy = dy / precisions[i] - - return dy - - -@public -@constant -def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - rates: uint256[N_COINS] = ZEROS - use_lending: bool[N_COINS] = USE_LENDING - - for j in range(N_COINS): - if use_lending[j]: - rates[j] = cERC20(self.coins[j]).exchangeRateStored() - else: - rates[j] = 10 ** 18 - - return self._calc_withdraw_one_coin(_token_amount, i, rates) - - -@public -@nonreentrant('lock') -def remove_liquidity_one_coin(_token_amount: uint256, i: int128, min_uamount: uint256, donate_dust: bool = False): - """ - Remove _amount of liquidity all in a form of coin i - """ - use_lending: bool[N_COINS] = USE_LENDING - rates: uint256[N_COINS] = ZEROS - _token: address = self.token - - for j in range(N_COINS): - if use_lending[j]: - rates[j] = cERC20(self.coins[j]).exchangeRateCurrent() - else: - rates[j] = LENDING_PRECISION - - dy: uint256 = self._calc_withdraw_one_coin(_token_amount, i, rates) - assert dy >= min_uamount, "Not enough coins removed" - - assert_modifiable( - ERC20(self.token).transferFrom(msg.sender, self, _token_amount)) - - amounts: uint256[N_COINS] = ZEROS - amounts[i] = dy * LENDING_PRECISION / rates[i] - token_amount_before: uint256 = ERC20(_token).balanceOf(self) - Curve(self.curve).remove_liquidity_imbalance(amounts, _token_amount) - - # Unwrap and transfer all the coins we've got - self._send_all(msg.sender, ZEROS, i) - - if not donate_dust: - # Transfer unused tokens back - token_amount_after: uint256 = ERC20(_token).balanceOf(self) - if token_amount_after > token_amount_before: - assert_modifiable(ERC20(_token).transfer( - msg.sender, token_amount_after - token_amount_before) - ) - - -@public -@nonreentrant('lock') -def withdraw_donated_dust(): - owner: address = Curve(self.curve).owner() - assert msg.sender == owner - - _token: address = self.token - assert_modifiable( - ERC20(_token).transfer(owner, ERC20(_token).balanceOf(self))) diff --git a/contracts/pools/susd/README.md b/contracts/pools/susd/README.md deleted file mode 100644 index 585abf45..00000000 --- a/contracts/pools/susd/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# curve-contract/contracts/pools/susd - -[Curve sUSD pool](https://www.curve.fi/susdv2). This is a no-lending pool. - -## Contracts - -* [`DepositSUSD`](DepositSUSD.vy): Depositor contract. Used to handle withdrawal of a single coin. -* [`StableSwapSUSD`](StableSwapSUSD.vy): Curve stablecoin AMM contract. - -## Deployments - -* [`CurveContractV1`](../../tokens/CurveTokenV1.vy): [0xC25a3A3b969415c80451098fa907EC722572917F](https://etherscan.io/address/0xC25a3A3b969415c80451098fa907EC722572917F) -* [`DepositSUSD`](DepositSUSD.vy): [0xfcba3e75865d2d561be8d220616520c171f12851](https://etherscan.io/address/0xfcba3e75865d2d561be8d220616520c171f12851) -* [`LiquidityGaugeReward`](../../gauges/LiquidityGaugeReward.vy): [0xA90996896660DEcC6E997655E065b23788857849](https://etherscan.io/address/0xa90996896660decc6e997655e065b23788857849) -* [`StableSwapSUSD`](StableSwapSUSD.vy): [0xA5407eAE9Ba41422680e2e00537571bcC53efBfD](https://etherscan.io/address/0xA5407eAE9Ba41422680e2e00537571bcC53efBfD) - -## Stablecoins - -Curve sUSD pool supports swaps between the following stablecoins: - -* `DAI`: [0x6b175474e89094c44da98b954eedeac495271d0f](https://etherscan.io/token/0x6b175474e89094c44da98b954eedeac495271d0f) -* `USDC`: [0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48](https://etherscan.io/token/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48) -* `USDT`: [0xdac17f958d2ee523a2206206994597c13d831ec7](https://etherscan.io/address/0xdac17f958d2ee523a2206206994597c13d831ec7) -* `sUSD`: [0x57ab1ec28d129707052df4df418d58a2d46d5f51](https://etherscan.io/address/0x57ab1ec28d129707052df4df418d58a2d46d5f51) diff --git a/contracts/pools/susd/StableSwapSUSD.vy b/contracts/pools/susd/StableSwapSUSD.vy deleted file mode 100644 index 31cd5cf9..00000000 --- a/contracts/pools/susd/StableSwapSUSD.vy +++ /dev/null @@ -1,658 +0,0 @@ -# @version 0.1.0b17 -# (c) Curve.Fi, 2020 - - -# External Contracts -contract ERC20m: - def totalSupply() -> uint256: constant - def allowance(_owner: address, _spender: address) -> uint256: constant - def transfer(_to: address, _value: uint256) -> bool: modifying - def transferFrom(_from: address, _to: address, _value: uint256) -> bool: modifying - def approve(_spender: address, _value: uint256) -> bool: modifying - def mint(_to: address, _value: uint256): modifying - def burn(_value: uint256): modifying - def burnFrom(_to: address, _value: uint256): modifying - def name() -> string[64]: constant - def symbol() -> string[32]: constant - def decimals() -> uint256: constant - def balanceOf(arg0: address) -> uint256: constant - def set_minter(_minter: address): modifying - - - -# External Contracts -contract cERC20: - def totalSupply() -> uint256: constant - def allowance(_owner: address, _spender: address) -> uint256: constant - def transfer(_to: address, _value: uint256) -> bool: modifying - def transferFrom(_from: address, _to: address, _value: uint256) -> bool: modifying - def approve(_spender: address, _value: uint256) -> bool: modifying - def burn(_value: uint256): modifying - def burnFrom(_to: address, _value: uint256): modifying - def name() -> string[64]: constant - def symbol() -> string[32]: constant - def decimals() -> uint256: constant - def balanceOf(arg0: address) -> uint256: constant - def mint(mintAmount: uint256) -> uint256: modifying - def redeem(redeemTokens: uint256) -> uint256: modifying - def redeemUnderlying(redeemAmount: uint256) -> uint256: modifying - def exchangeRateStored() -> uint256: constant - def exchangeRateCurrent() -> uint256: modifying - def supplyRatePerBlock() -> uint256: constant - def accrualBlockNumber() -> uint256: constant - - -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 - - -# This can (and needs to) be changed at compile time -N_COINS: constant(int128) = 4 # <- change - -ZERO256: constant(uint256) = 0 # This hack is really bad XXX -ZEROS: constant(uint256[N_COINS]) = [ZERO256, ZERO256, ZERO256, ZERO256] # <- change - -USE_LENDING: constant(bool[N_COINS]) = [False, False, False, False] - -# Flag "ERC20s" which don't return from transfer() and transferFrom() -TETHERED: constant(bool[N_COINS]) = [False, False, True, False] - -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]) = [convert(1, uint256), convert(1000000000000, uint256), convert(1000000000000, uint256), convert(1, uint256)] -# 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]) -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], - _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.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 _stored_rates() -> uint256[N_COINS]: - # exchangeRateStored * (1 + supplyRatePerBlock * (getBlockNumber - accrualBlockNumber) / 1e18) - result: uint256[N_COINS] = PRECISION_MUL - use_lending: bool[N_COINS] = USE_LENDING - for i in range(N_COINS): - rate: uint256 = LENDING_PRECISION # Used with no lending - if use_lending[i]: - rate = cERC20(self.coins[i]).exchangeRateStored() - supply_rate: uint256 = cERC20(self.coins[i]).supplyRatePerBlock() - old_block: uint256 = cERC20(self.coins[i]).accrualBlockNumber() - rate += rate * supply_rate * (block.number - old_block) / LENDING_PRECISION - result[i] *= rate - return result - - -@private -def _current_rates() -> uint256[N_COINS]: - result: uint256[N_COINS] = PRECISION_MUL - use_lending: bool[N_COINS] = USE_LENDING - for i in range(N_COINS): - rate: uint256 = LENDING_PRECISION # Used with no lending - if use_lending[i]: - rate = cERC20(self.coins[i]).exchangeRateCurrent() - 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._stored_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._stored_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( - cERC20(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._stored_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_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256: - # dx and dy in underlying units - rates: uint256[N_COINS] = self._stored_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 - - -@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(cERC20(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(cERC20(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 - - ok: uint256 = 0 - 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) - ok = cERC20(self.coins[i]).mint(dx) - if ok > 0: - raise "Could not mint coin" - if use_lending[j]: - ok = cERC20(self.coins[j]).redeem(dy_) - if ok > 0: - raise "Could not redeem coin" - 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(cERC20(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(cERC20(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) - - -### 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 = cERC20(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(cERC20(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 diff --git a/contracts/pools/susd/pooldata.json b/contracts/pools/susd/pooldata.json deleted file mode 100644 index 0cd18fab..00000000 --- a/contracts/pools/susd/pooldata.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "lp_contract": "CurveTokenV1", - "swap_address": "0xA5407eAE9Ba41422680e2e00537571bcC53efBfD", - "lp_token_address": "0xC25a3A3b969415c80451098fa907EC722572917F", - "zap_address": "0xfcba3e75865d2d561be8d220616520c171f12851", - "gauge_addresses": ["0xA90996896660DEcC6E997655E065b23788857849"], - "lp_constructor": { - "name": "Curve.fi DAI/USDC/USDT/sUSD", - "symbol": "crvPlain3andSUSD" - }, - "coins": [ - { - "name": "DAI", - "decimals": 18, - "tethered": false, - "underlying_address": "0x6b175474e89094c44da98b954eedeac495271d0f" - }, - { - "name": "USDC", - "decimals": 6, - "tethered": false, - "underlying_address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" - }, - { - "name": "USDT", - "decimals": 6, - "tethered": true, - "underlying_address": "0xdac17f958d2ee523a2206206994597c13d831ec7" - }, - { - "name": "SUSD", - "decimals": 18, - "tethered": false, - "underlying_address": "0x57ab1ec28d129707052df4df418d58a2d46d5f51" - } - ] -} diff --git a/contracts/pools/tbtc/DepositTBTC.vy b/contracts/pools/tbtc/DepositTBTC.vy deleted file mode 100644 index 03f77872..00000000 --- a/contracts/pools/tbtc/DepositTBTC.vy +++ /dev/null @@ -1,366 +0,0 @@ -# @version 0.2.7 -""" -@title "Zap" Depositer for Curve tBTC pool -@author Curve.Fi -@license Copyright (c) Curve.Fi, 2020 - all rights reserved -""" - -from vyper.interfaces import ERC20 - - -interface CurveMeta: - def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256) -> 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) -> 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 base_pool() -> address: 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: int128) -> 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 - - -pool: public(address) -token: public(address) -base_pool: public(address) - -coins: public(address[N_COINS]) -base_coins: public(address[BASE_N_COINS]) - - -@external -def __init__(_pool: address, _token: address): - """ - @notice Contract constructor - @param _pool Metapool address - @param _token Pool LP token address - """ - self.pool = _pool - self.token = _token - _base_pool: address = CurveMeta(_pool).base_pool() - self.base_pool = _base_pool - - for i in range(N_COINS): - coin: address = CurveMeta(_pool).coins(i) - self.coins[i] = coin - # approve coins for infinite transfers - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("approve(address,uint256)"), - convert(_pool, bytes32), - convert(MAX_UINT256, bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) - - for i in range(BASE_N_COINS): - coin: address = CurveBase(_base_pool).coins(i) - self.base_coins[i] = coin - # approve underlying coins for infinite transfers - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("approve(address,uint256)"), - convert(_base_pool, bytes32), - convert(MAX_UINT256, bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) - - -@external -def add_liquidity(amounts: uint256[N_ALL_COINS], min_mint_amount: uint256) -> uint256: - """ - @notice Wrap underlying coins and deposit them in the pool - @param amounts List of amounts of underlying coins to deposit - @param min_mint_amount Minimum amount of LP tokens to mint from the deposit - @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 - - # Transfer all coins in - for i in range(N_ALL_COINS): - amount: uint256 = amounts[i] - if amount == 0: - continue - coin: address = ZERO_ADDRESS - if i < MAX_COIN: - coin = self.coins[i] - meta_amounts[i] = amount - else: - x: int128 = i - MAX_COIN - coin = self.base_coins[x] - base_amounts[x] = amount - deposit_base = True - # "safeTransferFrom" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transferFrom(address,address,uint256)"), - convert(msg.sender, bytes32), - convert(self, bytes32), - convert(amount, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransferFrom" - - # Deposit to the base pool - if deposit_base: - CurveBase(self.base_pool).add_liquidity(base_amounts, 0) - meta_amounts[MAX_COIN] = ERC20(self.coins[MAX_COIN]).balanceOf(self) - - # Deposit to the meta pool - CurveMeta(self.pool).add_liquidity(meta_amounts, min_mint_amount) - - # Transfer meta token back - _lp_token: address = self.token - _lp_amount: uint256 = ERC20(_lp_token).balanceOf(self) - assert ERC20(_lp_token).transfer(msg.sender, _lp_amount) - - return _lp_amount - - -@external -def remove_liquidity(_amount: uint256, min_amounts: uint256[N_ALL_COINS]) -> uint256[N_ALL_COINS]: - """ - @notice Withdraw and unwrap coins from the pool - @dev Withdrawal amounts are based on current deposit ratios - @param _amount Quantity of LP tokens to burn in the withdrawal - @param min_amounts Minimum amounts of underlying coins to receive - @return List of amounts of underlying coins that were withdrawn - """ - _token: address = self.token - assert ERC20(_token).transferFrom(msg.sender, self, _amount) - - min_amounts_meta: uint256[N_COINS] = empty(uint256[N_COINS]) - 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 - for i in range(MAX_COIN): - min_amounts_meta[i] = min_amounts[i] - CurveMeta(self.pool).remove_liquidity(_amount, min_amounts_meta) - - # Withdraw from base - _base_amount: uint256 = ERC20(self.coins[MAX_COIN]).balanceOf(self) - for i in range(BASE_N_COINS): - min_amounts_base[i] = min_amounts[MAX_COIN+i] - CurveBase(self.base_pool).remove_liquidity(_base_amount, min_amounts_base) - - # Transfer all coins out - for i in range(N_ALL_COINS): - coin: address = ZERO_ADDRESS - if i < MAX_COIN: - coin = self.coins[i] - else: - coin = self.base_coins[i - MAX_COIN] - amounts[i] = ERC20(coin).balanceOf(self) - # "safeTransfer" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(amounts[i], bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - return amounts - - -@external -def remove_liquidity_one_coin(_token_amount: uint256, i: int128, _min_amount: uint256) -> uint256: - """ - @notice Withdraw and unwrap a single coin from the pool - @param _token_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 - @return Amount of underlying coin received - """ - assert ERC20(self.token).transferFrom(msg.sender, self, _token_amount) - - coin: address = ZERO_ADDRESS - if i < MAX_COIN: - coin = self.coins[i] - # Withdraw a metapool coin - CurveMeta(self.pool).remove_liquidity_one_coin(_token_amount, i, _min_amount) - else: - coin = self.base_coins[i - MAX_COIN] - # Withdraw a base pool coin - CurveMeta(self.pool).remove_liquidity_one_coin(_token_amount, MAX_COIN, 0) - CurveBase(self.base_pool).remove_liquidity_one_coin( - ERC20(self.coins[MAX_COIN]).balanceOf(self), i-MAX_COIN, _min_amount - ) - - # Tranfer the coin out - coin_amount: uint256 = ERC20(coin).balanceOf(self) - # "safeTransfer" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(coin_amount, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - return coin_amount - - -@external -def remove_liquidity_imbalance(amounts: uint256[N_ALL_COINS], max_burn_amount: uint256) -> uint256: - """ - @notice Withdraw coins from the pool in an imbalanced amount - @param amounts List of amounts of underlying coins to withdraw - @param max_burn_amount Maximum amount of LP token to burn in the withdrawal. - This value cannot exceed the caller's LP token balance. - @return Actual amount of the LP token burned in the withdrawal - """ - _base_pool: address = self.base_pool - _meta_pool: address = self.pool - _base_coins: address[BASE_N_COINS] = self.base_coins - _meta_coins: address[N_COINS] = self.coins - _lp_token: address = self.token - - 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 - assert ERC20(_lp_token).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]) - leftover_amounts: uint256[N_COINS] = empty(uint256[N_COINS]) - - # Prepare quantities - for i in range(MAX_COIN): - amounts_meta[i] = amounts[i] - - for i in range(BASE_N_COINS): - amount: uint256 = amounts[MAX_COIN + i] - if amount != 0: - amounts_base[i] = amount - withdraw_base = True - - if withdraw_base: - amounts_meta[MAX_COIN] = CurveBase(self.base_pool).calc_token_amount(amounts_base, False) - amounts_meta[MAX_COIN] += amounts_meta[MAX_COIN] * fee / FEE_DENOMINATOR + 1 - - # Remove liquidity and deposit leftovers back - CurveMeta(_meta_pool).remove_liquidity_imbalance(amounts_meta, max_burn_amount) - if withdraw_base: - CurveBase(_base_pool).remove_liquidity_imbalance(amounts_base, amounts_meta[MAX_COIN]) - leftover_amounts[MAX_COIN] = ERC20(_meta_coins[MAX_COIN]).balanceOf(self) - if leftover_amounts[MAX_COIN] > 0: - CurveMeta(_meta_pool).add_liquidity(leftover_amounts, 0) - - # Transfer all coins out - for i in range(N_ALL_COINS): - coin: address = ZERO_ADDRESS - amount: uint256 = 0 - if i < MAX_COIN: - coin = _meta_coins[i] - amount = amounts_meta[i] - else: - coin = _base_coins[i - MAX_COIN] - amount = amounts_base[i - MAX_COIN] - # "safeTransfer" which works for ERC20s which return bool or not - if amount > 0: - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(amount, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - # Transfer the leftover LP token out - leftover: uint256 = ERC20(_lp_token).balanceOf(self) - if leftover > 0: - assert ERC20(_lp_token).transfer(msg.sender, leftover) - - return max_burn_amount - leftover - - -@view -@external -def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - """ - @notice Calculate the amount received when withdrawing and unwrapping a single coin - @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(self.pool).calc_withdraw_one_coin(_token_amount, i) - else: - _base_tokens: uint256 = CurveMeta(self.pool).calc_withdraw_one_coin(_token_amount, MAX_COIN) - return CurveBase(self.base_pool).calc_withdraw_one_coin(_base_tokens, i-MAX_COIN) - - -@view -@external -def calc_token_amount(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 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]) - - for i in range(MAX_COIN): - meta_amounts[i] = amounts[i] - - for i in range(BASE_N_COINS): - base_amounts[i] = amounts[i + MAX_COIN] - - _base_tokens: uint256 = CurveBase(self.base_pool).calc_token_amount(base_amounts, is_deposit) - meta_amounts[MAX_COIN] = _base_tokens - - return CurveMeta(self.pool).calc_token_amount(meta_amounts, is_deposit) diff --git a/contracts/pools/tbtc/README.md b/contracts/pools/tbtc/README.md deleted file mode 100644 index e23b1dd7..00000000 --- a/contracts/pools/tbtc/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# curve-contract/contracts/pools/tbtc - -[Curve tBTC metapool](https://www.curve.fi/tbtc), allowing swaps via the Curve [sBTC pool](../sbtc). - -## Contracts - -* [`DepositTBTC`](DepositTBTC.vy): Depositor contract, used to wrap underlying tokens prior to depositing them into the pool -* [`StableSwapTBTC`](StableSwapTBTC.vy): Curve stablecoin AMM contract - -## Deployments - -* [`CurveContractV2`](../../tokens/CurveTokenV2.vy): [0x64eda51d3Ad40D56b9dFc5554E06F94e1Dd786Fd](https://etherscan.io/address/0x64eda51d3Ad40D56b9dFc5554E06F94e1Dd786Fd) -* [`DepositTBTC`](DepositTBTC.vy): [0xaa82ca713D94bBA7A89CEAB55314F9EfFEdDc78c](https://etherscan.io/address/0xaa82ca713D94bBA7A89CEAB55314F9EfFEdDc78c) -* [`LiquidityGaugeReward`](../../gauges/LiquidityGaugeReward.vy): [0x6828bcF74279eE32f2723eC536c22c51Eed383C6](https://etherscan.io/address/0x6828bcF74279eE32f2723eC536c22c51Eed383C6) -* [`StableSwapTBTC`](StableSwapTBTC.vy): [0xC25099792E9349C7DD09759744ea681C7de2cb66](https://etherscan.io/address/0xC25099792E9349C7DD09759744ea681C7de2cb66) - -## Stablecoins - -Curve tBTC metapool utilizes the supports swaps between the following assets: - -## Direct swaps - -Direct swaps are possible between tBTC and the Curve sBTC LP token. - -* `tBTC`: [0x8dAEBADE922dF735c38C80C7eBD708Af50815fAa](https://etherscan.io/address/0x8dAEBADE922dF735c38C80C7eBD708Af50815fAa) -* `sbtcCRV`: [0x075b1bb99792c9E1041bA13afEf80C91a1e70fB3](https://etherscan.io/address/0x075b1bb99792c9E1041bA13afEf80C91a1e70fB3) - -## Base Pool coins - -The sBTC LP token may be wrapped or unwrapped to provide swaps between tBTC and the following coins: - -* `renBTC`: [0xeb4c2781e4eba804ce9a9803c67d0893436bb27d](https://etherscan.io/address/0xeb4c2781e4eba804ce9a9803c67d0893436bb27d) -* `wBTC`: [0x2260fac5e5542a773aa44fbcfedf7c193bc2c599](https://etherscan.io/address/0x2260fac5e5542a773aa44fbcfedf7c193bc2c599) -* `sBTC`: [0xfe18be6b3bd88a2d2a7f928d00292e7a9963cfc6](https://etherscan.io/address/0xfe18be6b3bd88a2d2a7f928d00292e7a9963cfc6) diff --git a/contracts/pools/tbtc/StableSwapTBTC.vy b/contracts/pools/tbtc/StableSwapTBTC.vy deleted file mode 100644 index e3aa1149..00000000 --- a/contracts/pools/tbtc/StableSwapTBTC.vy +++ /dev/null @@ -1,1078 +0,0 @@ -# @version 0.2.7 -""" -@title Curve tBTC Metapool -@author Curve.Fi -@license Copyright (c) Curve.Fi, 2020 - all rights reserved -@dev Utilizes sBTC pool to allow swaps between tBTC / rebBTC / wBTC / sBTC -""" - -from vyper.interfaces import ERC20 - -interface CurveToken: - def totalSupply() -> uint256: view - def mint(_to: address, _value: uint256) -> bool: nonpayable - def burnFrom(_to: address, _value: uint256) -> bool: nonpayable - -interface Curve: - def coins(i: int128) -> address: view - def get_virtual_price() -> uint256: view - def calc_token_amount(amounts: uint256[BASE_N_COINS], deposit: bool) -> uint256: view - def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: view - def fee() -> uint256: view - def get_dy(i: int128, j: int128, dx: uint256) -> uint256: view - def get_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256: view - def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256): nonpayable - def add_liquidity(amounts: uint256[BASE_N_COINS], min_mint_amount: uint256): nonpayable - def remove_liquidity_one_coin(_token_amount: uint256, i: int128, min_amount: uint256): nonpayable - - -# Events -event TokenExchange: - buyer: indexed(address) - sold_id: int128 - tokens_sold: uint256 - bought_id: int128 - tokens_bought: uint256 - -event TokenExchangeUnderlying: - buyer: indexed(address) - sold_id: int128 - tokens_sold: uint256 - bought_id: int128 - tokens_bought: uint256 - -event AddLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event RemoveLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - token_supply: uint256 - -event RemoveLiquidityOne: - provider: indexed(address) - token_amount: uint256 - coin_amount: uint256 - token_supply: uint256 - -event RemoveLiquidityImbalance: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event CommitNewAdmin: - deadline: indexed(uint256) - admin: indexed(address) - -event NewAdmin: - admin: indexed(address) - -event CommitNewFee: - deadline: indexed(uint256) - fee: uint256 - admin_fee: uint256 - -event NewFee: - fee: uint256 - admin_fee: uint256 - -event RampA: - old_A: uint256 - new_A: uint256 - initial_time: uint256 - future_time: uint256 - -event StopRampA: - A: uint256 - t: uint256 - - -N_COINS: constant(int128) = 2 -MAX_COIN: constant(int128) = N_COINS - 1 - -FEE_DENOMINATOR: constant(uint256) = 10 ** 10 -PRECISION: constant(uint256) = 10 ** 18 # The precision to convert to -PRECISION_MUL: constant(uint256[N_COINS]) = [1, 1] -RATES: constant(uint256[N_COINS]) = [1000000000000000000, 1000000000000000000] -BASE_N_COINS: constant(int128) = 3 - -# An asset which may have a transfer fee (renBTC) -FEE_ASSET: constant(address) = 0xEB4C2781e4ebA804CE9a9803C67d0893436bB27D - -MAX_ADMIN_FEE: constant(uint256) = 10 * 10 ** 9 -MAX_FEE: constant(uint256) = 5 * 10 ** 9 -MAX_A: constant(uint256) = 10 ** 6 -MAX_A_CHANGE: constant(uint256) = 10 - -ADMIN_ACTIONS_DELAY: constant(uint256) = 3 * 86400 -MIN_RAMP_TIME: constant(uint256) = 86400 - -coins: public(address[N_COINS]) -balances: public(uint256[N_COINS]) -fee: public(uint256) # fee * 1e10 -admin_fee: public(uint256) # admin_fee * 1e10 - -owner: public(address) -token: public(CurveToken) - -# Token corresponding to the pool is always the last one -BASE_POOL_COINS: constant(int128) = 3 -BASE_CACHE_EXPIRES: constant(int128) = 10 * 60 # 10 min -base_pool: public(address) -base_virtual_price: public(uint256) -base_cache_updated: public(uint256) -base_coins: public(address[BASE_POOL_COINS]) - -A_PRECISION: constant(uint256) = 100 -initial_A: public(uint256) -future_A: public(uint256) -initial_A_time: public(uint256) -future_A_time: public(uint256) - -admin_actions_deadline: public(uint256) -transfer_ownership_deadline: public(uint256) -future_fee: public(uint256) -future_admin_fee: public(uint256) -future_owner: public(address) - -is_killed: bool -kill_deadline: uint256 -KILL_DEADLINE_DT: constant(uint256) = 2 * 30 * 86400 - - -@external -def __init__( - _owner: address, - _coins: address[N_COINS], - _pool_token: address, - _base_pool: address, - _A: uint256, - _fee: uint256, - _admin_fee: uint256 -): - """ - @notice Contract constructor - @param _owner Contract owner address - @param _coins Addresses of ERC20 conracts of coins - @param _pool_token Address of the token representing LP share - @param _base_pool Address of the base pool (which will have a virtual price) - @param _A Amplification coefficient multiplied by n * (n - 1) - @param _fee Fee to charge for exchanges - @param _admin_fee Admin fee - """ - for i in range(N_COINS): - assert _coins[i] != ZERO_ADDRESS - self.coins = _coins - self.initial_A = _A * A_PRECISION - self.future_A = _A * A_PRECISION - self.fee = _fee - self.admin_fee = _admin_fee - self.owner = _owner - self.kill_deadline = block.timestamp + KILL_DEADLINE_DT - self.token = CurveToken(_pool_token) - - self.base_pool = _base_pool - self.base_virtual_price = Curve(_base_pool).get_virtual_price() - self.base_cache_updated = block.timestamp - for i in range(BASE_POOL_COINS): - _base_coin: address = Curve(_base_pool).coins(i) - self.base_coins[i] = _base_coin - - # approve underlying coins for infinite transfers - _response: Bytes[32] = raw_call( - _base_coin, - concat( - method_id("approve(address,uint256)"), - convert(_base_pool, bytes32), - convert(MAX_UINT256, bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) - - -@view -@internal -def _A() -> uint256: - """ - Handle ramping A up or down - """ - t1: uint256 = self.future_A_time - A1: uint256 = self.future_A - - if block.timestamp < t1: - A0: uint256 = self.initial_A - t0: uint256 = self.initial_A_time - # Expressions in uint256 cannot have negative numbers, thus "if" - if A1 > A0: - return A0 + (A1 - A0) * (block.timestamp - t0) / (t1 - t0) - else: - return A0 - (A0 - A1) * (block.timestamp - t0) / (t1 - t0) - - else: # when t1 == 0 or block.timestamp >= t1 - return A1 - - -@view -@external -def A() -> uint256: - return self._A() / A_PRECISION - - -@view -@external -def A_precise() -> uint256: - return self._A() - - -@view -@internal -def _xp(vp_rate: uint256) -> uint256[N_COINS]: - result: uint256[N_COINS] = RATES - result[MAX_COIN] = vp_rate # virtual price for the metacurrency - for i in range(N_COINS): - result[i] = result[i] * self.balances[i] / PRECISION - return result - - -@pure -@internal -def _xp_mem(vp_rate: uint256, _balances: uint256[N_COINS]) -> uint256[N_COINS]: - result: uint256[N_COINS] = RATES - result[MAX_COIN] = vp_rate # virtual price for the metacurrency - for i in range(N_COINS): - result[i] = result[i] * _balances[i] / PRECISION - return result - - -@internal -def _vp_rate() -> uint256: - if block.timestamp > self.base_cache_updated + BASE_CACHE_EXPIRES: - vprice: uint256 = Curve(self.base_pool).get_virtual_price() - self.base_virtual_price = vprice - self.base_cache_updated = block.timestamp - return vprice - else: - return self.base_virtual_price - - -@internal -@view -def _vp_rate_ro() -> uint256: - if block.timestamp > self.base_cache_updated + BASE_CACHE_EXPIRES: - return Curve(self.base_pool).get_virtual_price() - else: - return self.base_virtual_price - - -@pure -@internal -def get_D(xp: uint256[N_COINS], amp: uint256) -> uint256: - S: uint256 = 0 - Dprev: uint256 = 0 - for _x in xp: - S += _x - if S == 0: - return 0 - - D: uint256 = S - Ann: uint256 = amp * N_COINS - for _i in range(255): - D_P: uint256 = D - for _x in xp: - D_P = D_P * D / (_x * N_COINS) # If division by 0, this will be borked: only withdrawal will work. And that is good - Dprev = D - D = (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (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 - - -@view -@internal -def get_D_mem(vp_rate: uint256, _balances: uint256[N_COINS], amp: uint256) -> uint256: - xp: uint256[N_COINS] = self._xp_mem(vp_rate, _balances) - return self.get_D(xp, amp) - - -@view -@external -def get_virtual_price() -> uint256: - """ - @notice The current virtual price of the pool LP token - @dev Useful for calculating profits - @return LP token virtual price normalized to 1e18 - """ - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate_ro() - xp: uint256[N_COINS] = self._xp(vp_rate) - D: uint256 = self.get_D(xp, amp) - # 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 - - -@view -@external -def calc_token_amount(amounts: uint256[N_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 amounts Amount of each coin being deposited - @param is_deposit set True for deposits, False for withdrawals - @return Expected amount of LP tokens received - """ - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate_ro() - _balances: uint256[N_COINS] = self.balances - D0: uint256 = self.get_D_mem(vp_rate, _balances, amp) - for i in range(N_COINS): - if is_deposit: - _balances[i] += amounts[i] - else: - _balances[i] -= amounts[i] - D1: uint256 = self.get_D_mem(vp_rate, _balances, amp) - token_amount: uint256 = self.token.totalSupply() - diff: uint256 = 0 - if is_deposit: - diff = D1 - D0 - else: - diff = D0 - D1 - return diff * token_amount / D0 - - -@external -@nonreentrant('lock') -def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256) -> uint256: - """ - @notice Deposit coins into the pool - @param amounts List of amounts of coins to deposit - @param min_mint_amount Minimum amount of LP tokens to mint from the deposit - @return Amount of LP tokens received by depositing - """ - assert not self.is_killed # dev: is killed - - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate() - token_supply: uint256 = self.token.totalSupply() - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - _admin_fee: uint256 = self.admin_fee - - # Initial invariant - D0: uint256 = 0 - old_balances: uint256[N_COINS] = self.balances - if token_supply > 0: - D0 = self.get_D_mem(vp_rate, old_balances, amp) - new_balances: uint256[N_COINS] = old_balances - - for i in range(N_COINS): - if token_supply == 0: - assert amounts[i] > 0 # dev: initial deposit requires all coins - # balances store amounts of c-tokens - new_balances[i] = old_balances[i] + amounts[i] - - # Invariant after change - D1: uint256 = self.get_D_mem(vp_rate, new_balances, amp) - assert D1 > D0 - - # We need to recalculate the invariant accounting for fees - # to calculate fair user's share - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - 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(vp_rate, new_balances, amp) - 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 amounts[i] > 0: - assert ERC20(self.coins[i]).transferFrom(msg.sender, self, amounts[i]) # dev: failed transfer - - # Mint pool tokens - self.token.mint(msg.sender, mint_amount) - - log AddLiquidity(msg.sender, amounts, fees, D1, token_supply + mint_amount) - - return mint_amount - - -@view -@internal -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 # dev: same coin - assert j >= 0 # dev: j below zero - assert j < N_COINS # dev: j above N_COINS - - # should be unreachable, but good for safety - assert i >= 0 - assert i < N_COINS - - amp: uint256 = self._A() - D: uint256 = self.get_D(xp_, amp) - - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: uint256 = 0 - c: uint256 = D - Ann: uint256 = amp * N_COINS - - 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 * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann # - D - 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 - - -@view -@external -def get_dy(i: int128, j: int128, dx: uint256) -> uint256: - # dx and dy in c-units - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = self._vp_rate_ro() - xp: uint256[N_COINS] = self._xp(rates[MAX_COIN]) - - x: uint256 = xp[i] + (dx * rates[i] / PRECISION) - y: uint256 = self.get_y(i, j, x, xp) - dy: uint256 = xp[j] - y - 1 - _fee: uint256 = self.fee * dy / FEE_DENOMINATOR - return (dy - _fee) * PRECISION / rates[j] - - -@view -@external -def get_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256: - # dx and dy in underlying units - vp_rate: uint256 = self._vp_rate_ro() - xp: uint256[N_COINS] = self._xp(vp_rate) - precisions: uint256[N_COINS] = PRECISION_MUL - _base_pool: address = self.base_pool - - # Use base_i or base_j if they are >= 0 - base_i: int128 = i - MAX_COIN - base_j: int128 = j - MAX_COIN - meta_i: int128 = MAX_COIN - meta_j: int128 = MAX_COIN - if base_i < 0: - meta_i = i - if base_j < 0: - meta_j = j - - x: uint256 = 0 - if base_i < 0: - x = xp[i] + dx * precisions[i] - else: - if base_j < 0: - # i is from BasePool - # At first, get the amount of pool tokens - base_inputs: uint256[BASE_N_COINS] = empty(uint256[BASE_N_COINS]) - base_inputs[base_i] = dx - # Token amount transformed to underlying "dollars" - x = Curve(_base_pool).calc_token_amount(base_inputs, True) * vp_rate / PRECISION - # Accounting for deposit/withdraw fees approximately - x -= x * Curve(_base_pool).fee() / (2 * FEE_DENOMINATOR) - # Adding number of pool tokens - x += xp[MAX_COIN] - else: - # If both are from the base pool - return Curve(_base_pool).get_dy(base_i, base_j, dx) - - # This pool is involved only when in-pool assets are used - y: uint256 = self.get_y(meta_i, meta_j, x, xp) - dy: uint256 = xp[meta_j] - y - 1 - dy = (dy - self.fee * dy / FEE_DENOMINATOR) - - # If output is going via the metapool - if base_j < 0: - dy /= precisions[meta_j] - else: - # j is from BasePool - # The fee is already accounted for - dy = Curve(_base_pool).calc_withdraw_one_coin(dy * PRECISION / vp_rate, base_j) - - return dy - - -@external -@nonreentrant('lock') -def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256) -> uint256: - """ - @notice Perform an exchange between two coins - @dev Index values can be found via the `coins` public getter method - @param i Index value for the coin to send - @param j Index valie of the coin to recieve - @param dx Amount of `i` being exchanged - @param min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - assert not self.is_killed # dev: is killed - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = self._vp_rate() - - old_balances: uint256[N_COINS] = self.balances - xp: uint256[N_COINS] = self._xp_mem(rates[MAX_COIN], old_balances) - - x: uint256 = xp[i] + dx * rates[i] / PRECISION - y: uint256 = self.get_y(i, j, x, xp) - - dy: uint256 = xp[j] - y - 1 # -1 just in case there were some rounding errors - dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR - - # Convert all to real units - dy = (dy - dy_fee) * PRECISION / rates[j] - assert dy >= min_dy, "Too few coins in result" - - dy_admin_fee: uint256 = dy_fee * self.admin_fee / FEE_DENOMINATOR - dy_admin_fee = dy_admin_fee * PRECISION / rates[j] - - # Change balances exactly in same way as we change actual ERC20 coin amounts - self.balances[i] = old_balances[i] + dx - # When rounding errors happen, we undercharge admin fee in favor of LP - self.balances[j] = old_balances[j] - dy - dy_admin_fee - - assert ERC20(self.coins[i]).transferFrom(msg.sender, self, dx) - assert ERC20(self.coins[j]).transfer(msg.sender, dy) - - log TokenExchange(msg.sender, i, dx, j, dy) - - return dy - - -@external -@nonreentrant('lock') -def exchange_underlying(i: int128, j: int128, dx: uint256, min_dy: uint256) -> uint256: - """ - @notice Perform an exchange between two underlying coins - @dev Index values can be found via the `underlying_coins` public getter method - @param i Index value for the underlying coin to send - @param j Index valie of the underlying coin to recieve - @param dx Amount of `i` being exchanged - @param min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - assert not self.is_killed # dev: is killed - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = self._vp_rate() - _base_pool: address = self.base_pool - - # Use base_i or base_j if they are >= 0 - base_i: int128 = i - MAX_COIN - base_j: int128 = j - MAX_COIN - meta_i: int128 = MAX_COIN - meta_j: int128 = MAX_COIN - if base_i < 0: - meta_i = i - if base_j < 0: - meta_j = j - dy: uint256 = 0 - - # Addresses for input and output coins - input_coin: address = ZERO_ADDRESS - if base_i < 0: - input_coin = self.coins[i] - else: - input_coin = self.base_coins[base_i] - output_coin: address = ZERO_ADDRESS - if base_j < 0: - output_coin = self.coins[j] - else: - output_coin = self.base_coins[base_j] - - # Handle potential Tether fees - dx_w_fee: uint256 = dx - if input_coin == FEE_ASSET: - dx_w_fee = ERC20(FEE_ASSET).balanceOf(self) - # "safeTransferFrom" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - input_coin, - concat( - method_id("transferFrom(address,address,uint256)"), - convert(msg.sender, bytes32), - convert(self, bytes32), - convert(dx, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransferFrom" - # Handle potential Tether fees - if input_coin == FEE_ASSET: - dx_w_fee = ERC20(FEE_ASSET).balanceOf(self) - dx_w_fee - - if base_i < 0 or base_j < 0: - old_balances: uint256[N_COINS] = self.balances - xp: uint256[N_COINS] = self._xp_mem(rates[MAX_COIN], old_balances) - - x: uint256 = 0 - if base_i < 0: - x = xp[i] + dx_w_fee * rates[i] / PRECISION - else: - # i is from BasePool - # At first, get the amount of pool tokens - base_inputs: uint256[BASE_N_COINS] = empty(uint256[BASE_N_COINS]) - base_inputs[base_i] = dx_w_fee - coin_i: address = self.coins[MAX_COIN] - # Deposit and measure delta - x = ERC20(coin_i).balanceOf(self) - Curve(_base_pool).add_liquidity(base_inputs, 0) - # Need to convert pool token to "virtual" units using rates - # dx is also different now - dx_w_fee = ERC20(coin_i).balanceOf(self) - x - x = dx_w_fee * rates[MAX_COIN] / PRECISION - # Adding number of pool tokens - x += xp[MAX_COIN] - - y: uint256 = self.get_y(meta_i, meta_j, x, xp) - - # Either a real coin or token - dy = xp[meta_j] - y - 1 # -1 just in case there were some rounding errors - dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR - - # Convert all to real units - # Works for both pool coins and real coins - dy = (dy - dy_fee) * PRECISION / rates[meta_j] - - dy_admin_fee: uint256 = dy_fee * self.admin_fee / FEE_DENOMINATOR - dy_admin_fee = dy_admin_fee * PRECISION / rates[meta_j] - - # Change balances exactly in same way as we change actual ERC20 coin amounts - self.balances[meta_i] = old_balances[meta_i] + dx_w_fee - # When rounding errors happen, we undercharge admin fee in favor of LP - self.balances[meta_j] = old_balances[meta_j] - dy - dy_admin_fee - - # Withdraw from the base pool if needed - if base_j >= 0: - out_amount: uint256 = ERC20(output_coin).balanceOf(self) - Curve(_base_pool).remove_liquidity_one_coin(dy, base_j, 0) - dy = ERC20(output_coin).balanceOf(self) - out_amount - - assert dy >= min_dy, "Too few coins in result" - - else: - # If both are from the base pool - dy = ERC20(output_coin).balanceOf(self) - Curve(_base_pool).exchange(base_i, base_j, dx_w_fee, min_dy) - dy = ERC20(output_coin).balanceOf(self) - dy - - # "safeTransfer" which works for ERC20s which return bool or not - _response = raw_call( - output_coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(dy, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - log TokenExchangeUnderlying(msg.sender, i, dx, j, dy) - - return dy - - -@external -@nonreentrant('lock') -def remove_liquidity(_amount: uint256, min_amounts: uint256[N_COINS]) -> uint256[N_COINS]: - """ - @notice Withdraw coins from the pool - @dev Withdrawal amounts are based on current deposit ratios - @param _amount Quantity of LP tokens to burn in the withdrawal - @param min_amounts Minimum amounts of underlying coins to receive - @return List of amounts of coins that were withdrawn - """ - total_supply: uint256 = self.token.totalSupply() - amounts: uint256[N_COINS] = empty(uint256[N_COINS]) - fees: uint256[N_COINS] = empty(uint256[N_COINS]) # Fees are unused but we've got them historically in event - - for i in range(N_COINS): - value: uint256 = self.balances[i] * _amount / total_supply - assert value >= min_amounts[i], "Too few coins in result" - self.balances[i] -= value - amounts[i] = value - assert ERC20(self.coins[i]).transfer(msg.sender, value) - - self.token.burnFrom(msg.sender, _amount) # dev: insufficient funds - - log RemoveLiquidity(msg.sender, amounts, fees, total_supply - _amount) - - return amounts - - -@external -@nonreentrant('lock') -def remove_liquidity_imbalance(amounts: uint256[N_COINS], max_burn_amount: uint256) -> uint256: - """ - @notice Withdraw coins from the pool in an imbalanced amount - @param amounts List of amounts of underlying coins to withdraw - @param max_burn_amount Maximum amount of LP token to burn in the withdrawal - @return Actual amount of the LP token burned in the withdrawal - """ - assert not self.is_killed # dev: is killed - - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate() - - token_supply: uint256 = self.token.totalSupply() - assert token_supply != 0 # dev: zero total supply - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - _admin_fee: uint256 = self.admin_fee - - old_balances: uint256[N_COINS] = self.balances - new_balances: uint256[N_COINS] = old_balances - D0: uint256 = self.get_D_mem(vp_rate, old_balances, amp) - for i in range(N_COINS): - new_balances[i] -= amounts[i] - D1: uint256 = self.get_D_mem(vp_rate, new_balances, amp) - - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - 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(vp_rate, new_balances, amp) - - token_amount: uint256 = (D0 - D2) * token_supply / D0 - assert token_amount != 0 # dev: zero tokens burned - token_amount += 1 # In case of rounding errors - make it unfavorable for the "attacker" - assert token_amount <= max_burn_amount, "Slippage screwed you" - - self.token.burnFrom(msg.sender, token_amount) # dev: insufficient funds - for i in range(N_COINS): - if amounts[i] != 0: - assert ERC20(self.coins[i]).transfer(msg.sender, amounts[i]) - - log RemoveLiquidityImbalance(msg.sender, amounts, fees, D1, token_supply - token_amount) - - return token_amount - - -@view -@internal -def get_y_D(A_: uint256, i: int128, xp: uint256[N_COINS], D: uint256) -> uint256: - """ - Calculate x[i] if one reduces D from being calculated for xp to D - - Done by solving quadratic equation iteratively. - x_1**2 + x1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) - x_1**2 + b*x_1 = c - - x_1 = (x_1**2 + c) / (2*x_1 + b) - """ - # x in the input is converted to the same price/precision - - assert i >= 0 # dev: i below zero - assert i < N_COINS # dev: i above N_COINS - - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: uint256 = 0 - - c: uint256 = D - Ann: uint256 = A_ * N_COINS - - for _i in range(N_COINS): - if _i != i: - _x = xp[_i] - else: - continue - S_ += _x - c = c * D / (_x * N_COINS) - c = c * D * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann - 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 - - -@view -@internal -def _calc_withdraw_one_coin(_token_amount: uint256, i: int128, vp_rate: uint256) -> (uint256, uint256, uint256): - # First, need to calculate - # * Get current D - # * Solve Eqn against y_i for D - _token_amount - amp: uint256 = self._A() - xp: uint256[N_COINS] = self._xp(vp_rate) - D0: uint256 = self.get_D(xp, amp) - - total_supply: uint256 = self.token.totalSupply() - D1: uint256 = D0 - _token_amount * D0 / total_supply - new_y: uint256 = self.get_y_D(amp, i, xp, D1) - - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = vp_rate - - xp_reduced: uint256[N_COINS] = xp - dy_0: uint256 = (xp[i] - new_y) * PRECISION / rates[i] # w/o fees - - for j in range(N_COINS): - dx_expected: uint256 = 0 - if j == i: - dx_expected = xp[j] * D1 / D0 - new_y - else: - dx_expected = xp[j] - xp[j] * D1 / D0 - xp_reduced[j] -= _fee * dx_expected / FEE_DENOMINATOR - - dy: uint256 = xp_reduced[i] - self.get_y_D(amp, i, xp_reduced, D1) - dy = (dy - 1) * PRECISION / rates[i] # Withdraw less to account for rounding errors - - return dy, dy_0 - dy, total_supply - - -@view -@external -def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - """ - @notice Calculate the amount received when withdrawing a single coin - @param _token_amount Amount of LP tokens to burn in the withdrawal - @param i Index value of the coin to withdraw - @return Amount of coin received - """ - vp_rate: uint256 = self._vp_rate_ro() - return self._calc_withdraw_one_coin(_token_amount, i, vp_rate)[0] - - -@external -@nonreentrant('lock') -def remove_liquidity_one_coin(_token_amount: uint256, i: int128, _min_amount: uint256) -> uint256: - """ - @notice Withdraw a single coin from the pool - @param _token_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 coin to receive - @return Amount of coin received - """ - assert not self.is_killed # dev: is killed - - vp_rate: uint256 = self._vp_rate() - dy: uint256 = 0 - dy_fee: uint256 = 0 - total_supply: uint256 = 0 - dy, dy_fee, total_supply = self._calc_withdraw_one_coin(_token_amount, i, vp_rate) - assert dy >= _min_amount, "Not enough coins removed" - - self.balances[i] -= (dy + dy_fee * self.admin_fee / FEE_DENOMINATOR) - self.token.burnFrom(msg.sender, _token_amount) # dev: insufficient funds - assert ERC20(self.coins[i]).transfer(msg.sender, dy) - - log RemoveLiquidityOne(msg.sender, _token_amount, dy, total_supply - _token_amount) - - return dy - - -### Admin functions ### -@external -def ramp_A(_future_A: uint256, _future_time: uint256): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.initial_A_time + MIN_RAMP_TIME - assert _future_time >= block.timestamp + MIN_RAMP_TIME # dev: insufficient time - - _initial_A: uint256 = self._A() - _future_A_p: uint256 = _future_A * A_PRECISION - - assert _future_A > 0 and _future_A < MAX_A - if _future_A_p < _initial_A: - assert _future_A_p * MAX_A_CHANGE >= _initial_A - else: - assert _future_A_p <= _initial_A * MAX_A_CHANGE - - self.initial_A = _initial_A - self.future_A = _future_A_p - self.initial_A_time = block.timestamp - self.future_A_time = _future_time - - log RampA(_initial_A, _future_A_p, block.timestamp, _future_time) - - -@external -def stop_ramp_A(): - assert msg.sender == self.owner # dev: only owner - - current_A: uint256 = self._A() - self.initial_A = current_A - self.future_A = current_A - self.initial_A_time = block.timestamp - self.future_A_time = block.timestamp - # now (block.timestamp < t1) is always False, so we return saved A - - log StopRampA(current_A, block.timestamp) - - -@external -def commit_new_fee(new_fee: uint256, new_admin_fee: uint256): - assert msg.sender == self.owner # dev: only owner - assert self.admin_actions_deadline == 0 # dev: active action - assert new_fee <= MAX_FEE # dev: fee exceeds maximum - assert new_admin_fee <= MAX_ADMIN_FEE # dev: admin fee exceeds maximum - - _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.admin_actions_deadline = _deadline - self.future_fee = new_fee - self.future_admin_fee = new_admin_fee - - log CommitNewFee(_deadline, new_fee, new_admin_fee) - - -@external -def apply_new_fee(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.admin_actions_deadline # dev: insufficient time - assert self.admin_actions_deadline != 0 # dev: no active action - - self.admin_actions_deadline = 0 - _fee: uint256 = self.future_fee - _admin_fee: uint256 = self.future_admin_fee - self.fee = _fee - self.admin_fee = _admin_fee - - log NewFee(_fee, _admin_fee) - - -@external -def revert_new_parameters(): - assert msg.sender == self.owner # dev: only owner - - self.admin_actions_deadline = 0 - - -@external -def commit_transfer_ownership(_owner: address): - assert msg.sender == self.owner # dev: only owner - assert self.transfer_ownership_deadline == 0 # dev: active transfer - - _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.transfer_ownership_deadline = _deadline - self.future_owner = _owner - - log CommitNewAdmin(_deadline, _owner) - - -@external -def apply_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.transfer_ownership_deadline # dev: insufficient time - assert self.transfer_ownership_deadline != 0 # dev: no active transfer - - self.transfer_ownership_deadline = 0 - _owner: address = self.future_owner - self.owner = _owner - - log NewAdmin(_owner) - - -@external -def revert_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - - self.transfer_ownership_deadline = 0 - - -@view -@external -def admin_balances(i: uint256) -> uint256: - return ERC20(self.coins[i]).balanceOf(self) - self.balances[i] - - -@external -def withdraw_admin_fees(): - assert msg.sender == self.owner # dev: only owner - - for i in range(N_COINS): - c: address = self.coins[i] - value: uint256 = ERC20(c).balanceOf(self) - self.balances[i] - if value > 0: - assert ERC20(c).transfer(msg.sender, value) - - -@external -def donate_admin_fees(): - assert msg.sender == self.owner # dev: only owner - for i in range(N_COINS): - self.balances[i] = ERC20(self.coins[i]).balanceOf(self) - - -@external -def kill_me(): - assert msg.sender == self.owner # dev: only owner - assert self.kill_deadline > block.timestamp # dev: deadline has passed - self.is_killed = True - - -@external -def unkill_me(): - assert msg.sender == self.owner # dev: only owner - self.is_killed = False diff --git a/contracts/pools/tbtc/pooldata.json b/contracts/pools/tbtc/pooldata.json deleted file mode 100644 index a72f1247..00000000 --- a/contracts/pools/tbtc/pooldata.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "base_pool": "sbtc", - "pool_types": ["meta"], - "swap_address": "0xC25099792E9349C7DD09759744ea681C7de2cb66", - "lp_token_address": "0x64eda51d3Ad40D56b9dFc5554E06F94e1Dd786Fd", - "zap_address": "0xaa82ca713D94bBA7A89CEAB55314F9EfFEdDc78c", - "gauge_addresses": ["0x6828bcF74279eE32f2723eC536c22c51Eed383C6"], - "lp_contract": "CurveTokenV2", - "lp_constructor": { - "symbol": "tBTC/sbtcCRV", - "name": "Curve.fi tBTC/sbtcCRV" - }, - "swap_constructor": { - "_A": 200, - "_fee": 4000000, - "_admin_fee": 0 - }, - "coins": [ - { - "name": "tBTC", - "decimals": 18, - "tethered": false, - "wrapped": false, - "underlying_address": "0x8dAEBADE922dF735c38C80C7eBD708Af50815fAa" - }, - { - "name": "sbtcCRV", - "decimals": 18, - "wrapped": false, - "base_pool_token": true, - "underlying_address": "0x075b1bb99792c9E1041bA13afEf80C91a1e70fB3" - } - ], - "testing": { - "initial_amount": 100 - } -} diff --git a/contracts/pools/usdk/DepositUSDK.vy b/contracts/pools/usdk/DepositUSDK.vy deleted file mode 100644 index e6f3377f..00000000 --- a/contracts/pools/usdk/DepositUSDK.vy +++ /dev/null @@ -1,376 +0,0 @@ -# @version 0.2.7 -""" -@title "Zap" Depositer for Curve USDK pool -@author Curve.Fi -@license Copyright (c) Curve.Fi, 2020 - all rights reserved -""" - -from vyper.interfaces import ERC20 - - -interface CurveMeta: - def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256) -> 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) -> 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 base_pool() -> address: 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 - -# An asset which may have a transfer fee (USDT) -FEE_ASSET: constant(address) = 0xdAC17F958D2ee523a2206206994597C13D831ec7 - -FEE_DENOMINATOR: constant(uint256) = 10 ** 10 -FEE_IMPRECISION: constant(uint256) = 100 * 10 ** 8 # % of the fee - - -pool: public(address) -token: public(address) -base_pool: public(address) - -coins: public(address[N_COINS]) -base_coins: public(address[BASE_N_COINS]) - - -@external -def __init__(_pool: address, _token: address): - """ - @notice Contract constructor - @param _pool Metapool address - @param _token Pool LP token address - """ - self.pool = _pool - self.token = _token - _base_pool: address = CurveMeta(_pool).base_pool() - self.base_pool = _base_pool - - for i in range(N_COINS): - coin: address = CurveMeta(_pool).coins(convert(i, uint256)) - self.coins[i] = coin - # approve coins for infinite transfers - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("approve(address,uint256)"), - convert(_pool, bytes32), - convert(MAX_UINT256, bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) - - for i in range(BASE_N_COINS): - coin: address = CurveBase(_base_pool).coins(convert(i, uint256)) - self.base_coins[i] = coin - # approve underlying coins for infinite transfers - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("approve(address,uint256)"), - convert(_base_pool, bytes32), - convert(MAX_UINT256, bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) - - -@external -def add_liquidity(amounts: uint256[N_ALL_COINS], min_mint_amount: uint256) -> uint256: - """ - @notice Wrap underlying coins and deposit them in the pool - @param amounts List of amounts of underlying coins to deposit - @param min_mint_amount Minimum amount of LP tokens to mint from the deposit - @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 - - # Transfer all coins in - for i in range(N_ALL_COINS): - amount: uint256 = amounts[i] - if amount == 0: - continue - coin: address = ZERO_ADDRESS - if i < MAX_COIN: - coin = self.coins[i] - meta_amounts[i] = amount - else: - x: int128 = i - MAX_COIN - coin = self.base_coins[x] - base_amounts[x] = amount - deposit_base = True - # "safeTransferFrom" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transferFrom(address,address,uint256)"), - convert(msg.sender, bytes32), - convert(self, bytes32), - convert(amount, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransferFrom" - # Handle potential Tether fees - if coin == FEE_ASSET: - amount = ERC20(FEE_ASSET).balanceOf(self) - if i < MAX_COIN: - meta_amounts[i] = amount - else: - base_amounts[i - MAX_COIN] = amount - - # Deposit to the base pool - if deposit_base: - CurveBase(self.base_pool).add_liquidity(base_amounts, 0) - meta_amounts[MAX_COIN] = ERC20(self.coins[MAX_COIN]).balanceOf(self) - - # Deposit to the meta pool - CurveMeta(self.pool).add_liquidity(meta_amounts, min_mint_amount) - - # Transfer meta token back - _lp_token: address = self.token - _lp_amount: uint256 = ERC20(_lp_token).balanceOf(self) - assert ERC20(_lp_token).transfer(msg.sender, _lp_amount) - - return _lp_amount - - -@external -def remove_liquidity(_amount: uint256, min_amounts: uint256[N_ALL_COINS]) -> uint256[N_ALL_COINS]: - """ - @notice Withdraw and unwrap coins from the pool - @dev Withdrawal amounts are based on current deposit ratios - @param _amount Quantity of LP tokens to burn in the withdrawal - @param min_amounts Minimum amounts of underlying coins to receive - @return List of amounts of underlying coins that were withdrawn - """ - _token: address = self.token - assert ERC20(_token).transferFrom(msg.sender, self, _amount) - - min_amounts_meta: uint256[N_COINS] = empty(uint256[N_COINS]) - 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 - for i in range(MAX_COIN): - min_amounts_meta[i] = min_amounts[i] - CurveMeta(self.pool).remove_liquidity(_amount, min_amounts_meta) - - # Withdraw from base - _base_amount: uint256 = ERC20(self.coins[MAX_COIN]).balanceOf(self) - for i in range(BASE_N_COINS): - min_amounts_base[i] = min_amounts[MAX_COIN+i] - CurveBase(self.base_pool).remove_liquidity(_base_amount, min_amounts_base) - - # Transfer all coins out - for i in range(N_ALL_COINS): - coin: address = ZERO_ADDRESS - if i < MAX_COIN: - coin = self.coins[i] - else: - coin = self.base_coins[i - MAX_COIN] - amounts[i] = ERC20(coin).balanceOf(self) - # "safeTransfer" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(amounts[i], bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - return amounts - - -@external -def remove_liquidity_one_coin(_token_amount: uint256, i: int128, _min_amount: uint256) -> uint256: - """ - @notice Withdraw and unwrap a single coin from the pool - @param _token_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 - @return Amount of underlying coin received - """ - assert ERC20(self.token).transferFrom(msg.sender, self, _token_amount) - - coin: address = ZERO_ADDRESS - if i < MAX_COIN: - coin = self.coins[i] - # Withdraw a metapool coin - CurveMeta(self.pool).remove_liquidity_one_coin(_token_amount, i, _min_amount) - else: - coin = self.base_coins[i - MAX_COIN] - # Withdraw a base pool coin - CurveMeta(self.pool).remove_liquidity_one_coin(_token_amount, MAX_COIN, 0) - CurveBase(self.base_pool).remove_liquidity_one_coin( - ERC20(self.coins[MAX_COIN]).balanceOf(self), i-MAX_COIN, _min_amount - ) - - # Tranfer the coin out - coin_amount: uint256 = ERC20(coin).balanceOf(self) - # "safeTransfer" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(coin_amount, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - return coin_amount - - -@external -def remove_liquidity_imbalance(amounts: uint256[N_ALL_COINS], max_burn_amount: uint256) -> uint256: - """ - @notice Withdraw coins from the pool in an imbalanced amount - @param amounts List of amounts of underlying coins to withdraw - @param max_burn_amount Maximum amount of LP token to burn in the withdrawal. - This value cannot exceed the caller's LP token balance. - @return Actual amount of the LP token burned in the withdrawal - """ - _base_pool: address = self.base_pool - _meta_pool: address = self.pool - _base_coins: address[BASE_N_COINS] = self.base_coins - _meta_coins: address[N_COINS] = self.coins - _lp_token: address = self.token - - 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 - assert ERC20(_lp_token).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]) - leftover_amounts: uint256[N_COINS] = empty(uint256[N_COINS]) - - # Prepare quantities - for i in range(MAX_COIN): - amounts_meta[i] = amounts[i] - - for i in range(BASE_N_COINS): - amount: uint256 = amounts[MAX_COIN + i] - if amount != 0: - amounts_base[i] = amount - withdraw_base = True - - if withdraw_base: - amounts_meta[MAX_COIN] = CurveBase(self.base_pool).calc_token_amount(amounts_base, False) - amounts_meta[MAX_COIN] += amounts_meta[MAX_COIN] * fee / FEE_DENOMINATOR + 1 - - # Remove liquidity and deposit leftovers back - CurveMeta(_meta_pool).remove_liquidity_imbalance(amounts_meta, max_burn_amount) - if withdraw_base: - CurveBase(_base_pool).remove_liquidity_imbalance(amounts_base, amounts_meta[MAX_COIN]) - leftover_amounts[MAX_COIN] = ERC20(_meta_coins[MAX_COIN]).balanceOf(self) - if leftover_amounts[MAX_COIN] > 0: - CurveMeta(_meta_pool).add_liquidity(leftover_amounts, 0) - - # Transfer all coins out - for i in range(N_ALL_COINS): - coin: address = ZERO_ADDRESS - amount: uint256 = 0 - if i < MAX_COIN: - coin = _meta_coins[i] - amount = amounts_meta[i] - else: - coin = _base_coins[i - MAX_COIN] - amount = amounts_base[i - MAX_COIN] - # "safeTransfer" which works for ERC20s which return bool or not - if amount > 0: - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(amount, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - # Transfer the leftover LP token out - leftover: uint256 = ERC20(_lp_token).balanceOf(self) - if leftover > 0: - assert ERC20(_lp_token).transfer(msg.sender, leftover) - - return max_burn_amount - leftover - - -@view -@external -def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - """ - @notice Calculate the amount received when withdrawing and unwrapping a single coin - @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(self.pool).calc_withdraw_one_coin(_token_amount, i) - else: - _base_tokens: uint256 = CurveMeta(self.pool).calc_withdraw_one_coin(_token_amount, MAX_COIN) - return CurveBase(self.base_pool).calc_withdraw_one_coin(_base_tokens, i-MAX_COIN) - - -@view -@external -def calc_token_amount(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 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]) - - for i in range(MAX_COIN): - meta_amounts[i] = amounts[i] - - for i in range(BASE_N_COINS): - base_amounts[i] = amounts[i + MAX_COIN] - - _base_tokens: uint256 = CurveBase(self.base_pool).calc_token_amount(base_amounts, is_deposit) - meta_amounts[MAX_COIN] = _base_tokens - - return CurveMeta(self.pool).calc_token_amount(meta_amounts, is_deposit) diff --git a/contracts/pools/usdk/README.md b/contracts/pools/usdk/README.md deleted file mode 100644 index 95906277..00000000 --- a/contracts/pools/usdk/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# curve-contract/contracts/pools/usdk - -[Curve USDK metapool](https://www.curve.fi/usdk), allowing swaps via the Curve [tri-pool](../3pool). - -## Contracts - -* [`DepositUSDK`](DepositUSDK.vy): Depositor contract, used to wrap underlying tokens prior to depositing them into the pool -* [`StableSwapUSDK`](StableSwapUSDK.vy): Curve stablecoin AMM contract - -## Deployments - -* [`CurveContractV2`](../../tokens/CurveTokenV2.vy): [0x97E2768e8E73511cA874545DC5Ff8067eB19B787](https://etherscan.io/address/0x97E2768e8E73511cA874545DC5Ff8067eB19B787) -* [`DepositUSDK`](DepositUSDK.vy): [0xF1f85a74AD6c64315F85af52d3d46bF715236ADc](https://etherscan.io/address/0xF1f85a74AD6c64315F85af52d3d46bF715236ADc) -* [`LiquidityGauge`](../../gauges/LiquidityGauge.vy): [0xC2b1DF84112619D190193E48148000e3990Bf627](https://etherscan.io/address/0xC2b1DF84112619D190193E48148000e3990Bf627) -* [`StableSwapUSDK`](StableSwapUSDK.vy): [0x3E01dD8a5E1fb3481F0F589056b428Fc308AF0Fb](https://etherscan.io/address/0x3E01dD8a5E1fb3481F0F589056b428Fc308AF0Fb) - -## Stablecoins - -Curve USDK metapool utilizes the supports swaps between the following assets: - -## Direct swaps - -Direct swaps are possible between USDK and the Curve tri-pool LP token. - -* `USDK`: [0x1c48f86ae57291f7686349f12601910bd8d470bb](https://etherscan.io/address/0x1c48f86ae57291f7686349f12601910bd8d470bb) -* `3CRV`: [0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490](https://etherscan.io/address/0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490) - -## Base Pool coins - -The tri-pool LP token may be wrapped or unwrapped to provide swaps between USDK and the following stablecoins: - -* `DAI`: [0x6b175474e89094c44da98b954eedeac495271d0f](https://etherscan.io/address/0x6b175474e89094c44da98b954eedeac495271d0f) -* `USDC`: [0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48](https://etherscan.io/address/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48) -* `USDT`: [0xdac17f958d2ee523a2206206994597c13d831ec7](https://etherscan.io/address/0xdac17f958d2ee523a2206206994597c13d831ec7) diff --git a/contracts/pools/usdk/StableSwapUSDK.vy b/contracts/pools/usdk/StableSwapUSDK.vy deleted file mode 100644 index d7c236e5..00000000 --- a/contracts/pools/usdk/StableSwapUSDK.vy +++ /dev/null @@ -1,1083 +0,0 @@ -# @version 0.2.5 -""" -@title Curve USDK Metapool -@author Curve.Fi -@license Copyright (c) Curve.Fi, 2020 - all rights reserved -@dev Utilizes 3Pool to allow swaps between USDK / DAI / USDC / USDT -""" - -from vyper.interfaces import ERC20 - -interface CurveToken: - def totalSupply() -> uint256: view - def mint(_to: address, _value: uint256) -> bool: nonpayable - def burnFrom(_to: address, _value: uint256) -> bool: nonpayable - - -interface Curve: - def coins(i: uint256) -> address: view - def get_virtual_price() -> uint256: view - def calc_token_amount(amounts: uint256[BASE_N_COINS], deposit: bool) -> uint256: view - def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: view - def fee() -> uint256: view - def get_dy(i: int128, j: int128, dx: uint256) -> uint256: view - def get_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256: view - def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256): nonpayable - def add_liquidity(amounts: uint256[BASE_N_COINS], min_mint_amount: uint256): nonpayable - def remove_liquidity_one_coin(_token_amount: uint256, i: int128, min_amount: uint256): nonpayable - - -# Events -event TokenExchange: - buyer: indexed(address) - sold_id: int128 - tokens_sold: uint256 - bought_id: int128 - tokens_bought: uint256 - -event TokenExchangeUnderlying: - buyer: indexed(address) - sold_id: int128 - tokens_sold: uint256 - bought_id: int128 - tokens_bought: uint256 - -event AddLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event RemoveLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - token_supply: uint256 - -event RemoveLiquidityOne: - provider: indexed(address) - token_amount: uint256 - coin_amount: uint256 - token_supply: uint256 - -event RemoveLiquidityImbalance: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event CommitNewAdmin: - deadline: indexed(uint256) - admin: indexed(address) - -event NewAdmin: - admin: indexed(address) - -event CommitNewFee: - deadline: indexed(uint256) - fee: uint256 - admin_fee: uint256 - -event NewFee: - fee: uint256 - admin_fee: uint256 - -event RampA: - old_A: uint256 - new_A: uint256 - initial_time: uint256 - future_time: uint256 - -event StopRampA: - A: uint256 - t: uint256 - - -N_COINS: constant(int128) = 2 -MAX_COIN: constant(int128) = N_COINS - 1 - -FEE_DENOMINATOR: constant(uint256) = 10 ** 10 -PRECISION: constant(uint256) = 10 ** 18 # The precision to convert to -PRECISION_MUL: constant(uint256[N_COINS]) = [1, 1] -RATES: constant(uint256[N_COINS]) = [1000000000000000000, 1000000000000000000] - -BASE_N_COINS: constant(int128) = 3 -N_ALL_COINS: constant(int128) = N_COINS + BASE_N_COINS - 1 -BASE_PRECISION_MUL: constant(uint256[BASE_N_COINS]) = [1, 1000000000000, 1000000000000] -BASE_RATES: constant(uint256[BASE_N_COINS]) = [1000000000000000000, 1000000000000000000000000000000, 1000000000000000000000000000000] - -# An asset which may have a transfer fee (USDT) -FEE_ASSET: constant(address) = 0xdAC17F958D2ee523a2206206994597C13D831ec7 - -MAX_ADMIN_FEE: constant(uint256) = 10 * 10 ** 9 -MAX_FEE: constant(uint256) = 5 * 10 ** 9 -MAX_A: constant(uint256) = 10 ** 6 -MAX_A_CHANGE: constant(uint256) = 10 - -ADMIN_ACTIONS_DELAY: constant(uint256) = 3 * 86400 -MIN_RAMP_TIME: constant(uint256) = 86400 - -coins: public(address[N_COINS]) -balances: public(uint256[N_COINS]) -fee: public(uint256) # fee * 1e10 -admin_fee: public(uint256) # admin_fee * 1e10 - -owner: public(address) -token: CurveToken - -# Token corresponding to the pool is always the last one -BASE_POOL_COINS: constant(int128) = 3 -BASE_CACHE_EXPIRES: constant(int128) = 10 * 60 # 10 min -base_pool: public(address) -base_virtual_price: public(uint256) -base_cache_updated: public(uint256) -base_coins: public(address[BASE_POOL_COINS]) - -A_PRECISION: constant(uint256) = 100 -initial_A: public(uint256) -future_A: public(uint256) -initial_A_time: public(uint256) -future_A_time: public(uint256) - -admin_actions_deadline: public(uint256) -transfer_ownership_deadline: public(uint256) -future_fee: public(uint256) -future_admin_fee: public(uint256) -future_owner: public(address) - -is_killed: bool -kill_deadline: uint256 -KILL_DEADLINE_DT: constant(uint256) = 2 * 30 * 86400 - - -@external -def __init__( - _owner: address, - _coins: address[N_COINS], - _pool_token: address, - _base_pool: address, - _A: uint256, - _fee: uint256, - _admin_fee: uint256 -): - """ - @notice Contract constructor - @param _owner Contract owner address - @param _coins Addresses of ERC20 conracts of coins - @param _pool_token Address of the token representing LP share - @param _base_pool Address of the base pool (which will have a virtual price) - @param _A Amplification coefficient multiplied by n * (n - 1) - @param _fee Fee to charge for exchanges - @param _admin_fee Admin fee - """ - for i in range(N_COINS): - assert _coins[i] != ZERO_ADDRESS - self.coins = _coins - self.initial_A = _A * A_PRECISION - self.future_A = _A * A_PRECISION - self.fee = _fee - self.admin_fee = _admin_fee - self.owner = _owner - self.kill_deadline = block.timestamp + KILL_DEADLINE_DT - self.token = CurveToken(_pool_token) - - self.base_pool = _base_pool - self.base_virtual_price = Curve(_base_pool).get_virtual_price() - self.base_cache_updated = block.timestamp - for i in range(BASE_POOL_COINS): - _base_coin: address = Curve(_base_pool).coins(convert(i, uint256)) - self.base_coins[i] = _base_coin - - # approve underlying coins for infinite transfers - _response: Bytes[32] = raw_call( - _base_coin, - concat( - method_id("approve(address,uint256)"), - convert(_base_pool, bytes32), - convert(MAX_UINT256, bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) - - -@view -@internal -def _A() -> uint256: - """ - Handle ramping A up or down - """ - t1: uint256 = self.future_A_time - A1: uint256 = self.future_A - - if block.timestamp < t1: - A0: uint256 = self.initial_A - t0: uint256 = self.initial_A_time - # Expressions in uint256 cannot have negative numbers, thus "if" - if A1 > A0: - return A0 + (A1 - A0) * (block.timestamp - t0) / (t1 - t0) - else: - return A0 - (A0 - A1) * (block.timestamp - t0) / (t1 - t0) - - else: # when t1 == 0 or block.timestamp >= t1 - return A1 - - -@view -@external -def A() -> uint256: - return self._A() / A_PRECISION - - -@view -@external -def A_precise() -> uint256: - return self._A() - - -@view -@internal -def _xp(vp_rate: uint256) -> uint256[N_COINS]: - result: uint256[N_COINS] = RATES - result[MAX_COIN] = vp_rate # virtual price for the metacurrency - for i in range(N_COINS): - result[i] = result[i] * self.balances[i] / PRECISION - return result - - -@pure -@internal -def _xp_mem(vp_rate: uint256, _balances: uint256[N_COINS]) -> uint256[N_COINS]: - result: uint256[N_COINS] = RATES - result[MAX_COIN] = vp_rate # virtual price for the metacurrency - for i in range(N_COINS): - result[i] = result[i] * _balances[i] / PRECISION - return result - - -@internal -def _vp_rate() -> uint256: - if block.timestamp > self.base_cache_updated + BASE_CACHE_EXPIRES: - vprice: uint256 = Curve(self.base_pool).get_virtual_price() - self.base_virtual_price = vprice - self.base_cache_updated = block.timestamp - return vprice - else: - return self.base_virtual_price - - -@internal -@view -def _vp_rate_ro() -> uint256: - if block.timestamp > self.base_cache_updated + BASE_CACHE_EXPIRES: - return Curve(self.base_pool).get_virtual_price() - else: - return self.base_virtual_price - - -@pure -@internal -def get_D(xp: uint256[N_COINS], amp: uint256) -> uint256: - S: uint256 = 0 - Dprev: uint256 = 0 - for _x in xp: - S += _x - if S == 0: - return 0 - - D: uint256 = S - Ann: uint256 = amp * N_COINS - for _i in range(255): - D_P: uint256 = D - for _x in xp: - D_P = D_P * D / (_x * N_COINS) # If division by 0, this will be borked: only withdrawal will work. And that is good - Dprev = D - D = (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (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 - - -@view -@internal -def get_D_mem(vp_rate: uint256, _balances: uint256[N_COINS], amp: uint256) -> uint256: - xp: uint256[N_COINS] = self._xp_mem(vp_rate, _balances) - return self.get_D(xp, amp) - - -@view -@external -def get_virtual_price() -> uint256: - """ - @notice The current virtual price of the pool LP token - @dev Useful for calculating profits - @return LP token virtual price normalized to 1e18 - """ - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate_ro() - xp: uint256[N_COINS] = self._xp(vp_rate) - D: uint256 = self.get_D(xp, amp) - # 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 - - -@view -@external -def calc_token_amount(amounts: uint256[N_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 amounts Amount of each coin being deposited - @param is_deposit set True for deposits, False for withdrawals - @return Expected amount of LP tokens received - """ - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate_ro() - _balances: uint256[N_COINS] = self.balances - D0: uint256 = self.get_D_mem(vp_rate, _balances, amp) - for i in range(N_COINS): - if is_deposit: - _balances[i] += amounts[i] - else: - _balances[i] -= amounts[i] - D1: uint256 = self.get_D_mem(vp_rate, _balances, amp) - token_amount: uint256 = self.token.totalSupply() - diff: uint256 = 0 - if is_deposit: - diff = D1 - D0 - else: - diff = D0 - D1 - return diff * token_amount / D0 - - -@external -@nonreentrant('lock') -def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256) -> uint256: - """ - @notice Deposit coins into the pool - @param amounts List of amounts of coins to deposit - @param min_mint_amount Minimum amount of LP tokens to mint from the deposit - @return Amount of LP tokens received by depositing - """ - assert not self.is_killed # dev: is killed - - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate() - token_supply: uint256 = self.token.totalSupply() - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - _admin_fee: uint256 = self.admin_fee - - # Initial invariant - D0: uint256 = 0 - old_balances: uint256[N_COINS] = self.balances - if token_supply > 0: - D0 = self.get_D_mem(vp_rate, old_balances, amp) - new_balances: uint256[N_COINS] = old_balances - - for i in range(N_COINS): - if token_supply == 0: - assert amounts[i] > 0 # dev: initial deposit requires all coins - # balances store amounts of c-tokens - new_balances[i] = old_balances[i] + amounts[i] - - # Invariant after change - D1: uint256 = self.get_D_mem(vp_rate, new_balances, amp) - assert D1 > D0 - - # We need to recalculate the invariant accounting for fees - # to calculate fair user's share - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - 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(vp_rate, new_balances, amp) - 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 amounts[i] > 0: - assert ERC20(self.coins[i]).transferFrom(msg.sender, self, amounts[i]) # dev: failed transfer - - # Mint pool tokens - self.token.mint(msg.sender, mint_amount) - - log AddLiquidity(msg.sender, amounts, fees, D1, token_supply + mint_amount) - - return mint_amount - - -@view -@internal -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 # dev: same coin - assert j >= 0 # dev: j below zero - assert j < N_COINS # dev: j above N_COINS - - # should be unreachable, but good for safety - assert i >= 0 - assert i < N_COINS - - amp: uint256 = self._A() - D: uint256 = self.get_D(xp_, amp) - - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: uint256 = 0 - c: uint256 = D - Ann: uint256 = amp * N_COINS - - 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 * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann # - D - 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 - - -@view -@external -def get_dy(i: int128, j: int128, dx: uint256) -> uint256: - # dx and dy in c-units - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = self._vp_rate_ro() - xp: uint256[N_COINS] = self._xp(rates[MAX_COIN]) - - x: uint256 = xp[i] + (dx * rates[i] / PRECISION) - y: uint256 = self.get_y(i, j, x, xp) - dy: uint256 = xp[j] - y - 1 - _fee: uint256 = self.fee * dy / FEE_DENOMINATOR - return (dy - _fee) * PRECISION / rates[j] - - -@view -@external -def get_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256: - # dx and dy in underlying units - vp_rate: uint256 = self._vp_rate_ro() - xp: uint256[N_COINS] = self._xp(vp_rate) - precisions: uint256[N_COINS] = PRECISION_MUL - _base_pool: address = self.base_pool - - # Use base_i or base_j if they are >= 0 - base_i: int128 = i - MAX_COIN - base_j: int128 = j - MAX_COIN - meta_i: int128 = MAX_COIN - meta_j: int128 = MAX_COIN - if base_i < 0: - meta_i = i - if base_j < 0: - meta_j = j - - x: uint256 = 0 - if base_i < 0: - x = xp[i] + dx * precisions[i] - else: - if base_j < 0: - # i is from BasePool - # At first, get the amount of pool tokens - base_inputs: uint256[BASE_N_COINS] = empty(uint256[BASE_N_COINS]) - base_inputs[base_i] = dx - # Token amount transformed to underlying "dollars" - x = Curve(_base_pool).calc_token_amount(base_inputs, True) * vp_rate / PRECISION - # Accounting for deposit/withdraw fees approximately - x -= x * Curve(_base_pool).fee() / (2 * FEE_DENOMINATOR) - # Adding number of pool tokens - x += xp[MAX_COIN] - else: - # If both are from the base pool - return Curve(_base_pool).get_dy(base_i, base_j, dx) - - # This pool is involved only when in-pool assets are used - y: uint256 = self.get_y(meta_i, meta_j, x, xp) - dy: uint256 = xp[meta_j] - y - 1 - dy = (dy - self.fee * dy / FEE_DENOMINATOR) - - # If output is going via the metapool - if base_j < 0: - dy /= precisions[meta_j] - else: - # j is from BasePool - # The fee is already accounted for - dy = Curve(_base_pool).calc_withdraw_one_coin(dy * PRECISION / vp_rate, base_j) - - return dy - - -@external -@nonreentrant('lock') -def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256) -> uint256: - """ - @notice Perform an exchange between two coins - @dev Index values can be found via the `coins` public getter method - @param i Index value for the coin to send - @param j Index valie of the coin to recieve - @param dx Amount of `i` being exchanged - @param min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - assert not self.is_killed # dev: is killed - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = self._vp_rate() - - old_balances: uint256[N_COINS] = self.balances - xp: uint256[N_COINS] = self._xp_mem(rates[MAX_COIN], old_balances) - - x: uint256 = xp[i] + dx * rates[i] / PRECISION - y: uint256 = self.get_y(i, j, x, xp) - - dy: uint256 = xp[j] - y - 1 # -1 just in case there were some rounding errors - dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR - - # Convert all to real units - dy = (dy - dy_fee) * PRECISION / rates[j] - assert dy >= min_dy, "Too few coins in result" - - dy_admin_fee: uint256 = dy_fee * self.admin_fee / FEE_DENOMINATOR - dy_admin_fee = dy_admin_fee * PRECISION / rates[j] - - # Change balances exactly in same way as we change actual ERC20 coin amounts - self.balances[i] = old_balances[i] + dx - # When rounding errors happen, we undercharge admin fee in favor of LP - self.balances[j] = old_balances[j] - dy - dy_admin_fee - - assert ERC20(self.coins[i]).transferFrom(msg.sender, self, dx) - assert ERC20(self.coins[j]).transfer(msg.sender, dy) - - log TokenExchange(msg.sender, i, dx, j, dy) - - return dy - - -@external -@nonreentrant('lock') -def exchange_underlying(i: int128, j: int128, dx: uint256, min_dy: uint256) -> uint256: - """ - @notice Perform an exchange between two underlying coins - @dev Index values can be found via the `underlying_coins` public getter method - @param i Index value for the underlying coin to send - @param j Index valie of the underlying coin to recieve - @param dx Amount of `i` being exchanged - @param min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - assert not self.is_killed # dev: is killed - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = self._vp_rate() - _base_pool: address = self.base_pool - - # Use base_i or base_j if they are >= 0 - base_i: int128 = i - MAX_COIN - base_j: int128 = j - MAX_COIN - meta_i: int128 = MAX_COIN - meta_j: int128 = MAX_COIN - if base_i < 0: - meta_i = i - if base_j < 0: - meta_j = j - dy: uint256 = 0 - - # Addresses for input and output coins - input_coin: address = ZERO_ADDRESS - if base_i < 0: - input_coin = self.coins[i] - else: - input_coin = self.base_coins[base_i] - output_coin: address = ZERO_ADDRESS - if base_j < 0: - output_coin = self.coins[j] - else: - output_coin = self.base_coins[base_j] - - # Handle potential Tether fees - dx_w_fee: uint256 = dx - if input_coin == FEE_ASSET: - dx_w_fee = ERC20(FEE_ASSET).balanceOf(self) - # "safeTransferFrom" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - input_coin, - concat( - method_id("transferFrom(address,address,uint256)"), - convert(msg.sender, bytes32), - convert(self, bytes32), - convert(dx, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransferFrom" - # Handle potential Tether fees - if input_coin == FEE_ASSET: - dx_w_fee = ERC20(FEE_ASSET).balanceOf(self) - dx_w_fee - - if base_i < 0 or base_j < 0: - old_balances: uint256[N_COINS] = self.balances - xp: uint256[N_COINS] = self._xp_mem(rates[MAX_COIN], old_balances) - - x: uint256 = 0 - if base_i < 0: - x = xp[i] + dx_w_fee * rates[i] / PRECISION - else: - # i is from BasePool - # At first, get the amount of pool tokens - base_inputs: uint256[BASE_N_COINS] = empty(uint256[BASE_N_COINS]) - base_inputs[base_i] = dx_w_fee - coin_i: address = self.coins[MAX_COIN] - # Deposit and measure delta - x = ERC20(coin_i).balanceOf(self) - Curve(_base_pool).add_liquidity(base_inputs, 0) - # Need to convert pool token to "virtual" units using rates - # dx is also different now - dx_w_fee = ERC20(coin_i).balanceOf(self) - x - x = dx_w_fee * rates[MAX_COIN] / PRECISION - # Adding number of pool tokens - x += xp[MAX_COIN] - - y: uint256 = self.get_y(meta_i, meta_j, x, xp) - - # Either a real coin or token - dy = xp[meta_j] - y - 1 # -1 just in case there were some rounding errors - dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR - - # Convert all to real units - # Works for both pool coins and real coins - dy = (dy - dy_fee) * PRECISION / rates[meta_j] - - dy_admin_fee: uint256 = dy_fee * self.admin_fee / FEE_DENOMINATOR - dy_admin_fee = dy_admin_fee * PRECISION / rates[meta_j] - - # Change balances exactly in same way as we change actual ERC20 coin amounts - self.balances[meta_i] = old_balances[meta_i] + dx_w_fee - # When rounding errors happen, we undercharge admin fee in favor of LP - self.balances[meta_j] = old_balances[meta_j] - dy - dy_admin_fee - - # Withdraw from the base pool if needed - if base_j >= 0: - out_amount: uint256 = ERC20(output_coin).balanceOf(self) - Curve(_base_pool).remove_liquidity_one_coin(dy, base_j, 0) - dy = ERC20(output_coin).balanceOf(self) - out_amount - - assert dy >= min_dy, "Too few coins in result" - - else: - # If both are from the base pool - dy = ERC20(output_coin).balanceOf(self) - Curve(_base_pool).exchange(base_i, base_j, dx_w_fee, min_dy) - dy = ERC20(output_coin).balanceOf(self) - dy - - # "safeTransfer" which works for ERC20s which return bool or not - _response = raw_call( - output_coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(dy, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - log TokenExchangeUnderlying(msg.sender, i, dx, j, dy) - - return dy - - -@external -@nonreentrant('lock') -def remove_liquidity(_amount: uint256, min_amounts: uint256[N_COINS]) -> uint256[N_COINS]: - """ - @notice Withdraw coins from the pool - @dev Withdrawal amounts are based on current deposit ratios - @param _amount Quantity of LP tokens to burn in the withdrawal - @param min_amounts Minimum amounts of underlying coins to receive - @return List of amounts of coins that were withdrawn - """ - total_supply: uint256 = self.token.totalSupply() - amounts: uint256[N_COINS] = empty(uint256[N_COINS]) - fees: uint256[N_COINS] = empty(uint256[N_COINS]) # Fees are unused but we've got them historically in event - - for i in range(N_COINS): - value: uint256 = self.balances[i] * _amount / total_supply - assert value >= min_amounts[i], "Too few coins in result" - self.balances[i] -= value - amounts[i] = value - assert ERC20(self.coins[i]).transfer(msg.sender, value) - - self.token.burnFrom(msg.sender, _amount) # dev: insufficient funds - - log RemoveLiquidity(msg.sender, amounts, fees, total_supply - _amount) - - return amounts - - -@external -@nonreentrant('lock') -def remove_liquidity_imbalance(amounts: uint256[N_COINS], max_burn_amount: uint256) -> uint256: - """ - @notice Withdraw coins from the pool in an imbalanced amount - @param amounts List of amounts of underlying coins to withdraw - @param max_burn_amount Maximum amount of LP token to burn in the withdrawal - @return Actual amount of the LP token burned in the withdrawal - """ - assert not self.is_killed # dev: is killed - - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate() - - token_supply: uint256 = self.token.totalSupply() - assert token_supply != 0 # dev: zero total supply - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - _admin_fee: uint256 = self.admin_fee - - old_balances: uint256[N_COINS] = self.balances - new_balances: uint256[N_COINS] = old_balances - D0: uint256 = self.get_D_mem(vp_rate, old_balances, amp) - for i in range(N_COINS): - new_balances[i] -= amounts[i] - D1: uint256 = self.get_D_mem(vp_rate, new_balances, amp) - - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - 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(vp_rate, new_balances, amp) - - token_amount: uint256 = (D0 - D2) * token_supply / D0 - assert token_amount != 0 # dev: zero tokens burned - token_amount += 1 # In case of rounding errors - make it unfavorable for the "attacker" - assert token_amount <= max_burn_amount, "Slippage screwed you" - - self.token.burnFrom(msg.sender, token_amount) # dev: insufficient funds - for i in range(N_COINS): - if amounts[i] != 0: - assert ERC20(self.coins[i]).transfer(msg.sender, amounts[i]) - - log RemoveLiquidityImbalance(msg.sender, amounts, fees, D1, token_supply - token_amount) - - return token_amount - - -@view -@internal -def get_y_D(A_: uint256, i: int128, xp: uint256[N_COINS], D: uint256) -> uint256: - """ - Calculate x[i] if one reduces D from being calculated for xp to D - - Done by solving quadratic equation iteratively. - x_1**2 + x1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) - x_1**2 + b*x_1 = c - - x_1 = (x_1**2 + c) / (2*x_1 + b) - """ - # x in the input is converted to the same price/precision - - assert i >= 0 # dev: i below zero - assert i < N_COINS # dev: i above N_COINS - - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: uint256 = 0 - - c: uint256 = D - Ann: uint256 = A_ * N_COINS - - for _i in range(N_COINS): - if _i != i: - _x = xp[_i] - else: - continue - S_ += _x - c = c * D / (_x * N_COINS) - c = c * D * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann - 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 - - -@view -@internal -def _calc_withdraw_one_coin(_token_amount: uint256, i: int128, vp_rate: uint256) -> (uint256, uint256, uint256): - # First, need to calculate - # * Get current D - # * Solve Eqn against y_i for D - _token_amount - amp: uint256 = self._A() - xp: uint256[N_COINS] = self._xp(vp_rate) - D0: uint256 = self.get_D(xp, amp) - - total_supply: uint256 = self.token.totalSupply() - D1: uint256 = D0 - _token_amount * D0 / total_supply - new_y: uint256 = self.get_y_D(amp, i, xp, D1) - - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = vp_rate - - xp_reduced: uint256[N_COINS] = xp - dy_0: uint256 = (xp[i] - new_y) * PRECISION / rates[i] # w/o fees - - for j in range(N_COINS): - dx_expected: uint256 = 0 - if j == i: - dx_expected = xp[j] * D1 / D0 - new_y - else: - dx_expected = xp[j] - xp[j] * D1 / D0 - xp_reduced[j] -= _fee * dx_expected / FEE_DENOMINATOR - - dy: uint256 = xp_reduced[i] - self.get_y_D(amp, i, xp_reduced, D1) - dy = (dy - 1) * PRECISION / rates[i] # Withdraw less to account for rounding errors - - return dy, dy_0 - dy, total_supply - - -@view -@external -def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - """ - @notice Calculate the amount received when withdrawing a single coin - @param _token_amount Amount of LP tokens to burn in the withdrawal - @param i Index value of the coin to withdraw - @return Amount of coin received - """ - vp_rate: uint256 = self._vp_rate_ro() - return self._calc_withdraw_one_coin(_token_amount, i, vp_rate)[0] - - -@external -@nonreentrant('lock') -def remove_liquidity_one_coin(_token_amount: uint256, i: int128, _min_amount: uint256) -> uint256: - """ - @notice Withdraw a single coin from the pool - @param _token_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 coin to receive - @return Amount of coin received - """ - assert not self.is_killed # dev: is killed - - vp_rate: uint256 = self._vp_rate() - dy: uint256 = 0 - dy_fee: uint256 = 0 - total_supply: uint256 = 0 - dy, dy_fee, total_supply = self._calc_withdraw_one_coin(_token_amount, i, vp_rate) - assert dy >= _min_amount, "Not enough coins removed" - - self.balances[i] -= (dy + dy_fee * self.admin_fee / FEE_DENOMINATOR) - self.token.burnFrom(msg.sender, _token_amount) # dev: insufficient funds - assert ERC20(self.coins[i]).transfer(msg.sender, dy) - - log RemoveLiquidityOne(msg.sender, _token_amount, dy, total_supply - _token_amount) - - return dy - - -### Admin functions ### -@external -def ramp_A(_future_A: uint256, _future_time: uint256): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.initial_A_time + MIN_RAMP_TIME - assert _future_time >= block.timestamp + MIN_RAMP_TIME # dev: insufficient time - - _initial_A: uint256 = self._A() - _future_A_p: uint256 = _future_A * A_PRECISION - - assert _future_A > 0 and _future_A < MAX_A - if _future_A_p < _initial_A: - assert _future_A_p * MAX_A_CHANGE >= _initial_A - else: - assert _future_A_p <= _initial_A * MAX_A_CHANGE - - self.initial_A = _initial_A - self.future_A = _future_A_p - self.initial_A_time = block.timestamp - self.future_A_time = _future_time - - log RampA(_initial_A, _future_A_p, block.timestamp, _future_time) - - -@external -def stop_ramp_A(): - assert msg.sender == self.owner # dev: only owner - - current_A: uint256 = self._A() - self.initial_A = current_A - self.future_A = current_A - self.initial_A_time = block.timestamp - self.future_A_time = block.timestamp - # now (block.timestamp < t1) is always False, so we return saved A - - log StopRampA(current_A, block.timestamp) - - -@external -def commit_new_fee(new_fee: uint256, new_admin_fee: uint256): - assert msg.sender == self.owner # dev: only owner - assert self.admin_actions_deadline == 0 # dev: active action - assert new_fee <= MAX_FEE # dev: fee exceeds maximum - assert new_admin_fee <= MAX_ADMIN_FEE # dev: admin fee exceeds maximum - - _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.admin_actions_deadline = _deadline - self.future_fee = new_fee - self.future_admin_fee = new_admin_fee - - log CommitNewFee(_deadline, new_fee, new_admin_fee) - - -@external -def apply_new_fee(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.admin_actions_deadline # dev: insufficient time - assert self.admin_actions_deadline != 0 # dev: no active action - - self.admin_actions_deadline = 0 - _fee: uint256 = self.future_fee - _admin_fee: uint256 = self.future_admin_fee - self.fee = _fee - self.admin_fee = _admin_fee - - log NewFee(_fee, _admin_fee) - - -@external -def revert_new_parameters(): - assert msg.sender == self.owner # dev: only owner - - self.admin_actions_deadline = 0 - - -@external -def commit_transfer_ownership(_owner: address): - assert msg.sender == self.owner # dev: only owner - assert self.transfer_ownership_deadline == 0 # dev: active transfer - - _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.transfer_ownership_deadline = _deadline - self.future_owner = _owner - - log CommitNewAdmin(_deadline, _owner) - - -@external -def apply_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.transfer_ownership_deadline # dev: insufficient time - assert self.transfer_ownership_deadline != 0 # dev: no active transfer - - self.transfer_ownership_deadline = 0 - _owner: address = self.future_owner - self.owner = _owner - - log NewAdmin(_owner) - - -@external -def revert_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - - self.transfer_ownership_deadline = 0 - - -@view -@external -def admin_balances(i: uint256) -> uint256: - return ERC20(self.coins[i]).balanceOf(self) - self.balances[i] - - -@external -def withdraw_admin_fees(): - assert msg.sender == self.owner # dev: only owner - - for i in range(N_COINS): - c: address = self.coins[i] - value: uint256 = ERC20(c).balanceOf(self) - self.balances[i] - if value > 0: - assert ERC20(c).transfer(msg.sender, value) - - -@external -def donate_admin_fees(): - assert msg.sender == self.owner # dev: only owner - for i in range(N_COINS): - self.balances[i] = ERC20(self.coins[i]).balanceOf(self) - - -@external -def kill_me(): - assert msg.sender == self.owner # dev: only owner - assert self.kill_deadline > block.timestamp # dev: deadline has passed - self.is_killed = True - - -@external -def unkill_me(): - assert msg.sender == self.owner # dev: only owner - self.is_killed = False diff --git a/contracts/pools/usdk/pooldata.json b/contracts/pools/usdk/pooldata.json deleted file mode 100644 index 2f25c9df..00000000 --- a/contracts/pools/usdk/pooldata.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "base_pool": "3pool", - "pool_types": ["meta"], - "lp_contract": "CurveTokenV2", - "swap_address": "0x3E01dD8a5E1fb3481F0F589056b428Fc308AF0Fb", - "lp_token_address": "0x97E2768e8E73511cA874545DC5Ff8067eB19B787", - "zap_address": "0xF1f85a74AD6c64315F85af52d3d46bF715236ADc", - "gauge_addresses": ["0xC2b1DF84112619D190193E48148000e3990Bf627"], - "lp_constructor": { - "symbol": "usdk3CRV", - "name": "Curve.fi USDK/3Crv" - }, - "swap_constructor": { - "_A": 200, - "_fee": 4000000, - "_admin_fee": 0 - }, - "coins": [ - { - "name": "USDK", - "decimals": 18, - "tethered": false, - "underlying_address": "0x1c48f86ae57291f7686349f12601910bd8d470bb" - }, - { - "name": "3CRV", - "decimals": 18, - "base_pool_token": true, - "underlying_address": "0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490" - } - ] -} diff --git a/contracts/pools/usdn/DepositUSDN.vy b/contracts/pools/usdn/DepositUSDN.vy deleted file mode 100644 index 2b51d5d7..00000000 --- a/contracts/pools/usdn/DepositUSDN.vy +++ /dev/null @@ -1,376 +0,0 @@ -# @version 0.2.7 -""" -@title "Zap" Depositer for Curve USDN pool -@author Curve.Fi -@license Copyright (c) Curve.Fi, 2020 - all rights reserved -""" - -from vyper.interfaces import ERC20 - - -interface CurveMeta: - def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256) -> 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) -> 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 base_pool() -> address: 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 - -# An asset which may have a transfer fee (USDT) -FEE_ASSET: constant(address) = 0xdAC17F958D2ee523a2206206994597C13D831ec7 - -FEE_DENOMINATOR: constant(uint256) = 10 ** 10 -FEE_IMPRECISION: constant(uint256) = 100 * 10 ** 8 # % of the fee - - -pool: public(address) -token: public(address) -base_pool: public(address) - -coins: public(address[N_COINS]) -base_coins: public(address[BASE_N_COINS]) - - -@external -def __init__(_pool: address, _token: address): - """ - @notice Contract constructor - @param _pool Metapool address - @param _token Pool LP token address - """ - self.pool = _pool - self.token = _token - _base_pool: address = CurveMeta(_pool).base_pool() - self.base_pool = _base_pool - - for i in range(N_COINS): - coin: address = CurveMeta(_pool).coins(convert(i, uint256)) - self.coins[i] = coin - # approve coins for infinite transfers - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("approve(address,uint256)"), - convert(_pool, bytes32), - convert(MAX_UINT256, bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) - - for i in range(BASE_N_COINS): - coin: address = CurveBase(_base_pool).coins(convert(i, uint256)) - self.base_coins[i] = coin - # approve underlying coins for infinite transfers - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("approve(address,uint256)"), - convert(_base_pool, bytes32), - convert(MAX_UINT256, bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) - - -@external -def add_liquidity(amounts: uint256[N_ALL_COINS], min_mint_amount: uint256) -> uint256: - """ - @notice Wrap underlying coins and deposit them in the pool - @param amounts List of amounts of underlying coins to deposit - @param min_mint_amount Minimum amount of LP tokens to mint from the deposit - @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 - - # Transfer all coins in - for i in range(N_ALL_COINS): - amount: uint256 = amounts[i] - if amount == 0: - continue - coin: address = ZERO_ADDRESS - if i < MAX_COIN: - coin = self.coins[i] - meta_amounts[i] = amount - else: - x: int128 = i - MAX_COIN - coin = self.base_coins[x] - base_amounts[x] = amount - deposit_base = True - # "safeTransferFrom" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transferFrom(address,address,uint256)"), - convert(msg.sender, bytes32), - convert(self, bytes32), - convert(amount, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransferFrom" - # Handle potential Tether fees - if coin == FEE_ASSET: - amount = ERC20(FEE_ASSET).balanceOf(self) - if i < MAX_COIN: - meta_amounts[i] = amount - else: - base_amounts[i - MAX_COIN] = amount - - # Deposit to the base pool - if deposit_base: - CurveBase(self.base_pool).add_liquidity(base_amounts, 0) - meta_amounts[MAX_COIN] = ERC20(self.coins[MAX_COIN]).balanceOf(self) - - # Deposit to the meta pool - CurveMeta(self.pool).add_liquidity(meta_amounts, min_mint_amount) - - # Transfer meta token back - _lp_token: address = self.token - _lp_amount: uint256 = ERC20(_lp_token).balanceOf(self) - assert ERC20(_lp_token).transfer(msg.sender, _lp_amount) - - return _lp_amount - - -@external -def remove_liquidity(_amount: uint256, min_amounts: uint256[N_ALL_COINS]) -> uint256[N_ALL_COINS]: - """ - @notice Withdraw and unwrap coins from the pool - @dev Withdrawal amounts are based on current deposit ratios - @param _amount Quantity of LP tokens to burn in the withdrawal - @param min_amounts Minimum amounts of underlying coins to receive - @return List of amounts of underlying coins that were withdrawn - """ - _token: address = self.token - assert ERC20(_token).transferFrom(msg.sender, self, _amount) - - min_amounts_meta: uint256[N_COINS] = empty(uint256[N_COINS]) - 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 - for i in range(MAX_COIN): - min_amounts_meta[i] = min_amounts[i] - CurveMeta(self.pool).remove_liquidity(_amount, min_amounts_meta) - - # Withdraw from base - _base_amount: uint256 = ERC20(self.coins[MAX_COIN]).balanceOf(self) - for i in range(BASE_N_COINS): - min_amounts_base[i] = min_amounts[MAX_COIN+i] - CurveBase(self.base_pool).remove_liquidity(_base_amount, min_amounts_base) - - # Transfer all coins out - for i in range(N_ALL_COINS): - coin: address = ZERO_ADDRESS - if i < MAX_COIN: - coin = self.coins[i] - else: - coin = self.base_coins[i - MAX_COIN] - amounts[i] = ERC20(coin).balanceOf(self) - # "safeTransfer" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(amounts[i], bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - return amounts - - -@external -def remove_liquidity_one_coin(_token_amount: uint256, i: int128, _min_amount: uint256) -> uint256: - """ - @notice Withdraw and unwrap a single coin from the pool - @param _token_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 - @return Amount of underlying coin received - """ - assert ERC20(self.token).transferFrom(msg.sender, self, _token_amount) - - coin: address = ZERO_ADDRESS - if i < MAX_COIN: - coin = self.coins[i] - # Withdraw a metapool coin - CurveMeta(self.pool).remove_liquidity_one_coin(_token_amount, i, _min_amount) - else: - coin = self.base_coins[i - MAX_COIN] - # Withdraw a base pool coin - CurveMeta(self.pool).remove_liquidity_one_coin(_token_amount, MAX_COIN, 0) - CurveBase(self.base_pool).remove_liquidity_one_coin( - ERC20(self.coins[MAX_COIN]).balanceOf(self), i-MAX_COIN, _min_amount - ) - - # Tranfer the coin out - coin_amount: uint256 = ERC20(coin).balanceOf(self) - # "safeTransfer" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(coin_amount, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - return coin_amount - - -@external -def remove_liquidity_imbalance(amounts: uint256[N_ALL_COINS], max_burn_amount: uint256) -> uint256: - """ - @notice Withdraw coins from the pool in an imbalanced amount - @param amounts List of amounts of underlying coins to withdraw - @param max_burn_amount Maximum amount of LP token to burn in the withdrawal. - This value cannot exceed the caller's LP token balance. - @return Actual amount of the LP token burned in the withdrawal - """ - _base_pool: address = self.base_pool - _meta_pool: address = self.pool - _base_coins: address[BASE_N_COINS] = self.base_coins - _meta_coins: address[N_COINS] = self.coins - _lp_token: address = self.token - - 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 - assert ERC20(_lp_token).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]) - leftover_amounts: uint256[N_COINS] = empty(uint256[N_COINS]) - - # Prepare quantities - for i in range(MAX_COIN): - amounts_meta[i] = amounts[i] - - for i in range(BASE_N_COINS): - amount: uint256 = amounts[MAX_COIN + i] - if amount != 0: - amounts_base[i] = amount - withdraw_base = True - - if withdraw_base: - amounts_meta[MAX_COIN] = CurveBase(self.base_pool).calc_token_amount(amounts_base, False) - amounts_meta[MAX_COIN] += amounts_meta[MAX_COIN] * fee / FEE_DENOMINATOR + 1 - - # Remove liquidity and deposit leftovers back - CurveMeta(_meta_pool).remove_liquidity_imbalance(amounts_meta, max_burn_amount) - if withdraw_base: - CurveBase(_base_pool).remove_liquidity_imbalance(amounts_base, amounts_meta[MAX_COIN]) - leftover_amounts[MAX_COIN] = ERC20(_meta_coins[MAX_COIN]).balanceOf(self) - if leftover_amounts[MAX_COIN] > 0: - CurveMeta(_meta_pool).add_liquidity(leftover_amounts, 0) - - # Transfer all coins out - for i in range(N_ALL_COINS): - coin: address = ZERO_ADDRESS - amount: uint256 = 0 - if i < MAX_COIN: - coin = _meta_coins[i] - amount = amounts_meta[i] - else: - coin = _base_coins[i - MAX_COIN] - amount = amounts_base[i - MAX_COIN] - # "safeTransfer" which works for ERC20s which return bool or not - if amount > 0: - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(amount, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - # Transfer the leftover LP token out - leftover: uint256 = ERC20(_lp_token).balanceOf(self) - if leftover > 0: - assert ERC20(_lp_token).transfer(msg.sender, leftover) - - return max_burn_amount - leftover - - -@view -@external -def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - """ - @notice Calculate the amount received when withdrawing and unwrapping a single coin - @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(self.pool).calc_withdraw_one_coin(_token_amount, i) - else: - _base_tokens: uint256 = CurveMeta(self.pool).calc_withdraw_one_coin(_token_amount, MAX_COIN) - return CurveBase(self.base_pool).calc_withdraw_one_coin(_base_tokens, i-MAX_COIN) - - -@view -@external -def calc_token_amount(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 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]) - - for i in range(MAX_COIN): - meta_amounts[i] = amounts[i] - - for i in range(BASE_N_COINS): - base_amounts[i] = amounts[i + MAX_COIN] - - _base_tokens: uint256 = CurveBase(self.base_pool).calc_token_amount(base_amounts, is_deposit) - meta_amounts[MAX_COIN] = _base_tokens - - return CurveMeta(self.pool).calc_token_amount(meta_amounts, is_deposit) diff --git a/contracts/pools/usdn/README.md b/contracts/pools/usdn/README.md deleted file mode 100644 index dcec480f..00000000 --- a/contracts/pools/usdn/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# curve-contract/contracts/pools/usdn - -[Curve USDN metapool](https://www.curve.fi/usdn), allowing swaps via the Curve [tri-pool](../3pool). - -## Contracts - -* [`DepositUSDN`](DepositUSDN.vy): Depositor contract, used to wrap underlying tokens prior to depositing them into the pool -* [`StableSwapUSDN`](StableSwapUSDN.vy): Curve stablecoin AMM contract - -## Deployments - -* [`CurveContractV2`](../../tokens/CurveTokenV2.vy): [0x4f3E8F405CF5aFC05D68142F3783bDfE13811522](https://etherscan.io/address/0x4f3E8F405CF5aFC05D68142F3783bDfE13811522) -* [`DepositUSDN`](DepositUSDN.vy): [0x094d12e5b541784701FD8d65F11fc0598FBC6332](https://etherscan.io/address/0x094d12e5b541784701FD8d65F11fc0598FBC6332) -* [`LiquidityGauge`](../../gauges/LiquidityGauge.vy): [0xF98450B5602fa59CC66e1379DFfB6FDDc724CfC4](https://etherscan.io/address/0xF98450B5602fa59CC66e1379DFfB6FDDc724CfC4) -* [`StableSwapUSDN`](StableSwapUSDN.vy): [0x0f9cb53Ebe405d49A0bbdBD291A65Ff571bC83e1](https://etherscan.io/address/0x0f9cb53Ebe405d49A0bbdBD291A65Ff571bC83e1) - -## Stablecoins - -Curve USDN metapool utilizes the supports swaps between the following assets: - -## Direct swaps - -Direct swaps are possible between USDN and the Curve tri-pool LP token. - -* `USDN`: [0x674C6Ad92Fd080e4004b2312b45f796a192D27a0](https://etherscan.io/address/0x674C6Ad92Fd080e4004b2312b45f796a192D27a0) -* `3CRV`: [0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490](https://etherscan.io/address/0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490) - -## Base Pool coins - -The tri-pool LP token may be wrapped or unwrapped to provide swaps between USDN and the following stablecoins: - -* `DAI`: [0x6b175474e89094c44da98b954eedeac495271d0f](https://etherscan.io/address/0x6b175474e89094c44da98b954eedeac495271d0f) -* `USDC`: [0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48](https://etherscan.io/address/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48) -* `USDT`: [0xdac17f958d2ee523a2206206994597c13d831ec7](https://etherscan.io/address/0xdac17f958d2ee523a2206206994597c13d831ec7) diff --git a/contracts/pools/usdn/StableSwapUSDN.vy b/contracts/pools/usdn/StableSwapUSDN.vy deleted file mode 100644 index 80b84636..00000000 --- a/contracts/pools/usdn/StableSwapUSDN.vy +++ /dev/null @@ -1,1083 +0,0 @@ -# @version 0.2.5 -""" -@title Curve USDN Metapool -@author Curve.Fi -@license Copyright (c) Curve.Fi, 2020 - all rights reserved -@dev Utilizes 3Pool to allow swaps between USDN / DAI / USDC / USDT -""" - -from vyper.interfaces import ERC20 - -interface CurveToken: - def totalSupply() -> uint256: view - def mint(_to: address, _value: uint256) -> bool: nonpayable - def burnFrom(_to: address, _value: uint256) -> bool: nonpayable - - -interface Curve: - def coins(i: uint256) -> address: view - def get_virtual_price() -> uint256: view - def calc_token_amount(amounts: uint256[BASE_N_COINS], deposit: bool) -> uint256: view - def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: view - def fee() -> uint256: view - def get_dy(i: int128, j: int128, dx: uint256) -> uint256: view - def get_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256: view - def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256): nonpayable - def add_liquidity(amounts: uint256[BASE_N_COINS], min_mint_amount: uint256): nonpayable - def remove_liquidity_one_coin(_token_amount: uint256, i: int128, min_amount: uint256): nonpayable - - -# Events -event TokenExchange: - buyer: indexed(address) - sold_id: int128 - tokens_sold: uint256 - bought_id: int128 - tokens_bought: uint256 - -event TokenExchangeUnderlying: - buyer: indexed(address) - sold_id: int128 - tokens_sold: uint256 - bought_id: int128 - tokens_bought: uint256 - -event AddLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event RemoveLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - token_supply: uint256 - -event RemoveLiquidityOne: - provider: indexed(address) - token_amount: uint256 - coin_amount: uint256 - token_supply: uint256 - -event RemoveLiquidityImbalance: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event CommitNewAdmin: - deadline: indexed(uint256) - admin: indexed(address) - -event NewAdmin: - admin: indexed(address) - -event CommitNewFee: - deadline: indexed(uint256) - fee: uint256 - admin_fee: uint256 - -event NewFee: - fee: uint256 - admin_fee: uint256 - -event RampA: - old_A: uint256 - new_A: uint256 - initial_time: uint256 - future_time: uint256 - -event StopRampA: - A: uint256 - t: uint256 - - -N_COINS: constant(int128) = 2 -MAX_COIN: constant(int128) = N_COINS - 1 - -FEE_DENOMINATOR: constant(uint256) = 10 ** 10 -PRECISION: constant(uint256) = 10 ** 18 # The precision to convert to -PRECISION_MUL: constant(uint256[N_COINS]) = [1, 1] -RATES: constant(uint256[N_COINS]) = [1000000000000000000, 1000000000000000000] - -BASE_N_COINS: constant(int128) = 3 -N_ALL_COINS: constant(int128) = N_COINS + BASE_N_COINS - 1 -BASE_PRECISION_MUL: constant(uint256[BASE_N_COINS]) = [1, 1000000000000, 1000000000000] -BASE_RATES: constant(uint256[BASE_N_COINS]) = [1000000000000000000, 1000000000000000000000000000000, 1000000000000000000000000000000] - -# An asset which may have a transfer fee (USDT) -FEE_ASSET: constant(address) = 0xdAC17F958D2ee523a2206206994597C13D831ec7 - -MAX_ADMIN_FEE: constant(uint256) = 10 * 10 ** 9 -MAX_FEE: constant(uint256) = 5 * 10 ** 9 -MAX_A: constant(uint256) = 10 ** 6 -MAX_A_CHANGE: constant(uint256) = 10 - -ADMIN_ACTIONS_DELAY: constant(uint256) = 3 * 86400 -MIN_RAMP_TIME: constant(uint256) = 86400 - -coins: public(address[N_COINS]) -balances: public(uint256[N_COINS]) -fee: public(uint256) # fee * 1e10 -admin_fee: public(uint256) # admin_fee * 1e10 - -owner: public(address) -token: CurveToken - -# Token corresponding to the pool is always the last one -BASE_POOL_COINS: constant(int128) = 3 -BASE_CACHE_EXPIRES: constant(int128) = 10 * 60 # 10 min -base_pool: public(address) -base_virtual_price: public(uint256) -base_cache_updated: public(uint256) -base_coins: public(address[BASE_POOL_COINS]) - -A_PRECISION: constant(uint256) = 100 -initial_A: public(uint256) -future_A: public(uint256) -initial_A_time: public(uint256) -future_A_time: public(uint256) - -admin_actions_deadline: public(uint256) -transfer_ownership_deadline: public(uint256) -future_fee: public(uint256) -future_admin_fee: public(uint256) -future_owner: public(address) - -is_killed: bool -kill_deadline: uint256 -KILL_DEADLINE_DT: constant(uint256) = 2 * 30 * 86400 - - -@external -def __init__( - _owner: address, - _coins: address[N_COINS], - _pool_token: address, - _base_pool: address, - _A: uint256, - _fee: uint256, - _admin_fee: uint256 -): - """ - @notice Contract constructor - @param _owner Contract owner address - @param _coins Addresses of ERC20 conracts of coins - @param _pool_token Address of the token representing LP share - @param _base_pool Address of the base pool (which will have a virtual price) - @param _A Amplification coefficient multiplied by n * (n - 1) - @param _fee Fee to charge for exchanges - @param _admin_fee Admin fee - """ - for i in range(N_COINS): - assert _coins[i] != ZERO_ADDRESS - self.coins = _coins - self.initial_A = _A * A_PRECISION - self.future_A = _A * A_PRECISION - self.fee = _fee - self.admin_fee = _admin_fee - self.owner = _owner - self.kill_deadline = block.timestamp + KILL_DEADLINE_DT - self.token = CurveToken(_pool_token) - - self.base_pool = _base_pool - self.base_virtual_price = Curve(_base_pool).get_virtual_price() - self.base_cache_updated = block.timestamp - for i in range(BASE_POOL_COINS): - _base_coin: address = Curve(_base_pool).coins(convert(i, uint256)) - self.base_coins[i] = _base_coin - - # approve underlying coins for infinite transfers - _response: Bytes[32] = raw_call( - _base_coin, - concat( - method_id("approve(address,uint256)"), - convert(_base_pool, bytes32), - convert(MAX_UINT256, bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) - - -@view -@internal -def _A() -> uint256: - """ - Handle ramping A up or down - """ - t1: uint256 = self.future_A_time - A1: uint256 = self.future_A - - if block.timestamp < t1: - A0: uint256 = self.initial_A - t0: uint256 = self.initial_A_time - # Expressions in uint256 cannot have negative numbers, thus "if" - if A1 > A0: - return A0 + (A1 - A0) * (block.timestamp - t0) / (t1 - t0) - else: - return A0 - (A0 - A1) * (block.timestamp - t0) / (t1 - t0) - - else: # when t1 == 0 or block.timestamp >= t1 - return A1 - - -@view -@external -def A() -> uint256: - return self._A() / A_PRECISION - - -@view -@external -def A_precise() -> uint256: - return self._A() - - -@view -@internal -def _xp(vp_rate: uint256) -> uint256[N_COINS]: - result: uint256[N_COINS] = RATES - result[MAX_COIN] = vp_rate # virtual price for the metacurrency - for i in range(N_COINS): - result[i] = result[i] * self.balances[i] / PRECISION - return result - - -@pure -@internal -def _xp_mem(vp_rate: uint256, _balances: uint256[N_COINS]) -> uint256[N_COINS]: - result: uint256[N_COINS] = RATES - result[MAX_COIN] = vp_rate # virtual price for the metacurrency - for i in range(N_COINS): - result[i] = result[i] * _balances[i] / PRECISION - return result - - -@internal -def _vp_rate() -> uint256: - if block.timestamp > self.base_cache_updated + BASE_CACHE_EXPIRES: - vprice: uint256 = Curve(self.base_pool).get_virtual_price() - self.base_virtual_price = vprice - self.base_cache_updated = block.timestamp - return vprice - else: - return self.base_virtual_price - - -@internal -@view -def _vp_rate_ro() -> uint256: - if block.timestamp > self.base_cache_updated + BASE_CACHE_EXPIRES: - return Curve(self.base_pool).get_virtual_price() - else: - return self.base_virtual_price - - -@pure -@internal -def get_D(xp: uint256[N_COINS], amp: uint256) -> uint256: - S: uint256 = 0 - Dprev: uint256 = 0 - for _x in xp: - S += _x - if S == 0: - return 0 - - D: uint256 = S - Ann: uint256 = amp * N_COINS - for _i in range(255): - D_P: uint256 = D - for _x in xp: - D_P = D_P * D / (_x * N_COINS) # If division by 0, this will be borked: only withdrawal will work. And that is good - Dprev = D - D = (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (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 - - -@view -@internal -def get_D_mem(vp_rate: uint256, _balances: uint256[N_COINS], amp: uint256) -> uint256: - xp: uint256[N_COINS] = self._xp_mem(vp_rate, _balances) - return self.get_D(xp, amp) - - -@view -@external -def get_virtual_price() -> uint256: - """ - @notice The current virtual price of the pool LP token - @dev Useful for calculating profits - @return LP token virtual price normalized to 1e18 - """ - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate_ro() - xp: uint256[N_COINS] = self._xp(vp_rate) - D: uint256 = self.get_D(xp, amp) - # 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 - - -@view -@external -def calc_token_amount(amounts: uint256[N_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 amounts Amount of each coin being deposited - @param is_deposit set True for deposits, False for withdrawals - @return Expected amount of LP tokens received - """ - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate_ro() - _balances: uint256[N_COINS] = self.balances - D0: uint256 = self.get_D_mem(vp_rate, _balances, amp) - for i in range(N_COINS): - if is_deposit: - _balances[i] += amounts[i] - else: - _balances[i] -= amounts[i] - D1: uint256 = self.get_D_mem(vp_rate, _balances, amp) - token_amount: uint256 = self.token.totalSupply() - diff: uint256 = 0 - if is_deposit: - diff = D1 - D0 - else: - diff = D0 - D1 - return diff * token_amount / D0 - - -@external -@nonreentrant('lock') -def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256) -> uint256: - """ - @notice Deposit coins into the pool - @param amounts List of amounts of coins to deposit - @param min_mint_amount Minimum amount of LP tokens to mint from the deposit - @return Amount of LP tokens received by depositing - """ - assert not self.is_killed # dev: is killed - - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate() - token_supply: uint256 = self.token.totalSupply() - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - _admin_fee: uint256 = self.admin_fee - - # Initial invariant - D0: uint256 = 0 - old_balances: uint256[N_COINS] = self.balances - if token_supply > 0: - D0 = self.get_D_mem(vp_rate, old_balances, amp) - new_balances: uint256[N_COINS] = old_balances - - for i in range(N_COINS): - if token_supply == 0: - assert amounts[i] > 0 # dev: initial deposit requires all coins - # balances store amounts of c-tokens - new_balances[i] = old_balances[i] + amounts[i] - - # Invariant after change - D1: uint256 = self.get_D_mem(vp_rate, new_balances, amp) - assert D1 > D0 - - # We need to recalculate the invariant accounting for fees - # to calculate fair user's share - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - 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(vp_rate, new_balances, amp) - 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 amounts[i] > 0: - assert ERC20(self.coins[i]).transferFrom(msg.sender, self, amounts[i]) # dev: failed transfer - - # Mint pool tokens - self.token.mint(msg.sender, mint_amount) - - log AddLiquidity(msg.sender, amounts, fees, D1, token_supply + mint_amount) - - return mint_amount - - -@view -@internal -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 # dev: same coin - assert j >= 0 # dev: j below zero - assert j < N_COINS # dev: j above N_COINS - - # should be unreachable, but good for safety - assert i >= 0 - assert i < N_COINS - - amp: uint256 = self._A() - D: uint256 = self.get_D(xp_, amp) - - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: uint256 = 0 - c: uint256 = D - Ann: uint256 = amp * N_COINS - - 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 * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann # - D - 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 - - -@view -@external -def get_dy(i: int128, j: int128, dx: uint256) -> uint256: - # dx and dy in c-units - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = self._vp_rate_ro() - xp: uint256[N_COINS] = self._xp(rates[MAX_COIN]) - - x: uint256 = xp[i] + (dx * rates[i] / PRECISION) - y: uint256 = self.get_y(i, j, x, xp) - dy: uint256 = xp[j] - y - 1 - _fee: uint256 = self.fee * dy / FEE_DENOMINATOR - return (dy - _fee) * PRECISION / rates[j] - - -@view -@external -def get_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256: - # dx and dy in underlying units - vp_rate: uint256 = self._vp_rate_ro() - xp: uint256[N_COINS] = self._xp(vp_rate) - precisions: uint256[N_COINS] = PRECISION_MUL - _base_pool: address = self.base_pool - - # Use base_i or base_j if they are >= 0 - base_i: int128 = i - MAX_COIN - base_j: int128 = j - MAX_COIN - meta_i: int128 = MAX_COIN - meta_j: int128 = MAX_COIN - if base_i < 0: - meta_i = i - if base_j < 0: - meta_j = j - - x: uint256 = 0 - if base_i < 0: - x = xp[i] + dx * precisions[i] - else: - if base_j < 0: - # i is from BasePool - # At first, get the amount of pool tokens - base_inputs: uint256[BASE_N_COINS] = empty(uint256[BASE_N_COINS]) - base_inputs[base_i] = dx - # Token amount transformed to underlying "dollars" - x = Curve(_base_pool).calc_token_amount(base_inputs, True) * vp_rate / PRECISION - # Accounting for deposit/withdraw fees approximately - x -= x * Curve(_base_pool).fee() / (2 * FEE_DENOMINATOR) - # Adding number of pool tokens - x += xp[MAX_COIN] - else: - # If both are from the base pool - return Curve(_base_pool).get_dy(base_i, base_j, dx) - - # This pool is involved only when in-pool assets are used - y: uint256 = self.get_y(meta_i, meta_j, x, xp) - dy: uint256 = xp[meta_j] - y - 1 - dy = (dy - self.fee * dy / FEE_DENOMINATOR) - - # If output is going via the metapool - if base_j < 0: - dy /= precisions[meta_j] - else: - # j is from BasePool - # The fee is already accounted for - dy = Curve(_base_pool).calc_withdraw_one_coin(dy * PRECISION / vp_rate, base_j) - - return dy - - -@external -@nonreentrant('lock') -def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256) -> uint256: - """ - @notice Perform an exchange between two coins - @dev Index values can be found via the `coins` public getter method - @param i Index value for the coin to send - @param j Index valie of the coin to recieve - @param dx Amount of `i` being exchanged - @param min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - assert not self.is_killed # dev: is killed - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = self._vp_rate() - - old_balances: uint256[N_COINS] = self.balances - xp: uint256[N_COINS] = self._xp_mem(rates[MAX_COIN], old_balances) - - x: uint256 = xp[i] + dx * rates[i] / PRECISION - y: uint256 = self.get_y(i, j, x, xp) - - dy: uint256 = xp[j] - y - 1 # -1 just in case there were some rounding errors - dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR - - # Convert all to real units - dy = (dy - dy_fee) * PRECISION / rates[j] - assert dy >= min_dy, "Too few coins in result" - - dy_admin_fee: uint256 = dy_fee * self.admin_fee / FEE_DENOMINATOR - dy_admin_fee = dy_admin_fee * PRECISION / rates[j] - - # Change balances exactly in same way as we change actual ERC20 coin amounts - self.balances[i] = old_balances[i] + dx - # When rounding errors happen, we undercharge admin fee in favor of LP - self.balances[j] = old_balances[j] - dy - dy_admin_fee - - assert ERC20(self.coins[i]).transferFrom(msg.sender, self, dx) - assert ERC20(self.coins[j]).transfer(msg.sender, dy) - - log TokenExchange(msg.sender, i, dx, j, dy) - - return dy - - -@external -@nonreentrant('lock') -def exchange_underlying(i: int128, j: int128, dx: uint256, min_dy: uint256) -> uint256: - """ - @notice Perform an exchange between two underlying coins - @dev Index values can be found via the `underlying_coins` public getter method - @param i Index value for the underlying coin to send - @param j Index valie of the underlying coin to recieve - @param dx Amount of `i` being exchanged - @param min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - assert not self.is_killed # dev: is killed - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = self._vp_rate() - _base_pool: address = self.base_pool - - # Use base_i or base_j if they are >= 0 - base_i: int128 = i - MAX_COIN - base_j: int128 = j - MAX_COIN - meta_i: int128 = MAX_COIN - meta_j: int128 = MAX_COIN - if base_i < 0: - meta_i = i - if base_j < 0: - meta_j = j - dy: uint256 = 0 - - # Addresses for input and output coins - input_coin: address = ZERO_ADDRESS - if base_i < 0: - input_coin = self.coins[i] - else: - input_coin = self.base_coins[base_i] - output_coin: address = ZERO_ADDRESS - if base_j < 0: - output_coin = self.coins[j] - else: - output_coin = self.base_coins[base_j] - - # Handle potential Tether fees - dx_w_fee: uint256 = dx - if input_coin == FEE_ASSET: - dx_w_fee = ERC20(FEE_ASSET).balanceOf(self) - # "safeTransferFrom" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - input_coin, - concat( - method_id("transferFrom(address,address,uint256)"), - convert(msg.sender, bytes32), - convert(self, bytes32), - convert(dx, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransferFrom" - # Handle potential Tether fees - if input_coin == FEE_ASSET: - dx_w_fee = ERC20(FEE_ASSET).balanceOf(self) - dx_w_fee - - if base_i < 0 or base_j < 0: - old_balances: uint256[N_COINS] = self.balances - xp: uint256[N_COINS] = self._xp_mem(rates[MAX_COIN], old_balances) - - x: uint256 = 0 - if base_i < 0: - x = xp[i] + dx_w_fee * rates[i] / PRECISION - else: - # i is from BasePool - # At first, get the amount of pool tokens - base_inputs: uint256[BASE_N_COINS] = empty(uint256[BASE_N_COINS]) - base_inputs[base_i] = dx_w_fee - coin_i: address = self.coins[MAX_COIN] - # Deposit and measure delta - x = ERC20(coin_i).balanceOf(self) - Curve(_base_pool).add_liquidity(base_inputs, 0) - # Need to convert pool token to "virtual" units using rates - # dx is also different now - dx_w_fee = ERC20(coin_i).balanceOf(self) - x - x = dx_w_fee * rates[MAX_COIN] / PRECISION - # Adding number of pool tokens - x += xp[MAX_COIN] - - y: uint256 = self.get_y(meta_i, meta_j, x, xp) - - # Either a real coin or token - dy = xp[meta_j] - y - 1 # -1 just in case there were some rounding errors - dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR - - # Convert all to real units - # Works for both pool coins and real coins - dy = (dy - dy_fee) * PRECISION / rates[meta_j] - - dy_admin_fee: uint256 = dy_fee * self.admin_fee / FEE_DENOMINATOR - dy_admin_fee = dy_admin_fee * PRECISION / rates[meta_j] - - # Change balances exactly in same way as we change actual ERC20 coin amounts - self.balances[meta_i] = old_balances[meta_i] + dx_w_fee - # When rounding errors happen, we undercharge admin fee in favor of LP - self.balances[meta_j] = old_balances[meta_j] - dy - dy_admin_fee - - # Withdraw from the base pool if needed - if base_j >= 0: - out_amount: uint256 = ERC20(output_coin).balanceOf(self) - Curve(_base_pool).remove_liquidity_one_coin(dy, base_j, 0) - dy = ERC20(output_coin).balanceOf(self) - out_amount - - assert dy >= min_dy, "Too few coins in result" - - else: - # If both are from the base pool - dy = ERC20(output_coin).balanceOf(self) - Curve(_base_pool).exchange(base_i, base_j, dx_w_fee, min_dy) - dy = ERC20(output_coin).balanceOf(self) - dy - - # "safeTransfer" which works for ERC20s which return bool or not - _response = raw_call( - output_coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(dy, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - log TokenExchangeUnderlying(msg.sender, i, dx, j, dy) - - return dy - - -@external -@nonreentrant('lock') -def remove_liquidity(_amount: uint256, min_amounts: uint256[N_COINS]) -> uint256[N_COINS]: - """ - @notice Withdraw coins from the pool - @dev Withdrawal amounts are based on current deposit ratios - @param _amount Quantity of LP tokens to burn in the withdrawal - @param min_amounts Minimum amounts of underlying coins to receive - @return List of amounts of coins that were withdrawn - """ - total_supply: uint256 = self.token.totalSupply() - amounts: uint256[N_COINS] = empty(uint256[N_COINS]) - fees: uint256[N_COINS] = empty(uint256[N_COINS]) # Fees are unused but we've got them historically in event - - for i in range(N_COINS): - value: uint256 = self.balances[i] * _amount / total_supply - assert value >= min_amounts[i], "Too few coins in result" - self.balances[i] -= value - amounts[i] = value - assert ERC20(self.coins[i]).transfer(msg.sender, value) - - self.token.burnFrom(msg.sender, _amount) # dev: insufficient funds - - log RemoveLiquidity(msg.sender, amounts, fees, total_supply - _amount) - - return amounts - - -@external -@nonreentrant('lock') -def remove_liquidity_imbalance(amounts: uint256[N_COINS], max_burn_amount: uint256) -> uint256: - """ - @notice Withdraw coins from the pool in an imbalanced amount - @param amounts List of amounts of underlying coins to withdraw - @param max_burn_amount Maximum amount of LP token to burn in the withdrawal - @return Actual amount of the LP token burned in the withdrawal - """ - assert not self.is_killed # dev: is killed - - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate() - - token_supply: uint256 = self.token.totalSupply() - assert token_supply != 0 # dev: zero total supply - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - _admin_fee: uint256 = self.admin_fee - - old_balances: uint256[N_COINS] = self.balances - new_balances: uint256[N_COINS] = old_balances - D0: uint256 = self.get_D_mem(vp_rate, old_balances, amp) - for i in range(N_COINS): - new_balances[i] -= amounts[i] - D1: uint256 = self.get_D_mem(vp_rate, new_balances, amp) - - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - 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(vp_rate, new_balances, amp) - - token_amount: uint256 = (D0 - D2) * token_supply / D0 - assert token_amount != 0 # dev: zero tokens burned - token_amount += 1 # In case of rounding errors - make it unfavorable for the "attacker" - assert token_amount <= max_burn_amount, "Slippage screwed you" - - self.token.burnFrom(msg.sender, token_amount) # dev: insufficient funds - for i in range(N_COINS): - if amounts[i] != 0: - assert ERC20(self.coins[i]).transfer(msg.sender, amounts[i]) - - log RemoveLiquidityImbalance(msg.sender, amounts, fees, D1, token_supply - token_amount) - - return token_amount - - -@view -@internal -def get_y_D(A_: uint256, i: int128, xp: uint256[N_COINS], D: uint256) -> uint256: - """ - Calculate x[i] if one reduces D from being calculated for xp to D - - Done by solving quadratic equation iteratively. - x_1**2 + x1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) - x_1**2 + b*x_1 = c - - x_1 = (x_1**2 + c) / (2*x_1 + b) - """ - # x in the input is converted to the same price/precision - - assert i >= 0 # dev: i below zero - assert i < N_COINS # dev: i above N_COINS - - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: uint256 = 0 - - c: uint256 = D - Ann: uint256 = A_ * N_COINS - - for _i in range(N_COINS): - if _i != i: - _x = xp[_i] - else: - continue - S_ += _x - c = c * D / (_x * N_COINS) - c = c * D * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann - 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 - - -@view -@internal -def _calc_withdraw_one_coin(_token_amount: uint256, i: int128, vp_rate: uint256) -> (uint256, uint256, uint256): - # First, need to calculate - # * Get current D - # * Solve Eqn against y_i for D - _token_amount - amp: uint256 = self._A() - xp: uint256[N_COINS] = self._xp(vp_rate) - D0: uint256 = self.get_D(xp, amp) - - total_supply: uint256 = self.token.totalSupply() - D1: uint256 = D0 - _token_amount * D0 / total_supply - new_y: uint256 = self.get_y_D(amp, i, xp, D1) - - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = vp_rate - - xp_reduced: uint256[N_COINS] = xp - dy_0: uint256 = (xp[i] - new_y) * PRECISION / rates[i] # w/o fees - - for j in range(N_COINS): - dx_expected: uint256 = 0 - if j == i: - dx_expected = xp[j] * D1 / D0 - new_y - else: - dx_expected = xp[j] - xp[j] * D1 / D0 - xp_reduced[j] -= _fee * dx_expected / FEE_DENOMINATOR - - dy: uint256 = xp_reduced[i] - self.get_y_D(amp, i, xp_reduced, D1) - dy = (dy - 1) * PRECISION / rates[i] # Withdraw less to account for rounding errors - - return dy, dy_0 - dy, total_supply - - -@view -@external -def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - """ - @notice Calculate the amount received when withdrawing a single coin - @param _token_amount Amount of LP tokens to burn in the withdrawal - @param i Index value of the coin to withdraw - @return Amount of coin received - """ - vp_rate: uint256 = self._vp_rate_ro() - return self._calc_withdraw_one_coin(_token_amount, i, vp_rate)[0] - - -@external -@nonreentrant('lock') -def remove_liquidity_one_coin(_token_amount: uint256, i: int128, _min_amount: uint256) -> uint256: - """ - @notice Withdraw a single coin from the pool - @param _token_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 coin to receive - @return Amount of coin received - """ - assert not self.is_killed # dev: is killed - - vp_rate: uint256 = self._vp_rate() - dy: uint256 = 0 - dy_fee: uint256 = 0 - total_supply: uint256 = 0 - dy, dy_fee, total_supply = self._calc_withdraw_one_coin(_token_amount, i, vp_rate) - assert dy >= _min_amount, "Not enough coins removed" - - self.balances[i] -= (dy + dy_fee * self.admin_fee / FEE_DENOMINATOR) - self.token.burnFrom(msg.sender, _token_amount) # dev: insufficient funds - assert ERC20(self.coins[i]).transfer(msg.sender, dy) - - log RemoveLiquidityOne(msg.sender, _token_amount, dy, total_supply - _token_amount) - - return dy - - -### Admin functions ### -@external -def ramp_A(_future_A: uint256, _future_time: uint256): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.initial_A_time + MIN_RAMP_TIME - assert _future_time >= block.timestamp + MIN_RAMP_TIME # dev: insufficient time - - _initial_A: uint256 = self._A() - _future_A_p: uint256 = _future_A * A_PRECISION - - assert _future_A > 0 and _future_A < MAX_A - if _future_A_p < _initial_A: - assert _future_A_p * MAX_A_CHANGE >= _initial_A - else: - assert _future_A_p <= _initial_A * MAX_A_CHANGE - - self.initial_A = _initial_A - self.future_A = _future_A_p - self.initial_A_time = block.timestamp - self.future_A_time = _future_time - - log RampA(_initial_A, _future_A_p, block.timestamp, _future_time) - - -@external -def stop_ramp_A(): - assert msg.sender == self.owner # dev: only owner - - current_A: uint256 = self._A() - self.initial_A = current_A - self.future_A = current_A - self.initial_A_time = block.timestamp - self.future_A_time = block.timestamp - # now (block.timestamp < t1) is always False, so we return saved A - - log StopRampA(current_A, block.timestamp) - - -@external -def commit_new_fee(new_fee: uint256, new_admin_fee: uint256): - assert msg.sender == self.owner # dev: only owner - assert self.admin_actions_deadline == 0 # dev: active action - assert new_fee <= MAX_FEE # dev: fee exceeds maximum - assert new_admin_fee <= MAX_ADMIN_FEE # dev: admin fee exceeds maximum - - _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.admin_actions_deadline = _deadline - self.future_fee = new_fee - self.future_admin_fee = new_admin_fee - - log CommitNewFee(_deadline, new_fee, new_admin_fee) - - -@external -def apply_new_fee(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.admin_actions_deadline # dev: insufficient time - assert self.admin_actions_deadline != 0 # dev: no active action - - self.admin_actions_deadline = 0 - _fee: uint256 = self.future_fee - _admin_fee: uint256 = self.future_admin_fee - self.fee = _fee - self.admin_fee = _admin_fee - - log NewFee(_fee, _admin_fee) - - -@external -def revert_new_parameters(): - assert msg.sender == self.owner # dev: only owner - - self.admin_actions_deadline = 0 - - -@external -def commit_transfer_ownership(_owner: address): - assert msg.sender == self.owner # dev: only owner - assert self.transfer_ownership_deadline == 0 # dev: active transfer - - _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.transfer_ownership_deadline = _deadline - self.future_owner = _owner - - log CommitNewAdmin(_deadline, _owner) - - -@external -def apply_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.transfer_ownership_deadline # dev: insufficient time - assert self.transfer_ownership_deadline != 0 # dev: no active transfer - - self.transfer_ownership_deadline = 0 - _owner: address = self.future_owner - self.owner = _owner - - log NewAdmin(_owner) - - -@external -def revert_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - - self.transfer_ownership_deadline = 0 - - -@view -@external -def admin_balances(i: uint256) -> uint256: - return ERC20(self.coins[i]).balanceOf(self) - self.balances[i] - - -@external -def withdraw_admin_fees(): - assert msg.sender == self.owner # dev: only owner - - for i in range(N_COINS): - c: address = self.coins[i] - value: uint256 = ERC20(c).balanceOf(self) - self.balances[i] - if value > 0: - assert ERC20(c).transfer(msg.sender, value) - - -@external -def donate_admin_fees(): - assert msg.sender == self.owner # dev: only owner - for i in range(N_COINS): - self.balances[i] = ERC20(self.coins[i]).balanceOf(self) - - -@external -def kill_me(): - assert msg.sender == self.owner # dev: only owner - assert self.kill_deadline > block.timestamp # dev: deadline has passed - self.is_killed = True - - -@external -def unkill_me(): - assert msg.sender == self.owner # dev: only owner - self.is_killed = False diff --git a/contracts/pools/usdn/pooldata.json b/contracts/pools/usdn/pooldata.json deleted file mode 100644 index d04fa8c4..00000000 --- a/contracts/pools/usdn/pooldata.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "base_pool": "3pool", - "pool_types": ["meta"], - "swap_address": "0x0f9cb53Ebe405d49A0bbdBD291A65Ff571bC83e1", - "lp_token_address": "0x4f3E8F405CF5aFC05D68142F3783bDfE13811522", - "zap_address": "0x094d12e5b541784701FD8d65F11fc0598FBC6332", - "gauge_addresses": ["0xF98450B5602fa59CC66e1379DFfB6FDDc724CfC4"], - "lp_contract": "CurveTokenV2", - "lp_constructor": { - "symbol": "usdn3CRV", - "name": "Curve.fi USDN/3Crv" - }, - "swap_constructor": { - "_A": 100, - "_fee": 4000000, - "_admin_fee": 0 - }, - "coins": [ - { - "name": "USDN", - "decimals": 18, - "tethered": false, - "underlying_address": "0x674C6Ad92Fd080e4004b2312b45f796a192D27a0" - }, - { - "name": "3CRV", - "decimals": 18, - "base_pool_token": true, - "underlying_address": "0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490" - } - ] -} diff --git a/contracts/pools/usdp/DepositUSDP.vy b/contracts/pools/usdp/DepositUSDP.vy deleted file mode 100644 index b236bde8..00000000 --- a/contracts/pools/usdp/DepositUSDP.vy +++ /dev/null @@ -1,376 +0,0 @@ -# @version 0.2.8 -""" -@title "Zap" Depositer for metapool -@author Curve.Fi -@license Copyright (c) Curve.Fi, 2020 - all rights reserved -@notice deposit/withdraw to Curve pool without too many transactions -""" - -from vyper.interfaces import ERC20 - - -interface CurveMeta: - def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256) -> 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) -> 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 base_pool() -> address: 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 - -# An asset which may have a transfer fee (USDT) -FEE_ASSET: constant(address) = 0xdAC17F958D2ee523a2206206994597C13D831ec7 - -FEE_DENOMINATOR: constant(uint256) = 10 ** 10 -FEE_IMPRECISION: constant(uint256) = 100 * 10 ** 8 # % of the fee - - -pool: public(address) -token: public(address) -base_pool: public(address) - -coins: public(address[N_COINS]) -base_coins: public(address[BASE_N_COINS]) - - -@external -def __init__(_pool: address, _token: address): - """ - @notice Contract constructor - @param _pool Metapool address - @param _token Pool LP token address - """ - self.pool = _pool - self.token = _token - base_pool: address = CurveMeta(_pool).base_pool() - self.base_pool = base_pool - - for i in range(N_COINS): - coin: address = CurveMeta(_pool).coins(convert(i, uint256)) - self.coins[i] = coin - # approve coins for infinite transfers - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("approve(address,uint256)"), - convert(_pool, bytes32), - convert(MAX_UINT256, bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) - - for i in range(BASE_N_COINS): - coin: address = CurveBase(base_pool).coins(i) - self.base_coins[i] = coin - # approve underlying coins for infinite transfers - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("approve(address,uint256)"), - convert(base_pool, bytes32), - convert(MAX_UINT256, bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) - - -@external -def add_liquidity(_amounts: uint256[N_ALL_COINS], _min_mint_amount: uint256) -> uint256: - """ - @notice Wrap underlying coins and deposit them in the pool - @param _amounts List of amounts of underlying coins to deposit - @param _min_mint_amount Minimum amount of LP tokens to mint from the deposit - @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 - - # Transfer all coins in - for i in range(N_ALL_COINS): - amount: uint256 = _amounts[i] - if amount == 0: - continue - coin: address = ZERO_ADDRESS - if i < MAX_COIN: - coin = self.coins[i] - meta_amounts[i] = amount - else: - x: int128 = i - MAX_COIN - coin = self.base_coins[x] - base_amounts[x] = amount - deposit_base = True - # "safeTransferFrom" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transferFrom(address,address,uint256)"), - convert(msg.sender, bytes32), - convert(self, bytes32), - convert(amount, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransferFrom" - # Handle potential Tether fees - if coin == FEE_ASSET: - amount = ERC20(FEE_ASSET).balanceOf(self) - if i < MAX_COIN: - meta_amounts[i] = amount - else: - base_amounts[i - MAX_COIN] = amount - - # Deposit to the base pool - if deposit_base: - CurveBase(self.base_pool).add_liquidity(base_amounts, 0) - meta_amounts[MAX_COIN] = ERC20(self.coins[MAX_COIN]).balanceOf(self) - - # Deposit to the meta pool - CurveMeta(self.pool).add_liquidity(meta_amounts, _min_mint_amount) - - # Transfer meta token back - lp_token: address = self.token - lp_amount: uint256 = ERC20(lp_token).balanceOf(self) - assert ERC20(lp_token).transfer(msg.sender, lp_amount) - - return lp_amount - - -@external -def remove_liquidity(_amount: uint256, _min_amounts: uint256[N_ALL_COINS]) -> uint256[N_ALL_COINS]: - """ - @notice Withdraw and unwrap coins from the pool - @dev Withdrawal amounts are based on current deposit ratios - @param _amount Quantity of LP tokens to burn in the withdrawal - @param _min_amounts Minimum amounts of underlying coins to receive - @return List of amounts of underlying coins that were withdrawn - """ - _token: address = self.token - assert ERC20(_token).transferFrom(msg.sender, self, _amount) - - min_amounts_meta: uint256[N_COINS] = empty(uint256[N_COINS]) - 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 - for i in range(MAX_COIN): - min_amounts_meta[i] = _min_amounts[i] - CurveMeta(self.pool).remove_liquidity(_amount, min_amounts_meta) - - # Withdraw from base - _base_amount: uint256 = ERC20(self.coins[MAX_COIN]).balanceOf(self) - for i in range(BASE_N_COINS): - min_amounts_base[i] = _min_amounts[MAX_COIN+i] - CurveBase(self.base_pool).remove_liquidity(_base_amount, min_amounts_base) - - # Transfer all coins out - for i in range(N_ALL_COINS): - coin: address = ZERO_ADDRESS - if i < MAX_COIN: - coin = self.coins[i] - else: - coin = self.base_coins[i - MAX_COIN] - amounts[i] = ERC20(coin).balanceOf(self) - # "safeTransfer" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(amounts[i], bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - return amounts - - -@external -def remove_liquidity_one_coin(_token_amount: uint256, i: int128, _min_amount: uint256) -> uint256: - """ - @notice Withdraw and unwrap a single coin from the pool - @param _token_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 - @return Amount of underlying coin received - """ - assert ERC20(self.token).transferFrom(msg.sender, self, _token_amount) - - coin: address = ZERO_ADDRESS - if i < MAX_COIN: - coin = self.coins[i] - # Withdraw a metapool coin - CurveMeta(self.pool).remove_liquidity_one_coin(_token_amount, i, _min_amount) - else: - coin = self.base_coins[i - MAX_COIN] - # Withdraw a base pool coin - CurveMeta(self.pool).remove_liquidity_one_coin(_token_amount, MAX_COIN, 0) - CurveBase(self.base_pool).remove_liquidity_one_coin( - ERC20(self.coins[MAX_COIN]).balanceOf(self), i-MAX_COIN, _min_amount - ) - - # Tranfer the coin out - coin_amount: uint256 = ERC20(coin).balanceOf(self) - # "safeTransfer" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(coin_amount, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - return coin_amount - - -@external -def remove_liquidity_imbalance(_amounts: uint256[N_ALL_COINS], _max_burn_amount: uint256) -> uint256: - """ - @notice Withdraw coins from the pool in an imbalanced amount - @param _amounts List of amounts of underlying coins to withdraw - @param _max_burn_amount Maximum amount of LP token to burn in the withdrawal - @return Actual amount of the LP token burned in the withdrawal - """ - base_pool: address = self.base_pool - meta_pool: address = self.pool - base_coins: address[BASE_N_COINS] = self.base_coins - meta_coins: address[N_COINS] = self.coins - lp_token: address = self.token - - 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 - assert ERC20(lp_token).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]) - leftover_amounts: uint256[N_COINS] = empty(uint256[N_COINS]) - - # Prepare quantities - for i in range(MAX_COIN): - amounts_meta[i] = _amounts[i] - - for i in range(BASE_N_COINS): - amount: uint256 = _amounts[MAX_COIN + i] - if amount != 0: - amounts_base[i] = amount - withdraw_base = True - - if withdraw_base: - amounts_meta[MAX_COIN] = CurveBase(self.base_pool).calc_token_amount(amounts_base, False) - amounts_meta[MAX_COIN] += amounts_meta[MAX_COIN] * fee / FEE_DENOMINATOR + 1 - - # Remove liquidity and deposit leftovers back - CurveMeta(meta_pool).remove_liquidity_imbalance(amounts_meta, _max_burn_amount) - if withdraw_base: - CurveBase(base_pool).remove_liquidity_imbalance(amounts_base, amounts_meta[MAX_COIN]) - leftover_amounts[MAX_COIN] = ERC20(meta_coins[MAX_COIN]).balanceOf(self) - if leftover_amounts[MAX_COIN] > 0: - CurveMeta(meta_pool).add_liquidity(leftover_amounts, 0) - - # Transfer all coins out - for i in range(N_ALL_COINS): - coin: address = ZERO_ADDRESS - amount: uint256 = 0 - if i < MAX_COIN: - coin = meta_coins[i] - amount = amounts_meta[i] - else: - coin = base_coins[i - MAX_COIN] - amount = amounts_base[i - MAX_COIN] - # "safeTransfer" which works for ERC20s which return bool or not - if amount > 0: - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(amount, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - # Transfer the leftover LP token out - leftover: uint256 = ERC20(lp_token).balanceOf(self) - if leftover > 0: - assert ERC20(lp_token).transfer(msg.sender, leftover) - - return _max_burn_amount - leftover - - -@view -@external -def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - """ - @notice Calculate the amount received when withdrawing and unwrapping a single coin - @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(self.pool).calc_withdraw_one_coin(_token_amount, i) - else: - base_tokens: uint256 = CurveMeta(self.pool).calc_withdraw_one_coin(_token_amount, MAX_COIN) - return CurveBase(self.base_pool).calc_withdraw_one_coin(base_tokens, i-MAX_COIN) - - -@view -@external -def calc_token_amount(_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 _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]) - - for i in range(MAX_COIN): - meta_amounts[i] = _amounts[i] - - for i in range(BASE_N_COINS): - base_amounts[i] = _amounts[i + MAX_COIN] - - base_tokens: uint256 = CurveBase(self.base_pool).calc_token_amount(base_amounts, _is_deposit) - meta_amounts[MAX_COIN] = base_tokens - - return CurveMeta(self.pool).calc_token_amount(meta_amounts, _is_deposit) diff --git a/contracts/pools/usdp/README.md b/contracts/pools/usdp/README.md deleted file mode 100644 index 852f4cd9..00000000 --- a/contracts/pools/usdp/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# curve-contract/contracts/pools/usdp - -[Curve USDP metapool](https://www.curve.fi/usdp), allowing swaps via the Curve [tri-pool](../3pool). - -## Contracts - -* [`DepositUSDP`](DepositUSDP.vy): Depositor contract, used to wrap underlying tokens prior to depositing them into the pool -* [`StableSwapUSDP`](StableSwapUSDP.vy): Curve stablecoin AMM contract - -## Deployments - -* [`CurveContractV3`](../../tokens/CurveTokenV3.vy): [0x7Eb40E450b9655f4B3cC4259BCC731c63ff55ae6](https://etherscan.io/address/0x7Eb40E450b9655f4B3cC4259BCC731c63ff55ae6) -* [`DepositUSDP`](DepositUSDP.vy): [0x3c8cAee4E09296800f8D29A68Fa3837e2dae4940](https://etherscan.io/address/0x3c8cAee4E09296800f8D29A68Fa3837e2dae4940) -* [`LiquidityGaugeV2`](https://github.com/curvefi/curve-dao-contracts/blob/master/contracts/gauges/LiquidityGaugeV2.vy): [0x055be5DDB7A925BfEF3417FC157f53CA77cA7222](https://etherscan.io/address/0x055be5DDB7A925BfEF3417FC157f53CA77cA7222) -* [`StableSwapUSDP`](StableSwapUSDP.vy): [0x42d7025938bEc20B69cBae5A77421082407f053A](https://etherscan.io/address/0x42d7025938bEc20B69cBae5A77421082407f053A) - -## Stablecoins - -Curve USDP metapool utilizes the supports swaps between the following assets: - -## Direct swaps - -Direct swaps are possible between USDP and the Curve tri-pool LP token. - -* `USDP`: [0x1456688345527bE1f37E9e627DA0837D6f08C925](https://etherscan.io/address/0x1456688345527bE1f37E9e627DA0837D6f08C925) -* `3CRV`: [0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490](https://etherscan.io/address/0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490) - -## Base Pool coins - -The tri-pool LP token may be wrapped or unwrapped to provide swaps between USDP and the following stablecoins: - -* `DAI`: [0x6b175474e89094c44da98b954eedeac495271d0f](https://etherscan.io/address/0x6b175474e89094c44da98b954eedeac495271d0f) -* `USDC`: [0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48](https://etherscan.io/address/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48) -* `USDT`: [0xdac17f958d2ee523a2206206994597c13d831ec7](https://etherscan.io/address/0xdac17f958d2ee523a2206206994597c13d831ec7) diff --git a/contracts/pools/usdp/StableSwapUSDP.vy b/contracts/pools/usdp/StableSwapUSDP.vy deleted file mode 100644 index cf5bf1e8..00000000 --- a/contracts/pools/usdp/StableSwapUSDP.vy +++ /dev/null @@ -1,1113 +0,0 @@ -# @version 0.2.8 -""" -@title StableSwap -@author Curve.Fi -@license Copyright (c) Curve.Fi, 2021 - all rights reserved -@notice Metapool implementation -@dev Swaps between 3pool and USDP -""" - -from vyper.interfaces import ERC20 - -interface CurveToken: - def totalSupply() -> uint256: view - def mint(_to: address, _value: uint256) -> bool: nonpayable - def burnFrom(_to: address, _value: uint256) -> bool: nonpayable - -interface Curve: - def coins(i: uint256) -> address: view - def get_virtual_price() -> uint256: view - def calc_token_amount(amounts: uint256[BASE_N_COINS], deposit: bool) -> uint256: view - def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: view - def fee() -> uint256: view - def get_dy(i: int128, j: int128, dx: uint256) -> uint256: view - def get_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256: view - def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256): nonpayable - def add_liquidity(amounts: uint256[BASE_N_COINS], min_mint_amount: uint256): nonpayable - def remove_liquidity_one_coin(_token_amount: uint256, i: int128, min_amount: uint256): nonpayable - - -# Events -event TokenExchange: - buyer: indexed(address) - sold_id: int128 - tokens_sold: uint256 - bought_id: int128 - tokens_bought: uint256 - -event TokenExchangeUnderlying: - buyer: indexed(address) - sold_id: int128 - tokens_sold: uint256 - bought_id: int128 - tokens_bought: uint256 - -event AddLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event RemoveLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - token_supply: uint256 - -event RemoveLiquidityOne: - provider: indexed(address) - token_amount: uint256 - coin_amount: uint256 - token_supply: uint256 - -event RemoveLiquidityImbalance: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event CommitNewAdmin: - deadline: indexed(uint256) - admin: indexed(address) - -event NewAdmin: - admin: indexed(address) - -event CommitNewFee: - deadline: indexed(uint256) - fee: uint256 - admin_fee: uint256 - -event NewFee: - fee: uint256 - admin_fee: uint256 - -event RampA: - old_A: uint256 - new_A: uint256 - initial_time: uint256 - future_time: uint256 - -event StopRampA: - A: uint256 - t: uint256 - - -N_COINS: constant(int128) = 2 -MAX_COIN: constant(int128) = N_COINS - 1 - -FEE_DENOMINATOR: constant(uint256) = 10 ** 10 -PRECISION: constant(uint256) = 10 ** 18 # The precision to convert to -RATES: constant(uint256[N_COINS]) = [1000000000000000000, 1000000000000000000] -BASE_N_COINS: constant(int128) = 3 - -# An asset which may have a transfer fee (USDT) -FEE_ASSET: constant(address) = 0xdAC17F958D2ee523a2206206994597C13D831ec7 - -MAX_ADMIN_FEE: constant(uint256) = 10 * 10 ** 9 -MAX_FEE: constant(uint256) = 5 * 10 ** 9 -MAX_A: constant(uint256) = 10 ** 6 -MAX_A_CHANGE: constant(uint256) = 10 - -ADMIN_ACTIONS_DELAY: constant(uint256) = 3 * 86400 -MIN_RAMP_TIME: constant(uint256) = 86400 - -coins: public(address[N_COINS]) -balances: public(uint256[N_COINS]) -fee: public(uint256) # fee * 1e10 -admin_fee: public(uint256) # admin_fee * 1e10 - -owner: public(address) -lp_token: public(address) - -# Token corresponding to the pool is always the last one -BASE_CACHE_EXPIRES: constant(int128) = 10 * 60 # 10 min -base_pool: public(address) -base_virtual_price: public(uint256) -base_cache_updated: public(uint256) -base_coins: public(address[BASE_N_COINS]) - -A_PRECISION: constant(uint256) = 100 -initial_A: public(uint256) -future_A: public(uint256) -initial_A_time: public(uint256) -future_A_time: public(uint256) - -admin_actions_deadline: public(uint256) -transfer_ownership_deadline: public(uint256) -future_fee: public(uint256) -future_admin_fee: public(uint256) -future_owner: public(address) - -is_killed: bool -kill_deadline: uint256 -KILL_DEADLINE_DT: constant(uint256) = 2 * 30 * 86400 - - -@external -def __init__( - _owner: address, - _coins: address[N_COINS], - _pool_token: address, - _base_pool: address, - _A: uint256, - _fee: uint256, - _admin_fee: uint256 -): - """ - @notice Contract constructor - @param _owner Contract owner address - @param _coins Addresses of ERC20 conracts of coins - @param _pool_token Address of the token representing LP share - @param _base_pool Address of the base pool (which will have a virtual price) - @param _A Amplification coefficient multiplied by n * (n - 1) - @param _fee Fee to charge for exchanges - @param _admin_fee Admin fee - """ - for i in range(N_COINS): - assert _coins[i] != ZERO_ADDRESS - self.coins = _coins - self.initial_A = _A * A_PRECISION - self.future_A = _A * A_PRECISION - self.fee = _fee - self.admin_fee = _admin_fee - self.owner = _owner - self.kill_deadline = block.timestamp + KILL_DEADLINE_DT - self.lp_token = _pool_token - - self.base_pool = _base_pool - self.base_virtual_price = Curve(_base_pool).get_virtual_price() - self.base_cache_updated = block.timestamp - for i in range(BASE_N_COINS): - base_coin: address = Curve(_base_pool).coins(convert(i, uint256)) - self.base_coins[i] = base_coin - - # approve underlying coins for infinite transfers - response: Bytes[32] = raw_call( - base_coin, - concat( - method_id("approve(address,uint256)"), - convert(_base_pool, bytes32), - convert(MAX_UINT256, bytes32), - ), - max_outsize=32, - ) - if len(response) > 0: - assert convert(response, bool) - - -@view -@internal -def _A() -> uint256: - """ - Handle ramping A up or down - """ - t1: uint256 = self.future_A_time - A1: uint256 = self.future_A - - if block.timestamp < t1: - A0: uint256 = self.initial_A - t0: uint256 = self.initial_A_time - # Expressions in uint256 cannot have negative numbers, thus "if" - if A1 > A0: - return A0 + (A1 - A0) * (block.timestamp - t0) / (t1 - t0) - else: - return A0 - (A0 - A1) * (block.timestamp - t0) / (t1 - t0) - - else: # when t1 == 0 or block.timestamp >= t1 - return A1 - - -@view -@external -def A() -> uint256: - return self._A() / A_PRECISION - - -@view -@external -def A_precise() -> uint256: - return self._A() - - -@view -@internal -def _xp(_vp_rate: uint256) -> uint256[N_COINS]: - result: uint256[N_COINS] = RATES - result[MAX_COIN] = _vp_rate # virtual price for the metacurrency - for i in range(N_COINS): - result[i] = result[i] * self.balances[i] / PRECISION - return result - - -@pure -@internal -def _xp_mem(_vp_rate: uint256, _balances: uint256[N_COINS]) -> uint256[N_COINS]: - result: uint256[N_COINS] = RATES - result[MAX_COIN] = _vp_rate # virtual price for the metacurrency - for i in range(N_COINS): - result[i] = result[i] * _balances[i] / PRECISION - return result - - -@internal -def _vp_rate() -> uint256: - if block.timestamp > self.base_cache_updated + BASE_CACHE_EXPIRES: - vprice: uint256 = Curve(self.base_pool).get_virtual_price() - self.base_virtual_price = vprice - self.base_cache_updated = block.timestamp - return vprice - else: - return self.base_virtual_price - - -@internal -@view -def _vp_rate_ro() -> uint256: - if block.timestamp > self.base_cache_updated + BASE_CACHE_EXPIRES: - return Curve(self.base_pool).get_virtual_price() - else: - return self.base_virtual_price - - -@pure -@internal -def _get_D(_xp: uint256[N_COINS], _amp: uint256) -> uint256: - S: uint256 = 0 - Dprev: uint256 = 0 - - for _x in _xp: - S += _x - if S == 0: - return 0 - - D: uint256 = S - Ann: uint256 = _amp * N_COINS - for _i in range(255): - D_P: uint256 = D - for _x in _xp: - D_P = D_P * D / (_x * N_COINS) # If division by 0, this will be borked: only withdrawal will work. And that is good - Dprev = D - D = (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (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` - raise - - -@view -@internal -def _get_D_mem(_vp_rate: uint256, _balances: uint256[N_COINS], _amp: uint256) -> uint256: - return self._get_D(self._xp_mem(_vp_rate, _balances), _amp) - - -@view -@external -def get_virtual_price() -> uint256: - """ - @notice The current virtual price of the pool LP token - @dev Useful for calculating profits - @return LP token virtual price normalized to 1e18 - """ - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate_ro() - xp: uint256[N_COINS] = self._xp(vp_rate) - D: uint256 = self._get_D(xp, amp) - # 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 = CurveToken(self.lp_token).totalSupply() - return D * PRECISION / token_supply - - -@view -@external -def calc_token_amount(_amounts: uint256[N_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 _amounts Amount of each coin being deposited - @param _is_deposit set True for deposits, False for withdrawals - @return Expected amount of LP tokens received - """ - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate_ro() - balances: uint256[N_COINS] = self.balances - D0: uint256 = self._get_D_mem(vp_rate, balances, amp) - for i in range(N_COINS): - if _is_deposit: - balances[i] += _amounts[i] - else: - balances[i] -= _amounts[i] - D1: uint256 = self._get_D_mem(vp_rate, balances, amp) - token_amount: uint256 = CurveToken(self.lp_token).totalSupply() - diff: uint256 = 0 - if _is_deposit: - diff = D1 - D0 - else: - diff = D0 - D1 - return diff * token_amount / D0 - - -@external -@nonreentrant('lock') -def add_liquidity(_amounts: uint256[N_COINS], _min_mint_amount: uint256) -> uint256: - """ - @notice Deposit coins into the pool - @param _amounts List of amounts of coins to deposit - @param _min_mint_amount Minimum amount of LP tokens to mint from the deposit - @return Amount of LP tokens received by depositing - """ - assert not self.is_killed # dev: is killed - - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate() - old_balances: uint256[N_COINS] = self.balances - - # Initial invariant - D0: uint256 = self._get_D_mem(vp_rate, old_balances, amp) - - lp_token: address = self.lp_token - token_supply: uint256 = CurveToken(lp_token).totalSupply() - new_balances: uint256[N_COINS] = old_balances - - for i in range(N_COINS): - if token_supply == 0: - assert _amounts[i] > 0 # dev: initial deposit requires all coins - # balances store amounts of c-tokens - new_balances[i] = old_balances[i] + _amounts[i] - - # Invariant after change - D1: uint256 = self._get_D_mem(vp_rate, new_balances, amp) - assert D1 > D0 - - # We need to recalculate the invariant accounting for fees - # to calculate fair user's share - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - D2: uint256 = D1 - mint_amount: uint256 = 0 - if token_supply > 0: - fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - admin_fee: uint256 = self.admin_fee - # 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(vp_rate, new_balances, amp) - mint_amount = token_supply * (D2 - D0) / D0 - else: - self.balances = new_balances - mint_amount = D1 # Take the dust if there was any - - assert mint_amount >= _min_mint_amount, "Slippage screwed you" - - # Take coins from the sender - for i in range(N_COINS): - if _amounts[i] > 0: - # "safeTransferFrom" which works for ERC20s which return bool or not - response: Bytes[32] = raw_call( - self.coins[i], - concat( - method_id("transferFrom(address,address,uint256)"), - convert(msg.sender, bytes32), - convert(self, bytes32), - convert(_amounts[i], bytes32), - ), - max_outsize=32, - ) - if len(response) > 0: - assert convert(response, bool) # dev: failed transfer - # end "safeTransferFrom" - - # Mint pool tokens - CurveToken(lp_token).mint(msg.sender, mint_amount) - - log AddLiquidity(msg.sender, _amounts, fees, D1, token_supply + mint_amount) - - return mint_amount - - -@view -@internal -def _get_y(i: int128, j: int128, x: uint256, _xp: uint256[N_COINS]) -> uint256: - """ - Calculate x[j] if one makes x[i] = x - - Done by solving quadratic equation iteratively. - x_1**2 + x_1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) - x_1**2 + b*x_1 = c - - x_1 = (x_1**2 + c) / (2*x_1 + b) - """ - # x in the input is converted to the same price/precision - - assert i != j # dev: same coin - assert j >= 0 # dev: j below zero - assert j < N_COINS # dev: j above N_COINS - - # should be unreachable, but good for safety - assert i >= 0 - assert i < N_COINS - - A: uint256 = self._A() - D: uint256 = self._get_D(_xp, A) - Ann: uint256 = A * N_COINS - c: uint256 = D - S: uint256 = 0 - _x: uint256 = 0 - y_prev: 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 * A_PRECISION / (Ann * N_COINS) - b: uint256 = S + D * A_PRECISION / Ann # - D - 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: - return y - else: - if y_prev - y <= 1: - return y - raise - - -@view -@external -def get_dy(i: int128, j: int128, _dx: uint256) -> uint256: - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = self._vp_rate_ro() - xp: uint256[N_COINS] = self._xp(rates[MAX_COIN]) - - x: uint256 = xp[i] + (_dx * rates[i] / PRECISION) - y: uint256 = self._get_y(i, j, x, xp) - dy: uint256 = xp[j] - y - 1 - fee: uint256 = self.fee * dy / FEE_DENOMINATOR - return (dy - fee) * PRECISION / rates[j] - - -@view -@external -def get_dy_underlying(i: int128, j: int128, _dx: uint256) -> uint256: - # dx and dy in underlying units - vp_rate: uint256 = self._vp_rate_ro() - xp: uint256[N_COINS] = self._xp(vp_rate) - base_pool: address = self.base_pool - - # Use base_i or base_j if they are >= 0 - base_i: int128 = i - MAX_COIN - base_j: int128 = j - MAX_COIN - meta_i: int128 = MAX_COIN - meta_j: int128 = MAX_COIN - if base_i < 0: - meta_i = i - if base_j < 0: - meta_j = j - - x: uint256 = 0 - if base_i < 0: - x = xp[i] + _dx - else: - if base_j < 0: - # i is from BasePool - # At first, get the amount of pool tokens - base_inputs: uint256[BASE_N_COINS] = empty(uint256[BASE_N_COINS]) - base_inputs[base_i] = _dx - # Token amount transformed to underlying "dollars" - x = Curve(base_pool).calc_token_amount(base_inputs, True) * vp_rate / PRECISION - # Accounting for deposit/withdraw fees approximately - x -= x * Curve(base_pool).fee() / (2 * FEE_DENOMINATOR) - # Adding number of pool tokens - x += xp[MAX_COIN] - else: - # If both are from the base pool - return Curve(base_pool).get_dy(base_i, base_j, _dx) - - # This pool is involved only when in-pool assets are used - y: uint256 = self._get_y(meta_i, meta_j, x, xp) - dy: uint256 = xp[meta_j] - y - 1 - dy = (dy - self.fee * dy / FEE_DENOMINATOR) - - # If output is going via the metapool - if base_j >= 0: - # j is from BasePool - # The fee is already accounted for - dy = Curve(base_pool).calc_withdraw_one_coin(dy * PRECISION / vp_rate, base_j) - - return dy - - -@external -@nonreentrant('lock') -def exchange(i: int128, j: int128, _dx: uint256, _min_dy: uint256) -> uint256: - """ - @notice Perform an exchange between two coins - @dev Index values can be found via the `coins` public getter method - @param i Index value for the coin to send - @param j Index valie of the coin to recieve - @param _dx Amount of `i` being exchanged - @param _min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - assert not self.is_killed # dev: is killed - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = self._vp_rate() - - old_balances: uint256[N_COINS] = self.balances - xp: uint256[N_COINS] = self._xp_mem(rates[MAX_COIN], old_balances) - - x: uint256 = xp[i] + _dx * rates[i] / PRECISION - y: uint256 = self._get_y(i, j, x, xp) - - dy: uint256 = xp[j] - y - 1 # -1 just in case there were some rounding errors - dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR - - # Convert all to real units - dy = (dy - dy_fee) * PRECISION / rates[j] - assert dy >= _min_dy, "Too few coins in result" - - dy_admin_fee: uint256 = dy_fee * self.admin_fee / FEE_DENOMINATOR - dy_admin_fee = dy_admin_fee * PRECISION / rates[j] - - # Change balances exactly in same way as we change actual ERC20 coin amounts - self.balances[i] = old_balances[i] + _dx - # When rounding errors happen, we undercharge admin fee in favor of LP - self.balances[j] = old_balances[j] - dy - dy_admin_fee - - response: Bytes[32] = raw_call( - self.coins[i], - concat( - method_id("transferFrom(address,address,uint256)"), - convert(msg.sender, bytes32), - convert(self, bytes32), - convert(_dx, bytes32), - ), - max_outsize=32, - ) - if len(response) > 0: - assert convert(response, bool) - - response = raw_call( - self.coins[j], - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(dy, bytes32), - ), - max_outsize=32, - ) - if len(response) > 0: - assert convert(response, bool) - - log TokenExchange(msg.sender, i, _dx, j, dy) - - return dy - - -@external -@nonreentrant('lock') -def exchange_underlying(i: int128, j: int128, _dx: uint256, _min_dy: uint256) -> uint256: - """ - @notice Perform an exchange between two underlying coins - @dev Index values can be found via the `underlying_coins` public getter method - @param i Index value for the underlying coin to send - @param j Index valie of the underlying coin to recieve - @param _dx Amount of `i` being exchanged - @param _min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - assert not self.is_killed # dev: is killed - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = self._vp_rate() - base_pool: address = self.base_pool - - # Use base_i or base_j if they are >= 0 - base_i: int128 = i - MAX_COIN - base_j: int128 = j - MAX_COIN - meta_i: int128 = MAX_COIN - meta_j: int128 = MAX_COIN - if base_i < 0: - meta_i = i - if base_j < 0: - meta_j = j - dy: uint256 = 0 - - # Addresses for input and output coins - input_coin: address = ZERO_ADDRESS - output_coin: address = ZERO_ADDRESS - if base_i < 0: - input_coin = self.coins[i] - else: - input_coin = self.base_coins[base_i] - if base_j < 0: - output_coin = self.coins[j] - else: - output_coin = self.base_coins[base_j] - - # Handle potential Tether fees - dx_w_fee: uint256 = _dx - if input_coin == FEE_ASSET: - dx_w_fee = ERC20(FEE_ASSET).balanceOf(self) - - response: Bytes[32] = raw_call( - input_coin, - concat( - method_id("transferFrom(address,address,uint256)"), - convert(msg.sender, bytes32), - convert(self, bytes32), - convert(_dx, bytes32), - ), - max_outsize=32, - ) - if len(response) > 0: - assert convert(response, bool) - - # Handle potential Tether fees - if input_coin == FEE_ASSET: - dx_w_fee = ERC20(FEE_ASSET).balanceOf(self) - dx_w_fee - - if base_i < 0 or base_j < 0: - old_balances: uint256[N_COINS] = self.balances - xp: uint256[N_COINS] = self._xp_mem(rates[MAX_COIN], old_balances) - - x: uint256 = 0 - if base_i < 0: - x = xp[i] + dx_w_fee * rates[i] / PRECISION - else: - # i is from BasePool - # At first, get the amount of pool tokens - base_inputs: uint256[BASE_N_COINS] = empty(uint256[BASE_N_COINS]) - base_inputs[base_i] = dx_w_fee - coin_i: address = self.coins[MAX_COIN] - # Deposit and measure delta - x = ERC20(coin_i).balanceOf(self) - Curve(base_pool).add_liquidity(base_inputs, 0) - # Need to convert pool token to "virtual" units using rates - # dx is also different now - dx_w_fee = ERC20(coin_i).balanceOf(self) - x - x = dx_w_fee * rates[MAX_COIN] / PRECISION - # Adding number of pool tokens - x += xp[MAX_COIN] - - y: uint256 = self._get_y(meta_i, meta_j, x, xp) - - # Either a real coin or token - dy = xp[meta_j] - y - 1 # -1 just in case there were some rounding errors - dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR - - # Convert all to real units - # Works for both pool coins and real coins - dy = (dy - dy_fee) * PRECISION / rates[meta_j] - - dy_admin_fee: uint256 = dy_fee * self.admin_fee / FEE_DENOMINATOR - dy_admin_fee = dy_admin_fee * PRECISION / rates[meta_j] - - # Change balances exactly in same way as we change actual ERC20 coin amounts - self.balances[meta_i] = old_balances[meta_i] + dx_w_fee - # When rounding errors happen, we undercharge admin fee in favor of LP - self.balances[meta_j] = old_balances[meta_j] - dy - dy_admin_fee - - # Withdraw from the base pool if needed - if base_j >= 0: - out_amount: uint256 = ERC20(output_coin).balanceOf(self) - Curve(base_pool).remove_liquidity_one_coin(dy, base_j, 0) - dy = ERC20(output_coin).balanceOf(self) - out_amount - - assert dy >= _min_dy, "Too few coins in result" - - else: - # If both are from the base pool - dy = ERC20(output_coin).balanceOf(self) - Curve(base_pool).exchange(base_i, base_j, dx_w_fee, _min_dy) - dy = ERC20(output_coin).balanceOf(self) - dy - - # "safeTransfer" which works for ERC20s which return bool or not - response = raw_call( - output_coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(dy, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(response) > 0: - assert convert(response, bool) # dev: failed transfer - # end "safeTransfer" - - log TokenExchangeUnderlying(msg.sender, i, _dx, j, dy) - - return dy - - -@external -@nonreentrant('lock') -def remove_liquidity(_amount: uint256, _min_amounts: uint256[N_COINS]) -> uint256[N_COINS]: - """ - @notice Withdraw coins from the pool - @dev Withdrawal amounts are based on current deposit ratios - @param _amount Quantity of LP tokens to burn in the withdrawal - @param _min_amounts Minimum amounts of underlying coins to receive - @return List of amounts of coins that were withdrawn - """ - lp_token: address = self.lp_token - total_supply: uint256 = CurveToken(lp_token).totalSupply() - amounts: uint256[N_COINS] = empty(uint256[N_COINS]) - - for i in range(N_COINS): - old_balance: uint256 = self.balances[i] - value: uint256 = old_balance * _amount / total_supply - assert value >= _min_amounts[i], "Withdrawal resulted in fewer coins than expected" - self.balances[i] = old_balance - value - amounts[i] = value - ERC20(self.coins[i]).transfer(msg.sender, value) - - CurveToken(lp_token).burnFrom(msg.sender, _amount) # dev: insufficient funds - - log RemoveLiquidity(msg.sender, amounts, empty(uint256[N_COINS]), total_supply - _amount) - - return amounts - - -@external -@nonreentrant('lock') -def remove_liquidity_imbalance(_amounts: uint256[N_COINS], _max_burn_amount: uint256) -> uint256: - """ - @notice Withdraw coins from the pool in an imbalanced amount - @param _amounts List of amounts of underlying coins to withdraw - @param _max_burn_amount Maximum amount of LP token to burn in the withdrawal - @return Actual amount of the LP token burned in the withdrawal - """ - assert not self.is_killed # dev: is killed - - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate() - old_balances: uint256[N_COINS] = self.balances - new_balances: uint256[N_COINS] = old_balances - D0: uint256 = self._get_D_mem(vp_rate, old_balances, amp) - for i in range(N_COINS): - new_balances[i] -= _amounts[i] - D1: uint256 = self._get_D_mem(vp_rate, new_balances, amp) - - fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - admin_fee: uint256 = self.admin_fee - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - 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(vp_rate, new_balances, amp) - - lp_token: address = self.lp_token - token_supply: uint256 = CurveToken(lp_token).totalSupply() - token_amount: uint256 = (D0 - D2) * token_supply / D0 - assert token_amount != 0 # dev: zero tokens burned - token_amount += 1 # In case of rounding errors - make it unfavorable for the "attacker" - assert token_amount <= _max_burn_amount, "Slippage screwed you" - - CurveToken(lp_token).burnFrom(msg.sender, token_amount) # dev: insufficient funds - for i in range(N_COINS): - if _amounts[i] != 0: - ERC20(self.coins[i]).transfer(msg.sender, _amounts[i]) - - log RemoveLiquidityImbalance(msg.sender, _amounts, fees, D1, token_supply - token_amount) - - return token_amount - - -@pure -@internal -def _get_y_D(A: uint256, i: int128, _xp: uint256[N_COINS], D: uint256) -> uint256: - """ - Calculate x[i] if one reduces D from being calculated for xp to D - - Done by solving quadratic equation iteratively. - x_1**2 + x_1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) - x_1**2 + b*x_1 = c - - x_1 = (x_1**2 + c) / (2*x_1 + b) - """ - # x in the input is converted to the same price/precision - - assert i >= 0 # dev: i below zero - assert i < N_COINS # dev: i above N_COINS - - Ann: uint256 = A * N_COINS - c: uint256 = D - S: uint256 = 0 - _x: uint256 = 0 - y_prev: uint256 = 0 - - for _i in range(N_COINS): - if _i != i: - _x = _xp[_i] - else: - continue - S += _x - c = c * D / (_x * N_COINS) - c = c * D * A_PRECISION / (Ann * N_COINS) - b: uint256 = S + D * A_PRECISION / Ann - 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: - return y - else: - if y_prev - y <= 1: - return y - raise - - -@view -@internal -def _calc_withdraw_one_coin(_token_amount: uint256, i: int128, _vp_rate: uint256) -> (uint256, uint256, uint256): - # First, need to calculate - # * Get current D - # * Solve Eqn against y_i for D - _token_amount - amp: uint256 = self._A() - xp: uint256[N_COINS] = self._xp(_vp_rate) - D0: uint256 = self._get_D(xp, amp) - - total_supply: uint256 = CurveToken(self.lp_token).totalSupply() - D1: uint256 = D0 - _token_amount * D0 / total_supply - new_y: uint256 = self._get_y_D(amp, i, xp, D1) - - fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = _vp_rate - - xp_reduced: uint256[N_COINS] = xp - dy_0: uint256 = (xp[i] - new_y) * PRECISION / rates[i] # w/o fees - - for j in range(N_COINS): - dx_expected: uint256 = 0 - if j == i: - dx_expected = xp[j] * D1 / D0 - new_y - else: - dx_expected = xp[j] - xp[j] * D1 / D0 - xp_reduced[j] -= fee * dx_expected / FEE_DENOMINATOR - - dy: uint256 = xp_reduced[i] - self._get_y_D(amp, i, xp_reduced, D1) - dy = (dy - 1) * PRECISION / rates[i] # Withdraw less to account for rounding errors - - return dy, dy_0 - dy, total_supply - - -@view -@external -def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - """ - @notice Calculate the amount received when withdrawing a single coin - @param _token_amount Amount of LP tokens to burn in the withdrawal - @param i Index value of the coin to withdraw - @return Amount of coin received - """ - vp_rate: uint256 = self._vp_rate_ro() - return self._calc_withdraw_one_coin(_token_amount, i, vp_rate)[0] - - -@external -@nonreentrant('lock') -def remove_liquidity_one_coin(_token_amount: uint256, i: int128, _min_amount: uint256) -> uint256: - """ - @notice Withdraw a single coin from the pool - @param _token_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 coin to receive - @return Amount of coin received - """ - assert not self.is_killed # dev: is killed - - vp_rate: uint256 = self._vp_rate() - dy: uint256 = 0 - dy_fee: uint256 = 0 - total_supply: uint256 = 0 - dy, dy_fee, total_supply = self._calc_withdraw_one_coin(_token_amount, i, vp_rate) - assert dy >= _min_amount, "Not enough coins removed" - - self.balances[i] -= (dy + dy_fee * self.admin_fee / FEE_DENOMINATOR) - CurveToken(self.lp_token).burnFrom(msg.sender, _token_amount) # dev: insufficient funds - - ERC20(self.coins[i]).transfer(msg.sender, dy) - - log RemoveLiquidityOne(msg.sender, _token_amount, dy, total_supply - _token_amount) - - return dy - - -### Admin functions ### -@external -def ramp_A(_future_A: uint256, _future_time: uint256): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.initial_A_time + MIN_RAMP_TIME - assert _future_time >= block.timestamp + MIN_RAMP_TIME # dev: insufficient time - - initial_A: uint256 = self._A() - future_A_p: uint256 = _future_A * A_PRECISION - - assert _future_A > 0 and _future_A < MAX_A - if future_A_p < initial_A: - assert future_A_p * MAX_A_CHANGE >= initial_A - else: - assert future_A_p <= initial_A * MAX_A_CHANGE - - self.initial_A = initial_A - self.future_A = future_A_p - self.initial_A_time = block.timestamp - self.future_A_time = _future_time - - log RampA(initial_A, future_A_p, block.timestamp, _future_time) - - -@external -def stop_ramp_A(): - assert msg.sender == self.owner # dev: only owner - - current_A: uint256 = self._A() - self.initial_A = current_A - self.future_A = current_A - self.initial_A_time = block.timestamp - self.future_A_time = block.timestamp - # now (block.timestamp < t1) is always False, so we return saved A - - log StopRampA(current_A, block.timestamp) - - -@external -def commit_new_fee(_new_fee: uint256, _new_admin_fee: uint256): - assert msg.sender == self.owner # dev: only owner - assert self.admin_actions_deadline == 0 # dev: active action - assert _new_fee <= MAX_FEE # dev: fee exceeds maximum - assert _new_admin_fee <= MAX_ADMIN_FEE # dev: admin fee exceeds maximum - - deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.admin_actions_deadline = deadline - self.future_fee = _new_fee - self.future_admin_fee = _new_admin_fee - - log CommitNewFee(deadline, _new_fee, _new_admin_fee) - - -@external -def apply_new_fee(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.admin_actions_deadline # dev: insufficient time - assert self.admin_actions_deadline != 0 # dev: no active action - - self.admin_actions_deadline = 0 - fee: uint256 = self.future_fee - admin_fee: uint256 = self.future_admin_fee - self.fee = fee - self.admin_fee = admin_fee - - log NewFee(fee, admin_fee) - - -@external -def revert_new_parameters(): - assert msg.sender == self.owner # dev: only owner - - self.admin_actions_deadline = 0 - - -@external -def commit_transfer_ownership(_owner: address): - assert msg.sender == self.owner # dev: only owner - assert self.transfer_ownership_deadline == 0 # dev: active transfer - - deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.transfer_ownership_deadline = deadline - self.future_owner = _owner - - log CommitNewAdmin(deadline, _owner) - - -@external -def apply_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.transfer_ownership_deadline # dev: insufficient time - assert self.transfer_ownership_deadline != 0 # dev: no active transfer - - self.transfer_ownership_deadline = 0 - owner: address = self.future_owner - self.owner = owner - - log NewAdmin(owner) - - -@external -def revert_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - - self.transfer_ownership_deadline = 0 - - -@view -@external -def admin_balances(i: uint256) -> uint256: - return ERC20(self.coins[i]).balanceOf(self) - self.balances[i] - - -@external -def withdraw_admin_fees(): - assert msg.sender == self.owner # dev: only owner - - for i in range(N_COINS): - coin: address = self.coins[i] - value: uint256 = ERC20(coin).balanceOf(self) - self.balances[i] - if value > 0: - ERC20(coin).transfer(msg.sender, value) - -@external -def donate_admin_fees(): - assert msg.sender == self.owner # dev: only owner - for i in range(N_COINS): - self.balances[i] = ERC20(self.coins[i]).balanceOf(self) - - -@external -def kill_me(): - assert msg.sender == self.owner # dev: only owner - assert self.kill_deadline > block.timestamp # dev: deadline has passed - self.is_killed = True - - -@external -def unkill_me(): - assert msg.sender == self.owner # dev: only owner - self.is_killed = False diff --git a/contracts/pools/usdp/pooldata.json b/contracts/pools/usdp/pooldata.json deleted file mode 100644 index bdf90800..00000000 --- a/contracts/pools/usdp/pooldata.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "base_pool": "3pool", - "pool_types": ["meta"], - "lp_contract": "CurveTokenV3", - "swap_address": "0x42d7025938bEc20B69cBae5A77421082407f053A", - "lp_token_address": "0x7Eb40E450b9655f4B3cC4259BCC731c63ff55ae6", - "zap_address": "0x3c8cAee4E09296800f8D29A68Fa3837e2dae4940", - "gauge_addresses": ["0x055be5DDB7A925BfEF3417FC157f53CA77cA7222"], - "lp_constructor": { - "symbol": "usdp3CRV", - "name": "Curve.fi USDP/3Crv" - }, - "swap_constructor": { - "_A": 100, - "_fee": 4000000, - "_admin_fee": 5000000000 - }, - "coins": [ - { - "name": "USDP", - "decimals": 18, - "tethered": false, - "underlying_address": "0x1456688345527bE1f37E9e627DA0837D6f08C925" - }, - { - "name": "3CRV", - "decimals": 18, - "base_pool_token": true, - "underlying_address": "0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490" - } - ] -} diff --git a/contracts/pools/usdt/DepositUSDT.vy b/contracts/pools/usdt/DepositUSDT.vy deleted file mode 100644 index a09d5a79..00000000 --- a/contracts/pools/usdt/DepositUSDT.vy +++ /dev/null @@ -1,374 +0,0 @@ -# @version 0.1.0b16 - -# A "zap" to deposit/withdraw Curve contract without too many transactions -# (c) Curve.Fi, 2020 -from vyper.interfaces import ERC20 - -# External Contracts -contract cERC20: - def totalSupply() -> uint256: constant - def allowance(_owner: address, _spender: address) -> uint256: constant - def transfer(_to: address, _value: uint256) -> bool: modifying - def transferFrom(_from: address, _to: address, _value: uint256) -> bool: modifying - def approve(_spender: address, _value: uint256) -> bool: modifying - def burn(_value: uint256): modifying - def burnFrom(_to: address, _value: uint256): modifying - def name() -> string[64]: constant - def symbol() -> string[32]: constant - def decimals() -> uint256: constant - def balanceOf(arg0: address) -> uint256: constant - def mint(mintAmount: uint256) -> uint256: modifying - def redeem(redeemTokens: uint256) -> uint256: modifying - def redeemUnderlying(redeemAmount: uint256) -> uint256: modifying - def exchangeRateStored() -> uint256: constant - def exchangeRateCurrent() -> uint256: modifying - def supplyRatePerBlock() -> uint256: constant - def accrualBlockNumber() -> uint256: constant - - - - -# Tether transfer-only ABI -contract USDT: - def transfer(_to: address, _value: uint256): modifying - def transferFrom(_from: address, _to: address, _value: uint256): modifying - - -contract Curve: - def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256): modifying - def remove_liquidity(_amount: uint256, min_amounts: uint256[N_COINS]): modifying - def remove_liquidity_imbalance(amounts: uint256[N_COINS], max_burn_amount: uint256): modifying - def balances(i: int128) -> uint256: constant - def A() -> uint256: constant - def fee() -> uint256: constant - def owner() -> address: constant - - -N_COINS: constant(int128) = 3 -TETHERED: constant(bool[N_COINS]) = [False, False, True] -USE_LENDING: constant(bool[N_COINS]) = [True, True, False] -ZERO256: constant(uint256) = 0 # This hack is really bad XXX -ZEROS: constant(uint256[N_COINS]) = [ZERO256, ZERO256, ZERO256] # <- change -LENDING_PRECISION: constant(uint256) = 10 ** 18 -PRECISION: constant(uint256) = 10 ** 18 -PRECISION_MUL: constant(uint256[N_COINS]) = [convert(1, uint256), convert(1000000000000, uint256), convert(1000000000000, uint256)] -FEE_DENOMINATOR: constant(uint256) = 10 ** 10 -FEE_IMPRECISION: constant(uint256) = 25 * 10 ** 8 # % of the fee - -coins: public(address[N_COINS]) -underlying_coins: public(address[N_COINS]) -curve: public(address) -token: public(address) - - -@public -def __init__(_coins: address[N_COINS], _underlying_coins: address[N_COINS], - _curve: address, _token: address): - self.coins = _coins - self.underlying_coins = _underlying_coins - self.curve = _curve - self.token = _token - - -@public -@nonreentrant('lock') -def add_liquidity(uamounts: uint256[N_COINS], min_mint_amount: uint256): - use_lending: bool[N_COINS] = USE_LENDING - tethered: bool[N_COINS] = TETHERED - amounts: uint256[N_COINS] = ZEROS - - for i in range(N_COINS): - uamount: uint256 = uamounts[i] - - if uamount > 0: - # Transfer the underlying coin from owner - if tethered[i]: - USDT(self.underlying_coins[i]).transferFrom( - msg.sender, self, uamount) - else: - assert_modifiable(ERC20(self.underlying_coins[i])\ - .transferFrom(msg.sender, self, uamount)) - - # Mint if needed - if use_lending[i]: - ERC20(self.underlying_coins[i]).approve(self.coins[i], uamount) - ok: uint256 = cERC20(self.coins[i]).mint(uamount) - if ok > 0: - raise "Could not mint coin" - amounts[i] = cERC20(self.coins[i]).balanceOf(self) - ERC20(self.coins[i]).approve(self.curve, amounts[i]) - else: - amounts[i] = uamount - ERC20(self.underlying_coins[i]).approve(self.curve, uamount) - - Curve(self.curve).add_liquidity(amounts, min_mint_amount) - - tokens: uint256 = ERC20(self.token).balanceOf(self) - assert_modifiable(ERC20(self.token).transfer(msg.sender, tokens)) - - -@private -def _send_all(_addr: address, min_uamounts: uint256[N_COINS], one: int128): - use_lending: bool[N_COINS] = USE_LENDING - tethered: bool[N_COINS] = TETHERED - - for i in range(N_COINS): - if (one < 0) or (i == one): - if use_lending[i]: - _coin: address = self.coins[i] - _balance: uint256 = cERC20(_coin).balanceOf(self) - if _balance == 0: # Do nothing if there are 0 coins - continue - ok: uint256 = cERC20(_coin).redeem(_balance) - if ok > 0: - raise "Could not redeem coin" - - _ucoin: address = self.underlying_coins[i] - _uamount: uint256 = ERC20(_ucoin).balanceOf(self) - assert _uamount >= min_uamounts[i], "Not enough coins withdrawn" - - # Send only if we have something to send - if _uamount >= 0: - if tethered[i]: - USDT(_ucoin).transfer(_addr, _uamount) - else: - assert_modifiable(ERC20(_ucoin).transfer(_addr, _uamount)) - - -@public -@nonreentrant('lock') -def remove_liquidity(_amount: uint256, min_uamounts: uint256[N_COINS]): - zeros: uint256[N_COINS] = ZEROS - - assert_modifiable(ERC20(self.token).transferFrom(msg.sender, self, _amount)) - Curve(self.curve).remove_liquidity(_amount, zeros) - - self._send_all(msg.sender, min_uamounts, -1) - - -@public -@nonreentrant('lock') -def remove_liquidity_imbalance(uamounts: uint256[N_COINS], max_burn_amount: uint256): - """ - Get max_burn_amount in, remove requested liquidity and transfer back what is left - """ - use_lending: bool[N_COINS] = USE_LENDING - tethered: bool[N_COINS] = TETHERED - _token: address = self.token - - amounts: uint256[N_COINS] = uamounts - for i in range(N_COINS): - if use_lending[i] and amounts[i] > 0: - rate: uint256 = cERC20(self.coins[i]).exchangeRateCurrent() - amounts[i] = amounts[i] * LENDING_PRECISION / rate - # if not use_lending - all good already - - # Transfrer max tokens in - _tokens: uint256 = ERC20(_token).balanceOf(msg.sender) - if _tokens > max_burn_amount: - _tokens = max_burn_amount - assert_modifiable(ERC20(_token).transferFrom(msg.sender, self, _tokens)) - - Curve(self.curve).remove_liquidity_imbalance(amounts, max_burn_amount) - - # Transfer unused tokens back - _tokens = ERC20(_token).balanceOf(self) - assert_modifiable(ERC20(_token).transfer(msg.sender, _tokens)) - - # Unwrap and transfer all the coins we've got - self._send_all(msg.sender, ZEROS, -1) - - -@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(A: uint256, 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 = 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_y(A: uint256, i: int128, _xp: uint256[N_COINS], D: uint256) -> uint256: - """ - Calculate x[i] if one reduces D from being calculated for _xp to D - - Done by solving quadratic equation iteratively. - x_1**2 + x1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) - x_1**2 + b*x_1 = c - - x_1 = (x_1**2 + c) / (2*x_1 + b) - """ - # x in the input is converted to the same price/precision - - assert (i >= 0) and (i < N_COINS) - - c: uint256 = D - S_: uint256 = 0 - Ann: uint256 = A * N_COINS - - _x: uint256 = 0 - for _i in range(N_COINS): - if _i != i: - _x = _xp[_i] - else: - continue - S_ += _x - c = c * D / (_x * N_COINS) - c = c * D / (Ann * N_COINS) - b: uint256 = S_ + D / Ann - 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 - - -@private -@constant -def _calc_withdraw_one_coin(_token_amount: uint256, i: int128, rates: uint256[N_COINS]) -> uint256: - # First, need to calculate - # * Get current D - # * Solve Eqn against y_i for D - _token_amount - use_lending: bool[N_COINS] = USE_LENDING - # tethered: bool[N_COINS] = TETHERED - crv: address = self.curve - A: uint256 = Curve(crv).A() - fee: uint256 = Curve(crv).fee() * N_COINS / (4 * (N_COINS - 1)) - fee += fee * FEE_IMPRECISION / FEE_DENOMINATOR # Overcharge to account for imprecision - precisions: uint256[N_COINS] = PRECISION_MUL - total_supply: uint256 = ERC20(self.token).totalSupply() - - xp: uint256[N_COINS] = PRECISION_MUL - S: uint256 = 0 - for j in range(N_COINS): - xp[j] *= Curve(crv).balances(j) - if use_lending[j]: - # Use stored rate b/c we have imprecision anyway - xp[j] = xp[j] * rates[j] / LENDING_PRECISION - S += xp[j] - # if not use_lending - all good already - - D0: uint256 = self.get_D(A, xp) - D1: uint256 = D0 - _token_amount * D0 / total_supply - xp_reduced: uint256[N_COINS] = xp - - # xp = xp - fee * | xp * D1 / D0 - (xp - S * dD / D0 * (0, ... 1, ..0))| - for j in range(N_COINS): - dx_expected: uint256 = 0 - b_ideal: uint256 = xp[j] * D1 / D0 - b_expected: uint256 = xp[j] - if j == i: - b_expected -= S * (D0 - D1) / D0 - if b_ideal >= b_expected: - dx_expected = (b_ideal - b_expected) - else: - dx_expected = (b_expected - b_ideal) - xp_reduced[j] -= fee * dx_expected / FEE_DENOMINATOR - - dy: uint256 = xp_reduced[i] - self.get_y(A, i, xp_reduced, D1) - dy = dy / precisions[i] - - return dy - - -@public -@constant -def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - rates: uint256[N_COINS] = ZEROS - use_lending: bool[N_COINS] = USE_LENDING - - for j in range(N_COINS): - if use_lending[j]: - rates[j] = cERC20(self.coins[j]).exchangeRateStored() - else: - rates[j] = 10 ** 18 - - return self._calc_withdraw_one_coin(_token_amount, i, rates) - - -@public -@nonreentrant('lock') -def remove_liquidity_one_coin(_token_amount: uint256, i: int128, min_uamount: uint256, donate_dust: bool = False): - """ - Remove _amount of liquidity all in a form of coin i - """ - use_lending: bool[N_COINS] = USE_LENDING - rates: uint256[N_COINS] = ZEROS - _token: address = self.token - - for j in range(N_COINS): - if use_lending[j]: - rates[j] = cERC20(self.coins[j]).exchangeRateCurrent() - else: - rates[j] = LENDING_PRECISION - - dy: uint256 = self._calc_withdraw_one_coin(_token_amount, i, rates) - assert dy >= min_uamount, "Not enough coins removed" - - assert_modifiable( - ERC20(self.token).transferFrom(msg.sender, self, _token_amount)) - - amounts: uint256[N_COINS] = ZEROS - amounts[i] = dy * LENDING_PRECISION / rates[i] - token_amount_before: uint256 = ERC20(_token).balanceOf(self) - Curve(self.curve).remove_liquidity_imbalance(amounts, _token_amount) - - # Unwrap and transfer all the coins we've got - self._send_all(msg.sender, ZEROS, i) - - if not donate_dust: - # Transfer unused tokens back - token_amount_after: uint256 = ERC20(_token).balanceOf(self) - if token_amount_after > token_amount_before: - assert_modifiable(ERC20(_token).transfer( - msg.sender, token_amount_after - token_amount_before) - ) - - -@public -@nonreentrant('lock') -def withdraw_donated_dust(): - owner: address = Curve(self.curve).owner() - assert msg.sender == owner - - _token: address = self.token - assert_modifiable( - ERC20(_token).transfer(owner, ERC20(_token).balanceOf(self))) diff --git a/contracts/pools/usdt/README.md b/contracts/pools/usdt/README.md deleted file mode 100644 index 919853b5..00000000 --- a/contracts/pools/usdt/README.md +++ /dev/null @@ -1,32 +0,0 @@ -# curve-contract/contracts/pools/usdt - -[Curve USDT pool](https://www.curve.fi/usdt), with lending on [Compound](https://compound.finance/). - -**NOTE**: This pool was deprecated. It has very little liquidity and trade volumes. - -## Contracts - -* [`DepositUSDT`](DepositUSDT.vy): Depositor contract, used to wrap underlying tokens prior to depositing them into the pool -* [`StableSwapUSDT`](StableSwapUSDT.vy): Curve stablecoin AMM contract - -## Deployments - -* [`CurveContractV1`](../../tokens/CurveTokenV1.vy): [0x9fC689CCaDa600B6DF723D9E47D84d76664a1F23](https://etherscan.io/address/0x9fC689CCaDa600B6DF723D9E47D84d76664a1F23) -* [`DepositUSDT`](DepositUSDT.vy): [0xac795d2c97e60df6a99ff1c814727302fd747a80](https://etherscan.io/address/0xac795d2c97e60df6a99ff1c814727302fd747a80) -* [`LiquidityGauge`](../../gauges/LiquidityGauge.vy): [0xBC89cd85491d81C6AD2954E6d0362Ee29fCa8F53](https://etherscan.io/address/0xbc89cd85491d81c6ad2954e6d0362ee29fca8f53) -* [`StableSwapUSDT`](StableSwapUSDT.vy): [0x52EA46506B9CC5Ef470C5bf89f17Dc28bB35D85C](https://etherscan.io/address/0x52EA46506B9CC5Ef470C5bf89f17Dc28bB35D85C) - -## Stablecoins - -Curve USDT pool supports swaps between the following stablecoins: - -### Wrapped - -* `cDAI`: [0x5d3a536e4d6dbd6114cc1ead35777bab948e3643](https://etherscan.io/token/0x5d3a536e4d6dbd6114cc1ead35777bab948e3643) -* `cUSDC`: [0x39aa39c021dfbae8fac545936693ac917d5e7563](https://etherscan.io/token/0x39aa39c021dfbae8fac545936693ac917d5e7563) - -### Underlying - -* `DAI`: [0x6b175474e89094c44da98b954eedeac495271d0f](https://etherscan.io/token/0x6b175474e89094c44da98b954eedeac495271d0f) -* `USDC`: [0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48](https://etherscan.io/token/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48) -* `USDT`: [0xdac17f958d2ee523a2206206994597c13d831ec7](https://etherscan.io/address/0xdac17f958d2ee523a2206206994597c13d831ec7) diff --git a/contracts/pools/usdt/StableSwapUSDT.vy b/contracts/pools/usdt/StableSwapUSDT.vy deleted file mode 100644 index 6f140b7a..00000000 --- a/contracts/pools/usdt/StableSwapUSDT.vy +++ /dev/null @@ -1,677 +0,0 @@ -# @version 0.1.0b16 -# (c) Curve.Fi, 2020 - - -# External Contracts -contract ERC20m: - def totalSupply() -> uint256: constant - def allowance(_owner: address, _spender: address) -> uint256: constant - def transfer(_to: address, _value: uint256) -> bool: modifying - def transferFrom(_from: address, _to: address, _value: uint256) -> bool: modifying - def approve(_spender: address, _value: uint256) -> bool: modifying - def mint(_to: address, _value: uint256): modifying - def burn(_value: uint256): modifying - def burnFrom(_to: address, _value: uint256): modifying - def name() -> string[64]: constant - def symbol() -> string[32]: constant - def decimals() -> uint256: constant - def balanceOf(arg0: address) -> uint256: constant - def set_minter(_minter: address): modifying - - - -# External Contracts -contract cERC20: - def totalSupply() -> uint256: constant - def allowance(_owner: address, _spender: address) -> uint256: constant - def transfer(_to: address, _value: uint256) -> bool: modifying - def transferFrom(_from: address, _to: address, _value: uint256) -> bool: modifying - def approve(_spender: address, _value: uint256) -> bool: modifying - def burn(_value: uint256): modifying - def burnFrom(_to: address, _value: uint256): modifying - def name() -> string[64]: constant - def symbol() -> string[32]: constant - def decimals() -> uint256: constant - def balanceOf(arg0: address) -> uint256: constant - def mint(mintAmount: uint256) -> uint256: modifying - def redeem(redeemTokens: uint256) -> uint256: modifying - def redeemUnderlying(redeemAmount: uint256) -> uint256: modifying - def exchangeRateStored() -> uint256: constant - def exchangeRateCurrent() -> uint256: modifying - def supplyRatePerBlock() -> uint256: constant - def accrualBlockNumber() -> uint256: constant - - -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 - - -# This can (and needs to) be changed at compile time -N_COINS: constant(int128) = 3 # <- change - -ZERO256: constant(uint256) = 0 # This hack is really bad XXX -ZEROS: constant(uint256[N_COINS]) = [ZERO256, ZERO256, ZERO256] # <- change - -USE_LENDING: constant(bool[N_COINS]) = [True, True, False] -TETHERED: constant(bool[N_COINS]) = [False, False, True] - -FEE_DENOMINATOR: constant(uint256) = 10 ** 10 -PRECISION: constant(uint256) = 10 ** 18 # The precision to convert to -PRECISION_MUL: constant(uint256[N_COINS]) = [convert(1, uint256), convert(1000000000000, uint256), convert(1000000000000, uint256)] -# 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]) -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 - -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], - _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.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 _stored_rates() -> uint256[N_COINS]: - # exchangeRateStored * (1 + supplyRatePerBlock * (getBlockNumber - accrualBlockNumber) / 1e18) - result: uint256[N_COINS] = PRECISION_MUL - use_lending: bool[N_COINS] = USE_LENDING - for i in range(N_COINS): - rate: uint256 = PRECISION # Used with no lending - if use_lending[i]: - rate = cERC20(self.coins[i]).exchangeRateStored() - supply_rate: uint256 = cERC20(self.coins[i]).supplyRatePerBlock() - old_block: uint256 = cERC20(self.coins[i]).accrualBlockNumber() - rate += rate * supply_rate * (block.number - old_block) / PRECISION - result[i] *= rate - return result - - -@private -def _current_rates() -> uint256[N_COINS]: - result: uint256[N_COINS] = PRECISION_MUL - use_lending: bool[N_COINS] = USE_LENDING - for i in range(N_COINS): - rate: uint256 = PRECISION # Used with no lending - if use_lending[i]: - rate = cERC20(self.coins[i]).exchangeRateCurrent() - 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._stored_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._stored_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( - cERC20(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._stored_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._stored_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._stored_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._stored_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(cERC20(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(cERC20(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 - - ok: uint256 = 0 - 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) - ok = cERC20(self.coins[i]).mint(dx) - if ok > 0: - raise "Could not mint coin" - if use_lending[j]: - ok = cERC20(self.coins[j]).redeem(dy_) - if ok > 0: - raise "Could not redeem coin" - 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(cERC20(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(cERC20(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) - - -### 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 - - _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 = cERC20(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(cERC20(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 diff --git a/contracts/pools/usdt/pooldata.json b/contracts/pools/usdt/pooldata.json deleted file mode 100644 index 7b55eeab..00000000 --- a/contracts/pools/usdt/pooldata.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "lp_contract": "CurveTokenV1", - "wrapped_contract": "cERC20", - "swap_address": "0x52EA46506B9CC5Ef470C5bf89f17Dc28bB35D85C", - "lp_token_address": "0x9fC689CCaDa600B6DF723D9E47D84d76664a1F23", - "zap_address": "0xac795d2c97e60df6a99ff1c814727302fd747a80", - "gauge_addresses": ["0xBC89cd85491d81C6AD2954E6d0362Ee29fCa8F53"], - "lp_constructor": { - "name": "Curve.fi cDAI/cUSDC/USDT", - "symbol": "cDAI+cUSDC+USDT" - }, - "coins": [ - { - "name": "cDAI", - "underlying_name": "DAI", - "decimals": 18, - "tethered": false, - "wrapped_decimals": 8, - "underlying_address": "0x6b175474e89094c44da98b954eedeac495271d0f", - "wrapped_address": "0x5d3a536e4d6dbd6114cc1ead35777bab948e3643" - }, - { - "name": "cUSDC", - "underlying_name": "USDC", - "decimals": 6, - "tethered": false, - "wrapped_decimals": 8, - "underlying_address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", - "wrapped_address": "0x39aa39c021dfbae8fac545936693ac917d5e7563" - }, - { - "name": "USDT", - "decimals": 6, - "tethered": true, - "underlying_address": "0xdac17f958d2ee523a2206206994597c13d831ec7" - } - ] -} diff --git a/contracts/pools/ust/DepositUST.vy b/contracts/pools/ust/DepositUST.vy deleted file mode 100644 index 6a5e0534..00000000 --- a/contracts/pools/ust/DepositUST.vy +++ /dev/null @@ -1,376 +0,0 @@ -# @version 0.2.8 -""" -@title "Zap" Depositer for Curve USDK pool -@author Curve.Fi -@license Copyright (c) Curve.Fi, 2020 - all rights reserved -""" - -from vyper.interfaces import ERC20 - - -interface CurveMeta: - def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256) -> 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) -> 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 base_pool() -> address: 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 - -# An asset which may have a transfer fee (USDT) -FEE_ASSET: constant(address) = 0xdAC17F958D2ee523a2206206994597C13D831ec7 - -FEE_DENOMINATOR: constant(uint256) = 10 ** 10 -FEE_IMPRECISION: constant(uint256) = 100 * 10 ** 8 # % of the fee - - -pool: public(address) -token: public(address) -base_pool: public(address) - -coins: public(address[N_COINS]) -base_coins: public(address[BASE_N_COINS]) - - -@external -def __init__(_pool: address, _token: address): - """ - @notice Contract constructor - @param _pool Metapool address - @param _token Pool LP token address - """ - self.pool = _pool - self.token = _token - _base_pool: address = CurveMeta(_pool).base_pool() - self.base_pool = _base_pool - - for i in range(N_COINS): - coin: address = CurveMeta(_pool).coins(convert(i, uint256)) - self.coins[i] = coin - # approve coins for infinite transfers - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("approve(address,uint256)"), - convert(_pool, bytes32), - convert(MAX_UINT256, bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) - - for i in range(BASE_N_COINS): - coin: address = CurveBase(_base_pool).coins(convert(i, uint256)) - self.base_coins[i] = coin - # approve underlying coins for infinite transfers - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("approve(address,uint256)"), - convert(_base_pool, bytes32), - convert(MAX_UINT256, bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) - - -@external -def add_liquidity(amounts: uint256[N_ALL_COINS], min_mint_amount: uint256) -> uint256: - """ - @notice Wrap underlying coins and deposit them in the pool - @param amounts List of amounts of underlying coins to deposit - @param min_mint_amount Minimum amount of LP tokens to mint from the deposit - @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 - - # Transfer all coins in - for i in range(N_ALL_COINS): - amount: uint256 = amounts[i] - if amount == 0: - continue - coin: address = ZERO_ADDRESS - if i < MAX_COIN: - coin = self.coins[i] - meta_amounts[i] = amount - else: - x: int128 = i - MAX_COIN - coin = self.base_coins[x] - base_amounts[x] = amount - deposit_base = True - # "safeTransferFrom" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transferFrom(address,address,uint256)"), - convert(msg.sender, bytes32), - convert(self, bytes32), - convert(amount, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransferFrom" - # Handle potential Tether fees - if coin == FEE_ASSET: - amount = ERC20(FEE_ASSET).balanceOf(self) - if i < MAX_COIN: - meta_amounts[i] = amount - else: - base_amounts[i - MAX_COIN] = amount - - # Deposit to the base pool - if deposit_base: - CurveBase(self.base_pool).add_liquidity(base_amounts, 0) - meta_amounts[MAX_COIN] = ERC20(self.coins[MAX_COIN]).balanceOf(self) - - # Deposit to the meta pool - CurveMeta(self.pool).add_liquidity(meta_amounts, min_mint_amount) - - # Transfer meta token back - _lp_token: address = self.token - _lp_amount: uint256 = ERC20(_lp_token).balanceOf(self) - assert ERC20(_lp_token).transfer(msg.sender, _lp_amount) - - return _lp_amount - - -@external -def remove_liquidity(_amount: uint256, min_amounts: uint256[N_ALL_COINS]) -> uint256[N_ALL_COINS]: - """ - @notice Withdraw and unwrap coins from the pool - @dev Withdrawal amounts are based on current deposit ratios - @param _amount Quantity of LP tokens to burn in the withdrawal - @param min_amounts Minimum amounts of underlying coins to receive - @return List of amounts of underlying coins that were withdrawn - """ - _token: address = self.token - assert ERC20(_token).transferFrom(msg.sender, self, _amount) - - min_amounts_meta: uint256[N_COINS] = empty(uint256[N_COINS]) - 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 - for i in range(MAX_COIN): - min_amounts_meta[i] = min_amounts[i] - CurveMeta(self.pool).remove_liquidity(_amount, min_amounts_meta) - - # Withdraw from base - _base_amount: uint256 = ERC20(self.coins[MAX_COIN]).balanceOf(self) - for i in range(BASE_N_COINS): - min_amounts_base[i] = min_amounts[MAX_COIN+i] - CurveBase(self.base_pool).remove_liquidity(_base_amount, min_amounts_base) - - # Transfer all coins out - for i in range(N_ALL_COINS): - coin: address = ZERO_ADDRESS - if i < MAX_COIN: - coin = self.coins[i] - else: - coin = self.base_coins[i - MAX_COIN] - amounts[i] = ERC20(coin).balanceOf(self) - # "safeTransfer" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(amounts[i], bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - return amounts - - -@external -def remove_liquidity_one_coin(_token_amount: uint256, i: int128, _min_amount: uint256) -> uint256: - """ - @notice Withdraw and unwrap a single coin from the pool - @param _token_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 - @return Amount of underlying coin received - """ - assert ERC20(self.token).transferFrom(msg.sender, self, _token_amount) - - coin: address = ZERO_ADDRESS - if i < MAX_COIN: - coin = self.coins[i] - # Withdraw a metapool coin - CurveMeta(self.pool).remove_liquidity_one_coin(_token_amount, i, _min_amount) - else: - coin = self.base_coins[i - MAX_COIN] - # Withdraw a base pool coin - CurveMeta(self.pool).remove_liquidity_one_coin(_token_amount, MAX_COIN, 0) - CurveBase(self.base_pool).remove_liquidity_one_coin( - ERC20(self.coins[MAX_COIN]).balanceOf(self), i-MAX_COIN, _min_amount - ) - - # Tranfer the coin out - coin_amount: uint256 = ERC20(coin).balanceOf(self) - # "safeTransfer" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(coin_amount, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - return coin_amount - - -@external -def remove_liquidity_imbalance(amounts: uint256[N_ALL_COINS], max_burn_amount: uint256) -> uint256: - """ - @notice Withdraw coins from the pool in an imbalanced amount - @param amounts List of amounts of underlying coins to withdraw - @param max_burn_amount Maximum amount of LP token to burn in the withdrawal. - This value cannot exceed the caller's LP token balance. - @return Actual amount of the LP token burned in the withdrawal - """ - _base_pool: address = self.base_pool - _meta_pool: address = self.pool - _base_coins: address[BASE_N_COINS] = self.base_coins - _meta_coins: address[N_COINS] = self.coins - _lp_token: address = self.token - - 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 - assert ERC20(_lp_token).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]) - leftover_amounts: uint256[N_COINS] = empty(uint256[N_COINS]) - - # Prepare quantities - for i in range(MAX_COIN): - amounts_meta[i] = amounts[i] - - for i in range(BASE_N_COINS): - amount: uint256 = amounts[MAX_COIN + i] - if amount != 0: - amounts_base[i] = amount - withdraw_base = True - - if withdraw_base: - amounts_meta[MAX_COIN] = CurveBase(self.base_pool).calc_token_amount(amounts_base, False) - amounts_meta[MAX_COIN] += amounts_meta[MAX_COIN] * fee / FEE_DENOMINATOR + 1 - - # Remove liquidity and deposit leftovers back - CurveMeta(_meta_pool).remove_liquidity_imbalance(amounts_meta, max_burn_amount) - if withdraw_base: - CurveBase(_base_pool).remove_liquidity_imbalance(amounts_base, amounts_meta[MAX_COIN]) - leftover_amounts[MAX_COIN] = ERC20(_meta_coins[MAX_COIN]).balanceOf(self) - if leftover_amounts[MAX_COIN] > 0: - CurveMeta(_meta_pool).add_liquidity(leftover_amounts, 0) - - # Transfer all coins out - for i in range(N_ALL_COINS): - coin: address = ZERO_ADDRESS - amount: uint256 = 0 - if i < MAX_COIN: - coin = _meta_coins[i] - amount = amounts_meta[i] - else: - coin = _base_coins[i - MAX_COIN] - amount = amounts_base[i - MAX_COIN] - # "safeTransfer" which works for ERC20s which return bool or not - if amount > 0: - _response: Bytes[32] = raw_call( - coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(amount, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - # Transfer the leftover LP token out - leftover: uint256 = ERC20(_lp_token).balanceOf(self) - if leftover > 0: - assert ERC20(_lp_token).transfer(msg.sender, leftover) - - return max_burn_amount - leftover - - -@view -@external -def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - """ - @notice Calculate the amount received when withdrawing and unwrapping a single coin - @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(self.pool).calc_withdraw_one_coin(_token_amount, i) - else: - _base_tokens: uint256 = CurveMeta(self.pool).calc_withdraw_one_coin(_token_amount, MAX_COIN) - return CurveBase(self.base_pool).calc_withdraw_one_coin(_base_tokens, i-MAX_COIN) - - -@view -@external -def calc_token_amount(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 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]) - - for i in range(MAX_COIN): - meta_amounts[i] = amounts[i] - - for i in range(BASE_N_COINS): - base_amounts[i] = amounts[i + MAX_COIN] - - _base_tokens: uint256 = CurveBase(self.base_pool).calc_token_amount(base_amounts, is_deposit) - meta_amounts[MAX_COIN] = _base_tokens - - return CurveMeta(self.pool).calc_token_amount(meta_amounts, is_deposit) diff --git a/contracts/pools/ust/README.md b/contracts/pools/ust/README.md deleted file mode 100644 index f778bd54..00000000 --- a/contracts/pools/ust/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# curve-contract/contracts/pools/ust - -[Curve UST metapool](https://www.curve.fi/ust), allowing swaps via the Curve [tri-pool](../3pool). - -## Contracts - -* [`DepositUST`](DepositUST.vy): Depositor contract, used to wrap underlying tokens prior to depositing them into the pool -* [`StableSwapUST`](StableSwapUST.vy): Curve stablecoin AMM contract - -## Deployments - -* [`CurveContractV3`](../../tokens/CurveTokenV3.vy): [0x94e131324b6054c0D789b190b2dAC504e4361b53](https://etherscan.io/address/0x94e131324b6054c0D789b190b2dAC504e4361b53) -* [`DepositUST`](DepositUST.vy): [0xB0a0716841F2Fc03fbA72A891B8Bb13584F52F2d](https://etherscan.io/address/0xB0a0716841F2Fc03fbA72A891B8Bb13584F52F2d) -* [`LiquidityGaugeV2`](https://github.com/curvefi/curve-dao-contracts/blob/master/contracts/gauges/LiquidityGaugeV2.vy): [0x3B7020743Bc2A4ca9EaF9D0722d42E20d6935855](https://etherscan.io/address/0x3B7020743Bc2A4ca9EaF9D0722d42E20d6935855) -* [`StableSwapUST`](StableSwapUST.vy): [0x890f4e345B1dAED0367A877a1612f86A1f86985f](https://etherscan.io/address/0x890f4e345B1dAED0367A877a1612f86A1f86985f) - -## Stablecoins - -Curve UST metapool utilizes the supports swaps between the following assets: - -## Direct swaps - -Direct swaps are possible between UST and the Curve tri-pool LP token. - -* `UST`: [0xa47c8bf37f92aBed4A126BDA807A7b7498661acD](https://etherscan.io/address/0xa47c8bf37f92aBed4A126BDA807A7b7498661acD) -* `3CRV`: [0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490](https://etherscan.io/address/0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490) - -## Base Pool coins - -The tri-pool LP token may be wrapped or unwrapped to provide swaps between UST and the following stablecoins: - -* `DAI`: [0x6b175474e89094c44da98b954eedeac495271d0f](https://etherscan.io/address/0x6b175474e89094c44da98b954eedeac495271d0f) -* `USDC`: [0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48](https://etherscan.io/address/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48) -* `USDT`: [0xdac17f958d2ee523a2206206994597c13d831ec7](https://etherscan.io/address/0xdac17f958d2ee523a2206206994597c13d831ec7) diff --git a/contracts/pools/ust/StableSwapUST.vy b/contracts/pools/ust/StableSwapUST.vy deleted file mode 100644 index 832750ad..00000000 --- a/contracts/pools/ust/StableSwapUST.vy +++ /dev/null @@ -1,1081 +0,0 @@ -# @version 0.2.8 -""" -@title Curve UST Metapool -@author Curve.Fi -@license Copyright (c) Curve.Fi, 2020 - all rights reserved -@dev Utilizes 3Pool to allow swaps between UST / DAI / USDC / USDT -""" - -from vyper.interfaces import ERC20 - - -interface CurveToken: - def totalSupply() -> uint256: view - def mint(_to: address, _value: uint256) -> bool: nonpayable - def burnFrom(_to: address, _value: uint256) -> bool: nonpayable - -interface Curve: - def coins(i: uint256) -> address: view - def get_virtual_price() -> uint256: view - def calc_token_amount(amounts: uint256[BASE_N_COINS], deposit: bool) -> uint256: view - def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: view - def fee() -> uint256: view - def get_dy(i: int128, j: int128, dx: uint256) -> uint256: view - def get_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256: view - def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256): nonpayable - def add_liquidity(amounts: uint256[BASE_N_COINS], min_mint_amount: uint256): nonpayable - def remove_liquidity_one_coin(_token_amount: uint256, i: int128, min_amount: uint256): nonpayable - - -# Events -event TokenExchange: - buyer: indexed(address) - sold_id: int128 - tokens_sold: uint256 - bought_id: int128 - tokens_bought: uint256 - -event TokenExchangeUnderlying: - buyer: indexed(address) - sold_id: int128 - tokens_sold: uint256 - bought_id: int128 - tokens_bought: uint256 - -event AddLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event RemoveLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - token_supply: uint256 - -event RemoveLiquidityOne: - provider: indexed(address) - token_amount: uint256 - coin_amount: uint256 - token_supply: uint256 - -event RemoveLiquidityImbalance: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event CommitNewAdmin: - deadline: indexed(uint256) - admin: indexed(address) - -event NewAdmin: - admin: indexed(address) - -event CommitNewFee: - deadline: indexed(uint256) - fee: uint256 - admin_fee: uint256 - -event NewFee: - fee: uint256 - admin_fee: uint256 - -event RampA: - old_A: uint256 - new_A: uint256 - initial_time: uint256 - future_time: uint256 - -event StopRampA: - A: uint256 - t: uint256 - - -N_COINS: constant(int128) = 2 -MAX_COIN: constant(int128) = N_COINS - 1 - -FEE_DENOMINATOR: constant(uint256) = 10 ** 10 -PRECISION: constant(uint256) = 10 ** 18 # The precision to convert to -PRECISION_MUL: constant(uint256[N_COINS]) = [1, 1] -RATES: constant(uint256[N_COINS]) = [1000000000000000000, 1000000000000000000] - -BASE_N_COINS: constant(int128) = 3 - -# An asset which may have a transfer fee (USDT) -FEE_ASSET: constant(address) = 0xdAC17F958D2ee523a2206206994597C13D831ec7 - -MAX_ADMIN_FEE: constant(uint256) = 10 * 10 ** 9 -MAX_FEE: constant(uint256) = 5 * 10 ** 9 -MAX_A: constant(uint256) = 10 ** 6 -MAX_A_CHANGE: constant(uint256) = 10 - -ADMIN_ACTIONS_DELAY: constant(uint256) = 3 * 86400 -MIN_RAMP_TIME: constant(uint256) = 86400 - -coins: public(address[N_COINS]) -balances: public(uint256[N_COINS]) -fee: public(uint256) # fee * 1e10 -admin_fee: public(uint256) # admin_fee * 1e10 - -owner: public(address) -token: public(CurveToken) - -# Token corresponding to the pool is always the last one -BASE_CACHE_EXPIRES: constant(int128) = 10 * 60 # 10 min -base_pool: public(address) -base_virtual_price: public(uint256) -base_cache_updated: public(uint256) -base_coins: public(address[BASE_N_COINS]) - -A_PRECISION: constant(uint256) = 100 -initial_A: public(uint256) -future_A: public(uint256) -initial_A_time: public(uint256) -future_A_time: public(uint256) - -admin_actions_deadline: public(uint256) -transfer_ownership_deadline: public(uint256) -future_fee: public(uint256) -future_admin_fee: public(uint256) -future_owner: public(address) - -is_killed: bool -kill_deadline: uint256 -KILL_DEADLINE_DT: constant(uint256) = 2 * 30 * 86400 - - -@external -def __init__( - _owner: address, - _coins: address[N_COINS], - _pool_token: address, - _base_pool: address, - _A: uint256, - _fee: uint256, - _admin_fee: uint256 -): - """ - @notice Contract constructor - @param _owner Contract owner address - @param _coins Addresses of ERC20 conracts of coins - @param _pool_token Address of the token representing LP share - @param _base_pool Address of the base pool (which will have a virtual price) - @param _A Amplification coefficient multiplied by n * (n - 1) - @param _fee Fee to charge for exchanges - @param _admin_fee Admin fee - """ - for i in range(N_COINS): - assert _coins[i] != ZERO_ADDRESS - self.coins = _coins - self.initial_A = _A * A_PRECISION - self.future_A = _A * A_PRECISION - self.fee = _fee - self.admin_fee = _admin_fee - self.owner = _owner - self.kill_deadline = block.timestamp + KILL_DEADLINE_DT - self.token = CurveToken(_pool_token) - - self.base_pool = _base_pool - self.base_virtual_price = Curve(_base_pool).get_virtual_price() - self.base_cache_updated = block.timestamp - for i in range(BASE_N_COINS): - _base_coin: address = Curve(_base_pool).coins(convert(i, uint256)) - self.base_coins[i] = _base_coin - - # approve underlying coins for infinite transfers - _response: Bytes[32] = raw_call( - _base_coin, - concat( - method_id("approve(address,uint256)"), - convert(_base_pool, bytes32), - convert(MAX_UINT256, bytes32), - ), - max_outsize=32, - ) - if len(_response) > 0: - assert convert(_response, bool) - - -@view -@internal -def _A() -> uint256: - """ - Handle ramping A up or down - """ - t1: uint256 = self.future_A_time - A1: uint256 = self.future_A - - if block.timestamp < t1: - A0: uint256 = self.initial_A - t0: uint256 = self.initial_A_time - # Expressions in uint256 cannot have negative numbers, thus "if" - if A1 > A0: - return A0 + (A1 - A0) * (block.timestamp - t0) / (t1 - t0) - else: - return A0 - (A0 - A1) * (block.timestamp - t0) / (t1 - t0) - - else: # when t1 == 0 or block.timestamp >= t1 - return A1 - - -@view -@external -def A() -> uint256: - return self._A() / A_PRECISION - - -@view -@external -def A_precise() -> uint256: - return self._A() - - -@view -@internal -def _xp(vp_rate: uint256) -> uint256[N_COINS]: - result: uint256[N_COINS] = RATES - result[MAX_COIN] = vp_rate # virtual price for the metacurrency - for i in range(N_COINS): - result[i] = result[i] * self.balances[i] / PRECISION - return result - - -@pure -@internal -def _xp_mem(vp_rate: uint256, _balances: uint256[N_COINS]) -> uint256[N_COINS]: - result: uint256[N_COINS] = RATES - result[MAX_COIN] = vp_rate # virtual price for the metacurrency - for i in range(N_COINS): - result[i] = result[i] * _balances[i] / PRECISION - return result - - -@internal -def _vp_rate() -> uint256: - if block.timestamp > self.base_cache_updated + BASE_CACHE_EXPIRES: - vprice: uint256 = Curve(self.base_pool).get_virtual_price() - self.base_virtual_price = vprice - self.base_cache_updated = block.timestamp - return vprice - else: - return self.base_virtual_price - - -@internal -@view -def _vp_rate_ro() -> uint256: - if block.timestamp > self.base_cache_updated + BASE_CACHE_EXPIRES: - return Curve(self.base_pool).get_virtual_price() - else: - return self.base_virtual_price - - -@pure -@internal -def get_D(xp: uint256[N_COINS], amp: uint256) -> uint256: - S: uint256 = 0 - Dprev: uint256 = 0 - for _x in xp: - S += _x - if S == 0: - return 0 - - D: uint256 = S - Ann: uint256 = amp * N_COINS - for _i in range(255): - D_P: uint256 = D - for _x in xp: - D_P = D_P * D / (_x * N_COINS) # If division by 0, this will be borked: only withdrawal will work. And that is good - Dprev = D - D = (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (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` - raise - - -@view -@internal -def get_D_mem(vp_rate: uint256, _balances: uint256[N_COINS], amp: uint256) -> uint256: - xp: uint256[N_COINS] = self._xp_mem(vp_rate, _balances) - return self.get_D(xp, amp) - - -@view -@external -def get_virtual_price() -> uint256: - """ - @notice The current virtual price of the pool LP token - @dev Useful for calculating profits - @return LP token virtual price normalized to 1e18 - """ - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate_ro() - xp: uint256[N_COINS] = self._xp(vp_rate) - D: uint256 = self.get_D(xp, amp) - # 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 - - -@view -@external -def calc_token_amount(amounts: uint256[N_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 amounts Amount of each coin being deposited - @param is_deposit set True for deposits, False for withdrawals - @return Expected amount of LP tokens received - """ - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate_ro() - _balances: uint256[N_COINS] = self.balances - D0: uint256 = self.get_D_mem(vp_rate, _balances, amp) - for i in range(N_COINS): - if is_deposit: - _balances[i] += amounts[i] - else: - _balances[i] -= amounts[i] - D1: uint256 = self.get_D_mem(vp_rate, _balances, amp) - token_amount: uint256 = self.token.totalSupply() - diff: uint256 = 0 - if is_deposit: - diff = D1 - D0 - else: - diff = D0 - D1 - return diff * token_amount / D0 - - -@external -@nonreentrant('lock') -def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256) -> uint256: - """ - @notice Deposit coins into the pool - @param amounts List of amounts of coins to deposit - @param min_mint_amount Minimum amount of LP tokens to mint from the deposit - @return Amount of LP tokens received by depositing - """ - assert not self.is_killed # dev: is killed - - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate() - token_supply: uint256 = self.token.totalSupply() - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - _admin_fee: uint256 = self.admin_fee - - # Initial invariant - D0: uint256 = 0 - old_balances: uint256[N_COINS] = self.balances - if token_supply > 0: - D0 = self.get_D_mem(vp_rate, old_balances, amp) - new_balances: uint256[N_COINS] = old_balances - - for i in range(N_COINS): - if token_supply == 0: - assert amounts[i] > 0 # dev: initial deposit requires all coins - # balances store amounts of c-tokens - new_balances[i] = old_balances[i] + amounts[i] - - # Invariant after change - D1: uint256 = self.get_D_mem(vp_rate, new_balances, amp) - assert D1 > D0 - - # We need to recalculate the invariant accounting for fees - # to calculate fair user's share - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - 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(vp_rate, new_balances, amp) - 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 amounts[i] > 0: - assert ERC20(self.coins[i]).transferFrom(msg.sender, self, amounts[i]) # dev: failed transfer - - # Mint pool tokens - self.token.mint(msg.sender, mint_amount) - - log AddLiquidity(msg.sender, amounts, fees, D1, token_supply + mint_amount) - - return mint_amount - - -@view -@internal -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 # dev: same coin - assert j >= 0 # dev: j below zero - assert j < N_COINS # dev: j above N_COINS - - # should be unreachable, but good for safety - assert i >= 0 - assert i < N_COINS - - amp: uint256 = self._A() - D: uint256 = self.get_D(xp_, amp) - - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: uint256 = 0 - c: uint256 = D - Ann: uint256 = amp * N_COINS - - 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 * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann # - D - 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: - return y - else: - if y_prev - y <= 1: - return y - raise - - -@view -@external -def get_dy(i: int128, j: int128, dx: uint256) -> uint256: - # dx and dy in c-units - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = self._vp_rate_ro() - xp: uint256[N_COINS] = self._xp(rates[MAX_COIN]) - - x: uint256 = xp[i] + (dx * rates[i] / PRECISION) - y: uint256 = self.get_y(i, j, x, xp) - dy: uint256 = xp[j] - y - 1 - _fee: uint256 = self.fee * dy / FEE_DENOMINATOR - return (dy - _fee) * PRECISION / rates[j] - - -@view -@external -def get_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256: - # dx and dy in underlying units - vp_rate: uint256 = self._vp_rate_ro() - xp: uint256[N_COINS] = self._xp(vp_rate) - precisions: uint256[N_COINS] = PRECISION_MUL - _base_pool: address = self.base_pool - - # Use base_i or base_j if they are >= 0 - base_i: int128 = i - MAX_COIN - base_j: int128 = j - MAX_COIN - meta_i: int128 = MAX_COIN - meta_j: int128 = MAX_COIN - if base_i < 0: - meta_i = i - if base_j < 0: - meta_j = j - - x: uint256 = 0 - if base_i < 0: - x = xp[i] + dx * precisions[i] - else: - if base_j < 0: - # i is from BasePool - # At first, get the amount of pool tokens - base_inputs: uint256[BASE_N_COINS] = empty(uint256[BASE_N_COINS]) - base_inputs[base_i] = dx - # Token amount transformed to underlying "dollars" - x = Curve(_base_pool).calc_token_amount(base_inputs, True) * vp_rate / PRECISION - # Accounting for deposit/withdraw fees approximately - x -= x * Curve(_base_pool).fee() / (2 * FEE_DENOMINATOR) - # Adding number of pool tokens - x += xp[MAX_COIN] - else: - # If both are from the base pool - return Curve(_base_pool).get_dy(base_i, base_j, dx) - - # This pool is involved only when in-pool assets are used - y: uint256 = self.get_y(meta_i, meta_j, x, xp) - dy: uint256 = xp[meta_j] - y - 1 - dy = (dy - self.fee * dy / FEE_DENOMINATOR) - - # If output is going via the metapool - if base_j < 0: - dy /= precisions[meta_j] - else: - # j is from BasePool - # The fee is already accounted for - dy = Curve(_base_pool).calc_withdraw_one_coin(dy * PRECISION / vp_rate, base_j) - - return dy - - -@external -@nonreentrant('lock') -def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256) -> uint256: - """ - @notice Perform an exchange between two coins - @dev Index values can be found via the `coins` public getter method - @param i Index value for the coin to send - @param j Index valie of the coin to recieve - @param dx Amount of `i` being exchanged - @param min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - assert not self.is_killed # dev: is killed - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = self._vp_rate() - - old_balances: uint256[N_COINS] = self.balances - xp: uint256[N_COINS] = self._xp_mem(rates[MAX_COIN], old_balances) - - x: uint256 = xp[i] + dx * rates[i] / PRECISION - y: uint256 = self.get_y(i, j, x, xp) - - dy: uint256 = xp[j] - y - 1 # -1 just in case there were some rounding errors - dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR - - # Convert all to real units - dy = (dy - dy_fee) * PRECISION / rates[j] - assert dy >= min_dy, "Too few coins in result" - - dy_admin_fee: uint256 = dy_fee * self.admin_fee / FEE_DENOMINATOR - dy_admin_fee = dy_admin_fee * PRECISION / rates[j] - - # Change balances exactly in same way as we change actual ERC20 coin amounts - self.balances[i] = old_balances[i] + dx - # When rounding errors happen, we undercharge admin fee in favor of LP - self.balances[j] = old_balances[j] - dy - dy_admin_fee - - assert ERC20(self.coins[i]).transferFrom(msg.sender, self, dx) - assert ERC20(self.coins[j]).transfer(msg.sender, dy) - - log TokenExchange(msg.sender, i, dx, j, dy) - - return dy - - -@external -@nonreentrant('lock') -def exchange_underlying(i: int128, j: int128, dx: uint256, min_dy: uint256) -> uint256: - """ - @notice Perform an exchange between two underlying coins - @dev Index values can be found via the `underlying_coins` public getter method - @param i Index value for the underlying coin to send - @param j Index valie of the underlying coin to recieve - @param dx Amount of `i` being exchanged - @param min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - assert not self.is_killed # dev: is killed - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = self._vp_rate() - _base_pool: address = self.base_pool - - # Use base_i or base_j if they are >= 0 - base_i: int128 = i - MAX_COIN - base_j: int128 = j - MAX_COIN - meta_i: int128 = MAX_COIN - meta_j: int128 = MAX_COIN - if base_i < 0: - meta_i = i - if base_j < 0: - meta_j = j - dy: uint256 = 0 - - # Addresses for input and output coins - input_coin: address = ZERO_ADDRESS - if base_i < 0: - input_coin = self.coins[i] - else: - input_coin = self.base_coins[base_i] - output_coin: address = ZERO_ADDRESS - if base_j < 0: - output_coin = self.coins[j] - else: - output_coin = self.base_coins[base_j] - - # Handle potential Tether fees - dx_w_fee: uint256 = dx - if input_coin == FEE_ASSET: - dx_w_fee = ERC20(FEE_ASSET).balanceOf(self) - # "safeTransferFrom" which works for ERC20s which return bool or not - _response: Bytes[32] = raw_call( - input_coin, - concat( - method_id("transferFrom(address,address,uint256)"), - convert(msg.sender, bytes32), - convert(self, bytes32), - convert(dx, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransferFrom" - # Handle potential Tether fees - if input_coin == FEE_ASSET: - dx_w_fee = ERC20(FEE_ASSET).balanceOf(self) - dx_w_fee - - if base_i < 0 or base_j < 0: - old_balances: uint256[N_COINS] = self.balances - xp: uint256[N_COINS] = self._xp_mem(rates[MAX_COIN], old_balances) - - x: uint256 = 0 - if base_i < 0: - x = xp[i] + dx_w_fee * rates[i] / PRECISION - else: - # i is from BasePool - # At first, get the amount of pool tokens - base_inputs: uint256[BASE_N_COINS] = empty(uint256[BASE_N_COINS]) - base_inputs[base_i] = dx_w_fee - coin_i: address = self.coins[MAX_COIN] - # Deposit and measure delta - x = ERC20(coin_i).balanceOf(self) - Curve(_base_pool).add_liquidity(base_inputs, 0) - # Need to convert pool token to "virtual" units using rates - # dx is also different now - dx_w_fee = ERC20(coin_i).balanceOf(self) - x - x = dx_w_fee * rates[MAX_COIN] / PRECISION - # Adding number of pool tokens - x += xp[MAX_COIN] - - y: uint256 = self.get_y(meta_i, meta_j, x, xp) - - # Either a real coin or token - dy = xp[meta_j] - y - 1 # -1 just in case there were some rounding errors - dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR - - # Convert all to real units - # Works for both pool coins and real coins - dy = (dy - dy_fee) * PRECISION / rates[meta_j] - - dy_admin_fee: uint256 = dy_fee * self.admin_fee / FEE_DENOMINATOR - dy_admin_fee = dy_admin_fee * PRECISION / rates[meta_j] - - # Change balances exactly in same way as we change actual ERC20 coin amounts - self.balances[meta_i] = old_balances[meta_i] + dx_w_fee - # When rounding errors happen, we undercharge admin fee in favor of LP - self.balances[meta_j] = old_balances[meta_j] - dy - dy_admin_fee - - # Withdraw from the base pool if needed - if base_j >= 0: - out_amount: uint256 = ERC20(output_coin).balanceOf(self) - Curve(_base_pool).remove_liquidity_one_coin(dy, base_j, 0) - dy = ERC20(output_coin).balanceOf(self) - out_amount - - assert dy >= min_dy, "Too few coins in result" - - else: - # If both are from the base pool - dy = ERC20(output_coin).balanceOf(self) - Curve(_base_pool).exchange(base_i, base_j, dx_w_fee, min_dy) - dy = ERC20(output_coin).balanceOf(self) - dy - - # "safeTransfer" which works for ERC20s which return bool or not - _response = raw_call( - output_coin, - concat( - method_id("transfer(address,uint256)"), - convert(msg.sender, bytes32), - convert(dy, bytes32), - ), - max_outsize=32, - ) # dev: failed transfer - if len(_response) > 0: - assert convert(_response, bool) # dev: failed transfer - # end "safeTransfer" - - log TokenExchangeUnderlying(msg.sender, i, dx, j, dy) - - return dy - - -@external -@nonreentrant('lock') -def remove_liquidity(_amount: uint256, min_amounts: uint256[N_COINS]) -> uint256[N_COINS]: - """ - @notice Withdraw coins from the pool - @dev Withdrawal amounts are based on current deposit ratios - @param _amount Quantity of LP tokens to burn in the withdrawal - @param min_amounts Minimum amounts of underlying coins to receive - @return List of amounts of coins that were withdrawn - """ - total_supply: uint256 = self.token.totalSupply() - amounts: uint256[N_COINS] = empty(uint256[N_COINS]) - fees: uint256[N_COINS] = empty(uint256[N_COINS]) # Fees are unused but we've got them historically in event - - for i in range(N_COINS): - value: uint256 = self.balances[i] * _amount / total_supply - assert value >= min_amounts[i], "Too few coins in result" - self.balances[i] -= value - amounts[i] = value - assert ERC20(self.coins[i]).transfer(msg.sender, value) - - self.token.burnFrom(msg.sender, _amount) # dev: insufficient funds - - log RemoveLiquidity(msg.sender, amounts, fees, total_supply - _amount) - - return amounts - - -@external -@nonreentrant('lock') -def remove_liquidity_imbalance(amounts: uint256[N_COINS], max_burn_amount: uint256) -> uint256: - """ - @notice Withdraw coins from the pool in an imbalanced amount - @param amounts List of amounts of underlying coins to withdraw - @param max_burn_amount Maximum amount of LP token to burn in the withdrawal - @return Actual amount of the LP token burned in the withdrawal - """ - assert not self.is_killed # dev: is killed - - amp: uint256 = self._A() - vp_rate: uint256 = self._vp_rate() - - token_supply: uint256 = self.token.totalSupply() - assert token_supply != 0 # dev: zero total supply - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - _admin_fee: uint256 = self.admin_fee - - old_balances: uint256[N_COINS] = self.balances - new_balances: uint256[N_COINS] = old_balances - D0: uint256 = self.get_D_mem(vp_rate, old_balances, amp) - for i in range(N_COINS): - new_balances[i] -= amounts[i] - D1: uint256 = self.get_D_mem(vp_rate, new_balances, amp) - - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - 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(vp_rate, new_balances, amp) - - token_amount: uint256 = (D0 - D2) * token_supply / D0 - assert token_amount != 0 # dev: zero tokens burned - token_amount += 1 # In case of rounding errors - make it unfavorable for the "attacker" - assert token_amount <= max_burn_amount, "Slippage screwed you" - - self.token.burnFrom(msg.sender, token_amount) # dev: insufficient funds - for i in range(N_COINS): - if amounts[i] != 0: - assert ERC20(self.coins[i]).transfer(msg.sender, amounts[i]) - - log RemoveLiquidityImbalance(msg.sender, amounts, fees, D1, token_supply - token_amount) - - return token_amount - - -@view -@internal -def get_y_D(A_: uint256, i: int128, xp: uint256[N_COINS], D: uint256) -> uint256: - """ - Calculate x[i] if one reduces D from being calculated for xp to D - - Done by solving quadratic equation iteratively. - x_1**2 + x1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) - x_1**2 + b*x_1 = c - - x_1 = (x_1**2 + c) / (2*x_1 + b) - """ - # x in the input is converted to the same price/precision - - assert i >= 0 # dev: i below zero - assert i < N_COINS # dev: i above N_COINS - - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: uint256 = 0 - - c: uint256 = D - Ann: uint256 = A_ * N_COINS - - for _i in range(N_COINS): - if _i != i: - _x = xp[_i] - else: - continue - S_ += _x - c = c * D / (_x * N_COINS) - c = c * D * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann - 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: - return y - else: - if y_prev - y <= 1: - return y - raise - - -@view -@internal -def _calc_withdraw_one_coin(_token_amount: uint256, i: int128, vp_rate: uint256) -> (uint256, uint256, uint256): - # First, need to calculate - # * Get current D - # * Solve Eqn against y_i for D - _token_amount - amp: uint256 = self._A() - xp: uint256[N_COINS] = self._xp(vp_rate) - D0: uint256 = self.get_D(xp, amp) - - total_supply: uint256 = self.token.totalSupply() - D1: uint256 = D0 - _token_amount * D0 / total_supply - new_y: uint256 = self.get_y_D(amp, i, xp, D1) - - _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - rates: uint256[N_COINS] = RATES - rates[MAX_COIN] = vp_rate - - xp_reduced: uint256[N_COINS] = xp - dy_0: uint256 = (xp[i] - new_y) * PRECISION / rates[i] # w/o fees - - for j in range(N_COINS): - dx_expected: uint256 = 0 - if j == i: - dx_expected = xp[j] * D1 / D0 - new_y - else: - dx_expected = xp[j] - xp[j] * D1 / D0 - xp_reduced[j] -= _fee * dx_expected / FEE_DENOMINATOR - - dy: uint256 = xp_reduced[i] - self.get_y_D(amp, i, xp_reduced, D1) - dy = (dy - 1) * PRECISION / rates[i] # Withdraw less to account for rounding errors - - return dy, dy_0 - dy, total_supply - - -@view -@external -def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - """ - @notice Calculate the amount received when withdrawing a single coin - @param _token_amount Amount of LP tokens to burn in the withdrawal - @param i Index value of the coin to withdraw - @return Amount of coin received - """ - vp_rate: uint256 = self._vp_rate_ro() - return self._calc_withdraw_one_coin(_token_amount, i, vp_rate)[0] - - -@external -@nonreentrant('lock') -def remove_liquidity_one_coin(_token_amount: uint256, i: int128, _min_amount: uint256) -> uint256: - """ - @notice Withdraw a single coin from the pool - @param _token_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 coin to receive - @return Amount of coin received - """ - assert not self.is_killed # dev: is killed - - vp_rate: uint256 = self._vp_rate() - dy: uint256 = 0 - dy_fee: uint256 = 0 - total_supply: uint256 = 0 - dy, dy_fee, total_supply = self._calc_withdraw_one_coin(_token_amount, i, vp_rate) - assert dy >= _min_amount, "Not enough coins removed" - - self.balances[i] -= (dy + dy_fee * self.admin_fee / FEE_DENOMINATOR) - self.token.burnFrom(msg.sender, _token_amount) # dev: insufficient funds - assert ERC20(self.coins[i]).transfer(msg.sender, dy) - - log RemoveLiquidityOne(msg.sender, _token_amount, dy, total_supply - _token_amount) - - return dy - - -### Admin functions ### -@external -def ramp_A(_future_A: uint256, _future_time: uint256): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.initial_A_time + MIN_RAMP_TIME - assert _future_time >= block.timestamp + MIN_RAMP_TIME # dev: insufficient time - - _initial_A: uint256 = self._A() - _future_A_p: uint256 = _future_A * A_PRECISION - - assert _future_A > 0 and _future_A < MAX_A - if _future_A_p < _initial_A: - assert _future_A_p * MAX_A_CHANGE >= _initial_A - else: - assert _future_A_p <= _initial_A * MAX_A_CHANGE - - self.initial_A = _initial_A - self.future_A = _future_A_p - self.initial_A_time = block.timestamp - self.future_A_time = _future_time - - log RampA(_initial_A, _future_A_p, block.timestamp, _future_time) - - -@external -def stop_ramp_A(): - assert msg.sender == self.owner # dev: only owner - - current_A: uint256 = self._A() - self.initial_A = current_A - self.future_A = current_A - self.initial_A_time = block.timestamp - self.future_A_time = block.timestamp - # now (block.timestamp < t1) is always False, so we return saved A - - log StopRampA(current_A, block.timestamp) - - -@external -def commit_new_fee(new_fee: uint256, new_admin_fee: uint256): - assert msg.sender == self.owner # dev: only owner - assert self.admin_actions_deadline == 0 # dev: active action - assert new_fee <= MAX_FEE # dev: fee exceeds maximum - assert new_admin_fee <= MAX_ADMIN_FEE # dev: admin fee exceeds maximum - - _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.admin_actions_deadline = _deadline - self.future_fee = new_fee - self.future_admin_fee = new_admin_fee - - log CommitNewFee(_deadline, new_fee, new_admin_fee) - - -@external -def apply_new_fee(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.admin_actions_deadline # dev: insufficient time - assert self.admin_actions_deadline != 0 # dev: no active action - - self.admin_actions_deadline = 0 - _fee: uint256 = self.future_fee - _admin_fee: uint256 = self.future_admin_fee - self.fee = _fee - self.admin_fee = _admin_fee - - log NewFee(_fee, _admin_fee) - - -@external -def revert_new_parameters(): - assert msg.sender == self.owner # dev: only owner - - self.admin_actions_deadline = 0 - - -@external -def commit_transfer_ownership(_owner: address): - assert msg.sender == self.owner # dev: only owner - assert self.transfer_ownership_deadline == 0 # dev: active transfer - - _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY - self.transfer_ownership_deadline = _deadline - self.future_owner = _owner - - log CommitNewAdmin(_deadline, _owner) - - -@external -def apply_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - assert block.timestamp >= self.transfer_ownership_deadline # dev: insufficient time - assert self.transfer_ownership_deadline != 0 # dev: no active transfer - - self.transfer_ownership_deadline = 0 - _owner: address = self.future_owner - self.owner = _owner - - log NewAdmin(_owner) - - -@external -def revert_transfer_ownership(): - assert msg.sender == self.owner # dev: only owner - - self.transfer_ownership_deadline = 0 - - -@view -@external -def admin_balances(i: uint256) -> uint256: - return ERC20(self.coins[i]).balanceOf(self) - self.balances[i] - - -@external -def withdraw_admin_fees(): - assert msg.sender == self.owner # dev: only owner - - for i in range(N_COINS): - c: address = self.coins[i] - value: uint256 = ERC20(c).balanceOf(self) - self.balances[i] - if value > 0: - assert ERC20(c).transfer(msg.sender, value) - - -@external -def donate_admin_fees(): - assert msg.sender == self.owner # dev: only owner - for i in range(N_COINS): - self.balances[i] = ERC20(self.coins[i]).balanceOf(self) - - -@external -def kill_me(): - assert msg.sender == self.owner # dev: only owner - assert self.kill_deadline > block.timestamp # dev: deadline has passed - self.is_killed = True - - -@external -def unkill_me(): - assert msg.sender == self.owner # dev: only owner - self.is_killed = False diff --git a/contracts/pools/ust/pooldata.json b/contracts/pools/ust/pooldata.json deleted file mode 100644 index 099e39bd..00000000 --- a/contracts/pools/ust/pooldata.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "base_pool": "3pool", - "pool_types": ["meta"], - "lp_contract": "CurveTokenV3", - "swap_address": "0x890f4e345B1dAED0367A877a1612f86A1f86985f", - "lp_token_address": "0x94e131324b6054c0D789b190b2dAC504e4361b53", - "zap_address": "0xB0a0716841F2Fc03fbA72A891B8Bb13584F52F2d", - "gauge_addresses": ["0x3B7020743Bc2A4ca9EaF9D0722d42E20d6935855"], - "lp_constructor": { - "symbol": "ust3CRV", - "name": "Curve.fi UST/3Crv" - }, - "swap_constructor": { - "_A": 100, - "_fee": 4000000, - "_admin_fee": 5000000000 - }, - "coins": [ - { - "name": "UST", - "decimals": 18, - "tethered": false, - "underlying_address": "0xa47c8bf37f92aBed4A126BDA807A7b7498661acD" - }, - { - "name": "3CRV", - "decimals": 18, - "base_pool_token": true, - "underlying_address": "0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490" - } - ] -} diff --git a/contracts/pools/y/DepositY.vy b/contracts/pools/y/DepositY.vy deleted file mode 100644 index 5b8972a4..00000000 --- a/contracts/pools/y/DepositY.vy +++ /dev/null @@ -1,338 +0,0 @@ -# @version 0.1.0b17 -# A "zap" to deposit/withdraw Curve contract without too many transactions -# (c) Curve.Fi, 2020 -from vyper.interfaces import ERC20 - -# External Contracts -contract yERC20: - def totalSupply() -> uint256: constant - def allowance(_owner: address, _spender: address) -> uint256: constant - def transfer(_to: address, _value: uint256) -> bool: modifying - def transferFrom(_from: address, _to: address, _value: uint256) -> bool: modifying - def approve(_spender: address, _value: uint256) -> bool: modifying - def name() -> string[64]: constant - def symbol() -> string[32]: constant - def decimals() -> uint256: constant - def balanceOf(arg0: address) -> uint256: constant - def deposit(depositAmount: uint256): modifying - def withdraw(withdrawTokens: uint256): modifying - def getPricePerFullShare() -> uint256: constant - - - - -# Tether transfer-only ABI -contract USDT: - def transfer(_to: address, _value: uint256): modifying - def transferFrom(_from: address, _to: address, _value: uint256): modifying - - -contract Curve: - def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256): modifying - def remove_liquidity(_amount: uint256, min_amounts: uint256[N_COINS]): modifying - def remove_liquidity_imbalance(amounts: uint256[N_COINS], max_burn_amount: uint256): modifying - def balances(i: int128) -> uint256: constant - def A() -> uint256: constant - def fee() -> uint256: constant - def owner() -> address: constant - - -N_COINS: constant(int128) = 4 -TETHERED: constant(bool[N_COINS]) = [False, False, True, False] -ZERO256: constant(uint256) = 0 # This hack is really bad XXX -ZEROS: constant(uint256[N_COINS]) = [ZERO256, ZERO256, ZERO256, ZERO256] # <- change -LENDING_PRECISION: constant(uint256) = 10 ** 18 -PRECISION: constant(uint256) = 10 ** 18 -PRECISION_MUL: constant(uint256[N_COINS]) = [convert(1, uint256), convert(1000000000000, uint256), convert(1000000000000, uint256), convert(1, uint256)] -FEE_DENOMINATOR: constant(uint256) = 10 ** 10 -FEE_IMPRECISION: constant(uint256) = 25 * 10 ** 8 # % of the fee - -coins: public(address[N_COINS]) -underlying_coins: public(address[N_COINS]) -curve: public(address) -token: public(address) - - -@public -def __init__(_coins: address[N_COINS], _underlying_coins: address[N_COINS], - _curve: address, _token: address): - self.coins = _coins - self.underlying_coins = _underlying_coins - self.curve = _curve - self.token = _token - - -@public -@nonreentrant('lock') -def add_liquidity(uamounts: uint256[N_COINS], min_mint_amount: uint256): - tethered: bool[N_COINS] = TETHERED - amounts: uint256[N_COINS] = ZEROS - - for i in range(N_COINS): - uamount: uint256 = uamounts[i] - - if uamount > 0: - # Transfer the underlying coin from owner - if tethered[i]: - USDT(self.underlying_coins[i]).transferFrom( - msg.sender, self, uamount) - else: - assert_modifiable(ERC20(self.underlying_coins[i])\ - .transferFrom(msg.sender, self, uamount)) - - # Mint if needed - ERC20(self.underlying_coins[i]).approve(self.coins[i], uamount) - yERC20(self.coins[i]).deposit(uamount) - amounts[i] = yERC20(self.coins[i]).balanceOf(self) - ERC20(self.coins[i]).approve(self.curve, amounts[i]) - - Curve(self.curve).add_liquidity(amounts, min_mint_amount) - - tokens: uint256 = ERC20(self.token).balanceOf(self) - assert_modifiable(ERC20(self.token).transfer(msg.sender, tokens)) - - -@private -def _send_all(_addr: address, min_uamounts: uint256[N_COINS], one: int128): - tethered: bool[N_COINS] = TETHERED - - for i in range(N_COINS): - if (one < 0) or (i == one): - _coin: address = self.coins[i] - _balance: uint256 = yERC20(_coin).balanceOf(self) - if _balance == 0: # Do nothing for 0 coins - continue - yERC20(_coin).withdraw(_balance) - - _ucoin: address = self.underlying_coins[i] - _uamount: uint256 = ERC20(_ucoin).balanceOf(self) - assert _uamount >= min_uamounts[i], "Not enough coins withdrawn" - - if tethered[i]: - USDT(_ucoin).transfer(_addr, _uamount) - else: - assert_modifiable(ERC20(_ucoin).transfer(_addr, _uamount)) - - -@public -@nonreentrant('lock') -def remove_liquidity(_amount: uint256, min_uamounts: uint256[N_COINS]): - zeros: uint256[N_COINS] = ZEROS - - assert_modifiable(ERC20(self.token).transferFrom(msg.sender, self, _amount)) - Curve(self.curve).remove_liquidity(_amount, zeros) - - self._send_all(msg.sender, min_uamounts, -1) - - -@public -@nonreentrant('lock') -def remove_liquidity_imbalance(uamounts: uint256[N_COINS], max_burn_amount: uint256): - """ - Get max_burn_amount in, remove requested liquidity and transfer back what is left - """ - tethered: bool[N_COINS] = TETHERED - _token: address = self.token - - amounts: uint256[N_COINS] = uamounts - for i in range(N_COINS): - if amounts[i] > 0: - rate: uint256 = yERC20(self.coins[i]).getPricePerFullShare() - amounts[i] = amounts[i] * LENDING_PRECISION / rate - - # Transfrer max tokens in - _tokens: uint256 = ERC20(_token).balanceOf(msg.sender) - if _tokens > max_burn_amount: - _tokens = max_burn_amount - assert_modifiable(ERC20(_token).transferFrom(msg.sender, self, _tokens)) - - Curve(self.curve).remove_liquidity_imbalance(amounts, max_burn_amount) - - # Transfer unused tokens back - _tokens = ERC20(_token).balanceOf(self) - assert_modifiable(ERC20(_token).transfer(msg.sender, _tokens)) - - # Unwrap and transfer all the coins we've got - self._send_all(msg.sender, ZEROS, -1) - - -@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(A: uint256, 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 = 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_y(A: uint256, i: int128, _xp: uint256[N_COINS], D: uint256) -> uint256: - """ - Calculate x[i] if one reduces D from being calculated for _xp to D - - Done by solving quadratic equation iteratively. - x_1**2 + x1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) - x_1**2 + b*x_1 = c - - x_1 = (x_1**2 + c) / (2*x_1 + b) - """ - # x in the input is converted to the same price/precision - - assert (i >= 0) and (i < N_COINS) - - c: uint256 = D - S_: uint256 = 0 - Ann: uint256 = A * N_COINS - - _x: uint256 = 0 - for _i in range(N_COINS): - if _i != i: - _x = _xp[_i] - else: - continue - S_ += _x - c = c * D / (_x * N_COINS) - c = c * D / (Ann * N_COINS) - b: uint256 = S_ + D / Ann - 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 - - -@private -@constant -def _calc_withdraw_one_coin(_token_amount: uint256, i: int128, rates: uint256[N_COINS]) -> uint256: - # First, need to calculate - # * Get current D - # * Solve Eqn against y_i for D - _token_amount - crv: address = self.curve - A: uint256 = Curve(crv).A() - fee: uint256 = Curve(crv).fee() * N_COINS / (4 * (N_COINS - 1)) - fee += fee * FEE_IMPRECISION / FEE_DENOMINATOR # Overcharge to account for imprecision - precisions: uint256[N_COINS] = PRECISION_MUL - total_supply: uint256 = ERC20(self.token).totalSupply() - - xp: uint256[N_COINS] = PRECISION_MUL - S: uint256 = 0 - for j in range(N_COINS): - xp[j] *= Curve(crv).balances(j) - xp[j] = xp[j] * rates[j] / LENDING_PRECISION - S += xp[j] - - D0: uint256 = self.get_D(A, xp) - D1: uint256 = D0 - _token_amount * D0 / total_supply - xp_reduced: uint256[N_COINS] = xp - - # xp = xp - fee * | xp * D1 / D0 - (xp - S * dD / D0 * (0, ... 1, ..0))| - for j in range(N_COINS): - dx_expected: uint256 = 0 - b_ideal: uint256 = xp[j] * D1 / D0 - b_expected: uint256 = xp[j] - if j == i: - b_expected -= S * (D0 - D1) / D0 - if b_ideal >= b_expected: - dx_expected += (b_ideal - b_expected) - else: - dx_expected += (b_expected - b_ideal) - xp_reduced[j] -= fee * dx_expected / FEE_DENOMINATOR - - dy: uint256 = xp_reduced[i] - self.get_y(A, i, xp_reduced, D1) - dy = dy / precisions[i] - - return dy - - -@public -@constant -def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: - rates: uint256[N_COINS] = ZEROS - - for j in range(N_COINS): - rates[j] = yERC20(self.coins[j]).getPricePerFullShare() - - return self._calc_withdraw_one_coin(_token_amount, i, rates) - - -@public -@nonreentrant('lock') -def remove_liquidity_one_coin(_token_amount: uint256, i: int128, min_uamount: uint256, donate_dust: bool = False): - """ - Remove _amount of liquidity all in a form of coin i - """ - rates: uint256[N_COINS] = ZEROS - _token: address = self.token - - for j in range(N_COINS): - rates[j] = yERC20(self.coins[j]).getPricePerFullShare() - - dy: uint256 = self._calc_withdraw_one_coin(_token_amount, i, rates) - assert dy >= min_uamount, "Not enough coins removed" - - assert_modifiable( - ERC20(self.token).transferFrom(msg.sender, self, _token_amount)) - - amounts: uint256[N_COINS] = ZEROS - amounts[i] = dy * LENDING_PRECISION / rates[i] - token_amount_before: uint256 = ERC20(_token).balanceOf(self) - Curve(self.curve).remove_liquidity_imbalance(amounts, _token_amount) - - # Unwrap and transfer all the coins we've got - self._send_all(msg.sender, ZEROS, i) - - if not donate_dust: - # Transfer unused tokens back - token_amount_after: uint256 = ERC20(_token).balanceOf(self) - if token_amount_after > token_amount_before: - assert_modifiable(ERC20(_token).transfer( - msg.sender, token_amount_after - token_amount_before) - ) - - -@public -@nonreentrant('lock') -def withdraw_donated_dust(): - owner: address = Curve(self.curve).owner() - assert msg.sender == owner - - _token: address = self.token - assert_modifiable( - ERC20(_token).transfer(owner, ERC20(_token).balanceOf(self))) diff --git a/contracts/pools/y/README.md b/contracts/pools/y/README.md deleted file mode 100644 index 0d77b70f..00000000 --- a/contracts/pools/y/README.md +++ /dev/null @@ -1,33 +0,0 @@ -# curve-contract/contracts/pools/y - -[Curve Y pool](https://www.curve.fi/y), with lending on [yearn.finance](https://yearn.finance/). - -## Contracts - -* [`DepositY`](DepositY.vy): Depositor contract, used to wrap underlying tokens prior to depositing them into the pool. -* [`StableSwapY`](StableSwapY.vy): Curve stablecoin AMM contract - -## Deployments - -* [`CurveContractV1`](../../tokens/CurveTokenV1.vy): [0xdF5e0e81Dff6FAF3A7e52BA697820c5e32D806A8](https://etherscan.io/address/0xdF5e0e81Dff6FAF3A7e52BA697820c5e32D806A8) -* [`DepositUSDT`](DepositUSDT.vy): [0xbbc81d23ea2c3ec7e56d39296f0cbb648873a5d3](https://etherscan.io/address/0xbbc81d23ea2c3ec7e56d39296f0cbb648873a5d3) -* [`LiquidityGauge`](../../gauges/LiquidityGauge.vy): [0xFA712EE4788C042e2B7BB55E6cb8ec569C4530c1](https://etherscan.io/address/0xfa712ee4788c042e2b7bb55e6cb8ec569c4530c1) -* [`StableSwapUSDT`](StableSwapUSDT.vy): [0x45F783CCE6B7FF23B2ab2D70e416cdb7D6055f51](https://etherscan.io/address/0x45F783CCE6B7FF23B2ab2D70e416cdb7D6055f51) - -## Stablecoins - -Curve Y pool supports swaps between the following stablecoins: - -### Wrapped - -* `yDAI`: [0x16de59092dAE5CcF4A1E6439D611fd0653f0Bd01](https://etherscan.io/address/0x16de59092dAE5CcF4A1E6439D611fd0653f0Bd01) -* `yUSDC`: [0xd6aD7a6750A7593E092a9B218d66C0A814a3436e](https://etherscan.io/address/0xd6aD7a6750A7593E092a9B218d66C0A814a3436e) -* `yUSDT`: [0x83f798e925BcD4017Eb265844FDDAbb448f1707D](https://etherscan.io/address/0x83f798e925BcD4017Eb265844FDDAbb448f1707D) -* `yTUSD`: [0x73a052500105205d34daf004eab301916da8190f](https://etherscan.io/address/0x73a052500105205d34daf004eab301916da8190f) - -### Underlying - -* `DAI`: [0x6b175474e89094c44da98b954eedeac495271d0f](https://etherscan.io/token/0x6b175474e89094c44da98b954eedeac495271d0f) -* `USDC`: [0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48](https://etherscan.io/token/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48) -* `USDT`: [0xdac17f958d2ee523a2206206994597c13d831ec7](https://etherscan.io/address/0xdac17f958d2ee523a2206206994597c13d831ec7) -* `TUSD`: [0x0000000000085d4780b73119b644ae5ecd22b376](https://etherscan.io/address/0x0000000000085d4780b73119b644ae5ecd22b376) diff --git a/contracts/pools/y/StableSwapY.vy b/contracts/pools/y/StableSwapY.vy deleted file mode 100644 index 2ffc9a44..00000000 --- a/contracts/pools/y/StableSwapY.vy +++ /dev/null @@ -1,620 +0,0 @@ -# @version 0.1.0b16 -# (c) Curve.Fi, 2020 - - -# External Contracts -contract ERC20m: - def totalSupply() -> uint256: constant - def allowance(_owner: address, _spender: address) -> uint256: constant - def transfer(_to: address, _value: uint256) -> bool: modifying - def transferFrom(_from: address, _to: address, _value: uint256) -> bool: modifying - def approve(_spender: address, _value: uint256) -> bool: modifying - def mint(_to: address, _value: uint256): modifying - def burn(_value: uint256): modifying - def burnFrom(_to: address, _value: uint256): modifying - def name() -> string[64]: constant - def symbol() -> string[32]: constant - def decimals() -> uint256: constant - def balanceOf(arg0: address) -> uint256: constant - def set_minter(_minter: address): modifying - - - -# External Contracts -contract yERC20: - def totalSupply() -> uint256: constant - def allowance(_owner: address, _spender: address) -> uint256: constant - def transfer(_to: address, _value: uint256) -> bool: modifying - def transferFrom(_from: address, _to: address, _value: uint256) -> bool: modifying - def approve(_spender: address, _value: uint256) -> bool: modifying - def name() -> string[64]: constant - def symbol() -> string[32]: constant - def decimals() -> uint256: constant - def balanceOf(arg0: address) -> uint256: constant - def deposit(depositAmount: uint256): modifying - def withdraw(withdrawTokens: uint256): modifying - def getPricePerFullShare() -> uint256: constant - - -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 - -# This can (and needs to) be changed at compile time -N_COINS: constant(int128) = 4 # <- change - -ZERO256: constant(uint256) = 0 # This hack is really bad XXX -ZEROS: constant(uint256[N_COINS]) = [ZERO256, ZERO256, ZERO256, ZERO256] # <- change - -TETHERED: constant(bool[N_COINS]) = [False, False, True, False] - -FEE_DENOMINATOR: constant(uint256) = 10 ** 10 -PRECISION: constant(uint256) = 10 ** 18 # The precision to convert to -PRECISION_MUL: constant(uint256[N_COINS]) = [convert(1, uint256), convert(1000000000000, uint256), convert(1000000000000, uint256), convert(1, uint256)] -# PRECISION_MUL: constant(uint256[N_COINS]) = [ -# PRECISION / convert(10 ** 18, uint256), # DAI -# PRECISION / convert(10 ** 6, uint256), # USDC -# PRECISION / convert(10 ** 6, uint256), # USDT -# PRECISION / convert(10 ** 18, uint256)] # TUSD - -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]) -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 - -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], - _pool_token: address, - _A: uint256, _fee: uint256): - """ - _coins: Addresses of ERC20 contracts of coins (y-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.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 _stored_rates() -> uint256[N_COINS]: - result: uint256[N_COINS] = PRECISION_MUL - for i in range(N_COINS): - result[i] *= yERC20(self.coins[i]).getPricePerFullShare() - 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._stored_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._stored_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 - - 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._stored_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): - assert_modifiable( - yERC20(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._stored_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._stored_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._stored_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._stored_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._stored_rates() - dy: uint256 = self._exchange(i, j, dx, rates) - assert dy >= min_dy, "Exchange resulted in fewer coins than expected" - - assert_modifiable(yERC20(self.coins[i]).transferFrom(msg.sender, self, dx)) - - assert_modifiable(yERC20(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._stored_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" - tethered: bool[N_COINS] = TETHERED - - ok: uint256 = 0 - 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)) - ERC20(self.underlying_coins[i]).approve(self.coins[i], dx) - yERC20(self.coins[i]).deposit(dx) - 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 - - 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 - assert_modifiable(yERC20(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 - - 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._stored_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): - assert_modifiable(yERC20(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) - - -### 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 - - _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 - - for i in range(N_COINS): - c: address = self.coins[i] - value: uint256 = yERC20(c).balanceOf(self) - self.balances[i] - if value > 0: - assert_modifiable(yERC20(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 diff --git a/contracts/pools/y/pooldata.json b/contracts/pools/y/pooldata.json deleted file mode 100644 index db2a3977..00000000 --- a/contracts/pools/y/pooldata.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "lp_contract": "CurveTokenV1", - "wrapped_contract": "yERC20", - "swap_address": "0x45F783CCE6B7FF23B2ab2D70e416cdb7D6055f51", - "lp_token_address": "0xdF5e0e81Dff6FAF3A7e52BA697820c5e32D806A8", - "zap_address": "0xbbc81d23ea2c3ec7e56d39296f0cbb648873a5d3", - "gauge_addresses": ["0xFA712EE4788C042e2B7BB55E6cb8ec569C4530c1"], - "lp_constructor": { - "name": "Curve.fi yDAI/yUSDC/yUSDT/yTUSD", - "symbol": "yDAI+yUSDC+yUSDT+yTUSD" - }, - "coins": [ - { - "name": "yDAI", - "underlying_name": "DAI", - "decimals": 18, - "tethered": false, - "wrapped_decimals": 18, - "underlying_address": "0x6b175474e89094c44da98b954eedeac495271d0f", - "wrapped_address": "0x16de59092dAE5CcF4A1E6439D611fd0653f0Bd01" - }, - { - "name": "yUSDC", - "underlying_name": "USDC", - "decimals": 6, - "tethered": false, - "wrapped_decimals": 6, - "underlying_address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", - "wrapped_address": "0xd6aD7a6750A7593E092a9B218d66C0A814a3436e" - }, - { - "name": "yUSDT", - "underlying_name": "USDT", - "decimals": 6, - "tethered": true, - "wrapped_decimals": 6, - "underlying_address": "0xdac17f958d2ee523a2206206994597c13d831ec7", - "wrapped_address": "0x83f798e925BcD4017Eb265844FDDAbb448f1707D" - }, - { - "name": "yTUSD", - "underlying_name": "TUSD", - "decimals": 18, - "tethered": false, - "wrapped_decimals": 18, - "underlying_address": "0x0000000000085d4780b73119b644ae5ecd22b376", - "wrapped_address": "0x73a052500105205d34daf004eab301916da8190f" - } - ] -} diff --git a/contracts/tokens/CurveTokenV1.vy b/contracts/tokens/CurveTokenV1.vy index 1c9ada41..bd5a5fb7 100644 --- a/contracts/tokens/CurveTokenV1.vy +++ b/contracts/tokens/CurveTokenV1.vy @@ -24,7 +24,7 @@ decimals: public(uint256) balanceOf: public(map(address, uint256)) allowances: map(address, map(address, uint256)) total_supply: uint256 -minter: address +minter: public(address) @public @@ -121,7 +121,7 @@ def approve(_spender : address, _value : uint256) -> bool: @public -def mint(_to: address, _value: uint256): +def mint(_to: address, _value: uint256) -> bool: """ @dev Mint an amount of the token and assigns it to an account. This encapsulates the modification of balances such that the @@ -134,6 +134,7 @@ def mint(_to: address, _value: uint256): self.total_supply += _value self.balanceOf[_to] += _value log.Transfer(ZERO_ADDRESS, _to, _value) + return True @private diff --git a/notes.md b/notes.md new file mode 100644 index 00000000..34deff9e --- /dev/null +++ b/notes.md @@ -0,0 +1,17 @@ +brownie networks add Zetachain athens host="https://zetachain-athens-evm.blockpi.network/v1/rpc/public" chainid=7001 + +brownie networks modify athens host=https://zetachain-testnet-evm.itrocket.net + +pip3 install urllib3==1.26.6 +/Library/Developer/CommandLineTools/usr/bin/python3 -m pip install --upgrade pip + +brownie run deploy --network athens + +work ing versions: +pip: pip 24.0 from /Users/andresaiello/Library/Python/3.9/lib/python/site-packages/pip (python 3.9) +brownie: Brownie v1.14.5 - Python development framework for Ethereum + +command to install brownie latest version: +Workaround: +pip install "cython<3.0.0" && pip install --no-build-isolation eth-brownie + diff --git a/requirements.txt b/requirements.txt index 13356b7b..d98c8725 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ black==19.10b0 -eth-brownie>=1.13.2,<2.0.0 +eth-brownie>=1.20,<2.0.0 flake8==3.8.4 isort==5.7.0 brownie-token-tester>=0.1.0 diff --git a/scripts/deploy.py b/scripts/deploy.py index 4a0a3115..260b2cd2 100644 --- a/scripts/deploy.py +++ b/scripts/deploy.py @@ -1,40 +1,49 @@ import json +import os +from dotenv import load_dotenv from brownie import accounts -from brownie.network.gas.strategies import GasNowScalingStrategy from brownie.project import load as load_project from brownie.project.main import get_loaded_projects +from brownie import network + +load_dotenv() # set a throwaway admin account here -DEPLOYER = accounts.add() +deployer_pk = os.getenv('DEPLOYER_PK') + +DEPLOYER = accounts.add(deployer_pk) REQUIRED_CONFIRMATIONS = 1 # deployment settings # most settings are taken from `contracts/pools/{POOL_NAME}/pooldata.json` -POOL_NAME = "" +POOL_NAME = "ZRC20" # temporary owner address -POOL_OWNER = "0xedf2c58e16cc606da1977e79e1e69e79c54fe242" -GAUGE_OWNER = "0xedf2c58e16cc606da1977e79e1e69e79c54fe242" - -MINTER = "0xd061D61a4d941c39E5453435B6345Dc261C2fcE0" +POOL_OWNER = "0x19caCb4c0A7fC25598CC44564ED0eCA01249fc31" +# GAUGE_OWNER = "0xedf2c58e16cc606da1977e79e1e69e79c54fe242" -# POOL_OWNER = "0xeCb456EA5365865EbAb8a2661B0c503410e9B347" # PoolProxy -# GAUGE_OWNER = "0x519AFB566c05E00cfB9af73496D00217A630e4D5" # GaugeProxy +# MINTER = "0xd061D61a4d941c39E5453435B6345Dc261C2fcE0" def _tx_params(): return { + # "gas": 12000000, "from": DEPLOYER, - "required_confs": REQUIRED_CONFIRMATIONS, - "gas_price": GasNowScalingStrategy("standard", "fast"), + "gasPrice": 8000000000, # Optional: Adjust gas price if needed } def main(): + print(network.show_active()) # Shows the active network + print(network.chain.id) + project = get_loaded_projects()[0] balance = DEPLOYER.balance() + print(f"Deployer address: {DEPLOYER}") + print(f"Deployer balance: {balance / 1e18:.4f} ETH") + # load data about the deployment from `pooldata.json` contracts_path = project._path.joinpath("contracts/pools") with contracts_path.joinpath(f"{POOL_NAME}/pooldata.json").open() as fp: @@ -54,8 +63,17 @@ def main(): base_pool = base_pool_data["swap_address"] # deploy the token + decimals = 18 + total_supply = 0 token_args = pool_data["lp_constructor"] - token = token_deployer.deploy(token_args["name"], token_args["symbol"], _tx_params()) + + token = token_deployer.deploy( + token_args["name"], + token_args["symbol"], + decimals, + total_supply, + _tx_params(), + ) # deploy the pool abi = next(i["inputs"] for i in swap_deployer.abi if i["type"] == "constructor") @@ -74,15 +92,15 @@ def main(): # set the minter token.set_minter(swap, _tx_params()) + # @dev: not working, check why # deploy the liquidity gauge - LiquidityGaugeV3 = load_project("curvefi/curve-dao-contracts@1.2.0").LiquidityGaugeV3 - LiquidityGaugeV3.deploy(token, MINTER, GAUGE_OWNER, _tx_params()) + # LiquidityGaugeV3 = load_project("curvefi/curve-dao-contracts@1.2.0").LiquidityGaugeV3 + # LiquidityGaugeV3.deploy(token, MINTER, GAUGE_OWNER, _tx_params()) # deploy the zap zap_name = next((i.stem for i in contracts_path.glob(f"{POOL_NAME}/Deposit*")), None) if zap_name is not None: zap_deployer = getattr(project, zap_name) - abi = next(i["inputs"] for i in zap_deployer.abi if i["type"] == "constructor") args = { "_coins": wrapped_coins, @@ -92,7 +110,6 @@ def main(): "_curve": swap, } deployment_args = [args[i["name"]] for i in abi] + [_tx_params()] - zap_deployer.deploy(*deployment_args) # deploy the rate calculator