def test_call_fee(geth_node, rpc_client, deployed_contracts):
    alarm = deployed_contracts.Alarm
    client_contract = deployed_contracts.PassesUInt

    deposit_amount = get_max_gas(rpc_client) * rpc_client.get_gas_price() * 20
    alarm.deposit.sendTransaction(client_contract._meta.address, value=deposit_amount)

    txn_hash = client_contract.scheduleIt.sendTransaction(alarm._meta.address, 3)
    wait_for_transaction(client_contract._meta.rpc_client, txn_hash)

    assert client_contract.value.call() == 0

    callKey = alarm.getLastCallKey.call()
    assert callKey is not None

    owner = '0xd3cda913deb6f67967b99d67acdfa1712c293601'

    assert alarm.getCallFee.call(callKey) == 0
    assert alarm.accountBalances.call(owner) == 0

    wait_for_block(rpc_client, alarm.getCallTargetBlock.call(callKey), 120)
    call_txn_hash = alarm.doCall.sendTransaction(callKey)
    wait_for_transaction(alarm._meta.rpc_client, call_txn_hash)

    balance = alarm.accountBalances.call(owner)
    assert balance > 0
    assert alarm.getCallFee.call(callKey) == balance
def test_getting_called_at_block(geth_node, rpc_client, deployed_contracts):
    alarm = deployed_contracts.Alarm
    client_contract = deployed_contracts.NoArgs

    deposit_amount = get_max_gas(rpc_client) * rpc_client.get_gas_price() * 20
    alarm.deposit.sendTransaction(client_contract._meta.address, value=deposit_amount)

    txn_hash = client_contract.scheduleIt.sendTransaction(alarm._meta.address)
    wait_for_transaction(rpc_client, txn_hash)

    assert client_contract.value.call() is False

    callKey = alarm.getLastCallKey.call()
    assert callKey is not None

    assert alarm.getCallGasUsed.call(callKey) == 0

    wait_for_block(rpc_client, alarm.getCallTargetBlock.call(callKey), 120)
    call_txn_hash = alarm.doCall.sendTransaction(callKey)
    wait_for_transaction(rpc_client, call_txn_hash)

    call_txn = rpc_client.get_transaction_by_hash(call_txn_hash)

    assert client_contract.value.call() is True
    assert alarm.getCallCalledAtBlock.call(callKey) == int(call_txn['blockNumber'], 16)
def test_scheduled_call_execution_without_pool(geth_node, geth_coinbase, rpc_client, deployed_contracts, contracts):
    alarm = deployed_contracts.Alarm
    client_contract = deployed_contracts.SpecifyBlock
    caller_pool = contracts.CallerPool(alarm.getCallerPoolAddress.call(), rpc_client)

    deposit_amount = get_max_gas(rpc_client) * rpc_client.get_gas_price() * 20
    alarm.deposit.sendTransaction(client_contract._meta.address, value=deposit_amount)

    target_block = rpc_client.get_block_number() + 45

    txn_hash = client_contract.scheduleIt.sendTransaction(alarm._meta.address, target_block)
    wait_for_transaction(client_contract._meta.rpc_client, txn_hash)

    callKey = alarm.getLastCallKey.call()
    assert callKey is not None

    pool_manager = PoolManager(caller_pool)
    scheduled_call = ScheduledCall(alarm, pool_manager, callKey)

    assert pool_manager.in_active_pool is False
    scheduled_call.execute_async()

    wait_for_block(rpc_client, scheduled_call.target_block + 4, 180)
    assert pool_manager.in_active_pool is False

    assert scheduled_call.txn_hash
    assert scheduled_call.txn_receipt
    assert scheduled_call.txn

    assert alarm.checkIfCalled.call(scheduled_call.call_key)
    assert scheduled_call.was_called
    assert scheduled_call.target_block <= scheduled_call.called_at_block
    assert scheduled_call.called_at_block <= scheduled_call.target_block + scheduled_call.grace_period
def test_entering_pool(geth_node, geth_coinbase, rpc_client, deployed_contracts):
    caller_pool = deployed_contracts.CallerPool

    assert caller_pool.callerBonds.call(geth_coinbase) == 0
    deposit_amount = caller_pool.getMinimumBond.call() * 10

    txn_1_hash = caller_pool.depositBond.sendTransaction(value=deposit_amount)
    wait_for_transaction(rpc_client, txn_1_hash)

    assert caller_pool.isInAnyPool.call() is False
    assert caller_pool.canEnterPool.call() is True

    wait_for_transaction(rpc_client, caller_pool.enterPool.sendTransaction())

    # Now queued to be in the next pool.
    assert caller_pool.getActivePoolKey.call() == 0
    assert caller_pool.isInAnyPool.call() is True
    assert caller_pool.canEnterPool.call() is False

    next_pool_key = caller_pool.getNextPoolKey.call()
    assert next_pool_key > 0
    assert caller_pool.isInPool.call(next_pool_key) is True

    wait_for_block(rpc_client, next_pool_key, 180)

    assert caller_pool.isInAnyPool.call() is True
    assert caller_pool.canEnterPool.call() is False
def test_extra_call_gas_constant_when_gas_price_lower(geth_node, rpc_client, deployed_contracts):
    alarm = deployed_contracts.Alarm
    client_contract = deployed_contracts.PassesInt

    deposit_amount = get_max_gas(rpc_client) * rpc_client.get_gas_price() * 20
    alarm.deposit.sendTransaction(client_contract._meta.address, value=deposit_amount)

    txn_hash = client_contract.scheduleIt.sendTransaction(alarm._meta.address, -12345)
    wait_for_transaction(client_contract._meta.rpc_client, txn_hash)

    callKey = alarm.getLastCallKey.call()
    assert callKey is not None

    base_gas_price = alarm.getCallBaseGasPrice.call(callKey)

    wait_for_block(rpc_client, alarm.getCallTargetBlock.call(callKey), 120)
    call_txn_hash = alarm.doCall.sendTransaction(callKey, gas_price=base_gas_price - 10)

    call_txn_receipt = wait_for_transaction(alarm._meta.rpc_client, call_txn_hash)

    assert alarm.checkIfCalled.call(callKey) is True

    recorded_gas_used = alarm.getCallGasUsed.call(callKey)
    actual_gas_used = int(call_txn_receipt['gasUsed'], 16)

    try:
        assert actual_gas_used == recorded_gas_used
    except AssertionError:
        assert actual_gas_used == recorded_gas_used + 64
def test_cost_of_duplicate_call(geth_node, rpc_client, deployed_contracts):
    alarm = deployed_contracts.Alarm
    client_contract = deployed_contracts.NoArgs

    deposit_amount = get_max_gas(rpc_client) * rpc_client.get_gas_price() * 20
    alarm.deposit.sendTransaction(client_contract._meta.address, value=deposit_amount)

    txn_hash = client_contract.scheduleIt.sendTransaction(alarm._meta.address)
    wait_for_transaction(client_contract._meta.rpc_client, txn_hash)

    assert client_contract.value.call() is False

    callKey = alarm.getLastCallKey.call()
    assert callKey is not None

    assert alarm.checkIfCalled.call(callKey) is False

    wait_for_block(rpc_client, alarm.getCallTargetBlock.call(callKey), 120)
    call_txn_hash = alarm.doCall.sendTransaction(callKey)
    call_txn_receipt = wait_for_transaction(alarm._meta.rpc_client, call_txn_hash)

    assert alarm.checkIfCalled.call(callKey) is True

    call_txn_hash = alarm.doCall.sendTransaction(callKey)
    call_txn_receipt = wait_for_transaction(alarm._meta.rpc_client, call_txn_hash)

    assert call_txn_receipt['gasUsed'] == '0x67f0'
