Skip to content

[SC-2] Comprehensive Foundry Test Suite for FishnetWallet #32

@iamyxsh

Description

@iamyxsh

[SC-2] Comprehensive Foundry Test Suite for FishnetWallet

Labels: smart-contract, testing, priority:high, week-3-4
Assignee: Yash


Context

Per the Source of Truth (Section 10, Week 3-4 deliverable), the FishnetWallet needs a comprehensive Foundry test suite covering all permit validation paths, owner functions, and edge cases. The contract is security-critical — it gates real fund execution.


Test File

File: contracts/test/FishnetWallet.t.sol

1. Test Setup

  • Deploy FishnetWallet with test owner and test signer addresses
  • Fund wallet with test ETH (e.g., vm.deal)
  • Deploy a mock target contract (simple receive() + a state-changing function)
  • Helper function: _signPermit(FishnetPermit memory permit, uint256 signerPrivateKey) — generates valid EIP712 signature off-chain using vm.sign
  • Helper function: _buildPermit(...) — constructs permit struct with defaults

2. Happy Path Tests

  • test_executeValidPermit — sign valid permit, call execute(), verify:
    • Target contract receives the call with correct calldata
    • Target receives correct ETH value
    • Nonce is marked as used
    • ActionExecuted event emitted with correct args
  • test_executeMultiplePermitsSequentially — execute 3 permits with incrementing nonces, all succeed
  • test_executeWithZeroValue — permit with value = 0 (pure function call, no ETH transfer)
  • test_executeComplexCalldata — permit with realistic swap calldata (Uniswap-like selector + params)

3. Permit Validation Failure Tests

Each test verifies the specific revert reason:

  • test_revert_expiredPermit — set permit.expiry to block.timestamp - 1, expect revert "permit expired"
  • test_revert_usedNonce — execute once with nonce=1, try again with nonce=1, expect revert "nonce already used"
  • test_revert_targetMismatchpermit.target = addressA, call execute(addressB, ...), expect revert "target mismatch"
  • test_revert_calldataMismatchpermit.calldataHash = keccak256(dataA), call with dataB, expect revert "calldata mismatch"
  • test_revert_walletMismatchpermit.wallet = otherAddress (not this contract), expect revert "wallet mismatch"
  • test_revert_invalidSignature — sign with a random key (not fishnetSigner), expect revert "invalid signature"
  • test_revert_zeroAddressSigner — signature that recovers to address(0), expect revert

4. Pause Tests

  • test_revert_executeWhenPaused — owner pauses wallet, valid permit still reverts with "wallet paused"
  • test_executeAfterUnpause — pause, unpause, then execute succeeds
  • test_revert_pauseByNonOwner — non-owner calls pause(), expect revert "not owner"
  • test_pauseEmitsEventpause() emits Paused event
  • test_unpauseEmitsEventunpause() emits Unpaused event

5. Owner Function Tests

  • test_setSigner — owner calls setSigner(newSigner):
    • Old signer's permits no longer work
    • New signer's permits work
    • SignerUpdated event emitted
  • test_revert_setSignerByNonOwner — non-owner calls setSigner, expect revert
  • test_withdraw — owner withdraws ETH, verify balance transferred
  • test_revert_withdrawByNonOwner — non-owner calls withdraw, expect revert
  • test_receiveETH — send ETH directly to wallet, verify balance increases

6. Edge Case Tests

  • test_revert_executionFailure — target contract reverts (e.g., revert("nope")), outer call reverts with "execution failed"
  • test_executeExactExpirypermit.expiry == block.timestamp (boundary condition), should succeed
  • test_nonceOrdering — nonces don't need to be sequential (nonce=5, then nonce=2, then nonce=99 all work if unused)
  • test_reentryProtection — target contract tries to re-enter execute() during callback, verify behavior (should fail due to nonce already used)
  • test_largeNoncenonce = type(uint256).max, verify it works
  • test_chainIdMismatch — permit signed with wrong chainId, signature recovery produces wrong address, expect revert

7. Gas Benchmarks

  • test_gasExecute — measure gas for a standard execute call, log it
  • test_gasDeniedPermit — measure gas for a denied (expired) permit, log it
  • Ensure execute cost is reasonable (target: < 80k gas excluding target call)

Acceptance Criteria

  • All tests pass with forge test -vvv
  • Every revert path has a dedicated test verifying the exact error message
  • No test relies on hardcoded block numbers or timestamps (use vm.warp, vm.roll)
  • Happy path tests verify both state changes and events
  • Edge cases cover boundary conditions (exact expiry, max nonce, zero value)
  • Gas benchmarks documented for core operations

Metadata

Metadata

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions