Skip to content

[Language/stdlib] Add typed CKB temporal and Since domains #12

Description

@a19q3

Summary

Replace ambiguous raw-u64 time values at the authoring boundary with explicit CKB temporal domain types.

CellScript currently returns u64 for the HeaderDep epoch number, epoch start block number, epoch length, input since, and absolute/relative epoch-since constructors. The runtime checks are bounded and fail-closed, but the type system cannot prevent mixing values with different units or comparing an encoded since bitfield to a plain epoch number.

Argent's temporal-value work demonstrates the value of preserving temporal identity in artifacts even when the runtime representation remains integer-based. CellScript should define a CKB-specific, wire-compatible version rather than a generic temporal wrapper.

Problem examples

All of the following values are currently represented as u64:

HeaderDep epoch number
HeaderDep epoch start block number
HeaderDep epoch length
encoded input since
absolute epoch since
relative epoch since
application epoch deadline
application epoch duration

This makes invalid combinations type-correct:

let epoch = env::current_timepoint()
let encoded_since = ckb::input_since(source::input(0))

// Numerically valid u64 operations, semantically invalid domains:
require encoded_since >= epoch
let deadline = encoded_since + epoch

The current implementation also names an epoch-number read current_timepoint, which is easy to misread as a Unix timestamp.

Proposed type families

The exact surface requires an RFC, but the semantic domains should include:

Type Meaning Candidate runtime representation
EpochNumber whole CKB epoch number checked u64
BlockNumber absolute block number checked u64
Timestamp CKB timestamp metric checked u64
EpochLength blocks in one epoch nonzero checked u64
EpochFraction CKB epoch number/index/length tuple canonical fixed fields or encoded u64
Since<Absolute, BlockNumber> absolute block-number constraint canonical encoded u64
Since<Absolute, EpochFraction> absolute epoch-fraction constraint canonical encoded u64
Since<Absolute, Timestamp> absolute timestamp constraint canonical encoded u64
Since<Relative, BlockNumber> relative block-number constraint canonical encoded u64
Since<Relative, EpochFraction> relative epoch-fraction constraint canonical encoded u64
Since<Relative, Timestamp> relative timestamp constraint canonical encoded u64
DecodedSince tagged union returned by decoding raw input since one of the six validated variants
EpochDuration application duration in whole epochs checked u64

The exact generic spelling is an RFC decision. The invariant is not: both the relative/absolute mode and the block/epoch/timestamp metric must remain in the type identity. A wrapper that distinguishes only AbsoluteSince from RelativeSince is insufficient.

flowchart TB
    H["HeaderDep fields"] --> EN["EpochNumber"]
    H --> BN["BlockNumber"]
    H --> EL["EpochLength"]

    EN --> EWF["EpochNumberWithFraction"]
    EL --> EWF

    EWF --> SAE["Since<Absolute, EpochFraction>"]
    EWF --> SRE["Since<Relative, EpochFraction>"]
    BN --> SAB["Since<Absolute, BlockNumber>"]
    BN --> SRB["Since<Relative, BlockNumber>"]
    TS["Timestamp"] --> SAT["Since<Absolute, Timestamp>"]
    TS --> SRT["Since<Relative, Timestamp>"]

    RAW["Raw u64"] -. "explicit checked conversion only" .-> EN
    RAW -. "decode flags + metric + payload" .-> DS["DecodedSince"]
Loading

Required type rules

Allowed by default

  • compare two values of the same temporal domain;
  • add/subtract a matching duration with overflow/underflow checks;
  • construct EpochNumberWithFraction from validated number/index/length;
  • encode a validated epoch fraction into absolute or relative Since;
  • decode an input since into a tagged validated representation;
  • serialize to the existing fixed-width ABI where wire compatibility allows.

Rejected without explicit conversion

  • EpochNumber versus BlockNumber;
  • epoch values versus encoded Since;
  • absolute versus relative Since;
  • block-number versus epoch-fraction versus timestamp Since;
  • temporal values versus arbitrary u64;
  • application durations versus absolute timepoints;
  • malformed epoch fractions;
  • arithmetic that discards metric or relative flags.

Explicit escape hatch

Low-level protocol work may need raw bits. Provide named operations such as:

