Repository navigation
Check the Plutus script language against the era when scripts are deserialised - #1372
Closed
carbolymer wants to merge 8 commits into
Closed
carbolymer wants to merge 8 commits into
carbolymer wants to merge 8 commits into
Conversation
carbolymer
changed the base branch from
master
to
mgalazyn/fix/unsupported-plutus-language-error
October 7, 2026 13:35
carbolymer
force-pushed
the
mgalazyn/fix/unsupported-plutus-language-error
branch
from
October 8, 2026 08:12
b2764fd to
88b834f
Compare
carbolymer
force-pushed
the
mgalazyn/fix/jordans-plutus-language-in-era
branch
from
October 8, 2026 08:12
3db491a to
029efd1
Compare
PlutusLangInEra lang era is a Constraint-kinded closed type family listing the language/era pairs the ledger accepts, mirroring eraMaxLanguage. The PlutusLangsInEra class reflects it back as runtime evidence for code that only learns the language at runtime. Nothing uses it yet. Compiles.
fromPlutusRunnable now demands PlutusLangInEra lang era and returns
PlutusScript era instead of Maybe. The only way ledger's mkPlutusScript
returned Nothing was an unsupported language/era pairing, which the
constraint now excludes, so the Nothing branch is unreachable.
With the Left gone, the Either L.Language plumbing through
getAnyWitnessScript, getTxScriptWitnessRequirements,
collectTxBodyScriptWitnessRequirements and makeUnsignedTx is dropped and
MakeUnsignedTxPlutusLanguageNotSupportedInEra is removed.
Does not compile. The constraint has to come from somewhere:
AnyScriptWitness.hs:263:31: error:
Could not deduce 'PlutusLangInEra lang era'
arising from a use of 'fromPlutusRunnable'
from the context: L.AlonzoEraScript era
or from: L.PlutusLanguage lang
bound by a pattern with constructor: PlutusScriptInEra
The caller only has the witness in hand, and lang is existential in
AnyWitness. The evidence must travel inside the value: the next commit
puts it on the PlutusScriptInEra constructor.
The constraint is packed into the constructor so that any pattern match on
PlutusScriptInEra brings it back into scope. This is what lets
resolvePlutusWitnessScript call fromPlutusRunnable even though lang is
existential in AnyWitness: the evidence travels with the value.
Within this module GHC then demanded the constraint, in order, at:
Script.hs:141 deserialiseFromCBOR Could not deduce 'PlutusLangInEra lang era'
arising from a use of 'PlutusScriptInEra'
Script.hs:90 HasTextEnvelope arising from the superclasses of an instance
Script.hs:153 deserialisePlutusScriptInEra arising from 'deserialiseFromCBOR'
Script.hs:214 decodeAnyPlutusScript arising from 'deserialisePlutusScriptInEra'
Script.hs:239 serialiseAnyPlutusScriptToTextEnvelope arising from 'serialiseToTextEnvelope'
Script.hs:258 deserialiseAnyPlutusScriptFromTextEnvelope arising from 'FromSomeType'
Three kinds of fix, by what is in scope at each site:
* lang and era are type variables of a signature or instance head:
add the constraint to the context (CBOR instance, HasTextEnvelope
instance, deserialisePlutusScriptInEra).
* the value is already a PlutusScriptInEra: match the constructor
(serialiseAnyPlutusScriptToTextEnvelope).
* lang is existential, learned from an envelope type string at runtime:
look up PlutusLangsInEra evidence and match it (decodeAnyPlutusScript,
deserialiseAnyPlutusScriptFromTextEnvelope). An unsupported language is
now a decoder error, or an envelope type that is not offered at all.
Does not compile. Next module:
AnyScript.hs:107 tryPlutusScript Could not deduce 'PlutusLangInEra l era'
AnyScript.hs:133 deserialiseAnyPlutusScriptOfLanguage
Could not deduce 'PlutusLangInEra lang (LedgerEra era)'
AnyScript.hs:204 readTextEnvelopeScript
Could not deduce 'PlutusLangsInEra (LedgerEra era)'
AnyScript is where cardano-cli reads script files for every era, so this
is where the runtime check pays once.
AnyScript.hs:133 deserialiseAnyPlutusScriptOfLanguage
lang and era are both type variables. Look up PlutusLangsInEra
evidence; Nothing becomes a decoder error.
AnyScript.hs:107 tryPlutusScript
the ledger's withPlutusScript hands over an existential language
with no era evidence. Look it up; Nothing falls through to the
next decoder. Ledger only yields scripts valid for the era, so this
Nothing is unreachable, but GHC cannot know that.
AnyScript.hs:204 readTextEnvelopeScript
needs PlutusLangsInEra (LedgerEra era) from obtainCommonConstraints,
so EraCommonConstraints gains it.
The SerialiseAsCBOR (AnyScript era) instance gains PlutusLangsInEra era in
its context for the same reason.
Does not compile. GHC now stops at the function this chain started from:
AnyScriptWitness.hs:273:6: error: [-Wredundant-constraints]
Redundant constraint: PlutusLangInEra lang era
In the type signature for: fromPlutusRunnable
Nothing in the body consumes the constraint. Ledger's mkPlutusScript is
value-level and still returns Maybe, so the constraint on the function
does no work GHC can see. The next commit addresses that.
…nt type
GHC rejected the previous signature:
AnyScriptWitness.hs:273:6: error: [-Wredundant-constraints]
Redundant constraint: PlutusLangInEra lang era
In the type signature for: fromPlutusRunnable
The constraint did no work in the body because ledger's mkPlutusScript is
value-level and still returns Maybe. The restriction that actually holds
is on the constructor, so fromPlutusRunnable now takes the
PlutusScriptInEra lang era itself. That type is only inhabited for
pairings in the PlutusLangInEra table, so the signature still excludes
invalid pairings, and the Nothing branch stays unreachable.
Does not compile. The legacy shim is next. GHC stops at its first error,
the Either wrapper added by the PR around a function that no longer
returns Either:
LegacyScripts.hs:401:12: error:
Couldn't match expected type: Either a0 (TxScriptWitnessRequirements ...)
with actual type: TxScriptWitnessRequirements ...
Behind it, mkPlutusScriptWitness builds PlutusScriptInEra four times with
lang and era abstract over AlonzoEraOnwards, and will demand
PlutusLangInEra plutuslang (ShelleyLedgerEra era).
…roof The legacy Old.PlutusScriptWitness already carries a ScriptLanguageInEra lang era proof that the shim was discarding with a wildcard. Matching its ten Plutus constructors yields PlutusLangInEraEvidence for the ledger era; the seven simple-script constructors are closed with EmptyCase on the PlutusScriptVersion, which has no SimpleScript' inhabitant. mkPlutusScriptWitness gains PlutusLangInEra plutuslang (ShelleyLedgerEra era) in its context, discharged by matching the evidence in toNewPlutusScriptWitness. The error call the PR added to legacyWitnessToScriptRequirements is removed, since the Either it guarded is gone. This is why the PlutusLangInEra table has Alonzo and Babbage rows: the legacy createTransactionBody path reaches this shim for every era from Alonzo onwards. The library compiles. Tests and generators do not yet.
Cardano.Api.Experimental and Cardano.Api.Experimental.Plutus export PlutusLangInEra, PlutusLangInEraEvidence and PlutusLangsInEra. assertValidPlutusScriptBytesExperimental is polymorphic in both lang and era, so it looks up evidence before deserialising. The PR's test that built a PlutusV4 inline witness in Conway no longer typechecks, which is the point of the change. It is replaced by a test that deserialising V4 bytes in Conway fails. Compiles. cardano-api-test and cardano-api-golden pass.
carbolymer
force-pushed
the
mgalazyn/fix/unsupported-plutus-language-error
branch
from
October 8, 2026 08:38
88b834f to
bec442d
Compare
carbolymer
force-pushed
the
mgalazyn/fix/jordans-plutus-language-in-era
branch
9 times, most recently
from
October 8, 2026 13:14
ef10201 to
fa793a8
Compare
PlutusLangInEra lang era is now a single-constructor GADT holding the language singleton, with the ledger's EraPlutusTxInfo lang era class as its context, so the ledger's instances are the table and cardano-api keeps no copy. An unsupported pairing has no ledger instance, so the closed type family, its TypeError and UndecidableInstances go. plutusLangInShelleyBasedEra gives the proof at runtime from a ShelleyBasedEra witness by asking the ledger's mkSupportedLanguage, and HasPlutusLangInEra wraps it for code that only has the ledger era, with instances for all seven eras. PlutusScriptInEra carries the ledger script next to the runnable, built by mkPlutusScriptInEra under the EraPlutusTxInfo constraint through the ledger's mkSupportedPlutusScript, so plutusScriptInEraToScript is a field read and fromPlutusRunnable goes. The unsupported language error names the era, and the text envelope decoder recognises the envelope's language before the type lookup so that error is reported instead of an unknown envelope type. The SerialiseAsCBOR and HasTextEnvelope instances of PlutusScriptInEra require EraPlutusTxInfo lang era, so they exist only for supported pairings and carry no runtime check.
carbolymer
force-pushed
the
mgalazyn/fix/jordans-plutus-language-in-era
branch
from
October 8, 2026 13:22
fa793a8 to
0e243ec
Compare
4 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on PR 1363.
The seven commits are Jordan's, from the branch he linked on that PR, rebased onto 1363.
PlutusScriptInEranow requires the type-level constraintPlutusLangInEra lang era, so a Plutus script in a language the era does not support cannot be built in the experimental API.The check runs once, where the bytes enter:
decodeAnyPlutusScript,deserialiseAnyPlutusScriptOfLanguageand the text envelope decoder reject an unsupported language with a decoder error.fromPlutusRunnableis total again.The
Either L.Languageresults andMakeUnsignedTxPlutusLanguageNotSupportedInEraadded by PR 1363 are removed.The legacy shim turns the
ScriptLanguageInEraproof of a legacy witness into the new evidence, so the legacy path needs no runtime check.The
createTransactionBodyreference script check, the generator fix and the V4 fixture from PR 1363 stay.Context
Jordan proposed this design in a comment on PR 1363:
Open points for review:
PlutusLangsInErahas no instances for Shelley, Allegra and Mary, soreadFilePlutusScriptinCardano.CLI.Compatible.Read, which runs under a plainShelleyBasedEra, does not compile against this branch.check-changelogjob fails because the fragments carrypr: 1363; this PR needs its own fragment once the design is settled.PlutusLangInErais a third copy of the language per era table, next to ledger'smkPlutusScriptand the legacyScriptLanguageInEra.How to trust this PR
The test that built a Plutus V4 inline witness in Conway no longer typechecks, which is the point of the change.
prop_deserialise_rejects_unsupported_plutus_languagereplaces it and checks that V4 bytes fail to deserialise in Conway.prop_makeUnsignedTx_accepts_reference_plutus_v4_witnessandprop_createTransactionBody_rejects_unsupported_reference_script_languagefrom PR 1363 are kept.Checklist
.changes/