diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 64c353f..c2033d5 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -24,31 +24,36 @@ jobs: - name: Run Tox run: tox -e lint - # TODO uncomment once test suite is ready - # core: - # runs-on: ${{ matrix.os }} - # strategy: - # fail-fast: false - # matrix: - # os: [macos-latest, ubuntu-latest, windows-latest] - # python-minor: [6, 7, 8] - - # steps: - # - uses: actions/checkout@v2 - - # - name: Setup Python 3.${{ matrix.python-minor }} - # uses: actions/setup-python@v2 - # with: - # python-version: 3.${{ matrix.python-minor }} - - # - name: Install Tox - # run: pip install tox wheel - - # - name: Run Tox - # run: tox -e py3${{ matrix.python-minor }} - - # - name: Upload coverage to Codecov - # uses: codecov/codecov-action@v1 - # with: - # file: ./coverage.xml - # fail_ci_if_error: true + core: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [macos-latest, ubuntu-latest, windows-latest] + python-minor: [6, 7, 8] + + steps: + - uses: actions/checkout@v2 + + - name: Setup Node.js + uses: actions/setup-node@v1 + + - name: Install Ganache + run: npm install -g ganache-cli@6.11.0 + + - name: Setup Python 3.${{ matrix.python-minor }} + uses: actions/setup-python@v2 + with: + python-version: 3.${{ matrix.python-minor }} + + - name: Install Tox + run: pip install tox wheel + + - name: Run Tox + run: tox -e py3${{ matrix.python-minor }} + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v1 + with: + file: ./coverage.xml + fail_ci_if_error: true diff --git a/CHANGELOG.md b/CHANGELOG.md index d0104c5..8b92be0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,12 @@ This changelog format is based on [Keep a Changelog](https://keepachangelog.com/ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased](https://github.com/iamdefinitelyahuman/brownie-token-tester) -- Add custom minting logic for tokens: pBTC, wZEC, renZEC, renBTC, wBTC, sBTC, tBTC, aave tokens + +## [0.1.0](https://github.com/iamdefinitelyahuman/brownie-token-tester/tree/v0.1.0) - 2021-01-25 +### Added +- Custom minting logic for tokens: pBTC, wZEC, renZEC, renBTC, wBTC, sBTC, tBTC, aave tokens - `forked.py`: Add handling of custom minting logic if token name starts with a specified prefix (e.g. 'AAVE') and custom minting logic for address does not exist +- Better error when using incompatible return types for `ERC20` ## [0.0.3](https://github.com/iamdefinitelyahuman/brownie-token-tester/tree/v0.0.3) - 2021-01-13 ### Fixed diff --git a/brownie_tokens/forked.py b/brownie_tokens/forked.py index 5d787ef..51c965b 100644 --- a/brownie_tokens/forked.py +++ b/brownie_tokens/forked.py @@ -39,7 +39,7 @@ def _mint_for_testing(self, target: str, amount: Wei, tx: Dict = None) -> None: # check for token name if no custom minting # logic exists for address for name in _token_names: - if hasattr(self, 'name') and self.name().startswith(name): + if hasattr(self, "name") and self.name().startswith(name): fn_name = f"mint_{name}" if hasattr(sys.modules[__name__], fn_name): getattr(sys.modules[__name__], fn_name)(self, target, amount) @@ -76,13 +76,14 @@ def mint_0x5228a22e72ccC52d415EcFd199F99D0665E7733b( token: MintableForkToken, target: str, amount: int ) -> None: # pBTC - token.mint(target, amount, {'from': "0x3423Fb35149875e965f06c926DA8BA82D63f7ddb"}) + token.mint(target, amount, {"from": "0x3423Fb35149875e965f06c926DA8BA82D63f7ddb"}) -def mint_0xEB4C2781e4ebA804CE9a9803C67d0893436bB27D(token: MintableForkToken, target: str, amount: int) -> None: +def mint_0xEB4C2781e4ebA804CE9a9803C67d0893436bB27D( + token: MintableForkToken, target: str, amount: int +) -> None: # renBTC - token.mint(target, amount, { - "from": "0xe4b679400F0f267212D5D812B95f58C83243EE71"}) + token.mint(target, amount, {"from": "0xe4b679400F0f267212D5D812B95f58C83243EE71"}) def mint_0x1C5db575E2Ff833E46a2E9864C22F4B22E0B37C2( @@ -100,18 +101,19 @@ def mint_0x196f4727526eA7FB1e17b2071B3d8eAA38486988( token.mint(target, amount, {"from": token.minter()}) -def mint_0xfE18be6b3Bd88A2D2A7f928d00292E7a9963CfC6(token: MintableForkToken, target: str, amount: int) -> None: +def mint_0xfE18be6b3Bd88A2D2A7f928d00292E7a9963CfC6( + token: MintableForkToken, target: str, amount: int +) -> None: # Synth sBTC - target_contract = Contract( - "0xDB91E4B3b6E19bF22E810C43273eae48C9037e74") - target_contract.issue(target, amount, { - "from": "0x778D2d3E3515e42573EB1e6a8d8915D4a22D9d54"}) + target_contract = Contract("0xDB91E4B3b6E19bF22E810C43273eae48C9037e74") + target_contract.issue(target, amount, {"from": "0x778D2d3E3515e42573EB1e6a8d8915D4a22D9d54"}) -def mint_0x8dAEBADE922dF735c38C80C7eBD708Af50815fAa(token: MintableForkToken, target: str, amount: int) -> None: +def mint_0x8dAEBADE922dF735c38C80C7eBD708Af50815fAa( + token: MintableForkToken, target: str, amount: int +) -> None: # tBTC - token.mint(target, amount, { - "from": "0x526c08E5532A9308b3fb33b7968eF78a5005d2AC"}) + token.mint(target, amount, {"from": "0x526c08E5532A9308b3fb33b7968eF78a5005d2AC"}) def mint_0x674C6Ad92Fd080e4004b2312b45f796a192D27a0( @@ -121,7 +123,9 @@ def mint_0x674C6Ad92Fd080e4004b2312b45f796a192D27a0( token.deposit(target, amount, {"from": "0x90f85042533F11b362769ea9beE20334584Dcd7D"}) -def mint_0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599(token: MintableForkToken, target: str, amount: int) -> None: +def mint_0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599( + token: MintableForkToken, target: str, amount: int +) -> None: # wBTC token.mint(target, amount, {"from": "0xCA06411bd7a7296d7dbdd0050DFc846E95fEBEB7"}) return @@ -134,15 +138,14 @@ def mint_0x4A64515E5E1d1073e83f30cB97BEd20400b66E10( token.mint(target, amount, {"from": "0x5Ca1262e25A5Fb6CA8d74850Da2753f0c896e16c"}) -# to add custom minting logic for a token that starts with [NAME], add [NAME] to `_token_names` and add a function `mint_[NAME]` +# to add custom minting logic for a token that starts with [NAME], add [NAME] to +# `_token_names` and add a function `mint_[NAME]` def mint_Aave(token: MintableForkToken, target: str, amount: int) -> None: # aave token token = MintableForkToken(token.UNDERLYING_ASSET_ADDRESS()) - lending_pool = Contract( - "0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9") + lending_pool = Contract("0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9") token._mint_for_testing(target, amount) - token.approve(lending_pool, amount, {'from': target}) - lending_pool.deposit(token, amount, - target, 0, {'from': target}) + token.approve(lending_pool, amount, {"from": target}) + lending_pool.deposit(token, amount, target, 0, {"from": target}) diff --git a/brownie_tokens/template.py b/brownie_tokens/template.py index b3cfd8d..5561884 100644 --- a/brownie_tokens/template.py +++ b/brownie_tokens/template.py @@ -1,27 +1,27 @@ from brownie import Contract, compile_source from pathlib import Path -from typing import Union +from typing import Dict, Union -RETURN_TYPE = { +RETURN_TYPE: Dict = { True: " -> bool", False: " -> bool", None: "", } -RETURN_STATEMENT = { +RETURN_STATEMENT: Dict = { True: "return True", False: "return False", None: "return", } -FAIL_STATEMENT = { +FAIL_STATEMENT: Dict = { "revert": "raise", True: "return True", False: "return False", None: "return", } -STRING_CONVERT = { +STRING_CONVERT: Dict = { "true": True, "false": False, "none": None, @@ -72,6 +72,8 @@ def ERC20( if fail not in FAIL_STATEMENT: valid_keys = [str(i) for i in FAIL_STATEMENT.keys()] raise ValueError(f"Invalid value for `fail`, valid options are: {', '.join(valid_keys)}") + if None in (fail, success) and fail is not success: + raise ValueError("Cannot use `None` for only one of `success` and `fail`.") source = TEMPLATE.format( return_type=RETURN_TYPE[success], diff --git a/setup.cfg b/setup.cfg index 5643409..cddaeef 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.0.3 +current_version = 0.1.0 [bumpversion:file:setup.py] @@ -28,3 +28,4 @@ addopts = --cov brownie_tokens/ --cov-report term --cov-report xml + diff --git a/setup.py b/setup.py index 1985f7a..0974c2c 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ name="brownie-token-tester", packages=find_packages(exclude=["tests", "tests.*"]), py_modules=["brownie_tokens"], - version="0.0.3", # don't change this manually, use bumpversion instead + version="0.1.0", # don't change this manually, use bumpversion instead license="MIT", description="Helper objects for generating ERC20s while testing a Brownie project.", long_description=long_description, diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..e82a56e --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,23 @@ +import brownie +import pytest + + +def pytest_configure(): + brownie.network.connect("development") + + +@pytest.fixture(scope="function", autouse=True) +def isolation(): + brownie.chain.snapshot() + yield + brownie.chain.revert() + + +@pytest.fixture(scope="session") +def alice(): + yield brownie.accounts[0] + + +@pytest.fixture(scope="session") +def bob(): + yield brownie.accounts[1] diff --git a/tests/test_erc20_return_values.py b/tests/test_erc20_return_values.py new file mode 100644 index 0000000..2913fee --- /dev/null +++ b/tests/test_erc20_return_values.py @@ -0,0 +1,83 @@ +import itertools +import pytest +from brownie.exceptions import VirtualMachineError + +from brownie_tokens import ERC20 +from brownie_tokens.template import FAIL_STATEMENT, RETURN_STATEMENT + + +@pytest.fixture(scope="module", params=itertools.product(RETURN_STATEMENT, FAIL_STATEMENT)) +def token(request, alice): + success, fail = request.param + if None in (fail, success) and fail is not success: + pytest.skip() + contract = ERC20(success=success, fail=fail) + contract._mint_for_testing(alice, 10 ** 18, {"from": alice}) + yield (contract, success, fail) + + +def test_transfer_success(alice, bob, token): + token, success, fail = token + tx = token.transfer(bob, 0, {"from": alice}) + if success is None: + assert len(tx.return_value) == 0 + else: + assert tx.return_value is success + + +def test_transfer_fail(alice, bob, token): + token, success, fail = token + if fail == "revert": + with pytest.raises(VirtualMachineError): + token.transfer(bob, 10 ** 19, {"from": alice}) + else: + tx = token.transfer(bob, 10 ** 19, {"from": alice}) + if fail is None: + assert len(tx.return_value) == 0 + else: + assert tx.return_value is fail + + +def test_approve(alice, bob, token): + token, success, fail = token + tx = token.approve(alice, 10 ** 21, {"from": bob}) + if success is None: + assert len(tx.return_value) == 0 + else: + assert tx.return_value is success + + +def test_transferFrom_success(alice, bob, token): + token, success, fail = token + tx = token.transferFrom(alice, bob, 0, {"from": alice}) + if success is None: + assert len(tx.return_value) == 0 + else: + assert tx.return_value is success + + +def test_transferFrom_fail_allowance(alice, bob, token): + token, success, fail = token + if fail == "revert": + with pytest.raises(VirtualMachineError): + token.transferFrom(alice, bob, 10 ** 18, {"from": bob}) + else: + tx = token.transferFrom(alice, bob, 10 ** 18, {"from": bob}) + if fail is None: + assert len(tx.return_value) == 0 + else: + assert tx.return_value is fail + + +def test_transferFrom_fail_balance(alice, bob, token): + token, success, fail = token + token.approve(bob, 10 ** 21, {"from": alice}) + if fail == "revert": + with pytest.raises(VirtualMachineError): + token.transferFrom(alice, bob, 10 ** 21, {"from": bob}) + else: + tx = token.transferFrom(alice, bob, 10 ** 21, {"from": bob}) + if fail is None: + assert len(tx.return_value) == 0 + else: + assert tx.return_value is fail