Since::from_raw_checked(u64) -> DecodedSince
Since<Mode, Metric>::to_raw()
EpochNumber::from_u64_checked(u64)
EpochNumber::to_u64()

The unsafe-looking direction must be explicit in source and typed-semantics metadata. No implicit widening or assignment conversion should cross these domains.

API migration sketch

A candidate additive first step:

let epoch: EpochNumber = env::current_epoch_number()
let start: BlockNumber = ckb::header_epoch_start_block_number(header)
let length: EpochLength = ckb::header_epoch_length(header)

let maturity: Since<Relative, EpochFraction> =
    ckb::since_epoch_relative(number, index, length)

let observed: DecodedSince = ckb::input_since(input)
require observed.satisfies(maturity)

The existing env::current_timepoint() -> u64 should remain only under the existing edition contract, emit a targeted migration warning, and migrate mechanically to the precisely named API. Do not silently change its return type within an edition.

Artifact and interface requirements

Temporal identity must survive every compiler layer:

flowchart LR
    SRC["Typed source"] --> AST["Temporal AST type"]
    AST --> IR["Distinct IR type / checked ops"]
    IR --> TS["typed-semantics record"]
    IR --> ABI["Fixed wire representation"]
    TS --> CHECK["Standalone checker"]
    ABI --> VM["CKB-VM execution"]
Loading

The public package interface must record:

  • semantic temporal type;
  • exact serialized representation;
  • constructor/decoder version;
  • Since metric and relative/absolute policy;
  • target/profile identity;
  • migration behavior.

A type alias to u64 is insufficient because it would erase the safety property before IR.

Implementation phases

Phase 0 — inventory and RFC

  • inventory every stdlib/source/IR/metadata path that currently uses a temporal u64;
  • define the type domains and legal operations;
  • define ABI and interface compatibility;
  • define edition migration and diagnostics;
  • align with official CKB Since and epoch-fraction semantics.

Phase 1 — additive typed APIs

  • add typed constructors, readers, decoders, comparisons, and checked arithmetic;
  • preserve old APIs with warnings;
  • add typed-semantics and interface records;
  • add formatter, LSP, docs, and Playground support.

Phase 2 — migration

  • provide a mechanical migration command or code action;
  • migrate bundled examples and package fixtures;
  • keep old-edition dependencies interoperable;
  • reject implicit cross-domain arithmetic in the new edition.

Phase 3 — stabilization

  • mutation-test Since flag/metric validation;
  • add exact CKB-VM vectors for valid and malformed encodings;
  • document compatibility and wire identity;
  • stabilize only after real timelock, DAO, vesting, and atomic-swap fixtures migrate.

Acceptance matrix

Case Expected
compare two EpochNumber values pass
add EpochDuration to EpochNumber pass with overflow guard
compare EpochNumber and BlockNumber type error
compare encoded Since and epoch type error
construct fraction with index >= length stable error
construct fraction with zero/overflowing length stable error
decode unknown Since flags stable runtime error
mix absolute and relative Since type error unless explicitly normalized by an accepted operation
round-trip valid CKB Since vectors exact match
compile old-edition current_timepoint source preserved behavior plus migration policy
cross-package interface changes raw u64 to typed domain correctly classified as compatible or breaking according to the accepted edition/ABI policy

Completion criteria

  • An accepted RFC defines every temporal domain and operation.
  • Official CKB Since and epoch semantics are cited and pinned with vectors.
  • Temporal identity survives AST, type checking, IR, typed semantics, interfaces, ABI, lowering, and checker.
  • The wire representation remains deterministic and versioned.
  • New-edition code cannot implicitly mix temporal domains with u64.
  • Existing-edition behavior is preserved with a mechanical migration path.
  • Timelock, DAO, vesting, NFT expiry, governance, and atomic-swap examples use the typed API.
  • Malformed flags, fractions, and arithmetic boundaries have CKB-VM negative tests.
  • LSP, formatter, VS Code, Playground, docs, and generated builders agree.
  • dev, ci, and backend gates pass.

Non-goals

  • inventing wall-clock semantics that CKB does not expose;
  • treating a HeaderDep epoch number as a Unix timestamp;
  • implicit conversion from arbitrary integers;
  • changing existing edition semantics in place;
  • generic dimensional-analysis or units-of-measure support;
  • hiding CKB Since flags behind unchecked arithmetic.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions