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
112 changes: 112 additions & 0 deletions BEPs/BEP-706.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
<pre>
BEP: 706
Title: Millisecond-Precision Block Timestamp Precompile
Status: Draft
Type: Standards
Created: 2026-08-10
Description: Add a precompile that exposes the block header's millisecond-precision timestamp to smart contracts.
</pre>

# BEP-706: Millisecond-Precision Block Timestamp Precompile

- [BEP-706: Millisecond-Precision Block Timestamp Precompile](#bep-706-millisecond-precision-block-timestamp-precompile)
- [1. Summary](#1-summary)
- [2. Abstract](#2-abstract)
- [3. Motivation](#3-motivation)
- [4. Specification](#4-specification)
- [4.1 Precompiled Contract](#41-precompiled-contract)
- [4.2 Input](#42-input)
- [4.3 Output](#43-output)
- [4.4 Gas Cost](#44-gas-cost)
- [4.5 Solidity Usage](#45-solidity-usage)
- [5. Rationale](#5-rationale)
- [6. Backward Compatibility](#6-backward-compatibility)
- [7. Security Considerations](#7-security-considerations)
- [8. License](#8-license)

## 1. Summary

This BEP introduces a new precompiled contract that returns the current block's timestamp in **milliseconds** since the Unix epoch, giving smart contracts access to the sub-second precision that already exists in the block header but is currently unreachable from the EVM.

## 2. Abstract

Since [BEP-520](./BEP-520.md), BSC block headers carry a millisecond-precision timestamp: `Header.Time` (seconds) is unchanged for backward compatibility, and the sub-second remainder is stored in the last two bytes of `Header.MixDigest`. Full nodes compute this value via `Header.MilliTimestamp()`. This value is validated as part of consensus, but it is only accessible in client code — the EVM's `TIMESTAMP` (`0x42`) opcode still returns only `Header.Time`, truncated to whole seconds.

This BEP adds a precompiled contract that returns `Header.MilliTimestamp()` of the currently executing block, so contracts can read block time at the same precision the protocol already commits to, without any change to the semantics of `block.timestamp`.

## 3. Motivation

BSC's block interval has been shortened multiple times ([BEP-520](./BEP-520.md) and subsequent reductions), to the point where `block.timestamp`'s one-second resolution is now coarser than the block interval itself — multiple blocks can share the same second-level timestamp. On-chain logic that needs sub-second real-world time has no reliable way to get it today.

A concrete example is market maker / RFQ signature validity windows: an off-chain quote is signed with a short expiry (e.g. a few hundred milliseconds) to bound the maker's price risk, and a settlement contract must reject the quote once it has expired. With only second-level `block.timestamp`, the contract can only round the expiry up or down to the nearest second, forcing makers to widen the effective validity window and quote worse prices to offset the added risk.

Other cases with the same shape include auction/order cutoffs, price-feed staleness checks, and any settlement logic keyed to a sub-second deadline. Exposing the header's existing millisecond timestamp via a precompile solves this directly, using data nodes already agree on, with no new trust assumptions.

## 4. Specification

### 4.1 Precompiled Contract

As of `FORK_TIMESTAMP`, a new precompiled contract is added at address `MILLI_TIMESTAMP_PRECOMPILE_ADDRESS`. The exact address is `0x70`.

The precompile returns the millisecond timestamp of the block currently being executed, equivalent to calling `Header.MilliTimestamp()` on the executing block's header:

```go
// existing, introduced by BEP-520
func (h *Header) MilliTimestamp() uint64 {
milliseconds := uint64(0)
if h.MixDigest != (common.Hash{}) {
milliseconds = uint256.NewInt(0).SetBytes32(h.MixDigest[:]).Uint64()
}
return h.Time*1000 + milliseconds
}
```

```go
// new precompile
func (c *milliTimestamp) Run(evm *EVM, input []byte) ([]byte, error) {
ts := evm.Context.Header.MilliTimestamp() // or evm.Context.Time equivalent, in milliseconds
return common.LeftPadBytes(new(big.Int).SetUint64(ts).Bytes(), 32), nil
}
```

Since the value is derived solely from the executing block's own header fields (both already validated by consensus), the precompile is a pure, deterministic function of the execution context and is safe to call from `STATICCALL`.

### 4.2 Input

The precompile takes no arguments. Calldata is ignored — the call succeeds regardless of calldata length or content. This mirrors context-only reads such as the `TIMESTAMP` opcode, and keeps the interface trivial to call from both Solidity and raw bytecode.

### 4.3 Output

A single 32-byte big-endian unsigned integer: the current block's timestamp in milliseconds since the Unix epoch, i.e. `Header.MilliTimestamp()`.

### 4.4 Gas Cost

This is a read-only call whose computational cost is comparable to that of the `0x4` (`identity`/`dataCopy`) precompile, whose base cost is 15 gas. Accordingly, this precompile's gas cost is set at 20 gas.

### 4.5 Solidity Usage

```solidity
function milliTimestamp() internal view returns (uint64) {
(bool ok, bytes memory data) = MILLI_TIMESTAMP_PRECOMPILE_ADDRESS.staticcall("");
require(ok, "milliTimestamp: call failed");
return uint64(uint256(bytes32(data)));
}
```

## 5. Rationale

- **Precompile, not an opcode change.** Changing `TIMESTAMP` itself to return milliseconds was considered and rejected: `block.timestamp` is deeply embedded in deployed contracts and tooling that assume second units (e.g. `+ 1 days` arithmetic), so silently changing its unit would be a severe, silent breaking change. A new precompile is strictly additive and opt-in.
- **Precompile, not a new EVM opcode.** A precompile needs no changes to the opcode table or gas schedule that EVM tooling (disassemblers, static analyzers, gas estimators) special-cases, and matches BSC's existing convention of adding chain-specific functionality via precompiles rather than extending core EVM opcodes.

## 6. Backward Compatibility

This is a purely additive change. Before `FORK_TIMESTAMP`, the target address has no code, matching standard EVM behavior for calls to empty accounts. After activation, existing contracts are unaffected unless they explicitly call the new address. `block.timestamp` / the `TIMESTAMP` opcode's semantics are unchanged.

## 7. Security Considerations

- **No new trust assumptions.** The returned value is derived entirely from the executing block's own header fields (`Header.Time` and the millisecond component in `Header.MixDigest`), both of which are already validated as part of block consensus since [BEP-520](./BEP-520.md). The precompile does not introduce any data that isn't already agreed upon by validators.
- **Same timing trust model as `block.timestamp`.** The millisecond timestamp is validator-supplied and bounded by the same consensus-level constraints (e.g. monotonicity, permitted drift) that already apply to `Header.Time`, just at finer granularity. It offers no stronger guarantee than `block.timestamp` does today — a validator still has some latitude within protocol bounds, so timing-sensitive logic (e.g. auctions) carries the same caveats as before, just at millisecond rather than second resolution.

## 8. License

The content is licensed under [CC0](https://creativecommons.org/publicdomain/zero/1.0/).
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ Here is the list of subjects of BEPs:
| [BEP-677](./BEPs/BEP-677.md) | Implement EIP-8056 Scaled UI Amount | Standards | Draft |
| [BEP-682](./BEPs/BEP-682.md) | Reject Duplicate Validators in CometBFT Light Block Validation | Standards | Draft |
| [BEP-695](./BEPs/BEP-695.md) | Staking and Governance Security Hardening | Standards | Draft |
| [BEP-706](./BEPs/BEP-706.md) | Millisecond-Precision Block Timestamp Precompile | Standards | Draft |

# BAPs
BAP (BNB Application Proposal) defines standards for application layer interactions on BNB Chain. Unlike BEPs which govern core protocol changes, BAPs focus on establishing conventions and interfaces for how applications communicate and interact with each other within the BNB Chain ecosystem.
Expand Down