Add account selection to collect-reward. Complete tests for stake via contract.

pull/1402/head
David Núñez 2019-10-04 22:03:20 +02:00
parent 8e2bb3e217
commit d968cdd848
6 changed files with 352 additions and 18 deletions

View File

@ -827,8 +827,8 @@ class Staker(NucypherTokenActor):
"""Withdraw tokens rewarded for staking."""
if self.is_contract:
reward_amount = self.staking_agent.calculate_staking_reward(staker_address=self.checksum_address)
self.log.debug(f"Withdrawing staking reward, {reward_amount}, to {self.checksum_address}")
receipt = self.preallocation_escrow_agent.withdraw_as_staker(amount=reward_amount)
self.log.debug(f"Withdrawing staking reward ({NU.from_nunits(reward_amount)}) to {self.checksum_address}")
receipt = self.preallocation_escrow_agent.withdraw_as_staker(value=reward_amount)
else:
receipt = self.staking_agent.collect_staking_reward(staker_address=self.checksum_address)
return receipt
@ -838,7 +838,7 @@ class Staker(NucypherTokenActor):
def withdraw(self, amount: NU) -> str:
"""Withdraw tokens (assuming they're unlocked)"""
if self.is_contract:
receipt = self.preallocation_escrow_agent.withdraw_as_staker(amount=int(amount))
receipt = self.preallocation_escrow_agent.withdraw_as_staker(value=int(amount))
else:
receipt = self.staking_agent.withdraw(staker_address=self.checksum_address,
amount=int(amount))
@ -861,7 +861,6 @@ class Worker(NucypherTokenActor):
work_tracker: WorkTracker = None,
worker_address: str = None,
start_working_now: bool = True,
confirm_now: bool = True,
check_active_worker: bool = True,
*args, **kwargs):

View File

@ -374,7 +374,8 @@ class StakingEscrowAgent(EthereumContractAgent):
def collect_staking_reward(self, staker_address: str):
"""Withdraw tokens rewarded for staking."""
reward_amount = self.calculate_staking_reward(staker_address=staker_address)
self.log.debug(f"Withdrawing staking reward, {reward_amount}, to {staker_address}")
from nucypher.blockchain.eth.token import NU
self.log.debug(f"Withdrawing staking reward ({NU.from_nunits(reward_amount)}) to {staker_address}")
return self.withdraw(staker_address=staker_address, amount=reward_amount)
@validate_checksum_address

View File

@ -796,6 +796,7 @@ class Ursula(Teacher, Character, Worker):
checksum_address: str = None, # Staker address
worker_address: str = None,
work_tracker: WorkTracker = None,
start_working_now: bool = True,
client_password: str = None,
# Character
@ -856,7 +857,8 @@ class Ursula(Teacher, Character, Worker):
registry=self.registry,
checksum_address=checksum_address,
worker_address=worker_address,
work_tracker=work_tracker)
work_tracker=work_tracker,
start_working_now=start_working_now)
#
# ProxyRESTServer and TLSHostingPower #

View File

