def test_confirm_large_stake_cli_action(test_emitter,
                                        mock_stdin,
                                        capsys,
                                        value,
                                        duration,
                                        must_confirm_value,
                                        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

    # 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)
    assert result
    captured = capsys.readouterr()
    assert must_confirm_value == asked_about_value(captured.out)
    assert must_confirm_duration == asked_about_duration(captured.out)
    assert mock_stdin.empty()

    if must_confirm_value or must_confirm_duration:
        # Negative cases - must confirm something and say no
        if must_confirm_value and must_confirm_duration:
            # yes to the former but not to the latter
            mock_stdin.line(YES)
            mock_stdin.line(NO)
        else:
            # no to whatever one we are asked about
            mock_stdin.line(NO)

        with pytest.raises(click.Abort):
            confirm_large_stake(value=value, lock_periods=duration)
        captured = capsys.readouterr()
        assert must_confirm_value == asked_about_value(captured.out)
        assert must_confirm_duration == asked_about_duration(captured.out)
        assert mock_stdin.empty()
Example #2
0
def divide(general_config, transacting_staker_options, config_file, force,
           value, lock_periods, index):
    """Create a new stake from part of an existing one."""

    # Setup
    emitter = setup_emitter(general_config)
    STAKEHOLDER = transacting_staker_options.create_character(
        emitter, config_file)
    blockchain = transacting_staker_options.get_blockchain()
    economics = STAKEHOLDER.economics
    action_period = STAKEHOLDER.staking_agent.get_current_period()

    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)

    # Dynamic click types (Economics)
    min_locked = economics.minimum_allowed_locked
    stake_value_range = click.FloatRange(
        min=NU.from_nunits(min_locked).to_tokens(), clamp=False)

    if transacting_staker_options.staker_options.staking_address and index is not None:  # 0 is valid.
        STAKEHOLDER.stakes = StakeList(
            registry=STAKEHOLDER.registry,
            checksum_address=transacting_staker_options.staker_options.
            staking_address)
        STAKEHOLDER.stakes.refresh()
        current_stake = STAKEHOLDER.stakes[index]
    else:
        current_stake = select_stake(stakeholder=STAKEHOLDER,
                                     emitter=emitter,
                                     divisible=True,
                                     staker_address=client_account)

    #
    # Stage Stake
    #

    # Value
    if not value:
        min_allowed_locked = NU.from_nunits(
            STAKEHOLDER.economics.minimum_allowed_locked)
        max_divide_value = max(min_allowed_locked,
                               current_stake.value - min_allowed_locked)
        prompt = PROMPT_STAKE_DIVIDE_VALUE.format(
            minimm=min_allowed_locked, maximum=str(max_divide_value))
        value = click.prompt(prompt, type=stake_value_range)
    value = NU(value, 'NU')

    # Duration
    if not lock_periods:
        max_extension = MAX_UINT16 - current_stake.final_locked_period
        divide_extension_range = click.IntRange(min=1,
                                                max=max_extension,
                                                clamp=False)
        extension = click.prompt(PROMPT_STAKE_EXTEND_VALUE,
                                 type=divide_extension_range)
    else:
        extension = lock_periods

    if not force:
        confirm_large_stake(lock_periods=extension, value=value)
        paint_staged_stake_division(emitter=emitter,
                                    stakeholder=STAKEHOLDER,
                                    original_stake=current_stake,
                                    target_value=value,
                                    extension=extension)
        click.confirm(CONFIRM_BROADCAST_STAKE_DIVIDE, abort=True)

    # Authenticate
    password = transacting_staker_options.get_password(blockchain,
                                                       client_account)

    # Consistency check to prevent the above agreement from going stale.
    last_second_current_period = STAKEHOLDER.staking_agent.get_current_period()
    if action_period != last_second_current_period:
        emitter.echo(PERIOD_ADVANCED_WARNING, red='red')
        raise click.Abort

    # Execute
    STAKEHOLDER.assimilate(checksum_address=current_stake.staker_address,
                           password=password)
    modified_stake, new_stake = STAKEHOLDER.divide_stake(
        stake_index=current_stake.index,
        target_value=value,
        additional_periods=extension)
    emitter.echo(SUCCESSFUL_STAKE_DIVIDE, color='green', verbosity=1)
    paint_receipt_summary(emitter=emitter,
                          receipt=new_stake.receipt,
                          chain_name=blockchain.client.chain_name)

    # Show the resulting stake list
    paint_stakes(emitter=emitter, stakeholder=STAKEHOLDER)
