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.Successobject (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).evaluateDebugThe 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
- Run tests in debug mode - Use your IDE’s debug test runner
- Set breakpoints - Click in the gutter next to line numbers
- Inspect variables - Hover over variables or use the debug panel
- 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:
- Set a breakpoint in your validator
- Run test in debug mode
- When breakpoint hits, inspect variables
- Step through execution to understand behavior
- 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