def token_economics(testerchain): # Get current blocktime blockchain = BlockchainInterfaceFactory.get_interface( provider_uri=testerchain.provider_uri) now = blockchain.w3.eth.getBlock(block_identifier='latest').timestamp # Calculate instant start time one_hour_in_seconds = (60 * 60) start_date = now bidding_start_date = start_date # Ends in one hour bidding_end_date = start_date + one_hour_in_seconds cancellation_end_date = bidding_end_date + one_hour_in_seconds economics = StandardTokenEconomics( worklock_boosting_refund_rate=200, worklock_commitment_duration=60, # periods worklock_supply=10 * BaseEconomics._default_maximum_allowed_locked, bidding_start_date=bidding_start_date, bidding_end_date=bidding_end_date, cancellation_end_date=cancellation_end_date, worklock_min_allowed_bid=Web3.toWei(1, "ether")) return economics
def test_economic_parameter_aliases(): e = StandardTokenEconomics() assert e.locked_periods_coefficient == 365 assert int(e.staking_coefficient) == 768812 assert e.maximum_rewarded_periods == 365 deployment_params = e.staking_deployment_parameters assert isinstance(deployment_params, tuple) for parameter in deployment_params: assert isinstance(parameter, int)
def test_crawler_init(get_agent, get_economics): staking_agent = MagicMock(spec=StakingEscrowAgent) contract_agency = MockContractAgency(staking_agent=staking_agent) get_agent.side_effect = contract_agency.get_agent token_economics = StandardTokenEconomics() get_economics.return_value = token_economics crawler = create_crawler(dont_set_teacher=True) # crawler not yet started assert not crawler.is_running
def test_rough_economics(): """ Formula for staking in one period: (totalSupply - currentSupply) * (lockedValue / totalLockedValue) * (k1 + allLockedPeriods) / d / k2 d - Coefficient which modifies the rate at which the maximum issuance decays k1 - Numerator of the locking duration coefficient k2 - Denominator of the locking duration coefficient if allLockedPeriods > awarded_periods then allLockedPeriods = awarded_periods kappa * log(2) / halving_delay === (k1 + allLockedPeriods) / d / k2 kappa = small_stake_multiplier + (1 - small_stake_multiplier) * min(T, T1) / T1 where allLockedPeriods == min(T, T1) """ e = StandardTokenEconomics(initial_supply=int(1e9), first_phase_supply=1829579800, first_phase_duration=5, decay_half_life=2, reward_saturation=1, small_stake_multiplier=Decimal(0.5)) assert float(round(e.erc20_total_supply / Decimal(1e9), 2)) == 3.89 # As per economics paper # Check that we have correct numbers in day 1 of the second phase one_year_in_periods = Decimal(365 / 7) initial_rate = (e.erc20_total_supply - int(e.first_phase_total_supply)) \ * (e.lock_duration_coefficient_1 + one_year_in_periods) \ / (e.issuance_decay_coefficient * e.lock_duration_coefficient_2) assert int(initial_rate) == int(e.first_phase_max_issuance) initial_rate_small = (e.erc20_total_supply - int(e.first_phase_total_supply))\ * e.lock_duration_coefficient_1 \ / (e.issuance_decay_coefficient * e.lock_duration_coefficient_2) assert int(initial_rate_small) == int(initial_rate / 2) # Sanity check that total and reward supply calculated correctly assert int(LOG2 / (e.token_halving * one_year_in_periods) * (e.erc20_total_supply - int(e.first_phase_total_supply))) == int(initial_rate) assert int(e.reward_supply) == int(e.erc20_total_supply - Decimal(int(1e9))) with localcontext( ) as ctx: # TODO: Needs follow up - why the sudden failure (python 3.8.0)? ctx.prec = 18 # Perform a high precision calculation # Sanity check for lock_duration_coefficient_1 (k1), issuance_decay_coefficient (d) and lock_duration_coefficient_2 (k2) expected = e.lock_duration_coefficient_1 * e.token_halving result = e.issuance_decay_coefficient * e.lock_duration_coefficient_2 * LOG2 * e.small_stake_multiplier / one_year_in_periods assert expected == result
def test_economic_parameter_aliases(): e = StandardTokenEconomics() assert e.lock_duration_coefficient_1 == 365 assert e.lock_duration_coefficient_2 == 2 * 365 assert int(e.issuance_decay_coefficient) == 1053 assert e.maximum_rewarded_periods == 365 deployment_params = e.staking_deployment_parameters assert isinstance(deployment_params, tuple) for parameter in deployment_params: assert isinstance(parameter, int)
def test_crawler_start_no_influx_db_connection(get_agent, get_economics): staking_agent = MagicMock(spec=StakingEscrowAgent, autospec=True) contract_agency = MockContractAgency(staking_agent=staking_agent) get_agent.side_effect = contract_agency.get_agent token_economics = StandardTokenEconomics() get_economics.return_value = token_economics crawler = create_crawler(dont_set_teacher=True) try: with pytest.raises(ConnectionError): crawler.start() finally: crawler.stop()
def test_crawler_stop_before_start(new_influx_db, get_agent, get_economics): mock_influxdb_client = new_influx_db.return_value staking_agent = MagicMock(spec=StakingEscrowAgent) contract_agency = MockContractAgency(staking_agent=staking_agent) get_agent.side_effect = contract_agency.get_agent token_economics = StandardTokenEconomics() get_economics.return_value = token_economics crawler = create_crawler() crawler.stop() new_influx_db.assert_not_called() # db only initialized when crawler is started mock_influxdb_client.close.assert_not_called() # just to be sure assert not crawler.is_running
def test_rough_economics(): """ Formula for staking in one period: (totalSupply - currentSupply) * (lockedValue / totalLockedValue) * (k1 + allLockedPeriods) / d / k2 d - Coefficient which modifies the rate at which the maximum issuance decays k1 - Numerator of the locking duration coefficient k2 - Denominator of the locking duration coefficient if allLockedPeriods > awarded_periods then allLockedPeriods = awarded_periods kappa * log(2) / halving_delay === (k1 + allLockedPeriods) / d / k2 kappa = small_stake_multiplier + (1 - small_stake_multiplier) * min(T, T1) / T1 where allLockedPeriods == min(T, T1) """ e = StandardTokenEconomics(initial_supply=int(1e9), first_phase_supply=1829579800, first_phase_duration=5, decay_half_life=2, reward_saturation=1, small_stake_multiplier=Decimal(0.5)) assert float(round(e.erc20_total_supply / Decimal(1e9), 2)) == 3.89 # As per economics paper # Check that we have correct numbers in day 1 of the second phase initial_rate = (e.erc20_total_supply - int(e.first_phase_total_supply)) * (e.lock_duration_coefficient_1 + 365) / \ (e.issuance_decay_coefficient * e.lock_duration_coefficient_2) assert int(initial_rate) == int(e.first_phase_max_issuance) initial_rate_small = (e.erc20_total_supply - int(e.first_phase_total_supply)) * e.lock_duration_coefficient_1 / \ (e.issuance_decay_coefficient * e.lock_duration_coefficient_2) assert int(initial_rate_small) == int(initial_rate / 2) # Sanity check that total and reward supply calculated correctly assert int(LOG2 / (e.token_halving * 365) * (e.erc20_total_supply - int(e.first_phase_total_supply))) == int(initial_rate) assert int(e.reward_supply) == int(e.erc20_total_supply - Decimal(int(1e9))) # Sanity check for lock_duration_coefficient_1 (k1), issuance_decay_coefficient (d) and lock_duration_coefficient_2 (k2) assert e.lock_duration_coefficient_1 * e.token_halving == \ e.issuance_decay_coefficient * e.lock_duration_coefficient_2 * LOG2 * e.small_stake_multiplier / 365
def token_economics(testerchain): # Get current blocktime blockchain = BlockchainInterfaceFactory.get_interface() now = blockchain.w3.eth.getBlock(block_identifier='latest').timestamp # Calculate instant start time one_hour_in_seconds = (60 * 60) start_date = now bidding_start_date = start_date # Ends in one hour bidding_end_date = start_date + one_hour_in_seconds economics = StandardTokenEconomics( worklock_boosting_refund_rate=200, worklock_commitment_duration=60 * 60, # seconds worklock_supply=NU.from_tokens(1_000_000), bidding_start_date=bidding_start_date, bidding_end_date=bidding_end_date) return economics
def __init__(self, registry: BaseContractRegistry, deployer_address: str = None, client_password: str = None, economics: TokenEconomics = None): """ Note: super() is not called here to avoid setting the token agent. TODO: Review this logic ^^ "bare mode". """ self.log = Logger("Deployment-Actor") self.deployer_address = deployer_address self.checksum_address = self.deployer_address self.economics = economics or StandardTokenEconomics() self.registry = registry self.preallocation_escrow_deployers = dict() self.deployers = {d.contract_name: d for d in self.deployer_classes} self.transacting_power = TransactingPower(password=client_password, account=deployer_address, cache=True) self.transacting_power.activate()
def __init__(self, registry: BaseContractRegistry, economics: TokenEconomics = None, deployer_address: str = None): # # Validate # self.blockchain = BlockchainInterfaceFactory.get_interface() if not isinstance(self.blockchain, BlockchainDeployerInterface): raise ValueError("No deployer interface connection available.") # # Defaults # self.registry = registry self.deployment_receipts = dict() self._contract = CONTRACT_NOT_DEPLOYED self.__proxy_contract = NotImplemented self.__deployer_address = deployer_address self.__ready_to_deploy = False self.__economics = economics or StandardTokenEconomics()
def make_token_economics(blockchain): # Get current blocktime now = blockchain.w3.eth.getBlock('latest').timestamp # Calculate instant start time one_hour_in_seconds = (60 * 60) start_date = now bidding_start_date = start_date # Ends in one hour bidding_end_date = start_date + one_hour_in_seconds cancellation_end_date = bidding_end_date + one_hour_in_seconds economics = StandardTokenEconomics( worklock_boosting_refund_rate=200, worklock_commitment_duration=60, # genesis periods worklock_supply=10 * BaseEconomics._default_maximum_allowed_locked, bidding_start_date=bidding_start_date, bidding_end_date=bidding_end_date, cancellation_end_date=cancellation_end_date, worklock_min_allowed_bid=Web3.toWei(1, "ether")) return economics
def test_rough_economics(): """ Formula for staking in one period: (totalSupply - currentSupply) * (lockedValue / totalLockedValue) * (k1 + allLockedPeriods) / k2 K2 - Staking coefficient K1 - Locked periods coefficient if allLockedPeriods > awarded_periods then allLockedPeriods = awarded_periods kappa * log(2) / halving_delay === (k1 + allLockedPeriods) / k2 kappa = small_stake_multiplier + (1 - small_stake_multiplier) * min(T, T1) / T1 where allLockedPeriods == min(T, T1) """ e = StandardTokenEconomics(initial_supply=int(1e9), initial_inflation=1, halving_delay=2, reward_saturation=1, small_stake_multiplier=Decimal(0.5)) assert float(round(e.erc20_total_supply / Decimal(1e9), 2)) == 3.89 # As per economics paper # Check that we have correct numbers in day 1 initial_rate = (e.erc20_total_supply - e.initial_supply) * (e.locked_periods_coefficient + 365) / e.staking_coefficient assert int(initial_rate) == int(e.initial_inflation * e.initial_supply / 365) initial_rate_small = (e.erc20_total_supply - e.initial_supply) * e.locked_periods_coefficient / e.staking_coefficient assert int(initial_rate_small) == int(initial_rate / 2) # Sanity check that total and reward supply calculated correctly assert int(LOG2 / (e.token_halving * 365) * (e.erc20_total_supply - e.initial_supply)) == int(initial_rate) assert int(e.reward_supply) == int(e.erc20_total_supply - Decimal(int(1e9))) # Sanity check for locked_periods_coefficient (k1) and staking_coefficient (k2) assert e.locked_periods_coefficient * e.token_halving == e.staking_coefficient * LOG2 * e.small_stake_multiplier / 365
assert confirmation in captured.out # Negative case mock_stdin.line(NO) with pytest.raises(click.Abort): confirm_staged_stake(staker_address=staking_address, value=value, lock_periods=lock_periods) captured = capsys.readouterr() assert confirmation in captured.out assert mock_stdin.empty() STANDARD_ECONOMICS = StandardTokenEconomics() MIN_ALLOWED_LOCKED = STANDARD_ECONOMICS.minimum_allowed_locked @pytest.mark.parametrize( 'value,duration,must_confirm_value,must_confirm_duration', ( (NU.from_tokens(1), 1, False, False), (NU.from_tokens(1), STANDARD_ECONOMICS.minimum_locked_periods + 1, False, False), (NU.from_tokens(15), STANDARD_ECONOMICS.minimum_locked_periods + 1, False, False), (((NU.from_nunits(MIN_ALLOWED_LOCKED) * 10) + 1), STANDARD_ECONOMICS.minimum_locked_periods + 1, True, False), (NU.from_nunits(MIN_ALLOWED_LOCKED) * 10, STANDARD_ECONOMICS.maximum_rewarded_periods + 1, False, True), (((NU.from_nunits(MIN_ALLOWED_LOCKED) * 10) + 1),
def estimate_gas(analyzer: AnalyzeGas = None) -> None: """ Execute a linear sequence of NyCypher transactions mimicking post-deployment usage on a local PyEVM blockchain; Record the resulting estimated transaction gas expenditure. Note: The function calls below are *order dependant* """ # # Setup # if analyzer is None: analyzer = AnalyzeGas() log = Logger(AnalyzeGas.LOG_NAME) os.environ['GAS_ESTIMATOR_BACKEND_FUNC'] = 'eth.estimators.gas.binary_gas_search_exact' # Blockchain economics = StandardTokenEconomics( base_penalty=MIN_ALLOWED_LOCKED - 1, penalty_history_coefficient=0, percentage_penalty_coefficient=2, reward_coefficient=2 ) testerchain, registry = TesterBlockchain.bootstrap_network(economics=economics) web3 = testerchain.w3 # Accounts origin, ursula1, ursula2, ursula3, alice1, alice2, *everyone_else = testerchain.client.accounts ursula_with_stamp = mock_ursula(testerchain, ursula1) # Contracts token_agent = NucypherTokenAgent(registry=registry) staking_agent = StakingEscrowAgent(registry=registry) policy_agent = PolicyManagerAgent(registry=registry) adjudicator_agent = AdjudicatorAgent(registry=registry) # Contract Callers token_functions = token_agent.contract.functions staker_functions = staking_agent.contract.functions policy_functions = policy_agent.contract.functions adjudicator_functions = adjudicator_agent.contract.functions analyzer.start_collection() print("********* Estimating Gas *********") def transact_and_log(label, function, transaction): estimates = function.estimateGas(transaction) transaction.update(gas=estimates) tx = function.transact(transaction) receipt = testerchain.wait_for_receipt(tx) log.info(f"{label} = {estimates}|{receipt['gasUsed']}") def transact(function, transaction): transaction.update(gas=1000000) tx = function.transact(transaction) testerchain.wait_for_receipt(tx) # # Give Ursula and Alice some coins # transact_and_log("Transfer tokens", token_functions.transfer(ursula1, MIN_ALLOWED_LOCKED * 10), {'from': origin}) transact(token_functions.transfer(ursula2, MIN_ALLOWED_LOCKED * 10), {'from': origin}) transact(token_functions.transfer(ursula3, MIN_ALLOWED_LOCKED * 10), {'from': origin}) # # Ursula and Alice give Escrow rights to transfer # transact_and_log("Approving transfer", token_functions.approve(staking_agent.contract_address, MIN_ALLOWED_LOCKED * 6), {'from': ursula1}) transact(token_functions.approve(staking_agent.contract_address, MIN_ALLOWED_LOCKED * 6), {'from': ursula2}) transact(token_functions.approve(staking_agent.contract_address, MIN_ALLOWED_LOCKED * 6), {'from': ursula3}) # # Ursula and Alice transfer some tokens to the escrow and lock them # transact_and_log("Initial deposit tokens, 1st", staker_functions.deposit(MIN_ALLOWED_LOCKED * 3, MIN_LOCKED_PERIODS), {'from': ursula1}) transact_and_log("Initial deposit tokens, 2nd", staker_functions.deposit(MIN_ALLOWED_LOCKED * 3, MIN_LOCKED_PERIODS), {'from': ursula2}) transact(staker_functions.deposit(MIN_ALLOWED_LOCKED * 3, MIN_LOCKED_PERIODS), {'from': ursula3}) transact(staker_functions.setWorker(ursula1), {'from': ursula1}) transact(staker_functions.setWorker(ursula2), {'from': ursula2}) transact(staker_functions.setWorker(ursula3), {'from': ursula3}) transact(staker_functions.setReStake(False), {'from': ursula1}) transact(staker_functions.setReStake(False), {'from': ursula2}) transact(staker_functions.confirmActivity(), {'from': ursula1}) transact(staker_functions.confirmActivity(), {'from': ursula2}) # # Wait 1 period and confirm activity # testerchain.time_travel(periods=1) transact_and_log("Confirm activity, 1st", staker_functions.confirmActivity(), {'from': ursula1}) transact_and_log("Confirm activity, 2nd", staker_functions.confirmActivity(), {'from': ursula2}) # # Wait 1 period and mint tokens # testerchain.time_travel(periods=1) transact_and_log("Mining (1 stake), 1st", staker_functions.mint(), {'from': ursula1}) transact_and_log("Mining (1 stake), 2nd", staker_functions.mint(), {'from': ursula2}) transact_and_log("Confirm activity again, 1st", staker_functions.confirmActivity(), {'from': ursula1}) transact_and_log("Confirm activity again, 2nd", staker_functions.confirmActivity(), {'from': ursula2}) # # Confirm again # testerchain.time_travel(periods=1) transact_and_log("Confirm activity + mint, 1st", staker_functions.confirmActivity(), {'from': ursula1}) transact_and_log("Confirm activity + mint, 2nd", staker_functions.confirmActivity(), {'from': ursula2}) # # Get locked tokens # transact_and_log("Getting locked tokens", staker_functions.getLockedTokens(ursula1, 0), {}) # # Wait 1 period and withdraw tokens # testerchain.time_travel(periods=1) transact_and_log("Withdraw", staker_functions.withdraw(1), {'from': ursula1}) # # Confirm activity with re-stake # transact(staker_functions.setReStake(True), {'from': ursula1}) transact(staker_functions.setReStake(True), {'from': ursula2}) transact_and_log("Confirm activity + mint with re-stake, 1st", staker_functions.confirmActivity(), {'from': ursula1}) transact_and_log("Confirm activity + mint with re-stake, 2nd", staker_functions.confirmActivity(), {'from': ursula2}) transact(staker_functions.setReStake(False), {'from': ursula1}) transact(staker_functions.setReStake(False), {'from': ursula2}) # # Wait 1 period # testerchain.time_travel(periods=1) # # Create policy # policy_id_1 = os.urandom(int(Policy.POLICY_ID_LENGTH)) policy_id_2 = os.urandom(int(Policy.POLICY_ID_LENGTH)) number_of_periods = 10 rate = 100 one_period = economics.hours_per_period * 60 * 60 value = number_of_periods * rate current_timestamp = testerchain.w3.eth.getBlock(block_identifier='latest').timestamp end_timestamp = current_timestamp + (number_of_periods - 1) * one_period transact_and_log("Creating policy (1 node, 10 periods, pre-confirmed), 1st", policy_functions.createPolicy(policy_id_1, alice1, end_timestamp, [ursula1]), {'from': alice1, 'value': value}) transact_and_log("Creating policy (1 node, 10 periods, pre-confirmed), 2nd", policy_functions.createPolicy(policy_id_2, alice1, end_timestamp, [ursula1]), {'from': alice1, 'value': value}) # # Wait 2 periods and confirm activity after downtime # testerchain.time_travel(periods=1) transact_and_log("Confirm activity after downtime, 1st", staker_functions.confirmActivity(), {'from': ursula1}) transact_and_log("Confirm activity after downtime, 2nd", staker_functions.confirmActivity(), {'from': ursula2}) transact(staker_functions.confirmActivity(), {'from': ursula3}) # # Ursula and Alice deposit some tokens to the escrow again # transact_and_log("Deposit tokens after confirming activity", staker_functions.deposit(MIN_ALLOWED_LOCKED * 2, MIN_LOCKED_PERIODS), {'from': ursula1}) transact(staker_functions.deposit(MIN_ALLOWED_LOCKED * 2, MIN_LOCKED_PERIODS), {'from': ursula2}) # # Revoke policy # transact_and_log("Revoking policy", policy_functions.revokePolicy(policy_id_1), {'from': alice1}) # # Wait 1 period # testerchain.time_travel(periods=1) # # Create policy with multiple pre-confirmed nodes # policy_id_1 = os.urandom(int(Policy.POLICY_ID_LENGTH)) policy_id_2 = os.urandom(int(Policy.POLICY_ID_LENGTH)) policy_id_3 = os.urandom(int(Policy.POLICY_ID_LENGTH)) number_of_periods = 100 value = 3 * number_of_periods * rate current_timestamp = testerchain.w3.eth.getBlock(block_identifier='latest').timestamp end_timestamp = current_timestamp + (number_of_periods - 1) * one_period transact_and_log("Creating policy (3 nodes, 100 periods, pre-confirmed), 1st", policy_functions.createPolicy(policy_id_1, alice1, end_timestamp, [ursula1, ursula2, ursula3]), {'from': alice1, 'value': value}) transact_and_log("Creating policy (3 nodes, 100 periods, pre-confirmed), 2nd", policy_functions.createPolicy(policy_id_2, alice1, end_timestamp, [ursula1, ursula2, ursula3]), {'from': alice1, 'value': value}) value = 2 * number_of_periods * rate transact_and_log("Creating policy (2 nodes, 100 periods, pre-confirmed), 3rd", policy_functions.createPolicy(policy_id_3, alice1, end_timestamp, [ursula1, ursula2]), {'from': alice1, 'value': value}) # # Wait 1 period and mint tokens # testerchain.time_travel(periods=1) transact_and_log("Mining with updating reward, 1st", staker_functions.mint(), {'from': ursula1}) transact_and_log("Mining with updating reward, 2nd", staker_functions.mint(), {'from': ursula2}) # # Create policy again without pre-confirmed nodes # policy_id_1 = os.urandom(int(Policy.POLICY_ID_LENGTH)) policy_id_2 = os.urandom(int(Policy.POLICY_ID_LENGTH)) policy_id_3 = os.urandom(int(Policy.POLICY_ID_LENGTH)) number_of_periods = 100 value = number_of_periods * rate current_timestamp = testerchain.w3.eth.getBlock(block_identifier='latest').timestamp end_timestamp = current_timestamp + (number_of_periods - 1) * one_period transact_and_log("Creating policy (1 node, 100 periods), 1st", policy_functions.createPolicy(policy_id_1, alice2, end_timestamp, [ursula2]), {'from': alice1, 'value': value}) testerchain.time_travel(periods=1) current_timestamp = testerchain.w3.eth.getBlock(block_identifier='latest').timestamp end_timestamp = current_timestamp + (number_of_periods - 1) * one_period transact_and_log("Creating policy (1 node, 100 periods), 2nd", policy_functions.createPolicy(policy_id_2, alice2, end_timestamp, [ursula2]), {'from': alice1, 'value': value}) transact_and_log("Creating policy (1 node, 100 periods), 3rd", policy_functions.createPolicy(policy_id_3, alice2, end_timestamp, [ursula1]), {'from': alice1, 'value': value}) # # Mine and revoke policy # testerchain.time_travel(periods=10) transact(staker_functions.confirmActivity(), {'from': ursula1}) testerchain.time_travel(periods=2) transact_and_log("Mining after downtime", staker_functions.mint(), {'from': ursula1}) testerchain.time_travel(periods=10) transact_and_log("Revoking policy after downtime, 1st", policy_functions.revokePolicy(policy_id_1), {'from': alice2}) transact_and_log("Revoking policy after downtime, 2nd", policy_functions.revokePolicy(policy_id_2), {'from': alice2}) transact_and_log("Revoking policy after downtime, 3rd", policy_functions.revokePolicy(policy_id_3), {'from': alice2}) for index in range(5): transact(staker_functions.confirmActivity(), {'from': ursula1}) testerchain.time_travel(periods=1) transact(staker_functions.mint(), {'from': ursula1}) # # Check regular deposit # transact_and_log("Deposit tokens", staker_functions.deposit(MIN_ALLOWED_LOCKED, MIN_LOCKED_PERIODS), {'from': ursula1}) # # ApproveAndCall # testerchain.time_travel(periods=1) transact(staker_functions.mint(), {'from': ursula1}) transact_and_log("ApproveAndCall", token_functions.approveAndCall(staking_agent.contract_address, MIN_ALLOWED_LOCKED * 2, web3.toBytes(MIN_LOCKED_PERIODS)), {'from': ursula1}) # # Locking tokens # testerchain.time_travel(periods=1) transact(staker_functions.confirmActivity(), {'from': ursula1}) transact_and_log("Locking tokens", staker_functions.lock(MIN_ALLOWED_LOCKED, MIN_LOCKED_PERIODS), {'from': ursula1}) # # Divide stake # transact_and_log("Divide stake", staker_functions.divideStake(1, MIN_ALLOWED_LOCKED, 2), {'from': ursula1}) transact(staker_functions.divideStake(3, MIN_ALLOWED_LOCKED, 2), {'from': ursula1}) # # Divide almost finished stake # testerchain.time_travel(periods=1) transact(staker_functions.confirmActivity(), {'from': ursula1}) testerchain.time_travel(periods=1) transact(staker_functions.confirmActivity(), {'from': ursula1}) # # Slashing tests # transact(staker_functions.confirmActivity(), {'from': ursula1}) testerchain.time_travel(periods=1) # # Slashing # slashing_args = generate_args_for_slashing(ursula_with_stamp) transact_and_log("Slash just value", adjudicator_functions.evaluateCFrag(*slashing_args), {'from': alice1}) deposit = staker_functions.stakerInfo(ursula1).call()[0] unlocked = deposit - staker_functions.getLockedTokens(ursula1, 0).call() transact(staker_functions.withdraw(unlocked), {'from': ursula1}) sub_stakes_length = str(staker_functions.getSubStakesLength(ursula1).call()) slashing_args = generate_args_for_slashing(ursula_with_stamp) transact_and_log("Slashing one sub stake and saving old one (" + sub_stakes_length + " sub stakes), 1st", adjudicator_functions.evaluateCFrag(*slashing_args), {'from': alice1}) sub_stakes_length = str(staker_functions.getSubStakesLength(ursula1).call()) slashing_args = generate_args_for_slashing(ursula_with_stamp) transact_and_log("Slashing one sub stake and saving old one (" + sub_stakes_length + " sub stakes), 2nd", adjudicator_functions.evaluateCFrag(*slashing_args), {'from': alice1}) sub_stakes_length = str(staker_functions.getSubStakesLength(ursula1).call()) slashing_args = generate_args_for_slashing(ursula_with_stamp) transact_and_log("Slashing one sub stake and saving old one (" + sub_stakes_length + " sub stakes), 3rd", adjudicator_functions.evaluateCFrag(*slashing_args), {'from': alice1}) sub_stakes_length = str(staker_functions.getSubStakesLength(ursula1).call()) slashing_args = generate_args_for_slashing(ursula_with_stamp) transact_and_log("Slashing two sub stakes and saving old one (" + sub_stakes_length + " sub stakes)", adjudicator_functions.evaluateCFrag(*slashing_args), {'from': alice1}) for index in range(18): transact(staker_functions.confirmActivity(), {'from': ursula1}) testerchain.time_travel(periods=1) transact(staker_functions.lock(MIN_ALLOWED_LOCKED, MIN_LOCKED_PERIODS), {'from': ursula1}) deposit = staker_functions.stakerInfo(ursula1).call()[0] unlocked = deposit - staker_functions.getLockedTokens(ursula1, 1).call() transact(staker_functions.withdraw(unlocked), {'from': ursula1}) sub_stakes_length = str(staker_functions.getSubStakesLength(ursula1).call()) slashing_args = generate_args_for_slashing(ursula_with_stamp) transact_and_log("Slashing two sub stakes, shortest and new one (" + sub_stakes_length + " sub stakes)", adjudicator_functions.evaluateCFrag(*slashing_args), {'from': alice1}) sub_stakes_length = str(staker_functions.getSubStakesLength(ursula1).call()) slashing_args = generate_args_for_slashing(ursula_with_stamp) transact_and_log("Slashing three sub stakes, two shortest and new one (" + sub_stakes_length + " sub stakes)", adjudicator_functions.evaluateCFrag(*slashing_args), {'from': alice1}) slashing_args = generate_args_for_slashing(ursula_with_stamp, corrupt_cfrag=False) transact_and_log("Evaluating correct CFrag", adjudicator_functions.evaluateCFrag(*slashing_args), {'from': alice1}) transact_and_log("Prolong stake", staker_functions.prolongStake(0, 20), {'from': ursula1}) print("********* All Done! *********")
from nucypher.blockchain.economics import StandardTokenEconomics from nucypher.blockchain.eth.agents import NucypherTokenAgent, StakingEscrowAgent, PolicyManagerAgent, AdjudicatorAgent from nucypher.crypto.signing import SignatureStamp from nucypher.policy.policies import Policy from nucypher.utilities.sandbox.blockchain import TesterBlockchain from umbral.keys import UmbralPrivateKey from umbral.signing import Signer # FIXME: Needed to use a fixture here, but now estimate_gas.py only runs if executed from main directory sys.path.insert(0, abspath('tests')) from fixtures import _mock_ursula_reencrypts as mock_ursula_reencrypts ALGORITHM_SHA256 = 1 TOKEN_ECONOMICS = StandardTokenEconomics() MIN_ALLOWED_LOCKED = TOKEN_ECONOMICS.minimum_allowed_locked MIN_LOCKED_PERIODS = TOKEN_ECONOMICS.minimum_locked_periods MAX_ALLOWED_LOCKED = TOKEN_ECONOMICS.maximum_allowed_locked MAX_MINTING_PERIODS = TOKEN_ECONOMICS.maximum_rewarded_periods class AnalyzeGas: """ Callable twisted log observer with built-in record-keeping for gas estimation runs. """ # Logging LOG_NAME = 'estimate-gas' LOG_FILENAME = '{}.log.json'.format(LOG_NAME) OUTPUT_DIR = os.path.join(abspath(dirname(__file__)), 'results')
def token_economics(): economics = StandardTokenEconomics() return economics
def token_economics(): economics = StandardTokenEconomics(genesis_hours_per_period=24, hours_per_period=48, minimum_locked_periods=2) return economics
class TesterBlockchain(BlockchainDeployerInterface): """ Blockchain subclass with additional test utility methods and options. """ _instance = None GAS_STRATEGIES = { **BlockchainDeployerInterface.GAS_STRATEGIES, 'free': free_gas_price_strategy } _PROVIDER_URI = 'tester://pyevm' TEST_CONTRACTS_DIR = os.path.join(BASE_DIR, 'tests', 'blockchain', 'eth', 'contracts', 'contracts') _compiler = SolidityCompiler( source_dirs=[(SolidityCompiler.default_contract_dir(), {TEST_CONTRACTS_DIR})]) _test_account_cache = list() _default_test_accounts = NUMBER_OF_ETH_TEST_ACCOUNTS # Reserved addresses _ETHERBASE = 0 _ALICE = 1 _BOB = 2 _FIRST_STAKER = 5 _stakers_range = range(NUMBER_OF_STAKERS_IN_BLOCKCHAIN_TESTS) _FIRST_URSULA = _FIRST_STAKER + NUMBER_OF_STAKERS_IN_BLOCKCHAIN_TESTS _ursulas_range = range(NUMBER_OF_URSULAS_IN_BLOCKCHAIN_TESTS) _default_token_economics = StandardTokenEconomics() def __init__(self, test_accounts=None, poa=True, light=False, eth_airdrop=False, free_transactions=False, compiler: SolidityCompiler = None, *args, **kwargs): if not test_accounts: test_accounts = self._default_test_accounts self.free_transactions = free_transactions if compiler: TesterBlockchain._compiler = compiler super().__init__(provider_uri=self._PROVIDER_URI, provider_process=None, poa=poa, light=light, compiler=self._compiler, *args, **kwargs) self.log = Logger("test-blockchain") self.connect() # Generate additional ethereum accounts for testing population = test_accounts enough_accounts = len(self.client.accounts) >= population if not enough_accounts: accounts_to_make = population - len(self.client.accounts) self.__generate_insecure_unlocked_accounts( quantity=accounts_to_make) assert test_accounts == len(self.w3.eth.accounts) if eth_airdrop is True: # ETH for everyone! self.ether_airdrop(amount=DEVELOPMENT_ETH_AIRDROP_AMOUNT) def attach_middleware(self): if self.free_transactions: self.w3.eth.setGasPriceStrategy(free_gas_price_strategy) def __generate_insecure_unlocked_accounts(self, quantity: int) -> List[str]: # # Sanity Check - Only PyEVM can be used. # # Detect provider platform client_version = self.w3.clientVersion if 'Geth' in client_version: raise RuntimeError("WARNING: Geth providers are not implemented.") elif "Parity" in client_version: raise RuntimeError( "WARNING: Parity providers are not implemented.") addresses = list() for _ in range(quantity): address = self.provider.ethereum_tester.add_account( '0x' + os.urandom(32).hex()) addresses.append(address) self._test_account_cache.append(address) self.log.info('Generated new insecure account {}'.format(address)) return addresses def ether_airdrop(self, amount: int) -> List[str]: """Airdrops ether from creator address to all other addresses!""" coinbase, *addresses = self.w3.eth.accounts tx_hashes = list() for address in addresses: tx = {'to': address, 'from': coinbase, 'value': amount} txhash = self.w3.eth.sendTransaction(tx) _receipt = self.wait_for_receipt(txhash) tx_hashes.append(txhash) eth_amount = Web3().fromWei(amount, 'ether') self.log.info("Airdropped {} ETH {} -> {}".format( eth_amount, tx['from'], tx['to'])) return tx_hashes def time_travel(self, hours: int = None, seconds: int = None, periods: int = None): """ Wait the specified number of wait_hours by comparing block timestamps and mines a single block. """ more_than_one_arg = sum(map(bool, (hours, seconds, periods))) > 1 if more_than_one_arg: raise ValueError( "Specify hours, seconds, or periods, not a combination") if periods: duration = self._default_token_economics.seconds_per_period * periods base = self._default_token_economics.seconds_per_period elif hours: duration = hours * (60 * 60) base = 60 * 60 elif seconds: duration = seconds base = 1 else: raise ValueError("Specify either hours, seconds, or periods.") now = self.w3.eth.getBlock(block_identifier='latest').timestamp end_timestamp = ((now + duration) // base) * base self.w3.eth.web3.testing.timeTravel(timestamp=end_timestamp) self.w3.eth.web3.testing.mine(1) delta = maya.timedelta(seconds=end_timestamp - now) self.log.info( f"Time traveled {delta} " f"| period {epoch_to_period(epoch=end_timestamp, seconds_per_period=self._default_token_economics.seconds_per_period)} " f"| epoch {end_timestamp}") @classmethod def bootstrap_network( cls, economics: BaseEconomics = None ) -> Tuple['TesterBlockchain', 'InMemoryContractRegistry']: """For use with metric testing scripts""" registry = InMemoryContractRegistry() testerchain = cls(compiler=SolidityCompiler()) BlockchainInterfaceFactory.register_interface(testerchain) power = TransactingPower(password=INSECURE_DEVELOPMENT_PASSWORD, account=testerchain.etherbase_account) power.activate() testerchain.transacting_power = power origin = testerchain.client.etherbase deployer = ContractAdministrator(deployer_address=origin, registry=registry, economics=economics or cls._default_token_economics, staking_escrow_test_mode=True) secrets = dict() for deployer_class in deployer.upgradeable_deployer_classes: secrets[ deployer_class.contract_name] = INSECURE_DEVELOPMENT_PASSWORD _receipts = deployer.deploy_network_contracts(secrets=secrets, interactive=False) return testerchain, registry @property def etherbase_account(self): return self.client.accounts[self._ETHERBASE] @property def alice_account(self): return self.client.accounts[self._ALICE] @property def bob_account(self): return self.client.accounts[self._BOB] def ursula_account(self, index): if index not in self._ursulas_range: raise ValueError( f"Ursula index must be lower than {NUMBER_OF_URSULAS_IN_BLOCKCHAIN_TESTS}" ) return self.client.accounts[index + self._FIRST_URSULA] def staker_account(self, index): if index not in self._stakers_range: raise ValueError( f"Staker index must be lower than {NUMBER_OF_STAKERS_IN_BLOCKCHAIN_TESTS}" ) return self.client.accounts[index + self._FIRST_STAKER] @property def ursulas_accounts(self): return list(self.ursula_account(i) for i in self._ursulas_range) @property def stakers_accounts(self): return list(self.staker_account(i) for i in self._stakers_range) @property def unassigned_accounts(self): special_accounts = [ self.etherbase_account, self.alice_account, self.bob_account ] assigned_accounts = set(self.stakers_accounts + self.ursulas_accounts + special_accounts) accounts = set(self.client.accounts) return list(accounts.difference(assigned_accounts)) def wait_for_receipt(self, txhash: bytes, timeout: int = None) -> dict: """Wait for a transaction receipt and return it""" timeout = timeout or self.TIMEOUT result = self.w3.eth.waitForTransactionReceipt(txhash, timeout=timeout) if result.status == 0: raise TransactionFailed() return result
def estimate_gas(analyzer: AnalyzeGas = None) -> None: """ Execute a linear sequence of NyCypher transactions mimicking post-deployment usage on a local PyEVM blockchain; Record the resulting estimated transaction gas expenditure. Note: The function calls below are *order dependant* """ # # Setup # if analyzer is None: analyzer = AnalyzeGas() log = Logger(AnalyzeGas.LOG_NAME) os.environ[ 'GAS_ESTIMATOR_BACKEND_FUNC'] = 'eth.estimators.gas.binary_gas_search_exact' # Blockchain economics = StandardTokenEconomics(base_penalty=MIN_ALLOWED_LOCKED - 1, penalty_history_coefficient=0, percentage_penalty_coefficient=2, reward_coefficient=2) testerchain, registry = TesterBlockchain.bootstrap_network( economics=economics) web3 = testerchain.w3 print("\n********* SIZE OF MAIN CONTRACTS *********") MAX_SIZE = 24576 rows = list() for contract_name in NUCYPHER_CONTRACT_NAMES: compiled_contract = testerchain._raw_contract_cache[contract_name] version = list(compiled_contract).pop() # FIXME this value includes constructor code size but should not bin_runtime = compiled_contract[version]['evm']['bytecode']['object'] bin_length_in_bytes = len(bin_runtime) // 2 percentage = int(100 * bin_length_in_bytes / MAX_SIZE) bar = ('*' * (percentage // 2)).ljust(50) rows.append( (contract_name, bin_length_in_bytes, f'{bar} {percentage}%')) headers = ('Contract', 'Size (B)', f'% of max allowed contract size ({MAX_SIZE} B)') print(tabulate.tabulate(rows, headers=headers, tablefmt="simple"), end="\n\n") # Accounts origin, staker1, staker2, staker3, staker4, alice1, alice2, *everyone_else = testerchain.client.accounts ursula_with_stamp = mock_ursula(testerchain, staker1) # Contracts token_agent = NucypherTokenAgent(registry=registry) staking_agent = StakingEscrowAgent(registry=registry) policy_agent = PolicyManagerAgent(registry=registry) adjudicator_agent = AdjudicatorAgent(registry=registry) # Contract Callers token_functions = token_agent.contract.functions staker_functions = staking_agent.contract.functions policy_functions = policy_agent.contract.functions adjudicator_functions = adjudicator_agent.contract.functions analyzer.start_collection() print("********* Estimating Gas *********") def transact_and_log(label, function, transaction): estimates = function.estimateGas(transaction) transaction.update(gas=estimates) tx = function.transact(transaction) receipt = testerchain.wait_for_receipt(tx) log.info(f"{label} = {estimates} | {receipt['gasUsed']}") def transact(function, transaction): transaction.update(gas=1000000) tx = function.transact(transaction) testerchain.wait_for_receipt(tx) # First deposit ever is the most expensive, make it to remove unusual gas spending transact( token_functions.approve(staking_agent.contract_address, MIN_ALLOWED_LOCKED * 10), {'from': origin}) transact( staker_functions.deposit(everyone_else[0], MIN_ALLOWED_LOCKED, LOCKED_PERIODS), {'from': origin}) testerchain.time_travel(periods=1) # # Give Ursula and Alice some coins # transact_and_log( "Transfer tokens", token_functions.transfer(staker1, MIN_ALLOWED_LOCKED * 10), {'from': origin}) transact(token_functions.transfer(staker2, MIN_ALLOWED_LOCKED * 10), {'from': origin}) transact(token_functions.transfer(staker3, MIN_ALLOWED_LOCKED * 10), {'from': origin}) # # Ursula and Alice give Escrow rights to transfer # transact_and_log( "Approving transfer", token_functions.approve(staking_agent.contract_address, MIN_ALLOWED_LOCKED * 7), {'from': staker1}) transact( token_functions.approve(staking_agent.contract_address, MIN_ALLOWED_LOCKED * 6), {'from': staker2}) transact( token_functions.approve(staking_agent.contract_address, MIN_ALLOWED_LOCKED * 6), {'from': staker3}) # # Ursula and Alice transfer some tokens to the escrow and lock them # transact_and_log( "Initial deposit tokens, first", staker_functions.deposit(staker1, MIN_ALLOWED_LOCKED * 3, LOCKED_PERIODS), {'from': staker1}) transact_and_log( "Initial deposit tokens, other", staker_functions.deposit(staker2, MIN_ALLOWED_LOCKED * 3, LOCKED_PERIODS), {'from': staker2}) transact( staker_functions.deposit(staker3, MIN_ALLOWED_LOCKED * 3, LOCKED_PERIODS), {'from': staker3}) transact(staker_functions.bondWorker(staker1), {'from': staker1}) transact(staker_functions.bondWorker(staker2), {'from': staker2}) transact(staker_functions.bondWorker(staker3), {'from': staker3}) transact(staker_functions.setReStake(False), {'from': staker1}) transact(staker_functions.setReStake(False), {'from': staker2}) transact(staker_functions.setWindDown(True), {'from': staker1}) transact(staker_functions.setWindDown(True), {'from': staker2}) transact(staker_functions.commitToNextPeriod(), {'from': staker1}) transact(staker_functions.commitToNextPeriod(), {'from': staker2}) # # Wait 1 period and make a commitment # testerchain.time_travel(periods=1) transact_and_log("Make a commitment, first", staker_functions.commitToNextPeriod(), {'from': staker1}) transact_and_log("Make a commitment, other", staker_functions.commitToNextPeriod(), {'from': staker2}) # # Wait 1 period and mint tokens # testerchain.time_travel(periods=1) transact_and_log("Minting (1 stake), first", staker_functions.mint(), {'from': staker1}) transact_and_log("Minting (1 stake), other", staker_functions.mint(), {'from': staker2}) transact_and_log("Make a commitment again, first", staker_functions.commitToNextPeriod(), {'from': staker1}) transact_and_log("Make a commitment again, other", staker_functions.commitToNextPeriod(), {'from': staker2}) transact(staker_functions.commitToNextPeriod(), {'from': staker3}) # # Commit again # testerchain.time_travel(periods=1) transact_and_log("Make a commitment + mint, first", staker_functions.commitToNextPeriod(), {'from': staker1}) transact_and_log("Make a commitment + mint, other", staker_functions.commitToNextPeriod(), {'from': staker2}) # # Create policy # policy_id_1 = os.urandom(int(Policy.POLICY_ID_LENGTH)) policy_id_2 = os.urandom(int(Policy.POLICY_ID_LENGTH)) number_of_periods = 10 rate = 100 one_period = economics.hours_per_period * 60 * 60 value = number_of_periods * rate current_timestamp = testerchain.w3.eth.getBlock('latest').timestamp end_timestamp = current_timestamp + (number_of_periods - 1) * one_period transact_and_log( "Creating policy (1 node, 10 periods, pre-committed), first", policy_functions.createPolicy(policy_id_1, alice1, end_timestamp, [staker1]), { 'from': alice1, 'value': value }) transact_and_log( "Creating policy (1 node, 10 periods, pre-committed), other", policy_functions.createPolicy(policy_id_2, alice1, end_timestamp, [staker1]), { 'from': alice1, 'value': value }) # # Get locked tokens # transact_and_log("Getting locked tokens", staker_functions.getLockedTokens(staker1, 0), {}) # # Wait 1 period and withdraw tokens # testerchain.time_travel(periods=1) transact_and_log("Withdraw", staker_functions.withdraw(1), {'from': staker1}) # # Make a commitment with re-stake # transact(staker_functions.setReStake(True), {'from': staker1}) transact(staker_functions.setReStake(True), {'from': staker2}) # Used to remove spending for first call in a period for mint and commitToNextPeriod transact(staker_functions.commitToNextPeriod(), {'from': staker3}) transact_and_log("Make a commitment + mint + re-stake", staker_functions.commitToNextPeriod(), {'from': staker2}) transact_and_log( "Make a commitment + mint + re-stake + first fee + first fee rate", staker_functions.commitToNextPeriod(), {'from': staker1}) transact(staker_functions.setReStake(False), {'from': staker1}) transact(staker_functions.setReStake(False), {'from': staker2}) # # Wait 2 periods and make a commitment after downtime # testerchain.time_travel(periods=2) transact(staker_functions.commitToNextPeriod(), {'from': staker3}) transact_and_log("Make a commitment after downtime", staker_functions.commitToNextPeriod(), {'from': staker2}) transact_and_log("Make a commitment after downtime + updating fee", staker_functions.commitToNextPeriod(), {'from': staker1}) # # Ursula and Alice deposit some tokens to the escrow again # transact_and_log( "Deposit tokens after making a commitment", staker_functions.deposit(staker1, MIN_ALLOWED_LOCKED * 2, LOCKED_PERIODS), {'from': staker1}) transact( staker_functions.deposit(staker2, MIN_ALLOWED_LOCKED * 2, LOCKED_PERIODS), {'from': staker2}) # # Revoke policy # transact_and_log("Revoking policy", policy_functions.revokePolicy(policy_id_1), {'from': alice1}) # # Wait 1 period # testerchain.time_travel(periods=1) # # Create policy with multiple pre-committed nodes # policy_id_1 = os.urandom(int(Policy.POLICY_ID_LENGTH)) policy_id_2 = os.urandom(int(Policy.POLICY_ID_LENGTH)) policy_id_3 = os.urandom(int(Policy.POLICY_ID_LENGTH)) number_of_periods = 100 value = 3 * number_of_periods * rate current_timestamp = testerchain.w3.eth.getBlock('latest').timestamp end_timestamp = current_timestamp + (number_of_periods - 1) * one_period transact_and_log( "Creating policy (3 nodes, 100 periods, pre-committed), first", policy_functions.createPolicy(policy_id_1, alice1, end_timestamp, [staker1, staker2, staker3]), { 'from': alice1, 'value': value }) transact_and_log( "Creating policy (3 nodes, 100 periods, pre-committed), other", policy_functions.createPolicy(policy_id_2, alice1, end_timestamp, [staker1, staker2, staker3]), { 'from': alice1, 'value': value }) value = 2 * number_of_periods * rate transact_and_log( "Creating policy (2 nodes, 100 periods, pre-committed), other", policy_functions.createPolicy(policy_id_3, alice1, end_timestamp, [staker1, staker2]), { 'from': alice1, 'value': value }) # # Wait 1 period and mint tokens # testerchain.time_travel(periods=1) transact(staker_functions.mint(), {'from': staker3}) transact_and_log("Last minting + updating fee + updating fee rate", staker_functions.mint(), {'from': staker1}) transact_and_log("Last minting + first fee + first fee rate", staker_functions.mint(), {'from': staker2}) # # Create policy again without pre-committed nodes # policy_id_1 = os.urandom(int(Policy.POLICY_ID_LENGTH)) policy_id_2 = os.urandom(int(Policy.POLICY_ID_LENGTH)) policy_id_3 = os.urandom(int(Policy.POLICY_ID_LENGTH)) number_of_periods = 100 value = number_of_periods * rate current_timestamp = testerchain.w3.eth.getBlock('latest').timestamp end_timestamp = current_timestamp + (number_of_periods - 1) * one_period transact_and_log( "Creating policy (1 node, 100 periods)", policy_functions.createPolicy(policy_id_1, alice2, end_timestamp, [staker2]), { 'from': alice1, 'value': value }) testerchain.time_travel(periods=1) current_timestamp = testerchain.w3.eth.getBlock('latest').timestamp end_timestamp = current_timestamp + (number_of_periods - 1) * one_period transact_and_log( "Creating policy (1 node, 100 periods), next period", policy_functions.createPolicy(policy_id_2, alice2, end_timestamp, [staker2]), { 'from': alice1, 'value': value }) transact_and_log( "Creating policy (1 node, 100 periods), another node", policy_functions.createPolicy(policy_id_3, alice2, end_timestamp, [staker1]), { 'from': alice1, 'value': value }) # # Mint and revoke policy # testerchain.time_travel(periods=10) transact(staker_functions.commitToNextPeriod(), {'from': staker1}) transact(staker_functions.commitToNextPeriod(), {'from': staker3}) testerchain.time_travel(periods=2) transact(staker_functions.mint(), {'from': staker3}) transact_and_log("Last minting after downtime + updating fee", staker_functions.mint(), {'from': staker1}) testerchain.time_travel(periods=10) transact_and_log("Revoking policy after downtime, 1st policy", policy_functions.revokePolicy(policy_id_1), {'from': alice2}) transact_and_log("Revoking policy after downtime, 2nd policy", policy_functions.revokePolicy(policy_id_2), {'from': alice2}) transact_and_log("Revoking policy after downtime, 3rd policy", policy_functions.revokePolicy(policy_id_3), {'from': alice2}) transact(staker_functions.commitToNextPeriod(), {'from': staker1}) transact(staker_functions.commitToNextPeriod(), {'from': staker2}) transact(staker_functions.commitToNextPeriod(), {'from': staker3}) testerchain.time_travel(periods=1) # # Batch granting # policy_id_1 = os.urandom(int(Policy.POLICY_ID_LENGTH)) policy_id_2 = os.urandom(int(Policy.POLICY_ID_LENGTH)) current_timestamp = testerchain.w3.eth.getBlock('latest').timestamp end_timestamp = current_timestamp + (number_of_periods - 1) * one_period value = 3 * number_of_periods * rate transact_and_log( "Creating 2 policies (3 nodes, 100 periods, pre-committed)", policy_functions.createPolicies([policy_id_1, policy_id_2], alice1, end_timestamp, [staker1, staker2, staker3]), { 'from': alice1, 'value': 2 * value }) for index in range(4): transact(staker_functions.commitToNextPeriod(), {'from': staker1}) testerchain.time_travel(periods=1) transact(staker_functions.mint(), {'from': staker1}) # # Check regular deposit # transact_and_log( "Deposit tokens to new sub-stake", staker_functions.deposit(staker1, MIN_ALLOWED_LOCKED, LOCKED_PERIODS), {'from': staker1}) transact_and_log( "Deposit tokens using existing sub-stake", staker_functions.depositAndIncrease(0, MIN_ALLOWED_LOCKED), {'from': staker1}) # # ApproveAndCall # testerchain.time_travel(periods=1) transact(staker_functions.mint(), {'from': staker1}) transact_and_log( "ApproveAndCall", token_functions.approveAndCall(staking_agent.contract_address, MIN_ALLOWED_LOCKED * 2, web3.toBytes(LOCKED_PERIODS)), {'from': staker1}) # # Locking tokens # testerchain.time_travel(periods=1) transact(staker_functions.commitToNextPeriod(), {'from': staker1}) transact_and_log( "Locking tokens and creating new sub-stake", staker_functions.lockAndCreate(MIN_ALLOWED_LOCKED, LOCKED_PERIODS), {'from': staker1}) transact_and_log("Locking tokens using existing sub-stake", staker_functions.lockAndIncrease(0, MIN_ALLOWED_LOCKED), {'from': staker1}) # # Divide stake # transact_and_log("Divide stake", staker_functions.divideStake(1, MIN_ALLOWED_LOCKED, 2), {'from': staker1}) transact(staker_functions.divideStake(3, MIN_ALLOWED_LOCKED, 2), {'from': staker1}) # # Divide almost finished stake # testerchain.time_travel(periods=1) transact(staker_functions.commitToNextPeriod(), {'from': staker1}) testerchain.time_travel(periods=1) transact(staker_functions.commitToNextPeriod(), {'from': staker1}) testerchain.time_travel(periods=1) for index in range(18): transact(staker_functions.commitToNextPeriod(), {'from': staker1}) testerchain.time_travel(periods=1) transact( staker_functions.lockAndCreate(MIN_ALLOWED_LOCKED, LOCKED_PERIODS), {'from': staker1}) deposit = staker_functions.stakerInfo(staker1).call()[0] unlocked = deposit - staker_functions.getLockedTokens(staker1, 1).call() transact(staker_functions.withdraw(unlocked), {'from': staker1}) transact_and_log("Prolong stake", staker_functions.prolongStake(0, 20), {'from': staker1}) transact_and_log("Merge sub-stakes", staker_functions.mergeStake(2, 3), {'from': staker1}) # Large number of sub-stakes number_of_sub_stakes = 24 transact(token_functions.approve(staking_agent.contract_address, 0), {'from': origin}) transact( token_functions.approve(staking_agent.contract_address, MIN_ALLOWED_LOCKED * number_of_sub_stakes), {'from': origin}) for i in range(number_of_sub_stakes): transact( staker_functions.deposit(staker4, MIN_ALLOWED_LOCKED, LOCKED_PERIODS), {'from': origin}) transact(staker_functions.bondWorker(staker4), {'from': staker4}) transact(staker_functions.setWindDown(True), {'from': staker4}) # Used to remove spending for first call in a period for mint and commitToNextPeriod transact(staker_functions.commitToNextPeriod(), {'from': staker1}) transact_and_log(f"Make a commitment ({number_of_sub_stakes} sub-stakes)", staker_functions.commitToNextPeriod(), {'from': staker4}) testerchain.time_travel(periods=1) transact(staker_functions.commitToNextPeriod(), {'from': staker4}) testerchain.time_travel(periods=1) # Used to remove spending for first call in a period for mint and commitToNextPeriod transact(staker_functions.commitToNextPeriod(), {'from': staker1}) transact_and_log( f"Make a commitment + mint + re-stake ({number_of_sub_stakes} sub-stakes)", staker_functions.commitToNextPeriod(), {'from': staker4}) print("********* Estimates of migration *********") registry = InMemoryContractRegistry() deployer_power = TransactingPower(signer=Web3Signer(testerchain.client), account=testerchain.etherbase_account) def deploy_contract(contract_name, *args, **kwargs): return testerchain.deploy_contract(deployer_power, registry, contract_name, *args, **kwargs) token_economics = StandardTokenEconomics( genesis_hours_per_period=StandardTokenEconomics. _default_hours_per_period, hours_per_period=2 * StandardTokenEconomics._default_hours_per_period) token, _ = deploy_contract( 'NuCypherToken', _totalSupplyOfTokens=token_economics.erc20_total_supply) # Deploy Adjudicator mock adjudicator, _ = deploy_contract('AdjudicatorForStakingEscrowMock', token_economics.reward_coefficient) # Deploy old StakingEscrow contract deploy_args = token_economics.staking_deployment_parameters deploy_args = (deploy_args[0], *deploy_args[2:]) escrow_old_library, _ = deploy_contract( 'StakingEscrowOld', token.address, *deploy_args, False # testContract ) escrow_dispatcher, _ = deploy_contract('Dispatcher', escrow_old_library.address) escrow = testerchain.client.get_contract(abi=escrow_old_library.abi, address=escrow_dispatcher.address, ContractFactoryClass=Contract) # Deploy old PolicyManager contract policy_manager_old_library, _ = deploy_contract( contract_name='PolicyManagerOld', _escrow=escrow.address) policy_manager_dispatcher, _ = deploy_contract( 'Dispatcher', policy_manager_old_library.address) policy_manager = testerchain.client.get_contract( abi=policy_manager_old_library.abi, address=policy_manager_dispatcher.address, ContractFactoryClass=Contract) tx = adjudicator.functions.setStakingEscrow(escrow.address).transact() testerchain.wait_for_receipt(tx) tx = escrow.functions.setPolicyManager(policy_manager.address).transact() testerchain.wait_for_receipt(tx) tx = escrow.functions.setAdjudicator(adjudicator.address).transact() testerchain.wait_for_receipt(tx) # Initialize Escrow contract tx = token.functions.approve( escrow.address, token_economics.erc20_reward_supply).transact() testerchain.wait_for_receipt(tx) tx = escrow.functions.initialize(token_economics.erc20_reward_supply, testerchain.etherbase_account).transact() testerchain.wait_for_receipt(tx) # Prepare stakers stakers = (staker1, staker2, staker3, staker4) for staker in stakers: max_stake_size = token_economics.maximum_allowed_locked tx = token.functions.transfer(staker, max_stake_size).transact() testerchain.wait_for_receipt(tx) tx = token.functions.approve(escrow.address, max_stake_size).transact({'from': staker}) testerchain.wait_for_receipt(tx) sub_stakes_1 = 2 duration = token_economics.minimum_locked_periods stake_size = token_economics.minimum_allowed_locked for staker in (staker1, staker3): for i in range(1, sub_stakes_1 + 1): tx = escrow.functions.deposit(staker, stake_size, duration * i).transact({'from': staker}) testerchain.wait_for_receipt(tx) sub_stakes_2 = 24 for staker in (staker2, staker4): for i in range(1, sub_stakes_2 + 1): tx = escrow.functions.deposit(staker, stake_size, duration * i).transact({'from': staker}) testerchain.wait_for_receipt(tx) for staker in stakers: tx = escrow.functions.bondWorker(staker).transact({'from': staker}) testerchain.wait_for_receipt(tx) for i in range(duration): tx = escrow.functions.commitToNextPeriod().transact({'from': staker1}) testerchain.wait_for_receipt(tx) tx = escrow.functions.commitToNextPeriod().transact({'from': staker3}) testerchain.wait_for_receipt(tx) if i % 2 == 0: tx = escrow.functions.commitToNextPeriod().transact( {'from': staker2}) testerchain.wait_for_receipt(tx) tx = escrow.functions.commitToNextPeriod().transact( {'from': staker4}) testerchain.wait_for_receipt(tx) testerchain.time_travel( periods=1, periods_base=token_economics.genesis_seconds_per_period) ########## # Deploy new version of contracts ########## deploy_args = token_economics.staking_deployment_parameters escrow_library, _ = deploy_contract('StakingEscrow', token.address, policy_manager.address, adjudicator.address, NULL_ADDRESS, *deploy_args) escrow = testerchain.client.get_contract(abi=escrow_library.abi, address=escrow_dispatcher.address, ContractFactoryClass=Contract) policy_manager_library, _ = deploy_contract( contract_name='PolicyManager', _escrowDispatcher=escrow.address, _escrowImplementation=escrow_library.address) tx = escrow_dispatcher.functions.upgrade(escrow_library.address).transact() testerchain.wait_for_receipt(tx) tx = policy_manager_dispatcher.functions.upgrade( policy_manager_library.address).transact() testerchain.wait_for_receipt(tx) for staker in (staker1, staker2): downtime_length = escrow.functions.getPastDowntimeLength(staker).call() sub_stakes_length = escrow.functions.getSubStakesLength(staker).call() transact_and_log( f"Migrate with {sub_stakes_length} sub-stakes and {downtime_length} downtimes", escrow.functions.migrate(staker), {'from': staker}) downtime_length = escrow.functions.getPastDowntimeLength(staker).call() sub_stakes_length = escrow.functions.getSubStakesLength(staker).call() transact_and_log( f"Commit after migration with {sub_stakes_length} sub-stakes and {downtime_length} downtimes", escrow.functions.commitToNextPeriod(), {'from': staker}) for staker in (staker3, staker4): downtime_length = escrow.functions.getPastDowntimeLength(staker).call() sub_stakes_length = escrow.functions.getSubStakesLength(staker).call() transact_and_log( f"Commit together with migration with {sub_stakes_length} sub-stakes and {downtime_length} downtimes", escrow.functions.commitToNextPeriod(), {'from': staker}) transact_and_log(f"Dummy migrate call", escrow.functions.migrate(staker1), {'from': staker1}) print("********* All Done! *********")
class TesterBlockchain(BlockchainDeployerInterface): """ Blockchain subclass with additional test utility methods and options. """ __test__ = False # prohibit pytest from collecting this object as a test # Solidity SOURCES: List[SourceBundle] = [ SourceBundle(base_path=SOLIDITY_SOURCE_ROOT, other_paths=(TEST_SOLIDITY_SOURCE_ROOT, )) ] # Web3 GAS_STRATEGIES = { **BlockchainDeployerInterface.GAS_STRATEGIES, 'free': free_gas_price_strategy } PROVIDER_URI = PYEVM_DEV_URI DEFAULT_GAS_STRATEGY = 'free' # Reserved addresses _ETHERBASE = 0 _ALICE = 1 _BOB = 2 _FIRST_STAKER = 5 _FIRST_URSULA = _FIRST_STAKER + NUMBER_OF_STAKERS_IN_BLOCKCHAIN_TESTS # Internal __STAKERS_RANGE = range(NUMBER_OF_STAKERS_IN_BLOCKCHAIN_TESTS) __WORKERS_RANGE = range(NUMBER_OF_URSULAS_IN_BLOCKCHAIN_TESTS) __ACCOUNT_CACHE = list() # Defaults DEFAULT_ECONOMICS = StandardTokenEconomics() def __init__(self, test_accounts: int = NUMBER_OF_ETH_TEST_ACCOUNTS, poa: bool = True, light: bool = False, eth_airdrop: bool = False, free_transactions: bool = False, compile_now: bool = True, *args, **kwargs): self.free_transactions = free_transactions EXPECTED_CONFIRMATION_TIME_IN_SECONDS[ 'free'] = 5 # Just some upper-limit super().__init__(provider_uri=self.PROVIDER_URI, poa=poa, light=light, *args, **kwargs) self.log = Logger("test-blockchain") self.connect(compile_now=compile_now) # Generate additional ethereum accounts for testing population = test_accounts enough_accounts = len(self.client.accounts) >= population if not enough_accounts: accounts_to_make = population - len(self.client.accounts) self.__generate_insecure_unlocked_accounts( quantity=accounts_to_make) assert test_accounts == len(self.w3.eth.accounts) if eth_airdrop is True: # ETH for everyone! self.ether_airdrop(amount=DEVELOPMENT_ETH_AIRDROP_AMOUNT) def attach_middleware(self): if self.free_transactions: self.w3.eth.setGasPriceStrategy(free_gas_price_strategy) def __generate_insecure_unlocked_accounts(self, quantity: int) -> List[str]: # # Sanity Check - Only PyEVM can be used. # # Detect provider platform client_version = self.w3.clientVersion if 'Geth' in client_version: raise RuntimeError("WARNING: Geth providers are not implemented.") elif "Parity" in client_version: raise RuntimeError( "WARNING: Parity providers are not implemented.") addresses = list() for _ in range(quantity): address = self.provider.ethereum_tester.add_account( '0x' + os.urandom(32).hex()) addresses.append(address) self.__ACCOUNT_CACHE.append(address) self.log.info('Generated new insecure account {}'.format(address)) return addresses def ether_airdrop(self, amount: int) -> List[str]: """Airdrops ether from creator address to all other addresses!""" coinbase, *addresses = self.w3.eth.accounts tx_hashes = list() for address in addresses: tx = {'to': address, 'from': coinbase, 'value': amount} txhash = self.w3.eth.sendTransaction(tx) _receipt = self.wait_for_receipt(txhash) tx_hashes.append(txhash) eth_amount = Web3().fromWei(amount, 'ether') self.log.info("Airdropped {} ETH {} -> {}".format( eth_amount, tx['from'], tx['to'])) return tx_hashes def time_travel(self, hours: int = None, seconds: int = None, periods: int = None): """ Wait the specified number of wait_hours by comparing block timestamps and mines a single block. """ more_than_one_arg = sum(map(bool, (hours, seconds, periods))) > 1 if more_than_one_arg: raise ValueError( "Specify hours, seconds, or periods, not a combination") if periods: duration = self.DEFAULT_ECONOMICS.seconds_per_period * periods base = self.DEFAULT_ECONOMICS.seconds_per_period elif hours: duration = hours * (60 * 60) base = 60 * 60 elif seconds: duration = seconds base = 1 else: raise ValueError("Specify either hours, seconds, or periods.") now = self.w3.eth.getBlock('latest').timestamp end_timestamp = ((now + duration) // base) * base self.w3.eth.web3.testing.timeTravel(timestamp=end_timestamp) self.w3.eth.web3.testing.mine(1) delta = maya.timedelta(seconds=end_timestamp - now) self.log.info( f"Time traveled {delta} " f"| period {epoch_to_period(epoch=end_timestamp, seconds_per_period=self.DEFAULT_ECONOMICS.seconds_per_period)} " f"| epoch {end_timestamp}") @classmethod def bootstrap_network( cls, registry: Optional[BaseContractRegistry] = None, economics: BaseEconomics = None ) -> Tuple['TesterBlockchain', 'InMemoryContractRegistry']: """For use with metric testing scripts""" if registry is None: registry = InMemoryContractRegistry() testerchain = cls() if not BlockchainInterfaceFactory.is_interface_initialized( provider_uri=testerchain.provider_uri): BlockchainInterfaceFactory.register_interface( interface=testerchain) origin = testerchain.client.etherbase admin = ContractAdministrator(deployer_address=origin, registry=registry, signer=Web3Signer(testerchain.client), economics=economics or cls.DEFAULT_ECONOMICS) gas_limit = None # TODO: Gas management - #842 for deployer_class in admin.primary_deployer_classes: if deployer_class is StakingEscrowDeployer: admin.deploy_contract( contract_name=deployer_class.contract_name, gas_limit=gas_limit, deployment_mode=INIT) else: admin.deploy_contract( contract_name=deployer_class.contract_name, gas_limit=gas_limit) admin.deploy_contract( contract_name=StakingEscrowDeployer.contract_name, gas_limit=gas_limit) return testerchain, registry @property def etherbase_account(self): return self.client.accounts[self._ETHERBASE] @property def alice_account(self): return self.client.accounts[self._ALICE] @property def bob_account(self): return self.client.accounts[self._BOB] def ursula_account(self, index): if index not in self.__WORKERS_RANGE: raise ValueError( f"Ursula index must be lower than {NUMBER_OF_URSULAS_IN_BLOCKCHAIN_TESTS}" ) return self.client.accounts[index + self._FIRST_URSULA] def staker_account(self, index): if index not in self.__STAKERS_RANGE: raise ValueError( f"Staker index must be lower than {NUMBER_OF_STAKERS_IN_BLOCKCHAIN_TESTS}" ) return self.client.accounts[index + self._FIRST_STAKER] @property def ursulas_accounts(self): return list(self.ursula_account(i) for i in self.__WORKERS_RANGE) @property def stakers_accounts(self): return list(self.staker_account(i) for i in self.__STAKERS_RANGE) @property def unassigned_accounts(self): special_accounts = [ self.etherbase_account, self.alice_account, self.bob_account ] assigned_accounts = set(self.stakers_accounts + self.ursulas_accounts + special_accounts) accounts = set(self.client.accounts) return list(accounts.difference(assigned_accounts)) def wait_for_receipt(self, txhash: Union[bytes, str, HexBytes], timeout: int = None) -> dict: """Wait for a transaction receipt and return it""" timeout = timeout or self.TIMEOUT result = self.client.wait_for_receipt(transaction_hash=txhash, timeout=timeout) if result.status == 0: raise TransactionFailed() return result def get_block_number(self) -> int: return self.client.block_number def read_storage_slot(self, address, slot): # https://github.com/ethereum/web3.py/issues/1490 address = to_canonical_address(address) return self.client.w3.provider.ethereum_tester.backend.chain.get_vm( ).state.get_storage(address, slot)
def estimate_gas(analyzer: AnalyzeGas = None) -> None: """ Execute a linear sequence of NyCypher transactions mimicking post-deployment usage on a local PyEVM blockchain; Record the resulting estimated transaction gas expenditure. Note: The function calls below are *order dependant* """ # # Setup # if analyzer is None: analyzer = AnalyzeGas() log = Logger(AnalyzeGas.LOG_NAME) os.environ[ 'GAS_ESTIMATOR_BACKEND_FUNC'] = 'eth.estimators.gas.binary_gas_search_exact' # Blockchain economics = StandardTokenEconomics(base_penalty=MIN_ALLOWED_LOCKED - 1, penalty_history_coefficient=0, percentage_penalty_coefficient=2, reward_coefficient=2) testerchain, registry = TesterBlockchain.bootstrap_network( economics=economics) web3 = testerchain.w3 print("\n********* SIZE OF MAIN CONTRACTS *********") MAX_SIZE = 24576 rows = list() for contract_name in NUCYPHER_CONTRACT_NAMES: compiled_contract = testerchain._raw_contract_cache[contract_name] version = list(compiled_contract).pop() bin_runtime = compiled_contract[version]['bin-runtime'] bin_length_in_bytes = len(bin_runtime) // 2 percentage = int(100 * bin_length_in_bytes / MAX_SIZE) bar = ('*' * (percentage // 2)).ljust(50) rows.append( (contract_name, bin_length_in_bytes, f'{bar} {percentage}%')) headers = ('Contract', 'Size (B)', f'% of max allowed contract size ({MAX_SIZE} B)') print(tabulate.tabulate(rows, headers=headers, tablefmt="simple"), end="\n\n") # Accounts origin, ursula1, ursula2, ursula3, alice1, alice2, *everyone_else = testerchain.client.accounts ursula_with_stamp = mock_ursula(testerchain, ursula1) # Contracts token_agent = NucypherTokenAgent(registry=registry) staking_agent = StakingEscrowAgent(registry=registry) policy_agent = PolicyManagerAgent(registry=registry) adjudicator_agent = AdjudicatorAgent(registry=registry) # Contract Callers token_functions = token_agent.contract.functions staker_functions = staking_agent.contract.functions policy_functions = policy_agent.contract.functions adjudicator_functions = adjudicator_agent.contract.functions analyzer.start_collection() print("********* Estimating Gas *********") def transact_and_log(label, function, transaction): estimates = function.estimateGas(transaction) transaction.update(gas=estimates) tx = function.transact(transaction) receipt = testerchain.wait_for_receipt(tx) log.info(f"{label} = {estimates} | {receipt['gasUsed']}") def transact(function, transaction): transaction.update(gas=1000000) tx = function.transact(transaction) testerchain.wait_for_receipt(tx) # # Give Ursula and Alice some coins # transact_and_log( "Transfer tokens", token_functions.transfer(ursula1, MIN_ALLOWED_LOCKED * 10), {'from': origin}) transact(token_functions.transfer(ursula2, MIN_ALLOWED_LOCKED * 10), {'from': origin}) transact(token_functions.transfer(ursula3, MIN_ALLOWED_LOCKED * 10), {'from': origin}) # # Ursula and Alice give Escrow rights to transfer # transact_and_log( "Approving transfer", token_functions.approve(staking_agent.contract_address, MIN_ALLOWED_LOCKED * 6), {'from': ursula1}) transact( token_functions.approve(staking_agent.contract_address, MIN_ALLOWED_LOCKED * 6), {'from': ursula2}) transact( token_functions.approve(staking_agent.contract_address, MIN_ALLOWED_LOCKED * 6), {'from': ursula3}) # # Batch deposit tokens # transact( token_functions.approve(staking_agent.contract_address, MIN_ALLOWED_LOCKED * 10), {'from': origin}) transact_and_log( "Batch deposit tokens for 5 owners x 2 sub-stakes", staker_functions.batchDeposit(everyone_else[0:5], [2] * 5, [MIN_ALLOWED_LOCKED] * 10, [MIN_LOCKED_PERIODS] * 10), {'from': origin}) # # Ursula and Alice transfer some tokens to the escrow and lock them # transact_and_log( "Initial deposit tokens, first", staker_functions.deposit(MIN_ALLOWED_LOCKED * 3, MIN_LOCKED_PERIODS), {'from': ursula1}) transact_and_log( "Initial deposit tokens, other", staker_functions.deposit(MIN_ALLOWED_LOCKED * 3, MIN_LOCKED_PERIODS), {'from': ursula2}) transact( staker_functions.deposit(MIN_ALLOWED_LOCKED * 3, MIN_LOCKED_PERIODS), {'from': ursula3}) transact(staker_functions.setWorker(ursula1), {'from': ursula1}) transact(staker_functions.setWorker(ursula2), {'from': ursula2}) transact(staker_functions.setWorker(ursula3), {'from': ursula3}) transact(staker_functions.setReStake(False), {'from': ursula1}) transact(staker_functions.setReStake(False), {'from': ursula2}) transact(staker_functions.setWindDown(True), {'from': ursula1}) transact(staker_functions.setWindDown(True), {'from': ursula2}) transact(staker_functions.confirmActivity(), {'from': ursula1}) transact(staker_functions.confirmActivity(), {'from': ursula2}) # # Wait 1 period and confirm activity # testerchain.time_travel(periods=1) transact_and_log("Confirm activity, first", staker_functions.confirmActivity(), {'from': ursula1}) transact_and_log("Confirm activity, other", staker_functions.confirmActivity(), {'from': ursula2}) # # Wait 1 period and mint tokens # testerchain.time_travel(periods=1) transact_and_log("Mining (1 stake), first", staker_functions.mint(), {'from': ursula1}) transact_and_log("Mining (1 stake), other", staker_functions.mint(), {'from': ursula2}) transact_and_log("Confirm activity again, first", staker_functions.confirmActivity(), {'from': ursula1}) transact_and_log("Confirm activity again, other", staker_functions.confirmActivity(), {'from': ursula2}) transact(staker_functions.confirmActivity(), {'from': ursula3}) # # Confirm again # testerchain.time_travel(periods=1) transact_and_log("Confirm activity + mint, first", staker_functions.confirmActivity(), {'from': ursula1}) transact_and_log("Confirm activity + mint, other", staker_functions.confirmActivity(), {'from': ursula2}) # # Create policy # policy_id_1 = os.urandom(int(Policy.POLICY_ID_LENGTH)) policy_id_2 = os.urandom(int(Policy.POLICY_ID_LENGTH)) number_of_periods = 10 rate = 100 one_period = economics.hours_per_period * 60 * 60 value = number_of_periods * rate current_timestamp = testerchain.w3.eth.getBlock( block_identifier='latest').timestamp end_timestamp = current_timestamp + (number_of_periods - 1) * one_period transact_and_log( "Creating policy (1 node, 10 periods, pre-confirmed), first", policy_functions.createPolicy(policy_id_1, alice1, end_timestamp, [ursula1]), { 'from': alice1, 'value': value }) transact_and_log( "Creating policy (1 node, 10 periods, pre-confirmed), other", policy_functions.createPolicy(policy_id_2, alice1, end_timestamp, [ursula1]), { 'from': alice1, 'value': value }) # # Get locked tokens # transact_and_log("Getting locked tokens", staker_functions.getLockedTokens(ursula1, 0), {}) # # Wait 1 period and withdraw tokens # testerchain.time_travel(periods=1) transact_and_log("Withdraw", staker_functions.withdraw(1), {'from': ursula1}) # # Confirm activity with re-stake # transact(staker_functions.setReStake(True), {'from': ursula1}) transact(staker_functions.setReStake(True), {'from': ursula2}) # Used to remove spending for first call in a day for mint and confirmActivity transact(staker_functions.confirmActivity(), {'from': ursula3}) transact_and_log("Confirm activity + mint + re-stake", staker_functions.confirmActivity(), {'from': ursula2}) transact_and_log( "Confirm activity + mint + re-stake + first reward + first reward rate", staker_functions.confirmActivity(), {'from': ursula1}) transact(staker_functions.setReStake(False), {'from': ursula1}) transact(staker_functions.setReStake(False), {'from': ursula2}) # # Wait 2 periods and confirm activity after downtime # testerchain.time_travel(periods=2) transact(staker_functions.confirmActivity(), {'from': ursula3}) transact_and_log("Confirm activity after downtime", staker_functions.confirmActivity(), {'from': ursula2}) transact_and_log("Confirm activity after downtime + updating reward", staker_functions.confirmActivity(), {'from': ursula1}) # # Ursula and Alice deposit some tokens to the escrow again # transact_and_log( "Deposit tokens after confirming activity", staker_functions.deposit(MIN_ALLOWED_LOCKED * 2, MIN_LOCKED_PERIODS), {'from': ursula1}) transact( staker_functions.deposit(MIN_ALLOWED_LOCKED * 2, MIN_LOCKED_PERIODS), {'from': ursula2}) # # Revoke policy # transact_and_log("Revoking policy", policy_functions.revokePolicy(policy_id_1), {'from': alice1}) # # Wait 1 period # testerchain.time_travel(periods=1) # # Create policy with multiple pre-confirmed nodes # policy_id_1 = os.urandom(int(Policy.POLICY_ID_LENGTH)) policy_id_2 = os.urandom(int(Policy.POLICY_ID_LENGTH)) policy_id_3 = os.urandom(int(Policy.POLICY_ID_LENGTH)) number_of_periods = 100 value = 3 * number_of_periods * rate current_timestamp = testerchain.w3.eth.getBlock( block_identifier='latest').timestamp end_timestamp = current_timestamp + (number_of_periods - 1) * one_period transact_and_log( "Creating policy (3 nodes, 100 periods, pre-confirmed), first", policy_functions.createPolicy(policy_id_1, alice1, end_timestamp, [ursula1, ursula2, ursula3]), { 'from': alice1, 'value': value }) transact_and_log( "Creating policy (3 nodes, 100 periods, pre-confirmed), other", policy_functions.createPolicy(policy_id_2, alice1, end_timestamp, [ursula1, ursula2, ursula3]), { 'from': alice1, 'value': value }) value = 2 * number_of_periods * rate transact_and_log( "Creating policy (2 nodes, 100 periods, pre-confirmed), other", policy_functions.createPolicy(policy_id_3, alice1, end_timestamp, [ursula1, ursula2]), { 'from': alice1, 'value': value }) # # Wait 1 period and mint tokens # testerchain.time_travel(periods=1) transact(staker_functions.mint(), {'from': ursula3}) transact_and_log("Last mining + updating reward + updating reward rate", staker_functions.mint(), {'from': ursula1}) transact_and_log("Last mining + first reward + first reward rate", staker_functions.mint(), {'from': ursula2}) # # Create policy again without pre-confirmed nodes # policy_id_1 = os.urandom(int(Policy.POLICY_ID_LENGTH)) policy_id_2 = os.urandom(int(Policy.POLICY_ID_LENGTH)) policy_id_3 = os.urandom(int(Policy.POLICY_ID_LENGTH)) number_of_periods = 100 value = number_of_periods * rate current_timestamp = testerchain.w3.eth.getBlock( block_identifier='latest').timestamp end_timestamp = current_timestamp + (number_of_periods - 1) * one_period transact_and_log( "Creating policy (1 node, 100 periods)", policy_functions.createPolicy(policy_id_1, alice2, end_timestamp, [ursula2]), { 'from': alice1, 'value': value }) testerchain.time_travel(periods=1) current_timestamp = testerchain.w3.eth.getBlock( block_identifier='latest').timestamp end_timestamp = current_timestamp + (number_of_periods - 1) * one_period transact_and_log( "Creating policy (1 node, 100 periods), next period", policy_functions.createPolicy(policy_id_2, alice2, end_timestamp, [ursula2]), { 'from': alice1, 'value': value }) transact_and_log( "Creating policy (1 node, 100 periods), another node", policy_functions.createPolicy(policy_id_3, alice2, end_timestamp, [ursula1]), { 'from': alice1, 'value': value }) # # Mine and revoke policy # testerchain.time_travel(periods=10) transact(staker_functions.confirmActivity(), {'from': ursula1}) transact(staker_functions.confirmActivity(), {'from': ursula3}) testerchain.time_travel(periods=2) transact(staker_functions.mint(), {'from': ursula3}) transact_and_log("Last mining after downtime + updating reward", staker_functions.mint(), {'from': ursula1}) testerchain.time_travel(periods=10) transact_and_log("Revoking policy after downtime, 1st policy", policy_functions.revokePolicy(policy_id_1), {'from': alice2}) transact_and_log("Revoking policy after downtime, 2nd policy", policy_functions.revokePolicy(policy_id_2), {'from': alice2}) transact_and_log("Revoking policy after downtime, 3rd policy", policy_functions.revokePolicy(policy_id_3), {'from': alice2}) for index in range(5): transact(staker_functions.confirmActivity(), {'from': ursula1}) testerchain.time_travel(periods=1) transact(staker_functions.mint(), {'from': ursula1}) # # Check regular deposit # transact_and_log( "Deposit tokens", staker_functions.deposit(MIN_ALLOWED_LOCKED, MIN_LOCKED_PERIODS), {'from': ursula1}) # # ApproveAndCall # testerchain.time_travel(periods=1) transact(staker_functions.mint(), {'from': ursula1}) transact_and_log( "ApproveAndCall", token_functions.approveAndCall(staking_agent.contract_address, MIN_ALLOWED_LOCKED * 2, web3.toBytes(MIN_LOCKED_PERIODS)), {'from': ursula1}) # # Locking tokens # testerchain.time_travel(periods=1) transact(staker_functions.confirmActivity(), {'from': ursula1}) transact_and_log( "Locking tokens", staker_functions.lock(MIN_ALLOWED_LOCKED, MIN_LOCKED_PERIODS), {'from': ursula1}) # # Divide stake # transact_and_log("Divide stake", staker_functions.divideStake(1, MIN_ALLOWED_LOCKED, 2), {'from': ursula1}) transact(staker_functions.divideStake(3, MIN_ALLOWED_LOCKED, 2), {'from': ursula1}) # # Divide almost finished stake # testerchain.time_travel(periods=1) transact(staker_functions.confirmActivity(), {'from': ursula1}) testerchain.time_travel(periods=1) transact(staker_functions.confirmActivity(), {'from': ursula1}) # # Slashing tests # transact(staker_functions.confirmActivity(), {'from': ursula1}) testerchain.time_travel(periods=1) # # Slashing # slashing_args = generate_args_for_slashing(ursula_with_stamp) transact_and_log("Slash just value", adjudicator_functions.evaluateCFrag(*slashing_args), {'from': alice1}) deposit = staker_functions.stakerInfo(ursula1).call()[0] unlocked = deposit - staker_functions.getLockedTokens(ursula1, 0).call() transact(staker_functions.withdraw(unlocked), {'from': ursula1}) sub_stakes_length = str( staker_functions.getSubStakesLength(ursula1).call()) slashing_args = generate_args_for_slashing(ursula_with_stamp) transact_and_log( "Slashing one sub stake and saving old one (" + sub_stakes_length + " sub stakes), 1st", adjudicator_functions.evaluateCFrag(*slashing_args), {'from': alice1}) sub_stakes_length = str( staker_functions.getSubStakesLength(ursula1).call()) slashing_args = generate_args_for_slashing(ursula_with_stamp) transact_and_log( "Slashing one sub stake and saving old one (" + sub_stakes_length + " sub stakes), 2nd", adjudicator_functions.evaluateCFrag(*slashing_args), {'from': alice1}) sub_stakes_length = str( staker_functions.getSubStakesLength(ursula1).call()) slashing_args = generate_args_for_slashing(ursula_with_stamp) transact_and_log( "Slashing one sub stake and saving old one (" + sub_stakes_length + " sub stakes), 3rd", adjudicator_functions.evaluateCFrag(*slashing_args), {'from': alice1}) sub_stakes_length = str( staker_functions.getSubStakesLength(ursula1).call()) slashing_args = generate_args_for_slashing(ursula_with_stamp) transact_and_log( "Slashing two sub stakes and saving old one (" + sub_stakes_length + " sub stakes)", adjudicator_functions.evaluateCFrag(*slashing_args), {'from': alice1}) for index in range(18): transact(staker_functions.confirmActivity(), {'from': ursula1}) testerchain.time_travel(periods=1) transact(staker_functions.lock(MIN_ALLOWED_LOCKED, MIN_LOCKED_PERIODS), {'from': ursula1}) deposit = staker_functions.stakerInfo(ursula1).call()[0] unlocked = deposit - staker_functions.getLockedTokens(ursula1, 1).call() transact(staker_functions.withdraw(unlocked), {'from': ursula1}) sub_stakes_length = str( staker_functions.getSubStakesLength(ursula1).call()) slashing_args = generate_args_for_slashing(ursula_with_stamp) transact_and_log( "Slashing two sub stakes, shortest and new one (" + sub_stakes_length + " sub stakes)", adjudicator_functions.evaluateCFrag(*slashing_args), {'from': alice1}) sub_stakes_length = str( staker_functions.getSubStakesLength(ursula1).call()) slashing_args = generate_args_for_slashing(ursula_with_stamp) transact_and_log( "Slashing three sub stakes, two shortest and new one (" + sub_stakes_length + " sub stakes)", adjudicator_functions.evaluateCFrag(*slashing_args), {'from': alice1}) slashing_args = generate_args_for_slashing(ursula_with_stamp, corrupt_cfrag=False) transact_and_log("Evaluating correct CFrag", adjudicator_functions.evaluateCFrag(*slashing_args), {'from': alice1}) transact_and_log("Prolong stake", staker_functions.prolongStake(0, 20), {'from': ursula1}) print("********* All Done! *********")
def test_crawler_learn_about_nodes(new_influx_db, get_agent, get_economics, tempfile_path): mock_influxdb_client = new_influx_db.return_value mock_influxdb_client.write_points.return_value = True # TODO: issue with use of `agent.blockchain` causes spec=StakingEscrowAgent not to be specified in MagicMock # Get the following - AttributeError: Mock object has no attribute 'blockchain' staking_agent = MagicMock(autospec=True) contract_agency = MockContractAgency(staking_agent=staking_agent) get_agent.side_effect = contract_agency.get_agent token_economics = StandardTokenEconomics() get_economics.return_value = token_economics crawler = create_crawler(node_db_filepath=tempfile_path) node_db_client = CrawlerStorageClient(db_filepath=tempfile_path) try: crawler.start() assert crawler.is_running for i in range(0, 5): random_node = create_random_mock_node(generate_certificate=True) crawler.remember_node(node=random_node, record_fleet_state=True) known_nodes = node_db_client.get_known_nodes_metadata() assert len(known_nodes) > i assert random_node.checksum_address in known_nodes previous_states = node_db_client.get_previous_states_metadata() assert len(previous_states) > i # configure staking agent for blockchain calls tokens = NU(int(15000 + i * 2500), 'NU').to_nunits() current_period = datetime_to_period( maya.now(), token_economics.seconds_per_period) initial_period = current_period - i terminal_period = current_period + (i + 50) last_active_period = current_period - i staking_agent.get_worker_from_staker.side_effect = \ lambda staker_address: crawler.node_storage.get(federated_only=False, checksum_address=staker_address).worker_address configure_mock_staking_agent(staking_agent=staking_agent, tokens=tokens, current_period=current_period, initial_period=initial_period, terminal_period=terminal_period, last_active_period=last_active_period) # run crawler callable crawler._learn_about_nodes() # ensure data written to influx table mock_influxdb_client.write_points.assert_called_once() # expected db row added write_points_call_args_list = mock_influxdb_client.write_points.call_args_list influx_db_line_protocol_statement = str( write_points_call_args_list[0][0]) expected_arguments = [ f'staker_address={random_node.checksum_address}', f'worker_address="{random_node.worker_address}"', f'stake={float(NU.from_nunits(tokens).to_tokens())}', f'locked_stake={float(NU.from_nunits(tokens).to_tokens())}', f'current_period={current_period}i', f'last_confirmed_period={last_active_period}i', f'work_orders={len(random_node.work_orders())}i' ] for arg in expected_arguments: assert arg in influx_db_line_protocol_statement, \ f"{arg} in {influx_db_line_protocol_statement} for iteration {i}" mock_influxdb_client.reset_mock() finally: crawler.stop() mock_influxdb_client.close.assert_called_once() assert not crawler.is_running
def test_exact_economics(): """ Formula for staking in one period: (totalSupply - currentSupply) * (lockedValue / totalLockedValue) * (k1 + allLockedPeriods) / k2 K2 - Staking coefficient K1 - Locked periods coefficient if allLockedPeriods > awarded_periods then allLockedPeriods = awarded_periods kappa * log(2) / halving_delay === (k1 + allLockedPeriods) / k2 kappa = small_stake_multiplier + (1 - small_stake_multiplier) * min(T, T1) / T1 where allLockedPeriods == min(T, T1) """ # # Expected Output # # Supply expected_total_supply = 3885390081777926911255691439 expected_supply_ratio = Decimal('3.885390081777926911255691439') expected_initial_supply = 1000000000000000000000000000 # Reward expected_reward_supply = 2885390081777926911255691439 reward_saturation = 1 # Staking halving = 2 multiplier = 0.5 expected_locked_periods_coefficient = 365 expected_staking_coefficient = 768812 assert expected_locked_periods_coefficient * halving == round(expected_staking_coefficient * log(2) * multiplier / 365) # # Sanity # # Sanity check ratio accuracy expected_scaled_ratio = str(expected_supply_ratio).replace('.', '') assert str(expected_total_supply) == expected_scaled_ratio # Sanity check denomination size expected_scale = 28 assert len(str(expected_total_supply)) == expected_scale assert len(str(expected_initial_supply)) == expected_scale assert len(str(expected_reward_supply)) == expected_scale # Use same precision as economics class with localcontext() as ctx: ctx.prec = StandardTokenEconomics._precision # Sanity check expected testing outputs assert Decimal(expected_total_supply) / expected_initial_supply == expected_supply_ratio assert expected_reward_supply == expected_total_supply - expected_initial_supply assert reward_saturation * 365 * multiplier == expected_locked_periods_coefficient * (1 - multiplier) assert int(365 ** 2 * reward_saturation * halving / log(2) / (1-multiplier)) == expected_staking_coefficient # After sanity checking, assemble expected test deployment parameters expected_deployment_parameters = (24, # Hours in single period 768812, # Staking coefficient (k2) 365, # Locked periods coefficient (k1) 365, # Max periods that will be additionally rewarded (awarded_periods) 30, # Min amount of periods during which tokens can be locked 15000000000000000000000, # min locked NuNits 4000000000000000000000000, # max locked NuNits 2) # Min worker periods # # Token Economics # # Check creation e = StandardTokenEconomics() with localcontext() as ctx: ctx.prec = StandardTokenEconomics._precision # Check that total_supply calculated correctly assert Decimal(e.erc20_total_supply) / e.initial_supply == expected_supply_ratio assert e.erc20_total_supply == expected_total_supply # Check reward rates initial_rate = Decimal((e.erc20_total_supply - e.initial_supply) * (e.locked_periods_coefficient + 365) / e.staking_coefficient) assert initial_rate == Decimal((e.initial_inflation * e.initial_supply) / 365) initial_rate_small = (e.erc20_total_supply - e.initial_supply) * e.locked_periods_coefficient / e.staking_coefficient assert Decimal(initial_rate_small) == Decimal(initial_rate / 2) # Check reward supply assert Decimal(LOG2 / (e.token_halving * 365) * (e.erc20_total_supply - e.initial_supply)) == initial_rate assert e.reward_supply == expected_total_supply - expected_initial_supply # Check deployment parameters assert e.staking_deployment_parameters == expected_deployment_parameters assert e.erc20_initial_supply == expected_initial_supply assert e.erc20_reward_supply == expected_reward_supply # Additional checks on supply assert e.token_supply_at_period(period=0) == expected_initial_supply assert e.cumulative_rewards_at_period(0) == 0 # Last NuNit is mined after 184 years (or 67000 periods). # That's the year 2203, if token is launched in 2019. # 23rd century schizoid man! assert expected_total_supply == e.token_supply_at_period(period=67000) # After 1 year: assert 1_845_111_188_584347879497984668 == e.token_supply_at_period(period=365) assert 845_111_188_584347879497984668 == e.cumulative_rewards_at_period(365) assert e.erc20_initial_supply + e.cumulative_rewards_at_period(365) == e.token_supply_at_period(period=365) # Checking that the supply function is monotonic todays_supply = e.token_supply_at_period(period=0) for t in range(67000): tomorrows_supply = e.token_supply_at_period(period=t + 1) assert tomorrows_supply >= todays_supply todays_supply = tomorrows_supply
def new_token_economics(token_economics): economics = StandardTokenEconomics( genesis_hours_per_period=token_economics.hours_per_period, hours_per_period=2 * token_economics.hours_per_period) return economics
def test_exact_economics(): """ Formula for staking in one period: (totalSupply - currentSupply) * (lockedValue / totalLockedValue) * (k1 + allLockedPeriods) / d / k2 d - Coefficient which modifies the rate at which the maximum issuance decays k1 - Numerator of the locking duration coefficient k2 - Denominator of the locking duration coefficient if allLockedPeriods > awarded_periods then allLockedPeriods = awarded_periods kappa * log(2) / halving_delay === (k1 + allLockedPeriods) / d / k2 kappa = small_stake_multiplier + (1 - small_stake_multiplier) * min(T, T1) / T1 where allLockedPeriods == min(T, T1) """ # # Expected Output # # Supply expected_total_supply = 3885390081748248632541961138 expected_supply_ratio = Decimal('3.885390081748248632541961138') expected_initial_supply = 1000000000000000000000000000 expected_phase1_supply = 1829579800000000000000000000 # Reward expected_reward_supply = 2885390081748248632541961138 reward_saturation = 1 # Staking 2 phase decay_half_life = 2 multiplier = 0.5 expected_lock_duration_coefficient_1 = 365 expected_lock_duration_coefficient_2 = 2 * expected_lock_duration_coefficient_1 expected_phase2_coefficient = 1053 expected_minting_coefficient = expected_phase2_coefficient * expected_lock_duration_coefficient_2 assert expected_lock_duration_coefficient_1 * decay_half_life == round( expected_minting_coefficient * log(2) * multiplier / 365) # # Sanity # # Sanity check ratio accuracy expected_scaled_ratio = str(expected_supply_ratio).replace('.', '') assert str(expected_total_supply) == expected_scaled_ratio # Sanity check denomination size expected_scale = 28 assert len(str(expected_total_supply)) == expected_scale assert len(str(expected_initial_supply)) == expected_scale assert len(str(expected_reward_supply)) == expected_scale # Use same precision as economics class with localcontext() as ctx: ctx.prec = StandardTokenEconomics._precision # Sanity check expected testing outputs assert Decimal(expected_total_supply ) / expected_initial_supply == expected_supply_ratio assert expected_reward_supply == expected_total_supply - expected_initial_supply assert reward_saturation * 365 * multiplier == expected_lock_duration_coefficient_1 * ( 1 - multiplier) assert int(365 ** 2 * reward_saturation * decay_half_life / log(2) / (1-multiplier) / expected_lock_duration_coefficient_2) == \ expected_phase2_coefficient # After sanity checking, assemble expected test deployment parameters expected_deployment_parameters = ( 24, # Hours in single period 1053, # Coefficient which modifies the rate at which the maximum issuance decays (d) 365, # Numerator of the locking duration coefficient (k1) 730, # Denominator of the locking duration coefficient (k2) 365, # Max periods that will be additionally rewarded (awarded_periods) 2829579800000000000000000000, # Total supply for the first phase 1002509479452054794520547, # Max possible reward for one period for all stakers in the first phase 30, # Min amount of periods during which tokens can be locked 15000000000000000000000, # min locked NuNits 30000000000000000000000000, # max locked NuNits 2) # Min worker periods # # Token Economics # # Check creation e = StandardTokenEconomics() with localcontext() as ctx: ctx.prec = StandardTokenEconomics._precision # Check that total_supply calculated correctly assert Decimal( e.erc20_total_supply) / e.initial_supply == expected_supply_ratio assert e.erc20_total_supply == expected_total_supply # Check reward rates for the second phase initial_rate = (e.erc20_total_supply - int(e.first_phase_total_supply)) * (e.lock_duration_coefficient_1 + 365) / \ (e.issuance_decay_coefficient * e.lock_duration_coefficient_2) assert int(initial_rate) == int(e.first_phase_max_issuance) assert Decimal(LOG2 / (e.token_halving * 365) * (e.erc20_total_supply - int(e.first_phase_total_supply))) == initial_rate initial_rate_small = (e.erc20_total_supply - int(e.first_phase_total_supply)) * e.lock_duration_coefficient_1 / \ (e.issuance_decay_coefficient * e.lock_duration_coefficient_2) assert int(initial_rate_small) == int(initial_rate / 2) # Check reward supply assert e.reward_supply == expected_total_supply - expected_initial_supply # Check deployment parameters assert e.staking_deployment_parameters == expected_deployment_parameters assert e.erc20_initial_supply == expected_initial_supply assert e.erc20_reward_supply == expected_reward_supply # Additional checks on supply assert e.token_supply_at_period(period=0) == expected_initial_supply assert e.cumulative_rewards_at_period(0) == 0 # Check phase 1 doesn't overshoot switch_period = 5 * 365 assert e.first_phase_final_period() == switch_period assert e.token_supply_at_period( period=switch_period ) == expected_phase1_supply + expected_initial_supply assert e.token_supply_at_period( period=switch_period) < e.token_supply_at_period( period=switch_period + 1) assert e.rewards_during_period(period=1) == round( e.first_phase_max_issuance) assert e.rewards_during_period(period=switch_period) == round( e.first_phase_max_issuance) assert e.rewards_during_period(period=switch_period + 1) < int( e.first_phase_max_issuance) # Last NuNit is minted after 188 years (or 68500 periods). # That's the year 2208, if token is launched in 2020. # 23rd century schizoid man! assert expected_total_supply == e.token_supply_at_period(period=68500) # After 1 year: assert 1_365_915_960_000000000000000000 == e.token_supply_at_period( period=365) assert 365_915_960_000000000000000000 == e.cumulative_rewards_at_period( period=365) assert e.erc20_initial_supply + e.cumulative_rewards_at_period( 365) == e.token_supply_at_period(period=365) # Checking that the supply function is monotonic in phase 1 todays_supply = e.token_supply_at_period(period=0) for t in range(68500): tomorrows_supply = e.token_supply_at_period(period=t + 1) assert tomorrows_supply >= todays_supply todays_supply = tomorrows_supply