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 |
Table of Contents
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
- Rebalancing DeFi positions - A vault contract schedules its own future rebalances.
- Vesting claims - A token vesting contract automatically schedules cliff unlocks.
- 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
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: