Beispiel #1
0
def test_transacting_power_sign_transaction(testerchain):

    eth_address = testerchain.unassigned_accounts[2]
    power = TransactingPower(password=INSECURE_DEVELOPMENT_PASSWORD,
                             signer=Web3Signer(testerchain.client),
                             account=eth_address)

    transaction_dict = {
        'nonce': testerchain.client.w3.eth.getTransactionCount(eth_address),
        'gasPrice': testerchain.client.w3.eth.gasPrice,
        'gas': 100000,
        'from': eth_address,
        'to': testerchain.unassigned_accounts[1],
        'value': 1,
        'data': b''
    }

    # Sign
    power.activate()
    signed_transaction = power.sign_transaction(
        transaction_dict=transaction_dict)

    # Demonstrate that the transaction is valid RLP encoded.
    from eth_account._utils.transactions import Transaction
    restored_transaction = Transaction.from_bytes(
        serialized_bytes=signed_transaction)
    restored_dict = restored_transaction.as_dict()
    assert to_checksum_address(restored_dict['to']) == transaction_dict['to']

    # Try signing with missing transaction fields
    del transaction_dict['gas']
    del transaction_dict['nonce']
    with pytest.raises(TypeError):
        power.sign_transaction(transaction_dict=transaction_dict)
Beispiel #2
0
    def bootstrap_network(
        cls,
        economics: BaseEconomics = None
    ) -> Tuple['TesterBlockchain', 'InMemoryContractRegistry']:
        """For use with metric testing scripts"""

        registry = InMemoryContractRegistry()
        testerchain = cls(compiler=SolidityCompiler())
        BlockchainInterfaceFactory.register_interface(testerchain)
        power = TransactingPower(password=INSECURE_DEVELOPMENT_PASSWORD,
                                 account=testerchain.etherbase_account)
        power.activate()
        testerchain.transacting_power = power

        origin = testerchain.client.etherbase
        deployer = ContractAdministrator(deployer_address=origin,
                                         registry=registry,
                                         economics=economics
                                         or cls._default_token_economics,
                                         staking_escrow_test_mode=True)
        secrets = dict()
        for deployer_class in deployer.upgradeable_deployer_classes:
            secrets[
                deployer_class.contract_name] = INSECURE_DEVELOPMENT_PASSWORD
        _receipts = deployer.deploy_network_contracts(secrets=secrets,
                                                      interactive=False)
        return testerchain, registry
Beispiel #3
0
def test_transacting_power_sign_agent_transaction(testerchain, agency):

    token_agent = NucypherTokenAgent(blockchain=testerchain)
    contract_function = token_agent.contract.functions.approve(
        testerchain.etherbase_account, 100)

    payload = {
        'chainId':
        int(testerchain.client.chain_id),
        'nonce':
        testerchain.client.w3.eth.getTransactionCount(
            testerchain.etherbase_account),
        'from':
        testerchain.etherbase_account,
        'gasPrice':
        testerchain.client.gas_price
    }

    unsigned_transaction = contract_function.buildTransaction(payload)

    # Sign with Transacting Power
    transacting_power = TransactingPower(
        blockchain=testerchain,
        password=INSECURE_DEVELOPMENT_PASSWORD,
        account=testerchain.etherbase_account)
    transacting_power.activate()
    signed_raw_transaction = transacting_power.sign_transaction(
        unsigned_transaction)

    # Demonstrate that the transaction is valid RLP encoded.
    restored_transaction = Transaction.from_bytes(
        serialized_bytes=signed_raw_transaction)
    restored_dict = restored_transaction.as_dict()
    assert to_checksum_address(
        restored_dict['to']) == unsigned_transaction['to']
Beispiel #4
0
def test_transacting_power_sign_transaction(testerchain):

    eth_address = testerchain.unassigned_accounts[2]
    power = TransactingPower(password=INSECURE_DEVELOPMENT_PASSWORD,
                             signer=Web3Signer(testerchain.client),
                             account=eth_address)

    assert power.is_active is False
    assert power.is_unlocked is False

    transaction_dict = {
        'nonce': testerchain.client.w3.eth.getTransactionCount(eth_address),
        'gasPrice': testerchain.client.w3.eth.gasPrice,
        'gas': 100000,
        'from': eth_address,
        'to': testerchain.unassigned_accounts[1],
        'value': 1,
        'data': b''
    }

    # The default state of the account is locked.
    assert not power.is_unlocked

    # Test a signature without unlocking the account
    with pytest.raises(power.AccountLocked):
        power.sign_transaction(transaction_dict=transaction_dict)

    # Sign
    power.activate()
    assert power.is_unlocked is True
    signed_transaction = power.sign_transaction(
        transaction_dict=transaction_dict)

    # Demonstrate that the transaction is valid RLP encoded.
    from eth_account._utils.transactions import Transaction
    restored_transaction = Transaction.from_bytes(
        serialized_bytes=signed_transaction)
    restored_dict = restored_transaction.as_dict()
    assert to_checksum_address(restored_dict['to']) == transaction_dict['to']

    # Try signing with missing transaction fields
    del transaction_dict['gas']
    del transaction_dict['nonce']
    with pytest.raises(TypeError):
        power.sign_transaction(transaction_dict=transaction_dict)

    # Try signing with a re-locked account.
    power.lock_account()
    with pytest.raises(power.AccountLocked):
        power.sign_transaction(transaction_dict=transaction_dict)

    power.unlock_account(password=INSECURE_DEVELOPMENT_PASSWORD)
    assert power.is_unlocked is True

    # Tear-Down Test
    power = TransactingPower(password=INSECURE_DEVELOPMENT_PASSWORD,
                             signer=Web3Signer(testerchain.client),
                             account=testerchain.etherbase_account)
    power.activate(password=INSECURE_DEVELOPMENT_PASSWORD)
