ComposableCoW follows Foundry best practices with unit tests, fuzz tests, and fork tests to ensure contract reliability and security.
Test Structure
The project includes three types of tests:
- Unit Tests: Test individual contract functions in isolation
- Fuzz Tests: Randomized input testing to discover edge cases
- Fork Tests: End-to-end testing against mainnet contract forks
Prerequisites
Before running tests, ensure you have:
- Foundry installed
- Environment variables configured (copy
.env.example to .env)
- An Ethereum RPC URL for fork tests (archive node recommended)
ETH_RPC_URL=http://your-archive-node:8545
PRIVATE_KEY=your_private_key_here
SETTLEMENT=0x9008D19f58AAbD9eD0D60971565AA8510560ab41
Fork tests require ETH_RPC_URL to be set to an archive node for full historical state access.
Running Tests
Unit Tests Only
Run basic unit tests without fuzz or fork tests:
forge test -vvv --no-match-test "fork|[fF]uzz"
This excludes both fork tests and fuzz tests, running only isolated unit tests.
Unit + Fuzz Tests
Include fuzzing tests but skip fork tests:
forge test -vvv --no-match-test "fork"
Fuzz tests include simulate functions that run full end-to-end integration testing, including conditional order settlement.
All Tests (Unit + Fuzz + Fork)
Run the complete test suite including fork tests:
Fork tests simulate against production Ethereum mainnet contracts and require an archive node RPC endpoint.
Test Verbosity
Control test output detail with verbosity flags:
-v: Show test results
-vv: Show test results and console.log output
-vvv: Show test results, logs, and stack traces for failing tests
-vvvv: Show stack traces for all tests and setup
-vvvvv: Show full stack traces with decoded data
# Minimal output
forge test -v
# Detailed output with stack traces
forge test -vvvv
Test Coverage
Generate a coverage report for the test suite:
forge coverage -vvv --no-match-test "fork" --report summary
Fork tests are excluded from coverage reports to avoid external contract noise.
For detailed coverage output:
# Generate detailed HTML coverage report
forge coverage --no-match-test "fork" --report lcov
genhtml lcov.info -o coverage/
Test Organization
Tests are organized by functionality:
test/
├── Base.t.sol # Base test setup and utilities
├── ComposableCoW.t.sol # Core ComposableCoW tests
├── ComposableCoW.base.t.sol # Shared test infrastructure
├── ComposableCoW.twap.t.sol # TWAP order type tests
├── ComposableCoW.gat.t.sol # GoodAfterTime tests
├── ComposableCoW.stoploss.t.sol # StopLoss tests
├── ComposableCoW.tat.t.sol # TradeAboveThreshold tests
├── ComposableCoW.guards.t.sol # Guard functionality tests
├── ComposableCoW.forwarder.t.sol # Order forwarding tests
├── helpers/ # Test helper contracts
│ ├── Safe.t.sol
│ ├── CoWProtocol.t.sol
│ └── Tokens.t.sol
└── libraries/ # Test libraries
├── ComposableCoWLib.t.sol
├── SafeLib.t.sol
└── TestAccountLib.t.sol
Writing Tests
Tests inherit from Base.t.sol which provides:
- Test accounts (alice, bob, carol)
- Pre-deployed Safe contracts
- CoW Protocol infrastructure
- Token contracts and balances
Example Unit Test
contract MyTest is BaseComposableCoWTest {
function setUp() public override {
super.setUp();
}
function test_createOrder() public {
// Test implementation
IConditionalOrder.ConditionalOrderParams memory params =
IConditionalOrder.ConditionalOrderParams({
handler: address(twapHandler),
salt: keccak256("unique-salt"),
staticInput: abi.encode(twapData)
});
composableCow.create(params, true);
// Assertions
assertTrue(composableCow.singleOrders(address(safe1), orderHash));
}
}
Example Fuzz Test
function testFuzz_setRoot(address owner, bytes32 root) public {
vm.assume(owner != address(0));
composableCow.setRoot(root, ComposableCoW.Proof({location: 0, data: ""}));
assertEq(composableCow.roots(owner), root);
}
Continuous Integration
The project includes a CI profile in foundry.toml:
[profile.ci]
verbosity = 3
Use this profile in CI/CD pipelines:
FOUNDRY_PROFILE=ci forge test --no-match-test "fork"
Debugging Tests
Using Console Logs
import {console} from "forge-std/console.sol";
function test_debug() public {
console.log("Order hash:", orderHash);
console.logAddress(safe1);
}
Using Traces
Inspect call traces for failing tests:
forge test --match-test test_failing -vvvv
Gas Snapshots
Create gas usage snapshots:
forge snapshot --no-match-test "fork"
Compare gas usage:
forge snapshot --diff .gas-snapshot
Common Test Patterns
Testing Reverts
function test_revertOnInvalidParams() public {
vm.expectRevert(ComposableCoW.InvalidParams.selector);
composableCow.create(invalidParams, true);
}
Testing Events
function test_emitsEvent() public {
vm.expectEmit(true, true, true, true);
emit ConditionalOrderCreated(address(safe1), orderHash);
composableCow.create(params, true);
}
Testing with Time Warps
function test_orderValidAfterTime() public {
// Move forward 1 hour
vm.warp(block.timestamp + 3600);
(GPv2Order.Data memory order,) =
composableCow.getTradeableOrderWithSignature(
address(safe1), params, bytes(""), new bytes32[](0)
);
assertEq(order.validTo, block.timestamp + 1800);
}
Speed up test execution:
# Run tests in parallel
forge test --no-match-test "fork" -j 4
# Skip specific slow tests
forge test --no-match-test "fork|slow"
Integration Testing
For integration testing with external services like Watch Tower, see the local deployment guide.