def test_caller_payout(geth_node, geth_coinbase, rpc_client, deployed_contracts):
    alarm = deployed_contracts.Alarm
    client_contract = deployed_contracts.PassesUInt

    deposit_amount = get_max_gas(rpc_client) * rpc_client.get_gas_price() * 20
    alarm.deposit.sendTransaction(client_contract._meta.address, value=deposit_amount)

    txn_hash = client_contract.scheduleIt.sendTransaction(alarm._meta.address, 3)
    wait_for_transaction(client_contract._meta.rpc_client, txn_hash)

    assert client_contract.value.call() == 0

    callKey = alarm.getLastCallKey.call()
    assert callKey is not None

    assert alarm.getCallPayout.call(callKey) == 0
    assert alarm.accountBalances.call(geth_coinbase) == 0

    wait_for_block(rpc_client, alarm.getCallTargetBlock.call(callKey), 120)
    call_txn_hash = alarm.doCall.sendTransaction(callKey)
    wait_for_transaction(alarm._meta.rpc_client, call_txn_hash)

    gas_used = alarm.getCallGasUsed.call(callKey)
    gas_price = alarm.getCallGasPrice.call(callKey)
    base_gas_price = alarm.getCallBaseGasPrice.call(callKey)

    scalar = 100 * base_gas_price / (abs(gas_price - base_gas_price) + base_gas_price)
    expected_payout = gas_used * gas_price * scalar * 101 / 10000

    balance = alarm.accountBalances.call(geth_coinbase)
    assert balance == expected_payout
    assert alarm.getCallPayout.call(callKey) == balance
def test_call_fee(geth_node, rpc_client, deployed_contracts):
    alarm = deployed_contracts.Alarm
    client_contract = deployed_contracts.PassesUInt

    deposit_amount = get_max_gas(rpc_client) * rpc_client.get_gas_price() * 20
    alarm.deposit.sendTransaction(client_contract._meta.address, value=deposit_amount)

    txn_hash = client_contract.scheduleIt.sendTransaction(alarm._meta.address, 3)
    wait_for_transaction(client_contract._meta.rpc_client, txn_hash)

    assert client_contract.value.call() == 0

    callKey = alarm.getLastCallKey.call()
    assert callKey is not None

    before_balance = alarm.accountBalances.call(client_contract._meta.address)

    wait_for_block(rpc_client, alarm.getCallTargetBlock.call(callKey), 120)
    call_txn_hash = alarm.doCall.sendTransaction(callKey)
    wait_for_transaction(alarm._meta.rpc_client, call_txn_hash)

    after_balance = alarm.accountBalances.call(client_contract._meta.address)
    fee = alarm.getCallFee.call(callKey)
    payout = alarm.getCallPayout.call(callKey)

    assert after_balance == before_balance - payout - fee
Example #9
0
def test_scheduled_call_execution_without_pool(geth_node, geth_node_config, deploy_client, deployed_contracts, contracts):
    alarm = deployed_contracts.Alarm
    client_contract = deployed_contracts.SpecifyBlock

    deposit_amount = get_max_gas(deploy_client) * deploy_client.get_gas_price() * 20
    alarm.deposit.sendTransaction(client_contract._meta.address, value=deposit_amount)

    target_block = deploy_client.get_block_number() + 45

    txn_hash = client_contract.scheduleIt.sendTransaction(alarm._meta.address, target_block)
    wait_for_transaction(deploy_client, txn_hash)

    call_key = alarm.getLastCallKey()
    assert call_key is not None

    scheduled_call = ScheduledCall(alarm, call_key)
    block_sage = scheduled_call.block_sage
    pool_manager = PoolManager(alarm, block_sage=block_sage)

    time.sleep(1)

    assert pool_manager.in_any_pool is False
    scheduled_call.execute_async()

    # let the scheduled call do it's thing.
    assert block_sage.current_block_number < target_block

    wait_till = scheduled_call.target_block + 10
    wait_for_block(
        deploy_client,
        wait_till,
        2 * block_sage.estimated_time_to_block(wait_till),
    )

    for i in range(5):
        if scheduled_call.txn_hash:
            break
        time.sleep(block_sage.block_time)

    assert scheduled_call.txn_hash
    assert scheduled_call.txn_receipt
    assert scheduled_call.txn

    assert alarm.checkIfCalled(scheduled_call.call_key)
    assert scheduled_call.was_called
    assert scheduled_call.target_block <= scheduled_call.called_at_block
    assert scheduled_call.called_at_block <= scheduled_call.target_block + scheduled_call.grace_period
def test_scheduled_call_execution_without_pool(
    geth_node, geth_node_config, deploy_client, deployed_contracts, contracts
):
    alarm = deployed_contracts.Alarm
    client_contract = deployed_contracts.SpecifyBlock

    deposit_amount = get_max_gas(deploy_client) * deploy_client.get_gas_price() * 20
    alarm.deposit.sendTransaction(client_contract._meta.address, value=deposit_amount)

    target_block = deploy_client.get_block_number() + 45

    txn_hash = client_contract.scheduleIt.sendTransaction(alarm._meta.address, target_block)
    wait_for_transaction(deploy_client, txn_hash)

    call_key = alarm.getLastCallKey()
    assert call_key is not None

    scheduled_call = ScheduledCall(alarm, call_key)
    block_sage = scheduled_call.block_sage
    pool_manager = PoolManager(alarm, block_sage=block_sage)

    time.sleep(1)

    assert pool_manager.in_any_pool is False
    scheduled_call.execute_async()

    # let the scheduled call do it's thing.
    assert block_sage.current_block_number < target_block

    wait_till = scheduled_call.target_block + 10
    wait_for_block(deploy_client, wait_till, 2 * block_sage.estimated_time_to_block(wait_till))

    for i in range(5):
        if scheduled_call.txn_hash:
            break
        time.sleep(block_sage.block_time)

    assert scheduled_call.txn_hash
    assert scheduled_call.txn_receipt
    assert scheduled_call.txn

    assert alarm.checkIfCalled(scheduled_call.call_key)
    assert scheduled_call.was_called
    assert scheduled_call.target_block <= scheduled_call.called_at_block
    assert scheduled_call.called_at_block <= scheduled_call.target_block + scheduled_call.grace_period
def test_scheduler(geth_node, geth_node_config, deploy_client,
                   deployed_contracts, contracts):
    block_sage = BlockSage(deploy_client)

    alarm = deployed_contracts.Alarm
    client_contract = deployed_contracts.SpecifyBlock

    deposit_amount = get_max_gas(
        deploy_client) * deploy_client.get_gas_price() * 20
    alarm.deposit.sendTransaction(client_contract._meta.address,
                                  value=deposit_amount)

    anchor_block = deploy_client.get_block_number()

    blocks = (1, 4, 4, 8, 30, 40, 50, 60)

    call_keys = []

    for n in blocks:
        wait_for_transaction(
            deploy_client,
            client_contract.scheduleIt.sendTransaction(alarm._meta.address,
                                                       anchor_block + 100 + n))

        last_call_key = alarm.getLastCallKey()
        assert last_call_key is not None

        call_keys.append(last_call_key)

    pool_manager = PoolManager(alarm, block_sage)
    scheduler = Scheduler(alarm, pool_manager, block_sage=block_sage)
    scheduler.monitor_async()

    final_block = anchor_block + 100 + 70
    wait_for_block(
        deploy_client,
        final_block,
        2 * block_sage.estimated_time_to_block(final_block),
    )

    scheduler.stop()
    block_sage.stop()

    results = [alarm.checkIfCalled(k) for k in call_keys]
    assert all(results)
