Skip to content

feat(context): add ClockContext for opt-in timestamp/block_id access#227

Draft
vpavlin wants to merge 4 commits into
mainfrom
feat/clock-context
Draft

feat(context): add ClockContext for opt-in timestamp/block_id access#227
vpavlin wants to merge 4 commits into
mainfrom
feat/clock-context

Conversation

@vpavlin
Copy link
Copy Markdown
Collaborator

@vpavlin vpavlin commented Jun 1, 2026

Summary

Implements Option 1 from the design discussion in #226: a ClockContext parameter that gives instruction handlers opt-in access to the current block number and timestamp without polluting the ABI or IDL.

  • Add ClockContext { block_id: BlockId, timestamp: Timestamp } struct in spel-framework-core (borsh-decodable, matches LEZ ClockAccountData layout)
  • Macro recognises ClockContext by type name (same as ProgramContext) and injects it from pre_states[0]
  • has_clock_context: bool added to IdlInstruction — transaction builders (FFI, Rust client codegen) automatically prepend the LEZ clock account (/LEZ/ClockProgramAccount/0000001) when the flag is set
  • idl_gen.rs updated to parse and propagate the new context type
  • 6 new unit tests for ClockContext construction, copy/clone, equality, debug, borsh roundtrip, and borsh error path

Usage

#[instruction]
pub fn time_locked_transfer(
    ctx: ProgramContext,
    clock: ClockContext,   // opt-in; injected by dispatcher, not in IDL/ABI
    #[account(owner = self_program_id)]
    vault: AccountWithMetadata,
    unlock_timestamp: u64,
) -> SpelResult {
    if clock.timestamp < unlock_timestamp {
        return Err(SpelError::new(1, "transfer is still time-locked"));
    }
    // ...
}

Instructions that do not declare ClockContext are completely unaffected — no breaking changes.

Implementation notes

  • The clock account is re-inserted as an unchanged post-state at position 0 after the handler returns, so the pre_states_clone.zip(post_states) loop in the generated main() stays aligned.
  • Position rule: ClockContext must come before any account or arg parameters (enforced at macro parse time). It may follow ProgramContext or appear first if there is no ProgramContext.
  • Default clock account used: CLOCK_01 (updated every block, *b"/LEZ/ClockProgramAccount/0000001"). Cadence selection can be added as a follow-up.

Test plan

  • All existing workspace tests pass (cargo test --workspace --exclude spel-ffi-compile-test)
  • New test_clock_context_* tests in spel-framework-core/tests/context_parameter.rs all pass
  • Manually write a small program using ClockContext and verify the generated IDL contains "has_clock_context":true
  • Verify generated FFI code prepends the clock account when has_clock_context is set

Closes #226 (Option 1)

🤖 Generated with Claude Code

vpavlin and others added 2 commits June 1, 2026 12:10
Adds `ClockContext` as a new injected parameter type for `#[instruction]`
handlers, giving programs access to the current block number and timestamp
without adding them to the instruction ABI or IDL.

The dispatcher reads the LEZ clock account (`/LEZ/ClockProgramAccount/0000001`)
from `pre_states[0]`, borsh-decodes it into `ClockContext`, and injects it into
the handler call — exactly like `ProgramContext`. Transaction builders
(ffi_codegen, codegen, logos_module_codegen) automatically prepend the clock
account ID when `has_clock_context` is set in the IDL.

Closes #226 (option 1 of the design discussion).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The setup-logos-blockchain-circuits.sh script was removed from the
logos-blockchain/logos-blockchain repo on 2026-05-30 (commit 89259924)
in "chore: Purge old circuits (#2829)". The curl call now fetches a 404
page and bash interprets "404: Not Found" as a command, causing all E2E
jobs to fail immediately with exit code 127.

The step was never needed for CI: the sequencer is built with
`--features standalone`, which activates `sequencer_core/mock` and
replaces actual ZK proof generation with mock implementations. Circuits
are only required for production proving.

Also removes two duplicate occurrences of the step in the
privacy-smoke-test and ffi-call-test jobs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds an opt-in ClockContext execution parameter to instruction handlers so programs can access the current LEZ block timestamp and block ID without adding new ABI/IDL arguments, and wires that opt-in through IDL + client generators so callers automatically prepend the LEZ clock account.

