Skip to main content

Abstract

Denim installs the BaseTime predeploy at 0x4200000000000000000000000000000000000030. It exposes the millisecond component of the current block timestamp. Base Std adds the read-only IBaseTime interface and a typed handle, StdPredeploys.BASE_TIME. Call StdPredeploys.BASE_TIME.timestampMs() for the full millisecond timestamp.

Motivation

block.timestamp has second precision, but from Denim a block’s timestamp carries a sub-second component. Contracts that need the exact block time, such as auctions, rate limits, or time-weighted pricing, can’t read the millisecond component from the EVM alone.

What changed

BaseTime is a proxied predeploy: Solidity bytecode at a 0x4200… address, not a native precompile. Its handle therefore lives in a new StdPredeploys library instead of StdPrecompiles.

New interface and library

IBaseTime and StdPredeploys
The depositor-only setter and its errors are protocol-internal and are not part of IBaseTime.

Behavior

  • No existing selector, event, error, or storage slot changes. The predeploy is new.
  • Storage: uint16 timestampMillisPart sits in the low-order 2 bytes of slot 0.
  • The protocol writes the millisecond component once per Denim block, through a depositor-only setter called by the block-scoped deposit at tx[1]. Until that deposit executes (for example, during the tx[0] L1-info deposit), timestampMs() combines the current block’s seconds with the previous block’s millisecond component.

Example

Read the millisecond timestamp

Migration

This change is additive. No existing integration changes.
  • After Denim activates, read StdPredeploys.BASE_TIME.timestampMs() for millisecond block time.
  • Code that runs before tx[1] in a block, such as the L1-info deposit, sees the previous block’s millisecond component.
  • Before Denim, the predeploy has no implementation and calls to it revert. Don’t depend on it on a chain where Denim hasn’t activated.

Alternatives considered

  • Read-only interface. Only the protocol deposit can write the value, so exposing the setter would give integrators a selector that always reverts for them.
  • Separate StdPredeploys library. Predeploys are EVM bytecode and precompiles are native code. Separate libraries keep that distinction visible at the call site.
  • Slot-0 mock layout. MockBaseTime stores the uint16 at slot 0 instead of an ERC-7201 namespace. This matches the real contract’s layout, so vm.store-based tests behave the same against either.