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
- Always validate datum and redeemer before submitting transactions
- Use budget tracking during development to catch inefficiencies early
- Benchmark critical paths to ensure consistent performance
- Monitor budget efficiency to stay well under protocol limits
- Compare implementations using
compareBudgets()when optimizing - Enable verbose mode during development for detailed traces