Example #1
0
def select_stake(staker: Staker,
                 emitter: StdoutEmitter,
                 stakes_status: Stake.Status = Stake.Status.EDITABLE,
                 filter_function: Callable[[Stake], bool] = None) -> Stake:
    """Interactively select a stake or abort if there are no eligible stakes."""

    if stakes_status.is_child(Stake.Status.DIVISIBLE):
        emitter.echo(ONLY_DISPLAYING_DIVISIBLE_STAKES_NOTE, color='yellow')

    # Filter stakes by status
    stakes = staker.sorted_stakes(parent_status=stakes_status,
                                  filter_function=filter_function)
    if not stakes:
        emitter.echo(NO_STAKES_FOUND, color='red')
        raise click.Abort

    # Interactive Selection
    paint_unlocked = stakes_status.is_child(Stake.Status.UNLOCKED)
    paint_stakes(staker=staker,
                 emitter=emitter,
                 stakes=stakes,
                 paint_unlocked=paint_unlocked)
    indexed_stakes = {stake.index: stake for stake in stakes}
    indices = [str(index) for index in indexed_stakes.keys()]
    choice = click.prompt(SELECT_STAKE, type=click.Choice(indices))
    chosen_stake = indexed_stakes[int(choice)]
    return chosen_stake
Example #2
0
    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
Example #3
0
def select_client_account_for_staking(emitter: StdoutEmitter,
                                      stakeholder: StakeHolder,
                                      staking_address: Optional[str],
                                      individual_allocation: Optional[IndividualAllocationRegistry],
                                      force: bool,
                                      ) -> Tuple[str, str]:
    """
    Manages client account selection for stake-related operations.
    It always returns a tuple of addresses: the first is the local client account and the second is the staking address.

    When this is not a preallocation staker (which is the normal use case), both addresses are the same.
    Otherwise, when the staker is a contract managed by a beneficiary account,
    then the local client account is the beneficiary, and the staking address is the address of the staking contract.
    """

    if individual_allocation:
        client_account = individual_allocation.beneficiary_address
        staking_address = individual_allocation.contract_address
        message = PREALLOCATION_STAKE_ADVISORY.format(client_account=client_account, staking_address=staking_address)
        emitter.echo(message, color='yellow', verbosity=1)
        if not force:
            click.confirm(IS_THIS_CORRECT, abort=True)
    else:
        if staking_address:
            client_account = staking_address
        else:
            client_account = select_client_account(prompt=SELECT_STAKING_ACCOUNT_INDEX,
                                                   emitter=emitter,
                                                   registry=stakeholder.registry,
                                                   network=stakeholder.network,
                                                   wallet=stakeholder.wallet)
            staking_address = client_account

    return client_account, staking_address
Example #4
0
def get_or_update_configuration(emitter: StdoutEmitter,
                                filepath: str,
                                config_class: Type[CharacterConfiguration],
                                updates: Optional[dict] = None) -> None:
    """
    Utility for writing updates to an existing configuration file then displaying the result.
    If the config file is invalid, try very hard to display the problem.  If there are no updates,
    the config file will be displayed without changes.
    """
    try:
        config = config_class.from_configuration_file(filepath=filepath)
    except FileNotFoundError:
        return handle_missing_configuration_file(
            character_config_class=config_class, config_file=filepath)
    except config_class.ConfigurationError:
        return handle_invalid_configuration_file(emitter=emitter,
                                                 config_class=config_class,
                                                 filepath=filepath)

    emitter.echo(
        f"{config_class.NAME.capitalize()} Configuration {filepath} \n {'='*55}"
    )
    if updates:
        pretty_fields = ', '.join(updates)
        emitter.message(SUCCESSFUL_UPDATE_CONFIGURATION_VALUES.format(
            fields=pretty_fields),
                        color='yellow')
        config.update(**updates)
    emitter.echo(config.serialize())
Example #5
0
def select_network(emitter: StdoutEmitter) -> str:
    """Interactively select a network from nucypher networks inventory list"""
    headers = ["Network"]
    rows = [[n] for n in NetworksInventory.NETWORKS]
    emitter.echo(tabulate(rows, headers=headers, showindex='always'))
    choice = click.prompt(SELECT_NETWORK, default=0, type=click.IntRange(0, len(NetworksInventory.NETWORKS)-1))
    network = NetworksInventory.NETWORKS[choice]
    return network
Example #6
0
def transfer_ownership(  # Admin Actor Options
        provider_uri,
        contract_name,
        config_root,
        poa,
        force,
        etherscan,
        hw_wallet,
        deployer_address,
        registry_infile,
        registry_outfile,
        dev,
        se_test_mode,

        # Other
        target_address,
        gas):
    """
    Transfer ownership of contracts to another address.
    """
    # Init
    emitter = StdoutEmitter()
    _ensure_config_root(config_root)
    deployer_interface = _initialize_blockchain(poa, provider_uri)

    # Warnings
    _pre_launch_warnings(emitter, etherscan, hw_wallet)

    #
    # Make Authenticated Deployment Actor
    #
    ADMINISTRATOR, deployer_address, local_registry = _make_authenticated_deployment_actor(
        emitter, provider_uri, deployer_address, deployer_interface,
        contract_name, registry_infile, registry_outfile, hw_wallet, dev,
        force, se_test_mode)

    if not target_address:
        target_address = click.prompt("Enter new owner's checksum address",
                                      type=EIP55_CHECKSUM_ADDRESS)

    if contract_name:
        try:
            contract_deployer_class = ADMINISTRATOR.deployers[contract_name]
        except KeyError:
            message = f"No such contract {contract_name}. Available contracts are {ADMINISTRATOR.deployers.keys()}"
            emitter.echo(message, color='red', bold=True)
            raise click.Abort()
        else:
            contract_deployer = contract_deployer_class(
                registry=ADMINISTRATOR.registry,
                deployer_address=ADMINISTRATOR.deployer_address)
            receipt = contract_deployer.transfer_ownership(
                new_owner=target_address, transaction_gas_limit=gas)
            emitter.ipc(receipt, request_id=0, duration=0)  # TODO: #1216
    else:
        receipts = ADMINISTRATOR.relinquish_ownership(
            new_owner=target_address, transaction_gas_limit=gas)
        emitter.ipc(receipts, request_id=0, duration=0)  # TODO: #1216
Example #7
0
def transfer_tokens(  # Admin Actor Options
        provider_uri,
        contract_name,
        config_root,
        poa,
        force,
        etherscan,
        hw_wallet,
        deployer_address,
        registry_infile,
        registry_outfile,
        dev,
        se_test_mode,

        # Other
        target_address,
        value):
    """
    Transfer tokens from contract's owner address to another address
    """
    # Init
    emitter = StdoutEmitter()
    _ensure_config_root(config_root)
    deployer_interface = _initialize_blockchain(poa, provider_uri)

    # Warnings
    _pre_launch_warnings(emitter, etherscan, hw_wallet)

    #
    # Make Authenticated Deployment Actor
    #
    ADMINISTRATOR, deployer_address, local_registry = _make_authenticated_deployment_actor(
        emitter, provider_uri, deployer_address, deployer_interface,
        contract_name, registry_infile, registry_outfile, hw_wallet, dev,
        force, se_test_mode)

    token_agent = ContractAgency.get_agent(NucypherTokenAgent,
                                           registry=local_registry)
    if not target_address:
        target_address = click.prompt("Enter recipient's checksum address",
                                      type=EIP55_CHECKSUM_ADDRESS)
    if not value:
        stake_value_range = click.FloatRange(min=0, clamp=False)
        value = NU.from_tokens(
            click.prompt(f"Enter value in NU", type=stake_value_range))

    click.confirm(
        f"Transfer {value} from {deployer_address} to {target_address}?",
        abort=True)
    receipt = token_agent.transfer(amount=value,
                                   sender_address=deployer_address,
                                   target_address=target_address)
    emitter.echo(f"OK | Receipt: {receipt['transactionHash'].hex()}")
