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
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