def test_staker_locking_tokens(testerchain, agency, staker, token_economics): token_agent, staking_agent, policy_agent = agency # Mock Powerup consumption (Ursula-Staker) testerchain.transacting_power = BlockchainPower( blockchain=testerchain, account=staker.checksum_address) assert NU(token_economics.minimum_allowed_locked, 'NuNit') < staker.token_balance, "Insufficient staker balance" staker.initialize_stake( amount=NU(token_economics.minimum_allowed_locked, 'NuNit'), # Lock the minimum amount of tokens lock_periods=token_economics.minimum_locked_periods) # Verify that the escrow is "approved" to receive tokens allowance = token_agent.contract.functions.allowance( staker.checksum_address, staking_agent.contract_address).call() assert 0 == allowance # Staking starts after one period locked_tokens = staker.locked_tokens() assert 0 == locked_tokens locked_tokens = staker.locked_tokens(periods=1) assert token_economics.minimum_allowed_locked == locked_tokens
def test_staker_locking_tokens(testerchain, agency, staker, token_economics, test_registry): token_agent = ContractAgency.get_agent(NucypherTokenAgent, registry=test_registry) staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry) assert NU(token_economics.minimum_allowed_locked, 'NuNit') < staker.token_balance, "Insufficient staker balance" staker.initialize_stake( amount=NU(token_economics.minimum_allowed_locked, 'NuNit'), # Lock the minimum amount of tokens lock_periods=token_economics.minimum_locked_periods) # Verify that the escrow is "approved" to receive tokens allowance = token_agent.contract.functions.allowance( staker.checksum_address, staking_agent.contract_address).call() assert 0 == allowance # Staking starts after one period locked_tokens = staker.locked_tokens() assert 0 == locked_tokens locked_tokens = staker.locked_tokens(periods=1) assert token_economics.minimum_allowed_locked == locked_tokens
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_staker_locking_tokens(testerchain, agency, staker, token_economics, mock_transacting_power_activation): token_agent, staking_agent, policy_agent = agency mock_transacting_power_activation(account=staker.checksum_address, password=INSECURE_DEVELOPMENT_PASSWORD) assert NU(token_economics.minimum_allowed_locked, 'NuNit') < staker.token_balance, "Insufficient staker balance" staker.initialize_stake( amount=NU(token_economics.minimum_allowed_locked, 'NuNit'), # Lock the minimum amount of tokens lock_periods=token_economics.minimum_locked_periods) # Verify that the escrow is "approved" to receive tokens allowance = token_agent.contract.functions.allowance( staker.checksum_address, staking_agent.contract_address).call() assert 0 == allowance # Staking starts after one period locked_tokens = staker.locked_tokens() assert 0 == locked_tokens locked_tokens = staker.locked_tokens(periods=1) assert token_economics.minimum_allowed_locked == locked_tokens
def test_miner_locking_tokens(testerchain, three_agents, miner, token_economics): token_agent, miner_agent, policy_agent = three_agents assert NU(token_economics.minimum_allowed_locked, 'NuNit') < miner.token_balance, "Insufficient miner balance" miner.initialize_stake( amount=NU(token_economics.minimum_allowed_locked, 'NuNit'), # Lock the minimum amount of tokens lock_periods=token_economics.minimum_locked_periods) # Verify that the escrow is "approved" to receive tokens allowance = token_agent.contract.functions.allowance( miner.checksum_address, miner_agent.contract_address).call() assert 0 == allowance # Staking starts after one period locked_tokens = miner_agent.contract.functions.getLockedTokens( miner.checksum_address).call() assert 0 == locked_tokens locked_tokens = miner_agent.contract.functions.getLockedTokens( miner.checksum_address, 1).call() assert token_economics.minimum_allowed_locked == locked_tokens
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 test_miner_divides_stake(miner, token_economics): stake_value = NU(token_economics.minimum_allowed_locked*5, 'NuNit') new_stake_value = NU(token_economics.minimum_allowed_locked*2, 'NuNit') stake_index = 0 miner.initialize_stake(amount=stake_value, lock_periods=int(token_economics.minimum_locked_periods)) miner.divide_stake(target_value=new_stake_value, stake_index=stake_index+1, additional_periods=2) current_period = miner.miner_agent.get_current_period() expected_old_stake = (current_period + 1, current_period + 30, stake_value - new_stake_value) expected_new_stake = (current_period + 1, current_period + 32, new_stake_value) assert 3 == len(miner.stakes), 'A new stake was not added to this miners stakes' assert expected_old_stake == miner.stakes[stake_index + 1].to_stake_info(), 'Old stake values are invalid' assert expected_new_stake == miner.stakes[stake_index + 2].to_stake_info(), 'New stake values are invalid' yet_another_stake_value = NU(token_economics.minimum_allowed_locked, 'NuNit') miner.divide_stake(target_value=yet_another_stake_value, stake_index=stake_index + 2, additional_periods=2) expected_new_stake = (current_period + 1, current_period + 32, new_stake_value - yet_another_stake_value) expected_yet_another_stake = Stake(start_period=current_period + 1, end_period=current_period + 34, value=yet_another_stake_value, miner=miner, index=3) assert 4 == len(miner.stakes), 'A new stake was not added after two stake divisions' assert expected_old_stake == miner.stakes[stake_index + 1].to_stake_info(), 'Old stake values are invalid after two stake divisions' assert expected_new_stake == miner.stakes[stake_index + 2].to_stake_info(), 'New stake values are invalid after two stake divisions' assert expected_yet_another_stake == miner.stakes[stake_index + 3], 'Third stake values are invalid'
def test_miner_locking_tokens(testerchain, three_agents, miner): token_agent, miner_agent, policy_agent = three_agents assert NU(MIN_ALLOWED_LOCKED, 'NuNit') < miner.token_balance, "Insufficient miner balance" expiration = maya.now().add(days=MIN_LOCKED_PERIODS) miner.initialize_stake( amount=NU(MIN_ALLOWED_LOCKED, 'NuNit'), # Lock the minimum amount of tokens expiration=expiration) # Verify that the escrow is "approved" to receive tokens allowance = token_agent.contract.functions.allowance( miner.checksum_public_address, miner_agent.contract_address).call() assert 0 == allowance # Staking starts after one period locked_tokens = miner_agent.contract.functions.getLockedTokens( miner.checksum_public_address).call() assert 0 == locked_tokens locked_tokens = miner_agent.contract.functions.getLockedTokens( miner.checksum_public_address, 1).call() assert MIN_ALLOWED_LOCKED == locked_tokens
def test_ursula_divide_stakes(click_runner, configuration_file_location, token_economics): divide_args = ('ursula', 'stake', '--divide', '--config-file', configuration_file_location, '--poa', '--force', '--index', 0, '--value', NU(token_economics.minimum_allowed_locked, 'NuNit').to_tokens(), '--duration', 10) result = click_runner.invoke( nucypher_cli, divide_args, catch_exceptions=False, env=dict(NUCYPHER_KEYRING_PASSWORD=INSECURE_DEVELOPMENT_PASSWORD)) assert result.exit_code == 0 stake_args = ('ursula', 'stake', '--config-file', configuration_file_location, '--list', '--poa') user_input = f'{INSECURE_DEVELOPMENT_PASSWORD}' result = click_runner.invoke(nucypher_cli, stake_args, input=user_input, catch_exceptions=False) assert result.exit_code == 0 assert str( NU(token_economics.minimum_allowed_locked, 'NuNit').to_tokens()) in result.output
def test_staker_divide_stakes(click_runner, stakeholder_configuration_file_location, token_economics, manual_staker, testerchain, agency_local_registry): divide_args = ('stake', 'divide', '--config-file', stakeholder_configuration_file_location, '--force', '--staking-address', manual_staker, '--index', 0, '--value', NU(token_economics.minimum_allowed_locked, 'NuNit').to_tokens(), '--lock-periods', 10) result = click_runner.invoke( nucypher_cli, divide_args, catch_exceptions=False, env=dict(NUCYPHER_KEYRING_PASSWORD=INSECURE_DEVELOPMENT_PASSWORD)) assert result.exit_code == 0 stake_args = ('stake', 'list', '--config-file', stakeholder_configuration_file_location) user_input = INSECURE_DEVELOPMENT_PASSWORD result = click_runner.invoke(nucypher_cli, stake_args, input=user_input, catch_exceptions=False) assert result.exit_code == 0 assert str( NU(token_economics.minimum_allowed_locked, 'NuNit').to_tokens()) in result.output
def test_stake(testerchain, token_economics, agency): token_agent, staking_agent, _policy_agent = agency class FakeUrsula: token_agent, staking_agent, _policy_agent = agency burner_wallet = Web3().eth.account.create(INSECURE_DEVELOPMENT_PASSWORD) checksum_address = burner_wallet.address staking_agent = staking_agent token_agent = token_agent blockchain = testerchain ursula = FakeUrsula() stake = Stake(checksum_address=ursula.checksum_address, first_locked_period=1, final_locked_period=100, value=NU(100, 'NU'), index=0, staking_agent=staking_agent, economics=token_economics) assert stake.value, 'NU' == NU(100, 'NU') assert isinstance(stake.time_remaining(), int) # seconds slang_remaining = stake.time_remaining(slang=True) # words assert isinstance(slang_remaining, str)
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 current_stake(self) -> NU: """ The total number of staked tokens, either locked or unlocked in the current period. """ if self.stakes: return NU(sum(int(stake.value) for stake in self.stakes), 'NuNit') else: return NU.ZERO()
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 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 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], 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 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 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 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 paint_staking_rewards(stakeholder, blockchain, emitter, past_periods, staking_address, staking_agent): if not past_periods: reward_amount = stakeholder.staker.calculate_staking_reward() emitter.echo(message=TOKEN_REWARD_CURRENT.format( reward_amount=round(reward_amount, TOKEN_DECIMAL_PLACE))) return economics = stakeholder.staker.economics seconds_per_period = economics.seconds_per_period current_period = staking_agent.get_current_period() from_period = current_period - past_periods latest_block = blockchain.client.block_number from_block = estimate_block_number_for_period( period=from_period, seconds_per_period=seconds_per_period, latest_block=latest_block) argument_filters = {'staker': staking_address} event_type = staking_agent.contract.events['Minted'] entries = event_type.getLogs(fromBlock=from_block, toBlock='latest', argument_filters=argument_filters) rows = [] rewards_total = NU(0, 'NU') for event_record in entries: event_block_number = int(event_record['blockNumber']) event_period = event_record['args']['period'] event_reward = NU(event_record['args']['value'], 'NuNit') timestamp = blockchain.client.get_block(event_block_number).timestamp event_date = maya.MayaDT( epoch=timestamp).local_datetime().strftime("%b %d %Y") rows.append([ event_date, event_block_number, int(event_period), round(event_reward, TOKEN_DECIMAL_PLACE), ]) rewards_total += event_reward if not rows: emitter.echo(TOKEN_REWARD_NOT_FOUND) return periods_as_days = economics.days_per_period * past_periods emitter.echo(message=TOKEN_REWARD_PAST_HEADER.format(periods=past_periods, days=periods_as_days)) emitter.echo( tabulate.tabulate(rows, headers=REWARDS_TABLE_COLUMNS, tablefmt="fancy_grid")) emitter.echo(message=TOKEN_REWARD_PAST.format( reward_amount=round(rewards_total, TOKEN_DECIMAL_PLACE)))
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, 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_worker_auto_commitments(mocker, testerchain, test_registry, staker, agency, token_economics, mock_transacting_power_activation, ursula_decentralized_test_config): mock_transacting_power_activation(account=staker.checksum_address, password=INSECURE_DEVELOPMENT_PASSWORD) staker.initialize_stake( amount=NU(token_economics.minimum_allowed_locked, 'NuNit'), lock_periods=int(token_economics.minimum_locked_periods)) # Get an unused address and create a new worker worker_address = testerchain.unassigned_accounts[-1] # Control time clock = Clock() WorkTracker.CLOCK = clock # Bond the Worker and Staker staker.bond_worker(worker_address=worker_address) commit_spy = mocker.spy(Worker, 'commit_to_next_period') # Make the Worker ursula = make_decentralized_ursulas( ursula_config=ursula_decentralized_test_config, stakers_addresses=[staker.checksum_address], workers_addresses=[worker_address], commit_to_next_period=False, registry=test_registry).pop() commit_spy.assert_not_called() initial_period = staker.staking_agent.get_current_period() def start(): # Start running the worker start_pytest_ursula_services(ursula=ursula) ursula.work_tracker.start(act_now=True) def time_travel(_): testerchain.time_travel(periods=1) clock.advance(WorkTracker.REFRESH_RATE + 1) def verify(_): # Verify that periods were committed on-chain automatically last_committed_period = staker.staking_agent.get_last_committed_period( staker_address=staker.checksum_address) current_period = staker.staking_agent.get_current_period() assert (last_committed_period - current_period) == 1 assert commit_spy.call_count == current_period - initial_period + 1 # Run the callbacks d = threads.deferToThread(start) d.addCallback(verify) for i in range(5): d.addCallback(time_travel) d.addCallback(verify) yield d
def test_staker_collects_staking_reward(testerchain, test_registry, staker, blockchain_ursulas, agency, token_economics, ursula_decentralized_test_config): token_agent = ContractAgency.get_agent(NucypherTokenAgent, registry=test_registry) tpower = TransactingPower(account=testerchain.etherbase_account, signer=Web3Signer(testerchain.client)) # Give more tokens to staker token_airdrop(token_agent=token_agent, transacting_power=tpower, addresses=[staker.checksum_address], amount=DEVELOPMENT_TOKEN_AIRDROP_AMOUNT) staker.initialize_stake( amount=NU(token_economics.minimum_allowed_locked, 'NuNit'), # Lock the minimum amount of tokens lock_periods=int(token_economics.minimum_locked_periods) ) # ... for the fewest number of periods # Get an unused address for a new worker worker_address = testerchain.unassigned_accounts[-1] staker.bond_worker(worker_address=worker_address) # Create this worker and bond it with the staker ursula = make_decentralized_ursulas( ursula_config=ursula_decentralized_test_config, stakers_addresses=[staker.checksum_address], workers_addresses=[worker_address], registry=test_registry, commit_now=False).pop() # ...mint few tokens... for _ in range(2): ursula.commit_to_next_period() testerchain.time_travel(periods=1) # Check mintable periods assert staker.mintable_periods() == 1 ursula.commit_to_next_period() # ...wait more... assert staker.mintable_periods() == 0 testerchain.time_travel(periods=2) assert staker.mintable_periods() == 2 # Capture the current token balance of the staker initial_balance = staker.token_balance assert token_agent.get_balance(staker.checksum_address) == initial_balance # Profit! staked = staker.non_withdrawable_stake() owned = staker.owned_tokens() staker.collect_staking_reward() assert staker.owned_tokens() == staked final_balance = staker.token_balance assert final_balance == initial_balance + owned - staked
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 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