Example #8
0
    def sync(self, show_progress: bool = False) -> None:

        sync_state = self.client.sync()
        if show_progress:
            import click
            # TODO: #1503 - It is possible that output has been redirected from a higher-level emitter.
            # TODO: #1503 - Use console logging instead of StdOutEmitter here.
            emitter = StdoutEmitter()

            emitter.echo(
                f"Syncing: {self.client.chain_name.capitalize()}. Waiting for sync to begin."
            )

            while not len(self.client.peers):
                emitter.echo("waiting for peers...")
                time.sleep(5)

            peer_count = len(self.client.peers)
            emitter.echo(
                f"Found {'an' if peer_count == 1 else peer_count} Ethereum peer{('s' if peer_count > 1 else '')}."
            )

            try:
                emitter.echo("Beginning sync...")
                initial_state = next(sync_state)
            except StopIteration:  # will occur if no syncing needs to happen
                emitter.echo("Local blockchain data is already synced.")
                return

            prior_state = initial_state
            total_blocks_to_sync = int(initial_state.get(
                'highestBlock', 0)) - int(initial_state.get('currentBlock', 0))
            with click.progressbar(length=total_blocks_to_sync,
                                   label="sync progress") as bar:
                for syncdata in sync_state:
                    if syncdata:
                        blocks_accomplished = int(
                            syncdata['currentBlock']) - int(
                                prior_state.get('currentBlock', 0))
                        bar.update(blocks_accomplished)
                        prior_state = syncdata
        else:
            try:
                for syncdata in sync_state:
                    self.client.log.info(
                        f"Syncing {syncdata['currentBlock']}/{syncdata['highestBlock']}"
                    )
            except TypeError:  # it's already synced
                return
        return
Example #9
0
def confirm_deployment(emitter: StdoutEmitter, deployer_interface: BlockchainDeployerInterface) -> bool:
    """
    Interactively confirm deployment by asking the user to type the ALL CAPS name of
    the network they are deploying to or 'DEPLOY' if the network if not a known public chain.

    Aborts if the confirmation word is incorrect.
    """
    if deployer_interface.client.chain_name == UNKNOWN_DEVELOPMENT_CHAIN_ID or deployer_interface.client.is_local:
        expected_chain_name = 'DEPLOY'
    else:
        expected_chain_name = deployer_interface.client.chain_name
    if click.prompt(f"Type '{expected_chain_name.upper()}' to continue") != expected_chain_name.upper():
        emitter.echo(ABORT_DEPLOYMENT, color='red', bold=True)
        raise click.Abort(ABORT_DEPLOYMENT)
    return True
Example #10
0
def handle_invalid_configuration_file(emitter: StdoutEmitter,
                                      config_class: Type[CharacterConfiguration],
                                      filepath: str) -> None:
    """
    Attempt to deserialize a config file that is not a valid nucypher character configuration
    as a means of user-friendly debugging. :-)  I hope this helps!
    """
    # Issue warning for invalid configuration...
    emitter.message(INVALID_CONFIGURATION_FILE_WARNING.format(filepath=filepath))
    try:
        # ... but try to display it anyways
        response = config_class._read_configuration_file(filepath=filepath)
        emitter.echo(json.dumps(response, indent=4))
        raise config_class.ConfigurationError
    except (TypeError, JSONDecodeError):
        emitter.message(INVALID_JSON_IN_CONFIGURATION_WARNING.format(filepath=filepath))
        # ... sorry.. we tried as hard as we could
        raise  # crash :-(
Example #11
0
def connect_to_blockchain(emitter: StdoutEmitter,
                          provider_uri: str,
                          debug: bool = False,
                          light: bool = False) -> BlockchainInterface:
    try:
        # Note: Conditional for test compatibility.
        if not BlockchainInterfaceFactory.is_interface_initialized(
                provider_uri=provider_uri):
            BlockchainInterfaceFactory.initialize_interface(
                provider_uri=provider_uri, light=light, emitter=emitter)
        emitter.echo(message=CONNECTING_TO_BLOCKCHAIN)
        blockchain = BlockchainInterfaceFactory.get_interface(
            provider_uri=provider_uri)
        return blockchain
    except Exception as e:
        if debug:
            raise
        emitter.echo(str(e), bold=True, color='red')
        raise click.Abort
Example #12
0
def paint_all_stakes(emitter: StdoutEmitter,
                     stakeholder: 'StakeHolder',
                     paint_unlocked: bool = False) -> None:

    stakers = stakeholder.get_stakers()
    if not stakers:
        emitter.echo("No staking accounts found.")

    total_stakers = 0
    for staker in stakers:
        if not staker.stakes:
            # This staker has no active stakes.
            # TODO: Something with non-staking accounts?
            continue

        paint_stakes(emitter=emitter, staker=staker, paint_unlocked=paint_unlocked, stakeholder=stakeholder)
        total_stakers += 1

    if not total_stakers:
        emitter.echo("No Stakes found", color='red')
Example #13
0
def select_stake(stakeholder: StakeHolder,
                 emitter: StdoutEmitter,
                 divisible: bool = False,
                 staker_address: str = None
                 ) -> Stake:
    """Interactively select a stake or abort if there are no eligible stakes."""

    # Precondition: Active Stakes
    if staker_address:
        staker = stakeholder.get_staker(checksum_address=staker_address)
        stakes = staker.stakes
    else:
        stakes = stakeholder.all_stakes
    if not stakes:
        emitter.echo(NO_STAKES_FOUND, color='red')
        raise click.Abort

    # Precondition: Divisible Stakes
    stakes = stakeholder.sorted_stakes
    if divisible:
        emitter.echo(ONLY_DISPLAYING_DIVISIBLE_STAKES_NOTE, color='yellow')
        stakes = stakeholder.divisible_stakes
        if not stakes:
            emitter.echo(NO_DIVISIBLE_STAKES, color='red')
            raise click.Abort

    # Interactive Selection
    enumerated_stakes = dict(enumerate(stakes))
    paint_stakes(stakeholder=stakeholder, emitter=emitter, staker_address=staker_address)
    choice = click.prompt(SELECT_STAKE, type=click.IntRange(min=0, max=len(enumerated_stakes)-1))
    chosen_stake = enumerated_stakes[choice]
    return chosen_stake
Example #14
0
def deployer_pre_launch_warnings(emitter: StdoutEmitter, etherscan: bool, hw_wallet: bool) -> None:
    if not hw_wallet:
        emitter.echo(NO_HARDWARE_WALLET_WARNING, color='yellow')
    if etherscan:
        emitter.echo(ETHERSCAN_FLAG_ENABLED_WARNING, color='yellow')
    else:
        emitter.echo(ETHERSCAN_FLAG_DISABLED_WARNING, color='yellow')
