Skip to content

v1.4.0

Latest

Choose a tag to compare

@nau nau released this 20 Oct 18:14
· 63 commits to master since this release

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.create takes protocolParams to override
    fields of the preset, treasury, clock: "wall" to follow Date.now(), and pool registrations
    by { poolId } alone. addRewards pays lovelace into a registered reward account, getAccount
    reads its balance, deposit, pool and DRep, getTreasury reads the treasury, and
    getStakeDistribution entries carry rewardAddress. Utxo.script and PlainUtxo.script return
    the reference script as { type, script }, the shape withScriptRef takes; withScriptRef and
    scriptHash also take "PlutusV4".
  • Emulator.addDatum adds a datum to the store that getDatum reads, so a UTxO added with
    addUtxo that holds only a datum hash has its datum found. It keeps the given bytes and returns
    their hash. ImmutableEmulator.withDatum is the immutable form. The Scala Emulator also gets
    addRewards and treasury. Donations move into the treasury at the next epoch boundary.
  • JavaScript TxEvaluator evaluates many transactions under one set of parameters and prepares the
    cost models once. ScriptCache keeps decoded scripts across evaluations: pass it to
    TxEvaluator as scriptCache, or on the JVM to the new PlutusScriptEvaluator overload. On
    recorded Midgard requests the cache saves 17–25% on the JVM.
  • scalus-verification and scalus-verification-scalatest: state properties of Scalus functions
    in Scala (Props: forAll, exists, call, denotes) and prove them against the compiled
    UPLC with the UplcBlaster tactic (Lean 4 and IOG's Blaster). A counterexample is replayed on
    the CEK machine. LeanProofsSuite runs the proofs as ScalaTest tests. Experimental.
  • Spec.expects, Spec.ensures and ensuring state 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, expects and ensuring throw SpecificationError.
  • The scalus Claude Code plugin has a session-start hook: in a Scalus project it loads the
    using-scalus routing 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 with IssueCertificate still pays it, as for a new pool.
    TransactionBuilder.calculateChangeValue, balanceFeeAndChange,
    balanceFeeAndChangeWithTokens, Context.balance and Context.balanceContext have overloads
    that take a CertState, and Context.validate, validateContext and finalizeContext
    overloads that take the treasury. The old validate forms validate against a treasury of 0,
    so they reject a transaction that states a non-zero currentTreasuryValue.

    TxBalance.produced(tx, certState, params) and
    Certificate.shelleyTotalDeposits(params, isRegisteredPool, certificates) charge no deposit for
    a pool re-registration.
  • DatumOption.Inline.fromCbor, DatumOption.Inline.fromBinaryData and Script.Native.fromCbor
    build values that keep their bytes. Script.fromCbor decodes 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.fromBytesLenient and fromBech32Lenient read an address
    that a node already holds, as the ledger's decompactAddr does.
  • 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 throw IllegalStateException; 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.VBuiltin keeps the arguments of a partly applied builtin instead of a copied
    BuiltinRuntime and a closure that rebuilds its term. Code that builds or matches VBuiltin
    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, unListData and unMapData build their
    result in one pass.
  • Breaking: DatumOption is a sealed trait instead of an enum, and Inline holds a
    KeepRaw[Data] with the original CBOR. Inline(data), Hash(hash) and case Inline(d) with
    d: Data still compile; ordinal, fromOrdinal and Inline.copy are gone. Two inline
    datums with the same Data and different bytes are no longer equal
    ; use contentEquals to
    compare by Data. The EmulatorState field datums and the testkit ImmutableEmulator field
    datums are now binaryDatums, a Map[DataHash, KeepRaw[Data]]; datums remains as a decoded
    view. copy(datums = ...) and a named datums argument of ImmutableEmulator no longer
    compile.
  • Breaking: Script.Native holds a KeepRaw[Timelock] with the original CBOR, its
    constructor is private, and copy is gone. Native(timelock), case Native(t) and
    native.script still compile. Two native scripts with the same timelock and different bytes
    are no longer equal.
    Replace Cbor.decode(bytes).to[Script].value with
    Script.fromCbor(bytes)
    : Codec[Script] is now an Encoder and a Decoder that needs an
    OriginalCborByteArray. Encode a native script in a non-canonical encoding, or a value that
    holds one, with scalus.serialization.cbor.Cbor: borer's validating Cbor.encode rejects its
    bytes.
  • Breaking: DelegationState stores one ConwayAccountState per account in accounts. The
    rewards, deposits, stakePools and dreps maps 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, and copy with the old field names and a pattern match on 4 fields no
    longer compile
    ; update accounts instead.
  • Breaking: UtxoEnv has a fifth field, treasury. Under the deprecated 4-argument apply
    and constructor, which use Coin.zero, a transaction that states a non-zero
    currentTreasuryValue fails with TreasuryValueMismatch.
    A positional pattern match on
    UtxoEnv needs the fifth field, and a 1.3 binary call of copy no longer links.
  • Breaking: StakeCertificatesException has 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
    copy no longer links.
  • Breaking: the V3 on-chain Constitution is a case class,
    Constitution(constitutionScript: Option[ScriptHash]), instead of an alias of
    Option[ScriptHash]. A node wraps the guardrails script hash in the Constitution
    constructor, so a script read a different proposal than on chain; LedgerToPlutusTranslation
    and the bloxbean Interop now encode it as the node does. Code that builds or matches a
    GovernanceAction.NewConstitution has to wrap the script hash
    , and a file that imports both
    scalus.cardano.ledger.* and the V3 on-chain types must qualify one Constitution.
  • CertsMutator applies the withdrawals, then every certificate in one pass, in transaction
    order, as the Conway LEDGER and CERTS rules do. DefaultMutators.all is CertsMutator and
    PlutusScriptsTransactionMutator. A mutator set that lists CertsMutator skips the deprecated
    certificate mutators.
  • JavaScript Emulator.create throws a TypeError for every invalid option, where a bad clock,
    pool registration or credential type threw an IllegalArgumentException. It also rejects a
    datums entry whose hash is not the blake2b-256 hash of its datum bytes.
  • 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 value constant 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 value constant 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.builtin aliases and the scalus.prelude package-object forwarders: use
    scalus.uplc.builtin and scalus.cardano.onchain.plutus.prelude.
  • Value.lovelaceAmount: use getLovelace, which lowers to the lookupCoin builtin at PV11.
    lovelaceAmount walks the Data map, and on a value without lovelace it returns another
    token's quantity.
  • VarUInt: use Pointer.toBytes and Pointer.parseFrom. VarUInt now writes the most
    significant 7-bit group first, so bytes stored with the old VarUInt read back as other
    numbers.
  • StakeCertificatesMutator, StakePoolCertificatesMutator and VotingCertificatesMutator: use
    CertsMutator.
  • The DelegationState maps rewards, deposits, stakePools and dreps, and its 4-map
    apply and constructor: use accounts.
  • The 4-argument UtxoEnv and 6-argument StakeCertificatesException apply and constructors,
    EmulatorState.datums and the 9-argument ImmutableEmulator.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. Use findInput, findInputOrFail, findDatum,
    findOutputsByScriptHash, findInputsByCredential and findOutputsByCredential. For
    findOwnInputs(pred) and findOwnOutputs(pred), use inputs.filter(pred) and
    outputs.filter(pred).
  • V3 TxInfo.getValidityStartTime is removed. Use validFromOrFail(message), which fails on
    an unbounded range instead of returning 0.
  • V3 Utils.getAdaFromOutputs and Utils.getAdaFromInputs are removed. Use
    TxInfo.valuePaidTo(addr) and TxInfo.valueSpentFrom(addr), which sum the whole Value; add
    .getLovelace for ADA only.
  • List.single and PairList.single are removed. Use List.singleton and
    PairList.singleton.
  • The SIR extensions toLoweredValue and lowerToUplc are removed. Use toUplc, or
    SirToUplcV3Lowering.fromOptions(sir, options).toLoweredValue() to inspect the lowering.
  • The Ordering[ExUnits] given is removed. Execution units are only partially ordered. Use
    fitsWithin or exceeds for budget checks. To sort, give an explicit ordering, for example
    sortBy(e => (e.memory, e.steps)).
  • scalus.compiler.sir.BooleanOptimizer and scalus.compiler.sir.StaticArgumentTransformation
    are removed.
    Use the passes in scalus.compiler.sir.transform.
  • JavaScript Emulator.getUtxosForAddress and Emulator.getAllUtxos are removed. Use
    getUtxos({ address }) and getUtxos(), which return Utxo handles instead of CBOR maps.
  • JavaScript Emulator.withState, Emulator.withAddresses and the EmulatorInitialState type
    are removed.
    Use Emulator.create(info, options); seed funds with utxos, and stake, pool,
    DRep and datum state with the options of the same names.
  • G1Element.apply(P1), G2Element.apply(P2) and MLResult.apply(PT) are removed, without
    a deprecation first: the blst-java types are gone with that dependency. G1Element, G2Element
    and MLResult now hold the blst point as Array[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.MaxValue wrapped 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, MultiAsset and Value arithmetic, TxBalance.consumed,
    TxBalance.produced and the Certificate deposit and refund totals throw ArithmeticException
    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) through scalus-crypto-jni 0.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: verifyEcdsaSecp256k1Signature returns False for r = 0 or s = 0, and
    JavaScript reads the signature as compact only; bls12_381_G1_hashToGroup and
    bls12_381_G2_hashToGroup give the node's point for a DST byte of 0x80 or more on the JVM;
    bls12_381_G1_scalarMul and bls12_381_G2_scalarMul reject a scalar outside the signed
    4096-bit range in semantics variants D and E; sliceByteString reads 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.fromCostModels costs verifyEd25519Signature by 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.fromCbor reads its outputs at its
    version, and Address.fromBytes, fromBech32, Pointer.fromBytes and the CBOR decoders read
    as at version 9. Before version 9, a pointer that does not fit reads as Pointer(Slot(0), 0, 0),
    as the node keeps it. An address with bytes after its pointer now fails to decode, and
    constructing a Pointer that does not fit the ledger's 32-bit slot and 16-bit indexes
    (Pointer.MaxSlot, Pointer.MaxIndex) throws IllegalArgumentException.

    BlockfrostProvider and the bloxbean Interop read UTxO addresses leniently, as a node holds
    them. UtxoCellBuilder.toOffchainAddress now builds a pointer address for a StakingPtr, 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 currentTreasuryValue other 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 to AlwaysAbstain.
      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 default ProposalDepositValidator).
  • 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)], c had 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 with ValueNotConserved; it now pays none, and two
    registrations of one new pool in a transaction pay one deposit. TxBuilder now 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.finalizeContext balances 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 legacy UnregCert(cred, None)
    unless the CertState held the account; both were rejected with ValueNotConserved. They now
    refund the current stakeAddressDeposit.
  • JavaScript balancer.balanceTx priced 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 d879811a0000000a or
    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
    bloxbean Interop, the testkit Preconfiguration and the JavaScript Utxo API, and
    getDatum(h) returns bytes whose hash is h. contentEquals of 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 a Signature of 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: TxBuilder refused it with InputAlreadyExists, 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 UpdateCommittee governance action listed the removed committee members in Scala Set
    order and passed the quorum as encoded, such as 6/10. LedgerToPlutusTranslation now 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.fromEmulator and toEmulator dropped the fees and donations.
  • .toData on an enum case written directly, such as Action.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