Skip to Content
Scalus Club is now open! Join us to get an early access to new features 🎉

Debugging Smart Contracts

Scala-Level Debugging

Before compiling to UPLC, you can debug your validator as regular Scala code:

  • Use your IDE’s debugger
  • Set breakpoints in validator methods
  • Step through execution
  • Inspect variables and data structures

This is the fastest way to debug logic errors.

Using log for Script Logging

You can add log messages to your validator using the log function:

import scalus.prelude.log @Compile object MyValidator extends Validator: inline override def spend( datum: Option[Data], redeemer: Data, tx: TxInfo, ownRef: TxOutRef ): Unit = { log("Starting validation") val myDatum = datum.getOrFail("Datum required").to[MyDatum] log(s"Validating for owner: ${myDatum.owner}") val isValid = tx.isSignedBy(myDatum.owner) log(s"Signature check: $isValid") require(isValid, "Must be signed by owner") }

Where Do Logs Appear?

When scripts are evaluated (on-chain or via PlutusScriptEvaluator), logs are collected and included in:

  • The Result.Success object (when evaluation succeeds)
  • The PlutusScriptEvaluationException (when evaluation fails)

Off-chain, you can access logs through the evaluation result to understand what happened during script execution. This is particularly useful when debugging why a transaction failed validation.

Evaluating with Error Traces

import scalus.uplc.eval.PlutusVM given PlutusVM = PlutusVM.makePlutusV3VM() val compiled = compile { log("Validator starting") // your validator code log("Validator completed") } // Evaluate with error traces enabled val result = compiled.toUplc(generateErrorTraces = true).evaluateDebug

The generateErrorTraces flag:

  • true: Adds error location information (useful for debugging, but increases script size)
  • false: Minimal script size (for production deployment)

Debugging with IDE

One of Scalus’s biggest advantages is the ability to debug validators as regular Scala code:

Setting Up Debug Mode

  1. Run tests in debug mode - Use your IDE’s debug test runner
  2. Set breakpoints - Click in the gutter next to line numbers
  3. Inspect variables - Hover over variables or use the debug panel
  4. Step through code - Use step over, step into, step out

Example Debug Session

@Compile object MyValidator extends Validator: inline override def spend( datum: Option[Data], redeemer: Data, tx: TxInfo, ownRef: TxOutRef ): Unit = { val owner = datum.getOrFail("No datum").to[PubKeyHash] // Set breakpoint here ⬅ val signed = tx.signatories.contains(owner) // Inspect 'signed' variable require(signed, "Not signed") }

Debugging workflow:

  1. Set a breakpoint in your validator
  2. Run test in debug mode
  3. When breakpoint hits, inspect variables
  4. Step through execution to understand behavior
  5. Fix logic errors before compiling to UPLC

Next Steps

  • Testing - Write comprehensive tests for your validators
  • Compiling - Compile debugged validators to Plutus scripts
  • Optimisations - Optimize script size and execution costs
Last updated on