def test_scheduled_call_python_object(geth_node, geth_coinbase, rpc_client, deployed_contracts, contracts):
    alarm = deployed_contracts.Alarm
    caller_pool = contracts.CallerPool(alarm.getCallerPoolAddress.call(), rpc_client)
    client_contract = deployed_contracts.PassesUInt

    deposit_amount = get_max_gas(rpc_client) * rpc_client.get_gas_price() * 20
    alarm.deposit.sendTransaction(client_contract._meta.address, value=deposit_amount)

    txn_hash = client_contract.scheduleIt.sendTransaction(alarm._meta.address, 3)
    wait_for_transaction(client_contract._meta.rpc_client, txn_hash)
    txn = rpc_client.get_transaction_by_hash(txn_hash)

    assert client_contract.value.call() == 0

    callKey = alarm.getLastCallKey.call()
    assert callKey is not None

    owner = '0xd3cda913deb6f67967b99d67acdfa1712c293601'

    wait_for_block(rpc_client, alarm.getCallTargetBlock.call(callKey), 120)
    txn_receipt = wait_for_transaction(alarm._meta.rpc_client, alarm.doCall.sendTransaction(callKey))
    call_txn = rpc_client.get_transaction_by_hash(txn_receipt['transactionHash'])

    pool_manager = PoolManager(caller_pool)
    scheduled_call = ScheduledCall(alarm, pool_manager, callKey)

    assert scheduled_call.scheduler_account_balance == alarm.accountBalances.call(client_contract._meta.address)
    assert scheduled_call.target_block == alarm.getCallTargetBlock.call(callKey)
    assert scheduled_call.scheduled_by == client_contract._meta.address
    assert scheduled_call.called_at_block == int(txn_receipt['blockNumber'], 16)
    assert scheduled_call.contract_address == client_contract._meta.address
    assert scheduled_call.base_gas_price == int(txn['gasPrice'], 16)
    assert scheduled_call.gas_price == int(call_txn['gasPrice'], 16)
    try:
        assert scheduled_call.gas_used == int(txn_receipt['gasUsed'], 16)
    except AssertionError:
        assert scheduled_call.gas_used == int(txn_receipt['gasUsed'], 16) + 64
    assert scheduled_call.payout == alarm.accountBalances.call(geth_coinbase) == alarm.getCallPayout.call(callKey)
    assert scheduled_call.fee == alarm.accountBalances.call(owner) == alarm.getCallFee.call(callKey)
    assert scheduled_call.abi_signature == client_contract.doIt.encoded_abi_function_signature == alarm.getCallABISignature.call(callKey)
    assert scheduled_call.is_cancelled is False
    assert scheduled_call.was_called is True
    assert scheduled_call.was_successful is True
    assert utils.encode_hex(scheduled_call.data_hash) == 'c2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b'
def test_executing_scheduled_call_with_int(geth_node, rpc_client, deployed_contracts):
    alarm = deployed_contracts.Alarm
    client_contract = deployed_contracts.PassesInt

    deposit_amount = get_max_gas(rpc_client) * rpc_client.get_gas_price() * 20
    alarm.deposit.sendTransaction(client_contract._meta.address, value=deposit_amount)

    txn_hash = client_contract.scheduleIt.sendTransaction(alarm._meta.address, -12345)
    wait_for_transaction(client_contract._meta.rpc_client, txn_hash)

    assert client_contract.value.call() == 0

    callKey = alarm.getLastCallKey.call()
    assert callKey is not None
    wait_for_block(rpc_client, alarm.getCallTargetBlock.call(callKey), 120)
    call_txn_hash = alarm.doCall.sendTransaction(callKey)
    wait_for_transaction(alarm._meta.rpc_client, call_txn_hash)

    assert client_contract.value.call() == -12345
def test_executing_scheduled_call_with_address(geth_node, rpc_client, deployed_contracts):
    alarm = deployed_contracts.Alarm
    client_contract = deployed_contracts.PassesAddress

    deposit_amount = get_max_gas(rpc_client) * rpc_client.get_gas_price() * 20
    alarm.deposit.sendTransaction(client_contract._meta.address, value=deposit_amount)

    address = '0xc948453368e5ddc7bc00bb52b5809138217a068d'

    txn_hash = client_contract.scheduleIt.sendTransaction(alarm._meta.address, address)
    wait_for_transaction(client_contract._meta.rpc_client, txn_hash)

    assert client_contract.value.call() == '0x0000000000000000000000000000000000000000'

    callKey = alarm.getLastCallKey.call()
    assert callKey is not None
    wait_for_block(rpc_client, alarm.getCallTargetBlock.call(callKey), 120)
    call_txn_hash = alarm.doCall.sendTransaction(callKey)
    wait_for_transaction(alarm._meta.rpc_client, call_txn_hash)

    assert client_contract.value.call() == address
def test_exiting_pool(geth_node, geth_coinbase, rpc_client, deployed_contracts):
    caller_pool = deployed_contracts.CallerPool

    assert caller_pool.callerBonds.call(geth_coinbase) == 0
    deposit_amount = caller_pool.getMinimumBond.call() * 10

    txn_1_hash = caller_pool.depositBond.sendTransaction(value=deposit_amount)
    wait_for_transaction(rpc_client, txn_1_hash)

    assert caller_pool.getActivePoolKey.call() == 0
    assert caller_pool.getNextPoolKey.call() == 0
    assert caller_pool.isInAnyPool.call(geth_coinbase) is False
    assert caller_pool.canEnterPool.call(geth_coinbase) is True
    assert caller_pool.canExitPool.call(geth_coinbase) is False

    wait_for_transaction(rpc_client, caller_pool.enterPool.sendTransaction())
    first_pool_key = caller_pool.getNextPoolKey.call()
    wait_for_block(rpc_client, first_pool_key, 180)

    assert caller_pool.getActivePoolKey.call() == first_pool_key
    assert caller_pool.getNextPoolKey.call() == 0
    assert caller_pool.isInAnyPool.call(geth_coinbase) is True
    assert caller_pool.isInPool.call(geth_coinbase, first_pool_key) is True
    assert caller_pool.canEnterPool.call(geth_coinbase) is False
    assert caller_pool.canExitPool.call(geth_coinbase) is True

    wait_for_transaction(rpc_client, caller_pool.exitPool.sendTransaction())
    second_pool_key = caller_pool.getNextPoolKey.call()

    assert second_pool_key > first_pool_key
    assert caller_pool.isInAnyPool.call(geth_coinbase) is True
    assert caller_pool.isInPool.call(geth_coinbase, first_pool_key) is True

    wait_for_block(rpc_client, second_pool_key, 180)

    assert caller_pool.getActivePoolKey.call() == second_pool_key
    assert caller_pool.getNextPoolKey.call() == 0
    assert caller_pool.isInAnyPool.call(geth_coinbase) is False
    assert caller_pool.canEnterPool.call(geth_coinbase) is True
    assert caller_pool.canExitPool.call(geth_coinbase) is False
def test_infinite_loop_protection(geth_node, rpc_client, deployed_contracts):
    alarm = deployed_contracts.Alarm
    client_contract = deployed_contracts.InfiniteLoop

    deposit_amount = get_max_gas(rpc_client) * rpc_client.get_gas_price() * 20
    alarm.deposit.sendTransaction(client_contract._meta.address, value=deposit_amount)
    rpc_client.send_transaction(to=client_contract._meta.address, value=1000000000)

    txn_hash = client_contract.scheduleIt.sendTransaction(alarm._meta.address)
    wait_for_transaction(client_contract._meta.rpc_client, txn_hash)

    callKey = alarm.getLastCallKey.call()
    assert callKey is not None
    wait_for_block(rpc_client, alarm.getCallTargetBlock.call(callKey), 300)
    call_txn_hash = alarm.doCall.sendTransaction(callKey)
    call_txn_receipt = wait_for_transaction(alarm._meta.rpc_client, call_txn_hash)
    call_txn = rpc_client.get_transaction_by_hash(call_txn_hash)

    assert alarm.checkIfCalled.call(callKey) is True
    assert alarm.checkIfSuccess.call(callKey) is False

    assert call_txn_receipt['gasUsed'] == call_txn['gas']
