diff --git a/contracts/DepositAndStakeZap0.3.10.vy b/contracts/DepositAndStakeZap0.3.10.vy new file mode 100644 index 0000000..ced56b7 --- /dev/null +++ b/contracts/DepositAndStakeZap0.3.10.vy @@ -0,0 +1,212 @@ +# @version 0.3.10 + +""" +@title CurveDeposit&StakeZap +@author Curve.Fi +@license Copyright (c) Curve.Fi, 2020-2024 - all rights reserved +@notice A zap to add liquidity to pool and deposit into gauge in one transaction +""" + +MAX_COINS: constant(uint256) = 8 +ETH_ADDRESS: constant(address) = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE + +# External Contracts +interface ERC20: + def transfer(_receiver: address, _amount: uint256): nonpayable + def transferFrom(_sender: address, _receiver: address, _amount: uint256): nonpayable + def approve(_spender: address, _amount: uint256): nonpayable + def decimals() -> uint256: view + def balanceOf(_owner: address) -> uint256: view + def allowance(_owner : address, _spender : address) -> uint256: view + +interface Pool2: + def add_liquidity(amounts: uint256[2], min_mint_amount: uint256): payable + +interface Pool3: + def add_liquidity(amounts: uint256[3], min_mint_amount: uint256): payable + +interface Pool4: + def add_liquidity(amounts: uint256[4], min_mint_amount: uint256): payable + +interface Pool5: + def add_liquidity(amounts: uint256[5], min_mint_amount: uint256): payable + +interface Pool6: + def add_liquidity(amounts: uint256[6], min_mint_amount: uint256): payable + +interface PoolUseUnderlying2: + def add_liquidity(amounts: uint256[2], min_mint_amount: uint256, use_underlying: bool): payable + +interface PoolUseUnderlying3: + def add_liquidity(amounts: uint256[3], min_mint_amount: uint256, use_underlying: bool): payable + +interface PoolUseUnderlying4: + def add_liquidity(amounts: uint256[4], min_mint_amount: uint256, use_underlying: bool): payable + +interface PoolUseUnderlying5: + def add_liquidity(amounts: uint256[5], min_mint_amount: uint256, use_underlying: bool): payable + +interface PoolUseUnderlying6: + def add_liquidity(amounts: uint256[6], min_mint_amount: uint256, use_underlying: bool): payable + +interface PoolFactory2: + def add_liquidity(pool: address, amounts: uint256[2], min_mint_amount: uint256): payable + +interface PoolFactory3: + def add_liquidity(pool: address, amounts: uint256[3], min_mint_amount: uint256): payable + +interface PoolFactory4: + def add_liquidity(pool: address, amounts: uint256[4], min_mint_amount: uint256): payable + +interface PoolFactory5: + def add_liquidity(pool: address, amounts: uint256[5], min_mint_amount: uint256): payable + +interface PoolFactory6: # CRV/ATRICRYPTO, MATIC/ATRICRYPTO + def add_liquidity(pool: address, amounts: uint256[6], min_mint_amount: uint256, use_eth: bool): payable + +interface PoolStableNg: + def add_liquidity(_amounts: DynArray[uint256, MAX_COINS], _min_mint_amount: uint256): nonpayable + +interface PoolFactoryWithStableNgBase: + def add_liquidity(pool: address, _amounts: DynArray[uint256, MAX_COINS], _min_mint_amount: uint256): nonpayable + +interface Gauge: + def deposit(lp_token_amount: uint256, addr: address): nonpayable + + +allowance: public(HashMap[address, HashMap[address, bool]]) +gauge_allowance: HashMap[address, bool] + + +@internal +def _add_liquidity( + deposit: address, + n_coins: uint256, + amounts: DynArray[uint256, MAX_COINS], + min_mint_amount: uint256, + eth_value: uint256, + use_underlying: bool, + use_dynarray: bool, + pool: address +): + if pool != empty(address): + if use_dynarray: + PoolFactoryWithStableNgBase(deposit).add_liquidity(pool, amounts, min_mint_amount) + elif n_coins == 2: + PoolFactory2(deposit).add_liquidity(pool, [amounts[0], amounts[1]], min_mint_amount, value=eth_value) + elif n_coins == 3: + PoolFactory3(deposit).add_liquidity(pool, [amounts[0], amounts[1], amounts[2]], min_mint_amount, value=eth_value) + elif n_coins == 4: + PoolFactory4(deposit).add_liquidity(pool, [amounts[0], amounts[1], amounts[2], amounts[3]], min_mint_amount, value=eth_value) + elif n_coins == 5: + PoolFactory5(deposit).add_liquidity(pool, [amounts[0], amounts[1], amounts[2], amounts[3], amounts[4]], min_mint_amount, value=eth_value) + elif n_coins == 6: + PoolFactory6(deposit).add_liquidity(pool, [amounts[0], amounts[1], amounts[2], amounts[3], amounts[4], amounts[5]], min_mint_amount, True, value=eth_value) + else: + raise + elif use_dynarray: + PoolStableNg(deposit).add_liquidity(amounts, min_mint_amount) + elif use_underlying: + if n_coins == 2: + PoolUseUnderlying2(deposit).add_liquidity([amounts[0], amounts[1]], min_mint_amount, True, value=eth_value) + elif n_coins == 3: + PoolUseUnderlying3(deposit).add_liquidity([amounts[0], amounts[1], amounts[2]], min_mint_amount, True, value=eth_value) + elif n_coins == 4: + PoolUseUnderlying4(deposit).add_liquidity([amounts[0], amounts[1], amounts[2], amounts[3]], min_mint_amount, True, value=eth_value) + elif n_coins == 5: + PoolUseUnderlying5(deposit).add_liquidity([amounts[0], amounts[1], amounts[2], amounts[3], amounts[4]], min_mint_amount, True, value=eth_value) + elif n_coins == 6: + PoolUseUnderlying6(deposit).add_liquidity([amounts[0], amounts[1], amounts[2], amounts[3], amounts[4], amounts[5]], min_mint_amount, True, value=eth_value) + else: + raise + else: + if n_coins == 2: + Pool2(deposit).add_liquidity([amounts[0], amounts[1]], min_mint_amount, value=eth_value) + elif n_coins == 3: + Pool3(deposit).add_liquidity([amounts[0], amounts[1], amounts[2]], min_mint_amount, value=eth_value) + elif n_coins == 4: + Pool4(deposit).add_liquidity([amounts[0], amounts[1], amounts[2], amounts[3]], min_mint_amount, value=eth_value) + elif n_coins == 5: + Pool5(deposit).add_liquidity([amounts[0], amounts[1], amounts[2], amounts[3], amounts[4]], min_mint_amount, value=eth_value) + elif n_coins == 6: + Pool6(deposit).add_liquidity([amounts[0], amounts[1], amounts[2], amounts[3], amounts[4], amounts[5]], min_mint_amount, value=eth_value) + else: + raise + + +@payable +@external +@nonreentrant('lock') +def deposit_and_stake( + deposit: address, + lp_token: address, + gauge: address, + n_coins: uint256, + coins: DynArray[address, MAX_COINS], + amounts: DynArray[uint256, MAX_COINS], + min_mint_amount: uint256, + use_underlying: bool, # for aave, saave, ib (use_underlying) and crveth, cvxeth (use_eth) + use_dynarray: bool, + pool: address = empty(address), # for factory +): + assert n_coins >= 2, 'n_coins must be >=2' + assert n_coins <= MAX_COINS, 'n_coins must be <=MAX_COINS' + + # Ensure allowance for swap or zap + for i in range(MAX_COINS): + if i >= n_coins: + break + + if coins[i] == ETH_ADDRESS or amounts[i] == 0 or self.allowance[deposit][coins[i]]: + continue + + self.allowance[deposit][coins[i]] = True + ERC20(coins[i]).approve(deposit, max_value(uint256)) + + # Ensure allowance for gauge + if not self.gauge_allowance[gauge]: + self.gauge_allowance[gauge] = True + ERC20(lp_token).approve(gauge, max_value(uint256)) + + # Transfer coins from owner + has_eth: bool = False + for i in range(MAX_COINS): + if i >= n_coins: + break + + if coins[i] == ETH_ADDRESS: + assert msg.value == amounts[i] + has_eth = True + continue + + if amounts[i] > 0: + # "safeTransferFrom" which works for ERC20s which return bool or not + _response: Bytes[32] = raw_call( + 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) # dev: failed transfer + + if not has_eth: + assert msg.value == 0 + + # Reverts if n_coins is wrong + self._add_liquidity(deposit, n_coins, amounts, min_mint_amount, msg.value, use_underlying, use_dynarray, pool) + + lp_token_amount: uint256 = ERC20(lp_token).balanceOf(self) + assert lp_token_amount > 0 # dev: swap-token mismatch + + Gauge(gauge).deposit(lp_token_amount, msg.sender) + + +@payable +@external +def __default__(): + pass diff --git a/scripts/zksync-deploy.py b/scripts/zksync-deploy.py new file mode 100755 index 0000000..d5fb465 --- /dev/null +++ b/scripts/zksync-deploy.py @@ -0,0 +1,23 @@ +import os +import json +import boa +from getpass import getpass +import boa_zksync +from eth_account import account + +zksync_rpc = "https://mainnet.era.zksync.io" + +boa_zksync.set_zksync_env(zksync_rpc) + +def account_load(fname): + path = os.path.expanduser(os.path.join('~', '.brownie', 'accounts', fname + '.json')) + with open(path, 'r') as f: + pkey = account.decode_keyfile_json(json.load(f), getpass()) + return account.Account.from_key(pkey) + +def main(): + boa.env.add_account(account_load('curve-deployer')) + boa.load("contracts/DepositAndStakeZap0.3.10.vy") + +if __name__ == "__main__": + main()