Example #15
0
def retrieve_events(emitter: StdoutEmitter,
                    agent: EthereumContractAgent,
                    event_name: str,
                    from_block: BlockIdentifier,
                    to_block: BlockIdentifier,
                    argument_filters: Dict,
                    csv_output_file: Optional[str] = None) -> None:
    if csv_output_file:
        if Path(csv_output_file).exists():
            click.confirm(CONFIRM_OVERWRITE_EVENTS_CSV_FILE.format(
                csv_file=csv_output_file),
                          abort=True)
        available_events = write_events_to_csv_file(
            csv_file=csv_output_file,
            agent=agent,
            event_name=event_name,
            from_block=from_block,
            to_block=to_block,
            argument_filters=argument_filters)
        if available_events:
            emitter.echo(
                f"{agent.contract_name}::{event_name} events written to {csv_output_file}",
                bold=True,
                color='green')
        else:
            emitter.echo(
                f'No {agent.contract_name}::{event_name} events found',
                color='yellow')
    else:
        event = agent.contract.events[event_name]
        emitter.echo(f"{event_name}:", bold=True, color='yellow')
        entries = event.getLogs(fromBlock=from_block,
                                toBlock=to_block,
                                argument_filters=argument_filters)
        for event_record in entries:
            emitter.echo(f"  - {EventRecord(event_record)}")
Example #16
0
    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
Example #17
0
def paint_stakes(emitter: StdoutEmitter,
                 staker: 'Staker',
                 stakes: List[Stake] = None,
                 paint_unlocked: bool = False,
                 stakeholder=None) -> None:

    stakes = stakes or staker.sorted_stakes()

    fees = staker.policy_agent.get_fee_amount(staker.checksum_address)
    pretty_fees = prettify_eth_amount(fees)
    last_committed = staker.staking_agent.get_last_committed_period(
        staker.checksum_address)
    missing = staker.missing_commitments
    min_fee_rate = prettify_eth_amount(staker.min_fee_rate)

    if missing == -1:
        missing_info = "Never Made a Commitment (New Stake)"
    else:
        missing_info = f'Missing {missing} commitments{"s" if missing > 1 else ""}' if missing else f'Committed #{last_committed}'

    staker_data = [
        missing_info, "Yes" if staker.is_restaking else "No",
        "Yes" if bool(staker.is_winding_down) else "No",
        "Yes" if bool(staker.is_taking_snapshots) else "No", pretty_fees,
        min_fee_rate
    ]

    line_width = 54
    if staker.registry.source:  # TODO: #1580 - Registry source might be Falsy in tests.
        network_snippet = f"\nNetwork {staker.registry.source.network.capitalize()} "
        snippet_with_line = network_snippet + '═' * (line_width -
                                                     len(network_snippet) + 1)
        emitter.echo(snippet_with_line, bold=True)
    emitter.echo(f"Staker {staker.checksum_address} ════",
                 bold=True,
                 color='red' if missing else 'green')
    worker = staker.worker_address if staker.worker_address != NULL_ADDRESS else 'not bonded'
    emitter.echo(
        f"Worker {worker} ════",
        color='red' if staker.worker_address == NULL_ADDRESS else None)
    if stakeholder and stakeholder.worker_data:
        worker_data = stakeholder.worker_data.get(staker.checksum_address)
        if worker_data:
            emitter.echo(f"\t public address: {worker_data['publicaddress']}")
            if worker_data.get('nucypher version'):
                emitter.echo(
                    f"\t NuCypher Version: {worker_data['nucypher version']}")
            if worker_data.get('blockchain_provider'):
                emitter.echo(
                    f"\t Blockchain Provider: {worker_data['blockchain_provider']}"
                )
    emitter.echo(
        tabulate.tabulate(zip(STAKER_TABLE_COLUMNS, staker_data),
                          floatfmt="fancy_grid"))

    rows, inactive_substakes = list(), list()
    for index, stake in enumerate(stakes):
        is_inactive = False

        if stake.status().is_child(Stake.Status.INACTIVE):
            inactive_substakes.append(index)
            is_inactive = True

        if stake.status().is_child(
                Stake.Status.UNLOCKED) and not paint_unlocked:
            # This stake is unlocked.
            continue

        stake_description = stake.describe()
        if is_inactive:
            # stake is inactive - update display values since they don't make much sense to display
            stake_description['remaining'] = 'N/A'
            stake_description['last_period'] = 'N/A'
            stake_description['boost'] = 'N/A'

        rows.append(list(stake_description.values()))

    if not rows:
        emitter.echo(f"There are no locked stakes\n")

    emitter.echo(
        tabulate.tabulate(rows,
                          headers=STAKE_TABLE_COLUMNS,
                          tablefmt="fancy_grid"))  # newline

    if not paint_unlocked and inactive_substakes:
        emitter.echo(
            f"Note that some sub-stakes are inactive: {inactive_substakes}\n"
            f"Run `nucypher stake list --all` to show all sub-stakes.\n"
            f"Run `nucypher stake remove-inactive --all` to remove inactive sub-stakes; removal of inactive "
            f"sub-stakes will reduce commitment gas costs.",
            color='yellow')
Example #18
0
def paint_stakes(emitter: StdoutEmitter,
                 staker: 'Staker',
                 stakes: List[Stake] = None,
                 paint_unlocked: bool = False) -> None:

    stakes = stakes or staker.sorted_stakes()

    fees = staker.policy_agent.get_fee_amount(staker.checksum_address)
    pretty_fees = prettify_eth_amount(fees)
    last_committed = staker.staking_agent.get_last_committed_period(
        staker.checksum_address)
    missing = staker.missing_commitments
    min_fee_rate = prettify_eth_amount(staker.min_fee_rate)

    if missing == -1:
        missing_info = "Never Made a Commitment (New Stake)"
    else:
        missing_info = f'Missing {missing} commitments{"s" if missing > 1 else ""}' if missing else f'Committed #{last_committed}'

    staker_data = [
        missing_info,
        f'{"Yes" if staker.is_restaking else "No"} ({"Locked" if staker.restaking_lock_enabled else "Unlocked"})',
        "Yes" if bool(staker.is_winding_down) else "No", pretty_fees,
        min_fee_rate
    ]

    line_width = 54
    if staker.registry.source:  # TODO: #1580 - Registry source might be Falsy in tests.
        network_snippet = f"\nNetwork {staker.registry.source.network.capitalize()} "
        snippet_with_line = network_snippet + '═' * (line_width -
                                                     len(network_snippet) + 1)
        emitter.echo(snippet_with_line, bold=True)
    emitter.echo(f"Staker {staker.checksum_address} ════",
                 bold=True,
                 color='red' if missing else 'green')
    worker = staker.worker_address if staker.worker_address != NULL_ADDRESS else 'not bonded'
    emitter.echo(
        f"Worker {worker} ════",
        color='red' if staker.worker_address == NULL_ADDRESS else None)
    emitter.echo(
        tabulate.tabulate(zip(STAKER_TABLE_COLUMNS, staker_data),
                          floatfmt="fancy_grid"))

    rows = list()
    for index, stake in enumerate(stakes):
        if stake.status().is_child(
                Stake.Status.UNLOCKED) and not paint_unlocked:
            # This stake is unlocked.
            continue
        rows.append(list(stake.describe().values()))

    if not rows:
        emitter.echo(f"There are no locked stakes\n")

    emitter.echo(
        tabulate.tabulate(rows,
                          headers=STAKE_TABLE_COLUMNS,
                          tablefmt="fancy_grid"))  # newline
