def acquire_agency(self) -> None: self.token_agent = NucypherTokenAgent(blockchain=self.blockchain) self.staking_agent = StakingEscrowAgent(blockchain=self.blockchain) self.policy_agent = PolicyManagerAgent(blockchain=self.blockchain) self.log.debug("Established connection to nucypher contracts")
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_ID_LENGTH)) policy_id_2 = os.urandom(int(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_ID_LENGTH)) policy_id_2 = os.urandom(int(POLICY_ID_LENGTH)) policy_id_3 = os.urandom(int(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_ID_LENGTH)) policy_id_2 = os.urandom(int(POLICY_ID_LENGTH)) policy_id_3 = os.urandom(int(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_ID_LENGTH)) policy_id_2 = os.urandom(int(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! *********")
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) # Blockchain testerchain = TesterBlockchain.bootstrap_network() web3 = testerchain.w3 # Accounts origin, ursula1, ursula2, ursula3, alice1, *everyone_else = testerchain.client.accounts ursula_with_stamp = mock_ursula(testerchain, ursula1) # Contracts token_agent = NucypherTokenAgent(blockchain=testerchain) staking_agent = StakingEscrowAgent(blockchain=testerchain) policy_agent = PolicyManagerAgent(blockchain=testerchain) adjudicator_agent = AdjudicatorAgent(blockchain=testerchain) # 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 *********") # # Give Ursula and Alice some coins # log.info("Transfer tokens = " + str( token_functions.transfer(ursula1, MIN_ALLOWED_LOCKED * 10).estimateGas({'from': origin}))) tx = token_functions.transfer(ursula1, MIN_ALLOWED_LOCKED * 10).transact({'from': origin}) testerchain.wait_for_receipt(tx) tx = token_functions.transfer(ursula2, MIN_ALLOWED_LOCKED * 10).transact({'from': origin}) testerchain.wait_for_receipt(tx) tx = token_functions.transfer(ursula3, MIN_ALLOWED_LOCKED * 10).transact({'from': origin}) testerchain.wait_for_receipt(tx) # # Ursula and Alice give Escrow rights to transfer # log.info("Approving transfer = " + str( token_functions.approve(staking_agent.contract_address, MIN_ALLOWED_LOCKED * 6).estimateGas({'from': ursula1}))) tx = token_functions.approve(staking_agent.contract_address, MIN_ALLOWED_LOCKED * 6).transact({'from': ursula1}) testerchain.wait_for_receipt(tx) tx = token_functions.approve(staking_agent.contract_address, MIN_ALLOWED_LOCKED * 6).transact({'from': ursula2}) testerchain.wait_for_receipt(tx) tx = token_functions.approve(staking_agent.contract_address, MIN_ALLOWED_LOCKED * 6).transact({'from': ursula3}) testerchain.wait_for_receipt(tx) # # Ursula and Alice transfer some tokens to the escrow and lock them # log.info("First initial deposit tokens = " + str(staker_functions.deposit(MIN_ALLOWED_LOCKED * 3, MIN_LOCKED_PERIODS).estimateGas({'from': ursula1}))) tx = staker_functions.deposit(MIN_ALLOWED_LOCKED * 3, MIN_LOCKED_PERIODS).transact({'from': ursula1}) testerchain.wait_for_receipt(tx) log.info("Second initial deposit tokens = " + str(staker_functions.deposit(MIN_ALLOWED_LOCKED * 3, MIN_LOCKED_PERIODS).estimateGas({'from': ursula2}))) tx = staker_functions.deposit(MIN_ALLOWED_LOCKED * 3, MIN_LOCKED_PERIODS).transact({'from': ursula2}) testerchain.wait_for_receipt(tx) log.info("Third initial deposit tokens = " + str(staker_functions.deposit(MIN_ALLOWED_LOCKED * 3, MIN_LOCKED_PERIODS).estimateGas({'from': ursula3}))) tx = staker_functions.deposit(MIN_ALLOWED_LOCKED * 3, MIN_LOCKED_PERIODS).transact({'from': ursula3}) testerchain.wait_for_receipt(tx) tx = staker_functions.setWorker(ursula1).transact({'from': ursula1}) testerchain.wait_for_receipt(tx) tx = staker_functions.setWorker(ursula2).transact({'from': ursula2}) testerchain.wait_for_receipt(tx) tx = staker_functions.setWorker(ursula3).transact({'from': ursula3}) testerchain.wait_for_receipt(tx) tx = staker_functions.confirmActivity().transact({'from': ursula1}) testerchain.wait_for_receipt(tx) tx = staker_functions.confirmActivity().transact({'from': ursula2}) testerchain.wait_for_receipt(tx) tx = staker_functions.confirmActivity().transact({'from': ursula3}) testerchain.wait_for_receipt(tx) # # Wait 1 period and confirm activity # testerchain.time_travel(periods=1) log.info("First confirm activity = " + str(staker_functions.confirmActivity().estimateGas({'from': ursula1}))) tx = staker_functions.confirmActivity().transact({'from': ursula1}) testerchain.wait_for_receipt(tx) log.info("Second confirm activity = " + str(staker_functions.confirmActivity().estimateGas({'from': ursula2}))) tx = staker_functions.confirmActivity().transact({'from': ursula2}) testerchain.wait_for_receipt(tx) log.info("Third confirm activity = " + str(staker_functions.confirmActivity().estimateGas({'from': ursula3}))) tx = staker_functions.confirmActivity().transact({'from': ursula3}) testerchain.wait_for_receipt(tx) # # Wait 1 period and mint tokens # testerchain.time_travel(periods=1) log.info("First mining (1 stake) = " + str(staker_functions.mint().estimateGas({'from': ursula1}))) tx = staker_functions.mint().transact({'from': ursula1}) testerchain.wait_for_receipt(tx) log.info("Second mining (1 stake) = " + str(staker_functions.mint().estimateGas({'from': ursula2}))) tx = staker_functions.mint().transact({'from': ursula2}) testerchain.wait_for_receipt(tx) log.info("Third/last mining (1 stake) = " + str(staker_functions.mint().estimateGas({'from': ursula3}))) tx = staker_functions.mint().transact({'from': ursula3}) testerchain.wait_for_receipt(tx) log.info("First confirm activity again = " + str(staker_functions.confirmActivity().estimateGas({'from': ursula1}))) tx = staker_functions.confirmActivity().transact({'from': ursula1}) testerchain.wait_for_receipt(tx) log.info("Second confirm activity again = " + str(staker_functions.confirmActivity().estimateGas({'from': ursula2}))) tx = staker_functions.confirmActivity().transact({'from': ursula2}) testerchain.wait_for_receipt(tx) log.info("Third confirm activity again = " + str(staker_functions.confirmActivity().estimateGas({'from': ursula3}))) tx = staker_functions.confirmActivity().transact({'from': ursula3}) testerchain.wait_for_receipt(tx) # # Confirm again # testerchain.time_travel(periods=1) log.info("First confirm activity + mint = " + str(staker_functions.confirmActivity().estimateGas({'from': ursula1}))) tx = staker_functions.confirmActivity().transact({'from': ursula1}) testerchain.wait_for_receipt(tx) log.info("Second confirm activity + mint = " + str(staker_functions.confirmActivity().estimateGas({'from': ursula2}))) tx = staker_functions.confirmActivity().transact({'from': ursula2}) testerchain.wait_for_receipt(tx) log.info("Third confirm activity + mint = " + str(staker_functions.confirmActivity().estimateGas({'from': ursula3}))) tx = staker_functions.confirmActivity().transact({'from': ursula3}) testerchain.wait_for_receipt(tx) # # Get locked tokens # log.info("Getting locked tokens = " + str(staker_functions.getLockedTokens(ursula1).estimateGas())) # # Wait 1 period and withdraw tokens # testerchain.time_travel(periods=1) log.info("First withdraw = " + str(staker_functions.withdraw(1).estimateGas({'from': ursula1}))) tx = staker_functions.withdraw(1).transact({'from': ursula1}) testerchain.wait_for_receipt(tx) log.info("Second withdraw = " + str(staker_functions.withdraw(1).estimateGas({'from': ursula2}))) tx = staker_functions.withdraw(1).transact({'from': ursula2}) testerchain.wait_for_receipt(tx) log.info("Third withdraw = " + str(staker_functions.withdraw(1).estimateGas({'from': ursula3}))) tx = staker_functions.withdraw(1).transact({'from': ursula3}) testerchain.wait_for_receipt(tx) # # Confirm activity with re-stake # tx = staker_functions.setReStake(True).transact({'from': ursula1}) testerchain.wait_for_receipt(tx) tx = staker_functions.setReStake(True).transact({'from': ursula2}) testerchain.wait_for_receipt(tx) tx = staker_functions.setReStake(True).transact({'from': ursula3}) testerchain.wait_for_receipt(tx) log.info("First confirm activity + mint with re-stake = " + str(staker_functions.confirmActivity().estimateGas({'from': ursula1}))) tx = staker_functions.confirmActivity().transact({'from': ursula1}) testerchain.wait_for_receipt(tx) log.info("Second confirm activity + mint with re-stake = " + str(staker_functions.confirmActivity().estimateGas({'from': ursula2}))) tx = staker_functions.confirmActivity().transact({'from': ursula2}) testerchain.wait_for_receipt(tx) log.info("Third confirm activity + mint with re-stake = " + str(staker_functions.confirmActivity().estimateGas({'from': ursula3}))) tx = staker_functions.confirmActivity().transact({'from': ursula3}) testerchain.wait_for_receipt(tx) tx = staker_functions.setReStake(False).transact({'from': ursula1}) testerchain.wait_for_receipt(tx) tx = staker_functions.setReStake(False).transact({'from': ursula2}) testerchain.wait_for_receipt(tx) tx = staker_functions.setReStake(False).transact({'from': ursula3}) testerchain.wait_for_receipt(tx) # # Wait 2 periods and confirm activity after downtime # testerchain.time_travel(periods=2) log.info("First confirm activity after downtime = " + str(staker_functions.confirmActivity().estimateGas({'from': ursula1}))) tx = staker_functions.confirmActivity().transact({'from': ursula1}) testerchain.wait_for_receipt(tx) log.info("Second confirm activity after downtime = " + str(staker_functions.confirmActivity().estimateGas({'from': ursula2}))) tx = staker_functions.confirmActivity().transact({'from': ursula2}) testerchain.wait_for_receipt(tx) log.info("Third confirm activity after downtime = " + str(staker_functions.confirmActivity().estimateGas({'from': ursula3}))) tx = staker_functions.confirmActivity().transact({'from': ursula3}) testerchain.wait_for_receipt(tx) # # Ursula and Alice deposit some tokens to the escrow again # log.info("First deposit tokens again = " + str(staker_functions.deposit(MIN_ALLOWED_LOCKED * 2, MIN_LOCKED_PERIODS).estimateGas({'from': ursula1}))) tx = staker_functions.deposit(MIN_ALLOWED_LOCKED * 2, MIN_LOCKED_PERIODS).transact({'from': ursula1}) testerchain.wait_for_receipt(tx) log.info("Second deposit tokens again = " + str(staker_functions.deposit(MIN_ALLOWED_LOCKED * 2, MIN_LOCKED_PERIODS).estimateGas({'from': ursula2}))) tx = staker_functions.deposit(MIN_ALLOWED_LOCKED * 2, MIN_LOCKED_PERIODS).transact({'from': ursula2}) testerchain.wait_for_receipt(tx) log.info("Third deposit tokens again = " + str(staker_functions.deposit(MIN_ALLOWED_LOCKED * 2, MIN_LOCKED_PERIODS).estimateGas({'from': ursula3}))) tx = staker_functions.deposit(MIN_ALLOWED_LOCKED * 2, MIN_LOCKED_PERIODS).transact({'from': ursula3}) testerchain.wait_for_receipt(tx) # # Wait 1 period and mint tokens # testerchain.time_travel(periods=1) log.info("First mining again = " + str(staker_functions.mint().estimateGas({'from': ursula1}))) tx = staker_functions.mint().transact({'from': ursula1}) testerchain.wait_for_receipt(tx) log.info("Second mining again = " + str(staker_functions.mint().estimateGas({'from': ursula2}))) tx = staker_functions.mint().transact({'from': ursula2}) testerchain.wait_for_receipt(tx) log.info("Third/last mining again = " + str(staker_functions.mint().estimateGas({'from': ursula3}))) tx = staker_functions.mint().transact({'from': ursula3}) testerchain.wait_for_receipt(tx) # # 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 log.info("First creating policy (1 node, 10 periods) = " + str(policy_functions.createPolicy(policy_id_1, number_of_periods, 0, [ursula1]).estimateGas( {'from': alice1, 'value': 10000}))) tx = policy_functions.createPolicy(policy_id_1, number_of_periods, 0, [ursula1]).transact( {'from': alice1, 'value': 10000}) testerchain.wait_for_receipt(tx) log.info("Second creating policy (1 node, 10 periods) = " + str(policy_functions.createPolicy(policy_id_2, number_of_periods, 0, [ursula1]).estimateGas( {'from': alice1, 'value': 10000}))) tx = policy_functions.createPolicy(policy_id_2, number_of_periods, 0, [ursula1]).transact( {'from': alice1, 'value': 10000}) testerchain.wait_for_receipt(tx) # # Revoke policy # log.info("Revoking policy = " + str(policy_functions.revokePolicy(policy_id_1).estimateGas({'from': alice1}))) tx = policy_functions.revokePolicy(policy_id_1).transact({'from': alice1}) testerchain.wait_for_receipt(tx) tx = policy_functions.revokePolicy(policy_id_2).transact({'from': alice1}) testerchain.wait_for_receipt(tx) # # Create policy with more periods # 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 log.info("First creating policy (1 node, " + str(number_of_periods) + " periods, first reward) = " + str(policy_functions.createPolicy(policy_id_1, number_of_periods, 50, [ursula2]).estimateGas( {'from': alice1, 'value': 10050}))) tx = policy_functions.createPolicy(policy_id_1, number_of_periods, 50, [ursula2]).transact( {'from': alice1, 'value': 10050}) testerchain.wait_for_receipt(tx) testerchain.time_travel(periods=1) log.info("Second creating policy (1 node, " + str(number_of_periods) + " periods, first reward) = " + str(policy_functions.createPolicy(policy_id_2, number_of_periods, 50, [ursula2]).estimateGas( {'from': alice1, 'value': 10050}))) tx = policy_functions.createPolicy(policy_id_2, number_of_periods, 50, [ursula2]).transact( {'from': alice1, 'value': 10050}) testerchain.wait_for_receipt(tx) log.info("Third creating policy (1 node, " + str(number_of_periods) + " periods, first reward) = " + str(policy_functions.createPolicy(policy_id_3, number_of_periods, 50, [ursula1]).estimateGas( {'from': alice1, 'value': 10050}))) tx = policy_functions.createPolicy(policy_id_3, number_of_periods, 50, [ursula1]).transact( {'from': alice1, 'value': 10050}) testerchain.wait_for_receipt(tx) # # Mine and revoke policy # testerchain.time_travel(periods=10) tx = staker_functions.confirmActivity().transact({'from': ursula2}) testerchain.wait_for_receipt(tx) tx = staker_functions.confirmActivity().transact({'from': ursula1}) testerchain.wait_for_receipt(tx) testerchain.time_travel(periods=1) log.info("First mining after downtime = " + str(staker_functions.mint().estimateGas({'from': ursula1}))) tx = staker_functions.mint().transact({'from': ursula1}) testerchain.wait_for_receipt(tx) log.info("Second mining after downtime = " + str(staker_functions.mint().estimateGas({'from': ursula2}))) tx = staker_functions.mint().transact({'from': ursula2}) testerchain.wait_for_receipt(tx) testerchain.time_travel(periods=10) log.info("First revoking policy after downtime = " + str(policy_functions.revokePolicy(policy_id_1).estimateGas({'from': alice1}))) tx = policy_functions.revokePolicy(policy_id_1).transact({'from': alice1}) testerchain.wait_for_receipt(tx) log.info("Second revoking policy after downtime = " + str(policy_functions.revokePolicy(policy_id_2).estimateGas({'from': alice1}))) tx = policy_functions.revokePolicy(policy_id_2).transact({'from': alice1}) testerchain.wait_for_receipt(tx) log.info("Second revoking policy after downtime = " + str(policy_functions.revokePolicy(policy_id_3).estimateGas({'from': alice1}))) tx = policy_functions.revokePolicy(policy_id_3).transact({'from': alice1}) testerchain.wait_for_receipt(tx) # # Create policy with multiple 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 log.info("First creating policy (3 nodes, 100 periods, first reward) = " + str(policy_functions .createPolicy(policy_id_1, number_of_periods, 50, [ursula1, ursula2, ursula3]) .estimateGas({'from': alice1, 'value': 30150}))) tx = policy_functions.createPolicy(policy_id_1, number_of_periods, 50, [ursula1, ursula2, ursula3]).transact( {'from': alice1, 'value': 30150}) testerchain.wait_for_receipt(tx) log.info("Second creating policy (3 nodes, 100 periods, first reward) = " + str(policy_functions .createPolicy(policy_id_2, number_of_periods, 50, [ursula1, ursula2, ursula3]) .estimateGas({'from': alice1, 'value': 30150}))) tx = policy_functions.createPolicy(policy_id_2, number_of_periods, 50, [ursula1, ursula2, ursula3]).transact( {'from': alice1, 'value': 30150}) testerchain.wait_for_receipt(tx) log.info("Third creating policy (2 nodes, 100 periods, first reward) = " + str(policy_functions.createPolicy(policy_id_3, number_of_periods, 50, [ursula1, ursula2]).estimateGas( {'from': alice1, 'value': 20100}))) tx = policy_functions.createPolicy(policy_id_3, number_of_periods, 50, [ursula1, ursula2]).transact( {'from': alice1, 'value': 20100}) testerchain.wait_for_receipt(tx) for index in range(5): tx = staker_functions.confirmActivity().transact({'from': ursula1}) testerchain.wait_for_receipt(tx) tx = staker_functions.confirmActivity().transact({'from': ursula2}) testerchain.wait_for_receipt(tx) tx = staker_functions.confirmActivity().transact({'from': ursula3}) testerchain.wait_for_receipt(tx) testerchain.time_travel(periods=1) tx = staker_functions.mint().transact({'from': ursula1}) testerchain.wait_for_receipt(tx) tx = staker_functions.mint().transact({'from': ursula2}) testerchain.wait_for_receipt(tx) tx = staker_functions.mint().transact({'from': ursula3}) testerchain.wait_for_receipt(tx) # # Check regular deposit # log.info("First deposit tokens = " + str( staker_functions.deposit(MIN_ALLOWED_LOCKED, MIN_LOCKED_PERIODS).estimateGas({'from': ursula1}))) tx = staker_functions.deposit(MIN_ALLOWED_LOCKED, MIN_LOCKED_PERIODS).transact({'from': ursula1}) testerchain.wait_for_receipt(tx) log.info("Second deposit tokens = " + str( staker_functions.deposit(MIN_ALLOWED_LOCKED, MIN_LOCKED_PERIODS).estimateGas({'from': ursula2}))) tx = staker_functions.deposit(MIN_ALLOWED_LOCKED, MIN_LOCKED_PERIODS).transact({'from': ursula2}) testerchain.wait_for_receipt(tx) log.info("Third deposit tokens = " + str( staker_functions.deposit(MIN_ALLOWED_LOCKED, MIN_LOCKED_PERIODS).estimateGas({'from': ursula3}))) tx = staker_functions.deposit(MIN_ALLOWED_LOCKED, MIN_LOCKED_PERIODS).transact({'from': ursula3}) testerchain.wait_for_receipt(tx) # # ApproveAndCall # testerchain.time_travel(periods=1) tx = staker_functions.mint().transact({'from': ursula1}) testerchain.wait_for_receipt(tx) tx = staker_functions.mint().transact({'from': ursula2}) testerchain.wait_for_receipt(tx) tx = staker_functions.mint().transact({'from': ursula3}) testerchain.wait_for_receipt(tx) log.info("First approveAndCall = " + str(token_functions.approveAndCall(staking_agent.contract_address, MIN_ALLOWED_LOCKED * 2, web3.toBytes(MIN_LOCKED_PERIODS)).estimateGas({'from': ursula1}))) tx = token_functions.approveAndCall(staking_agent.contract_address, MIN_ALLOWED_LOCKED * 2, web3.toBytes(MIN_LOCKED_PERIODS)).transact({'from': ursula1}) testerchain.wait_for_receipt(tx) log.info("Second approveAndCall = " + str(token_functions.approveAndCall(staking_agent.contract_address, MIN_ALLOWED_LOCKED * 2, web3.toBytes(MIN_LOCKED_PERIODS)).estimateGas({'from': ursula2}))) tx = token_functions.approveAndCall(staking_agent.contract_address, MIN_ALLOWED_LOCKED * 2, web3.toBytes(MIN_LOCKED_PERIODS)).transact({'from': ursula2}) testerchain.wait_for_receipt(tx) log.info("Third approveAndCall = " + str(token_functions.approveAndCall(staking_agent.contract_address, MIN_ALLOWED_LOCKED * 2, web3.toBytes(MIN_LOCKED_PERIODS)).estimateGas({'from': ursula3}))) tx = token_functions.approveAndCall(staking_agent.contract_address, MIN_ALLOWED_LOCKED * 2, web3.toBytes(MIN_LOCKED_PERIODS)).transact({'from': ursula3}) testerchain.wait_for_receipt(tx) # # Locking tokens # testerchain.time_travel(periods=1) tx = staker_functions.confirmActivity().transact({'from': ursula1}) testerchain.wait_for_receipt(tx) tx = staker_functions.confirmActivity().transact({'from': ursula2}) testerchain.wait_for_receipt(tx) tx = staker_functions.confirmActivity().transact({'from': ursula3}) testerchain.wait_for_receipt(tx) log.info("First locking tokens = " + str(staker_functions.lock(MIN_ALLOWED_LOCKED, MIN_LOCKED_PERIODS).estimateGas({'from': ursula1}))) tx = staker_functions.lock(MIN_ALLOWED_LOCKED, MIN_LOCKED_PERIODS).transact({'from': ursula1}) testerchain.wait_for_receipt(tx) log.info("Second locking tokens = " + str(staker_functions.lock(MIN_ALLOWED_LOCKED, MIN_LOCKED_PERIODS).estimateGas({'from': ursula2}))) tx = staker_functions.lock(MIN_ALLOWED_LOCKED, MIN_LOCKED_PERIODS).transact({'from': ursula2}) testerchain.wait_for_receipt(tx) log.info("Third locking tokens = " + str(staker_functions.lock(MIN_ALLOWED_LOCKED, MIN_LOCKED_PERIODS).estimateGas({'from': ursula3}))) tx = staker_functions.lock(MIN_ALLOWED_LOCKED, MIN_LOCKED_PERIODS).transact({'from': ursula3}) testerchain.wait_for_receipt(tx) # # Divide stake # log.info("First divide stake = " + str( staker_functions.divideStake(1, MIN_ALLOWED_LOCKED, 2).estimateGas({'from': ursula1}))) tx = staker_functions.divideStake(1, MIN_ALLOWED_LOCKED, 2).transact({'from': ursula1}) testerchain.wait_for_receipt(tx) log.info("Second divide stake = " + str( staker_functions.divideStake(3, MIN_ALLOWED_LOCKED, 2).estimateGas({'from': ursula1}))) tx = staker_functions.divideStake(3, MIN_ALLOWED_LOCKED, 2).transact({'from': ursula1}) testerchain.wait_for_receipt(tx) # # Divide almost finished stake # testerchain.time_travel(periods=1) tx = staker_functions.confirmActivity().transact({'from': ursula1}) testerchain.wait_for_receipt(tx) testerchain.time_travel(periods=1) log.info("Divide stake (next period is not confirmed) = " + str( staker_functions.divideStake(0, MIN_ALLOWED_LOCKED, 2).estimateGas({'from': ursula1}))) tx = staker_functions.confirmActivity().transact({'from': ursula1}) testerchain.wait_for_receipt(tx) log.info("Divide stake (next period is confirmed) = " + str( staker_functions.divideStake(0, MIN_ALLOWED_LOCKED, 2).estimateGas({'from': ursula1}))) # # Slashing tests # tx = staker_functions.confirmActivity().transact({'from': ursula1}) testerchain.wait_for_receipt(tx) testerchain.time_travel(periods=1) # # Slashing # slashing_args = generate_args_for_slashing(ursula_with_stamp) log.info("Slash just value = " + str( adjudicator_functions.evaluateCFrag(*slashing_args).estimateGas({'from': alice1}))) tx = adjudicator_functions.evaluateCFrag(*slashing_args).transact({'from': alice1}) testerchain.wait_for_receipt(tx) deposit = staker_functions.stakerInfo(ursula1).call()[0] unlocked = deposit - staker_functions.getLockedTokens(ursula1).call() tx = staker_functions.withdraw(unlocked).transact({'from': ursula1}) testerchain.wait_for_receipt(tx) sub_stakes_length = str(staker_functions.getSubStakesLength(ursula1).call()) slashing_args = generate_args_for_slashing(ursula_with_stamp) log.info("First slashing one sub stake and saving old one (" + sub_stakes_length + " sub stakes) = " + str(adjudicator_functions.evaluateCFrag(*slashing_args).estimateGas({'from': alice1}))) tx = adjudicator_functions.evaluateCFrag(*slashing_args).transact({'from': alice1}) testerchain.wait_for_receipt(tx) sub_stakes_length = str(staker_functions.getSubStakesLength(ursula1).call()) slashing_args = generate_args_for_slashing(ursula_with_stamp) log.info("Second slashing one sub stake and saving old one (" + sub_stakes_length + " sub stakes) = " + str(adjudicator_functions.evaluateCFrag(*slashing_args).estimateGas({'from': alice1}))) tx = adjudicator_functions.evaluateCFrag(*slashing_args).transact({'from': alice1}) testerchain.wait_for_receipt(tx) sub_stakes_length = str(staker_functions.getSubStakesLength(ursula1).call()) slashing_args = generate_args_for_slashing(ursula_with_stamp) log.info("Third slashing one sub stake and saving old one (" + sub_stakes_length + " sub stakes) = " + str(adjudicator_functions.evaluateCFrag(*slashing_args).estimateGas({'from': alice1}))) tx = adjudicator_functions.evaluateCFrag(*slashing_args).transact({'from': alice1}) testerchain.wait_for_receipt(tx) sub_stakes_length = str(staker_functions.getSubStakesLength(ursula1).call()) slashing_args = generate_args_for_slashing(ursula_with_stamp) log.info("Slashing two sub stakes and saving old one (" + sub_stakes_length + " sub stakes) = " + str(adjudicator_functions.evaluateCFrag(*slashing_args).estimateGas({'from': alice1}))) tx = adjudicator_functions.evaluateCFrag(*slashing_args).transact({'from': alice1}) testerchain.wait_for_receipt(tx) for index in range(18): tx = staker_functions.confirmActivity().transact({'from': ursula1}) testerchain.wait_for_receipt(tx) testerchain.time_travel(periods=1) tx = staker_functions.lock(MIN_ALLOWED_LOCKED, MIN_LOCKED_PERIODS).transact({'from': ursula1}) testerchain.wait_for_receipt(tx) deposit = staker_functions.stakerInfo(ursula1).call()[0] unlocked = deposit - staker_functions.getLockedTokens(ursula1, 1).call() tx = staker_functions.withdraw(unlocked).transact({'from': ursula1}) testerchain.wait_for_receipt(tx) sub_stakes_length = str(staker_functions.getSubStakesLength(ursula1).call()) slashing_args = generate_args_for_slashing(ursula_with_stamp) log.info("Slashing two sub stakes, shortest and new one (" + sub_stakes_length + " sub stakes) = " + str(adjudicator_functions.evaluateCFrag(*slashing_args).estimateGas({'from': alice1}))) tx = adjudicator_functions.evaluateCFrag(*slashing_args).transact({'from': alice1}) testerchain.wait_for_receipt(tx) sub_stakes_length = str(staker_functions.getSubStakesLength(ursula1).call()) slashing_args = generate_args_for_slashing(ursula_with_stamp) log.info("Slashing three sub stakes, two shortest and new one (" + sub_stakes_length + " sub stakes) = " + str(adjudicator_functions.evaluateCFrag(*slashing_args).estimateGas({'from': alice1}))) tx = adjudicator_functions.evaluateCFrag(*slashing_args).transact({'from': alice1}) testerchain.wait_for_receipt(tx) slashing_args = generate_args_for_slashing(ursula_with_stamp, corrupt_cfrag=False) log.info("Evaluating correct CFrag = " + str(adjudicator_functions.evaluateCFrag(*slashing_args).estimateGas({'from': alice1}))) tx = adjudicator_functions.evaluateCFrag(*slashing_args).transact({'from': alice1}) testerchain.wait_for_receipt(tx) print("********* All Done! *********")
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, staker1, staker2, staker3, 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) # # 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}) # # 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}) transact(token_functions.approve(staking_agent.contract_address, MIN_ALLOWED_LOCKED * 24), {'from': origin}) transact_and_log("Batch deposit tokens for 1 owners x 24 sub-stakes", staker_functions.batchDeposit([everyone_else[6]], [24], [MIN_ALLOWED_LOCKED] * 24, [MIN_LOCKED_PERIODS] * 24), {'from': origin}) transact(token_functions.approve(staking_agent.contract_address, MIN_ALLOWED_LOCKED * 24 * 5), {'from': origin}) transact_and_log("Batch deposit tokens for 5 owners x 24 sub-stakes", staker_functions.batchDeposit(everyone_else[7:12], [24]*5, [MIN_ALLOWED_LOCKED] * (24 * 5), [MIN_LOCKED_PERIODS] * (24 * 5)), {'from': origin}) # # 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, MIN_LOCKED_PERIODS), {'from': staker1}) transact_and_log("Initial deposit tokens, other", staker_functions.deposit(staker2, MIN_ALLOWED_LOCKED * 3, MIN_LOCKED_PERIODS), {'from': staker2}) transact(staker_functions.deposit(staker3, MIN_ALLOWED_LOCKED * 3, MIN_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(block_identifier='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 day 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, MIN_LOCKED_PERIODS), {'from': staker1}) transact(staker_functions.deposit(staker2, MIN_ALLOWED_LOCKED * 2, MIN_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(block_identifier='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(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, [staker2]), {'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, [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}) for index in range(5): 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, MIN_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(MIN_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, MIN_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}) # # Slashing tests # transact(staker_functions.commitToNextPeriod(), {'from': staker1}) 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(staker1).call()[0] unlocked = deposit - staker_functions.getLockedTokens(staker1, 0).call() transact(staker_functions.withdraw(unlocked), {'from': staker1}) sub_stakes_length = str(staker_functions.getSubStakesLength(staker1).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(staker1).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(staker1).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(staker1).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.commitToNextPeriod(), {'from': staker1}) testerchain.time_travel(periods=1) transact(staker_functions.lockAndCreate(MIN_ALLOWED_LOCKED, MIN_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}) sub_stakes_length = str(staker_functions.getSubStakesLength(staker1).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(staker1).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': staker1}) transact_and_log("Merge sub-stakes", staker_functions.mergeStake(2, 3), {'from': staker1}) print("********* All Done! *********")
class Worker(NucypherTokenActor): """ Ursula baseclass for blockchain operations, practically carrying a pickaxe. """ class WorkerError(NucypherTokenActor.ActorError): pass class DetachedWorker(WorkerError): """Raised when the worker address is not assigned an on-chain stake in the StakingEscrow contract.""" def __init__(self, is_me: bool, stake_tracker: StakeTracker = None, worker_address: str = None, start_working_loop: bool = True, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self.log = Logger("worker") self.__worker_address = worker_address self.is_me = is_me # Agency self.token_agent = NucypherTokenAgent(blockchain=self.blockchain) self.staking_agent = StakingEscrowAgent(blockchain=self.blockchain) # Stakes self.__start_time = WORKER_NOT_RUNNING self.__uptime_period = WORKER_NOT_RUNNING # Workers cannot be started without being assigned a stake first. if is_me: self.stake_tracker = stake_tracker or StakeTracker( checksum_addresses=[self.checksum_address]) if not self.stake_tracker.stakes( checksum_address=self.checksum_address): raise self.DetachedWorker else: self.stake_tracker.add_action(self._confirm_period) if start_working_loop: self.stake_tracker.start() @property def last_active_period(self) -> int: period = self.staking_agent.get_last_active_period( address=self.checksum_address) return period @only_me @save_receipt def confirm_activity(self) -> str: """For each period that the worker confirms activity, the staker is rewarded""" receipt = self.staking_agent.confirm_activity( worker_address=self.__worker_address) return receipt @only_me def _confirm_period(self) -> None: # TODO: Follow-up actions for downtime # TODO: Check for stake expiration and exit missed_periods = self.stake_tracker.current_period - self.last_active_period if missed_periods: self.log.warn( f"MISSED CONFIRMATIONS - {missed_periods} missed staking confirmations detected!" ) self.confirm_activity() # < --- blockchain WRITE self.log.info("Confirmed activity for period {}".format( self.stake_tracker.current_period))
def test_sampling_distribution(testerchain, token, deploy_contract, token_economics): # # SETUP # policy_manager, _ = deploy_contract('PolicyManagerForStakingEscrowMock', NULL_ADDRESS, token_economics.seconds_per_period) adjudicator, _ = deploy_contract('AdjudicatorForStakingEscrowMock', token_economics.reward_coefficient) staking_escrow_contract, _ = deploy_contract( STAKING_ESCROW_CONTRACT_NAME, token.address, policy_manager.address, adjudicator.address, NULL_ADDRESS, *token_economics.staking_deployment_parameters) staking_agent = StakingEscrowAgent(registry=None, contract=staking_escrow_contract) # Travel to the start of the next period to prevent problems with unexpected overflow first period testerchain.time_travel(hours=1) creator = testerchain.etherbase_account # Give Escrow tokens for reward and initialize contract tx = token.functions.approve(staking_escrow_contract.address, 10**9).transact({'from': creator}) testerchain.wait_for_receipt(tx) tx = staking_escrow_contract.functions.initialize(10**9, creator).transact( {'from': creator}) testerchain.wait_for_receipt(tx) stakers = testerchain.stakers_accounts amount = token.functions.balanceOf(creator).call() // len(stakers) # Airdrop for staker in stakers: tx = token.functions.transfer(staker, amount).transact({'from': creator}) testerchain.wait_for_receipt(tx) all_locked_tokens = len(stakers) * amount for staker in stakers: balance = token.functions.balanceOf(staker).call() tx = token.functions.approve(staking_escrow_contract.address, balance).transact({'from': staker}) testerchain.wait_for_receipt(tx) staker_power = TransactingPower(account=staker, signer=Web3Signer(testerchain.client)) staking_agent.deposit_tokens(amount=balance, lock_periods=10, transacting_power=staker_power) staking_agent.bond_worker(transacting_power=staker_power, worker_address=staker) staking_agent.commit_to_next_period(transacting_power=staker_power) # Wait next period and check all locked tokens testerchain.time_travel(hours=1) # # Test sampling distribution # ERROR_TOLERANCE = 0.05 # With this tolerance, all sampling ratios should between 5% and 15% (expected is 10%) SAMPLES = 1000 quantity = 3 counter = Counter() sampled, failed = 0, 0 while sampled < SAMPLES: try: reservoir = staking_agent.get_stakers_reservoir(duration=1) addresses = set(reservoir.draw(quantity)) addresses.discard(NULL_ADDRESS) except staking_agent.NotEnoughStakers: failed += 1 continue else: sampled += 1 counter.update(addresses) total_times = sum(counter.values()) expected = amount / all_locked_tokens for staker in stakers: times = counter[staker] sampled_ratio = times / total_times abs_error = abs(expected - sampled_ratio) assert abs_error < ERROR_TOLERANCE
class StakeHolder(BaseConfiguration): _NAME = 'stakeholder' TRANSACTION_GAS = {} class NoFundingAccount(BaseConfiguration.ConfigurationError): pass class NoStakes(BaseConfiguration.ConfigurationError): pass def __init__(self, blockchain: BlockchainInterface, sync_now: bool = True, *args, **kwargs): super().__init__(*args, **kwargs) self.log = Logger(f"stakeholder") # Blockchain and Contract connection self.blockchain = blockchain self.staking_agent = StakingEscrowAgent(blockchain=blockchain) self.token_agent = NucypherTokenAgent(blockchain=blockchain) self.economics = TokenEconomics() # Mode self.connect(blockchain=blockchain) self.__accounts = list() self.__stakers = dict() self.__transacting_powers = dict() self.__get_accounts() if sync_now: self.read_onchain_stakes() # Stakes # # Configuration # def static_payload(self) -> dict: """Values to read/write from stakeholder JSON configuration files""" payload = dict(provider_uri=self.blockchain.provider_uri, blockchain=self.blockchain.to_dict(), accounts=self.__accounts, stakers=self.__serialize_stakers()) return payload @classmethod def from_configuration_file(cls, filepath: str = None, sync_now: bool = True, **overrides) -> 'StakeHolder': filepath = filepath or cls.default_filepath() payload = cls._read_configuration_file(filepath=filepath) # Sub config blockchain_payload = payload.pop('blockchain') blockchain = BlockchainInterface.from_dict(payload=blockchain_payload) blockchain.connect(sync_now=sync_now) # TODO: Leave this here? payload.update(dict(blockchain=blockchain)) payload.update(overrides) instance = cls(filepath=filepath, **payload) return instance @validate_checksum_address def attach_transacting_power(self, checksum_address: str, password: str = None) -> None: try: transacting_power = self.__transacting_powers[checksum_address] except KeyError: transacting_power = TransactingPower(blockchain=self.blockchain, password=password, account=checksum_address) self.__transacting_powers[checksum_address] = transacting_power transacting_power.activate(password=password) def to_configuration_file(self, *args, **kwargs) -> str: filepath = super().to_configuration_file(*args, **kwargs) return filepath def connect(self, blockchain: BlockchainInterface = None) -> None: """Go Online""" if not self.staking_agent: self.staking_agent = StakingEscrowAgent(blockchain=blockchain) if not self.token_agent: self.token_agent = NucypherTokenAgent(blockchain=blockchain) self.blockchain = self.token_agent.blockchain # # Account Utilities # @property def accounts(self) -> list: return self.__accounts def __get_accounts(self) -> None: accounts = self.blockchain.client.accounts self.__accounts.extend(accounts) # # Staking Utilities # def read_onchain_stakes(self, account: str = None) -> None: if account: accounts = [account] else: accounts = self.__accounts for account in accounts: stakes = list( self.staking_agent.get_all_stakes(staker_address=account)) if stakes: staker = Staker(is_me=True, checksum_address=account, blockchain=self.blockchain) self.__stakers[account] = staker @property def total_stake(self) -> NU: total = sum(staker.locked_tokens() for staker in self.stakers) return total @property def stakers(self) -> List[Staker]: return list(self.__stakers.values()) @property def stakes(self) -> list: payload = list() for staker in self.__stakers.values(): payload.extend(staker.stakes) return payload @property def account_balances(self) -> dict: balances = dict() for account in self.__accounts: funds = { 'ETH': self.blockchain.client.get_balance(account), 'NU': self.token_agent.get_balance(account) } balances.update({account: funds}) return balances @property def staker_balances(self) -> dict: balances = dict() for staker in self.stakers: staker_funds = { 'ETH': staker.eth_balance, 'NU': staker.token_balance } balances[staker.checksum_address] = { staker.checksum_address: staker_funds } return balances def __serialize_stakers(self) -> list: payload = list() for staker in self.stakers: payload.append(staker.to_dict()) return payload def get_active_staker(self, address: str) -> Staker: self.read_onchain_stakes(account=address) try: return self.__stakers[address] except KeyError: raise self.NoStakes(f"{address} does not have any stakes.") def create_worker_configuration(self, staking_address: str, worker_address: str, password: str, **configuration): """Generates a worker JSON configuration file for a given staking address.""" from nucypher.config.characters import UrsulaConfiguration worker_configuration = UrsulaConfiguration.generate( checksum_address=staking_address, worker_address=worker_address, password=password, config_root=self.config_root, federated_only=False, provider_uri=self.blockchain.provider_uri, **configuration) return worker_configuration # # Actions # def set_worker(self, staker_address: str, worker_address: str, password: str = None): self.attach_transacting_power(checksum_address=staker_address, password=password) staker = self.get_active_staker(address=staker_address) receipt = self.staking_agent.set_worker( staker_address=staker.checksum_address, worker_address=worker_address) self.to_configuration_file(override=True) return receipt def initialize_stake( self, amount: NU, duration: int, checksum_address: str, password: str = None, ) -> Stake: # Existing Staker address if not is_checksum_address(checksum_address): raise ValueError( f"{checksum_address} is an invalid EIP-55 checksum address.") try: staker = self.__stakers[checksum_address] except KeyError: if checksum_address not in self.__accounts: raise ValueError( f"{checksum_address} is an unknown wallet address.") else: staker = Staker(is_me=True, checksum_address=checksum_address, blockchain=self.blockchain) # Don the transacting power for the staker's account. self.attach_transacting_power(checksum_address=staker.checksum_address, password=password) new_stake = staker.initialize_stake(amount=amount, lock_periods=duration) # Update local cache and save to disk. self.__stakers[checksum_address] = staker staker.stake_tracker.refresh( checksum_addresses=[staker.checksum_address]) self.to_configuration_file(override=True) return new_stake def divide_stake( self, address: str, index: int, value: NU, duration: int, password: str = None, ): staker = self.get_active_staker(address=address) if not staker.is_staking: raise Stake.StakingError( f"{staker.checksum_address} has no published stakes.") self.attach_transacting_power(checksum_address=staker.checksum_address, password=password) result = staker.divide_stake(stake_index=index, additional_periods=duration, target_value=value) # Save results to disk self.to_configuration_file(override=True) return result def calculate_rewards(self) -> dict: rewards = dict() for staker in self.stakers: reward = staker.calculate_reward() rewards[staker.checksum_address] = reward return rewards def collect_rewards(self, staker_address: str, password: str = None, withdraw_address: str = None, staking: bool = True, policy: bool = True) -> Dict[str, dict]: if not staking and not policy: raise ValueError( "Either staking or policy must be True in order to collect rewards" ) try: staker = self.get_active_staker(address=staker_address) except self.NoStakes: staker = Staker(is_me=True, checksum_address=staker_address, blockchain=self.blockchain) self.attach_transacting_power(checksum_address=staker.checksum_address, password=password) receipts = dict() if staking: receipts['staking_reward'] = staker.collect_staking_reward() if policy: receipts['policy_reward'] = staker.collect_policy_reward( collector_address=withdraw_address) self.to_configuration_file(override=True) return receipts
class Stake: """ A quantity of tokens and staking duration for one stake for one staker. """ class StakingError(Exception): """Raised when a staking operation cannot be executed due to failure.""" __ID_LENGTH = 16 def __init__(self, checksum_address: str, value: NU, start_period: int, end_period: int, index: int, economics=None, validate_now: bool = True): self.log = Logger(f'stake-{checksum_address}-{index}') # Stake Metadata self.owner_address = checksum_address self.index = index self.value = value self.start_period = start_period self.end_period = end_period # Time self.start_datetime = datetime_at_period(period=start_period) self.end_datetime = datetime_at_period(period=end_period) self.duration_delta = self.end_datetime - self.start_datetime # Agency self.staking_agent = None self.token_agent = NucypherTokenAgent() self.blockchain = self.token_agent.blockchain # Economics from nucypher.blockchain.economics import TokenEconomics self.economics = economics or TokenEconomics() self.minimum_nu = NU(int(self.economics.minimum_allowed_locked), 'NuNit') self.maximum_nu = NU(int(self.economics.maximum_allowed_locked), 'NuNit') if validate_now: self.validate_duration() self.transactions = NO_STAKING_RECEIPT self.receipt = NO_STAKING_RECEIPT def __repr__(self) -> str: r = f'Stake(index={self.index}, value={self.value}, end_period={self.end_period})' return r def __eq__(self, other) -> bool: return bool(self.value == other.value) # # Metadata # @property def is_expired(self) -> bool: if self.staking_agent: current_period = self.staking_agent.get_current_period( ) # on-chain else: current_period = datetime_to_period(maya.now()) # off-chain return bool(current_period >= self.end_period) @property def is_active(self) -> bool: return not self.is_expired @classmethod @validate_checksum_address def from_stake_info(cls, checksum_address: str, index: int, stake_info: Tuple[int, int, int]) -> 'Stake': """Reads staking values as they exist on the blockchain""" start_period, end_period, value = stake_info instance = cls(checksum_address=checksum_address, index=index, start_period=start_period, end_period=end_period, value=NU(value, 'NuNit')) return instance def to_stake_info(self) -> Tuple[int, int, int]: """Returns a tuple representing the blockchain record of a stake""" return self.start_period, self.end_period, int(self.value) # # Duration # @property def duration(self) -> int: """Return stake duration in periods""" result = (self.end_period - self.start_period) + 1 return result @property def periods_remaining(self) -> int: """Returns the number of periods remaining in the stake from now.""" current_period = datetime_to_period(datetime=maya.now()) return self.end_period - current_period def time_remaining(self, slang: bool = False) -> Union[int, str]: """Returns the time delta remaining in the stake from now.""" now = maya.now() delta = self.end_datetime - now if slang: result = self.end_datetime.slang_date() else: result = delta.seconds return result # # Validation # @staticmethod def __handle_validation_failure( rulebook: Tuple[Tuple[bool, str], ...]) -> bool: """Validate a staking rulebook""" for rule, failure_message in rulebook: if not rule: raise ValueError(failure_message) return True def validate(self) -> bool: return all((self.validate_value(), self.validate_duration())) def validate_value( self, raise_on_fail: bool = True ) -> Union[bool, Tuple[Tuple[bool, str]]]: """Validate a single staking value against pre-defined requirements""" rulebook = ( (self.minimum_nu <= self.value, 'Stake amount too low; ({amount}) must be at least {minimum}'. format(minimum=self.minimum_nu, amount=self.value)), (self.maximum_nu >= self.value, 'Stake amount too high; ({amount}) must be no more than {maximum}.' .format(maximum=self.maximum_nu, amount=self.value)), ) if raise_on_fail is True: self.__handle_validation_failure(rulebook=rulebook) return all(rulebook) def validate_duration(self, raise_on_fail=True ) -> Union[bool, Tuple[Tuple[bool, str]]]: """Validate a single staking lock-time against pre-defined requirements""" rulebook = (( self.economics.minimum_locked_periods <= self.duration, 'Stake duration of ({duration}) is too short; must be at least {minimum} periods.' .format(minimum=self.economics.minimum_locked_periods, duration=self.duration)), ) if raise_on_fail is True: self.__handle_validation_failure(rulebook=rulebook) return all(rulebook) # # Blockchain # def sync(self) -> None: """Update this stakes attributes with on-chain values.""" if not self.staking_agent: self.staking_agent = StakingEscrowAgent() if not self.token_agent: self.token_agent = NucypherTokenAgent() # Read from blockchain stake_info = self.staking_agent.get_substake_info( staker_address=self.owner_address, stake_index=self.index) # < -- Read from blockchain first_period, last_period, locked_value = stake_info if not self.start_period == first_period: # TODO: Provide an escape path or re-attempt in implementation raise self.StakingError( "Inconsistent staking cache, aborting stake division.") # Mutate the instance with the on-chain values self.end_period = last_period self.value = NU.from_nunits(locked_value) @classmethod def __deposit(cls, staker, amount: int, lock_periods: int) -> Tuple[str, str]: """Public facing method for token locking.""" approve_txhash = staker.token_agent.approve_transfer( amount=amount, target_address=staker.staking_agent.contract_address, sender_address=staker.checksum_address) deposit_txhash = staker.staking_agent.deposit_tokens( amount=amount, lock_periods=lock_periods, sender_address=staker.checksum_address) return approve_txhash, deposit_txhash def divide(self, target_value: NU, additional_periods: int = None) -> Tuple['Stake', 'Stake']: """ Modifies the unlocking schedule and value of already locked tokens. This actor requires that is_me is True, and that the expiration datetime is after the existing locking schedule of this staker, or an exception will be raised. """ # Read on-chain stake self.sync() # Ensure selected stake is active if self.is_expired: raise self.StakingError( f'Cannot divide an expired stake. Selected stake expired {self.end_datetime}.' ) if target_value >= self.value: raise self.StakingError( f"Cannot divide stake; Target value ({target_value}) must be less " f"than the existing stake value {self.value}.") # # Generate SubStakes # # Modified Original Stake remaining_stake_value = self.value - target_value modified_stake = Stake(checksum_address=self.owner_address, index=self.index, start_period=self.start_period, end_period=self.end_period, value=remaining_stake_value) # New Derived Stake end_period = self.end_period + additional_periods new_stake = Stake(checksum_address=self.owner_address, start_period=self.start_period, end_period=end_period, value=target_value, index=NEW_STAKE) # # Validate # # Ensure both halves are for valid amounts modified_stake.validate_value() new_stake.validate_value() # # Transmit # # Transmit the stake division transaction receipt = self.staking_agent.divide_stake( staker_address=self.owner_address, stake_index=self.index, target_value=int(target_value), periods=additional_periods) new_stake.receipt = receipt return modified_stake, new_stake @classmethod def initialize_stake(cls, staker, amount: NU, lock_periods: int = None) -> 'Stake': # Value amount = NU(int(amount), 'NuNit') # Duration current_period = staker.staking_agent.get_current_period() end_period = current_period + lock_periods stake = Stake(checksum_address=staker.checksum_address, start_period=current_period + 1, end_period=end_period, value=amount, index=NEW_STAKE) # Validate stake.validate_value() stake.validate_duration() # Transmit approve_txhash, initial_deposit_txhash = stake.__deposit( amount=int(amount), lock_periods=lock_periods, staker=staker) # Store the staking transactions on the instance staking_transactions = dict(approve=approve_txhash, deposit=initial_deposit_txhash) stake.transactions = staking_transactions # Log and return Stake instance log = Logger(f'stake-{staker.checksum_address}-creation') log.info("{} Initialized new stake: {} tokens for {} periods".format( staker.checksum_address, amount, lock_periods)) return stake
def test_nucypher_deploy_contracts(click_runner, mock_primary_registry_filepath, mock_allocation_infile, token_economics): Agency.clear() # # Setup # # We start with a blockchain node, and nothing else... if os.path.isfile(mock_primary_registry_filepath): os.remove(mock_primary_registry_filepath) assert not os.path.isfile(mock_primary_registry_filepath) # # Main # command = [ 'contracts', '--registry-outfile', mock_primary_registry_filepath, '--provider-uri', TEST_PROVIDER_URI, '--poa' ] user_input = '0\n' + 'Y\n' + (f'{INSECURE_SECRETS[1]}\n' * 8) + 'DEPLOY' result = click_runner.invoke(deploy, command, input=user_input, catch_exceptions=False) assert result.exit_code == 0 # Ensure there is a report on each contract for registry_name in Deployer.contract_names: assert registry_name in result.output # Check that the primary contract registry was written # and peek at some of the registered entries assert os.path.isfile(mock_primary_registry_filepath) with open(mock_primary_registry_filepath, 'r') as file: # Ensure every contract's name was written to the file, somehow raw_registry_data = file.read() for registry_name in Deployer.contract_names: assert registry_name in raw_registry_data # Ensure the Registry is JSON deserializable registry_data = json.loads(raw_registry_data) # and that is has the correct number of entries assert len(registry_data) == 9 # Read several records token_record, escrow_record, dispatcher_record, *other_records = registry_data registered_name, registered_address, registered_abi = token_record # # Agency # token_agent = NucypherTokenAgent() assert token_agent.contract_name == registered_name assert token_agent.registry_contract_name == registered_name assert token_agent.contract_address == registered_address # Now show that we can use contract Agency and read from the blockchain assert token_agent.get_balance() == 0 staking_agent = StakingEscrowAgent() assert staking_agent.get_current_period() # and at least the others can be instantiated assert PolicyAgent()
def __init__(self, domains: Set = None, is_me: bool = True, federated_only: bool = False, blockchain: BlockchainInterface = None, checksum_address: str = NO_BLOCKCHAIN_CONNECTION.bool_value( False), network_middleware: RestMiddleware = None, keyring_root: str = None, crypto_power: CryptoPower = None, crypto_power_ups: List[CryptoPowerUp] = None, *args, **kwargs) -> None: """ Base class for Nucypher protocol actors. PowerUps ======== :param crypto_power: A CryptoPower object; if provided, this will be the character's CryptoPower. :param crypto_power_ups: If crypto_power is not provided, a new one will be made to consume all CryptoPowerUps. If neither crypto_power nor crypto_power_ups are provided, we give this Character all CryptoPowerUps listed in their _default_crypto_powerups attribute. :param is_me: Set this to True when you want this Character to represent the owner of the configuration under which the program is being run. A Character who is_me can do things that other Characters can't, like run servers, sign messages, and decrypt messages which are encrypted for them. Typically this will be True for exactly one Character, but there are scenarios in which its imaginable to be represented by zero Characters or by more than one Character. """ self.federated_only = federated_only # type: bool # # Powers # if crypto_power and crypto_power_ups: raise ValueError( "Pass crypto_power or crypto_power_ups (or neither), but not both." ) crypto_power_ups = crypto_power_ups or list() # type: list if crypto_power: self._crypto_power = crypto_power # type: CryptoPower elif crypto_power_ups: self._crypto_power = CryptoPower(power_ups=crypto_power_ups) else: self._crypto_power = CryptoPower( power_ups=self._default_crypto_powerups) self._checksum_address = checksum_address # Fleet and Blockchain Connection (Everyone) if not domains: domains = (CharacterConfiguration.DEFAULT_DOMAIN, ) # Needed for on-chain verification if not self.federated_only: self.blockchain = blockchain self.staking_agent = StakingEscrowAgent(blockchain=blockchain) else: self.blockchain = FEDERATED_ONLY self.staking_agent = FEDERATED_ONLY # # Self-Character # if is_me is True: self.keyring_root = keyring_root # type: str self.treasure_maps = {} # type: dict self.network_middleware = network_middleware or RestMiddleware() # # Signing Power # try: signing_power = self._crypto_power.power_ups( SigningPower) # type: SigningPower self._stamp = signing_power.get_signature_stamp( ) # type: SignatureStamp except NoSigningPower: self._stamp = NO_SIGNING_POWER # # Learner # Learner.__init__(self, domains=domains, network_middleware=network_middleware, *args, **kwargs) # # Stranger-Character # else: # Feel like a stranger if network_middleware is not None: raise TypeError( "Network middleware cannot be attached to a Stranger-Character." ) self._stamp = StrangerStamp(self.public_keys(SigningPower)) self.keyring_root = STRANGER self.network_middleware = STRANGER # # Decentralized # if not federated_only: if not checksum_address: raise ValueError( "No checksum_address provided while running in a non-federated mode." ) else: self._checksum_address = checksum_address # TODO: Check that this matches BlockchainPower # # Federated # elif federated_only: try: self._set_checksum_address() # type: str except NoSigningPower: self._checksum_address = NO_BLOCKCHAIN_CONNECTION if checksum_address: # We'll take a checksum address, as long as it matches their singing key if not checksum_address == self.checksum_address: error = "Federated-only Characters derive their address from their Signing key; got {} instead." raise self.SuspiciousActivity( error.format(checksum_address)) # # Nicknames # try: self.nickname, self.nickname_metadata = nickname_from_seed( self.checksum_address) except SigningPower.not_found_error: if self.federated_only: self.nickname = self.nickname_metadata = NO_NICKNAME else: raise # # Fleet state # if is_me is True: self.known_nodes.record_fleet_state() # # Character Control # self.controller = NO_CONTROL_PROTOCOL
def test_deploy_ethereum_contracts(testerchain): origin, *everybody_else = testerchain.client.accounts # # Nucypher Token # token_deployer = NucypherTokenDeployer(blockchain=testerchain, deployer_address=origin) assert token_deployer.deployer_address == origin with pytest.raises(ContractDeployer.ContractDeploymentError): assert token_deployer.contract_address is constants.CONTRACT_NOT_DEPLOYED assert not token_deployer.is_deployed token_deployer.deploy() assert token_deployer.is_deployed assert len(token_deployer.contract_address) == 42 token_agent = NucypherTokenAgent(blockchain=testerchain) assert len(token_agent.contract_address) == 42 assert token_agent.contract_address == token_deployer.contract_address another_token_agent = token_deployer.make_agent() assert len(another_token_agent.contract_address) == 42 assert another_token_agent.contract_address == token_deployer.contract_address == token_agent.contract_address # # StakingEscrow # stakers_escrow_secret = os.urandom( DispatcherDeployer.DISPATCHER_SECRET_LENGTH) staking_escrow_deployer = StakingEscrowDeployer(blockchain=testerchain, deployer_address=origin) assert staking_escrow_deployer.deployer_address == origin with pytest.raises(ContractDeployer.ContractDeploymentError): assert staking_escrow_deployer.contract_address is constants.CONTRACT_NOT_DEPLOYED assert not staking_escrow_deployer.is_deployed staking_escrow_deployer.deploy(secret_hash=keccak(stakers_escrow_secret)) assert staking_escrow_deployer.is_deployed assert len(staking_escrow_deployer.contract_address) == 42 staking_agent = StakingEscrowAgent(blockchain=testerchain) assert len(staking_agent.contract_address) == 42 assert staking_agent.contract_address == staking_escrow_deployer.contract_address another_staking_agent = staking_escrow_deployer.make_agent() assert len(another_staking_agent.contract_address) == 42 assert another_staking_agent.contract_address == staking_escrow_deployer.contract_address == staking_agent.contract_address # # Policy Manager # policy_manager_secret = os.urandom( DispatcherDeployer.DISPATCHER_SECRET_LENGTH) policy_manager_deployer = PolicyManagerDeployer(blockchain=testerchain, deployer_address=origin) assert policy_manager_deployer.deployer_address == origin with pytest.raises(ContractDeployer.ContractDeploymentError): assert policy_manager_deployer.contract_address is constants.CONTRACT_NOT_DEPLOYED assert not policy_manager_deployer.is_deployed policy_manager_deployer.deploy(secret_hash=keccak(policy_manager_secret)) assert policy_manager_deployer.is_deployed assert len(policy_manager_deployer.contract_address) == 42 policy_agent = policy_manager_deployer.make_agent() assert len(policy_agent.contract_address) == 42 assert policy_agent.contract_address == policy_manager_deployer.contract_address another_policy_agent = policy_manager_deployer.make_agent() assert len(another_policy_agent.contract_address) == 42 assert another_policy_agent.contract_address == policy_manager_deployer.contract_address == policy_agent.contract_address # # Adjudicator # adjudicator_secret = os.urandom( DispatcherDeployer.DISPATCHER_SECRET_LENGTH) adjudicator_deployer = AdjudicatorDeployer(blockchain=testerchain, deployer_address=origin) assert adjudicator_deployer.deployer_address == origin with pytest.raises(ContractDeployer.ContractDeploymentError): assert adjudicator_deployer.contract_address is constants.CONTRACT_NOT_DEPLOYED assert not adjudicator_deployer.is_deployed adjudicator_deployer.deploy(secret_hash=keccak(adjudicator_secret)) assert adjudicator_deployer.is_deployed assert len(adjudicator_deployer.contract_address) == 42 adjudicator_agent = adjudicator_deployer.make_agent() assert len(adjudicator_agent.contract_address) == 42 assert adjudicator_agent.contract_address == adjudicator_deployer.contract_address another_adjudicator_agent = adjudicator_deployer.make_agent() assert len(another_adjudicator_agent.contract_address) == 42 assert another_adjudicator_agent.contract_address == adjudicator_deployer.contract_address == adjudicator_agent.contract_address
def test_unknown_contract(testerchain): with pytest.raises(EthereumContractRegistry.UnknownContract) as exception: _staking_agent = StakingEscrowAgent(blockchain=testerchain) assert exception.value.args[0] == StakingEscrowAgent.registry_contract_name
class StakeTracker: REFRESH_RATE = 60 tracking_addresses = set() __stakes = dict() # type: Dict[str: List[Stake]] __actions = list() # type: List[Tuple[Callable, tuple]] def __init__(self, checksum_addresses: List[str], refresh_rate: int = None, start_now: bool = False, *args, **kwargs): super().__init__(*args, **kwargs) self.log = Logger('stake-tracker') self.staking_agent = StakingEscrowAgent() self._refresh_rate = refresh_rate or self.REFRESH_RATE self._tracking_task = task.LoopingCall(self.__update) self.__current_period = None self.__stakes = dict() self.__start_time = NOT_STAKING self.__uptime_period = NOT_STAKING self.__terminal_period = NOT_STAKING self._abort_on_stake_tracking_error = True # "load-in": Read on-chain stakes for checksum_address in checksum_addresses: if not is_checksum_address(checksum_address): raise ValueError( f'{checksum_address} is not a valid EIP-55 checksum address' ) self.tracking_addresses.add(checksum_address) if start_now: self.start() # deamonize else: self.refresh(checksum_addresses=checksum_addresses) # read-once @validate_checksum_address def __getitem__(self, checksum_address: str): stakes = self.stakes(checksum_address=checksum_address) return stakes def add_action(self, func: Callable, args=()) -> None: self.__actions.append((func, args)) def clear_actions(self) -> None: self.__actions.clear() @property def current_period(self): return self.__current_period @validate_checksum_address def stakes(self, checksum_address: str) -> List[Stake]: """Return all cached stake instances from the blockchain.""" try: return self.__stakes[checksum_address] except KeyError: return NO_STAKES.bool_value(False) except TypeError: if self.__stakes in (UNKNOWN_STAKES, NO_STAKES): return NO_STAKES.bool_value(False) raise @validate_checksum_address def refresh(self, checksum_addresses: List[str] = None) -> None: """Public staking cache invalidation method""" return self.__read_stakes(checksum_addresses=checksum_addresses) def stop(self) -> None: self._tracking_task.stop() self.log.info(f"STOPPED STAKE TRACKING") def start(self, force: bool = False) -> None: """ High-level stake tracking initialization, this function aims to be safely called at any time - For example, it is okay to call this function multiple times within the same period. """ if self._tracking_task.running and not force: return # Record the start time and period self.__start_time = maya.now() self.__uptime_period = self.staking_agent.get_current_period() self.__current_period = self.__uptime_period d = self._tracking_task.start(interval=self._refresh_rate) d.addErrback(self.handle_tracking_errors) self.log.info( f"STARTED STAKE TRACKING for {len(self.tracking_addresses)} addresses" ) def _crash_gracefully(self, failure=None) -> None: """ A facility for crashing more gracefully in the event that an exception is unhandled in a different thread. """ self._crashed = failure failure.raiseException() def handle_tracking_errors(self, *args, **kwargs) -> None: failure = args[0] if self._abort_on_stake_tracking_error: self.log.critical( f"Unhandled error during node stake tracking. {failure}") reactor.callFromThread(self._crash_gracefully, failure=failure) else: self.log.warn( f"Unhandled error during stake tracking: {failure.getTraceback()}" ) def __update(self) -> None: self.log.info( f"Checking for new period. Current period is {self.__current_period}" ) onchain_period = self.staking_agent.get_current_period( ) # < -- Read from contract if self.__current_period != onchain_period: self.__current_period = onchain_period self.__read_stakes() for action, args in self.__actions: action(*args) @validate_checksum_address def __read_stakes(self, checksum_addresses: List[str] = None) -> None: """Rewrite the local staking cache by reading on-chain stakes""" if not checksum_addresses: checksum_addresses = self.tracking_addresses for checksum_address in checksum_addresses: if not is_checksum_address(checksum_address): if self._abort_on_stake_tracking_error: raise ValueError( f'{checksum_address} is not a valid EIP-55 checksum address' ) self.tracking_addresses.remove(checksum_address) # Prune existing_records = len( self.stakes(checksum_address=checksum_address)) # Candidate replacement cache values onchain_stakes, terminal_period = list(), 0 # Read from blockchain stakes_reader = self.staking_agent.get_all_stakes( staker_address=checksum_address) for onchain_index, stake_info in enumerate(stakes_reader): if not stake_info: onchain_stake = EMPTY_STAKING_SLOT else: onchain_stake = Stake.from_stake_info( checksum_address=checksum_address, stake_info=stake_info, index=onchain_index) # rack the latest terminal period if onchain_stake.end_period > terminal_period: terminal_period = onchain_stake.end_period # Store the replacement stake onchain_stakes.append(onchain_stake) # Commit the new stake and terminal values to the cache if not onchain_stakes: self.__stakes[checksum_address] = NO_STAKES.bool_value(False) else: self.__terminal_period = terminal_period self.__stakes[checksum_address] = onchain_stakes new_records = existing_records - len( self.__stakes[checksum_address]) self.log.debug( f"Updated local staking cache ({new_records} new stakes).") # Record most recent cache update self.__updated = maya.now()
def test_sampling_distribution(testerchain, token, deploy_contract): # # SETUP # max_allowed_locked_tokens = 5 * 10 ** 8 _staking_coefficient = 2 * 10 ** 7 staking_escrow_contract, _ = deploy_contract( contract_name=STAKING_ESCROW_CONTRACT_NAME, _token=token.address, _hoursPerPeriod=1, _miningCoefficient=4 * _staking_coefficient, _lockedPeriodsCoefficient=4, _rewardedPeriods=4, _minLockedPeriods=2, _minAllowableLockedTokens=100, _maxAllowableLockedTokens=max_allowed_locked_tokens, _minWorkerPeriods=1, _isTestContract=False ) staking_agent = StakingEscrowAgent(registry=None, contract=staking_escrow_contract) policy_manager, _ = deploy_contract( 'PolicyManagerForStakingEscrowMock', token.address, staking_escrow_contract.address ) tx = staking_escrow_contract.functions.setPolicyManager(policy_manager.address).transact() testerchain.wait_for_receipt(tx) # Travel to the start of the next period to prevent problems with unexpected overflow first period testerchain.time_travel(hours=1) creator = testerchain.etherbase_account # Give Escrow tokens for reward and initialize contract tx = token.functions.approve(staking_escrow_contract.address, 10 ** 9).transact({'from': creator}) testerchain.wait_for_receipt(tx) tx = staking_escrow_contract.functions.initialize(10 ** 9).transact({'from': creator}) testerchain.wait_for_receipt(tx) stakers = testerchain.stakers_accounts amount = token.functions.balanceOf(creator).call() // len(stakers) # Airdrop for staker in stakers: tx = token.functions.transfer(staker, amount).transact({'from': creator}) testerchain.wait_for_receipt(tx) all_locked_tokens = len(stakers) * amount for staker in stakers: balance = token.functions.balanceOf(staker).call() tx = token.functions.approve(staking_escrow_contract.address, balance).transact({'from': staker}) testerchain.wait_for_receipt(tx) staking_agent.deposit_tokens(amount=balance, lock_periods=10, sender_address=staker, staker_address=staker) staking_agent.set_worker(staker_address=staker, worker_address=staker) staking_agent.confirm_activity(staker) # Wait next period and check all locked tokens testerchain.time_travel(hours=1) # # Test sampling distribution # ERROR_TOLERANCE = 0.05 # With this tolerance, all sampling ratios should between 5% and 15% (expected is 10%) SAMPLES = 1000 quantity = 3 counter = Counter() sampled, failed = 0, 0 while sampled < SAMPLES: try: addresses = set(staking_agent.sample(quantity=quantity, additional_ursulas=1, duration=1)) addresses.discard(NULL_ADDRESS) except staking_agent.NotEnoughStakers: failed += 1 continue else: sampled += 1 counter.update(addresses) total_times = sum(counter.values()) expected = amount / all_locked_tokens for staker in stakers: times = counter[staker] sampled_ratio = times / total_times abs_error = abs(expected - sampled_ratio) assert abs_error < ERROR_TOLERANCE
class Staker(NucypherTokenActor): """ Baseclass for staking-related operations on the blockchain. """ class StakerError(NucypherTokenActor.ActorError): pass def __init__(self, is_me: bool, economics: TokenEconomics = None, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self.log = Logger("staker") self.stake_tracker = StakeTracker( checksum_addresses=[self.checksum_address]) self.staking_agent = StakingEscrowAgent(blockchain=self.blockchain) self.economics = economics or TokenEconomics() self.is_me = is_me @property def stakes(self) -> List[Stake]: stakes = self.stake_tracker.stakes( checksum_address=self.checksum_address) return stakes @property def is_staking(self) -> bool: """Checks if this Staker currently has active stakes / locked tokens.""" return bool(self.stakes) def locked_tokens(self, periods: int = 0) -> NU: """Returns the amount of tokens this staker has locked for a given duration in periods.""" raw_value = self.staking_agent.get_locked_tokens( staker_address=self.checksum_address, periods=periods) value = NU.from_nunits(raw_value) return value @property def current_stake(self) -> NU: """ The total number of staked tokens, either locked or unlocked in the current period. """ if self.stakes: return NU(sum(int(stake.value) for stake in self.stakes), 'NuNit') else: return NU.ZERO() @only_me def divide_stake(self, stake_index: int, target_value: NU, additional_periods: int = None, expiration: maya.MayaDT = None) -> tuple: # Calculate duration in periods if additional_periods and expiration: raise ValueError( "Pass the number of lock periods or an expiration MayaDT; not both." ) # Select stake to divide from local cache try: current_stake = self.stakes[stake_index] except KeyError: if len(self.stakes): message = f"Cannot divide stake - No stake exists with index {stake_index}." else: message = "Cannot divide stake - There are no active stakes." raise Stake.StakingError(message) # Calculate stake duration in periods if expiration: additional_periods = datetime_to_period( datetime=expiration) - current_stake.end_period if additional_periods <= 0: raise Stake.StakingError( f"New expiration {expiration} must be at least 1 period from the " f"current stake's end period ({current_stake.end_period})." ) # Do it already! modified_stake, new_stake = current_stake.divide( target_value=target_value, additional_periods=additional_periods) # Update staking cache element self.stake_tracker.refresh(checksum_addresses=[self.checksum_address]) return modified_stake, new_stake @only_me def initialize_stake(self, amount: NU, lock_periods: int = None, expiration: maya.MayaDT = None, entire_balance: bool = False) -> Stake: """Create a new stake.""" # Duration if lock_periods and expiration: raise ValueError( "Pass the number of lock periods or an expiration MayaDT; not both." ) if expiration: lock_periods = calculate_period_duration(future_time=expiration) # Value if entire_balance and amount: raise ValueError("Specify an amount or entire balance, not both") if entire_balance: amount = self.token_balance if not self.token_balance >= amount: raise self.StakerError( f"Insufficient token balance ({self.token_agent}) " f"for new stake initialization of {amount}") # Ensure the new stake will not exceed the staking limit if (self.current_stake + amount) > self.economics.maximum_allowed_locked: raise Stake.StakingError( f"Cannot divide stake - " f"Maximum stake value exceeded with a target value of {amount}." ) # Write to blockchain new_stake = Stake.initialize_stake(staker=self, amount=amount, lock_periods=lock_periods) # Update stake tracker cache element self.stake_tracker.refresh(checksum_addresses=[self.checksum_address]) return new_stake # # Reward and Collection # @only_me @save_receipt def set_worker(self, worker_address: str) -> str: # TODO: Set a Worker for this staker, not just in StakingEscrow receipt = self.staking_agent.set_worker( staker_address=self.checksum_address, worker_address=worker_address) return receipt @only_me @save_receipt def mint(self) -> Tuple[str, str]: """Computes and transfers tokens to the staker's account""" receipt = self.staking_agent.mint(staker_address=self.checksum_address) return receipt def calculate_reward(self) -> int: staking_reward = self.staking_agent.calculate_staking_reward( staker_address=self.checksum_address) return staking_reward @only_me @save_receipt def collect_policy_reward(self, collector_address=None, policy_agent: PolicyAgent = None): """Collect rewarded ETH""" policy_agent = policy_agent if policy_agent is not None else PolicyAgent( blockchain=self.blockchain) withdraw_address = collector_address or self.checksum_address receipt = policy_agent.collect_policy_reward( collector_address=withdraw_address, staker_address=self.checksum_address) return receipt @only_me @save_receipt def collect_staking_reward(self) -> str: """Withdraw tokens rewarded for staking.""" receipt = self.staking_agent.collect_staking_reward( staker_address=self.checksum_address) return receipt @only_me @save_receipt def withdraw(self, amount: NU) -> str: """Withdraw tokens (assuming they're unlocked)""" receipt = self.staking_agent.withdraw( staker_address=self.checksum_address, amount=int(amount)) return receipt
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.token_agent = NucypherTokenAgent(blockchain=self.blockchain) self.staking_agent = StakingEscrowAgent(blockchain=self.blockchain)
def test_deploy_ethereum_contracts(session_testerchain, deployment_progress): testerchain = session_testerchain origin, *everybody_else = testerchain.client.accounts # # Nucypher Token # token_deployer = NucypherTokenDeployer(blockchain=testerchain, deployer_address=origin) assert token_deployer.deployer_address == origin with pytest.raises(ContractDeployer.ContractDeploymentError): assert token_deployer.contract_address is constants.CONTRACT_NOT_DEPLOYED assert not token_deployer.is_deployed token_deployer.deploy(progress=deployment_progress) assert token_deployer.is_deployed assert len(token_deployer.contract_address) == 42 token_agent = NucypherTokenAgent(blockchain=testerchain) assert len(token_agent.contract_address) == 42 assert token_agent.contract_address == token_deployer.contract_address another_token_agent = token_deployer.make_agent() assert len(another_token_agent.contract_address) == 42 assert another_token_agent.contract_address == token_deployer.contract_address == token_agent.contract_address # # StakingEscrow # stakers_escrow_secret = os.urandom( DispatcherDeployer.DISPATCHER_SECRET_LENGTH) staking_escrow_deployer = StakingEscrowDeployer(blockchain=testerchain, deployer_address=origin) assert staking_escrow_deployer.deployer_address == origin with pytest.raises(ContractDeployer.ContractDeploymentError): assert staking_escrow_deployer.contract_address is constants.CONTRACT_NOT_DEPLOYED assert not staking_escrow_deployer.is_deployed staking_escrow_deployer.deploy(secret_hash=keccak(stakers_escrow_secret), progress=deployment_progress) assert staking_escrow_deployer.is_deployed assert len(staking_escrow_deployer.contract_address) == 42 staking_agent = StakingEscrowAgent(blockchain=testerchain) assert len(staking_agent.contract_address) == 42 assert staking_agent.contract_address == staking_escrow_deployer.contract_address another_staking_agent = staking_escrow_deployer.make_agent() assert len(another_staking_agent.contract_address) == 42 assert another_staking_agent.contract_address == staking_escrow_deployer.contract_address == staking_agent.contract_address # # Policy Manager # policy_manager_secret = os.urandom( DispatcherDeployer.DISPATCHER_SECRET_LENGTH) policy_manager_deployer = PolicyManagerDeployer(blockchain=testerchain, deployer_address=origin) assert policy_manager_deployer.deployer_address == origin with pytest.raises(ContractDeployer.ContractDeploymentError): assert policy_manager_deployer.contract_address is constants.CONTRACT_NOT_DEPLOYED assert not policy_manager_deployer.is_deployed policy_manager_deployer.deploy(secret_hash=keccak(policy_manager_secret), progress=deployment_progress) assert policy_manager_deployer.is_deployed assert len(policy_manager_deployer.contract_address) == 42 policy_agent = policy_manager_deployer.make_agent() assert len(policy_agent.contract_address) == 42 assert policy_agent.contract_address == policy_manager_deployer.contract_address another_policy_agent = policy_manager_deployer.make_agent() assert len(another_policy_agent.contract_address) == 42 assert another_policy_agent.contract_address == policy_manager_deployer.contract_address == policy_agent.contract_address # # Adjudicator # adjudicator_secret = os.urandom( DispatcherDeployer.DISPATCHER_SECRET_LENGTH) adjudicator_deployer = AdjudicatorDeployer(blockchain=testerchain, deployer_address=origin) assert adjudicator_deployer.deployer_address == origin with pytest.raises(ContractDeployer.ContractDeploymentError): assert adjudicator_deployer.contract_address is constants.CONTRACT_NOT_DEPLOYED assert not adjudicator_deployer.is_deployed adjudicator_deployer.deploy(secret_hash=keccak(adjudicator_secret), progress=deployment_progress) assert adjudicator_deployer.is_deployed assert len(adjudicator_deployer.contract_address) == 42 adjudicator_agent = adjudicator_deployer.make_agent() assert len(adjudicator_agent.contract_address) == 42 assert adjudicator_agent.contract_address == adjudicator_deployer.contract_address another_adjudicator_agent = AdjudicatorAgent() assert len(another_adjudicator_agent.contract_address) == 42 assert another_adjudicator_agent.contract_address == adjudicator_deployer.contract_address == adjudicator_agent.contract_address # overall deployment steps must match aggregated individual expected number of steps assert deployment_progress.num_steps == ( token_deployer.number_of_deployment_transactions + staking_escrow_deployer.number_of_deployment_transactions + policy_manager_deployer.number_of_deployment_transactions + adjudicator_deployer.number_of_deployment_transactions)