Beispiel #5
0
 def attach_transacting_power(self,
                              checksum_address: str,
                              password: str = None) -> None:
     try:
         transacting_power = self.__transacting_powers[checksum_address]
     except KeyError:
         transacting_power = TransactingPower(blockchain=self.blockchain,
                                              password=password,
                                              account=checksum_address)
         self.__transacting_powers[checksum_address] = transacting_power
     transacting_power.activate(password=password)
Beispiel #6
0
def software_stakeholder(testerchain, agency,
                         stakeholder_config_file_location):

    # Setup
    path = stakeholder_config_file_location
    if os.path.exists(path):
        os.remove(path)

    #                          0xaAa482c790b4301bE18D75A0D1B11B2ACBEF798B
    stakeholder_private_key = '255f64a948eeb1595b8a2d1e76740f4683eca1c8f1433d13293db9b6e27676cc'
    address = testerchain.provider.ethereum_tester.add_account(
        stakeholder_private_key, password=INSECURE_DEVELOPMENT_PASSWORD)

    testerchain.provider.ethereum_tester.unlock_account(
        address, password=INSECURE_DEVELOPMENT_PASSWORD)

    tx = {
        'to': address,
        'from': testerchain.etherbase_account,
        'value': Web3.toWei('1', 'ether')
    }

    txhash = testerchain.client.w3.eth.sendTransaction(tx)
    _receipt = testerchain.wait_for_receipt(txhash)

    # Mock TransactingPower consumption (Etherbase)
    transacting_power = TransactingPower(
        account=testerchain.etherbase_account,
        password=INSECURE_DEVELOPMENT_PASSWORD,
        blockchain=testerchain)
    transacting_power.activate()

    token_agent = Agency.get_agent(NucypherTokenAgent)
    token_agent.transfer(amount=NU(200_000, 'NU').to_nunits(),
                         sender_address=testerchain.etherbase_account,
                         target_address=address)

    # Create stakeholder from on-chain values given accounts over a web3 provider
    stakeholder = StakeHolder(blockchain=testerchain,
                              funding_account=address,
                              funding_password=INSECURE_DEVELOPMENT_PASSWORD,
                              trezor=False)

    # Teardown
    yield stakeholder
    if os.path.exists(path):
        os.remove(path)
Beispiel #7
0
    def bootstrap_network(cls) -> Tuple['TesterBlockchain', Dict[str, EthereumContractAgent]]:
        """For use with metric testing scripts"""

        testerchain = cls(compiler=SolidityCompiler())
        power = TransactingPower(blockchain=testerchain,
                                 password=INSECURE_DEVELOPMENT_PASSWORD,
                                 account=testerchain.etherbase_account)
        power.activate()
        testerchain.transacting_power = power

        origin = testerchain.client.etherbase
        deployer = Deployer(blockchain=testerchain, deployer_address=origin, bare=True)
        _txhashes, agents = deployer.deploy_network_contracts(staker_secret=STAKING_ESCROW_DEPLOYMENT_SECRET,
                                                              policy_secret=POLICY_MANAGER_DEPLOYMENT_SECRET,
                                                              adjudicator_secret=ADJUDICATOR_DEPLOYMENT_SECRET,
                                                              user_escrow_proxy_secret=USER_ESCROW_PROXY_DEPLOYMENT_SECRET)
        return testerchain, agents
Beispiel #8
0
    def bootstrap_network(cls, economics: BaseEconomics = None) -> Tuple['TesterBlockchain', 'InMemoryContractRegistry']:
        """For use with metric testing scripts"""

        registry = InMemoryContractRegistry()
        testerchain = cls()
        BlockchainInterfaceFactory.register_interface(testerchain)
        power = TransactingPower(password=INSECURE_DEVELOPMENT_PASSWORD,
                                 account=testerchain.etherbase_account)
        power.activate()
        testerchain.transacting_power = power

        origin = testerchain.client.etherbase
        deployer = ContractAdministrator(deployer_address=origin,
                                         registry=registry,
                                         economics=economics or cls.DEFAULT_ECONOMICS,
                                         staking_escrow_test_mode=True)

        _receipts = deployer.deploy_network_contracts(interactive=False)
        return testerchain, registry
