class PolicyAuthor(NucypherTokenActor): """Alice base class for blockchain operations, mocking up new policies!""" def __init__(self, checksum_address: str, policy_agent: PolicyAgent = None, economics: TokenEconomics = None, *args, **kwargs) -> None: """ :param policy_agent: A policy agent with the blockchain attached; If not passed, a default policy agent and blockchain connection will be created from default values. """ super().__init__(checksum_address=checksum_address, *args, **kwargs) # From defaults if not policy_agent: self.token_agent = NucypherTokenAgent(blockchain=self.blockchain) self.staking_agent = StakingEscrowAgent(blockchain=self.blockchain) self.policy_agent = PolicyAgent(blockchain=self.blockchain) # Injected else: self.policy_agent = policy_agent self.economics = economics or TokenEconomics() def recruit(self, quantity: int, **options) -> List[str]: """ Uses sampling logic to gather stakers from the blockchain and caches the resulting node ethereum addresses. :param quantity: Number of ursulas to sample from the blockchain. """ staker_addresses = self.staking_agent.sample(quantity=quantity, **options) return staker_addresses def create_policy(self, *args, **kwargs): """ Hence the name, a PolicyAuthor can create a BlockchainPolicy with themself as the author. :return: Returns a newly authored BlockchainPolicy with n proposed arrangements. """ from nucypher.blockchain.eth.policies import BlockchainPolicy blockchain_policy = BlockchainPolicy(alice=self, *args, **kwargs) return blockchain_policy
def test_sampling_distribution(testerchain, token, deploy_contract): # # SETUP # max_allowed_locked_tokens = 5 * 10 ** 8 _staking_coefficient = 2 * 10 ** 7 staking_escrow_contract, _ = deploy_contract( contract_name=STAKING_ESCROW_CONTRACT_NAME, _token=token.address, _hoursPerPeriod=1, _miningCoefficient=4 * _staking_coefficient, _lockedPeriodsCoefficient=4, _rewardedPeriods=4, _minLockedPeriods=2, _minAllowableLockedTokens=100, _maxAllowableLockedTokens=max_allowed_locked_tokens, _minWorkerPeriods=1, _isTestContract=False ) staking_agent = StakingEscrowAgent(registry=None, contract=staking_escrow_contract) policy_manager, _ = deploy_contract( 'PolicyManagerForStakingEscrowMock', token.address, staking_escrow_contract.address ) tx = staking_escrow_contract.functions.setPolicyManager(policy_manager.address).transact() testerchain.wait_for_receipt(tx) # Travel to the start of the next period to prevent problems with unexpected overflow first period testerchain.time_travel(hours=1) creator = testerchain.etherbase_account # Give Escrow tokens for reward and initialize contract tx = token.functions.approve(staking_escrow_contract.address, 10 ** 9).transact({'from': creator}) testerchain.wait_for_receipt(tx) tx = staking_escrow_contract.functions.initialize(10 ** 9).transact({'from': creator}) testerchain.wait_for_receipt(tx) stakers = testerchain.stakers_accounts amount = token.functions.balanceOf(creator).call() // len(stakers) # Airdrop for staker in stakers: tx = token.functions.transfer(staker, amount).transact({'from': creator}) testerchain.wait_for_receipt(tx) all_locked_tokens = len(stakers) * amount for staker in stakers: balance = token.functions.balanceOf(staker).call() tx = token.functions.approve(staking_escrow_contract.address, balance).transact({'from': staker}) testerchain.wait_for_receipt(tx) staking_agent.deposit_tokens(amount=balance, lock_periods=10, sender_address=staker, staker_address=staker) staking_agent.set_worker(staker_address=staker, worker_address=staker) staking_agent.confirm_activity(staker) # Wait next period and check all locked tokens testerchain.time_travel(hours=1) # # Test sampling distribution # ERROR_TOLERANCE = 0.05 # With this tolerance, all sampling ratios should between 5% and 15% (expected is 10%) SAMPLES = 1000 quantity = 3 counter = Counter() sampled, failed = 0, 0 while sampled < SAMPLES: try: addresses = set(staking_agent.sample(quantity=quantity, additional_ursulas=1, duration=1)) addresses.discard(NULL_ADDRESS) except staking_agent.NotEnoughStakers: failed += 1 continue else: sampled += 1 counter.update(addresses) total_times = sum(counter.values()) expected = amount / all_locked_tokens for staker in stakers: times = counter[staker] sampled_ratio = times / total_times abs_error = abs(expected - sampled_ratio) assert abs_error < ERROR_TOLERANCE