def test_funds_are_locked_during_execution(geth_node, rpc_client, deployed_contracts):
    alarm = deployed_contracts.Alarm
    client_contract = deployed_contracts.WithdrawsDuringCall

    wait_for_transaction(rpc_client, client_contract.setAlarm.sendTransaction(alarm._meta.address))

    deposit_amount = get_max_gas(rpc_client) * rpc_client.get_gas_price() * 20
    alarm.deposit.sendTransaction(client_contract._meta.address, value=deposit_amount)

    txn_hash = client_contract.scheduleIt.sendTransaction()
    wait_for_transaction(client_contract._meta.rpc_client, txn_hash)

    assert client_contract.wasCalled.call() is False

    callKey = alarm.getLastCallKey.call()
    assert callKey is not None

    pre_balance = client_contract.getAlarmBalance.call()
    assert pre_balance == deposit_amount

    wait_for_block(rpc_client, alarm.getCallTargetBlock.call(callKey), 120)
    call_txn_hash = alarm.doCall.sendTransaction(callKey)
    wait_for_transaction(alarm._meta.rpc_client, call_txn_hash)

    fee = alarm.getCallFee.call(callKey)
    payout = alarm.getCallPayout.call(callKey)
    withdrawn_amount = client_contract.withdrawAmount.call()

    assert all(v > 0 for v in (fee, payout, withdrawn_amount))

    post_balance = client_contract.getAlarmBalance.call()
    # Sanity check that an underflow error didn't occur
    assert post_balance < pre_balance
    # During the call, the WithdrawsDuringCall contract tries to withdraw it's
    # entire account balance.  This should only leave the max call cost minus
    # fees left in the account.
    assert post_balance == pre_balance - fee - payout - withdrawn_amount

    assert alarm.checkIfCalled.call(callKey) is True
def test_check_if_call_successful_for_failed_call(geth_node, rpc_client, deployed_contracts):
    alarm = deployed_contracts.Alarm
    client_contract = deployed_contracts.InfiniteLoop

    deposit_amount = get_max_gas(rpc_client) * rpc_client.get_gas_price() * 20
    alarm.deposit.sendTransaction(client_contract._meta.address, value=deposit_amount)

    txn_hash = client_contract.scheduleIt.sendTransaction(alarm._meta.address)
    wait_for_transaction(client_contract._meta.rpc_client, txn_hash)

    callKey = alarm.getLastCallKey.call()
    assert callKey is not None

    assert alarm.checkIfCalled.call(callKey) is False
    assert alarm.checkIfSuccess.call(callKey) is False

    wait_for_block(rpc_client, alarm.getCallTargetBlock.call(callKey), 300)
    call_txn_hash = alarm.doCall.sendTransaction(callKey)
    wait_for_transaction(alarm._meta.rpc_client, call_txn_hash)

    assert alarm.checkIfCalled.call(callKey) is True
    assert alarm.checkIfSuccess.call(callKey) is False
def test_scheduler(geth_node, geth_node_config, deploy_client, deployed_contracts, contracts):
    block_sage = BlockSage(deploy_client)

    alarm = deployed_contracts.Alarm
    client_contract = deployed_contracts.SpecifyBlock

    deposit_amount = get_max_gas(deploy_client) * deploy_client.get_gas_price() * 20
    alarm.deposit.sendTransaction(client_contract._meta.address, value=deposit_amount)

    anchor_block = deploy_client.get_block_number()

    blocks = (1, 4, 4, 8, 30, 40, 50, 60)

    call_keys = []

    for n in blocks:
        wait_for_transaction(deploy_client, client_contract.scheduleIt.sendTransaction(alarm._meta.address, anchor_block + 100 + n))

        last_call_key = alarm.getLastCallKey()
        assert last_call_key is not None

        call_keys.append(last_call_key)

    pool_manager = PoolManager(alarm, block_sage)
    scheduler = Scheduler(alarm, pool_manager, block_sage=block_sage)
    scheduler.monitor_async()

    final_block = anchor_block + 100 + 70
    wait_for_block(
        deploy_client,
        final_block,
        2 * block_sage.estimated_time_to_block(final_block),
    )

    scheduler.stop()
    block_sage.stop()

    results = [alarm.checkIfCalled(k) for k in call_keys]
    assert all(results)
def test_pool_manager(geth_node, geth_coinbase, rpc_client, deployed_contracts):
    caller_pool = deployed_contracts.CallerPool

    deposit_amount = caller_pool.getMinimumBond.call() * 10
    # Put in our bond
    wait_for_transaction(
        rpc_client, caller_pool.depositBond.sendTransaction(value=deposit_amount)
    )

    pool_manager = PoolManager(caller_pool)

    assert pool_manager.in_any_pool is False

    pool_manager.monitor_async()

    wait_for_block(rpc_client, rpc_client.get_block_number() + 5, 180)

    first_pool_number = pool_manager.next_pool

    assert first_pool_number > 0
    assert pool_manager.in_any_pool is True
    assert pool_manager.in_next_pool is True

    wait_for_block(rpc_client, first_pool_number + 5, 180)

    # Now leave the pool (like we got kicked out)

    wait_for_transaction(rpc_client, caller_pool.exitPool.sendTransaction())

    second_pool_number = pool_manager.next_pool
    assert second_pool_number > first_pool_number

    # we should not be in the next pool since we left it.
    assert pool_manager.in_next_pool is False

    wait_for_block(rpc_client, second_pool_number + 5, 180)

    # pool manager should have rejoined now that the next pool is live and we
    # aren't in a freeze.
    third_pool_number = pool_manager.next_pool
    assert third_pool_number > second_pool_number

    assert pool_manager.in_active_pool is False
    assert pool_manager.in_next_pool is True
def test_rotating_tree_right(geth_node, rpc_client, deployed_contracts):
    """
    Before right rotation
    ====================

         1
          \
           15
          /  \
         4    \
        / \    \
       3   7    \
                 1800
                /    \
               /      \
             1700      2000
                      /    \
                     /      \
                    /        \
                   1900       2001


    After right rotation
    ====================

               15
              /  \
             /    \
            /      \
           1        1800
            \      /  \
             4    /    \
            / \  1700   2000
           3   7       /  \
                     1900  2001


    """
    initial_state = {
        (2001, (None, None)),
        (1900, (None, None)),
        (1700, (None, None)),
        (4, (3, 7)),
        (3, (None, None)),
        (7, (None, None)),
        (1, (None, 15)),
        (15, (4, 1800)),
        (1800, (1700, 2000)),
        (2000, (1900, 2001)),
    }
    alarm = deployed_contracts.Alarm
    client_contract = deployed_contracts.SpecifyBlock

    anchor_block = rpc_client.get_block_number()

    blocks = (2000, 1800, 2001, 15, 1700, 1900, 1, 4, 3, 7)

    call_keys = []

    for n in blocks:
        wait_for_transaction(rpc_client, client_contract.scheduleIt.sendTransaction(alarm._meta.address, anchor_block + 65 + n))

        last_call_key = alarm.getLastCallKey.call()
        assert last_call_key is not None

        call_keys.append(last_call_key)

    assert len(set(call_keys)) == len(blocks)

    calls_to_blocks = dict(zip(call_keys, blocks))
    calls_to_blocks[None] = None

    initial_state = {
        (2001, (None, None)),
        (1900, (None, None)),
        (1700, (None, None)),
        (4, (3, 7)),
        (3, (None, None)),
        (7, (None, None)),
        (1, (None, 15)),
        (15, (4, 1800)),
        (1800, (1700, 2000)),
        (2000, (1900, 2001)),
    }

    assert get_tree_state(alarm, calls_to_blocks) == initial_state

    wait_for_block(rpc_client, anchor_block + 65 + 16, max_wait=180)

    wait_for_transaction(rpc_client, alarm.rotateTree.sendTransaction())

    expected_state = {
        (1700, (None, None)),
        (2001, (None, None)),
        (3, (None, None)),
        (1, (None, 4)),
        (4, (3, 7)),
        (15, (1, 1800)),
        (2000, (1900, 2001)),
        (1900, (None, None)),
        (7, (None, None)),
        (1800, (1700, 2000)),
    }

    assert get_tree_state(alarm, calls_to_blocks) == expected_state
