Skip to content

Encode NewConstitution with the Constitution wrapper, as plutus-ledger-api (#223) - #235

Open
satran004 wants to merge 1 commit into
mainfrom
fix/new-constitution-wrapper
Open

satran004 wants to merge 1 commit into
mainfrom
fix/new-constitution-wrapper

Conversation

@satran004

Copy link
Copy Markdown
Member

Fixes #223.

Summary

plutus-ledger-api V3 encodes NewConstitution as Constr 5 [Maybe GovernanceActionId, Constr 0 [Maybe ScriptHash]]. The guardrail script hash sits inside a Constitution record (Contexts.hs:692, 706; cardano-ledger Conway/TxInfo.hs transConstitution). julc left out the Constr 0 wrapper.

  • New ledger record Constitution(Optional<ScriptHash> script), encoded as Constr 0. It follows the Committee convention, and NewConstitution(Optional<GovernanceActionId>, Constitution) now carries it.
  • No compiler change: the compiler builds on-chain types from the bundled ledger-api sources, so nc.constitution().script() compiles to the Haskell layout. A compiled-script test checks this against Haskell-layout data, for the hash both present and absent.
  • Golden hex from Haskell for all seven governance actions, both encode and decode. It's derived from the plutus-ledger-api makeIsDataSchemaIndexed declarations, Maybe/Rational ToData and transGovAction.
  • Also updated: the catalog (LedgerCatalog), playground completions, native metadata and docs, plus a release note.

Compatibility

  • On-chain: contracts that read constitution() must be recompiled, and their script hashes change. Contracts that only match the NewConstitution case compile to identical bytes; the byte-snapshot tests pass.
  • Off-chain: code that constructs NewConstitution now passes a Constitution.

Known: julc-verification Lean model

The pinned CardanoLedgerApiBlaster model (@5dab3c43) declares Constitution as Option ScriptHash, without the wrapper, and upstream HEAD is the same.

  • Recorded: in the capability note and verification ADR. Formal verification of constitution().script() isn't sound until the pin is bumped.
  • Not done: the capability status isn't flipped and the model isn't patched; it is to be reported upstream.
  • Nothing silently passes or fails today: the checked-in Lean test asserts only the constructor's tag and arity.

Tests

Suite Result
julc, root ./gradlew test 11,219 tests, 0 failures, before the review minors
julc reruns after review GovernanceTypeTest 27/0, julc-verification 92/0, julc-cli 442/0
Yano, local build ledger-conformance 133/0, anchor 15/15, appchain-anchor-onchain 120/120
Yano script-evaluators All pass, apart from the 4 expected deviation canaries that #219/#227/#228 fix

Independently reviewed: the reviewer hand-checked all seven goldens against Haskell and approved. Minors applied.

🤖 Generated with Claude Code

…r-api

plutus-ledger-api V3 wraps the guardrail script hash of a NewConstitution
action in a Constitution record: Constr 5 [Maybe GovernanceActionId,
Constr 0 [Maybe ScriptHash]] (Contexts.hs:692, 706; cardano-ledger
Conway/TxInfo.hs transConstitution). julc encoded Constr 5 [Maybe id,
Maybe hash], so a Julc contract reading a NewConstitution proposal saw the
wrong layout.

- New ledger record Constitution(Optional<ScriptHash> script), Constr 0,
  following the Committee convention; NewConstitution now carries it. The
  compiler builds on-chain types from the bundled ledger-api sources, so
  nc.constitution().script() compiles to the Haskell layout with no
  compiler change.
- Golden hex derived from Haskell for all seven GovernanceAction
  constructors (encode and decode), replacing the self-comparing
  assertions in LedgerParityTest. No other action differed. All seven
  also match Yano's independent ConwayTxInfoTranslator byte for byte.
- Catalog, completions, native metadata and docs updated; release note.
- julc-verification: the pinned CardanoLedgerApiBlaster model declares
  Constitution as Option ScriptHash without the Constr 0 wrapper;
  recorded in the capability note and ADR, to be reported upstream.

Contracts that read constitution() must be recompiled; contracts that
only match the NewConstitution case keep identical bytes.

Fixes #223

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012MQvHGqr1BvpfjkzfbHWfW
@satran004 satran004 added the yano Found or needed by Yano (Cardano node) label Oct 1, 2026

@satran004 satran004 left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Correctness-focused review of 09ad5a8: recommend changes before merge. Two P1 findings are attached inline:

  1. Adding the bundled Constitution type silently changes binding and typed-boundary semantics for an existing user-declared nested record of the same name. A before/after PV11 Java-VM probe reverses acceptance of the intended integer payload and a wrong optional payload, with no compiler diagnostics.
  2. Formal verification still admits/promotes Constitution properties against the acknowledged incompatible pinned Lean layout. Documentation alone does not fail this path closed.

The actual ledger wrapper and CCL conversion match the Haskell representation. Independently ran the full compiler suite (1,782 tests), ledger-api (218), CCL integration (235), verification suite, and CLI suite (442 discovered, 5 skipped): no failures. Also ran a separate DSL-admission probe and a before/after compiled-UPLC/VM probe; their results expose the findings despite the passing existing suites. No external DevKit, Yano, or full Lean proof run was performed.

Posted as a COMMENT review because the authenticated GitHub account owns this PR; the recommendation is request changes.

* A constitution: the optional guardrails script hash. The anchor is omitted, as in plutus-ledger-api V3.
* Encoded as {@code Constr 0 [Maybe ScriptHash]}.
*/
public record Constitution(Optional<ScriptHash> script) implements PlutusDataConvertible {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

[P1] Preserve source-declared Constitution binding before bundling this type

The newly registered ledger type captures a previously valid nested source record. Reproduced with @MintingValidator class ReviewBinding { record Constitution(BigInteger value) {} @Entrypoint static boolean validate(Constitution c, ScriptContext ctx) { return true; } } (ordinary imports for BigInteger, annotations and ScriptContext). Both versions compile with no diagnostics. Evaluating the generated programs with the Java VM configured for PV11 and the same V3 context gives:

Redeemer Before this PR This PR
Constr 0 [I 42] success UnConstrData: not ConstrData
Constr 0 [Constr 1 []] UnIData: not IntData success

The compiler/ledger source used for the baseline is identical to this PR's parent. This is not merely a source-compatibility warning: an unused typed parameter silently validates against the ledger record instead of the declaration Java binds to, changing which datum/redeemer representations are accepted. Referencing c.value() also newly fails member resolution. The release-note workaround to rename the user's record does not prevent the silent case. Please fix source-type resolution/registration precedence as part of adding this bundled name, and add both compile/evaluate regressions so local declarations retain their own schema. This violates the repository's source-name binding and typed-boundary invariants.

{"id":"constructor.governance.noConfidence","kind":"CONSTRUCTOR","status":"TYPED","source":"Governance.lean","signature":"| NoConfidence : Option GovernanceActionId → GovernanceAction","note":"E.4k exposes the optional prior action under its guard."},
{"id":"constructor.governance.updateCommittee","kind":"CONSTRUCTOR","status":"TYPED","source":"Governance.lean","signature":"| UpdateCommittee : Option GovernanceActionId → OldCommitteeMembers → NewCommitteeMembers → Quorum → GovernanceAction","note":"E.4k exposes reviewed collections while withholding raw quorum."},
{"id":"constructor.governance.newConstitution","kind":"CONSTRUCTOR","status":"TYPED","source":"Governance.lean","signature":"| NewConstitution : Option GovernanceActionId → Constitution → GovernanceAction","note":"E.4k exposes prior action and optional script hash only."},
{"id":"constructor.governance.newConstitution","kind":"CONSTRUCTOR","status":"TYPED","source":"Governance.lean","signature":"| NewConstitution : Option GovernanceActionId → Constitution → GovernanceAction","note":"E.4k exposes prior action and optional script hash only. The pinned model (@5dab3c43) declares Constitution as Option ScriptHash and omits the Constr 0 wrapper of plutus-ledger-api, so a verified contract that reads constitution().script() is evaluated against the wrong data shape until the pin is bumped (julc #223; to be reported upstream)."},

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

[P1] Fail closed on the incompatible Constitution verification path

Leaving this capability TYPED makes the new note informational only. I reproduced successful DslPropertyValidator.validateAndNormalize and ComposedDslPromotion.promote for tx.proposals().exists(p -> p.actionStrict().exists(a -> a.whenNewConstitution((previous, script) -> script.isPresent()))); its generated Lean still calls the pinned IsData.fromData ... : Option GovernanceAction and matches a bare optional script.

At the pinned Governance.lean, Constitution is Option ScriptHash. Therefore the canonical new payload Constr 5 [Nothing, Constr 0 [Just (B hash)]] fails that strict action decoder: it tries to decode Just (B hash) as the bytes of a ScriptHash. The Nothing case fails for the same reason. The compiled validator now reads those ledger values correctly, while the admitted property cannot recognize them. Merely warning in JSON/ADR/release notes leaves a runnable verification path with the wrong meaning.

Please update/patch the model pin and add full payload encode/decode controls, or reject the affected strict-action/Constitution verification paths until that is available. Add an admission regression and cross-model controls for both script-present and script-absent canonical data; the existing tag/arity-only control does not detect this mismatch.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

yano Found or needed by Yano (Cardano node)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

julc-ledger-api: NewConstitution misses the Constitution wrapper (Constr 0) in its Plutus data encoding

1 participant