Example #19
0
def deploy(action, poa, etherscan, provider_uri, gas, deployer_address,
           contract_name, allocation_infile, allocation_outfile,
           registry_infile, registry_outfile, amount, recipient_address,
           config_root, hw_wallet, force, dev):
    """
    Manage contract and registry deployment.

    \b
    Actions
    -----------------------------------------------------------------------------
    contracts              Compile and deploy contracts.
    allocations            Deploy pre-allocation contracts.
    upgrade                Upgrade NuCypher existing proxy contract deployments.
    rollback               Rollback a proxy contract's target.
    status                 Echo owner information and bare contract metadata.
    transfer-tokens        Transfer tokens to another address.
    transfer-ownership     Transfer ownership of contracts to another address.
    """

    emitter = StdoutEmitter()

    #
    # Validate
    #

    # Ensure config root exists, because we need a default place to put output files.
    config_root = config_root or DEFAULT_CONFIG_ROOT
    if not os.path.exists(config_root):
        os.makedirs(config_root)

    #
    # Pre-Launch Warnings
    #

    if not hw_wallet:
        emitter.echo("WARNING: --no-hw-wallet is enabled.", color='yellow')

    if etherscan:
        emitter.echo(
            "WARNING: --etherscan is enabled. "
            "A browser tab will be opened with deployed contracts and TXs as provided by Etherscan.",
            color='yellow')
    else:
        emitter.echo(
            "WARNING: --etherscan is disabled. "
            "If you want to see deployed contracts and TXs in your browser, activate --etherscan.",
            color='yellow')

    #
    # Connect to Registry
    #

    # Establish a contract registry from disk if specified
    registry_filepath = registry_outfile or registry_infile
    if dev:
        # TODO: Need a way to detect a geth--dev registry filepath here. (then deprecate the --dev flag)
        registry_filepath = os.path.join(DEFAULT_CONFIG_ROOT,
                                         'dev_contract_registry.json')
    registry = EthereumContractRegistry(registry_filepath=registry_filepath)
    emitter.echo(f"Using contract registry filepath {registry.filepath}")

    #
    # Connect to Blockchain
    #

    blockchain = BlockchainDeployerInterface(provider_uri=provider_uri,
                                             poa=poa,
                                             registry=registry)
    try:
        blockchain.connect(fetch_registry=False, sync_now=False)
    except BlockchainDeployerInterface.ConnectionFailed as e:
        emitter.echo(str(e), color='red', bold=True)
        raise click.Abort()

    #
    # Make Authenticated Deployment Actor
    #

    # Verify Address & collect password
    if not deployer_address:
        prompt = "Select deployer account"
        deployer_address = select_client_account(emitter=emitter,
                                                 blockchain=blockchain,
                                                 prompt=prompt)

    if not force:
        click.confirm("Selected {} - Continue?".format(deployer_address),
                      abort=True)

    password = None
    if not hw_wallet and not blockchain.client.is_local:
        password = get_client_password(checksum_address=deployer_address)

    # Produce Actor
    DEPLOYER = DeployerActor(blockchain=blockchain,
                             client_password=password,
                             deployer_address=deployer_address)

    # Verify ETH Balance
    emitter.echo(f"\n\nDeployer ETH balance: {DEPLOYER.eth_balance}")
    if DEPLOYER.eth_balance == 0:
        emitter.echo("Deployer address has no ETH.", color='red', bold=True)
        raise click.Abort()

    #
    # Action switch
    #

    if action == 'upgrade':
        if not contract_name:
            raise click.BadArgumentUsage(
                message="--contract-name is required when using --upgrade")
        existing_secret = click.prompt(
            'Enter existing contract upgrade secret', hide_input=True)
        new_secret = click.prompt('Enter new contract upgrade secret',
                                  hide_input=True,
                                  confirmation_prompt=True)
        DEPLOYER.upgrade_contract(contract_name=contract_name,
                                  existing_plaintext_secret=existing_secret,
                                  new_plaintext_secret=new_secret)
        return  # Exit

    elif action == 'rollback':
        if not contract_name:
            raise click.BadArgumentUsage(
                message="--contract-name is required when using --rollback")
        existing_secret = click.prompt(
            'Enter existing contract upgrade secret', hide_input=True)
        new_secret = click.prompt('Enter new contract upgrade secret',
                                  hide_input=True,
                                  confirmation_prompt=True)
        DEPLOYER.rollback_contract(contract_name=contract_name,
                                   existing_plaintext_secret=existing_secret,
                                   new_plaintext_secret=new_secret)
        return  # Exit

    elif action == "contracts":

        #
        # Deploy Single Contract (Amend Registry)
        #

        if contract_name:
            try:
                contract_deployer = DEPLOYER.deployers[contract_name]
            except KeyError:
                message = f"No such contract {contract_name}. Available contracts are {DEPLOYER.deployers.keys()}"
                emitter.echo(message, color='red', bold=True)
                raise click.Abort()
            else:
                emitter.echo(f"Deploying {contract_name}")
                if contract_deployer._upgradeable:
                    secret = DEPLOYER.collect_deployment_secret(
                        deployer=contract_deployer)
                    receipts, agent = DEPLOYER.deploy_contract(
                        contract_name=contract_name, plaintext_secret=secret)
                else:
                    receipts, agent = DEPLOYER.deploy_contract(
                        contract_name=contract_name, gas_limit=gas)
                paint_contract_deployment(
                    contract_name=contract_name,
                    contract_address=agent.contract_address,
                    receipts=receipts,
                    emitter=emitter,
                    chain_name=blockchain.client.chain_name,
                    open_in_browser=etherscan)
            return  # Exit

        #
        # Deploy Automated Series (Create Registry)
        #

        # Confirm filesystem registry writes.
        registry_filepath = DEPLOYER.blockchain.registry.filepath
        if os.path.isfile(registry_filepath):
            emitter.echo(
                f"\nThere is an existing contract registry at {registry_filepath}.\n"
                f"Did you mean 'nucypher-deploy upgrade'?\n",
                color='yellow')
            click.confirm("*DESTROY* existing local registry and continue?",
                          abort=True)
            os.remove(registry_filepath)

        # Stage Deployment
        secrets = DEPLOYER.collect_deployment_secrets()
        paint_staged_deployment(deployer=DEPLOYER, emitter=emitter)

        # Confirm Trigger Deployment
        if not actions.confirm_deployment(emitter=emitter, deployer=DEPLOYER):
            raise click.Abort()

        # Delay - Last chance to abort via KeyboardInterrupt
        paint_deployment_delay(emitter=emitter)

        # Execute Deployment
        deployment_receipts = DEPLOYER.deploy_network_contracts(
            secrets=secrets,
            emitter=emitter,
            interactive=not force,
            etherscan=etherscan)

        # Paint outfile paths
        registry_outfile = DEPLOYER.blockchain.registry.filepath
        emitter.echo('Generated registry {}'.format(registry_outfile),
                     bold=True,
                     color='blue')

        # Save transaction metadata
        receipts_filepath = DEPLOYER.save_deployment_receipts(
            receipts=deployment_receipts)
        emitter.echo(f"Saved deployment receipts to {receipts_filepath}",
                     color='blue',
                     bold=True)
        return  # Exit

    elif action == "allocations":
        if not allocation_infile:
            allocation_infile = click.prompt("Enter allocation data filepath")
        click.confirm("Continue deploying and allocating?", abort=True)
        DEPLOYER.deploy_beneficiaries_from_file(
            allocation_data_filepath=allocation_infile,
            allocation_outfile=allocation_outfile)
        return  # Exit

    elif action == "transfer":
        token_agent = NucypherTokenAgent(blockchain=blockchain)
        missing_options = list()
        if recipient_address is None:
            missing_options.append("--recipient-address")
        if amount is None:
            missing_options.append("--amount")
        if missing_options:
            raise click.BadOptionUsage(
                f"Need {' and '.join(missing_options)} to transfer tokens.")

        click.confirm(
            f"Transfer {amount} from {deployer_address} to {recipient_address}?",
            abort=True)
        receipt = token_agent.transfer(amount=amount,
                                       sender_address=deployer_address,
                                       target_address=recipient_address)
        emitter.echo(f"OK | Receipt: {receipt['transactionHash'].hex()}")
        return  # Exit

    else:
        raise click.BadArgumentUsage(message=f"Unknown action '{action}'")
