Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

📝 Add Wake fuzzing cookbook #364

Merged
merged 1 commit into from
Jan 14, 2025
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Context-Based Balance Tracking Pattern

Example of testing a token contract with a context-based balance tracking pattern.

```python
class BalanceTracker:
def track_transfer(self, token, from_: Address, to: Address,
amount: int) -> tuple[int, int]:
before_from = token.balanceOf(from_)
before_to = token.balanceOf(to)

yield

after_from = token.balanceOf(from_)
after_to = token.balanceOf(to)
return (before_from - after_from, after_to - before_to)

class BalanceTest(FuzzTest):
tracker: BalanceTracker

@flow()
def flow_transfer(self):
with self.tracker.track_transfer(self.token, sender, receiver,
amount) as changes:
self.token.transfer(receiver, amount, from_=sender)

delta_from, delta_to = changes
assert delta_from == amount
assert delta_to == amount
```
29 changes: 29 additions & 0 deletions docs/cookbook/advanced-testing-features/differential-testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Differential Testing

Example of testing a token contract with a differential testing approach.

```python
# Model class mirrors contract state
class TokenModel:
balances: dict[Address, int]
total_supply: int

def transfer(self, from_: Address, to_: Address, amount: int):
self.balances[from_] -= amount
self.balances[to_] += amount

class ModelBasedTest(FuzzTest):
token: Token
model: TokenModel

@flow()
def flow_action(self):
# Perform action on both contract and model
self.token.transfer(to, amount)
self.model.transfer(to, amount)

@invariant()
def invariant_state(self):
# Compare contract state with model
assert self.token.balanceOf(user) == self.model.balances[user]
```
16 changes: 16 additions & 0 deletions docs/cookbook/advanced-testing-features/error-tolerance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Error Tolerance

Example of testing a contract with a defined error tolerance for invariants.

```python
class PrecisionTest(FuzzTest):
ERROR_TOLERANCE = 10**10 # Define acceptable rounding error

@invariant()
def invariant_with_tolerance(self):
contract_value = self.contract.getValue()
model_value = self.model.getValue()

# Assert with tolerance
assert abs(contract_value - model_value) < self.ERROR_TOLERANCE
```
8 changes: 8 additions & 0 deletions docs/cookbook/advanced-testing-features/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Advanced Testing Features
Sophisticated testing patterns for complex scenarios, including differential testing, precision handling, and time-based operations. These patterns help when basic testing approaches aren't sufficient.

- [Context Based Balance Tracking Pattern](context-based-balance-tracking-pattern.md)
- [Differential Testing](differential-testing.md)
- [Error Tolerance](error-tolerance.md)
- [Time Based Testing](time-based-testing.md)
- [Token Allowances with Multiple Branches](token-allowances-with-multiple-branches.md)
20 changes: 20 additions & 0 deletions docs/cookbook/advanced-testing-features/time-based-testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Time-Based Testing

Example of testing a contract with time-based operations.

```python
class TimeBasedTest(FuzzTest):
day: int = 0

@flow(weight=lambda self: min(self.day * 0.1, 0.5))
def flow_time_sensitive(self):
days_advance = random_int(1, 7)

if self.day > 0:
chain.mine(lambda x: x + days_advance * 86400)

self.day += days_advance

# Perform time-sensitive operations
self.contract.update()
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Token Allowances with Multiple Branches

Example of testing a token contract with multiple branches in the transferFrom function.

```python
@flow()
def flow_transfer_from(self) -> None:
sender = random_account()
recipient = random_account()
executor = random_account()
insufficient_allowance = random_bool(true_prob=0.15)

if insufficient_allowance:
amount = random_int(self._allowances[sender][executor] + 1, 2**256 - 1)
insufficient_balance = False
else:
amount = random_int(0, min(self._allowances[sender][executor], self._balances[sender]))
insufficient_balance = random_bool(true_prob=0.15)
if insufficient_balance:
amount = random_int(self._balances[sender] + 1, 2**256 - 1)

with may_revert() as e:
self.token.transferFrom(sender, recipient, amount, from_=executor)