@ -191,6 +191,7 @@ def stake(click_config,
return # Exit
elif action == 'accounts':
# TODO: Order accounts like shown by blockchain.client.accounts
for address, balances in STAKEHOLDER.wallet.balances.items():
emitter.echo(f"{address} | {Web3.fromWei(balances['ETH'], 'ether')} ETH | {NU.from_nunits(balances['NU'])}")
return # Exit
@ -420,16 +421,22 @@ def stake(click_config,
elif action == 'collect-reward':
"""Withdraw staking reward to the specified wallet address"""
# TODO: Missing account selection
# Authenticate
client_account, staking_address = handle_client_account_for_staking(emitter=emitter,
stakeholder=STAKEHOLDER,
staking_address=staking_address,
is_preallocation_staker=is_preallocation_staker,
beneficiary_address=beneficiary_address,
force=force)
password = None
if not hw_wallet and not blockchain.client.is_local:
password = get_client_password(checksum_address=staking_address)
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=staking_address, password=password)
STAKEHOLDER.assimilate(checksum_address=client_account, password=password)
if staking_reward:
# Note: Sending staking / inflation rewards to another account is not allowed.
staking_receipt = STAKEHOLDER.collect_staking_reward()

View File

@ -15,19 +15,33 @@ You should have received a copy of the GNU Affero General Public License
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
import datetime
import json
import os
import random
import maya
import pytest
from twisted.logger import Logger
from web3 import Web3
from nucypher.blockchain.eth.actors import Staker
from nucypher.blockchain.eth.agents import StakingEscrowAgent, ContractAgency, PreallocationEscrowAgent, NucypherTokenAgent
from nucypher.blockchain.eth.token import NU, Stake
from nucypher.blockchain.eth.token import NU, Stake, StakeList
from nucypher.characters.lawful import Enrico, Ursula
from nucypher.cli.main import nucypher_cli
from nucypher.config.characters import UrsulaConfiguration
from nucypher.crypto.powers import TransactingPower
from nucypher.utilities.sandbox.constants import (
TEST_PROVIDER_URI,
INSECURE_DEVELOPMENT_PASSWORD,
MOCK_IP_ADDRESS,
MOCK_URSULA_STARTING_PORT,
TEMPORARY_DOMAIN,
MOCK_KNOWN_URSULAS_CACHE,
select_test_port,
)
from nucypher.utilities.sandbox.middleware import MockRestMiddleware
#
# This test module is intended to mirror tests/cli/ursula/test_stakeholder_and_ursula.py,
@ -169,6 +183,68 @@ def test_stake_set_worker(click_runner,
assert staker.worker_address == manual_worker
def test_stake_detach_worker(click_runner,
testerchain,
token_economics,
beneficiary,
preallocation_escrow_agent,
mock_allocation_registry,
manual_worker,
test_registry,
stakeholder_configuration_file_location):
staker_address = preallocation_escrow_agent.principal_contract.address
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry)
assert manual_worker == staking_agent.get_worker_from_staker(staker_address=staker_address)
testerchain.time_travel(periods=token_economics.minimum_worker_periods)
init_args = ('stake', 'detach-worker',
'--config-file', stakeholder_configuration_file_location,
'--escrow',
'--beneficiary-address', beneficiary,
'--allocation-filepath', mock_allocation_registry.filepath,
'--force')
result = click_runner.invoke(nucypher_cli,
init_args,
input=INSECURE_DEVELOPMENT_PASSWORD,
catch_exceptions=False)
assert result.exit_code == 0
staker = Staker(is_me=True,
checksum_address=beneficiary,
allocation_registry=mock_allocation_registry,
registry=test_registry)
assert not staker.worker_address
# Ok ok, let's set the worker again.
init_args = ('stake', 'set-worker',
'--config-file', stakeholder_configuration_file_location,
'--escrow',
'--beneficiary-address', beneficiary,
'--allocation-filepath', mock_allocation_registry.filepath,
'--worker-address', manual_worker,
'--force')
user_input = f'{INSECURE_DEVELOPMENT_PASSWORD}'
result = click_runner.invoke(nucypher_cli,
init_args,
input=user_input,
catch_exceptions=False)
assert result.exit_code == 0
staker = Staker(is_me=True,
checksum_address=beneficiary,
allocation_registry=mock_allocation_registry,
registry=test_registry)
assert staker.worker_address == manual_worker
def test_stake_restake(click_runner,
beneficiary,
preallocation_escrow_agent,
@ -253,3 +329,248 @@ def test_stake_restake(click_runner,
allocation_registry=mock_allocation_registry)
assert not staker.is_restaking
assert "Successfully disabled" in result.output
def test_ursula_init(click_runner,
custom_filepath,
mock_registry_filepath,
preallocation_escrow_agent,
manual_worker,
testerchain):
init_args = ('ursula', 'init',
'--poa',
'--network', TEMPORARY_DOMAIN,
'--staker-address', preallocation_escrow_agent.principal_contract.address,
'--worker-address', manual_worker,
'--config-root', custom_filepath,
'--provider', TEST_PROVIDER_URI,
'--registry-filepath', mock_registry_filepath,
'--rest-host', MOCK_IP_ADDRESS,
'--rest-port', MOCK_URSULA_STARTING_PORT)
user_input = '{password}\n{password}'.format(password=INSECURE_DEVELOPMENT_PASSWORD)
result = click_runner.invoke(nucypher_cli,
init_args,
input=user_input,
catch_exceptions=False)
assert result.exit_code == 0
# Files and Directories
assert os.path.isdir(custom_filepath), 'Configuration file does not exist'
assert os.path.isdir(os.path.join(custom_filepath, 'keyring')), 'Keyring does not exist'
assert os.path.isdir(os.path.join(custom_filepath, 'known_nodes')), 'known_nodes directory does not exist'
custom_config_filepath = os.path.join(custom_filepath, UrsulaConfiguration.generate_filename())
assert os.path.isfile(custom_config_filepath), 'Configuration file does not exist'
with open(custom_config_filepath, 'r') as config_file:
raw_config_data = config_file.read()
config_data = json.loads(raw_config_data)
assert config_data['provider_uri'] == TEST_PROVIDER_URI
assert config_data['worker_address'] == manual_worker
assert config_data['checksum_address'] == preallocation_escrow_agent.principal_contract.address
assert TEMPORARY_DOMAIN in config_data['domains']
def test_ursula_run(click_runner,
manual_worker,
custom_filepath,
testerchain):
custom_config_filepath = os.path.join(custom_filepath, UrsulaConfiguration.generate_filename())
# Now start running your Ursula!
init_args = ('ursula', 'run',
'--dry-run',
'--config-file', custom_config_filepath)
user_input = f'{INSECURE_DEVELOPMENT_PASSWORD}\n' * 2
result = click_runner.invoke(nucypher_cli,
init_args,
input=user_input,
catch_exceptions=False)
assert result.exit_code == 0
def test_collect_rewards_integration(click_runner,
testerchain,
test_registry,
stakeholder_configuration_file_location,
blockchain_alice,
blockchain_bob,
random_policy_label,
beneficiary,
preallocation_escrow_agent,
mock_allocation_registry,
manual_worker,
token_economics,
stake_value,
policy_value,
policy_rate):
half_stake_time = token_economics.minimum_locked_periods // 2 # Test setup
logger = Logger("Test-CLI") # Enter the Teacher's Logger, and
current_period = 0 # State the initial period for incrementing
staker_address = preallocation_escrow_agent.principal_contract.address
worker_address = manual_worker
# The staker is staking.
stakes = StakeList(registry=test_registry, checksum_address=staker_address)
stakes.refresh()
assert stakes
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry)
assert worker_address == staking_agent.get_worker_from_staker(staker_address=staker_address)
ursula_port = select_test_port()
ursula = Ursula(is_me=True,
checksum_address=staker_address,
worker_address=worker_address,
registry=test_registry,
rest_host='127.0.0.1',
rest_port=ursula_port,
start_working_now=False,
network_middleware=MockRestMiddleware())
MOCK_KNOWN_URSULAS_CACHE[ursula_port] = ursula
assert ursula.worker_address == worker_address
assert ursula.checksum_address == staker_address
# Mock TransactingPower consumption (Worker-Ursula)
testerchain.transacting_power = TransactingPower(account=worker_address, password=INSECURE_DEVELOPMENT_PASSWORD)
testerchain.transacting_power.activate()
# Confirm for half the first stake duration
for _ in range(half_stake_time):
logger.debug(f">>>>>>>>>>> TEST PERIOD {current_period} <<<<<<<<<<<<<<<<")
ursula.confirm_activity()
testerchain.time_travel(periods=1)
current_period += 1
# Alice creates a policy and grants Bob access
blockchain_alice.selection_buffer = 1
M, N = 1, 1
expiration = maya.now() + datetime.timedelta(days=3)
blockchain_policy = blockchain_alice.grant(bob=blockchain_bob,
label=random_policy_label,
m=M, n=N,
value=policy_value,
expiration=expiration,
handpicked_ursulas={ursula})
# Ensure that the handpicked Ursula was selected for the policy
arrangement = list(blockchain_policy._accepted_arrangements)[0]
assert arrangement.ursula == ursula
# Bob learns about the new staker and joins the policy
blockchain_bob.start_learning_loop()
blockchain_bob.remember_node(node=ursula)
blockchain_bob.join_policy(random_policy_label, bytes(blockchain_alice.stamp))
# Enrico Encrypts (of course)
enrico = Enrico(policy_encrypting_key=blockchain_policy.public_key,
network_middleware=MockRestMiddleware())
verifying_key = blockchain_alice.stamp.as_umbral_pubkey()
for index in range(half_stake_time - 5):
logger.debug(f">>>>>>>>>>> TEST PERIOD {current_period} <<<<<<<<<<<<<<<<")
ursula.confirm_activity()
# Encrypt
random_data = os.urandom(random.randrange(20, 100))
ciphertext, signature = enrico.encrypt_message(message=random_data)
# Decrypt
cleartexts = blockchain_bob.retrieve(message_kit=ciphertext,
data_source=enrico,
alice_verifying_key=verifying_key,
label=random_policy_label)
assert random_data == cleartexts[0]
# Ursula Staying online and the clock advancing
testerchain.time_travel(periods=1)
current_period += 1
# Finish the passage of time
for _ in range(5 - 1): # minus 1 because the first period was already confirmed in test_ursula_run
logger.debug(f">>>>>>>>>>> TEST PERIOD {current_period} <<<<<<<<<<<<<<<<")
ursula.confirm_activity()
current_period += 1
testerchain.time_travel(periods=1)
#
# WHERES THE MONEY URSULA?? - Collecting Rewards
#
# The address the client wants Ursula to send policy rewards to
burner_wallet = testerchain.w3.eth.account.create(INSECURE_DEVELOPMENT_PASSWORD)
# The policy rewards wallet is initially empty, because it is freshly created
assert testerchain.client.get_balance(burner_wallet.address) == 0
# Rewards will be unlocked after the
# final confirmed period has passed (+1).
logger.debug(f">>>>>>>>>>> TEST PERIOD {current_period} <<<<<<<<<<<<<<<<")
testerchain.time_travel(periods=1)
current_period += 1
logger.debug(f">>>>>>>>>>> TEST PERIOD {current_period} <<<<<<<<<<<<<<<<")
# Since we are mocking the blockchain connection, manually consume the transacting power of the Beneficiary.
testerchain.transacting_power = TransactingPower(account=beneficiary,
password=INSECURE_DEVELOPMENT_PASSWORD)
testerchain.transacting_power.activate()
# Collect Policy Reward
collection_args = ('stake', 'collect-reward',
'--mock-networking',
'--config-file', stakeholder_configuration_file_location,
'--policy-reward',
'--no-staking-reward',
'--withdraw-address', burner_wallet.address,
'--escrow',
'--beneficiary-address', beneficiary,
'--allocation-filepath', mock_allocation_registry.filepath,
'--force')
result = click_runner.invoke(nucypher_cli,
collection_args,
input=INSECURE_DEVELOPMENT_PASSWORD,
catch_exceptions=False)
assert result.exit_code == 0
# Policy Reward
collected_policy_reward = testerchain.client.get_balance(burner_wallet.address)
expected_collection = policy_rate * 30
assert collected_policy_reward == expected_collection
#
# Collect Staking Reward
#
token_agent = ContractAgency.get_agent(agent_class=NucypherTokenAgent, registry=test_registry)
balance_before_collecting = token_agent.get_balance(address=staker_address)
collection_args = ('stake', 'collect-reward',
'--mock-networking',
'--config-file', stakeholder_configuration_file_location,
'--no-policy-reward',
'--staking-reward',
'--escrow',
'--beneficiary-address', beneficiary,
'--allocation-filepath', mock_allocation_registry.filepath,
'--force')
result = click_runner.invoke(nucypher_cli,
collection_args,
input=INSECURE_DEVELOPMENT_PASSWORD,
catch_exceptions=False)
assert result.exit_code == 0
# The beneficiary has withdrawn her staking rewards, which are now in the staking contract
assert token_agent.get_balance(address=staker_address) >= balance_before_collecting

View File

@ -400,9 +400,8 @@ def test_collect_rewards_integration(click_runner,
verifying_key = blockchain_alice.stamp.as_umbral_pubkey()
for index in range(half_stake_time - 5):
ursula.confirm_activity()
logger.debug(f">>>>>>>>>>> TEST PERIOD {current_period} <<<<<<<<<<<<<<<<")
ursula.confirm_activity()
# Encrypt
random_data = os.urandom(random.randrange(20, 100))
@ -421,10 +420,10 @@ def test_collect_rewards_integration(click_runner,
# Finish the passage of time for the first Stake
for _ in range(5): # plus the extended periods from stake division
ursula.confirm_activity()
current_period += 1
logger.debug(f">>>>>>>>>>> TEST PERIOD {current_period} <<<<<<<<<<<<<<<<")
ursula.confirm_activity()
testerchain.time_travel(periods=1)
current_period += 1
#
# WHERES THE MONEY URSULA?? - Collecting Rewards
@ -438,6 +437,7 @@ def test_collect_rewards_integration(click_runner,
# Rewards will be unlocked after the
# final confirmed period has passed (+1).
logger.debug(f">>>>>>>>>>> TEST PERIOD {current_period} <<<<<<<<<<<<<<<<")
testerchain.time_travel(periods=1)
current_period += 1
logger.debug(f">>>>>>>>>>> TEST PERIOD {current_period} <<<<<<<<<<<<<<<<")
@ -479,14 +479,18 @@ def test_collect_rewards_integration(click_runner,
logger.debug(f">>>>>>>>>>> TEST PERIOD {current_period} <<<<<<<<<<<<<<<<")
testerchain.time_travel(periods=1)
# Collect Inflation Reward
#
# Collect Staking Reward
#
balance_before_collecting = staker.token_agent.get_balance(address=staker_address)
collection_args = ('stake', 'collect-reward',
'--mock-networking',
'--config-file', stakeholder_configuration_file_location,
'--no-policy-reward',
'--staking-reward',
'--staking-address', staker_address,
'--withdraw-address', burner_wallet.address,
'--force')
result = click_runner.invoke(nucypher_cli,
@ -495,8 +499,8 @@ def test_collect_rewards_integration(click_runner,
catch_exceptions=False)
assert result.exit_code == 0
# The burner wallet has the reward ethers
assert staker.token_agent.get_balance(address=staker_address)
# The staker has withdrawn her staking rewards
assert staker.token_agent.get_balance(address=staker_address) >= balance_before_collecting
def test_stake_detach_worker(click_runner,