Beispiel #9
0
    def bootstrap_network(cls) -> 'TesterBlockchain':
        """For use with metric testing scripts"""

        testerchain = cls(compiler=SolidityCompiler())
        power = TransactingPower(blockchain=testerchain,
                                 password=INSECURE_DEVELOPMENT_PASSWORD,
                                 account=testerchain.etherbase_account)
        power.activate()
        testerchain.transacting_power = power

        origin = testerchain.client.etherbase
        deployer = DeployerActor(blockchain=testerchain,
                                 deployer_address=origin,
                                 bare=True)
        secrets = dict()
        for deployer_class in deployer.upgradeable_deployer_classes:
            secrets[
                deployer_class.contract_name] = INSECURE_DEVELOPMENT_PASSWORD
        _receipts = deployer.deploy_network_contracts(secrets=secrets,
                                                      interactive=False)
        return testerchain
Beispiel #10
0
    def bootstrap_network(
        cls,
        registry: Optional[BaseContractRegistry] = None,
        economics: BaseEconomics = None
    ) -> Tuple['TesterBlockchain', 'InMemoryContractRegistry']:
        """For use with metric testing scripts"""

        if registry is None:
            registry = InMemoryContractRegistry()
        testerchain = cls()
        if not BlockchainInterfaceFactory.is_interface_initialized(
                provider_uri=testerchain.provider_uri):
            BlockchainInterfaceFactory.register_interface(
                interface=testerchain)
        power = TransactingPower(password=INSECURE_DEVELOPMENT_PASSWORD,
                                 account=testerchain.etherbase_account)
        power.activate()
        testerchain.transacting_power = power

        origin = testerchain.client.etherbase
        admin = ContractAdministrator(deployer_address=origin,
                                      registry=registry,
                                      economics=economics
                                      or cls.DEFAULT_ECONOMICS)

        gas_limit = None  # TODO: Gas management - #842
        for deployer_class in admin.primary_deployer_classes:
            if deployer_class is StakingEscrowDeployer:
                admin.deploy_contract(
                    contract_name=deployer_class.contract_name,
                    gas_limit=gas_limit,
                    deployment_mode=INIT)
            else:
                admin.deploy_contract(
                    contract_name=deployer_class.contract_name,
                    gas_limit=gas_limit)
        admin.deploy_contract(
            contract_name=StakingEscrowDeployer.contract_name,
            gas_limit=gas_limit)
        return testerchain, registry
