Skip to main content

Overview

This guide covers testing strategies for EVM CCTP Contracts, including unit tests, integration tests, linting, and static analysis.

Prerequisites

  • Foundry CLI installed (forge 0.2.0)
  • Git submodules initialized: git submodule update --init --recursive
  • Yarn installed: yarn install

Unit Tests

Running Tests

Run unit tests using the Foundry forge CLI:
forge test
Alternatively, run tests in a Docker container:
make test

Test Output

Successful test output:
[PASS] testDepositForBurn() (gas: 123456)
[PASS] testReceiveMessage() (gas: 234567)
Test result: ok. 42 passed; 0 failed; finished in 2.34s

Debug Logs

Control test verbosity using the -v flag to display logs and traces:

Verbosity Levels

FlagOutput LevelDescription
-vBasicShows test results
-vvDetailedShows console.log() statements
-vvvVerboseShows execution traces
-vvvvVery VerboseShows execution traces with stack
-vvvvvMaximumShows all execution details

Examples

Display console.log statements:
forge test -vv
Show full execution traces:
forge test -vvvv
Test specific contract:
forge test --match-contract MessageTransmitterTest -vv
Test specific function:
forge test --match-test testDepositForBurn -vvv

Adding Debug Logs

To use console.log() in your contracts, import the console library:
import "lib/forge-std/src/console.sol";

contract MyContract {
    function myFunction() public {
        console.log("Debug value:", someValue);
    }
}

Integration Tests

Integration tests run against a local Anvil test node and simulate full cross-chain scenarios.

Running Integration Tests

make anvil-test
This command:
  1. Starts an Anvil test node in a Docker container
  2. Deploys all contracts
  3. Runs integration test suite
  4. Cleans up resources

Example Integration Test

An example integration test is available in the anvil/ folder that demonstrates:
  • Deploying contracts to local testnet
  • Configuring cross-chain messaging
  • Executing full deposit and burn flow
  • Simulating attestation
  • Receiving messages on destination chain

Manual Integration Testing

Start Anvil manually for interactive testing:
# Start Anvil node
anvil

# In another terminal, deploy contracts
forge script scripts/v1/deploy.s.sol --rpc-url http://localhost:8545 --broadcast

# Run your tests
forge test --fork-url http://localhost:8545

Linting

Lint Solidity code to enforce style guidelines and catch common issues:
yarn lint
This checks all .sol files in the src/ and test/ directories.

Linting Configuration

Linting rules are defined in .solhint.json. Common checks include:
  • Code style and formatting
  • Best practice violations
  • Security anti-patterns
  • Gas optimization opportunities

Fixing Linting Issues

Some linting issues can be auto-fixed:
yarn lint:fix

Common Linting Rules

Naming Conventions:
// Bad
function MyFunction() public {}

// Good
function myFunction() public {}
Visibility Modifiers:
// Bad
function transfer() {}

// Good
function transfer() external {}
Import Organization:
// Good - imports at top, organized
import "./interfaces/IReceiver.sol";
import "./libraries/Message.sol";

Static Analysis

Run static analysis using Mythril to detect security vulnerabilities:

Analyzing Contracts

MessageTransmitter:
make analyze-message-transmitter
MessageTransmitterV2:
make analyze-message-transmitter-v2
TokenMessengerMinter:
make analyze-token-messenger-minter

Analyzing Individual Files

If Mythril is already installed:
myth -v4 analyze $FILE_PATH \
  --solc-json mythril.config.json \
  --solv 0.7.6
Example:
myth -v4 analyze src/MessageTransmitter.sol \
  --solc-json mythril.config.json \
  --solv 0.7.6
Static analysis can take several minutes to complete, especially for complex contracts.

Understanding Results

Mythril reports potential issues with severity levels:
  • High: Critical security vulnerabilities
  • Medium: Significant issues that should be addressed
  • Low: Minor issues or informational warnings

Continuous Integration

GitHub Actions automatically runs tests on every push and pull request.

CI Workflow

The workflow configuration is in .github/workflows/ci.yml and includes:
  1. Linting checks
  2. Unit tests
  3. Integration tests
  4. Code coverage reports

Viewing CI Results

Check CI status:
  1. Navigate to the repository on GitHub
  2. Click the “Actions” tab
  3. Select the workflow run to view details

Local CI Simulation

Run the same checks locally before pushing:
# Run all checks
yarn lint && forge test && make anvil-test

Security Scanning

Olympix AI Scanning

Manually trigger Olympix.ai security scanning:
1

Navigate to Actions

Click on the “Actions” tab in your GitHub repository
2

Select Olympix Scan

In the left sidebar, select “Olympix Scan”
3

Run Workflow

Select the branch and click “Run workflow”
4

Review Results

Wait for the scan to complete and review security alerts

Test Coverage

Generate test coverage reports:
forge coverage
View detailed coverage:
forge coverage --report lcov

Coverage Goals

  • Critical paths: 100% coverage
  • Core contracts: >95% coverage
  • Utility functions: >90% coverage
  • Overall project: >90% coverage

Testing Best Practices

Write Comprehensive Tests

function testDepositForBurn() public {
    // Setup
    uint256 amount = 1000e6;
    
    // Execute
    uint64 nonce = tokenMessenger.depositForBurn(
        amount,
        destinationDomain,
        recipient,
        token
    );
    
    // Assert
    assertEq(nonce, 1);
    assertEq(usdc.balanceOf(address(this)), 0);
}

Test Edge Cases

  • Zero amounts
  • Maximum values
  • Invalid addresses
  • Unauthorized callers
  • Paused state
  • Reentrancy scenarios

Use Fuzz Testing

function testFuzz_depositForBurn(uint256 amount) public {
    vm.assume(amount > 0 && amount <= maxAmount);
    
    tokenMessenger.depositForBurn(
        amount,
        destinationDomain,
        recipient,
        token
    );
    
    // Assertions
}

Mock External Dependencies

function setUp() public {
    // Deploy mocks
    mockAttester = new MockAttester();
    
    // Deploy contracts with mocks
    messageTransmitter = new MessageTransmitter(
        address(mockAttester)
    );
}

Troubleshooting

Tests Fail to Compile

  • Check Solidity version (should be 0.7.6)
  • Verify all dependencies are installed
  • Run git submodule update --init --recursive

Tests Timeout

  • Increase timeout in foundry.toml
  • Check for infinite loops in contracts
  • Verify RPC connection is stable

Coverage Reports Empty

  • Ensure tests are passing first
  • Check that test files are in test/ directory
  • Verify forge coverage is configured correctly

Next Steps

Build docs developers (and LLMs) love