Example #3
0
def create(general_config, transacting_staker_options, config_file, force,
           value, lock_periods):
    """Initialize a new 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)

    # Dynamic click types (Economics)
    min_locked = economics.minimum_allowed_locked
    stake_value_range = click.FloatRange(
        min=NU.from_nunits(min_locked).to_tokens(), clamp=False)
    stake_duration_range = click.IntRange(min=economics.minimum_locked_periods,
                                          clamp=False)

    #
    # Stage Stake
    #

    if not value:
        token_balance = NU.from_nunits(
            STAKEHOLDER.token_agent.get_balance(staking_address))
        lower_limit = NU.from_nunits(
            STAKEHOLDER.economics.minimum_allowed_locked)
        upper_limit = min(
            token_balance,
            NU.from_nunits(STAKEHOLDER.economics.maximum_allowed_locked))
        value = click.prompt(
            f"Enter stake value in NU "
            f"({lower_limit} - {upper_limit})",
            type=stake_value_range,
            default=upper_limit.to_tokens())
    value = NU.from_tokens(value)

    if not lock_periods:
        min_locktime = STAKEHOLDER.economics.minimum_locked_periods
        default_locktime = STAKEHOLDER.economics.maximum_rewarded_periods
        max_locktime = MAX_UINT16 - STAKEHOLDER.staking_agent.get_current_period(
        )
        prompt = f"Enter stake duration ({min_locktime} - {max_locktime})"
        lock_periods = click.prompt(prompt,
                                    type=stake_duration_range,
                                    default=default_locktime)

    start_period = STAKEHOLDER.staking_agent.get_current_period() + 1
    unlock_period = start_period + lock_periods

    #
    # Review and Publish
    #

    if not force:
        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=start_period,
                           unlock_period=unlock_period)

        confirm_staged_stake(staker_address=staking_address,
                             value=value,
                             lock_periods=lock_periods)

    # Last chance to bail
    click.confirm(CONFIRM_BROADCAST_CREATE_STAKE, abort=True)

    # Authenticate
    password = transacting_staker_options.get_password(blockchain,
                                                       client_account)

    # Consistency check to prevent the above agreement from going stale.
    last_second_current_period = STAKEHOLDER.staking_agent.get_current_period()
    if start_period != last_second_current_period + 1:
        emitter.echo(PERIOD_ADVANCED_WARNING, color='red')
        raise click.Abort

    # Authenticate and Execute
    STAKEHOLDER.assimilate(checksum_address=client_account, password=password)

    new_stake = STAKEHOLDER.initialize_stake(amount=value,
                                             lock_periods=lock_periods)
    paint_staking_confirmation(emitter=emitter,
                               staker=STAKEHOLDER,
                               new_stake=new_stake)
Example #4
0
def increase(general_config, transacting_staker_options, config_file, force, value, index, only_lock):
    """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()

    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:
        if not only_lock:
            only_lock = not click.prompt(PROMPT_DEPOSIT_OR_LOCK, type=click.BOOL, default=True)

        token_balance = STAKEHOLDER.token_balance if not only_lock else STAKEHOLDER.calculate_staking_reward()
        locked_tokens = STAKEHOLDER.locked_tokens(periods=1).to_nunits()
        upper_limit = min(token_balance, NU.from_nunits(STAKEHOLDER.economics.maximum_allowed_locked - locked_tokens))

        if token_balance == 0:
            emitter.echo(INSUFFICIENT_BALANCE_TO_INCREASE, color='red')
            raise click.Abort
        if upper_limit == 0:
            emitter.echo(MAXIMUM_STAKE_REACHED, color='red')
            raise click.Abort

        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, only_lock=only_lock)

    # 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)
Example #5
0
def create(general_config, transacting_staker_options, config_file, force, value, lock_periods, only_lock):
    """Initialize a new 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)

    # Dynamic click types (Economics)
    min_locked = economics.minimum_allowed_locked
    stake_value_range = click.FloatRange(min=NU.from_nunits(min_locked).to_tokens(), clamp=False)
    stake_duration_range = click.IntRange(min=economics.minimum_locked_periods, clamp=False)

    #
    # Stage Stake
    #

    if not value:
        if not only_lock:
            only_lock = not click.prompt(PROMPT_DEPOSIT_OR_LOCK, type=click.BOOL, default=True)

        token_balance = STAKEHOLDER.token_balance if not only_lock else STAKEHOLDER.calculate_staking_reward()
        lower_limit = NU.from_nunits(STAKEHOLDER.economics.minimum_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))

        if token_balance <= lower_limit:
            emitter.echo(INSUFFICIENT_BALANCE_TO_CREATE, color='red')
            raise click.Abort
        if upper_limit <= lower_limit:
            emitter.echo(MAXIMUM_STAKE_REACHED, color='red')
            raise click.Abort

        value = click.prompt(PROMPT_STAKE_CREATE_VALUE.format(lower_limit=lower_limit, upper_limit=upper_limit),
                             type=stake_value_range,
                             default=upper_limit.to_tokens())
    value = NU.from_tokens(value)

    if not lock_periods:
        min_locktime = STAKEHOLDER.economics.minimum_locked_periods
        default_locktime = STAKEHOLDER.economics.maximum_rewarded_periods
        max_locktime = MAX_UINT16 - STAKEHOLDER.staking_agent.get_current_period()
        lock_periods = click.prompt(PROMPT_STAKE_CREATE_LOCK_PERIODS.format(min_locktime=min_locktime,
                                                                            max_locktime=max_locktime),
                                    type=stake_duration_range,
                                    default=default_locktime)

    start_period = STAKEHOLDER.staking_agent.get_current_period() + 1
    unlock_period = start_period + lock_periods

    #
    # Review and Publish
    #

    if not force:
        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=start_period,
                           unlock_period=unlock_period)

        confirm_staged_stake(staker_address=staking_address, value=value, lock_periods=lock_periods)

    # Last chance to bail
    click.confirm(CONFIRM_BROADCAST_CREATE_STAKE, abort=True)

    # Authenticate
    password = transacting_staker_options.get_password(blockchain, client_account)
    STAKEHOLDER.assimilate(password=password)

    # Consistency check to prevent the above agreement from going stale.
    last_second_current_period = STAKEHOLDER.staking_agent.get_current_period()
    if start_period != last_second_current_period + 1:
        emitter.echo(PERIOD_ADVANCED_WARNING, color='red')
        raise click.Abort

    # Execute
    receipt = STAKEHOLDER.initialize_stake(amount=value, lock_periods=lock_periods, only_lock=only_lock)
    paint_staking_confirmation(emitter=emitter, staker=STAKEHOLDER, receipt=receipt)