def make_decentralized_staker_reservoir(staking_agent: StakingEscrowAgent, duration_periods: int, exclude_addresses: Optional[Iterable[ChecksumAddress]] = None, include_addresses: Optional[Iterable[ChecksumAddress]] = None, pagination_size: int = None): """ Get a sampler object containing the currently registered stakers. """ # needs to not include both exclude and include addresses # so that they aren't included in reservoir, include_address will be re-added to reservoir afterwards include_addresses = include_addresses or () without_set = set(include_addresses) | set(exclude_addresses or ()) try: reservoir = staking_agent.get_stakers_reservoir(duration=duration_periods, without=without_set, pagination_size=pagination_size) except StakingEscrowAgent.NotEnoughStakers: # TODO: do that in `get_stakers_reservoir()`? reservoir = StakersReservoir({}) # add include addresses return MergedReservoir(include_addresses, reservoir)
def test_sampling_distribution(testerchain, token, deploy_contract, token_economics): # # SETUP # policy_manager, _ = deploy_contract('PolicyManagerForStakingEscrowMock', NULL_ADDRESS, token_economics.seconds_per_period) adjudicator, _ = deploy_contract('AdjudicatorForStakingEscrowMock', token_economics.reward_coefficient) staking_escrow_contract, _ = deploy_contract( STAKING_ESCROW_CONTRACT_NAME, token.address, policy_manager.address, adjudicator.address, NULL_ADDRESS, *token_economics.staking_deployment_parameters) staking_agent = StakingEscrowAgent(registry=None, contract=staking_escrow_contract) # 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, creator).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) staker_power = TransactingPower(account=staker, signer=Web3Signer(testerchain.client)) staking_agent.deposit_tokens(amount=balance, lock_periods=10, transacting_power=staker_power) staking_agent.bond_worker(transacting_power=staker_power, worker_address=staker) staking_agent.commit_to_next_period(transacting_power=staker_power) # 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: reservoir = staking_agent.get_stakers_reservoir(duration=1) addresses = set(reservoir.draw(quantity)) 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