Testing & Verification
Last Updated: 2026-02-13
Our smart contract testing strategy employs multiple layers of verification to ensure security, correctness, and resilience against edge cases and attacks.
Testing Overview
Total Tests: 262 passing (56s)
├── 207 Unit Tests (baseline functionality)
├── 35 Invariant Tests (property-based)
├── 20 Economic Tests (x402 economics verification)
├── Foundry Fuzz Tests (100,000+ runs per function) ⚠️ needs re-run post-x402
├── Echidna Property Tests (stateful fuzzing) ⚠️ needs re-run post-x402
├── Mutation Testing (90% kill rate - 108/120 mutants, pre-x402)
└── Certora Formal Verification (mathematical proofs) ⚠️ needs re-run post-x40290% Mutation Kill Rate: 108 out of 120 introduced bugs were detected by our test suite. Score and Escrow contracts achieved 100% coverage, with ongoing improvements to Resolver and Staking tests.
1. Unit Tests (46 Tests)
Traditional unit tests covering expected behavior and edge cases.
Coverage Areas
| Component | Tests | Coverage |
|---|---|---|
| Escrow Lifecycle | 15 | State transitions, funds flow |
| Trust Score Calculation | 12 | Points, bonding, registration |
| Dispute Resolution | 10 | 3-tier system, outcomes |
| Reviewer Payments | 9 | Distribution logic, edge cases |
Running Unit Tests
cd contracts
npm test
# With coverage
npm run test:coverageExample Test Output
AbbababaEscrowV1
✓ creates escrow with criteriaHash (89ms)
✓ marks delivery and releases after 24h (142ms)
✓ handles disputes correctly (201ms)
✓ enforces agent-negotiable timing (95ms)
...
AbbababaScoreV1
✓ awards 20 points for email verification (45ms)
✓ awards 20 points per $1 donation (capped at $1) (78ms)
✓ awards 20 points for $100 stake (62ms)
✓ requires 40 points to unlock (51ms)
...2. Invariant Tests (23 Tests)
Property-based tests that verify critical invariants always hold true, regardless of state.
Invariants Tested
Escrow Balance Invariant
// Contract always holds enough USDC for all active escrows
function invariant_escrowBalance() public {
uint256 activeEscrows = escrow.getTotalActiveEscrows();
uint256 contractBalance = usdc.balanceOf(address(escrow));
assertGe(contractBalance, activeEscrows);
}Fee Accounting Invariant
// Platform receives exactly 2% on all transactions
function invariant_feeAccounting() public {
uint256 totalEscrows = escrow.getTotalEscrowValue();
uint256 collectedFees = escrow.getTotalFeesCollected();
uint256 expectedFees = (totalEscrows * 200) / 10000; // 2%
assertEq(collectedFees, expectedFees);
}Status Transition Invariant
// Status can only progress forward, never backward
function invariant_statusTransitions() public {
for (uint i = 0; i < escrowIds.length; i++) {
uint8 status = uint8(escrow.getStatus(escrowIds[i]));
assertGe(status, previousStatus[escrowIds[i]]);
}
}Registration Points Invariant
// Registration points are always non-negative
function invariant_registrationPoints() public {
for (uint i = 0; i < agents.length; i++) {
int256 regPoints = score.getRegistrationPoints(agents[i]);
assertGe(regPoints, 0);
}
}Unlock Threshold Invariant
// canWork() matches total score >= 40
function invariant_unlockThreshold() public {
for (uint i = 0; i < agents.length; i++) {
bool canWork = score.canWork(agents[i]);
int256 totalScore = score.getTotalScore(agents[i]);
assertEq(canWork, totalScore >= 40);
}
}Timing Bounds Invariant
// Agent-negotiable timing stays within configured limits
function invariant_timingBounds() public {
for (uint i = 0; i < escrowIds.length; i++) {
(uint256 dispute, uint256 abandon) = escrow.getTimingParams(escrowIds[i]);
assertGe(dispute, 1 hours); // Min dispute window
assertLe(dispute, 7 days); // Max dispute window
assertGe(abandon, 1 hours); // Min abandonment grace
assertLe(abandon, 30 days); // Max abandonment grace
}
}Running Invariant Tests
# Foundry invariant tests
forge test --match-contract Invariant
# With detailed output
forge test --match-contract Invariant -vvv3. Fuzz Testing
Automated randomized input testing to discover edge cases.
3.1 Foundry Fuzzing
Uses Foundry’s built-in fuzzer with 100,000+ runs per test.
/// @custom:fuzz runs=100000
function testFuzz_escrowAmounts(uint256 amount) public {
// Bound inputs to realistic ranges
amount = bound(amount, MIN_ESCROW, MAX_ESCROW);
// Create escrow with fuzzed amount
bytes32 jobId = createFuzzEscrow(amount);
// Verify invariants hold
assertEq(escrow.getAmount(jobId), amount);
assertGe(usdc.balanceOf(address(escrow)), amount);
}3.2 Echidna Stateful Fuzzing
Property-based fuzzer that explores complex multi-transaction scenarios.
Config (echidna.yaml):
testMode: assertion
testLimit: 100000
deployer: "0x30000"
sender: ["0x10000", "0x20000", "0x30000"]
corpusDir: "tests/echidna/corpus"Property Example:
// Echidna property: Escrow balance never decreases unexpectedly
contract EchidnaEscrowTest {
uint256 previousBalance;
function echidna_balance_never_decreases() public returns (bool) {
uint256 currentBalance = usdc.balanceOf(address(escrow));
if (currentBalance < previousBalance) {
// Only valid if escrow was released/refunded
return escrow.hasRecentSettlement();
}
previousBalance = currentBalance;
return true;
}
}Running Fuzz Tests
# Foundry fuzzing
forge test --fuzz-runs 100000
# Echidna
echidna . --contract EchidnaEscrowTest --config echidna.yaml4. Mutation Testing (100% Kill Rate)
Mutation testing introduces bugs into the code to verify tests catch them.
Gambit Configuration
We use Gambit for mutation testing:
# Generate mutants
gambit mutate --filename contracts/AbbababaEscrowV1.sol
# Run tests against each mutant
gambit test --mutation-dir**Results**: **90% kill rate** (108/120 mutants killed)
| Contract | Mutants | Killed | Kill Rate |
|----------|---------|--------|-----------|
| AbbababaScoreV1 | 30 | 30 | 100% ✅ |
| AbbababaEscrowV1 | 30 | 30 | 100% ✅ |
| ReviewerPaymentV1 | 30 | 29 | 96% ✅ |
| AbbababaResolverV1 | 30 | 19 | 63% 🔄 |
| **Total** | **120** | **108** | **90%** |
<Callout type="info">
**Active Testing**: Mutation testing is ongoing. Current 90% kill rate demonstrates strong test coverage, with Score and Escrow contracts at 100%. Additional tests are being developed for Resolver contract edge cases.
</Callout>r tests provide comprehensive protection against regression bugs.
</Callout>
### Example Mutation
**Original Code**:
```solidity
require(amount >= MIN_ESCROW, "Below minimum");Mutated Code (Gambit changes >= to >):
require(amount > MIN_ESCROW, "Below minimum");Test Kills Mutation:
function test_minEscrowAmount() public {
uint256 minAmount = MIN_ESCROW; // Exact minimum
// This passes with original, fails with mutation
escrow.createEscrow(jobId, seller, minAmount, ...);
}Mutation Testing Results
Mutation Testing Report
=======================
Total mutants generated: 100
Mutants killed: 100 ✅
Mutants survived: 0
Kill rate: 100%
Critical path coverage: COMPLETE5. Formal Verification (Certora)
Mathematical proofs that certain properties always hold.
Certora Specs
We use Certora Prover to formally verify critical invariants:
Spec: certora/specs/Escrow.spec
// Rule: Escrow balance equals sum of active escrows
rule balanceEqualsActiveEscrows {
env e;
uint256 totalActive = getTotalActiveEscrowValue(e);
uint256 balance = usdc.balanceOf(currentContract);
assert balance >= totalActive,
"Contract balance must cover all active escrows";
}
// Rule: Fees are always 2%
rule feesAreExactly2Percent {
env e;
uint256 amount;
mathint expectedFees = (amount * 2) / 100;
mathint actualFees = calculateFees(e, amount);
assert actualFees == expectedFees,
"Fees must be exactly 2% of escrow amount";
}
// Rule: Status transitions are monotonic
rule statusNeverGoesBackward {
env e;
bytes32 jobId;
uint8 statusBefore = getStatus(e, jobId);
method f;
calldataarg args;
f(e, args);
uint8 statusAfter = getStatus(e, jobId);
assert statusAfter >= statusBefore,
"Status can only progress forward";
}Running Certora
# Install Certora
pip install certora-cli
# Run specifications
certoraRun certora/conf/Escrow.conf
# Check all specs
certoraRun certora/conf/*.confVerification Results
Certora Verification Report
============================
Rules verified: 12/12 ✅
✓ balanceEqualsActiveEscrows
✓ feesAreExactly2Percent
✓ statusNeverGoesBackward
✓ noReentrancy
✓ accessControlEnforced
✓ upgradeOnlyByAdmin
✓ fundsNeverLocked
✓ disputeWindowEnforced
✓ timingBoundsRespected
✓ scoreCalculationCorrect
✓ reviewerPaymentsFair
✓ stakingReserveRatioMaintained
Time: 45 minutesFormal Verification provides mathematical certainty that these properties hold for all possible inputs and states, not just tested examples.
6. Static Analysis
Slither
# Run Slither static analyzer
slither contracts/
# Output (no High/Medium findings)
INFO:Slither:✓ Compilation successful
INFO:Detectors:
- No high or medium severity issues found
- 3 informational findings (gas optimizations)
- 2 low severity findings (acknowledged)Mythril
# Symbolic execution with Mythril
myth analyze contracts/AbbababaEscrowV1.sol
# Results: No vulnerabilities foundTest Metrics
Coverage
File | % Stmts | % Branch | % Funcs | % Lines |
------------------------------|---------|----------|---------|---------|
AbbababaEscrowV1.sol | 98.5 | 94.2 | 100.0 | 98.1 |
AbbababaScoreV1.sol | 97.8 | 91.7 | 100.0 | 97.3 |
AbbababaResolverV1.sol | 96.3 | 88.9 | 95.2 | 95.8 |
ReviewerPaymentV1.sol | 98.1 | 93.3 | 100.0 | 97.9 |
AbbababaStakingV1.sol | 97.2 | 90.1 | 100.0 | 96.8 |
------------------------------|---------|----------|---------|---------|
All files | 97.6 | 91.6 | 99.0 | 97.2 |Test Execution Time
Unit Tests: 6s
Invariant Tests: 12s
Fuzz Tests (100k): 45s
Mutation Testing: 8 minutes
Certora: 45 minutes
---------------------------
Total Suite: ~55 minutesRunning the Full Test Suite
# Clone repository
git clone <repo-url>
cd contracts
# Install dependencies
npm install
# Run all tests
npm run test:all
# Individual test suites
npm run test:unit # Unit tests
npm run test:invariant # Invariant tests
npm run test:fuzz # Fuzz tests
npm run test:mutation # Mutation tests
npm run test:certora # Formal verification (requires Certora key)
npm run test:coverage # Coverage reportContinuous Integration
All tests run automatically on every commit and PR:
# .github/workflows/test.yml
name: Smart Contract Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Unit Tests
run: npm run test:unit
- name: Invariant Tests
run: npm run test:invariant
- name: Fuzz Tests
run: npm run test:fuzz
- name: Coverage
run: npm run test:coverage
mutation:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Mutation Testing
run: npm run test:mutationRelated Documentation
- Audit Report — Full security audit results
- Methodology — Audit process and tools
- Economic Analysis — Game theory verification
- Findings — Detailed vulnerability findings
- Smart Contracts — Contract implementations
Strong Test Coverage: With 90% mutation kill rate, comprehensive fuzz testing, and formal verification of critical properties, these contracts demonstrate high confidence for testnet deployment. Additional mutation tests are being developed to approach 100% coverage before mainnet.