Skip to content

feat(lang): attach ref-backed policy script as a reference input#332

Merged
scarmuega merged 3 commits into
mainfrom
feat/policy-ref-script-reference-input
Jun 7, 2026
Merged

feat(lang): attach ref-backed policy script as a reference input#332
scarmuega merged 3 commits into
mainfrom
feat/policy-ref-script-reference-input

Conversation

@scarmuega

Copy link
Copy Markdown
Contributor

Context

Today a policy in tx3 is effectively a named hash. You can declare a ref-backed policy:

policy Validator {
    hash: 0xABCDEF1234,
    ref: 0x…#0,
}

…and the ref is captured during lowering — but when Validator is used as the from of an input (spending a script-locked UTxO), the lowering threw the script source away and kept only the hash. As a result the policy's ref UTxO was never added to the transaction's reference_inputs, so the compiled Cardano tx had no way to find the script that must validate the spend.

This closes that gap for the flow:

  1. define policy P { hash, ref }
  2. use P as from in an input
  3. expect P's ref UTxO to appear in the compiled tx's reference_inputs

In Cardano, including the ref UTxO (which carries the script) as a reference input is the complete and correct mechanism for a ref-backed policy — no witness-set script bytes are needed.

Changes

All in crates/tx3-lang/src/lowering.rs:

  • Context gains a shared ref accumulator (Rc<RefCell<…>>) carried across every enter_* clone, with record_script_ref / drain_script_refs helpers. Dedups on insert.
  • Symbol::PolicyDef lowering — when a policy is used in address context, capture policy.script.as_utxo_ref() into the accumulator before returning BuildScriptAddress(hash). Hash-only policies yield None (no-op).
  • TxDef::into_lower restructured to seed the accumulator with explicit reference blocks first, lower the body (which records policy refs), then drain into Tx.references. Explicit refs keep their leading position; coincident refs dedup to one entry.

No backend change — compile_reference_inputs in tx3-cardano already maps Tx.references → Cardano reference_inputs.

Scope

Input from path for ref-backed policies. Out of scope (noted for follow-up): mint/burn under a ref-backed policy, and embedded-script (witness-set) attachment.

Verification

  • New fixture examples/policy_reference_script.tx3 + generated .tir snapshot: ref UTxO appears in references, input resolves to BuildScriptAddress(hash).
  • Unit tests: ref-backed policy → 1 reference input; same policy on two inputs → deduped to 1; hash-only policy → 0.
  • cargo test -p tx3-lang: 170 passed, 0 failed — all pre-existing snapshots byte-identical (no regressions).

🤖 Generated with Claude Code

When a policy declared with a `ref` UTxO is used as the `from` of an
input, the lowering previously kept only the policy hash and discarded
its script source. As a result the policy's reference-script UTxO never
reached the transaction's `reference_inputs`, so the compiled Cardano tx
had no way to find the script validating the spend.

Capture the policy's `ref` at the lowering decision point and drain it
into `Tx.references` once the tx body is lowered, deduped against
explicit `reference` blocks and repeated policy uses. The Cardano
backend already maps `Tx.references` to `reference_inputs`, so no backend
change is needed.

Scope: the input `from` path for ref-backed policies. Hash-only policies
and non-address uses are unaffected (no-op). Mint/burn and embedded
witness-set attachment remain out of scope.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
scarmuega and others added 2 commits June 7, 2026 10:47
Add a checks variant to `test_lowering!` that binds the lowered TIRs
(keyed by tx name) to an identifier, so an example can carry inline
assertions instead of separate hand-rolled tests.

Expand the policy_reference_script example to cover all three edge cases
in one file (ref-backed `from`, dedup across two inputs, hash-only
policy) and migrate the loose unit tests into the macro's checks block.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extend reference-script tracking to mint and burn blocks: a ref-backed
policy used as the policy of a minted or burned asset now contributes its
`ref` UTxO to the transaction's reference inputs, just like a policy used
as an input's `from`.

Introduce a sticky `capture_policy_ref` context flag (carried through the
`enter_*` transitions like `script_refs`) set over the mint/burn amount
subtree. `Symbol::PolicyDef` captures the ref whenever the policy stands
in for a script that must run — an address credential or a mint/burn
policy — while still lowering to a plain hash outside address context.

Expand the policy_reference_script example with mint/burn txs (ref-backed
and hash-only) and corresponding checks.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@scarmuega scarmuega merged commit 5ebed76 into main Jun 7, 2026
6 checks passed
@scarmuega scarmuega deleted the feat/policy-ref-script-reference-input branch June 7, 2026 14:07
scarmuega added a commit that referenced this pull request Jun 7, 2026
PR #332 captured a ref-backed policy's `ref` UTxO whenever the policy
appeared in an address position, keyed on `is_address_expr()`. But that
flag is also set for an output's `to`, so sending funds *to* a script
address wrongly pulled the policy's reference script into the
transaction — the script does not run when receiving, only when spending.

Drive the capture solely off the explicit `capture_policy_ref` signal,
set at the script-executing sites: an input's `from` and mint/burn
amounts. Output `to` keeps its address lowering but no longer captures.

Add a `send_to_policy` example tx that outputs to a ref-backed policy
without spending from it, asserting it contributes no reference input.

Co-Authored-By: Claude Opus 4.8 (1M context) <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

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant