Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,21 @@ small.label {
}
</style>

## 4.22.0 <small>(Jan 3, 2026)</small> { id="4.22.0" }

Features & improvements:

- added support for Solidity 0.8.31, 0.8.32 and 0.8.33 <small class="label">[core]</small>
- improved warning compilation messages when files cannot be compiled together due to version constraints <small class="label">[core]</small>
- improved warning compilation messages when a file from a subproject is included into a different subproject <small class="label">[core]</small>
- limited the maximum number of running solc instances in parallel to CPU count <small class="label">[core]</small>
- documented how to use contract-defined invariants in fuzz tests <small class="label">[documentation]</small>

Fixes:

- `incremental` compilation setting is now respected on compilation JSON import & export <small class="label">[core]</small>
- fixed `read_storage_variable` and `write_storage_variable` when working with contract and enum types <small class="label">[testing framework]</small>

## 4.21.0 <small>(Nov 17, 2025)</small> { id="4.21.0" }

Features & improvements:
Expand Down
32 changes: 32 additions & 0 deletions docs/testing-framework/fuzzing.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,38 @@ def invariant_count(self) -> None:

An optional `period` argument can be passed to the `@invariant` decorator. If specified, the invariant is executed only after every `period` flows.

#### Contract-defined invariants (Echidna / Medusa / Foundry style)

In addition to Python-defined invariants, invariants implemented directly in contracts (in the style of Echidna / Medusa / Foundry) can be reused by calling those functions from Python.

For example, the following Solidity contract can be used:

```solidity
contract Counter {
uint256 public totalSupply;
bool public overflowed;

// Reverts on violation
function echidna_total_supply_invariant() public view {
require(totalSupply <= 1_000_000, "totalSupply too high");
}

// Returns false on violation
function invariant_no_overflow() public view returns (bool) {
return !overflowed;
}
}
```

These contract-level invariants can be wired into a Wake fuzz test as follows:

```python
@invariant()
def invariant_contract_invariants(self) -> None:
self.counter.echidna_total_supply_invariant() # reverts on violation
assert self.counter.invariant_no_overflow() # returns bool
```

### Execution hooks

Execution hooks are functions that are executed during the `FuzzTest` lifecycle. This is the list of all available execution hooks:
Expand Down
3 changes: 2 additions & 1 deletion wake/cli/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from .run import run_run
from .svm import run_svm
from .test import run_test
from .mutate import run_mutate

if platform.system() != "Windows":
try:
Expand Down Expand Up @@ -232,7 +233,7 @@ def exit():
main.add_command(run_run)
main.add_command(run_svm)
main.add_command(run_test)

main.add_command(run_mutate)

@main.command(name="config")
@click.pass_context
Expand Down
4 changes: 4 additions & 0 deletions wake/cli/compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def export_json(

out = {
"version": build_info.wake_version,
"incremental": build_info.incremental,
"system": platform.system(),
"project_root": str(config.project_root_path),
"wake_contracts_path": str(config.wake_contracts_path),
Expand Down Expand Up @@ -93,6 +94,9 @@ async def compile(
wake_contracts_path = PurePosixPath(loaded["wake_contracts_path"])
original_project_root = PurePosixPath(loaded["project_root"])

if incremental is None and loaded.get("incremental", None) is not None:
incremental = loaded["incremental"]

config = WakeConfig.fromdict(
loaded["config"],
wake_contracts_path=wake_contracts_path,
Expand Down
4 changes: 4 additions & 0 deletions wake/cli/detect.py
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,7 @@ def process_detection(detection: Detection) -> Dict[str, Any]:
sys.exit(0 if len(all_detections) == 0 else 3)

if import_json is None:
incremental = None
scan_extra = {}
sol_files: Set[Path] = set()
modified_files: Dict[Path, bytes] = {}
Expand Down Expand Up @@ -746,6 +747,8 @@ def process_detection(detection: Detection) -> Dict[str, Any]:
wake_contracts_path = PurePosixPath(loaded["wake_contracts_path"])
original_project_root = PurePosixPath(loaded["project_root"])

incremental = loaded.get("incremental", None)

config = WakeConfig.fromdict(
loaded["config"],
wake_contracts_path=wake_contracts_path,
Expand Down Expand Up @@ -820,6 +823,7 @@ def process_detection(detection: Detection) -> Dict[str, Any]:
console=console,
no_warnings=True,
modified_files=modified_files,
incremental=incremental,
)

assert compiler.latest_build_info is not None
Expand Down
Loading
Loading