def test_call_window_divided_between_callers(geth_node, geth_coinbase, rpc_client, deployed_contracts, contracts):
    alarm = deployed_contracts.Alarm
    caller_pool = contracts.CallerPool(alarm.getCallerPoolAddress.call(), rpc_client)
    joiner = deployed_contracts.JoinsPool
    client_contract = deployed_contracts.NoArgs

    # Put in our deposit with the alarm contract.
    deposit_amount = get_max_gas(rpc_client) * rpc_client.get_gas_price() * 20
    alarm.deposit.sendTransaction(client_contract._meta.address, value=deposit_amount)

    wait_for_transaction(rpc_client, joiner.setCallerPool.sendTransaction(caller_pool._meta.address))

    assert caller_pool.callerBonds.call(geth_coinbase) == 0
    deposit_amount = caller_pool.getMinimumBond.call() * 10
    # Put in our bond
    wait_for_transaction(
        rpc_client, caller_pool.depositBond.sendTransaction(value=deposit_amount)
    )

    # Put the contract's bond in
    wait_for_transaction(
        rpc_client,
        rpc_client.send_transaction(to=joiner._meta.address, value=deposit_amount)
    )
    wait_for_transaction(
        rpc_client, joiner.deposit.sendTransaction(deposit_amount)
    )

    # Both join the pool
    wait_for_transaction(rpc_client, joiner.enter.sendTransaction())
    wait_for_transaction(rpc_client, caller_pool.enterPool.sendTransaction())

    # New pool is formed but not active
    first_pool_key = caller_pool.getNextPoolKey.call()
    assert first_pool_key > 0

    # Wait for it to become active
    wait_for_block(rpc_client, first_pool_key, 180)

    # We should both be in the pool
    assert caller_pool.getActivePoolKey.call() == first_pool_key
    assert caller_pool.isInPool.call(joiner._meta.address, first_pool_key) is True
    assert caller_pool.isInPool.call(geth_coinbase, first_pool_key) is True

    # Schedule the function call.
    txn_hash = client_contract.scheduleIt.sendTransaction(alarm._meta.address)
    wait_for_transaction(client_contract._meta.rpc_client, txn_hash)

    callKey = alarm.getLastCallKey.call()
    assert callKey is not None

    target_block = alarm.getCallTargetBlock.call(callKey)
    grace_period = alarm.getCallGracePeriod.call(callKey)

    callers = [
        caller_pool.getCallerFromPool.call(callKey, target_block, grace_period, target_block + n)
        for n in range(grace_period)
    ]
    caller_a = callers[0]
    caller_b = callers[4]

    assert {geth_coinbase, joiner._meta.address, '0x0000000000000000000000000000000000000000'} == set(callers)

    call_on_block = None

    for i, caller in enumerate(callers):
        if i / 4 + 2 > len(callers) / 4:
            assert caller == '0x0000000000000000000000000000000000000000'
        elif (i / 4) % 2 == 0:
            assert caller == caller_a
        else:
            assert caller == caller_b

        if call_on_block is None and i > 4 and caller == geth_coinbase:
            call_on_block = target_block + i * 4

    wait_for_block(rpc_client, call_on_block, 240)

    assert caller_pool.getNextPoolKey.call() == 0
    before_balance = caller_pool.callerBonds.call(joiner._meta.address)
    assert alarm.checkIfCalled.call(callKey) is False

    call_txn_hash = alarm.doCall.sendTransaction(callKey)
    call_txn_receipt = wait_for_transaction(alarm._meta.rpc_client, call_txn_hash)
    call_txn = rpc_client.get_transaction_by_hash(call_txn_hash)
    call_block = rpc_client.get_block_by_hash(call_txn_receipt['blockHash'])

    after_balance = caller_pool.callerBonds.call(joiner._meta.address)
    assert alarm.checkIfCalled.call(callKey) is True

    assert after_balance < before_balance

    minimum_bond = int(call_block['gasLimit'], 16) * int(call_txn['gasPrice'], 16)
    assert after_balance == before_balance - minimum_bond

    next_pool_key = caller_pool.getNextPoolKey.call()
    assert next_pool_key > 0

    assert caller_pool.isInPool.call(joiner._meta.address, first_pool_key) is True
    assert caller_pool.isInPool.call(joiner._meta.address, next_pool_key) is False
def test_call_window_divided_between_callers(geth_node, geth_coinbase, rpc_client, deployed_contracts, contracts):
    alarm = deployed_contracts.Alarm
    caller_pool = contracts.CallerPool(alarm.getCallerPoolAddress.call(), rpc_client)
    joiner = deployed_contracts.JoinsPool
    client_contract = deployed_contracts.NoArgs

    # Put in our deposit with the alarm contract.
    deposit_amount = get_max_gas(rpc_client) * rpc_client.get_gas_price() * 20
    alarm.deposit.sendTransaction(client_contract._meta.address, value=deposit_amount)

    wait_for_transaction(rpc_client, joiner.setCallerPool.sendTransaction(caller_pool._meta.address))

    assert caller_pool.callerBonds.call(geth_coinbase) == 0
    deposit_amount = caller_pool.getMinimumBond.call() * 10
    # Put in our bond
    wait_for_transaction(
        rpc_client, caller_pool.depositBond.sendTransaction(value=deposit_amount)
    )

    # Put the contract's bond in
    wait_for_transaction(
        rpc_client,
        rpc_client.send_transaction(to=joiner._meta.address, value=deposit_amount)
    )
    wait_for_transaction(
        rpc_client, joiner.deposit.sendTransaction(deposit_amount)
    )

    # Both join the pool
    wait_for_transaction(rpc_client, joiner.enter.sendTransaction())
    wait_for_transaction(rpc_client, caller_pool.enterPool.sendTransaction())

    # New pool is formed but not active
    first_pool_key = caller_pool.getNextPoolKey.call()
    assert first_pool_key > 0

    # Wait for it to become active
    wait_for_block(rpc_client, first_pool_key, 180)

    # We should both be in the pool
    assert caller_pool.getActivePoolKey.call() == first_pool_key
    assert caller_pool.isInPool.call(joiner._meta.address, first_pool_key) is True
    assert caller_pool.isInPool.call(geth_coinbase, first_pool_key) is True

    # Schedule the function call.
    for _ in range(5):
        txn_hash = client_contract.scheduleIt.sendTransaction(alarm._meta.address)
        wait_for_transaction(client_contract._meta.rpc_client, txn_hash)

        callKey = alarm.getLastCallKey.call()
        assert callKey is not None

        target_block = alarm.getCallTargetBlock.call(callKey)
        grace_period = alarm.getCallGracePeriod.call(callKey)
        first_caller = caller_pool.getDesignatedCaller.call(callKey, target_block, grace_period, target_block)
        if first_caller == geth_coinbase:
            break
    else:
        raise ValueError("Was never first caller")

    wait_for_block(rpc_client, target_block, 240)

    before_balance = caller_pool.callerBonds.call(joiner._meta.address)
    assert alarm.checkIfCalled.call(callKey) is False

    call_txn_hash = alarm.doCall.sendTransaction(callKey)
    wait_for_transaction(alarm._meta.rpc_client, call_txn_hash)

    after_balance = caller_pool.callerBonds.call(joiner._meta.address)
    assert alarm.checkIfCalled.call(callKey) is True

    assert after_balance == before_balance
