Fix CLI/UX issues related to change of period length.

pull/2614/head
derekpierre 2021-03-25 11:14:10 -04:00 committed by Kieran Prasch
parent 53804b53f3
commit 1bb9290c34
8 changed files with 62 additions and 45 deletions

View File

@ -90,7 +90,7 @@ def collect_expiration(alice: Alice, expiration: maya.MayaDT, force: bool) -> ma
default_expiration = None
expiration_prompt = 'Enter policy expiration (Y-M-D H:M:S)'
if alice.duration_periods:
default_expiration = maya.now() + timedelta(days=alice.duration_periods)
default_expiration = maya.now() + timedelta(hours=alice.duration_periods * alice.economics.hours_per_period)
expiration = click.prompt(expiration_prompt, type=click.DateTime(), default=default_expiration)
return expiration

View File

@ -18,15 +18,18 @@
import click
from constant_sorrow.constants import UNKNOWN_DEVELOPMENT_CHAIN_ID
from datetime import datetime
from maya import MayaDT
from tabulate import tabulate
from typing import Type, Union, Dict
from web3.main import Web3
from nucypher.blockchain.economics import BaseEconomics
from nucypher.blockchain.eth.deployers import BaseContractDeployer
from nucypher.blockchain.eth.interfaces import BlockchainDeployerInterface, VersionedContract, BlockchainInterface
from nucypher.blockchain.eth.registry import LocalContractRegistry
from nucypher.blockchain.eth.token import NU
from nucypher.blockchain.eth.utils import calculate_period_duration
from nucypher.characters.control.emitters import StdoutEmitter
from nucypher.cli.literature import (
ABORT_DEPLOYMENT,
@ -92,12 +95,14 @@ def confirm_staged_stake(staker_address: str, value: NU, lock_periods: int) -> b
return True
def confirm_large_stake(value: NU = None, lock_periods: int = None) -> bool:
def confirm_large_and_or_long_stake(value: NU = None, lock_periods: int = None, economics: BaseEconomics = None) -> bool:
"""Interactively confirm a large stake and/or a long stake duration."""
if value and (value > NU.from_tokens(150000)):
if economics and value and (value > (NU.from_nunits(economics.minimum_allowed_locked) * 10)): # > 10x min stake
click.confirm(CONFIRM_LARGE_STAKE_VALUE.format(value=value), abort=True)
if lock_periods and (lock_periods > 365):
click.confirm(CONFIRM_LARGE_STAKE_DURATION.format(lock_periods=lock_periods), abort=True)
if economics and lock_periods and (lock_periods > economics.maximum_rewarded_periods): # > 1 year
lock_days = (lock_periods * economics.hours_per_period) // 24
click.confirm(CONFIRM_LARGE_STAKE_DURATION.format(lock_periods=lock_periods, lock_days=lock_days),
abort=True)
return True
@ -141,7 +146,7 @@ def verify_upgrade_details(blockchain: Union[BlockchainDeployerInterface, Blockc
new_version=new_version), abort=True)
def confirm_staged_grant(emitter, grant_request: Dict, federated: bool) -> None:
def confirm_staged_grant(emitter, grant_request: Dict, federated: bool, seconds_per_period=None) -> None:
pretty_request = grant_request.copy() # WARNING: Do not mutate
@ -154,7 +159,8 @@ def confirm_staged_grant(emitter, grant_request: Dict, federated: bool) -> None:
pretty_request['rate'] = f"{pretty_request['rate']} wei/period * {pretty_request['n']} nodes"
expiration = pretty_request['expiration']
periods = (expiration - datetime.now()).days
periods = calculate_period_duration(future_time=MayaDT(expiration),
seconds_per_period=seconds_per_period)
pretty_request['expiration'] = f"{pretty_request['expiration']} ({periods} periods)"
# M of N
@ -171,11 +177,6 @@ def confirm_staged_grant(emitter, grant_request: Dict, federated: bool) -> None:
table.append(['Period Rate', f'{period_rate} gwei'])
table.append(['Policy Value', f'{period_rate * periods} gwei'])
# TODO: Use period calculation utilities instead of days
# periods = calculate_period_duration(future_time=maya.MayaDT(pretty_request['expiration']),
# seconds_per_period=StandardTokenEconomics().seconds_per_period)
emitter.echo("\nSuccessfully staged grant, Please review the details:\n", color='green')
emitter.echo(tabulate(table, tablefmt="simple"))
click.confirm('\nGrant access and sign transaction?', abort=True)

View File

@ -17,13 +17,10 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
import click
import os
from constant_sorrow.constants import NO_PASSWORD
from nucypher.blockchain.eth.signers.software import ClefSigner
from nucypher.characters.control.emitters import StdoutEmitter
from nucypher.characters.control.interfaces import AliceInterface
from nucypher.cli.actions.auth import get_client_password, get_nucypher_password
from nucypher.cli.actions.auth import get_nucypher_password
from nucypher.cli.actions.collect import collect_bob_public_keys, collect_policy_parameters
from nucypher.cli.actions.configure import (
destroy_configuration,
@ -65,7 +62,6 @@ from nucypher.cli.types import EIP55_CHECKSUM_ADDRESS
from nucypher.cli.utils import make_cli_character, setup_emitter
from nucypher.config.characters import AliceConfiguration
from nucypher.config.constants import (
NUCYPHER_ENVVAR_ALICE_ETH_PASSWORD,
TEMPORARY_DOMAIN,
)
from nucypher.config.keyring import NucypherKeyring
@ -498,7 +494,10 @@ def grant(general_config,
# Grant
if not force and not general_config.json_ipc:
confirm_staged_grant(emitter=emitter, grant_request=grant_request, federated=ALICE.federated_only)
confirm_staged_grant(emitter=emitter,
grant_request=grant_request,
federated=ALICE.federated_only,
seconds_per_period=(None if ALICE.federated_only else ALICE.economics.seconds_per_period))
emitter.echo(f'Granting Access to {bob_public_keys.verifying_key[:8]}', color='yellow')
return ALICE.controller.grant(request=grant_request)

View File

@ -32,7 +32,7 @@ from nucypher.cli.actions.configure import get_or_update_configuration, handle_m
from nucypher.cli.actions.confirm import (
confirm_enable_restaking,
confirm_enable_winding_down,
confirm_large_stake,
confirm_large_and_or_long_stake,
confirm_staged_stake,
confirm_disable_snapshots
)
@ -545,7 +545,7 @@ def create(general_config: GroupGeneralConfig,
#
if not force:
confirm_large_stake(value=value, lock_periods=lock_periods)
confirm_large_and_or_long_stake(value=value, lock_periods=lock_periods, economics=economics)
paint_staged_stake(emitter=emitter,
blockchain=blockchain,
stakeholder=STAKEHOLDER,
@ -634,7 +634,7 @@ def increase(general_config: GroupGeneralConfig,
current_period = STAKEHOLDER.staker.staking_agent.get_current_period()
unlock_period = current_stake.final_locked_period + 1
confirm_large_stake(value=value, lock_periods=lock_periods)
confirm_large_and_or_long_stake(value=value, lock_periods=lock_periods, economics=STAKEHOLDER.staker.economics)
paint_staged_stake(emitter=emitter,
blockchain=blockchain,
stakeholder=STAKEHOLDER,
@ -878,7 +878,7 @@ def divide(general_config: GroupGeneralConfig,
extension = lock_periods
if not force:
confirm_large_stake(lock_periods=extension, value=value)
confirm_large_and_or_long_stake(lock_periods=extension, value=value, economics=economics)
paint_staged_stake_division(emitter=emitter,
blockchain=blockchain,
stakeholder=STAKEHOLDER,

View File

@ -78,7 +78,7 @@ Accept ursula node operator obligation?"""
CONFIRM_LARGE_STAKE_VALUE = "Wow, {value} - That's a lot of NU - Are you sure this is correct?"
CONFIRM_LARGE_STAKE_DURATION = "Woah, {lock_periods} is a long time - Are you sure this is correct?"
CONFIRM_LARGE_STAKE_DURATION = "Woah, {lock_periods} periods ({lock_days} days) is a long time - Are you sure this is correct?"
PROMPT_STAKE_CREATE_VALUE = "Enter stake value in NU ({lower_limit} - {upper_limit})"

View File

@ -114,9 +114,13 @@ def paint_stakes(emitter: StdoutEmitter,
emitter.echo(tabulate.tabulate(rows, headers=STAKE_TABLE_COLUMNS, tablefmt="fancy_grid")) # newline
if not paint_unlocked and inactive_substakes:
emitter.echo(f"Note that some sub-stakes are inactive: {inactive_substakes}\n"
f"Run `nucypher stake list --all` to show all sub-stakes.", color='yellow')
if inactive_substakes:
emitter.echo(f"Note that some sub-stakes are inactive: {inactive_substakes}")
if paint_unlocked:
emitter.echo("Run `nucypher stake remove-inactive --all` to remove inactive sub-stakes; removing them will "
"reduce future commitment gas costs", color='yellow')
else:
emitter.echo("Run `nucypher stake list --all` to show all sub-stakes.", color='yellow')
def prettify_stake(stake, index: int = None) -> str:
@ -171,16 +175,18 @@ def paint_staged_stake(emitter,
start_period,
unlock_period,
division_message: str = None):
economics = stakeholder.staker.economics
start_datetime = datetime_at_period(period=start_period,
seconds_per_period=stakeholder.staker.economics.seconds_per_period,
seconds_per_period=economics.seconds_per_period,
start_of_period=True)
unlock_datetime = datetime_at_period(period=unlock_period,
seconds_per_period=stakeholder.staker.economics.seconds_per_period,
seconds_per_period=economics.seconds_per_period,
start_of_period=True)
locked_days = (lock_periods * economics.hours_per_period) // 24
start_datetime_pretty = start_datetime.local_datetime().strftime("%b %d %H:%M %Z")
unlock_datetime_pretty = unlock_datetime.local_datetime().strftime("%b %d %H:%M %Z")
start_datetime_pretty = start_datetime.local_datetime().strftime("%b %d %Y %H:%M %Z")
unlock_datetime_pretty = unlock_datetime.local_datetime().strftime("%b %d %Y %H:%M %Z")
if division_message:
emitter.echo(f"\n{'' * 30} ORIGINAL STAKE {'' * 28}", bold=True)
@ -192,7 +198,7 @@ def paint_staged_stake(emitter,
Staking address: {staking_address}
~ Chain -> ID # {blockchain.client.chain_id} | {blockchain.client.chain_name}
~ Value -> {stake_value} ({int(stake_value)} NuNits)
~ Duration -> {lock_periods} Days ({lock_periods} Periods)
~ Duration -> {locked_days} Days ({lock_periods} Periods)
~ Enactment -> {start_datetime_pretty} (period #{start_period})
~ Expiration -> {unlock_datetime_pretty} (period #{unlock_period})
""")

View File

@ -18,10 +18,11 @@
import click
import pytest
from nucypher.blockchain.economics import StandardTokenEconomics
from nucypher.blockchain.eth.clients import EthereumTesterClient, PUBLIC_CHAINS
from nucypher.blockchain.eth.token import NU
from nucypher.cli.actions.confirm import (confirm_deployment, confirm_enable_restaking,
confirm_enable_winding_down, confirm_large_stake, confirm_staged_stake)
confirm_enable_winding_down, confirm_large_and_or_long_stake, confirm_staged_stake)
from nucypher.cli.literature import (ABORT_DEPLOYMENT, RESTAKING_AGREEMENT,
WINDING_DOWN_AGREEMENT, CONFIRM_STAGED_STAKE,
CONFIRM_LARGE_STAKE_VALUE, CONFIRM_LARGE_STAKE_DURATION)
@ -163,13 +164,17 @@ def test_confirm_staged_stake_cli_action(test_emitter, mock_stdin, capsys):
assert mock_stdin.empty()
STANDARD_ECONOMICS = StandardTokenEconomics()
MIN_ALLOWED_LOCKED = STANDARD_ECONOMICS.minimum_allowed_locked
@pytest.mark.parametrize('value,duration,must_confirm_value,must_confirm_duration', (
(NU.from_tokens(1), 1, False, False),
(NU.from_tokens(1), 31, False, False),
(NU.from_tokens(15), 31, False, False),
(NU.from_tokens(150001), 31, True, False),
(NU.from_tokens(150000), 366, False, True),
(NU.from_tokens(150001), 366, True, True),
(NU.from_tokens(1), STANDARD_ECONOMICS.minimum_locked_periods + 1, False, False),
(NU.from_tokens(15), STANDARD_ECONOMICS.minimum_locked_periods + 1, False, False),
(((NU.from_nunits(MIN_ALLOWED_LOCKED) * 10) + 1), STANDARD_ECONOMICS.minimum_locked_periods + 1, True, False),
(NU.from_nunits(MIN_ALLOWED_LOCKED) * 10, STANDARD_ECONOMICS.maximum_rewarded_periods + 1, False, True),
(((NU.from_nunits(MIN_ALLOWED_LOCKED) * 10) + 1), STANDARD_ECONOMICS.maximum_rewarded_periods + 1, True, True),
))
def test_confirm_large_stake_cli_action(test_emitter,
mock_stdin,
@ -180,14 +185,16 @@ def test_confirm_large_stake_cli_action(test_emitter,
must_confirm_duration):
asked_about_value = lambda output: CONFIRM_LARGE_STAKE_VALUE.format(value=value) in output
asked_about_duration = lambda output: CONFIRM_LARGE_STAKE_DURATION.format(lock_periods=duration) in output
lock_days = (duration * STANDARD_ECONOMICS.hours_per_period) // 24
asked_about_duration = lambda output: CONFIRM_LARGE_STAKE_DURATION.format(lock_periods=duration,
lock_days=lock_days) in output
# Positive Cases - either do not need to confirm anything, or say yes
if must_confirm_value:
mock_stdin.line(YES)
if must_confirm_duration:
mock_stdin.line(YES)
result = confirm_large_stake(value=value, lock_periods=duration)
result = confirm_large_and_or_long_stake(value=value, lock_periods=duration, economics=STANDARD_ECONOMICS)
assert result
captured = capsys.readouterr()
assert must_confirm_value == asked_about_value(captured.out)
@ -205,7 +212,7 @@ def test_confirm_large_stake_cli_action(test_emitter,
mock_stdin.line(NO)
with pytest.raises(click.Abort):
confirm_large_stake(value=value, lock_periods=duration)
confirm_large_and_or_long_stake(value=value, lock_periods=duration, economics=STANDARD_ECONOMICS)
captured = capsys.readouterr()
assert must_confirm_value == asked_about_value(captured.out)
assert must_confirm_duration == asked_about_duration(captured.out)

View File

@ -957,7 +957,8 @@ def test_create_interactive(click_runner,
lock_periods=lock_periods) in result.output
assert CONFIRM_BROADCAST_CREATE_STAKE in result.output
assert CONFIRM_LARGE_STAKE_VALUE.format(value=value) in result.output
assert CONFIRM_LARGE_STAKE_DURATION.format(lock_periods=lock_periods) in result.output
lock_days = (lock_periods * token_economics.hours_per_period) // 24
assert CONFIRM_LARGE_STAKE_DURATION.format(lock_periods=lock_periods, lock_days=lock_days) in result.output
mock_staking_agent.get_all_stakes.assert_called()
mock_staking_agent.get_current_period.assert_called()
@ -1017,7 +1018,8 @@ def test_create_non_interactive(click_runner,
lock_periods=lock_periods) not in result.output
assert CONFIRM_BROADCAST_CREATE_STAKE in result.output
assert CONFIRM_LARGE_STAKE_VALUE.format(value=value) not in result.output
assert CONFIRM_LARGE_STAKE_DURATION.format(lock_periods=lock_periods) not in result.output
lock_days = (lock_periods * token_economics.hours_per_period) // 24
assert CONFIRM_LARGE_STAKE_DURATION.format(lock_periods=lock_periods, lock_days=lock_days) not in result.output
mock_staking_agent.get_all_stakes.assert_called()
mock_staking_agent.get_current_period.assert_called()
@ -1096,7 +1098,8 @@ def test_create_lock_interactive(click_runner,
lock_periods=lock_periods) in result.output
assert CONFIRM_BROADCAST_CREATE_STAKE in result.output
assert CONFIRM_LARGE_STAKE_VALUE.format(value=value) not in result.output
assert CONFIRM_LARGE_STAKE_DURATION.format(lock_periods=lock_periods) in result.output
lock_days = (lock_periods * token_economics.hours_per_period) // 24
assert CONFIRM_LARGE_STAKE_DURATION.format(lock_periods=lock_periods, lock_days=lock_days) in result.output
mock_staking_agent.get_all_stakes.assert_called()
mock_staking_agent.get_current_period.assert_called()
@ -1154,7 +1157,8 @@ def test_create_lock_non_interactive(click_runner,
lock_periods=lock_periods) not in result.output
assert CONFIRM_BROADCAST_CREATE_STAKE in result.output
assert CONFIRM_LARGE_STAKE_VALUE.format(value=value) not in result.output
assert CONFIRM_LARGE_STAKE_DURATION.format(lock_periods=lock_periods) not in result.output
lock_days = (lock_periods * token_economics.hours_per_period) // 24
assert CONFIRM_LARGE_STAKE_DURATION.format(lock_periods=lock_periods, lock_days=lock_days) not in result.output
mock_staking_agent.get_all_stakes.assert_called()
mock_staking_agent.get_current_period.assert_called()