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 divide_stake(self, stake_index: int, target_value: NU, additional_periods: int = None, expiration: maya.MayaDT = None) -> tuple: # Calculate duration in periods if additional_periods and expiration: raise ValueError("Pass the number of lock periods or an expiration MayaDT; not both.") # Select stake to divide from local cache try: current_stake = self.stakes[stake_index] except KeyError: if len(self.stakes): message = f"Cannot divide stake - No stake exists with index {stake_index}." else: message = "Cannot divide stake - There are no active stakes." raise Stake.StakingError(message) # Calculate stake duration in periods if expiration: additional_periods = datetime_to_period(datetime=expiration) - current_stake.end_period if additional_periods <= 0: raise Stake.StakingError(f"New expiration {expiration} must be at least 1 period from the " f"current stake's end period ({current_stake.end_period}).") # Do it already! modified_stake, new_stake = current_stake.divide(target_value=target_value, additional_periods=additional_periods) # Update staking cache self.__read_stakes() return modified_stake, new_stake
def initialize_stake(self, amount: NU, lock_periods: int = None, expiration: maya.MayaDT = None, entire_balance: bool = False) -> Stake: """Create a new stake.""" # # Duration # if lock_periods and expiration: raise ValueError( "Pass the number of lock periods or an expiration MayaDT; not both." ) if expiration: lock_periods = calculate_period_duration(future_time=expiration) # # Value # if entire_balance and amount: raise ValueError("Specify an amount or entire balance, not both") if entire_balance: amount = self.token_balance if not self.token_balance >= amount: raise self.MinerError( f"Insufficient token balance ({self.token_agent}) for new stake initialization of {amount}" ) # Ensure the new stake will not exceed the staking limit if (self.current_stake + amount) > self.economics.maximum_allowed_locked: raise Stake.StakingError( f"Cannot divide stake - Maximum stake value exceeded with a target value of {amount}." ) # # Stake # # Write to blockchain new_stake = Stake.initialize_stake(miner=self, amount=amount, lock_periods=lock_periods) self.__read_stakes() # Update local staking cache return new_stake
def test_select_divisible_stake(test_emitter, token_economics, mock_staking_agent, test_registry, mock_testerchain, mock_stdin, capsys, divisible_stakes, stakeholder_with_divisible_stakes): expected_stake = Stake.from_stake_info( stake_info=divisible_stakes[0], staking_agent=mock_staking_agent, # stakinator index=0, checksum_address=stakeholder_with_divisible_stakes.checksum_address, economics=token_economics) # SUCCESS: Display all divisible-only stakes and make a selection mock_stdin.line(str(SELECTION)) selected_stake = select_stake( emitter=test_emitter, divisible=True, stakeholder=stakeholder_with_divisible_stakes) assert isinstance(selected_stake, Stake) assert selected_stake == expected_stake # Examine the output captured = capsys.readouterr() assert NO_STAKES_FOUND not in captured.out assert ONLY_DISPLAYING_DIVISIBLE_STAKES_NOTE in captured.out assert_stake_table_painted(output=captured.out) assert mock_stdin.empty()
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_stake_equality(token_economics, get_random_checksum_address, mocker): address = get_random_checksum_address() a_different_address = get_random_checksum_address() mock_agent = mocker.Mock(contract_address=a_different_address) stake = Stake(checksum_address=address, first_locked_period=1, final_locked_period=2, value=NU(100, 'NU'), index=0, staking_agent=mock_agent, economics=token_economics, validate_now=False) assert stake == stake duck_stake = mocker.Mock(index=0, value=NU(100, 'NU'), first_locked_period=1, final_locked_period=2, staker_address=address, staking_agent=mock_agent) assert stake == duck_stake a_different_stake = Stake(checksum_address=address, first_locked_period=0, final_locked_period=2, value=NU(100, 'NU'), index=1, staking_agent=mock_agent, economics=token_economics, validate_now=False) assert stake != a_different_stake undercover_agent = mocker.Mock(contract_address=address) another_different_stake = Stake(checksum_address=a_different_address, first_locked_period=1, final_locked_period=2, value=NU(100, 'NU'), index=0, staking_agent=undercover_agent, economics=token_economics, validate_now=False) assert stake != another_different_stake
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)
def __read_stakes(self) -> None: stakes_reader = self.miner_agent.get_all_stakes(miner_address=self.checksum_public_address) stakes = dict() for index, stake_info in enumerate(stakes_reader): stake = Stake.from_stake_info(owner_address=self.checksum_public_address, stake_info=stake_info, index=index) stakes[index] = stake self.__stakes = stakes
def initialize_stake(self, amount: NU, lock_periods: int = None, expiration: maya.MayaDT = None, entire_balance: bool = False) -> Stake: """Create a new stake.""" # Duration if lock_periods and expiration: raise ValueError( "Pass the number of lock periods or an expiration MayaDT; not both." ) if expiration: lock_periods = calculate_period_duration( future_time=expiration, seconds_per_period=self.economics.seconds_per_period) # Value if entire_balance and amount: raise ValueError("Specify an amount or entire balance, not both") if entire_balance: amount = self.token_balance if not self.token_balance >= amount: raise self.InsufficientTokens( f"Insufficient token balance ({self.token_agent}) " f"for new stake initialization of {amount}") # Ensure the new stake will not exceed the staking limit if (self.current_stake + amount) > self.economics.maximum_allowed_locked: raise Stake.StakingError( f"Cannot initialize stake - " f"Maximum stake value exceeded for {self.checksum_address} " f"with a target value of {amount}.") # Write to blockchain new_stake = Stake.initialize_stake(staker=self, amount=amount, lock_periods=lock_periods) # Update staking cache element self.stakes.refresh() return new_stake
def test_stake_init(click_runner, stakeholder_configuration_file_location, stake_value, mock_registry_filepath, token_economics, testerchain, agency, manual_staker): # Simulate "Reconnection" cached_blockchain = BlockchainInterface.reconnect() registry = cached_blockchain.registry assert registry.filepath == mock_registry_filepath def from_dict(*args, **kwargs): return testerchain BlockchainInterface.from_dict = from_dict # Staker address has not stakes staking_agent = Agency.get_agent(StakingEscrowAgent) stakes = list(staking_agent.get_all_stakes(staker_address=manual_staker)) assert not stakes stake_args = ('stake', 'init', '--config-file', stakeholder_configuration_file_location, '--registry-filepath', mock_registry_filepath, '--staking-address', manual_staker, '--value', stake_value.to_tokens(), '--duration', token_economics.minimum_locked_periods, '--force') # TODO: This test it writing to the default system directory and ignoring updates to the passes filepath user_input = f'0\n' + f'{INSECURE_DEVELOPMENT_PASSWORD}\n' + f'Y\n' result = click_runner.invoke(nucypher_cli, stake_args, input=user_input, catch_exceptions=False) assert result.exit_code == 0 # Test integration with BaseConfiguration with open(stakeholder_configuration_file_location, 'r') as config_file: _config_data = json.loads(config_file.read()) # Verify the stake is on-chain # Test integration with Agency stakes = list(staking_agent.get_all_stakes(staker_address=manual_staker)) assert len(stakes) == 1 # Test integration with NU start_period, end_period, value = stakes[0] assert NU(int(value), 'NuNit') == stake_value assert (end_period - start_period) == token_economics.minimum_locked_periods - 1 # Test integration with Stake stake = Stake.from_stake_info(index=0, checksum_address=manual_staker, stake_info=stakes[0]) assert stake.value == stake_value assert stake.duration == token_economics.minimum_locked_periods
def test_stake(): class FakeUrsula: burner_wallet = Web3().eth.account.create(INSECURE_DEVELOPMENT_PASSWORD) checksum_public_address = burner_wallet.address miner_agent = None ursula = FakeUrsula() stake = Stake(owner_address=ursula.checksum_public_address, start_period=1, end_period=100, value=NU(100, 'NU'), index=0) assert len(stake.id) == 16 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 test_stake(testerchain, three_agents): class FakeUrsula: token_agent, miner_agent, _policy_agent = three_agents burner_wallet = Web3().eth.account.create( INSECURE_DEVELOPMENT_PASSWORD) checksum_public_address = burner_wallet.address miner_agent = miner_agent token_agent = token_agent blockchain = testerchain economics = TokenEconomics() ursula = FakeUrsula() stake = Stake(miner=ursula, start_period=1, end_period=100, value=NU(100, 'NU'), index=0) 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 test_stake_init(click_runner, stakeholder_configuration_file_location, stake_value, token_economics, testerchain, agency_local_registry, manual_staker): # Staker address has not stakes staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=agency_local_registry) stakes = list(staking_agent.get_all_stakes(staker_address=manual_staker)) assert not stakes stake_args = ('stake', 'create', '--config-file', stakeholder_configuration_file_location, '--staking-address', manual_staker, '--value', stake_value.to_tokens(), '--lock-periods', token_economics.minimum_locked_periods, '--force') # TODO: This test is writing to the default system directory and ignoring updates to the passed filepath user_input = f'0\n' + f'{INSECURE_DEVELOPMENT_PASSWORD}\n' + YES_ENTER result = click_runner.invoke(nucypher_cli, stake_args, input=user_input, catch_exceptions=False) assert result.exit_code == 0 # Test integration with BaseConfiguration with open(stakeholder_configuration_file_location, 'r') as config_file: _config_data = json.loads(config_file.read()) # Verify the stake is on-chain # Test integration with Agency stakes = list(staking_agent.get_all_stakes(staker_address=manual_staker)) assert len(stakes) == 1 # Test integration with NU start_period, end_period, value = stakes[0] assert NU(int(value), 'NuNit') == stake_value assert (end_period - start_period) == token_economics.minimum_locked_periods - 1 # Test integration with Stake stake = Stake.from_stake_info(index=0, checksum_address=manual_staker, stake_info=stakes[0], staking_agent=staking_agent, economics=token_economics) assert stake.value == stake_value assert stake.duration == token_economics.minimum_locked_periods
def test_miner_divides_stake(miner): stake_value = NU(MIN_ALLOWED_LOCKED * 5, 'NuNit') new_stake_value = NU(MIN_ALLOWED_LOCKED * 2, 'NuNit') stake_index = 0 miner.initialize_stake(amount=stake_value, lock_periods=int(MIN_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(MIN_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, owner_address=miner.checksum_public_address, 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 __read_stakes(self) -> None: """Rewrite the local staking cache by reading on-chain stakes""" existing_records = len(self.__stakes) # Candidate replacement cache values onchain_stakes, terminal_period = list(), 0 # Read from blockchain stakes_reader = self.miner_agent.get_all_stakes( miner_address=self.checksum_address) for onchain_index, stake_info in enumerate(stakes_reader): if not stake_info: # This stake index is empty on-chain onchain_stake = EMPTY_STAKING_SLOT else: # On-chain stake detected onchain_stake = Stake.from_stake_info(miner=self, stake_info=stake_info, index=onchain_index) # Search for the terminal period if onchain_stake.end_period > terminal_period: terminal_period = onchain_stake.end_period # Store the replacement stake onchain_stakes.append(onchain_stake) # Commit the new stake and terminal values to the cache if not onchain_stakes: self.__stakes = NO_STAKES.bool_value(False) else: self.__terminal_period = terminal_period self.__stakes = onchain_stakes # Record most recent cache update self.__updated = maya.now() new_records = existing_records - len(self.__stakes) self.log.debug( f"Updated local staking cache ({new_records} new records).")
def test_select_using_filter_function( test_emitter, stakeholder, mock_staking_agent, mock_testerchain, mock_stdin, # used to assert user hasn't been prompted capsys, current_period, token_economics, sub_stakes_functions): # Setup mock_stakes = make_sub_stakes(current_period, token_economics, sub_stakes_functions) mock_staking_agent.get_all_stakes.return_value = mock_stakes staker = mock_testerchain.unassigned_accounts[0] stakeholder.set_staker(staker) selection = len(mock_stakes) - 1 expected_stake = Stake.from_stake_info( stake_info=mock_stakes[selection], staking_agent=mock_staking_agent, # stakinator index=selection, checksum_address=stakeholder.checksum_address, economics=token_economics) # SUCCESS: Display all editable-only stakes with specified final period mock_stdin.line(str(selection)) selected_stake = select_stake(emitter=test_emitter, staker=stakeholder, stakes_status=Stake.Status.LOCKED, filter_function=lambda stake: stake. final_locked_period == current_period) assert isinstance(selected_stake, Stake) assert selected_stake == expected_stake # Examine the output captured = capsys.readouterr() assert NO_STAKES_FOUND not in captured.out assert_stake_table_painted(output=captured.out) assert mock_stdin.empty()
def test_select_divisible_stake( test_emitter, stakeholder, mock_staking_agent, mock_testerchain, mock_stdin, # used to assert user hasn't been prompted capsys, current_period, token_economics, sub_stakes_functions): # Setup mock_stakes = make_sub_stakes(current_period, token_economics, sub_stakes_functions) mock_staking_agent.get_all_stakes.return_value = mock_stakes staker = mock_testerchain.unassigned_accounts[0] stakeholder.set_staker(staker) selection = len(mock_stakes) - 1 expected_stake = Stake.from_stake_info( stake_info=mock_stakes[selection], staking_agent=mock_staking_agent, # stakinator index=selection, checksum_address=stakeholder.checksum_address, economics=token_economics) # SUCCESS: Display all divisible-only stakes and make a selection mock_stdin.line(str(selection)) selected_stake = select_stake(emitter=test_emitter, staker=stakeholder, stakes_status=Stake.Status.DIVISIBLE) assert isinstance(selected_stake, Stake) assert selected_stake == expected_stake # Examine the output captured = capsys.readouterr() assert NO_STAKES_FOUND not in captured.out assert ONLY_DISPLAYING_DIVISIBLE_STAKES_NOTE in captured.out assert_stake_table_painted(output=captured.out) assert mock_stdin.empty()
def divide_stake( self, address: str, index: int, value: NU, duration: int, password: str = None, ): staker = self.get_active_staker(address=address) if not staker.is_staking: raise Stake.StakingError( f"{staker.checksum_address} has no published stakes.") self.attach_transacting_power(checksum_address=staker.checksum_address, password=password) result = staker.divide_stake(stake_index=index, additional_periods=duration, target_value=value) # Save results to disk self.to_configuration_file(override=True) return result
def test_select_editable_stake(test_emitter, stakeholder, mock_staking_agent, mock_testerchain, mock_stdin, # used to assert user hasn't been prompted capsys, current_period, token_economics, sub_stakes_functions): mock_stakes = make_sub_stakes(current_period, token_economics, sub_stakes_functions) mock_staking_agent.get_all_stakes.return_value = mock_stakes staker = mock_testerchain.unassigned_accounts[0] stakeholder.assimilate(staker, password=INSECURE_DEVELOPMENT_PASSWORD) selection = len(mock_stakes) - 1 expected_stake = Stake.from_stake_info(stake_info=mock_stakes[selection], staking_agent=mock_staking_agent, # stakinator index=selection, checksum_address=stakeholder.checksum_address, economics=token_economics) # User's selection mock_stdin.line(str(selection)) selected_stake = select_stake(emitter=test_emitter, staker=stakeholder.staker) # Check stake accuracy assert isinstance(selected_stake, Stake) assert selected_stake == expected_stake # Examine the output captured = capsys.readouterr() assert NO_STAKES_FOUND not in captured.out assert ONLY_DISPLAYING_DIVISIBLE_STAKES_NOTE not in captured.out assert_stake_table_painted(output=captured.out) assert mock_stdin.empty()
def test_stake_validation(mock_testerchain, token_economics, mock_staking_agent): address = mock_testerchain.etherbase_account # Validate stake initialization with pytest.raises(Stake.StakingError): Stake.initialize_stake(staking_agent=mock_staking_agent, checksum_address=address, economics=token_economics, amount=token_economics.minimum_allowed_locked - 1, lock_periods=token_economics.minimum_locked_periods) with pytest.raises(Stake.StakingError): Stake.initialize_stake(staking_agent=mock_staking_agent, checksum_address=address, economics=token_economics, amount=token_economics.minimum_allowed_locked, lock_periods=token_economics.minimum_locked_periods - 1) with pytest.raises(Stake.StakingError): Stake.initialize_stake(staking_agent=mock_staking_agent, checksum_address=address, economics=token_economics, amount=token_economics.maximum_allowed_locked + 1, lock_periods=token_economics.minimum_locked_periods) mock_staking_agent.get_locked_tokens.return_value = 0 Stake.initialize_stake(staking_agent=mock_staking_agent, checksum_address=address, economics=token_economics, amount=token_economics.maximum_allowed_locked, lock_periods=token_economics.minimum_locked_periods) # Validate divide method current_period = 10 mock_staking_agent.get_current_period.return_value = 10 def make_sub_stake(value, first_locked_period, final_locked_period, index=0): return Stake(checksum_address=address, first_locked_period=first_locked_period, final_locked_period=final_locked_period, value=value, index=index, staking_agent=mock_staking_agent, economics=token_economics) nu = NU.from_nunits(2 * token_economics.minimum_allowed_locked - 1) stake = make_sub_stake(first_locked_period=current_period - 2, final_locked_period=current_period + 1, value=nu) with pytest.raises(Stake.StakingError): validate_divide(stake=stake, target_value=token_economics.minimum_allowed_locked, additional_periods=1) stake = make_sub_stake(first_locked_period=current_period - 2, final_locked_period=current_period, value=nu + 1) with pytest.raises(Stake.StakingError): validate_divide(stake=stake, target_value=token_economics.minimum_allowed_locked, additional_periods=1) stake = make_sub_stake(first_locked_period=current_period - 2, final_locked_period=current_period + 1, value=nu + 1) with pytest.raises(Stake.StakingError): validate_divide(stake=stake, target_value=token_economics.minimum_allowed_locked - 1, additional_periods=1) validate_divide(stake=stake, target_value=token_economics.minimum_allowed_locked, additional_periods=1) # Validate prolong method stake = make_sub_stake(first_locked_period=current_period - 2, final_locked_period=current_period, value=nu) with pytest.raises(Stake.StakingError): validate_prolong(stake=stake, additional_periods=1) stake = make_sub_stake(first_locked_period=current_period - 2, final_locked_period=current_period + 2, value=nu) with pytest.raises(Stake.StakingError): validate_prolong(stake=stake, additional_periods=1) with pytest.raises(Stake.StakingError): validate_prolong(stake=stake, additional_periods=token_economics.minimum_locked_periods - 3) validate_prolong(stake=stake, additional_periods=token_economics.minimum_locked_periods - 2) # Validate increase method stake = make_sub_stake(first_locked_period=current_period - 2, final_locked_period=current_period, value=nu) with pytest.raises(Stake.StakingError): validate_increase(stake=stake, amount=nu) stake = make_sub_stake(first_locked_period=current_period - 2, final_locked_period=current_period + 1, value=nu) stake.staking_agent.get_locked_tokens.return_value = nu with pytest.raises(Stake.StakingError): validate_increase(stake=stake, amount=NU.from_nunits(token_economics.maximum_allowed_locked - int(nu) + 1)) validate_increase(stake=stake, amount=NU.from_nunits(token_economics.maximum_allowed_locked - int(nu))) # Validate merge method stake_1 = make_sub_stake(first_locked_period=current_period - 1, final_locked_period=current_period, value=nu, index=0) stake_2 = make_sub_stake(first_locked_period=current_period - 1, final_locked_period=current_period, value=nu, index=1) with pytest.raises(Stake.StakingError): validate_merge(stake_1=stake_1, stake_2=stake_2) stake_1 = make_sub_stake(first_locked_period=current_period - 1, final_locked_period=current_period + 1, value=nu, index=2) stake_2 = make_sub_stake(first_locked_period=current_period - 1, final_locked_period=current_period + 2, value=nu, index=3) with pytest.raises(Stake.StakingError): validate_merge(stake_1=stake_1, stake_2=stake_2) with pytest.raises(Stake.StakingError): validate_merge(stake_1=stake_1, stake_2=stake_1) stake_2 = make_sub_stake(first_locked_period=current_period - 3, final_locked_period=current_period + 1, value=nu, index=4) validate_merge(stake_1=stake_1, stake_2=stake_2)
def test_stake_sync(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 # Prepare sub-stake nu = NU.from_nunits(2 * token_economics.minimum_allowed_locked - 1) stake = Stake(checksum_address=address, first_locked_period=current_period - 2, final_locked_period=current_period + 1, value=nu, index=0, staking_agent=mock_staking_agent, economics=token_economics, validate_now=False) assert stake.status() == Stake.Status.EDITABLE # Update locked value and sync sub_stake_info = stake.to_stake_info() nunits = 2 * token_economics.minimum_allowed_locked sub_stake_info = sub_stake_info._replace(locked_value=nunits) mock_staking_agent.get_substake_info.return_value = sub_stake_info stake.sync() assert stake.status() == Stake.Status.DIVISIBLE assert stake.value == NU.from_nunits(nunits) # Update current period and sync mock_staking_agent.get_current_period.return_value = current_period + 1 sub_stake_info = sub_stake_info._replace(locked_value=nunits) mock_staking_agent.get_substake_info.return_value = sub_stake_info stake.sync() assert stake.status() == Stake.Status.LOCKED assert stake.final_locked_period == current_period + 1 # Update final period and sync sub_stake_info = sub_stake_info._replace(last_period=current_period) mock_staking_agent.get_substake_info.return_value = sub_stake_info stake.sync() assert stake.status() == Stake.Status.UNLOCKED assert stake.final_locked_period == current_period # Update first period and sync sub_stake_info = sub_stake_info._replace(first_period=current_period) mock_staking_agent.get_substake_info.return_value = sub_stake_info with pytest.raises(Stake.StakingError): stake.sync()
def test_stake_via_contract(click_runner, custom_filepath, agency_local_registry, mock_allocation_registry, testerchain, stakeholder_configuration_file_location, stake_value, token_economics, agency, beneficiary, preallocation_escrow_agent ): # # Inital setup and checks: beneficiary and pre-allocation contract # # First, let's be sure the beneficiary is in the allocation registry... assert mock_allocation_registry.is_beneficiary_enrolled(beneficiary) # ... and that the pre-allocation contract has enough tokens preallocation_contract_address = preallocation_escrow_agent.principal_contract.address token_agent = ContractAgency.get_agent(NucypherTokenAgent, registry=agency_local_registry) assert token_agent.get_balance(preallocation_contract_address) >= token_economics.minimum_allowed_locked # Let's not forget to create a stakeholder init_args = ('stake', 'init-stakeholder', '--poa', '--config-root', custom_filepath, '--provider', TEST_PROVIDER_URI, '--network', TEMPORARY_DOMAIN, '--registry-filepath', agency_local_registry.filepath) result = click_runner.invoke(nucypher_cli, init_args, catch_exceptions=False) assert result.exit_code == 0 with open(stakeholder_configuration_file_location) as f: print(f.read()) # # The good stuff: Using `nucypher stake create --escrow` # # Staking contract has no stakes yet staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=agency_local_registry) stakes = list(staking_agent.get_all_stakes(staker_address=preallocation_contract_address)) assert not stakes stake_args = ('stake', 'create', '--config-file', stakeholder_configuration_file_location, '--allocation-filepath', MOCK_INDIVIDUAL_ALLOCATION_FILEPATH, '--value', str(stake_value.to_tokens()), '--lock-periods', token_economics.minimum_locked_periods, '--force') # TODO: This test is writing to the default system directory and ignoring updates to the passed filepath user_input = '0\n' + 'Y\n' + f'{INSECURE_DEVELOPMENT_PASSWORD}\n' + 'Y\n' result = click_runner.invoke(nucypher_cli, stake_args, input=user_input, catch_exceptions=False) assert result.exit_code == 0 # Test integration with BaseConfiguration with open(stakeholder_configuration_file_location, 'r') as config_file: _config_data = json.loads(config_file.read()) # Verify the stake is on-chain # Test integration with Agency stakes = list(staking_agent.get_all_stakes(staker_address=preallocation_contract_address)) assert len(stakes) == 1 # Test integration with NU start_period, end_period, value = stakes[0] assert NU(int(value), 'NuNit') == stake_value assert (end_period - start_period) == token_economics.minimum_locked_periods - 1 # Test integration with Stake stake = Stake.from_stake_info(index=0, checksum_address=preallocation_contract_address, stake_info=stakes[0], staking_agent=staking_agent, economics=token_economics) assert stake.value == stake_value assert stake.duration == token_economics.minimum_locked_periods
def test_staker_divides_stake(staker, 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 staker.initialize_stake(amount=stake_value, lock_periods=int(token_economics.minimum_locked_periods)) stake = staker.stakes[stake_index + 1] # Can't use additional periods and expiration together with pytest.raises(ValueError): staker.divide_stake(target_value=new_stake_value, stake=stake, additional_periods=2, expiration=maya.now()) staker.divide_stake(target_value=new_stake_value, stake=stake, additional_periods=2) current_period = staker.staking_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(staker.stakes), 'A new stake was not added to this stakers stakes' assert expected_old_stake == staker.stakes[stake_index + 1].to_stake_info(), 'Old stake values are invalid' assert expected_new_stake == staker.stakes[stake_index + 2].to_stake_info(), 'New stake values are invalid' # Provided stake must be part of current stakes new_stake_value = NU.from_nunits(token_economics.minimum_allowed_locked) with pytest.raises(ValueError): staker.divide_stake(target_value=new_stake_value, stake=stake, additional_periods=2) stake = staker.stakes[stake_index + 1] stake.index = len(staker.stakes) with pytest.raises(ValueError): staker.divide_stake(target_value=new_stake_value, stake=stake, additional_periods=2) yet_another_stake_value = NU(token_economics.minimum_allowed_locked, 'NuNit') stake = staker.stakes[stake_index + 2] # New expiration date must extend stake duration origin_stake = stake new_expiration = datetime_at_period(period=origin_stake.final_locked_period, seconds_per_period=token_economics.seconds_per_period, start_of_period=True) with pytest.raises(ValueError): staker.divide_stake(target_value=yet_another_stake_value, stake=stake, expiration=new_expiration) new_expiration = datetime_at_period(period=origin_stake.final_locked_period + 2, seconds_per_period=token_economics.seconds_per_period, start_of_period=True) staker.divide_stake(target_value=yet_another_stake_value, stake=stake, expiration=new_expiration) expected_new_stake = (current_period + 1, current_period + 32, new_stake_value) expected_yet_another_stake = Stake(first_locked_period=current_period + 1, final_locked_period=current_period + 34, value=yet_another_stake_value, checksum_address=staker.checksum_address, index=3, staking_agent=staker.staking_agent, economics=token_economics) assert 4 == len(staker.stakes), 'A new stake was not added after two stake divisions' assert expected_old_stake == staker.stakes[ stake_index + 1].to_stake_info(), 'Old stake values are invalid after two stake divisions' assert expected_new_stake == staker.stakes[ stake_index + 2].to_stake_info(), 'New stake values are invalid after two stake divisions' assert expected_yet_another_stake.value == staker.stakes[stake_index + 3].value, 'Third stake values are invalid'