Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions packages/aiken-uplc/src/Evaluator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import * as Bytes from "@evolution-sdk/evolution/core/Bytes"
import * as CBOR from "@evolution-sdk/evolution/core/CBOR"
import * as CostModel from "@evolution-sdk/evolution/core/CostModel"
import * as Redeemer from "@evolution-sdk/evolution/core/Redeemer"
import * as Script from "@evolution-sdk/evolution/core/Script"
import * as ScriptRef from "@evolution-sdk/evolution/core/ScriptRef"
Expand Down Expand Up @@ -154,11 +155,14 @@ export function makeEvaluator(wasmModule: WasmLoader.WasmModule): TransactionBui

const { slotLength, zeroSlot, zeroTime } = context.slotConfig

// Encode cost models to CBOR bytes for WASM evaluator
const costModelsCBOR = CostModel.toCBOR(context.costModels)

yield* Effect.logDebug(
`[Aiken UPLC] Slot config - zeroTime: ${zeroTime}, zeroSlot: ${zeroSlot}, slotLength: ${slotLength}`
)
yield* Effect.logDebug(`[Aiken UPLC] Cost models CBOR length: ${context.costModels.length} bytes`)
yield* Effect.logDebug(`[Aiken UPLC] Cost models hex: ${Bytes.toHex(context.costModels)}`)
yield* Effect.logDebug(`[Aiken UPLC] Cost models CBOR length: ${costModelsCBOR.length} bytes`)
yield* Effect.logDebug(`[Aiken UPLC] Cost models hex: ${Bytes.toHex(costModelsCBOR)}`)
yield* Effect.logDebug(
`[Aiken UPLC] Max execution - steps: ${context.maxTxExSteps}, mem: ${context.maxTxExMem}`
)
Expand All @@ -173,7 +177,7 @@ export function makeEvaluator(wasmModule: WasmLoader.WasmModule): TransactionBui
txBytes,
utxosX,
utxosY,
context.costModels,
costModelsCBOR,
context.maxTxExSteps,
context.maxTxExMem,
BigInt(zeroTime),
Expand Down
1 change: 1 addition & 0 deletions packages/evolution-devnet/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"peerDependencies": {
"@evolution-sdk/aiken-uplc": "workspace:*",
"@evolution-sdk/evolution": "workspace:*",
"@evolution-sdk/scalus-uplc": "workspace:*",
"@effect/platform": "^0.90.10",
"@effect/platform-node": "^0.96.1",
"effect": "^3.19.3"
Expand Down
75 changes: 70 additions & 5 deletions packages/evolution-devnet/test/TxBuilder.Scripts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import * as ScriptHash from "@evolution-sdk/evolution/core/ScriptHash"
import type { TxBuilderConfig } from "@evolution-sdk/evolution/sdk/builders/TransactionBuilder"
import { makeTxBuilder } from "@evolution-sdk/evolution/sdk/builders/TransactionBuilder"
import { KupmiosProvider } from "@evolution-sdk/evolution/sdk/provider/Kupmios"
import { createScalusEvaluator } from "@evolution-sdk/scalus-uplc"
import { Schema } from "effect"

import * as Cluster from "../src/Cluster.js"
Expand Down Expand Up @@ -264,11 +265,75 @@ describe("TxBuilder Script Handling", () => {

const redeemer = tx.witnessSet.redeemers![0]
expect(redeemer.tag).toBe("spend")
expect(redeemer.exUnits.mem).toBeGreaterThan(0n) // mem > 0
expect(redeemer.exUnits.steps).toBeGreaterThan(0n) // steps > 0

// eslint-disable-next-line no-console
console.log(`✓ Aiken evaluator: mem=${redeemer.exUnits.mem}, steps=${redeemer.exUnits.steps}`)
expect(redeemer.exUnits.mem).toBe(1100n)
expect(redeemer.exUnits.steps).toBe(160100n)
})

it.fails("should build transaction with Scalus evaluator", async () => {
const alwaysSucceedsScript = makePlutusV2Script(ALWAYS_SUCCEED_SCRIPT_CBOR)
const scriptAddress = scriptToAddress(ALWAYS_SUCCEED_SCRIPT_CBOR)

// Create script UTxO with inline datum
const ownerPubKeyHash = "00000000000000000000000000000000000000000000000000000000"
const datum = Data.toCBORHex(Data.constr(0n, [Data.bytearray(ownerPubKeyHash)]))

const scriptUtxo = createCoreTestUtxo({
transactionId: "a".repeat(64),
index: 0,
address: scriptAddress,
lovelace: 5_000_000n,
datumOption: { type: "inlineDatum", inline: datum }
})

// Create funding UTxO
const fundingUtxo = createCoreTestUtxo({
transactionId: "b".repeat(64),
index: 0,
address: CHANGE_ADDRESS,
lovelace: 10_000_000n
})

// Create redeemer
const redeemerData = Data.constr(0n, [Data.bytearray("48656c6c6f2c20576f726c6421")])

const builder = makeTxBuilder(baseConfig)
.collectFrom({
inputs: [scriptUtxo],
redeemer: redeemerData
})
.attachScript({ script: alwaysSucceedsScript })
.payToAddress({
address: CoreAddress.fromBech32(RECEIVER_ADDRESS),
assets: CoreAssets.fromLovelace(2_000_000n)
})

// Build with Scalus evaluator and debug enabled
const signBuilder = await builder.build({
changeAddress: CoreAddress.fromBech32(CHANGE_ADDRESS),
availableUtxos: [fundingUtxo],
protocolParameters: PROTOCOL_PARAMS,
evaluator: createScalusEvaluator,
debug: true // Enable debug logging
})

const tx = await signBuilder.toTransaction()

// Verify transaction structure
expect(tx.body.inputs.length).toBe(1)
expect(tx.body.outputs.length).toBeGreaterThanOrEqual(2) // Payment + change

// Verify script witnesses
expect(tx.witnessSet.plutusV2Scripts).toBeDefined()
expect(tx.witnessSet.plutusV2Scripts!.length).toBe(1)

// Verify redeemers with evaluated exUnits
expect(tx.witnessSet.redeemers).toBeDefined()
expect(tx.witnessSet.redeemers!.length).toBe(1)

const redeemer = tx.witnessSet.redeemers![0]
expect(redeemer.tag).toBe("spend")
expect(redeemer.exUnits.mem).toBe(1100n)
expect(redeemer.exUnits.steps).toBe(160100n)
})

it("should handle collateral inputs with multiassets and return excess to user as collateral return", async () => {
Expand Down
5 changes: 3 additions & 2 deletions packages/evolution/src/sdk/builders/TransactionBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import * as CoreAssets from "../../core/Assets/index.js"
import type * as AuxiliaryData from "../../core/AuxiliaryData.js"
import type * as Certificate from "../../core/Certificate.js"
import type * as Coin from "../../core/Coin.js"
import type * as CostModel from "../../core/CostModel.js"
import type * as PlutusData from "../../core/Data.js"
import type * as KeyHash from "../../core/KeyHash.js"
import type * as Mint from "../../core/Mint.js"
Expand Down Expand Up @@ -687,7 +688,7 @@ export interface ChainResult {
*/
export interface EvaluationContext {
/** Cost models for script evaluation */
readonly costModels: Uint8Array
readonly costModels: CostModel.CostModels
/** Maximum execution steps allowed */
readonly maxTxExSteps: bigint
/** Maximum execution memory allowed */
Expand Down Expand Up @@ -763,7 +764,7 @@ export interface ScriptFailure {
* @category errors
*/
export class EvaluationError extends Data.TaggedError("EvaluationError")<{
readonly cause: unknown
readonly cause?: unknown
readonly message?: string
/** Parsed script failures with labels */
readonly failures?: ReadonlyArray<ScriptFailure>
Expand Down
28 changes: 9 additions & 19 deletions packages/evolution/src/sdk/builders/phases/Evaluation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@ import { assembleTransaction, buildTransactionInputs } from "../TxBuilderImpl.js
import type { PhaseResult } from "./Phases.js"

/**
* Convert ProtocolParameters cost models to CBOR bytes for evaluation.
* Convert ProtocolParameters cost models to CostModels core type for evaluation.
*
* Takes the cost models from protocol parameters (Record<string, number> format)
* and converts them to the CBOR-encoded format expected by UPLC evaluators.
* and converts them to the CostModels core type.
*/
const costModelsToCBOR = (
const buildCostModels = (
protocolParams: ProtocolParametersModule.ProtocolParameters
): Effect.Effect<Uint8Array, TransactionBuilderError> =>
): Effect.Effect<CostModel.CostModels, TransactionBuilderError> =>
Effect.gen(function* () {
// Convert Record<string, number> format to bigint arrays
const plutusV1Costs = Object.values(protocolParams.costModels.PlutusV1).map((v) => BigInt(v))
Expand All @@ -51,22 +51,12 @@ const costModelsToCBOR = (
.filter((v) => v <= INT64_MAX)
const plutusV3Costs = Object.values(protocolParams.costModels.PlutusV3).map((v) => BigInt(v))

// Create CostModels instance
const costModels = new CostModel.CostModels({
// Create and return CostModels instance
return new CostModel.CostModels({
PlutusV1: new CostModel.CostModel({ costs: plutusV1Costs }),
PlutusV2: new CostModel.CostModel({ costs: plutusV2Costs }),
PlutusV3: new CostModel.CostModel({ costs: plutusV3Costs })
})

// Encode to CBOR bytes
return yield* Effect.try({
try: () => CostModel.toCBOR(costModels),
catch: (error) =>
new TransactionBuilderError({
message: "Failed to encode cost models to CBOR",
cause: error
})
})
})

/**
Expand Down Expand Up @@ -457,8 +447,8 @@ export const executeEvaluation = (): Effect.Effect<
}

// Step 6: Prepare evaluation context
// Encode cost models from full protocol parameters
const costModelsCBOR = yield* costModelsToCBOR(fullProtocolParams)
// Build cost models from full protocol parameters
const costModels = yield* buildCostModels(fullProtocolParams)

// Get slot configuration from BuildOptions (resolved from network or explicit override)
const slotConfig = buildOptions.slotConfig ?? {
Expand All @@ -468,7 +458,7 @@ export const executeEvaluation = (): Effect.Effect<
}

const evaluationContext: EvaluationContext = {
costModels: costModelsCBOR,
costModels,
maxTxExSteps: fullProtocolParams.maxTxExSteps,
maxTxExMem: fullProtocolParams.maxTxExMem,
slotConfig
Expand Down
32 changes: 32 additions & 0 deletions packages/scalus-uplc/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "@evolution-sdk/scalus-uplc",
"version": "0.0.1",
"description": "Scalus UPLC evaluator adapter for Evolution SDK",
"type": "module",
"exports": {
".": {
"types": "./src/index.node.ts",
"node": "./src/index.node.ts",
"default": "./src/index.node.ts"
}
},
"scripts": {
"build": "tsc -b tsconfig.build.json",
"dev": "tsc -b tsconfig.build.json --watch",
"type-check": "tsc --noEmit",
"test": "vitest run",
"clean": "rm -rf dist .turbo .tsbuildinfo"
},
"dependencies": {
"effect": "^3.19.3",
"scalus": "^0.14.0"
},
"peerDependencies": {
"@evolution-sdk/evolution": "workspace:*"
},
"devDependencies": {
"typescript": "^5.9.2",
"@effect/vitest": "^0.19.3",
"vitest": "^3.2.4"
}
}
Loading