if insufficient_allowance or insufficient_balance:
assert e.value == Panic(PanicCodeEnum.UNDERFLOW_OVERFLOW)
else:
self._balances[sender] -= amount
self._balances[recipient] += amount
self._allowances[sender][executor] -= amount
```
21 changes: 21 additions & 0 deletions docs/cookbook/common-testing-patterns/account-balance-testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Account Balance Testing

Testing account balances and registration in a contract that tracks user accounts and their balances.

```python
class AccountManagementTest(FuzzTest):
accounts: dict[Address, bool] # Track active accounts

def pre_sequence(self):
self.accounts = {}

@flow()
def flow_register_account(self):
user = random_account(lower_bound=1) # Skip account 0
self.accounts[user.address] = True

@invariant()
def invariant_accounts(self):
for account in self.accounts:
assert self.contract.balanceOf(account) >= 0
```
7 changes: 7 additions & 0 deletions docs/cookbook/common-testing-patterns/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Common Testing Patterns
Frequently used testing approaches that apply to most smart contract testing scenarios, focusing on balance tracking, state changes, and common interactions.

- [Account Balance Testing](account-balance-testing.md)
- [Multi Token Interaction](multi-token-interaction.md)
- [State Change Tracking](state-change-tracking.md)
- [Test Flow Branching](test-flow-branching.md)
21 changes: 21 additions & 0 deletions docs/cookbook/common-testing-patterns/multi-token-interaction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Multi-Token Interaction

Example of testing a contract that interacts with multiple tokens.

```python
class MultiTokenTest(FuzzTest):
token_a: Token
token_b: Token

def random_amount(self) -> int:
return random_int(1, 10) * 10**18 # Handle decimals

@flow()
def flow_swap(self):
amount = self.random_amount()
user = random_account()

# Approve and swap
self.token_a.approve(self.pool, amount, from_=user)
self.pool.swap(self.token_a, self.token_b, amount, from_=user)
```
28 changes: 28 additions & 0 deletions docs/cookbook/common-testing-patterns/state-change-tracking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# State Change Tracking

Example of tracking complex state changes in a contract.

```python
class StateTracker:
claimed: int
total_reward: int
balances: dict[Address, int]

def track_claim(self, user: Address, amount: int):
self.claimed += amount
self.balances[user] += amount


class StateTrackingTest(FuzzTest):
tracker: StateTracker

@flow()
def flow_claim(self):
user = random_account()
before = self.token.balanceOf(user)
self.contract.claim(from_=user)
after = self.token.balanceOf(user)

claimed = after - before
self.tracker.track_claim(user, claimed)
```
24 changes: 24 additions & 0 deletions docs/cookbook/common-testing-patterns/test-flow-branching.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Test Flow Branching

Example of testing a contract with branching logic in the test flow.

```python
class CrossAccountTest(FuzzTest):
@flow()
def flow_multi_account(self):
user_count = random_int(1, 5)

for _ in range(user_count):
user = random_account(lower_bound=1).address

if self.pool.balanceOf(user) == 0:
self.deposit(user, random_amount())
else:
self.claim(user)

if random_bool():
self.deposit(user, random_amount())
else:
balance = self.pool.balanceOf(user)
self.withdraw(user, min(random_amount(), balance))
```
36 changes: 36 additions & 0 deletions docs/cookbook/essential-fundamentals/basic-fuzz-test-structure.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Basic Fuzz Test Structure

The basic structure of a fuzz test.

```python
from wake.testing import *
from wake.testing.fuzzing import *

# Import the contract using pytypes path.
# The path is the same as the one used in the Solidity codebase.
from pytypes.contracts.Token import Token


class BasicFuzzTest(FuzzTest):
token: Token # Contract instance
owner: Account

def pre_sequence(self):
self.owner = chain.accounts[0]
self.token = Token.deploy("Name", "SYM", from_=self.owner)

@flow()
def flow_transfer(self):
amount = random_int(1, 1000)
user = random_account()
self.token.transfer(user, amount, from_=self.owner)

@invariant()
def invariant_supply(self):
assert self.token.totalSupply() == self.initial_supply


@chain.connect()
def test_basic():
BasicFuzzTest().run(sequences_count=1, flows_count=100)
```
Loading
Loading