def test_scheduled_call_execution_with_pool(geth_node, geth_coinbase, geth_node_config, deploy_client, deployed_contracts, contracts):
    alarm = deployed_contracts.Alarm
    joiner = deployed_contracts.JoinsPool
    client_contract = deployed_contracts.SpecifyBlock

    coinbase = geth_coinbase

    block_sage = BlockSage(deploy_client)

    # Put in our deposit with the alarm contract.
    deposit_amount = get_max_gas(deploy_client) * deploy_client.get_gas_price() * 20
    alarm.deposit.sendTransaction(client_contract._meta.address, value=deposit_amount)

    wait_for_transaction(deploy_client, joiner.setCallerPool.sendTransaction(alarm._meta.address))

    assert alarm.getBondBalance(coinbase) == 0
    bond_amount = alarm.getMinimumBond() * 10
    # Put in our bond
    wait_for_transaction(
        deploy_client, alarm.depositBond.sendTransaction(value=bond_amount)
    )

    # Put the contract's bond in
    wait_for_transaction(
        deploy_client,
        deploy_client.send_transaction(to=joiner._meta.address, value=bond_amount)
    )
    wait_for_transaction(
        deploy_client, joiner.deposit.sendTransaction(bond_amount)
    )

    # Both join the pool
    wait_for_transaction(deploy_client, joiner.enter.sendTransaction())
    wait_for_transaction(deploy_client, alarm.enterPool.sendTransaction())

    # New pool is formed but not active
    first_generation_id = alarm.getNextGenerationId()
    assert first_generation_id > 0

    # Go ahead and schedule the call.
    generation_start_at = alarm.getGenerationStartAt(first_generation_id)
    target_block = generation_start_at + 5

    txn_hash = client_contract.scheduleIt.sendTransaction(alarm._meta.address, target_block)
    wait_for_transaction(deploy_client, txn_hash)

    # Wait for the pool to become active
    wait_for_block(
        deploy_client,
        generation_start_at,
        2 * block_sage.estimated_time_to_block(generation_start_at),
    )

    # We should both be in the pool
    assert alarm.getCurrentGenerationId() == first_generation_id
    assert alarm.isInGeneration(joiner._meta.address, first_generation_id) is True
    assert alarm.isInGeneration(coinbase, first_generation_id) is True

    call_key = alarm.getLastCallKey()
    assert call_key is not None

    scheduled_call = ScheduledCall(alarm, call_key, block_sage=block_sage)
    pool_manager = PoolManager(alarm, block_sage=block_sage)

    scheduled_call.execute_async()

    wait_for_block(
        deploy_client,
        scheduled_call.target_block,
        2 * block_sage.estimated_time_to_block(scheduled_call.target_block),
    )

    for i in range(alarm.getCallWindowSize() * 2):
        if scheduled_call.txn_hash:
            break
        time.sleep(block_sage.block_time)

    assert scheduled_call.txn_hash
    assert scheduled_call.txn_receipt
    assert scheduled_call.txn

    assert scheduled_call.was_called
    assert alarm.checkIfCalled(scheduled_call.call_key)
    assert scheduled_call.target_block <= scheduled_call.called_at_block
    assert scheduled_call.called_at_block <= scheduled_call.target_block + scheduled_call.grace_period
def test_free_for_all_window_awards_mega_bonus(geth_node, geth_coinbase, rpc_client, deployed_contracts, contracts):
    alarm = deployed_contracts.Alarm
    caller_pool = contracts.CallerPool(alarm.getCallerPoolAddress.call(), rpc_client)
    client_contract = deployed_contracts.NoArgs
    joiner_a = deployed_contracts.JoinsPool
    deploy_txn = deploy_contract(rpc_client, contracts.JoinsPool, _from=geth_coinbase, gas=get_max_gas(rpc_client))
    joiner_b = contracts.JoinsPool(get_contract_address_from_txn(rpc_client, deploy_txn, 30), rpc_client)

    # Put in our deposit with the alarm contract.
    deposit_amount = get_max_gas(rpc_client) * rpc_client.get_gas_price() * 20
    alarm.deposit.sendTransaction(client_contract._meta.address, value=deposit_amount)

    wait_for_transaction(rpc_client, joiner_a.setCallerPool.sendTransaction(caller_pool._meta.address))
    wait_for_transaction(rpc_client, joiner_b.setCallerPool.sendTransaction(caller_pool._meta.address))

    assert caller_pool.callerBonds.call(geth_coinbase) == 0
    deposit_amount = caller_pool.getMinimumBond.call() * 10
    # Put in our bond
    wait_for_transaction(rpc_client, caller_pool.depositBond.sendTransaction(value=deposit_amount))

    # Put contract A's bond in
    wait_for_transaction(rpc_client, rpc_client.send_transaction(to=joiner_a._meta.address, value=deposit_amount))
    wait_for_transaction(rpc_client, joiner_a.deposit.sendTransaction(deposit_amount))
    # Put contract B's bond in
    wait_for_transaction(rpc_client, rpc_client.send_transaction(to=joiner_b._meta.address, value=deposit_amount))
    wait_for_transaction(rpc_client, joiner_b.deposit.sendTransaction(deposit_amount))

    # All join the pool
    wait_for_transaction(rpc_client, joiner_a.enter.sendTransaction())
    wait_for_transaction(rpc_client, joiner_b.enter.sendTransaction())
    wait_for_transaction(rpc_client, caller_pool.enterPool.sendTransaction())

    # New pool is formed but not active
    first_pool_key = caller_pool.getNextPoolKey.call()
    assert first_pool_key > 0

    # Wait for it to become active
    wait_for_block(rpc_client, first_pool_key, 240)

    # We should both be in the pool
    assert caller_pool.getActivePoolKey.call() == first_pool_key
    assert caller_pool.isInPool.call(joiner_a._meta.address, first_pool_key) is True
    assert caller_pool.isInPool.call(joiner_b._meta.address, first_pool_key) is True
    assert caller_pool.isInPool.call(geth_coinbase, first_pool_key) is True

    # Schedule the function call.
    txn_hash = client_contract.setGracePeriod.sendTransaction(24)
    txn_hash = client_contract.scheduleIt.sendTransaction(alarm._meta.address)
    wait_for_transaction(client_contract._meta.rpc_client, txn_hash)

    callKey = alarm.getLastCallKey.call()
    assert callKey is not None

    target_block = alarm.getCallTargetBlock.call(callKey)
    grace_period = alarm.getCallGracePeriod.call(callKey)

    wait_for_block(rpc_client, target_block + grace_period - 4, 300)

    my_before_balance = caller_pool.callerBonds.call(geth_coinbase)
    before_balance_a = caller_pool.callerBonds.call(joiner_a._meta.address)
    before_balance_b = caller_pool.callerBonds.call(joiner_b._meta.address)
    assert alarm.checkIfCalled.call(callKey) is False

    call_txn_hash = alarm.doCall.sendTransaction(callKey)
    call_txn_receipt = wait_for_transaction(alarm._meta.rpc_client, call_txn_hash)
    call_txn = rpc_client.get_transaction_by_hash(call_txn_hash)
    call_block = rpc_client.get_block_by_hash(call_txn_receipt["blockHash"])

    my_after_balance = caller_pool.callerBonds.call(geth_coinbase)
    after_balance_a = caller_pool.callerBonds.call(joiner_a._meta.address)
    after_balance_b = caller_pool.callerBonds.call(joiner_b._meta.address)
    assert alarm.checkIfCalled.call(callKey) is True

    assert after_balance_a < before_balance_a
    assert after_balance_b < before_balance_b

    minimum_bond = int(call_block["gasLimit"], 16) * int(call_txn["gasPrice"], 16)
    assert after_balance_a == before_balance_a - minimum_bond
    assert after_balance_b == before_balance_b - minimum_bond

    assert my_after_balance > my_before_balance
    assert my_after_balance == my_before_balance + 2 * minimum_bond

    assert caller_pool.getNextPoolKey.call() == 0
