Esempio n. 1
0
class Deployer(NucypherTokenActor):

    # Registry of deployer classes
    deployer_classes = (
        NucypherTokenDeployer,
        StakingEscrowDeployer,
        PolicyManagerDeployer,
        AdjudicatorDeployer,
        UserEscrowProxyDeployer,
    )

    contract_names = tuple(a.registry_contract_name
                           for a in EthereumContractAgent.__subclasses__())

    __interface_class = BlockchainDeployerInterface

    class UnknownContract(ValueError):
        pass

    def __init__(self,
                 blockchain: BlockchainInterface,
                 deployer_address: str = None,
                 client_password: str = None,
                 bare: bool = True) -> None:

        self.blockchain = blockchain
        self.__deployer_address = NO_DEPLOYER_ADDRESS
        self.deployer_address = deployer_address
        self.checksum_address = self.deployer_address

        if not bare:
            self.token_agent = NucypherTokenAgent(blockchain=blockchain)
            self.staking_agent = StakingEscrowAgent(blockchain=blockchain)
            self.policy_agent = PolicyAgent(blockchain=blockchain)
            self.adjudicator_agent = AdjudicatorAgent(blockchain=blockchain)

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

        blockchain.transacting_power = TransactingPower(
            blockchain=blockchain,
            account=deployer_address,
            password=client_password)
        blockchain.transacting_power.activate()
        self.log = Logger("Deployment-Actor")

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

    @property
    def deployer_address(self):
        return self.blockchain.deployer_address

    @deployer_address.setter
    def deployer_address(self, value):
        """Used for validated post-init setting of deployer's address"""
        self.blockchain.deployer_address = value

    @property
    def token_balance(self) -> NU:
        if self.token_agent is CONTRACT_NOT_DEPLOYED:
            message = f"{self.token_agent.contract_name} contract is not deployed, or the registry has missing records."
            raise self.ActorError(message)
        return super().token_balance

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

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

        Deployer = self.__get_deployer(contract_name=contract_name)
        deployer = Deployer(blockchain=self.blockchain,
                            deployer_address=self.deployer_address)
        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)
        else:
            txhashes = deployer.deploy(gas_limit=gas_limit)
        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(blockchain=self.blockchain,
                            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(blockchain=self.blockchain,
                            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(
            blockchain=self.blockchain,
            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,
        staker_secret: str,
        policy_secret: str,
        adjudicator_secret: str,
        user_escrow_proxy_secret: str,
    ) -> Tuple[dict, dict]:
        """
        Musketeers, if you will; Deploy the "big three" contracts to the blockchain.
        """

        token_txs, token_deployer = self.deploy_contract(
            contract_name='NuCypherToken')
        staking_txs, staking_deployer = self.deploy_contract(
            contract_name='StakingEscrow', plaintext_secret=staker_secret)
        policy_txs, policy_deployer = self.deploy_contract(
            contract_name='PolicyManager', plaintext_secret=policy_secret)
        adjudicator_txs, adjudicator_deployer = self.deploy_contract(
            contract_name='Adjudicator', plaintext_secret=adjudicator_secret)
        user_escrow_proxy_txs, user_escrow_proxy_deployer = self.deploy_contract(
            contract_name='UserEscrowProxy',
            plaintext_secret=user_escrow_proxy_secret)

        deployers = (
            token_deployer,
            staking_deployer,
            policy_deployer,
            adjudicator_deployer,
            user_escrow_proxy_deployer,
        )

        txhashes = {
            NucypherTokenDeployer.contract_name: token_txs,
            StakingEscrowDeployer.contract_name: staking_txs,
            PolicyManagerDeployer.contract_name: policy_txs,
            AdjudicatorDeployer.contract_name: adjudicator_txs,
            UserEscrowProxyDeployer.contract_name: user_escrow_proxy_txs,
        }

        deployers = {
            deployer.contract_name: deployer
            for deployer in deployers
        }
        return txhashes, deployers

    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 = [{'address': '0xdeadbeef', 'amount': 100, 'duration': 31536000},
                {'address': '0xabced120', 'amount': 133432, 'duration': 31536000*2},
                {'address': '0xf7aefec2', 'amount': 999, 'duration': 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(
                registry_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'],
                    beneficiary_address=allocation['address'])
            except TransactionFailed:
                if crash_on_failure:
                    raise
                self.log.debug(
                    f"Failed allocation transaction for {allocation['amount']} to {allocation['address']}"
                )
                failed.append(allocation)
                continue

            else:
                allocation_txhashes[allocation['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, transactions: 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, transactions in transactions.items():
                contract_records = dict()
                for tx_name, txhash in transactions.items():
                    receipt = self.blockchain.client.wait_for_receipt(
                        txhash, timeout=self.blockchain.TIMEOUT)
                    receipt = {
                        item: str(result)
                        for item, result in receipt.items()
                    }
                    contract_records.update(
                        {tx_name: receipt
                         for tx_name in transactions})
                data[contract_name] = contract_records
            data = json.dumps(data, indent=4)
            file.write(data)
        return filepath
Esempio n. 2
0
def events(general_config, registry_options, contract_name, from_block,
           to_block, event_name, csv, csv_file, event_filters, legacy):
    """Show events associated with NuCypher contracts."""

    if csv or csv_file:
        if csv and csv_file:
            raise click.BadOptionUsage(
                option_name='--event-filter',
                message=click.style(
                    'Pass either --csv or --csv-file, not both.', fg="red"))

        # ensure that event name is specified - different events would have different columns in the csv file
        if csv_file and not all((event_name, contract_name)):
            # TODO consider a single csv that just gets appended to for each event
            #  - each appended event adds their column names first
            #  - single report-type functionality, see #2561
            raise click.BadOptionUsage(
                option_name='--csv-file, --event-name, --contract_name',
                message=click.style(
                    '--event-name and --contract-name must be specified when outputting to '
                    'specific file using --csv-file; alternatively use --csv',
                    fg="red"))
    if not contract_name:
        if event_name:
            raise click.BadOptionUsage(
                option_name='--event-name',
                message=click.style('--event-name requires --contract-name',
                                    fg="red"))
        # FIXME should we force a contract name to be specified?
        # default to PREApplication contract
        contract_names = [PREApplicationAgent.contract_name]
    else:
        contract_names = [contract_name]

    emitter, registry, blockchain = registry_options.setup(
        general_config=general_config)

    if from_block is None:
        # by default, this command only shows events of the current period
        blocks_since_yesterday_kinda = ((60 * 60 * 24) //
                                        AVERAGE_BLOCK_TIME_IN_SECONDS)
        from_block = blockchain.client.block_number - blocks_since_yesterday_kinda
    if to_block is None:
        to_block = 'latest'
    else:
        # validate block range
        if from_block > to_block:
            raise click.BadOptionUsage(
                option_name='--to-block, --from-block',
                message=click.style(
                    f'Invalid block range provided, '
                    f'from-block ({from_block}) > to-block ({to_block})',
                    fg="red"))

    # event argument filters
    argument_filters = None
    if event_filters:
        try:
            argument_filters = parse_event_filters_into_argument_filters(
                event_filters)
        except ValueError as e:
            raise click.BadOptionUsage(
                option_name='--event-filter',
                message=click.style(
                    f'Event filter must be specified as name-value pairs of '
                    f'the form `<name>=<value>` - {str(e)}',
                    fg="red"))

    emitter.echo(f"Retrieving events from block {from_block} to {to_block}")

    contract_version = None
    if legacy and contract_name in LEGACY_CONTRACT_VERSIONS:
        contract_version = LEGACY_CONTRACT_VERSIONS[contract_name]

    for contract_name in contract_names:
        if legacy:
            versioned_contract = blockchain.get_contract_by_name(
                registry=registry,
                contract_name=contract_name,
                contract_version=contract_version,
                proxy_name='Dispatcher',
                use_proxy_address=True)
            agent = EthereumContractAgent(contract=versioned_contract)
            agent.contract_name = contract_name
        else:
            agent = ContractAgency.get_agent_by_contract_name(
                contract_name=contract_name,
                contract_version=contract_version,
                registry=registry)

        if event_name and event_name not in agent.events.names:
            raise click.BadOptionUsage(
                option_name='--event-name, --contract_name',
                message=click.style(
                    f'{contract_name} contract does not have an event named {event_name}',
                    fg="red"))

        title = f" {agent.contract_name} Events ".center(40, "-")
        emitter.echo(f"\n{title}\n", bold=True, color='green')
        names = agent.events.names if not event_name else [event_name]
        for name in names:
            # csv output file - one per (contract_name, event_name) pair
            csv_output_file = csv_file
            if csv or csv_output_file:
                if not csv_output_file:
                    csv_output_file = generate_events_csv_filepath(
                        contract_name=agent.contract_name, event_name=name)

            retrieve_events(
                emitter=emitter,
                agent=agent,
                event_name=name,  # None is fine - just means all events
                from_block=from_block,
                to_block=to_block,
                argument_filters=argument_filters,
                csv_output_file=csv_output_file)
Esempio n. 3
0
def test_nucypher_deploy_contracts(click_runner, mock_allocation_infile,
                                   token_economics, registry_filepath):

    #
    # Main
    #

    command = [
        'contracts', '--registry-outfile', registry_filepath, '--provider',
        TEST_PROVIDER_URI, '--poa'
    ]

    user_input = '0\n' + 'Y\n' + (f'{INSECURE_SECRETS[1]}\n' * 8) + 'DEPLOY'
    result = click_runner.invoke(deploy,
                                 command,
                                 input=user_input,
                                 catch_exceptions=False)
    assert result.exit_code == 0

    # Ensure there is a report on each contract
    contract_names = tuple(a.registry_contract_name
                           for a in EthereumContractAgent.__subclasses__())
    for registry_name in contract_names:
        assert registry_name in result.output

    # Check that the primary contract registry was written
    # and peek at some of the registered entries
    assert os.path.isfile(registry_filepath)
    with open(registry_filepath, 'r') as file:

        # Ensure every contract's name was written to the file, somehow
        raw_registry_data = file.read()
        for registry_name in contract_names:
            assert registry_name in raw_registry_data

        # Ensure the Registry is JSON deserializable
        registry_data = json.loads(raw_registry_data)

        # and that is has the correct number of entries
        assert len(registry_data) == 9

        # Read several records
        token_record, escrow_record, dispatcher_record, *other_records = registry_data
        registered_name, registered_address, registered_abi = token_record

    #
    # Agency
    #
    registry = LocalContractRegistry(filepath=registry_filepath)

    token_agent = NucypherTokenAgent(registry=registry)
    assert token_agent.contract_name == registered_name
    assert token_agent.registry_contract_name == registered_name
    assert token_agent.contract_address == registered_address

    # Now show that we can use contract Agency and read from the blockchain
    assert token_agent.get_balance() == 0
    staking_agent = ContractAgency.get_agent(StakingEscrowAgent,
                                             registry=registry)
    assert staking_agent.get_current_period()

    # and at least the others can be instantiated
    assert PolicyManagerAgent(registry=registry)

    # This agent wasn't instantiated before, so we have to supply the blockchain
    blockchain = staking_agent.blockchain
    assert AdjudicatorAgent(registry=registry)
Esempio n. 4
0
class Deployer(NucypherTokenActor):

    # Registry of deployer classes
    deployers = (
        NucypherTokenDeployer,
        MinerEscrowDeployer,
        PolicyManagerDeployer,
        MiningAdjudicatorDeployer,
        UserEscrowProxyDeployer,
    )

    contract_names = tuple(a.registry_contract_name
                           for a in EthereumContractAgent.__subclasses__())

    __interface_class = BlockchainDeployerInterface

    def __init__(self,
                 blockchain: Blockchain,
                 deployer_address: str = None,
                 bare: bool = True) -> None:

        self.blockchain = blockchain
        self.__deployer_address = NO_DEPLOYER_ADDRESS
        if deployer_address:
            self.deployer_address = deployer_address

        if not bare:
            self.token_agent = NucypherTokenAgent(blockchain=blockchain)
            self.miner_agent = MinerAgent(blockchain=blockchain)
            self.policy_agent = PolicyAgent(blockchain=blockchain)
            self.adjudicator_agent = MiningAdjudicatorAgent(
                blockchain=blockchain)

        self.user_escrow_deployers = dict()

        self.deployers = {
            NucypherTokenDeployer.contract_name:
            self.deploy_token_contract,
            MinerEscrowDeployer.contract_name:
            self.deploy_miner_contract,
            PolicyManagerDeployer.contract_name:
            self.deploy_policy_contract,
            UserEscrowProxyDeployer.contract_name:
            self.deploy_escrow_proxy,
            MiningAdjudicatorDeployer.contract_name:
            self.deploy_mining_adjudicator_contract,
        }

        self.log = Logger("Deployment-Actor")

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

    @classmethod
    def from_blockchain(cls,
                        provider_uri: str,
                        registry=None,
                        *args,
                        **kwargs):
        blockchain = Blockchain.connect(provider_uri=provider_uri,
                                        registry=registry)
        instance = cls(blockchain=blockchain, *args, **kwargs)
        return instance

    @property
    def deployer_address(self):
        return self.blockchain.interface.deployer_address

    @deployer_address.setter
    def deployer_address(self, value):
        """Used for validated post-init setting of deployer's address"""
        self.blockchain.interface.deployer_address = value

    @property
    def token_balance(self) -> NU:
        if self.token_agent is CONTRACT_NOT_DEPLOYED:
            message = f"{self.token_agent.contract_name} contract is not deployed, or the registry has missing records."
            raise self.ActorError(message)
        return super().token_balance

    def deploy_token_contract(self) -> dict:
        token_deployer = NucypherTokenDeployer(
            blockchain=self.blockchain, deployer_address=self.deployer_address)
        txhashes = token_deployer.deploy()
        self.token_agent = token_deployer.make_agent()
        return txhashes

    def deploy_miner_contract(self, secret: bytes) -> dict:
        secret = self.blockchain.interface.w3.keccak(secret)
        miner_escrow_deployer = MinerEscrowDeployer(
            blockchain=self.blockchain,
            deployer_address=self.deployer_address,
            secret_hash=secret)

        txhashes = miner_escrow_deployer.deploy()
        self.miner_agent = miner_escrow_deployer.make_agent()
        return txhashes

    def deploy_policy_contract(self, secret: bytes) -> dict:
        secret = self.blockchain.interface.w3.keccak(secret)
        policy_manager_deployer = PolicyManagerDeployer(
            blockchain=self.blockchain,
            deployer_address=self.deployer_address,
            secret_hash=secret)

        txhashes = policy_manager_deployer.deploy()
        self.policy_agent = policy_manager_deployer.make_agent()
        return txhashes

    def deploy_mining_adjudicator_contract(self, secret: bytes) -> dict:
        secret = self.blockchain.interface.w3.keccak(secret)
        mining_adjudicator_deployer = MiningAdjudicatorDeployer(
            blockchain=self.blockchain,
            deployer_address=self.deployer_address,
            secret_hash=secret)

        txhashes = mining_adjudicator_deployer.deploy()
        self.adjudicator_agent = mining_adjudicator_deployer.make_agent()
        return txhashes

    def deploy_escrow_proxy(self, secret: bytes) -> dict:
        secret = self.blockchain.interface.w3.keccak(secret)
        escrow_proxy_deployer = UserEscrowProxyDeployer(
            blockchain=self.blockchain,
            deployer_address=self.deployer_address,
            secret_hash=secret)

        txhashes = escrow_proxy_deployer.deploy()
        return txhashes

    def deploy_user_escrow(
            self,
            allocation_registry: AllocationRegistry) -> UserEscrowDeployer:
        user_escrow_deployer = UserEscrowDeployer(
            blockchain=self.blockchain,
            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, miner_secret: bytes, policy_secret: bytes,
            adjudicator_secret: bytes) -> Tuple[dict, dict]:
        """
        Musketeers, if you will; Deploy the "big three" contracts to the blockchain.
        """
        token_txhashes = self.deploy_token_contract()
        miner_txhashes = self.deploy_miner_contract(secret=miner_secret)
        policy_txhashes = self.deploy_policy_contract(secret=policy_secret)
        adjudicator_txhashes = self.deploy_mining_adjudicator_contract(
            secret=adjudicator_secret)

        txhashes = {
            NucypherTokenDeployer.contract_name: token_txhashes,
            MinerEscrowDeployer.contract_name: miner_txhashes,
            PolicyManagerDeployer.contract_name: policy_txhashes,
            MiningAdjudicatorDeployer.contract_name: adjudicator_txhashes
        }

        agents = {
            NucypherTokenDeployer.contract_name: self.token_agent,
            MinerEscrowDeployer.contract_name: self.miner_agent,
            PolicyManagerDeployer.contract_name: self.policy_agent,
            MiningAdjudicatorDeployer.contract_name: self.adjudicator_agent
        }

        return txhashes, agents

    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 31540000 seconds):

        data = [{'address': '0xdeadbeef', 'amount': 100, 'duration': 31540000},
                {'address': '0xabced120', 'amount': 133432, 'duration': 31540000*2},
                {'address': '0xf7aefec2', 'amount': 999, 'duration': 31540000*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(
                registry_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'],
                    beneficiary_address=allocation['address'])
            except TransactionFailed:
                if crash_on_failure:
                    raise
                self.log.debug(
                    f"Failed allocation transaction for {allocation['amount']} to {allocation['address']}"
                )
                failed.append(allocation)
                continue

            else:
                allocation_txhashes[allocation['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