Skip to main content

Test Strategy

This document outlines the comprehensive test strategy for the Meta Contract smart contracts. It provides guidelines for writing and maintaining tests, ensuring code quality, and optimizing gas usage.

Test Types

  1. Unit Tests
  2. Integration Tests
  3. State-Focused Integration Tests
  4. Behavior-Focused Integration Tests
  5. Fuzzing Tests
  6. Gas Optimization Tests
  7. Upgrade Tests
  8. Snapshot Tests

Test Location

Given the structure of the Meta Contract where functions are separated into individual files, we adopt the following test location strategy:

  1. Unit Tests: Colocated with the function implementation in the same file under the src directory.
  2. Integration Tests: Located in separate files under the test directory.
  3. Upgrade Tests: Located in the test directory, typically in a file named UpgradeTest.sol or similar.
  4. Snapshot Tests: Incorporated into State-Focused Integration Tests or other test types as needed.

Test File Structure

A typical Solidity file containing a function implementation and its unit tests should have the following structure:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "...";

contract SomeFunction {
// Function implementation
}

// Testing
import {MCTest} from "@mc-devkit/Flattened.sol";

contract SomeFunctionTest is MCTest {
// Unit tests
}

Test Function Naming Convention

Use the following format for test function names:

test_[MethodName]_[ExpectedBehavior]_[TestCondition]()

Example:

function test_propose_success_withValidInput() public {
// Test implementation
}

Integration Testing Strategy

State-Focused Integration Tests

These tests utilize MC's State Fuzzing capabilities to test individual functions and state transitions.

Example:

function test_someStateCondition() public {
Schema.Proposal storage $proposal = Storage.Deliberation().getProposal(0);
// ... perform state modifications and assertions
}

Behavior-Focused Integration Tests

These tests simulate end-to-end flows and specific use cases of the TextDAO system from an end-user perspective.

Example:

function test_fullProposalLifecycle() public {
uint256 proposalId = textDAO.propose("Test Proposal", new Schema.Action[](0));
textDAO.vote(proposalId, someVote);
// ... other interactions and assertions
}

Fuzzing Tests

For fuzz tests, include "Random" in the name and describe the varying input:

function test_vote_success_withRandomVoteValues(uint8 rnd_headerChoice, uint8 rnd_commandChoice) public {
// Implement test with random inputs
}

Gas Optimization Testing

Include gas usage checks for potentially heavy operations:

function test_heavyOperation_gasUsage(uint256 inputSize) public {
vm.assume(inputSize > 0 && inputSize <= 1000);

uint256 gasStart = gasleft();
// Perform the heavy operation
uint256 gasUsed = gasStart - gasleft();

uint256 expectedMaxGas = inputSize * 5000; // Adjust based on operation complexity
assertLt(gasUsed, expectedMaxGas, "Gas usage exceeds expected maximum");
}

Upgrade Tests

Ensure that contract upgrades maintain expected functionality:

  1. Test the upgrade process itself
  2. Verify that existing state is preserved after an upgrade
  3. Test new functionality introduced in the upgrade
  4. Ensure that existing functionality continues to work as expected post-upgrade

Example:

function testUpgrade() public {
// Set up initial state
// ...

// Perform upgrade
textDAO.upgrade(newImplementationAddress);

// Verify state preservation
// ...

// Test new functionality
// ...

// Verify existing functionality
// ...
}

Snapshot Tests

Use Foundry's snapshot feature to capture and verify the state of the system at specific points:

function testSnapshot() public {
// Initial setup
textDAO.propose("Test Proposal", new Schema.Action[](0));

uint256 snapshotId = vm.snapshot();

// Modify state
textDAO.vote(0, someVote);
textDAO.tally(0);

// Verify changed state
// ... assertions ...

// Revert to snapshot
vm.revertTo(snapshotId);

// Verify reverted state
// ... assertions ...
}

Best Practices

  1. Use descriptive test names that clearly indicate what is being tested.
  2. Write both positive and negative test cases.
  3. Use mock contracts when testing interactions with external contracts.
  4. Leverage MC DevKit's capabilities for state management and testing.
  5. Keep tests independent and idempotent.
  6. Use setup functions to initialize common test scenarios.

Assertion Best Practices

  1. Use specific assertion messages to provide clear feedback on test failures.
  2. Prefer equality assertions (assertEq) over boolean assertions when possible.
  3. Use fuzzing to test with a wide range of inputs.

Example:

function test_calculateScore_success(uint256 votes) public {
vm.assume(votes > 0 && votes <= 1000000);
uint256 expectedScore = votes * 2;
uint256 actualScore = textDAO.calculateScore(votes);
assertEq(actualScore, expectedScore, "Score calculation incorrect");
}

Code Coverage

Aim for high code coverage, but remember that coverage alone doesn't guarantee comprehensive testing:

  1. Use Foundry's coverage reports to identify untested code paths.
  2. Aim for 100% coverage of critical contract logic.
  3. Write tests that cover edge cases and boundary conditions.

Continuous Integration

Integrate testing into the CI/CD pipeline:

  1. Run all tests as part of the CI process.
  2. Include gas usage checks in CI to catch performance regressions.
  3. Use static analysis tools (e.g., Slither) in conjunction with tests.

Test Maintenance

  1. Review and update tests when contract logic changes.
  2. Regularly run the full test suite to catch regressions.
  3. Refactor tests as needed to improve clarity and reduce duplication.

MC DevKit Specific Testing

Leverage MC DevKit's features for enhanced testing:

  1. Use State Fuzzing for comprehensive state testing.
  2. Utilize MC DevKit's storage management utilities in tests.
  3. Implement custom state generators for complex scenarios.

Example:

function test_complexScenario_withCustomState() public {
Schema.Deliberation memory delib = generateCustomDeliberation();
// ... use the custom state in your test
}

function generateCustomDeliberation() internal returns (Schema.Deliberation memory) {
// Generate and return a custom Deliberation state
}

Security-Focused Testing

  1. Implement specific tests for known vulnerabilities (e.g., reentrancy, integer overflow).
  2. Use symbolic execution tools to identify potential security issues.
  3. Test access control and permission systems thoroughly.

Performance Testing

For critical operations, include performance tests:

  1. Measure execution time and resource usage.
  2. Set performance benchmarks and ensure they are met in each release.
  3. Test performance under various network conditions and gas prices.

Documentation

  1. Include inline comments explaining the purpose and setup of complex tests.
  2. Maintain a separate document describing the overall test strategy and any test-specific setup required.

Conclusion

This test strategy provides a comprehensive approach to ensuring the quality, security, and performance of the TextDAO smart contracts. By following these guidelines and leveraging the power of MC DevKit and Foundry, developers can create robust and reliable tests that contribute to the overall stability of the TextDAO ecosystem.

Remember that testing is an ongoing process. As new features are added and the system evolves, the test suite should be continuously updated and expanded to maintain comprehensive coverage.