Skip to main content

Smart Contract Debugger

The Smart Contract Debugger provides comprehensive tools for debugging and analyzing Plutus smart contracts.

Overview

import { createSmartContractDebugger, SmartContractDebugger } from 'cardano-devkit';

const debugger = createSmartContractDebugger({
scriptType: 'PlutusV2',
scriptHash: 'abc123...',
verbose: true,
});

Creating a Debugger

createSmartContractDebugger(config)

Factory function to create a debugger instance.

interface DebugConfig {
scriptType: 'PlutusV1' | 'PlutusV2' | 'PlutusV3';
scriptHash?: string;
verbose?: boolean;
costModel?: CostModelParams;
}

const debugger = createSmartContractDebugger({
scriptType: 'PlutusV2',
verbose: true,
});

Core Methods

debugTransaction(tx, redeemer?, datum?)

Debug a transaction involving a smart contract.

const result = await debugger.debugTransaction(tx, redeemer, datum);

console.log("Success:", result.success);
console.log("CPU Units:", result.executionUnits?.cpu);
console.log("Memory Units:", result.executionUnits?.mem);
console.log("Trace:", result.trace);

validateDatum(datum)

Validate datum structure.

const result = debugger.validateDatum({
owner: "addr_test1...",
deadline: 1234567890,
});

if (!result.valid) {
console.error("Datum validation failed:", result.errors);
}

validateRedeemer(redeemer)

Validate redeemer structure.

const result = debugger.validateRedeemer({
action: "Claim",
signature: "abc123...",
});

UPLC Analysis

analyzeUPLC(code)

Analyze UPLC (Untyped Plutus Lambda Calculus) code.

const analysis = debugger.analyzeUPLC(uplcCode);

console.log("AST Size:", analysis.astSize);
console.log("Builtins Used:", analysis.builtinsUsed);
console.log("Memory Estimate:", analysis.memoryEstimate);
console.log("CPU Estimate:", analysis.cpuEstimate);
console.log("Is Optimized:", analysis.isOptimized);

Returns:

interface UPLCAnalysis {
astSize: number; // AST node count
builtinsUsed: string[]; // List of builtin functions used
memoryEstimate: bigint; // Estimated memory units
cpuEstimate: bigint; // Estimated CPU units
isOptimized: boolean; // Whether code appears optimized
warnings: string[]; // Potential issues
}

estimateCostFromUPLC(code, params?)

Estimate execution cost from UPLC code.

const cost = debugger.estimateCostFromUPLC(uplcCode);

console.log("CPU Cost:", cost.cpu);
console.log("Memory Cost:", cost.mem);
console.log("Fee Estimate:", cost.feeEstimate);

Budget Tracking

trackExecution(label, fn)

Track execution budget for a code block.

const result = await debugger.trackExecution("MyScript", async () => {
// Your contract execution here
return await executeContract();
});

console.log("Duration:", result.durationMs, "ms");
console.log("CPU Used:", result.cpuUsed);
console.log("Memory Used:", result.memUsed);

compareBudgets(budget1, budget2)

Compare two execution budgets.

const comparison = debugger.compareBudgets(
{ cpu: 1000000n, mem: 500000n },
{ cpu: 1200000n, mem: 450000n }
);

console.log("CPU Difference:", comparison.cpuDiff);
console.log("Memory Difference:", comparison.memDiff);
console.log("More Efficient:", comparison.recommendation);

Returns:

interface BudgetComparison {
cpuDiff: bigint; // budget2.cpu - budget1.cpu
memDiff: bigint; // budget2.mem - budget1.mem
cpuPercentChange: number;
memPercentChange: number;
recommendation: 'first' | 'second' | 'similar';
}

analyzeBudgetEfficiency(budget)

Analyze budget efficiency against protocol limits.

const analysis = debugger.analyzeBudgetEfficiency({
cpu: 5000000n,
mem: 2000000n,
});

console.log("CPU Usage:", analysis.cpuPercentage, "%");
console.log("Memory Usage:", analysis.memPercentage, "%");
console.log("Status:", analysis.status); // 'efficient' | 'warning' | 'critical'
console.log("Recommendations:", analysis.recommendations);

Script Benchmarking

createScriptBenchmark(name, config?)

Create a benchmark configuration for script testing.

const benchmark = debugger.createScriptBenchmark("VestingContract", {
iterations: 100,
warmupIterations: 10,
includeGC: true,
});

benchmarkScript(benchmark, fn)

Run a benchmark on script execution.

