- Introduction To testing
- Foundry Cheatcodes
- Fork Testing
- Fuzz Testing
- Invariant Testing
- Differential Testing
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {Test, console2} from "forge-std/Test.sol";
import {Counter} from "../src/Counter.sol";
contract CounterTest is Test {
Counter public counter;
function setUp() public {
counter = new Counter();
counter.setNumber(0);
}
function test_Increment() public {
counter.increment();
assertEq(counter.number(), 1);
}
function testFuzz_SetNumber(uint256 x) public {
counter.setNumber(x);
assertEq(counter.number(), x);
}
function testFail() public{
counter.setNumber(type(uint256).max);
}
}Key points:
- Line4: Import the
Testcontract; - line7: Inheriting the
TestContract; - line10: An optional function
setUpinvoked before each test case is run; - line15:
test: Functions prefixed withtestare run as a test case; - line20:
testFail: The inverse of thetestprefix - if the function does not revert, the test fails.
forge test --summary
[⠒] Compiling...
No files changed, compilation skipped
Running 2 tests for test/Counter.t.sol:CounterTest
[PASS] testFuzz_SetNumber(uint256) (runs: 256, μ: 27553, ~: 28409)
[PASS] test_Increment() (gas: 28379)
Test result: ok. 2 passed; 0 failed; 0 skipped; finished in 44.46ms
Ran 1 test suites: 2 tests passed, 0 failed, 0 skipped (2 total tests)
Test Summary:
╭-------------+--------+--------+---------╮
| Test Suite | Passed | Failed | Skipped |
+=========================================+
| CounterTest | 2 | 0 | 0 |
╰-------------+--------+--------+---------╯Usage: forge test [OPTIONS]
Options:
-h, --help Print help (see more with '--help')
Test options:
--debug <TEST_FUNCTION> Run a test in the debugger
--gas-report Print a gas report [env: FORGE_GAS_REPORT=]
--allow-failure Exit with code 0 even if a test fails [env: FORGE_ALLOW_FAILURE=]
--fail-fast Stop running tests after the first failure
--etherscan-api-key <KEY>
--fuzz-seed <FUZZ_SEED> Set seed used to generate randomness during your fuzz runs
--fuzz-runs <RUNS> [env: FOUNDRY_FUZZ_RUNS=]
Display options:
-j, --json Output test results in JSON format
-l, --list List tests instead of running them
--summary Print test summary table
--detailed Print detailed test summary table
Test filtering:
--match-test <REGEX> Only run test functions matching the specified regex pattern [aliases: mt]
--no-match-test <REGEX> Only run test functions that do not match the specified regex pattern [aliases: nmt]
--match-contract <REGEX> Only run tests in contracts matching the specified regex pattern [aliases: mc]
--no-match-contract <REGEX> Only run tests in contracts that do not match the specified regex pattern [aliases: nmc]
--match-path <GLOB> Only run tests in source files matching the specified glob pattern [aliases: mp]
--no-match-path <GLOB> Only run tests in source files that do not match the specified glob pattern [aliases: nmp]
Watch options:
-w, --watch [<PATH>...] Watch the given files or directories for changes
--no-restart Do not restart the command while it's still running
--run-all Explicitly re-run all tests when a change is made
--watch-delay <DELAY> File update debounce delay- ❓How to call contract with Bob wallet?
- ❓How to reset and get Bob's ETH balance?
- ❓How to change block number or block timestamp?
- ❓How to check revert message ?
- ❓How to check event data?
Cheatcodes give you powerful assertions, the ability to alter the state of the EVM, mock data, and more.
https://book.getfoundry.sh/cheatcodes/
Foundry gives you complete control !
- Std Logs: Expand upon the logging events from the DSTest library.
- Std Assertions: Expand upon the assertion functions from the DSTest library.
- Std Cheats: Wrappers around Forge cheatcodes for improved safety and DX.
- Std Errors: Wrappers around common internal Solidity errors and reverts.
- Std Storage: Utilities for storage manipulation.
- Std Math: Useful mathematical functions.
- Script Utils: Utility functions which can be accessed in tests and scripts.
- Console Logging: Console logging functions.
function testStd() public {
address alice = makeAddr("alice");
// Log with the Hardhat `console` (`console2`)
console.log("Alice:", alice);
// output: Alice: 0x328809Bc894f92807417D2dAD6b7C998c1aFdac6
// Assert and log using Dappsys Test
assertEq(token.balanceOf(alice), 0, "expect alice balance is zero");
emit log_named_uint("Amount:", 1 ether);
//output: Amount: 1000000000000000000
// Use the alternative signature for ERC20 tokens
deal(address(token), alice, 10000e18);
// check that alice has 10000 tokens
assertEq(token.balanceOf(alice), 10000e18);
// Returns the difference between two numbers in percentage, where 1e18 is 100%.
uint256 delta = stdMath.percentDelta(uint256(102), 100);
assertEq(delta, 0.02 * 1e18, "expect diff == 0.02");
// Use utils
address nonce1Addr = vm.computeCreateAddress(alice, 1);
console.log("Nonce1 addr:", nonce1Addr);
// output: Nonce1 addr: 0x9c1eF3D4c320eC7ecF88c8e8a8f47DB2af5c69b8
// Sets all subsequent calls' msg.sender to alice until `stopPrank` is called
vm.startPrank(alice);
new MockERC20(); // nonce = 0
MockERC20 token1 = new MockERC20(); // nonce=1
assertEq(nonce1Addr, address(token1));
}❓ Why need a forked environment?
- Debug mainnet contract
- Interact with third-party contracts, such as Uniswap.
Forge supports testing in a forked environment with two different approaches:
- Forking Mode — use a single fork for all your tests via the
forge test --fork-url xxxflag - Forking Cheatcodes — create, select, and manage multiple forks directly in Solidity test code via forking cheatcodes
❓ Which approach to use?
Example: Play the game, the winner gets a prize.
-
Have you found the bug❓
-
How to use fuzz testing to test it and find bugs❓
contract Lotto {
bool public payedOut = false;
uint256 public winAmount;
// ... extra functionality here
function play() public payable {
winAmount += msg.value;
}
function sendToWinner(address winner) public {
require(!payedOut);
// send ETH to winner
payable(winner).send(winAmount);
payedOut = true; // play end
}
function withdrawLeftOver() public {
require(payedOut);
payable(msg.sender).transfer(address(this).balance);
}
}Do you have any questions ❓
forge test --match-contract FuzzTest
[⠔] Compiling...
No files changed, compilation skipped
Running 1 tests for test/HowToTest/05.FuzzTest.sol:FuzzTest
[PASS] testFuzzSendToWinner(address) (runs: 256, μ: 96784, ~: 96892) How to set up test ❓
What is invariant testing ❓
Invariant (properties or conditions) remains unchanged during the execution of the code. more Invariant
Examples:
- "The xy=k formula always holds" for Uniswap
- "The sum of all user balances is equal to the total supply" for an ERC-20 token.
- "The wallet's debt is always less than 90% of their collateral." for an lending.
- "Where is "
- more ....
What is differential testing ❓
It is a method of comparing the outputs of different software implementations or versions to identify discrepancies and ensure consistency.
Examples:
- Comparison between versions:
- Ensure that the result of 'balanceOf(account)' is equivalent between protocol version 1 and version 2.
- Comparison of different implementations:
- Implementation of the F(x) formula by f1(x) and f2(x): check
log(x)between solidity and python.
- Implementation of the F(x) formula by f1(x) and f2(x): check
- Cross-platform testing
- Use Foundry to test Exercise01.sol and identify bugs in them
- Use Foundry to test your NFTMarket.sol