Example #20
0
        sys.exit(-1)

try:
    network = sys.argv[1]
except IndexError:
    network = "ibex"

BlockchainInterfaceFactory.initialize_interface(provider_uri=provider_uri,
                                                light=False,
                                                sync=False,
                                                emitter=emitter)

blockchain = BlockchainInterfaceFactory.get_interface(
    provider_uri=provider_uri)

emitter.echo(message="Reading Latest Chaindata...")
blockchain.connect()

registry = InMemoryContractRegistry.from_latest_publication(network=network)

emitter.echo(f"NOTICE: Connecting to {network} network", color='yellow')

staking_agent = ContractAgency.get_agent(
    agent_class=StakingEscrowAgent,
    registry=registry)  # type: StakingEscrowAgent
policy_agent = ContractAgency.get_agent(
    agent_class=PolicyManagerAgent,
    registry=registry)  # type: PolicyManagerAgent
token_agent = ContractAgency.get_agent(
    agent_class=NucypherTokenAgent,
    registry=registry)  # type: NucypherTokenAgent
Example #21
0
def deploy(action, poa, etherscan, provider_uri, gas, deployer_address,
           contract_name, allocation_infile, allocation_outfile,
           registry_infile, registry_outfile, value, target_address,
           config_root, hw_wallet, force, dev):
    """
    Manage contract and registry deployment.

    \b
    Actions
    -----------------------------------------------------------------------------
    contracts              Compile and deploy contracts.
    allocations            Deploy pre-allocation contracts.
    upgrade                Upgrade NuCypher existing proxy contract deployments.
    rollback               Rollback a proxy contract's target.
    inspect                Echo owner information and bare contract metadata.
    transfer-tokens        Transfer tokens from a contract to another address using the owner's address.
    transfer-ownership     Transfer ownership of contracts to another address.
    """

    emitter = StdoutEmitter()

    #
    # Validate
    #

    # Ensure config root exists, because we need a default place to put output files.
    config_root = config_root or DEFAULT_CONFIG_ROOT
    if not os.path.exists(config_root):
        os.makedirs(config_root)

    if not provider_uri:
        raise click.BadOptionUsage(
            message=f"--provider is required to deploy.",
            option_name="--provider")

    #
    # Pre-Launch Warnings
    #

    if not hw_wallet:
        emitter.echo("WARNING: --no-hw-wallet is enabled.", color='yellow')

    if etherscan:
        emitter.echo(
            "WARNING: --etherscan is enabled. "
            "A browser tab will be opened with deployed contracts and TXs as provided by Etherscan.",
            color='yellow')
    else:
        emitter.echo(
            "WARNING: --etherscan is disabled. "
            "If you want to see deployed contracts and TXs in your browser, activate --etherscan.",
            color='yellow')

    #
    # Connect to Blockchain
    #

    if not BlockchainInterfaceFactory.is_interface_initialized(
            provider_uri=provider_uri):
        # Note: For test compatibility.
        deployer_interface = BlockchainDeployerInterface(
            provider_uri=provider_uri, poa=poa)
        BlockchainInterfaceFactory.register_interface(
            interface=deployer_interface, sync=False, show_sync_progress=False)
    else:
        deployer_interface = BlockchainInterfaceFactory.get_interface(
            provider_uri=provider_uri)

    if action == "inspect":
        if registry_infile:
            registry = LocalContractRegistry(filepath=registry_infile)
        else:
            registry = InMemoryContractRegistry.from_latest_publication()
        administrator = ContractAdministrator(
            registry=registry, deployer_address=deployer_address)
        paint_deployer_contract_inspection(emitter=emitter,
                                           administrator=administrator)
        return  # Exit

    #
    # Establish Registry
    #

    # Establish a contract registry from disk if specified
    default_registry_filepath = os.path.join(
        DEFAULT_CONFIG_ROOT, BaseContractRegistry.REGISTRY_NAME)
    registry_filepath = (registry_outfile
                         or registry_infile) or default_registry_filepath
    if dev:
        # TODO: Need a way to detect a geth --dev registry filepath here. (then deprecate the --dev flag)
        registry_filepath = os.path.join(config_root,
                                         'dev_contract_registry.json')
    registry = LocalContractRegistry(filepath=registry_filepath)
    emitter.message(f"Configured to registry filepath {registry_filepath}")

    #
    # Make Authenticated Deployment Actor
    #

    # Verify Address & collect password
    if not deployer_address:
        prompt = "Select deployer account"
        deployer_address = select_client_account(emitter=emitter,
                                                 prompt=prompt,
                                                 provider_uri=provider_uri,
                                                 show_balances=False)

    if not force:
        click.confirm("Selected {} - Continue?".format(deployer_address),
                      abort=True)

    password = None
    if not hw_wallet and not deployer_interface.client.is_local:
        password = get_client_password(checksum_address=deployer_address)

    # Produce Actor
    ADMINISTRATOR = ContractAdministrator(registry=registry,
                                          client_password=password,
                                          deployer_address=deployer_address)

    # Verify ETH Balance
    emitter.echo(f"\n\nDeployer ETH balance: {ADMINISTRATOR.eth_balance}")
    if ADMINISTRATOR.eth_balance == 0:
        emitter.echo("Deployer address has no ETH.", color='red', bold=True)
        raise click.Abort()

    #
    # Action switch
    #

    if action == 'upgrade':
        if not contract_name:
            raise click.BadArgumentUsage(
                message="--contract-name is required when using --upgrade")
        existing_secret = click.prompt(
            'Enter existing contract upgrade secret', hide_input=True)
        new_secret = click.prompt('Enter new contract upgrade secret',
                                  hide_input=True,
                                  confirmation_prompt=True)
        ADMINISTRATOR.upgrade_contract(
            contract_name=contract_name,
            existing_plaintext_secret=existing_secret,
            new_plaintext_secret=new_secret)
        return  # Exit

    elif action == 'rollback':
        if not contract_name:
            raise click.BadArgumentUsage(
                message="--contract-name is required when using --rollback")
        existing_secret = click.prompt(
            'Enter existing contract upgrade secret', hide_input=True)
        new_secret = click.prompt('Enter new contract upgrade secret',
                                  hide_input=True,
                                  confirmation_prompt=True)
        ADMINISTRATOR.rollback_contract(
            contract_name=contract_name,
            existing_plaintext_secret=existing_secret,
            new_plaintext_secret=new_secret)
        return  # Exit

    elif action == "contracts":

        #
        # Deploy Single Contract (Amend Registry)
        #

        if contract_name:
            try:
                contract_deployer = ADMINISTRATOR.deployers[contract_name]
            except KeyError:
                message = f"No such contract {contract_name}. Available contracts are {ADMINISTRATOR.deployers.keys()}"
                emitter.echo(message, color='red', bold=True)
                raise click.Abort()
            else:
                emitter.echo(f"Deploying {contract_name}")
                if contract_deployer._upgradeable:
                    secret = ADMINISTRATOR.collect_deployment_secret(
                        deployer=contract_deployer)
                    receipts, agent = ADMINISTRATOR.deploy_contract(
                        contract_name=contract_name, plaintext_secret=secret)
                else:
                    receipts, agent = ADMINISTRATOR.deploy_contract(
                        contract_name=contract_name, gas_limit=gas)
                paint_contract_deployment(
                    contract_name=contract_name,
                    contract_address=agent.contract_address,
                    receipts=receipts,
                    emitter=emitter,
                    chain_name=deployer_interface.client.chain_name,
                    open_in_browser=etherscan)
            return  # Exit

        #
        # Deploy Automated Series (Create Registry)
        #

        # Confirm filesystem registry writes.
        if os.path.isfile(registry_filepath):
            emitter.echo(
                f"\nThere is an existing contract registry at {registry_filepath}.\n"
                f"Did you mean 'nucypher-deploy upgrade'?\n",
                color='yellow')
            click.confirm("*DESTROY* existing local registry and continue?",
                          abort=True)
            os.remove(registry_filepath)

        # Stage Deployment
        secrets = ADMINISTRATOR.collect_deployment_secrets()
        paint_staged_deployment(deployer_interface=deployer_interface,
                                administrator=ADMINISTRATOR,
                                emitter=emitter)

        # Confirm Trigger Deployment
        if not confirm_deployment(emitter=emitter,
                                  deployer_interface=deployer_interface):
            raise click.Abort()

        # Delay - Last chance to abort via KeyboardInterrupt
        paint_deployment_delay(emitter=emitter)

        # Execute Deployment
        deployment_receipts = ADMINISTRATOR.deploy_network_contracts(
            secrets=secrets,
            emitter=emitter,
            interactive=not force,
            etherscan=etherscan)

        # Paint outfile paths
        registry_outfile = registry_filepath
        emitter.echo('Generated registry {}'.format(registry_outfile),
                     bold=True,
                     color='blue')

        # Save transaction metadata
        receipts_filepath = ADMINISTRATOR.save_deployment_receipts(
            receipts=deployment_receipts)
        emitter.echo(f"Saved deployment receipts to {receipts_filepath}",
                     color='blue',
                     bold=True)
        return  # Exit

    elif action == "allocations":
        if not allocation_infile:
            allocation_infile = click.prompt("Enter allocation data filepath")
        click.confirm("Continue deploying and allocating?", abort=True)
        ADMINISTRATOR.deploy_beneficiaries_from_file(
            allocation_data_filepath=allocation_infile,
            allocation_outfile=allocation_outfile)
        return  # Exit

    elif action == "transfer-tokens":
        token_agent = ContractAgency.get_agent(NucypherTokenAgent,
                                               registry=registry)
        if not target_address:
            target_address = click.prompt("Enter recipient's checksum address",
                                          type=EIP55_CHECKSUM_ADDRESS)
        if not value:
            stake_value_range = click.FloatRange(min=0, clamp=False)
            value = NU.from_tokens(
                click.prompt(f"Enter value in NU", type=stake_value_range))

        click.confirm(
            f"Transfer {value} from {deployer_address} to {target_address}?",
            abort=True)
        receipt = token_agent.transfer(amount=value,
                                       sender_address=deployer_address,
                                       target_address=target_address)
        emitter.echo(f"OK | Receipt: {receipt['transactionHash'].hex()}")
        return  # Exit

    elif action == "transfer-ownership":
        if not target_address:
            target_address = click.prompt("Enter new owner's checksum address",
                                          type=EIP55_CHECKSUM_ADDRESS)

        if contract_name:
            try:
                contract_deployer_class = ADMINISTRATOR.deployers[
                    contract_name]
            except KeyError:
                message = f"No such contract {contract_name}. Available contracts are {ADMINISTRATOR.deployers.keys()}"
                emitter.echo(message, color='red', bold=True)
                raise click.Abort()
            else:
                contract_deployer = contract_deployer_class(
                    registry=ADMINISTRATOR.registry,
                    deployer_address=ADMINISTRATOR.deployer_address)
                receipt = contract_deployer.transfer_ownership(
                    new_owner=target_address, transaction_gas_limit=gas)
                emitter.ipc(receipt, request_id=0, duration=0)  # TODO: #1216
                return  # Exit
        else:
            receipts = ADMINISTRATOR.relinquish_ownership(
                new_owner=target_address, transaction_gas_limit=gas)
            emitter.ipc(receipts, request_id=0, duration=0)  # TODO: #1216
            return  # Exit

    else:
        raise click.BadArgumentUsage(message=f"Unknown action '{action}'")