const results = await debugger.benchmarkScript(benchmark, async () => {
return await executeVestingClaim();
});

console.log("Average CPU:", results.avgCpu);
console.log("Average Memory:", results.avgMem);
console.log("Min/Max CPU:", results.minCpu, "/", results.maxCpu);
console.log("Min/Max Memory:", results.minMem, "/", results.maxMem);
console.log("P95 CPU:", results.p95Cpu);
console.log("P95 Memory:", results.p95Mem);

Returns:

interface ScriptBenchmarkResult {
name: string;
iterations: number;
avgCpu: bigint;
avgMem: bigint;
minCpu: bigint;
maxCpu: bigint;
minMem: bigint;
maxMem: bigint;
p95Cpu: bigint;
p95Mem: bigint;
totalDurationMs: number;
avgDurationMs: number;
}

Formatting Helpers

formatDatum(datum)

Format datum for display.

import { formatDatum } from 'cardano-devkit';

const formatted = formatDatum({
owner: "addr_test1...",
deadline: 1234567890,
});
// Returns pretty-printed JSON string

formatRedeemer(redeemer)

Format redeemer for display.

import { formatRedeemer } from 'cardano-devkit';

const formatted = formatRedeemer({
action: "Claim",
});

Protocol Parameters

DEFAULT_PROTOCOL_PARAMS

Default protocol parameters for cost estimation.

const { DEFAULT_PROTOCOL_PARAMS } = require('cardano-devkit');

console.log("Max Tx CPU:", DEFAULT_PROTOCOL_PARAMS.maxTxExUnits.cpu);
console.log("Max Tx Memory:", DEFAULT_PROTOCOL_PARAMS.maxTxExUnits.mem);
console.log("Price per CPU:", DEFAULT_PROTOCOL_PARAMS.executionUnitPrices.priceMemory);

DEFAULT_COST_MODEL

Default Plutus cost model.

const { DEFAULT_COST_MODEL } = require('cardano-devkit');

// Access individual cost model parameters
console.log("addInteger-cpu:", DEFAULT_COST_MODEL['addInteger-cpu-arguments-intercept']);

Complete Example

import {
createSmartContractDebugger,
formatDatum,
formatRedeemer,
} from 'cardano-devkit';

async function debugVestingContract() {
// Create debugger
const debug = createSmartContractDebugger({
scriptType: 'PlutusV2',
verbose: true,
});

// Define datum and redeemer
const datum = {
beneficiary: "addr_test1...",
deadline: Date.now() + 86400000, // 24 hours from now
amount: 100000000n,
};

const redeemer = {
action: "Claim",
};

// Validate inputs
const datumValid = debug.validateDatum(datum);
const redeemerValid = debug.validateRedeemer(redeemer);

if (!datumValid.valid || !redeemerValid.valid) {
console.error("Validation failed");
return;
}

// Analyze budget efficiency
const budget = { cpu: 5000000n, mem: 2000000n };
const efficiency = debug.analyzeBudgetEfficiency(budget);

console.log("Budget Analysis:");
console.log(" CPU Usage:", efficiency.cpuPercentage.toFixed(2), "%");
console.log(" Memory Usage:", efficiency.memPercentage.toFixed(2), "%");
console.log(" Status:", efficiency.status);

// Compare with alternative implementation
const altBudget = { cpu: 4500000n, mem: 2200000n };
const comparison = debug.compareBudgets(budget, altBudget);

console.log("\nComparison with alternative:");
console.log(" CPU Diff:", comparison.cpuDiff.toString());
console.log(" Memory Diff:", comparison.memDiff.toString());
console.log(" Recommendation:", comparison.recommendation);

// Run benchmark
const benchmark = debug.createScriptBenchmark("VestingClaim", {
iterations: 50,
});

const results = await debug.benchmarkScript(benchmark, async () => {
// Simulate contract execution
return { cpu: budget.cpu, mem: budget.mem };
});

console.log("\nBenchmark Results:");
console.log(" Avg CPU:", results.avgCpu.toString());
console.log(" Avg Memory:", results.avgMem.toString());
console.log(" Avg Duration:", results.avgDurationMs.toFixed(2), "ms");
}

debugVestingContract();

Best Practices

  1. Always validate datum and redeemer before submitting transactions
  2. Use budget tracking during development to catch inefficiencies early
  3. Benchmark critical paths to ensure consistent performance
  4. Monitor budget efficiency to stay well under protocol limits
  5. Compare implementations using compareBudgets() when optimizing
  6. Enable verbose mode during development for detailed traces