Scheduling

Call scheduling is the core of the Ethereum Alarm Service. Calls can be scheduled on any block at least 40 blocks (~10 minutes) in the future.

When a call is scheduled, the service deploys a new contract that represents the scheduled function call. This contract is referred to as the call contract. It holds all of the metadata associated with the call as well as the funds that will be used to pay for the call.

Lifecycle of a Call Contract

  • creation - The call contract is created as part of call scheduling.
  • data-registered - The data for the call is registered with the contract. This step can be skipped for function calls that take no arguments.
  • locked - The contract is locked, preventing cancellation starting 10 blocks before the call’s target block through the last block in the call window.
  • execution - The executing transaction is sent, which triggers the call contract to execute the function call.
  • payment - payments are sent to the executor and the creator of the alarm service.
  • suicide - The contract suicides itself, sending the remainder of it’s funds to the call scheduler.

Scheduling the Call

Function calls are scheduled with the scheduleCall function on the Alarm service. This creates a new call contract that represents the function call.

  • Solidity Function Signature: function scheduleCall(address contractAddress, bytes4 abiSignature, uint targetBlock, uint suggestedGas, uint8 gracePeriod, uint basePayment, uint baseFee) public returns (address)
  • ABI Signature: 0x8b676ae8

The scheduleCall function takes the following parameters:

Required Arguments

  • address contractAddress: The address of the contract for the function call.
  • bytes4 abiSignature: The 4 byte ABI signature of the function to be called.
  • uint targetBlock: The block number the call should be executed on.

Optional Arguments

  • uint suggestedGas: A suggestion to the call executor as to how much gas should be provided to execute the call. (default: 0)
  • uint8 gracePeriod: The number of blocks after targetBlock that it is ok to still execute this call. Cannot be less than 64. (default: 255)
  • uint basePayment: The base amount in wei that should be paid to the executor of the call. (default: 1 ether)
  • uint baseFee: The base amount in wei that should be paid to the creator of the alarm service. (default: 100 finney)

The optional arguments are implemented through the following alternate invocation signatures. The default value for each of the optional arguments will be used if any of the following signatures are used.

  • Solidity Function Signature: function scheduleCall(address contractAddress, bytes4 abiSignature, uint targetBlock) public returns (address)
  • ABI Signature: 0x1991313
  • Solidity Function Signature: function scheduleCall(address contractAddress, bytes4 abiSignature, uint targetBlock, uint suggestedGas) public returns (address)
  • ABI Signature: 0x49ae734
  • Solidity Function Signature: function scheduleCall(address contractAddress, bytes4 abiSignature, uint targetBlock, uint suggestedGas, uint8 gracePeriod) public returns (address)
  • ABI Signature: 0x480b70bd
  • Solidity Function Signature: function scheduleCall(address contractAddress, bytes4 abiSignature, uint targetBlock, uint suggestedGas, uint8 gracePeriod, uint basePayment) public returns (address)
  • ABI Signature: 0x68402460

If the scheduleCall function is being used from within a contract, the address of the newly created call contract is returned. If instead, the function is being called directly in a transaction, the address of the call contract can be extracted from the transaction logs under the CallScheduled event.

Contract scheduling its own call

Contracts can take care of their own call scheduling.

contract Lottery {
    address alarm; // set by some other mechanism.

    function beginLottery() public {
        ... // Do whatever setup needs to take place.

        // Now we schedule the picking of the winner.

        bytes4 sig = bytes4(sha3("pickWinner()"));
        // approximately 24 hours from now
        uint targetBlock = block.number + 5760;
        // 0x1991313 is the ABI signature computed from `bytes4(sha3("scheduleCall(...)"))`.
        alarm.call(0x1991313, address(this), sig, targetBlock)
    }

    function pickWinner() public {
        ...
    }
}

In this example Lottery contract, every time the beginLottery function is called, a call to the pickWinner function is scheduled for approximately 24 hours later (5760 blocks).

Scheduling a call for a contract

Alternatively, calls can be scheduled to be executed on other contracts

Lets look at an example where we want to schedule a funds transfer for a wallet contract of some sort.

Note

This example assuming that you have the Alarm contract ABI loaded into a web3 contract object.

// Now schedule the call
> signature = ... // the 4-byte ABI function signature for the wallet function that transfers funds.
> targetBlock = eth.getBlock('latest') + 100  // 100 blocks in the future.
> alarm.scheduleCall.sendTransaction(walletAddress, signature, targetBlock, {from: eth.coinbase, value: web3.toWei(10, "ether")})

Registering Call Data

If a function call requires arguments then it is up to the scheduler to register the call data. This needs to be done prior to execution.

The call contract allows for call data registration via two mechanisms. The primary mechanism is through the fallback function on the contract. This will set the call data as the full call data of the transaction.

// Register some call data
> web3.eth.sendTransaction({to: scheduler.address, data: "0x...."})

Or, from within your contract.

contract Lottery {
    address alarm; // set by some other mechanism.

    function beginLottery() public {
        uint lotteryId = ...;

        // Now we schedule the picking of the winner.
        bytes4 sig = bytes4(sha3("pickWinner(uint256)"));
        // 0x1991313 is the ABI signature computed from `bytes4(sha3("scheduleCall(address,bytes4,uint256)"))`.
        alarm.call(0x1991313, address(this), sig, 100)

        // Register the call data
        alarm.call(lotteryId);
    }

    function pickWinner(uint lotteryId) public {
        ...
    }
}

If however, your call data either has a bytes4 value as it’s first argument, or, the first 4 bytes of the call data have a collision with one of the existing function signatures on the call contract, you can use the registerData function instead.

  • Solidity Function Signature: registerData()
  • ABI Signature: 0xb0f07e44

In solidity, this would look something like the following.

Upon receiving this call, the Alarm service strips off the first four bytes from msg.data to remove the ABI function signature and then stores the full call data.

Once data has been registered, it cannot be modified. Attempts to do so will result in an exception.

ABI Encoding and address.call

The call() function on an address in solidity does not do any ABI encoding, so in cases where a scheduled call must pass something like a bytes variable, you will need to handle the ABI encoding yourself.

Cancelling a call

A scheduled call can be cancelled by its scheduler up to 10 blocks before it’s target block. To cancel a scheduled call use the cancel function.

  • Solidity Function Signature: cancel()
  • ABI Signature: 0xea8a1af0

This will cause the call to be set as cancelled, which will return any funds currently being held by the contract.

Looking up a Call

You can lookup whether a particular address is a known scheduled call with the isKnownCall function.

  • Solidity Function Signature: isKnownCall(address callAddress) returns (bool)
  • ABI Signature: 0x523ccfa8

Returns a boolean as to whether this address represents a known scheduled call.