Skip to content

Commit

Permalink
Merge pull request #357 from Anastasia-Labs/feat/canonical-options
Browse files Browse the repository at this point in the history
feat: add canonical format to tx builder and Data.to
  • Loading branch information
solidsnakedev authored Oct 8, 2024
2 parents 50ae067 + a14aa8b commit 115327e
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 29 deletions.
6 changes: 6 additions & 0 deletions .changeset/famous-rings-unite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@lucid-evolution/plutus": patch
"@lucid-evolution/lucid": patch
---

add canonical format option
72 changes: 49 additions & 23 deletions packages/lucid/src/tx-builder/internal/CompleteTxBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ export type CompleteOptions = {
coinSelection?: boolean;
changeAddress?: Address;
localUPLCEval?: boolean; // replaces nativeUPLC
setCollateral: bigint;
setCollateral?: bigint;
canonical?: boolean;
};

export const completeTxError = (cause: unknown) =>
Expand Down Expand Up @@ -85,55 +86,72 @@ const getWalletInfo = (

export const complete = (
config: TxBuilder.TxBuilderConfig,
options: CompleteOptions = {
coinSelection: true,
localUPLCEval: true,
setCollateral: 5_000_000n,
},
options: CompleteOptions = {},
) =>
Effect.gen(function* () {
yield* Effect.all(config.programs, { concurrency: "unbounded" });
const walletInfo = yield* getWalletInfo(config);
// Set default options
const {
coinSelection = true,
changeAddress = walletInfo.address,
localUPLCEval = true,
setCollateral = 5_000_000n,
canonical = false,
} = options;
// Execute programs sequentially
yield* Effect.all(config.programs);
const hasScriptExecutions: Boolean = config.scripts.size > 0;
// Set collateral input if there are script executions
if (hasScriptExecutions) {
const collateralInput = yield* findCollateral(
config.lucidConfig.protocolParameters.coinsPerUtxoByte,
options.setCollateral,
setCollateral,
walletInfo.inputs,
);
setCollateral(
applyCollateral(
config,
options.setCollateral,
setCollateral,
collateralInput,
walletInfo.address,
);
}
// First round of coin selection and UPLC evaluation. The fee estimation is lacking
// the script execution costs as they aren't available yet.
yield* selectionAndEvaluation(config, options, walletInfo, false);
yield* selectionAndEvaluation(
config,
walletInfo,
coinSelection,
localUPLCEval,
false,
);
// Second round of coin selection by including script execution costs in fee estimation.
// UPLC evaluation need to be performed again if new inputs are selected during coin selection.
// Because increasing the inputs can increase the script execution budgets.
if (hasScriptExecutions)
yield* selectionAndEvaluation(config, options, walletInfo, true);
yield* selectionAndEvaluation(
config,
walletInfo,
coinSelection,
localUPLCEval,
true,
);

config.txBuilder.add_change_if_needed(
CML.Address.from_bech32(walletInfo.address),
CML.Address.from_bech32(changeAddress),
true,
);
const tx = yield* Effect.try({
const transaction = yield* Effect.try({
try: () =>
config.txBuilder
.build(
CML.ChangeSelectionAlgo.Default,
CML.Address.from_bech32(walletInfo.address),
CML.Address.from_bech32(changeAddress),
)
.build_unchecked(),
catch: (error) => completeTxError(error),
});

const derivedInputs = deriveInputsFromTransaction(tx);
const derivedInputs = deriveInputsFromTransaction(transaction);

const derivedWalletInputs = derivedInputs.filter(
(utxo) => utxo.address === walletInfo.address,
Expand All @@ -151,14 +169,22 @@ export const complete = (
return Tuple.make(
updatedWalletInputs,
derivedInputs,
TxSignBuilder.makeTxSignBuilder(config.lucidConfig, tx),
TxSignBuilder.makeTxSignBuilder(
config.lucidConfig,
canonical
? CML.Transaction.from_cbor_bytes(
transaction.to_canonical_cbor_bytes(),
)
: transaction,
),
);
}).pipe(Effect.catchAllDefect((cause) => new RunTimeError({ cause })));

export const selectionAndEvaluation = (
config: TxBuilder.TxBuilderConfig,
options: CompleteOptions,
walletInfo: WalletInfo,
coinSelection: boolean,
localUPLCEval: boolean,
script_calculation: boolean,
): Effect.Effect<void, TransactionError, never> =>
Effect.gen(function* () {
Expand All @@ -168,8 +194,8 @@ export const selectionAndEvaluation = (
);

const inputsToAdd =
options.coinSelection !== false
? yield* coinSelection(config, availableInputs, script_calculation)
coinSelection !== false
? yield* doCoinSelection(config, availableInputs, script_calculation)
: [];

// Skip UPLC evaluation for the second time if no new inputs are added
Expand Down Expand Up @@ -213,7 +239,7 @@ export const selectionAndEvaluation = (
});

if (txRedeemerBuilder.draft_tx().witness_set().redeemers()) {
if (options.localUPLCEval !== false) {
if (localUPLCEval !== false) {
applyUPLCEval(
yield* evalTransaction(config, txRedeemerBuilder, walletInfo.inputs),
config.txBuilder,
Expand Down Expand Up @@ -358,7 +384,7 @@ export const setRedeemerstoZero = (tx: CML.Transaction) => {
}
};

const setCollateral = (
const applyCollateral = (
config: TxBuilder.TxBuilderConfig,
setCollateral: bigint,
collateralInputs: UTxO[],
Expand Down Expand Up @@ -418,7 +444,7 @@ const findCollateral = (
return selected;
});

const coinSelection = (
const doCoinSelection = (
config: TxBuilder.TxBuilderConfig,
availableInputs: UTxO[],
script_calculation: boolean,
Expand Down
30 changes: 24 additions & 6 deletions packages/plutus/src/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,10 +263,28 @@ export const Data = {
};

/**
* Convert PlutusData to Cbor encoded data.\
* Or apply a shape and convert the provided data struct to Cbor encoded data.
* Convert PlutusData or Schema to CBOR-encoded data
*
* By default, the `canonical` option is set to `false`.
*
* @example Non Canonical format:
* ```ts
* Data.to<Data>(new Constr(0, ["deadbeef"])) -> 'd8799f44deadbeefff';
* ```
*
* @example Canonical format:
* ```ts
* Data.to<Data>(new Constr(0, ["deadbeef"]), undefined, { canonical: true }) -> 'd8798144deadbeef';
* ```
*
* Returns the encoded CBOR data as either `Datum` or `Redeemer`.
*/
function to<T = Data>(data: Exact<T>, type?: T): Datum | Redeemer {
function to<T = Data>(
data: Exact<T>,
type?: T,
options: { canonical?: boolean } = {},
): Datum | Redeemer {
const { canonical = false } = options;
function serialize(data: Data): CML.PlutusData {
try {
if (typeof data === "bigint") {
Expand Down Expand Up @@ -308,9 +326,9 @@ function to<T = Data>(data: Exact<T>, type?: T): Datum | Redeemer {
}
}
const d = type ? castTo<T>(data, type) : (data as Data);
return serialize(d).to_cardano_node_format().to_cbor_hex() as
| Datum
| Redeemer;
return canonical
? (serialize(d).to_canonical_cbor_hex() as Datum | Redeemer)
: (serialize(d).to_cardano_node_format().to_cbor_hex() as Datum | Redeemer);
}

/**
Expand Down

0 comments on commit 115327e

Please sign in to comment.