Skip to content

Commit

Permalink
fix: datumHash
Browse files Browse the repository at this point in the history
  • Loading branch information
solidsnakedev committed Dec 21, 2024
1 parent cfe9827 commit 59b51f9
Show file tree
Hide file tree
Showing 6 changed files with 88 additions and 30 deletions.
7 changes: 7 additions & 0 deletions .changeset/large-pans-promise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@lucid-evolution/lucid": patch
"@lucid-evolution/utils": patch
---

UTXO handling to normalize entries containing both a `datum` and `datumHash`.
The `datum` field is now removed when a `datumHash` is present to ensure consistency and avoid errors during transaction evaluation.
24 changes: 15 additions & 9 deletions packages/lucid/src/tx-builder/internal/Collect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { Effect, pipe } from "effect";
import { Data } from "@lucid-evolution/plutus";
import { utxoToCore } from "@lucid-evolution/utils";
import { Redeemer, RedeemerBuilder, UTxO } from "@lucid-evolution/core-types";
import * as TxBuilder from "../TxBuilder.js";
import { ERROR_MESSAGE, TxBuilderError } from "../../Errors.js";
import * as CML from "@anastasia-labs/cardano-multiplatform-lib-nodejs";
import { toPartial, toV1, toV2, toV3 } from "./TxUtils.js";
Expand All @@ -19,15 +18,22 @@ export const collectFromUTxO =
Effect.gen(function* () {
const { config } = yield* TxConfig;
if (utxos.length === 0) yield* collectError(ERROR_MESSAGE.EMPTY_UTXO);
for (const utxo of utxos) {
if (utxo.datumHash && !utxo.datum) {
const data = yield* Effect.tryPromise({
try: () => datumOf(config.lucidConfig.provider, utxo),
catch: (cause) => collectError({ cause }),
});
for (const { datumHash, datum, ...rest } of utxos) {
// This UTXO value is intended solely for internal use.
// When the UTXO contains a datumHash but no datum, the datum must be fetched and included.
// This ensures the txBuilder can later add the datum into the Plutus data transaction witness field.
// Additionally, when evaluating a transaction, the datum field must be removed if the datumHash has a value.
const resolvedDatum =
datumHash && !datum
? yield* pipe(
Effect.promise(() =>
config.lucidConfig.provider.getDatum(datumHash),
),
Effect.map(Data.to),
)
: datum;
const utxo: UTxO = { ...rest, datumHash, datum: resolvedDatum };

utxo.datum = Data.to(data);
}
const coreUtxo = utxoToCore(utxo);

// An array of unspent transaction outputs to be used as inputs when running uplc eval.
Expand Down
20 changes: 18 additions & 2 deletions packages/lucid/src/tx-builder/internal/CompleteTxBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -597,7 +597,16 @@ const evalTransactionProvider = (
): Effect.Effect<EvalRedeemer[], TxBuilderError> =>
Effect.gen(function* () {
const txEvaluation = setRedeemerstoZero(txRedeemerBuilder.draft_tx())!;
const txUtxos = [...config.collectedInputs, ...config.readInputs];
// Normalize UTXOs that include both a datum and a datumHash.
// If a datumHash is present, the datum field is removed.
// This ensures consistency when preparing UTXOs for evaluation.
const txUtxos = [...config.collectedInputs, ...config.readInputs].map(
({ datumHash, datum, ...rest }) => ({
...rest,
datumHash,
datum: datumHash ? undefined : datum,
}),
);
const uplc_eval = yield* Effect.tryPromise({
try: () =>
config.lucidConfig.provider.evaluateTx(
Expand All @@ -616,11 +625,18 @@ const evalTransaction = (
): Effect.Effect<Uint8Array[], TxBuilderError> =>
Effect.gen(function* () {
const txEvaluation = setRedeemerstoZero(txRedeemerBuilder.draft_tx())!;
// Normalize UTXOs that include both a datum and a datumHash.
// If a datumHash is present, the datum field is removed.
// This ensures consistency when preparing UTXOs for evaluation.
const txUtxos = [
...walletInputs,
...config.collectedInputs,
...config.readInputs,
];
].map(({ datumHash, datum, ...rest }) => ({
...rest,
datumHash,
datum: datumHash ? undefined : datum,
}));
const ins = txUtxos.map((utxo) => utxoToTransactionInput(utxo));
const outs = txUtxos.map((utxo) => utxoToTransactionOutput(utxo));
const slotConfig = SLOT_CONFIG_NETWORK[config.lucidConfig.network];
Expand Down
31 changes: 18 additions & 13 deletions packages/lucid/src/tx-builder/internal/Read.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,33 @@ import { Data } from "@lucid-evolution/plutus";
import { utxoToCore } from "@lucid-evolution/utils";
import { UTxO } from "@lucid-evolution/core-types";
import * as TxBuilder from "../TxBuilder.js";
import { datumOf } from "../../lucid-evolution/utils.js";
import { ERROR_MESSAGE, TxBuilderError } from "../../Errors.js";

export const readError = (cause: unknown) =>
new TxBuilderError({ cause: `{ Read : ${cause} }` });

export const readFrom = (
config: TxBuilder.TxBuilderConfig,
utxos: UTxO[],
): Effect.Effect<void, TxBuilderError> =>
export const readFrom = (config: TxBuilder.TxBuilderConfig, utxos: UTxO[]) =>
Effect.gen(function* () {
if (utxos.length === 0) yield* readError(ERROR_MESSAGE.EMPTY_UTXO);
for (const utxo of utxos) {
if (utxo.datumHash) {
const data = yield* Effect.tryPromise({
try: () => datumOf(config.lucidConfig.provider, utxo),
catch: (cause) => readError(cause),
});
for (const { datumHash, datum, ...rest } of utxos) {
// This UTXO value is intended solely for internal use.
// When the UTXO contains a datumHash but no datum, the datum must be fetched and included.
// This ensures the txBuilder can later add the datum into the Plutus data transaction witness field.
// Additionally, when evaluating a transaction, the datum field must be removed if the datumHash has a value.
const resolvedDatum =
datumHash && !datum
? yield* pipe(
Effect.promise(() =>
config.lucidConfig.provider.getDatum(datumHash),
),
Effect.map(Data.to),
)
: datum;

const utxo: UTxO = { ...rest, datumHash, datum: resolvedDatum };

utxo.datum = Data.to(data);
}
const coreUtxo = utxoToCore(utxo);

// An array of unspent transaction outputs to be used as inputs when running uplc eval.
config.readInputs.push(utxo);
config.txBuilder.add_reference_input(coreUtxo);
Expand Down
10 changes: 5 additions & 5 deletions packages/utils/src/utxo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,12 +279,12 @@ const buildDatum = (
utxo: UTxO,
builder: CML.TransactionOutputBuilder,
): CML.TransactionOutputBuilder => {
//TODO: test with DatumHash
if (utxo.datumHash)
return builder.with_data(
CML.DatumOption.new_hash(CML.DatumHash.from_hex(utxo.datumHash)),
// with DatumHash
if (utxo.datumHash && utxo.datum)
return builder.with_communication_data(
CML.PlutusData.from_cbor_hex(utxo.datum),
);
// inline datum
// with InlineDatum
if (utxo.datum)
return builder.with_data(
CML.DatumOption.new_datum(CML.PlutusData.from_cbor_hex(utxo.datum)),
Expand Down
26 changes: 25 additions & 1 deletion packages/utils/test/utxo.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { expect, it } from "vitest";
import * as CML from "@anastasia-labs/cardano-multiplatform-lib-nodejs";
import { coreToUtxo } from "../src/index.js";
import { coreToUtxo, utxoToCore } from "../src/index.js";
import { UTxO } from "@lucid-evolution/core-types";

it("should deserialize CBOR UTXO data from the first sample", () => {
Expand Down Expand Up @@ -38,3 +38,27 @@ it("should deserialize CBOR UTXO data from the second sample", () => {
};
expect(coreToUtxo(cmlUTXO)).toStrictEqual(utxo);
});

it("should deserialize to utxo withn datumHash", () => {
const utxo: UTxO = {
txHash: "f061f4e8490472008182fc922e8ecd414f2fa5005ea218202bd324fbe8746919",
outputIndex: 0,
address: "addr_test1wqn7wnkhny2j475ac709vg3kw6wf59f3hdl3htfpwg8xmtqtxyz6a",
assets: { lovelace: 10000000n },
datumHash:
"48300c087b5dbc6198ed7cfbd5c04028360e0d9270eec318addbae8b6305a909",
datum:
"d8799f581c9fc430ea1f3adc20eebb813b2649e85c934ea5bc13d7b7fbe2b24e50ff",
scriptRef: undefined,
};
expect(coreToUtxo(utxoToCore(utxo))).toStrictEqual({
txHash: "f061f4e8490472008182fc922e8ecd414f2fa5005ea218202bd324fbe8746919",
outputIndex: 0,
assets: { lovelace: 10000000n },
address: "addr_test1wqn7wnkhny2j475ac709vg3kw6wf59f3hdl3htfpwg8xmtqtxyz6a",
datumHash:
"48300c087b5dbc6198ed7cfbd5c04028360e0d9270eec318addbae8b6305a909",
datum: undefined,
scriptRef: undefined,
});
});

0 comments on commit 59b51f9

Please sign in to comment.