Beispiel #11
0
class ContractAdministrator(NucypherTokenActor):
    """
    The administrator of network contracts.
    """

    __interface_class = BlockchainDeployerInterface

    #
    # Deployer classes sorted by deployment dependency order.
    #

    standard_deployer_classes = (NucypherTokenDeployer, )

    dispatched_upgradeable_deployer_classes = (
        StakingEscrowDeployer,
        PolicyManagerDeployer,
        AdjudicatorDeployer,
    )

    upgradeable_deployer_classes = (
        *dispatched_upgradeable_deployer_classes,
        StakingInterfaceDeployer,
    )

    ownable_deployer_classes = (*dispatched_upgradeable_deployer_classes, )

    deployer_classes = (*standard_deployer_classes,
                        *upgradeable_deployer_classes)

    class UnknownContract(ValueError):
        pass

    def __init__(self,
                 registry: BaseContractRegistry,
                 deployer_address: str = None,
                 client_password: str = None,
                 economics: TokenEconomics = None):
        """
        Note: super() is not called here to avoid setting the token agent.
        TODO: Review this logic ^^ "bare mode".
        """
        self.log = Logger("Deployment-Actor")

        self.deployer_address = deployer_address
        self.checksum_address = self.deployer_address
        self.economics = economics or StandardTokenEconomics()

        self.registry = registry
        self.preallocation_escrow_deployers = dict()
        self.deployers = {d.contract_name: d for d in self.deployer_classes}

        self.transacting_power = TransactingPower(password=client_password,
                                                  account=deployer_address)
        self.transacting_power.activate()

    def __repr__(self):
        r = '{name} - {deployer_address})'.format(
            name=self.__class__.__name__,
            deployer_address=self.deployer_address)
        return r

    def __get_deployer(self, contract_name: str):
        try:
            Deployer = self.deployers[contract_name]
        except KeyError:
            raise self.UnknownContract(contract_name)
        return Deployer

    @staticmethod
    def collect_deployment_secret(deployer) -> str:
        secret = click.prompt(
            f'Enter {deployer.contract_name} Deployment Secret',
            hide_input=True,
            confirmation_prompt=True)
        return secret

    def collect_deployment_secrets(self) -> dict:
        secrets = dict()
        for deployer in self.upgradeable_deployer_classes:
            secrets[deployer.contract_name] = self.collect_deployment_secret(
                deployer)
        return secrets

    def deploy_contract(
        self,
        contract_name: str,
        gas_limit: int = None,
        plaintext_secret: str = None,
        bare: bool = False,
        progress=None,
        *args,
        **kwargs,
    ) -> Tuple[dict, BaseContractDeployer]:

        Deployer = self.__get_deployer(contract_name=contract_name)
        deployer = Deployer(registry=self.registry,
                            deployer_address=self.deployer_address,
                            economics=self.economics,
                            *args,
                            **kwargs)

        if Deployer._upgradeable:
            is_initial_deployment = not bare
            if is_initial_deployment and not plaintext_secret:
                raise ValueError(
                    "An upgrade secret must be passed to perform initial deployment of a Dispatcher."
                )
            secret_hash = None
            if plaintext_secret:
                secret_hash = keccak(bytes(plaintext_secret, encoding='utf-8'))
            receipts = deployer.deploy(
                secret_hash=secret_hash,
                gas_limit=gas_limit,
                initial_deployment=is_initial_deployment,
                progress=progress)
        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:
        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)
        return receipts

    def retarget_proxy(self, contract_name: str, target_address: str,
                       existing_plaintext_secret: str,
                       new_plaintext_secret: str):
        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.retarget(target_address=target_address,
                                     existing_secret_plaintext=bytes(
                                         existing_plaintext_secret,
                                         encoding='utf-8'),
                                     new_secret_hash=new_secret_hash)
        return receipts

    def rollback_contract(self, contract_name: str,
                          existing_plaintext_secret: str,
                          new_plaintext_secret: str):
        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.rollback(existing_secret_plaintext=bytes(
            existing_plaintext_secret, encoding='utf-8'),
                                     new_secret_hash=new_secret_hash)
        return receipts

    def deploy_preallocation_escrow(
            self,
            allocation_registry: AllocationRegistry,
            progress=None) -> PreallocationEscrowDeployer:
        preallocation_escrow_deployer = PreallocationEscrowDeployer(
            registry=self.registry,
            deployer_address=self.deployer_address,
            allocation_registry=allocation_registry)
        preallocation_escrow_deployer.deploy(progress=progress)
        principal_address = preallocation_escrow_deployer.contract.address
        self.preallocation_escrow_deployers[
            principal_address] = preallocation_escrow_deployer
        return preallocation_escrow_deployer

    def deploy_network_contracts(self,
                                 secrets: dict,
                                 interactive: bool = True,
                                 emitter: StdoutEmitter = None,
                                 etherscan: 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
        :return: Returns a dictionary of deployment receipts keyed by contract name
        """

        if interactive and not emitter:
            raise ValueError(
                "'emitter' is a required keyword argument when interactive is True."
            )

        deployment_receipts = dict()
        gas_limit = None  # TODO: Gas management

        # deploy contracts
        total_deployment_transactions = 0
        for deployer_class in self.deployer_classes:
            total_deployment_transactions += len(
                deployer_class.deployment_steps)

        first_iteration = True
        with click.progressbar(length=total_deployment_transactions,
                               label="Deployment progress",
                               show_eta=False) as bar:
            bar.short_limit = 0
            for deployer_class in self.deployer_classes:
                if interactive and not first_iteration:
                    click.pause(
                        info=
                        f"\nPress any key to continue with deployment of {deployer_class.contract_name}"
                    )

                if emitter:
                    emitter.echo(
                        f"\nDeploying {deployer_class.contract_name} ...")
                    bar._last_line = None
                    bar.render_progress()

                if deployer_class in self.standard_deployer_classes:
                    receipts, deployer = self.deploy_contract(
                        contract_name=deployer_class.contract_name,
                        gas_limit=gas_limit,
                        progress=bar)
                else:
                    receipts, deployer = self.deploy_contract(
                        contract_name=deployer_class.contract_name,
                        plaintext_secret=secrets[deployer_class.contract_name],
                        gas_limit=gas_limit,
                        progress=bar)

                if emitter:
                    blockchain = BlockchainInterfaceFactory.get_interface()
                    paint_contract_deployment(
                        contract_name=deployer_class.contract_name,
                        receipts=receipts,
                        contract_address=deployer.contract_address,
                        emitter=emitter,
                        chain_name=blockchain.client.chain_name,
                        open_in_browser=etherscan)

                deployment_receipts[deployer_class.contract_name] = receipts
                first_iteration = False

        return deployment_receipts

    def relinquish_ownership(self,
                             new_owner: str,
                             emitter: StdoutEmitter = None,
                             interactive: bool = True,
                             transaction_gas_limit: int = None) -> dict:

        if not is_checksum_address(new_owner):
            raise ValueError(
                f"{new_owner} is an invalid EIP-55 checksum address.")

        receipts = dict()

        for contract_deployer in self.ownable_deployer_classes:
            deployer = contract_deployer(
                registry=self.registry, deployer_address=self.deployer_address)
            deployer.transfer_ownership(
                new_owner=new_owner,
                transaction_gas_limit=transaction_gas_limit)

            if emitter:
                emitter.echo(
                    f"Transferred ownership of {deployer.contract_name} to {new_owner}"
                )

            if interactive:
                click.pause(info="Press any key to continue")

            receipts[contract_deployer.contract_name] = receipts

        return receipts

    def deploy_beneficiary_contracts(
        self,
        allocations: List[Dict[str, Union[str, int]]],
        allocation_outfile: str = None,
        allocation_registry: AllocationRegistry = None,
        crash_on_failure: bool = True,
        interactive: bool = True,
        emitter: StdoutEmitter = None,
    ) -> Dict[str, dict]:
        """
        The allocation file is a JSON file containing a list of allocations. Each allocation has a:
          * 'beneficiary_address': Checksum address of the beneficiary
          * 'name': User-friendly name of the beneficiary (Optional)
          * 'amount': Amount of tokens locked, in NuNits
          * 'duration_seconds': Lock duration expressed in seconds

        Example allocation file:

        [ {'beneficiary_address': '0xdeadbeef', 'name': 'H. E. Pennypacker', 'amount': 100, 'duration_seconds': 31536000},
          {'beneficiary_address': '0xabced120', 'amount': 133432, 'duration_seconds': 31536000},
          {'beneficiary_address': '0xf7aefec2', 'amount': 999, 'duration_seconds': 31536000}]

        """

        if interactive and not emitter:
            raise ValueError(
                "'emitter' is a required keyword argument when interactive is True."
            )

        if allocation_registry and allocation_outfile:
            raise self.ActorError(
                "Pass either allocation registry or allocation_outfile, not both."
            )
        if allocation_registry is None:
            allocation_registry = AllocationRegistry(
                filepath=allocation_outfile)

        if emitter:
            paint_input_allocation_file(emitter, allocations)

        if interactive:
            click.confirm("Continue with the allocation process?", abort=True)

        total_to_allocate = NU.from_nunits(
            sum(allocation['amount'] for allocation in allocations))
        balance = ContractAgency.get_agent(NucypherTokenAgent,
                                           self.registry).get_balance(
                                               self.deployer_address)
        if balance < total_to_allocate:
            raise ValueError(
                f"Not enough tokens to allocate. We need at least {total_to_allocate}."
            )

        allocation_receipts, failed, allocated = dict(), list(), list()
        total_deployment_transactions = len(allocations) * 4

        # Create an allocation template file, containing the allocation contract ABI and placeholder values
        # for the beneficiary and contract addresses. This file will be shared with all allocation users.
        empty_allocation_escrow_deployer = PreallocationEscrowDeployer(
            registry=self.registry)
        allocation_contract_abi = empty_allocation_escrow_deployer.get_contract_abi(
        )
        allocation_template = {
            "BENEFICIARY_ADDRESS":
            ["ALLOCATION_CONTRACT_ADDRESS", allocation_contract_abi]
        }

        parent_path = Path(allocation_registry.filepath
                           ).parent  # Use same folder as allocation registry
        template_filename = IndividualAllocationRegistry.REGISTRY_NAME
        template_filepath = os.path.join(parent_path, template_filename)
        AllocationRegistry(filepath=template_filepath).write(
            registry_data=allocation_template)
        if emitter:
            emitter.echo(
                f"Saved allocation template file to {template_filepath}",
                color='blue',
                bold=True)

        # Deploy each allocation contract
        with click.progressbar(length=total_deployment_transactions,
                               label="Allocation progress",
                               show_eta=False) as bar:
            bar.short_limit = 0
            for allocation in allocations:

                # TODO: Check if allocation already exists in allocation registry

                beneficiary = allocation['beneficiary_address']
                name = allocation.get('name', 'No name provided')

                if interactive:
                    click.pause(
                        info=f"\nPress any key to continue with allocation for "
                        f"beneficiary {beneficiary} ({name})")

                if emitter:
                    emitter.echo(
                        f"\nDeploying PreallocationEscrow contract for beneficiary {beneficiary} ({name})..."
                    )
                    bar._last_line = None
                    bar.render_progress()

                deployer = self.deploy_preallocation_escrow(
                    allocation_registry=allocation_registry, progress=bar)

                amount = allocation['amount']
                duration = allocation['duration_seconds']
                try:
                    receipts = deployer.deliver(
                        value=amount,
                        duration=duration,
                        beneficiary_address=beneficiary,
                        progress=bar)
                except TransactionFailed as e:
                    if crash_on_failure:
                        raise
                    self.log.debug(
                        f"Failed allocation transaction for {NU.from_nunits(amount)} to {beneficiary}: {e}"
                    )
                    failed.append(allocation)
                    continue

                else:
                    allocation_receipts[beneficiary] = receipts
                    allocation_contract_address = deployer.contract_address
                    self.log.info(
                        f"Created {deployer.contract_name} contract at {allocation_contract_address} "
                        f"for beneficiary {beneficiary}.")
                    allocated.append((allocation, allocation_contract_address))

                    # Create individual allocation file
                    individual_allocation_filename = f'allocation-{beneficiary}.json'
                    individual_allocation_filepath = os.path.join(
                        parent_path, individual_allocation_filename)
                    individual_allocation_file_data = {
                        'beneficiary_address': beneficiary,
                        'contract_address': allocation_contract_address
                    }
                    with open(individual_allocation_filepath, 'w') as outfile:
                        json.dump(individual_allocation_file_data, outfile)

                    if emitter:
                        blockchain = BlockchainInterfaceFactory.get_interface()
                        paint_contract_deployment(
                            contract_name=deployer.contract_name,
                            receipts=receipts,
                            contract_address=deployer.contract_address,
                            emitter=emitter,
                            chain_name=blockchain.client.chain_name,
                            open_in_browser=False)
                        emitter.echo(
                            f"Saved individual allocation file to {individual_allocation_filepath}",
                            color='blue',
                            bold=True)

            if emitter:
                paint_deployed_allocations(emitter, allocated, failed)

            csv_filename = f'allocations-{self.deployer_address[:6]}-{maya.now().epoch}.csv'
            csv_filepath = os.path.join(parent_path, csv_filename)
            write_deployed_allocations_to_csv(csv_filepath, allocated, failed)
            if emitter:
                emitter.echo(f"Saved allocation summary CSV to {csv_filepath}",
                             color='blue',
                             bold=True)

            if failed:
                # TODO: More with these failures: send to isolated logfile, and reattempt
                self.log.critical(
                    f"FAILED TOKEN ALLOCATION - {len(failed)} allocations failed."
                )

        return allocation_receipts

    @staticmethod
    def __read_allocation_data(filepath: str) -> list:
        with open(filepath, 'r') as allocation_file:
            data = allocation_file.read()
            try:
                allocation_data = json.loads(data)
            except JSONDecodeError:
                raise
        return allocation_data

    def deploy_beneficiaries_from_file(self,
                                       allocation_data_filepath: str,
                                       allocation_outfile: str = None,
                                       emitter=None,
                                       interactive=None) -> dict:

        allocations = self.__read_allocation_data(
            filepath=allocation_data_filepath)
        receipts = self.deploy_beneficiary_contracts(
            allocations=allocations,
            allocation_outfile=allocation_outfile,
            emitter=emitter,
            interactive=interactive)
        # Save transaction metadata
        receipts_filepath = self.save_deployment_receipts(
            receipts=receipts, filename_prefix='allocation')
        if emitter:
            emitter.echo(f"Saved allocation receipts to {receipts_filepath}",
                         color='blue',
                         bold=True)
        return receipts

    def save_deployment_receipts(self,
                                 receipts: dict,
                                 filename_prefix: str = 'deployment') -> str:
        filename = f'{filename_prefix}-receipts-{self.deployer_address[:6]}-{maya.now().epoch}.json'
        filepath = os.path.join(DEFAULT_CONFIG_ROOT, filename)
        # TODO: Do not assume default config root
        os.makedirs(DEFAULT_CONFIG_ROOT, exist_ok=True)
        with open(filepath, 'w') as file:
            data = dict()
            for contract_name, receipts in receipts.items():
                contract_records = dict()
                for tx_name, receipt in receipts.items():
                    # Formatting
                    receipt = {
                        item: str(result)
                        for item, result in receipt.items()
                    }
                    contract_records.update(
                        {tx_name: receipt
                         for tx_name in receipts})
                data[contract_name] = contract_records
            data = json.dumps(data, indent=4)
            file.write(data)
        return filepath
Beispiel #12
0
class ContractAdministrator(NucypherTokenActor):
    """
    The administrator of network contracts.
    """

    __interface_class = BlockchainDeployerInterface

    #
    # Deployer classes sorted by deployment dependency order.
    #

    standard_deployer_classes = (NucypherTokenDeployer, )

    dispatched_upgradeable_deployer_classes = (
        StakingEscrowDeployer,
        PolicyManagerDeployer,
        AdjudicatorDeployer,
    )

    upgradeable_deployer_classes = (
        *dispatched_upgradeable_deployer_classes,
        UserEscrowProxyDeployer,
    )

    deployer_classes = (*standard_deployer_classes,
                        *upgradeable_deployer_classes)

    class UnknownContract(ValueError):
        pass

    def __init__(self,
                 registry: BaseContractRegistry,
                 deployer_address: str = None,
                 client_password: str = None,
                 economics: TokenEconomics = None):
        """
        Note: super() is not called here to avoid setting the token agent.
        TODO: Review this logic ^^ "bare mode".
        """
        self.log = Logger("Deployment-Actor")

        self.deployer_address = deployer_address
        self.checksum_address = self.deployer_address
        self.economics = economics or StandardTokenEconomics()

        self.registry = registry
        self.user_escrow_deployers = dict()
        self.deployers = {d.contract_name: d for d in self.deployer_classes}

        self.transacting_power = TransactingPower(password=client_password,
                                                  account=deployer_address)
        self.transacting_power.activate()

    def __repr__(self):
        r = '{name} - {deployer_address})'.format(
            name=self.__class__.__name__,
            deployer_address=self.deployer_address)
        return r

    def __get_deployer(self, contract_name: str):
        try:
            Deployer = self.deployers[contract_name]
        except KeyError:
            raise self.UnknownContract(contract_name)
        return Deployer

    @staticmethod
    def collect_deployment_secret(deployer) -> str:
        secret = click.prompt(
            f'Enter {deployer.contract_name} Deployment Secret',
            hide_input=True,
            confirmation_prompt=True)
        return secret

    def collect_deployment_secrets(self) -> dict:
        secrets = dict()
        for deployer in self.upgradeable_deployer_classes:
            secrets[deployer.contract_name] = self.collect_deployment_secret(
                deployer)
        return secrets

    def deploy_contract(
        self,
        contract_name: str,
        gas_limit: int = None,
        plaintext_secret: str = None,
        progress=None,
        *args,
        **kwargs,
    ) -> Tuple[dict, ContractDeployer]:

        Deployer = self.__get_deployer(contract_name=contract_name)
        deployer = Deployer(registry=self.registry,
                            deployer_address=self.deployer_address,
                            economics=self.economics,
                            *args,
                            **kwargs)
        if Deployer._upgradeable:
            if not plaintext_secret:
                raise ValueError(
                    "Upgrade plaintext_secret must be passed to deploy an upgradeable contract."
                )
            secret_hash = keccak(bytes(plaintext_secret, encoding='utf-8'))
            txhashes = deployer.deploy(secret_hash=secret_hash,
                                       gas_limit=gas_limit,
                                       progress=progress)
        else:
            txhashes = deployer.deploy(gas_limit=gas_limit, progress=progress)
        return txhashes, deployer

    def upgrade_contract(self, contract_name: str,
                         existing_plaintext_secret: str,
                         new_plaintext_secret: str) -> 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'))
        txhashes = deployer.upgrade(existing_secret_plaintext=bytes(
            existing_plaintext_secret, encoding='utf-8'),
                                    new_secret_hash=new_secret_hash)
        return txhashes

    def rollback_contract(self, contract_name: str,
                          existing_plaintext_secret: str,
                          new_plaintext_secret: str):
        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'))
        txhash = deployer.rollback(existing_secret_plaintext=bytes(
            existing_plaintext_secret, encoding='utf-8'),
                                   new_secret_hash=new_secret_hash)
        return txhash

    def deploy_user_escrow(self, allocation_registry: AllocationRegistry):
        user_escrow_deployer = UserEscrowDeployer(
            registry=self.registry,
            deployer_address=self.deployer_address,
            allocation_registry=allocation_registry)
        user_escrow_deployer.deploy()
        principal_address = user_escrow_deployer.contract.address
        self.user_escrow_deployers[principal_address] = user_escrow_deployer
        return user_escrow_deployer

    def deploy_network_contracts(self,
                                 secrets: dict,
                                 interactive: bool = True,
                                 emitter: StdoutEmitter = None,
                                 etherscan: 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
        :return: Returns a dictionary of deployment receipts keyed by contract name
        """

        if interactive and not emitter:
            raise ValueError(
                "'emitter' is a required keyword argument when interactive is True."
            )

        deployment_receipts = dict()
        gas_limit = None  # TODO: Gas management

        # deploy contracts
        total_deployment_transactions = 0
        for deployer_class in self.deployer_classes:
            total_deployment_transactions += len(
                deployer_class.deployment_steps)

        first_iteration = True
        with click.progressbar(length=total_deployment_transactions,
                               label="Deployment progress",
                               show_eta=False) as bar:
            bar.short_limit = 0
            for deployer_class in self.deployer_classes:
                if interactive and not first_iteration:
                    click.pause(
                        info=
                        f"\nPress any key to continue with deployment of {deployer_class.contract_name}"
                    )

                if emitter:
                    emitter.echo(
                        f"\nDeploying {deployer_class.contract_name} ...")
                    bar._last_line = None
                    bar.render_progress()

                if deployer_class in self.standard_deployer_classes:
                    receipts, deployer = self.deploy_contract(
                        contract_name=deployer_class.contract_name,
                        gas_limit=gas_limit,
                        progress=bar)
                else:
                    receipts, deployer = self.deploy_contract(
                        contract_name=deployer_class.contract_name,
                        plaintext_secret=secrets[deployer_class.contract_name],
                        gas_limit=gas_limit,
                        progress=bar)

                if emitter:
                    blockchain = BlockchainInterfaceFactory.get_interface()
                    paint_contract_deployment(
                        contract_name=deployer_class.contract_name,
                        receipts=receipts,
                        contract_address=deployer.contract_address,
                        emitter=emitter,
                        chain_name=blockchain.client.chain_name,
                        open_in_browser=etherscan)

                deployment_receipts[deployer_class.contract_name] = receipts
                first_iteration = False

        return deployment_receipts

    def relinquish_ownership(self,
                             new_owner: str,
                             emitter: StdoutEmitter = None,
                             interactive: bool = True,
                             transaction_gas_limit: int = None) -> dict:

        if not is_checksum_address(new_owner):
            raise ValueError(
                f"{new_owner} is an invalid EIP-55 checksum address.")

        receipts = dict()

        for contract_deployer in self.upgradeable_deployer_classes:
            deployer = contract_deployer(
                registry=self.registry, deployer_address=self.deployer_address)
            deployer.transfer_ownership(
                new_owner=new_owner,
                transaction_gas_limit=transaction_gas_limit)

            if emitter:
                emitter.echo(
                    f"Transferred ownership of {deployer.contract_name} to {new_owner}"
                )

            if interactive:
                click.pause(info="Press any key to continue")

            receipts[contract_deployer.contract_name] = receipts

        return receipts

    def deploy_beneficiary_contracts(
        self,
        allocations: List[Dict[str, Union[str, int]]],
        allocation_outfile: str = None,
        allocation_registry: AllocationRegistry = None,
        crash_on_failure: bool = True,
    ) -> Dict[str, dict]:
        """

        Example allocation dataset (one year is 31536000 seconds):

        data = [{'beneficiary_address': '0xdeadbeef', 'amount': 100, 'duration_seconds': 31536000},
                {'beneficiary_address': '0xabced120', 'amount': 133432, 'duration_seconds': 31536000*2},
                {'beneficiary_address': '0xf7aefec2', 'amount': 999, 'duration_seconds': 31536000*3}]
        """
        if allocation_registry and allocation_outfile:
            raise self.ActorError(
                "Pass either allocation registry or allocation_outfile, not both."
            )
        if allocation_registry is None:
            allocation_registry = AllocationRegistry(
                filepath=allocation_outfile)

        allocation_txhashes, failed = dict(), list()
        for allocation in allocations:
            deployer = self.deploy_user_escrow(
                allocation_registry=allocation_registry)

            try:
                txhashes = deployer.deliver(
                    value=allocation['amount'],
                    duration=allocation['duration_seconds'],
                    beneficiary_address=allocation['beneficiary_address'])
            except TransactionFailed:
                if crash_on_failure:
                    raise
                self.log.debug(
                    f"Failed allocation transaction for {allocation['amount']} to {allocation['beneficiary_address']}"
                )
                failed.append(allocation)
                continue

            else:
                allocation_txhashes[
                    allocation['beneficiary_address']] = txhashes

        if failed:
            # TODO: More with these failures: send to isolated logfile, and reattempt
            self.log.critical(
                f"FAILED TOKEN ALLOCATION - {len(failed)} Allocations failed.")

        return allocation_txhashes

    @staticmethod
    def __read_allocation_data(filepath: str) -> list:
        with open(filepath, 'r') as allocation_file:
            data = allocation_file.read()
            try:
                allocation_data = json.loads(data)
            except JSONDecodeError:
                raise
        return allocation_data

    def deploy_beneficiaries_from_file(self,
                                       allocation_data_filepath: str,
                                       allocation_outfile: str = None) -> dict:

        allocations = self.__read_allocation_data(
            filepath=allocation_data_filepath)
        txhashes = self.deploy_beneficiary_contracts(
            allocations=allocations, allocation_outfile=allocation_outfile)
        return txhashes

    def save_deployment_receipts(self, receipts: dict) -> str:
        filename = f'deployment-receipts-{self.deployer_address[:6]}-{maya.now().epoch}.json'
        filepath = os.path.join(DEFAULT_CONFIG_ROOT, filename)
        # TODO: Do not assume default config root
        os.makedirs(DEFAULT_CONFIG_ROOT, exist_ok=True)
        with open(filepath, 'w') as file:
            data = dict()
            for contract_name, receipts in receipts.items():
                contract_records = dict()
                for tx_name, receipt in receipts.items():
                    # Formatting
                    receipt = {
                        item: str(result)
                        for item, result in receipt.items()
                    }
                    contract_records.update(
                        {tx_name: receipt
                         for tx_name in receipts})
                data[contract_name] = contract_records
            data = json.dumps(data, indent=4)
            file.write(data)
        return filepath