Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
---
title: "BaseTime Millisecond Timestamp"
description: "Denim adds the BaseTime predeploy so contracts can read the block timestamp at millisecond precision through a read-only interface."
---

## 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

```solidity title="IBaseTime and StdPredeploys" lines expandable
interface IBaseTime {
function timestampMillisPart() external view returns (uint16);
function timestampMs() external view returns (uint64);
function version() external view returns (string memory);
}

library StdPredeploys {
address internal constant BASE_TIME_ADDRESS = 0x4200000000000000000000000000000000000030;
IBaseTime internal constant BASE_TIME = IBaseTime(BASE_TIME_ADDRESS);
}
```

| Function | Selector | Status | Notes |
| -------- | -------- | ------ | ----- |
| `timestampMillisPart()` | `0x7b2fea99` | New (view) | Millisecond component: `0`, `200`, `400`, `600`, or `800`. Never reverts. |
| `timestampMs()` | `0x5745a677` | New (view) | `block.timestamp * 1000 + timestampMillisPart()`. Never reverts. |
| `version()` | `0x54fd4d50` | New (view) | Returns `"1.0.0"`. |

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

```solidity title="Read the millisecond timestamp" highlight={3}
import {StdPredeploys} from "base-std/StdPredeploys.sol";

uint64 nowMs = StdPredeploys.BASE_TIME.timestampMs();
// block.timestamp == 1_800_000_000, timestampMillisPart() == 400
// nowMs == 1_800_000_000_400
```

## 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.
3 changes: 2 additions & 1 deletion docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,8 @@
"upgrades/denim/migrate-from-flashblocks",
"base-chain/specs/reference/b20/changelog/03-denim-b20-token-receiver",
"base-chain/specs/reference/b20/changelog/03-denim-b20-transfer-executor-enforcement",
"base-chain/specs/reference/b20/changelog/03-denim-policyregistry-not-policy"
"base-chain/specs/reference/b20/changelog/03-denim-policyregistry-not-policy",
"base-chain/specs/reference/b20/changelog/03-denim-basetime-millisecond-timestamp"
]
},
{
Expand Down
1 change: 1 addition & 0 deletions docs/specifications/b20/changelog.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ B20 method and event signatures are part of the chain's consensus surface. Exist
| B20 Asset, B20 Stablecoin | (Breaking) Reject the token itself as a credit recipient | `IB20` (shared surface), `IB20Asset` (`batchMint`) | [Token receiver](/base-chain/specs/reference/b20/changelog/03-denim-b20-token-receiver) |
| B20 Asset, B20 Stablecoin | (Breaking) Transfer executor policy on every transfer path | `IB20` (shared surface) | [Transfer executor enforcement](/base-chain/specs/reference/b20/changelog/03-denim-b20-transfer-executor-enforcement) |
| PolicyRegistry | NOT / invert policies | `IPolicyRegistry` | [NOT policies](/base-chain/specs/reference/b20/changelog/03-denim-policyregistry-not-policy) |
| BaseTime | Millisecond block timestamp predeploy (read-only) | `IBaseTime`, `src/StdPredeploys.sol` | [Millisecond block timestamp predeploy (read-only)](/base-chain/specs/reference/b20/changelog/03-denim-basetime-millisecond-timestamp) |

## [Cobalt](/upgrades/cobalt/overview), Ordinal 02

Expand Down
Loading