Repository navigation
Release Notes
Scalus 1.4 runs Ed25519, secp256k1 and BLS12-381 on the C libraries that cardano-node links, and
brings the Emulator closer to the Cardano ledger: withdrawals, delegation targets, the treasury,
pool updates, pointer addresses, and datum and native-script bytes now behave as on chain. The
JavaScript Emulator gains what a Lucid Evolution provider needs, and the new scalus-verification
module proves properties of compiled validators with Lean.
Added
- JavaScript Emulator API for SDK providers.
Emulator.createtakesprotocolParamsto override
fields of the preset,treasury,clock: "wall"to followDate.now(), and pool registrations
by{ poolId }alone.addRewardspays lovelace into a registered reward account,getAccount
reads its balance, deposit, pool and DRep,getTreasuryreads the treasury, and
getStakeDistributionentries carryrewardAddress.Utxo.scriptandPlainUtxo.scriptreturn
the reference script as{ type, script }, the shapewithScriptReftakes;withScriptRefand
scriptHashalso take"PlutusV4". Emulator.addDatumadds a datum to the store thatgetDatumreads, so a UTxO added with
addUtxothat holds only a datum hash has its datum found. It keeps the given bytes and returns
their hash.ImmutableEmulator.withDatumis the immutable form. The Scala Emulator also gets
addRewardsandtreasury. Donations move into the treasury at the next epoch boundary.- JavaScript
TxEvaluatorevaluates many transactions under one set of parameters and prepares the
cost models once.ScriptCachekeeps decoded scripts across evaluations: pass it to
TxEvaluatorasscriptCache, or on the JVM to the newPlutusScriptEvaluatoroverload. On
recorded Midgard requests the cache saves 17–25% on the JVM. scalus-verificationandscalus-verification-scalatest: state properties of Scalus functions
in Scala (Props:forAll,exists,call,denotes) and prove them against the compiled
UPLC with theUplcBlastertactic (Lean 4 and IOG's Blaster). A counterexample is replayed on
the CEK machine.LeanProofsSuiteruns the proofs as ScalaTest tests. Experimental.Spec.expects,Spec.ensuresandensuringstate a function's contract in its body for the
verifier. They are removed before lowering, so a script has the same bytes and hash with and
without them. Off-chain,expectsandensuringthrowSpecificationError.- The
scalusClaude Code plugin has a session-start hook: in a Scalus project it loads the
using-scalusrouting skill, so an agent picks the contract skills before it writes code.
Plugin 1.4.0 follows the 1.4 API and the new Emulator rules. TxBuilder.updatePool(cert)updates a registered pool and pays no pool deposit; a certificate
added withIssueCertificatestill pays it, as for a new pool.
TransactionBuilder.calculateChangeValue,balanceFeeAndChange,
balanceFeeAndChangeWithTokens,Context.balanceandContext.balanceContexthave overloads
that take aCertState, andContext.validate,validateContextandfinalizeContext
overloads that take the treasury. The oldvalidateforms validate against a treasury of 0,
so they reject a transaction that states a non-zerocurrentTreasuryValue.
TxBalance.produced(tx, certState, params)and
Certificate.shelleyTotalDeposits(params, isRegisteredPool, certificates)charge no deposit for
a pool re-registration.DatumOption.Inline.fromCbor,DatumOption.Inline.fromBinaryDataandScript.Native.fromCbor
build values that keep their bytes.Script.fromCbordecodes a[language, script]script.Address.fromBytes(bytes, protocolVersion),Pointer.fromBytes(bytes, protocolVersion)and
Pointer.parseFrom(bytes, startIndex, protocolVersion)read a pointer address as a node reads it
at that protocol version.Address.fromBytesLenientandfromBech32Lenientread an address
that a node already holds, as the ledger'sdecompactAddrdoes.- The bloxbean
StakeStateResolver.resolveForTx(tx, slot)gives the cert state that a mainnet
transaction saw: each account's reward balance, deposit, pool and DRep, and the pools and DReps
it delegates to, as of its slot.treasuryAt(epoch)gives the treasury at the start of an epoch.
Changed
- Breaking: on the JVM, Ed25519, secp256k1 and BLS12-381 run on the native library of
scalus-crypto-jni, which needs Linux with glibc 2.34+, macOS 11+ (arm64) or 10.15+ (x64), or
Windows x64. On other platforms these operations throwIllegalStateException; Alpine/musl
is not supported. blst-java is no longer a dependency. Ed25519 verification on the JVM takes
about 30 µs instead of 52 µs. CekValue.VBuiltinkeeps the arguments of a partly applied builtin instead of a copied
BuiltinRuntimeand a closure that rebuilds its term. Code that builds or matchesVBuiltin
directly must change; code that only evaluates scripts is not affected.- Script evaluation is faster on recorded Midgard replays: 13–15% on the JVM, because source
positions are read only for profiling and tracing, and up to 19% in JavaScript, because
builtins are applied without copies.unConstrData,unListDataandunMapDatabuild their
result in one pass. - Breaking:
DatumOptionis a sealed trait instead of an enum, andInlineholds a
KeepRaw[Data]with the original CBOR.Inline(data),Hash(hash)andcase Inline(d)with
d: Datastill compile;ordinal,fromOrdinalandInline.copyare gone. Two inline
datums with the sameDataand different bytes are no longer equal; usecontentEqualsto
compare byData. TheEmulatorStatefielddatumsand the testkitImmutableEmulatorfield
datumsare nowbinaryDatums, aMap[DataHash, KeepRaw[Data]];datumsremains as a decoded
view.copy(datums = ...)and a nameddatumsargument ofImmutableEmulatorno longer
compile. - Breaking:
Script.Nativeholds aKeepRaw[Timelock]with the original CBOR, its
constructor is private, andcopyis gone.Native(timelock),case Native(t)and
native.scriptstill compile. Two native scripts with the same timelock and different bytes
are no longer equal. ReplaceCbor.decode(bytes).to[Script].valuewith
Script.fromCbor(bytes):Codec[Script]is now anEncoderand aDecoderthat needs an
OriginalCborByteArray. Encode a native script in a non-canonical encoding, or a value that
holds one, withscalus.serialization.cbor.Cbor: borer's validatingCbor.encoderejects its
bytes. - Breaking:
DelegationStatestores oneConwayAccountStateper account inaccounts. The
rewards,deposits,stakePoolsanddrepsmaps remain as deprecated read-only views, and
DelegationState(r, d, p, v)still compiles and links. A 1.3 binary that left out one of the 4
maps does not link, andcopywith the old field names and a pattern match on 4 fields no
longer compile; updateaccountsinstead. - Breaking:
UtxoEnvhas a fifth field,treasury. Under the deprecated 4-argumentapply
and constructor, which useCoin.zero, a transaction that states a non-zero
currentTreasuryValuefails withTreasuryValueMismatch. A positional pattern match on
UtxoEnvneeds the fifth field, and a 1.3 binary call ofcopyno longer links. - Breaking:
StakeCertificatesExceptionhas 2 new fields, the unregistered pools and DReps
that a transaction delegates to. A positional pattern match needs them, and a 1.3 binary call of
copyno longer links. - Breaking: the V3 on-chain
Constitutionis a case class,
Constitution(constitutionScript: Option[ScriptHash]), instead of an alias of
Option[ScriptHash]. A node wraps the guardrails script hash in theConstitution
constructor, so a script read a different proposal than on chain;LedgerToPlutusTranslation
and the bloxbeanInteropnow encode it as the node does. Code that builds or matches a
GovernanceAction.NewConstitutionhas to wrap the script hash, and a file that imports both
scalus.cardano.ledger.*and the V3 on-chain types must qualify oneConstitution. CertsMutatorapplies the withdrawals, then every certificate in one pass, in transaction
order, as the Conway LEDGER and CERTS rules do.DefaultMutators.allisCertsMutatorand
PlutusScriptsTransactionMutator. A mutator set that listsCertsMutatorskips the deprecated
certificate mutators.- JavaScript
Emulator.createthrows aTypeErrorfor every invalid option, where a badclock,
pool registration or credential type threw anIllegalArgumentException. It also rejects a
datumsentry whosehashis not the blake2b-256 hash of itsdatumbytes. - Dependency updates: cardano-client-lib 0.7.3, Yaci DevKit 0.4.6, BouncyCastle 1.86,
jsoniter-scala 2.41.2, sttp client4 4.0.27, fs2 3.14.0 with cats-effect 3.7.1, slf4j 2.0.20,
jackson-databind 2.22.3, magnolia 1.3.24. - The UPLC parser rejects a
valueconstant that is not in canonical form: currency symbols or
token names out of strictly ascending order, a zero quantity, or an empty token list. It used to
sort, merge and drop them. Plutus 1.66 made the same change. - The flat decoder rejects a
valueconstant that Plutus rejects: keys out of strictly ascending
order, an empty token list, a zero quantity, a key over 32 bytes or a quantity outside the
signed 128-bit range. It used to repair the value, so Scalus could accept a script that the
node fails to deserialise. - The Plutus conformance tests run on the Plutus 1.70.0.0 corpus, the version cardano-node 11.1.3
uses: 1009 UPLC evaluation cases, none skipped on JVM, JavaScript and Native.
Deprecated
- The
scalus.builtinaliases and thescalus.preludepackage-object forwarders: use
scalus.uplc.builtinandscalus.cardano.onchain.plutus.prelude. Value.lovelaceAmount: usegetLovelace, which lowers to thelookupCoinbuiltin at PV11.
lovelaceAmountwalks theDatamap, and on a value without lovelace it returns another
token's quantity.VarUInt: usePointer.toBytesandPointer.parseFrom.VarUIntnow writes the most
significant 7-bit group first, so bytes stored with the oldVarUIntread back as other
numbers.StakeCertificatesMutator,StakePoolCertificatesMutatorandVotingCertificatesMutator: use
CertsMutator.- The
DelegationStatemapsrewards,deposits,stakePoolsanddreps, and its 4-map
applyand constructor: useaccounts. - The 4-argument
UtxoEnvand 6-argumentStakeCertificatesExceptionapplyand constructors,
EmulatorState.datumsand the 9-argumentImmutableEmulator.apply. - The bloxbean
StakeStateResolver.resolveForTx(tx, epoch, defaultDeposit): use
resolveForTx(tx, slot). It now registers an account whose cached state has no deposit with a
deposit of 0.
Removed
API deprecated in 1.2.0 or earlier is removed. Two JavaScript members stay deprecated: the
new Emulator(...) constructor, because Scala.js needs a public constructor on an exported class,
and the Scalus exports (evaluateScript, evalPlutusScripts and others), which are deprecated
only in their documentation.
- V3
TxInfo.findOwn*is removed. UsefindInput,findInputOrFail,findDatum,
findOutputsByScriptHash,findInputsByCredentialandfindOutputsByCredential. For
findOwnInputs(pred)andfindOwnOutputs(pred), useinputs.filter(pred)and
outputs.filter(pred). - V3
TxInfo.getValidityStartTimeis removed. UsevalidFromOrFail(message), which fails on
an unbounded range instead of returning0. - V3
Utils.getAdaFromOutputsandUtils.getAdaFromInputsare removed. Use
TxInfo.valuePaidTo(addr)andTxInfo.valueSpentFrom(addr), which sum the wholeValue; add
.getLovelacefor ADA only. List.singleandPairList.singleare removed. UseList.singletonand
PairList.singleton.- The
SIRextensionstoLoweredValueandlowerToUplcare removed. UsetoUplc, or
SirToUplcV3Lowering.fromOptions(sir, options).toLoweredValue()to inspect the lowering. - The
Ordering[ExUnits]given is removed. Execution units are only partially ordered. Use
fitsWithinorexceedsfor budget checks. To sort, give an explicit ordering, for example
sortBy(e => (e.memory, e.steps)). scalus.compiler.sir.BooleanOptimizerandscalus.compiler.sir.StaticArgumentTransformation
are removed. Use the passes inscalus.compiler.sir.transform.- JavaScript
Emulator.getUtxosForAddressandEmulator.getAllUtxosare removed. Use
getUtxos({ address })andgetUtxos(), which returnUtxohandles instead of CBOR maps. - JavaScript
Emulator.withState,Emulator.withAddressesand theEmulatorInitialStatetype
are removed. UseEmulator.create(info, options); seed funds withutxos, and stake, pool,
DRep and datum state with the options of the same names. G1Element.apply(P1),G2Element.apply(P2)andMLResult.apply(PT)are removed, without
a deprecation first: the blst-java types are gone with that dependency.G1Element,G2Element
andMLResultnow hold the blst point asArray[Byte]. Build points with
G1Element(ByteString)or the builtins.
Fixed
- The Emulator's value-conservation check added amounts as
Longs, so a sum past
Long.MaxValuewrapped around, and a transaction could create ADA or tokens from nothing. Both
sides of the check, the deposits and refunds, and the collateral are now summed exactly, as the
ledger sums them.Coin,MultiAssetandValuearithmetic,TxBalance.consumed,
TxBalance.producedand theCertificatedeposit and refund totals throwArithmeticException
on overflow instead of wrapping. - Ed25519, secp256k1 and BLS12-381 give the Cardano node's verdicts on every platform. On the JVM
they run the C libraries that cardano-node 11.1.3 links (libsodium IOG fork, libsecp256k1
v0.3.2, blst v0.3.15) throughscalus-crypto-jni0.1.0, called as cardano-crypto-class calls
them; JavaScript Ed25519 applies libsodium's rules. Before, the JVM and JavaScript accepted some
Ed25519 signatures that the node rejects. - Builtins match Plutus:
verifyEcdsaSecp256k1Signaturereturns False for r = 0 or s = 0, and
JavaScript reads the signature as compact only;bls12_381_G1_hashToGroupand
bls12_381_G2_hashToGroupgive the node's point for a DST byte of 0x80 or more on the JVM;
bls12_381_G1_scalarMulandbls12_381_G2_scalarMulreject a scalar outside the signed
4096-bit range in semantics variants D and E;sliceByteStringreads its start and length as
64-bit integers, where a start of 2^32 returned the whole bytestring. Scala Native adds a
BLS12-381 point to itself correctly and signs with extended (BIP32-Ed25519) keys; JavaScript
pairs the point at infinity as blst does. MachineParams.fromCostModelscostsverifyEd25519Signatureby the signature in semantics
variant A (PlutusV1 and V2 before protocol version 9), as Plutus does. It costed by the message,
so a script that verifies a long message exceeded a budget that the node accepts.- Pointer addresses wrote and read their variable-length numbers least significant 7-bit group
first, where the ledger and CIP-19 put the most significant group first, so every slot or index
of 128 or more came out wrong. A pointer address that Scalus built with such a slot or index is
a different address from the one now built. Pointer addresses now decode as a node decodes
them at the transaction's protocol version:Transaction.fromCborreads its outputs at its
version, andAddress.fromBytes,fromBech32,Pointer.fromBytesand the CBOR decoders read
as at version 9. Before version 9, a pointer that does not fit reads asPointer(Slot(0), 0, 0),
as the node keeps it. An address with bytes after its pointer now fails to decode, and
constructing aPointerthat does not fit the ledger's 32-bit slot and 16-bit indexes
(Pointer.MaxSlot,Pointer.MaxIndex) throwsIllegalArgumentException.
BlockfrostProviderand the bloxbeanInteropread UTxO addresses leniently, as a node holds
them.UtxoCellBuilder.toOffchainAddressnow builds a pointer address for aStakingPtr, where
it dropped the delegation. - The Emulator now rejects these transactions, as a node does. A test that submits one now
fails:- a delegation to an unregistered pool or DRep; register it first, or seed it with
poolRegistrations; - a
currentTreasuryValueother than the treasury (TreasuryValueMismatch); - from protocol version 10, a withdrawal from a key-hash reward account with no DRep delegation
(WithdrawalsNotDelegatedToDRep); delegate the votes first, for example toAlwaysAbstain.
Script-hash accounts are exempt; - reference scripts that total more than 200 KiB (
TxRefScriptsSizeTooBig); - a proposal whose deposit differs from
govActionDeposit(ProposalDepositIncorrect, from the
new defaultProposalDepositValidator).
- a delegation to an unregistered pool or DRep; register it first, or seed it with
- The Emulator accepted the same reward withdrawn twice. Withdrawals now subtract from the balance
and keep the account registered. A test that expected a drained account to be deregistered
now finds it registered with balance 0. - The Emulator applied the certificates of a transaction by kind, not in their order, so after
[UnregDRepCert(X), RegDRepCert(X), VoteDelegCert(c, X)],chad no DRep. A DRep
deregistration now also clears the vote delegations to that DRep. - A pool update could not be built or applied. A re-registration of a registered pool was charged
a second pool deposit and rejected withValueNotConserved; it now pays none, and two
registrations of one new pool in a transaction pay one deposit.TxBuildernow expects the
signatures of a pool's operator and owners for a registration or update, and of its operator for
a retirement, so the fee covers them. The fee of such a transaction rises by about 100 bytes
for each distinct operator or owner key.Context.finalizeContextbalances against its
certState, as it validates. - A stake deregistration missed its refund.
TxBuilder.deregisterStake(stakeAddress)emitted a
certificate without one, and the balancer refunded nothing for a legacyUnregCert(cred, None)
unless theCertStateheld the account; both were rejected withValueNotConserved. They now
refund the currentstakeAddressDeposit. - JavaScript
balancer.balanceTxpriced only the signatures of key inputs, collateral and
required signers. A withdrawal, a certificate or a vote that a key must sign now adds its
signature too, so the fee covers it. - An inline datum or a native script in a non-minimal encoding, such as
d879811a0000000aor
82041a0000000a, was re-encoded, so its hash and the bytes of the output or witness set
differed from the chain. They now match in the decoder, the Emulator,BlockfrostProvider, the
bloxbeanInterop, the testkitPreconfigurationand the JavaScriptUtxoAPI, and
getDatum(h)returns bytes whose hash ish.contentEqualsof a datum hash and an inline
datum now compares against the original bytes. - A native script encoded with indefinite-length arrays failed to decode, though the ledger
accepts it. A definite-length array of the wrong size, such as aSignatureof size 3, decoded;
it is now rejected, as by the ledger. - A transaction with a repeat in a set or map decoded, keeping one of the repeats, where a node
fails to deserialize it. From protocol version 9 Scalus rejects a repeated input, collateral
input, reference input, required signer, withdrawal account, voter, vote or proposal, and in a
governance action a repeated committee member, treasury withdrawal account, protocol-parameter
key or cost-model language. The witness set's key and bootstrap witnesses still merge repeats,
as the ledger does until protocol version 12. - Spending and referencing the same UTxO:
TxBuilderrefused it withInputAlreadyExists, which
the ledger accepts from protocol version 11, and the Emulator rejected it at protocol version 8,
where the ledger accepts it. Only versions 9 and 10 reject it now. The min fee counted the
reference script of such a UTxO twice; it now counts it once, as on chain. - A V3
UpdateCommitteegovernance action listed the removed committee members in ScalaSet
order and passed the quorum as encoded, such as 6/10.LedgerToPlutusTranslationnow lists
scripts first, then by hash, and gives the quorum in lowest terms, 3/5, as the node does. - A phase-2-invalid transaction with an invalid certificate or withdrawal was rejected. It is now
accepted, and only its collateral is taken. snapshot(),ImmutableEmulator.fromEmulatorandtoEmulatordropped the fees and donations..toDataon an enum case written directly, such asAction.Timeout.toData, failed to compile
with "Unsupported conversion from ProdDataList to DataConstr" (#377).- An inline method of a trait, such as
validate, called next to a computed argument or in a
block failed to compile with "Module ... is not found".
Documentation
- Documentation: https://scalus.org/docs
- Getting started: https://scalus.org/docs/get-started