def prolong(general_config, transacting_staker_options, config_file, force, lock_periods, index): """Prolong an existing stake's duration.""" # Setup emitter = _setup_emitter(general_config) STAKEHOLDER = transacting_staker_options.create_character(emitter, config_file) action_period = STAKEHOLDER.staking_agent.get_current_period() blockchain = transacting_staker_options.get_blockchain() economics = STAKEHOLDER.economics # Handle account selection client_account, staking_address = handle_client_account_for_staking( emitter=emitter, stakeholder=STAKEHOLDER, staking_address=transacting_staker_options.staker_options.staking_address, individual_allocation=STAKEHOLDER.individual_allocation, force=force) # Handle stake update and selection if transacting_staker_options.staker_options.staking_address and index is not None: # 0 is valid. STAKEHOLDER.stakes = StakeList(registry=STAKEHOLDER.registry, checksum_address=transacting_staker_options.staker_options.staking_address) STAKEHOLDER.stakes.refresh() current_stake = STAKEHOLDER.stakes[index] else: current_stake = select_stake(stakeholder=STAKEHOLDER, emitter=emitter) # # Prolong # # Interactive if not lock_periods: max_extension = MAX_UINT16 - current_stake.final_locked_period # +1 because current period excluded min_extension = economics.minimum_locked_periods - current_stake.periods_remaining + 1 if min_extension < 1: min_extension = 1 duration_extension_range = click.IntRange(min=min_extension, max=max_extension, clamp=False) lock_periods = click.prompt(f"Enter number of periods to extend ({min_extension}-{max_extension})", type=duration_extension_range) if not force: click.confirm(f"Publish stake extension of {lock_periods} period(s) to the blockchain?", abort=True) password = transacting_staker_options.get_password(blockchain, client_account) # Non-interactive: Consistency check to prevent the above agreement from going stale. last_second_current_period = STAKEHOLDER.staking_agent.get_current_period() if action_period != last_second_current_period: emitter.echo("Current period advanced before transaction was broadcasted. Please try again.", red='red') raise click.Abort # Authenticate and Execute STAKEHOLDER.assimilate(checksum_address=current_stake.staker_address, password=password) receipt = STAKEHOLDER.prolong_stake(stake_index=current_stake.index, additional_periods=lock_periods) # Report emitter.echo('Successfully Prolonged Stake', color='green', verbosity=1) paint_receipt_summary(emitter=emitter, receipt=receipt, chain_name=blockchain.client.chain_name) painting.paint_stakes(emitter=emitter, stakeholder=STAKEHOLDER) return # Exit
def refund(general_config, worklock_options, force, hw_wallet): """Reclaim ETH unlocked by your work""" emitter, registry, blockchain = worklock_options.setup( general_config=general_config) if not worklock_options.bidder_address: worklock_options.bidder_address = select_client_account( emitter=emitter, provider_uri=worklock_options.provider_uri, poa=worklock_options.poa, network=worklock_options.network, registry=registry, show_balances=True) if not force: click.confirm( f"Collect ETH refund for bidder {worklock_options.bidder_address}?", abort=True) emitter.echo("Submitting WorkLock refund request...") bidder = worklock_options.create_bidder(registry=registry, hw_wallet=hw_wallet) receipt = bidder.refund_deposit() paint_receipt_summary( receipt=receipt, emitter=emitter, chain_name=bidder.staking_agent.blockchain.client.chain_name) return # Exit
def cancel_bid(general_config, worklock_options, force, hw_wallet): """Cancel your bid and receive your ETH back""" emitter, registry, blockchain = worklock_options.setup( general_config=general_config) worklock_agent = ContractAgency.get_agent( WorkLockAgent, registry=registry) # type: WorkLockAgent now = maya.now().epoch if not worklock_agent.start_bidding_date <= now <= worklock_agent.end_cancellation_date: raise click.Abort( f"You can't cancel your bid. The cancellation window is closed.") if not worklock_options.bidder_address: # TODO: Consider bundle this in worklock_options worklock_options.bidder_address = select_client_account( emitter=emitter, provider_uri=worklock_options.provider_uri, poa=worklock_options.poa, network=worklock_options.network, show_balances=True, registry=registry) bidder = worklock_options.create_bidder(registry=registry, hw_wallet=hw_wallet) if not force: value = bidder.get_deposited_eth click.confirm( f"Confirm bid cancellation of {prettify_eth_amount(value)}" f" for {worklock_options.bidder_address}?", abort=True) receipt = bidder.cancel_bid() emitter.echo("Bid canceled\n", color='green') paint_receipt_summary( receipt=receipt, emitter=emitter, chain_name=bidder.staking_agent.blockchain.client.chain_name) return # Exit
def claim(general_config, worklock_options, registry_options, force, hw_wallet): """Claim tokens for a successful bid, and start staking them""" emitter = _setup_emitter(general_config) if not worklock_options.bidder_address: worklock_options.bidder_address = select_client_account( emitter=emitter, provider_uri=registry_options.provider_uri, network=registry_options.network, show_balances=True) # TODO: Show amount of tokens to claim if not force: emitter.echo( "Note: Claiming WorkLock NU tokens will initialize a new stake.", color='blue') click.confirm( f"Continue worklock claim for bidder {worklock_options.bidder_address}?", abort=True) emitter.message("Submitting Claim...") registry = registry_options.get_registry(emitter, general_config.debug) bidder = worklock_options.create_bidder(registry=registry, hw_wallet=hw_wallet) receipt = bidder.claim() paint_receipt_summary( receipt=receipt, emitter=emitter, chain_name=bidder.staking_agent.blockchain.client.chain_name) paint_worklock_claim(emitter=emitter, bidder_address=worklock_options.bidder_address, network=registry_options.network, provider_uri=registry_options.provider_uri) return # Exit
def cancel_bid(general_config, registry_options, worklock_options, force, hw_wallet): """Cancel your bid""" emitter = _setup_emitter(general_config) if not worklock_options.bidder_address: # TODO: Consider bundle this in worklock_options worklock_options.bidder_address = select_client_account( emitter=emitter, provider_uri=registry_options.provider_uri, network=registry_options.network, show_balances=True) registry = registry_options.get_registry(emitter, general_config.debug) bidder = worklock_options.create_bidder(registry=registry, hw_wallet=hw_wallet) if not force: value = bidder.get_deposited_eth click.confirm( f"Confirm bid cancellation of {prettify_eth_amount(value)}" f" for {worklock_options.bidder_address}?", abort=True) receipt = bidder.cancel_bid() emitter.echo("Bid canceled\n", color='green') paint_receipt_summary( receipt=receipt, emitter=emitter, chain_name=bidder.staking_agent.blockchain.client.chain_name) return # Exit
def transfer_tokens(general_config, actor_options, target_address, value): """ Transfer tokens from contract's owner address to another address """ emitter = general_config.emitter ADMINISTRATOR, deployer_address, _, local_registry = actor_options.create_actor( emitter) 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=int(value), sender_address=deployer_address, target_address=target_address) paint_receipt_summary(emitter=emitter, receipt=receipt)
def confirm_activity(click_config, # API Options geth, provider_uri, network, registry_filepath, staker_address, worker_address, federated_only, rest_host, rest_port, db_filepath, poa, light, config_file, dev, lonely, teacher_uri, min_stake): """ Manually confirm-activity for the current period. """ ### Setup ### _validate_args(geth, federated_only, staker_address, registry_filepath) emitter = _setup_emitter(click_config, worker_address) _pre_launch_warnings(emitter, dev=dev, force=None) ursula_config, provider_uri = _get_ursula_config(emitter, geth, provider_uri, network, registry_filepath, dev, config_file, staker_address, worker_address, federated_only, rest_host, rest_port, db_filepath, poa, light) ############# URSULA = _create_ursula(ursula_config, click_config, dev, emitter, lonely, teacher_uri, min_stake) receipt = URSULA.confirm_activity() confirmed_period = URSULA.staking_agent.get_current_period() + 1 date = datetime_at_period(period=confirmed_period, seconds_per_period=URSULA.economics.seconds_per_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.staking_agent.blockchain.client.chain_name)
def confirm_activity(general_config, character_options, config_file): """ Manually confirm-activity for the current period. """ emitter = _setup_emitter(general_config, character_options.config_options.worker_address) _pre_launch_warnings(emitter, dev=character_options.config_options.dev, force=None) _, URSULA = character_options.create_character(emitter, config_file, general_config.json_ipc, load_seednodes=False) confirmed_period = URSULA.staking_agent.get_current_period() + 1 click.echo(f"Confirming activity for period {confirmed_period}", color='blue') receipt = URSULA.confirm_activity() economics = TokenEconomicsFactory.get_economics(registry=URSULA.registry) date = datetime_at_period(period=confirmed_period, seconds_per_period=economics.seconds_per_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.staking_agent.blockchain.client.chain_name)
def winddown(general_config, transacting_staker_options, config_file, enable, lock_until, force): """ Manage winding down with --enable or --disable. """ emitter = _setup_emitter(general_config) STAKEHOLDER = transacting_staker_options.create_character(emitter, config_file) blockchain = transacting_staker_options.get_blockchain() client_account, staking_address = handle_client_account_for_staking( emitter=emitter, stakeholder=STAKEHOLDER, staking_address=transacting_staker_options.staker_options.staking_address, individual_allocation=STAKEHOLDER.individual_allocation, force=force) # Authenticate password = transacting_staker_options.get_password(blockchain, client_account) STAKEHOLDER.assimilate(checksum_address=client_account, password=password) # Inner Exclusive Switch if enable: if not force: confirm_enable_winding_down(emitter, staking_address=staking_address) receipt = STAKEHOLDER.enable_winding_down() emitter.echo(f'Successfully enabled winding down for {staking_address}', color='green', verbosity=1) else: if not force: click.confirm(f"Confirm disable winding down for staker {staking_address}?", abort=True) receipt = STAKEHOLDER.disable_winding_down() emitter.echo(f'Successfully disabled winding down for {staking_address}', color='green', verbosity=1) paint_receipt_summary(receipt=receipt, emitter=emitter, chain_name=blockchain.client.chain_name)
def transfer_ownership(general_config, actor_options, target_address, gas): """ Transfer ownership of contracts to another address. """ emitter = general_config.emitter ADMINISTRATOR, _, _, _ = actor_options.create_actor(emitter) if not target_address: target_address = click.prompt("Enter new owner's checksum address", type=EIP55_CHECKSUM_ADDRESS) contract_name = actor_options.contract_name 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) receipts = contract_deployer.transfer_ownership(new_owner=target_address, transaction_gas_limit=gas) else: click.confirm(f"You are about to relinquish ownership of all ownable contracts in favor of {target_address}.\n" f"Are you sure you want to continue?", abort=True) receipts = ADMINISTRATOR.relinquish_ownership(new_owner=target_address, transaction_gas_limit=gas) for tx_type, receipt in receipts.items(): paint_receipt_summary(emitter=emitter, receipt=receipt, transaction_type=tx_type)
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 set_worker(general_config, transacting_staker_options, config_file, force, worker_address): """ Bond a worker to a staker. """ emitter = _setup_emitter(general_config) STAKEHOLDER = transacting_staker_options.create_character(emitter, config_file) blockchain = transacting_staker_options.get_blockchain() economics = STAKEHOLDER.economics client_account, staking_address = handle_client_account_for_staking( emitter=emitter, stakeholder=STAKEHOLDER, staking_address=transacting_staker_options.staker_options.staking_address, individual_allocation=STAKEHOLDER.individual_allocation, force=force) if not worker_address: worker_address = click.prompt("Enter worker address", type=EIP55_CHECKSUM_ADDRESS) if (worker_address == staking_address) and not force: click.confirm("The worker address provided is the same as the staking account. " "It is *highly recommended* to use a different accounts for staker and worker roles.\n" "Continue?", abort=True) # TODO: Check preconditions (e.g., minWorkerPeriods, already in use, etc) password = transacting_staker_options.get_password(blockchain, client_account) # TODO: Double-check dates # Calculate release datetime 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.economics.minimum_worker_periods 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) if not force: click.confirm(f"Commit to bonding " f"worker {worker_address} to staker {staking_address} " f"for a minimum of {STAKEHOLDER.economics.minimum_worker_periods} periods?", abort=True) STAKEHOLDER.assimilate(checksum_address=client_account, password=password) receipt = STAKEHOLDER.set_worker(worker_address=worker_address) # Report Success 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 upgrade(general_config, actor_options, retarget, target_address, ignore_deployed, multisig): """ Upgrade NuCypher existing proxy contract deployments. """ # Init emitter = general_config.emitter ADMINISTRATOR, _, _, registry = actor_options.create_actor(emitter, is_multisig=bool(multisig)) # FIXME: Workaround for building MultiSig TXs contract_name = actor_options.contract_name if not contract_name: raise click.BadArgumentUsage(message="--contract-name is required when using --upgrade") if multisig: if not target_address: raise click.BadArgumentUsage(message="--multisig requires using --target-address.") if not actor_options.force: click.confirm(f"Confirm building a re-target transaction for {contract_name}'s proxy to {target_address}?", abort=True) transaction = ADMINISTRATOR.retarget_proxy(contract_name=contract_name, target_address=target_address, just_build_transaction=True) trustee_address = select_client_account(emitter=emitter, prompt="Select trustee address", provider_uri=actor_options.provider_uri, show_balances=False) if not actor_options.force: click.confirm(f"Selected {trustee_address} - Continue?", abort=True) trustee = Trustee(registry=registry, checksum_address=trustee_address) transaction_proposal = trustee.create_transaction_proposal(transaction) emitter.message(f"Transaction to retarget {contract_name} proxy to {target_address} was built:", color='green') paint_multisig_proposed_transaction(emitter, transaction_proposal) # TODO: Show decoded function too filepath = f'proposal-{trustee.multisig_agent.contract_address[:8]}-TX-{transaction_proposal.nonce}.json' transaction_proposal.write(filepath=filepath) emitter.echo(f"Saved proposal to {filepath}", color='blue', bold=True) elif retarget: if not target_address: raise click.BadArgumentUsage(message="--target-address is required when using --retarget") if not actor_options.force: click.confirm(f"Confirm re-target {contract_name}'s proxy to {target_address}?", abort=True) receipt = ADMINISTRATOR.retarget_proxy(contract_name=contract_name, target_address=target_address) emitter.message(f"Successfully re-targeted {contract_name} proxy to {target_address}", color='green') paint_receipt_summary(emitter=emitter, receipt=receipt) else: if not actor_options.force: click.confirm(f"Confirm deploy new version of {contract_name} and retarget proxy?", abort=True) receipts = ADMINISTRATOR.upgrade_contract(contract_name=contract_name, ignore_deployed=ignore_deployed) emitter.message(f"Successfully deployed and upgraded {contract_name}", color='green') for name, receipt in receipts.items(): paint_receipt_summary(emitter=emitter, receipt=receipt)
def burn_unclaimed_tokens(general_config, registry_options, checksum_address): emitter = _setup_emitter(general_config) registry = registry_options.get_registry(emitter, general_config.debug) worklock_agent = ContractAgency.get_agent(WorkLockAgent, registry=registry) if not checksum_address: checksum_address = select_client_account(emitter=emitter, provider_uri=general_config.provider_uri) receipt = worklock_agent.burn_unclaimed(sender_address=checksum_address) paint_receipt_summary(receipt=receipt, emitter=emitter, chain_name=worklock_agent.blockchain.client.chain_name) return # Exit
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 enable_claiming(general_config, registry_options, worklock_options, force, hw_wallet, gas_limit): """Ensure correctness of bidding and enable claiming""" emitter = _setup_emitter(general_config) registry = registry_options.get_registry(emitter, general_config.debug) if not worklock_options.bidder_address: # TODO: Consider bundle this in worklock_options worklock_options.bidder_address = select_client_account(emitter=emitter, provider_uri=registry_options.provider_uri, network=registry_options.network, registry=registry, show_balances=True) bidder = worklock_options.create_bidder(registry=registry, hw_wallet=hw_wallet) whales = bidder.get_whales() if whales: headers = ("Bidders that require correction", "Current bid bonus") columns = (whales.keys(), map(prettify_eth_amount, whales.values())) emitter.echo(tabulate.tabulate(dict(zip(headers, columns)), headers=headers, floatfmt="fancy_grid")) if not force: click.confirm(f"Confirm force refund to at least {len(whales)} bidders" f" using {worklock_options.bidder_address}?", abort=True) force_refund_receipt = bidder.force_refund() emitter.echo(f"At least {len(whales)} bidders got a force refund\n", color='green') paint_receipt_summary(receipt=force_refund_receipt, emitter=emitter, chain_name=bidder.staking_agent.blockchain.client.chain_name, transaction_type=f"force-refund") else: emitter.echo(f"All bids are correct, force refund is not needed\n", color='yellow') if not bidder.worklock_agent.bidders_checked(): if not gas_limit: # TODO print gas estimations min_gas = 180000 gas_limit = click.prompt(f"Enter gas limit per each verification transaction (at least {min_gas})", type=click.IntRange(min=min_gas)) if not force: click.confirm(f"Confirm verifying of bidding from {worklock_options.bidder_address} " f"using {gas_limit} gas per each transaction?", abort=True) verification_receipts = bidder.verify_bidding_correctness(gas_limit=gas_limit) emitter.echo("Bidding has been checked\n", color='green') for iteration, receipt in verification_receipts.items(): paint_receipt_summary(receipt=receipt, emitter=emitter, chain_name=bidder.staking_agent.blockchain.client.chain_name, transaction_type=f"verify-correctness[{iteration}]") else: emitter.echo(f"Bidders have already been checked\n", color='yellow') return # Exit
def bid(general_config, worklock_options, registry_options, force, hw_wallet, value): """Place a bid, or increase an existing bid""" emitter = _setup_emitter(general_config) registry = registry_options.get_registry(emitter, general_config.debug) worklock_agent = ContractAgency.get_agent(WorkLockAgent, registry=registry) # type: WorkLockAgent now = maya.now().epoch if not worklock_agent.start_bidding_date <= now <= worklock_agent.end_bidding_date: raise click.Abort(f"You can't bid, the bidding window is closed.") if not worklock_options.bidder_address: worklock_options.bidder_address = select_client_account(emitter=emitter, provider_uri=registry_options.provider_uri, poa=registry_options.poa, network=registry_options.network, registry=registry, show_balances=True) bidder = worklock_options.create_bidder(registry=registry, hw_wallet=hw_wallet) if not value: if force: raise click.MissingParameter("Missing --value.") existing_bid_amount = bidder.get_deposited_eth if not existing_bid_amount: # It's the first bid minimum_bid = bidder.worklock_agent.minimum_allowed_bid minimum_bid_in_eth = Web3.fromWei(minimum_bid, 'ether') prompt = f"Enter bid amount in ETH (at least {minimum_bid_in_eth} ETH)" else: # There's an existing bid and the bidder is increasing the amount emitter.message(f"You have an existing bid of {Web3.fromWei(existing_bid_amount, 'ether')} ETH") minimum_bid_in_eth = Web3.fromWei(1, 'ether') prompt = f"Enter the amount in ETH that you want to increase your bid" value = click.prompt(prompt, type=DecimalRange(min=minimum_bid_in_eth)) value = int(Web3.toWei(Decimal(value), 'ether')) if not force: paint_bidding_notice(emitter=emitter, bidder=bidder) click.confirm(f"Place WorkLock bid of {prettify_eth_amount(value)}?", abort=True) receipt = bidder.place_bid(value=value) emitter.message("Publishing WorkLock Bid...") maximum = NU.from_nunits(bidder.economics.maximum_allowed_locked) available_claim = NU.from_nunits(bidder.available_claim) message = f'Current bid: {prettify_eth_amount(bidder.get_deposited_eth)} | Claim: {available_claim}\n' if available_claim > maximum: message += f"This claim is currently above the allowed max ({maximum}), so the bid may be partially refunded.\n" message += f'Note that available claim value may fluctuate until bidding closes and claims are finalized.\n' emitter.echo(message, color='yellow') paint_receipt_summary(receipt=receipt, emitter=emitter, chain_name=bidder.staking_agent.blockchain.client.chain_name) return # Exit
def refund(general_config, worklock_options, registry_options, force): emitter = _setup_emitter(general_config) if not worklock_options.bidder_address: worklock_options.bidder_address = select_client_account(emitter=emitter, provider_uri=general_config.provider_uri) if not force: click.confirm(f"Collect ETH refund for bidder {worklock_options.bidder_address}?", abort=True) emitter.message("Submitting WorkLock refund request...") registry = registry_options.get_registry(emitter, general_config.debug) bidder = worklock_options.create_bidder(registry=registry) receipt = bidder.refund_deposit() paint_receipt_summary(receipt=receipt, emitter=emitter, chain_name=bidder.staking_agent.blockchain.client.chain_name) return # Exit
def preallocation(general_config, transacting_staker_options, config_file, action, force): """ Claim token rewards collected by a preallocation contract. """ ### Setup ### emitter = _setup_emitter(general_config) STAKEHOLDER = transacting_staker_options.create_character( emitter, config_file) blockchain = transacting_staker_options.get_blockchain() # Unauthenticated actions: status if action == 'status': painting.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=transacting_staker_options.staker_options. staking_address, individual_allocation=STAKEHOLDER.individual_allocation, force=force) password = transacting_staker_options.get_password(blockchain, 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 collect_reward(general_config, transacting_staker_options, config_file, staking_reward, policy_reward, withdraw_address, force): """ Withdraw staking reward. """ ### Setup ### emitter = _setup_emitter(general_config) STAKEHOLDER = transacting_staker_options.create_character( emitter, config_file) blockchain = transacting_staker_options.get_blockchain() if not staking_reward and not policy_reward: raise click.BadArgumentUsage( f"Either --staking-reward or --policy-reward must be True to collect rewards." ) client_account, staking_address = handle_client_account_for_staking( emitter=emitter, stakeholder=STAKEHOLDER, staking_address=transacting_staker_options.staker_options. staking_address, individual_allocation=STAKEHOLDER.individual_allocation, force=force) password = transacting_staker_options.get_password(blockchain, client_account) 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 upgrade(general_config, actor_options, retarget, target_address, ignore_deployed): """ Upgrade NuCypher existing proxy contract deployments. """ # Init emitter = general_config.emitter ADMINISTRATOR, _, _, _ = actor_options.create_actor(emitter) contract_name = actor_options.contract_name 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) if retarget: if not target_address: raise click.BadArgumentUsage( message="--target-address is required when using --retarget") if not actor_options.force: click.confirm( f"Confirm re-target {contract_name}'s proxy to {target_address}?", abort=True) receipt = ADMINISTRATOR.retarget_proxy( contract_name=contract_name, target_address=target_address, existing_plaintext_secret=existing_secret, new_plaintext_secret=new_secret) emitter.message( f"Successfully re-targeted {contract_name} proxy to {target_address}", color='green') paint_receipt_summary(emitter=emitter, receipt=receipt) else: if not actor_options.force: click.confirm( f"Confirm deploy new version of {contract_name} and retarget proxy?", abort=True) receipts = ADMINISTRATOR.upgrade_contract( contract_name=contract_name, existing_plaintext_secret=existing_secret, new_plaintext_secret=new_secret, ignore_deployed=ignore_deployed) emitter.message(f"Successfully deployed and upgraded {contract_name}", color='green') for name, receipt in receipts.items(): paint_receipt_summary(emitter=emitter, receipt=receipt)
def cancel_bid(general_config, registry_options, worklock_options, force): emitter = _setup_emitter(general_config) if not worklock_options.bidder_address: worklock_options.bidder_address = select_client_account(emitter=emitter, provider_uri=general_config.provider_uri) registry = registry_options.get_registry(emitter, general_config.debug) bidder = worklock_options.create_bidder(registry=registry) if not force: value = bidder.get_deposited_eth click.confirm(f"Confirm bid cancellation of {Web3.fromWei(value, 'ether')} ETH" f" for {worklock_options.bidder_address}?", abort=True) receipt = bidder.cancel_bid() paint_receipt_summary(receipt=receipt, emitter=emitter, chain_name=bidder.staking_agent.blockchain.client.chain_name) return # Exit
def claim(general_config, worklock_options, registry_options, force, hw_wallet): """Claim tokens for your bid, and start staking them""" emitter = _setup_emitter(general_config) registry = registry_options.get_registry(emitter, general_config.debug) worklock_agent = ContractAgency.get_agent(WorkLockAgent, registry=registry) # type: WorkLockAgent if not worklock_agent.is_claiming_available(): raise click.Abort(f"You can't claim tokens. Claiming is not currently available.") if not worklock_options.bidder_address: worklock_options.bidder_address = select_client_account(emitter=emitter, provider_uri=registry_options.provider_uri, poa=registry_options.poa, network=registry_options.network, registry=registry, show_balances=True) bidder = worklock_options.create_bidder(registry=registry, hw_wallet=hw_wallet) unspent_bid = bidder.available_compensation if unspent_bid: emitter.echo(f"Note that WorkLock did not use your entire bid due to a maximum claim limit.\n" f"Therefore, an unspent amount of {prettify_eth_amount(unspent_bid)} is available for refund.") if not force: click.confirm(f"Before claiming your NU tokens for {worklock_options.bidder_address}, you will need to be refunded your unspent bid amount. Would you like to proceed?", abort=True) emitter.echo("Requesting refund of unspent bid amount...") receipt = bidder.withdraw_compensation() paint_receipt_summary(receipt=receipt, emitter=emitter, chain_name=bidder.staking_agent.blockchain.client.chain_name) has_claimed = bidder._has_claimed if has_claimed: emitter.echo(f"Claim was already done for {bidder.checksum_address}", color='red') return tokens = NU.from_nunits(bidder.available_claim) emitter.echo(f"\nYou have an available claim of {tokens} 🎉 \n", color='green', bold=True) if not force: lock_duration = bidder.worklock_agent.worklock_parameters()[-2] emitter.echo(f"Note: Claiming WorkLock NU tokens will initialize a new stake to be locked for {lock_duration} periods.", color='blue') click.confirm(f"Continue WorkLock claim for bidder {worklock_options.bidder_address}?", abort=True) emitter.echo("Submitting Claim...") receipt = bidder.claim() paint_receipt_summary(receipt=receipt, emitter=emitter, chain_name=bidder.staking_agent.blockchain.client.chain_name) paint_worklock_claim(emitter=emitter, bidder_address=worklock_options.bidder_address, network=registry_options.network, provider_uri=registry_options.provider_uri) return # Exit
def claim(general_config, worklock_options, registry_options, force): emitter = _setup_emitter(general_config) if not worklock_options.bidder_address: worklock_options.bidder_address = select_client_account(emitter=emitter, provider_uri=general_config.provider_uri) if not force: emitter.message("Note: Claiming WorkLock NU tokens will initialize a new stake.", color='blue') click.confirm(f"Continue worklock claim for bidder {worklock_options.bidder_address}?", abort=True) emitter.message("Submitting Claim...") registry = registry_options.get_registry(emitter, general_config.debug) bidder = worklock_options.create_bidder(registry=registry) receipt = bidder.claim() paint_receipt_summary(receipt=receipt, emitter=emitter, chain_name=bidder.staking_agent.blockchain.client.chain_name) paint_worklock_claim(emitter, bidder_address=worklock_options.bidder_address) return # Exit
def execute(general_config, blockchain_options, multisig_options, proposal): """ Collect authorizations from executives and execute transaction through MultiSig contract """ # Init emitter = general_config.emitter #_ensure_config_root(actor_options.config_root) blockchain = blockchain_options.connect_blockchain(emitter, general_config.debug) registry = blockchain_options.get_registry() proposal = Proposal.from_file(proposal) if not multisig_options.checksum_address: multisig_options.checksum_address = select_client_account( emitter=emitter, provider_uri=blockchain_options.provider_uri, poa=blockchain_options.poa, network=blockchain_options.network, registry=registry, show_balances=True) name, version, address, abi = registry.search( contract_address=proposal.target_address) # TODO: This assumes that we're always signing proxy retargetting. For the moment is true. proxy_contract = blockchain.client.w3.eth.contract( abi=abi, address=address, version=version, ContractFactoryClass=blockchain._contract_factory) paint_multisig_proposed_transaction(emitter, proposal, proxy_contract) trustee = multisig_options.create_trustee(registry) threshold = trustee.multisig_agent.threshold while len(trustee.authorizations) < threshold: auth_hex = click.prompt("Signature", type=click.STRING) authorization = Authorization.from_hex(auth_hex) executive_address = trustee.add_authorization(authorization, proposal) emitter.echo(f"Added authorization from executive {executive_address}", color='green') click.confirm( "\nCollected required authorizations. Proceed with execution?", abort=True) receipt = trustee.execute(proposal) paint_receipt_summary(emitter, receipt)
def bid(general_config, worklock_options, registry_options, force, hw_wallet, value): """Place a bid""" emitter = _setup_emitter(general_config) if not worklock_options.bidder_address: worklock_options.bidder_address = select_client_account( emitter=emitter, provider_uri=registry_options.provider_uri, network=registry_options.network, show_balances=True) if not value: if force: raise click.MissingParameter("Missing --value.") value = click.prompt("Enter bid amount in ETH", type=click.STRING) value = int(Web3.toWei(Decimal(value), 'ether')) registry = registry_options.get_registry(emitter, general_config.debug) bidder = worklock_options.create_bidder(registry=registry, hw_wallet=hw_wallet) if not force: paint_bidding_notice(emitter=emitter, bidder=bidder) click.confirm(f"Place WorkLock bid of {prettify_eth_amount(value)}?", abort=True) receipt = bidder.place_bid(value=value) emitter.message("Publishing WorkLock Bid...") # Ensure the total bid value is worth a claim that is at # least large enough for the minimum stake. minimum = bidder.economics.minimum_allowed_locked available_claim = NU.from_nunits(bidder.available_claim) if available_claim < minimum: warning = f"Total bid ({available_claim}) is too small for a claim, please bid more or cancel.\n" \ f"Total must be worth at least {NU.from_nunits(minimum)})." emitter.echo(warning, color='yellow') else: message = f'Current bid: {prettify_eth_amount(bidder.get_deposited_eth)} | ' \ f'Available claim: {available_claim}\n' \ f'Note that available claim value may fluctuate until bidding closes and claims are finalized.\n' emitter.echo(message, color='yellow') paint_receipt_summary( receipt=receipt, emitter=emitter, chain_name=bidder.staking_agent.blockchain.client.chain_name) return # Exit
def detach_worker(general_config, transacting_staker_options, config_file, force): """ Detach worker currently bonded to a staker. """ emitter = _setup_emitter(general_config) STAKEHOLDER = transacting_staker_options.create_character( emitter, config_file) blockchain = transacting_staker_options.get_blockchain() economics = STAKEHOLDER.economics client_account, staking_address = handle_client_account_for_staking( emitter=emitter, stakeholder=STAKEHOLDER, staking_address=transacting_staker_options.staker_options. staking_address, individual_allocation=STAKEHOLDER.individual_allocation, force=force) # TODO: Check preconditions (e.g., minWorkerPeriods) worker_address = STAKEHOLDER.staking_agent.get_worker_from_staker( staking_address) password = transacting_staker_options.get_password(blockchain, 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 set_min_rate(general_config, transacting_staker_options, config_file, force, min_rate): """ Set minimum acceptable value for the reward rate. """ emitter = _setup_emitter(general_config) STAKEHOLDER = transacting_staker_options.create_character( emitter, config_file) blockchain = transacting_staker_options.get_blockchain() client_account, staking_address = handle_client_account_for_staking( emitter=emitter, stakeholder=STAKEHOLDER, staking_address=transacting_staker_options.staker_options. staking_address, individual_allocation=STAKEHOLDER.individual_allocation, force=force) if not min_rate: painting.paint_min_rate(emitter, STAKEHOLDER.registry, STAKEHOLDER.policy_agent, staking_address) # TODO check range min_rate = click.prompt( "Enter new value for min reward rate within range", type=WEI) password = transacting_staker_options.get_password(blockchain, client_account) if not force: click.confirm( f"Commit new value {min_rate} for " f"minimum acceptable reward rate?", abort=True) STAKEHOLDER.assimilate(checksum_address=client_account, password=password) receipt = STAKEHOLDER.set_min_reward_rate(min_rate=min_rate) # Report Success emitter.echo( f"\nMinimum reward rate {min_rate} successfully set by staker {staking_address}", color='green') paint_receipt_summary(emitter=emitter, receipt=receipt, chain_name=blockchain.client.chain_name, transaction_type='set_min_rate')