GitHub Logo HIP-1215: Generalized Schedule Contract Call

Author Matthew DeLorenzo, Michael Tinker
Working Group Richard Bair, Luke Lee, Keith Kowal (, Ali Nikan (, Fernando Paris (
Requested By Hashgraph
Discussions-To https://github.com/hiero-ledger/hiero-improvement-proposals/discussions/1096
Status Last Call
Needs Hedera Approval Yes
Needs Hiero Review Yes
Review period ends Mon, 14 Jul 2025 07:00:00 +0000
Type Standards Track
Category Service
Created 2025-04-09
Updated 2025-06-30
Requires 756

Abstract

We generalize HIP-756 (“Contract Scheduled Token Create”) by enabling smart contracts to schedule any contract call via Hedera Schedule Service (HSS). The main design concern is throttling; that is, what happens when a contract call is scheduled at an expiry second that is already full of scheduled work. We support cheap retry logic inside the EVM by adding a view system contract hasScheduleCapacity(uint256 expiry, uint256 gasLimit) with the approximate gas cost of a cold SLOAD. This lets the contract find an acceptable second (if one exists) with an affordable gas budget.

Motivation

HIP-755 (“Schedule Service System Contract”) extended the ability to schedule transactions to the Hedera Smart Contract Service. While this is very useful for smart contract calls by externally owned accounts (EOAs), it requires off-chain coordination and it does not extend to smart contracts calling other smart contracts as the origin of the transaction. Furthermore, HIP-755 does not fully support regularly scheduled transactions, as the off-chain signatures and message submission must be repeated for each transaction.

Extending HIP-756 to call contracts in the future would enable recursive execution and rescheduling of contract calls, resulting in a “set it and forget it” system by which contract calls can be made at regularly scheduled intervals as long as the transaction payer has enough HBAR to cover the scheduling and gas and fees.

Rationale

This HIP provides the possiblility to have fully on-chain “cron jobs” that regularly call smart contracts, replacing various off-chain mechanisms users have relied on til now.

User stories

  1. Rebalancing DeFi positions - A vault contract schedules its own future rebalances.
  2. Vesting claims - A token vesting contract automatically schedules cliff unlocks.
  3. DAO automation - Governance logic schedules periodic housekeeping calls (e.g., interest reallocation) without relying on bots.

Specification

HSS System Contract

The IHederaScheduleService.sol interface must be updated to support everything needed to schedule an arbtirary call from within the EVM, including the to address, the call data to use, the gas limit for the future call, and the value to send with that call.

An important option is to let the scheduling contract specify a sender address that may be different from its own. In this case the scheduled call can only execute after receiving enough valid signatures to activate the sender’s key. The difference between scheduleCallWithPayer() and executeCallOnPayerSignature() is that the former still waits until the consensus second is not before expiry to execute, while the latter executes as soon as the payer signs (unless consensus time is already past the expiry, of course).

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Hash        | Function signature                                                                                                                                                   |
|-------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 0x6f5bfde8  | scheduleCall(address to, uint256 expiry, uint256 gasLimit, uint64 value, bytes memory callData) returns (int64 responseCode)                                            |
| 0xeb459436  | scheduleCallWithSender(address to, address sender, uint256 expiry, uint256 gasLimit, uint64 value, bytes memory callData) returns (int64 responseCode)                  |
| 0x2c300715  | executeCallOnSenderSignature(address to, address sender, uint256 expiry, uint256 gasLimit, uint64 value, bytes memory callData) returns (int64 responseCode)         |
| 0x07cdefc0  | hasScheduleCapacity(uint256 expirySecond, uint256 gasLimit) view returns (bool capacity) |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

The scheduleCall() variants revert with SCHEDULE_EXPIRY_IS_BUSY is the specified second is saturated. The hasScheduleCapacity() view function never reverts, but returns true iff the given second still has capacity to schedule a contract call with the specified gas limit.

The gas cost for scheduling variants will follow Hedera’s standard system contract gas schedule, which is set based on the relative resource usage of the equivalent HAPI ScheduleCreate transaction. This approach remains consistent with existing Hedera prices and ensures predictable fees between system contract and HAPI.

We suggest the following retry pattern to find an available second not before the first second a contract would want a scheduled call to execute. This pattern keeps the simplicity and rapid convergence of exponential back‑off while adding a harmless, non‑manipulable jitter so that many contracts using it to probe the same ideal second naturally scatter across nearby seconds instead of stampeding into the same one.

function findAvailableSecond(
    uint256 expiry,
    uint256 gasLimit,
    uint maxProbes
) returns (uint256 second) {
    if (HSS.hasScheduleCapacity(expiry, gasLimit)) {
        return expiry;
    }
    // Use the PREVRANDAO seed to add jitter to probes
    bytes32 seed = block.prevrandao;
    for (uint i; i < maxProbes; ++i) {
        // 1, 2, 4, 8, ... seconds
        uint256 baseDelay = 1 << i;
        // Jitter, hash the seed with loop‑index
        bytes32 h = keccak256(abi.encodePacked(seed, i));
        // Clamp the lowest 16 bits to [0, min(65536, baseDelay))
        uint16 r = uint16(uint256(h));
        uint256 jitter = uint256(r) % baseDelay;
        uint256 candidate = expiry + baseDelay + jitter;
        if (HSS.hasScheduleCapacity(candidate, gasLimit)) {
            return candidate;
        }
    }
    revert("No unsaturated second after max probes");
}

Backwards Compatibility

No existing features are modified as this only exposes HAPI functionality to smart contracts.

Security Implications

On the surface, one might worry this proposal would let a contract create an infinite loop by scheduling a call to itself at the same second it is currently executing. However, HSS requires the scheduled second to be strictly later than the current consensus second. So it would be strictly more expensive to saturate a Hiero network’s gas throttle by scheduling contract calls than it would be to simply submit those calls through HAPI.

How to Teach This

Smart contracts can now schedule calls to other contracts (or themselves!) from inside the EVM.

Reference Implementation

In progress, see an initial hackathon implementation here.

Rejected Ideas

We considered a proposal to add “schedule not before” semantics to the HAPI ScheduleCreate operation. But since it is relatively trivial to support a cheap hasScheduleCapacity() probe, and it is possible contract authors might want to customize their retry strategies in many ways, we instead settled on providing an example retry strategy and letting contract developers roll their own.

Open Issues

There are no known open issues.

References

  1. HIP-755
  2. HIP-756
  3. HIP-351

Copyright/license

This document is licensed under the Apache License, Version 2.0 — see LICENSE or https://www.apache.org/licenses/LICENSE-2.0.

Citation

Please cite this document as: