mirror of https://github.com/nucypher/nucypher.git
Merge pull request #2439 from vzotova/compile-std
Standard Solc Compile (Multiversion)pull/2452/head
commit
5972896510
|
@ -253,6 +253,7 @@ def run_apidoc(_):
|
|||
'scripts',
|
||||
Path('nucypher', 'utilities'),
|
||||
Path('nucypher', 'blockchain', 'eth', 'sol'),
|
||||
Path('nucypher', 'blockchain', 'eth', 'economics.py'),
|
||||
]
|
||||
for exclusion_item in exclusion_items:
|
||||
apidoc_command.append(f'{nucypher_module_dir / exclusion_item}')
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Rework internal solidity compiler usage to implement "Standard JSON Compile".
|
|
@ -18,12 +18,26 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
|||
import math
|
||||
import os
|
||||
import pprint
|
||||
import threading
|
||||
import time
|
||||
from typing import Callable, NamedTuple, Tuple, Union, Optional
|
||||
from typing import List
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import click
|
||||
import requests
|
||||
from eth_tester import EthereumTester
|
||||
from eth_tester.exceptions import TransactionFailed as TestTransactionFailed
|
||||
from eth_typing import ChecksumAddress
|
||||
from eth_utils import to_checksum_address
|
||||
from hexbytes.main import HexBytes
|
||||
from web3 import Web3, middleware, IPCProvider, WebsocketProvider, HTTPProvider
|
||||
from web3.contract import Contract, ContractConstructor, ContractFunction
|
||||
from web3.exceptions import ValidationError, TimeExhausted
|
||||
from web3.middleware import geth_poa_middleware
|
||||
from web3.providers import BaseProvider
|
||||
from web3.types import TxReceipt
|
||||
|
||||
from constant_sorrow.constants import (
|
||||
INSUFFICIENT_ETH,
|
||||
NO_BLOCKCHAIN_CONNECTION,
|
||||
|
@ -32,36 +46,29 @@ from constant_sorrow.constants import (
|
|||
READ_ONLY_INTERFACE,
|
||||
UNKNOWN_TX_STATUS
|
||||
)
|
||||
from eth_tester.exceptions import TransactionFailed as TestTransactionFailed
|
||||
from eth_typing import ChecksumAddress
|
||||
from eth_utils import to_checksum_address
|
||||
from hexbytes.main import HexBytes
|
||||
from web3 import Web3, middleware
|
||||
from web3.contract import Contract, ContractConstructor, ContractFunction
|
||||
from web3.exceptions import TimeExhausted, ValidationError
|
||||
from web3.middleware import geth_poa_middleware
|
||||
from web3.providers import BaseProvider
|
||||
from web3.types import TxReceipt
|
||||
|
||||
from nucypher.blockchain.eth.clients import EthereumClient, POA_CHAINS, InfuraClient
|
||||
from nucypher.blockchain.eth.decorators import validate_checksum_address
|
||||
from nucypher.blockchain.eth.providers import (
|
||||
_get_auto_provider,
|
||||
_get_HTTP_provider,
|
||||
_get_IPC_provider,
|
||||
_get_auto_provider,
|
||||
_get_mock_test_provider,
|
||||
_get_pyevm_test_provider,
|
||||
_get_test_geth_parity_provider,
|
||||
_get_websocket_provider
|
||||
)
|
||||
from nucypher.blockchain.eth.registry import BaseContractRegistry
|
||||
from nucypher.blockchain.eth.sol.compile import SolidityCompiler
|
||||
from nucypher.blockchain.eth.sol.compile.compile import multiversion_compile
|
||||
from nucypher.blockchain.eth.sol.compile.constants import SOLIDITY_SOURCE_ROOT
|
||||
from nucypher.blockchain.eth.sol.compile.types import SourceBundle
|
||||
from nucypher.blockchain.eth.utils import get_transaction_name, prettify_eth_amount
|
||||
from nucypher.characters.control.emitters import JSONRPCStdoutEmitter, StdoutEmitter
|
||||
from nucypher.utilities.ethereum import encode_constructor_arguments
|
||||
from nucypher.utilities.gas_strategies import datafeed_fallback_gas_price_strategy, WEB3_GAS_STRATEGIES
|
||||
from nucypher.utilities.logging import GlobalLoggerSettings, Logger
|
||||
|
||||
Web3Providers = Union[IPCProvider, WebsocketProvider, HTTPProvider, EthereumTester] # TODO: Move to types.py
|
||||
|
||||
|
||||
class VersionedContract(Contract):
|
||||
version = None
|
||||
|
@ -79,9 +86,9 @@ class BlockchainInterface:
|
|||
GAS_STRATEGIES = WEB3_GAS_STRATEGIES
|
||||
|
||||
process = NO_PROVIDER_PROCESS.bool_value(False)
|
||||
Web3 = Web3
|
||||
Web3 = Web3 # TODO: This is name-shadowing the actual Web3. Is this intentional?
|
||||
|
||||
_contract_factory = VersionedContract
|
||||
_CONTRACT_FACTORY = VersionedContract
|
||||
|
||||
class InterfaceError(Exception):
|
||||
pass
|
||||
|
@ -227,7 +234,7 @@ class BlockchainInterface:
|
|||
self._provider = provider
|
||||
self._provider_process = provider_process
|
||||
self.w3 = NO_BLOCKCHAIN_CONNECTION
|
||||
self.client = NO_BLOCKCHAIN_CONNECTION # type: EthereumClient
|
||||
self.client = NO_BLOCKCHAIN_CONNECTION
|
||||
self.transacting_power = READ_ONLY_INTERFACE
|
||||
self.is_light = light
|
||||
self.gas_strategy = gas_strategy or self.DEFAULT_GAS_STRATEGY
|
||||
|
@ -602,7 +609,6 @@ class BlockchainInterface:
|
|||
#
|
||||
# Broadcast
|
||||
#
|
||||
|
||||
emitter.message(f'Broadcasting {transaction_name} Transaction ({cost} ETH @ {price_gwei} gwei)...',
|
||||
color='yellow')
|
||||
try:
|
||||
|
@ -716,7 +722,7 @@ class BlockchainInterface:
|
|||
proxy_contract = self.client.w3.eth.contract(abi=proxy_abi,
|
||||
address=proxy_address,
|
||||
version=proxy_version,
|
||||
ContractFactoryClass=self._contract_factory)
|
||||
ContractFactoryClass=self._CONTRACT_FACTORY)
|
||||
|
||||
# Read this dispatcher's target address from the blockchain
|
||||
proxy_live_target_address = proxy_contract.functions.target().call()
|
||||
|
@ -768,7 +774,7 @@ class BlockchainInterface:
|
|||
unified_contract = self.client.w3.eth.contract(abi=selected_abi,
|
||||
address=selected_address,
|
||||
version=selected_version,
|
||||
ContractFactoryClass=self._contract_factory)
|
||||
ContractFactoryClass=self._CONTRACT_FACTORY)
|
||||
|
||||
return unified_contract
|
||||
|
||||
|
@ -797,7 +803,15 @@ class BlockchainInterface:
|
|||
class BlockchainDeployerInterface(BlockchainInterface):
|
||||
|
||||
TIMEOUT = 600 # seconds
|
||||
_contract_factory = VersionedContract
|
||||
_CONTRACT_FACTORY = VersionedContract
|
||||
|
||||
# TODO: Make more func - use as a parameter
|
||||
# Source directories to (recursively) compile
|
||||
SOURCES: List[SourceBundle] = [
|
||||
SourceBundle(base_path=SOLIDITY_SOURCE_ROOT),
|
||||
]
|
||||
|
||||
_raw_contract_cache = NO_COMPILATION_PERFORMED
|
||||
|
||||
class NoDeployerAddress(RuntimeError):
|
||||
pass
|
||||
|
@ -805,32 +819,15 @@ class BlockchainDeployerInterface(BlockchainInterface):
|
|||
class DeploymentFailed(RuntimeError):
|
||||
pass
|
||||
|
||||
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):
|
||||
def connect(self, compile_now: bool = True, ignore_solidity_check: bool = False) -> bool:
|
||||
super().connect()
|
||||
self._setup_solidity(compiler=self.compiler)
|
||||
return self.is_connected
|
||||
|
||||
def _setup_solidity(self, compiler: SolidityCompiler = None) -> None:
|
||||
if self.dry_run:
|
||||
self.log.info("Dry run is active, skipping solidity compile steps.")
|
||||
return
|
||||
if compiler:
|
||||
if compile_now:
|
||||
# Execute the compilation if we're recompiling
|
||||
# Otherwise read compiled contract data from the registry.
|
||||
_raw_contract_cache = compiler.compile()
|
||||
else:
|
||||
_raw_contract_cache = NO_COMPILATION_PERFORMED
|
||||
self._raw_contract_cache = _raw_contract_cache
|
||||
check = not ignore_solidity_check
|
||||
compiled_contracts = multiversion_compile(source_bundles=self.SOURCES, compiler_version_check=check)
|
||||
self._raw_contract_cache = compiled_contracts
|
||||
return self.is_connected
|
||||
|
||||
@validate_checksum_address
|
||||
def deploy_contract(self,
|
||||
|
@ -892,7 +889,7 @@ class BlockchainDeployerInterface(BlockchainInterface):
|
|||
contract = self.client.w3.eth.contract(address=address,
|
||||
abi=contract_factory.abi,
|
||||
version=contract_factory.version,
|
||||
ContractFactoryClass=self._contract_factory)
|
||||
ContractFactoryClass=self._CONTRACT_FACTORY)
|
||||
|
||||
if enroll is True:
|
||||
registry.enroll(contract_name=contract_name,
|
||||
|
@ -917,9 +914,9 @@ class BlockchainDeployerInterface(BlockchainInterface):
|
|||
return requested_version, contract_data[requested_version]
|
||||
except KeyError:
|
||||
if requested_version != 'latest' and requested_version != 'earliest':
|
||||
raise self.UnknownContract('Version {} of contract {} is not a locally compiled. '
|
||||
'Available versions: {}'
|
||||
.format(requested_version, contract_name, contract_data.keys()))
|
||||
available = ', '.join(contract_data.keys())
|
||||
raise self.UnknownContract(f'Version {contract_name} of contract {contract_name} is not a locally compiled. '
|
||||
f'Available versions: {available}')
|
||||
|
||||
if len(contract_data.keys()) == 1:
|
||||
return next(iter(contract_data.items()))
|
||||
|
@ -945,10 +942,10 @@ class BlockchainDeployerInterface(BlockchainInterface):
|
|||
"""Retrieve compiled interface data from the cache and return web3 contract"""
|
||||
version, interface = self.find_raw_contract_data(contract_name, version)
|
||||
contract = self.client.w3.eth.contract(abi=interface['abi'],
|
||||
bytecode=interface['bin'],
|
||||
bytecode=interface['evm']['bytecode']['object'],
|
||||
version=version,
|
||||
address=address,
|
||||
ContractFactoryClass=self._contract_factory)
|
||||
ContractFactoryClass=self._CONTRACT_FACTORY)
|
||||
return contract
|
||||
|
||||
def get_contract_instance(self,
|
||||
|
@ -977,7 +974,7 @@ class BlockchainDeployerInterface(BlockchainInterface):
|
|||
wrapped_contract = self.client.w3.eth.contract(abi=target_contract.abi,
|
||||
address=wrapper_contract.address,
|
||||
version=target_contract.version,
|
||||
ContractFactoryClass=self._contract_factory)
|
||||
ContractFactoryClass=self._CONTRACT_FACTORY)
|
||||
return wrapped_contract
|
||||
|
||||
@validate_checksum_address
|
||||
|
@ -994,7 +991,7 @@ class BlockchainDeployerInterface(BlockchainInterface):
|
|||
proxy_contract = self.client.w3.eth.contract(abi=abi,
|
||||
address=address,
|
||||
version=version,
|
||||
ContractFactoryClass=self._contract_factory)
|
||||
ContractFactoryClass=self._CONTRACT_FACTORY)
|
||||
|
||||
# Read this dispatchers target address from the blockchain
|
||||
proxy_live_target_address = proxy_contract.functions.target().call()
|
||||
|
|
|
@ -87,7 +87,7 @@ class Proposal:
|
|||
contract = blockchain.client.w3.eth.contract(abi=abi,
|
||||
address=address,
|
||||
version=version,
|
||||
ContractFactoryClass=blockchain._contract_factory)
|
||||
ContractFactoryClass=blockchain._CONTRACT_FACTORY)
|
||||
contract_function, params = contract.decode_function_input(self.data)
|
||||
return contract_function, params
|
||||
else:
|
||||
|
|
|
@ -15,4 +15,4 @@ You should have received a copy of the GNU Affero General Public License
|
|||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
SOLIDITY_COMPILER_VERSION = 'v0.7.3'
|
||||
SOLIDITY_COMPILER_VERSION = 'v0.7.5'
|
||||
|
|
|
@ -1,171 +0,0 @@
|
|||
"""
|
||||
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/>.
|
||||
"""
|
||||
|
||||
|
||||
from os.path import abspath, dirname
|
||||
|
||||
import itertools
|
||||
import os
|
||||
import re
|
||||
from typing import List, NamedTuple, Optional, Set
|
||||
|
||||
from nucypher.blockchain.eth.sol import SOLIDITY_COMPILER_VERSION
|
||||
from nucypher.utilities.logging import Logger
|
||||
|
||||
|
||||
class SourceDirs(NamedTuple):
|
||||
root_source_dir: str
|
||||
other_source_dirs: Optional[Set[str]] = None
|
||||
|
||||
|
||||
class SolidityCompiler:
|
||||
|
||||
__default_contract_version = 'v0.0.0'
|
||||
__default_contract_dir = os.path.join(dirname(abspath(__file__)), 'source')
|
||||
|
||||
__compiled_contracts_dir = 'contracts'
|
||||
__zeppelin_library_dir = 'zeppelin'
|
||||
__aragon_library_dir = 'aragon'
|
||||
|
||||
optimization_runs = 200
|
||||
|
||||
class CompilerError(Exception):
|
||||
pass
|
||||
|
||||
class VersionError(Exception):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def default_contract_dir(cls):
|
||||
return cls.__default_contract_dir
|
||||
|
||||
def __init__(self,
|
||||
source_dirs: List[SourceDirs] = None,
|
||||
ignore_solidity_check: bool = False
|
||||
) -> None:
|
||||
|
||||
# Allow for optional installation
|
||||
from solcx.install import get_executable
|
||||
|
||||
self.log = Logger('solidity-compiler')
|
||||
|
||||
version = SOLIDITY_COMPILER_VERSION if not ignore_solidity_check else None
|
||||
self.__sol_binary_path = get_executable(version=version)
|
||||
|
||||
if source_dirs is None or len(source_dirs) == 0:
|
||||
self.source_dirs = [SourceDirs(root_source_dir=self.__default_contract_dir)]
|
||||
else:
|
||||
self.source_dirs = source_dirs
|
||||
|
||||
def compile(self) -> dict:
|
||||
interfaces = dict()
|
||||
for root_source_dir, other_source_dirs in self.source_dirs:
|
||||
if root_source_dir is None:
|
||||
self.log.warn("One of the root directories is None")
|
||||
continue
|
||||
|
||||
raw_interfaces = self._compile(root_source_dir, other_source_dirs)
|
||||
for name, data in raw_interfaces.items():
|
||||
# Extract contract version from docs
|
||||
version_search = re.search(r"""
|
||||
|
||||
\"details\": # @dev tag in contract docs
|
||||
\".*? # Skip any data in the beginning of details
|
||||
\| # Beginning of version definition |
|
||||
(v # Capture version starting from symbol v
|
||||
\d+ # At least one digit of major version
|
||||
\. # Digits splitter
|
||||
\d+ # At least one digit of minor version
|
||||
\. # Digits splitter
|
||||
\d+ # At least one digit of patch
|
||||
) # End of capturing
|
||||
\| # End of version definition |
|
||||
.*?\" # Skip any data in the end of details
|
||||
|
||||
""", data['devdoc'], re.VERBOSE)
|
||||
version = version_search.group(1) if version_search else self.__default_contract_version
|
||||
try:
|
||||
existence_data = interfaces[name]
|
||||
except KeyError:
|
||||
existence_data = dict()
|
||||
interfaces.update({name: existence_data})
|
||||
if version not in existence_data:
|
||||
existence_data.update({version: data})
|
||||
return interfaces
|
||||
|
||||
def _compile(self, root_source_dir: str, other_source_dirs: [str]) -> dict:
|
||||
"""Executes the compiler with parameters specified in the json config"""
|
||||
|
||||
# Allow for optional installation
|
||||
from solcx import compile_files
|
||||
from solcx.exceptions import SolcError
|
||||
|
||||
self.log.info("Using solidity compiler binary at {}".format(self.__sol_binary_path))
|
||||
contracts_dir = os.path.join(root_source_dir, self.__compiled_contracts_dir)
|
||||
self.log.info("Compiling solidity source files at {}".format(contracts_dir))
|
||||
|
||||
source_paths = set()
|
||||
source_walker = os.walk(top=contracts_dir, topdown=True)
|
||||
if other_source_dirs is not None:
|
||||
for source_dir in other_source_dirs:
|
||||
other_source_walker = os.walk(top=source_dir, topdown=True)
|
||||
source_walker = itertools.chain(source_walker, other_source_walker)
|
||||
|
||||
for root, dirs, files in source_walker:
|
||||
for filename in files:
|
||||
if filename.endswith('.sol'):
|
||||
path = os.path.join(root, filename)
|
||||
source_paths.add(path)
|
||||
self.log.debug("Collecting solidity source {}".format(path))
|
||||
|
||||
# Compile with remappings: https://github.com/ethereum/py-solc
|
||||
zeppelin_dir = os.path.join(root_source_dir, self.__zeppelin_library_dir)
|
||||
aragon_dir = os.path.join(root_source_dir, self.__aragon_library_dir)
|
||||
|
||||
remappings = ("contracts={}".format(contracts_dir),
|
||||
"zeppelin={}".format(zeppelin_dir),
|
||||
"aragon={}".format(aragon_dir),
|
||||
)
|
||||
|
||||
self.log.info("Compiling with import remappings {}".format(", ".join(remappings)))
|
||||
|
||||
optimization_runs = self.optimization_runs
|
||||
|
||||
try:
|
||||
compiled_sol = compile_files(source_files=source_paths,
|
||||
solc_binary=self.__sol_binary_path,
|
||||
import_remappings=remappings,
|
||||
allow_paths=root_source_dir,
|
||||
optimize=True,
|
||||
optimize_runs=optimization_runs)
|
||||
|
||||
self.log.info("Successfully compiled {} contracts with {} optimization runs".format(len(compiled_sol),
|
||||
optimization_runs))
|
||||
|
||||
except FileNotFoundError:
|
||||
raise RuntimeError("The solidity compiler is not at the specified path. "
|
||||
"Check that the file exists and is executable.")
|
||||
except PermissionError:
|
||||
raise RuntimeError("The solidity compiler binary at {} is not executable. "
|
||||
"Check the file's permissions.".format(self.__sol_binary_path))
|
||||
|
||||
except SolcError:
|
||||
raise
|
||||
|
||||
# Cleanup the compiled data keys
|
||||
interfaces = {name.split(':')[-1]: compiled_sol[name] for name in compiled_sol}
|
||||
return interfaces
|
|
@ -0,0 +1,16 @@
|
|||
"""
|
||||
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/>.
|
||||
"""
|
|
@ -0,0 +1,162 @@
|
|||
"""
|
||||
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 re
|
||||
from re import Pattern
|
||||
from typing import Dict
|
||||
|
||||
from cytoolz.dicttoolz import merge
|
||||
|
||||
from nucypher.blockchain.eth.sol.compile.constants import DEFAULT_VERSION_STRING, SOLC_LOGGER
|
||||
from nucypher.blockchain.eth.sol.compile.exceptions import CompilationError, ProgrammingError
|
||||
from nucypher.blockchain.eth.sol.compile.types import VersionedContractOutputs, CompiledContractOutputs
|
||||
|
||||
# RE pattern for matching solidity source compile version specification in devdoc details.
|
||||
DEVDOC_VERSION_PATTERN: Pattern = re.compile(r"""
|
||||
\A # Anchor must be first
|
||||
\| # Anchored pipe literal at beginning of version definition
|
||||
( # Start Inner capture group
|
||||
v # Capture version starting from symbol v
|
||||
\d+ # At least one digit of major version
|
||||
\. # Digits splitter
|
||||
\d+ # At least one digit of minor version
|
||||
\. # Digits splitter
|
||||
\d+ # At least one digit of patch
|
||||
) # End of capturing
|
||||
\| # Anchored end of version definition |
|
||||
\Z # Anchor must be the end of the match
|
||||
""", re.VERBOSE)
|
||||
|
||||
# simplified version of pattern to extract metadata hash from bytecode
|
||||
# see https://docs.soliditylang.org/en/latest/metadata.html#encoding-of-the-metadata-hash-in-the-bytecode
|
||||
METADATA_HASH_PATTERN: Pattern = re.compile(r"""
|
||||
a2
|
||||
64
|
||||
69706673 # 'i' 'p' 'f' 's'
|
||||
58
|
||||
22
|
||||
\w{68} # 34 bytes IPFS hash
|
||||
64
|
||||
736f6c63 # 's' 'o' 'l' 'c'
|
||||
43
|
||||
\w{6} # <3 byte version encoding>
|
||||
0033
|
||||
""", re.VERBOSE)
|
||||
|
||||
|
||||
def extract_version(compiled_contract_outputs: dict) -> str:
|
||||
"""
|
||||
Returns the source specified version of a compiled solidity contract.
|
||||
Examines compiled contract output for devdoc details and perform a fulltext search for a source version specifier.
|
||||
"""
|
||||
try:
|
||||
devdoc: Dict[str, str] = compiled_contract_outputs['devdoc']
|
||||
except KeyError:
|
||||
# Edge Case
|
||||
# ---------
|
||||
# If this block is reached, the compiler did not produce results for devdoc at all.
|
||||
# Ensure 'devdoc' is listed in `CONTRACT_OUTPUTS` and that solc is the latest version.
|
||||
raise CompilationError(f'Solidity compiler did not output devdoc.'
|
||||
f'Check the contract output compiler settings.')
|
||||
else:
|
||||
title = devdoc.get('title', '')
|
||||
|
||||
try:
|
||||
devdoc_details: str = devdoc['details']
|
||||
except KeyError:
|
||||
# This is acceptable behaviour, most likely an un-versioned contract
|
||||
SOLC_LOGGER.debug(f'No solidity source version specified.')
|
||||
return DEFAULT_VERSION_STRING
|
||||
|
||||
# RE Full Match
|
||||
raw_matches = DEVDOC_VERSION_PATTERN.fullmatch(devdoc_details)
|
||||
|
||||
# Positive match(es)
|
||||
if raw_matches:
|
||||
matches = raw_matches.groups()
|
||||
if len(matches) != 1: # sanity check
|
||||
# Severe Edge Case
|
||||
# -----------------
|
||||
# "Impossible" situation: If this block is ever reached,
|
||||
# the regular expression matching contract versions
|
||||
# inside devdoc details matched multiple groups (versions).
|
||||
# If you are here, and this exception is raised - do not panic!
|
||||
# This most likely means there is a programming error
|
||||
# in the `VERSION_PATTERN` regular expression or the surrounding logic.
|
||||
raise ProgrammingError(f"Multiple version matches in {title} devdoc.")
|
||||
version = matches[0] # good match
|
||||
return version # OK
|
||||
else:
|
||||
# Negative match: Devdoc included without a version
|
||||
SOLC_LOGGER.debug(f"Contract {title} not versioned.")
|
||||
return DEFAULT_VERSION_STRING
|
||||
|
||||
|
||||
def validate_merge(existing_version: CompiledContractOutputs,
|
||||
new_version: CompiledContractOutputs,
|
||||
version_specifier: str) -> None:
|
||||
"""Compare with incoming compiled contract data"""
|
||||
new_title = new_version['devdoc'].get('title')
|
||||
versioned: bool = version_specifier != DEFAULT_VERSION_STRING
|
||||
if versioned and new_title:
|
||||
existing_title = existing_version['devdoc'].get('title')
|
||||
if existing_title == new_title: # This is the same contract
|
||||
# TODO this code excludes hash of metadata, it's not perfect because format of metadata could change
|
||||
# ideally use a proper CBOR parser
|
||||
existing_bytecode = METADATA_HASH_PATTERN.sub('', existing_version['evm']['bytecode']['object'])
|
||||
new_bytecode = METADATA_HASH_PATTERN.sub('', new_version['evm']['bytecode']['object'])
|
||||
if not existing_bytecode == new_bytecode:
|
||||
message = f"Two solidity sources ({new_title}, {existing_title}) specify version '{version_specifier}' " \
|
||||
"but have different compiled bytecode. Ensure that the devdoc version is " \
|
||||
"accurately updated before trying again."
|
||||
raise CompilationError(message)
|
||||
|
||||
|
||||
def merge_contract_sources(*compiled_sources):
|
||||
return merge(*compiled_sources) # TODO: Handle file-level output aggregation
|
||||
|
||||
|
||||
def merge_contract_outputs(*compiled_versions) -> VersionedContractOutputs:
|
||||
versioned_outputs = dict()
|
||||
|
||||
for bundle in compiled_versions:
|
||||
|
||||
for contract_outputs in bundle:
|
||||
version = extract_version(compiled_contract_outputs=contract_outputs)
|
||||
|
||||
try:
|
||||
existing_version = versioned_outputs[version]
|
||||
|
||||
except KeyError:
|
||||
# New Version Entry
|
||||
bytecode = contract_outputs['evm']['bytecode']['object']
|
||||
for existing_version, existing_contract_outputs in versioned_outputs.items():
|
||||
existing_bytecode = existing_contract_outputs['evm']['bytecode']['object']
|
||||
if bytecode == existing_bytecode:
|
||||
raise CompilationError(f"Two solidity sources compiled identical bytecode for version {version}. "
|
||||
"Ensure the correct solidity paths are targeted for compilation.")
|
||||
versioned_outputs[version] = contract_outputs
|
||||
|
||||
else:
|
||||
# Existing Version Update
|
||||
validate_merge(existing_version=existing_version,
|
||||
new_version=contract_outputs,
|
||||
version_specifier=version)
|
||||
versioned_outputs[version] = contract_outputs
|
||||
|
||||
return VersionedContractOutputs(versioned_outputs)
|
|
@ -0,0 +1,55 @@
|
|||
"""
|
||||
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/>.
|
||||
"""
|
||||
|
||||
from nucypher.blockchain.eth.sol.compile.types import SourceBundle
|
||||
from nucypher.exceptions import DevelopmentInstallationRequired
|
||||
try:
|
||||
import tests
|
||||
except ImportError:
|
||||
raise DevelopmentInstallationRequired(importable_name='tests')
|
||||
from nucypher.blockchain.eth.sol.compile.constants import IGNORE_CONTRACT_PREFIXES, SOLC_LOGGER
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Dict, Iterator
|
||||
|
||||
|
||||
def source_filter(filename: str) -> bool:
|
||||
"""Helper function for filtering out contracts not intended for compilation"""
|
||||
contains_ignored_prefix: bool = any(prefix in filename for prefix in IGNORE_CONTRACT_PREFIXES)
|
||||
is_solidity_file: bool = filename.endswith('.sol')
|
||||
return is_solidity_file and not contains_ignored_prefix
|
||||
|
||||
|
||||
def collect_sources(source_bundle: SourceBundle) -> Dict[str, Path]:
|
||||
"""
|
||||
Combines sources bundle paths. Walks source_dir top-down to the bottom filepath of
|
||||
each subdirectory recursively nd filtrates by __source_filter, setting values into `source_paths`.
|
||||
"""
|
||||
source_paths = dict()
|
||||
combined_paths = (source_bundle.base_path, *source_bundle.other_paths)
|
||||
for source_dir in combined_paths:
|
||||
source_walker: Iterator = os.walk(top=source_dir, topdown=True)
|
||||
for root, dirs, files in source_walker: # Collect single directory
|
||||
for filename in filter(source_filter, files): # Collect files in source dir
|
||||
path = Path(root) / filename
|
||||
if filename in source_paths:
|
||||
raise RuntimeError(f'"{filename}" source is already collected. Verify source bundle filepaths.'
|
||||
f' Existing {source_paths[filename]}; Duplicate {path}.')
|
||||
source_paths[filename] = path
|
||||
SOLC_LOGGER.debug(f"Collecting solidity source {path}")
|
||||
SOLC_LOGGER.info(f"Collected {len(source_paths)} solidity source files at {source_bundle}")
|
||||
return source_paths
|
|
@ -0,0 +1,82 @@
|
|||
"""
|
||||
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 itertools
|
||||
from pathlib import Path
|
||||
from typing import Tuple, List, Dict, Optional
|
||||
|
||||
from cytoolz.dicttoolz import merge, merge_with
|
||||
|
||||
from nucypher.blockchain.eth.sol.__conf__ import SOLIDITY_COMPILER_VERSION
|
||||
from nucypher.blockchain.eth.sol.compile.aggregation import merge_contract_outputs
|
||||
from nucypher.blockchain.eth.sol.compile.collect import collect_sources
|
||||
from nucypher.blockchain.eth.sol.compile.config import BASE_COMPILER_CONFIGURATION, REMAPPINGS
|
||||
from nucypher.blockchain.eth.sol.compile.solc import __execute
|
||||
from nucypher.blockchain.eth.sol.compile.types import (
|
||||
VersionString,
|
||||
VersionedContractOutputs,
|
||||
CompiledContractOutputs,
|
||||
SourceBundle
|
||||
)
|
||||
|
||||
CompilerSources = Dict[str, Dict[str, List[str]]]
|
||||
|
||||
|
||||
def prepare_source_configuration(sources: Dict[str, Path]) -> CompilerSources:
|
||||
input_sources = dict()
|
||||
for source_name, path in sources.items():
|
||||
source_url = path.resolve(strict=True) # require source path existence
|
||||
input_sources[source_name] = dict(urls=[str(source_url)])
|
||||
return input_sources
|
||||
|
||||
|
||||
def prepare_remappings_configuration(base_path: Path) -> Dict:
|
||||
remappings_array = list()
|
||||
for i, value in enumerate(REMAPPINGS):
|
||||
remappings_array.append(f"{value}={str(base_path / value)}")
|
||||
remappings = dict(remappings=remappings_array)
|
||||
return remappings
|
||||
|
||||
|
||||
def compile_sources(source_bundle: SourceBundle, version_check: bool = True) -> Dict:
|
||||
"""Compiled solidity contracts for a single source directory"""
|
||||
sources = collect_sources(source_bundle=source_bundle)
|
||||
source_config = prepare_source_configuration(sources=sources)
|
||||
solc_configuration = merge(BASE_COMPILER_CONFIGURATION, dict(sources=source_config)) # does not mutate.
|
||||
|
||||
remappings_config = prepare_remappings_configuration(base_path=source_bundle.base_path)
|
||||
solc_configuration['settings'].update(remappings_config)
|
||||
|
||||
version: VersionString = VersionString(SOLIDITY_COMPILER_VERSION) if version_check else None
|
||||
allow_paths = [source_bundle.base_path, *source_bundle.other_paths]
|
||||
compiler_output = __execute(compiler_version=version, input_config=solc_configuration, allow_paths=allow_paths)
|
||||
return compiler_output
|
||||
|
||||
|
||||
def multiversion_compile(source_bundles: List[SourceBundle],
|
||||
compiler_version_check: bool = True
|
||||
) -> VersionedContractOutputs:
|
||||
"""Compile contracts from `source_dirs` and aggregate the resulting source contract outputs by version"""
|
||||
raw_compiler_results: List[CompiledContractOutputs] = list()
|
||||
for bundle in source_bundles:
|
||||
compile_result = compile_sources(source_bundle=bundle,
|
||||
version_check=compiler_version_check)
|
||||
raw_compiler_results.append(compile_result['contracts'])
|
||||
raw_compiled_contracts = itertools.chain.from_iterable(output.values() for output in raw_compiler_results)
|
||||
versioned_contract_outputs = VersionedContractOutputs(merge_with(merge_contract_outputs, *raw_compiled_contracts))
|
||||
return versioned_contract_outputs
|
|
@ -0,0 +1,124 @@
|
|||
"""
|
||||
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/>.
|
||||
"""
|
||||
|
||||
|
||||
from typing import List, Dict
|
||||
|
||||
from nucypher.blockchain.eth.sol.compile.constants import (
|
||||
CONTRACTS, ZEPPELIN, ARAGON
|
||||
)
|
||||
from nucypher.blockchain.eth.sol.compile.types import CompilerConfiguration
|
||||
|
||||
"""
|
||||
Standard "JSON I/O" Compiler Config Reference:
|
||||
|
||||
Input: https://solidity.readthedocs.io/en/latest/using-the-compiler.html#input-description
|
||||
Output: https://solidity.readthedocs.io/en/latest/using-the-compiler.html#output-description
|
||||
|
||||
WARNING: Do not change these values unless you know what you are doing.
|
||||
"""
|
||||
|
||||
|
||||
# Debug
|
||||
# -----
|
||||
# How to treat revert (and require) reason strings.
|
||||
# "default", "strip", "debug" and "verboseDebug".
|
||||
# "default" does not inject compiler-generated revert strings and keeps user-supplied ones.
|
||||
# "strip" removes all revert strings (if possible, i.e. if literals are used) keeping side-effects
|
||||
# "debug" injects strings for compiler-generated internal reverts, implemented for ABI encoders V1 and V2 for now.
|
||||
# "verboseDebug" even appends further information to user-supplied revert strings (not yet implemented)
|
||||
# DEBUG = 'default'
|
||||
|
||||
# Source code language. Currently supported are "Solidity" and "Yul".
|
||||
LANGUAGE: str = 'Solidity'
|
||||
|
||||
# Version of the EVM to compile for. Affects type checking and code generation.
|
||||
EVM_VERSION: str = 'berlin'
|
||||
|
||||
# File level compiler outputs (needs empty string as contract name):
|
||||
FILE_OUTPUTS: List[str] = [
|
||||
'ast' # AST of all source files
|
||||
# 'legacyAST' # legacy AST of all source files
|
||||
]
|
||||
|
||||
# Contract level (needs the contract name or "*")
|
||||
CONTRACT_OUTPUTS: List[str] = [
|
||||
|
||||
'abi', # ABI
|
||||
'devdoc', # Developer documentation (natspec)
|
||||
'userdoc', # User documentation (natspec)
|
||||
'evm.bytecode.object', # Bytecode object
|
||||
|
||||
# 'metadata', # Metadata
|
||||
# 'ir', # Yul intermediate representation of the code before optimization
|
||||
# 'irOptimized', # Intermediate representation after optimization
|
||||
# 'storageLayout', # Slots, offsets and types of the contract's state variables.
|
||||
# 'evm.assembly', # New assembly format
|
||||
# 'evm.legacyAssembly', # Old-style assembly format in JSON
|
||||
# 'evm.bytecode.opcodes', # Opcodes list
|
||||
# 'evm.bytecode.sourceMap', # Source mapping (useful for debugging)
|
||||
# 'evm.bytecode.linkReferences', # Link references (if unlinked object)
|
||||
# 'evm.deployedBytecode*', # Deployed bytecode (has all the options that evm.bytecode has)
|
||||
# 'evm.deployedBytecode.immutableReferences', # Map from AST ids to bytecode ranges that reference immutables
|
||||
# 'evm.methodIdentifiers', # The list of function hashes
|
||||
# 'evm.gasEstimates', # Function gas estimates
|
||||
# 'ewasm.wast', # eWASM S-expressions format (not supported at the moment)
|
||||
# 'ewasm.wasm', # eWASM binary format (not supported at the moment)
|
||||
]
|
||||
|
||||
# Optimizer Details - Switch optimizer components on or off in detail.
|
||||
# The "enabled" switch above provides two defaults which can be tweaked here (yul, and ...).
|
||||
OPTIMIZER_DETAILS = dict(
|
||||
peephole=True, # The peephole optimizer is always on if no details are given (switch it off here).
|
||||
jumpdestRemover=True, # The unused jumpdest remover is always on if no details are given (switch it off here).
|
||||
orderLiterals=False, # Sometimes re-orders literals in commutative operations.
|
||||
deduplicate=False, # Removes duplicate code blocks
|
||||
cse=False, # Common subexpression elimination, Most complicated step but provides the largest gain.
|
||||
constantOptimizer=False, # Optimize representation of literal numbers and strings in code.
|
||||
|
||||
# The new Yul optimizer. Mostly operates on the code of ABIEncoderV2 and inline assembly.
|
||||
# It is activated together with the global optimizer setting and can be deactivated here.
|
||||
# Before Solidity 0.6.0 it had to be activated through this switch. Also see 'yulDetails options'.
|
||||
yul=True
|
||||
)
|
||||
|
||||
# Optimize for how many times you intend to run the code.
|
||||
# Lower values will optimize more for initial deployment cost, higher
|
||||
# values will optimize more for high-frequency usage.
|
||||
OPTIMIZER_RUNS = 200
|
||||
|
||||
OPTIMIZER_SETTINGS = dict(
|
||||
enabled=True,
|
||||
runs=OPTIMIZER_RUNS,
|
||||
# details=OPTIMIZER_DETAILS # Optional - If "details" is given, "enabled" can be omitted.
|
||||
)
|
||||
|
||||
# Complete compiler settings
|
||||
COMPILER_SETTINGS: Dict = dict(
|
||||
optimizer=OPTIMIZER_SETTINGS,
|
||||
evmVersion=EVM_VERSION,
|
||||
outputSelection={"*": {"*": CONTRACT_OUTPUTS, "": FILE_OUTPUTS}}, # all contacts(*), all files("")
|
||||
)
|
||||
|
||||
REMAPPINGS: List = [CONTRACTS, ZEPPELIN, ARAGON]
|
||||
|
||||
# Base configuration for programmatic usage
|
||||
BASE_COMPILER_CONFIGURATION = CompilerConfiguration(
|
||||
language=LANGUAGE,
|
||||
settings=COMPILER_SETTINGS,
|
||||
# sources and remappings added dynamically during runtime
|
||||
)
|
|
@ -0,0 +1,44 @@
|
|||
"""
|
||||
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/>.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Tuple
|
||||
from nucypher.config.constants import NUCYPHER_TEST_DIR
|
||||
|
||||
# Logging
|
||||
from nucypher.utilities.logging import Logger
|
||||
|
||||
SOLC_LOGGER = Logger("solidity-compilation")
|
||||
|
||||
# Vocabulary
|
||||
CONTRACTS = 'contracts'
|
||||
|
||||
TEST_SOLIDITY_SOURCE_ROOT: Path = Path(NUCYPHER_TEST_DIR) / CONTRACTS / CONTRACTS
|
||||
TEST_MULTIVERSION_CONTRACTS: Path = Path(NUCYPHER_TEST_DIR) / 'acceptance' / 'blockchain' / 'interfaces' / 'test_contracts' / 'multiversion'
|
||||
|
||||
from nucypher.blockchain.eth import sol
|
||||
SOLIDITY_SOURCE_ROOT: Path = Path(sol.__file__).parent / 'source'
|
||||
ZEPPELIN = 'zeppelin'
|
||||
ARAGON = 'aragon'
|
||||
|
||||
# Do not compile contracts containing...
|
||||
IGNORE_CONTRACT_PREFIXES: Tuple[str, ...] = (
|
||||
'Abstract',
|
||||
'Interface'
|
||||
)
|
||||
|
||||
DEFAULT_VERSION_STRING: str = 'v0.0.0' # for both compiler and devdoc versions (must fully match regex pattern below)
|
|
@ -0,0 +1,27 @@
|
|||
"""
|
||||
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/>.
|
||||
"""
|
||||
|
||||
|
||||
class CompilationError(RuntimeError):
|
||||
"""
|
||||
Raised when there is a problem compiling nucypher contracts
|
||||
or with the expected compiler configuration.
|
||||
"""
|
||||
|
||||
|
||||
class ProgrammingError(RuntimeError):
|
||||
"""Caused by a human error in code"""
|
|
@ -0,0 +1,55 @@
|
|||
"""
|
||||
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/>.
|
||||
"""
|
||||
from pathlib import Path
|
||||
from typing import Dict, Optional, List
|
||||
|
||||
from nucypher.blockchain.eth.sol.compile.config import OPTIMIZER_RUNS
|
||||
from nucypher.blockchain.eth.sol.compile.constants import SOLC_LOGGER
|
||||
from nucypher.blockchain.eth.sol.compile.exceptions import CompilationError
|
||||
from nucypher.blockchain.eth.sol.compile.types import VersionString
|
||||
from nucypher.exceptions import DevelopmentInstallationRequired
|
||||
|
||||
|
||||
def __execute(compiler_version: VersionString, input_config: Dict, allow_paths: Optional[List[str]]):
|
||||
"""Executes the solcx command and underlying solc wrapper"""
|
||||
|
||||
# Lazy import to allow for optional installation of solcx
|
||||
try:
|
||||
from solcx.install import get_executable
|
||||
from solcx.main import compile_standard
|
||||
except ImportError:
|
||||
raise DevelopmentInstallationRequired(importable_name='solcx')
|
||||
|
||||
# Prepare Solc Command
|
||||
solc_binary_path: Path = get_executable(version=compiler_version)
|
||||
SOLC_LOGGER.info(f"Compiling with base path") # TODO: Add base path
|
||||
|
||||
_allow_paths = ',' + ','.join(str(p) for p in allow_paths)
|
||||
|
||||
# Execute Compilation
|
||||
try:
|
||||
compiler_output = compile_standard(input_data=input_config,
|
||||
allow_paths=_allow_paths,
|
||||
solc_binary=solc_binary_path)
|
||||
except FileNotFoundError:
|
||||
raise CompilationError("The solidity compiler is not at the specified path. "
|
||||
"Check that the file exists and is executable.")
|
||||
except PermissionError:
|
||||
raise CompilationError(f"The solidity compiler binary at {solc_binary_path} is not executable. "
|
||||
"Check the file's permissions.")
|
||||
SOLC_LOGGER.info(f"Successfully compiled {len(compiler_output)} sources with {OPTIMIZER_RUNS} optimization runs")
|
||||
return compiler_output
|
|
@ -0,0 +1,48 @@
|
|||
"""
|
||||
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/>.
|
||||
"""
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Union, NewType, NamedTuple, Tuple, Optional
|
||||
|
||||
|
||||
class ABI(Dict):
|
||||
inputs: List
|
||||
name: str
|
||||
outputs: List[Dict[str, str]]
|
||||
stateMutability: str
|
||||
type: str
|
||||
|
||||
|
||||
class CompiledContractOutputs(Dict):
|
||||
abi: ABI
|
||||
devdoc: Dict[str, Union[str, Dict[str, str]]]
|
||||
evm: Dict[str, Dict]
|
||||
userdoc: Dict
|
||||
|
||||
|
||||
VersionString = NewType('VersionString', str)
|
||||
VersionedContractOutputs = NewType('VersionedContractOutputs', Dict[VersionString, CompiledContractOutputs])
|
||||
|
||||
|
||||
class CompilerConfiguration(Dict):
|
||||
language: str
|
||||
sources: Dict[str, Dict[str, str]]
|
||||
settings: Dict
|
||||
|
||||
|
||||
class SourceBundle(NamedTuple):
|
||||
base_path: Path
|
||||
other_paths: Tuple[Path, ...] = tuple()
|
|
@ -229,7 +229,7 @@ class Alice(Character, BlockchainPolicyAuthor):
|
|||
policy = FederatedPolicy(alice=self, **payload)
|
||||
|
||||
else:
|
||||
# Sample from blockchain via PolicyManager
|
||||
# Sample from blockchain PolicyManager
|
||||
from nucypher.policy.policies import BlockchainPolicy
|
||||
payload.update(**policy_params)
|
||||
policy = BlockchainPolicy(alice=self, **payload)
|
||||
|
|
|
@ -248,7 +248,7 @@ def sign(general_config, blockchain_options, multisig_options, proposal):
|
|||
proxy_contract = blockchain.client.w3.eth.contract(abi=abi,
|
||||
address=address,
|
||||
version=version,
|
||||
ContractFactoryClass=blockchain._contract_factory)
|
||||
ContractFactoryClass=blockchain._CONTRACT_FACTORY)
|
||||
paint_multisig_proposed_transaction(emitter, proposal, proxy_contract)
|
||||
|
||||
click.confirm(PROMPT_CONFIRM_MULTISIG_SIGNATURE, abort=True)
|
||||
|
@ -290,7 +290,7 @@ def execute(general_config, blockchain_options, multisig_options, proposal):
|
|||
proxy_contract = blockchain.client.w3.eth.contract(abi=abi,
|
||||
address=address,
|
||||
version=version,
|
||||
ContractFactoryClass=blockchain._contract_factory)
|
||||
ContractFactoryClass=blockchain._CONTRACT_FACTORY)
|
||||
paint_multisig_proposed_transaction(emitter, proposal, proxy_contract)
|
||||
|
||||
trustee = multisig_options.create_trustee(registry)
|
||||
|
|
|
@ -16,13 +16,15 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
|||
"""
|
||||
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
import os
|
||||
from appdirs import AppDirs
|
||||
from collections import namedtuple
|
||||
from os.path import dirname
|
||||
from pathlib import Path
|
||||
|
||||
from appdirs import AppDirs
|
||||
|
||||
import nucypher
|
||||
from nucypher.exceptions import DevelopmentInstallationRequired
|
||||
|
||||
# Environment variables
|
||||
NUCYPHER_ENVVAR_KEYRING_PASSWORD = "NUCYPHER_KEYRING_PASSWORD"
|
||||
|
@ -38,6 +40,17 @@ NUCYPHER_PACKAGE = Path(nucypher.__file__).parent.resolve()
|
|||
BASE_DIR = NUCYPHER_PACKAGE.parent.resolve()
|
||||
DEPLOY_DIR = BASE_DIR / 'deploy'
|
||||
|
||||
# Test Filepaths
|
||||
try:
|
||||
import tests
|
||||
except ImportError:
|
||||
raise DevelopmentInstallationRequired(importable_name='tests')
|
||||
else:
|
||||
# TODO: Another way to handle this situation?
|
||||
# __file__ can be None, especially with namespace packages on
|
||||
# Python 3.7 or when using apidoc and sphinx-build.
|
||||
file_path = tests.__file__
|
||||
NUCYPHER_TEST_DIR = dirname(file_path) if file_path is not None else str()
|
||||
|
||||
# User Application Filepaths
|
||||
APP_DIR = AppDirs(nucypher.__title__, nucypher.__author__)
|
||||
|
|
|
@ -16,8 +16,9 @@
|
|||
"""
|
||||
|
||||
|
||||
from eth_typing.evm import ChecksumAddress
|
||||
from typing import TypeVar, NewType, Tuple, NamedTuple, Union
|
||||
|
||||
from eth_typing.evm import ChecksumAddress
|
||||
from web3.types import Wei, Timestamp, TxReceipt
|
||||
|
||||
NuNits = NewType("NuNits", int)
|
||||
|
@ -29,11 +30,7 @@ Evidence = TypeVar('Evidence', bound='IndisputableEvidence')
|
|||
ContractReturnValue = TypeVar('ContractReturnValue', bound=Union[TxReceipt, Wei, int, str, bool])
|
||||
|
||||
|
||||
class ContractParams(Tuple):
|
||||
pass
|
||||
|
||||
|
||||
class WorklockParameters(ContractParams):
|
||||
class WorklockParameters(Tuple):
|
||||
token_supply: NuNits
|
||||
start_bid_date: Timestamp
|
||||
end_bid_date: Timestamp
|
||||
|
@ -43,7 +40,7 @@ class WorklockParameters(ContractParams):
|
|||
min_allowed_bid: Wei
|
||||
|
||||
|
||||
class StakingEscrowParameters(ContractParams):
|
||||
class StakingEscrowParameters(Tuple):
|
||||
seconds_per_period: int
|
||||
minting_coefficient: int
|
||||
lock_duration_coefficient_1: int
|
||||
|
|
|
@ -22,21 +22,18 @@ import pytest
|
|||
|
||||
from nucypher.blockchain.eth.actors import ContractAdministrator
|
||||
from nucypher.blockchain.eth.agents import StakingEscrowAgent, ContractAgency
|
||||
from nucypher.blockchain.eth.sol.compile import SolidityCompiler
|
||||
from nucypher.characters.control.emitters import StdoutEmitter
|
||||
from nucypher.crypto.powers import TransactingPower
|
||||
from tests.constants import INSECURE_DEVELOPMENT_PASSWORD, NUMBER_OF_ALLOCATIONS_IN_TESTS
|
||||
# Prevents TesterBlockchain to be picked up by py.test as a test class
|
||||
from tests.utils.blockchain import TesterBlockchain as _TesterBlockchain
|
||||
from tests.constants import INSECURE_DEVELOPMENT_PASSWORD, NUMBER_OF_ALLOCATIONS_IN_TESTS
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('testerchain')
|
||||
def test_rapid_deployment(token_economics, test_registry, tmpdir, get_random_checksum_address):
|
||||
compiler = SolidityCompiler()
|
||||
|
||||
blockchain = _TesterBlockchain(eth_airdrop=False,
|
||||
test_accounts=4,
|
||||
compiler=compiler)
|
||||
test_accounts=4)
|
||||
|
||||
# TODO: #1092 - TransactingPower
|
||||
blockchain.transacting_power = TransactingPower(password=INSECURE_DEVELOPMENT_PASSWORD,
|
||||
|
|
|
@ -25,7 +25,7 @@ def test_token_deployer_and_agent(testerchain, deployment_progress, test_registr
|
|||
|
||||
origin = testerchain.etherbase_account
|
||||
|
||||
# Trying to get token from blockchain before it's been published fails
|
||||
# Trying to get token from blockchain before it's been published should fail
|
||||
with pytest.raises(BaseContractRegistry.UnknownContract):
|
||||
NucypherTokenAgent(registry=test_registry)
|
||||
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
"""
|
||||
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/>.
|
||||
"""
|
||||
|
||||
|
||||
from nucypher.blockchain.eth.interfaces import BlockchainDeployerInterface, BlockchainInterfaceFactory
|
||||
from nucypher.blockchain.eth.registry import InMemoryContractRegistry
|
||||
from nucypher.blockchain.eth.sol.compile.constants import TEST_MULTIVERSION_CONTRACTS
|
||||
from nucypher.blockchain.eth.sol.compile.types import SourceBundle
|
||||
from nucypher.crypto.powers import TransactingPower
|
||||
from tests.constants import INSECURE_DEVELOPMENT_PASSWORD
|
||||
from tests.utils.blockchain import free_gas_price_strategy
|
||||
|
||||
|
||||
def test_deployer_interface_multiversion_contract():
|
||||
|
||||
# Prepare compiler
|
||||
base_dir = TEST_MULTIVERSION_CONTRACTS
|
||||
v1_dir, v2_dir = base_dir / 'v1', base_dir / 'v2'
|
||||
|
||||
# TODO: Check type of sources
|
||||
# I am a contract administrator and I an compiling a new updated version of an existing contract...
|
||||
# Represents "Manually hardcoding" a new source directory on BlockchainDeployerInterface.SOURCES.
|
||||
BlockchainDeployerInterface.SOURCES = (
|
||||
SourceBundle(base_path=v1_dir),
|
||||
SourceBundle(base_path=v2_dir)
|
||||
)
|
||||
|
||||
# Prepare chain
|
||||
BlockchainInterfaceFactory._interfaces.clear()
|
||||
blockchain_interface = BlockchainDeployerInterface(provider_uri='tester://pyevm',
|
||||
gas_strategy=free_gas_price_strategy)
|
||||
blockchain_interface.connect()
|
||||
BlockchainInterfaceFactory.register_interface(interface=blockchain_interface) # Lets this test run in isolation
|
||||
|
||||
origin = blockchain_interface.client.accounts[0]
|
||||
blockchain_interface.transacting_power = TransactingPower(password=INSECURE_DEVELOPMENT_PASSWORD, account=origin)
|
||||
blockchain_interface.transacting_power.activate()
|
||||
|
||||
# Searching both contract through raw data
|
||||
contract_name = "VersionTest"
|
||||
requested_version = "v1.2.3"
|
||||
version, _data = blockchain_interface.find_raw_contract_data(contract_name=contract_name,
|
||||
requested_version=requested_version)
|
||||
assert version == requested_version
|
||||
version, _data = blockchain_interface.find_raw_contract_data(contract_name=contract_name,
|
||||
requested_version="latest")
|
||||
assert version == requested_version
|
||||
|
||||
requested_version = "v1.1.4"
|
||||
version, _data = blockchain_interface.find_raw_contract_data(contract_name=contract_name,
|
||||
requested_version=requested_version)
|
||||
assert version == requested_version
|
||||
version, _data = blockchain_interface.find_raw_contract_data(contract_name=contract_name,
|
||||
requested_version="earliest")
|
||||
assert version == requested_version
|
||||
|
||||
# Deploy different contracts and check their versions
|
||||
registry = InMemoryContractRegistry()
|
||||
contract, receipt = blockchain_interface.deploy_contract(deployer_address=origin,
|
||||
registry=registry,
|
||||
contract_name=contract_name,
|
||||
contract_version="v1.1.4")
|
||||
assert contract.version == "v1.1.4"
|
||||
assert contract.functions.VERSION().call() == 1
|
||||
contract, receipt = blockchain_interface.deploy_contract(deployer_address=origin,
|
||||
registry=registry,
|
||||
contract_name=contract_name,
|
||||
contract_version="earliest")
|
||||
assert contract.version == "v1.1.4"
|
||||
assert contract.functions.VERSION().call() == 1
|
||||
|
||||
contract, receipt = blockchain_interface.deploy_contract(deployer_address=origin,
|
||||
registry=registry,
|
||||
contract_name=contract_name,
|
||||
contract_version="v1.2.3")
|
||||
assert contract.version == "v1.2.3"
|
||||
assert contract.functions.VERSION().call() == 2
|
||||
contract, receipt = blockchain_interface.deploy_contract(deployer_address=origin,
|
||||
registry=registry,
|
||||
contract_name=contract_name,
|
||||
contract_version="latest")
|
||||
assert contract.version == "v1.2.3"
|
||||
assert contract.functions.VERSION().call() == 2
|
||||
contract, receipt = blockchain_interface.deploy_contract(deployer_address=origin,
|
||||
registry=registry,
|
||||
contract_name=contract_name)
|
||||
assert contract.version == "v1.2.3"
|
||||
assert contract.functions.VERSION().call() == 2
|
|
@ -14,54 +14,46 @@ 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 os
|
||||
from os.path import abspath, dirname
|
||||
|
||||
from nucypher.blockchain.eth.deployers import NucypherTokenDeployer
|
||||
from nucypher.blockchain.eth.sol.compile import SolidityCompiler, SourceDirs
|
||||
from tests.constants import TEST_CONTRACTS_DIR
|
||||
from nucypher.blockchain.eth.sol.compile.compile import multiversion_compile
|
||||
from nucypher.blockchain.eth.sol.compile.constants import DEFAULT_VERSION_STRING, TEST_MULTIVERSION_CONTRACTS, \
|
||||
TEST_SOLIDITY_SOURCE_ROOT, SOLIDITY_SOURCE_ROOT
|
||||
from nucypher.blockchain.eth.sol.compile.types import SourceBundle
|
||||
|
||||
|
||||
def test_nucypher_contract_compiled(testerchain, test_registry):
|
||||
# Ensure that solidity smart contacts are available, post-compile.
|
||||
"""Ensure that solidity smart contacts are available, post-compile."""
|
||||
origin, *everybody_else = testerchain.client.accounts
|
||||
|
||||
token_contract_identifier = NucypherTokenDeployer(registry=test_registry, deployer_address=origin).contract_name
|
||||
assert token_contract_identifier in testerchain._raw_contract_cache
|
||||
token_data = testerchain._raw_contract_cache[token_contract_identifier]
|
||||
assert len(token_data) == 1
|
||||
assert "v0.0.0" in token_data
|
||||
assert DEFAULT_VERSION_STRING in token_data
|
||||
|
||||
|
||||
def test_multi_source_compilation(testerchain):
|
||||
solidity_compiler = SolidityCompiler(source_dirs=[
|
||||
(SolidityCompiler.default_contract_dir(), None),
|
||||
(SolidityCompiler.default_contract_dir(), {TEST_CONTRACTS_DIR})
|
||||
])
|
||||
interfaces = solidity_compiler.compile()
|
||||
|
||||
# Remove AST because id in tree node depends on compilation scope
|
||||
for contract_name, contract_data in interfaces.items():
|
||||
for version, data in contract_data.items():
|
||||
data.pop("ast")
|
||||
bundles = [
|
||||
SourceBundle(base_path=SOLIDITY_SOURCE_ROOT),
|
||||
SourceBundle(base_path=SOLIDITY_SOURCE_ROOT, other_paths=(TEST_SOLIDITY_SOURCE_ROOT,))
|
||||
]
|
||||
interfaces = multiversion_compile(source_bundles=bundles)
|
||||
raw_cache = testerchain._raw_contract_cache.copy()
|
||||
for contract_name, contract_data in raw_cache.items():
|
||||
for version, data in contract_data.items():
|
||||
data.pop("ast")
|
||||
assert interfaces == raw_cache
|
||||
|
||||
|
||||
def test_multi_versions():
|
||||
base_dir = os.path.join(dirname(abspath(__file__)), "contracts", "multiversion")
|
||||
v1_dir = os.path.join(base_dir, "v1")
|
||||
v2_dir = os.path.join(base_dir, "v2")
|
||||
root_dir = SolidityCompiler.default_contract_dir()
|
||||
solidity_compiler = SolidityCompiler(source_dirs=[SourceDirs(root_dir, {v1_dir}),
|
||||
SourceDirs(root_dir, {v2_dir})])
|
||||
interfaces = solidity_compiler.compile()
|
||||
base_dir = TEST_MULTIVERSION_CONTRACTS
|
||||
v1_dir, v2_dir = base_dir / "v1", base_dir / "v2"
|
||||
bundles = [
|
||||
SourceBundle(base_path=v1_dir),
|
||||
SourceBundle(base_path=v2_dir)
|
||||
]
|
||||
interfaces = multiversion_compile(source_bundles=bundles)
|
||||
assert "VersionTest" in interfaces
|
||||
contract_data = interfaces["VersionTest"]
|
||||
assert len(contract_data) == 2
|
||||
assert "v1.2.3" in contract_data
|
||||
assert "v1.1.4" in contract_data
|
||||
assert contract_data["v1.2.3"]["devdoc"] != contract_data["v1.1.4"]["devdoc"]
|
||||
assert contract_data["v1.2.3"]["devdoc"]['details'] != contract_data["v1.1.4"]["devdoc"]['details']
|
||||
|
|
|
@ -14,33 +14,30 @@ 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 types
|
||||
from os.path import abspath, dirname
|
||||
|
||||
import os
|
||||
from unittest.mock import PropertyMock
|
||||
|
||||
import maya
|
||||
import pytest
|
||||
from hexbytes import HexBytes
|
||||
|
||||
from nucypher.blockchain.eth.clients import EthereumClient
|
||||
from nucypher.blockchain.eth.interfaces import BlockchainDeployerInterface, BlockchainInterfaceFactory
|
||||
from nucypher.blockchain.eth.interfaces import BlockchainDeployerInterface
|
||||
from nucypher.blockchain.eth.registry import InMemoryContractRegistry
|
||||
from nucypher.blockchain.eth.sol.compile import SolidityCompiler, SourceDirs
|
||||
from nucypher.blockchain.eth.sol.compile.compile import multiversion_compile
|
||||
from nucypher.blockchain.eth.sol.compile.constants import TEST_MULTIVERSION_CONTRACTS, SOLIDITY_SOURCE_ROOT
|
||||
from nucypher.blockchain.eth.sol.compile.types import SourceBundle
|
||||
from nucypher.config.constants import NUCYPHER_TEST_DIR
|
||||
from nucypher.crypto.powers import TransactingPower
|
||||
from tests.constants import (
|
||||
DEVELOPMENT_ETH_AIRDROP_AMOUNT,
|
||||
NUMBER_OF_ETH_TEST_ACCOUNTS,
|
||||
NUMBER_OF_STAKERS_IN_BLOCKCHAIN_TESTS,
|
||||
NUMBER_OF_URSULAS_IN_BLOCKCHAIN_TESTS, INSECURE_DEVELOPMENT_PASSWORD
|
||||
)
|
||||
# Prevents TesterBlockchain to be picked up by py.test as a test class
|
||||
from tests.fixtures import _make_testerchain
|
||||
from tests.mock.interfaces import MockBlockchain
|
||||
from tests.utils.blockchain import TesterBlockchain as _TesterBlockchain, free_gas_price_strategy
|
||||
from tests.constants import (DEVELOPMENT_ETH_AIRDROP_AMOUNT, INSECURE_DEVELOPMENT_PASSWORD,
|
||||
NUMBER_OF_ETH_TEST_ACCOUNTS, NUMBER_OF_STAKERS_IN_BLOCKCHAIN_TESTS,
|
||||
NUMBER_OF_URSULAS_IN_BLOCKCHAIN_TESTS)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def another_testerchain(solidity_compiler):
|
||||
testerchain = _TesterBlockchain(eth_airdrop=True, free_transactions=True, light=True, compiler=solidity_compiler)
|
||||
def another_testerchain():
|
||||
testerchain = _TesterBlockchain(eth_airdrop=True, free_transactions=True, light=True)
|
||||
testerchain.deployer_address = testerchain.etherbase_account
|
||||
assert testerchain.is_light
|
||||
yield testerchain
|
||||
|
@ -96,19 +93,22 @@ def test_testerchain_creation(testerchain, another_testerchain):
|
|||
|
||||
|
||||
def test_multiversion_contract():
|
||||
|
||||
# Prepare compiler
|
||||
base_dir = os.path.join(dirname(abspath(__file__)), "contracts", "multiversion")
|
||||
v1_dir = os.path.join(base_dir, "v1")
|
||||
v2_dir = os.path.join(base_dir, "v2")
|
||||
root_dir = SolidityCompiler.default_contract_dir()
|
||||
solidity_compiler = SolidityCompiler(source_dirs=[SourceDirs(root_dir, {v2_dir}),
|
||||
SourceDirs(root_dir, {v1_dir})])
|
||||
base_dir = TEST_MULTIVERSION_CONTRACTS
|
||||
v1_dir, v2_dir = base_dir / 'v1', base_dir / 'v2'
|
||||
bundles = [
|
||||
SourceBundle(base_path=SOLIDITY_SOURCE_ROOT, other_paths=(v1_dir,)),
|
||||
SourceBundle(base_path=SOLIDITY_SOURCE_ROOT, other_paths=(v2_dir,))
|
||||
]
|
||||
compiled_contracts = multiversion_compile(source_bundles=bundles)
|
||||
|
||||
# Prepare chain
|
||||
blockchain_interface = BlockchainDeployerInterface(provider_uri='tester://pyevm/2',
|
||||
compiler=solidity_compiler,
|
||||
gas_strategy=free_gas_price_strategy)
|
||||
blockchain_interface.connect()
|
||||
blockchain_interface.connect(compile_now=False)
|
||||
blockchain_interface._raw_contract_cache = compiled_contracts
|
||||
|
||||
origin = blockchain_interface.client.accounts[0]
|
||||
blockchain_interface.transacting_power = TransactingPower(password=INSECURE_DEVELOPMENT_PASSWORD, account=origin)
|
||||
blockchain_interface.transacting_power.activate()
|
|
@ -27,7 +27,7 @@ from nucypher.blockchain.eth.interfaces import BlockchainInterface
|
|||
from nucypher.blockchain.eth.networks import NetworksInventory
|
||||
from nucypher.blockchain.eth.registry import LocalContractRegistry
|
||||
from nucypher.blockchain.eth.signers import Signer
|
||||
from nucypher.blockchain.eth.sol.compile import SOLIDITY_COMPILER_VERSION
|
||||
from nucypher.blockchain.eth.sol import SOLIDITY_COMPILER_VERSION
|
||||
from nucypher.cli.commands.deploy import deploy
|
||||
from nucypher.config.constants import TEMPORARY_DOMAIN
|
||||
from tests.constants import TEST_PROVIDER_URI, YES_ENTER
|
||||
|
|
|
@ -16,16 +16,16 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
|||
"""
|
||||
|
||||
import string
|
||||
from random import SystemRandom
|
||||
|
||||
import tempfile
|
||||
import time
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from random import SystemRandom
|
||||
|
||||
from web3 import Web3
|
||||
|
||||
from nucypher.blockchain.eth.token import NU
|
||||
from nucypher.config.constants import BASE_DIR, NUCYPHER_ENVVAR_KEYRING_PASSWORD, NUCYPHER_ENVVAR_WORKER_ETH_PASSWORD
|
||||
from nucypher.config.constants import NUCYPHER_ENVVAR_KEYRING_PASSWORD, NUCYPHER_ENVVAR_WORKER_ETH_PASSWORD
|
||||
|
||||
#
|
||||
# Ursula
|
||||
|
@ -57,7 +57,6 @@ NUMBER_OF_MOCK_KEYSTORE_ACCOUNTS = NUMBER_OF_ETH_TEST_ACCOUNTS
|
|||
# Testerchain
|
||||
#
|
||||
|
||||
TEST_CONTRACTS_DIR = Path(BASE_DIR) / 'tests' / 'contracts' / 'contracts'
|
||||
|
||||
ONE_YEAR_IN_SECONDS = ((60 * 60) * 24) * 365
|
||||
|
||||
|
|
|
@ -17,17 +17,20 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
|||
import contextlib
|
||||
|
||||
import os
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
from web3.exceptions import ValidationError
|
||||
|
||||
from nucypher.blockchain.eth.deployers import AdjudicatorDeployer, BaseContractDeployer, NucypherTokenDeployer, \
|
||||
PolicyManagerDeployer, StakingEscrowDeployer
|
||||
PolicyManagerDeployer, StakingEscrowDeployer, WorklockDeployer
|
||||
from nucypher.blockchain.eth.interfaces import BlockchainDeployerInterface, BlockchainInterfaceFactory
|
||||
from nucypher.blockchain.eth.registry import InMemoryContractRegistry
|
||||
from nucypher.blockchain.eth.sol.compile import SolidityCompiler, SourceDirs
|
||||
from nucypher.blockchain.eth.sol.compile.constants import SOLIDITY_SOURCE_ROOT
|
||||
from nucypher.blockchain.eth.sol.compile.types import SourceBundle
|
||||
from nucypher.crypto.powers import TransactingPower
|
||||
from tests.constants import INSECURE_DEVELOPMENT_PASSWORD
|
||||
from tests.fixtures import make_token_economics
|
||||
from tests.utils.blockchain import free_gas_price_strategy
|
||||
|
||||
USER = "nucypher"
|
||||
|
@ -97,14 +100,16 @@ def deploy_earliest_contract(blockchain_interface: BlockchainDeployerInterface,
|
|||
def test_upgradeability(temp_dir_path):
|
||||
# Prepare remote source for compilation
|
||||
download_github_dir(GITHUB_SOURCE_LINK, temp_dir_path)
|
||||
solidity_compiler = SolidityCompiler(source_dirs=[SourceDirs(SolidityCompiler.default_contract_dir()),
|
||||
SourceDirs(temp_dir_path)])
|
||||
|
||||
# Prepare the blockchain
|
||||
provider_uri = 'tester://pyevm/2'
|
||||
BlockchainDeployerInterface.SOURCES = [
|
||||
SourceBundle(base_path=SOLIDITY_SOURCE_ROOT),
|
||||
SourceBundle(base_path=Path(temp_dir_path))
|
||||
]
|
||||
|
||||
provider_uri = 'tester://pyevm/2' # TODO: Testerchain caching Issues
|
||||
try:
|
||||
blockchain_interface = BlockchainDeployerInterface(provider_uri=provider_uri,
|
||||
compiler=solidity_compiler,
|
||||
gas_strategy=free_gas_price_strategy)
|
||||
blockchain_interface.connect()
|
||||
origin = blockchain_interface.client.accounts[0]
|
||||
|
@ -112,6 +117,8 @@ def test_upgradeability(temp_dir_path):
|
|||
blockchain_interface.transacting_power = TransactingPower(password=INSECURE_DEVELOPMENT_PASSWORD, account=origin)
|
||||
blockchain_interface.transacting_power.activate()
|
||||
|
||||
economics = make_token_economics(blockchain_interface)
|
||||
|
||||
# Check contracts with multiple versions
|
||||
raw_contracts = blockchain_interface._raw_contract_cache
|
||||
contract_name = AdjudicatorDeployer.contract_name
|
||||
|
@ -127,25 +134,45 @@ def test_upgradeability(temp_dir_path):
|
|||
# Prepare master version of contracts and upgrade to the latest
|
||||
registry = InMemoryContractRegistry()
|
||||
|
||||
token_deployer = NucypherTokenDeployer(registry=registry, deployer_address=origin)
|
||||
token_deployer = NucypherTokenDeployer(registry=registry,
|
||||
deployer_address=origin,
|
||||
economics=economics)
|
||||
token_deployer.deploy()
|
||||
|
||||
staking_escrow_deployer = StakingEscrowDeployer(registry=registry, deployer_address=origin)
|
||||
staking_escrow_deployer = StakingEscrowDeployer(registry=registry,
|
||||
deployer_address=origin,
|
||||
economics=economics)
|
||||
deploy_earliest_contract(blockchain_interface, staking_escrow_deployer)
|
||||
|
||||
policy_manager_deployer = None
|
||||
if test_staking_escrow or test_policy_manager:
|
||||
policy_manager_deployer = PolicyManagerDeployer(registry=registry,
|
||||
deployer_address=origin,
|
||||
economics=economics)
|
||||
deploy_earliest_contract(blockchain_interface, policy_manager_deployer)
|
||||
|
||||
adjudicator_deployer = None
|
||||
if test_staking_escrow or test_adjudicator:
|
||||
adjudicator_deployer = AdjudicatorDeployer(registry=registry,
|
||||
deployer_address=origin,
|
||||
economics=economics)
|
||||
deploy_earliest_contract(blockchain_interface, adjudicator_deployer)
|
||||
|
||||
if test_staking_escrow:
|
||||
worklock_deployer = WorklockDeployer(registry=registry,
|
||||
deployer_address=origin,
|
||||
economics=economics)
|
||||
worklock_deployer.deploy()
|
||||
# TODO prepare at least one staker before calling upgrade
|
||||
staking_escrow_deployer.upgrade(contract_version="latest", confirmations=0)
|
||||
|
||||
if test_policy_manager:
|
||||
policy_manager_deployer = PolicyManagerDeployer(registry=registry, deployer_address=origin)
|
||||
deploy_earliest_contract(blockchain_interface, policy_manager_deployer)
|
||||
policy_manager_deployer.upgrade(contract_version="latest", confirmations=0)
|
||||
|
||||
if test_adjudicator:
|
||||
adjudicator_deployer = AdjudicatorDeployer(registry=registry, deployer_address=origin)
|
||||
deploy_earliest_contract(blockchain_interface, adjudicator_deployer)
|
||||
adjudicator_deployer.upgrade(contract_version="latest", confirmations=0)
|
||||
|
||||
finally:
|
||||
# Unregister interface
|
||||
# Unregister interface # TODO: Move to method?
|
||||
with contextlib.suppress(KeyError):
|
||||
del BlockchainInterfaceFactory._interfaces[provider_uri]
|
||||
|
|
|
@ -23,6 +23,7 @@ import shutil
|
|||
import tempfile
|
||||
from datetime import datetime, timedelta
|
||||
from functools import partial
|
||||
from typing import Tuple
|
||||
|
||||
import maya
|
||||
import pytest
|
||||
|
@ -52,10 +53,9 @@ from nucypher.blockchain.eth.deployers import (
|
|||
from nucypher.blockchain.eth.interfaces import BlockchainInterfaceFactory
|
||||
from nucypher.blockchain.eth.registry import InMemoryContractRegistry, LocalContractRegistry
|
||||
from nucypher.blockchain.eth.signers.software import Web3Signer
|
||||
from nucypher.blockchain.eth.sol.compile import SolidityCompiler
|
||||
from nucypher.blockchain.eth.token import NU
|
||||
from nucypher.characters.control.emitters import StdoutEmitter
|
||||
from nucypher.characters.lawful import Bob, Enrico
|
||||
from nucypher.characters.lawful import Enrico
|
||||
from nucypher.config.characters import (
|
||||
AliceConfiguration,
|
||||
BobConfiguration,
|
||||
|
@ -64,9 +64,7 @@ from nucypher.config.characters import (
|
|||
)
|
||||
from nucypher.config.constants import TEMPORARY_DOMAIN
|
||||
from nucypher.crypto.powers import TransactingPower
|
||||
from nucypher.crypto.utils import canonical_address_from_umbral_key
|
||||
from nucypher.datastore import datastore
|
||||
from nucypher.policy.collections import IndisputableEvidence, WorkOrder
|
||||
from nucypher.utilities.logging import GlobalLoggerSettings, Logger
|
||||
|
||||
from tests.constants import (
|
||||
|
@ -110,8 +108,7 @@ from tests.utils.config import (
|
|||
from tests.utils.middleware import MockRestMiddleware, MockRestMiddlewareForLargeFleetTests
|
||||
from tests.utils.policy import generate_random_label
|
||||
from tests.utils.ursula import MOCK_URSULA_STARTING_PORT, make_decentralized_ursulas, make_federated_ursulas, \
|
||||
MOCK_KNOWN_URSULAS_CACHE
|
||||
|
||||
MOCK_KNOWN_URSULAS_CACHE, _mock_ursula_reencrypts
|
||||
|
||||
test_logger = Logger("test-logger")
|
||||
|
||||
|
@ -458,13 +455,6 @@ def token_economics(testerchain):
|
|||
return make_token_economics(blockchain=testerchain)
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def solidity_compiler():
|
||||
"""Doing this more than once per session will result in slower test run times."""
|
||||
compiler = SolidityCompiler()
|
||||
yield compiler
|
||||
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def test_registry():
|
||||
registry = InMemoryContractRegistry()
|
||||
|
@ -783,53 +773,6 @@ def funded_blockchain(testerchain, agency, token_economics, test_registry):
|
|||
# Re-Encryption
|
||||
#
|
||||
|
||||
def _mock_ursula_reencrypts(ursula, corrupt_cfrag: bool = False):
|
||||
delegating_privkey = UmbralPrivateKey.gen_key()
|
||||
_symmetric_key, capsule = pre._encapsulate(delegating_privkey.get_pubkey())
|
||||
signing_privkey = UmbralPrivateKey.gen_key()
|
||||
signing_pubkey = signing_privkey.get_pubkey()
|
||||
signer = Signer(signing_privkey)
|
||||
priv_key_bob = UmbralPrivateKey.gen_key()
|
||||
pub_key_bob = priv_key_bob.get_pubkey()
|
||||
kfrags = pre.generate_kfrags(delegating_privkey=delegating_privkey,
|
||||
signer=signer,
|
||||
receiving_pubkey=pub_key_bob,
|
||||
threshold=2,
|
||||
N=4,
|
||||
sign_delegating_key=False,
|
||||
sign_receiving_key=False)
|
||||
capsule.set_correctness_keys(delegating_privkey.get_pubkey(), pub_key_bob, signing_pubkey)
|
||||
|
||||
ursula_pubkey = ursula.stamp.as_umbral_pubkey()
|
||||
|
||||
alice_address = canonical_address_from_umbral_key(signing_pubkey)
|
||||
blockhash = bytes(32)
|
||||
|
||||
specification = b''.join((bytes(capsule),
|
||||
bytes(ursula_pubkey),
|
||||
bytes(ursula.decentralized_identity_evidence),
|
||||
alice_address,
|
||||
blockhash))
|
||||
|
||||
bobs_signer = Signer(priv_key_bob)
|
||||
task_signature = bytes(bobs_signer(specification))
|
||||
|
||||
metadata = bytes(ursula.stamp(task_signature))
|
||||
|
||||
cfrag = pre.reencrypt(kfrags[0], capsule, metadata=metadata)
|
||||
|
||||
if corrupt_cfrag:
|
||||
cfrag.proof.bn_sig = CurveBN.gen_rand(capsule.params.curve)
|
||||
|
||||
cfrag_signature = ursula.stamp(bytes(cfrag))
|
||||
|
||||
bob = Bob.from_public_keys(verifying_key=pub_key_bob)
|
||||
task = WorkOrder.PRETask(capsule, task_signature, cfrag, cfrag_signature)
|
||||
work_order = WorkOrder(bob, None, alice_address, {capsule: task}, None, ursula, blockhash)
|
||||
|
||||
evidence = IndisputableEvidence(task, work_order)
|
||||
return evidence
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def mock_ursula_reencrypts():
|
||||
|
@ -951,10 +894,15 @@ def manual_worker(testerchain):
|
|||
#
|
||||
|
||||
# TODO : Use a pytest Flag to enable/disable this functionality
|
||||
test_logger = Logger("test-logger")
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, scope='function')
|
||||
def log_in_and_out_of_test(request):
|
||||
test_name = request.node.name
|
||||
module_name = request.module.__name__
|
||||
|
||||
|
||||
test_logger.info(f"Starting {module_name}.py::{test_name}")
|
||||
yield
|
||||
test_logger.info(f"Finalized {module_name}.py::{test_name}")
|
||||
|
|
|
@ -18,32 +18,38 @@ 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 json
|
||||
from os.path import abspath, dirname
|
||||
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import tabulate
|
||||
import time
|
||||
from os.path import abspath, dirname
|
||||
from unittest.mock import Mock
|
||||
|
||||
import tabulate
|
||||
from twisted.logger import ILogObserver, globalLogPublisher, jsonFileLogObserver
|
||||
from umbral.keys import UmbralPrivateKey
|
||||
from umbral.signing import Signer
|
||||
from unittest.mock import Mock
|
||||
from zope.interface import provider
|
||||
|
||||
from nucypher.exceptions import DevelopmentInstallationRequired
|
||||
from nucypher.blockchain.economics import StandardTokenEconomics
|
||||
from nucypher.blockchain.eth.agents import AdjudicatorAgent, NucypherTokenAgent, PolicyManagerAgent, StakingEscrowAgent
|
||||
from nucypher.blockchain.eth.agents import (
|
||||
AdjudicatorAgent,
|
||||
NucypherTokenAgent,
|
||||
PolicyManagerAgent,
|
||||
StakingEscrowAgent
|
||||
)
|
||||
from nucypher.blockchain.eth.constants import NUCYPHER_CONTRACT_NAMES
|
||||
from nucypher.crypto.signing import SignatureStamp
|
||||
from nucypher.policy.policies import Policy
|
||||
from nucypher.utilities.logging import Logger
|
||||
from tests.utils.blockchain import TesterBlockchain
|
||||
|
||||
# FIXME: Needed to use a fixture here, but now estimate_gas.py only runs if executed from main directory
|
||||
sys.path.insert(0, abspath('tests'))
|
||||
from fixtures import _mock_ursula_reencrypts as mock_ursula_reencrypts
|
||||
try:
|
||||
from tests.utils.ursula import _mock_ursula_reencrypts as mock_ursula_reencrypts
|
||||
except ImportError:
|
||||
raise DevelopmentInstallationRequired(importable_name='tests.utils.ursula')
|
||||
|
||||
|
||||
ALGORITHM_SHA256 = 1
|
||||
|
@ -176,7 +182,7 @@ def estimate_gas(analyzer: AnalyzeGas = None) -> None:
|
|||
compiled_contract = testerchain._raw_contract_cache[contract_name]
|
||||
|
||||
version = list(compiled_contract).pop()
|
||||
bin_runtime = compiled_contract[version]['bin-runtime']
|
||||
bin_runtime = compiled_contract[version]['evm']['bytecode']['object']
|
||||
bin_length_in_bytes = len(bin_runtime) // 2
|
||||
percentage = int(100 * bin_length_in_bytes / MAX_SIZE)
|
||||
bar = ('*'*(percentage//2)).ljust(50)
|
||||
|
|
|
@ -73,14 +73,18 @@ def mock_registry_source_manager(blockchain, test_registry, mock_backend: bool =
|
|||
|
||||
class MockBlockchain(TesterBlockchain):
|
||||
|
||||
_PROVIDER_URI = MOCK_PROVIDER_URI
|
||||
_compiler = None
|
||||
PROVIDER_URI = MOCK_PROVIDER_URI
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(mock_backend=True)
|
||||
super().__init__()
|
||||
|
||||
|
||||
class MockEthereumClient(EthereumClient):
|
||||
|
||||
def __init__(self, w3):
|
||||
super().__init__(w3, None, None, None, None)
|
||||
super().__init__(w3=w3, node_technology=None, version=None, platform=None, backend=None)
|
||||
|
||||
def connect(self, *args, **kwargs) -> bool:
|
||||
if 'compile_now' in kwargs:
|
||||
raise ValueError("Mock testerchain cannot handle solidity source compilation.")
|
||||
return super().connect(compile_now=False, *args, **kwargs)
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
"""
|
||||
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 re
|
||||
from hypothesis import given, example, settings
|
||||
from hypothesis import strategies
|
||||
|
||||
from nucypher.blockchain.eth.sol.compile.aggregation import DEVDOC_VERSION_PATTERN
|
||||
from nucypher.blockchain.eth.sol.compile.constants import DEFAULT_VERSION_STRING
|
||||
|
||||
|
||||
@example('|v1.2.3|')
|
||||
@example('|v99.99.99|')
|
||||
@example(f'|{DEFAULT_VERSION_STRING}|')
|
||||
@given(strategies.from_regex(DEVDOC_VERSION_PATTERN, fullmatch=True))
|
||||
@settings(max_examples=5000)
|
||||
def test_devdoc_regex_pattern(full_match):
|
||||
|
||||
# Not empty
|
||||
assert full_match, 'Devdoc regex pattern matched an empty value: "{version_string}"'
|
||||
|
||||
# Anchors
|
||||
assert full_match.startswith('|'), 'Version string does not end in "|" delimiter: "{version_string}"'
|
||||
assert full_match.endswith('|'), 'Version string does not end in "|" delimiter: "{version_string}"'
|
||||
|
||||
# Max Size
|
||||
numbers_only = re.sub("[^0-9]", "", full_match)
|
||||
# I mean really... who has a version with more than 6 numbers (v99.99.99)
|
||||
assert len(numbers_only) <= 10, 'Version string is too long: "{version_string}"'
|
||||
|
||||
# "v" specifier
|
||||
version_string = full_match[1:-1]
|
||||
assert version_string.startswith('v'), 'Version string does not start with "v": "{version_string}"'
|
||||
assert version_string.count('v') == 1, 'Version string contains more than one "v": "{version_string}"'
|
||||
|
||||
# Version parts
|
||||
assert version_string.count('.') == 2, f'Version string has more than two periods: "{version_string}"'
|
||||
parts = version_string[1:]
|
||||
version_parts = parts.split('.')
|
||||
assert len(version_parts) == 3, f'Version string has more than three parts: "{version_string}"'
|
||||
|
||||
# Parts are numbers
|
||||
assert all(p.isdigit() for p in version_parts), f'Non-digit found in version string: "{version_string}"'
|
|
@ -274,3 +274,7 @@ class TestGenerateJSON(unittest.TestCase):
|
|||
123.000456, "exemplar": {}}, {"sample_name": "ts", "labels": {"foo": "f"}, "value": "0.0", "timestamp":
|
||||
123.000000456, "exemplar": {}}], "help": "help", "type": "unknown"}}"""), json.loads(
|
||||
self.json_exporter.generate_latest_json()))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
|
|
@ -15,20 +15,23 @@ 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 os
|
||||
from pathlib import Path
|
||||
from typing import List, Tuple, Union
|
||||
|
||||
import maya
|
||||
import os
|
||||
from eth_tester.exceptions import TransactionFailed
|
||||
from eth_utils import to_canonical_address
|
||||
from hexbytes import HexBytes
|
||||
from typing import List, Tuple, Union
|
||||
from web3 import Web3
|
||||
|
||||
from nucypher.blockchain.economics import BaseEconomics, StandardTokenEconomics
|
||||
from nucypher.blockchain.eth.actors import ContractAdministrator
|
||||
from nucypher.blockchain.eth.interfaces import BlockchainDeployerInterface, BlockchainInterfaceFactory
|
||||
from nucypher.blockchain.eth.registry import InMemoryContractRegistry
|
||||
from nucypher.blockchain.eth.sol.compile import SolidityCompiler
|
||||
from nucypher.blockchain.eth.sol.compile.compile import multiversion_compile
|
||||
from nucypher.blockchain.eth.sol.compile.constants import TEST_SOLIDITY_SOURCE_ROOT, SOLIDITY_SOURCE_ROOT
|
||||
from nucypher.blockchain.eth.sol.compile.types import SourceBundle
|
||||
from nucypher.blockchain.eth.token import NU
|
||||
from nucypher.blockchain.eth.utils import epoch_to_period
|
||||
from nucypher.crypto.powers import TransactingPower
|
||||
|
@ -40,8 +43,7 @@ from tests.constants import (
|
|||
INSECURE_DEVELOPMENT_PASSWORD,
|
||||
NUMBER_OF_ETH_TEST_ACCOUNTS,
|
||||
NUMBER_OF_STAKERS_IN_BLOCKCHAIN_TESTS,
|
||||
NUMBER_OF_URSULAS_IN_BLOCKCHAIN_TESTS,
|
||||
TEST_CONTRACTS_DIR
|
||||
NUMBER_OF_URSULAS_IN_BLOCKCHAIN_TESTS, PYEVM_DEV_URI
|
||||
)
|
||||
|
||||
|
||||
|
@ -72,56 +74,50 @@ class TesterBlockchain(BlockchainDeployerInterface):
|
|||
Blockchain subclass with additional test utility methods and options.
|
||||
"""
|
||||
|
||||
__test__ = False # prohibit Pytest from picking it up
|
||||
__test__ = False # prohibit pytest from collecting this object as a test
|
||||
|
||||
_instance = None
|
||||
# Solidity
|
||||
SOURCES: List[SourceBundle] = [
|
||||
SourceBundle(base_path=SOLIDITY_SOURCE_ROOT,
|
||||
other_paths=(TEST_SOLIDITY_SOURCE_ROOT,))
|
||||
]
|
||||
|
||||
GAS_STRATEGIES = {**BlockchainDeployerInterface.GAS_STRATEGIES,
|
||||
'free': free_gas_price_strategy}
|
||||
# Web3
|
||||
GAS_STRATEGIES = {**BlockchainDeployerInterface.GAS_STRATEGIES, 'free': free_gas_price_strategy}
|
||||
PROVIDER_URI = PYEVM_DEV_URI
|
||||
DEFAULT_GAS_STRATEGY = 'free'
|
||||
|
||||
_PROVIDER_URI = 'tester://pyevm'
|
||||
_compiler = SolidityCompiler(source_dirs=[(SolidityCompiler.default_contract_dir(), {TEST_CONTRACTS_DIR})])
|
||||
_test_account_cache = list()
|
||||
|
||||
_default_test_accounts = NUMBER_OF_ETH_TEST_ACCOUNTS
|
||||
|
||||
# Reserved addresses
|
||||
_ETHERBASE = 0
|
||||
_ALICE = 1
|
||||
_BOB = 2
|
||||
_FIRST_STAKER = 5
|
||||
_stakers_range = range(NUMBER_OF_STAKERS_IN_BLOCKCHAIN_TESTS)
|
||||
_FIRST_URSULA = _FIRST_STAKER + NUMBER_OF_STAKERS_IN_BLOCKCHAIN_TESTS
|
||||
_ursulas_range = range(NUMBER_OF_URSULAS_IN_BLOCKCHAIN_TESTS)
|
||||
|
||||
_default_token_economics = StandardTokenEconomics()
|
||||
# Internal
|
||||
__STAKERS_RANGE = range(NUMBER_OF_STAKERS_IN_BLOCKCHAIN_TESTS)
|
||||
__WORKERS_RANGE = range(NUMBER_OF_URSULAS_IN_BLOCKCHAIN_TESTS)
|
||||
__ACCOUNT_CACHE = list()
|
||||
|
||||
# Defaults
|
||||
DEFAULT_ECONOMICS = StandardTokenEconomics()
|
||||
|
||||
def __init__(self,
|
||||
test_accounts=None,
|
||||
poa=True,
|
||||
light=False,
|
||||
eth_airdrop=False,
|
||||
free_transactions=False,
|
||||
compiler: SolidityCompiler = None,
|
||||
mock_backend: bool = False,
|
||||
test_accounts: int = NUMBER_OF_ETH_TEST_ACCOUNTS,
|
||||
poa: bool = True,
|
||||
light: bool = False,
|
||||
eth_airdrop: bool = False,
|
||||
free_transactions: bool = False,
|
||||
*args, **kwargs):
|
||||
|
||||
if not test_accounts:
|
||||
test_accounts = self._default_test_accounts
|
||||
self.free_transactions = free_transactions
|
||||
|
||||
EXPECTED_CONFIRMATION_TIME_IN_SECONDS['free'] = 5 # Just some upper-limit
|
||||
|
||||
if compiler:
|
||||
TesterBlockchain._compiler = compiler
|
||||
|
||||
super().__init__(provider_uri=self._PROVIDER_URI,
|
||||
super().__init__(provider_uri=self.PROVIDER_URI,
|
||||
provider_process=None,
|
||||
poa=poa,
|
||||
light=light,
|
||||
compiler=self._compiler,
|
||||
dry_run=mock_backend,
|
||||
*args, **kwargs)
|
||||
|
||||
self.log = Logger("test-blockchain")
|
||||
|
@ -138,6 +134,17 @@ class TesterBlockchain(BlockchainDeployerInterface):
|
|||
if eth_airdrop is True: # ETH for everyone!
|
||||
self.ether_airdrop(amount=DEVELOPMENT_ETH_AIRDROP_AMOUNT)
|
||||
|
||||
# TODO: DRY this up
|
||||
def connect(self, compile_now: bool = True, ignore_solidity_check: bool = False) -> bool:
|
||||
super().connect()
|
||||
if compile_now:
|
||||
# Execute the compilation if we're recompiling
|
||||
# Otherwise read compiled contract data from the registry.
|
||||
check = not ignore_solidity_check
|
||||
compiled_contracts = multiversion_compile(source_bundles=self.SOURCES, compiler_version_check=check)
|
||||
self._raw_contract_cache = compiled_contracts
|
||||
return self.is_connected
|
||||
|
||||
def attach_middleware(self):
|
||||
if self.free_transactions:
|
||||
self.w3.eth.setGasPriceStrategy(free_gas_price_strategy)
|
||||
|
@ -160,7 +167,7 @@ class TesterBlockchain(BlockchainDeployerInterface):
|
|||
for _ in range(quantity):
|
||||
address = self.provider.ethereum_tester.add_account('0x' + os.urandom(32).hex())
|
||||
addresses.append(address)
|
||||
self._test_account_cache.append(address)
|
||||
self.__ACCOUNT_CACHE.append(address)
|
||||
self.log.info('Generated new insecure account {}'.format(address))
|
||||
return addresses
|
||||
|
||||
|
@ -192,8 +199,8 @@ class TesterBlockchain(BlockchainDeployerInterface):
|
|||
raise ValueError("Specify hours, seconds, or periods, not a combination")
|
||||
|
||||
if periods:
|
||||
duration = self._default_token_economics.seconds_per_period * periods
|
||||
base = self._default_token_economics.seconds_per_period
|
||||
duration = self.DEFAULT_ECONOMICS.seconds_per_period * periods
|
||||
base = self.DEFAULT_ECONOMICS.seconds_per_period
|
||||
elif hours:
|
||||
duration = hours * (60*60)
|
||||
base = 60 * 60
|
||||
|
@ -211,7 +218,7 @@ class TesterBlockchain(BlockchainDeployerInterface):
|
|||
|
||||
delta = maya.timedelta(seconds=end_timestamp-now)
|
||||
self.log.info(f"Time traveled {delta} "
|
||||
f"| period {epoch_to_period(epoch=end_timestamp, seconds_per_period=self._default_token_economics.seconds_per_period)} "
|
||||
f"| period {epoch_to_period(epoch=end_timestamp, seconds_per_period=self.DEFAULT_ECONOMICS.seconds_per_period)} "
|
||||
f"| epoch {end_timestamp}")
|
||||
|
||||
@classmethod
|
||||
|
@ -219,7 +226,7 @@ class TesterBlockchain(BlockchainDeployerInterface):
|
|||
"""For use with metric testing scripts"""
|
||||
|
||||
registry = InMemoryContractRegistry()
|
||||
testerchain = cls(compiler=SolidityCompiler())
|
||||
testerchain = cls()
|
||||
BlockchainInterfaceFactory.register_interface(testerchain)
|
||||
power = TransactingPower(password=INSECURE_DEVELOPMENT_PASSWORD,
|
||||
account=testerchain.etherbase_account)
|
||||
|
@ -229,7 +236,7 @@ class TesterBlockchain(BlockchainDeployerInterface):
|
|||
origin = testerchain.client.etherbase
|
||||
deployer = ContractAdministrator(deployer_address=origin,
|
||||
registry=registry,
|
||||
economics=economics or cls._default_token_economics,
|
||||
economics=economics or cls.DEFAULT_ECONOMICS,
|
||||
staking_escrow_test_mode=True)
|
||||
|
||||
_receipts = deployer.deploy_network_contracts(interactive=False)
|
||||
|
@ -248,22 +255,22 @@ class TesterBlockchain(BlockchainDeployerInterface):
|
|||
return self.client.accounts[self._BOB]
|
||||
|
||||
def ursula_account(self, index):
|
||||
if index not in self._ursulas_range:
|
||||
if index not in self.__WORKERS_RANGE:
|
||||
raise ValueError(f"Ursula index must be lower than {NUMBER_OF_URSULAS_IN_BLOCKCHAIN_TESTS}")
|
||||
return self.client.accounts[index + self._FIRST_URSULA]
|
||||
|
||||
def staker_account(self, index):
|
||||
if index not in self._stakers_range:
|
||||
if index not in self.__STAKERS_RANGE:
|
||||
raise ValueError(f"Staker index must be lower than {NUMBER_OF_STAKERS_IN_BLOCKCHAIN_TESTS}")
|
||||
return self.client.accounts[index + self._FIRST_STAKER]
|
||||
|
||||
@property
|
||||
def ursulas_accounts(self):
|
||||
return list(self.ursula_account(i) for i in self._ursulas_range)
|
||||
return list(self.ursula_account(i) for i in self.__WORKERS_RANGE)
|
||||
|
||||
@property
|
||||
def stakers_accounts(self):
|
||||
return list(self.staker_account(i) for i in self._stakers_range)
|
||||
return list(self.staker_account(i) for i in self.__STAKERS_RANGE)
|
||||
|
||||
@property
|
||||
def unassigned_accounts(self):
|
||||
|
|
|
@ -23,15 +23,23 @@ import tempfile
|
|||
from cryptography.x509 import Certificate
|
||||
from typing import Iterable, List, Optional, Set
|
||||
|
||||
from nucypher.characters.lawful import Bob
|
||||
from nucypher.crypto.utils import canonical_address_from_umbral_key
|
||||
|
||||
from nucypher.blockchain.eth.actors import Staker
|
||||
from nucypher.blockchain.eth.interfaces import BlockchainInterface
|
||||
from nucypher.characters.lawful import Ursula
|
||||
from nucypher.config.characters import UrsulaConfiguration
|
||||
from nucypher.crypto.powers import TransactingPower
|
||||
from nucypher.policy.collections import WorkOrder, IndisputableEvidence
|
||||
from tests.constants import (
|
||||
MOCK_URSULA_DB_FILEPATH,
|
||||
NUMBER_OF_URSULAS_IN_DEVELOPMENT_NETWORK
|
||||
)
|
||||
from umbral import pre
|
||||
from umbral.curvebn import CurveBN
|
||||
from umbral.keys import UmbralPrivateKey
|
||||
from umbral.signing import Signer
|
||||
|
||||
|
||||
def select_test_port() -> int:
|
||||
|
@ -166,3 +174,51 @@ def start_pytest_ursula_services(ursula: Ursula) -> Certificate:
|
|||
|
||||
MOCK_KNOWN_URSULAS_CACHE = dict()
|
||||
MOCK_URSULA_STARTING_PORT = 51000 # select_test_port()
|
||||
|
||||
|
||||
def _mock_ursula_reencrypts(ursula, corrupt_cfrag: bool = False):
|
||||
delegating_privkey = UmbralPrivateKey.gen_key()
|
||||
_symmetric_key, capsule = pre._encapsulate(delegating_privkey.get_pubkey())
|
||||
signing_privkey = UmbralPrivateKey.gen_key()
|
||||
signing_pubkey = signing_privkey.get_pubkey()
|
||||
signer = Signer(signing_privkey)
|
||||
priv_key_bob = UmbralPrivateKey.gen_key()
|
||||
pub_key_bob = priv_key_bob.get_pubkey()
|
||||
kfrags = pre.generate_kfrags(delegating_privkey=delegating_privkey,
|
||||
signer=signer,
|
||||
receiving_pubkey=pub_key_bob,
|
||||
threshold=2,
|
||||
N=4,
|
||||
sign_delegating_key=False,
|
||||
sign_receiving_key=False)
|
||||
capsule.set_correctness_keys(delegating_privkey.get_pubkey(), pub_key_bob, signing_pubkey)
|
||||
|
||||
ursula_pubkey = ursula.stamp.as_umbral_pubkey()
|
||||
|
||||
alice_address = canonical_address_from_umbral_key(signing_pubkey)
|
||||
blockhash = bytes(32)
|
||||
|
||||
specification = b''.join((bytes(capsule),
|
||||
bytes(ursula_pubkey),
|
||||
bytes(ursula.decentralized_identity_evidence),
|
||||
alice_address,
|
||||
blockhash))
|
||||
|
||||
bobs_signer = Signer(priv_key_bob)
|
||||
task_signature = bytes(bobs_signer(specification))
|
||||
|
||||
metadata = bytes(ursula.stamp(task_signature))
|
||||
|
||||
cfrag = pre.reencrypt(kfrags[0], capsule, metadata=metadata)
|
||||
|
||||
if corrupt_cfrag:
|
||||
cfrag.proof.bn_sig = CurveBN.gen_rand(capsule.params.curve)
|
||||
|
||||
cfrag_signature = ursula.stamp(bytes(cfrag))
|
||||
|
||||
bob = Bob.from_public_keys(verifying_key=pub_key_bob)
|
||||
task = WorkOrder.PRETask(capsule, task_signature, cfrag, cfrag_signature)
|
||||
work_order = WorkOrder(bob, None, alice_address, {capsule: task}, None, ursula, blockhash)
|
||||
|
||||
evidence = IndisputableEvidence(task, work_order)
|
||||
return evidence
|
||||
|
|
Loading…
Reference in New Issue