diff --git a/.changeset/data-default-plutus-layout.md b/.changeset/data-default-plutus-layout.md
new file mode 100644
index 00000000..de2f7f79
--- /dev/null
+++ b/.changeset/data-default-plutus-layout.md
@@ -0,0 +1,24 @@
+---
+"@evolution-sdk/evolution": minor
+---
+
+Plutus data is now encoded by default with `CBOR.PLUTUS_DATA_OPTIONS`, the layout the node writes: non-empty lists and constructor fields indefinite, maps definite. Before, the default was `CBOR.CML_DATA_DEFAULT_OPTIONS`, which also wrote non-empty maps indefinite. `Data.DEFAULT_CBOR_OPTIONS` and `CBOR.TX_DEFAULT_OPTIONS.plutusData` are now `CBOR.PLUTUS_DATA_OPTIONS`, and `CBOR.toTxCodecOptions` maps `CBOR.CML_DEFAULT_OPTIONS` to it.
+
+Only data that holds a non-empty map changes, and only in its map headers. `Data.map([[1n, 2n]])` was `bf0102ff` and is now `a10102`. For such data these change:
+
+- datum hashes from `Data.toDatumHash`, which now match the hashes the node computes
+- inline datums, witness datums and redeemer data in new transactions
+- script data hashes of transactions that carry such datums or redeemers
+
+Applied script hashes do not change, since `UPLC.applyParamsToScript` already used `CBOR.PLUTUS_DATA_OPTIONS`. Decoding is unchanged, and a decoded transaction keeps the original bytes of its datums and redeemers.
+
+To keep the old bytes, pass the old options:
+
+```ts
+Data.toDatumHash(datum, CBOR.CML_DATA_DEFAULT_OPTIONS)
+Transaction.toCBORHex(tx, { ledger: CBOR.CML_DEFAULT_OPTIONS, plutusData: CBOR.CML_DATA_DEFAULT_OPTIONS })
+```
+
+The transaction options cover witness datums and redeemer data. An inline datum in a new output is written with the `Data` default under every option set, as before, so it takes the new layout.
+
+`CBOR.CML_DATA_DEFAULT_OPTIONS` is deprecated in favor of `CBOR.PLUTUS_DATA_OPTIONS`. It keeps its bytes, but matches no tool exactly, so use it only to reproduce bytes and datum hashes written before this release.
diff --git a/.changeset/plutus-data-tx-options.md b/.changeset/plutus-data-tx-options.md
index 44f571f6..3b799d9c 100644
--- a/.changeset/plutus-data-tx-options.md
+++ b/.changeset/plutus-data-tx-options.md
@@ -6,7 +6,7 @@ Witness datums and redeemer data are now written with their own Plutus data opti
`CBOR.TxCodecOptions` holds two sets of options. `ledger` is for ledger structures: the body, the witness set, and the containers that hold datums and redeemers. `plutusData` is for Plutus data items: witness datums and redeemer data. Two presets come with it:
-- `CBOR.TX_DEFAULT_OPTIONS`: `ledger` is `CBOR.CML_DEFAULT_OPTIONS` and `plutusData` is `CBOR.CML_DATA_DEFAULT_OPTIONS`, the `Data` default.
+- `CBOR.TX_DEFAULT_OPTIONS`: `ledger` is `CBOR.CML_DEFAULT_OPTIONS` and `plutusData` is `CBOR.PLUTUS_DATA_OPTIONS`, the `Data` default.
- `CBOR.TX_CANONICAL_OPTIONS`: `CBOR.CANONICAL_OPTIONS` for both.
The encoders and schemas of `Transaction`, `TransactionWitnessSet`, `Redeemers` and `Redeemer`, and `Redeemers.toScriptDataHash`, take `CBOR.TxCodecOptions` and default to `CBOR.TX_DEFAULT_OPTIONS`:
@@ -17,6 +17,6 @@ Transaction.toCBORHex(tx, CBOR.TX_CANONICAL_OPTIONS)
Transaction.toCBORHex(tx, { ledger: CBOR.CML_DEFAULT_OPTIONS, plutusData: CBOR.PLUTUS_DATA_OPTIONS })
```
-Plain `CBOR.CodecOptions` are still accepted, read as `CBOR.toTxCodecOptions` reads them: the options serve as both, except that `CBOR.CML_DEFAULT_OPTIONS` writes Plutus data with `CBOR.CML_DATA_DEFAULT_OPTIONS`, as passing no options does. Decoded datums and redeemers keep their bytes, and a datum or redeemer added to a decoded transaction is written with the default Plutus data options.
+Plain `CBOR.CodecOptions` are still accepted, read as `CBOR.toTxCodecOptions` reads them: the options serve as both, except that `CBOR.CML_DEFAULT_OPTIONS` writes Plutus data with `CBOR.PLUTUS_DATA_OPTIONS`, as passing no options does. Decoded datums and redeemers keep their bytes, and a datum or redeemer added to a decoded transaction is written with the default Plutus data options.
-Script transactions whose redeemer data or witness datums hold a non-empty list, map or constructor fields change bytes. Simple data such as `Data.constr(0n, [])` or an integer keeps its bytes. The builder wrote redeemer data definite, for example `d87982a101028103`, and now writes it in the data default with indefinite lists, maps and constructor fields: `d8799fbf0102ff9f03ffff`. To keep the old redeemer layout, pass `{ ledger: CBOR.CML_DEFAULT_OPTIONS, plutusData: CBOR.CML_DATA_DEFINITE_OPTIONS }` to the encoders and to `Redeemers.toScriptDataHash`.
+Script transactions whose redeemer data or witness datums hold a non-empty list, map or constructor fields change bytes. Simple data such as `Data.constr(0n, [])` or an integer keeps its bytes. The builder wrote redeemer data definite, for example `d87982a101028103`, and now writes it in the data default with indefinite lists and constructor fields and a definite map: `d8799fa101029f03ffff`. To keep the old redeemer layout, pass `{ ledger: CBOR.CML_DEFAULT_OPTIONS, plutusData: CBOR.CML_DATA_DEFINITE_OPTIONS }` to the encoders and to `Redeemers.toScriptDataHash`.
diff --git a/docs/content/docs/encoding/cbor.mdx b/docs/content/docs/encoding/cbor.mdx
index 6026a71b..7a9bc68a 100644
--- a/docs/content/docs/encoding/cbor.mdx
+++ b/docs/content/docs/encoding/cbor.mdx
@@ -123,8 +123,8 @@ The plain `toCBORHex`/`toCBORBytes` APIs accept a `CodecOptions` argument that c
|--------|----------|
| `CML_DEFAULT_OPTIONS` *(default)* | General Cardano use — definite lengths, minimal integer encoding |
| `CANONICAL_OPTIONS` | RFC 8949 canonical: sorted keys, minimal encoding |
-| `PLUTUS_DATA_OPTIONS` | Plutus data in the node layout: indefinite lists and constructor fields, definite maps. The default for `UPLC.applyParamsToScript` and `UPLC.dataConstant`; matches `cardano-cli`, Aiken `cbor.serialise()`, and `aiken blueprint apply` except for constructor indices above 127 and the integer -2^64 |
-| `CML_DATA_DEFAULT_OPTIONS` | Matches CML `PlutusData.to_cardano_node_format()`: indefinite lists and maps. CML also sorts map keys; this preset keeps insertion order. The default for `Data` encoding |
+| `PLUTUS_DATA_OPTIONS` | Plutus data in the node layout: indefinite lists and constructor fields, definite maps. The default for `Data` encoding, `UPLC.applyParamsToScript` and `UPLC.dataConstant`; matches `cardano-cli`, Aiken `cbor.serialise()`, and `aiken blueprint apply` |
+| `CML_DATA_DEFAULT_OPTIONS` | Deprecated. Indefinite lists, constructor fields and maps, keys in insertion order. The `Data` default before `PLUTUS_DATA_OPTIONS`; use it only to reproduce datum bytes and hashes the SDK wrote before. It matches no tool exactly: CML `PlutusData.to_cardano_node_format()` also sorts map keys |
| `CML_DATA_DEFINITE_OPTIONS` | Matches CML `PlutusData.to_cbor_hex()` on new data: definite lists and maps |
| `AIKEN_DEFAULT_OPTIONS` | Deprecated alias of `PLUTUS_DATA_OPTIONS` |
| `CARDANO_NODE_DATA_OPTIONS` | Deprecated alias of `CML_DATA_DEFINITE_OPTIONS` |
diff --git a/docs/content/docs/encoding/data.mdx b/docs/content/docs/encoding/data.mdx
index 10f82b2a..90137a69 100644
--- a/docs/content/docs/encoding/data.mdx
+++ b/docs/content/docs/encoding/data.mdx
@@ -199,7 +199,7 @@ const datum = Data.constr(0n, [
// Encode to hex string
const cborHex = Data.toCBORHex(datum)
-// "d8799fa2646265..."
+// "d8799fa24b62656e..."
// Encode to bytes
const cborBytes = Data.toCBORBytes(datum)
@@ -210,6 +210,8 @@ const decoded = Data.fromCBORHex(cborHex)
// Returns original PlutusData structure
```
+By default the encoder writes the layout the node writes, `CBOR.PLUTUS_DATA_OPTIONS`: non-empty lists and constructor fields indefinite, maps definite. The map above starts with `a2`, a definite map of two entries. Earlier releases wrote maps indefinite. To reproduce those bytes or datum hashes, pass the deprecated `CBOR.CML_DATA_DEFAULT_OPTIONS` to `Data.toCBORHex`, `Data.toCBORBytes` or `Data.toDatumHash`.
+
## Equality Comparison
Check if two PlutusData structures are equal:
diff --git a/docs/content/docs/introduction/important-defaults.mdx b/docs/content/docs/introduction/important-defaults.mdx
index ded8b1b3..7206c35c 100644
--- a/docs/content/docs/introduction/important-defaults.mdx
+++ b/docs/content/docs/introduction/important-defaults.mdx
@@ -78,16 +78,25 @@ const tx2 = await template.build() // No contamination from tx1
### CBOR: Aiken-Compatible by Default
-`UPLC.applyParamsToScript()` encodes parameters with `CBOR.PLUTUS_DATA_OPTIONS` by default: indefinite-length lists and constructor fields, definite-length maps. This is the layout the node, `cardano-cli`, and `aiken blueprint apply` use. If you're using a different Plutus toolchain, you may need different options.
+Plutus data is encoded with `CBOR.PLUTUS_DATA_OPTIONS` by default: indefinite-length lists and constructor fields, definite-length maps. This is the layout the node, `cardano-cli`, and `aiken blueprint apply` use. The default covers `Data.toCBORHex()`, `Data.toDatumHash()`, inline and witness datums, redeemer data, and the parameters of `UPLC.applyParamsToScript()`. If you're using a different Plutus toolchain, you may need different options.
```typescript
// Default: node layout, same as aiken blueprint apply
UPLC.applyParamsToScript(script, params)
-// Indefinite lists and maps, like CML to_cardano_node_format() (map keys keep insertion order):
-UPLC.applyParamsToScript(script, params, CBOR.CML_DATA_DEFAULT_OPTIONS)
+// Definite lists and maps, like CML to_cbor_hex() on new data:
+UPLC.applyParamsToScript(script, params, CBOR.CML_DATA_DEFINITE_OPTIONS)
```
+Before this default, `Data` wrote non-empty maps indefinite, so a datum that holds a map had different bytes and a different datum hash. To reproduce those, pass the deprecated `CBOR.CML_DATA_DEFAULT_OPTIONS`:
+
+```typescript
+Data.toDatumHash(datum, CBOR.CML_DATA_DEFAULT_OPTIONS)
+Transaction.toCBORHex(tx, { ledger: CBOR.CML_DEFAULT_OPTIONS, plutusData: CBOR.CML_DATA_DEFAULT_OPTIONS })
+```
+
+The transaction options cover witness datums and redeemer data. An inline datum in a new output always uses the `Data` default.
+
### Scripts Are Double-CBOR Encoded
Compiled Plutus scripts from Aiken/Plutarch are typically double-CBOR encoded (flat bytes → CBOR bytes → CBOR bytes). The SDK handles this automatically, but if you're working with raw script bytes, be aware of the encoding level.
diff --git a/docs/content/docs/smart-contracts/apply-params.mdx b/docs/content/docs/smart-contracts/apply-params.mdx
index 2f26266f..e0ea2a37 100644
--- a/docs/content/docs/smart-contracts/apply-params.mdx
+++ b/docs/content/docs/smart-contracts/apply-params.mdx
@@ -134,11 +134,11 @@ const applied = UPLC.applyParamsToScript(compiledScript, [
Data.int(42n),
])
-// With indefinite lists and maps, like CML to_cardano_node_format() (map keys keep insertion order)
+// With definite lists and maps, like CML to_cbor_hex() on new data
const appliedCml = UPLC.applyParamsToScript(
compiledScript,
[Data.int(42n)],
- CBOR.CML_DATA_DEFAULT_OPTIONS,
+ CBOR.CML_DATA_DEFINITE_OPTIONS,
)
```
diff --git a/packages/evolution-devnet/test/Transaction.InlineDatumBytes.test.ts b/packages/evolution-devnet/test/Transaction.InlineDatumBytes.test.ts
index a8841784..8bcc7060 100644
--- a/packages/evolution-devnet/test/Transaction.InlineDatumBytes.test.ts
+++ b/packages/evolution-devnet/test/Transaction.InlineDatumBytes.test.ts
@@ -84,10 +84,11 @@ describe("Inline datum bytes (Devnet Submit)", () => {
const client = createTestClient()
const myAddress = await client.address()
- // The builder writes both maps indefinite (bf...ff). The second one is then
- // rewritten as a definite map (a1...), one byte shorter, so the fee still covers it.
- const indefiniteDatum = "bf0102ff"
- const definiteDatum = "a10304"
+ // The builder writes both maps definite (a1...). The second one, {3: 1000}, is
+ // then rewritten as the indefinite map {3: 4} (bf...ff), one byte shorter, so
+ // the fee still covers it.
+ const definiteDatum = "a10102"
+ const indefiniteDatum = "bf0304ff"
const signBuilder = await client
.newTx()
.payToAddress({
@@ -98,14 +99,14 @@ describe("Inline datum bytes (Devnet Submit)", () => {
.payToAddress({
address: myAddress,
assets: Cardano.Assets.fromLovelace(5_000_000n),
- datum: new InlineDatum.InlineDatum({ data: PlutusData.map([[PlutusData.int(3n), PlutusData.int(4n)]]) })
+ datum: new InlineDatum.InlineDatum({ data: PlutusData.map([[PlutusData.int(3n), PlutusData.int(1000n)]]) })
})
.build({ availableUtxos: [...genesisUtxos] })
const builtHex = Transaction.toCBORHex(await signBuilder.toTransaction())
- expect(builtHex.split("d81844" + indefiniteDatum).length).toBe(2)
- expect(builtHex.split("d81844bf0304ff").length).toBe(2)
- const txHex = builtHex.replace("d81844bf0304ff", "d81843" + definiteDatum)
+ expect(builtHex.split("d81843" + definiteDatum).length).toBe(2)
+ expect(builtHex.split("d81845a1031903e8").length).toBe(2)
+ const txHex = builtHex.replace("d81845a1031903e8", "d81844" + indefiniteDatum)
const txBytes = Bytes.fromHex(txHex)
const bodyBytes = Transaction.extractBodyBytes(txBytes)
const expectedTxId = TransactionHash.toHex(TransactionBody.toHashFromBytes(bodyBytes))
@@ -131,6 +132,6 @@ describe("Inline datum bytes (Devnet Submit)", () => {
const datums = (utxos.result as Array<{ index: number; datum?: string }>)
.sort((a, b) => a.index - b.index)
.map((utxo) => utxo.datum)
- expect(datums).toEqual([indefiniteDatum, definiteDatum])
+ expect(datums).toEqual([definiteDatum, indefiniteDatum])
})
})
diff --git a/packages/evolution-devnet/test/Transaction.RedeemerMapBytesKey.test.ts b/packages/evolution-devnet/test/Transaction.RedeemerMapBytesKey.test.ts
index 5eb90f9e..c706dec5 100644
--- a/packages/evolution-devnet/test/Transaction.RedeemerMapBytesKey.test.ts
+++ b/packages/evolution-devnet/test/Transaction.RedeemerMapBytesKey.test.ts
@@ -129,11 +129,11 @@ describe("Redeemer map with byte-string key (Devnet Submit)", () => {
const scriptUtxos = await client.getUtxos(scriptAddress)
expect(scriptUtxos.length).toBe(1)
- // The builder writes {h'01': [100, 2]} in the data default, an indefinite map and
- // list (bf 41 01 9f 1864 02 ff ff). It is rewritten as {h'01': [1, 2]} with a
- // definite map and an indefinite list (a1 41 01 9f 01 02 ff), which is shorter,
- // so the fee still covers it. The script ignores the redeemer.
- const builtRedeemerData = "bf41019f186402ffff"
+ // The builder writes {h'01': [100, 2]} in the data default, a definite map and
+ // an indefinite list (a1 41 01 9f 1864 02 ff). It is rewritten as {h'01': [1, 2]}
+ // in the same layout (a1 41 01 9f 01 02 ff), which is shorter, so the fee still
+ // covers it. The script ignores the redeemer.
+ const builtRedeemerData = "a141019f186402ff"
const redeemerData = "a141019f0102ff"
const signBuilder = await client
.newTx()
diff --git a/packages/evolution-devnet/test/TxBuilder.WitnessDatum.test.ts b/packages/evolution-devnet/test/TxBuilder.WitnessDatum.test.ts
index 9311eeae..38ce9d6a 100644
--- a/packages/evolution-devnet/test/TxBuilder.WitnessDatum.test.ts
+++ b/packages/evolution-devnet/test/TxBuilder.WitnessDatum.test.ts
@@ -11,7 +11,7 @@
* signs with Transaction.addVKeyWitnessesBytes, and submits. Each case uses
* one set of options for the script data hash and the encoding, and their
* plutusData options for the datum hash: no options, CBOR.CML_DEFAULT_OPTIONS,
- * CBOR.TX_CANONICAL_OPTIONS, or plutusData set to PLUTUS_DATA_OPTIONS. The
+ * CBOR.TX_CANONICAL_OPTIONS, or plutusData set to CML_DATA_DEFAULT_OPTIONS. The
* node accepts the spend only when the written datum hashes to the locked
* datum hash and the script data hash covers the datum and redeemer as
* written.
@@ -57,8 +57,8 @@ describe("Witness datum layout (Devnet Submit)", () => {
const script = new PlutusV3.PlutusV3({ bytes: Bytes.fromHex(alwaysSucceed.compiledCode) })
const scriptAddress = Address.Address.make({ networkId: 0, paymentCredential: ScriptHash.fromScript(script) })
- // Constr 0 [{1: 2}, [3]]: the data default writes its map and lists
- // indefinite, PLUTUS_DATA_OPTIONS its map definite
+ // Constr 0 [{1: 2}, [3]]: the data default writes its lists indefinite and
+ // its map definite, CML_DATA_DEFAULT_OPTIONS its map indefinite
const mapAndList = Data.constr(0n, [Data.map([[Data.int(1n), Data.int(2n)]]), Data.list([Data.int(3n)])])
// Each datum, the options it is written with, and the bytes they give for
@@ -75,15 +75,15 @@ describe("Witness datum layout (Devnet Submit)", () => {
name: "Constr 0 [{1: 2}, [3]] with no options",
datum: mapAndList,
options: undefined,
- datumHex: "d8799fbf0102ff9f03ffff",
- redeemerHex: "d8799fbf0102ff9f03ffff"
+ datumHex: "d8799fa101029f03ffff",
+ redeemerHex: "d8799fa101029f03ffff"
},
{
name: "Constr 0 [1] with CBOR.CML_DEFAULT_OPTIONS",
datum: Data.constr(0n, [Data.int(1n)]),
options: CBOR.CML_DEFAULT_OPTIONS,
datumHex: "d8799f01ff",
- redeemerHex: "d8799fbf0102ff9f03ffff"
+ redeemerHex: "d8799fa101029f03ffff"
},
{
name: "Constr 0 [{1: 2}, [3]] with CBOR.TX_CANONICAL_OPTIONS",
@@ -93,11 +93,11 @@ describe("Witness datum layout (Devnet Submit)", () => {
redeemerHex: "d87982a101028103"
},
{
- name: "Constr 0 [{1: 2}, [3]] with plutusData PLUTUS_DATA_OPTIONS",
+ name: "Constr 0 [{1: 2}, [3]] with plutusData CML_DATA_DEFAULT_OPTIONS",
datum: mapAndList,
- options: { ledger: CBOR.CML_DEFAULT_OPTIONS, plutusData: CBOR.PLUTUS_DATA_OPTIONS },
- datumHex: "d8799fa101029f03ffff",
- redeemerHex: "d8799fa101029f03ffff"
+ options: { ledger: CBOR.CML_DEFAULT_OPTIONS, plutusData: CBOR.CML_DATA_DEFAULT_OPTIONS },
+ datumHex: "d8799fbf0102ff9f03ffff",
+ redeemerHex: "d8799fbf0102ff9f03ffff"
}
]
@@ -283,7 +283,7 @@ describe("Witness datum layout (Devnet Submit)", () => {
.build()
const builtHex = Transaction.toCBORHex(await signBuilder.toTransaction())
expect(builtHex).toContain(`05a182000082${Data.toCBORHex(mapAndList)}`)
- expect(Data.toCBORHex(mapAndList)).toBe("d8799fbf0102ff9f03ffff")
+ expect(Data.toCBORHex(mapAndList)).toBe("d8799fa101029f03ffff")
const txHash = await (await signBuilder.sign()).submit()
expect(await client.awaitTx(txHash, 1000)).toBe(true)
diff --git a/packages/evolution/src/CBOR.ts b/packages/evolution/src/CBOR.ts
index 0fcac8ed..1221e492 100644
--- a/packages/evolution/src/CBOR.ts
+++ b/packages/evolution/src/CBOR.ts
@@ -228,7 +228,8 @@ export const CML_DEFAULT_OPTIONS: CodecOptions = {
* - Maps: definite-length
* - Empty list: `80`; empty map: `a0`
*
- * `Data.toCBORBytes` with these options writes the bytes the node writes.
+ * The `Data` default. `Data.toCBORBytes` with these options writes the bytes
+ * the node writes.
* The rules that do not depend on options are applied by the Plutus data
* encoder under every preset: byte strings over 64 bytes in 64-byte chunks,
* bignums only outside -2^64 to 2^64 - 1 with their bytes chunked the same
@@ -248,17 +249,19 @@ export const PLUTUS_DATA_OPTIONS: CodecOptions = {
} as const
/**
- * CBOR encoding options for PlutusData matching CML
- * `PlutusData.to_cardano_node_format()`, except that CML also sorts map keys
- * and this preset keeps insertion order. This is the current default for
- * `Data` encoding.
+ * CBOR encoding options for PlutusData with non-empty lists, constructor
+ * fields and maps indefinite, map keys in the order given. This was the
+ * `Data` default before {@link PLUTUS_DATA_OPTIONS}.
*
- * Uses indefinite-length lists, constructor fields, and maps. It differs from
- * the node layout ({@link PLUTUS_DATA_OPTIONS}) by writing non-empty maps
- * indefinite. The `bounded_bytes` constraint (Conway CDDL: byte strings of at
- * most 64 bytes) is applied by the Plutus data encoder, independent of these
- * codec options.
+ * It matches no tool exactly. The node, cardano-cli and Aiken write maps
+ * definite. CML 6.2.0 `PlutusData.to_cardano_node_format()` writes maps
+ * indefinite but also sorts map keys. For a constructor index above 127 the
+ * Plutus data encoder writes the tag 102 `[index, fields]` pair definite
+ * under every preset, including this one. Use it only to reproduce the bytes
+ * and datum hashes the SDK wrote before {@link PLUTUS_DATA_OPTIONS} became
+ * the default.
*
+ * @deprecated Use {@link PLUTUS_DATA_OPTIONS}.
* @since 1.0.0
* @category constants
*/
@@ -357,8 +360,7 @@ export type TxCodecOptions = {
*
* - `ledger` is {@link CML_DEFAULT_OPTIONS}, CML's transaction encoding: map
* keys in insertion order, definite lengths.
- * - `plutusData` is {@link CML_DATA_DEFAULT_OPTIONS}, the current `Data`
- * default.
+ * - `plutusData` is {@link PLUTUS_DATA_OPTIONS}, the `Data` default.
*
* It does not match cardano-cli, which sorts map keys and writes indefinite
* lengths above 23 entries.
@@ -368,7 +370,7 @@ export type TxCodecOptions = {
*/
export const TX_DEFAULT_OPTIONS: TxCodecOptions = {
ledger: CML_DEFAULT_OPTIONS,
- plutusData: CML_DATA_DEFAULT_OPTIONS
+ plutusData: PLUTUS_DATA_OPTIONS
}
/**
@@ -386,7 +388,7 @@ export const TX_CANONICAL_OPTIONS: TxCodecOptions = {
/**
* Turns plain options into transaction options: `ledger` is the options and
* `plutusData` is the options too, except that {@link CML_DEFAULT_OPTIONS}
- * gives {@link CML_DATA_DEFAULT_OPTIONS}, as {@link TX_DEFAULT_OPTIONS} does.
+ * gives {@link PLUTUS_DATA_OPTIONS}, as {@link TX_DEFAULT_OPTIONS} does.
* Transaction options are returned as they are.
*
* @since 2.0.0
@@ -395,7 +397,7 @@ export const TX_CANONICAL_OPTIONS: TxCodecOptions = {
export const toTxCodecOptions = (options: TxCodecOptions | CodecOptions): TxCodecOptions =>
"ledger" in options
? options
- : { ledger: options, plutusData: options === CML_DEFAULT_OPTIONS ? CML_DATA_DEFAULT_OPTIONS : options }
+ : { ledger: options, plutusData: options === CML_DEFAULT_OPTIONS ? PLUTUS_DATA_OPTIONS : options }
const DEFAULT_OPTIONS: CodecOptions = {
mode: "custom",
diff --git a/packages/evolution/src/Data.ts b/packages/evolution/src/Data.ts
index 091bbe14..4d93ef83 100644
--- a/packages/evolution/src/Data.ts
+++ b/packages/evolution/src/Data.ts
@@ -490,12 +490,13 @@ export const arbitrary = arbitraryPlutusData(3)
// ============================================================================
/**
- * Default CBOR options for Data encoding/decoding
+ * Default CBOR options for Data encoding/decoding: {@link CBOR.PLUTUS_DATA_OPTIONS},
+ * the layout the node writes.
*
* @since 2.0.0
* @category constants
*/
-export const DEFAULT_CBOR_OPTIONS = CBOR.CML_DATA_DEFAULT_OPTIONS
+export const DEFAULT_CBOR_OPTIONS = CBOR.PLUTUS_DATA_OPTIONS
/**
* Convert a big-endian byte array to a positive bigint
@@ -1041,7 +1042,7 @@ export const FromCDDL = Schema.transformOrFail(CDDLSchema, Schema.typeSchema(Dat
* @since 2.0.0
* @category schemas
*/
-export const FromCBORBytes = (options: CBOR.CodecOptions = CBOR.CML_DATA_DEFAULT_OPTIONS) => {
+export const FromCBORBytes = (options: CBOR.CodecOptions = DEFAULT_CBOR_OPTIONS) => {
const fromBytes = Schema.compose(CBOR.FromBytes(options), FromCDDL)
const layout = toLayout(options)
return Schema.transformOrFail(Schema.Uint8ArrayFromSelf, Schema.typeSchema(DataSchema), {
@@ -1071,7 +1072,7 @@ export const FromCBORBytes = (options: CBOR.CodecOptions = CBOR.CML_DATA_DEFAULT
* @since 2.0.0
* @category schemas
*/
-export const FromCBORHex = (options: CBOR.CodecOptions = CBOR.CML_DATA_DEFAULT_OPTIONS) =>
+export const FromCBORHex = (options: CBOR.CodecOptions = DEFAULT_CBOR_OPTIONS) =>
Schema.compose(
Schema.Uint8ArrayFromHex, // string → Uint8Array
FromCBORBytes(options) // Uint8Array → Data
@@ -1096,7 +1097,7 @@ export const FromCBORHex = (options: CBOR.CodecOptions = CBOR.CML_DATA_DEFAULT_O
* @since 2.0.0
* @category transformation
*/
-export const toCBORBytes = (data: Data, options: CBOR.CodecOptions = CBOR.CML_DATA_DEFAULT_OPTIONS): Uint8Array =>
+export const toCBORBytes = (data: Data, options: CBOR.CodecOptions = DEFAULT_CBOR_OPTIONS): Uint8Array =>
encodeData(data, toLayout(options))
/**
@@ -1105,7 +1106,7 @@ export const toCBORBytes = (data: Data, options: CBOR.CodecOptions = CBOR.CML_DA
* @since 2.0.0
* @category transformation
*/
-export const toCBORHex = (data: Data, options: CBOR.CodecOptions = CBOR.CML_DATA_DEFAULT_OPTIONS): string =>
+export const toCBORHex = (data: Data, options: CBOR.CodecOptions = DEFAULT_CBOR_OPTIONS): string =>
Bytes.toHex(toCBORBytes(data, options))
/**
@@ -1114,7 +1115,7 @@ export const toCBORHex = (data: Data, options: CBOR.CodecOptions = CBOR.CML_DATA
* @since 2.0.0
* @category transformation
*/
-export const fromCBORBytes = (bytes: Uint8Array, options: CBOR.CodecOptions = CBOR.CML_DATA_DEFAULT_OPTIONS): Data =>
+export const fromCBORBytes = (bytes: Uint8Array, options: CBOR.CodecOptions = DEFAULT_CBOR_OPTIONS): Data =>
Schema.decodeSync(FromCBORBytes(options))(bytes)
/**
@@ -1123,7 +1124,7 @@ export const fromCBORBytes = (bytes: Uint8Array, options: CBOR.CodecOptions = CB
* @since 2.0.0
* @category transformation
*/
-export const fromCBORHex = (hex: string, options: CBOR.CodecOptions = CBOR.CML_DATA_DEFAULT_OPTIONS): Data =>
+export const fromCBORHex = (hex: string, options: CBOR.CodecOptions = DEFAULT_CBOR_OPTIONS): Data =>
Schema.decodeSync(FromCBORHex(options))(hex)
/**
@@ -1148,7 +1149,7 @@ export const withSchema = (
/**
* Compute the hash of PlutusData using blake2b-256 over its CBOR encoding.
- * Defaults to CML_DATA_DEFAULT_OPTIONS (indefinite-length arrays/maps).
+ * Defaults to PLUTUS_DATA_OPTIONS, the node layout.
*
* @since 2.0.0
* @category hashing
@@ -1171,7 +1172,7 @@ export const withSchema = (
*/
export const toDatumHash = (
data: Data,
- options: CBOR.CodecOptions = CBOR.CML_DATA_DEFAULT_OPTIONS
+ options: CBOR.CodecOptions = DEFAULT_CBOR_OPTIONS
): DatumHash.DatumHash => {
const bytes = toCBORBytes(data, options)
const digest = blake2b(bytes, { dkLen: 32 })
diff --git a/packages/evolution/test/CBOR.PlutusDataPresets.test.ts b/packages/evolution/test/CBOR.PlutusDataPresets.test.ts
index 85e0e90d..2be602f0 100644
--- a/packages/evolution/test/CBOR.PlutusDataPresets.test.ts
+++ b/packages/evolution/test/CBOR.PlutusDataPresets.test.ts
@@ -59,6 +59,11 @@ describe("PLUTUS_DATA_OPTIONS matches the node encoding", () => {
const encoded = Data.toCBORBytes(data, CBOR.PLUTUS_DATA_OPTIONS)
expect(Bytes.toHex(blake2b(encoded, { dkLen: 32 }))).toBe(hash)
})
+
+ it(`${name}: the Data default writes the same bytes and datum hash`, () => {
+ expect(Data.toCBORHex(data)).toBe(Data.toCBORHex(data, CBOR.PLUTUS_DATA_OPTIONS))
+ expect(Bytes.toHex(Data.toDatumHash(data).hash)).toBe(hash)
+ })
}
it("encodes the empty list as 80 and the empty map as a0", () => {
@@ -71,8 +76,13 @@ describe("PLUTUS_DATA_OPTIONS matches the node encoding", () => {
expect(CBOR.CARDANO_NODE_DATA_OPTIONS).toBe(CBOR.CML_DATA_DEFINITE_OPTIONS)
})
- it("leaves the Data default on CML_DATA_DEFAULT_OPTIONS", () => {
- expect(Data.toCBORHex(Data.map([[1n, 2n]]))).toBe("bf0102ff")
+ it("is the Data default", () => {
+ expect(Data.DEFAULT_CBOR_OPTIONS).toBe(CBOR.PLUTUS_DATA_OPTIONS)
+ expect(Data.toCBORHex(Data.map([[1n, 2n]]))).toBe("a10102")
+ })
+
+ it("leaves CML_DATA_DEFAULT_OPTIONS writing maps indefinite", () => {
+ expect(Data.toCBORHex(Data.map([[1n, 2n]]), CBOR.CML_DATA_DEFAULT_OPTIONS)).toBe("bf0102ff")
})
})
diff --git a/packages/evolution/test/Data.golden.test.ts b/packages/evolution/test/Data.golden.test.ts
index 34a76a68..8ab7e7a6 100644
--- a/packages/evolution/test/Data.golden.test.ts
+++ b/packages/evolution/test/Data.golden.test.ts
@@ -28,12 +28,17 @@ import * as fs from "fs"
import * as path from "path"
import { fileURLToPath } from "url"
+import * as CBOR from "../src/CBOR.js"
import * as Data from "../src/Data.js"
// ES module equivalent of __dirname
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
+// The fixtures were generated with the old Data default, which wrote maps
+// indefinite.
+const GOLDEN_OPTIONS = CBOR.CML_DATA_DEFAULT_OPTIONS
+
/**
* Test configuration defining the number of samples to test for each category
*
@@ -233,7 +238,7 @@ describe("Data Golden Tests", () => {
throw new Error(`Invalid integer sample at index ${entry.index}`)
}
const plutusData = Data.int(entry.sample.integer)
- const encoded = Data.toCBORHex(plutusData)
+ const encoded = Data.toCBORHex(plutusData, GOLDEN_OPTIONS)
expect(encoded, `Failed at sample index ${entry.index}`).toBe(entry.cborHex)
})
})
@@ -246,7 +251,7 @@ describe("Data Golden Tests", () => {
throw new Error(`Invalid integer sample at index ${entry.index}`)
}
const plutusData = Data.int(entry.sample.integer)
- const encoded = Data.toCBORBytes(plutusData)
+ const encoded = Data.toCBORBytes(plutusData, GOLDEN_OPTIONS)
expect(Array.from(encoded), `Failed at sample index ${entry.index}`).toEqual(entry.cborBytes)
})
})
@@ -277,7 +282,7 @@ describe("Data Golden Tests", () => {
throw new Error(`Invalid integer sample at index ${entry.index}`)
}
const plutusData = Data.int(entry.sample.integer)
- const encoded = Data.toCBORHex(plutusData)
+ const encoded = Data.toCBORHex(plutusData, GOLDEN_OPTIONS)
const decoded = Data.fromCBORHex(encoded)
expect(normalizeDecodedData(decoded), `Failed at sample index ${entry.index}`).toEqual(entry.sample)
@@ -294,7 +299,7 @@ describe("Data Golden Tests", () => {
throw new Error(`Invalid byte array sample at index ${entry.index}`)
}
const plutusData = Data.bytearray(entry.sample.bytearray)
- const encoded = Data.toCBORHex(plutusData)
+ const encoded = Data.toCBORHex(plutusData, GOLDEN_OPTIONS)
expect(encoded, `Failed at sample index ${entry.index}`).toBe(entry.cborHex)
})
})
@@ -307,7 +312,7 @@ describe("Data Golden Tests", () => {
throw new Error(`Invalid byte array sample at index ${entry.index}`)
}
const plutusData = Data.bytearray(entry.sample.bytearray)
- const encoded = Data.toCBORBytes(plutusData)
+ const encoded = Data.toCBORBytes(plutusData, GOLDEN_OPTIONS)
expect(Array.from(encoded), `Failed at sample index ${entry.index}`).toEqual(entry.cborBytes)
})
})
@@ -338,7 +343,7 @@ describe("Data Golden Tests", () => {
throw new Error(`Invalid byte array sample at index ${entry.index}`)
}
const plutusData = Data.bytearray(entry.sample.bytearray)
- const encoded = Data.toCBORHex(plutusData)
+ const encoded = Data.toCBORHex(plutusData, GOLDEN_OPTIONS)
const decoded = Data.fromCBORHex(encoded)
expect(normalizeDecodedData(decoded), `Failed at sample index ${entry.index}`).toEqual(entry.sample)
@@ -356,7 +361,7 @@ describe("Data Golden Tests", () => {
}
const plutusDataList = entry.sample.list.map(reconstructPlutusData)
const plutusData = Data.list(plutusDataList)
- const encoded = Data.toCBORHex(plutusData)
+ const encoded = Data.toCBORHex(plutusData, GOLDEN_OPTIONS)
expect(encoded, `Failed at sample index ${entry.index}`).toBe(entry.cborHex)
})
}, 30000) // 30 second timeout for large test cases
@@ -370,7 +375,7 @@ describe("Data Golden Tests", () => {
}
const plutusDataList = entry.sample.list.map(reconstructPlutusData)
const plutusData = Data.list(plutusDataList)
- const encoded = Data.toCBORBytes(plutusData)
+ const encoded = Data.toCBORBytes(plutusData, GOLDEN_OPTIONS)
expect(Array.from(encoded), `Failed at sample index ${entry.index}`).toEqual(entry.cborBytes)
})
})
@@ -402,7 +407,7 @@ describe("Data Golden Tests", () => {
}
const plutusDataList = entry.sample.list.map(reconstructPlutusData)
const plutusData = Data.list(plutusDataList)
- const encoded = Data.toCBORHex(plutusData)
+ const encoded = Data.toCBORHex(plutusData, GOLDEN_OPTIONS)
const decoded = Data.fromCBORHex(encoded)
expect(normalizeDecodedData(decoded), `Failed at sample index ${entry.index}`).toEqual(entry.sample)
@@ -423,7 +428,7 @@ describe("Data Golden Tests", () => {
[reconstructPlutusData(entryObj.key), reconstructPlutusData(entryObj.value)] as [Data.Data, Data.Data]
)
const plutusData = Data.map(plutusDataEntries)
- const encoded = Data.toCBORHex(plutusData)
+ const encoded = Data.toCBORHex(plutusData, GOLDEN_OPTIONS)
expect(encoded, `Failed at sample index ${entry.index}`).toBe(entry.cborHex)
})
})
@@ -440,7 +445,7 @@ describe("Data Golden Tests", () => {
[reconstructPlutusData(entryObj.key), reconstructPlutusData(entryObj.value)] as [Data.Data, Data.Data]
)
const plutusData = Data.map(plutusDataEntries)
- const encoded = Data.toCBORBytes(plutusData)
+ const encoded = Data.toCBORBytes(plutusData, GOLDEN_OPTIONS)
expect(Array.from(encoded), `Failed at sample index ${entry.index}`).toEqual(entry.cborBytes)
})
})
@@ -475,7 +480,7 @@ describe("Data Golden Tests", () => {
[reconstructPlutusData(entryObj.key), reconstructPlutusData(entryObj.value)] as [Data.Data, Data.Data]
)
const plutusData = Data.map(plutusDataEntries)
- const encoded = Data.toCBORHex(plutusData)
+ const encoded = Data.toCBORHex(plutusData, GOLDEN_OPTIONS)
const decoded = Data.fromCBORHex(encoded)
expect(normalizeDecodedData(decoded), `Failed at sample index ${entry.index}`).toEqual(entry.sample)
@@ -493,7 +498,7 @@ describe("Data Golden Tests", () => {
}
const plutusDataFields = entry.sample.fields.map(reconstructPlutusData)
const plutusData = Data.constr(BigInt(entry.sample.index), plutusDataFields)
- const encoded = Data.toCBORHex(plutusData)
+ const encoded = Data.toCBORHex(plutusData, GOLDEN_OPTIONS)
expect(encoded, `Failed at sample index ${entry.index}`).toBe(entry.cborHex)
})
})
@@ -507,7 +512,7 @@ describe("Data Golden Tests", () => {
}
const plutusDataFields = entry.sample.fields.map(reconstructPlutusData)
const plutusData = Data.constr(BigInt(entry.sample.index), plutusDataFields)
- const encoded = Data.toCBORBytes(plutusData)
+ const encoded = Data.toCBORBytes(plutusData, GOLDEN_OPTIONS)
expect(Array.from(encoded), `Failed at sample index ${entry.index}`).toEqual(entry.cborBytes)
})
})
@@ -539,7 +544,7 @@ describe("Data Golden Tests", () => {
}
const plutusDataFields = entry.sample.fields.map(reconstructPlutusData)
const plutusData = Data.constr(BigInt(entry.sample.index), plutusDataFields)
- const encoded = Data.toCBORHex(plutusData)
+ const encoded = Data.toCBORHex(plutusData, GOLDEN_OPTIONS)
const decoded = Data.fromCBORHex(encoded)
expect(normalizeDecodedData(decoded), `Failed at sample index ${entry.index}`).toEqual(entry.sample)
@@ -553,7 +558,7 @@ describe("Data Golden Tests", () => {
testCases.forEach((entry) => {
const plutusData = reconstructPlutusData(entry.sample)
- const encoded = Data.toCBORHex(plutusData)
+ const encoded = Data.toCBORHex(plutusData, GOLDEN_OPTIONS)
expect(encoded, `Failed at sample index ${entry.index}`).toBe(entry.cborHex)
})
})
@@ -563,7 +568,7 @@ describe("Data Golden Tests", () => {
testCases.forEach((entry) => {
const plutusData = reconstructPlutusData(entry.sample)
- const encoded = Data.toCBORBytes(plutusData)
+ const encoded = Data.toCBORBytes(plutusData, GOLDEN_OPTIONS)
expect(Array.from(encoded), `Failed at sample index ${entry.index}`).toEqual(entry.cborBytes)
})
})
@@ -591,7 +596,7 @@ describe("Data Golden Tests", () => {
testCases.forEach((entry) => {
const plutusData = reconstructPlutusData(entry.sample)
- const encoded = Data.toCBORHex(plutusData)
+ const encoded = Data.toCBORHex(plutusData, GOLDEN_OPTIONS)
const decoded = Data.fromCBORHex(encoded)
expect(normalizeDecodedData(decoded), `Failed at sample index ${entry.index}`).toEqual(entry.sample)
diff --git a/packages/evolution/test/Data.test.ts b/packages/evolution/test/Data.test.ts
index dbee2324..2bba9216 100644
--- a/packages/evolution/test/Data.test.ts
+++ b/packages/evolution/test/Data.test.ts
@@ -161,7 +161,7 @@ describe("CBOR Encoding/Decoding", () => {
{
name: "map with entries",
value: Data.map([[1n, Data.bytearray("cafe")]]),
- expectedHex: "bf0142cafeff"
+ expectedHex: "a10142cafe"
},
{
name: "small int",
diff --git a/packages/evolution/test/DatumOption.OriginalBytes.test.ts b/packages/evolution/test/DatumOption.OriginalBytes.test.ts
index 172d0eaf..0f7f5619 100644
--- a/packages/evolution/test/DatumOption.OriginalBytes.test.ts
+++ b/packages/evolution/test/DatumOption.OriginalBytes.test.ts
@@ -110,12 +110,12 @@ describe("inline datum keeps its original bytes", () => {
}
it("a new InlineDatum with the same Data uses the default encoding", () => {
- const option = "8201d818" + bstr("d8799fa10102ff")
+ const option = "8201d818" + bstr("d8799fbf0102ffff")
const decoded = DatumOption.fromCBORHex(option)
if (!InlineDatum.isInlineDatum(decoded)) throw new Error("expected an inline datum")
const edited = new InlineDatum.InlineDatum({ data: decoded.data })
const expected = "8201d818" + bstr(PlutusData.toCBORHex(decoded.data))
- expect(expected).toBe("8201d818" + bstr("d8799fbf0102ffff"))
+ expect(expected).toBe("8201d818" + bstr("d8799fa10102ff"))
expect(DatumOption.toCBORHex(edited)).toBe(expected)
})
})
diff --git a/packages/evolution/test/TSchema.test.ts b/packages/evolution/test/TSchema.test.ts
index 0ffbe6f0..e7b6d12e 100644
--- a/packages/evolution/test/TSchema.test.ts
+++ b/packages/evolution/test/TSchema.test.ts
@@ -170,7 +170,7 @@ describe("TypeTaggedSchema Tests", () => {
const encoded = Data.withSchema(IntegerByteArrayMap).toCBORHex(input)
const decoded = Data.withSchema(IntegerByteArrayMap).fromCBORHex(encoded)
- expect(encoded).toEqual("bf190c894231311b0000003a06945f464432323232ff")
+ expect(encoded).toEqual("a2190c894231311b0000003a06945f464432323232")
expect(eq(decoded, input)).toBe(true)
})