diff --git a/docs/source/architecture/contracts.md b/docs/source/architecture/contracts.md index 80a9689a3..573932157 100644 --- a/docs/source/architecture/contracts.md +++ b/docs/source/architecture/contracts.md @@ -122,3 +122,9 @@ All tokens will be unlocked after a specified time and the user can retrieve the When the user wants to become a staker - they use the `PreallocationEscrow` contract as a proxy for the `StakingEscrow` and `PolicyManager` contracts. +## Contracts Versioning +Upgradeable contracts, such as `Adjudicator`, `StakingEscrow`, `PolicyManager` and `StakingInterface`, have their version specified in contract doc inside @dev. +Version format is `|vi.j.k|`, where `i` - major version, `j` - minor version, `k` - patch, for example `|v1.2.3|`: +* Different major versions mean different forks and they are not upgradeable +* Minor versions relate to any signatures or state changes inside contract, contracts are upgradeable between minor versions, but have different ABI and follows different agent layers +* Patches involve changes inside function(s) with signature(s) untouched. All patches with a common minor version can be upgraded from one to another without other changes diff --git a/nucypher/blockchain/eth/actors.py b/nucypher/blockchain/eth/actors.py index f0db95dd5..d70feb2fc 100644 --- a/nucypher/blockchain/eth/actors.py +++ b/nucypher/blockchain/eth/actors.py @@ -229,6 +229,7 @@ class ContractAdministrator(NucypherTokenActor): gas_limit: int = None, plaintext_secret: str = None, bare: bool = False, + ignore_deployed: bool = False, progress=None, *args, **kwargs, ) -> Tuple[dict, BaseContractDeployer]: @@ -250,17 +251,24 @@ class ContractAdministrator(NucypherTokenActor): receipts = deployer.deploy(secret_hash=secret_hash, gas_limit=gas_limit, initial_deployment=is_initial_deployment, - progress=progress) + progress=progress, + ignore_deployed=ignore_deployed) else: receipts = deployer.deploy(gas_limit=gas_limit, progress=progress) return receipts, deployer - def upgrade_contract(self, contract_name: str, existing_plaintext_secret: str, new_plaintext_secret: str) -> dict: + def upgrade_contract(self, + contract_name: str, + existing_plaintext_secret: str, + new_plaintext_secret: str, + ignore_deployed: bool = False + ) -> dict: Deployer = self.__get_deployer(contract_name=contract_name) deployer = Deployer(registry=self.registry, deployer_address=self.deployer_address) new_secret_hash = keccak(bytes(new_plaintext_secret, encoding='utf-8')) receipts = deployer.upgrade(existing_secret_plaintext=bytes(existing_plaintext_secret, encoding='utf-8'), - new_secret_hash=new_secret_hash) + new_secret_hash=new_secret_hash, + ignore_deployed=ignore_deployed) return receipts def retarget_proxy(self, contract_name: str, target_address: str, existing_plaintext_secret: str, new_plaintext_secret: str): @@ -293,13 +301,15 @@ class ContractAdministrator(NucypherTokenActor): secrets: dict, interactive: bool = True, emitter: StdoutEmitter = None, - etherscan: bool = False) -> dict: + etherscan: bool = False, + ignore_deployed: bool = False) -> dict: """ :param secrets: Contract upgrade secrets dictionary :param interactive: If True, wait for keypress after each contract deployment :param emitter: A console output emitter instance. If emitter is None, no output will be echoed to the console. :param etherscan: Open deployed contracts in Etherscan + :param ignore_deployed: Ignore already deployed contracts if exist :return: Returns a dictionary of deployment receipts keyed by contract name """ @@ -336,7 +346,8 @@ class ContractAdministrator(NucypherTokenActor): receipts, deployer = self.deploy_contract(contract_name=deployer_class.contract_name, plaintext_secret=secrets[deployer_class.contract_name], gas_limit=gas_limit, - progress=bar) + progress=bar, + ignore_deployed=ignore_deployed) if emitter: blockchain = BlockchainInterfaceFactory.get_interface() diff --git a/nucypher/blockchain/eth/agents.py b/nucypher/blockchain/eth/agents.py index 03cf8eeb6..ece261a53 100644 --- a/nucypher/blockchain/eth/agents.py +++ b/nucypher/blockchain/eth/agents.py @@ -97,7 +97,7 @@ class EthereumContractAgent: if contract is None: # Fetch the contract contract = self.blockchain.get_contract_by_name(registry=self.registry, - name=self.registry_contract_name, + contract_name=self.registry_contract_name, proxy_name=self._proxy_name, use_proxy_address=self._forward_address) self.__contract = contract diff --git a/nucypher/blockchain/eth/deployers.py b/nucypher/blockchain/eth/deployers.py index e723122e5..8fc917fd2 100644 --- a/nucypher/blockchain/eth/deployers.py +++ b/nucypher/blockchain/eth/deployers.py @@ -34,7 +34,8 @@ from nucypher.blockchain.eth.agents import ( ) from nucypher.blockchain.eth.constants import DISPATCHER_CONTRACT_NAME from nucypher.blockchain.eth.decorators import validate_secret, validate_checksum_address -from nucypher.blockchain.eth.interfaces import BlockchainDeployerInterface, BlockchainInterfaceFactory +from nucypher.blockchain.eth.interfaces import BlockchainDeployerInterface, BlockchainInterfaceFactory, \ + VersionedContract from nucypher.blockchain.eth.registry import AllocationRegistry, BaseContractRegistry @@ -105,15 +106,25 @@ class BaseContractDeployer: def dispatcher(self): return self.__proxy_contract - @property - def is_deployed(self) -> bool: - return bool(self._contract is not CONTRACT_NOT_DEPLOYED) - @property def ready_to_deploy(self) -> bool: return bool(self.__ready_to_deploy is True) - def check_deployment_readiness(self, fail=True) -> Tuple[bool, list]: + def is_deployed(self, contract_version: str = None) -> bool: + try: + self.registry.search(contract_name=self.contract_name, contract_version=contract_version) + except BaseContractRegistry.UnknownContract: + return False + except BaseContractRegistry.NoRegistry: + return False + else: + return True + + def check_deployment_readiness(self, + contract_version: str = None, + ignore_deployed=False, + fail=True + ) -> Tuple[bool, list]: """ Iterates through a set of rules required for an ethereum contract deployer to be eligible for deployment returning a @@ -128,8 +139,12 @@ class BaseContractDeployer: if self.__ready_to_deploy is True: return True, list() + if not ignore_deployed and contract_version is not None: + contract_version, _data = self.blockchain.find_raw_contract_data(contract_name=self.contract_name, + requested_version=contract_version) + rules = [ - (self.is_deployed is not True, 'Contract already deployed'), + (ignore_deployed or self.is_deployed(contract_version) is not True, 'Contract already deployed'), (self.deployer_address is not None, 'No deployer address set.'), (self.deployer_address is not NO_DEPLOYER_CONFIGURED, 'No deployer address set.'), ] @@ -154,7 +169,7 @@ class BaseContractDeployer: raise self.ContractDeploymentError(message) return True - def deploy(self, gas_limit: int, progress) -> dict: + def deploy(self, gas_limit: int, progress, **overrides) -> dict: """ Provides for the setup, deployment, and initialization of ethereum smart contracts. Emits the configured blockchain network transactions for single contract instance publication. @@ -165,12 +180,12 @@ class BaseContractDeployer: agent = self.agency(registry=self.registry, contract=self._contract) return agent - def get_latest_enrollment(self, registry: BaseContractRegistry) -> Contract: + def get_latest_enrollment(self, registry: BaseContractRegistry) -> VersionedContract: """Get the latest enrolled version of the contract from the registry.""" - contract = self.blockchain.get_contract_by_name(name=self.contract_name, + contract = self.blockchain.get_contract_by_name(contract_name=self.contract_name, registry=registry, use_proxy_address=False, - version='latest') + enrollment_version='latest') return contract @@ -223,7 +238,14 @@ class UpgradeableContractMixin: class ContractNotUpgradeable(RuntimeError): pass - def deploy(self, secret_hash: bytes, initial_deployment: bool = True, gas_limit: int = None, progress = None) -> dict: + def deploy(self, + secret_hash: bytes, + initial_deployment: bool = True, + gas_limit: int = None, + progress=None, + contract_version: str = "latest", + ignore_deployed: bool = False + ) -> dict: """ Provides for the setup, deployment, and initialization of ethereum smart contracts. Emits the configured blockchain network transactions for single contract instance publication. @@ -232,7 +254,7 @@ class UpgradeableContractMixin: raise self.ContractNotUpgradeable(f"{self.contract_name} is not upgradeable.") raise NotImplementedError - def get_principal_contract(self, registry: BaseContractRegistry, provider_uri: str = None) -> Contract: + def get_principal_contract(self, registry: BaseContractRegistry, provider_uri: str = None) -> VersionedContract: """ Get the on-chain targeted version of the principal contract directly without assembling it with its proxy. @@ -240,13 +262,13 @@ class UpgradeableContractMixin: if not self._upgradeable: raise self.ContractNotUpgradeable(f"{self.contract_name} is not upgradeable.") blockchain = BlockchainInterfaceFactory.get_interface(provider_uri=provider_uri) - principal_contract = blockchain.get_contract_by_name(name=self.contract_name, + principal_contract = blockchain.get_contract_by_name(contract_name=self.contract_name, registry=registry, proxy_name=self._proxy_deployer.contract_name, use_proxy_address=False) return principal_contract - def get_proxy_contract(self, registry: BaseContractRegistry, provider_uri: str = None) -> Contract: + def get_proxy_contract(self, registry: BaseContractRegistry, provider_uri: str = None) -> VersionedContract: if not self._upgradeable: raise cls.ContractNotUpgradeable(f"{self.contract_name} is not upgradeable.") blockchain = BlockchainInterfaceFactory.get_interface(provider_uri=provider_uri) @@ -271,7 +293,8 @@ class UpgradeableContractMixin: target_address: str, existing_secret_plaintext: bytes, new_secret_hash: bytes, - gas_limit: int = None): + gas_limit: int = None, + version: str = "latest"): """ Directly engage a proxy contract for an existing deployment, executing the proxy's upgrade interfaces to verify upgradeability and modify the on-chain contract target. @@ -291,7 +314,13 @@ class UpgradeableContractMixin: gas_limit=gas_limit) return receipt - def upgrade(self, existing_secret_plaintext: bytes, new_secret_hash: bytes, gas_limit: int = None): + def upgrade(self, + existing_secret_plaintext: bytes, + new_secret_hash: bytes, + gas_limit: int = None, + contract_version: str = "latest", + ignore_deployed: bool = False, + **overrides): """ Deploy a new version of a contract, then engage the proxy contract's upgrade interfaces. """ @@ -299,14 +328,16 @@ class UpgradeableContractMixin: # 1 - Raise if not all-systems-go # if not self._upgradeable: raise self.ContractNotUpgradeable(f"{self.contract_name} is not upgradeable.") - self.check_deployment_readiness() + self.check_deployment_readiness(contract_version=contract_version, ignore_deployed=ignore_deployed) # 2 - Get Bare Contracts existing_bare_contract = self.get_principal_contract(registry=self.registry, provider_uri=self.blockchain.provider_uri) proxy_deployer = self.get_proxy_deployer(registry=self.registry, provider_uri=self.blockchain.provider_uri) # 3 - Deploy new version - new_contract, deploy_receipt = self._deploy_essential(gas_limit=gas_limit) + new_contract, deploy_receipt = self._deploy_essential(contract_version=contract_version, + gas_limit=gas_limit, + **overrides) # 4 - Wrap the escrow contract wrapped_contract = self.blockchain._wrap_contract(wrapper_contract=proxy_deployer.contract, @@ -333,7 +364,7 @@ class UpgradeableContractMixin: raise self.ContractNotUpgradeable(f"{self.contract_name} is not upgradeable.") existing_bare_contract = self.blockchain.get_contract_by_name(registry=self.registry, - name=self.contract_name, + contract_name=self.contract_name, proxy_name=self._proxy_deployer.contract_name, use_proxy_address=False) proxy_deployer = self.get_proxy_deployer(registry=self.registry, provider_uri=self.blockchain.provider_uri) @@ -361,7 +392,7 @@ class NucypherTokenDeployer(BaseContractDeployer): _upgradeable = False _ownable = False - def deploy(self, gas_limit: int = None, progress=None) -> dict: + def deploy(self, gas_limit: int = None, progress=None, **overrides) -> dict: """ Deploy and publish the NuCypher Token contract to the blockchain network specified in self.blockchain.network. @@ -371,11 +402,14 @@ class NucypherTokenDeployer(BaseContractDeployer): self.check_deployment_readiness() # Order-sensitive! + constructor_kwargs = {"_totalSupply": self.economics.erc20_total_supply} + constructor_kwargs.update(overrides) + constructor_kwargs = {k: v for k, v in constructor_kwargs.items() if v is not None} contract, deployment_receipt = self.blockchain.deploy_contract(self.deployer_address, self.registry, self.contract_name, gas_limit=gas_limit, - _totalSupply=self.economics.erc20_total_supply) + **constructor_kwargs) if progress: progress.update(1) self._contract = contract @@ -458,21 +492,36 @@ class StakingEscrowDeployer(BaseContractDeployer, UpgradeableContractMixin, Owna token_contract_name = NucypherTokenDeployer.contract_name self.token_contract = self.blockchain.get_contract_by_name(registry=self.registry, - name=token_contract_name) + contract_name=token_contract_name) def __check_policy_manager(self): result = self.contract.functions.policyManager().call() if result == self.blockchain.NULL_ADDRESS: raise RuntimeError("PolicyManager contract is not initialized.") - def _deploy_essential(self, gas_limit: int = None): - escrow_constructor_args = (self.token_contract.address, *self.economics.staking_deployment_parameters) + def _deploy_essential(self, contract_version: str, gas_limit: int = None, **overrides): + args = self.economics.staking_deployment_parameters + constructor_kwargs = { + "_hoursPerPeriod": args[0], + "_miningCoefficient": args[1], + "_lockedPeriodsCoefficient": args[2], + "_rewardedPeriods": args[3], + "_minLockedPeriods": args[4], + "_minAllowableLockedTokens": args[5], + "_maxAllowableLockedTokens": args[6], + "_minWorkerPeriods": args[7] + } + constructor_kwargs.update(overrides) + constructor_kwargs = {k: v for k, v in constructor_kwargs.items() if v is not None} + # Force use of the token address from the registry + constructor_kwargs.update({"_token": self.token_contract.address}) the_escrow_contract, deploy_receipt = self.blockchain.deploy_contract( self.deployer_address, self.registry, self.contract_name, gas_limit=gas_limit, - *escrow_constructor_args, + contract_version=contract_version, + **constructor_kwargs ) return the_escrow_contract, deploy_receipt @@ -481,7 +530,10 @@ class StakingEscrowDeployer(BaseContractDeployer, UpgradeableContractMixin, Owna initial_deployment: bool = True, secret_hash: bytes = None, gas_limit: int = None, - progress=None + progress=None, + contract_version: str = "latest", + ignore_deployed: bool = False, + **overrides ) -> dict: """ Deploy and publish the StakingEscrow contract @@ -503,7 +555,7 @@ class StakingEscrowDeployer(BaseContractDeployer, UpgradeableContractMixin, Owna f" deployment series for {self.contract_name}.") # Raise if not all-systems-go - self.check_deployment_readiness() + self.check_deployment_readiness(contract_version=contract_version, ignore_deployed=ignore_deployed) # Build deployment arguments origin_args = {} @@ -511,7 +563,9 @@ class StakingEscrowDeployer(BaseContractDeployer, UpgradeableContractMixin, Owna origin_args.update({'gas': gas_limit}) # 1 - Deploy # - the_escrow_contract, deploy_receipt = self._deploy_essential(gas_limit=gas_limit) + the_escrow_contract, deploy_receipt = self._deploy_essential(contract_version=contract_version, + gas_limit=gas_limit, + **overrides) # This is the end of bare deployment. if not initial_deployment: @@ -583,38 +637,42 @@ class PolicyManagerDeployer(BaseContractDeployer, UpgradeableContractMixin, Owna deployment_steps = ('deployment', 'dispatcher_deployment', 'set_policy_manager') _proxy_deployer = DispatcherDeployer - def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) proxy_name = StakingEscrowDeployer._proxy_deployer.contract_name staking_contract_name = StakingEscrowDeployer.contract_name self.staking_contract = self.blockchain.get_contract_by_name(registry=self.registry, - name=staking_contract_name, + contract_name=staking_contract_name, proxy_name=proxy_name) - def _deploy_essential(self, gas_limit: int = None) -> tuple: + def _deploy_essential(self, contract_version: str, gas_limit: int = None) -> tuple: + constructor_kwargs = {"_escrow": self.staking_contract.address} policy_manager_contract, deploy_receipt = self.blockchain.deploy_contract(self.deployer_address, self.registry, self.contract_name, - self.staking_contract.address, - gas_limit=gas_limit) + gas_limit=gas_limit, + contract_version=contract_version, + **constructor_kwargs) return policy_manager_contract, deploy_receipt def deploy(self, initial_deployment: bool = True, secret_hash: bytes = None, gas_limit: int = None, - progress=None + progress=None, + contract_version: str = "latest", + ignore_deployed: bool = False ) -> Dict[str, dict]: if initial_deployment and not secret_hash: raise ValueError(f"An upgrade secret hash is required to perform an initial" f" deployment series for {self.contract_name}.") - self.check_deployment_readiness() + self.check_deployment_readiness(contract_version=contract_version, ignore_deployed=ignore_deployed) # Creator deploys the policy manager - policy_manager_contract, deploy_receipt = self._deploy_essential(gas_limit=gas_limit) + policy_manager_contract, deploy_receipt = self._deploy_essential(contract_version=contract_version, + gas_limit=gas_limit) # This is the end of bare deployment. if not initial_deployment: @@ -718,21 +776,21 @@ class StakingInterfaceDeployer(BaseContractDeployer, UpgradeableContractMixin): token_contract_name = NucypherTokenDeployer.contract_name self.token_contract = self.blockchain.get_contract_by_name(registry=self.registry, - name=token_contract_name) + contract_name=token_contract_name) staking_contract_name = StakingEscrowDeployer.contract_name staking_proxy_name = StakingEscrowDeployer._proxy_deployer.contract_name self.staking_contract = self.blockchain.get_contract_by_name(registry=self.registry, - name=staking_contract_name, + contract_name=staking_contract_name, proxy_name=staking_proxy_name) policy_contract_name = PolicyManagerDeployer.contract_name policy_proxy_name = PolicyManagerDeployer._proxy_deployer.contract_name self.policy_contract = self.blockchain.get_contract_by_name(registry=self.registry, - name=policy_contract_name, + contract_name=policy_contract_name, proxy_name=policy_proxy_name) - def _deploy_essential(self, gas_limit: int = None): + def _deploy_essential(self, contract_version: str, gas_limit: int = None): """Note: These parameters are order-sensitive""" constructor_args = (self.token_contract.address, self.staking_contract.address, @@ -742,14 +800,17 @@ class StakingInterfaceDeployer(BaseContractDeployer, UpgradeableContractMixin): self.registry, self.contract_name, *constructor_args, - gas_limit=gas_limit) + gas_limit=gas_limit, + contract_version=contract_version) return contract, deployment_receipt def deploy(self, initial_deployment: bool = True, secret_hash: bytes = None, gas_limit: int = None, - progress=None + progress=None, + contract_version: str = "latest", + ignore_deployed: bool = False ) -> dict: """ Deploys a new StakingInterface contract, and a new StakingInterfaceRouter, targeting the former. @@ -759,9 +820,11 @@ class StakingInterfaceDeployer(BaseContractDeployer, UpgradeableContractMixin): if initial_deployment and not secret_hash: raise ValueError(f"An upgrade secret hash is required to perform an initial" f" deployment series for {self.contract_name}.") + self.check_deployment_readiness(contract_version=contract_version, ignore_deployed=ignore_deployed) # 1 - StakingInterface - staking_interface_contract, deployment_receipt = self._deploy_essential(gas_limit=gas_limit) + staking_interface_contract, deployment_receipt = self._deploy_essential(contract_version=contract_version, + gas_limit=gas_limit) # This is the end of bare deployment. if not initial_deployment: @@ -802,10 +865,10 @@ class PreallocationEscrowDeployer(BaseContractDeployer, UpgradeableContractMixin def __init__(self, allocation_registry: AllocationRegistry = None, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self.token_contract = self.blockchain.get_contract_by_name(registry=self.registry, - name=NucypherTokenDeployer.contract_name) + contract_name=NucypherTokenDeployer.contract_name) dispatcher_name = StakingEscrowDeployer._proxy_deployer.contract_name self.staking_escrow_contract = self.blockchain.get_contract_by_name(registry=self.registry, - name=StakingEscrowDeployer.contract_name, + contract_name=StakingEscrowDeployer.contract_name, proxy_name=dispatcher_name) self.__beneficiary_address = NO_BENEFICIARY self.__allocation_registry = allocation_registry or self.__allocation_registry() @@ -887,7 +950,7 @@ class PreallocationEscrowDeployer(BaseContractDeployer, UpgradeableContractMixin """Deploy a new instance of PreallocationEscrow to the blockchain.""" self.check_deployment_readiness() router_contract = self.blockchain.get_contract_by_name(registry=self.registry, - name=self._router_deployer.contract_name) + contract_name=self._router_deployer.contract_name) args = (self.deployer_address, self.registry, self.contract_name, @@ -923,32 +986,48 @@ class AdjudicatorDeployer(BaseContractDeployer, UpgradeableContractMixin, Ownabl staking_contract_name = StakingEscrowDeployer.contract_name proxy_name = StakingEscrowDeployer._proxy_deployer.contract_name self.staking_contract = self.blockchain.get_contract_by_name(registry=self.registry, - name=staking_contract_name, + contract_name=staking_contract_name, proxy_name=proxy_name) - def _deploy_essential(self, gas_limit: int = None): - constructor_args = (self.staking_contract.address, - *self.economics.slashing_deployment_parameters) + def _deploy_essential(self, contract_version: str, gas_limit: int = None, **overrides): + args = self.economics.slashing_deployment_parameters + constructor_kwargs = { + "_hashAlgorithm": args[0], + "_basePenalty": args[1], + "_penaltyHistoryCoefficient": args[2], + "_percentagePenaltyCoefficient": args[3], + "_rewardCoefficient": args[4] + } + constructor_kwargs.update(overrides) + constructor_kwargs = {k: v for k, v in constructor_kwargs.items() if v is not None} + # Force use of the escrow address from the registry + constructor_kwargs.update({"_escrow": self.staking_contract.address}) adjudicator_contract, deploy_receipt = self.blockchain.deploy_contract(self.deployer_address, self.registry, self.contract_name, - *constructor_args, - gas_limit=gas_limit) + gas_limit=gas_limit, + contract_version=contract_version, + **constructor_kwargs) return adjudicator_contract, deploy_receipt def deploy(self, initial_deployment: bool = True, secret_hash: bytes = None, gas_limit: int = None, - progress=None) -> Dict[str, str]: + progress=None, + contract_version: str = "latest", + ignore_deployed: bool = False, + **overrides) -> Dict[str, str]: if initial_deployment and not secret_hash: raise ValueError(f"An upgrade secret hash is required to perform an initial" f" deployment series for {self.contract_name}.") - self.check_deployment_readiness() + self.check_deployment_readiness(contract_version=contract_version, ignore_deployed=ignore_deployed) - adjudicator_contract, deploy_receipt = self._deploy_essential(gas_limit=gas_limit) + adjudicator_contract, deploy_receipt = self._deploy_essential(contract_version=contract_version, + gas_limit=gas_limit, + **overrides) # This is the end of bare deployment. if not initial_deployment: diff --git a/nucypher/blockchain/eth/interfaces.py b/nucypher/blockchain/eth/interfaces.py index 7f702abb3..fd35c1cec 100644 --- a/nucypher/blockchain/eth/interfaces.py +++ b/nucypher/blockchain/eth/interfaces.py @@ -37,8 +37,7 @@ from eth_tester import EthereumTester from eth_utils import to_checksum_address, is_checksum_address from twisted.logger import Logger from web3 import Web3, WebsocketProvider, HTTPProvider, IPCProvider -from web3.contract import Contract -from web3.contract import ContractConstructor +from web3.contract import ContractConstructor, Contract from web3.contract import ContractFunction from web3.exceptions import TimeExhausted from web3.exceptions import ValidationError @@ -59,11 +58,14 @@ from nucypher.blockchain.eth.providers import ( from nucypher.blockchain.eth.registry import BaseContractRegistry from nucypher.blockchain.eth.sol.compile import SolidityCompiler from nucypher.characters.control.emitters import StdoutEmitter -from nucypher.utilities.logging import console_observer, GlobalLoggerSettings Web3Providers = Union[IPCProvider, WebsocketProvider, HTTPProvider, EthereumTester] +class VersionedContract(Contract): + version = None + + class BlockchainInterface: """ Interacts with a solidity compiler and a registry in order to instantiate compiled @@ -76,7 +78,7 @@ class BlockchainInterface: process = NO_PROVIDER_PROCESS.bool_value(False) Web3 = Web3 - _contract_factory = Contract + _contract_factory = VersionedContract class InterfaceError(Exception): pass @@ -431,20 +433,21 @@ class BlockchainInterface: def get_contract_by_name(self, registry: BaseContractRegistry, - name: str, - version: int = None, + contract_name: str, + contract_version: str = None, + enrollment_version: Union[int, str] = None, proxy_name: str = None, use_proxy_address: bool = True - ) -> Union[Contract, List[tuple]]: + ) -> Union[VersionedContract, List[tuple]]: """ Instantiate a deployed contract from registry data, and assimilate it with its proxy if it is upgradeable, or return all registered records if use_proxy_address is False. """ - target_contract_records = registry.search(contract_name=name) + target_contract_records = registry.search(contract_name=contract_name, contract_version=contract_version) if not target_contract_records: - raise self.UnknownContract(f"No such contract records with name {name}.") + raise self.UnknownContract(f"No such contract records with name {contract_name}:{contract_version}.") if proxy_name: @@ -452,61 +455,69 @@ class BlockchainInterface: proxy_records = registry.search(contract_name=proxy_name) results = list() - for proxy_name, proxy_addr, proxy_abi in proxy_records: + for proxy_name, proxy_version, proxy_address, proxy_abi in proxy_records: proxy_contract = self.client.w3.eth.contract(abi=proxy_abi, - address=proxy_addr, + address=proxy_address, + version=proxy_version, ContractFactoryClass=self._contract_factory) # Read this dispatcher's target address from the blockchain proxy_live_target_address = proxy_contract.functions.target().call() - for target_name, target_addr, target_abi in target_contract_records: + for target_name, target_version, target_address, target_abi in target_contract_records: - if target_addr == proxy_live_target_address: + if target_address == proxy_live_target_address: if use_proxy_address: - pair = (proxy_addr, target_abi) + triplet = (proxy_address, target_version, target_abi) else: - pair = (target_addr, target_abi) + triplet = (target_address, target_version, target_abi) else: continue - results.append(pair) + results.append(triplet) if len(results) > 1: - address, abi = results[0] + address, _version, _abi = results[0] message = "Multiple {} deployments are targeting {}".format(proxy_name, address) - raise self.InterfaceError(message.format(name)) + raise self.InterfaceError(message.format(contract_name)) else: try: - selected_address, selected_abi = results[0] + selected_address, selected_version, selected_abi = results[0] except IndexError: - raise self.UnknownContract(f"There are no Dispatcher records targeting '{name}'") + raise self.UnknownContract( + f"There are no Dispatcher records targeting '{contract_name}':{contract_version}") else: # NOTE: 0 must be allowed as a valid version number if len(target_contract_records) != 1: - if version is None: - m = f"{len(target_contract_records)} records enrolled for contract {name} " \ + if enrollment_version is None: + m = f"{len(target_contract_records)} records enrolled " \ + f"for contract {contract_name}:{contract_version} " \ f"and no version index was supplied." raise self.InterfaceError(m) - version = self.__get_version_index(name=name, - version_index=version, - enrollments=len(target_contract_records)) + enrollment_version = self.__get_enrollment_version_index(name=contract_name, + contract_version=contract_version, + version_index=enrollment_version, + enrollments=len(target_contract_records)) else: - version = -1 # default + enrollment_version = -1 # default - _target_contract_name, selected_address, selected_abi = target_contract_records[version] + _contract_name, selected_version, selected_address, selected_abi = target_contract_records[enrollment_version] # Create the contract from selected sources unified_contract = self.client.w3.eth.contract(abi=selected_abi, address=selected_address, + version=selected_version, ContractFactoryClass=self._contract_factory) return unified_contract @staticmethod - def __get_version_index(version_index: Union[int, str], enrollments: int, name: str): + def __get_enrollment_version_index(version_index: Union[int, str], + enrollments: int, + name: str, + contract_version: str): version_names = {'latest': -1, 'earliest': 0} try: version = version_names[version_index] @@ -515,10 +526,11 @@ class BlockchainInterface: version = int(version_index) except ValueError: what_is_this = version_index - raise ValueError(f"'{what_is_this}' is not a valid version number") + raise ValueError(f"'{what_is_this}' is not a valid enrollment version number") else: if version > enrollments - 1: - message = f"Version index '{version}' is larger than the number of enrollments for {name}." + message = f"Version index '{version}' is larger than the number of enrollments " \ + f"for {name}:{contract_version}." raise ValueError(message) return version @@ -526,7 +538,7 @@ class BlockchainInterface: class BlockchainDeployerInterface(BlockchainInterface): TIMEOUT = 600 # seconds - _contract_factory = Contract + _contract_factory = VersionedContract class NoDeployerAddress(RuntimeError): pass @@ -544,18 +556,13 @@ class BlockchainDeployerInterface(BlockchainInterface): return self.is_connected def _setup_solidity(self, compiler: SolidityCompiler = None): - - # if a SolidityCompiler class instance was passed, - # compile from solidity source code. - self.__sol_compiler = compiler if compiler: # Execute the compilation if we're recompiling # Otherwise read compiled contract data from the registry. - interfaces = self.__sol_compiler.compile() - __raw_contract_cache = interfaces + _raw_contract_cache = compiler.compile() else: - __raw_contract_cache = NO_COMPILATION_PERFORMED - self.__raw_contract_cache = __raw_contract_cache + _raw_contract_cache = NO_COMPILATION_PERFORMED + self._raw_contract_cache = _raw_contract_cache @validate_checksum_address def deploy_contract(self, @@ -565,8 +572,9 @@ class BlockchainDeployerInterface(BlockchainInterface): *constructor_args, enroll: bool = True, gas_limit: int = None, + contract_version: str = 'latest', **constructor_kwargs - ) -> Tuple[Contract, dict]: + ) -> Tuple[VersionedContract, dict]: """ Retrieve compiled interface data from the cache and return an instantiated deployed contract @@ -585,11 +593,12 @@ class BlockchainDeployerInterface(BlockchainInterface): pprint_args = str(tuple(constructor_args)) pprint_args = pprint_args.replace("{", "{{").replace("}", "}}") # See #724 - self.log.info(f"Deploying contract {contract_name} with " + + contract_factory = self.get_contract_factory(contract_name=contract_name, version=contract_version) + self.log.info(f"Deploying contract {contract_name}:{contract_factory.version} with " f"deployer address {deployer_address} " f"and parameters {pprint_args}") - contract_factory = self.get_contract_factory(contract_name=contract_name) transaction_function = contract_factory.constructor(*constructor_args, **constructor_kwargs) # @@ -606,39 +615,74 @@ class BlockchainDeployerInterface(BlockchainInterface): # Success address = receipt['contractAddress'] - self.log.info(f"Confirmed {contract_name} deployment: new address {address}") + self.log.info(f"Confirmed {contract_name}:{contract_factory.version} deployment: new address {address}") # # Instantiate & Enroll contract # - contract = self.client.w3.eth.contract(address=address, abi=contract_factory.abi) + contract = self.client.w3.eth.contract(address=address, + abi=contract_factory.abi, + version=contract_factory.version, + ContractFactoryClass=self._contract_factory) if enroll is True: registry.enroll(contract_name=contract_name, contract_address=contract.address, - contract_abi=contract_factory.abi) + contract_abi=contract.abi, + contract_version=contract.version) return contract, receipt # receipt - def get_contract_factory(self, contract_name: str) -> Contract: - """Retrieve compiled interface data from the cache and return web3 contract""" + def find_raw_contract_data(self, contract_name: str, requested_version: str = 'latest') -> Tuple[str, dict]: try: - interface = self.__raw_contract_cache[contract_name] + contract_data = self._raw_contract_cache[contract_name] except KeyError: raise self.UnknownContract('{} is not a locally compiled contract.'.format(contract_name)) except TypeError: - if self.__raw_contract_cache is NO_COMPILATION_PERFORMED: + if self._raw_contract_cache is NO_COMPILATION_PERFORMED: message = "The local contract compiler cache is empty because no compilation was performed." raise self.InterfaceError(message) raise - else: - contract = self.client.w3.eth.contract(abi=interface['abi'], - bytecode=interface['bin'], - ContractFactoryClass=Contract) - return contract - def _wrap_contract(self, wrapper_contract: Contract, target_contract: Contract) -> Contract: + try: + 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())) + + if len(contract_data.keys()) == 1: + return next(iter(contract_data.items())) + + # Get the latest or the earliest versions + current_version_parsed = (-1, -1, -1) + current_version = None + current_data = None + for version, data in contract_data.items(): + major, minor, patch = [int(v) for v in version[1:].split(".", 3)] + if current_version_parsed[0] == -1 or \ + requested_version == 'latest' and (major, minor, patch) > current_version_parsed or \ + requested_version == 'earliest' and (major, minor, patch) < current_version_parsed: + current_version_parsed = (major, minor, patch) + current_data = data + current_version = version + return current_version, current_data + + def get_contract_factory(self, contract_name: str, version: str = 'latest') -> VersionedContract: + """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'], + version=version, + ContractFactoryClass=self._contract_factory) + return contract + + def _wrap_contract(self, + wrapper_contract: VersionedContract, + target_contract: VersionedContract + ) -> VersionedContract: """ Used for upgradeable contracts; Returns a new contract object assembled with its own address but the abi of the other. @@ -647,6 +691,7 @@ class BlockchainDeployerInterface(BlockchainInterface): # Wrap the contract wrapped_contract = self.client.w3.eth.contract(abi=target_contract.abi, address=wrapper_contract.address, + version=target_contract.version, ContractFactoryClass=self._contract_factory) return wrapped_contract @@ -654,15 +699,16 @@ class BlockchainDeployerInterface(BlockchainInterface): def get_proxy_contract(self, registry: BaseContractRegistry, target_address: str, - proxy_name: str) -> Contract: + proxy_name: str) -> VersionedContract: # Lookup proxies; Search for a registered proxy that targets this contract record records = registry.search(contract_name=proxy_name) dispatchers = list() - for name, addr, abi in records: + for name, version, address, abi in records: proxy_contract = self.client.w3.eth.contract(abi=abi, - address=addr, + address=address, + version=version, ContractFactoryClass=self._contract_factory) # Read this dispatchers target address from the blockchain diff --git a/nucypher/blockchain/eth/registry.py b/nucypher/blockchain/eth/registry.py index 7e42b2f9b..8366b9bec 100644 --- a/nucypher/blockchain/eth/registry.py +++ b/nucypher/blockchain/eth/registry.py @@ -282,7 +282,7 @@ class BaseContractRegistry(ABC): entries = iter(record[1] for record in self.read()) return entries - def enroll(self, contract_name, contract_address, contract_abi) -> None: + def enroll(self, contract_name, contract_address, contract_abi, contract_version) -> None: """ Enrolls a contract to the chain registry by writing the name, address, and abi information to the filesystem as JSON. @@ -290,32 +290,42 @@ class BaseContractRegistry(ABC): Note: Unless you are developing NuCypher, you most likely won't ever need to use this. """ - contract_data = [contract_name, contract_address, contract_abi] + contract_data = [contract_name, contract_version, contract_address, contract_abi] try: registry_data = self.read() except self.RegistryError: - self.log.info("Blank registry encountered: enrolling {}:{}".format(contract_name, contract_address)) + self.log.info("Blank registry encountered: enrolling {}:{}:{}" + .format(contract_name, contract_version, contract_address)) registry_data = list() # empty registry registry_data.append(contract_data) self.write(registry_data) - self.log.info("Enrolled {}:{} into registry.".format(contract_name, contract_address)) + self.log.info("Enrolled {}:{}:{} into registry.".format(contract_name, contract_version, contract_address)) - def search(self, contract_name: str = None, contract_address: str = None) -> tuple: + def search(self, contract_name: str = None, contract_version: str = None, contract_address: str = None) -> tuple: """ Searches the registry for a contract with the provided name or address and returns the contracts component data. """ if not (bool(contract_name) ^ bool(contract_address)): raise ValueError("Pass contract_name or contract_address, not both.") + if bool(contract_version) and not bool(contract_name): + raise ValueError("Pass contract_version together with contract_name.") contracts = list() registry_data = self.read() try: - for name, addr, abi in registry_data: - if contract_name == name or contract_address == addr: - contracts.append((name, addr, abi)) + for contract in registry_data: + if len(contract) == 3: + name, address, abi = contract + version = None + else: + name, version, address, abi = contract + if contract_name == name and \ + (contract_version is None or version == contract_version) or \ + contract_address == address: + contracts.append((name, version, address, abi)) except ValueError: message = "Missing or corrupted registry data" self.log.critical(message) diff --git a/nucypher/blockchain/eth/sol/compile.py b/nucypher/blockchain/eth/sol/compile.py index 4f92dcd20..9e9a545e6 100644 --- a/nucypher/blockchain/eth/sol/compile.py +++ b/nucypher/blockchain/eth/sol/compile.py @@ -14,9 +14,10 @@ GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with nucypher. If not, see . """ - - +import collections import os +import re +from typing import List, Set, Tuple import sys from twisted.logger import Logger @@ -32,10 +33,16 @@ except ImportError: # TODO: Issue #461 and #758 & PR #1480 - Include precompiled ABI; Do not use py-solc in standard installation pass +SourceDirs = collections.namedtuple('SourceDirs', ['root_source_dir', # type: str + 'other_source_dirs', # type: Set[str] + ]) +SourceDirs.__new__.__defaults__ = (None,) + class SolidityCompiler: - __default_version = 'v0.5.9' + __default_compiler_version = 'v0.5.9' + __default_contract_version = 'v0.0.0' __default_configuration_path = os.path.join(dirname(abspath(__file__)), './compiler.json') __default_sol_binary_path = shutil.which('solc') @@ -43,24 +50,35 @@ class SolidityCompiler: __bin_path = os.path.dirname(sys.executable) # type: str __default_sol_binary_path = os.path.join(__bin_path, 'solc') # type: str - __default_contract_dir = os.path.join(dirname(abspath(__file__)), 'source', 'contracts') + __default_contract_dir = os.path.join(dirname(abspath(__file__)), 'source') __default_chain_name = 'tester' + __compiled_contracts_dir = 'contracts' + __zeppelin_library_dir = 'zeppelin' + optimization_runs = 200 + class CompilerError(Exception): + pass + + @classmethod + def default_contract_dir(cls): + return cls.__default_contract_dir + def __init__(self, solc_binary_path: str = None, configuration_path: str = None, chain_name: str = None, - source_dir: str = None, - test_contract_dir: str = None + source_dirs: List[SourceDirs] = None ) -> None: - + self.log = Logger('solidity-compiler') # Compiler binary and root solidity source code directory self.__sol_binary_path = solc_binary_path if solc_binary_path is not None else self.__default_sol_binary_path - self.source_dir = source_dir if source_dir is not None else self.__default_contract_dir - self._test_solidity_source_dir = test_contract_dir + 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 # JSON config self.__configuration_path = configuration_path if configuration_path is not None else self.__default_configuration_path @@ -74,20 +92,58 @@ class SolidityCompiler: Installs the specified solidity compiler version. https://github.com/ethereum/py-solc#installing-the-solc-binary """ - version = version if version is not None else self.__default_version + version = version if version is not None else self.__default_compiler_version return install_solc(version, platform=None) # TODO: #1478 - Implement or remove this 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""" self.log.info("Using solidity compiler binary at {}".format(self.__sol_binary_path)) - self.log.info("Compiling solidity source files at {}".format(self.source_dir)) + 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=self.source_dir, topdown=True) - if self._test_solidity_source_dir: - test_source_walker = os.walk(top=self._test_solidity_source_dir, topdown=True) - source_walker = itertools.chain(source_walker, test_source_walker) + 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: @@ -97,10 +153,10 @@ class SolidityCompiler: self.log.debug("Collecting solidity source {}".format(path)) # Compile with remappings: https://github.com/ethereum/py-solc - project_root = dirname(self.source_dir) + zeppelin_dir = os.path.join(root_source_dir, self.__zeppelin_library_dir) - remappings = ("contracts={}".format(self.source_dir), - "zeppelin={}".format(os.path.join(project_root, 'zeppelin')), + remappings = ("contracts={}".format(contracts_dir), + "zeppelin={}".format(zeppelin_dir), ) self.log.info("Compiling with import remappings {}".format(", ".join(remappings))) @@ -109,7 +165,7 @@ class SolidityCompiler: try: compiled_sol = compile_files(source_files=source_paths, import_remappings=remappings, - allow_paths=project_root, + allow_paths=root_source_dir, optimize=True, optimize_runs=optimization_runs) diff --git a/nucypher/blockchain/eth/sol/source/contracts/Adjudicator.sol b/nucypher/blockchain/eth/sol/source/contracts/Adjudicator.sol index 6208e6bb9..de1a16228 100644 --- a/nucypher/blockchain/eth/sol/source/contracts/Adjudicator.sol +++ b/nucypher/blockchain/eth/sol/source/contracts/Adjudicator.sol @@ -10,7 +10,8 @@ import "zeppelin/math/Math.sol"; /** * @notice Supervises stakers' behavior and punishes when something's wrong. -**/ +* @dev |v1.1.1| +*/ contract Adjudicator is Upgradeable { using SafeMath for uint256; @@ -47,7 +48,7 @@ contract Adjudicator is Upgradeable { * @param _penaltyHistoryCoefficient Coefficient for calculating the penalty depending on the history * @param _percentagePenaltyCoefficient Coefficient for calculating the percentage penalty * @param _rewardCoefficient Coefficient for calculating the reward - **/ + */ constructor( StakingEscrow _escrow, SignatureVerifier.HashAlgorithm _hashAlgorithm, @@ -81,7 +82,7 @@ contract Adjudicator is Upgradeable { * @param _workerPublicKey Worker's signing public key, also known as "stamp" * @param _workerIdentityEvidence Signature of worker's public key by worker's eth-key * @param _preComputedData Additional pre-computed data for CFrag correctness verification - **/ + */ function evaluateCFrag( bytes memory _capsuleBytes, bytes memory _cFragBytes, @@ -174,7 +175,7 @@ contract Adjudicator is Upgradeable { * @notice Calculate penalty to the staker and reward to the investigator * @param _staker Staker's address * @param _stakerValue Amount of tokens that belong to the staker - **/ + */ function calculatePenaltyAndReward(address _staker, uint256 _stakerValue) internal returns (uint256 penalty, uint256 reward) { diff --git a/nucypher/blockchain/eth/sol/source/contracts/Issuer.sol b/nucypher/blockchain/eth/sol/source/contracts/Issuer.sol index dc8a3c17f..7fd2f7a5f 100644 --- a/nucypher/blockchain/eth/sol/source/contracts/Issuer.sol +++ b/nucypher/blockchain/eth/sol/source/contracts/Issuer.sol @@ -10,7 +10,8 @@ import "contracts/lib/AdditionalMath.sol"; /** * @notice Contract for calculate issued tokens -**/ +* @dev |v1.1.2| +*/ contract Issuer is Upgradeable { using SafeMath for uint256; using AdditionalMath for uint32; @@ -32,7 +33,7 @@ contract Issuer is Upgradeable { * supply for previous period (used in formula) and supply for current period which accumulates value * before end of period. There is no order between them because of storage savings. * So each time should check values of both variables. - **/ + */ uint256 public currentSupply1; uint256 public currentSupply2; @@ -46,7 +47,7 @@ contract Issuer is Upgradeable { * @param _miningCoefficient Mining coefficient (k2) * @param _lockedPeriodsCoefficient Locked periods coefficient (k1) * @param _rewardedPeriods Max periods that will be additionally rewarded - **/ + */ constructor( NuCypherToken _token, uint32 _hoursPerPeriod, @@ -79,7 +80,7 @@ contract Issuer is Upgradeable { /** * @dev Checks contract initialization - **/ + */ modifier isInitialized() { require(currentSupply1 != 0); @@ -88,14 +89,14 @@ contract Issuer is Upgradeable { /** * @return Number of current period - **/ + */ function getCurrentPeriod() public view returns (uint16) { return uint16(block.timestamp / secondsPerPeriod); } /** * @notice Initialize reserved tokens for reward - **/ + */ function initialize() public onlyOwner { require(currentSupply1 == 0); currentMintingPeriod = getCurrentPeriod(); @@ -162,7 +163,7 @@ contract Issuer is Upgradeable { /** * @notice Return tokens for future minting * @param _amount Amount of tokens - **/ + */ function unMint(uint256 _amount) internal { currentSupply1 = currentSupply1 - _amount; currentSupply2 = currentSupply2 - _amount; @@ -170,7 +171,7 @@ contract Issuer is Upgradeable { /** * @notice Returns the number of tokens that can be mined - **/ + */ function getReservedReward() public view returns (uint256) { return totalSupply - Math.max(currentSupply1, currentSupply2); } diff --git a/nucypher/blockchain/eth/sol/source/contracts/MultiSig.sol b/nucypher/blockchain/eth/sol/source/contracts/MultiSig.sol index 89e7e2737..6354a6b5e 100644 --- a/nucypher/blockchain/eth/sol/source/contracts/MultiSig.sol +++ b/nucypher/blockchain/eth/sol/source/contracts/MultiSig.sol @@ -6,7 +6,7 @@ import "zeppelin/math/SafeMath.sol"; /** * @notice Multi-signature contract with off-chain signing -**/ +*/ contract MultiSig { using SafeMath for uint256; @@ -33,7 +33,7 @@ contract MultiSig { /** * @param _required Number of required signings * @param _owners List of initial owners. - **/ + */ constructor (uint8 _required, address[] memory _owners) public { require(_owners.length <= MAX_OWNER_COUNT && _required <= _owners.length && @@ -56,7 +56,7 @@ contract MultiSig { * @param _value Amount of ETH to transfer * @param _data Call data * @param _nonce Nonce - **/ + */ function getUnsignedTransactionHash( address _sender, address _destination, @@ -78,7 +78,7 @@ contract MultiSig { * @param _destination Destination address * @param _value Amount of ETH to transfer * @param _data Call data - **/ + */ function execute( uint8[] calldata _sigV, bytes32[] calldata _sigR, @@ -111,7 +111,7 @@ contract MultiSig { * @notice Allows to add a new owner * @dev Transaction has to be sent by `execute` method. * @param _owner Address of new owner - **/ + */ function addOwner(address _owner) public onlyThisContract @@ -128,7 +128,7 @@ contract MultiSig { * @notice Allows to remove an owner * @dev Transaction has to be sent by `execute` method. * @param _owner Address of owner - **/ + */ function removeOwner(address _owner) public onlyThisContract @@ -149,7 +149,7 @@ contract MultiSig { * @notice Allows to change the number of required signings * @dev Transaction has to be sent by `execute` method * @param _required Number of required signings - **/ + */ function changeRequirement(uint8 _required) public onlyThisContract diff --git a/nucypher/blockchain/eth/sol/source/contracts/NuCypherToken.sol b/nucypher/blockchain/eth/sol/source/contracts/NuCypherToken.sol index 612ad4737..9f25f1f77 100644 --- a/nucypher/blockchain/eth/sol/source/contracts/NuCypherToken.sol +++ b/nucypher/blockchain/eth/sol/source/contracts/NuCypherToken.sol @@ -9,13 +9,13 @@ import "zeppelin/token/ERC20/ERC20Detailed.sol"; * @title NuCypher token * @notice ERC20 token * @dev Optional approveAndCall() functionality to notify a contract if an approve() has occurred. -**/ +*/ contract NuCypherToken is ERC20, ERC20Detailed('NuCypher', 'NU', 18) { /** * @notice Set amount of tokens * @param _totalSupply Total number of tokens - **/ + */ constructor (uint256 _totalSupply) public { _mint(msg.sender, _totalSupply); } @@ -25,7 +25,7 @@ contract NuCypherToken is ERC20, ERC20Detailed('NuCypher', 'NU', 18) { * * @dev call the receiveApproval function on the contract you want to be notified. * receiveApproval(address _from, uint256 _value, address _tokenContract, bytes _extraData) - **/ + */ function approveAndCall(address _spender, uint256 _value, bytes memory _extraData) public returns (bool success) { @@ -39,7 +39,7 @@ contract NuCypherToken is ERC20, ERC20Detailed('NuCypher', 'NU', 18) { /** * @dev Interface to use the receiveApproval method -**/ +*/ contract TokenRecipient { /** @@ -48,7 +48,7 @@ contract TokenRecipient { * @param _value The amount of tokens to be spent * @param _tokenContract Address of the token contract * @param _extraData Extra data - **/ + */ function receiveApproval(address _from, uint256 _value, address _tokenContract, bytes calldata _extraData) external; } diff --git a/nucypher/blockchain/eth/sol/source/contracts/PolicyManager.sol b/nucypher/blockchain/eth/sol/source/contracts/PolicyManager.sol index 84ffe8404..79d23cb14 100644 --- a/nucypher/blockchain/eth/sol/source/contracts/PolicyManager.sol +++ b/nucypher/blockchain/eth/sol/source/contracts/PolicyManager.sol @@ -13,7 +13,8 @@ import "contracts/proxy/Upgradeable.sol"; /** * @notice Contract holds policy data and locks fees -**/ +* @dev |v1.1.2| +*/ contract PolicyManager is Upgradeable { using SafeERC20 for NuCypherToken; using SafeMath for uint256; @@ -92,7 +93,7 @@ contract PolicyManager is Upgradeable { /** * @notice Constructor sets address of the escrow contract * @param _escrow Escrow contract - **/ + */ constructor(StakingEscrow _escrow) public { // if the input address is not the StakingEscrow then calling `secondsPerPeriod` will throw error secondsPerPeriod = _escrow.secondsPerPeriod(); @@ -102,7 +103,7 @@ contract PolicyManager is Upgradeable { /** * @dev Checks that sender is the StakingEscrow contract - **/ + */ modifier onlyEscrowContract() { require(msg.sender == address(escrow)); @@ -111,7 +112,7 @@ contract PolicyManager is Upgradeable { /** * @return Number of current period - **/ + */ function getCurrentPeriod() public view returns (uint16) { return uint16(block.timestamp / secondsPerPeriod); } @@ -120,7 +121,7 @@ contract PolicyManager is Upgradeable { * @notice Register a node * @param _node Node address * @param _period Initial period - **/ + */ function register(address _node, uint16 _period) external onlyEscrowContract { NodeInfo storage nodeInfo = nodes[_node]; require(nodeInfo.lastMinedPeriod == 0); @@ -129,7 +130,7 @@ contract PolicyManager is Upgradeable { /** * @notice Set the minimum reward that the node will take - **/ + */ function setMinRewardRate(uint256 _minRewardRate) public { NodeInfo storage node = nodes[msg.sender]; node.minRewardRate = _minRewardRate; @@ -143,7 +144,7 @@ contract PolicyManager is Upgradeable { * @param _numberOfPeriods Duration of the policy in periods except first period * @param _firstPartialReward Partial reward for first/current period * @param _nodes Nodes that will handle policy - **/ + */ function createPolicy( bytes16 _policyId, uint16 _numberOfPeriods, @@ -189,7 +190,7 @@ contract PolicyManager is Upgradeable { * @notice Update node reward * @param _node Node address * @param _period Processed period - **/ + */ function updateReward(address _node, uint16 _period) external onlyEscrowContract { NodeInfo storage node = nodes[_node]; if (node.lastMinedPeriod == 0 || _period <= node.lastMinedPeriod) { @@ -204,7 +205,7 @@ contract PolicyManager is Upgradeable { /** * @notice Withdraw reward by node - **/ + */ function withdraw() public returns (uint256) { return withdraw(msg.sender); } @@ -212,7 +213,7 @@ contract PolicyManager is Upgradeable { /** * @notice Withdraw reward by node * @param _recipient Recipient of the reward - **/ + */ function withdraw(address payable _recipient) public returns (uint256) { NodeInfo storage node = nodes[msg.sender]; uint256 reward = node.reward; @@ -227,7 +228,7 @@ contract PolicyManager is Upgradeable { * @notice Calculate amount of refund * @param _policy Policy * @param _arrangement Arrangement - **/ + */ function calculateRefundValue(Policy storage _policy, ArrangementInfo storage _arrangement) internal view returns (uint256 refundValue, uint256 indexOfDowntimePeriods, uint16 lastRefundedPeriod) { @@ -288,7 +289,7 @@ contract PolicyManager is Upgradeable { * @param _policyId Policy id * @param _node Node that will be excluded or RESERVED_NODE if full policy should be used ( @param _forceRevoke Force revoke arrangement/policy - **/ + */ function refundInternal(bytes16 _policyId, address _node, bool _forceRevoke) internal returns (uint256 refundValue) { @@ -348,7 +349,7 @@ contract PolicyManager is Upgradeable { * @notice Calculate amount of refund * @param _policyId Policy id * @param _node Node or RESERVED_NODE if all nodes should be used - **/ + */ function calculateRefundValueInternal(bytes16 _policyId, address _node) internal view returns (uint256 refundValue) { @@ -375,7 +376,7 @@ contract PolicyManager is Upgradeable { /** * @notice Revoke policy by client * @param _policyId Policy id - **/ + */ function revokePolicy(bytes16 _policyId) public { refundInternal(_policyId, RESERVED_NODE, true); } @@ -384,7 +385,7 @@ contract PolicyManager is Upgradeable { * @notice Revoke arrangement by client * @param _policyId Policy id * @param _node Node that will be excluded - **/ + */ function revokeArrangement(bytes16 _policyId, address _node) public returns (uint256 refundValue) { @@ -395,7 +396,7 @@ contract PolicyManager is Upgradeable { /** * @notice Refund part of fee by client * @param _policyId Policy id - **/ + */ function refund(bytes16 _policyId) public { refundInternal(_policyId, RESERVED_NODE, false); } @@ -404,7 +405,7 @@ contract PolicyManager is Upgradeable { * @notice Refund part of one node's fee by client * @param _policyId Policy id * @param _node Node address - **/ + */ function refund(bytes16 _policyId, address _node) public returns (uint256 refundValue) { @@ -415,7 +416,7 @@ contract PolicyManager is Upgradeable { /** * @notice Calculate amount of refund * @param _policyId Policy id - **/ + */ function calculateRefundValue(bytes16 _policyId) external view returns (uint256 refundValue) { @@ -426,7 +427,7 @@ contract PolicyManager is Upgradeable { * @notice Calculate amount of refund * @param _policyId Policy id * @param _node Node - **/ + */ function calculateRefundValue(bytes16 _policyId, address _node) external view returns (uint256 refundValue) { @@ -437,7 +438,7 @@ contract PolicyManager is Upgradeable { /** * @notice Get number of arrangements in the policy * @param _policyId Policy id - **/ + */ function getArrangementsLength(bytes16 _policyId) public view returns (uint256) { @@ -448,7 +449,7 @@ contract PolicyManager is Upgradeable { * @notice Get information about node reward * @param _node Address of node * @param _period Period to get reward delta - **/ + */ function getNodeRewardDelta(address _node, uint16 _period) public view returns (int256) { @@ -457,7 +458,7 @@ contract PolicyManager is Upgradeable { /** * @notice Return the information about arrangement - **/ + */ function getArrangementInfo(bytes16 _policyId, uint256 _index) // TODO change to structure when ABIEncoderV2 is released (#1501) // public view returns (ArrangementInfo) @@ -472,7 +473,7 @@ contract PolicyManager is Upgradeable { /** * @dev Get Policy structure by delegatecall - **/ + */ function delegateGetPolicy(address _target, bytes16 _policyId) internal returns (Policy memory result) { @@ -484,7 +485,7 @@ contract PolicyManager is Upgradeable { /** * @dev Get ArrangementInfo structure by delegatecall - **/ + */ function delegateGetArrangementInfo(address _target, bytes16 _policyId, uint256 _index) internal returns (ArrangementInfo memory result) { @@ -497,7 +498,7 @@ contract PolicyManager is Upgradeable { /** * @dev Get NodeInfo structure by delegatecall - **/ + */ function delegateGetNodeInfo(address _target, address _node) internal returns (NodeInfo memory result) { diff --git a/nucypher/blockchain/eth/sol/source/contracts/Seeder.sol b/nucypher/blockchain/eth/sol/source/contracts/Seeder.sol index 68f1bbb76..a073e150c 100644 --- a/nucypher/blockchain/eth/sol/source/contracts/Seeder.sol +++ b/nucypher/blockchain/eth/sol/source/contracts/Seeder.sol @@ -7,7 +7,7 @@ import "zeppelin/ownership/Ownable.sol"; /** * @notice Contract holds references to seed * node interface information for bootstrapping the network. -**/ +*/ contract Seeder is Ownable { struct SeedInfo { @@ -21,14 +21,14 @@ contract Seeder is Ownable { /** * @param _maxSeeds The quantity of maximum seed nodes the contract can store - **/ + */ constructor(uint256 _maxSeeds) public { seedArray = new address[](_maxSeeds); } /** * @notice Returns the length of the seed nodes array - **/ + */ function getSeedArrayLength() public view returns (uint256) { @@ -39,7 +39,7 @@ contract Seeder is Ownable { * @notice Write a new seed address and interface info to contract storage * @param _ip IPv4 address of the seed node * @param _port TCP port of the seed node - **/ + */ function enroll(address _seed, string memory _ip, uint16 _port) public onlyOwner { seeds[_seed] = SeedInfo(_ip, _port); @@ -61,7 +61,7 @@ contract Seeder is Ownable { * @notice Seed updates itself. * @param _ip Updated IPv4 address of the existing seed node * @param _port Updated TCP port of the existing seed node - **/ + */ function refresh(string memory _ip, uint16 _port) public { SeedInfo storage seed = seeds[msg.sender]; require(seed.port != 0); diff --git a/nucypher/blockchain/eth/sol/source/contracts/StakingEscrow.sol b/nucypher/blockchain/eth/sol/source/contracts/StakingEscrow.sol index 82bc69970..2b51b9e72 100644 --- a/nucypher/blockchain/eth/sol/source/contracts/StakingEscrow.sol +++ b/nucypher/blockchain/eth/sol/source/contracts/StakingEscrow.sol @@ -7,7 +7,7 @@ import "contracts/Issuer.sol"; /** * @notice PolicyManager interface -**/ +*/ contract PolicyManagerInterface { function register(address _node, uint16 _period) external; function updateReward(address _node, uint16 _period) external; @@ -17,7 +17,7 @@ contract PolicyManagerInterface { /** * @notice Adjudicator interface -**/ +*/ contract AdjudicatorInterface { function escrow() public view returns (address); } @@ -25,7 +25,7 @@ contract AdjudicatorInterface { /** * @notice WorkLock interface -**/ +*/ contract WorkLockInterface { function escrow() public view returns (address); } @@ -34,7 +34,8 @@ contract WorkLockInterface { /** * @notice Contract holds and locks stakers tokens. * Each staker that locks their tokens will receive some compensation -**/ +* @dev |v1.4.1| +*/ contract StakingEscrow is Issuer { using SafeERC20 for NuCypherToken; using AdditionalMath for uint256; @@ -129,7 +130,7 @@ contract StakingEscrow is Issuer { * @param _minAllowableLockedTokens Min amount of tokens that can be locked * @param _maxAllowableLockedTokens Max amount of tokens that can be locked * @param _minWorkerPeriods Min amount of periods while a worker can't be changed - **/ + */ constructor( NuCypherToken _token, uint32 _hoursPerPeriod, @@ -160,7 +161,7 @@ contract StakingEscrow is Issuer { /** * @dev Checks the existence of a staker in the contract - **/ + */ modifier onlyStaker() { require(stakerInfo[msg.sender].value > 0); @@ -170,7 +171,7 @@ contract StakingEscrow is Issuer { //------------------------Initialization------------------------ /** * @notice Set policy manager address - **/ + */ function setPolicyManager(PolicyManagerInterface _policyManager) external onlyOwner { // Policy manager can be set only once require(address(policyManager) == address(0)); @@ -181,7 +182,7 @@ contract StakingEscrow is Issuer { /** * @notice Set adjudicator address - **/ + */ function setAdjudicator(AdjudicatorInterface _adjudicator) external onlyOwner { // Adjudicator can be set only once require(address(adjudicator) == address(0)); @@ -192,7 +193,7 @@ contract StakingEscrow is Issuer { /** * @notice Set worklock address - **/ + */ function setWorkLock(WorkLockInterface _workLock) external onlyOwner { // WorkLock can be set only once require(address(workLock) == address(0)); @@ -204,7 +205,7 @@ contract StakingEscrow is Issuer { //------------------------Main getters------------------------ /** * @notice Get all tokens belonging to the staker - **/ + */ function getAllTokens(address _staker) external view returns (uint256) { return stakerInfo[_staker].value; } @@ -213,7 +214,7 @@ contract StakingEscrow is Issuer { * @notice Get the start period. Use in the calculation of the last period of the sub stake * @param _info Staker structure * @param _currentPeriod Current period - **/ + */ function getStartPeriod(StakerInfo storage _info, uint16 _currentPeriod) internal view returns (uint16) { @@ -228,7 +229,7 @@ contract StakingEscrow is Issuer { * @notice Get the last period of the sub stake * @param _subStake Sub stake structure * @param _startPeriod Pre-calculated start period - **/ + */ function getLastPeriodOfSubStake(SubStakeInfo storage _subStake, uint16 _startPeriod) internal view returns (uint16) { @@ -246,7 +247,7 @@ contract StakingEscrow is Issuer { * @notice Get the last period of the sub stake * @param _staker Staker * @param _index Stake index - **/ + */ function getLastPeriodOfSubStake(address _staker, uint256 _index) external view returns (uint16) { @@ -263,7 +264,7 @@ contract StakingEscrow is Issuer { * @param _info Staker structure * @param _currentPeriod Current period * @param _period Next period - **/ + */ function getLockedTokens(StakerInfo storage _info, uint16 _currentPeriod, uint16 _period) internal view returns (uint256 lockedValue) { @@ -282,7 +283,7 @@ contract StakingEscrow is Issuer { * @dev This function is used by PreallocationEscrow so its signature can't be updated. * @param _staker Staker * @param _periods Amount of periods that will be added to the current period - **/ + */ function getLockedTokens(address _staker, uint16 _periods) external view returns (uint256 lockedValue) { @@ -297,7 +298,7 @@ contract StakingEscrow is Issuer { * @dev Information may be incorrect for mined or unconfirmed surpassed period * @param _staker Staker * @param _periods Amount of periods that will be subtracted from the current period - **/ + */ function getLockedTokensInPast(address _staker, uint16 _periods) external view returns (uint256 lockedValue) { @@ -310,7 +311,7 @@ contract StakingEscrow is Issuer { /** * @notice Get the last active staker's period * @param _staker Staker - **/ + */ function getLastActivePeriod(address _staker) public view returns (uint16) { StakerInfo storage info = stakerInfo[_staker]; if (info.confirmedPeriod1 != EMPTY_CONFIRMED_PERIOD || @@ -328,7 +329,7 @@ contract StakingEscrow is Issuer { * @param _maxStakers Max stakers for looking, if set 0 then all will be used * @return allLockedTokens Sum of locked tokens for active stakers * @return activeStakers Array of stakers and their locked tokens. Stakers addresses stored as uint256 - **/ + */ function getActiveStakers(uint16 _periods, uint256 _startIndex, uint256 _maxStakers) external view returns (uint256 allLockedTokens, uint256[2][] memory activeStakers) { @@ -367,14 +368,14 @@ contract StakingEscrow is Issuer { /** * @notice Checks if `reStake` parameter is available for changing * @param _staker Staker - **/ + */ function isReStakeLocked(address _staker) public view returns (bool) { return getCurrentPeriod() < stakerInfo[_staker].lockReStakeUntilPeriod; } /** * @notice Get worker using staker's address - **/ + */ function getWorkerFromStaker(address _staker) public view returns (address) { StakerInfo storage info = stakerInfo[_staker]; // specified address is not a staker @@ -386,14 +387,14 @@ contract StakingEscrow is Issuer { /** * @notice Get staker using worker's address - **/ + */ function getStakerFromWorker(address _worker) public view returns (address) { return workerToStaker[_worker]; } /** * @notice Get work that completed by the staker - **/ + */ function getCompletedWork(address _staker) public view returns (uint256) { return stakerInfo[_staker].completedWork; } @@ -403,7 +404,7 @@ contract StakingEscrow is Issuer { * @dev If specified period is outside all downtime periods, the length of the array will be returned * @param _staker Staker * @param _period Specified period number - **/ + */ function findIndexOfPastDowntime(address _staker, uint16 _period) external view returns (uint256 index) { StakerInfo storage info = stakerInfo[_staker]; for (index = 0; index < info.pastDowntime.length; index++) { @@ -419,7 +420,7 @@ contract StakingEscrow is Issuer { * @param _staker Staker * @param _measureWork Value for `measureWork` parameter * @return Work that was previously done - **/ + */ function setWorkMeasurement(address _staker, bool _measureWork) public returns (uint256) { require(msg.sender == address(workLock)); StakerInfo storage info = stakerInfo[_staker]; @@ -430,7 +431,7 @@ contract StakingEscrow is Issuer { /** @notice Set worker * @param _worker Worker address. Must be a real address, not a contract - **/ + */ function setWorker(address _worker) public onlyStaker { StakerInfo storage info = stakerInfo[msg.sender]; require(_worker != info.worker, "Specified worker is already set for this staker"); @@ -461,7 +462,7 @@ contract StakingEscrow is Issuer { * @notice Set `reStake` parameter. If true then all staking rewards will be added to locked stake * Only if this parameter is not locked * @param _reStake Value for parameter - **/ + */ function setReStake(bool _reStake) public isInitialized { require(!isReStakeLocked(msg.sender)); StakerInfo storage info = stakerInfo[msg.sender]; @@ -475,7 +476,7 @@ contract StakingEscrow is Issuer { /** * @notice Lock `reStake` parameter. Only if this parameter is not locked * @param _lockReStakeUntilPeriod Can't change `reStake` value until this period - **/ + */ function lockReStake(uint16 _lockReStakeUntilPeriod) public isInitialized { require(!isReStakeLocked(msg.sender) && _lockReStakeUntilPeriod > getCurrentPeriod()); @@ -490,7 +491,7 @@ contract StakingEscrow is Issuer { * @param _value Amount of tokens to deposit * @param _tokenContract Token contract address * @notice (param _extraData) Amount of periods during which tokens will be locked - **/ + */ function receiveApproval( address _from, uint256 _value, @@ -517,7 +518,7 @@ contract StakingEscrow is Issuer { * @notice Deposit tokens * @param _value Amount of tokens to deposit * @param _periods Amount of periods during which tokens will be locked - **/ + */ function deposit(uint256 _value, uint16 _periods) external { deposit(msg.sender, msg.sender, _value, _periods); } @@ -527,7 +528,7 @@ contract StakingEscrow is Issuer { * @param _staker Staker * @param _value Amount of tokens to deposit * @param _periods Amount of periods during which tokens will be locked - **/ + */ function deposit(address _staker, uint256 _value, uint16 _periods) external { deposit(_staker, msg.sender, _value, _periods); } @@ -538,7 +539,7 @@ contract StakingEscrow is Issuer { * @param _payer Owner of tokens * @param _value Amount of tokens to deposit * @param _periods Amount of periods during which tokens will be locked - **/ + */ function deposit(address _staker, address _payer, uint256 _value, uint16 _periods) internal isInitialized { require(_value != 0); StakerInfo storage info = stakerInfo[_staker]; @@ -559,7 +560,7 @@ contract StakingEscrow is Issuer { * @notice Lock some tokens as a stake * @param _value Amount of tokens which will be locked * @param _periods Amount of periods during which tokens will be locked - **/ + */ function lock(uint256 _value, uint16 _periods) external onlyStaker { lock(msg.sender, _value, _periods); } @@ -569,7 +570,7 @@ contract StakingEscrow is Issuer { * @param _staker Staker * @param _value Amount of tokens which will be locked * @param _periods Amount of periods during which tokens will be locked - **/ + */ function lock(address _staker, uint256 _value, uint16 _periods) internal { require(_value >= minAllowableLockedTokens && _periods >= minLockedPeriods); @@ -601,7 +602,7 @@ contract StakingEscrow is Issuer { * @param _lastPeriod Last period of the sub stake * @param _periods Duration of the sub stake in periods * @param _lockedValue Amount of locked tokens - **/ + */ function saveSubStake( StakerInfo storage _info, uint16 _firstPeriod, @@ -635,7 +636,7 @@ contract StakingEscrow is Issuer { * @param _index Index of the sub stake * @param _newValue New sub stake value * @param _periods Amount of periods for extending sub stake - **/ + */ function divideStake(uint256 _index, uint256 _newValue, uint16 _periods) external onlyStaker { StakerInfo storage info = stakerInfo[msg.sender]; require(_newValue >= minAllowableLockedTokens && _periods > 0); @@ -658,7 +659,7 @@ contract StakingEscrow is Issuer { * @notice Prolong active sub stake * @param _index Index of the sub stake * @param _periods Amount of periods for extending sub stake - **/ + */ function prolongStake(uint256 _index, uint16 _periods) external onlyStaker { StakerInfo storage info = stakerInfo[msg.sender]; require(_periods > 0, "Incorrect parameters"); @@ -681,7 +682,7 @@ contract StakingEscrow is Issuer { /** * @notice Withdraw available amount of tokens to staker * @param _value Amount of tokens to withdraw - **/ + */ function withdraw(uint256 _value) external onlyStaker { uint16 currentPeriod = getCurrentPeriod(); uint16 nextPeriod = currentPeriod + 1; @@ -698,7 +699,7 @@ contract StakingEscrow is Issuer { /** * @notice Confirm activity for the next period and mine for the previous period - **/ + */ function confirmActivity() external { address staker = getStakerFromWorker(msg.sender); StakerInfo storage info = stakerInfo[staker]; @@ -745,7 +746,7 @@ contract StakingEscrow is Issuer { /** * @notice Mint tokens for previous periods if staker locked their tokens and confirmed activity - **/ + */ function mint() external onlyStaker { // save last active period to the storage if both periods will be empty after minting // because we won't be able to calculate last active period @@ -765,7 +766,7 @@ contract StakingEscrow is Issuer { /** * @notice Mint tokens for previous periods if staker locked their tokens and confirmed activity * @param _staker Staker - **/ + */ function mint(address _staker) internal { uint16 currentPeriod = getCurrentPeriod(); uint16 previousPeriod = currentPeriod - 1; @@ -813,7 +814,7 @@ contract StakingEscrow is Issuer { * @param _confirmedPeriodNumber Number of confirmed period (1 or 2) * @param _currentPeriod Current period * @param _startPeriod Pre-calculated start period - **/ + */ function mint( address _staker, StakerInfo storage _info, @@ -864,7 +865,7 @@ contract StakingEscrow is Issuer { * @param _penalty Penalty * @param _investigator Investigator * @param _reward Reward for the investigator - **/ + */ function slashStaker( address _staker, uint256 _penalty, @@ -927,7 +928,7 @@ contract StakingEscrow is Issuer { * @return nextLock Amount of tokens that locked in the next period and not locked in the current period * @return currentAndNextLock Amount of tokens that locked in the current period and in the next period * @return shortestSubStakeIndex Index of the shortest sub stake - **/ + */ function getLockedTokensAndShortestSubStake( StakerInfo storage _info, uint16 _currentPeriod, @@ -980,7 +981,7 @@ contract StakingEscrow is Issuer { * @param _decreasePeriod The period when the decrease begins * @param _startPeriod Pre-calculated start period * @param _shortestSubStakeIndex Index of the shortest period - **/ + */ function decreaseSubStakes( StakerInfo storage _info, uint256 _penalty, @@ -1037,7 +1038,7 @@ contract StakingEscrow is Issuer { * @return shortestSubStake The shortest sub stake * @return minSubStakeDuration Duration of the shortest sub stake * @return minSubStakeLastPeriod Last period of the shortest sub stake - **/ + */ function getShortestSubStake( StakerInfo storage _info, uint16 _currentPeriod, @@ -1078,7 +1079,7 @@ contract StakingEscrow is Issuer { * @param _firstPeriod First period of the old sub stake * @param _lockedValue Locked value of the old sub stake * @param _currentPeriod Current period, when the old sub stake is already unlocked - **/ + */ function saveOldSubStake( StakerInfo storage _info, uint16 _firstPeriod, @@ -1117,21 +1118,21 @@ contract StakingEscrow is Issuer { //-------------Additional getters for stakers info------------- /** * @notice Return the length of the array of stakers - **/ + */ function getStakersLength() external view returns (uint256) { return stakers.length; } /** * @notice Return the length of the array of sub stakes - **/ + */ function getSubStakesLength(address _staker) external view returns (uint256) { return stakerInfo[_staker].subStakes.length; } /** * @notice Return the information about sub stake - **/ + */ function getSubStakeInfo(address _staker, uint256 _index) // TODO change to structure when ABIEncoderV2 is released (#1501) // public view returns (SubStakeInfo) @@ -1146,14 +1147,14 @@ contract StakingEscrow is Issuer { /** * @notice Return the length of the array of past downtime - **/ + */ function getPastDowntimeLength(address _staker) external view returns (uint256) { return stakerInfo[_staker].pastDowntime.length; } /** * @notice Return the information about past downtime - **/ + */ function getPastDowntime(address _staker, uint256 _index) // TODO change to structure when ABIEncoderV2 is released (#1501) // public view returns (Downtime) @@ -1168,7 +1169,7 @@ contract StakingEscrow is Issuer { //------------------------Upgradeable------------------------ /** * @dev Get StakerInfo structure by delegatecall - **/ + */ function delegateGetStakerInfo(address _target, bytes32 _staker) internal returns (StakerInfo memory result) { @@ -1185,7 +1186,7 @@ contract StakingEscrow is Issuer { /** * @dev Get SubStakeInfo structure by delegatecall - **/ + */ function delegateGetSubStakeInfo(address _target, bytes32 _staker, uint256 _index) internal returns (SubStakeInfo memory result) { @@ -1198,7 +1199,7 @@ contract StakingEscrow is Issuer { /** * @dev Get Downtime structure by delegatecall - **/ + */ function delegateGetPastDowntime(address _target, bytes32 _staker, uint256 _index) internal returns (Downtime memory result) { diff --git a/nucypher/blockchain/eth/sol/source/contracts/WorkLock.sol b/nucypher/blockchain/eth/sol/source/contracts/WorkLock.sol index 55b79404a..43e290265 100644 --- a/nucypher/blockchain/eth/sol/source/contracts/WorkLock.sol +++ b/nucypher/blockchain/eth/sol/source/contracts/WorkLock.sol @@ -9,7 +9,7 @@ import "contracts/StakingEscrow.sol"; /** * @notice The WorkLock distribution contract -**/ +*/ contract WorkLock { using SafeMath for uint256; using Address for address payable; @@ -46,7 +46,7 @@ contract WorkLock { * @param _depositRate ETH -> NU rate * @param _refundRate Work -> ETH rate * @param _lockedPeriods Number of periods during which claimed tokens will be locked - **/ + */ constructor( NuCypherToken _token, StakingEscrow _escrow, @@ -78,7 +78,7 @@ contract WorkLock { /** * @notice Bid for tokens by transferring ETH - **/ + */ function bid() public payable returns (uint256 newClaimedTokens) { require(block.timestamp >= startBidDate && block.timestamp <= endBidDate, "Bid is open during a certain period"); @@ -96,7 +96,7 @@ contract WorkLock { /** * @notice Claimed tokens will be deposited and locked as stake in the StakingEscrow contract. - **/ + */ function claim() public returns (uint256 claimedTokens) { require(block.timestamp >= endBidDate, "Claiming tokens allowed after bidding is over"); WorkInfo storage info = workInfo[msg.sender]; @@ -111,7 +111,7 @@ contract WorkLock { /** * @notice Refund ETH for the completed work - **/ + */ function refund() public returns (uint256 refundETH) { WorkInfo storage info = workInfo[msg.sender]; require(info.claimed, "Tokens are not claimed"); @@ -135,7 +135,7 @@ contract WorkLock { /** * @notice Get remaining work to full refund - **/ + */ function getRemainingWork(address _staker) public view returns (uint256) { WorkInfo storage info = workInfo[_staker]; uint256 completedWork = escrow.getCompletedWork(_staker).sub(info.completedWork); diff --git a/nucypher/blockchain/eth/sol/source/contracts/lib/AdditionalMath.sol b/nucypher/blockchain/eth/sol/source/contracts/lib/AdditionalMath.sol index 5eeb26520..3358ee440 100644 --- a/nucypher/blockchain/eth/sol/source/contracts/lib/AdditionalMath.sol +++ b/nucypher/blockchain/eth/sol/source/contracts/lib/AdditionalMath.sol @@ -6,7 +6,7 @@ import "zeppelin/math/SafeMath.sol"; /** * @notice Additional math operations -**/ +*/ library AdditionalMath { using SafeMath for uint256; @@ -20,7 +20,7 @@ library AdditionalMath { /** * @notice Division and ceil - **/ + */ function divCeil(uint256 a, uint256 b) internal pure returns (uint256) { return (a.add(b) - 1) / b; } diff --git a/nucypher/blockchain/eth/sol/source/contracts/lib/ReEncryptionValidator.sol b/nucypher/blockchain/eth/sol/source/contracts/lib/ReEncryptionValidator.sol index 660ee5144..530184a7c 100644 --- a/nucypher/blockchain/eth/sol/source/contracts/lib/ReEncryptionValidator.sol +++ b/nucypher/blockchain/eth/sol/source/contracts/lib/ReEncryptionValidator.sol @@ -5,7 +5,7 @@ import "contracts/lib/SignatureVerifier.sol"; /** * @notice Validates re-encryption correctness. -**/ +*/ library ReEncryptionValidator { using UmbralDeserializer for bytes; @@ -43,7 +43,7 @@ library ReEncryptionValidator { * @param _capsuleBytes Capsule * @param _cFragBytes Capsule frag * @param _precomputedBytes Additional precomputed data - **/ + */ function validateCFrag( bytes memory _capsuleBytes, bytes memory _cFragBytes, diff --git a/nucypher/blockchain/eth/sol/source/contracts/lib/SignatureVerifier.sol b/nucypher/blockchain/eth/sol/source/contracts/lib/SignatureVerifier.sol index 04d1766f0..5669cfd56 100644 --- a/nucypher/blockchain/eth/sol/source/contracts/lib/SignatureVerifier.sol +++ b/nucypher/blockchain/eth/sol/source/contracts/lib/SignatureVerifier.sol @@ -4,7 +4,7 @@ pragma solidity ^0.5.3; /** * @notice Library to recover address and verify signatures * @dev Simple wrapper for `ecrecover` -**/ +*/ library SignatureVerifier { enum HashAlgorithm {KECCAK256, SHA256, RIPEMD160} @@ -16,7 +16,7 @@ library SignatureVerifier { * @notice Recover signer address from hash and signature * @param _hash 32 bytes message hash * @param _signature Signature of hash - 32 bytes r + 32 bytes s + 1 byte v (could be 0, 1, 27, 28) - **/ + */ function recover(bytes32 _hash, bytes memory _signature) internal pure @@ -44,7 +44,7 @@ library SignatureVerifier { /** * @notice Transform public key to address * @param _publicKey secp256k1 public key - **/ + */ function toAddress(bytes memory _publicKey) internal pure returns (address) { return address(uint160(uint256(keccak256(_publicKey)))); } @@ -53,7 +53,7 @@ library SignatureVerifier { * @notice Hash using one of pre built hashing algorithm * @param _message Signed message * @param _algorithm Hashing algorithm - **/ + */ function hash(bytes memory _message, HashAlgorithm _algorithm) internal pure @@ -75,7 +75,7 @@ library SignatureVerifier { * @param _signature Signature of message hash * @param _publicKey secp256k1 public key in uncompressed format without prefix byte (64 bytes) * @param _algorithm Hashing algorithm - **/ + */ function verify( bytes memory _message, bytes memory _signature, @@ -96,7 +96,7 @@ library SignatureVerifier { * @dev Only supports version 0 and version E (0x45) * @param _message Message to sign * @param _version EIP191 version to use - **/ + */ function hashEIP191( bytes memory _message, byte _version @@ -140,7 +140,7 @@ library SignatureVerifier { * @param _signature Signature of message hash * @param _publicKey secp256k1 public key in uncompressed format without prefix byte (64 bytes) * @param _version EIP191 version to use - **/ + */ function verifyEIP191( bytes memory _message, bytes memory _signature, diff --git a/nucypher/blockchain/eth/sol/source/contracts/lib/UmbralDeserializer.sol b/nucypher/blockchain/eth/sol/source/contracts/lib/UmbralDeserializer.sol index 9b29d3d50..57edf264f 100644 --- a/nucypher/blockchain/eth/sol/source/contracts/lib/UmbralDeserializer.sol +++ b/nucypher/blockchain/eth/sol/source/contracts/lib/UmbralDeserializer.sol @@ -3,7 +3,7 @@ pragma solidity ^0.5.3; /** * @notice Deserialization library for Umbral objects -**/ +*/ library UmbralDeserializer { struct Point { @@ -72,7 +72,7 @@ library UmbralDeserializer { /** * @notice Deserialize to capsule (not activated) - **/ + */ function toCapsule(bytes memory _capsuleBytes) internal pure returns (Capsule memory capsule) { @@ -87,7 +87,7 @@ library UmbralDeserializer { * @notice Deserialize to correctness proof * @param _pointer Proof bytes memory pointer * @param _proofBytesLength Proof bytes length - **/ + */ function toCorrectnessProof(uint256 _pointer, uint256 _proofBytesLength) internal pure returns (CorrectnessProof memory proof) { @@ -111,7 +111,7 @@ library UmbralDeserializer { /** * @notice Deserialize to correctness proof - **/ + */ function toCorrectnessProof(bytes memory _proofBytes) internal pure returns (CorrectnessProof memory proof) { @@ -121,7 +121,7 @@ library UmbralDeserializer { /** * @notice Deserialize to CapsuleFrag - **/ + */ function toCapsuleFrag(bytes memory _cFragBytes) internal pure returns (CapsuleFrag memory cFrag) { @@ -140,7 +140,7 @@ library UmbralDeserializer { /** * @notice Deserialize to precomputed data - **/ + */ function toPreComputedData(bytes memory _preComputedData) internal pure returns (PreComputedData memory data) { @@ -229,7 +229,7 @@ library UmbralDeserializer { // TODO extract to external library if needed (#1500) /** * @notice Get the memory pointer for start of array - **/ + */ function getPointer(bytes memory _bytes) internal pure returns (uint256 pointer) { assembly { pointer := add(_bytes, 32) // skip array length @@ -238,7 +238,7 @@ library UmbralDeserializer { /** * @notice Copy point data from memory in the pointer position - **/ + */ function copyPoint(uint256 _pointer, Point memory _point) internal pure returns (uint256 resultPointer) { @@ -256,7 +256,7 @@ library UmbralDeserializer { /** * @notice Read 1 byte from memory in the pointer position - **/ + */ function getByte(uint256 _pointer) internal pure returns (byte result) { bytes32 word; assembly { @@ -268,7 +268,7 @@ library UmbralDeserializer { /** * @notice Read 32 bytes from memory in the pointer position - **/ + */ function getBytes32(uint256 _pointer) internal pure returns (bytes32 result) { assembly { result := mload(_pointer) @@ -282,7 +282,7 @@ library UmbralDeserializer { * @param _bytesPointer Source memory pointer * @param _target Target array * @param _bytesLength Number of bytes to copy - **/ + */ function copyBytes(uint256 _bytesPointer, bytes memory _target, uint256 _bytesLength) internal pure diff --git a/nucypher/blockchain/eth/sol/source/contracts/proxy/Dispatcher.sol b/nucypher/blockchain/eth/sol/source/contracts/proxy/Dispatcher.sol index 502f6c20b..ff383753c 100644 --- a/nucypher/blockchain/eth/sol/source/contracts/proxy/Dispatcher.sol +++ b/nucypher/blockchain/eth/sol/source/contracts/proxy/Dispatcher.sol @@ -8,7 +8,7 @@ import "zeppelin/utils/Address.sol"; /** * @notice Proxying requests to other contracts. * Client should use ABI of real contract and address of this contract -**/ +*/ contract Dispatcher is Upgradeable { using Address for address; @@ -17,7 +17,7 @@ contract Dispatcher is Upgradeable { /** * @dev Set upgrading status before and after operations - **/ + */ modifier upgrading() { isUpgrade = UPGRADE_TRUE; @@ -28,7 +28,7 @@ contract Dispatcher is Upgradeable { /** * @param _target Target contract address * @param _newSecretHash Secret hash (keccak256) - **/ + */ constructor(address _target, bytes32 _newSecretHash) public upgrading { require(_target.isContract()); // Checks that target contract inherits Dispatcher state @@ -46,7 +46,7 @@ contract Dispatcher is Upgradeable { * @param _target New target contract address * @param _secret Secret for proof of contract owning * @param _newSecretHash New secret hash (keccak256) - **/ + */ function upgrade(address _target, bytes memory _secret, bytes32 _newSecretHash) public onlyOwner upgrading { require(_target.isContract()); require(keccak256(_secret) == secretHash && _newSecretHash != secretHash); @@ -69,7 +69,7 @@ contract Dispatcher is Upgradeable { * @dev Test storage carefully before upgrade again after rollback * @param _secret Secret for proof of contract owning * @param _newSecretHash New secret hash (keccak256) - **/ + */ function rollback(bytes memory _secret, bytes32 _newSecretHash) public onlyOwner upgrading { require(previousTarget.isContract()); require(keccak256(_secret) == secretHash && _newSecretHash != secretHash); @@ -88,7 +88,7 @@ contract Dispatcher is Upgradeable { /** * @dev Call verifyState method for Upgradeable contract - **/ + */ function verifyUpgradeableState(address _from, address _to) private { (bool callSuccess,) = _from.delegatecall(abi.encodeWithSignature("verifyState(address)", _to)); require(callSuccess); @@ -96,7 +96,7 @@ contract Dispatcher is Upgradeable { /** * @dev Call finishUpgrade method from the Upgradeable contract - **/ + */ function finishUpgrade() private { (bool callSuccess,) = target.delegatecall(abi.encodeWithSignature("finishUpgrade(address)", target)); require(callSuccess); @@ -113,12 +113,12 @@ contract Dispatcher is Upgradeable { /** * @dev Override function using empty code because no reason to call this function in Dispatcher - **/ + */ function finishUpgrade(address) public {} /** * @dev Fallback function send all requests to the target contract - **/ + */ function () external payable { assert(target.isContract()); // execute requested function from target contract using storage of the dispatcher diff --git a/nucypher/blockchain/eth/sol/source/contracts/proxy/Upgradeable.sol b/nucypher/blockchain/eth/sol/source/contracts/proxy/Upgradeable.sol index 4500bbf42..00236b848 100644 --- a/nucypher/blockchain/eth/sol/source/contracts/proxy/Upgradeable.sol +++ b/nucypher/blockchain/eth/sol/source/contracts/proxy/Upgradeable.sol @@ -9,7 +9,7 @@ import "zeppelin/ownership/Ownable.sol"; * @dev Inherited contract should implement verifyState(address) method by checking storage variables * (see verifyState(address) in Dispatcher). Also contract should implement finishUpgrade(address) * if it is using constructor parameters by coping this parameters to the dispatcher storage -**/ +*/ contract Upgradeable is Ownable { event StateVerified(address indexed testTarget, address sender); @@ -19,22 +19,22 @@ contract Upgradeable is Ownable { * @dev Contracts at the target must reserve the same location in storage for this address as in Dispatcher * Stored data actually lives in the Dispatcher * However the storage layout is specified here in the implementing contracts - **/ + */ address public target; /** * @dev Previous contract address (if available). Used for rollback - **/ + */ address public previousTarget; /** * @dev Secret hash to proof that user owns previous version of a contract - **/ + */ bytes32 public secretHash; /** * @dev Upgrade status. Explicit `uint8` type is used instead of `bool` to save gas by excluding 0 value - **/ + */ uint8 public isUpgrade; /** Constants for `isUpgrade` field **/ @@ -44,7 +44,7 @@ contract Upgradeable is Ownable { /** * @dev Checks that function executed while upgrading * Recommended to add to `verifyState` and `finishUpgrade` methods - **/ + */ modifier onlyWhileUpgrading() { require(isUpgrade == UPGRADE_TRUE); @@ -54,7 +54,7 @@ contract Upgradeable is Ownable { /** * @dev Method for verifying storage state. * Should check that new target contract returns right storage value - **/ + */ function verifyState(address _testTarget) public onlyWhileUpgrading { emit StateVerified(_testTarget, msg.sender); } @@ -62,7 +62,7 @@ contract Upgradeable is Ownable { /** * @dev Copy values from the new target to the current storage * @param _target New target contract address - **/ + */ function finishUpgrade(address _target) public onlyWhileUpgrading { emit UpgradeFinished(_target, msg.sender); } @@ -75,7 +75,7 @@ contract Upgradeable is Ownable { * @param _argument1 First method argument * @param _argument2 Second method argument * @return Address in memory where the data is located - **/ + */ function delegateGetData( address _target, string memory _signature, @@ -108,7 +108,7 @@ contract Upgradeable is Ownable { /** * @dev Call "getter" without parameters. * Result should not exceed 32 bytes - **/ + */ function delegateGet(address _target, string memory _signature) internal returns (uint256 result) { @@ -121,7 +121,7 @@ contract Upgradeable is Ownable { /** * @dev Call "getter" with one parameter. * Result should not exceed 32 bytes - **/ + */ function delegateGet(address _target, string memory _signature, bytes32 _argument) internal returns (uint256 result) { @@ -134,7 +134,7 @@ contract Upgradeable is Ownable { /** * @dev Call "getter" with two parameters. * Result should not exceed 32 bytes - **/ + */ function delegateGet( address _target, string memory _signature, diff --git a/nucypher/blockchain/eth/sol/source/contracts/staking_contracts/AbstractStakingContract.sol b/nucypher/blockchain/eth/sol/source/contracts/staking_contracts/AbstractStakingContract.sol index ea496c157..08b85f398 100644 --- a/nucypher/blockchain/eth/sol/source/contracts/staking_contracts/AbstractStakingContract.sol +++ b/nucypher/blockchain/eth/sol/source/contracts/staking_contracts/AbstractStakingContract.sol @@ -7,7 +7,7 @@ import "zeppelin/utils/Address.sol"; /** * @notice Router for accessing interface contract -**/ +*/ contract StakingInterfaceRouter is Ownable { using Address for address; @@ -17,7 +17,7 @@ contract StakingInterfaceRouter is Ownable { /** * @param _target Address of the interface contract * @param _newSecretHash Secret hash (keccak256) - **/ + */ constructor(address _target, bytes32 _newSecretHash) public { require(_target.isContract()); target = _target; @@ -29,7 +29,7 @@ contract StakingInterfaceRouter is Ownable { * @param _target New contract address * @param _secret Secret for proof of contract owning * @param _newSecretHash New secret hash (keccak256) - **/ + */ function upgrade(address _target, bytes memory _secret, bytes32 _newSecretHash) public onlyOwner { require(_target.isContract()); require(keccak256(_secret) == secretHash && _newSecretHash != secretHash); @@ -43,7 +43,7 @@ contract StakingInterfaceRouter is Ownable { /** * @notice Base class for any staking contract * @dev Implement `isFallbackAllowed()` or override fallback function -**/ +*/ contract AbstractStakingContract { using Address for address; @@ -51,7 +51,7 @@ contract AbstractStakingContract { /** * @param _router Interface router contract address - **/ + */ constructor(StakingInterfaceRouter _router) public { // check that the input address is contract require(_router.target().isContract()); @@ -60,12 +60,12 @@ contract AbstractStakingContract { /** * @dev Checks permission for calling fallback function - **/ + */ function isFallbackAllowed() public returns (bool); /** * @dev Function sends all requests to the target contract - **/ + */ function () external payable { require(isFallbackAllowed()); address target = router.target(); diff --git a/nucypher/blockchain/eth/sol/source/contracts/staking_contracts/PreallocationEscrow.sol b/nucypher/blockchain/eth/sol/source/contracts/staking_contracts/PreallocationEscrow.sol index db720d9df..0ef2b991d 100644 --- a/nucypher/blockchain/eth/sol/source/contracts/staking_contracts/PreallocationEscrow.sol +++ b/nucypher/blockchain/eth/sol/source/contracts/staking_contracts/PreallocationEscrow.sol @@ -10,7 +10,7 @@ import "contracts/staking_contracts/AbstractStakingContract.sol"; /** * @notice StakingEscrow interface -**/ +*/ contract StakingEscrowInterface { function getAllTokens(address _staker) public view returns (uint256); function secondsPerPeriod() public view returns (uint32); @@ -19,7 +19,7 @@ contract StakingEscrowInterface { /** * @notice Contract holds tokens for vesting. * Also tokens can be used as a stake in the staking escrow contract -**/ +*/ contract PreallocationEscrow is AbstractStakingContract, Ownable { using SafeERC20 for NuCypherToken; using SafeMath for uint256; @@ -38,7 +38,7 @@ contract PreallocationEscrow is AbstractStakingContract, Ownable { * @param _router Address of the StakingInterfaceRouter contract * @param _token Address of the NuCypher token contract * @param _stakingEscrow Address of the StakingEscrow contract - **/ + */ constructor( StakingInterfaceRouter _router, NuCypherToken _token, @@ -56,7 +56,7 @@ contract PreallocationEscrow is AbstractStakingContract, Ownable { * @notice Initial tokens deposit * @param _value Amount of token to deposit * @param _duration Duration of tokens locking - **/ + */ function initialDeposit(uint256 _value, uint256 _duration) public { require(lockedValue == 0 && _value > 0); endLockTimestamp = block.timestamp.add(_duration); @@ -67,7 +67,7 @@ contract PreallocationEscrow is AbstractStakingContract, Ownable { /** * @notice Get locked tokens value - **/ + */ function getLockedTokens() public view returns (uint256) { if (endLockTimestamp <= block.timestamp) { return 0; @@ -78,7 +78,7 @@ contract PreallocationEscrow is AbstractStakingContract, Ownable { /** * @notice Withdraw available amount of tokens to owner * @param _value Amount of token to withdraw - **/ + */ function withdrawTokens(uint256 _value) public onlyOwner { uint256 balance = token.balanceOf(address(this)); require(balance >= _value); @@ -91,7 +91,7 @@ contract PreallocationEscrow is AbstractStakingContract, Ownable { /** * @notice Withdraw available ETH to the owner - **/ + */ function withdrawETH() public onlyOwner { uint256 balance = address(this).balance; require(balance != 0); @@ -101,7 +101,7 @@ contract PreallocationEscrow is AbstractStakingContract, Ownable { /** * @notice Calling fallback function is allowed only for the owner - **/ + */ function isFallbackAllowed() public returns (bool) { return msg.sender == owner(); } diff --git a/nucypher/blockchain/eth/sol/source/contracts/staking_contracts/StakingInterface.sol b/nucypher/blockchain/eth/sol/source/contracts/staking_contracts/StakingInterface.sol index 5cec76ea8..993bbac3c 100644 --- a/nucypher/blockchain/eth/sol/source/contracts/staking_contracts/StakingInterface.sol +++ b/nucypher/blockchain/eth/sol/source/contracts/staking_contracts/StakingInterface.sol @@ -9,9 +9,10 @@ import "contracts/PolicyManager.sol"; /** * @notice Interface for accessing main contracts from a staking contract -* @dev All methods must be stateless because this code will execute by delegatecall call -* If state is needed - use getStateContract() method to access state of this contract -**/ +* @dev All methods must be stateless because this code will be executed by delegatecall call. +* If state is needed - use getStateContract() method to access state of this contract. +* @dev |v1.2.1| +*/ contract StakingInterface { event DepositedAsStaker(address indexed sender, uint256 value, uint16 periods); @@ -35,7 +36,7 @@ contract StakingInterface { * @param _token Token contract * @param _escrow Escrow contract * @param _policyManager PolicyManager contract - **/ + */ constructor( NuCypherToken _token, StakingEscrow _escrow, @@ -54,7 +55,7 @@ contract StakingInterface { /** * @notice Get contract which stores state * @dev Assume that `this` is the staking contract - **/ + */ function getStateContract() internal view returns (StakingInterface) { address payable stakingContractAddress = address(bytes20(address(this))); StakingInterfaceRouter router = AbstractStakingContract(stakingContractAddress).router(); @@ -64,7 +65,7 @@ contract StakingInterface { /** * @notice Set `worker` parameter in the staking escrow * @param _worker Worker address - **/ + */ function setWorker(address _worker) public { getStateContract().escrow().setWorker(_worker); emit WorkerSet(msg.sender, _worker); @@ -73,7 +74,7 @@ contract StakingInterface { /** * @notice Set `reStake` parameter in the staking escrow * @param _reStake Value for parameter - **/ + */ function setReStake(bool _reStake) public { getStateContract().escrow().setReStake(_reStake); emit ReStakeSet(msg.sender, _reStake); @@ -82,7 +83,7 @@ contract StakingInterface { /** * @notice Lock `reStake` parameter in the staking escrow * @param _lockReStakeUntilPeriod Can't change `reStake` value until this period - **/ + */ function lockReStake(uint16 _lockReStakeUntilPeriod) public { getStateContract().escrow().lockReStake(_lockReStakeUntilPeriod); emit ReStakeLocked(msg.sender, _lockReStakeUntilPeriod); @@ -92,7 +93,7 @@ contract StakingInterface { * @notice Deposit tokens to the staking escrow * @param _value Amount of token to deposit * @param _periods Amount of periods during which tokens will be locked - **/ + */ function depositAsStaker(uint256 _value, uint16 _periods) public { StakingInterface state = getStateContract(); NuCypherToken tokenFromState = state.token(); @@ -106,7 +107,7 @@ contract StakingInterface { /** * @notice Withdraw available amount of tokens from the staking escrow to the staking contract * @param _value Amount of token to withdraw - **/ + */ function withdrawAsStaker(uint256 _value) public { getStateContract().escrow().withdraw(_value); emit WithdrawnAsStaker(msg.sender, _value); @@ -116,7 +117,7 @@ contract StakingInterface { * @notice Lock some tokens or increase lock in the staking escrow * @param _value Amount of tokens which should lock * @param _periods Amount of periods during which tokens will be locked - **/ + */ function lock(uint256 _value, uint16 _periods) public { getStateContract().escrow().lock(_value, _periods); emit Locked(msg.sender, _value, _periods); @@ -127,7 +128,7 @@ contract StakingInterface { * @param _index Index of stake * @param _newValue New stake value * @param _periods Amount of periods for extending stake - **/ + */ function divideStake( uint256 _index, uint256 _newValue, @@ -141,7 +142,7 @@ contract StakingInterface { /** * @notice Mint tokens in the staking escrow - **/ + */ function mint() external { getStateContract().escrow().mint(); emit Mined(msg.sender); @@ -149,7 +150,7 @@ contract StakingInterface { /** * @notice Withdraw available reward from the policy manager to the staking contract - **/ + */ function withdrawPolicyReward(address payable _recipient) public { uint256 value = getStateContract().policyManager().withdraw(_recipient); emit PolicyRewardWithdrawn(_recipient, value); @@ -157,7 +158,7 @@ contract StakingInterface { /** * @notice Set the minimum reward that the staker will take in the policy manager - **/ + */ function setMinRewardRate(uint256 _minRewardRate) public { getStateContract().policyManager().setMinRewardRate(_minRewardRate); emit MinRewardRateSet(msg.sender, _minRewardRate); @@ -168,7 +169,7 @@ contract StakingInterface { * @notice Prolong active sub stake * @param _index Index of the sub stake * @param _periods Amount of periods for extending sub stake - **/ + */ function prolongStake(uint256 _index, uint16 _periods) public { getStateContract().escrow().prolongStake(_index, _periods); emit Prolonged(msg.sender, _index, _periods); diff --git a/nucypher/cli/deploy.py b/nucypher/cli/deploy.py index c03040ba1..38ef6ea91 100644 --- a/nucypher/cli/deploy.py +++ b/nucypher/cli/deploy.py @@ -135,12 +135,13 @@ def inspect(provider_uri, config_root, registry_infile, deployer_address, poa): @_admin_actor_options @click.option('--retarget', '-d', help="Retarget a contract's proxy.", is_flag=True) @click.option('--target-address', help="Address of the target contract", type=EIP55_CHECKSUM_ADDRESS) +@click.option('--ignore-deployed', help="Ignore already deployed contracts if exist.", is_flag=True) def upgrade(# Admin Actor Options provider_uri, contract_name, config_root, poa, force, etherscan, hw_wallet, deployer_address, registry_infile, registry_outfile, dev, # Other - retarget, target_address): + retarget, target_address, ignore_deployed): """ Upgrade NuCypher existing proxy contract deployments. """ @@ -188,7 +189,8 @@ def upgrade(# Admin Actor Options click.confirm(f"Confirm deploy new version of {contract_name} and retarget proxy?", abort=True) receipts = ADMINISTRATOR.upgrade_contract(contract_name=contract_name, existing_plaintext_secret=existing_secret, - new_plaintext_secret=new_secret) + new_plaintext_secret=new_secret, + ignore_deployed=ignore_deployed) emitter.message(f"Successfully deployed and upgraded {contract_name}", color='green') for name, receipt in receipts.items(): paint_receipt_summary(emitter=emitter, receipt=receipt) @@ -237,12 +239,13 @@ def rollback(# Admin Actor Options @_admin_actor_options @click.option('--bare', help="Deploy a contract *only* without any additional operations.", is_flag=True) @click.option('--gas', help="Operate with a specified gas per-transaction limit", type=click.IntRange(min=1)) +@click.option('--ignore-deployed', help="Ignore already deployed contracts if exist.", is_flag=True) def contracts(# Admin Actor Options provider_uri, contract_name, config_root, poa, force, etherscan, hw_wallet, deployer_address, registry_infile, registry_outfile, dev, # Other - bare, gas): + bare, gas, ignore_deployed): """ Compile and deploy contracts. """ @@ -288,12 +291,14 @@ def contracts(# Admin Actor Options receipts, agent = ADMINISTRATOR.deploy_contract(contract_name=contract_name, plaintext_secret=secret, gas_limit=gas, - bare=bare) + bare=bare, + ignore_deployed=ignore_deployed) else: # Non-Upgradeable or Bare receipts, agent = ADMINISTRATOR.deploy_contract(contract_name=contract_name, gas_limit=gas, - bare=bare) + bare=bare, + ignore_deployed=ignore_deployed) # Report paint_contract_deployment(contract_name=contract_name, @@ -330,7 +335,8 @@ def contracts(# Admin Actor Options deployment_receipts = ADMINISTRATOR.deploy_network_contracts(secrets=secrets, emitter=emitter, interactive=not force, - etherscan=etherscan) + etherscan=etherscan, + ignore_deployed=ignore_deployed) # Paint outfile paths registry_outfile = local_registry.filepath diff --git a/nucypher/cli/painting.py b/nucypher/cli/painting.py index e6b75ddeb..f1721644f 100644 --- a/nucypher/cli/painting.py +++ b/nucypher/cli/painting.py @@ -260,7 +260,7 @@ Registry ................ {registry.filepath} from nucypher.blockchain.eth.actors import ContractAdministrator for contract_deployer_class in ContractAdministrator.dispatched_upgradeable_deployer_classes: try: - bare_contract = blockchain.get_contract_by_name(name=contract_deployer_class.contract_name, + bare_contract = blockchain.get_contract_by_name(contract_name=contract_deployer_class.contract_name, proxy_name=DispatcherDeployer.contract_name, registry=registry, use_proxy_address=False) @@ -297,7 +297,7 @@ Registry ................ {registry.filepath} # staking_interface_agent = PreallocationEscrowAgent.StakingInterfaceAgent(registry=registry) - bare_contract = blockchain.get_contract_by_name(name=staking_interface_agent.contract_name, + bare_contract = blockchain.get_contract_by_name(contract_name=staking_interface_agent.contract_name, proxy_name=StakingInterfaceRouterDeployer.contract_name, use_proxy_address=False, registry=registry) diff --git a/nucypher/utilities/sandbox/blockchain.py b/nucypher/utilities/sandbox/blockchain.py index c1d72f9de..efb012827 100644 --- a/nucypher/utilities/sandbox/blockchain.py +++ b/nucypher/utilities/sandbox/blockchain.py @@ -69,7 +69,7 @@ class TesterBlockchain(BlockchainDeployerInterface): _PROVIDER_URI = 'tester://pyevm' TEST_CONTRACTS_DIR = os.path.join(BASE_DIR, 'tests', 'blockchain', 'eth', 'contracts', 'contracts') - _compiler = SolidityCompiler(test_contract_dir=TEST_CONTRACTS_DIR) + _compiler = SolidityCompiler(source_dirs=[(SolidityCompiler.default_contract_dir(), {TEST_CONTRACTS_DIR})]) _test_account_cache = list() _default_test_accounts = NUMBER_OF_ETH_TEST_ACCOUNTS diff --git a/tests/blockchain/eth/contracts/contracts/AdjudicatorTestSet.sol b/tests/blockchain/eth/contracts/contracts/AdjudicatorTestSet.sol index 47b228345..70168e249 100644 --- a/tests/blockchain/eth/contracts/contracts/AdjudicatorTestSet.sol +++ b/tests/blockchain/eth/contracts/contracts/AdjudicatorTestSet.sol @@ -9,7 +9,7 @@ import "contracts/proxy/Upgradeable.sol"; /** * @notice Contract for testing the Adjudicator contract -**/ +*/ contract StakingEscrowForAdjudicatorMock { uint32 public secondsPerPeriod = 1; @@ -50,7 +50,7 @@ contract StakingEscrowForAdjudicatorMock { /** * @notice Upgrade to this contract must lead to fail -**/ +*/ contract AdjudicatorBad is Upgradeable { StakingEscrow public escrow; @@ -67,7 +67,7 @@ contract AdjudicatorBad is Upgradeable { /** * @notice Contract for testing upgrading the Adjudicator contract -**/ +*/ contract AdjudicatorV2Mock is Adjudicator { uint256 public valueToCheck; diff --git a/tests/blockchain/eth/contracts/contracts/IssuerTestSet.sol b/tests/blockchain/eth/contracts/contracts/IssuerTestSet.sol index 5c0fada12..db520ef76 100644 --- a/tests/blockchain/eth/contracts/contracts/IssuerTestSet.sol +++ b/tests/blockchain/eth/contracts/contracts/IssuerTestSet.sol @@ -8,7 +8,7 @@ import "contracts/proxy/Upgradeable.sol"; /** * @dev Contract for testing internal methods in the Issuer contract -**/ +*/ contract IssuerMock is Issuer { constructor( @@ -54,7 +54,7 @@ contract IssuerMock is Issuer { /** * @notice Upgrade to this contract must lead to fail -**/ +*/ contract IssuerBad is Upgradeable { address public token; @@ -72,7 +72,7 @@ contract IssuerBad is Upgradeable { /** * @notice Contract for testing upgrading the Issuer contract -**/ +*/ contract IssuerV2Mock is Issuer { uint256 public valueToCheck; diff --git a/tests/blockchain/eth/contracts/contracts/LibTestSet.sol b/tests/blockchain/eth/contracts/contracts/LibTestSet.sol index e93bc1182..da622b215 100644 --- a/tests/blockchain/eth/contracts/contracts/LibTestSet.sol +++ b/tests/blockchain/eth/contracts/contracts/LibTestSet.sol @@ -7,7 +7,7 @@ import "contracts/lib/ReEncryptionValidator.sol"; /** * @notice Contract for using SignatureVerifier library -**/ +*/ contract SignatureVerifierMock { function recover(bytes32 _hash, bytes memory _signature) @@ -72,7 +72,7 @@ contract SignatureVerifierMock { /** * @dev Contract for testing UmbralDeserializer library -**/ +*/ contract UmbralDeserializerMock { using UmbralDeserializer for bytes; @@ -178,7 +178,7 @@ contract UmbralDeserializerMock { /** * @notice Contract for using ReEncryptionValidator library -**/ +*/ contract ReEncryptionValidatorMock { using UmbralDeserializer for bytes; diff --git a/tests/blockchain/eth/contracts/contracts/PolicyManagerTestSet.sol b/tests/blockchain/eth/contracts/contracts/PolicyManagerTestSet.sol index 647e2ec0e..d71dcabd2 100644 --- a/tests/blockchain/eth/contracts/contracts/PolicyManagerTestSet.sol +++ b/tests/blockchain/eth/contracts/contracts/PolicyManagerTestSet.sol @@ -7,7 +7,7 @@ import "contracts/StakingEscrow.sol"; /** * @notice Upgrade to this contract must lead to fail -**/ +*/ contract PolicyManagerBad is PolicyManager { constructor(StakingEscrow _escrow) public PolicyManager(_escrow) { @@ -22,7 +22,7 @@ contract PolicyManagerBad is PolicyManager { /** * @notice Contract for testing upgrading the PolicyManager contract -**/ +*/ contract PolicyManagerV2Mock is PolicyManager { uint256 public valueToCheck; @@ -43,7 +43,7 @@ contract PolicyManagerV2Mock is PolicyManager { /** * @notice Contract for using in PolicyManager tests -**/ +*/ contract StakingEscrowForPolicyMock { struct Downtime { @@ -58,28 +58,28 @@ contract StakingEscrowForPolicyMock { /** * @param _hoursPerPeriod Size of period in hours - **/ + */ constructor(uint16 _hoursPerPeriod) public { secondsPerPeriod = uint32(_hoursPerPeriod * 1 hours); } /** * @return Number of current period - **/ + */ function getCurrentPeriod() public view returns (uint16) { return uint16(block.timestamp / secondsPerPeriod); } /** * @notice Set last active period - **/ + */ function setLastActivePeriod(uint16 _lastActivePeriod) external { lastActivePeriod = _lastActivePeriod; } /** * @notice Add downtime period - **/ + */ function pushDowntimePeriod(uint16 _startPeriod, uint16 _endPeriod) external { downtime.push(Downtime(_startPeriod, _endPeriod)); } @@ -89,7 +89,7 @@ contract StakingEscrowForPolicyMock { * @param _staker Staker on behalf of whom minting will be * @param _startPeriod Start period for minting * @param _numberOfPeriods Number periods for minting - **/ + */ function mint(address _staker, uint16 _startPeriod, uint16 _numberOfPeriods) public { for (uint16 i = 0; i < _numberOfPeriods; i++) { policyManager.updateReward(_staker, i + _startPeriod); @@ -100,14 +100,14 @@ contract StakingEscrowForPolicyMock { * @notice Emulate mint method * @param _startPeriod Start period for minting * @param _numberOfPeriods Number periods for minting - **/ + */ function mint(uint16 _startPeriod, uint16 _numberOfPeriods) external { mint(msg.sender, _startPeriod, _numberOfPeriods); } /** * @notice Set policy manager address - **/ + */ function setPolicyManager(PolicyManager _policyManager) external { policyManager = _policyManager; } diff --git a/tests/blockchain/eth/contracts/contracts/ReceiveApprovalMethodMock.sol b/tests/blockchain/eth/contracts/contracts/ReceiveApprovalMethodMock.sol index faad9bf62..e46fae3b0 100644 --- a/tests/blockchain/eth/contracts/contracts/ReceiveApprovalMethodMock.sol +++ b/tests/blockchain/eth/contracts/contracts/ReceiveApprovalMethodMock.sol @@ -3,7 +3,7 @@ pragma solidity ^0.5.3; /** * @notice Contract for using in token tests -**/ +*/ contract ReceiveApprovalMethodMock { address public sender; diff --git a/tests/blockchain/eth/contracts/contracts/StakingContractsTestSet.sol b/tests/blockchain/eth/contracts/contracts/StakingContractsTestSet.sol index 560bc1ef0..f41219eda 100644 --- a/tests/blockchain/eth/contracts/contracts/StakingContractsTestSet.sol +++ b/tests/blockchain/eth/contracts/contracts/StakingContractsTestSet.sol @@ -6,7 +6,7 @@ import "contracts/NuCypherToken.sol"; /** * @notice Contract for using in staking contracts tests -**/ +*/ contract StakingEscrowForStakingContractMock { NuCypherToken token; @@ -83,7 +83,7 @@ contract StakingEscrowForStakingContractMock { /** * @notice Contract for staking contract tests -**/ +*/ contract PolicyManagerForStakingContractMock { uint32 public secondsPerPeriod = 1; @@ -110,7 +110,7 @@ contract PolicyManagerForStakingContractMock { /** * @notice Contract for staking contract tests -**/ +*/ contract StakingInterfaceMockV1 { function firstMethod() public pure {} @@ -124,7 +124,7 @@ contract StakingInterfaceMockV1 { /** * @notice Contract for staking contract tests -**/ +*/ contract StakingInterfaceMockV2 { function () external payable { @@ -145,7 +145,7 @@ contract StakingInterfaceMockV2 { /** * @dev Interface that could be destroyed by selfdestruct -**/ +*/ contract DestroyableStakingInterface { function method() public pure returns (uint256) { diff --git a/tests/blockchain/eth/contracts/contracts/StakingEscrowTestSet.sol b/tests/blockchain/eth/contracts/contracts/StakingEscrowTestSet.sol index 5bcf11faf..b6a5d3018 100644 --- a/tests/blockchain/eth/contracts/contracts/StakingEscrowTestSet.sol +++ b/tests/blockchain/eth/contracts/contracts/StakingEscrowTestSet.sol @@ -7,7 +7,7 @@ import "contracts/NuCypherToken.sol"; /** * @notice Upgrade to this contract must lead to fail -**/ +*/ contract StakingEscrowBad is StakingEscrow { constructor( @@ -45,7 +45,7 @@ contract StakingEscrowBad is StakingEscrow { /** * @notice Contract for testing upgrading the StakingEscrow contract -**/ +*/ contract StakingEscrowV2Mock is StakingEscrow { uint256 public valueToCheck; @@ -97,7 +97,7 @@ contract StakingEscrowV2Mock is StakingEscrow { /** * @notice Contract for testing staking escrow contract -**/ +*/ contract PolicyManagerForStakingEscrowMock { StakingEscrow public escrow; @@ -113,21 +113,21 @@ contract PolicyManagerForStakingEscrowMock { /** * @notice Update node info - **/ + */ function updateReward(address _node, uint16 _period) external { nodes[_node].push(_period); } /** * @notice Get length of array - **/ + */ function getPeriodsLength(address _node) public view returns (uint256) { return nodes[_node].length; } /** * @notice Get period info - **/ + */ function getPeriod(address _node, uint256 _index) public view returns (uint16) { return nodes[_node][_index]; } @@ -137,7 +137,7 @@ contract PolicyManagerForStakingEscrowMock { /** * @notice Contract for testing staking escrow contract -**/ +*/ contract AdjudicatorForStakingEscrowMock { StakingEscrow public escrow; @@ -160,7 +160,7 @@ contract AdjudicatorForStakingEscrowMock { /** * @notice Intermediary contract for testing worker -**/ +*/ contract Intermediary { NuCypherToken token; @@ -189,7 +189,7 @@ contract Intermediary { /** * @notice Contract for testing staking escrow contract -**/ +*/ contract WorkLockForStakingEscrowMock { StakingEscrow public escrow; diff --git a/tests/blockchain/eth/contracts/contracts/WorkLockTestSet.sol b/tests/blockchain/eth/contracts/contracts/WorkLockTestSet.sol index afc8bf3d5..ca7b2776d 100644 --- a/tests/blockchain/eth/contracts/contracts/WorkLockTestSet.sol +++ b/tests/blockchain/eth/contracts/contracts/WorkLockTestSet.sol @@ -6,7 +6,7 @@ import "contracts/NuCypherToken.sol"; /** * @notice Contract for using in WorkLock tests -**/ +*/ contract StakingEscrowForWorkLockMock { struct StakerInfo { diff --git a/tests/blockchain/eth/contracts/contracts/proxy/BadContracts.sol b/tests/blockchain/eth/contracts/contracts/proxy/BadContracts.sol index 765fbd76b..6443e5296 100644 --- a/tests/blockchain/eth/contracts/contracts/proxy/BadContracts.sol +++ b/tests/blockchain/eth/contracts/contracts/proxy/BadContracts.sol @@ -7,7 +7,7 @@ import "contracts/proxy/Upgradeable.sol"; /** * @dev This contract can't be target for dispatcher because missed `previousTarget` -**/ +*/ contract BadDispatcherStorage { address public owner; @@ -24,7 +24,7 @@ contract BadDispatcherStorage { /** * @dev Upgrade to this contract will fail because added `fakeValue` -**/ +*/ contract ContractV2BadStorage is Upgradeable { // TODO can't catch such a violation @@ -59,7 +59,7 @@ contract ContractV2BadStorage is Upgradeable { /** * @dev Upgrade to this contract will fail because `verifyState` is broken -**/ +*/ contract ContractV2BadVerifyState is ContractV1(1) { function verifyState(address) public { diff --git a/tests/blockchain/eth/contracts/contracts/proxy/ContractV1.sol b/tests/blockchain/eth/contracts/contracts/proxy/ContractV1.sol index cc270cf6a..3d16acc93 100644 --- a/tests/blockchain/eth/contracts/contracts/proxy/ContractV1.sol +++ b/tests/blockchain/eth/contracts/contracts/proxy/ContractV1.sol @@ -6,7 +6,7 @@ import "contracts/proxy/Upgradeable.sol"; /** * @dev Base contract for testing upgrading using dispatcher -**/ +*/ contract ContractV1 is Upgradeable { event EventV1(uint256 value); diff --git a/tests/blockchain/eth/contracts/contracts/proxy/ContractV2.sol b/tests/blockchain/eth/contracts/contracts/proxy/ContractV2.sol index 601bea4ad..49a2fed7d 100644 --- a/tests/blockchain/eth/contracts/contracts/proxy/ContractV2.sol +++ b/tests/blockchain/eth/contracts/contracts/proxy/ContractV2.sol @@ -6,7 +6,7 @@ import "contracts/proxy/Upgradeable.sol"; /** * @dev Extension of the contract using valid storage variables -**/ +*/ contract ContractV2 is Upgradeable { event EventV2(uint8 value); @@ -176,7 +176,7 @@ contract ContractV2 is Upgradeable { /** * @dev Get array by one parameter. - **/ + */ function delegateGetArray( address _target, string memory _signature, diff --git a/tests/blockchain/eth/contracts/contracts/proxy/ContractV4.sol b/tests/blockchain/eth/contracts/contracts/proxy/ContractV4.sol index b10f221c6..e74d1d200 100644 --- a/tests/blockchain/eth/contracts/contracts/proxy/ContractV4.sol +++ b/tests/blockchain/eth/contracts/contracts/proxy/ContractV4.sol @@ -10,7 +10,7 @@ import "contracts/proxy/Upgradeable.sol"; * This demonstrates how to mitigate possible changes in the compiler while using the proxy pattern * Many methods are not optimized on purpose to increase readability * see https://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage -**/ +*/ contract ContractV4 is Upgradeable { // slot allocation costs nothing diff --git a/tests/blockchain/eth/contracts/contracts/proxy/Destroyable.sol b/tests/blockchain/eth/contracts/contracts/proxy/Destroyable.sol index 8d46a35d5..d95b15340 100644 --- a/tests/blockchain/eth/contracts/contracts/proxy/Destroyable.sol +++ b/tests/blockchain/eth/contracts/contracts/proxy/Destroyable.sol @@ -6,7 +6,7 @@ import "contracts/proxy/Upgradeable.sol"; /** * @dev Contract that could be destroyed by selfdestruct -**/ +*/ contract Destroyable is Upgradeable { uint256 public constructorValue; diff --git a/tests/blockchain/eth/contracts/test_contracts_upgradeability.py b/tests/blockchain/eth/contracts/test_contracts_upgradeability.py new file mode 100644 index 000000000..8cfb83a76 --- /dev/null +++ b/tests/blockchain/eth/contracts/test_contracts_upgradeability.py @@ -0,0 +1,144 @@ +""" +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 . +""" +import os + +import pytest +import requests +from eth_utils import keccak + +from nucypher.blockchain.eth.deployers import NucypherTokenDeployer, StakingEscrowDeployer, PolicyManagerDeployer, \ + AdjudicatorDeployer, BaseContractDeployer, UpgradeableContractMixin, DispatcherDeployer +from nucypher.blockchain.eth.interfaces import BlockchainInterfaceFactory, BlockchainDeployerInterface +from nucypher.blockchain.eth.registry import InMemoryContractRegistry, BaseContractRegistry +from nucypher.blockchain.eth.sol.compile import SolidityCompiler, SourceDirs +from nucypher.crypto.powers import TransactingPower +from nucypher.utilities.sandbox.blockchain import TesterBlockchain +from nucypher.utilities.sandbox.constants import INSECURE_DEVELOPMENT_PASSWORD, STAKING_ESCROW_DEPLOYMENT_SECRET, \ + POLICY_MANAGER_DEPLOYMENT_SECRET, ADJUDICATOR_DEPLOYMENT_SECRET + +USER = "nucypher" +REPO = "nucypher" +BRANCH = "master" +GITHUB_SOURCE_LINK = f"https://api.github.com/repos/{USER}/{REPO}/contents/nucypher/blockchain/eth/sol/source?ref={BRANCH}" + + +def download_github_dir(source_link: str, target_folder: str): + response = requests.get(source_link) + if response.status_code != 200: + error = f"Failed to call api {source_link} with status code {response.status_code}" + raise RuntimeError(error) + + for content in response.json(): + path = os.path.join(target_folder, content["name"]) + if content["type"] == "dir": + os.mkdir(path) + download_github_dir(content["url"], path) + else: + download_github_file(content["download_url"], path) + + +def download_github_file(source_link: str, target_folder: str): + response = requests.get(source_link) + if response.status_code != 200: + error = f"Failed to call api {source_link} with status code {response.status_code}" + raise RuntimeError(error) + + raw_data = response.content + with open(target_folder, 'wb') as registry_file: + registry_file.seek(0) + registry_file.write(raw_data) + registry_file.truncate() + + +# Constructor parameters overrides for previous versions if needed +# 'None' value removes arg from list of constructor parameters +CONSTRUCTOR_OVERRIDES = { + # Test example + StakingEscrowDeployer.contract_name: {"v0.0.0": {"_hoursPerPeriod": 1}} +} + + +def deploy_earliest_contract(blockchain_interface: BlockchainDeployerInterface, + deployer: BaseContractDeployer, + secret: str): + contract_name = deployer.contract_name + earliest_version, _data = blockchain_interface.find_raw_contract_data(contract_name, "earliest") + try: + overrides = CONSTRUCTOR_OVERRIDES[contract_name][earliest_version] + except KeyError: + overrides = dict() + + deployer.deploy(secret_hash=keccak(text=secret), contract_version=earliest_version, **overrides) + + +def upgrade_to_latest_contract(deployer, secret: str): + old_secret = bytes(secret, encoding='utf-8') + new_secret_hash = keccak(b'new' + old_secret) + deployer.upgrade(existing_secret_plaintext=old_secret, + new_secret_hash=new_secret_hash, + contract_version="latest") + + +@pytest.mark.slow +def test_upgradeability(temp_dir_path, token_economics): + # 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 + blockchain_interface = BlockchainDeployerInterface(provider_uri='tester://pyevm/2', compiler=solidity_compiler) + blockchain_interface.connect() + origin = blockchain_interface.client.accounts[0] + BlockchainInterfaceFactory.register_interface(interface=blockchain_interface) + blockchain_interface.transacting_power = TransactingPower(password=INSECURE_DEVELOPMENT_PASSWORD, account=origin) + blockchain_interface.transacting_power.activate() + + # Check contracts with multiple versions + raw_contracts = blockchain_interface._raw_contract_cache + contract_name = AdjudicatorDeployer.contract_name + test_adjudicator = len(raw_contracts[contract_name]) > 1 + contract_name = StakingEscrowDeployer.contract_name + test_staking_escrow = len(raw_contracts[contract_name]) > 1 + contract_name = PolicyManagerDeployer.contract_name + test_policy_manager = len(raw_contracts[contract_name]) > 1 + + if not test_adjudicator and not test_staking_escrow and not test_policy_manager: + return + + # Prepare master version of contracts and upgrade to the latest + registry = InMemoryContractRegistry() + + token_deployer = NucypherTokenDeployer(registry=registry, deployer_address=origin) + token_deployer.deploy() + + staking_escrow_deployer = StakingEscrowDeployer(registry=registry, deployer_address=origin) + deploy_earliest_contract(blockchain_interface, staking_escrow_deployer, secret=STAKING_ESCROW_DEPLOYMENT_SECRET) + assert staking_escrow_deployer.contract.functions.secondsPerPeriod().call() == 3600 + if test_staking_escrow: + upgrade_to_latest_contract(staking_escrow_deployer, secret=STAKING_ESCROW_DEPLOYMENT_SECRET) + assert staking_escrow_deployer.contract.functions.secondsPerPeriod().call() == token_economics.seconds_per_period + + if test_policy_manager: + policy_manager_deployer = PolicyManagerDeployer(registry=registry, deployer_address=origin) + deploy_earliest_contract(blockchain_interface, policy_manager_deployer, secret=POLICY_MANAGER_DEPLOYMENT_SECRET) + upgrade_to_latest_contract(policy_manager_deployer, secret=POLICY_MANAGER_DEPLOYMENT_SECRET) + + if test_adjudicator: + adjudicator_deployer = AdjudicatorDeployer(registry=registry, deployer_address=origin) + deploy_earliest_contract(blockchain_interface, adjudicator_deployer, secret=ADJUDICATOR_DEPLOYMENT_SECRET) + upgrade_to_latest_contract(adjudicator_deployer, secret=ADJUDICATOR_DEPLOYMENT_SECRET) diff --git a/tests/blockchain/eth/entities/deployers/test_interdeployer_integration.py b/tests/blockchain/eth/entities/deployers/test_interdeployer_integration.py index f9bae2416..8b058f112 100644 --- a/tests/blockchain/eth/entities/deployers/test_interdeployer_integration.py +++ b/tests/blockchain/eth/entities/deployers/test_interdeployer_integration.py @@ -45,10 +45,10 @@ def test_deploy_ethereum_contracts(testerchain, with pytest.raises(BaseContractDeployer.ContractDeploymentError): assert token_deployer.contract_address is constants.CONTRACT_NOT_DEPLOYED - assert not token_deployer.is_deployed + assert not token_deployer.is_deployed() token_deployer.deploy(progress=deployment_progress) - assert token_deployer.is_deployed + assert token_deployer.is_deployed() assert len(token_deployer.contract_address) == 42 token_agent = NucypherTokenAgent(registry=test_registry) @@ -70,10 +70,10 @@ def test_deploy_ethereum_contracts(testerchain, with pytest.raises(BaseContractDeployer.ContractDeploymentError): assert staking_escrow_deployer.contract_address is constants.CONTRACT_NOT_DEPLOYED - assert not staking_escrow_deployer.is_deployed + assert not staking_escrow_deployer.is_deployed() staking_escrow_deployer.deploy(secret_hash=keccak(stakers_escrow_secret), progress=deployment_progress) - assert staking_escrow_deployer.is_deployed + assert staking_escrow_deployer.is_deployed() assert len(staking_escrow_deployer.contract_address) == 42 staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry) @@ -97,10 +97,10 @@ def test_deploy_ethereum_contracts(testerchain, with pytest.raises(BaseContractDeployer.ContractDeploymentError): assert policy_manager_deployer.contract_address is constants.CONTRACT_NOT_DEPLOYED - assert not policy_manager_deployer.is_deployed + assert not policy_manager_deployer.is_deployed() policy_manager_deployer.deploy(secret_hash=keccak(policy_manager_secret), progress=deployment_progress) - assert policy_manager_deployer.is_deployed + assert policy_manager_deployer.is_deployed() assert len(policy_manager_deployer.contract_address) == 42 policy_agent = policy_manager_deployer.make_agent() @@ -124,10 +124,10 @@ def test_deploy_ethereum_contracts(testerchain, with pytest.raises(BaseContractDeployer.ContractDeploymentError): assert adjudicator_deployer.contract_address is constants.CONTRACT_NOT_DEPLOYED - assert not adjudicator_deployer.is_deployed + assert not adjudicator_deployer.is_deployed() adjudicator_deployer.deploy(secret_hash=keccak(adjudicator_secret), progress=deployment_progress) - assert adjudicator_deployer.is_deployed + assert adjudicator_deployer.is_deployed() assert len(adjudicator_deployer.contract_address) == 42 adjudicator_agent = adjudicator_deployer.make_agent() diff --git a/tests/blockchain/eth/entities/deployers/test_policy_manager_deployer.py b/tests/blockchain/eth/entities/deployers/test_policy_manager_deployer.py index 4d48576d2..53876acdd 100644 --- a/tests/blockchain/eth/entities/deployers/test_policy_manager_deployer.py +++ b/tests/blockchain/eth/entities/deployers/test_policy_manager_deployer.py @@ -83,7 +83,7 @@ def test_policy_manager_has_dispatcher(policy_manager_deployer, testerchain, tes # Let's get the "bare" PolicyManager contract (i.e., unwrapped, no dispatcher) existing_bare_contract = testerchain.get_contract_by_name(registry=test_registry, - name=policy_manager_deployer.contract_name, + contract_name=policy_manager_deployer.contract_name, proxy_name=DispatcherDeployer.contract_name, use_proxy_address=False) @@ -106,7 +106,7 @@ def test_upgrade(testerchain, test_registry): deployer_address=testerchain.etherbase_account) bare_contract = testerchain.get_contract_by_name(registry=test_registry, - name=PolicyManagerDeployer.contract_name, + contract_name=PolicyManagerDeployer.contract_name, proxy_name=DispatcherDeployer.contract_name, use_proxy_address=False) old_address = bare_contract.address @@ -116,10 +116,11 @@ def test_upgrade(testerchain, test_registry): new_secret_hash=new_secret_hash) receipts = deployer.upgrade(existing_secret_plaintext=old_secret, - new_secret_hash=new_secret_hash) + new_secret_hash=new_secret_hash, + ignore_deployed=True) bare_contract = testerchain.get_contract_by_name(registry=test_registry, - name=PolicyManagerDeployer.contract_name, + contract_name=PolicyManagerDeployer.contract_name, proxy_name=DispatcherDeployer.contract_name, use_proxy_address=False) @@ -146,7 +147,8 @@ def test_rollback(testerchain, test_registry): # Let's do one more upgrade receipts = deployer.upgrade(existing_secret_plaintext=old_secret, - new_secret_hash=new_secret_hash) + new_secret_hash=new_secret_hash, + ignore_deployed=True) for title, receipt in receipts.items(): assert receipt['status'] == 1 diff --git a/tests/blockchain/eth/entities/deployers/test_preallocation_escrow_deployer.py b/tests/blockchain/eth/entities/deployers/test_preallocation_escrow_deployer.py index d58a75a62..83e0077d6 100644 --- a/tests/blockchain/eth/entities/deployers/test_preallocation_escrow_deployer.py +++ b/tests/blockchain/eth/entities/deployers/test_preallocation_escrow_deployer.py @@ -80,7 +80,7 @@ def test_deploy_multiple_preallocations(testerchain, test_registry): testerchain = testerchain deployer_account = testerchain.etherbase_account - router = testerchain.get_contract_by_name(registry=test_registry, name=StakingInterfaceRouterDeployer.contract_name) + router = testerchain.get_contract_by_name(registry=test_registry, contract_name=StakingInterfaceRouterDeployer.contract_name) router_address = router.address for index in range(NUMBER_OF_PREALLOCATIONS): deployer = PreallocationEscrowDeployer(deployer_address=deployer_account, registry=test_registry) @@ -108,10 +108,10 @@ def test_upgrade_staking_interface(testerchain, test_registry): old_secret = INSECURE_DEPLOYMENT_SECRET_PLAINTEXT new_secret = 'new' + STAKING_INTERFACE_DEPLOYMENT_SECRET new_secret_hash = keccak_digest(new_secret.encode()) - router = testerchain.get_contract_by_name(registry=test_registry, name=StakingInterfaceRouterDeployer.contract_name) + router = testerchain.get_contract_by_name(registry=test_registry, contract_name=StakingInterfaceRouterDeployer.contract_name) contract = testerchain.get_contract_by_name(registry=test_registry, - name=StakingInterfaceDeployer.contract_name, + contract_name=StakingInterfaceDeployer.contract_name, proxy_name=StakingInterfaceRouterDeployer.contract_name, use_proxy_address=False) @@ -122,7 +122,8 @@ def test_upgrade_staking_interface(testerchain, test_registry): registry=test_registry) receipts = staking_interface_deployer.upgrade(existing_secret_plaintext=old_secret, - new_secret_hash=new_secret_hash) + new_secret_hash=new_secret_hash, + ignore_deployed=True) assert len(receipts) == 2 @@ -135,7 +136,7 @@ def test_upgrade_staking_interface(testerchain, test_registry): new_target = router.functions.target().call() contract = testerchain.get_contract_by_name(registry=test_registry, - name=StakingInterfaceDeployer.contract_name, + contract_name=StakingInterfaceDeployer.contract_name, proxy_name=StakingInterfaceRouterDeployer.contract_name, use_proxy_address=False) assert new_target == contract.address diff --git a/tests/blockchain/eth/entities/deployers/test_staking_escrow_deployer.py b/tests/blockchain/eth/entities/deployers/test_staking_escrow_deployer.py index 5bf6e2aa6..29eb71629 100644 --- a/tests/blockchain/eth/entities/deployers/test_staking_escrow_deployer.py +++ b/tests/blockchain/eth/entities/deployers/test_staking_escrow_deployer.py @@ -66,7 +66,7 @@ def test_staking_escrow_has_dispatcher(staking_escrow_deployer, testerchain, tes # Let's get the "bare" StakingEscrow contract (i.e., unwrapped, no dispatcher) existing_bare_contract = testerchain.get_contract_by_name(registry=test_registry, - name=staking_escrow_deployer.contract_name, + contract_name=staking_escrow_deployer.contract_name, proxy_name=DispatcherDeployer.contract_name, use_proxy_address=False) @@ -92,7 +92,9 @@ def test_upgrade(testerchain, test_registry, token_economics): with pytest.raises(deployer.ContractDeploymentError): deployer.upgrade(existing_secret_plaintext=wrong_secret, new_secret_hash=new_secret_hash) - receipts = deployer.upgrade(existing_secret_plaintext=old_secret, new_secret_hash=new_secret_hash) + receipts = deployer.upgrade(existing_secret_plaintext=old_secret, + new_secret_hash=new_secret_hash, + ignore_deployed=True) for title, receipt in receipts.items(): assert receipt['status'] == 1 @@ -108,7 +110,9 @@ def test_rollback(testerchain, test_registry): current_target = staking_agent.contract.functions.target().call() # Let's do one more upgrade - receipts = deployer.upgrade(existing_secret_plaintext=old_secret, new_secret_hash=new_secret_hash) + receipts = deployer.upgrade(existing_secret_plaintext=old_secret, + new_secret_hash=new_secret_hash, + ignore_deployed=True) for title, receipt in receipts.items(): assert receipt['status'] == 1 @@ -145,7 +149,7 @@ def test_deploy_bare_upgradeable_contract_deployment(testerchain, test_registry, old_number_of_enrollments = enrolled_names.count(StakingEscrowDeployer.contract_name) old_number_of_proxy_enrollments = enrolled_names.count(StakingEscrowDeployer._proxy_deployer.contract_name) - receipts = deployer.deploy(initial_deployment=False) + receipts = deployer.deploy(initial_deployment=False, ignore_deployed=True) for title, receipt in receipts.items(): assert receipt['status'] == 1 diff --git a/tests/blockchain/eth/interfaces/contracts/multiversion/v1/VersionTest.sol b/tests/blockchain/eth/interfaces/contracts/multiversion/v1/VersionTest.sol new file mode 100644 index 000000000..128e8ee6a --- /dev/null +++ b/tests/blockchain/eth/interfaces/contracts/multiversion/v1/VersionTest.sol @@ -0,0 +1,10 @@ +pragma solidity ^0.5.3; + + +/** +* @notice Test extracting version number from dev docs +* @dev |v1.1.4| +*/ +contract VersionTest { + uint16 public constant VERSION = 1; +} diff --git a/tests/blockchain/eth/interfaces/contracts/multiversion/v2/VersionTest.sol b/tests/blockchain/eth/interfaces/contracts/multiversion/v2/VersionTest.sol new file mode 100644 index 000000000..72b9c7567 --- /dev/null +++ b/tests/blockchain/eth/interfaces/contracts/multiversion/v2/VersionTest.sol @@ -0,0 +1,10 @@ +pragma solidity ^0.5.3; + + +/** +* @notice Test extracting version number from dev docs +* @dev |v1.2.3| +*/ +contract VersionTest { + uint16 public constant VERSION = 2; +} diff --git a/tests/blockchain/eth/interfaces/test_chains.py b/tests/blockchain/eth/interfaces/test_chains.py index f14234950..d6e0ec0ff 100644 --- a/tests/blockchain/eth/interfaces/test_chains.py +++ b/tests/blockchain/eth/interfaces/test_chains.py @@ -14,26 +14,29 @@ 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 . """ - +import os +from os.path import dirname, abspath import pytest -from nucypher.blockchain.eth.interfaces import BlockchainDeployerInterface +from nucypher.blockchain.eth.interfaces import BlockchainDeployerInterface, BlockchainInterfaceFactory from nucypher.blockchain.eth.registry import InMemoryContractRegistry # Prevents TesterBlockchain to be picked up by py.test as a test class +from nucypher.blockchain.eth.sol.compile import SolidityCompiler, SourceDirs +from nucypher.crypto.powers import TransactingPower from nucypher.utilities.sandbox.blockchain import TesterBlockchain as _TesterBlockchain from nucypher.utilities.sandbox.constants import ( DEVELOPMENT_ETH_AIRDROP_AMOUNT, NUMBER_OF_ETH_TEST_ACCOUNTS, NUMBER_OF_STAKERS_IN_BLOCKCHAIN_TESTS, NUMBER_OF_URSULAS_IN_BLOCKCHAIN_TESTS, - TEST_PROVIDER_URI -) + TEST_PROVIDER_URI, + INSECURE_DEVELOPMENT_PASSWORD) @pytest.fixture() def another_testerchain(solidity_compiler): - testerchain = _TesterBlockchain(eth_airdrop=True, free_transactions=True, light=True) + testerchain = _TesterBlockchain(eth_airdrop=True, free_transactions=True, light=True, compiler=solidity_compiler) testerchain.deployer_address = testerchain.etherbase_account assert testerchain.is_light yield testerchain @@ -86,3 +89,71 @@ def test_testerchain_creation(testerchain, another_testerchain): tx = {'to': etherbase, 'from': account, 'value': 100} txhash = chain.client.send_transaction(tx) _receipt = chain.wait_for_receipt(txhash) + + +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})]) + + # Prepare chain + blockchain_interface = BlockchainDeployerInterface(provider_uri='tester://pyevm/2', compiler=solidity_compiler) + blockchain_interface.connect() + 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 diff --git a/tests/blockchain/eth/interfaces/test_registry.py b/tests/blockchain/eth/interfaces/test_registry.py index 6b125ad4a..fb298ecc3 100644 --- a/tests/blockchain/eth/interfaces/test_registry.py +++ b/tests/blockchain/eth/interfaces/test_registry.py @@ -47,28 +47,32 @@ def test_contract_registry(tempfile_path): test_name = 'TestContract' test_addr = '0xDEADBEEF' test_abi = ['fake', 'data'] + test_version = "some_version" test_registry.enroll(contract_name=test_name, contract_address=test_addr, - contract_abi=test_abi) + contract_abi=test_abi, + contract_version=test_version) # Search by name... contract_records = test_registry.search(contract_name=test_name) assert len(contract_records) == 1, 'More than one record for {}'.format(test_name) - assert len(contract_records[0]) == 3, 'Registry record is the wrong length' - name, address, abi = contract_records[0] + assert len(contract_records[0]) == 4, 'Registry record is the wrong length' + name, version, address, abi = contract_records[0] assert name == test_name assert address == test_addr assert abi == test_abi + assert version == test_version # ...or by address contract_record = test_registry.search(contract_address=test_addr) - name, address, abi = contract_record + name, version, address, abi = contract_record assert name == test_name assert address == test_addr assert abi == test_abi + assert version == test_version # Check that searching for an unknown contract raises with pytest.raises(BaseContractRegistry.UnknownContract): diff --git a/tests/blockchain/eth/interfaces/test_solidity_compiler.py b/tests/blockchain/eth/interfaces/test_solidity_compiler.py index 443b156a7..915525b31 100644 --- a/tests/blockchain/eth/interfaces/test_solidity_compiler.py +++ b/tests/blockchain/eth/interfaces/test_solidity_compiler.py @@ -14,9 +14,12 @@ 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 . """ - +import os +from os.path import dirname, abspath from nucypher.blockchain.eth.deployers import NucypherTokenDeployer +from nucypher.blockchain.eth.sol.compile import SolidityCompiler, SourceDirs +from nucypher.utilities.sandbox.blockchain import TesterBlockchain def test_nucypher_contract_compiled(testerchain, test_registry): @@ -24,4 +27,41 @@ def test_nucypher_contract_compiled(testerchain, test_registry): origin, *everybody_else = testerchain.client.accounts token_contract_identifier = NucypherTokenDeployer(registry=test_registry, deployer_address=origin).contract_name - assert token_contract_identifier in testerchain._BlockchainDeployerInterface__raw_contract_cache + 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 + + +def test_multi_source_compilation(testerchain): + solidity_compiler = SolidityCompiler(source_dirs=[ + (SolidityCompiler.default_contract_dir(), None), + (SolidityCompiler.default_contract_dir(), {TesterBlockchain.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") + 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() + 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"] diff --git a/tests/cli/test_deploy.py b/tests/cli/test_deploy.py index e43ea7646..016e9c64e 100644 --- a/tests/cli/test_deploy.py +++ b/tests/cli/test_deploy.py @@ -32,19 +32,13 @@ def generate_insecure_secret() -> str: return formatted_secret -# TODO: Use temp module -DEPLOYMENT_REGISTRY_FILEPATH = os.path.join('/', 'tmp', 'nucypher-test-autodeploy.json') PLANNED_UPGRADES = 4 INSECURE_SECRETS = {v: generate_insecure_secret() for v in range(1, PLANNED_UPGRADES+1)} @pytest.fixture(scope="module") -def registry_filepath(): - if os.path.exists(DEPLOYMENT_REGISTRY_FILEPATH): - os.remove(DEPLOYMENT_REGISTRY_FILEPATH) - yield DEPLOYMENT_REGISTRY_FILEPATH - if os.path.exists(DEPLOYMENT_REGISTRY_FILEPATH): - os.remove(DEPLOYMENT_REGISTRY_FILEPATH) +def registry_filepath(temp_dir_path): + return os.path.join(temp_dir_path, 'nucypher-test-autodeploy.json') def test_nucypher_deploy_contracts(click_runner, @@ -90,7 +84,7 @@ def test_nucypher_deploy_contracts(click_runner, # Read several records token_record, escrow_record, dispatcher_record, *other_records = registry_data - registered_name, registered_address, registered_abi = token_record + registered_name, registered_version, registered_address, registered_abi = token_record # # Agency @@ -101,6 +95,7 @@ def test_nucypher_deploy_contracts(click_runner, assert token_agent.contract_name == registered_name assert token_agent.registry_contract_name == registered_name assert token_agent.contract_address == registered_address + assert token_agent.contract.version == registered_version # Now show that we can use contract Agency and read from the blockchain assert token_agent.get_balance() == 0 @@ -214,7 +209,7 @@ def test_upgrade_contracts(click_runner, registry_filepath, testerchain): proxy_name = 'Dispatcher' registry = LocalContractRegistry(filepath=registry_filepath) - real_old_contract = testerchain.get_contract_by_name(name=contract_name, + real_old_contract = testerchain.get_contract_by_name(contract_name=contract_name, registry=registry, proxy_name=proxy_name, use_proxy_address=False) @@ -227,7 +222,7 @@ def test_upgrade_contracts(click_runner, registry_filepath, testerchain): assert targeted_address == real_old_contract.address # Assemble CLI command - command = (cli_action, '--contract-name', contract_name, *base_command) + command = (cli_action, '--contract-name', contract_name, '--ignore-deployed', *base_command) # Select upgrade interactive input scenario current_version = version_tracker[contract_name] @@ -268,8 +263,8 @@ def test_upgrade_contracts(click_runner, registry_filepath, testerchain): assert len(records) == contract_enrollments, error old, new = records[-2:] # Get the last two entries - old_name, old_address, *abi = old # Previous version - new_name, new_address, *abi = new # New version + old_name, _old_version, old_address, *abi = old # Previous version + new_name, _new_version, new_address, *abi = new # New version assert old_address == real_old_contract.address assert old_name == new_name # TODO: Inspect ABI / Move to different test. @@ -319,8 +314,8 @@ def test_rollback(click_runner, testerchain, registry_filepath): *old_records, v3, v4 = records current_target, rollback_target = v4, v3 - _name, current_target_address, *abi = current_target - _name, rollback_target_address, *abi = rollback_target + _name, _version, current_target_address, *abi = current_target + _name, _version, rollback_target_address, *abi = rollback_target assert current_target_address != rollback_target_address # Ensure the proxy targets the rollback target (previous version) diff --git a/tests/cli/test_deploy_commands.py b/tests/cli/test_deploy_commands.py index 8dd0bffed..b8b7a9e29 100644 --- a/tests/cli/test_deploy_commands.py +++ b/tests/cli/test_deploy_commands.py @@ -117,7 +117,8 @@ def test_bare_contract_deployment_to_alternate_registry(click_runner, test_regis '--provider', TEST_PROVIDER_URI, '--registry-infile', MOCK_REGISTRY_FILEPATH, '--registry-outfile', ALTERNATE_REGISTRY_FILEPATH, - '--poa') + '--poa', + '--ignore-deployed') user_input = '0\n' + 'Y\n' + 'DEPLOY' result = click_runner.invoke(deploy, command, input=user_input, catch_exceptions=False)