Hack and slash with mock web3 backend

pull/1949/head
Kieran Prasch 2020-05-01 11:30:33 -07:00 committed by Kieran R. Prasch
parent 2c9467ad7f
commit 923832b3c5
6 changed files with 347 additions and 31 deletions

View File

@ -436,7 +436,14 @@ class EthereumTesterClient(EthereumClient):
def unlock_account(self, account, password, duration: int = None) -> bool:
"""Returns True if the testing backend keyring has control of the given address."""
account = to_canonical_address(account)
keystore = self.w3.provider.ethereum_tester.backend._key_lookup
try:
# PyEVM backend
keystore = self.w3.provider.ethereum_tester.backend._key_lookup
except AttributeError:
# Mock provider, probably
keystore = self.w3.provider.ethereum_tester.backend.get_accounts()
if account in keystore:
return True
else:

View File

@ -17,15 +17,11 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
import collections
import os
import pprint
from typing import List, Callable
from typing import Tuple
from typing import Union
from urllib.parse import urlparse
import click
import maya
import os
import pprint
import requests
import time
from constant_sorrow.constants import (
@ -40,6 +36,10 @@ from eth_tester import EthereumTester
from eth_tester.exceptions import TransactionFailed as TestTransactionFailed
from eth_utils import to_checksum_address
from twisted.logger import Logger
from typing import List, Callable
from typing import Tuple
from typing import Union
from urllib.parse import urlparse
from web3 import Web3, WebsocketProvider, HTTPProvider, IPCProvider, middleware
from web3.contract import ContractConstructor, Contract
from web3.contract import ContractFunction
@ -51,13 +51,12 @@ from web3.middleware import geth_poa_middleware
from nucypher.blockchain.eth.clients import EthereumClient, POA_CHAINS
from nucypher.blockchain.eth.decorators import validate_checksum_address
from nucypher.blockchain.eth.providers import (
_get_tester_pyevm,
_get_test_geth_parity_provider,
_get_auto_provider,
_get_infura_provider,
_get_IPC_provider,
_get_websocket_provider,
_get_HTTP_provider
_get_HTTP_provider, _get_mock_test_provider, _get_pyevm_test_provider
)
from nucypher.blockchain.eth.registry import BaseContractRegistry
from nucypher.blockchain.eth.sol.compile import SolidityCompiler
@ -379,9 +378,10 @@ class BlockchainInterface:
if uri_breakdown.scheme == 'tester':
providers = {
'pyevm': _get_tester_pyevm,
'pyevm': _get_pyevm_test_provider,
'geth': _get_test_geth_parity_provider,
'parity-ethereum': _get_test_geth_parity_provider,
'mock': _get_mock_test_provider
}
provider_scheme = uri_breakdown.netloc
@ -746,8 +746,14 @@ class BlockchainDeployerInterface(BlockchainInterface):
class DeploymentFailed(RuntimeError):
pass
def __init__(self, compiler: SolidityCompiler = None, ignore_solidity_check: bool = False, *args, **kwargs):
def __init__(self,
compiler: SolidityCompiler = None,
ignore_solidity_check: bool = False,
dry_run: bool = False,
*args, **kwargs):
super().__init__(*args, **kwargs)
self.dry_run = dry_run
self.compiler = compiler or SolidityCompiler(ignore_solidity_check=ignore_solidity_check)
def connect(self):
@ -755,7 +761,9 @@ class BlockchainDeployerInterface(BlockchainInterface):
self._setup_solidity(compiler=self.compiler)
return self.is_connected
def _setup_solidity(self, compiler: SolidityCompiler = None):
def _setup_solidity(self, compiler: SolidityCompiler = None) -> None:
if self.dry_run:
return # TODO
if compiler:
# Execute the compilation if we're recompiling
# Otherwise read compiled contract data from the registry.

View File

@ -14,8 +14,10 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
from typing import Union
import os
from eth_tester.backends.mock.main import MockBackend
from urllib.parse import urlparse
from eth_tester import EthereumTester
@ -89,19 +91,34 @@ def _get_auto_provider(provider_uri):
return w3.provider
def _get_tester_pyevm(provider_uri):
# https://web3py.readthedocs.io/en/latest/providers.html#httpprovider
def _get_pyevm_test_backend() -> PyEVMBackend:
from nucypher.utilities.sandbox.constants import PYEVM_GAS_LIMIT, NUMBER_OF_ETH_TEST_ACCOUNTS
# Initialize
genesis_params = PyEVMBackend._generate_genesis_params(overrides={'gas_limit': PYEVM_GAS_LIMIT})
pyevm_backend = PyEVMBackend(genesis_parameters=genesis_params)
pyevm_backend.reset_to_genesis(genesis_params=genesis_params, num_accounts=NUMBER_OF_ETH_TEST_ACCOUNTS)
return pyevm_backend
# Test provider entry-point
eth_tester = EthereumTester(backend=pyevm_backend, auto_mine_transactions=True)
def _get_ethereum_tester(test_backend: Union[PyEVMBackend, MockBackend]) -> EthereumTesterProvider:
eth_tester = EthereumTester(backend=test_backend, auto_mine_transactions=True)
provider = EthereumTesterProvider(ethereum_tester=eth_tester)
return provider
def _get_pyevm_test_provider(provider_uri):
""" Test provider entry-point"""
# https://github.com/ethereum/eth-tester#pyevm-experimental
pyevm_eth_tester = _get_pyevm_test_backend()
provider = _get_ethereum_tester(test_backend=pyevm_eth_tester)
return provider
def _get_mock_test_provider(provider_uri):
# https://github.com/ethereum/eth-tester#mockbackend
mock_backend = MockBackend()
provider = _get_ethereum_tester(test_backend=mock_backend)
return provider

View File

@ -102,6 +102,7 @@ class TesterBlockchain(BlockchainDeployerInterface):
eth_airdrop=False,
free_transactions=False,
compiler: SolidityCompiler = None,
mock_backend: bool = False,
*args, **kwargs):
if not test_accounts:
@ -110,12 +111,19 @@ class TesterBlockchain(BlockchainDeployerInterface):
if compiler:
TesterBlockchain._compiler = compiler
elif mock_backend:
TesterBlockchain._compiler = None # TODO
super().__init__(provider_uri=self._PROVIDER_URI,
provider_uri = None
if mock_backend:
provider_uri = 'tester://mock'
super().__init__(provider_uri=provider_uri or self._PROVIDER_URI,
provider_process=None,
poa=poa,
light=light,
compiler=self._compiler,
dry_run=mock_backend,
*args, **kwargs)
self.log = Logger("test-blockchain")

View File

@ -0,0 +1,265 @@
"""
This file is part of nucypher.
nucypher is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
nucypher is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
import pytest
from web3 import Web3
from nucypher.blockchain.economics import StandardTokenEconomics, BaseEconomics
from nucypher.blockchain.eth.token import NU
from nucypher.cli.commands.worklock import worklock
from nucypher.utilities.sandbox.constants import (
TEST_PROVIDER_URI,
TEMPORARY_DOMAIN
)
#
# @pytest.fixture(scope='module', autouse=True)
# def llamas(module_mocker):
# module_mocker.patch.object(RPC, 'eth_estimateGas', autospec=True)
#
def test_status(click_runner):
command = ('status',
# '--registry-filepath', agency_local_registry.filepath,
'--provider', TEST_PROVIDER_URI,
'--network', TEMPORARY_DOMAIN)
result = click_runner.invoke(worklock, command, catch_exceptions=False)
assert result.exit_code == 0
# assert str(NU.from_nunits(token_economics.worklock_supply)) in result.output
# assert str(Web3.fromWei(token_economics.worklock_min_allowed_bid, 'ether')) in result.output
#
#
# def test_bid(click_runner, mock_testerchain, agency_local_registry, token_economics, bids):
#
# # Wait until biding window starts
# mock_testerchain.time_travel(seconds=90)
#
# base_command = ('bid',
# '--registry-filepath', agency_local_registry.filepath,
# '--provider', TEST_PROVIDER_URI,
# '--network', TEMPORARY_DOMAIN,
# '--force')
#
# worklock_agent = ContractAgency.get_agent(WorkLockAgent, registry=agency_local_registry)
# total_bids = 0
# # Multiple bidders
# for bidder, bid_eth_value in bids.items():
# pre_bid_balance = mock_testerchain.client.get_balance(bidder)
# assert pre_bid_balance > to_wei(bid_eth_value, 'ether')
#
# command = (*base_command, '--bidder-address', bidder, '--value', bid_eth_value)
# user_input = f'{INSECURE_DEVELOPMENT_PASSWORD}\n' + 'Y\n'
# result = click_runner.invoke(worklock, command, input=user_input, catch_exceptions=False)
# assert result.exit_code == 0
#
# post_bid_balance = mock_testerchain.client.get_balance(bidder)
# difference = pre_bid_balance - post_bid_balance
# assert difference >= to_wei(bid_eth_value, 'ether')
#
# total_bids += to_wei(bid_eth_value, 'ether')
# assert mock_testerchain.client.get_balance(worklock_agent.contract_address) == total_bids
#
#
# def test_cancel_bid(click_runner, mock_testerchain, agency_local_registry, token_economics, bids):
#
# bidders = list(bids.keys())
#
# bidder = bidders[-1]
# agent = ContractAgency.get_agent(WorkLockAgent, registry=agency_local_registry)
#
# command = ('cancel-bid',
# '--bidder-address', bidder,
# '--registry-filepath', agency_local_registry.filepath,
# '--provider', TEST_PROVIDER_URI,
# '--network', TEMPORARY_DOMAIN,
# '--force')
#
# user_input = f'{INSECURE_DEVELOPMENT_PASSWORD}\n' + 'Y\n'
# result = click_runner.invoke(worklock, command, input=user_input, catch_exceptions=False)
# assert result.exit_code == 0
# assert not agent.get_deposited_eth(bidder) # No more bid
#
# # Wait until the end of the bidding period
# mock_testerchain.time_travel(seconds=token_economics.bidding_duration + 2)
#
# bidder = bidders[-2]
# command = ('cancel-bid',
# '--bidder-address', bidder,
# '--registry-filepath', agency_local_registry.filepath,
# '--provider', TEST_PROVIDER_URI,
# '--network', TEMPORARY_DOMAIN,
# '--force')
#
# user_input = f'{INSECURE_DEVELOPMENT_PASSWORD}\n' + 'Y\n'
# result = click_runner.invoke(worklock, command, input=user_input, catch_exceptions=False)
# assert result.exit_code == 0
# assert not agent.get_deposited_eth(bidder) # No more bid
#
#
# def test_post_initialization(click_runner, mock_testerchain, agency_local_registry, token_economics):
#
# # Wait until the end of the cancellation period
# mock_testerchain.time_travel(seconds=token_economics.cancellation_window_duration+2)
#
# bidder = mock_testerchain.client.accounts[0]
# agent = ContractAgency.get_agent(WorkLockAgent, registry=agency_local_registry)
# assert not agent.is_claiming_available()
# assert not agent.bidders_checked()
#
# command = ('enable-claiming',
# '--bidder-address', bidder,
# '--registry-filepath', agency_local_registry.filepath,
# '--provider', TEST_PROVIDER_URI,
# '--force',
# '--network', TEMPORARY_DOMAIN,
# '--gas-limit', 100000)
#
# user_input = f'{INSECURE_DEVELOPMENT_PASSWORD}\n' + 'Y\n'
# result = click_runner.invoke(worklock, command, input=user_input, catch_exceptions=False)
# assert result.exit_code == 0
# assert agent.is_claiming_available()
# assert agent.bidders_checked()
#
#
# def test_claim(click_runner, mock_testerchain, agency_local_registry, token_economics):
# agent = ContractAgency.get_agent(WorkLockAgent, registry=agency_local_registry)
#
# bidder = mock_testerchain.client.accounts[2]
# command = ('claim',
# '--bidder-address', bidder,
# '--registry-filepath', agency_local_registry.filepath,
# '--provider', TEST_PROVIDER_URI,
# '--network', TEMPORARY_DOMAIN,
# '--force')
#
# user_input = f'{INSECURE_DEVELOPMENT_PASSWORD}\n' + 'Y\n'
# result = click_runner.invoke(worklock, command, input=user_input, catch_exceptions=False)
# assert result.exit_code == 0
#
# whale = mock_testerchain.client.accounts[0]
# assert agent.get_available_compensation(checksum_address=whale) > 0
# command = ('claim',
# '--bidder-address', whale,
# '--registry-filepath', agency_local_registry.filepath,
# '--provider', TEST_PROVIDER_URI,
# '--network', TEMPORARY_DOMAIN,
# '--force')
#
# user_input = f'{INSECURE_DEVELOPMENT_PASSWORD}\n' + 'Y\n'
# result = click_runner.invoke(worklock, command, input=user_input, catch_exceptions=False)
# assert result.exit_code == 0
# assert agent.get_available_compensation(checksum_address=whale) == 0
#
# # TODO: Check successful new stake in StakingEscrow
#
#
# def test_remaining_work(click_runner, mock_testerchain, agency_local_registry, token_economics):
# bidder = mock_testerchain.client.accounts[2]
#
# # Ensure there is remaining work one layer below
# worklock_agent = ContractAgency.get_agent(WorkLockAgent, registry=agency_local_registry)
# remaining_work = worklock_agent.get_remaining_work(checksum_address=bidder)
# assert remaining_work > 0
#
# command = ('remaining-work',
# '--bidder-address', bidder,
# '--registry-filepath', agency_local_registry.filepath,
# '--provider', TEST_PROVIDER_URI,
# '--network', TEMPORARY_DOMAIN)
#
# result = click_runner.invoke(worklock, command, catch_exceptions=False)
# assert result.exit_code == 0
#
# # Ensure were displaying the bidder address and remaining work in the output
# assert bidder in result.output
# assert str(remaining_work) in result.output
#
#
# def test_refund(click_runner, mock_testerchain, agency_local_registry, token_economics):
#
# bidder = mock_testerchain.client.accounts[2]
# worker_address = mock_testerchain.unassigned_accounts[-1]
#
# #
# # WorkLock Staker-Worker
# #
#
# worklock_agent = ContractAgency.get_agent(WorkLockAgent, registry=agency_local_registry)
#
# # Bidder is now STAKER. Bond a worker.
# staker = Staker(is_me=True, checksum_address=bidder, registry=agency_local_registry)
# receipt = staker.set_worker(worker_address=worker_address)
# assert receipt['status'] == 1
#
# worker = Ursula(is_me=True,
# registry=agency_local_registry,
# checksum_address=bidder,
# worker_address=worker_address,
# rest_host=MOCK_IP_ADDRESS,
# rest_port=select_test_port())
#
# # Ensure there is work to do
# remaining_work = worklock_agent.get_remaining_work(checksum_address=bidder)
# assert remaining_work > 0
#
# # Do some work
# for i in range(3):
# receipt = worker.confirm_activity()
# assert receipt['status'] == 1
# mock_testerchain.time_travel(periods=1)
#
# command = ('refund',
# '--bidder-address', bidder,
# '--registry-filepath', agency_local_registry.filepath,
# '--provider', TEST_PROVIDER_URI,
# '--network', TEMPORARY_DOMAIN,
# '--force')
#
# user_input = f'{INSECURE_DEVELOPMENT_PASSWORD}\n' + 'Y\n'
# result = click_runner.invoke(worklock, command, input=user_input, catch_exceptions=False)
# assert result.exit_code == 0
#
# # Less work to do...
# new_remaining_work = worklock_agent.get_remaining_work(checksum_address=bidder)
# assert new_remaining_work < remaining_work
#
#
# def test_participant_status(click_runner, mock_testerchain, agency_local_registry, token_economics):
# bidder = Bidder(checksum_address=mock_testerchain.client.accounts[2], registry=agency_local_registry)
#
# command = ('status',
# '--registry-filepath', agency_local_registry.filepath,
# '--bidder-address', bidder.checksum_address,
# '--provider', TEST_PROVIDER_URI,
# '--network', TEMPORARY_DOMAIN)
#
# result = click_runner.invoke(worklock, command, catch_exceptions=False)
# assert result.exit_code == 0
#
# # Bidder-specific data is displayed
# assert bidder.checksum_address in result.output
# assert str(bidder.remaining_work) in result.output
# assert str(bidder.available_refund) in result.output
#
# # Worklock economics are displayed
# assert str(token_economics.worklock_boosting_refund_rate) in result.output
# assert str(NU.from_nunits(token_economics.worklock_supply)) in result.output

View File

@ -438,7 +438,7 @@ def test_registry():
return registry
def _make_testerchain() -> TesterBlockchain:
def _make_testerchain(mock_backend: bool = False) -> TesterBlockchain:
"""
https://github.com/ethereum/eth-tester # available-backends
"""
@ -456,7 +456,9 @@ def _make_testerchain() -> TesterBlockchain:
web3.eth.get_buffered_gas_estimate = _get_buffered_gas_estimate
# Create the blockchain
testerchain = TesterBlockchain(eth_airdrop=True, free_transactions=True)
testerchain = TesterBlockchain(eth_airdrop=not mock_backend,
free_transactions=True,
mock_backend=mock_backend)
BlockchainInterfaceFactory.register_interface(interface=testerchain, force=True)
@ -468,12 +470,19 @@ def _make_testerchain() -> TesterBlockchain:
return testerchain
@pytest.fixture(scope='session')
@pytest.fixture(scope='module') # FIXME : make session
def _testerchain() -> TesterBlockchain:
assert False
testerchain = _make_testerchain()
yield testerchain
@pytest.fixture(scope='module')
def mock_testerchain() -> TesterBlockchain:
testerchain = _make_testerchain(mock_backend=True)
yield testerchain
@pytest.fixture(scope='module')
def testerchain(_testerchain) -> TesterBlockchain:
testerchain = _testerchain
@ -574,8 +583,8 @@ def _make_agency(testerchain,
return token_agent, staking_agent, policy_agent
@pytest.fixture(scope='module', autouse=True)
def test_registry_source_manager(testerchain, test_registry):
@pytest.fixture(scope='module')
def test_registry_source_manager(test_registry):
class MockRegistrySource(CanonicalRegistrySource):
name = "Mock Registry Source"
@ -586,21 +595,13 @@ def test_registry_source_manager(testerchain, test_registry):
if self.network != TEMPORARY_DOMAIN:
raise ValueError(f"Somehow, MockRegistrySource is trying to get a registry for '{self.network}'. "
f"Only '{TEMPORARY_DOMAIN}' is supported.'")
factory = testerchain.get_contract_factory(contract_name=PREALLOCATION_ESCROW_CONTRACT_NAME)
preallocation_escrow_abi = factory.abi
self.allocation_template = {
"BENEFICIARY_ADDRESS": ["ALLOCATION_CONTRACT_ADDRESS", preallocation_escrow_abi]
}
def get_publication_endpoint(self) -> str:
return f":mock-registry-source:/{self.network}/{self.registry_name}"
def fetch_latest_publication(self) -> Union[str, bytes]:
self.logger.debug(f"Reading registry at {self.get_publication_endpoint()}")
if self.registry_name == BaseContractRegistry.REGISTRY_NAME:
registry_data = test_registry.read()
elif self.registry_name == IndividualAllocationRegistry.REGISTRY_NAME:
registry_data = self.allocation_template
registry_data = test_registry.read()
raw_registry_data = json.dumps(registry_data)
return raw_registry_data
@ -628,6 +629,16 @@ def agency_local_registry(testerchain, agency, test_registry):
os.remove(MOCK_REGISTRY_FILEPATH)
# TODO
@pytest.fixture(scope='module')
def agency_local_registry(mock_testerchain, agency, test_registry):
registry = LocalContractRegistry(filepath=MOCK_REGISTRY_FILEPATH)
registry.write(test_registry.read())
yield registry
if os.path.exists(MOCK_REGISTRY_FILEPATH):
os.remove(MOCK_REGISTRY_FILEPATH)
@pytest.fixture(scope="module")
def stakers(testerchain, agency, token_economics, test_registry):
token_agent, _staking_agent, _policy_agent = agency