When a purge execution fails or contract behavior is unexpected, effective debugging can turn hours of frustration into minutes of focused investigation. Wake provides a full debugging ecosystem in Python combined with Solidity-specific visibility tools. This guide shows you the techniques Ackee’s audit team uses to quickly isolate problems.
Visibility into contract status
The first step in debugging a failing test is to understand what the contract actually does. Wake provides several approaches to exposing internal state.
Console login in Solidity
Wake includes a console logging library that works directly from Solidity code.
import "wake/console.sol";You can log values inline during contract execution using: console.log(), console.logBytes32()and console.logBytes(). The library includes transformations for dynamic length data. This allows printf-style debugging without deploying to a live network or setting up complex tools.
If you need readable call tracking, use: account.label Replace the raw address with a meaningful name. Instead of looking 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb If you trace it, you will see “TokenContract” or “UserWallet”.
For pure functions it is not possible to emit logs during normal execution, so a workaround is needed. A quick fix is to temporarily display the function and its dependent pure functions as follows: view. If this is not possible, emit a custom event instead.
Manually guided purging (MGF) technology
Wake’s Python-based fuzzing provides unmatched control over coverage-based approaches. The key is to maintain accurate Python state that reflects the state of the contract.
immutable function
Write more immutable functions than flow functions. Immutability ensures that Python state remains synchronized with on-chain reality, which is essential when debugging complex errors. If a test fails, you can trust the Python state to understand what went wrong.
Find the flow that caused the failure
To determine which flow caused an error, add logging to the beginning of each flow.
def pre_flow(self, flow: Callable):
logger.info(f"(FLOW) flow.__name__")This creates a breadcrumb trace that shows exactly which sequence of actions caused the error.
Interactive debugging with ipdb
Wake is fully integrated with Python’s debugger. Use ipdb when you need to check state during execution or step through a transaction sequence.
Transaction Debugging
To research recent transactions, use: chain.txs(-1).call_trace Most recent transaction or chain.txs(-2).call_trace Previously. This gives you full visibility into what’s happening on the chain.
> chain.txs(-1).call_trace
> chain.txs(-2).call_traceSystematic debugging settings
For systematic debugging, set up handlers that automatically print traces when problems occur.
def revert_handler(e: RevertError):
if e.tx is not None:
print(e.tx.call_trace)
def tx_callback_fn(tx: TransactionAbc) -> None:
print(tx.call_trace)
# so no print(tx.call_trace) everywhere, only for debug because too slow
@chain.connect()
@on_revert(revert_handler)
def test_fuzz_stethpool():
chain.tx_callback = tx_callback_fn
FuzzVWStEth().run(1, 100000)Printing a trace for every transaction will significantly slow down execution, so enable it. tx_callback This is only possible when actively debugging.
Reproduction with random seeds
If you find an error, reproduce it with the correct random seed.
wake test tests/test_counter_fuzz.py -S62061e838798ad0f -d -vthat -d Specifying the flag will take you into the debugger. -v It increases the details and ensures that the seeds follow the same order every time.
Reduce failed sequences
Once we find a failing sequence, we reduce it to isolate the minimal flow combination that causes the bug. This will determine if the problem appears early in the sequence or if a full setup is required.
scale down run wake test tests/test_something.py -SH reduce it to a human-readable sequence, or -SR For succinct expression. Smaller sequences often reveal core problems more clearly than full failures.
wake test tests/test_something.py -SH
wake test tests/test_something.py -SRAdditional Tips
Expected revert processing
When testing edge cases, you often want to see if an operation will revert under certain conditions. wakes may_revert The context manager allows you to assert both successful executions and expected failures.
with may_revert() as e:
tx = contract.operation()
if condition_that_causes_revert:
assert e.value == Contract.ExpectedError()
return "expected_revert_reason"
assert e.value is None # Must succeed otherwiseThis pattern makes your tests self-documenting. You will immediately know which path to retrace and which path to succeed.
Performance analysis
If your tests are running slowly, profile them to find bottlenecks.
wake --profile test tests/test_fuzz.pyThis will generate a profile file that can be visualized using:
gprof2dot -f pstats .wake/wake.prof | dot -Tsvg -o wake.prof.svgThe resulting SVG shows exactly where the execution time is going, making optimization goals clear.
Fuzzing coverage
To understand test coverage, run:
wake test tests/test_fuzz.py --coverageThis is coverage.cov It’s in the project root. Open VS Code’s command palette and select “Wake: Show Coverage” to visualize the contract code executed in the test.
Token Test Pattern
Testing a DeFi protocol requires issuing tokens and managing approvals. Wake makes this simple:
# Mint directly to your test user
mint_erc20(token, user, 100 * 10**18)
token.approve(contract, 100 * 10**18, from_=user)
contract.pullToken(from_=user)Cross-chain test
For cross-chain scenarios, Wake allows multiple independent chains to run simultaneously.
from wake.testing import Chain
# Create two separate blockchain instances
ethereum_chain = Chain()
polygon_chain = Chain()
@ethereum_chain.connect()
@polygon_chain.connect()
def test_cross_chain_transfer():
# Both chains are now connected and ready
passImproved debugging speed
Start with clear invariants that check your assumptions about the state of the contract. Handlers and callbacks provide easy access to call traces. If the test fails, scale back to minimal reproduction before going in depth. These patterns make the difference between spending hours hunting bugs and isolating them in minutes.
The technology here leverages Python’s mature debugging ecosystem while providing direct access to the EVM internals. Wake combines the expressive power of Python with the precision needed for smart contract security. This is the basis for rapid development and thorough testing.
