def create_character(self, emitter, config_file, json_ipc, load_seednodes=True): config = self.config_options.create_config(emitter, config_file) client_password = None if not config.federated_only: if (not self.hw_wallet or not config.dev_mode) and not json_ipc: client_password = get_client_password( checksum_address=config.checksum_address) try: ALICE = actions.make_cli_character( character_config=config, emitter=emitter, unlock_keyring=not config.dev_mode, teacher_uri=self.teacher_uri, min_stake=self.min_stake, client_password=client_password, load_preferred_teachers=load_seednodes, start_learning_now=load_seednodes) return ALICE except NucypherKeyring.AuthenticationFailed as e: emitter.echo(str(e), color='red', bold=True) click.get_current_context().exit(1)
def allocations(general_config, actor_options, allocation_infile, allocation_outfile, sidekick_account): """ Deploy pre-allocation contracts. """ emitter = general_config.emitter ADMINISTRATOR, _, deployer_interface, local_registry = actor_options.create_actor(emitter) if not sidekick_account and click.confirm('Do you want to use a sidekick account to assist during deployment?'): prompt = "Select sidekick account" sidekick_account = select_client_account(emitter=emitter, prompt=prompt, provider_uri=actor_options.provider_uri, registry=local_registry, show_balances=True) if not actor_options.force: click.confirm(f"Selected {sidekick_account} - Continue?", abort=True) if sidekick_account: password = None if not deployer_interface.client.is_local: password = get_client_password(checksum_address=sidekick_account) ADMINISTRATOR.recruit_sidekick(sidekick_address=sidekick_account, sidekick_password=password) if not allocation_infile: allocation_infile = click.prompt("Enter allocation data filepath") ADMINISTRATOR.deploy_beneficiaries_from_file(allocation_data_filepath=allocation_infile, allocation_outfile=allocation_outfile, emitter=emitter, interactive=not actor_options.force)
def create_character(self, emitter, config_file, debug): felix_config = self.config_options.create_config(emitter, config_file) try: # Authenticate unlock_nucypher_keyring( emitter, character_configuration=felix_config, password=get_nucypher_password(confirm=False)) client_password = get_client_password( checksum_address=felix_config.checksum_address, envvar=NUCYPHER_ENVVAR_WORKER_ETH_PASSWORD) # Produce Felix FELIX = felix_config.produce(domains=self.config_options.domains, client_password=client_password) FELIX.make_web_app( ) # attach web application, but dont start service return FELIX except Exception as e: if debug: raise else: emitter.echo(str(e), color='red', bold=True) raise click.Abort
def create_character(self, emitter, config_file, json_ipc, load_seednodes=True): ursula_config = self.config_options.create_config(emitter, config_file) client_password = None if not ursula_config.federated_only: if not self.config_options.dev and not json_ipc: client_password = get_client_password( checksum_address=ursula_config.worker_address, envvar="NUCYPHER_WORKER_ETH_PASSWORD") try: URSULA = actions.make_cli_character( character_config=ursula_config, emitter=emitter, min_stake=self.min_stake, teacher_uri=self.teacher_uri, unlock_keyring=not self.config_options.dev, lonely=self.lonely, client_password=client_password, load_preferred_teachers=load_seednodes, start_learning_now=load_seednodes) return ursula_config, URSULA except NucypherKeyring.AuthenticationFailed as e: emitter.echo(str(e), color='red', bold=True) # TODO: Exit codes (not only for this, but for other exceptions) return click.get_current_context().exit(1)
def create_character(self, emitter, config_file, json_ipc, load_seednodes=True): config = self.config_options.create_config(emitter, config_file) client_password = None eth_password_is_needed = not config.federated_only and not self.hw_wallet and not config.dev_mode if eth_password_is_needed: if json_ipc: client_password = os.environ.get(NUCYPHER_ENVVAR_ALICE_ETH_PASSWORD, NO_PASSWORD) if client_password is NO_PASSWORD: message = f"--json-ipc implies the {NUCYPHER_ENVVAR_ALICE_ETH_PASSWORD} envvar must be set." click.BadOptionUsage(option_name='--json-ipc', message=message) else: client_password = get_client_password(checksum_address=config.checksum_address, envvar=NUCYPHER_ENVVAR_ALICE_ETH_PASSWORD) try: ALICE = actions.make_cli_character(character_config=config, emitter=emitter, unlock_keyring=not config.dev_mode, teacher_uri=self.teacher_uri, min_stake=self.min_stake, client_password=client_password, load_preferred_teachers=load_seednodes, start_learning_now=load_seednodes) return ALICE except NucypherKeyring.AuthenticationFailed as e: emitter.echo(str(e), color='red', bold=True) click.get_current_context().exit(1)
def _create_ursula(ursula_config, click_config, dev, emitter, lonely, teacher_uri, min_stake): # # Make Ursula # client_password = None if not ursula_config.federated_only: if not dev and not click_config.json_ipc: client_password = get_client_password( checksum_address=ursula_config.worker_address, envvar="NUCYPHER_WORKER_ETH_PASSWORD") try: URSULA = actions.make_cli_character(character_config=ursula_config, click_config=click_config, min_stake=min_stake, teacher_uri=teacher_uri, dev=dev, lonely=lonely, client_password=client_password) return URSULA except NucypherKeyring.AuthenticationFailed as e: emitter.echo(str(e), color='red', bold=True) # TODO: Exit codes (not only for this, but for other exceptions) return click.get_current_context().exit(1)
def detach_worker(click_config, # Worker Options poa, light, registry_filepath, config_file, provider_uri, staking_address, hw_wallet, beneficiary_address, allocation_filepath, worker_address, # Other options force): """ Detach worker currently bonded to a staker. """ ### Setup ### emitter = _setup_emitter(click_config) STAKEHOLDER, blockchain = _create_stakeholder(config_file, provider_uri, poa, light, registry_filepath, staking_address, beneficiary_address=beneficiary_address, allocation_filepath=allocation_filepath) ############# economics = STAKEHOLDER.economics client_account, staking_address = handle_client_account_for_staking(emitter=emitter, stakeholder=STAKEHOLDER, staking_address=staking_address, individual_allocation=STAKEHOLDER.individual_allocation, force=force) if worker_address: raise click.BadOptionUsage(message="detach-worker cannot be used together with --worker-address", option_name='--worker-address') # TODO: Check preconditions (e.g., minWorkerPeriods) worker_address = STAKEHOLDER.staking_agent.get_worker_from_staker(staking_address) password = None if not hw_wallet and not blockchain.client.is_local: password = get_client_password(checksum_address=client_account) STAKEHOLDER.assimilate(checksum_address=client_account, password=password) receipt = STAKEHOLDER.detach_worker() # TODO: Double-check dates current_period = STAKEHOLDER.staking_agent.get_current_period() bonded_date = datetime_at_period(period=current_period, seconds_per_period=economics.seconds_per_period) emitter.echo(f"Successfully detached worker {worker_address} from staker {staking_address}", color='green') paint_receipt_summary(emitter=emitter, receipt=receipt, chain_name=blockchain.client.chain_name, transaction_type='detach_worker') emitter.echo(f"Detached at period #{current_period} ({bonded_date})", color='green')
def preallocation(click_config, # Stake Options poa, light, registry_filepath, config_file, provider_uri, staking_address, hw_wallet, beneficiary_address, allocation_filepath, # Preallocation subcommands, action, # Other force): """ Claim token rewards collected by a preallocation contract. """ ### Setup ### emitter = _setup_emitter(click_config) STAKEHOLDER, blockchain = _create_stakeholder(config_file, provider_uri, poa, light, registry_filepath, staking_address, beneficiary_address=beneficiary_address, allocation_filepath=allocation_filepath) ############# # Unauthenticated actions: status if action == 'status': paint_preallocation_status(emitter=emitter, token_agent=STAKEHOLDER.token_agent, preallocation_agent=STAKEHOLDER.preallocation_escrow_agent) return # Authenticated actions: withdraw-tokens client_account, staking_address = handle_client_account_for_staking(emitter=emitter, stakeholder=STAKEHOLDER, staking_address=staking_address, individual_allocation=STAKEHOLDER.individual_allocation, force=force) password = None if not hw_wallet and not blockchain.client.is_local: password = get_client_password(checksum_address=client_account) STAKEHOLDER.assimilate(checksum_address=client_account, password=password) if action == 'withdraw': token_balance = NU.from_nunits(STAKEHOLDER.token_agent.get_balance(staking_address)) locked_tokens = NU.from_nunits(STAKEHOLDER.preallocation_escrow_agent.unvested_tokens) unlocked_tokens = token_balance - locked_tokens emitter.echo(message=f'Collecting {unlocked_tokens} from PreallocationEscrow contract {staking_address}...') receipt = STAKEHOLDER.withdraw_preallocation_tokens(unlocked_tokens) paint_receipt_summary(receipt=receipt, chain_name=STAKEHOLDER.wallet.blockchain.client.chain_name, emitter=emitter)
def __create_trustee(self, registry, transacting: bool = False) -> Trustee: client_password = None if transacting and not self.hw_wallet: client_password = get_client_password( checksum_address=self.checksum_address) trustee = Trustee(checksum_address=self.checksum_address, registry=registry, client_password=client_password) return trustee
def create_actor(self, emitter, is_multisig: bool = False): _ensure_config_root(self.config_root) deployer_interface = _initialize_blockchain(poa=self.poa, provider_uri=self.provider_uri, emitter=emitter, ignore_solidity_check=self.ignore_solidity_check, gas_strategy=self.gas_strategy) # Warnings _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), registry_infile=self.registry_infile, registry_outfile=self.registry_outfile, dev=self.dev) # # 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: prompt = "Select deployer account" deployer_address = select_client_account(emitter=emitter, prompt=prompt, provider_uri=self.provider_uri, show_balances=False) if not self.force: click.confirm("Selected {} - Continue?".format(deployer_address), abort=True) if not self.hw_wallet and not deployer_interface.client.is_local: password = get_client_password(checksum_address=deployer_address) # Produce Actor ADMINISTRATOR = ContractAdministrator(registry=local_registry, client_password=password, deployer_address=deployer_address, is_transacting=is_transacting, staking_escrow_test_mode=self.se_test_mode) # Verify ETH Balance emitter.echo(f"\n\nDeployer ETH balance: {ADMINISTRATOR.eth_balance}") if is_transacting and ADMINISTRATOR.eth_balance == 0: emitter.echo("Deployer address has no ETH.", color='red', bold=True) raise click.Abort() return ADMINISTRATOR, deployer_address, deployer_interface, local_registry
def restake(click_config, # Stake Options poa, light, registry_filepath, config_file, provider_uri, staking_address, hw_wallet, beneficiary_address, allocation_filepath, # Other enable, lock_until, force): """ Manage re-staking with --enable or --disable. """ ### Setup ### emitter = _setup_emitter(click_config) STAKEHOLDER, blockchain = _create_stakeholder(config_file, provider_uri, poa, light, registry_filepath, staking_address, beneficiary_address=beneficiary_address, allocation_filepath=allocation_filepath) ############# client_account, staking_address = handle_client_account_for_staking(emitter=emitter, stakeholder=STAKEHOLDER, staking_address=staking_address, individual_allocation=STAKEHOLDER.individual_allocation, force=force) # Authenticate password = None if not hw_wallet and not blockchain.client.is_local: password = get_client_password(checksum_address=client_account) STAKEHOLDER.assimilate(checksum_address=client_account, password=password) # Inner Exclusive Switch if lock_until: if not force: confirm_enable_restaking_lock(emitter, staking_address=staking_address, release_period=lock_until) receipt = STAKEHOLDER.enable_restaking_lock(release_period=lock_until) emitter.echo(f'Successfully enabled re-staking lock for {staking_address} until {lock_until}', color='green', verbosity=1) elif enable: if not force: confirm_enable_restaking(emitter, staking_address=staking_address) receipt = STAKEHOLDER.enable_restaking() emitter.echo(f'Successfully enabled re-staking for {staking_address}', color='green', verbosity=1) else: if not force: click.confirm(f"Confirm disable re-staking for staker {staking_address}?", abort=True) receipt = STAKEHOLDER.disable_restaking() emitter.echo(f'Successfully disabled re-staking for {staking_address}', color='green', verbosity=1) paint_receipt_summary(receipt=receipt, emitter=emitter, chain_name=blockchain.client.chain_name)
def collect_reward(click_config, # Stake Options poa, light, registry_filepath, config_file, provider_uri, staking_address, hw_wallet, beneficiary_address, allocation_filepath, # Other staking_reward, policy_reward, withdraw_address, force): """ Withdraw staking reward. """ ### Setup ### emitter = _setup_emitter(click_config) STAKEHOLDER, blockchain = _create_stakeholder(config_file, provider_uri, poa, light, registry_filepath, staking_address, beneficiary_address=beneficiary_address, allocation_filepath=allocation_filepath) ############# client_account, staking_address = handle_client_account_for_staking(emitter=emitter, stakeholder=STAKEHOLDER, staking_address=staking_address, individual_allocation=STAKEHOLDER.individual_allocation, force=force) password = None if not hw_wallet and not blockchain.client.is_local: password = get_client_password(checksum_address=client_account) if not staking_reward and not policy_reward: raise click.BadArgumentUsage(f"Either --staking-reward or --policy-reward must be True to collect rewards.") STAKEHOLDER.assimilate(checksum_address=client_account, password=password) if staking_reward: # Note: Sending staking / inflation rewards to another account is not allowed. reward_amount = NU.from_nunits(STAKEHOLDER.calculate_staking_reward()) emitter.echo(message=f'Collecting {reward_amount} from staking rewards...') staking_receipt = STAKEHOLDER.collect_staking_reward() paint_receipt_summary(receipt=staking_receipt, chain_name=STAKEHOLDER.wallet.blockchain.client.chain_name, emitter=emitter) if policy_reward: reward_amount = Web3.fromWei(STAKEHOLDER.calculate_policy_reward(), 'ether') emitter.echo(message=f'Collecting {reward_amount} ETH from policy rewards...') policy_receipt = STAKEHOLDER.collect_policy_reward(collector_address=withdraw_address) paint_receipt_summary(receipt=policy_receipt, chain_name=STAKEHOLDER.wallet.blockchain.client.chain_name, emitter=emitter)
def __create_executive(self, registry, transacting: bool = False) -> Executive: client_password = None if transacting and not self.hw_wallet: client_password = get_client_password( checksum_address=self.checksum_address) executive = Executive(checksum_address=self.checksum_address, registry=registry, signer=ClefSigner(self.signer_uri), client_password=client_password) return executive
def __create_bidder(self, registry, transacting: bool = False, hw_wallet: bool = False): client_password = None if transacting and not hw_wallet: client_password = get_client_password( checksum_address=self.bidder_address) bidder = Bidder(checksum_address=self.bidder_address, registry=registry, client_password=client_password, is_transacting=transacting) return bidder
def __create_bidder(self, registry, signer: Optional[Signer] = None, transacting: bool = True, hw_wallet: bool = False): client_password = None if transacting and not signer and not hw_wallet: client_password = get_client_password(checksum_address=self.bidder_address) bidder = Bidder(checksum_address=self.bidder_address, registry=registry, client_password=client_password, signer=signer, transacting=transacting) return bidder
def _make_authenticated_deployment_actor(emitter, provider_uri, deployer_address, deployer_interface, contract_name, registry_infile, registry_outfile, hw_wallet, dev, force, se_test_mode): # # Establish Registry # local_registry = establish_deployer_registry( emitter=emitter, use_existing_registry=bool(contract_name), registry_infile=registry_infile, registry_outfile=registry_outfile, dev=dev) # # 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=local_registry, client_password=password, deployer_address=deployer_address, staking_escrow_test_mode=se_test_mode) # 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() return ADMINISTRATOR, deployer_address, local_registry
def _create_alice(alice_config, click_config, dev, emitter, hw_wallet, teacher_uri, min_stake): # # Produce Alice # client_password = None if not alice_config.federated_only: if (not hw_wallet or not dev) and not click_config.json_ipc: client_password = get_client_password(checksum_address=alice_config.checksum_address) try: ALICE = actions.make_cli_character(character_config=alice_config, click_config=click_config, dev=dev, teacher_uri=teacher_uri, min_stake=min_stake, client_password=client_password) return ALICE except NucypherKeyring.AuthenticationFailed as e: emitter.echo(str(e), color='red', bold=True) click.get_current_context().exit(1)
def alice( click_config, action, # Mode dev, force, dry_run, # Network teacher_uri, min_stake, federated_only, network, discovery_port, controller_port, # Filesystem config_root, config_file, # Blockchain pay_with, provider_uri, geth, sync, poa, registry_filepath, hw_wallet, # Alice bob_encrypting_key, bob_verifying_key, label, m, n, value, rate, duration_periods, expiration, message_kit, ): """ "Alice the Policy Authority" management commands. \b Actions ------------------------------------------------- \b init Create a brand new persistent Alice view View existing Alice's configuration. run Start Alice's controller. destroy Delete existing Alice's configuration. public-keys Obtain Alice's public verification and encryption keys. derive-policy-pubkey Get a policy public key from a policy label. grant Create and enact an access policy for some Bob. revoke Revoke a policy. decrypt Decrypt data encrypted under an Alice's policy public key. """ # # Validate # if federated_only and geth: raise click.BadOptionUsage( option_name="--geth", message="Federated only cannot be used with the --geth flag") # Banner emitter = click_config.emitter emitter.clear() emitter.banner(ALICE_BANNER) # # Managed Ethereum Client # ETH_NODE = NO_BLOCKCHAIN_CONNECTION if geth: ETH_NODE = actions.get_provider_process() provider_uri = ETH_NODE.provider_uri(scheme='file') # # Eager Actions (No Authentication Required) # if action == 'init': """Create a brand-new persistent Alice""" if dev: raise click.BadArgumentUsage( "Cannot create a persistent development character") if not provider_uri and not federated_only: raise click.BadOptionUsage( option_name='--provider', message= "--provider is required to create a new decentralized alice.") if not config_root: # Flag config_root = click_config.config_file # Envvar if not pay_with and not federated_only: pay_with = select_client_account(emitter=emitter, provider_uri=provider_uri) new_alice_config = AliceConfiguration.generate( password=get_nucypher_password(confirm=True), config_root=config_root, checksum_address=pay_with, domains={network} if network else None, federated_only=federated_only, registry_filepath=registry_filepath, provider_process=ETH_NODE, poa=poa, provider_uri=provider_uri, m=m, n=n, duration_periods=duration_periods, rate=rate) painting.paint_new_installation_help( emitter, new_configuration=new_alice_config) return # Exit elif action == "view": """Paint an existing configuration to the console""" configuration_file_location = config_file or AliceConfiguration.default_filepath( ) response = AliceConfiguration._read_configuration_file( filepath=configuration_file_location) return emitter.ipc( response=response, request_id=0, duration=0) # FIXME: what are request_id and duration here? # # Get Alice Configuration # if dev: alice_config = AliceConfiguration( dev_mode=True, network_middleware=click_config.middleware, domains={network}, provider_process=ETH_NODE, provider_uri=provider_uri, federated_only=True) else: try: alice_config = AliceConfiguration.from_configuration_file( dev_mode=False, filepath=config_file, domains={network} if network else None, network_middleware=click_config.middleware, rest_port=discovery_port, checksum_address=pay_with, provider_process=ETH_NODE, provider_uri=provider_uri, registry_filepath=registry_filepath) except FileNotFoundError: return actions.handle_missing_configuration_file( character_config_class=AliceConfiguration, config_file=config_file) if action == "destroy": """Delete all configuration files from the disk""" if dev: message = "'nucypher alice destroy' cannot be used in --dev mode" raise click.BadOptionUsage(option_name='--dev', message=message) return actions.destroy_configuration(emitter, character_config=alice_config, force=force) # # Produce Alice # # TODO: OH MY. client_password = None if not alice_config.federated_only: if (not hw_wallet or not dev) and not click_config.json_ipc: client_password = get_client_password( checksum_address=alice_config.checksum_address) try: ALICE = actions.make_cli_character(character_config=alice_config, click_config=click_config, dev=dev, teacher_uri=teacher_uri, min_stake=min_stake, client_password=client_password) except NucypherKeyring.AuthenticationFailed as e: emitter.echo(str(e), color='red', bold=True) click.get_current_context().exit(1) # TODO: Exit codes (not only for this, but for other exceptions) # # Admin Actions # if action == "run": """Start Alice Controller""" try: # RPC if click_config.json_ipc: rpc_controller = ALICE.make_rpc_controller() _transport = rpc_controller.make_control_transport() rpc_controller.start() return # HTTP else: emitter.message( f"Alice Verifying Key {bytes(ALICE.stamp).hex()}", color="green", bold=True) controller = ALICE.make_web_controller( crash_on_error=click_config.debug) ALICE.log.info('Starting HTTP Character Web Controller') emitter.message( f'Running HTTP Alice Controller at http://localhost:{controller_port}' ) return controller.start(http_port=controller_port, dry_run=dry_run) # Handle Crash except Exception as e: alice_config.log.critical(str(e)) emitter.message(f"{e.__class__.__name__} {e}", color='red', bold=True) if click_config.debug: raise # Crash :-( return # # Alice API # elif action == "public-keys": response = ALICE.controller.public_keys() return response elif action == "derive-policy-pubkey": # Validate if not label: raise click.BadOptionUsage( option_name='label', message= "--label is required for deriving a policy encrypting key.") # Request return ALICE.controller.derive_policy_encrypting_key(label=label) elif action == "grant": # Validate if not all((bob_verifying_key, bob_encrypting_key, label)): raise click.BadArgumentUsage( message= "--bob-verifying-key, --bob-encrypting-key, and --label are " "required options to grant (optionally --m, --n, and --expiration)." ) # Request grant_request = { 'bob_encrypting_key': bob_encrypting_key, 'bob_verifying_key': bob_verifying_key, 'label': label, 'm': m, 'n': n, 'expiration': expiration, } if not ALICE.federated_only: grant_request.update({'value': value}) return ALICE.controller.grant(request=grant_request) elif action == "revoke": # Validate if not label and bob_verifying_key: raise click.BadArgumentUsage( message= f"--label and --bob-verifying-key are required options for revoke." ) # Request revoke_request = { 'label': label, 'bob_verifying_key': bob_verifying_key } return ALICE.controller.revoke(request=revoke_request) elif action == "decrypt": # Validate if not all((label, message_kit)): input_specification, output_specification = ALICE.controller.get_specifications( interface_name=action) required_fields = ', '.join(input_specification) raise click.BadArgumentUsage( f'{required_fields} are required flags to decrypt') # Request request_data = {'label': label, 'message_kit': message_kit} response = ALICE.controller.decrypt(request=request_data) return response else: raise click.BadArgumentUsage(f"No such argument {action}")
def create( click_config, # Stake Options poa, light, registry_filepath, config_file, provider_uri, staking_address, hw_wallet, beneficiary_address, allocation_filepath, # Other force, value, lock_periods): """ Initialize a new stake. """ ### Setup ### emitter = _setup_emitter(click_config) STAKEHOLDER, blockchain = _create_stakeholder( config_file, provider_uri, poa, light, registry_filepath, staking_address, beneficiary_address=beneficiary_address, allocation_filepath=allocation_filepath) ############# economics = STAKEHOLDER.economics client_account, staking_address = handle_client_account_for_staking( emitter=emitter, stakeholder=STAKEHOLDER, staking_address=staking_address, individual_allocation=STAKEHOLDER.individual_allocation, force=force) # Dynamic click types (Economics) min_locked = economics.minimum_allowed_locked stake_value_range = click.FloatRange( min=NU.from_nunits(min_locked).to_tokens(), clamp=False) stake_duration_range = click.IntRange(min=economics.minimum_locked_periods, clamp=False) password = None if not hw_wallet and not blockchain.client.is_local: password = get_client_password(checksum_address=client_account) # # Stage Stake # if not value: value = click.prompt(f"Enter stake value in NU", type=stake_value_range, default=NU.from_nunits(min_locked).to_tokens()) value = NU.from_tokens(value) if not lock_periods: prompt = f"Enter stake duration ({STAKEHOLDER.economics.minimum_locked_periods} periods minimum)" lock_periods = click.prompt(prompt, type=stake_duration_range) start_period = STAKEHOLDER.staking_agent.get_current_period() + 1 unlock_period = start_period + lock_periods # # Review # if not force: painting.paint_staged_stake(emitter=emitter, stakeholder=STAKEHOLDER, staking_address=staking_address, stake_value=value, lock_periods=lock_periods, start_period=start_period, unlock_period=unlock_period) confirm_staged_stake(staker_address=staking_address, value=value, lock_periods=lock_periods) # Last chance to bail click.confirm("Publish staged stake to the blockchain?", abort=True) # Execute STAKEHOLDER.assimilate(checksum_address=client_account, password=password) new_stake = STAKEHOLDER.initialize_stake(amount=value, lock_periods=lock_periods) painting.paint_staking_confirmation(emitter=emitter, ursula=STAKEHOLDER, transactions=new_stake.transactions)
def set_worker( click_config, # Worker Options poa, light, registry_filepath, config_file, provider_uri, staking_address, hw_wallet, beneficiary_address, allocation_filepath, worker_address, # Other options force): """ Bond a worker to a staker. """ ### Setup ### emitter = _setup_emitter(click_config) STAKEHOLDER, blockchain = _create_stakeholder( config_file, provider_uri, poa, light, registry_filepath, staking_address, beneficiary_address=beneficiary_address, allocation_filepath=allocation_filepath) ############# economics = STAKEHOLDER.economics client_account, staking_address = handle_client_account_for_staking( emitter=emitter, stakeholder=STAKEHOLDER, staking_address=staking_address, individual_allocation=STAKEHOLDER.individual_allocation, force=force) if not worker_address: worker_address = click.prompt("Enter worker address", type=EIP55_CHECKSUM_ADDRESS) # TODO: Check preconditions (e.g., minWorkerPeriods, already in use, etc) password = None if not hw_wallet and not blockchain.client.is_local: password = get_client_password(checksum_address=client_account) STAKEHOLDER.assimilate(checksum_address=client_account, password=password) receipt = STAKEHOLDER.set_worker(worker_address=worker_address) # TODO: Double-check dates current_period = STAKEHOLDER.staking_agent.get_current_period() bonded_date = datetime_at_period( period=current_period, seconds_per_period=economics.seconds_per_period) min_worker_periods = STAKEHOLDER.staking_agent.staking_parameters()[7] release_period = current_period + min_worker_periods release_date = datetime_at_period( period=release_period, seconds_per_period=economics.seconds_per_period, start_of_period=True) emitter.echo( f"\nWorker {worker_address} successfully bonded to staker {staking_address}", color='green') paint_receipt_summary(emitter=emitter, receipt=receipt, chain_name=blockchain.client.chain_name, transaction_type='set_worker') emitter.echo(f"Bonded at period #{current_period} ({bonded_date})", color='green') emitter.echo( f"This worker can be replaced or detached after period " f"#{release_period} ({release_date})", color='green')
def get_password(self, blockchain, client_account): password = None if not self.hw_wallet and not blockchain.client.is_local: password = get_client_password(checksum_address=client_account) return password
def divide( click_config, # Stake Options poa, light, registry_filepath, config_file, provider_uri, staking_address, hw_wallet, beneficiary_address, allocation_filepath, # Other force, value, lock_periods, index): """ Create a new stake from part of an existing one. """ ### Setup ### emitter = _setup_emitter(click_config) STAKEHOLDER, blockchain = _create_stakeholder( config_file, provider_uri, poa, light, registry_filepath, staking_address, beneficiary_address=beneficiary_address, allocation_filepath=allocation_filepath) ############# client_account, staking_address = handle_client_account_for_staking( emitter=emitter, stakeholder=STAKEHOLDER, staking_address=staking_address, individual_allocation=STAKEHOLDER.individual_allocation, force=force) economics = STAKEHOLDER.economics # Dynamic click types (Economics) min_locked = economics.minimum_allowed_locked stake_value_range = click.FloatRange( min=NU.from_nunits(min_locked).to_tokens(), clamp=False) stake_extension_range = click.IntRange( min=1, max=economics.maximum_allowed_locked, clamp=False) if staking_address and index is not None: # 0 is valid. STAKEHOLDER.stakes = StakeList(registry=STAKEHOLDER.registry, checksum_address=staking_address) STAKEHOLDER.stakes.refresh() current_stake = STAKEHOLDER.stakes[index] else: current_stake = select_stake(stakeholder=STAKEHOLDER, emitter=emitter) # # Stage Stake # # Value if not value: value = click.prompt( f"Enter target value (must be less than or equal to {str(current_stake.value)})", type=stake_value_range) value = NU(value, 'NU') # Duration if not lock_periods: extension = click.prompt("Enter number of periods to extend", type=stake_extension_range) else: extension = lock_periods if not force: painting.paint_staged_stake_division(emitter=emitter, stakeholder=STAKEHOLDER, original_stake=current_stake, target_value=value, extension=extension) click.confirm("Is this correct?", abort=True) # Execute password = None if not hw_wallet and not blockchain.client.is_local: password = get_client_password( checksum_address=current_stake.staker_address) STAKEHOLDER.assimilate(checksum_address=current_stake.staker_address, password=password) modified_stake, new_stake = STAKEHOLDER.divide_stake( stake_index=current_stake.index, target_value=value, additional_periods=extension) emitter.echo('Successfully divided stake', color='green', verbosity=1) paint_receipt_summary(emitter=emitter, receipt=new_stake.receipt, chain_name=blockchain.client.chain_name) # Show the resulting stake list painting.paint_stakes(emitter=emitter, stakes=STAKEHOLDER.stakes)
def ursula( click_config, action, dev, dry_run, force, lonely, network, teacher_uri, min_stake, rest_host, rest_port, db_filepath, staker_address, worker_address, federated_only, poa, config_root, config_file, provider_uri, geth, registry_filepath, interactive, sync, ) -> None: """ "Ursula the Untrusted" PRE Re-encryption node management commands. \b Actions ------------------------------------------------- \b init Create a new Ursula node configuration. view View the Ursula node's configuration. run Run an "Ursula" node. save-metadata Manually write node metadata to disk without running forget Forget all known nodes. destroy Delete Ursula node configuration. confirm-activity Manually confirm-activity for the current period. """ emitter = click_config.emitter # # Validate # if federated_only: if geth: raise click.BadOptionUsage( option_name="--geth", message="Federated only cannot be used with the --geth flag") if staker_address: raise click.BadOptionUsage( option_name='--federated-only', message="Staking address cannot be used in federated mode.") # Banner emitter.banner(URSULA_BANNER.format(worker_address or '')) # # Pre-Launch Warnings # if dev: emitter.echo("WARNING: Running in Development mode", color='yellow', verbosity=1) if force: emitter.echo("WARNING: Force is enabled", color='yellow', verbosity=1) # # Internal Ethereum Client # ETH_NODE = NO_BLOCKCHAIN_CONNECTION if geth: ETH_NODE = actions.get_provider_process() provider_uri = ETH_NODE.provider_uri(scheme='file') # # Eager Actions # if action == "init": """Create a brand-new persistent Ursula""" if dev: raise click.BadArgumentUsage( "Cannot create a persistent development character") if (not staker_address or not worker_address) and not federated_only: # Connect to Blockchain fetch_registry = registry_filepath is None and not click_config.no_registry registry = None if registry_filepath: registry = EthereumContractRegistry( registry_filepath=registry_filepath) blockchain = BlockchainInterface(provider_uri=provider_uri, registry=registry, poa=poa) blockchain.connect(fetch_registry=fetch_registry, sync_now=sync, emitter=emitter) if not staker_address: prompt = "Select staker account" staker_address = select_client_account(emitter=emitter, blockchain=blockchain, prompt=prompt) if not worker_address: prompt = "Select worker account" worker_address = select_client_account(emitter=emitter, blockchain=blockchain, prompt=prompt) if not config_root: # Flag config_root = click_config.config_file # Envvar if not rest_host: rest_host = actions.determine_external_ip_address(emitter, force=force) download_registry = not federated_only and not click_config.no_registry ursula_config = UrsulaConfiguration.generate( password=get_nucypher_password(confirm=True), config_root=config_root, rest_host=rest_host, rest_port=rest_port, db_filepath=db_filepath, domains={network} if network else None, federated_only=federated_only, checksum_address=staker_address, worker_address=worker_address, download_registry=download_registry, registry_filepath=registry_filepath, provider_process=ETH_NODE, provider_uri=provider_uri, poa=poa) painting.paint_new_installation_help(emitter, new_configuration=ursula_config) return # # Make Ursula # if dev: ursula_config = UrsulaConfiguration( dev_mode=True, domains={TEMPORARY_DOMAIN}, poa=poa, download_registry=False, registry_filepath=registry_filepath, provider_process=ETH_NODE, provider_uri=provider_uri, checksum_address=staker_address, worker_address=worker_address, federated_only=federated_only, rest_host=rest_host, rest_port=rest_port, db_filepath=db_filepath) else: try: ursula_config = UrsulaConfiguration.from_configuration_file( filepath=config_file, domains={network} if network else None, registry_filepath=registry_filepath, provider_process=ETH_NODE, provider_uri=provider_uri, rest_host=rest_host, rest_port=rest_port, db_filepath=db_filepath, poa=poa, federated_only=federated_only) except FileNotFoundError: return actions.handle_missing_configuration_file( character_config_class=UrsulaConfiguration, config_file=config_file) except NucypherKeyring.AuthenticationFailed as e: emitter.echo(str(e), color='red', bold=True) click.get_current_context().exit(1) # TODO: Exit codes (not only for this, but for other exceptions) # # Configured Pre-Authentication Actions # # Handle destruction and forget *before* network bootstrap and character initialization below if action == "destroy": """Delete all configuration files from the disk""" if dev: message = "'nucypher ursula destroy' cannot be used in --dev mode - There is nothing to destroy." raise click.BadOptionUsage(option_name='--dev', message=message) actions.destroy_configuration(emitter, character_config=ursula_config, force=force) return elif action == "forget": actions.forget(emitter, configuration=ursula_config) return # # Make Ursula # client_password = None if not ursula_config.federated_only: if not dev and not click_config.json_ipc: client_password = get_client_password( checksum_address=ursula_config.worker_address, envvar="NUCYPHER_WORKER_ETH_PASSWORD") try: URSULA = actions.make_cli_character(character_config=ursula_config, click_config=click_config, min_stake=min_stake, teacher_uri=teacher_uri, dev=dev, lonely=lonely, client_password=client_password) except NucypherKeyring.AuthenticationFailed as e: emitter.echo(str(e), color='red', bold=True) click.get_current_context().exit(1) # TODO: Exit codes (not only for this, but for other exceptions) # # Authenticated Action Switch # if action == 'run': """Seed, Produce, Run!""" # GO! try: # Ursula Deploy Warnings emitter.message(f"Starting Ursula on {URSULA.rest_interface}", color='green', bold=True) emitter.message(f"Connecting to {','.join(ursula_config.domains)}", color='green', bold=True) emitter.message("Working ~ Keep Ursula Online!", color='blue', bold=True) if interactive: stdio.StandardIO( UrsulaCommandProtocol(ursula=URSULA, emitter=emitter)) if dry_run: return # <-- ABORT - (Last Chance) # Run - Step 3 node_deployer = URSULA.get_deployer() node_deployer.addServices() node_deployer.catalogServers(node_deployer.hendrix) node_deployer.run() # <--- Blocking Call (Reactor) # Handle Crash except Exception as e: ursula_config.log.critical(str(e)) emitter.message(f"{e.__class__.__name__} {e}", color='red', bold=True) raise # Crash :-( # Graceful Exit finally: emitter.message("Stopping Ursula", color='green') ursula_config.cleanup() emitter.message("Ursula Stopped", color='red') return elif action == "save-metadata": """Manually save a node self-metadata file""" metadata_path = ursula.write_node_metadata(node=URSULA) emitter.message( f"Successfully saved node metadata to {metadata_path}.", color='green') return elif action == "view": """Paint an existing configuration to the console""" if not URSULA.federated_only: emitter.echo("BLOCKCHAIN ----------\n") painting.paint_contract_status(emitter=emitter, blockchain=URSULA.blockchain) current_block = URSULA.blockchain.w3.eth.blockNumber emitter.echo(f'Block # {current_block}') # TODO: 1231 emitter.echo(f'NU Balance (staker): {URSULA.token_balance}') emitter.echo( f'ETH Balance (worker): {URSULA.blockchain.client.get_balance(URSULA.worker_address)}' ) emitter.echo( f'Current Gas Price {URSULA.blockchain.client.gas_price}') emitter.echo("CONFIGURATION --------") response = UrsulaConfiguration._read_configuration_file( filepath=config_file or ursula_config.config_file_location) return emitter.ipc( response=response, request_id=0, duration=0 ) # FIXME: #1216 - what are request_id and duration here? elif action == 'confirm-activity': receipt = URSULA.confirm_activity() confirmed_period = URSULA.staking_agent.get_current_period() + 1 date = datetime_at_period(period=confirmed_period) # TODO: Double-check dates here emitter.echo( f'\nActivity confirmed for period #{confirmed_period} ' f'(starting at {date})', bold=True, color='blue') painting.paint_receipt_summary( emitter=emitter, receipt=receipt, chain_name=URSULA.blockchain.client.chain_name) # TODO: Check ActivityConfirmation event (see #1193) return else: raise click.BadArgumentUsage("No such argument {}".format(action))
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}'")
def stake(click_config, action, config_root, config_file, # Mode force, offline, hw_wallet, # Blockchain poa, registry_filepath, provider_uri, sync, # Stake staking_address, worker_address, withdraw_address, value, lock_periods, index, policy_reward, staking_reward, enable, lock_until, ) -> None: """ Manage stakes and other staker-related operations. \b Actions ------------------------------------------------- init-stakeholder Create a new stakeholder configuration list List active stakes for current stakeholder accounts Show ETH and NU balances for stakeholder's accounts sync Synchronize stake data with on-chain information set-worker Bond a worker to a staker detach-worker Detach worker currently bonded to a staker init Create a new stake restake Manage re-staking with --enable or --disable divide Create a new stake from part of an existing one collect-reward Withdraw staking reward """ # Banner emitter = click_config.emitter emitter.clear() emitter.banner(StakeHolder.banner) if action == 'init-stakeholder': if not provider_uri: raise click.BadOptionUsage(option_name='--provider', message="--provider is required to create a new stakeholder.") new_stakeholder = StakeHolderConfiguration.generate(config_root=config_root, provider_uri=provider_uri, poa=poa, sync=False, registry_filepath=registry_filepath) filepath = new_stakeholder.to_configuration_file(override=force) emitter.echo(f"Wrote new stakeholder configuration to {filepath}", color='green') return # Exit try: stakeholder_config = StakeHolderConfiguration.from_configuration_file(filepath=config_file, provider_uri=provider_uri, poa=poa, sync=False, registry_filepath=registry_filepath) except FileNotFoundError: return actions.handle_missing_configuration_file(character_config_class=StakeHolderConfiguration, config_file=config_file) # # Make Stakeholder # STAKEHOLDER = stakeholder_config.produce(initial_address=staking_address) blockchain = BlockchainInterfaceFactory.get_interface(provider_uri=provider_uri) # Eager connection economics = STAKEHOLDER.economics # Dynamic click types (Economics) min_locked = economics.minimum_allowed_locked stake_value_range = click.FloatRange(min=NU.from_nunits(min_locked).to_tokens(), clamp=False) stake_duration_range = click.IntRange(min=economics.minimum_locked_periods, clamp=False) stake_extension_range = click.IntRange(min=1, max=economics.maximum_allowed_locked, clamp=False) # # Eager Actions # if action == 'list': stakes = STAKEHOLDER.all_stakes if not stakes: emitter.echo(f"There are no active stakes") else: painting.paint_stakes(emitter=emitter, stakes=stakes) return # Exit elif action == 'accounts': for address, balances in STAKEHOLDER.wallet.balances.items(): emitter.echo(f"{address} | {Web3.fromWei(balances['ETH'], 'ether')} ETH | {NU.from_nunits(balances['NU'])}") return # Exit elif action == 'set-worker': if not staking_address: staking_address = select_stake(stakeholder=STAKEHOLDER, emitter=emitter).staker_address if not worker_address: worker_address = click.prompt("Enter worker address", type=EIP55_CHECKSUM_ADDRESS) # TODO: Check preconditions (e.g., minWorkerPeriods, already in use, etc) password = None if not hw_wallet and not blockchain.client.is_local: password = get_client_password(checksum_address=staking_address) STAKEHOLDER.assimilate(checksum_address=staking_address, password=password) receipt = STAKEHOLDER.set_worker(worker_address=worker_address) # TODO: Double-check dates current_period = STAKEHOLDER.staking_agent.get_current_period() bonded_date = datetime_at_period(period=current_period, seconds_per_period=economics.seconds_per_period) min_worker_periods = STAKEHOLDER.staking_agent.staking_parameters()[7] release_period = current_period + min_worker_periods release_date = datetime_at_period(period=release_period, seconds_per_period=economics.seconds_per_period) emitter.echo(f"\nWorker {worker_address} successfully bonded to staker {staking_address}", color='green') paint_receipt_summary(emitter=emitter, receipt=receipt, chain_name=blockchain.client.chain_name, transaction_type='set_worker') emitter.echo(f"Bonded at period #{current_period} ({bonded_date})", color='green') emitter.echo(f"This worker can be replaced or detached after period " f"#{release_period} ({release_date})", color='green') return # Exit elif action == 'detach-worker': if not staking_address: staking_address = select_stake(stakeholder=STAKEHOLDER, emitter=emitter).staker_address if worker_address: raise click.BadOptionUsage(message="detach-worker cannot be used together with --worker-address", option_name='--worker-address') # TODO: Check preconditions (e.g., minWorkerPeriods) worker_address = STAKEHOLDER.staking_agent.get_worker_from_staker(staking_address) password = None if not hw_wallet and not blockchain.client.is_local: password = get_client_password(checksum_address=staking_address) # TODO: Create Stakeholder.detach_worker() and use it here STAKEHOLDER.assimilate(checksum_address=staking_address, password=password) receipt = STAKEHOLDER.set_worker(worker_address=BlockchainInterface.NULL_ADDRESS) # TODO: Double-check dates current_period = STAKEHOLDER.staking_agent.get_current_period() bonded_date = datetime_at_period(period=current_period, seconds_per_period=economics.seconds_per_period) emitter.echo(f"Successfully detached worker {worker_address} from staker {staking_address}", color='green') paint_receipt_summary(emitter=emitter, receipt=receipt, chain_name=blockchain.client.chain_name, transaction_type='detach_worker') emitter.echo(f"Detached at period #{current_period} ({bonded_date})", color='green') return # Exit elif action == 'create': """Initialize a new stake""" # # Get Staking Account # password = None if not staking_address: staking_address = select_client_account(prompt="Select staking account", emitter=emitter, provider_uri=STAKEHOLDER.wallet.blockchain.provider_uri) if not hw_wallet and not blockchain.client.is_local: password = click.prompt(f"Enter password to unlock {staking_address}", hide_input=True, confirmation_prompt=False) # # Stage Stake # if not value: value = click.prompt(f"Enter stake value in NU", type=stake_value_range, default=NU.from_nunits(min_locked).to_tokens()) value = NU.from_tokens(value) if not lock_periods: prompt = f"Enter stake duration ({STAKEHOLDER.economics.minimum_locked_periods} periods minimum)" lock_periods = click.prompt(prompt, type=stake_duration_range) start_period = STAKEHOLDER.staking_agent.get_current_period() end_period = start_period + lock_periods # # Review # if not force: painting.paint_staged_stake(emitter=emitter, stakeholder=STAKEHOLDER, staking_address=staking_address, stake_value=value, lock_periods=lock_periods, start_period=start_period, end_period=end_period) confirm_staged_stake(staker_address=staking_address, value=value, lock_periods=lock_periods) # Last chance to bail click.confirm("Publish staged stake to the blockchain?", abort=True) # Execute STAKEHOLDER.assimilate(checksum_address=staking_address, password=password) new_stake = STAKEHOLDER.initialize_stake(amount=value, lock_periods=lock_periods) painting.paint_staking_confirmation(emitter=emitter, ursula=STAKEHOLDER, transactions=new_stake.transactions) return # Exit elif action == "restake": # Authenticate if not staking_address: staking_address = select_stake(stakeholder=STAKEHOLDER, emitter=emitter).staker_address password = None if not hw_wallet and not blockchain.client.is_local: password = get_client_password(checksum_address=staking_address) STAKEHOLDER.assimilate(checksum_address=staking_address, password=password) # Inner Exclusive Switch if lock_until: if not force: confirm_enable_restaking_lock(emitter, staking_address=staking_address, release_period=lock_until) receipt = STAKEHOLDER.enable_restaking_lock(release_period=lock_until) emitter.echo(f'Successfully enabled re-staking lock for {staking_address} until {lock_until}', color='green', verbosity=1) elif enable: if not force: confirm_enable_restaking(emitter, staking_address=staking_address) receipt = STAKEHOLDER.enable_restaking() emitter.echo(f'Successfully enabled re-staking for {staking_address}', color='green', verbosity=1) else: if not force: click.confirm(f"Confirm disable re-staking for staker {staking_address}?", abort=True) receipt = STAKEHOLDER.disable_restaking() emitter.echo(f'Successfully disabled re-staking for {staking_address}', color='green', verbosity=1) paint_receipt_summary(receipt=receipt, emitter=emitter, chain_name=blockchain.client.chain_name) return # Exit elif action == 'divide': """Divide an existing stake by specifying the new target value and end period""" if staking_address and index is not None: # 0 is valid. current_stake = STAKEHOLDER.stakes[index] else: current_stake = select_stake(stakeholder=STAKEHOLDER, emitter=emitter) # # Stage Stake # # Value if not value: value = click.prompt(f"Enter target value (must be less than or equal to {str(current_stake.value)})", type=stake_value_range) value = NU(value, 'NU') # Duration if not lock_periods: extension = click.prompt("Enter number of periods to extend", type=stake_extension_range) else: extension = lock_periods if not force: painting.paint_staged_stake_division(emitter=emitter, stakeholder=STAKEHOLDER, original_stake=current_stake, target_value=value, extension=extension) click.confirm("Is this correct?", abort=True) # Execute password = None if not hw_wallet and not blockchain.client.is_local: password = get_client_password(checksum_address=current_stake.staker_address) STAKEHOLDER.assimilate(checksum_address=current_stake.staker_address, password=password) modified_stake, new_stake = STAKEHOLDER.divide_stake(stake_index=current_stake.index, target_value=value, additional_periods=extension) emitter.echo('Successfully divided stake', color='green', verbosity=1) paint_receipt_summary(emitter=emitter, receipt=new_stake.receipt, chain_name=blockchain.client.chain_name) # Show the resulting stake list painting.paint_stakes(emitter=emitter, stakes=STAKEHOLDER.stakes) return # Exit elif action == 'collect-reward': """Withdraw staking reward to the specified wallet address""" password = None if not hw_wallet and not blockchain.client.is_local: password = get_client_password(checksum_address=staking_address) if not staking_reward and not policy_reward: raise click.BadArgumentUsage(f"Either --staking-reward or --policy-reward must be True to collect rewards.") STAKEHOLDER.assimilate(checksum_address=staking_address, password=password) if staking_reward: # Note: Sending staking / inflation rewards to another account is not allowed. staking_receipt = STAKEHOLDER.collect_staking_reward() paint_receipt_summary(receipt=staking_receipt, chain_name=STAKEHOLDER.wallet.blockchain.client.chain_name, emitter=emitter) if policy_reward: policy_receipt = STAKEHOLDER.collect_policy_reward(collector_address=withdraw_address) paint_receipt_summary(receipt=policy_receipt, chain_name=STAKEHOLDER.wallet.blockchain.client.chain_name, emitter=emitter) return # Exit # Catch-All for unknown actions else: ctx = click.get_current_context() raise click.UsageError(message=f"Unknown action '{action}'.", ctx=ctx)
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}'")
def stake( click_config, action, config_root, config_file, # Mode force, offline, hw_wallet, # Blockchain poa, registry_filepath, provider_uri, sync, # Stake staking_address, worker_address, withdraw_address, value, duration, index, policy_reward, staking_reward, ) -> None: """ Manage stakes and other staker-related operations. \b Actions ------------------------------------------------- new-stakeholder Create a new stakeholder configuration list List active stakes for current stakeholder accounts Show ETH and NU balances for stakeholder's accounts sync Synchronize stake data with on-chain information set-worker Bound a worker to a staker detach-worker Detach worker currently bound to a staker init Create a new stake divide Create a new stake from part of an existing one collect-reward Withdraw staking reward """ # Banner emitter = click_config.emitter emitter.clear() emitter.banner(NU_BANNER) if action == 'new-stakeholder': if not provider_uri: raise click.BadOptionUsage( option_name='--provider', message="--provider is required to create a new stakeholder.") registry = None fetch_registry = True if registry_filepath: registry = EthereumContractRegistry( registry_filepath=registry_filepath) fetch_registry = False blockchain = BlockchainInterface(provider_uri=provider_uri, registry=registry, poa=poa) blockchain.connect(sync_now=sync, fetch_registry=fetch_registry) new_stakeholder = StakeHolder(config_root=config_root, offline_mode=offline, blockchain=blockchain) filepath = new_stakeholder.to_configuration_file(override=force) emitter.echo(f"Wrote new stakeholder configuration to {filepath}", color='green') return # Exit # # Make Staker # STAKEHOLDER = StakeHolder.from_configuration_file( filepath=config_file, provider_uri=provider_uri, registry_filepath=registry_filepath, offline=offline, sync_now=sync) # # Eager Actions # if action == 'list': if not STAKEHOLDER.stakes: emitter.echo(f"There are no active stakes") else: painting.paint_stakes(emitter=emitter, stakes=STAKEHOLDER.stakes) return elif action == 'accounts': for address, balances in STAKEHOLDER.account_balances.items(): emitter.echo( f"{address} | {Web3.fromWei(balances['ETH'], 'ether')} ETH | {NU.from_nunits(balances['NU'])}" ) return # Exit elif action == 'sync': emitter.echo("Reading on-chain stake data...") STAKEHOLDER.read_onchain_stakes() STAKEHOLDER.to_configuration_file(override=True) emitter.echo("OK!", color='green') return # Exit elif action in ('set-worker', 'detach-worker'): if not staking_address: staking_address = select_stake(stakeholder=STAKEHOLDER, emitter=emitter).owner_address if action == 'set-worker': if not worker_address: worker_address = click.prompt("Enter worker address", type=EIP55_CHECKSUM_ADDRESS) elif action == 'detach-worker': if worker_address: raise click.BadOptionUsage( message= "detach-worker cannot be used together with --worker-address option" ) worker_address = BlockchainInterface.NULL_ADDRESS password = None if not hw_wallet and not STAKEHOLDER.blockchain.client.is_local: password = get_client_password(checksum_address=staking_address) receipt = STAKEHOLDER.set_worker(staker_address=staking_address, password=password, worker_address=worker_address) emitter.echo(f"OK | Receipt: {receipt['transactionHash'].hex()}", color='green') return # Exit elif action == 'init': """Initialize a new stake""" # # Get Staking Account # password = None if not staking_address: staking_address = select_client_account( blockchain=STAKEHOLDER.blockchain, prompt="Select staking account", emitter=emitter) if not hw_wallet and not STAKEHOLDER.blockchain.client.is_local: password = click.prompt( f"Enter password to unlock {staking_address}", hide_input=True, confirmation_prompt=False) # # Stage Stake # if not value: min_locked = STAKEHOLDER.economics.minimum_allowed_locked value = click.prompt( f"Enter stake value in NU", type=STAKE_VALUE, default=NU.from_nunits(min_locked).to_tokens()) value = NU.from_tokens(value) if not duration: prompt = f"Enter stake duration ({STAKEHOLDER.economics.minimum_locked_periods} periods minimum)" duration = click.prompt(prompt, type=STAKE_DURATION) start_period = STAKEHOLDER.staking_agent.get_current_period() end_period = start_period + duration # # Review # if not force: painting.paint_staged_stake(emitter=emitter, stakeholder=STAKEHOLDER, staking_address=staking_address, stake_value=value, duration=duration, start_period=start_period, end_period=end_period) confirm_staged_stake(staker_address=staking_address, value=value, duration=duration) # Last chance to bail click.confirm("Publish staged stake to the blockchain?", abort=True) # Execute new_stake = STAKEHOLDER.initialize_stake( amount=value, duration=duration, checksum_address=staking_address, password=password) painting.paint_staking_confirmation( emitter=emitter, ursula=STAKEHOLDER, transactions=new_stake.transactions) return # Exit elif action == 'divide': """Divide an existing stake by specifying the new target value and end period""" if staking_address and index is not None: staker = STAKEHOLDER.get_active_staker(address=staking_address) current_stake = staker.stakes[index] else: current_stake = select_stake(stakeholder=STAKEHOLDER, emitter=emitter) # # Stage Stake # # Value if not value: value = click.prompt( f"Enter target value (must be less than or equal to {str(current_stake.value)})", type=STAKE_VALUE) value = NU(value, 'NU') # Duration if not duration: extension = click.prompt("Enter number of periods to extend", type=STAKE_EXTENSION) else: extension = duration if not force: painting.paint_staged_stake_division(emitter=emitter, stakeholder=STAKEHOLDER, original_stake=current_stake, target_value=value, extension=extension) click.confirm("Is this correct?", abort=True) # Execute password = None if not hw_wallet and not STAKEHOLDER.blockchain.client.is_local: password = get_client_password( checksum_address=current_stake.owner_address) modified_stake, new_stake = STAKEHOLDER.divide_stake( address=current_stake.owner_address, index=current_stake.index, value=value, duration=extension, password=password) emitter.echo('Successfully divided stake', color='green', verbosity=1) emitter.echo(f'Receipt ........... {new_stake.receipt}', verbosity=1) # Show the resulting stake list painting.paint_stakes(emitter=emitter, stakes=STAKEHOLDER.stakes) return # Exit elif action == 'collect-reward': """Withdraw staking reward to the specified wallet address""" password = None if not hw_wallet and not STAKEHOLDER.blockchain.client.is_local: password = get_client_password(checksum_address=staking_address) STAKEHOLDER.collect_rewards(staker_address=staking_address, withdraw_address=withdraw_address, password=password, staking=staking_reward, policy=policy_reward) else: ctx = click.get_current_context() click.UsageError(message=f"Unknown action '{action}'.", ctx=ctx).show() return # Exit