Example #22
0
    def connect(self,
                fetch_registry: bool = True,
                sync_now: bool = False,
                emitter: StdoutEmitter = None):

        # Spawn child process
        if self._provider_process:
            self._provider_process.start()
            provider_uri = self._provider_process.provider_uri(scheme='file')
        else:
            provider_uri = self.provider_uri
            self.log.info(
                f"Using external Web3 Provider '{self.provider_uri}'")

        # Attach Provider
        self._attach_provider(provider=self._provider,
                              provider_uri=provider_uri)
        self.log.info("Connecting to {}".format(self.provider_uri))
        if self._provider is NO_BLOCKCHAIN_CONNECTION:
            raise self.NoProvider(
                "There are no configured blockchain providers")

        # Connect if not connected
        try:
            self.w3 = self.Web3(provider=self._provider)
            self.client = Web3Client.from_w3(w3=self.w3)
        except requests.ConnectionError:  # RPC
            raise self.ConnectionFailed(
                f'Connection Failed - {str(self.provider_uri)} - is RPC enabled?'
            )
        except FileNotFoundError:  # IPC File Protocol
            raise self.ConnectionFailed(
                f'Connection Failed - {str(self.provider_uri)} - is IPC enabled?'
            )
        else:
            self.attach_middleware()

        # Establish contact with NuCypher contracts
        if not self.registry:
            self._configure_registry(fetch_registry=fetch_registry)

        # Wait for chaindata sync
        if sync_now:
            sync_state = self.client.sync()
            if emitter:
                import click
                emitter.echo(
                    f"Syncing: {self.client.chain_name.capitalize()}. Waiting for sync to begin."
                )

                while not len(self.client.peers):
                    emitter.echo("waiting for peers...")
                    time.sleep(5)

                peer_count = len(self.client.peers)
                emitter.echo(
                    f"Found {'an' if peer_count == 1 else peer_count} Ethereum peer{('s' if peer_count>1 else '')}."
                )

                try:
                    emitter.echo("Beginning sync...")
                    initial_state = next(sync_state)
                except StopIteration:  # will occur if no syncing needs to happen
                    emitter.echo("Local blockchain data is already synced.")
                    return True

                prior_state = initial_state
                total_blocks_to_sync = int(initial_state.get(
                    'highestBlock', 0)) - int(
                        initial_state.get('currentBlock', 0))
                with click.progressbar(length=total_blocks_to_sync,
                                       label="sync progress") as bar:
                    for syncdata in sync_state:
                        if syncdata:
                            blocks_accomplished = int(
                                syncdata['currentBlock']) - int(
                                    prior_state.get('currentBlock', 0))
                            bar.update(blocks_accomplished)
                            prior_state = syncdata
            else:
                try:
                    for syncdata in sync_state:
                        self.client.log.info(
                            f"Syncing {syncdata['currentBlock']}/{syncdata['highestBlock']}"
                        )
                except TypeError:  # it's already synced
                    return True

        return self.is_connected
