Abstract
This EIP adds a new opcode, SLOTDURATION, that pushes the duration of the current slot, in milliseconds, onto the stack. The value is the SLOT_DURATION_MS parameter in effect in the consensus layer for the block's slot. It is passed from the consensus layer to the execution layer and committed to in the block header.
Motivation
The slot duration has been a fixed 12 seconds since the Merge, and many contracts hardcode it. They use it to convert between slot counts and wall-clock time, to estimate how many blocks fit in a time window, or to calibrate per-block rate limits and reward rates. Proposals such as EIP-8198 plan to shorten the slot duration, possibly several times. Each change would silently break every contract that hardcodes it.
Liquid staking protocols (such as Lido) show the problem. Their oracle and validator exit contracts take the slot duration in seconds as a constructor argument and store it as an immutable. On mainnet it is set to 12. The value is used to convert between slots and timestamps, using GENESIS_TIME + slot * SECONDS_PER_SLOT, and to compute the time elapsed between oracle reports, which feeds the protocol's sanity checks on rewards. A slot duration change would require such a protocol to:
- deploy new oracle implementations and upgrade their proxies;
- redeploy the consensus and verifier contracts, then repoint the oracles and other contracts to them;
- re-tune governance parameters that were calibrated for 225 epochs per day.
All of this would have to happen at the same time as the fork. Even then, a single linear formula from genesis can no longer give correct timestamps for slots on both sides of the change. Other contracts that reason about beacon chain time, such as staking protocols, restaking systems and beacon proof verifiers, are likely to have the same problem.
EIP-7843 addresses part of this problem by exposing the slot number through the SLOTNUM (0x4b) opcode, so contracts no longer need the slot duration to compute the current slot. Contracts that need the duration itself still have no reliable way to get it:
- Hardcode the value. This breaks at the next slot duration change.
- Measure it with
SLOTNUM(0x4b), by dividing a timestamp difference by a slot difference. Missed slots do not affect this measurement, but it has other problems:- The contract needs a stored observation from an earlier transaction in a different slot. The measurement cannot be done within a single transaction.
- The result is an average over the interval. If the duration changed during the interval, the result is neither the old nor the new duration. After a change, the result stays wrong until a new observation is recorded.
TIMESTAMP(0x42) has one-second resolution. Chains with sub-second blocks need observations many slots apart to get even an approximate value.
- Prove it against the beacon block root (EIP-4788). This is not possible.
SLOT_DURATION_MSis a consensus layer configuration value, so it is not stored in theBeaconStateor the beacon block. A contract could prove theforkfield of the state, but it would still need a hardcoded table that maps each fork to its slot duration, which is the same problem as option 1.
Exposing SLOT_DURATION_MS through an opcode lets contracts adapt automatically to slot duration changes, the same way SLOTNUM (0x4b) removes the need to derive slot numbers from timestamps. This EIP does not depend on any slot duration change, such as the one proposed in EIP-8198. Activating it before such a change gives contracts time to stop hardcoding the duration and use the opcode instead, so that they need no upgrade when the duration changes.
Specification
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.
Parameters
| Constant | Value |
|---|---|
SLOTDURATION_OPCODE | 0x4c |
SLOTDURATION_GAS | 2 |
Opcode
A new opcode SLOTDURATION is introduced at SLOTDURATION_OPCODE. It takes no stack input and pushes one element onto the stack.
Output
One element, slotDurationMs, is pushed onto the stack. It is the duration of the slot of the current block, in milliseconds, as given by the block header's slotDurationMs field. It is a uint64, left-padded to 32 bytes.
Gas Cost
SLOTDURATION has a fixed cost of SLOTDURATION_GAS.
Block Header
The block header is extended with a new field, slotDurationMs, of type uint64. It MUST be appended after the slotNumber field introduced by EIP-7843. The execution layer does not validate the value of slotDurationMs beyond its type.
On chains with a beacon chain, the consensus layer MUST set this field to the slot duration, in milliseconds, that is in effect for the block's slot. This value MUST be non-zero. While the slot duration is fixed, it is the SLOT_DURATION_MS configuration value.
Chains without a beacon chain, such as layer 2 networks, MUST set slotDurationMs to their target block interval, in milliseconds. Some chains do not produce blocks at regular intervals, such as development chains that create a block whenever a transaction is submitted. These chains MUST set slotDurationMs to 0, which means that no regular block interval exists.
Engine API
The consensus layer passes slotDurationMs to the execution layer through the Engine API, in the same way as slotNumber in EIP-7843:
- A
slotDurationMsfield of typeQUANTITY(64 bits) is added to new versions of theExecutionPayloadandPayloadAttributesobjects. - New versions of
engine_newPayload,engine_getPayloadandengine_forkchoiceUpdatedare introduced to use these objects. - When building a payload, the execution layer MUST copy
PayloadAttributes.slotDurationMsinto the header. - When validating a payload, the consensus layer MUST reject the payload if
ExecutionPayload.slotDurationMsis not equal to the slot duration in effect for the payload's slot.
Rationale
Milliseconds
The consensus layer already expresses slot duration as SLOT_DURATION_MS. Using the same unit avoids conversion and rounding errors. EIP-8198 requires slot durations to be multiples of 1000 ms. Using milliseconds means a later change that drops this requirement would not require a change to the opcode. It also lets chains with much shorter block times adopt the opcode as specified. Some layer 2 networks produce blocks every 100–250 ms. Seconds cannot express these durations, but milliseconds can.
Source of Truth in the Consensus Layer
The slot duration is a beacon chain parameter. The consensus layer is the source of truth for this value and passes it to the execution layer. While the slot duration is fixed, this value is the constant SLOT_DURATION_MS. When the slot duration can change, the consensus layer computes the value for any epoch. For example, the consensus specification for EIP-8198 defines get_slot_duration_ms(epoch) (specs/_features/eip8198/beacon-chain.md), and the header field would then be set to get_slot_duration_ms(compute_epoch_at_slot(slot)). The execution layer therefore needs no copy of the schedule, and the two layers cannot disagree about it.
Execution Layer Awareness of the Slot Duration
EIP-8198 deliberately keeps the execution layer unaware of the slot duration configuration. Where the execution layer needs the duration, such as in the base fee rule, it derives it from the spacing of header timestamps. This EIP keeps that separation of concerns:
- The execution layer holds no slot duration configuration and computes nothing from it. It copies the value it receives from the consensus layer into the header and exposes it to the EVM. This is the same treatment EIP-7843 gives
slotNumber. - No execution client logic depends on the value. A future slot duration change still needs no execution layer configuration update for this opcode.
Contracts cannot easily use the execution layer's approach. They only see the current block's header. To measure the spacing between headers, they must store an earlier timestamp and slot number, as described in the Motivation. That gives an average over the interval, which is wrong when the interval contains a duration change, and lags after each change. With the header field, contracts read the current duration directly and do not have to measure it themselves.
Alternative: Execution Layer Schedule with Historical Lookup
An alternative design keeps the full slot duration schedule in the execution layer configuration, similar to the blob schedule in EIP-7840. Each entry would give the first slot at which a duration applies. This design needs no new header field and no Engine API change. It duplicates consensus layer parameters in the execution layer, and client configurations must stay in sync with the consensus layer schedule.
Because the execution layer would know the whole schedule, the opcode could take an argument:
- Input: one stack element,
slot. - Output: the
SLOT_DURATION_MSthat applied toslot. Ifslotis greater than the current slot number (as returned bySLOTNUM), the output is0.
SLOTNUM SLOTDURATION then returns the duration of the current slot, which is the same result as the design specified above.
Historical lookup fixes the main pitfall described in Security Considerations. A contract that needs the wall-clock length of an interval between two slots can find the duration in effect at each end of the interval, and detect that a schedule boundary was crossed. A contract that only reads the current duration cannot tell that the duration changed while it was not looking. A full solution would also need to know the slot at which each change happened. A companion lookup, or a binary search over SLOTDURATION calls, could find it.
Slots in the future return 0 instead of a value from the configured schedule. Entries for future slots can still be changed or removed before they activate, for example if a fork is rescheduled. Returning such a value would let contracts depend on a duration that may never take effect. 0 can never be a real slot duration, so contracts can tell it apart from a real result. 0 has the same meaning as on chains without a regular block interval: no duration is known.
In this variant, the cost of the opcode depends on the size of the schedule. Each slot duration change adds one entry, so a binary search stays cheap, and the opcode could be priced at W_verylow (3) to account for the stack input. The schedule must stay in client configuration forever, and every client must implement the exact slot-to-duration mapping, including the slots before the first schedule entry (12000 ms from the beacon chain genesis).
Header Field
Putting the value in the block header keeps blocks self-contained for re-execution and ZK proving. Executing a block needs no input from outside the block. The cost is 8 bytes per header (a little more after RLP encoding). The value rarely changes, so it compresses well.
Gas Cost
The opcode reads one block-context value, like TIMESTAMP (0x42), NUMBER (0x43) and SLOTNUM (0x4b). It is priced at W_base like those opcodes.
Opcode Value
0x4c is in the block information range 0x40–0x4f, directly after SLOTNUM (0x4b). Placing it there keeps the two slot-related opcodes next to each other.
Backwards Compatibility
This EIP introduces a new opcode. Before activation, executing SLOTDURATION_OPCODE causes an exceptional halt. No deployed contract can rely on the new behavior. The block header and Engine API changes need coordinated upgrades of consensus and execution clients at the fork.
Test Cases
The following cases describe the value pushed by SLOTDURATION in different configurations:
- With
SLOT_DURATION_MS = 12000,SLOTDURATIONpushes12000(0x2ee0) and consumes 2 gas. - With a slot duration of 10000 ms,
SLOTDURATIONpushes10000(0x2710). - When a block is in the first slot of an epoch where the slot duration changes from 12000 to 10000,
SLOTDURATIONreturns10000for that block. It returns12000for the block in the preceding slot. - On a chain without a regular block interval,
SLOTDURATIONpushes0.
Security Considerations
SLOTDURATION returns the nominal duration of the current slot. It does not measure time elapsed since the parent block, which can span several slots if slots were missed. Contracts that need elapsed time should use TIMESTAMP (0x42) differences.
The value can change at a fork or slot schedule boundary. Contracts should read it each time they need it and should not cache it in storage. Contracts that accumulate values over time, such as interest or vesting at a per-slot rate, should account for the duration changing between two observations. A naive slots × currentDuration product misprices any interval that crosses a schedule boundary. Contracts should not assume the value is a whole number of seconds or that it only decreases.
SLOTDURATION returns 0 on chains without a regular block interval, such as local development chains. Contracts that divide by the value should handle 0 explicitly. In the EVM, DIV (0x04) by zero returns 0 silently, and Solidity's checked division reverts. This matters most because local development chains, where contracts are usually tested, are exactly the chains that return 0.
Copyright
Copyright and related rights waived via CC0.
