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
- Unit Tests
- Integration Tests
- State-Focused Integration Tests
- Behavior-Focused Integration Tests
- Fuzzing Tests
- Gas Optimization Tests
- Upgrade Tests
- 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:
- Unit Tests: Colocated with the function implementation in the same file under the
src
directory.
- Integration Tests: Located in separate files under the
test
directory.
- Upgrade Tests: Located in the
test
directory, typically in a file named UpgradeTest.sol
or similar.
- 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:
pragma solidity ^0.8.24;
import "...";
contract SomeFunction {
}
import {MCTest} from "@mc-devkit/Flattened.sol";
contract SomeFunctionTest is MCTest {
}
Test Function Naming Convention
Use the following format for test function names:
test_[MethodName]_[ExpectedBehavior]_[TestCondition]()
Example:
function test_propose_success_withValidInput() public {
}
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);
}
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);
}
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 {
}
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();
uint256 gasUsed = gasStart - gasleft();
uint256 expectedMaxGas = inputSize * 5000;
assertLt(gasUsed, expectedMaxGas, "Gas usage exceeds expected maximum");
}
Upgrade Tests
Ensure that contract upgrades maintain expected functionality:
- Test the upgrade process itself
- Verify that existing state is preserved after an upgrade
- Test new functionality introduced in the upgrade
- Ensure that existing functionality continues to work as expected post-upgrade
Example:
function testUpgrade() public {
textDAO.upgrade(newImplementationAddress);
}
Snapshot Tests
Use Foundry's snapshot feature to capture and verify the state of the system at specific points:
function testSnapshot() public {
textDAO.propose("Test Proposal", new Schema.Action[](0));
uint256 snapshotId = vm.snapshot();
textDAO.vote(0, someVote);
textDAO.tally(0);
vm.revertTo(snapshotId);
}
Best Practices
- Use descriptive test names that clearly indicate what is being tested.
- Write both positive and negative test cases.
- Use mock contracts when testing interactions with external contracts.
- Leverage MC DevKit's capabilities for state management and testing.
- Keep tests independent and idempotent.
- Use setup functions to initialize common test scenarios.
Assertion Best Practices
- Use specific assertion messages to provide clear feedback on test failures.
- Prefer equality assertions (
assertEq
) over boolean assertions when possible.
- 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:
- Use Foundry's coverage reports to identify untested code paths.
- Aim for 100% coverage of critical contract logic.
- Write tests that cover edge cases and boundary conditions.
Continuous Integration
Integrate testing into the CI/CD pipeline:
- Run all tests as part of the CI process.
- Include gas usage checks in CI to catch performance regressions.
- Use static analysis tools (e.g., Slither) in conjunction with tests.
Test Maintenance
- Review and update tests when contract logic changes.
- Regularly run the full test suite to catch regressions.
- Refactor tests as needed to improve clarity and reduce duplication.
MC DevKit Specific Testing
Leverage MC DevKit's features for enhanced testing:
- Use State Fuzzing for comprehensive state testing.
- Utilize MC DevKit's storage management utilities in tests.
- Implement custom state generators for complex scenarios.
Example:
function test_complexScenario_withCustomState() public {
Schema.Deliberation memory delib = generateCustomDeliberation();
}
function generateCustomDeliberation() internal returns (Schema.Deliberation memory) {
}
Security-Focused Testing
- Implement specific tests for known vulnerabilities (e.g., reentrancy, integer overflow).
- Use symbolic execution tools to identify potential security issues.
- Test access control and permission systems thoroughly.
For critical operations, include performance tests:
- Measure execution time and resource usage.
- Set performance benchmarks and ensure they are met in each release.
- Test performance under various network conditions and gas prices.
Documentation
- Include inline comments explaining the purpose and setup of complex tests.
- 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.