예제 #1
0
 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")
예제 #2
0
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! *********")
예제 #3
0
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! *********")
예제 #4
0
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! *********")
예제 #5
0
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
예제 #7
0
파일: actors.py 프로젝트: vepkenez/nucypher
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
예제 #8
0
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
예제 #9
0
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()
예제 #10
0
    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
예제 #11
0
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
예제 #12
0
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
예제 #13
0
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()
예제 #14
0
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
예제 #15
0
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
예제 #16
0
 def __init__(self, *args, **kwargs):
     super().__init__(*args, **kwargs)
     self.token_agent = NucypherTokenAgent(blockchain=self.blockchain)
     self.staking_agent = StakingEscrowAgent(blockchain=self.blockchain)
예제 #17
0
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)