mirror of https://github.com/nucypher/nucypher.git
commit
264aa1e360
|
@ -0,0 +1 @@
|
|||
Accommodate migrated period duration in CLI UX.
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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,9 @@ 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.from_datetime(expiration),
|
||||
seconds_per_period=seconds_per_period)
|
||||
periods += 1 # current period is always included
|
||||
pretty_request['expiration'] = f"{pretty_request['expiration']} ({periods} periods)"
|
||||
|
||||
# M of N
|
||||
|
@ -171,11 +178,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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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})"
|
||||
|
||||
|
|
|
@ -101,13 +101,23 @@ def paint_stakes(emitter: StdoutEmitter,
|
|||
|
||||
rows, inactive_substakes = list(), list()
|
||||
for index, stake in enumerate(stakes):
|
||||
is_inactive = False
|
||||
|
||||
if stake.status().is_child(Stake.Status.INACTIVE):
|
||||
inactive_substakes.append(index)
|
||||
is_inactive = True
|
||||
|
||||
if stake.status().is_child(Stake.Status.UNLOCKED) and not paint_unlocked:
|
||||
# This stake is unlocked.
|
||||
continue
|
||||
rows.append(list(stake.describe().values()))
|
||||
|
||||
stake_description = stake.describe()
|
||||
if is_inactive:
|
||||
# stake is inactive - update display values since they don't make much sense to display
|
||||
stake_description['remaining'] = 'N/A'
|
||||
stake_description['last_period'] = 'N/A'
|
||||
|
||||
rows.append(list(stake_description.values()))
|
||||
|
||||
if not rows:
|
||||
emitter.echo(f"There are no locked stakes\n")
|
||||
|
@ -116,7 +126,11 @@ def paint_stakes(emitter: StdoutEmitter,
|
|||
|
||||
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')
|
||||
f"Run `nucypher stake list --all` to show all sub-stakes.\n"
|
||||
f"Run `nucypher stake remove-inactive --all` to remove inactive sub-stakes; removal of inactive "
|
||||
f"sub-stakes will reduce commitment gas costs.", color='yellow')
|
||||
# TODO - it would be nice to provide remove-inactive hint when painting_unlocked - however, this same function
|
||||
# is used by remove-inactive command is run, and it is redundant to be shown then
|
||||
|
||||
|
||||
def prettify_stake(stake, index: int = None) -> str:
|
||||
|
@ -171,16 +185,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 +208,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})
|
||||
""")
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
@ -1378,7 +1382,6 @@ def test_stake_list_all(click_runner, surrogate_stakers, surrogate_stakes, token
|
|||
for stakes in surrogate_stakes:
|
||||
for index, sub_stake in enumerate(stakes):
|
||||
value = NU.from_nunits(sub_stake.locked_value)
|
||||
remaining = sub_stake.last_period - current_period + 1
|
||||
start_datetime = datetime_at_period(period=sub_stake.first_period,
|
||||
seconds_per_period=token_economics.seconds_per_period,
|
||||
start_of_period=True)
|
||||
|
@ -1386,10 +1389,17 @@ def test_stake_list_all(click_runner, surrogate_stakers, surrogate_stakes, token
|
|||
seconds_per_period=token_economics.seconds_per_period,
|
||||
start_of_period=True)
|
||||
enactment = start_datetime.local_datetime().strftime("%b %d %Y")
|
||||
termination = unlock_datetime.local_datetime().strftime("%b %d %Y")
|
||||
|
||||
status = statuses[index]
|
||||
if status == Stake.Status.INACTIVE:
|
||||
remaining = 'N/A'
|
||||
termination = 'N/A'
|
||||
else:
|
||||
remaining = sub_stake.last_period - current_period + 1
|
||||
termination = unlock_datetime.local_datetime().strftime("%b %d %Y")
|
||||
assert re.search(f"{index}\\s+│\\s+"
|
||||
f"{value}\\s+│\\s+"
|
||||
f"{remaining}\\s+│\\s+"
|
||||
f"{enactment}\\s+│\\s+"
|
||||
f"{termination}\\s+│\\s+"
|
||||
f"{statuses[index].name}", result.output, re.MULTILINE)
|
||||
f"{status.name}", result.output, re.MULTILINE)
|
||||
|
|
Loading…
Reference in New Issue