mirror of https://github.com/nucypher/nucypher.git
New command in CLI to increase value of specified sub-stake
parent
883f5350e4
commit
f2acc2adda
|
@ -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)
|
||||
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue