Extracts staking CLI to 'nucypher stake' entry point

pull/1124/head
Kieran Prasch 2019-06-06 16:53:59 -07:00
parent 7b16196765
commit 4803831b18
No known key found for this signature in database
GPG Key ID: 199AB839D4125A62
3 changed files with 189 additions and 139 deletions

View File

@ -21,7 +21,6 @@ import click
from constant_sorrow.constants import NO_BLOCKCHAIN_CONNECTION
from twisted.internet import stdio
from nucypher.blockchain.eth.token import NU
from nucypher.characters.banners import URSULA_BANNER
from nucypher.cli import actions, painting
from nucypher.cli.actions import get_password
@ -30,10 +29,8 @@ from nucypher.cli.processes import UrsulaCommandProtocol
from nucypher.cli.types import (
EIP55_CHECKSUM_ADDRESS,
NETWORK_PORT,
EXISTING_READABLE_FILE,
STAKE_DURATION,
STAKE_EXTENSION,
STAKE_VALUE)
EXISTING_READABLE_FILE
)
from nucypher.config.characters import UrsulaConfiguration
from nucypher.utilities.sandbox.constants import (
TEMPORARY_DOMAIN,
@ -55,7 +52,6 @@ from nucypher.utilities.sandbox.constants import (
@click.option('--rest-port', help="The host port to run Ursula network services on", type=NETWORK_PORT)
@click.option('--db-filepath', help="The database filepath to connect to", type=click.STRING)
@click.option('--checksum-address', help="Run with a specified account", type=EIP55_CHECKSUM_ADDRESS)
@click.option('--withdraw-address', help="Send reward collection to an alternate address", type=EIP55_CHECKSUM_ADDRESS)
@click.option('--federated-only', '-F', help="Connect only to federated nodes", is_flag=True, default=None)
@click.option('--interactive', '-I', help="Launch command interface after connecting to seednodes.", is_flag=True, default=False)
@click.option('--config-root', help="Custom configuration directory", type=click.Path())
@ -67,11 +63,6 @@ from nucypher.utilities.sandbox.constants import (
@click.option('--provider-uri', help="Blockchain provider's URI", type=click.STRING)
@click.option('--no-registry', help="Skip importing the default contract registry", is_flag=True)
@click.option('--registry-filepath', help="Custom contract registry filepath", type=EXISTING_READABLE_FILE)
@click.option('--value', help="Token value of stake", type=click.INT)
@click.option('--duration', help="Period duration of stake", type=click.INT)
@click.option('--index', help="A specific stake index to resume", type=click.INT)
@click.option('--list', '-l', 'list_', help="List all blockchain stakes", is_flag=True)
@click.option('--divide', '-d', help="Divide an existing stake into sub-stakes.", is_flag=True)
@nucypher_click_config
def ursula(click_config,
action,
@ -87,24 +78,16 @@ def ursula(click_config,
rest_port,
db_filepath,
checksum_address,
withdraw_address,
federated_only,
poa,
sync,
config_root,
config_file,
provider_uri,
geth,
no_registry,
registry_filepath,
value,
duration,
index,
list_,
divide,
sync,
device,
interactive,
) -> None:
"""
Manage and run an "Ursula" PRE node.
@ -333,116 +316,6 @@ def ursula(click_config,
actions.forget(configuration=ursula_config)
return
elif action == 'stake':
# List Only
if list_:
if not URSULA.stakes:
click.echo(f"There are no active stakes for {URSULA.checksum_address}")
else:
painting.paint_stakes(stakes=URSULA.stakes)
return
# Divide Only
if divide:
"""Divide an existing stake by specifying the new target value and end period"""
# Validate
if not URSULA.stakes:
click.echo(f"There are no active stakes for {URSULA.checksum_address}")
return
# Selection
if index is None:
painting.paint_stakes(stakes=URSULA.stakes)
index = click.prompt("Select a stake to divide", type=click.IntRange(min=0, max=len(URSULA.stakes)-1))
# Lookup the stake
current_stake = URSULA.stakes[index]
# Value
if not value:
value = click.prompt(f"Enter target value (must be less than {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(ursula=URSULA,
original_index=index,
original_stake=current_stake,
target_value=value,
extension=extension)
click.confirm("Is this correct?", abort=True)
modified_stake, new_stake = URSULA.divide_stake(stake_index=index,
target_value=value,
additional_periods=extension)
if not quiet:
click.secho('Successfully divided stake', fg='green')
click.secho(f'Transaction Hash ........... {new_stake.receipt}')
# Show the resulting stake list
painting.paint_stakes(stakes=URSULA.stakes)
return
# Confirm new stake init
if not force:
click.confirm("Stage a new stake?", abort=True)
# Validate balance
balance = URSULA.token_balance
if balance == 0:
click.secho(f"{URSULA.checksum_address} has 0 NU.")
raise click.Abort
if not quiet:
click.echo(f"Current balance: {balance}")
# Gather stake value
if not value:
min_locked = NU(URSULA.economics.minimum_allowed_locked, 'NuNit')
value = click.prompt(f"Enter stake value", type=STAKE_VALUE, default=min_locked)
else:
value = NU(int(value), 'NU')
# Duration
if not quiet:
message = f"Minimum duration: {URSULA.economics.minimum_allowed_locked} | " \
f"Maximum Duration: {URSULA.economics.maximum_allowed_locked}"
click.echo(message)
if not duration:
duration = click.prompt("Enter stake duration in periods (1 Period = 24 Hours)", type=STAKE_DURATION)
start_period = URSULA.staking_agent.get_current_period()
end_period = start_period + duration
# Review
if not force:
painting.paint_staged_stake(ursula=URSULA,
stake_value=value,
duration=duration,
start_period=start_period,
end_period=end_period)
if not dev:
actions.confirm_staged_stake(ursula=URSULA, value=value, duration=duration)
# Last chance to bail
if not force:
click.confirm("Publish staged stake to the blockchain?", abort=True)
stake = URSULA.initialize_stake(amount=int(value), lock_periods=duration)
# TODO temporary fix to not break backward compatibility
URSULA.set_worker(worker_address=URSULA.checksum_address)
painting.paint_staking_confirmation(ursula=URSULA, transactions=stake.transactions)
return
elif action == 'confirm-activity':
if not URSULA.stakes:
click.secho("There are no active stakes for {}".format(URSULA.checksum_address))
@ -450,14 +323,5 @@ def ursula(click_config,
URSULA.staking_agent.confirm_activity(node_address=URSULA.checksum_address)
return
elif action == 'collect-reward':
"""Withdraw staking reward to the specified wallet address"""
if not force:
click.confirm(f"Send {URSULA.calculate_reward()} to {URSULA.checksum_address}?")
inflation_reward = URSULA.calculate_reward()
if inflation_reward:
URSULA.collect_staking_reward()
URSULA.collect_policy_reward(collector_address=withdraw_address or checksum_address)
else:
raise click.BadArgumentUsage("No such argument {}".format(action))

View File

@ -23,6 +23,8 @@ from twisted.logger import globalLogPublisher
from nucypher.characters.banners import NUCYPHER_BANNER
from nucypher.characters.control.emitters import StdoutEmitter, JSONRPCStdoutEmitter
from nucypher.cli import status
from nucypher.cli import status, stake
from nucypher.cli.actions import destroy_configuration_root
from nucypher.cli.characters import moe, ursula, alice, bob, enrico, felix
from nucypher.cli.config import nucypher_click_config, NucypherClickConfig
from nucypher.cli.painting import echo_version
@ -136,7 +138,10 @@ Inversely, commenting out an entry point here will disable it.
ENTRY_POINTS = (
# Utility Sub-Commands
# Utility Commands
status.status, # Network Status
stake.stake, # Stake Management
# device.device, # TODO: nucypher device # Hardware Wallet Management
# Characters
alice.alice, # Author of Policies

View File

@ -0,0 +1,181 @@
import click
from nucypher.blockchain.eth.actors import Staker
from nucypher.blockchain.eth.chains import Blockchain
from nucypher.blockchain.eth.token import NU
from nucypher.characters.banners import NU_BANNER
from nucypher.cli import painting
from nucypher.cli.actions import confirm_staged_stake
from nucypher.cli.types import (
EIP55_CHECKSUM_ADDRESS,
STAKE_VALUE, STAKE_DURATION, STAKE_EXTENSION)
@click.command()
@click.argument('action')
@click.option('--force', help="Don't ask for confirmation", is_flag=True)
@click.option('--quiet', '-Q', help="Disable logging", is_flag=True)
@click.option('--poa', help="Inject POA middleware", is_flag=True, default=None)
@click.option('--provider-uri', help="Blockchain provider's URI", type=click.STRING)
@click.option('--staking-address', help="Address to stake NU ERC20 tokens", type=EIP55_CHECKSUM_ADDRESS)
@click.option('--worker-address', help="Run with a specified account", type=EIP55_CHECKSUM_ADDRESS)
@click.option('--withdraw-address', help="Send reward collection to an alternate address", type=EIP55_CHECKSUM_ADDRESS)
@click.option('--value', help="Token value of stake", type=click.INT)
@click.option('--duration', help="Period duration of stake", type=click.INT)
@click.option('--index', help="A specific stake index to resume", type=click.INT)
def stake(action,
# Mode
force,
quiet,
# Blockchain
poa,
provider_uri,
# Stake
staking_address,
worker_address,
withdraw_address,
value,
duration,
index
) -> None:
# Banner
click.clear()
if not quiet:
click.secho(NU_BANNER)
#
# Make Staker
#
blockchain = Blockchain.connect(provider_uri=provider_uri, poa=poa)
STAKER = Staker(is_me=True,
checksum_address=staking_address,
blockchain=blockchain)
if action == 'list':
if not STAKER.stakes:
click.echo(f"There are no active stakes for {STAKER.checksum_public_address}")
else:
painting.paint_stakes(stakes=STAKER.stakes)
return
elif action == 'set-worker':
STAKER.set_worker(worker_address=worker_address)
pass
elif action == 'init':
# Confirm new stake init
if not force:
click.confirm("Stage a new stake?", abort=True)
# Validate balance
balance = STAKER.token_balance
if balance == 0:
click.secho(f"{STAKER.checksum_public_address} has 0 NU.")
raise click.Abort
if not quiet:
click.echo(f"Current balance: {balance}")
# Gather stake value
if not value:
min_locked = NU(STAKER.economics.minimum_allowed_locked, 'NuNit')
value = click.prompt(f"Enter stake value", type=STAKE_VALUE, default=min_locked)
else:
value = NU(int(value), 'NU')
# Duration
if not quiet:
message = f"Minimum duration: {STAKER.economics.minimum_allowed_locked} | " \
f"Maximum Duration: {STAKER.economics.maximum_allowed_locked}"
click.echo(message)
if not duration:
duration = click.prompt("Enter stake duration in periods (1 Period = 24 Hours)", type=STAKE_DURATION)
start_period = STAKER.staking_agent.get_current_period()
end_period = start_period + duration
# Review
if not force:
painting.paint_staged_stake(ursula=STAKER,
stake_value=value,
duration=duration,
start_period=start_period,
end_period=end_period)
confirm_staged_stake(ursula=STAKER, value=value, duration=duration)
# Last chance to bail
if not force:
click.confirm("Publish staged stake to the blockchain?", abort=True)
new_stake = STAKER.initialize_stake(amount=int(value), lock_periods=duration)
painting.paint_staking_confirmation(ursula=STAKER, transactions=new_stake.transactions)
return
elif action == 'divide':
"""Divide an existing stake by specifying the new target value and end period"""
# Validate
if not STAKER.stakes:
click.echo(f"There are no active stakes for {STAKER.checksum_public_address}")
return
# Selection
if index is None:
painting.paint_stakes(stakes=STAKER.stakes)
index = click.prompt("Select a stake to divide", type=click.IntRange(min=0, max=len(STAKER.stakes)-1))
# Lookup the stake
current_stake = STAKER.stakes[index]
# Value
if not value:
value = click.prompt(f"Enter target value (must be less than {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(ursula=STAKER,
original_index=index,
original_stake=current_stake,
target_value=value,
extension=extension)
click.confirm("Is this correct?", abort=True)
modified_stake, new_stake = STAKER.divide_stake(stake_index=index,
target_value=value,
additional_periods=extension)
if not quiet:
click.secho('Successfully divided stake', fg='green')
click.secho(f'Transaction Hash ........... {new_stake.receipt}')
# Show the resulting stake list
painting.paint_stakes(stakes=STAKER.stakes)
return
elif action == 'collect-reward':
"""Withdraw staking reward to the specified wallet address"""
if not force:
click.confirm(f"Send {STAKER.calculate_reward()} to {STAKER.checksum_public_address}?")
STAKER.collect_policy_reward(collector_address=withdraw_address)
STAKER.collect_staking_reward()