Debugging Aztec Code
This guide shows you how to debug issues in your Aztec development environment.
Prerequisites
- Running Aztec local network
- Aztec.nr contract or aztec.js application
- Basic understanding of Aztec architecture
Enable logging
For adding log statements to your contracts, controlling log verbosity, and understanding the LOG_LEVEL syntax, see the Logging from Contracts guide.
To enable verbose system-level logging on a local network:
LOG_LEVEL=verbose aztec start --local-network
Debugging common errors
Contract Errors
| Error | Solution |
|---|---|
Aztec dependency not found | Add to Nargo.toml: aztec = { git="https://github.com/AztecProtocol/aztec-packages/", tag="next", directory="noir-projects/aztec-nr/aztec" } |
Public state writes only supported in public functions | Move state writes to public functions |
Unknown contract 0x0 | Call wallet.registerContract(...) to register contract |
No public key registered for address | Call wallet.registerSender(...) |
Direct invocation of ... functions is not supported | Use self.call(), self.view(), or self.enqueue() to call contract functions |
Failed to solve brillig function | Check function parameters and note validity |
Cross-contract utility call denied | Configure an authorizeUtilityCall execution hook on your PXE |
Cross-contract utility call denied
Utility functions execute on the user's device and have access to private state. A cross-contract utility call made by a malicious or compromised contract could leak private information to an untrusted contract. PXE therefore denies all cross-contract utility calls by default and requires explicit authorization via an execution hook.
When a contract executes a utility function that calls into a different contract, PXE asks an execution hook whether the call should be allowed. If no hook is configured, or the hook denies the request, you will see:
Cross-contract utility call denied: <reason>. <caller> attempted to call <target>:<selector> (<name>).
In production
Pass an authorizeUtilityCall hook when creating your PXE:
import { PXE } from "@aztec/pxe/server";
const pxe = await PXE.create({
// ...other options
hooks: {
authorizeUtilityCall: async (request) => {
// Inspect request.caller, request.target, request.functionSelector, etc.
return { authorized: true };
},
},
});
The hook receives a UtilityCallAuthorizationRequest with the caller and target addresses, their contract class IDs, function selector, function name, arguments, and caller context ('private', 'private view', or 'utility'). Return { authorized: true } to allow or { authorized: false, reason: '...' } to deny with a message.
In Noir tests
When testing cross-contract utility calls in the Noir test environment (TXE), use with_authorized_utility_call_targets on your call options:
// For private calls:
env.call_private_opts(
account,
CallPrivateOptions::new().with_authorized_utility_call_targets([target_address]),
MyContract::at(caller).some_private_fn(),
);
// For private view calls:
env.view_private_opts(
account,
ViewPrivateOptions::new().with_authorized_utility_call_targets([target_address]),
MyContract::at(caller).some_view_fn(),
);
// For utility calls:
env.execute_utility_opts(
ExecuteUtilityOptions::new().with_authorized_utility_call_targets([target_address]),
MyContract::at(caller).some_utility_fn(),
);
Circuit Errors
| Error Code | Meaning | Fix |
|---|---|---|
2002 | Invalid contract address | Ensure contract is deployed and address is correct |
2005/2006 | Static call violations | Remove state modifications from static calls |
2017 | User intent mismatch | Verify transaction parameters match function call |
3001 | Unsupported operation | Check if operation is supported in current context |
3005 | Non-empty private call stack | Ensure private functions complete before public |
4007/4008 | Chain ID/version mismatch | Verify L1 chain ID and Aztec version |
7008 | Membership check failed | Ensure using valid historical state |
7009 | Array overflow | Reduce number of operations in transaction |
Quick Fixes for Common Issues
# Archiver sync issues - force progress with dummy transactions.
# Assumes you have imported the local network test accounts
# (aztec-wallet import-test-accounts) and have a deployed token
# aliased as `testtoken`.
aztec-wallet send transfer --from test0 --contract-address testtoken --args accounts:test0 0
aztec-wallet send transfer --from test0 --contract-address testtoken --args accounts:test0 0
# L1 to L2 message pending - wait for inclusion
# Messages need 2 blocks to be processed
Debugging WASM errors
Enable debug WASM
// In vite.config.ts or similar
export default {
define: {
"process.env.BB_WASM_PATH": JSON.stringify("https://debug.wasm.url"),
},
};
Profile transactions
import { serializePrivateExecutionSteps } from "@aztec/stdlib";
// Profile the transaction
const profileTx = await contract.methods
.myMethod(param1, param2)
.profile({ profileMode: "execution-steps" });
// Serialize for debugging
const ivcMessagePack = serializePrivateExecutionSteps(profileTx.executionSteps);
// Download debug file
const blob = new Blob([ivcMessagePack]);
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = "debug-steps.msgpack";
link.click();
⚠️ Warning: Debug files may contain private data. Use only in development.
Interpret error messages
Circuit and protocol errors
- Private kernel errors (2xxx): Issues with private function execution
- Public kernel errors (3xxx): Issues with public function execution
- Rollup errors (4xxx): Block production issues
- Generic errors (7xxx): Resource limits or state validation
Transaction limits
Current limits that trigger 7009 - ARRAY_OVERFLOW:
- Max new notes per tx: Check
MAX_NOTE_HASHES_PER_TX - Max nullifiers per tx: Check
MAX_NULLIFIERS_PER_TX - Max function calls: Check call stack size limits
- Max L2→L1 messages: Check message limits
Debugging sequencer issues
Common sequencer errors
| Error | Cause | Solution |
|---|---|---|
tree root mismatch | State inconsistency | Restart local network or check state transitions |
next available leaf index mismatch | Tree corruption | Verify tree updates are sequential |
Public call stack size exceeded | Too many public calls | Reduce public function calls |
Failed to publish block | L1 submission failed | Check L1 connection and gas |
Reporting issues
When debugging fails:
- Collect error messages and codes
- Generate transaction profile (if applicable)
- Note your environment setup
- Create issue at aztec-packages
Quick reference
Enable verbose logging
LOG_LEVEL=verbose aztec start --local-network
Contract logging
See the full Logging from Contracts guide for all available log functions and LOG_LEVEL configuration.
use aztec::oracle::logging::{debug_log, debug_log_format};
Check contract registration
await wallet.getContractMetadata(myContractInstance.address);
Decode L1 errors
Check hex errors against Errors.sol
Tips
- Always check logs before diving into circuit errors
- State-related errors often indicate timing issues
- Array overflow errors mean you hit transaction limits
- Use debug WASM for detailed stack traces
- Profile transactions when errors are unclear