diff --git a/.github/workflows/deployment-tests.yaml b/.github/workflows/deployment-tests.yaml new file mode 100644 index 00000000..75c55bd8 --- /dev/null +++ b/.github/workflows/deployment-tests.yaml @@ -0,0 +1,40 @@ +name: deployment-tests-ape + +on: ["push", "pull_request"] + +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + WEB3_ALCHEMY_API_KEY: ${{ secrets.WEB3_ALCHEMY_API_KEY }} + ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }} + +jobs: + deployment-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Cache Compiler Installations + uses: actions/cache@v2 + with: + path: | + ~/.vvm + key: compiler-cache + + - name: Setup Python 3.10.4 + uses: actions/setup-python@v2 + with: + python-version: 3.10.4 + + - name: Install Requirements + run: pip install -r requirements_ape.txt + + - name: Install node + uses: actions/setup-node@v3 + with: + node-version: 18 + + - name: Install Hardcat + run: npm install --save-dev hardhat + + - name: Run Deployment Tests + run: ape test --network ethereum:mainnet-fork:hardhat test_deployment diff --git a/hardhat.config.js b/hardhat.config.js index 68e471fe..d2d23fda 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -3,7 +3,7 @@ module.exports = { networks: { hardhat: { - hardfork: "london", + hardfork: "shanghai", // Base fee of 0 allows use of 0 gas price when testing initialBaseFeePerGas: 0, accounts: { diff --git a/requirements.txt b/requirements.txt index f6826346..a25274ec 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,5 +19,5 @@ pdbpp hypothesis>=6.68.1 # vyper and dev framework: -git+https://github.com/vyperlang/titanoboa +git+https://github.com/vyperlang/titanoboa@c1d741c26b34798ec1620859c7f3d8f42416e4be vyper>=0.3.9 diff --git a/requirements_ape.txt b/requirements_ape.txt index eb609fc8..90a7bd0b 100644 --- a/requirements_ape.txt +++ b/requirements_ape.txt @@ -4,9 +4,14 @@ pytest pdbpp # vyper and dev framework: -git+https://github.com/vyperlang/titanoboa -eth-ape>=0.6.9 +git+https://github.com/vyperlang/titanoboa@c1d741c26b34798ec1620859c7f3d8f42416e4be vyper>=0.3.9 +eth-ape==0.6.19 +ape-etherscan==0.6.10 +ape-hardhat==0.6.12 +ape-alchemy==0.6.4 +ape-vyper==0.6.10 +python-dotenv==1.0.0 # prices api: pycoingecko diff --git a/requirements_freeze.txt b/requirements_freeze.txt new file mode 100644 index 00000000..055954ae --- /dev/null +++ b/requirements_freeze.txt @@ -0,0 +1,154 @@ +aiohttp==3.8.5 +aiosignal==1.3.1 +ape-alchemy==0.6.4 +ape-etherscan==0.6.10 +ape-hardhat==0.6.12 +ape-vyper==0.6.10 +appnope==0.1.3 +asttokens==2.4.0 +async-timeout==4.0.3 +attrs==23.1.0 +backcall==0.2.0 +base58==1.0.3 +bitarray==2.8.1 +black==23.9.1 +build==1.0.3 +cached-property==1.5.2 +certifi==2023.7.22 +cffi==1.15.1 +cfgv==3.4.0 +charset-normalizer==3.2.0 +chompjs==1.2.2 +click==8.1.7 +commonmark==0.9.1 +cryptography==41.0.3 +cytoolz==0.12.2 +dataclassy==0.11.1 +decorator==5.1.1 +Deprecated==1.2.14 +distlib==0.3.7 +eip712==0.2.1 +eth-abi==4.2.0 +eth-account==0.8.0 +eth-ape==0.6.19 +eth-bloom==2.0.0 +eth-hash==0.5.2 +eth-keyfile==0.6.1 +eth-keys==0.4.0 +eth-rlp==0.3.0 +eth-stdlib==0.2.7 +eth-tester==0.9.1b1 +eth-typing==3.4.0 +eth-utils==2.2.0 +ethpm-types==0.5.6 +evm-trace==0.1.0a24 +exceptiongroup==1.1.3 +execnet==2.0.2 +executing==1.2.0 +fancycompleter==0.9.1 +filelock==3.12.3 +flake8==6.1.0 +frozenlist==1.4.0 +hexbytes==0.3.1 +hypothesis==6.84.3 +identify==2.5.27 +idna==3.4 +ijson==3.2.3 +importlib-metadata==6.8.0 +iniconfig==2.0.0 +ipython==8.15.0 +isort==5.12.0 +jedi==0.19.0 +jsonschema==4.19.0 +jsonschema-specifications==2023.7.1 +lark==1.1.7 +lazyasd==0.1.4 +lru-dict==1.2.0 +mamushi==0.0.2a1 +matplotlib-inline==0.1.6 +mccabe==0.7.0 +morphys==1.0 +msgspec==0.18.2 +multidict==6.0.4 +mypy-extensions==1.0.0 +nodeenv==1.8.0 +numpy==1.25.2 +packaging==23.1 +pandas==1.5.3 +parsimonious==0.9.0 +parso==0.8.3 +pathspec==0.11.2 +pdbpp==0.10.3 +pexpect==4.8.0 +pickleshare==0.7.5 +pip-tools==7.3.0 +platformdirs==3.10.0 +pluggy==1.3.0 +pre-commit==3.4.0 +prompt-toolkit==3.0.39 +protobuf==4.24.3 +ptyprocess==0.7.0 +pure-eval==0.2.2 +py==1.11.0 +py-cid==0.3.0 +py-ecc==6.0.0 +py-evm==0.7.0a4 +py-geth==3.13.0 +py-multibase==1.0.3 +py-multicodec==0.2.1 +py-multihash==0.2.3 +pycodestyle==2.11.0 +pycoingecko==3.1.0 +pycparser==2.21 +pycryptodome==3.18.0 +pydantic==1.10.12 +pyethash==0.1.27 +pyflakes==3.1.0 +PyGithub==1.59.1 +Pygments==2.16.1 +PyJWT==2.8.0 +PyNaCl==1.5.0 +pyproject_hooks==1.0.0 +pyrepl==0.9.0 +pytest==7.4.2 +pytest-forked==1.6.0 +pytest-repeat==0.9.1 +pytest-xdist==3.3.1 +python-baseconv==1.2.2 +python-dateutil==2.8.2 +python-dotenv==1.0.0 +pytz==2023.3.post1 +pyunormalize==15.0.0 +PyYAML==6.0.1 +referencing==0.30.2 +regex==2023.8.8 +requests==2.31.0 +rich==12.6.0 +rlp==3.0.0 +rpds-py==0.10.2 +safe-pysha3==1.0.4 +semantic-version==2.10.0 +six==1.16.0 +sortedcontainers==2.4.0 +SQLAlchemy==2.0.20 +stack-data==0.6.2 +titanoboa @ git+https://github.com/vyperlang/titanoboa@c1d741c26b34798ec1620859c7f3d8f42416e4be +tomli==2.0.1 +toolz==0.12.0 +tqdm==4.66.1 +traitlets==5.9.0 +trie==2.1.1 +typing_extensions==4.7.1 +urllib3==2.0.4 +varint==1.0.2 +virtualenv==20.24.5 +vvm==0.1.0 +vyper==0.3.9 +watchdog==3.0.0 +wcwidth==0.2.6 +web3==6.9.0 +websockets==11.0.3 +wmctrl==0.4 +wrapt==1.15.0 +yarl==1.9.2 +zipp==3.16.2 diff --git a/scripts/__init__.py b/scripts/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/scripts/deploy.py b/scripts/deploy.py index c052e82f..25fb9b30 100644 --- a/scripts/deploy.py +++ b/scripts/deploy.py @@ -27,6 +27,14 @@ "views": "0x06452f9c013fc37169B57Eab8F50A7A48c9198A3", "amm_impl": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B", }, + "mainnet-fork": { + "factory": "0x0c0e5f2fF0ff18a3be9b835635039256dC4B4963", + "math": "0xcBFf3004a20dBfE2731543AA38599A526e0fD6eE", + "views": "0x064253915b8449fdEFac2c4A74aA9fdF56691a31", + "amm_impl": "0x66442B0C5260B92cAa9c234ECf2408CBf6b19a6f", + "gauge_impl": "0x5fC124a161d888893529f67580ef94C2784e9233", + "factory_handler": "0x30a4249C42be05215b6063691949710592859697", + }, } @@ -181,21 +189,15 @@ def _get_encoded_constructor_args(tx, PARAMS): return args -@click.group(short_help="Deploy the project") -def cli(): - pass - - -@cli.command(cls=NetworkBoundCommand) -@network_option() -@account_option() -def deploy_and_test_infra(network, account): - - if account.alias == "fiddydeployer": +def deploy_infra(network, account): + # account is str in case of forked tests + if "mainnet-fork" in network: + account = "0x40907540d8a6C65c637785e8f8B742ae6b0b9968" + elif account.alias == "fiddydeployer": account.set_autosign(True) - if "ethereum:mainnet-fork" in network: - account = accounts["0xbabe61887f1de2713c6f97e567623453d3c79f67"] + owner = None + fee_receiver = None for _network, data in deploy_utils.curve_dao_network_settings.items(): @@ -222,9 +224,24 @@ def deploy_and_test_infra(network, account): ] _account = account - if "ethereum:mainnet-fork" in network: # bridge - _account = accounts["0x8EB8a3b98659Cce290402893d0123abb75E3ab28"] + if "mainnet-fork" in network: # bridge + _account = "0x8EB8a3b98659Cce290402893d0123abb75E3ab28" + + print("Success!") + + return pool, coins, fee_receiver, _account + + +@click.group(short_help="Deploy the project") +def cli(): + pass + +@cli.command(cls=NetworkBoundCommand) +@network_option() +@account_option() +def deploy_and_test_infra(network, account): + pool, coins, fee_receiver, _account = deploy_infra(network, account) deploy_utils.test_deployment(pool, coins, fee_receiver, _account) print("Success!") diff --git a/scripts/deployment_utils.py b/scripts/deployment_utils.py index efbf3786..d84bda19 100644 --- a/scripts/deployment_utils.py +++ b/scripts/deployment_utils.py @@ -164,6 +164,13 @@ class CurveNetworkSettings: wbtc_address="", weth_address="", ), + "mainnet-fork": CurveNetworkSettings( + dao_ownership_contract="0x40907540d8a6C65c637785e8f8B742ae6b0b9968", + fee_receiver_address="0xeCb456EA5365865EbAb8a2661B0c503410e9B347", + usdc_address="0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + wbtc_address="0x2260fac5e5542a773aa44fbcfedf7c193bc2c599", + weth_address="0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + ), } diff --git a/test_deployment/__init__.py b/test_deployment/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test_deployment/conftest.py b/test_deployment/conftest.py new file mode 100644 index 00000000..870c8a9b --- /dev/null +++ b/test_deployment/conftest.py @@ -0,0 +1,65 @@ +import pytest +from ape import Contract, Project, chain +from ape.contracts import ContractContainer + +from scripts.deploy import deploy_infra + + +@pytest.fixture(scope="function") +def admin(): + return "0x40907540d8a6C65c637785e8f8B742ae6b0b9968" + + +@pytest.fixture(scope="function") +def deploy(admin, project: Project): + pool, coins, fee_receiver, _account = deploy_infra( + project.network_manager.network.name, admin + ) + return Contract(pool), [Contract(c) for c in coins], fee_receiver, _account + + +@pytest.fixture(scope="function") +def admin_mint_tokens(admin, project): + # USDC + token_impl = ContractContainer( + Contract("0xB7277a6e95992041568D9391D09d0122023778A2").contract_type + ) + token_contract = token_impl.at( + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" + ) + token_minter = "0xe982615d461dd5cd06575bbea87624fda4e3de17" + project.provider.set_balance(token_minter, 10**18) + amount = 1000 * 10**6 + token_contract.configureMinter(token_minter, amount, sender=token_minter) + token_contract.mint(admin, amount, sender=token_minter) + assert token_contract.balanceOf(admin) >= amount + + # WETH + project.provider.set_balance(admin, 2 * 10**18) + weth_contract = Contract( + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + contract_type=chain.contracts._get_contract_type_from_explorer( + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" + ), + ) + weth_contract.deposit(value=1 * 10**18, sender=admin) + assert weth_contract.balanceOf(admin) >= 1 * 10**18 + + # WBTC + wbtc_contract = Contract( + "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", + contract_type=chain.contracts._get_contract_type_from_explorer( + "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599" + ), + ) + wbtc_owner = wbtc_contract.owner() + project.provider.set_balance(wbtc_owner, 1 * 10**18) + wbtc_contract.mint(admin, 1 * 10**7, sender=wbtc_owner) + assert wbtc_contract.balanceOf(admin) >= 1 * 10**7 + + +@pytest.fixture(scope="function") +def approve_admin(admin, deploy): + pool = deploy[0] + for coin in deploy[1]: + coin.approve(pool.address, 2**256 - 1, sender=admin) diff --git a/test_deployment/test_deploy.py b/test_deployment/test_deploy.py new file mode 100644 index 00000000..9cd30e19 --- /dev/null +++ b/test_deployment/test_deploy.py @@ -0,0 +1,78 @@ +import pytest + +from scripts.deployment_utils import ( + get_deposit_amounts, + get_tricrypto_usdc_params, +) + + +class TestDeploy: + PARAMS = get_tricrypto_usdc_params() + + @pytest.fixture(scope="function") + def tokens_to_add(self, deploy): + return get_deposit_amounts( + 500, self.PARAMS["initial_prices"], deploy[1] + ) + + def test_add_liquidity( + self, admin, deploy, admin_mint_tokens, approve_admin, tokens_to_add + ): + pool = deploy[0] + tx = pool.add_liquidity(tokens_to_add, 0, False, sender=admin) + d_tokens = tx.return_value + assert pool.balanceOf(admin) == pool.totalSupply() == d_tokens + + @pytest.mark.parametrize(("i", "j"), ((0, 1), (0, 2), (1, 2))) + def test_exchange( + self, + admin, + deploy, + admin_mint_tokens, + approve_admin, + tokens_to_add, + i, + j, + ): + pool = deploy[0] + pool.add_liquidity(tokens_to_add, 0, False, sender=admin) + + if i == 0: + amount = 100 * 10 * 6 + else: + amount = 1 * 10**6 + + tx = pool.exchange_underlying( + i, + j, + amount, + 0, + sender=admin, + ) + dy_eth = tx.events.filter(pool.TokenExchange)[ + 0 + ].tokens_bought # return_value is broken in ape somehow + assert dy_eth > 0 + + @pytest.mark.parametrize("i", (0, 1, 2)) + def test_remove_liquidity_one( + self, admin, deploy, admin_mint_tokens, approve_admin, tokens_to_add, i + ): + pool = deploy[0] + pool.add_liquidity(tokens_to_add, 0, False, sender=admin) + + bal = pool.balanceOf(admin) + coin_contract = deploy[1][i] + coin_balance = coin_contract.balanceOf(admin) + + tx = pool.remove_liquidity_one_coin( + bal // 2, + i, + 0, + False, + sender=admin, + ) # noqa: E501 + + dy_coin = tx.events.filter(pool.RemoveLiquidityOne)[0].coin_amount + assert dy_coin > 0 + assert coin_contract.balanceOf(admin) == coin_balance + dy_coin