New command in CLI to increase value of specified sub-stake

pull/2142/head
vzotova 2020-07-22 15:17:01 +03:00
parent 883f5350e4
commit f2acc2adda
5 changed files with 231 additions and 14 deletions

View File

@ -896,7 +896,8 @@ class Staker(NucypherTokenActor):
amount: NU = None,
lock_periods: int = None,
expiration: maya.MayaDT = None,
entire_balance: bool = False) -> Tuple[str, str]:
entire_balance: bool = False
) -> Tuple[str, str]:
"""Create a new stake."""
@ -948,7 +949,8 @@ class Staker(NucypherTokenActor):
stake: Stake,
target_value: NU,
additional_periods: int = None,
expiration: maya.MayaDT = None) -> Tuple[str, str]:
expiration: maya.MayaDT = None
) -> Tuple[str, str]:
self._ensure_stake_exists(stake)
# Calculate duration in periods
@ -980,7 +982,8 @@ class Staker(NucypherTokenActor):
def increase_stake(self,
stake: Stake,
amount: NU = None,
entire_balance: bool = False) -> Tuple[str, str]:
entire_balance: bool = False
) -> Tuple[str, str]:
"""Add tokens to existing stake."""
self._ensure_stake_exists(stake)

View File

@ -67,7 +67,8 @@ from nucypher.cli.literature import (
SUCCESSFUL_STAKE_DIVIDE,
SUCCESSFUL_STAKE_PROLONG,
SUCCESSFUL_WORKER_BONDING, NO_MINTABLE_PERIODS, STILL_LOCKED_TOKENS, CONFIRM_MINTING, SUCCESSFUL_MINTING,
CONFIRM_COLLECTING_WITHOUT_MINTING, NO_TOKENS_TO_WITHDRAW, NO_FEE_TO_WITHDRAW
CONFIRM_COLLECTING_WITHOUT_MINTING, NO_TOKENS_TO_WITHDRAW, NO_FEE_TO_WITHDRAW, CONFIRM_INCREASING_STAKE,
PROMPT_STAKE_INCREASE_VALUE, SUCCESSFUL_STAKE_INCREASE
)
from nucypher.cli.options import (
group_options,
@ -103,6 +104,7 @@ from nucypher.config.characters import StakeHolderConfiguration
option_value = click.option('--value', help="Token value of stake", type=click.INT)
option_lock_periods = click.option('--lock-periods', help="Duration of stake in periods.", type=click.INT)
option_worker_address = click.option('--worker-address', help="Address to bond as an Ursula-Worker", type=EIP55_CHECKSUM_ADDRESS)
option_index = click.option('--index', help="The staker-specific stake index to edit", type=click.INT)
class StakeHolderConfigOptions:
@ -471,9 +473,10 @@ def create(general_config, transacting_staker_options, config_file, force, value
#
if not value:
token_balance = NU.from_nunits(STAKEHOLDER.token_agent.get_balance(staking_address))
token_balance = STAKEHOLDER.token_balance
lower_limit = NU.from_nunits(STAKEHOLDER.economics.minimum_allowed_locked)
upper_limit = min(token_balance, NU.from_nunits(STAKEHOLDER.economics.maximum_allowed_locked))
locked_tokens = STAKEHOLDER.locked_tokens(periods=1).to_nunits()
upper_limit = min(token_balance, NU.from_nunits(STAKEHOLDER.economics.maximum_allowed_locked - locked_tokens))
value = click.prompt(f"Enter stake value in NU "
f"({lower_limit} - {upper_limit})",
type=stake_value_range,
@ -524,6 +527,80 @@ def create(general_config, transacting_staker_options, config_file, force, value
paint_staking_confirmation(emitter=emitter, staker=STAKEHOLDER, receipt=receipt)
@stake.command()
@group_transacting_staker_options
@option_config_file
@option_force
@option_value
@option_index
@group_general_config
def increase(general_config, transacting_staker_options, config_file, force, value, index):
"""Increase an existing stake."""
# Setup
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 = select_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 index is not None: # 0 is valid.
current_stake = STAKEHOLDER.stakes[index]
else:
current_stake = select_stake(staker=STAKEHOLDER, emitter=emitter)
#
# Stage Stake
#
if not value:
token_balance = STAKEHOLDER.token_balance
locked_tokens = STAKEHOLDER.locked_tokens(periods=1).to_nunits()
upper_limit = min(token_balance, NU.from_nunits(STAKEHOLDER.economics.maximum_allowed_locked - locked_tokens))
stake_value_range = click.FloatRange(min=0, max=upper_limit.to_tokens(), clamp=False)
value = click.prompt(PROMPT_STAKE_INCREASE_VALUE.format(upper_limit=upper_limit),
type=stake_value_range)
value = NU.from_tokens(value)
#
# Review and Publish
#
if not force:
lock_periods = current_stake.periods_remaining - 1
current_period = STAKEHOLDER.staking_agent.get_current_period()
unlock_period = current_stake.final_locked_period + 1
confirm_large_stake(value=value, lock_periods=lock_periods)
paint_staged_stake(emitter=emitter,
stakeholder=STAKEHOLDER,
staking_address=staking_address,
stake_value=value,
lock_periods=lock_periods,
start_period=current_period + 1,
unlock_period=unlock_period)
click.confirm(CONFIRM_INCREASING_STAKE.format(stake_index=current_stake.index, value=value), abort=True)
# Authenticate
password = transacting_staker_options.get_password(blockchain, client_account)
STAKEHOLDER.assimilate(password=password)
# Execute
receipt = STAKEHOLDER.increase_stake(stake=current_stake, amount=value)
# Report
emitter.echo(SUCCESSFUL_STAKE_INCREASE, color='green', verbosity=1)
paint_receipt_summary(emitter=emitter, receipt=receipt, chain_name=blockchain.client.chain_name)
paint_stakes(emitter=emitter, staker=STAKEHOLDER)
@stake.command()
@group_transacting_staker_options
@option_config_file
@ -634,7 +711,7 @@ def winddown(general_config, transacting_staker_options, config_file, enable, fo
@option_force
@option_value
@option_lock_periods
@click.option('--index', help="A specific stake index to resume", type=click.INT)
@option_index
@group_general_config
def divide(general_config, transacting_staker_options, config_file, force, value, lock_periods, index):
"""Create a new stake from part of an existing one."""
@ -718,7 +795,7 @@ def divide(general_config, transacting_staker_options, config_file, force, value
@option_config_file
@option_force
@option_lock_periods
@click.option('--index', help="The staker-specific stake index to prolong", type=click.INT)
@option_index
@group_general_config
def prolong(general_config, transacting_staker_options, config_file, force, lock_periods, index):
"""Prolong an existing stake's duration."""

View File

@ -82,6 +82,12 @@ CONFIRM_LARGE_STAKE_DURATION = "Woah, {lock_periods} is a long time - Are you su
CONFIRM_BROADCAST_CREATE_STAKE = "Publish staged stake to the blockchain?"
CONFIRM_INCREASING_STAKE = "Confirm increase stake ({stake_index} index) of {value}?"
PROMPT_STAKE_INCREASE_VALUE = "Enter stake value in NU (up to {upper_limit})"
SUCCESSFUL_STAKE_INCREASE = 'Successfully increased stake'
PREALLOCATION_STAKE_ADVISORY = "Beneficiary {client_account} will use preallocation contract {staking_address} to stake."
NO_STAKING_ACCOUNTS = "No staking accounts found."

View File

@ -199,6 +199,43 @@ def test_stake_prolong(click_runner,
assert new_termination == old_termination + 1
def test_stake_increase(click_runner,
stakeholder_configuration_file_location,
token_economics,
testerchain,
agency_local_registry,
manual_staker):
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=agency_local_registry)
stakes = list(staking_agent.get_all_stakes(staker_address=manual_staker))
stakes_length = len(stakes)
assert stakes_length > 0
selection = 0
new_value = NU.from_nunits(token_economics.minimum_allowed_locked // 10)
origin_stake = stakes[selection]
stake_args = ('stake', 'increase',
'--config-file', stakeholder_configuration_file_location,
'--staking-address', manual_staker,
'--value', new_value.to_tokens(),
'--index', selection,
'--force')
user_input = f'{INSECURE_DEVELOPMENT_PASSWORD}\n'
result = click_runner.invoke(nucypher_cli, stake_args, input=user_input, catch_exceptions=False)
assert result.exit_code == 0
# Verify the stake is on-chain
# Test integration with Agency
stakes = list(staking_agent.get_all_stakes(staker_address=manual_staker))
assert len(stakes) == stakes_length
# Test integration with NU
_start_period, end_period, value = stakes[selection]
assert NU(int(value), 'NuNit') == origin_stake.locked_value + new_value
assert end_period == origin_stake.last_period
def test_stake_bond_worker(click_runner,
testerchain,
agency_local_registry,

View File

@ -27,7 +27,8 @@ from nucypher.cli.literature import (
NO_TOKENS_TO_WITHDRAW, COLLECTING_TOKEN_REWARD, CONFIRM_COLLECTING_WITHOUT_MINTING,
NO_FEE_TO_WITHDRAW, COLLECTING_ETH_FEE, NO_MINTABLE_PERIODS, STILL_LOCKED_TOKENS, CONFIRM_MINTING,
PROMPT_PROLONG_VALUE, CONFIRM_PROLONG, SUCCESSFUL_STAKE_PROLONG, PERIOD_ADVANCED_WARNING, PROMPT_STAKE_DIVIDE_VALUE,
PROMPT_STAKE_EXTEND_VALUE, CONFIRM_BROADCAST_STAKE_DIVIDE, SUCCESSFUL_STAKE_DIVIDE
PROMPT_STAKE_EXTEND_VALUE, CONFIRM_BROADCAST_STAKE_DIVIDE, SUCCESSFUL_STAKE_DIVIDE, SUCCESSFUL_STAKE_INCREASE,
PROMPT_STAKE_INCREASE_VALUE, CONFIRM_INCREASING_STAKE
)
from nucypher.config.constants import TEMPORARY_DOMAIN
from nucypher.types import SubStakeInfo
@ -508,11 +509,11 @@ def test_divide_non_interactive(click_runner,
command = ('divide',
'--provider', MOCK_PROVIDER_URI,
'--network', TEMPORARY_DOMAIN,
'--staking-address', surrogate_staker.checksum_address,
'--index', sub_stake_index,
'--lock-periods', lock_periods,
'--value', NU.from_nunits(target_value).to_tokens(),
'--force')
'--staking-address', surrogate_staker.checksum_address,
'--index', sub_stake_index,
'--lock-periods', lock_periods,
'--value', NU.from_nunits(target_value).to_tokens(),
'--force')
user_input = INSECURE_DEVELOPMENT_PASSWORD
result = click_runner.invoke(stake, command, input=user_input, catch_exceptions=False)
@ -533,3 +534,96 @@ def test_divide_non_interactive(click_runner,
mock_staking_agent.assert_only_transactions([mock_staking_agent.divide_stake])
mock_staking_agent.get_substake_info.assert_called_once_with(staker_address=surrogate_staker.checksum_address,
stake_index=sub_stake_index)
@pytest.mark.usefixtures("test_registry_source_manager", "patch_stakeholder_configuration")
def test_increase_interactive(click_runner,
mocker,
surrogate_staker,
surrogate_stakes,
mock_token_agent,
mock_staking_agent,
token_economics,
mock_testerchain):
mock_refresh_stakes = mocker.spy(Staker, 'refresh_stakes')
selected_index = 0
sub_stake_index = len(surrogate_stakes) - 1
additional_value = NU.from_nunits(token_economics.minimum_allowed_locked // 10)
mock_staking_agent.get_locked_tokens.return_value = token_economics.maximum_allowed_locked // 2
balance = token_economics.minimum_allowed_locked * 5
mock_token_agent.get_balance.return_value = balance
command = ('increase',
'--provider', MOCK_PROVIDER_URI,
'--network', TEMPORARY_DOMAIN)
user_input = '\n'.join((str(selected_index),
str(sub_stake_index),
str(additional_value.to_tokens()),
YES,
INSECURE_DEVELOPMENT_PASSWORD))
result = click_runner.invoke(stake, command, input=user_input, catch_exceptions=False)
assert result.exit_code == 0
upper_limit = NU.from_nunits(balance)
assert PROMPT_STAKE_INCREASE_VALUE.format(upper_limit=upper_limit) in result.output
assert CONFIRM_INCREASING_STAKE.format(stake_index=sub_stake_index, value=additional_value) in result.output
assert SUCCESSFUL_STAKE_INCREASE in result.output
mock_staking_agent.get_all_stakes.assert_called()
mock_staking_agent.get_current_period.assert_called()
mock_refresh_stakes.assert_called()
mock_staking_agent.deposit_and_increase.assert_called_once_with(staker_address=surrogate_staker.checksum_address,
stake_index=sub_stake_index,
amount=additional_value.to_nunits())
mock_staking_agent.assert_only_transactions([mock_staking_agent.deposit_and_increase])
mock_staking_agent.get_substake_info.assert_called_once_with(staker_address=surrogate_staker.checksum_address,
stake_index=sub_stake_index)
@pytest.mark.usefixtures("test_registry_source_manager", "patch_stakeholder_configuration")
def test_divide_non_interactive(click_runner,
mocker,
surrogate_staker,
surrogate_stakes,
mock_token_agent,
mock_staking_agent,
token_economics,
mock_testerchain):
mock_refresh_stakes = mocker.spy(Staker, 'refresh_stakes')
sub_stake_index = len(surrogate_stakes) - 1
additional_value = NU.from_nunits(token_economics.minimum_allowed_locked // 10)
locked_tokens = token_economics.minimum_allowed_locked * 5
mock_staking_agent.get_locked_tokens.return_value = locked_tokens
mock_token_agent.get_balance.return_value = token_economics.maximum_allowed_locked // 2
command = ('increase',
'--provider', MOCK_PROVIDER_URI,
'--network', TEMPORARY_DOMAIN,
'--staking-address', surrogate_staker.checksum_address,
'--index', sub_stake_index,
'--value', additional_value.to_tokens(),
'--force')
user_input = INSECURE_DEVELOPMENT_PASSWORD
result = click_runner.invoke(stake, command, input=user_input, catch_exceptions=False)
assert result.exit_code == 0
upper_limit = NU.from_nunits(locked_tokens)
assert PROMPT_STAKE_INCREASE_VALUE.format(upper_limit=upper_limit) not in result.output
assert CONFIRM_INCREASING_STAKE.format(stake_index=sub_stake_index, value=additional_value) not in result.output
assert SUCCESSFUL_STAKE_INCREASE in result.output
mock_staking_agent.get_all_stakes.assert_called()
mock_staking_agent.get_current_period.assert_called()
mock_refresh_stakes.assert_called()
mock_staking_agent.deposit_and_increase.assert_called_once_with(staker_address=surrogate_staker.checksum_address,
stake_index=sub_stake_index,
amount=additional_value.to_nunits())
mock_staking_agent.assert_only_transactions([mock_staking_agent.deposit_and_increase])
mock_staking_agent.get_substake_info.assert_called_once_with(staker_address=surrogate_staker.checksum_address,
stake_index=sub_stake_index)