def test_pool_manager(geth_node, geth_coinbase, rpc_client, deployed_contracts):
    caller_pool = deployed_contracts.CallerPool

    pool_manager = PoolManager(caller_pool)

    # should be no pools, nor anyone in them.
    assert pool_manager.active_pool == 0
    assert pool_manager.next_pool == 0
    assert pool_manager.in_any_pool is False
    assert pool_manager.in_next_pool is False
    assert pool_manager.in_active_pool is False

    # check permissions
    assert pool_manager.can_enter_pool is True
    assert pool_manager.can_exit_pool is False

    pool_manager.run_async()

    # Wait a few blocks for the pool manager to spin up.
    wait_for_block(rpc_client, rpc_client.get_block_number() + 5, 60)

    first_pool = pool_manager.next_pool

    # should have initiated joining the next pool but won't be in it yet.
    assert pool_manager.active_pool == 0
    assert first_pool > 0
    assert pool_manager.in_any_pool is True
    assert pool_manager.in_next_pool is True
    assert pool_manager.in_active_pool is False

    # check permissions
    assert pool_manager.can_enter_pool is False
    assert pool_manager.can_exit_pool is False

    # Wait for the pool to become active.
    wait_for_block(rpc_client, first_pool)

    # we should now be in the active pool.
    assert pool_manager.active_pool == first_pool
    assert pool_manager.next_pool == 0
    assert pool_manager.in_any_pool is True
    assert pool_manager.in_next_pool is False
    assert pool_manager.in_active_pool is True

    # check permissions
    assert pool_manager.can_enter_pool is False
    assert pool_manager.can_exit_pool is True

    # Now we manually remove ourselves.
    wait_for_transaction(rpc_client, caller_pool.exitPool.sendTransaction())

    second_pool_key = pool_manager.next_pool

    # New pool should have been formed.
    assert second_pool_key > first_pool

    # Current status should not have changed except that the next pool has now
    # been formed but we aren't in it.
    assert pool_manager.in_any_pool is True
    assert pool_manager.in_next_pool is False
    assert pool_manager.in_active_pool is True

    # Wait for the next pool to become active plus a little.
    wait_for_block(rpc_client, second_pool_key + 5)

    # The manager should have rejoined.
    assert pool_manager.active_pool == second_pool_key
    assert False
def test_pool_membership_frozen_during_transition_period(geth_node, geth_coinbase, rpc_client, deployed_contracts):
    caller_pool = deployed_contracts.CallerPool
    joiner = deployed_contracts.JoinsPool

    wait_for_transaction(rpc_client, joiner.setCallerPool.sendTransaction(caller_pool._meta.address))

    assert caller_pool.callerBonds.call(geth_coinbase) == 0
    deposit_amount = caller_pool.getMinimumBond.call() * 10
    # Put in our bond
    wait_for_transaction(rpc_client, caller_pool.depositBond.sendTransaction(value=deposit_amount))

    # Put the contract's bond in
    wait_for_transaction(rpc_client, rpc_client.send_transaction(to=joiner._meta.address, value=deposit_amount))
    wait_for_transaction(rpc_client, joiner.deposit.sendTransaction(deposit_amount))
    wait_for_transaction(rpc_client, joiner.enter.sendTransaction())

    # New pool is formed but not active
    first_pool_key = caller_pool.getNextPoolKey.call()
    assert first_pool_key > 0

    # Only the contract is in the pool. (but it isn't active yet)
    assert caller_pool.getActivePoolKey.call() == 0
    assert caller_pool.isInPool.call(joiner._meta.address, first_pool_key) is True
    assert caller_pool.isInPool.call(geth_coinbase, first_pool_key) is False

    # Both are in the pool but it isn't active yet
    assert caller_pool.getActivePoolKey.call() == 0
    assert caller_pool.getNextPoolKey.call() == first_pool_key
    assert caller_pool.isInPool.call(joiner._meta.address, first_pool_key) is True
    assert caller_pool.isInPool.call(geth_coinbase, first_pool_key) is False

    # Wait for it to become active
    wait_for_block(rpc_client, first_pool_key, 180)
    assert caller_pool.getActivePoolKey.call() == first_pool_key
    assert caller_pool.getNextPoolKey.call() == 0

    # Now the contract leaves the pool.
    wait_for_transaction(rpc_client, joiner.exit.sendTransaction())
    second_pool_key = caller_pool.getNextPoolKey.call()

    # New pool should have been setup
    assert second_pool_key > first_pool_key

    # joiner should not be in next pool.
    assert caller_pool.isInPool.call(joiner._meta.address, second_pool_key) is False

    # should not be in the pool until it becomes active
    assert caller_pool.isInPool.call(joiner._meta.address, first_pool_key) is True
    assert caller_pool.isInPool.call(geth_coinbase, first_pool_key) is False

    # should still be allowed to enter
    assert caller_pool.canEnterPool.call(geth_coinbase) is True

    # wait for the pool to become active
    wait_for_block(rpc_client, second_pool_key - 20, 180)

    # should no longer be allowed to leave (within freeze window)
    assert caller_pool.canEnterPool.call(geth_coinbase) is False

    # wait for the pool to become active
    wait_for_block(rpc_client, second_pool_key, 180)

    # should now be allowed to leave.
    assert caller_pool.canEnterPool.call(geth_coinbase) is True
