def test_stakeholder_configuration(test_emitter, test_registry, mock_testerchain, mock_staking_agent): stakeholder_config_options = StakeHolderConfigOptions( provider_uri=MOCK_PROVIDER_URI, poa=None, light=None, registry_filepath=None, network=TEMPORARY_DOMAIN, signer_uri=None) mock_staking_agent.get_all_stakes.return_value = [SubStakeInfo(1, 2, 3)] force = False selected_index = 0 selected_account = mock_testerchain.client.accounts[selected_index] expected_stakeholder = StakeHolder(registry=test_registry, domains={TEMPORARY_DOMAIN}, initial_address=selected_account) expected_stakeholder.refresh_stakes() staker_options = StakerOptions(config_options=stakeholder_config_options, staking_address=selected_account) transacting_staker_options = TransactingStakerOptions( staker_options=staker_options, hw_wallet=None, beneficiary_address=None, allocation_filepath=None) stakeholder_from_configuration = transacting_staker_options.create_character( emitter=test_emitter, config_file=None) client_account, staking_address = select_client_account_for_staking( emitter=test_emitter, stakeholder=stakeholder_from_configuration, staking_address=selected_account, individual_allocation=None, force=force) assert client_account == staking_address == selected_account assert stakeholder_from_configuration.stakes == expected_stakeholder.stakes assert stakeholder_from_configuration.checksum_address == client_account staker_options = StakerOptions(config_options=stakeholder_config_options, staking_address=None) transacting_staker_options = TransactingStakerOptions( staker_options=staker_options, hw_wallet=None, beneficiary_address=None, allocation_filepath=None) stakeholder_from_configuration = transacting_staker_options.create_character( emitter=None, config_file=None) client_account, staking_address = select_client_account_for_staking( emitter=test_emitter, stakeholder=stakeholder_from_configuration, staking_address=selected_account, individual_allocation=None, force=force) assert client_account == staking_address == selected_account assert stakeholder_from_configuration.stakes == expected_stakeholder.stakes assert stakeholder_from_configuration.checksum_address == client_account
def test_select_client_account_for_staking_cli_action( test_emitter, test_registry, test_registry_source_manager, mock_stdin, mock_testerchain, capsys, mocker, mock_staking_agent): """Fine-grained assertions about the return value of interactive client account selection""" force = False mock_staking_agent.get_all_stakes.return_value = [] selected_index = 0 selected_account = mock_testerchain.client.accounts[selected_index] stakeholder = StakeHolder(registry=test_registry, domains={TEMPORARY_DOMAIN}) client_account, staking_address = select_client_account_for_staking( emitter=test_emitter, stakeholder=stakeholder, staking_address=selected_account, individual_allocation=None, force=force) assert client_account == staking_address == selected_account mock_stdin.line(str(selected_index)) client_account, staking_address = select_client_account_for_staking( emitter=test_emitter, stakeholder=stakeholder, staking_address=None, individual_allocation=None, force=force) assert client_account == staking_address == selected_account assert mock_stdin.empty() staking_contract_address = '0xFABADA' mock_individual_allocation = mocker.Mock( beneficiary_address=selected_account, contract_address=staking_contract_address) mock_stdin.line(YES) client_account, staking_address = select_client_account_for_staking( emitter=test_emitter, stakeholder=stakeholder, individual_allocation=mock_individual_allocation, staking_address=None, force=force) assert client_account == selected_account assert staking_address == staking_contract_address assert mock_stdin.empty() captured = capsys.readouterr() message = PREALLOCATION_STAKE_ADVISORY.format( client_account=selected_account, staking_address=staking_contract_address) assert message in captured.out
def events(general_config, staker_options, config_file, event_name): """See blockchain events associated to a staker""" # Setup emitter = setup_emitter(general_config) STAKEHOLDER = staker_options.create_character(emitter, config_file) _client_account, staking_address = select_client_account_for_staking( emitter=emitter, stakeholder=STAKEHOLDER, staking_address=staker_options.staking_address, individual_allocation=STAKEHOLDER.individual_allocation, force=True) title = f" {STAKEHOLDER.staking_agent.contract_name} Events ".center(40, "-") emitter.echo(f"\n{title}\n", bold=True, color='green') if event_name: events = [STAKEHOLDER.staking_agent.contract.events[event_name]] else: raise click.BadOptionUsage(message="You must specify an event name with --event-name") # TODO: Doesn't work for the moment # event_names = STAKEHOLDER.staking_agent.events.names # events = [STAKEHOLDER.staking_agent.contract.events[e] for e in event_names] # events = [e for e in events if 'staker' in e.argument_names] for event in events: emitter.echo(f"{event.event_name}:", bold=True, color='yellow') event_filter = event.createFilter(fromBlock=0, toBlock='latest', argument_filters={'staker': staking_address}) entries = event_filter.get_all_entries() for event_record in entries: emitter.echo(f" - {EventRecord(event_record)}")
def set_min_rate(general_config, transacting_staker_options, config_file, force, min_rate): """Staker sets the minimum acceptable fee rate for their associated worker.""" # 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) if not min_rate: paint_min_rate(emitter, STAKEHOLDER.registry, STAKEHOLDER.policy_agent, staking_address) # TODO check range min_rate = click.prompt(PROMPT_STAKER_MIN_POLICY_RATE, type=WEI) if not force: click.confirm(CONFIRM_NEW_MIN_POLICY_RATE.format(min_rate=min_rate), abort=True) password = transacting_staker_options.get_password(blockchain, client_account) STAKEHOLDER.assimilate(password=password) receipt = STAKEHOLDER.set_min_fee_rate(min_rate=min_rate) # Report Success message = SUCCESSFUL_SET_MIN_POLICY_RATE.format(min_rate=min_rate, staking_address=staking_address) emitter.echo(message, color='green') paint_receipt_summary(emitter=emitter, receipt=receipt, chain_name=blockchain.client.chain_name, transaction_type='set_min_rate')
def prolong(general_config, transacting_staker_options, config_file, force, lock_periods, index): """Prolong an existing stake's duration.""" # Setup emitter = setup_emitter(general_config) STAKEHOLDER = transacting_staker_options.create_character(emitter, config_file) action_period = STAKEHOLDER.staking_agent.get_current_period() blockchain = transacting_staker_options.get_blockchain() economics = STAKEHOLDER.economics # Handle account selection 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) # # Prolong # # Interactive if not lock_periods: max_extension = MAX_UINT16 - current_stake.final_locked_period # +1 because current period excluded min_extension = economics.minimum_locked_periods - current_stake.periods_remaining + 1 if min_extension < 1: min_extension = 1 duration_extension_range = click.IntRange(min=min_extension, max=max_extension, clamp=False) lock_periods = click.prompt(PROMPT_PROLONG_VALUE.format(minimum=min_extension, maximum=max_extension), type=duration_extension_range) if not force: click.confirm(CONFIRM_PROLONG.format(lock_periods=lock_periods), abort=True) # Authenticate password = transacting_staker_options.get_password(blockchain, client_account) STAKEHOLDER.assimilate(password=password) # Non-interactive: 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, color='red') raise click.Abort # Execute receipt = STAKEHOLDER.prolong_stake(stake=current_stake, additional_periods=lock_periods) # Report emitter.echo(SUCCESSFUL_STAKE_PROLONG, color='green', verbosity=1) paint_receipt_summary(emitter=emitter, receipt=receipt, chain_name=blockchain.client.chain_name) paint_stakes(emitter=emitter, staker=STAKEHOLDER)
def merge(general_config, transacting_staker_options, config_file, force, index_1, index_2): """Merge two stakes into one.""" # Setup emitter = setup_emitter(general_config) STAKEHOLDER = transacting_staker_options.create_character(emitter, config_file) action_period = STAKEHOLDER.staking_agent.get_current_period() 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 stakes selection stake_1, stake_2 = None, None if index_1 is not None and index_2 is not None: stake_1 = STAKEHOLDER.stakes[index_1] stake_2 = STAKEHOLDER.stakes[index_2] elif index_1 is not None: # 0 is valid. stake_1 = STAKEHOLDER.stakes[index_1] elif index_2 is not None: stake_1 = STAKEHOLDER.stakes[index_2] if stake_1 is None: stake_1 = select_stake(staker=STAKEHOLDER, emitter=emitter) if stake_2 is None: emitter.echo(ONLY_DISPLAYING_MERGEABLE_STAKES_NOTE.format(final_period=stake_1.final_locked_period), color='yellow') stake_2 = select_stake(staker=STAKEHOLDER, emitter=emitter, filter_function=lambda s: s.index != stake_1.index and s.final_locked_period == stake_1.final_locked_period) if not force: click.confirm(CONFIRM_MERGE.format(stake_index_1=stake_1.index, stake_index_2=stake_2.index), abort=True) # Authenticate password = transacting_staker_options.get_password(blockchain, client_account) STAKEHOLDER.assimilate(password=password) # Non-interactive: 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, color='red') raise click.Abort # Execute receipt = STAKEHOLDER.merge_stakes(stake_1=stake_1, stake_2=stake_2) # Report emitter.echo(SUCCESSFUL_STAKES_MERGE, color='green', verbosity=1) paint_receipt_summary(emitter=emitter, receipt=receipt, chain_name=blockchain.client.chain_name) paint_stakes(emitter=emitter, staker=STAKEHOLDER)
def restake(general_config, transacting_staker_options, config_file, enable, lock_until, force): """Manage re-staking with --enable or --disable.""" # 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) # Authenticate password = transacting_staker_options.get_password(blockchain, client_account) STAKEHOLDER.assimilate(checksum_address=client_account, password=password) # Inner Exclusive Switch if lock_until: if not force: confirm_enable_restaking_lock(emitter, staking_address=staking_address, release_period=lock_until) receipt = STAKEHOLDER.enable_restaking_lock(release_period=lock_until) emitter.echo(SUCCESSFUL_ENABLE_RESTAKE_LOCK.format( staking_address=staking_address, lock_until=lock_until), color='green', verbosity=1) elif enable: if not force: confirm_enable_restaking(emitter, staking_address=staking_address) receipt = STAKEHOLDER.enable_restaking() emitter.echo(SUCCESSFUL_ENABLE_RESTAKING.format( staking_address=staking_address), color='green', verbosity=1) else: if not force: click.confirm(CONFIRM_DISABLE_RESTAKING.format( staking_address=staking_address), abort=True) receipt = STAKEHOLDER.disable_restaking() emitter.echo(SUCCESSFUL_DISABLE_RESTAKING.format( staking_address=staking_address), color='green', verbosity=1) paint_receipt_summary(receipt=receipt, emitter=emitter, chain_name=blockchain.client.chain_name)
def winddown(general_config, transacting_staker_options, config_file, enable, force): """Manage winding down with --enable or --disable.""" # 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) # Inner Exclusive Switch if enable: if not force: confirm_enable_winding_down(emitter, staking_address=staking_address) # Authenticate and Execute password = transacting_staker_options.get_password( blockchain, client_account) STAKEHOLDER.assimilate(password=password) receipt = STAKEHOLDER.enable_winding_down() emitter.echo(SUCCESSFUL_ENABLE_WIND_DOWN.format( staking_address=staking_address), color='green', verbosity=1) else: if not force: click.confirm(CONFIRM_DISABLE_WIND_DOWN.format( staking_address=staking_address), abort=True) # Authenticate and Execute password = transacting_staker_options.get_password( blockchain, client_account) STAKEHOLDER.assimilate(password=password) receipt = STAKEHOLDER.disable_winding_down() emitter.echo(SUCCESSFUL_DISABLE_WIND_DOWN.format( staking_address=staking_address), color='green', verbosity=1) paint_receipt_summary(receipt=receipt, emitter=emitter, chain_name=blockchain.client.chain_name)
def bond_worker(general_config, transacting_staker_options, config_file, force, worker_address): """Bond a worker to a staker.""" 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) if not worker_address: worker_address = click.prompt(PROMPT_WORKER_ADDRESS, type=EIP55_CHECKSUM_ADDRESS) if (worker_address == staking_address) and not force: click.confirm(CONFIRM_WORKER_AND_STAKER_ADDRESSES_ARE_EQUAL.format(address=worker_address), abort=True) # TODO: Check preconditions (e.g., minWorkerPeriods, already in use, etc) # TODO: Double-check dates # Calculate release datetime current_period = STAKEHOLDER.staking_agent.get_current_period() bonded_date = datetime_at_period(period=current_period, seconds_per_period=economics.seconds_per_period) min_worker_periods = STAKEHOLDER.economics.minimum_worker_periods release_period = current_period + min_worker_periods release_date = datetime_at_period(period=release_period, seconds_per_period=economics.seconds_per_period, start_of_period=True) if not force: click.confirm(f"Commit to bonding " f"worker {worker_address} to staker {staking_address} " f"for a minimum of {STAKEHOLDER.economics.minimum_worker_periods} periods?", abort=True) password = transacting_staker_options.get_password(blockchain, client_account) STAKEHOLDER.assimilate(password=password) receipt = STAKEHOLDER.bond_worker(worker_address=worker_address) # Report Success message = SUCCESSFUL_WORKER_BONDING.format(worker_address=worker_address, staking_address=staking_address) emitter.echo(message, color='green') paint_receipt_summary(emitter=emitter, receipt=receipt, chain_name=blockchain.client.chain_name, transaction_type='bond_worker') emitter.echo(BONDING_DETAILS.format(current_period=current_period, bonded_date=bonded_date), color='green') emitter.echo(BONDING_RELEASE_INFO.format(release_period=release_period, release_date=release_date), color='green')
def test_select_client_account_for_staking_cli_action( test_emitter, test_registry, test_registry_source_manager, mock_stdin, mock_testerchain, capsys, mock_staking_agent): """Fine-grained assertions about the return value of interactive client account selection""" mock_staking_agent.get_all_stakes.return_value = [] selected_index = 0 selected_account = mock_testerchain.client.accounts[selected_index] stakeholder = StakeHolder(registry=test_registry, domain=TEMPORARY_DOMAIN, signer=Web3Signer(mock_testerchain.client)) client_account, staking_address = select_client_account_for_staking( emitter=test_emitter, stakeholder=stakeholder, staking_address=selected_account) assert client_account == staking_address == selected_account mock_stdin.line(str(selected_index)) client_account, staking_address = select_client_account_for_staking( emitter=test_emitter, stakeholder=stakeholder, staking_address=None) assert client_account == staking_address == selected_account assert mock_stdin.empty()
def collect_reward(general_config, transacting_staker_options, config_file, staking_reward, policy_fee, withdraw_address, force): """Withdraw staking reward.""" # Setup emitter = setup_emitter(general_config) STAKEHOLDER = transacting_staker_options.create_character( emitter, config_file) blockchain = transacting_staker_options.get_blockchain() if not staking_reward and not policy_fee: raise click.BadArgumentUsage( f"Either --staking-reward or --policy-fee must be True to collect rewards." ) 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) password = transacting_staker_options.get_password(blockchain, client_account) STAKEHOLDER.assimilate(checksum_address=client_account, password=password) if staking_reward: # Note: Sending staking / inflation rewards to another account is not allowed. reward_amount = NU.from_nunits(STAKEHOLDER.calculate_staking_reward()) emitter.echo(message=COLLECTING_TOKEN_REWARD.format( reward_amount=reward_amount)) staking_receipt = STAKEHOLDER.collect_staking_reward() paint_receipt_summary( receipt=staking_receipt, chain_name=STAKEHOLDER.wallet.blockchain.client.chain_name, emitter=emitter) if policy_fee: fee_amount = Web3.fromWei(STAKEHOLDER.calculate_policy_fee(), 'ether') emitter.echo(message=COLLECTING_ETH_REWARD.format( reward_amount=fee_amount)) policy_receipt = STAKEHOLDER.collect_policy_fee( collector_address=withdraw_address) paint_receipt_summary( receipt=policy_receipt, chain_name=STAKEHOLDER.wallet.blockchain.client.chain_name, emitter=emitter)
def preallocation(general_config, transacting_staker_options, config_file, action, force): """Claim token rewards collected by a preallocation contract.""" # Setup emitter = setup_emitter(general_config) STAKEHOLDER = transacting_staker_options.create_character( emitter, config_file) blockchain = transacting_staker_options.get_blockchain() # Unauthenticated actions: status if action == 'status': return paint_preallocation_status( emitter=emitter, token_agent=STAKEHOLDER.token_agent, preallocation_agent=STAKEHOLDER.preallocation_escrow_agent) # Authenticated actions: withdraw-tokens 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) # Authenticate password = transacting_staker_options.get_password(blockchain, client_account) STAKEHOLDER.assimilate(checksum_address=client_account, password=password) if action == 'withdraw': token_balance = NU.from_nunits( STAKEHOLDER.token_agent.get_balance(staking_address)) locked_tokens = NU.from_nunits( STAKEHOLDER.preallocation_escrow_agent.unvested_tokens) unlocked_tokens = token_balance - locked_tokens emitter.echo(message=COLLECTING_PREALLOCATION_REWARD.format( unlocked_tokens=unlocked_tokens, staking_address=staking_address)) receipt = STAKEHOLDER.withdraw_preallocation_tokens(unlocked_tokens) paint_receipt_summary( receipt=receipt, chain_name=STAKEHOLDER.wallet.blockchain.client.chain_name, emitter=emitter)
def unbond_worker(general_config, transacting_staker_options, config_file, force): """ Unbond worker currently bonded to a staker. """ 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) # TODO: Check preconditions (e.g., minWorkerPeriods) worker_address = STAKEHOLDER.staking_agent.get_worker_from_staker( staking_address) password = transacting_staker_options.get_password(blockchain, client_account) STAKEHOLDER.assimilate(password=password) receipt = STAKEHOLDER.unbond_worker() # TODO: Double-check dates current_period = STAKEHOLDER.staking_agent.get_current_period() bonded_date = datetime_at_period( period=current_period, seconds_per_period=economics.seconds_per_period) message = SUCCESSFUL_DETACH_WORKER.format(worker_address=worker_address, staking_address=staking_address) emitter.echo(message, color='green') paint_receipt_summary(emitter=emitter, receipt=receipt, chain_name=blockchain.client.chain_name, transaction_type='unbond_worker') emitter.echo(DETACH_DETAILS.format(current_period=current_period, bonded_date=bonded_date), color='green')
def mint(general_config, transacting_staker_options, config_file, force): """Mint last portion of reward""" # 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) # Nothing to mint mintable_periods = STAKEHOLDER.mintable_periods() if mintable_periods == 0: emitter.echo(NO_MINTABLE_PERIODS, color='red') raise click.Abort # Still locked token if STAKEHOLDER.non_withdrawable_stake() > 0: emitter.echo(STILL_LOCKED_TOKENS, color='yellow') if not force: click.confirm( CONFIRM_MINTING.format(mintable_periods=mintable_periods), abort=True) # Authenticate password = transacting_staker_options.get_password(blockchain, client_account) STAKEHOLDER.assimilate(password=password) receipt = STAKEHOLDER.mint() emitter.echo(SUCCESSFUL_MINTING, color='green', verbosity=1) paint_receipt_summary(receipt=receipt, emitter=emitter, chain_name=blockchain.client.chain_name, transaction_type='mint')
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)
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)
def collect_reward(general_config, transacting_staker_options, config_file, staking_reward, policy_fee, withdraw_address, force): """Withdraw staking reward.""" # Setup emitter = setup_emitter(general_config) STAKEHOLDER = transacting_staker_options.create_character( emitter, config_file) blockchain = transacting_staker_options.get_blockchain() if not staking_reward and not policy_fee: raise click.BadArgumentUsage( f"Either --staking-reward or --policy-fee must be True to collect rewards." ) 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) password = None if staking_reward: # Note: Sending staking / inflation rewards to another account is not allowed. reward_amount = NU.from_nunits(STAKEHOLDER.calculate_staking_reward()) if reward_amount == 0: emitter.echo(NO_TOKENS_TO_WITHDRAW, color='red') raise click.Abort emitter.echo(message=COLLECTING_TOKEN_REWARD.format( reward_amount=reward_amount)) withdrawing_last_portion = STAKEHOLDER.non_withdrawable_stake() == 0 if not force and withdrawing_last_portion and STAKEHOLDER.mintable_periods( ) > 0: click.confirm(CONFIRM_COLLECTING_WITHOUT_MINTING, abort=True) # Authenticate and Execute password = transacting_staker_options.get_password( blockchain, client_account) STAKEHOLDER.assimilate(password=password) staking_receipt = STAKEHOLDER.collect_staking_reward() paint_receipt_summary( receipt=staking_receipt, chain_name=STAKEHOLDER.wallet.blockchain.client.chain_name, emitter=emitter) if policy_fee: fee_amount = Web3.fromWei(STAKEHOLDER.calculate_policy_fee(), 'ether') if fee_amount == 0: emitter.echo(NO_FEE_TO_WITHDRAW, color='red') raise click.Abort emitter.echo(message=COLLECTING_ETH_FEE.format(fee_amount=fee_amount)) if password is None: # Authenticate and Execute password = transacting_staker_options.get_password( blockchain, client_account) STAKEHOLDER.assimilate(password=password) policy_receipt = STAKEHOLDER.collect_policy_fee( collector_address=withdraw_address) paint_receipt_summary( receipt=policy_receipt, chain_name=STAKEHOLDER.wallet.blockchain.client.chain_name, emitter=emitter)
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)
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)