Learn how to debug Leo programs, interpret error messages, and resolve common issues during development.
Error Messages
Leo provides structured error messages with unique error codes to help diagnose issues quickly.
Leo errors follow the format: E{PREFIX}037{CODE}
- Prefix: Indicates the error category
- 037: Fixed identifier for Leo errors
- Code: Unique error number within the category
Example: EPAR0370042 is parser error 42
Error Categories
Leo errors are organized by compiler component:
Parser Errors (PAR: 0-999)
Errors during source code parsing:Error [EPAR0370009]: unexpected string: expected 'ident', got 'let'
--> main.leo:5:5
|
5 | let let x: u32 = 5u32;
| ^^^
Common causes:
- Syntax errors
- Invalid token sequences
- Missing semicolons or braces
AST Errors (AST: 2000-2999)
Errors in abstract syntax tree construction:Error [EAST0372001]: Invalid type
--> main.leo:10:15
Common causes:
- Type mismatches
- Invalid type annotations
- Malformed AST nodes
Compiler Errors (CMP: 6000-6999)
Errors during compilation:Error [ECMP0376045]: Cannot assign to immutable variable
--> main.leo:8:5
|
8 | x = 10u32;
| ^
Common causes:
- Type checking failures
- Undefined variables
- Invalid operations
Package Errors (PAK: 5000-5999)
Errors related to project structure and dependencies:Error [EPAK0375001]: Failed to load package at 'program.json'
Common causes:
- Missing
program.json
- Invalid manifest format
- Dependency resolution failures
Understanding Error Output
Leo error messages include:
- Error code: Unique identifier for the error type
- Error message: Human-readable description
- Source location: File, line, and column
- Code snippet: Context showing where the error occurred
- Help text: Suggestions for fixing the issue (when available)
Error [ECMP0376123]: Undefined variable `y`
--> src/main.leo:12:20
|
12 | let total = x + y;
| ^
|
= help: Did you mean `x`?
Error codes never change once released. You can search for error codes in documentation or online for detailed explanations.
Debugging Techniques
Using Console Output
Leo supports console functions for debugging (development only):
fn debug_values(a: u32, b: u32) -> u32 {
console.log("Values: a={}, b={}", a, b);
let result: u32 = a + b;
console.log("Result: {}", result);
return result;
}
Console functions are removed during production compilation and should only be used for debugging.
Type Checking
Explicitly specify types to catch errors early:
// Explicit types help catch mismatches
let amount: u64 = 100u64; // Clear
let result: u32 = process(amount); // Type error caught
Assertions for Debugging
Use assertions to validate assumptions:
fn transfer(amount: u64, balance: u64) -> u64 {
// Debug assertion
assert_neq(amount, 0u64);
assert_eq(balance >= amount, true);
return balance - amount;
}
Incremental Testing
Break complex functions into testable parts:
fn complex_calculation(x: u32) -> u32 {
let step1: u32 = calculate_step1(x);
let step2: u32 = calculate_step2(step1);
let step3: u32 = calculate_step3(step2);
return step3;
}
// Test each step independently
@test
fn test_step1() {
let result: u32 = calculate_step1(5u32);
assert_eq(result, 10u32);
}
Common Issues
Type Mismatch Errors
Problem
Error: Type mismatch, expected `u32`, got `u64`
Cause
Mixing different integer types without explicit conversion:let a: u32 = 100u32;
let b: u64 = 200u64;
let c: u32 = a + b; // Error: can't add u32 and u64
Solution
Ensure type consistency:let a: u32 = 100u32;
let b: u32 = 200u32; // Use same type
let c: u32 = a + b; // Works
Undefined Variable
Problem
Error: Undefined variable `total`
Cause
Using a variable before declaration:fn calculate() -> u32 {
result = total + 10u32; // total not defined
let total: u32 = 5u32;
return result;
}
Solution
Declare variables before use:fn calculate() -> u32 {
let total: u32 = 5u32;
let result: u32 = total + 10u32;
return result;
}
Invalid Program Name
Problem
Error: Invalid program name '_myprogram.aleo'
Cause
Program name violates naming rules:
- Starts with underscore or number
- Contains invalid characters
- Contains the word “aleo” in the name
Solution
Follow naming conventions:// Invalid
"program": "_myprogram.aleo" // starts with _
"program": "123program.aleo" // starts with number
"program": "my-program.aleo" // contains hyphen
"program": "myaleo.aleo" // contains "aleo"
// Valid
"program": "my_program.aleo" // correct format
Circular Dependency
Problem
Error: Circular dependency detected
Cause
Programs importing each other:program_a.aleo imports program_b.aleo
program_b.aleo imports program_a.aleo
Solution
Restructure dependencies to be acyclic:program_a.aleo imports utils.aleo
program_b.aleo imports utils.aleo
// Both depend on utils, no circular dependency
Mapping Access Outside Finalize
Problem
Error: Cannot access mapping outside finalize block
Cause
Trying to use Mapping::get or Mapping::set in a regular function:fn invalid_access() -> bool {
let value: bool = Mapping::get(users, addr); // Error
return value;
}
Solution
Access mappings only in finalize functions:fn check_user() -> Final {
let addr: address = self.caller;
return final { finalize_check(addr); };
}
final fn finalize_check(addr: address) {
let is_registered: bool = Mapping::get(users, addr);
assert_eq(is_registered, true);
}
Build and Compilation Issues
Check Syntax
Run syntax validation without full compilation:
Clean Build
Remove build artifacts and rebuild:
rm -rf build/ outputs/
leo build
Verbose Output
Enable detailed compiler output:
Compiler Version
Verify you’re using the correct Leo version:
Update if needed:
Development Workflow
Write Code
Implement your feature with explicit types and comments:fn process_payment(amount: u64) -> u64 {
// Validate amount is non-zero
assert_neq(amount, 0u64);
return amount;
}
Check Syntax
Validate syntax before testing: Run Tests
Execute tests to verify behavior: Debug Failures
If tests fail, add debug output:@test
fn test_payment() {
console.log("Testing payment processing");
let result: u64 = process_payment(100u64);
console.log("Result: {}", result);
assert_eq(result, 100u64);
}
Build for Production
Compile the final program:
Rust-level Debugging
For compiler development, use Rust debugging tools:
Running Tests with Output
# Show test output
cargo test -- --nocapture
# Run specific test
TEST_FILTER=test_name cargo test -p leo-compiler
Using Clippy
Run the linter to catch common issues:
cargo clippy -p leo-compiler -- -D warnings
Ensure code is properly formatted:
cargo +nightly fmt --check
Leo uses nightly Rust formatter. Install with:
Circuit Size
Monitor generated circuit size:
leo build --verbose
# Check output for constraint counts
Loop Unrolling
Be aware of loop unrolling impact:
// Small loop - efficient
for i: u8 in 0u8..10u8 {
// 10 iterations unrolled
}
// Large loop - may cause issues
for i: u32 in 0u32..1000u32 {
// 1000 iterations - consider alternatives
}
Excessive loop unrolling increases circuit size dramatically. Keep iteration counts reasonable.
Memory Usage
From the compiler’s performance guidelines:
- Pre-allocate collections with
with_capacity when size is known
- Avoid unnecessary clones
- Use references when ownership isn’t needed
- Minimize intermediate allocations
Getting Help
Search Error Codes
Look up error codes in documentation:
https://docs.leo-lang.org/errors/EPAR0370042
Filing Bug Reports
When filing issues, include:
- Leo version (
leo --version)
- Minimal reproducible example
- Full error message with code
- Expected vs actual behavior
- Operating system
Provide a minimal test case that reproduces the issue. This helps maintainers diagnose and fix problems faster.
Next Steps