diff --git a/.env.sample b/.env.sample deleted file mode 100644 index def86f6..0000000 --- a/.env.sample +++ /dev/null @@ -1,7 +0,0 @@ -DEFAULT_NETWORK=development -WEB3_INFURA_PROJECT_ID=XXX -WALLET_PRIVATE_KEY=XXX -WALLET_MNEMONIC=XXX -WALLET_PASSWORD=XXX -ETHERSCAN_TOKEN=XXX -ETHPLORER_API_KEY=XXX diff --git a/.github/workflows/brownie.yaml1 b/.github/workflows/brownie.yaml1 deleted file mode 100644 index 28e11c5..0000000 --- a/.github/workflows/brownie.yaml1 +++ /dev/null @@ -1,58 +0,0 @@ -on: [push, pull_request] - -name: main workflow - -env: - DEFAULT_NETWORK: ${{ secrets.DEFAULT_NETWORK }} - WALLET_MNEMONIC: ${{ secrets.WALLET_MNEMONIC }} - WALLET_PASSWORD: ${{ secrets.WALLET_PASSWORD }} - WALLET_PRIVATE_KEY: ${{ secrets.WALLET_PRIVATE_KEY }} - WEB3_INFURA_PROJECT_ID: ${{ secrets.WEB3_INFURA_PROJECT_ID }} - NODE_OPTIONS: --max_old_space_size=4096 - # ^ increasing available memory for node reduces issues with ganache crashing - # https://nodejs.org/api/cli.html#cli_max_old_space_size_size_in_megabytes - -jobs: - tests: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v1 - - - name: Cache Solidity Installations - uses: actions/cache@v1 - with: - path: ~/.solcx - key: ${{ runner.os }}-solcx-cache - - - name: Setup Node.js - uses: actions/setup-node@v1 - - - name: Install Ganache - run: npm install -g ganache-cli@6.9.1 - - - name: Install Solhint - run: npm install -g solhint - - - name: Setup Python 3.8 - uses: actions/setup-python@v1 - with: - python-version: 3.8 - - - name: Install Requirements - run: pip install -r requirements.txt - - - name: Create .env - run: | - touch .env - echo DEFAULT_NETWORK=$DEFAULT_NETWORK >> .env - echo WALLET_MNEMONIC=$WALLET_MNEMONIC >> .env - echo WALLET_PASSWORD=$WALLET_PASSWORD >> .env - echo WALLET_PRIVATE_KEY=$WALLET_PRIVATE_KEY >> .env - echo WEB3_INFURA_PROJECT_ID=$WEB3_INFURA_PROJECT_ID >> .env - - - name: Run Tests - run: brownie test - - - name: Run Solhint - run: solhint 'contracts/**/*.sol' diff --git a/.gitignore b/.gitignore index 9bf0d88..61f479d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,3 @@ -.idea/ - -__pycache__ -.history -.hypothesis/ -client/src/artifacts -reports/ -venv -.env .env.dev .env.kovan .env.kovan2 @@ -14,11 +5,12 @@ venv .env.mainnet .env.mainnet_fork +/.idea/ # Local Netlify folder -.netlify - +/.netlify/ /node_modules/ /cache/ /artifacts/ /coverage/ +/.env /coverage.json diff --git a/brownie-config.yaml1 b/brownie-config.yaml1 deleted file mode 100644 index f309ebe..0000000 --- a/brownie-config.yaml1 +++ /dev/null @@ -1,60 +0,0 @@ -# Change the build directory to be within react's scope -project_structure: - build: client/src/artifacts - -# Set a custom mnemonic for the development network -networks: - default: ${DEFAULT_NETWORK} # development, kovan, rinkeby, mainnet, mainnet-fork, ... - development: - cmd_settings: - mnemonic: ${WALLET_MNEMONIC} - contracts: - ren_token: "0x0000000000000000000000000000000000000000" - ren_BTC: "0x0000000000000000000000000000000000000000" - darknode_registry: "0x0000000000000000000000000000000000000000" - darknode_registry_store: "0x0000000000000000000000000000000000000000" - darknode_payment: "0x0000000000000000000000000000000000000000" - claim_rewards: "0x0000000000000000000000000000000000000000" - gateway: "0x0000000000000000000000000000000000000000" - mainnet: - contracts: &mainnet_contracts - ren_token: "0x408e41876cCCDC0F92210600ef50372656052a38" - ren_BTC: "0xEB4C2781e4ebA804CE9a9803C67d0893436bB27D" - darknode_registry: "0x2D7b6C95aFeFFa50C068D50f89C5C0014e054f0A" - darknode_registry_store: "0x60Ab11FE605D2A2C3cf351824816772a131f8782" - darknode_payment: "0x098e1708b920EFBdD7afe33Adb6a4CBa30c370B9" - claim_rewards: "0x0000000000000000000000000000000000000000" - gateway: "0x0000000000000000000000000000000000000000" - mainnet-fork: - contracts: - <<: *mainnet_contracts - kovan: - contracts: &kovan_contracts - ren_token: "0x2CD647668494c1B15743AB283A0f980d90a87394" - ren_BTC: "0x0A9ADD98C076448CBcFAcf5E457DA12ddbEF4A8f" - darknode_registry: "0x9954C9F839b31E82bc9CA98F234313112D269712" - darknode_registry_store: "0x9daa16aA19e37f3de06197a8B5E638EC5e487392" - darknode_payment: "0x023f2e94C3eb128D3bFa6317a3fF860BF93C1616" - claim_rewards: "0x7F8f7Aff44a63f61b7a120Ef2c34Ea2c4D9bD216" - gateway: "0x0000000000000000000000000000000000000000" - kovan-fork: - contracts: - <<: *kovan_contracts - -wallets: - from_key: ${WALLET_PRIVATE_KEY} - -# Enable output of development artifacts to load with react -dev_deployment_artifacts: true - -dotenv: .env - -dependencies: - - OpenZeppelin/openzeppelin-contracts@4.0.0 - # - renproject/darknode-sol@1.0.1 - -compiler: - solc: - version: null # set version based on pragma - remappings: - # - '@openzeppelin=OpenZeppelin/openzeppelin-contracts@2.5.1' diff --git a/kovan_tokens/__init__.py b/kovan_tokens/__init__.py deleted file mode 100644 index 8e34706..0000000 --- a/kovan_tokens/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -import brownie - -from kovan_tokens.forked import BrownieTokensError, MintableKovanForkToken, skip_holders - -brownie.config["autofetch_sources"] = True diff --git a/kovan_tokens/forked.py b/kovan_tokens/forked.py deleted file mode 100644 index 94941e4..0000000 --- a/kovan_tokens/forked.py +++ /dev/null @@ -1,107 +0,0 @@ -import os -import requests -import sys -from brownie import Contract, Wei, web3 -from brownie.convert import to_address -from typing import Dict, List, Optional - -_ethplorer_api_key: str = os.getenv("ETHPLORER_API_KEY") or "freekey" - -_token_holders: Dict = {} -_skip_list: List[str] = [] - -_token_names = ["Aave"] - - -class BrownieTokensError(Exception): - pass - - -def update_skipped_addresses(token: Optional[str] = None) -> None: - if token: - _token_holders[token] = [ - a for a in _token_holders[token] if a not in _skip_list - ] - else: - for token in _token_holders: - update_skipped_addresses(token) - - -def get_top_holders(address: str) -> List: - address = to_address(address) - - if address not in _token_holders: - holders = requests.get( - f"https://kovan-api.ethplorer.io/getTopTokenHolders/{address}", - params={"apiKey": _ethplorer_api_key, "limit": "50"}, - ).json() - - if "error" in holders: - api_key_message = "" - if _ethplorer_api_key == "freekey": - api_key_message = ( - " Adding $ETHPLORER_API_KEY (from https://ethplorer.io) as environment variable" - " may solve the problem." - ) - raise BrownieTokensError( - f"Ethplorer returned error: {holders['error']}." + api_key_message - ) - - _token_holders[address] = [to_address(i["address"]) for i in holders["holders"]] - if address in _token_holders[address]: - # don't steal from the treasury - that could cause wierdness - _token_holders[address].remove(address) - update_skipped_addresses(address) - - return _token_holders[address] - - -def skip_holders(*addresses: str) -> None: - """ - Addresses that will remain untouched via '_mint_for_testing'. - Does not include minting with custom logic. - """ - global _skip_list - _skip_list = list(set(_skip_list + list(addresses))) - update_skipped_addresses() - - -class MintableKovanForkToken(Contract): - """ - ERC20 wrapper for forked kovan tests that allows standardized token minting. - """ - - def _mint_for_testing(self, target: str, amount: Wei, tx: Dict = None) -> None: - # check for custom minting logic - fn_name = f"mint_{self.address}" - if hasattr(sys.modules[__name__], fn_name): - getattr(sys.modules[__name__], fn_name)(self, target, amount) - return - - # check for token name if no custom minting logic exists for address - if hasattr(self, "name") and not self.name.abi["inputs"]: - name = self.name() - fn_name = next( - (f"mint_{i}" for i in _token_names if name.startswith(i)), "" - ) - if fn_name and hasattr(sys.modules[__name__], fn_name): - mint_result = getattr(sys.modules[__name__], fn_name)( - self, target, amount - ) - if mint_result: - return - - # if no custom logic, fetch a list of the top - # token holders and start stealing from them - for address in get_top_holders(self.address): - balance = self.balanceOf(address) - if not balance: - continue - if amount > balance: - self.transfer(target, balance, {"from": address}) - amount -= balance - else: - self.transfer(target, amount, {"from": address}) - return - - raise ValueError(f"Insufficient tokens available to mint {self.name()}") diff --git a/scripts/deploy.py b/scripts/deploy.py deleted file mode 100644 index 407b73d..0000000 --- a/scripts/deploy.py +++ /dev/null @@ -1,66 +0,0 @@ -from brownie import accounts, config, RenToken, RenPool -from brownie.network.account import Account -from brownie.network.contract import Contract -import constants as C -import utils - -active_network: str = config["networks"]["default"] -private_key: str = config["wallets"]["from_key"] -is_development: bool = active_network == "development" - -contracts = config["networks"][active_network]["contracts"] - -ren_token_addr: str = contracts["ren_token"] -darknode_registry_addr: str = contracts["darknode_registry"] -claim_rewards_addr: str = contracts["claim_rewards"] -gateway_addr: str = contracts["gateway"] - - -def get_owner() -> Account: - """ - When in development, use the first Ganache account as the owner. - Otherwise, load from config. - """ - return accounts[0] if is_development else accounts.add(private_key) - - -def get_node_operator() -> Account: - """ - For now we set node_operator to equal owner, could be a different account in the future. - """ - return get_owner() - - -def get_ren_token(owner: Account) -> Contract or None: - """ - When in development, we a mock to interact to the ren token contract. - Load the real contract otherwise. - """ - return ( - RenToken.deploy({"from": owner}) - if is_development - else utils.load_contract(ren_token_addr) - ) - - -def main() -> tuple[Contract, Contract]: - """ - Set your .env file accordingly before deploying the RenPool contract. - In case of live networks, make sure your account is funded. - """ - owner: Account = get_owner() - node_operator: Account = get_node_operator() - ren_token: Contract = get_ren_token(owner) - - ren_pool = RenPool.deploy( - ren_token.address(), - darknode_registry_addr, - claim_rewards_addr, - gateway_addr, - owner, - C.POOL_BOND, - {"from": node_operator}, - publish_source=True, - ) - - return ren_token, ren_pool diff --git a/scripts/register_darknode.py b/scripts/register_darknode.py deleted file mode 100644 index dff3351..0000000 --- a/scripts/register_darknode.py +++ /dev/null @@ -1,87 +0,0 @@ -from brownie import accounts, config, Contract, RenPool -from brownie.network.account import Account -from brownie_tokens import MintableForkToken -from kovan_tokens.forked import MintableKovanForkToken -import constants as C - -active_network: str = config["networks"]["default"] -supported_networks: list[str] = ["mainnet-fork", "kovan-fork"] - -contracts = config["networks"][active_network]["contracts"] - -ren_BTC_addr: str = contracts["ren_BTC"] -ren_token_addr: str = contracts["ren_token"] -darknode_registry_addr: str = contracts["darknode_registry"] -darknode_payment_addr: str = contracts["darknode_payment"] -claim_rewards_addr: str = contracts["payment_rewards"] -gateway_addr: str = contracts["gateway"] - - -def check_network() -> None: - if active_network not in supported_networks: - raise ValueError(f"Unsupported network, switch to {str(supported_networks)}") - - -def get_ren_token(owner: Account) -> Contract or None: - return ( - MintableForkToken(ren_token_addr) - if active_network == C.NETWORKS["MAINNET_FORK"] - else MintableKovanForkToken(ren_token_addr) - ) - - -def main() -> list[Contract, Contract, any, any]: - """ - Deploy a RenPool contract to the mainnet-fork, lock the - pool by providing liquidity and finally register a - darknode instance. - See: https://youtu.be/0JrDbvBClEA (brownie tutorial) - See: https://renproject.github.io/contracts-ts/#/mainnet - """ - check_network() - - owner: Account = accounts[0] - node_operator: Account = accounts[1] - user: Account = accounts[2] - - ren_pool: Contract = RenPool.deploy( - ren_token_addr, - darknode_registry_addr, - darknode_payment_addr, - claim_rewards_addr, - gateway_addr, - owner, - C.POOL_BOND, - {"from": node_operator}, - ) - - darknode_registry: Contract = Contract(darknode_registry_addr) - ren_BTC: Contract = Contract(ren_BTC_addr) - - ren_token = get_ren_token() - ren_token._mint_for_testing(user, C.POOL_BOND) - - ren_token.approve(ren_pool, C.POOL_BOND, {"from": user}) - ren_pool.deposit(C.POOL_BOND, {"from": user}) - - if ren_pool.isLocked() != True: - raise ValueError("Pool is not locked") - - ren_pool.approveBondTransfer({"from": node_operator}) - ren_pool.registerDarknode(C.NODE_ID_HEX, C.PUBLIC_KEY, {"from": node_operator}) - - # Skip to the next epoch (1 month) for the registration to settle - darknode_registry.epoch({"from": ren_pool}) - - # Transfer fees from darknode to the darknode's owner account on the REN protocol - tx1 = ren_pool.transferRewardsToDarknodeOwner([ren_BTC]) - # Is there any way to test this? - - # Transfer rewards from the REN protocol to the node operator wallet - # tx2 = ren_pool.claimDarknodeRewards('renBTC', 1, node_operator) - tx2 = ren_pool.claimDarknodeRewards("BTC", 1, node_operator) - - # Make sure rewards have been transferred to the target wallet - # ren_BTC.balanceOf(node_operator) > node_operator_init_BTC_balance # Try to improve this! - - return ren_token, ren_pool, tx1, tx2 diff --git a/tests/conftest.py b/tests/conftest.py deleted file mode 100644 index b32e2e7..0000000 --- a/tests/conftest.py +++ /dev/null @@ -1,150 +0,0 @@ -from brownie import network, accounts, config, RenPool -from brownie_tokens import MintableForkToken -from kovan_tokens.forked import MintableKovanForkToken -import pytest -import constants as C -import utils - -""" -A fixture is a function that is applied to one or more test functions, and is called -prior to the execution of each test. Fixtures are used to setup the initial conditions -required for a test. - -Fixtures are declared using the @pytest.fixture decorator. To pass a fixture to a test, -include the fixture name as an input argument for the test. - -When pytest goes to run a test, it looks at the parameters in that test function’s -signature, and then searches for fixtures that have the same names as those parameters. -Once pytest finds them, it runs those fixtures, captures what they returned -(if anything), and passes those objects into the test function as arguments. - -See: https://eth-brownie.readthedocs.io/en/stable/tests-pytest-intro.html#fixtures -""" -active_network: str = config["networks"]["default"] -supported_networks: list[str] = ["mainnet-fork", "kovan-fork"] - -if active_network not in supported_networks: - raise ValueError(f"Unsupported network, switch to {str(supported_networks)}") - -# Required due to this bug https://github.com/eth-brownie/brownie/issues/918 -network.connect(active_network) - -contracts = config["networks"][active_network]["contracts"] - -ren_BTC_addr: str = contracts["ren_BTC"] -ren_token_addr: str = contracts["ren_token"] -darknode_registry_addr: str = contracts["darknode_registry"] -darknode_registry_store_addr: str = contracts["darknode_registry_store"] -darknode_payment_addr: str = contracts["darknode_payment"] -claim_rewards_addr: str = contracts["claim_rewards"] -gateway_addr: str = contracts["gateway"] - -""" -A common pattern is to include one or more module-scoped setup fixtures that define -the initial test conditions, and then use fn_isolation to revert to this base state -at the start of each test. - -See: https://eth-brownie.readthedocs.io/en/stable/tests-pytest-intro.html#isolation-fixtures -""" - - -@pytest.fixture(scope="module", autouse=True) -def shared_setup(module_isolation): - """ - Resetting the local environment. - """ - pass - - -@pytest.fixture(scope="module", autouse=True) -def ren_token(): - """ - Yield a `Contract` object for the REN token contract. - """ - if active_network == "mainnet-fork": - yield MintableForkToken(ren_token_addr) - elif active_network == "kovan-fork": - yield MintableKovanForkToken(ren_token_addr) - - -@pytest.fixture(scope="module", autouse=True) -def ren_BTC(): - """ - Yield a `Contract` object for the renBTC contract. - """ - yield utils.load_contract(ren_BTC_addr) - - -@pytest.fixture(scope="module", autouse=True) -def distribute_ren_tokens(ren_token): - """ - Set accounts initial balance to match C.POOL_BOND - """ - for i in range(0, 10): - ren_token._mint_for_testing(accounts[i], C.POOL_BOND) - - -@pytest.fixture(scope="module") -def owner(): - """ - Yield an `Account` object for the contract's owner. - """ - yield accounts[0] - - -@pytest.fixture(scope="module") -def node_operator(): - """ - Yield an `Account` object for the contract's node operator. - """ - yield accounts[1] - - -@pytest.fixture(scope="module") -def darknode_registry(): - """ - Yield a `Contract` object for the DarknodeRegistry contract. - """ - yield utils.load_contract(darknode_registry_addr) - - -@pytest.fixture(scope="module") -def darknode_registry_store(): - """ - Yield a `Contract` object for the DarknodeRegistryStore contract. - """ - yield utils.load_contract(darknode_registry_store_addr) - - -@pytest.fixture(scope="module") -def darknode_payment(): - """ - Yield a `Contract` object for the DarknodePayment contract. - """ - yield utils.load_contract(darknode_payment_addr) - - -@pytest.fixture(scope="module") -def ren_pool(owner, node_operator): - """ - Yield a `Contract` object for the RenPool contract. - """ - yield RenPool.deploy( - ren_token_addr, - darknode_registry_addr, - darknode_payment_addr, - claim_rewards_addr, - gateway_addr, - owner, - C.POOL_BOND, - {"from": node_operator}, - ) - - -@pytest.fixture(autouse=True) -def setup(fn_isolation): - """ - Isolation setup fixture. - This ensures that each test runs against the same base environment. - """ - pass diff --git a/tests/ren_pool/test_darknode_claim_rewards.py b/tests/ren_pool/test_darknode_claim_rewards.py deleted file mode 100644 index 9564dfc..0000000 --- a/tests/ren_pool/test_darknode_claim_rewards.py +++ /dev/null @@ -1,54 +0,0 @@ -from brownie import chain, accounts, reverts -import pytest -import constants as C - - -@pytest.mark.parametrize("user", accounts[0:3]) # [owner, nodeOperator, user] -def test_darknode_claim_rewards( - node_operator, - ren_pool, - ren_token, - ren_BTC, - darknode_registry, - user, -): - """ - Test transfering rewards from the REN protocol to the given address. - """ - chain.snapshot() - - node_operator_init_BTC_balance: int = ren_BTC.balanceOf(node_operator) - - # Lock pool - ren_token.approve(ren_pool, C.POOL_BOND, {"from": user}) - ren_pool.deposit(C.POOL_BOND, {"from": user}) - - # Register darknode - ren_pool.approveBondTransfer({"from": node_operator}) - ren_pool.registerDarknode(C.NODE_ID_HEX, C.PUBLIC_KEY, {"from": node_operator}) - - # Skip to the next epoch (1 month) for the registration to settle - chain.mine(timedelta=C.ONE_MONTH) - darknode_registry.epoch({"from": ren_pool}) - - # Make sure the darknode is under the 'registered' state - assert darknode_registry.isRegistered(C.NODE_ID_HEX) == True - - # Skip to the next epoch to make sure we have fees to claim - chain.mine(timedelta=C.ONE_MONTH) - darknode_registry.epoch({"from": ren_pool}) - - # Transfer fees from darknode to the darknode's owner account on the REN protocol - ren_pool.transferRewardsToDarknodeOwner([ren_BTC]) - # Is there any way to test this? - - # Transfer rewards from the REN protocol to the node operator wallet - ren_pool.claimDarknodeRewards("renBTC", 1, node_operator) - - # Make sure rewards have been transferred to the target wallet - ren_BTC.balanceOf( - node_operator - ) > node_operator_init_BTC_balance # Try to improve this! - - -# TODO: test remaining paths diff --git a/utils.py b/utils.py deleted file mode 100644 index 7b8527f..0000000 --- a/utils.py +++ /dev/null @@ -1,19 +0,0 @@ -from brownie import ZERO_ADDRESS, Contract -import base58 - - -def load_contract(addr): - if addr == ZERO_ADDRESS: - return None - try: - c = Contract(addr) - except ValueError: - c = Contract.from_explorer(addr) - except ValueError: - c = Contract.from_ethpm(addr) - return c - - -def base58_to_hex(str): - hex = "0x" + base58.b58decode(str).hex()[4:] - return hex