Example #23
0
def contracts(  # Admin Actor Options
        provider_uri,
        contract_name,
        config_root,
        poa,
        force,
        etherscan,
        hw_wallet,
        deployer_address,
        registry_infile,
        registry_outfile,
        dev,
        se_test_mode,

        # Other
        bare,
        gas,
        ignore_deployed):
    """
    Compile and deploy contracts.
    """
    # Init
    emitter = StdoutEmitter()
    _ensure_config_root(config_root)
    deployer_interface = _initialize_blockchain(poa, provider_uri)

    # Warnings
    _pre_launch_warnings(emitter, etherscan, hw_wallet)

    #
    # Make Authenticated Deployment Actor
    #
    ADMINISTRATOR, deployer_address, local_registry = _make_authenticated_deployment_actor(
        emitter, provider_uri, deployer_address, deployer_interface,
        contract_name, registry_infile, registry_outfile, hw_wallet, dev,
        force, se_test_mode)

    #
    # Deploy Single Contract (Amend Registry)
    #

    if contract_name:
        try:
            contract_deployer = ADMINISTRATOR.deployers[contract_name]
        except KeyError:
            message = f"No such contract {contract_name}. Available contracts are {ADMINISTRATOR.deployers.keys()}"
            emitter.echo(message, color='red', bold=True)
            raise click.Abort()

        # Deploy
        emitter.echo(f"Deploying {contract_name}")
        if contract_deployer._upgradeable and not bare:
            # NOTE: Bare deployments do not engage the proxy contract
            secret = ADMINISTRATOR.collect_deployment_secret(
                deployer=contract_deployer)
            receipts, agent = ADMINISTRATOR.deploy_contract(
                contract_name=contract_name,
                plaintext_secret=secret,
                gas_limit=gas,
                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,
                ignore_deployed=ignore_deployed)

        # Report
        paint_contract_deployment(
            contract_name=contract_name,
            contract_address=agent.contract_address,
            receipts=receipts,
            emitter=emitter,
            chain_name=deployer_interface.client.chain_name,
            open_in_browser=etherscan)
        return  # Exit

    #
    # Deploy Automated Series (Create Registry)
    #

    # Confirm filesystem registry writes.
    if os.path.isfile(local_registry.filepath):
        emitter.echo(
            f"\nThere is an existing contract registry at {local_registry.filepath}.\n"
            f"Did you mean 'nucypher-deploy upgrade'?\n",
            color='yellow')
        click.confirm("*DESTROY* existing local registry and continue?",
                      abort=True)
        os.remove(local_registry.filepath)

    # Stage Deployment
    secrets = ADMINISTRATOR.collect_deployment_secrets()
    paint_staged_deployment(deployer_interface=deployer_interface,
                            administrator=ADMINISTRATOR,
                            emitter=emitter)

    # Confirm Trigger Deployment
    if not confirm_deployment(emitter=emitter,
                              deployer_interface=deployer_interface):
        raise click.Abort()

    # Delay - Last chance to abort via KeyboardInterrupt
    paint_deployment_delay(emitter=emitter)

    # Execute Deployment
    deployment_receipts = ADMINISTRATOR.deploy_network_contracts(
        secrets=secrets,
        emitter=emitter,
        interactive=not force,
        etherscan=etherscan,
        ignore_deployed=ignore_deployed)

    # Paint outfile paths
    registry_outfile = local_registry.filepath
    emitter.echo('Generated registry {}'.format(registry_outfile),
                 bold=True,
                 color='blue')

    # Save transaction metadata
    receipts_filepath = ADMINISTRATOR.save_deployment_receipts(
        receipts=deployment_receipts)
    emitter.echo(f"Saved deployment receipts to {receipts_filepath}",
                 color='blue',
                 bold=True)
Example #24
0
def paint_stakes(emitter: StdoutEmitter,
                 stakeholder: 'StakeHolder',
                 paint_inactive: bool = False,
                 staker_address: str = None) -> None:

    stakers = stakeholder.get_stakers()
    if not stakers:
        emitter.echo("No staking accounts found.")

    total_stakers = 0
    for staker in stakers:
        if not staker.stakes:
            # This staker has no active stakes.
            # TODO: Something with non-staking accounts?
            continue

        # Filter Target
        if staker_address and staker.checksum_address != staker_address:
            continue

        stakes = sorted(staker.stakes,
                        key=lambda s: s.address_index_ordering_key)
        active_stakes = filter(lambda s: s.is_active, stakes)
        if not active_stakes:
            emitter.echo(f"There are no active stakes\n")

        fees = staker.policy_agent.get_fee_amount(staker.checksum_address)
        pretty_fees = prettify_eth_amount(fees)
        last_committed = staker.staking_agent.get_last_committed_period(
            staker.checksum_address)
        missing = staker.missing_commitments
        min_fee_rate = prettify_eth_amount(staker.min_fee_rate)

        if missing == -1:
            missing_info = "Never Made a Commitment (New Stake)"
        else:
            missing_info = f'Missing {missing} commitments{"s" if missing > 1 else ""}' if missing else f'Committed #{last_committed}'

        staker_data = [
            missing_info,
            f'{"Yes" if staker.is_restaking else "No"} ({"Locked" if staker.restaking_lock_enabled else "Unlocked"})',
            "Yes" if bool(staker.is_winding_down) else "No", pretty_fees,
            min_fee_rate
        ]

        line_width = 54
        if staker.registry.source:  # TODO: #1580 - Registry source might be Falsy in tests.
            network_snippet = f"\nNetwork {staker.registry.source.network.capitalize()} "
            snippet_with_line = network_snippet + '═' * (
                line_width - len(network_snippet) + 1)
            emitter.echo(snippet_with_line, bold=True)
        emitter.echo(f"Staker {staker.checksum_address} ════",
                     bold=True,
                     color='red' if missing else 'green')
        emitter.echo(f"Worker {staker.worker_address} ════")
        emitter.echo(
            tabulate.tabulate(zip(STAKER_TABLE_COLUMNS, staker_data),
                              floatfmt="fancy_grid"))

        rows = list()
        for index, stake in enumerate(stakes):
            if not stake.is_active and not paint_inactive:
                # This stake is inactive.
                continue
            rows.append(list(stake.describe().values()))
        total_stakers += 1
        emitter.echo(
            tabulate.tabulate(rows,
                              headers=STAKE_TABLE_COLUMNS,
                              tablefmt="fancy_grid"))  # newline

    if not total_stakers:
        emitter.echo("No Stakes found", color='red')
Example #25
0
    def create_actor(self,
                     emitter: StdoutEmitter,
                     is_multisig: bool = False
                     ) -> Tuple[ContractAdministrator, str, BlockchainInterface, BaseContractRegistry]:

        ensure_config_root(self.config_root)
        deployer_interface = initialize_deployer_interface(poa=self.poa,
                                                           provider_uri=self.provider_uri,
                                                           emitter=emitter,
                                                           ignore_solidity_check=self.ignore_solidity_check,
                                                           gas_strategy=self.gas_strategy,
                                                           max_gas_price=self.max_gas_price)

        # Warnings
        deployer_pre_launch_warnings(emitter, self.etherscan, self.hw_wallet)

        #
        # Establish Registry
        #

        local_registry = establish_deployer_registry(emitter=emitter,
                                                     use_existing_registry=bool(self.contract_name),  # TODO: Issue #2314
                                                     registry_infile=self.registry_infile,
                                                     registry_outfile=self.registry_outfile,
                                                     dev=self.dev,
                                                     network=self.network)
        #
        # Make Authenticated Deployment Actor
        #
        # Verify Address & collect password
        password = None
        if is_multisig:
            multisig_agent = ContractAgency.get_agent(MultiSigAgent, registry=local_registry)
            deployer_address = multisig_agent.contract.address
            is_transacting = False
        else:
            is_transacting = True
            deployer_address = self.deployer_address
            if not deployer_address:
                deployer_address = select_client_account(emitter=emitter,
                                                         prompt=SELECT_DEPLOYER_ACCOUNT,
                                                         provider_uri=self.provider_uri,
                                                         signer_uri=self.signer_uri,
                                                         show_eth_balance=True)

            if not self.force:
                click.confirm(CONFIRM_SELECTED_ACCOUNT.format(address=deployer_address), abort=True)

            is_clef = ClefSigner.is_valid_clef_uri(self.signer_uri)
            eth_password_is_needed = not self.hw_wallet and not deployer_interface.client.is_local and not is_clef
            if eth_password_is_needed:
                password = get_client_password(checksum_address=deployer_address)

        # Produce Actor
        testnet = deployer_interface.client.chain_name != PUBLIC_CHAINS[1]  # Mainnet
        signer = Signer.from_signer_uri(self.signer_uri, testnet=testnet) if self.signer_uri else None
        ADMINISTRATOR = ContractAdministrator(registry=local_registry,
                                              client_password=password,
                                              deployer_address=deployer_address,
                                              is_transacting=is_transacting,
                                              signer=signer)
        # Verify ETH Balance
        emitter.echo(DEPLOYER_BALANCE.format(eth_balance=ADMINISTRATOR.eth_balance))
        if is_transacting and ADMINISTRATOR.eth_balance == 0:
            emitter.echo(DEPLOYER_ADDRESS_ZERO_ETH, color='red', bold=True)
            raise click.Abort()
        return ADMINISTRATOR, deployer_address, deployer_interface, local_registry
