def paint_stakes(emitter: StdoutEmitter, staker: 'Staker', stakes: List[Stake] = None, paint_unlocked: bool = False) -> None: stakes = stakes or staker.sorted_stakes() fees = staker.policy_agent.get_fee_amount(staker.checksum_address) pretty_fees = prettify_eth_amount(fees) last_committed = staker.staking_agent.get_last_committed_period( staker.checksum_address) missing = staker.missing_commitments min_fee_rate = prettify_eth_amount(staker.min_fee_rate) if missing == -1: missing_info = "Never Made a Commitment (New Stake)" else: missing_info = f'Missing {missing} commitments{"s" if missing > 1 else ""}' if missing else f'Committed #{last_committed}' staker_data = [ missing_info, f'{"Yes" if staker.is_restaking else "No"} ({"Locked" if staker.restaking_lock_enabled else "Unlocked"})', "Yes" if bool(staker.is_winding_down) else "No", "Yes" if bool(staker.is_taking_snapshots) else "No", pretty_fees, min_fee_rate ] line_width = 54 if staker.registry.source: # TODO: #1580 - Registry source might be Falsy in tests. network_snippet = f"\nNetwork {staker.registry.source.network.capitalize()} " snippet_with_line = network_snippet + '═' * (line_width - len(network_snippet) + 1) emitter.echo(snippet_with_line, bold=True) emitter.echo(f"Staker {staker.checksum_address} ════", bold=True, color='red' if missing else 'green') worker = staker.worker_address if staker.worker_address != NULL_ADDRESS else 'not bonded' emitter.echo( f"Worker {worker} ════", color='red' if staker.worker_address == NULL_ADDRESS else None) emitter.echo( tabulate.tabulate(zip(STAKER_TABLE_COLUMNS, staker_data), floatfmt="fancy_grid")) rows = list() for index, stake in enumerate(stakes): if stake.status().is_child( Stake.Status.UNLOCKED) and not paint_unlocked: # This stake is unlocked. continue rows.append(list(stake.describe().values())) if not rows: emitter.echo(f"There are no locked stakes\n") emitter.echo( tabulate.tabulate(rows, headers=STAKE_TABLE_COLUMNS, tablefmt="fancy_grid")) # newline
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 __log_transaction(self, transaction_dict: dict, contract_function: ContractFunction): """ Format and log a transaction dict and return the transaction name string. This method *must not* mutate the original transaction dict. """ # Do not mutate the original transaction dict tx = dict(transaction_dict).copy() # Format if tx.get('to'): tx['to'] = to_checksum_address(contract_function.address) try: tx['selector'] = contract_function.selector except AttributeError: pass tx['from'] = to_checksum_address(tx['from']) tx.update({ f: prettify_eth_amount(v) for f, v in tx.items() if f in ('gasPrice', 'value') }) payload_pprint = ', '.join("{}: {}".format(k, v) for k, v in tx.items()) # Log transaction_name = get_transaction_name( contract_function=contract_function) self.log.debug(f"[TX-{transaction_name}] | {payload_pprint}")
def test_valid_bid(click_runner, mocker, mock_worklock_agent, token_economics, test_registry_source_manager, surrogate_bidder, mock_testerchain): now = mock_testerchain.get_blocktime() sometime_later = now + 100 mocker.patch.object(BlockchainInterface, 'get_blocktime', return_value=sometime_later) minimum = token_economics.worklock_min_allowed_bid bid_value = random.randint(minimum, minimum * 100) bid_value_in_eth = Web3.fromWei(bid_value, 'ether') # Spy on the corresponding CLI function we are testing mock_ensure = mocker.spy(Bidder, 'ensure_bidding_is_open') mock_place_bid = mocker.spy(Bidder, 'place_bid') # Patch Bidder.get_deposited_eth so it returns what we expect, in the correct sequence deposited_eth_sequence = ( 0, # When deciding if it's a new bid or increasing the existing one 0, # When placing the bid, inside Bidder.place_bid bid_value, # When printing the CLI result, after the bid is placed .. bid_value, # .. we use it twice ) mocker.patch.object(Bidder, 'get_deposited_eth', new_callable=PropertyMock, side_effect=deposited_eth_sequence) command = ('escrow', '--participant-address', surrogate_bidder.checksum_address, '--value', bid_value_in_eth, '--provider', MOCK_PROVIDER_URI, '--signer', MOCK_PROVIDER_URI, '--network', TEMPORARY_DOMAIN, '--force') user_input = INSECURE_DEVELOPMENT_PASSWORD result = click_runner.invoke(worklock, command, catch_exceptions=False, input=user_input) assert result.exit_code == 0 # OK - Let's see what happened # Bidder mock_ensure.assert_called_once() # checked that the bidding window was open via actors layer mock_place_bid.assert_called_once() mock_place_bid.assert_called_once_with(surrogate_bidder, value=bid_value) assert_successful_transaction_echo(bidder_address=surrogate_bidder.checksum_address, cli_output=result.output) # Transactions mock_worklock_agent.assert_only_transactions(allowed=[mock_worklock_agent.bid]) mock_worklock_agent.bid.assert_called_with(checksum_address=surrogate_bidder.checksum_address, value=bid_value) # Calls expected_calls = (mock_worklock_agent.eth_to_tokens, ) for expected_call in expected_calls: expected_call.assert_called() # CLI output assert prettify_eth_amount(bid_value) in result.output
def paint_bidding_notice(emitter, bidder): message = WORKLOCK_AGREEMENT.format( refund_rate=prettify_eth_amount( bidder.worklock_agent.get_bonus_refund_rate()), end_date=maya.MayaDT( bidder.economics.bidding_end_date).local_datetime(), bidder_address=bidder.checksum_address, duration=bidder.economics.worklock_commitment_duration) emitter.echo(message) return
def build_transaction(self, contract_function: ContractFunction, sender_address: str, payload: dict = None, transaction_gas_limit: int = None, ) -> dict: # # Build # if not payload: payload = {} nonce = self.client.w3.eth.getTransactionCount(sender_address, 'pending') payload.update({'chainId': int(self.client.chain_id), 'nonce': nonce, 'from': sender_address, 'gasPrice': self.client.gas_price}) if transaction_gas_limit: payload['gas'] = int(transaction_gas_limit) # Get transaction type deployment = isinstance(contract_function, ContractConstructor) try: transaction_name = contract_function.fn_name.upper() except AttributeError: transaction_name = 'DEPLOY' if deployment else 'UNKNOWN' payload_pprint = dict(payload) payload_pprint['from'] = to_checksum_address(payload['from']) payload_pprint.update({f: prettify_eth_amount(v) for f, v in payload.items() if f in ('gasPrice', 'value')}) payload_pprint = ', '.join("{}: {}".format(k, v) for k, v in payload_pprint.items()) self.log.debug(f"[TX-{transaction_name}] | {payload_pprint}") # Build transaction payload try: unsigned_transaction = contract_function.buildTransaction(payload) except (ValidationError, ValueError) as e: # TODO: #1504 - Handle validation failures for gas limits, invalid fields, etc. # Note: Geth raises ValueError in the same condition that pyevm raises ValidationError here. # Treat this condition as "Transaction Failed". error = str(e).replace("{", "").replace("}", "") # See #724 self.log.critical(f"Validation error: {error}") raise else: if deployment: self.log.info(f"Deploying contract: {len(unsigned_transaction['data'])} bytes") return unsigned_transaction
def claim(general_config: GroupGeneralConfig, worklock_options: WorkLockOptions, force: bool, hw_wallet: bool): """Claim tokens for your escrow, and start staking them""" emitter, registry, blockchain = worklock_options.setup(general_config=general_config) worklock_agent = ContractAgency.get_agent(WorkLockAgent, registry=registry) # type: WorkLockAgent if not worklock_agent.is_claiming_available(): emitter.echo(CLAIMING_NOT_AVAILABLE, 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) unspent_bid = bidder.available_compensation if unspent_bid: emitter.echo(WORKLOCK_ADDITIONAL_COMPENSATION_AVAILABLE.format(amount=prettify_eth_amount(unspent_bid))) if not force: message = CONFIRM_REQUEST_WORKLOCK_COMPENSATION.format(bidder_address=bidder_address) click.confirm(message, abort=True) emitter.echo(REQUESTING_WORKLOCK_COMPENSATION) receipt = bidder.withdraw_compensation() paint_receipt_summary(receipt=receipt, emitter=emitter, chain_name=bidder.staking_agent.blockchain.client.chain_name) has_claimed = bidder.has_claimed if bool(has_claimed): emitter.echo(CLAIM_ALREADY_PLACED.format(bidder_address=bidder.checksum_address), color='red') raise click.Abort() tokens = NU.from_nunits(bidder.available_claim) emitter.echo(AVAILABLE_CLAIM_NOTICE.format(tokens=tokens), color='green', bold=True) if not force: lock_duration = bidder.worklock_agent.worklock_parameters()[-2] emitter.echo(WORKLOCK_CLAIM_ADVISORY.format(lock_duration=lock_duration), color='blue') click.confirm(CONFIRM_WORKLOCK_CLAIM.format(bidder_address=bidder_address), abort=True) emitter.echo(SUBMITTING_WORKLOCK_CLAIM) receipt = bidder.claim() paint_receipt_summary(receipt=receipt, emitter=emitter, chain_name=bidder.staking_agent.blockchain.client.chain_name) paint_worklock_claim(emitter=emitter, bidder_address=bidder_address, network=worklock_options.network, provider_uri=worklock_options.provider_uri)
def paint_stakes(emitter, stakeholder, paint_inactive: bool = False, staker_address: str = None): headers = ('Idx', 'Value', 'Remaining', 'Enactment', 'Termination') staker_headers = ('Status', 'Restaking', 'Winding Down', 'Unclaimed Fees', 'Min reward rate') stakers = stakeholder.get_stakers() if not stakers: emitter.echo("No staking accounts found.") total_stakers = 0 for staker in stakers: if not staker.stakes: # This staker has no active stakes. # TODO: Something with non-staking accounts? continue # Filter Target if staker_address and staker.checksum_address != staker_address: continue stakes = sorted(staker.stakes, key=lambda s: s.address_index_ordering_key) active_stakes = filter(lambda s: s.is_active, stakes) if not active_stakes: emitter.echo(f"There are no active stakes\n") fees = staker.policy_agent.get_reward_amount(staker.checksum_address) pretty_fees = prettify_eth_amount(fees) last_confirmed = staker.staking_agent.get_last_active_period( staker.checksum_address) missing = staker.missing_confirmations min_reward_rate = prettify_eth_amount(staker.min_reward_rate) if missing == -1: missing_info = "Never Confirmed (New Stake)" else: missing_info = f'Missing {missing} confirmation{"s" if missing > 1 else ""}' if missing else f'Confirmed #{last_confirmed}' staker_data = [ missing_info, f'{"Yes" if staker.is_restaking else "No"} ({"Locked" if staker.restaking_lock_enabled else "Unlocked"})', "Yes" if bool(staker.is_winding_down) else "No", pretty_fees, min_reward_rate ] line_width = 54 if staker.registry.source: # TODO: #1580 - Registry source might be Falsy in tests. network_snippet = f"\nNetwork {staker.registry.source.network.capitalize()} " snippet_with_line = network_snippet + '═' * ( line_width - len(network_snippet) + 1) emitter.echo(snippet_with_line, bold=True) emitter.echo(f"Staker {staker.checksum_address} ════", bold=True, color='red' if missing else 'green') emitter.echo(f"Worker {staker.worker_address} ════") emitter.echo( tabulate.tabulate(zip(staker_headers, staker_data), floatfmt="fancy_grid")) rows = list() for index, stake in enumerate(stakes): if not stake.is_active and not paint_inactive: # This stake is inactive. continue rows.append(list(stake.describe().values())) total_stakers += 1 emitter.echo( tabulate.tabulate(rows, headers=headers, tablefmt="fancy_grid")) # newline if not total_stakers: emitter.echo("No Stakes found", color='red')
def paint_stakes(emitter: StdoutEmitter, staker: 'Staker', stakes: List[Stake] = None, paint_unlocked: bool = False, stakeholder=None) -> None: stakes = stakes or staker.sorted_stakes() fees = staker.policy_agent.get_fee_amount(staker.checksum_address) pretty_fees = prettify_eth_amount(fees) last_committed = staker.staking_agent.get_last_committed_period( staker.checksum_address) missing = staker.missing_commitments min_fee_rate = prettify_eth_amount(staker.min_fee_rate) if missing == -1: missing_info = "Never Made a Commitment (New Stake)" else: missing_info = f'Missing {missing} commitments{"s" if missing > 1 else ""}' if missing else f'Committed #{last_committed}' staker_data = [ missing_info, "Yes" if staker.is_restaking else "No", "Yes" if bool(staker.is_winding_down) else "No", "Yes" if bool(staker.is_taking_snapshots) else "No", pretty_fees, min_fee_rate ] line_width = 54 if staker.registry.source: # TODO: #1580 - Registry source might be Falsy in tests. network_snippet = f"\nNetwork {staker.registry.source.network.capitalize()} " snippet_with_line = network_snippet + '═' * (line_width - len(network_snippet) + 1) emitter.echo(snippet_with_line, bold=True) emitter.echo(f"Staker {staker.checksum_address} ════", bold=True, color='red' if missing else 'green') worker = staker.worker_address if staker.worker_address != NULL_ADDRESS else 'not bonded' emitter.echo( f"Worker {worker} ════", color='red' if staker.worker_address == NULL_ADDRESS else None) if stakeholder and stakeholder.worker_data: worker_data = stakeholder.worker_data.get(staker.checksum_address) if worker_data: emitter.echo(f"\t public address: {worker_data['publicaddress']}") if worker_data.get('nucypher version'): emitter.echo( f"\t NuCypher Version: {worker_data['nucypher version']}") if worker_data.get('blockchain_provider'): emitter.echo( f"\t Blockchain Provider: {worker_data['blockchain_provider']}" ) emitter.echo( tabulate.tabulate(zip(STAKER_TABLE_COLUMNS, staker_data), floatfmt="fancy_grid")) rows, inactive_substakes = list(), list() for index, stake in enumerate(stakes): is_inactive = False if stake.status().is_child(Stake.Status.INACTIVE): inactive_substakes.append(index) is_inactive = True if stake.status().is_child( Stake.Status.UNLOCKED) and not paint_unlocked: # This stake is unlocked. continue stake_description = stake.describe() if is_inactive: # stake is inactive - update display values since they don't make much sense to display stake_description['remaining'] = 'N/A' stake_description['last_period'] = 'N/A' stake_description['boost'] = 'N/A' rows.append(list(stake_description.values())) if not rows: emitter.echo(f"There are no locked stakes\n") emitter.echo( tabulate.tabulate(rows, headers=STAKE_TABLE_COLUMNS, tablefmt="fancy_grid")) # newline if not paint_unlocked and inactive_substakes: emitter.echo( f"Note that some sub-stakes are inactive: {inactive_substakes}\n" f"Run `nucypher stake list --all` to show all sub-stakes.\n" f"Run `nucypher stake remove-inactive --all` to remove inactive sub-stakes; removal of inactive " f"sub-stakes will reduce commitment gas costs.", color='yellow')
def paint_stakers(emitter, stakers: List[str], registry: BaseContractRegistry) -> None: staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=registry) 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_address in stakers: staker = Staker(is_me=False, checksum_address=staker_address, registry=registry) nickname = Nickname.from_seed(staker_address) emitter.echo( f"{staker_address} {'Nickname:':10} {nickname} {nickname.icon}") tab = " " * len(staker_address) owned_tokens = staker.owned_tokens() last_committed_period = staker.last_committed_period worker = staker.worker_address is_restaking = staker.is_restaking is_winding_down = staker.is_winding_down is_taking_snapshots = staker.is_taking_snapshots missing_commitments = current_period - last_committed_period owned_in_nu = round(owned_tokens, 2) current_locked_tokens = round(staker.locked_tokens(periods=0), 2) next_locked_tokens = round(staker.locked_tokens(periods=1), 2) emitter.echo(f"{tab} {'Owned:':10} {owned_in_nu}") emitter.echo( f"{tab} Staked in current period: {current_locked_tokens}") emitter.echo(f"{tab} Staked in next period: {next_locked_tokens}") if is_restaking: emitter.echo(f"{tab} {'Re-staking:':10} Yes") 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} {'Snapshots:':10} {'Yes' if is_taking_snapshots else 'No'}" ) emitter.echo(f"{tab} {'Activity:':10} ", nl=False) if missing_commitments == -1: emitter.echo(f"Next period committed (#{last_committed_period})", color='green') elif missing_commitments == 0: emitter.echo( f"Current period committed (#{last_committed_period}). " f"Pending commitment to next period.", color='yellow') elif missing_commitments == current_period: emitter.echo(f"Never made a commitment", color='red') else: emitter.echo( f"Missing {missing_commitments} commitments " f"(last time for period #{last_committed_period})", color='red') emitter.echo(f"{tab} {'Worker:':10} ", nl=False) if worker == NULL_ADDRESS: emitter.echo(f"Worker not bonded", color='red') else: emitter.echo(f"{worker}") fees = prettify_eth_amount(staker.calculate_policy_fee()) emitter.echo(f"{tab} Unclaimed fees: {fees}") min_rate = prettify_eth_amount(staker.min_fee_rate) emitter.echo(f"{tab} Min fee rate: {min_rate}")