🔍 Contract AuditTest Coverage

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-x402

90% 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

ComponentTestsCoverage
Escrow Lifecycle15State transitions, funds flow
Trust Score Calculation12Points, bonding, registration
Dispute Resolution103-tier system, outcomes
Reviewer Payments9Distribution logic, edge cases

Running Unit Tests

cd contracts
npm test
 
# With coverage
npm run test:coverage

Example 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 -vvv

3. 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.yaml

4. 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: COMPLETE

5. 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/*.conf

Verification Results

Certora Verification Report
============================
Rules verified: 12/12 ✅

✓ balanceEqualsActiveEscrows
✓ feesAreExactly2Percent
✓ statusNeverGoesBackward
✓ noReentrancy
✓ accessControlEnforced
✓ upgradeOnlyByAdmin
✓ fundsNeverLocked
✓ disputeWindowEnforced
✓ timingBoundsRespected
✓ scoreCalculationCorrect
✓ reviewerPaymentsFair
✓ stakingReserveRatioMaintained

Time: 45 minutes

Formal 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 found

Test 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 minutes

Running 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 report

Continuous 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:mutation


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.