def test_pool_membership_is_carried_over(geth_node, geth_coinbase, rpc_client, deployed_contracts):
    caller_pool = deployed_contracts.CallerPool
    joiner = deployed_contracts.JoinsPool

    wait_for_transaction(rpc_client, joiner.setCallerPool.sendTransaction(caller_pool._meta.address))

    assert caller_pool.callerBonds.call(geth_coinbase) == 0
    deposit_amount = caller_pool.getMinimumBond.call() * 10
    # Put in our bond
    wait_for_transaction(
        rpc_client, caller_pool.depositBond.sendTransaction(value=deposit_amount)
    )

    # Put the contract's bond in
    wait_for_transaction(
        rpc_client,
        rpc_client.send_transaction(to=joiner._meta.address, value=deposit_amount)
    )
    wait_for_transaction(
        rpc_client, joiner.deposit.sendTransaction(deposit_amount)
    )
    wait_for_transaction(rpc_client, joiner.enter.sendTransaction())

    # New pool is formed but not active
    first_pool_key = caller_pool.getNextPoolKey.call()
    assert first_pool_key > 0

    # Only the contract is in the pool. (but it isn't active yet)
    assert caller_pool.getActivePoolKey.call() == 0
    assert caller_pool.isInPool.call(joiner._meta.address, first_pool_key) is True
    assert caller_pool.isInPool.call(geth_coinbase, first_pool_key) is False

    # Now we join the pool
    wait_for_transaction(rpc_client, caller_pool.enterPool.sendTransaction())

    # Both are in the pool but it isn't active yet
    assert caller_pool.getActivePoolKey.call() == 0
    assert caller_pool.getNextPoolKey.call() == first_pool_key
    assert caller_pool.isInPool.call(joiner._meta.address, first_pool_key) is True
    assert caller_pool.isInPool.call(geth_coinbase, first_pool_key) is True

    # Wait for it to become active
    wait_for_block(rpc_client, first_pool_key, 180)
    assert caller_pool.getActivePoolKey.call() == first_pool_key
    assert caller_pool.getNextPoolKey.call() == 0

    # Now the contract leaves the pool.
    wait_for_transaction(rpc_client, joiner.exit.sendTransaction())
    second_pool_key = caller_pool.getNextPoolKey.call()

    # New pool should have been setup
    assert second_pool_key > first_pool_key

    # should still be in the pool until it becomes active
    assert caller_pool.isInPool.call(joiner._meta.address, first_pool_key) is True
    assert caller_pool.isInPool.call(geth_coinbase, first_pool_key) is True

    # wait for the pool to become active
    wait_for_block(rpc_client, second_pool_key, 180)

    # contract shouldn't be in the pool anymore but we should.
    assert caller_pool.getActivePoolKey.call() == second_pool_key
    assert caller_pool.getNextPoolKey.call() == 0
    assert caller_pool.isInPool.call(joiner._meta.address, second_pool_key) is False
    assert caller_pool.isInPool.call(geth_coinbase, second_pool_key) is True
def test_scheduled_call_execution_with_pool(geth_node, geth_coinbase,
                                            geth_node_config, deploy_client,
                                            deployed_contracts, contracts):
    alarm = deployed_contracts.Alarm
    joiner = deployed_contracts.JoinsPool
    client_contract = deployed_contracts.SpecifyBlock

    coinbase = geth_coinbase

    block_sage = BlockSage(deploy_client)

    # Put in our deposit with the alarm contract.
    deposit_amount = get_max_gas(
        deploy_client) * deploy_client.get_gas_price() * 20
    alarm.deposit.sendTransaction(client_contract._meta.address,
                                  value=deposit_amount)

    wait_for_transaction(
        deploy_client,
        joiner.setCallerPool.sendTransaction(alarm._meta.address))

    assert alarm.getBondBalance(coinbase) == 0
    bond_amount = alarm.getMinimumBond() * 10
    # Put in our bond
    wait_for_transaction(deploy_client,
                         alarm.depositBond.sendTransaction(value=bond_amount))

    # Put the contract's bond in
    wait_for_transaction(
        deploy_client,
        deploy_client.send_transaction(to=joiner._meta.address,
                                       value=bond_amount))
    wait_for_transaction(deploy_client,
                         joiner.deposit.sendTransaction(bond_amount))

    # Both join the pool
    wait_for_transaction(deploy_client, joiner.enter.sendTransaction())
    wait_for_transaction(deploy_client, alarm.enterPool.sendTransaction())

    # New pool is formed but not active
    first_generation_id = alarm.getNextGenerationId()
    assert first_generation_id > 0

    # Go ahead and schedule the call.
    generation_start_at = alarm.getGenerationStartAt(first_generation_id)
    target_block = generation_start_at + 5

    txn_hash = client_contract.scheduleIt.sendTransaction(
        alarm._meta.address, target_block)
    wait_for_transaction(deploy_client, txn_hash)

    # Wait for the pool to become active
    wait_for_block(
        deploy_client,
        generation_start_at,
        2 * block_sage.estimated_time_to_block(generation_start_at),
    )

    # We should both be in the pool
    assert alarm.getCurrentGenerationId() == first_generation_id
    assert alarm.isInGeneration(joiner._meta.address,
                                first_generation_id) is True
    assert alarm.isInGeneration(coinbase, first_generation_id) is True

    call_key = alarm.getLastCallKey()
    assert call_key is not None

    scheduled_call = ScheduledCall(alarm, call_key, block_sage=block_sage)
    pool_manager = PoolManager(alarm, block_sage=block_sage)

    scheduled_call.execute_async()

    wait_for_block(
        deploy_client,
        scheduled_call.target_block,
        2 * block_sage.estimated_time_to_block(scheduled_call.target_block),
    )

    for i in range(alarm.getCallWindowSize() * 2):
        if scheduled_call.txn_hash:
            break
        time.sleep(block_sage.block_time)

    assert scheduled_call.txn_hash
    assert scheduled_call.txn_receipt
    assert scheduled_call.txn

    assert scheduled_call.was_called
    assert alarm.checkIfCalled(scheduled_call.call_key)
    assert scheduled_call.target_block <= scheduled_call.called_at_block
    assert scheduled_call.called_at_block <= scheduled_call.target_block + scheduled_call.grace_period
def test_scheduled_call_execution_with_pool(geth_node, geth_coinbase, rpc_client, deployed_contracts, contracts):
    alarm = deployed_contracts.Alarm
    joiner = deployed_contracts.JoinsPool
    client_contract = deployed_contracts.SpecifyBlock
    caller_pool = contracts.CallerPool(alarm.getCallerPoolAddress.call(), rpc_client)

    # Put in our deposit with the alarm contract.
    deposit_amount = get_max_gas(rpc_client) * rpc_client.get_gas_price() * 20
    alarm.deposit.sendTransaction(client_contract._meta.address, value=deposit_amount)

    wait_for_transaction(rpc_client, joiner.setCallerPool.sendTransaction(caller_pool._meta.address))

    assert caller_pool.callerBonds.call(geth_coinbase) == 0
    deposit_amount = caller_pool.getMinimumBond.call() * 10
    # Put in our bond
    wait_for_transaction(rpc_client, caller_pool.depositBond.sendTransaction(value=deposit_amount))

    # Put the contract's bond in
    wait_for_transaction(rpc_client, rpc_client.send_transaction(to=joiner._meta.address, value=deposit_amount))
    wait_for_transaction(rpc_client, joiner.deposit.sendTransaction(deposit_amount))

    # Both join the pool
    wait_for_transaction(rpc_client, joiner.enter.sendTransaction())
    wait_for_transaction(rpc_client, caller_pool.enterPool.sendTransaction())

    # New pool is formed but not active
    first_pool_key = caller_pool.getNextPoolKey.call()
    assert first_pool_key > 0

    # Go ahead and schedule the call.
    target_block = first_pool_key + 5

    txn_hash = client_contract.scheduleIt.sendTransaction(alarm._meta.address, target_block)
    wait_for_transaction(client_contract._meta.rpc_client, txn_hash)

    # Wait for the pool to become active
    wait_for_block(rpc_client, first_pool_key, 180)

    # We should both be in the pool
    assert caller_pool.getActivePoolKey.call() == first_pool_key
    assert caller_pool.isInPool.call(joiner._meta.address, first_pool_key) is True
    assert caller_pool.isInPool.call(geth_coinbase, first_pool_key) is True

    callKey = alarm.getLastCallKey.call()
    assert callKey is not None

    pool_manager = PoolManager(caller_pool)
    scheduled_call = ScheduledCall(alarm, pool_manager, callKey)

    scheduled_call.execute_async()

    wait_for_block(rpc_client, scheduled_call.target_block + 8, 180)

    assert scheduled_call.txn_hash
    assert scheduled_call.txn_receipt
    assert scheduled_call.txn

    assert scheduled_call.was_called
    assert alarm.checkIfCalled.call(scheduled_call.call_key)
    assert scheduled_call.target_block <= scheduled_call.called_at_block
    assert scheduled_call.called_at_block <= scheduled_call.target_block + scheduled_call.grace_period