Skip to content

v1.5.0

Latest

Choose a tag to compare

@nau nau released this 04 Nov 18:18
· 24 commits to master since this release

Release Notes

Scalus 1.5 turns the Emulator into a small Cardano node. Your tests get a mempool, real blocks, forks, rollbacks and epoch boundaries with rewards and governance, and the Emulator reports a failure by the Haskell ledger's own error name. All of it works from Scala, Java and JavaScript, with no devnet to start.

Highlights

  • Test forks and rollbacks in a unit test, with no devnet. The Emulator now works as a small Cardano node: a mempool, real blocks, forks and rollback. dApp, wallet and indexer authors can check what their code does with a pending, dropped or rolled-back transaction, from Scala, Java or JavaScript.
  • Test staking, rewards and governance across epochs. The Emulator runs the ledger's epoch boundary: stake snapshots, rewards, pool retirement, and the expiry, ratification and enactment of proposals. It is tested against 533 epoch transitions of the Haskell ledger.
  • Assert the node's own error names. Emulator.ledgerFailures(tx) lists every ledger failure of a transaction by its cardano-ledger name, such as MissingRedeemers. A test can check that a bad transaction fails for the right reason, or port a Haskell ledger test as it is.
  • Run one compiled validator on many transactions. The JIT compiles a script once, and applyTo runs it on each script context, with the CEK machine's result and budget. This suits indexers, chain replays and fuzzers.
  • Write a validator with two imports. scalus.cardano.onchain.plutus.v3.* and prelude.* now also bring OutputDatum, the builtin data types and Compile.
  • Get the node's verdict on unusual bytes. A deeply nested datum decodes, and a script with bytes after its program, or a string that is not UTF-8, fails as on chain.
  • Run on the fastest Plutus VM. On the Plutus VM Benchmark (15 evaluators, 89 scripts, every result checked against the node's plutus-core), the Scalus CEK machine ranks first on decode + eval: 1.42× faster than Plutigo (Go), 1.58× faster than amaru-uplc (Rust) and 2.65× faster than plutus-core (Haskell).

Upgrading

  • The Emulator runs the epoch rules at each epoch boundary, with no off switch. A test that crosses two or more boundaries and asserts an absolute treasury, reserves or reward balance changes.
  • Context.testMainnet(), UtxoEnv.testMainnet() and the JavaScript Emulator.create without treasury hold the mainnet treasury, not 0. Use emulator.treasury as currentTreasuryValue.
  • The Emulator rejects a PlutusV3 transaction that spends and references one UTxO, and the proposals and votes that the ledger rejects, such as a vote by an unregistered DRep.
  • ProtocolParams.monetaryExpansion and treasuryCut are UnitInterval, and poolPledgeInfluence is NonNegativeInterval; read them with .toDouble. Recompile a 1.4 jar that calls ProtocolParams.copy with named arguments: it fails with NoSuchMethodError.
  • GovState is a case class, not an Array[Dom.Element]. Use GovState.empty and ==.
  • A positional pattern on PoolsState, VotingState, EmulatorState, EmulatorInitialState or the testkit ImmutableEmulator no longer compiles. Use the field accessors.
  • Block.hash is the header hash. The old value is block.header.headerBody.blockBodyHash.
  • checkTransaction reports NotFound for a transaction of an imported applied-tx log.
  • Flat decoding (decodeFlat, fromCbor, PlutusScript.program) rejects bytes after the filler. Read a program that other bytes follow with decodeFlatWithRemainingBytes.
  • SubmitError.rule is PassedUnexpectedly or CollectErrors where it was IllegalArgument.
  • UplcEvaluator has a new abstract method, applyTo. An implementation outside Scalus must add it.
  • scalus-plugin is no longer published for Scala 3.3.7. Use Scala 3.3.8 or 3.3.9.
  • The API deprecated in 1.3.0 is removed. See Removed.

Added

  • Emulator chain: ChainConfig.manual keeps submitted transactions in a mempool until produceBlock(); ChainConfig.instant, the default, forges one block per submit. txStatus(hash) reports Pending, Confirmed, Dropped, RolledBack or NotFound.
  • Forks and rollback: produceBlock(on, txs) forges a block anywhere in the block tree, chain selection takes the longest chain, and rollback(n) removes blocks. ChainConfig.orphanTxs sends orphaned transactions back to the mempool or drops them.
  • findUtxos(query, view) reads the tip, the mempool or a ChainView.Stable(depth) block. EmulatorBase.diff(from, to) gives the ChainEvents between two tips (experimental).
  • StreamingEmulator streams the Emulator's real blocks, forks and rollbacks included.
  • Epoch boundary: stake snapshots, the reward update and its payout, pool reaping, proposal expiry, ratification and enactment, and DRep expiry with dormant epochs. ChainConfig.withPoolPerformance, withActiveSlotsCoeff and withBlockIssuer set how pools earn.
  • Governance: the Conway GOV rule, a GovState with proposals, votes, roots, the committee and the constitution, and committee hot keys and resignations. EmulatorInitialState takes committee, committeeHotKeys, constitution and reserves.
  • Emulator.ledgerState returns the whole ledger state in the node's NewEpochState shape. snapshots, pendingRewards, pendingRatify, govState, reserves and currentEpoch read its parts.
  • Emulator.ledgerFailures(tx) and TransactionException.ledgerNames give every ledger failure in the ledger's order, with the ledger's names.
  • Java: EmulatorJavaApi gains tryProduceBlock, tryRollback, getMempoolTxs, getDiff, getLedgerState and getLedgerFailureNames. The new parameterless enums are Java enums.
  • JavaScript: Emulator.create takes the chain, governance and reward options. The emulator gains produceBlock, rollback, getTxStatus, awaitTx, getLedgerState and ledgerFailures.
  • AI agents in TypeScript projects: scalus.org serves llms-ts.txt, the npm README and scalus.d.ts in one file. The scalus Claude Code plugin (1.9.0) adds the scalus:typescript skill and loads its routing table in any project whose package.json depends on scalus.
  • ProtocolParamUpdate.applyTo(params) applies a parameter update as the ledger enacts it.
  • BlockHeader.hash and Block.bodyHash(...).
  • JIT: UplcEvaluator.applyTo(args*) evaluates a compiled script applied to constant arguments.
  • scalus.cardano.onchain.plutus.v3 re-exports OutputDatum, the builtin data types and Compile.
  • scalus-verification (experimental): quantify over enums, lists, maps and Data encodings (forAllData, forAllDataLimited, DataEncoding), and over types that contain themselves. Budget.Within(n) proves a statement on the runs that end within n steps.
  • Examples: the USDCx minting policy, recreated from its Plinth script (446 bytes against 483), and verified guarantees of the one-shot minting policy.

Changed

  • StreamingEmulator keeps up to securityParam blocks and delivers rollbacks. Its default rollbackHorizon is Some(0), and newEmptyBlock() forges through produceBlock().
  • A script context error starts with the ledger's ContextError name, for example InlineDatumsNotSupported: ....
  • The _3 library artifacts are built with Scala 3.3.9.
  • scalus.js grows by about 83 KB for the chain features.
  • Dependency updates: jsoniter-scala 2.41.6, jackson-databind 2.22.4, Yaci DevKit 0.4.7.

Deprecated

  • RewardsCalculation and StakeDistribution: use snapshots and pendingRewards.
  • The EmulatorInitialState constructor, apply and copy: start from EmulatorInitialState.empty and use the with methods, or the Java Builder.
  • clearAppliedTxs, StreamingEmulator.syntheticBlockHash, and the initialAppliedTxLog and appliedTxLog parameters (in the scaladoc only). Use snapshot().
  • SIRHashCodeInRec: it never worked.

Removed

The API deprecated in 1.3.0. The JavaScript new Emulator(...) constructor stays deprecated.

  • The scalus.prelude package-object forwarders (===, asScalus, pow, log2 and the others): use the same names in scalus.cardano.onchain.plutus.prelude.
  • The scalus.builtin aliases G1Element, G2Element, MLResult and the JavaScript NodeJsPlatformSpecific: use scalus.uplc.builtin and scalus.uplc.builtin.bls12_381.
  • Value.lovelaceAmount: use getLovelace.
  • CaseDataBranchError: use CaseDataNonConstrError or CaseIndexOutOfBounds.
  • TallyingBudgetSpenderLogger.getLogsWithBudget: use traces, or getLogs for the text.
  • VarUInt: use Pointer.toBytes and Pointer.parseFrom.
  • The DelegationState maps rewards, deposits, stakePools and dreps, and its 4-map constructor and apply: use accounts.
  • The 4-argument UtxoEnv and the 6-argument StakeCertificatesException constructors and apply.
  • StakeCertificatesMutator, StakePoolCertificatesMutator and VotingCertificatesMutator: use CertsMutator.
  • EmulatorState.datums and the testkit ImmutableEmulator.apply with decoded datums: use binaryDatums.
  • bloxbean StakeStateResolver.resolveForTx(tx, epoch, defaultDeposit): use resolveForTx(tx, slot).

Fixed

  • The Emulator checks what the ledger checks and it missed: a repeated VRF key at protocol version 11, a pool metadata hash over 32 bytes, the Byron attributes of the collateral return, the output size as the ledger writes it, Byron attributesSize, and the reference-script limits of a block (1 MiB) and of the mempool (200 KiB).
  • The Emulator keeps the ledger's bookkeeping: State.deposited, DRepState.delegates, DRep expiry and the committee state.
  • Moving the slot back before a boundary that no block crossed restores the donations it moved.
  • Data CBOR decoding, encoding and serialiseData have no nesting limit, as in Plutus. Metadata and native scripts stop at 1000 levels with an error, not a stack overflow.
  • The flat decoder reads a data constant as its first CBOR item and rejects a string that is not UTF-8, as Plutus does. Its errors show the bytes around the failure.
  • PlutusScriptEvaluator names a script it cannot decode, and the Emulator rejects it in phase 1.
  • The compiler accepts a case class with a one-field wrapper of a List, such as Varargs, and Validator objects under -Yexplicit-nulls.

Get it

// sbt
addSbtPlugin("org.scalus" % "scalus-sbt-plugin" % "1.5.0")
libraryDependencies += "org.scalus" %% "scalus" % "1.5.0"
npm install scalus@1.5.0

Documentation