Changes:

  • Introduces ClockContext { block_id, timestamp } (borsh-decodable) in spel-framework-core and re-exports it via the prelude.
  • Updates the proc-macro dispatcher and IDL generation to detect ClockContext, inject it from pre_states[0], and emit has_clock_context in the IDL.
  • Updates Rust client codegen and FFI codegen to prepend /LEZ/ClockProgramAccount/0000001 when has_clock_context is set; adds unit tests for ClockContext.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
spel-framework-macros/src/lib.rs Detects ClockContext, injects it from pre_states[0], reinserts unchanged post-state, and emits has_clock_context in IDL outputs.
spel-framework-core/src/context.rs Adds the ClockContext struct and constructor.
spel-framework-core/src/lib.rs Re-exports ClockContext through prelude.
spel-framework-core/src/idl.rs Adds has_clock_context: bool to IdlInstruction (serde default/skip when false).
spel-framework-core/src/idl_gen.rs Parses ClockContext and propagates has_clock_context into IDL generation.
spel-framework-core/tests/context_parameter.rs Adds unit tests for ClockContext construction/traits/borsh decode behavior.
spel-client-gen/src/codegen.rs Prepends the LEZ clock account ID when ix.has_clock_context is true.
spel-client-gen/src/ffi_codegen.rs Prepends the LEZ clock account ID when ix.has_clock_context is true.
spel-client-gen/src/tests.rs Updates IDL test fixtures to include has_clock_context.
.github/workflows/ci.yml Removes Install logos-blockchain-circuits step(s) from CI jobs.
.github/workflows/weekly-rc.yml Removes Install logos-blockchain-circuits step.
.github/workflows/release.yml Removes Install logos-blockchain-circuits step.
.github/workflows/lez-compat.yml Removes Install logos-blockchain-circuits step.
.github/workflows/lez-compat-improved.yml Removes Install logos-blockchain-circuits step.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +869 to +876
let mut __ps = pre_states;
if __ps.is_empty() {
panic!("SPEL ClockContext: expected clock account as first pre_state, found none");
}
let __c = __ps.remove(0);
let __ctx = borsh::from_slice::<spel_framework::context::ClockContext>(&__c.account.data)
.expect("SPEL ClockContext: failed to decode clock account data");
(__c, __ctx, __ps)
Comment on lines 605 to +609
} else if is_context_type(ty) {
// ProgramContext is injected by the dispatcher and never part of the IDL/ABI.
} else if is_clock_context_type(ty) {
// ClockContext is injected by the dispatcher and never part of the IDL/ABI.
has_clock_context = true;
Comment thread .github/workflows/ci.yml
Comment on lines 31 to 36
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
with:
prefix-key: "e2e"
- name: Install logos-blockchain-circuits
run: |
curl -sSL https://raw.githubusercontent.com/logos-blockchain/logos-blockchain/main/scripts/setup-logos-blockchain-circuits.sh | bash
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Build (all packages including spel)
run: cargo build -p spel-framework -p spel-framework-core -p spel-framework-macros -p spel-client-gen -p spel
@vpavlin vpavlin force-pushed the feat/clock-context branch from 54419c0 to d817125 Compare June 1, 2026 10:13
vpavlin and others added 2 commits June 1, 2026 12:17
- Apply rustfmt: expand single-line writeln! calls and struct literal
  in ClockContext::new to conform to the project's 100-char line limit
- Restore the logos-blockchain-circuits install step in ci.yml with the
  pinned commit hash; removing it was wrong — cargo build -p spel
  triggers the circuits build.rs check even in tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Validate clock account ID before decoding ClockContext: compare
  pre_states[0].account_id against the well-known LEZ clock account
  address (*b"/LEZ/ClockProgramAccount/0000001"). Without this, a caller
  could supply an attacker-controlled account at position 0 to forge
  timestamp/block_id values seen by the handler.

- Mirror proc-macro constraints in idl_gen: reject duplicate ClockContext
  parameters and ClockContext appearing after account/arg params. The
  proc-macro already enforces these; idl_gen now matches so that it
  cannot produce an IDL that claims ClockContext injection for a signature
  the macro would refuse to compile.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: expose block timestamp and block ID in instruction execution context

2 participants