def paint_preallocation_status(emitter, preallocation_agent, token_agent) -> None: blockchain = token_agent.blockchain staking_address = preallocation_agent.principal_contract.address token_balance = NU.from_nunits(token_agent.get_balance(staking_address)) eth_balance = Web3.fromWei(blockchain.client.get_balance(staking_address), 'ether') initial_locked_amount = NU.from_nunits( preallocation_agent.initial_locked_amount) current_locked_amount = NU.from_nunits(preallocation_agent.unvested_tokens) available_amount = NU.from_nunits(preallocation_agent.available_balance) end_timestamp = preallocation_agent.end_timestamp width = 64 output = f""" {" Addresses ".center(width, "-")} Staking contract: ... {staking_address} Beneficiary: ........ {preallocation_agent.beneficiary} {" Locked Tokens ".center(width, "-")} Initial locked amount: {initial_locked_amount} Current locked amount: {current_locked_amount} Locked until: ........ {maya.MayaDT(epoch=end_timestamp)} {" NU and ETH Balance ".center(width, "-")} NU balance: .......... {token_balance} Available: ....... {available_amount} ETH balance: ......... {eth_balance} ETH """ emitter.echo(output)
def preallocation(click_config, # Stake Options poa, light, registry_filepath, config_file, provider_uri, staking_address, hw_wallet, beneficiary_address, allocation_filepath, # Preallocation subcommands, action, # Other force): """ Claim token rewards collected by a preallocation contract. """ ### Setup ### emitter = _setup_emitter(click_config) STAKEHOLDER, blockchain = _create_stakeholder(config_file, provider_uri, poa, light, registry_filepath, staking_address, beneficiary_address=beneficiary_address, allocation_filepath=allocation_filepath) ############# # Unauthenticated actions: status if action == 'status': paint_preallocation_status(emitter=emitter, token_agent=STAKEHOLDER.token_agent, preallocation_agent=STAKEHOLDER.preallocation_escrow_agent) return # Authenticated actions: withdraw-tokens client_account, staking_address = handle_client_account_for_staking(emitter=emitter, stakeholder=STAKEHOLDER, staking_address=staking_address, individual_allocation=STAKEHOLDER.individual_allocation, force=force) password = None if not hw_wallet and not blockchain.client.is_local: password = get_client_password(checksum_address=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=f'Collecting {unlocked_tokens} from PreallocationEscrow contract {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 test_nucypher_status_stakers(click_runner, agency_local_registry, stakers): # Get all stakers info stakers_command = ('stakers', '--registry-filepath', str(agency_local_registry.filepath.absolute()), '--provider', TEST_PROVIDER_URI, '--network', TEMPORARY_DOMAIN) result = click_runner.invoke(status, stakers_command, catch_exceptions=False) assert result.exit_code == 0 staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=agency_local_registry) # TODO: Use regex matching instead of this assert re.search(f"^Current period: {staking_agent.get_current_period()}", result.output, re.MULTILINE) for staker in stakers: assert re.search(f"^{staker.checksum_address}", result.output, re.MULTILINE) # Get info of only one staker some_dude = random.choice(stakers) staking_address = some_dude.checksum_address stakers_command = ('stakers', '--staking-address', staking_address, '--registry-filepath', str(agency_local_registry.filepath.absolute()), '--provider', TEST_PROVIDER_URI, '--network', TEMPORARY_DOMAIN) result = click_runner.invoke(status, stakers_command, catch_exceptions=False) assert result.exit_code == 0 owned_tokens = NU.from_nunits(staking_agent.owned_tokens(staking_address)) current_locked_tokens = NU.from_nunits( staking_agent.get_locked_tokens(staking_address)) next_locked_tokens = NU.from_nunits( staking_agent.get_locked_tokens(staking_address, 1)) assert re.search(f"^Current period: {staking_agent.get_current_period()}", result.output, re.MULTILINE) assert re.search(r"Worker:\s+" + some_dude.worker_address, result.output, re.MULTILINE) assert re.search(r"Owned:\s+" + str(round(owned_tokens, 2)), result.output, re.MULTILINE) assert re.search( r"Staked in current period: " + str(round(current_locked_tokens, 2)), result.output, re.MULTILINE) assert re.search( r"Staked in next period: " + str(round(next_locked_tokens, 2)), result.output, re.MULTILINE) _minimum, default, _maximum = FEE_RATE_RANGE assert f"Min fee rate: {default} wei" in result.output
def paint_stakers(emitter, stakers: List[str], agent) -> None: current_period = agent.get_current_period() emitter.echo(f"\nCurrent period: {current_period}") emitter.echo("\n| Stakers |\n") emitter.echo(f"{'Checksum address':42} Staker information") emitter.echo('=' * (42 + 2 + 53)) for staker in stakers: nickname, pairs = nickname_from_seed(staker) symbols = f"{pairs[0][1]} {pairs[1][1]}" emitter.echo(f"{staker} {'Nickname:':10} {nickname} {symbols}") tab = " " * len(staker) stake = agent.owned_tokens(staker) last_confirmed_period = agent.get_last_active_period(staker) worker = agent.get_worker_from_staker(staker) is_restaking = agent.is_restaking(staker) missing_confirmations = current_period - last_confirmed_period stake_in_nu = round(NU.from_nunits(stake), 2) locked_tokens = round(NU.from_nunits(agent.get_locked_tokens(staker)), 2) emitter.echo( f"{tab} {'Stake:':10} {stake_in_nu} (Locked: {locked_tokens})") if is_restaking: if agent.is_restaking_locked(staker): unlock_period = agent.get_restake_unlock_period(staker) emitter.echo( f"{tab} {'Re-staking:':10} Yes (Locked until period: {unlock_period})" ) else: emitter.echo(f"{tab} {'Re-staking:':10} Yes (Unlocked)") else: emitter.echo(f"{tab} {'Re-staking:':10} No") emitter.echo(f"{tab} {'Activity:':10} ", nl=False) if missing_confirmations == -1: emitter.echo(f"Next period confirmed (#{last_confirmed_period})", color='green') elif missing_confirmations == 0: emitter.echo( f"Current period confirmed (#{last_confirmed_period}). " f"Pending confirmation of next period.", color='yellow') elif missing_confirmations == current_period: emitter.echo(f"Never confirmed activity", color='red') else: emitter.echo( f"Missing {missing_confirmations} confirmations " f"(last time for period #{last_confirmed_period})", color='red') emitter.echo(f"{tab} {'Worker:':10} ", nl=False) if worker == BlockchainInterface.NULL_ADDRESS: emitter.echo(f"Worker not set\n", color='red') else: emitter.echo(f"{worker}\n")
def paint_stakers(emitter, stakers: List[str], staking_agent, policy_agent) -> None: current_period = staking_agent.get_current_period() emitter.echo(f"\nCurrent period: {current_period}") emitter.echo("\n| Stakers |\n") emitter.echo(f"{'Checksum address':42} Staker information") emitter.echo('=' * (42 + 2 + 53)) for staker in stakers: nickname, pairs = nickname_from_seed(staker) symbols = f"{pairs[0][1]} {pairs[1][1]}" emitter.echo(f"{staker} {'Nickname:':10} {nickname} {symbols}") tab = " " * len(staker) owned_tokens = staking_agent.owned_tokens(staker) last_confirmed_period = staking_agent.get_last_active_period(staker) worker = staking_agent.get_worker_from_staker(staker) is_restaking = staking_agent.is_restaking(staker) is_winding_down = staking_agent.is_winding_down(staker) missing_confirmations = current_period - last_confirmed_period owned_in_nu = round(NU.from_nunits(owned_tokens), 2) locked_tokens = round(NU.from_nunits(staking_agent.get_locked_tokens(staker)), 2) emitter.echo(f"{tab} {'Owned:':10} {owned_in_nu} (Staked: {locked_tokens})") if is_restaking: if staking_agent.is_restaking_locked(staker): unlock_period = staking_agent.get_restake_unlock_period(staker) emitter.echo(f"{tab} {'Re-staking:':10} Yes (Locked until period: {unlock_period})") else: emitter.echo(f"{tab} {'Re-staking:':10} Yes (Unlocked)") else: emitter.echo(f"{tab} {'Re-staking:':10} No") emitter.echo(f"{tab} {'Winding down:':10} {'Yes' if is_winding_down else 'No'}") emitter.echo(f"{tab} {'Activity:':10} ", nl=False) if missing_confirmations == -1: emitter.echo(f"Next period confirmed (#{last_confirmed_period})", color='green') elif missing_confirmations == 0: emitter.echo(f"Current period confirmed (#{last_confirmed_period}). " f"Pending confirmation of next period.", color='yellow') elif missing_confirmations == current_period: emitter.echo(f"Never confirmed activity", color='red') else: emitter.echo(f"Missing {missing_confirmations} confirmations " f"(last time for period #{last_confirmed_period})", color='red') emitter.echo(f"{tab} {'Worker:':10} ", nl=False) if worker == NULL_ADDRESS: emitter.echo(f"Worker not set", color='red') else: emitter.echo(f"{worker}") fees = prettify_eth_amount(policy_agent.get_reward_amount(staker)) emitter.echo(f"{tab} Unclaimed fees: {fees}") min_rate = prettify_eth_amount(policy_agent.get_min_reward_rate(staker)) emitter.echo(f"{tab} Min reward rate: {min_rate}")
def escrow(general_config: GroupGeneralConfig, worklock_options: WorkLockOptions, force: bool, hw_wallet: bool, value: Decimal): """Create an ETH escrow, or increase an existing escrow""" emitter, registry, blockchain = worklock_options.setup(general_config=general_config) worklock_agent = ContractAgency.get_agent(WorkLockAgent, registry=registry) # type: WorkLockAgent now = maya.now().epoch if not worklock_agent.start_bidding_date <= now <= worklock_agent.end_bidding_date: emitter.echo(BIDDING_WINDOW_CLOSED, color='red') raise click.Abort() _bidder_address = worklock_options.get_bidder_address(emitter, registry) bidder = worklock_options.create_bidder(registry=registry, hw_wallet=hw_wallet) existing_bid_amount = bidder.get_deposited_eth if not value: if force: raise click.MissingParameter("Missing --value.") if not existing_bid_amount: # It's the first bid minimum_bid = bidder.worklock_agent.minimum_allowed_bid minimum_bid_in_eth = Web3.fromWei(minimum_bid, 'ether') prompt = BID_AMOUNT_PROMPT_WITH_MIN_BID.format(minimum_bid_in_eth=minimum_bid_in_eth) else: # There's an existing bid and the bidder is increasing the amount emitter.message(EXISTING_BID_AMOUNT_NOTICE.format(eth_amount=Web3.fromWei(existing_bid_amount, 'ether'))) minimum_bid_in_eth = Web3.fromWei(1, 'ether') prompt = BID_INCREASE_AMOUNT_PROMPT value = click.prompt(prompt, type=DecimalRange(min=minimum_bid_in_eth)) value = int(Web3.toWei(Decimal(value), 'ether')) if not force: if not existing_bid_amount: paint_bidding_notice(emitter=emitter, bidder=bidder) click.confirm(f"Place WorkLock escrow of {prettify_eth_amount(value)}?", abort=True) else: click.confirm(f"Increase current escrow ({prettify_eth_amount(existing_bid_amount)}) " f"by {prettify_eth_amount(value)}?", abort=True) receipt = bidder.place_bid(value=value) emitter.message("Publishing WorkLock Escrow...") maximum = NU.from_nunits(bidder.economics.maximum_allowed_locked) available_claim = NU.from_nunits(bidder.available_claim) message = f'\nCurrent escrow: {prettify_eth_amount(bidder.get_deposited_eth)} | Allocation: {available_claim}\n' if available_claim > maximum: message += f"\nThis allocation is currently above the allowed max ({maximum}), " \ f"so the escrow may be partially refunded.\n" message += f'Note that the available allocation value may fluctuate until the escrow period closes and ' \ f'allocations are finalized.\n' emitter.echo(message, color='yellow') paint_receipt_summary(receipt=receipt, emitter=emitter, chain_name=bidder.staking_agent.blockchain.client.chain_name)
def test_stake_status(mock_testerchain, token_economics, mock_staking_agent): address = mock_testerchain.etherbase_account current_period = 3 staker_info = StakerInfo(current_committed_period=current_period - 1, next_committed_period=current_period, value=0, last_committed_period=0, lock_restake_until_period=False, completed_work=0, worker_start_period=0, worker=NULL_ADDRESS, flags=bytes()) mock_staking_agent.get_current_period.return_value = current_period mock_staking_agent.get_staker_info.return_value = staker_info def make_sub_stake(value, first_locked_period, final_locked_period): return Stake(checksum_address=address, first_locked_period=first_locked_period, final_locked_period=final_locked_period, value=value, index=0, staking_agent=mock_staking_agent, economics=token_economics, validate_now=False) # Prepare unlocked sub-stake nu = NU.from_nunits(2 * token_economics.minimum_allowed_locked - 1) stake = make_sub_stake(nu, current_period - 2, current_period - 1) assert stake.status() == Stake.Status.UNLOCKED # Prepare inactive sub-stake # Update staker info and create new state stake = make_sub_stake(nu, current_period - 2, current_period - 1) staker_info = staker_info._replace(current_committed_period=current_period, next_committed_period=current_period + 1) mock_staking_agent.get_staker_info.return_value = staker_info assert stake.status() == Stake.Status.INACTIVE # Prepare locked sub-stake stake = make_sub_stake(nu, current_period - 2, current_period) assert stake.status() == Stake.Status.LOCKED # Prepare editable sub-stake stake = make_sub_stake(nu, current_period - 2, current_period + 1) assert stake.status() == Stake.Status.EDITABLE # Prepare divisible sub-stake nu = NU.from_nunits(2 * token_economics.minimum_allowed_locked) stake = make_sub_stake(nu, current_period - 2, current_period + 1) assert stake.status() == Stake.Status.DIVISIBLE
def bid(general_config, worklock_options, registry_options, force, hw_wallet, value): """Place a bid, or increase an existing bid""" emitter = _setup_emitter(general_config) registry = registry_options.get_registry(emitter, general_config.debug) worklock_agent = ContractAgency.get_agent(WorkLockAgent, registry=registry) # type: WorkLockAgent now = maya.now().epoch if not worklock_agent.start_bidding_date <= now <= worklock_agent.end_bidding_date: raise click.Abort(f"You can't bid, the bidding window is closed.") if not worklock_options.bidder_address: worklock_options.bidder_address = select_client_account(emitter=emitter, provider_uri=registry_options.provider_uri, poa=registry_options.poa, network=registry_options.network, registry=registry, show_balances=True) bidder = worklock_options.create_bidder(registry=registry, hw_wallet=hw_wallet) if not value: if force: raise click.MissingParameter("Missing --value.") existing_bid_amount = bidder.get_deposited_eth if not existing_bid_amount: # It's the first bid minimum_bid = bidder.worklock_agent.minimum_allowed_bid minimum_bid_in_eth = Web3.fromWei(minimum_bid, 'ether') prompt = f"Enter bid amount in ETH (at least {minimum_bid_in_eth} ETH)" else: # There's an existing bid and the bidder is increasing the amount emitter.message(f"You have an existing bid of {Web3.fromWei(existing_bid_amount, 'ether')} ETH") minimum_bid_in_eth = Web3.fromWei(1, 'ether') prompt = f"Enter the amount in ETH that you want to increase your bid" value = click.prompt(prompt, type=DecimalRange(min=minimum_bid_in_eth)) value = int(Web3.toWei(Decimal(value), 'ether')) if not force: paint_bidding_notice(emitter=emitter, bidder=bidder) click.confirm(f"Place WorkLock bid of {prettify_eth_amount(value)}?", abort=True) receipt = bidder.place_bid(value=value) emitter.message("Publishing WorkLock Bid...") maximum = NU.from_nunits(bidder.economics.maximum_allowed_locked) available_claim = NU.from_nunits(bidder.available_claim) message = f'Current bid: {prettify_eth_amount(bidder.get_deposited_eth)} | Claim: {available_claim}\n' if available_claim > maximum: message += f"This claim is currently above the allowed max ({maximum}), so the bid may be partially refunded.\n" message += f'Note that available claim value may fluctuate until bidding closes and claims are finalized.\n' emitter.echo(message, color='yellow') paint_receipt_summary(receipt=receipt, emitter=emitter, chain_name=bidder.staking_agent.blockchain.client.chain_name) return # Exit
def _learn_about_nodes_contract_info(self): agent = self.staking_agent block_time = agent.blockchain.client.w3.eth.getBlock('latest').timestamp # precision in seconds current_period = agent.get_current_period() nodes_dict = self.known_nodes.abridged_nodes_dict() self.log.info(f'Processing {len(nodes_dict)} nodes at ' f'{MayaDT(epoch=block_time)} | Period {current_period}') data = [] for staker_address in nodes_dict: worker = agent.get_worker_from_staker(staker_address) stake = agent.owned_tokens(staker_address) staked_nu_tokens = float(NU.from_nunits(stake).to_tokens()) locked_nu_tokens = float(NU.from_nunits(agent.get_locked_tokens( staker_address=staker_address)).to_tokens()) economics = TokenEconomicsFactory.get_economics(registry=self.registry) stakes = StakeList(checksum_address=staker_address, registry=self.registry) stakes.refresh() # store dates as floats for comparison purposes start_date = datetime_at_period(stakes.initial_period, seconds_per_period=economics.seconds_per_period).datetime().timestamp() end_date = datetime_at_period(stakes.terminal_period, seconds_per_period=economics.seconds_per_period).datetime().timestamp() last_confirmed_period = agent.get_last_active_period(staker_address) # TODO: do we need to worry about how much information is in memory if number of nodes is # large i.e. should I check for size of data and write within loop if too big data.append(self.BLOCKCHAIN_DB_LINE_PROTOCOL.format( measurement=self.BLOCKCHAIN_DB_MEASUREMENT, staker_address=staker_address, worker_address=worker, start_date=start_date, end_date=end_date, stake=staked_nu_tokens, locked_stake=locked_nu_tokens, current_period=current_period, last_confirmed_period=last_confirmed_period, timestamp=block_time )) if not self._blockchain_db_client.write_points(data, database=self.BLOCKCHAIN_DB_NAME, time_precision='s', batch_size=10000, protocol='line'): # TODO: what do we do here self.log.warn(f'Unable to write to database {self.BLOCKCHAIN_DB_NAME} at ' f'{MayaDT(epoch=block_time)} | Period {current_period}')
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': painting.paint_preallocation_status( emitter=emitter, token_agent=STAKEHOLDER.token_agent, preallocation_agent=STAKEHOLDER.preallocation_escrow_agent) return # Authenticated actions: withdraw-tokens client_account, staking_address = handle_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 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= f'Collecting {unlocked_tokens} from PreallocationEscrow contract {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 test_withdraw_from_preallocation( click_runner, testerchain, agency_local_registry, stakeholder_configuration_file_location, beneficiary, preallocation_escrow_agent, ): staker_address = preallocation_escrow_agent.principal_contract.address token_agent = ContractAgency.get_agent(agent_class=NucypherTokenAgent, registry=agency_local_registry) tokens_in_contract = NU.from_nunits( token_agent.get_balance(address=staker_address)) locked_preallocation = NU.from_nunits( preallocation_escrow_agent.unvested_tokens) collection_args = ( 'stake', 'preallocation', 'status', '--config-file', stakeholder_configuration_file_location, '--allocation-filepath', MOCK_INDIVIDUAL_ALLOCATION_FILEPATH, ) result = click_runner.invoke(nucypher_cli, collection_args, input=INSECURE_DEVELOPMENT_PASSWORD, catch_exceptions=True) assert result.exit_code == 0 assert f'NU balance: .......... {tokens_in_contract}' in result.output balance_before_collecting = token_agent.get_balance(address=beneficiary) collection_args = ('stake', 'preallocation', 'withdraw', '--config-file', stakeholder_configuration_file_location, '--allocation-filepath', MOCK_INDIVIDUAL_ALLOCATION_FILEPATH, '--force') result = click_runner.invoke(nucypher_cli, collection_args, input=INSECURE_DEVELOPMENT_PASSWORD, catch_exceptions=True) assert result.exit_code == 0 assert token_agent.get_balance( address=staker_address) == locked_preallocation withdrawn_amount = tokens_in_contract - locked_preallocation balance_after_collecting = token_agent.get_balance(address=beneficiary) assert balance_after_collecting == balance_before_collecting + withdrawn_amount
def staked_tokens(n): nu = NU.from_nunits( monitor.staking_agent.get_global_locked_tokens()) return html.Div([ html.H4('Staked Tokens'), html.H5(f"{nu}", id='staked-tokens-value') ])
def current_stake(self) -> NU: """ The total number of staked tokens, either locked or unlocked in the current period. """ stake = self.staking_agent.owned_tokens(staker_address=self.checksum_address) nu_stake = NU.from_nunits(stake) return nu_stake
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_participant_status(click_runner, testerchain, agency_local_registry, token_economics): tpower = TransactingPower(account=testerchain.client.accounts[2], signer=Web3Signer(testerchain.client)) bidder = Bidder(transacting_power=tpower, domain=TEMPORARY_DOMAIN, registry=agency_local_registry) command = ('status', '--registry-filepath', agency_local_registry.filepath, '--participant-address', bidder.checksum_address, '--provider', TEST_PROVIDER_URI, '--signer', TEST_PROVIDER_URI, '--network', TEMPORARY_DOMAIN) result = click_runner.invoke(worklock, command, catch_exceptions=False) assert result.exit_code == 0 # Bidder-specific data is displayed assert bidder.checksum_address in result.output assert str(bidder.remaining_work) in result.output assert str(bidder.available_refund) in result.output # Worklock economics are displayed assert str(token_economics.worklock_boosting_refund_rate) in result.output assert str(NU.from_nunits( token_economics.worklock_supply)) in result.output
def transfer_tokens(general_config, actor_options, target_address, value): """Transfer tokens from contract's owner address to another address""" emitter = general_config.emitter ADMINISTRATOR, deployer_address, _, local_registry = actor_options.create_actor( emitter) token_agent = ContractAgency.get_agent( NucypherTokenAgent, registry=local_registry) # type: NucypherTokenAgent tokens = NU.from_nunits(token_agent.get_balance(deployer_address)) emitter.echo( DISPLAY_SENDER_TOKEN_BALANCE_BEFORE_TRANSFER.format( token_balance=tokens)) if not target_address: target_address = click.prompt(PROMPT_RECIPIENT_CHECKSUM_ADDRESS, type=EIP55_CHECKSUM_ADDRESS) if not value: stake_value_range = click.FloatRange(min=0, clamp=False) value = NU.from_tokens( click.prompt(PROMPT_TOKEN_VALUE, type=stake_value_range)) confirmation = CONFIRM_TOKEN_TRANSFER.format( value=value, deployer_address=deployer_address, target_address=target_address) click.confirm(confirmation, abort=True) receipt = token_agent.transfer(amount=int(value), sender_address=deployer_address, target_address=target_address) paint_receipt_summary(emitter=emitter, receipt=receipt)
def staked_tokens(n, latest_crawler_stats): data = self.verify_cached_stats(latest_crawler_stats) staked = NU.from_nunits(data['global_locked_tokens']) return html.Div([ html.H4('Staked Tokens'), html.H5(f"{staked}", id='staked-tokens-value') ])
def test_nucypher_status_locked_tokens(click_runner, testerchain, agency_local_registry, stakers): staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=agency_local_registry) # All workers make a commitment for ursula in testerchain.ursulas_accounts: staking_agent.commit_to_next_period(worker_address=ursula) testerchain.time_travel(periods=1) periods = 2 status_command = ('locked-tokens', '--registry-filepath', agency_local_registry.filepath, '--provider', TEST_PROVIDER_URI, '--network', TEMPORARY_DOMAIN, '--periods', periods) light_parameter = [False, True] for light in light_parameter: testerchain.is_light = light result = click_runner.invoke(status, status_command, catch_exceptions=False) assert result.exit_code == 0 current_period = staking_agent.get_current_period() all_locked = NU.from_nunits( staking_agent.get_global_locked_tokens(at_period=current_period)) assert re.search(f"Locked Tokens for next {periods} periods", result.output, re.MULTILINE) assert re.search(f"Min: {all_locked} - Max: {all_locked}", result.output, re.MULTILINE)
def test_staker_increases_stake(staker, token_economics): stake_index = 0 origin_stake = staker.stakes[stake_index] additional_amount = NU.from_nunits(token_economics.minimum_allowed_locked // 100) with pytest.raises(ValueError): staker.increase_stake(stake=origin_stake) # Can't use amount and entire balance flag together with pytest.raises(ValueError): staker.increase_stake(stake=origin_stake, amount=additional_amount, entire_balance=True) staker.increase_stake(stake=origin_stake, amount=additional_amount) stake = staker.stakes[stake_index] assert stake.first_locked_period == origin_stake.first_locked_period assert stake.final_locked_period == origin_stake.final_locked_period assert stake.value == origin_stake.value + additional_amount # Provided stake must be part of current stakes with pytest.raises(ValueError): staker.increase_stake(stake=origin_stake, amount=additional_amount) stake.index = len(staker.stakes) with pytest.raises(ValueError): staker.increase_stake(stake=stake, amount=additional_amount) stake.index = stake_index # Try to increase again using entire balance origin_stake = stake balance = staker.token_balance staker.increase_stake(stake=stake, entire_balance=True) stake = staker.stakes[stake_index] assert stake.first_locked_period == origin_stake.first_locked_period assert stake.final_locked_period == origin_stake.final_locked_period assert stake.value == origin_stake.value + balance
def paint_bidder_status(emitter, bidder): claim = NU.from_nunits(bidder.available_claim) if claim > bidder.economics.maximum_allowed_locked: claim = f"{claim} (Above the allowed max. The bid will be partially refunded)" message = f""" WorkLock Participant {bidder.checksum_address} ===================================================== Total Bid ............ {prettify_eth_amount(bidder.get_deposited_eth)} Tokens Allocated ..... {claim} Tokens Claimed? ...... {"Yes" if bidder._has_claimed else "No"}""" compensation = bidder.available_compensation if compensation: message += f""" Unspent Bid Amount ... {prettify_eth_amount(compensation)}""" message += f"""\n Completed Work ....... {bidder.completed_work} Available Refund ..... {prettify_eth_amount(bidder.available_refund)} Refunded Work ........ {bidder.refunded_work} Remaining Work ....... {bidder.remaining_work} """ emitter.echo(message) return
def locked_tokens(self, periods: int = 0) -> NU: """Returns the amount of tokens this staker has locked for a given duration in periods.""" self.stakes.refresh() raw_value = self.staking_agent.get_locked_tokens( staker_address=self.checksum_address, periods=periods) value = NU.from_nunits(raw_value) return value
def test_nucypher_status_stakers(click_runner, testerchain, test_registry, agency, stakers): # Get all stakers info stakers_command = ('stakers', '--registry-filepath', MOCK_REGISTRY_FILEPATH, '--provider', TEST_PROVIDER_URI, '--poa') result = click_runner.invoke(status, stakers_command, catch_exceptions=False) assert result.exit_code == 0 staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry) # TODO: Use regex matching instead of this assert re.search(f"^Current period: {staking_agent.get_current_period()}", result.output, re.MULTILINE) for staker in stakers: assert re.search(f"^{staker.checksum_address}", result.output, re.MULTILINE) # Get info of only one staker some_dude = random.choice(stakers) staking_address = some_dude.checksum_address stakers_command = ('stakers', '--staking-address', staking_address, '--registry-filepath', MOCK_REGISTRY_FILEPATH, '--provider', TEST_PROVIDER_URI, '--poa') result = click_runner.invoke(status, stakers_command, catch_exceptions=False) assert result.exit_code == 0 owned_tokens = NU.from_nunits(staking_agent.owned_tokens(staking_address)) locked_tokens = NU.from_nunits( staking_agent.get_locked_tokens(staking_address)) assert re.search(f"^Current period: {staking_agent.get_current_period()}", result.output, re.MULTILINE) assert re.search(r"Worker:\s+" + some_dude.worker_address, result.output, re.MULTILINE) assert re.search(r"Owned:\s+" + str(round(owned_tokens, 2)), result.output, re.MULTILINE) assert re.search(r"Staked: " + str(round(locked_tokens, 2)), result.output, re.MULTILINE)
def _measure_top_stakers(self) -> dict: _, stakers = self.staking_agent.get_all_active_stakers(periods=1) data = dict() for staker, stake in stakers: staker_address = to_checksum_address(staker) data[staker_address] = float(NU.from_nunits(stake).to_tokens()) data = dict(sorted(data.items(), key=lambda s: s[1], reverse=True)) return data
def test_divide_interactive(click_runner, mocker, surrogate_staker, surrogate_stakes, 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 lock_periods = 10 min_allowed_locked = token_economics.minimum_allowed_locked target_value = min_allowed_locked mock_staking_agent.get_worker_from_staker.return_value = surrogate_staker.checksum_address command = ('divide', '--provider', MOCK_PROVIDER_URI, '--network', TEMPORARY_DOMAIN) user_input = '\n'.join( (str(selected_index), str(sub_stake_index), str(NU.from_nunits(target_value).to_tokens()), str(lock_periods), YES, INSECURE_DEVELOPMENT_PASSWORD)) result = click_runner.invoke(stake, command, input=user_input, catch_exceptions=False) assert result.exit_code == 0 assert PROMPT_STAKE_DIVIDE_VALUE.format( minimum=NU.from_nunits(min_allowed_locked), maximum=NU.from_nunits(min_allowed_locked + 1)) in result.output assert PROMPT_STAKE_EXTEND_VALUE in result.output assert CONFIRM_BROADCAST_STAKE_DIVIDE in result.output assert SUCCESSFUL_STAKE_DIVIDE 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.divide_stake.assert_called_once_with( staker_address=surrogate_staker.checksum_address, stake_index=sub_stake_index, target_value=target_value, periods=lock_periods) 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)
def _measure_future_locked_tokens(self, periods: int = 365): period_range = range(1, periods + 1) token_counter = dict() for day in period_range: tokens, stakers = self.staking_agent.get_all_active_stakers( periods=day) token_counter[day] = (float(NU.from_nunits(tokens).to_tokens()), len(stakers)) return dict(token_counter)
def _snapshot_future_locked_tokens(): # TODO: Consider adopting this method here, or moving it to the crawler with database storage period_range = range(1, 365 + 1) token_counter = dict() for day in period_range: tokens, stakers = staking_agent.get_all_active_stakers(periods=day) token_counter[day] = (NU.from_nunits(tokens).to_tokens(), len(stakers)) return token_counter
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 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 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
def collect_reward(click_config, # Stake Options poa, light, registry_filepath, config_file, provider_uri, staking_address, hw_wallet, beneficiary_address, allocation_filepath, # Other staking_reward, policy_reward, withdraw_address, force): """ Withdraw staking reward. """ ### Setup ### emitter = _setup_emitter(click_config) STAKEHOLDER, blockchain = _create_stakeholder(config_file, provider_uri, poa, light, registry_filepath, staking_address, beneficiary_address=beneficiary_address, allocation_filepath=allocation_filepath) ############# client_account, staking_address = handle_client_account_for_staking(emitter=emitter, stakeholder=STAKEHOLDER, staking_address=staking_address, individual_allocation=STAKEHOLDER.individual_allocation, force=force) password = None if not hw_wallet and not blockchain.client.is_local: password = get_client_password(checksum_address=client_account) if not staking_reward and not policy_reward: raise click.BadArgumentUsage(f"Either --staking-reward or --policy-reward must be True to collect rewards.") 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=f'Collecting {reward_amount} from staking rewards...') staking_receipt = STAKEHOLDER.collect_staking_reward() paint_receipt_summary(receipt=staking_receipt, chain_name=STAKEHOLDER.wallet.blockchain.client.chain_name, emitter=emitter) if policy_reward: reward_amount = Web3.fromWei(STAKEHOLDER.calculate_policy_reward(), 'ether') emitter.echo(message=f'Collecting {reward_amount} ETH from policy rewards...') policy_receipt = STAKEHOLDER.collect_policy_reward(collector_address=withdraw_address) paint_receipt_summary(receipt=policy_receipt, chain_name=STAKEHOLDER.wallet.blockchain.client.chain_name, emitter=emitter)
def test_status(click_runner, testerchain, agency_local_registry, token_economics): command = ('status', '--registry-filepath', agency_local_registry.filepath, '--provider', TEST_PROVIDER_URI, '--network', TEMPORARY_DOMAIN) result = click_runner.invoke(worklock, command, catch_exceptions=False) assert result.exit_code == 0 assert str(NU.from_nunits(token_economics.worklock_supply)) in result.output assert str(Web3.fromWei(token_economics.worklock_min_allowed_bid, 'ether')) in result.output
def test_beneficiary_withdraws_tokens(testerchain, agent, agency, allocation_value, mock_transacting_power_activation, token_economics): token_agent, staking_agent, policy_agent = agency deployer_address, beneficiary_address, *everybody_else = testerchain.client.accounts contract_address = agent.contract_address assert token_agent.get_balance( address=contract_address ) == agent.unvested_tokens == agent.initial_locked_amount # Trying to withdraw the tokens now fails, obviously initial_amount = token_agent.get_balance(address=contract_address) with pytest.raises((TransactionFailed, ValueError)): agent.withdraw_tokens(value=initial_amount) # Let's deposit some of them (30% of initial amount) staked_amount = 3 * initial_amount // 10 mock_transacting_power_activation(account=agent.beneficiary, password=INSECURE_DEVELOPMENT_PASSWORD) _receipt = agent.deposit_as_staker( amount=staked_amount, lock_periods=token_economics.minimum_locked_periods) # Trying to withdraw the remaining fails too: # The locked amount is equal to the initial deposit (100% of the tokens). # Since 30% are staked, the locked amount is reduced by that 30% when withdrawing, # which results in an effective lock of 70%. However, the contract also has 70%, which means that, effectively, # the beneficiary can only withdraw 0 tokens. assert agent.available_balance == 0 with pytest.raises((TransactionFailed, ValueError)): agent.withdraw_tokens(value=initial_amount - staked_amount) agent.withdraw_tokens(value=0) # Now let's assume the contract has more tokens (e.g., coming from staking rewards). # The beneficiary should be able to collect this excess. mocked_rewards = NU.from_nunits(1000) token_airdrop(token_agent=token_agent, amount=mocked_rewards, origin=deployer_address, addresses=[contract_address]) assert agent.available_balance == mocked_rewards agent.withdraw_tokens(value=int(mocked_rewards)) # Once the lock passes, the beneficiary can withdraw what's left testerchain.time_travel(seconds=TEST_LOCK_DURATION_IN_SECONDS) receipt = agent.withdraw_tokens(value=initial_amount - staked_amount) assert receipt['status'] == 1, "Transaction Rejected" assert token_agent.get_balance(address=contract_address) == 0 assert token_agent.get_balance( address=beneficiary_address ) == initial_amount - staked_amount + mocked_rewards