Example #26
0
    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
Example #27
0
def select_config_file(
    emitter: StdoutEmitter,
    config_class: Type[CharacterConfiguration],
    config_root: str = None,
    checksum_address: str = None,
) -> str:
    """
    Selects a nucypher character configuration file from the disk automatically or interactively.

    Behaviour
    ~~~~~~~~~

    - If checksum address is supplied by parameter or worker address env var - confirm there is a corresponding
      file on the disk or raise ValueError.

    - If there is only one configuration file for the character, automatically return its filepath.

    - If there are multiple character configurations on the disk in the same configuration root,
      use interactive selection.

    - Aborts if there are no configurations associated with the supplied character configuration class.

    """

    #
    # Scrape Disk Configurations
    #

    config_root = config_root or DEFAULT_CONFIG_ROOT
    default_config_file = glob.glob(
        config_class.default_filepath(config_root=config_root))
    glob_pattern = f'{config_root}/{config_class.NAME}-0x*.{config_class._CONFIG_FILE_EXTENSION}'
    secondary_config_files = glob.glob(glob_pattern)
    config_files = [*default_config_file, *secondary_config_files]
    if not config_files:
        emitter.message(NO_CONFIGURATIONS_ON_DISK.format(
            name=config_class.NAME.capitalize(), command=config_class.NAME),
                        color='red')
        raise click.Abort()

    checksum_address = checksum_address or os.environ.get(
        NUCYPHER_ENVVAR_WORKER_ADDRESS,
        None)  # TODO: Deprecate worker_address in favor of checksum_address
    if checksum_address:

        #
        # Manual
        #

        parsed_addresses = {
            config_class.checksum_address_from_filepath(fp): fp
            for fp in config_files
        }
        try:
            config_file = parsed_addresses[checksum_address]
        except KeyError:
            raise ValueError(
                f"'{checksum_address}' is not a known {config_class.NAME} configuration account."
            )

    elif len(config_files) > 1:

        #
        # Interactive
        #

        parsed_addresses = tuple(
            [config_class.checksum_address_from_filepath(fp)]
            for fp in config_files)

        # Display account info
        headers = ['Account']
        emitter.echo(
            tabulate(parsed_addresses, headers=headers, showindex='always'))

        # Prompt the user for selection, and return
        prompt = f"Select {config_class.NAME} configuration"
        account_range = click.IntRange(min=0, max=len(config_files) - 1)
        choice = click.prompt(prompt, type=account_range, default=0)
        config_file = config_files[choice]
        emitter.echo(f"Selected {choice}: {config_file}", color='blue')

    else:
        # Default: Only one config file, use it.
        config_file = config_files[0]

    return config_file
Example #28
0
def select_config_file(
    emitter: StdoutEmitter,
    config_class: Type[CharacterConfiguration],
    config_root: str = None,
    checksum_address: str = None,
) -> str:
    """
    Selects a nucypher character configuration file from the disk automatically or interactively.

    Behaviour
    ~~~~~~~~~

    - If checksum address is supplied by parameter or worker address env var - confirm there is a corresponding
      file on the disk or raise ValueError.

    - If there is only one configuration file for the character, automatically return its filepath.

    - If there are multiple character configurations on the disk in the same configuration root,
      use interactive selection.

    - Aborts if there are no configurations associated with the supplied character configuration class.

    """

    config_root = config_root or DEFAULT_CONFIG_ROOT
    config_files = get_config_filepaths(config_class=config_class,
                                        config_root=config_root)
    if not config_files:
        emitter.message(NO_CONFIGURATIONS_ON_DISK.format(
            name=config_class.NAME.capitalize(), command=config_class.NAME),
                        color='red')
        raise click.Abort()

    checksum_address = checksum_address or os.environ.get(
        NUCYPHER_ENVVAR_WORKER_ADDRESS,
        None)  # TODO: Deprecate worker_address in favor of checksum_address

    parsed_config_files = list()
    parsed_addresses_and_filenames = list()
    # parse configuration files for checksum address values
    for fp in config_files:
        try:
            config_checksum_address = config_class.checksum_address_from_filepath(
                fp)
            if checksum_address and config_checksum_address == checksum_address:
                # matching configuration file found, no need to continue - return filepath
                return fp

            parsed_config_files.append(fp)
            parsed_addresses_and_filenames.append(
                [config_checksum_address,
                 Path(fp).name])  # store checksum & filename
        except config_class.OldVersion:
            # no use causing entire usage to crash if file can't be used anyway - inform the user; they can
            # decide for themself
            emitter.echo(IGNORE_OLD_CONFIGURATION.format(config_file=fp),
                         color='yellow')

    if checksum_address:
        # shouldn't get here if checksum address was specified and corresponding file found
        raise ValueError(
            f"'{checksum_address}' is not a known {config_class.NAME} configuration account."
        )

    if not parsed_config_files:
        # No available configuration files
        emitter.message(NO_CONFIGURATIONS_ON_DISK.format(
            name=config_class.NAME.capitalize(), command=config_class.NAME),
                        color='red')
        raise click.Abort()
    elif len(parsed_config_files) > 1:
        #
        # Interactive
        #
        emitter.echo(f"\nConfiguration Directory: {config_root}\n")

        parsed_addresses_and_filenames = tuple(
            parsed_addresses_and_filenames
        )  # must be tuple-of-iterables for tabulation

        # Display account info
        headers = ['Account', 'Configuration File']
        emitter.echo(
            tabulate(parsed_addresses_and_filenames,
                     headers=headers,
                     showindex='always'))

        # Prompt the user for selection, and return
        prompt = f"Select {config_class.NAME} configuration"
        account_range = click.IntRange(min=0, max=len(parsed_config_files) - 1)
        choice = click.prompt(prompt, type=account_range, default=0)
        config_file = parsed_config_files[choice]
        emitter.echo(f"Selected {choice}: {config_file}", color='blue')
    else:
        # Default: Only one config file, use it.
        config_file = parsed_config_files[0]
        emitter.echo(
            DEFAULT_TO_LONE_CONFIG_FILE.format(
                config_class=config_class.NAME.capitalize(),
                config_file=config_file))

    return config_file