Skip to content

Commit ea5b9fd

Browse files
authored
Merge pull request #1418 from HackTricks-wiki/update_Use_mutation_testing_to_find_the_bugs_your_tests_d_20250918_124237
Use mutation testing to find the bugs your tests don't catch
2 parents 773ac41 + 401de97 commit ea5b9fd

File tree

4 files changed

+274
-0
lines changed

4 files changed

+274
-0
lines changed

resolve_searchindex_conflicts.sh

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
#!/bin/bash
2+
3+
# Script to resolve searchindex.js conflicts by accepting master branch version
4+
# This script is designed to handle merge conflicts that occur when PRs become
5+
# desynchronized due to the auto-generated searchindex.js file.
6+
#
7+
# The searchindex.js file is automatically generated by the build process and
8+
# frequently causes conflicts when multiple PRs are waiting to be merged.
9+
# This script automatically resolves those conflicts by accepting the master
10+
# branch version of the file.
11+
#
12+
# Usage: resolve_searchindex_conflicts.sh <pr_number> <head_branch> <base_branch>
13+
14+
set -euo pipefail
15+
16+
# Validate arguments
17+
if [ $# -ne 3 ]; then
18+
echo "Usage: $0 <pr_number> <head_branch> <base_branch>"
19+
exit 1
20+
fi
21+
22+
PR_NUMBER="$1"
23+
HEAD_BRANCH="$2"
24+
BASE_BRANCH="$3"
25+
26+
# Validate required environment variables
27+
if [ -z "${GITHUB_REPOSITORY:-}" ]; then
28+
echo "Error: GITHUB_REPOSITORY environment variable is required"
29+
exit 1
30+
fi
31+
32+
if [ -z "${GH_TOKEN:-}" ]; then
33+
echo "Error: GH_TOKEN environment variable is required"
34+
exit 1
35+
fi
36+
37+
echo "Resolving conflicts for PR #$PR_NUMBER (branch: $HEAD_BRANCH -> $BASE_BRANCH)"
38+
39+
# Get current directory for safety
40+
ORIGINAL_DIR=$(pwd)
41+
42+
# Create a temporary directory for the operation
43+
TEMP_DIR=$(mktemp -d)
44+
echo "Working in temporary directory: $TEMP_DIR"
45+
46+
cleanup() {
47+
echo "Cleaning up..."
48+
cd "$ORIGINAL_DIR"
49+
rm -rf "$TEMP_DIR"
50+
}
51+
trap cleanup EXIT
52+
53+
# Clone the repository to the temp directory
54+
echo "Cloning repository..."
55+
cd "$TEMP_DIR"
56+
gh repo clone "$GITHUB_REPOSITORY" . --branch "$HEAD_BRANCH"
57+
58+
# Configure git
59+
git config user.email "[email protected]"
60+
git config user.name "GitHub Action"
61+
62+
# Fetch all branches
63+
git fetch origin
64+
65+
# Make sure we're on the correct branch
66+
git checkout "$HEAD_BRANCH"
67+
68+
# Try to merge the base branch
69+
echo "Attempting to merge $BASE_BRANCH into $HEAD_BRANCH..."
70+
if git merge "origin/$BASE_BRANCH" --no-edit; then
71+
echo "No conflicts found, merge successful"
72+
73+
# Push the updated branch
74+
echo "Pushing merged branch..."
75+
git push origin "$HEAD_BRANCH"
76+
exit 0
77+
fi
78+
79+
# Check what files have conflicts
80+
echo "Checking for conflicts..."
81+
conflicted_files=$(git diff --name-only --diff-filter=U)
82+
echo "Conflicted files: $conflicted_files"
83+
84+
# Check if searchindex.js is the only conflict or if conflicts are only in acceptable files
85+
acceptable_conflicts=true
86+
searchindex_conflict=false
87+
88+
for file in $conflicted_files; do
89+
case "$file" in
90+
"searchindex.js")
91+
searchindex_conflict=true
92+
echo "Found searchindex.js conflict (acceptable)"
93+
;;
94+
*)
95+
echo "Found unacceptable conflict in: $file"
96+
acceptable_conflicts=false
97+
;;
98+
esac
99+
done
100+
101+
if [ "$acceptable_conflicts" = false ]; then
102+
echo "Cannot auto-resolve: conflicts found in files other than searchindex.js"
103+
git merge --abort
104+
exit 1
105+
fi
106+
107+
if [ "$searchindex_conflict" = false ]; then
108+
echo "No searchindex.js conflicts found, but merge failed for unknown reason"
109+
git merge --abort
110+
exit 1
111+
fi
112+
113+
echo "Resolving searchindex.js conflict by accepting $BASE_BRANCH version..."
114+
115+
# Accept the base branch version of searchindex.js (--theirs refers to the branch being merged in)
116+
git checkout --theirs searchindex.js
117+
git add searchindex.js
118+
119+
# Check if there are any other staged changes from the merge
120+
staged_files=$(git diff --cached --name-only || true)
121+
echo "Staged files after resolution: $staged_files"
122+
123+
# Complete the merge
124+
if git commit --no-edit; then
125+
echo "Successfully resolved merge conflicts"
126+
127+
# Push the updated branch
128+
echo "Pushing resolved branch..."
129+
if git push origin "$HEAD_BRANCH"; then
130+
echo "Successfully pushed resolved branch"
131+
exit 0
132+
else
133+
echo "Failed to push resolved branch"
134+
exit 1
135+
fi
136+
else
137+
echo "Failed to commit merge resolution"
138+
exit 1
139+
fi

src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
- [Basic Python](generic-methodologies-and-resources/python/basic-python.md)
8484
- [Threat Modeling](generic-methodologies-and-resources/threat-modeling.md)
8585
- [Blockchain & Crypto](blockchain/blockchain-and-crypto-currencies/README.md)
86+
- [Mutation Testing With Slither](blockchain/smart-contract-security/mutation-testing-with-slither.md)
8687
- [Defi/AMM Hook Precision](blockchain/blockchain-and-crypto-currencies/defi-amm-hook-precision.md)
8788
- [Lua Sandbox Escape](generic-methodologies-and-resources/lua/bypass-lua-sandboxes/README.md)
8889

src/blockchain/blockchain-and-crypto-currencies/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,14 @@ Transactions in Ethereum involve a sender and a recipient, which can be either u
176176

177177
These practices and mechanisms are foundational for anyone looking to engage with cryptocurrencies while prioritizing privacy and security.
178178

179+
## Smart Contract Security
180+
181+
- Mutation testing to find blind spots in test suites:
182+
183+
{{#ref}}
184+
../smart-contract-security/mutation-testing-with-slither.md
185+
{{#endref}}
186+
179187
## References
180188

181189
- [https://en.wikipedia.org/wiki/Proof_of_stake](https://en.wikipedia.org/wiki/Proof_of_stake)
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
# Mutation Testing for Solidity with Slither (slither-mutate)
2+
3+
{{#include ../../../banners/hacktricks-training.md}}
4+
5+
Mutation testing "tests your tests" by systematically introducing small changes (mutants) into your Solidity code and re-running your test suite. If a test fails, the mutant is killed. If the tests still pass, the mutant survives, revealing a blind spot in your test suite that line/branch coverage cannot detect.
6+
7+
Key idea: Coverage shows code was executed; mutation testing shows whether behavior is actually asserted.
8+
9+
## Why coverage can deceive
10+
11+
Consider this simple threshold check:
12+
13+
```solidity
14+
function verifyMinimumDeposit(uint256 deposit) public returns (bool) {
15+
if (deposit >= 1 ether) {
16+
return true;
17+
} else {
18+
return false;
19+
}
20+
}
21+
```
22+
23+
Unit tests that only check a value below and a value above the threshold can reach 100% line/branch coverage while failing to assert the equality boundary (==). A refactor to `deposit >= 2 ether` would still pass such tests, silently breaking protocol logic.
24+
25+
Mutation testing exposes this gap by mutating the condition and verifying your tests fail.
26+
27+
## Common Solidity mutation operators
28+
29+
Slither’s mutation engine applies many small, semantics-changing edits, such as:
30+
- Operator replacement: `+``-`, `*``/`, etc.
31+
- Assignment replacement: `+=``=`, `-=``=`
32+
- Constant replacement: non-zero → `0`, `true``false`
33+
- Condition negation/replacement inside `if`/loops
34+
- Comment out whole lines (CR: Comment Replacement)
35+
- Replace a line with `revert()`
36+
- Data type swaps: e.g., `int128``int64`
37+
38+
Goal: Kill 100% of generated mutants, or justify survivors with clear reasoning.
39+
40+
## Running mutation testing with slither-mutate
41+
42+
Requirements: Slither v0.10.2+.
43+
44+
- List options and mutators:
45+
46+
```bash
47+
slither-mutate --help
48+
slither-mutate --list-mutators
49+
```
50+
51+
- Foundry example (capture results and keep a full log):
52+
53+
```bash
54+
slither-mutate ./src/contracts --test-cmd="forge test" &> >(tee mutation.results)
55+
```
56+
57+
- If you don’t use Foundry, replace `--test-cmd` with how you run tests (e.g., `npx hardhat test`, `npm test`).
58+
59+
Artifacts and reports are stored in `./mutation_campaign` by default. Uncaught (surviving) mutants are copied there for inspection.
60+
61+
### Understanding the output
62+
63+
Report lines look like:
64+
65+
```text
66+
INFO:Slither-Mutate:Mutating contract ContractName
67+
INFO:Slither-Mutate:[CR] Line 123: 'original line' ==> '//original line' --> UNCAUGHT
68+
```
69+
70+
- The tag in brackets is the mutator alias (e.g., `CR` = Comment Replacement).
71+
- `UNCAUGHT` means tests passed under the mutated behavior → missing assertion.
72+
73+
## Reducing runtime: prioritize impactful mutants
74+
75+
Mutation campaigns can take hours or days. Tips to reduce cost:
76+
- Scope: Start with critical contracts/directories only, then expand.
77+
- Prioritize mutators: If a high-priority mutant on a line survives (e.g., entire line commented), you can skip lower-priority variants for that line.
78+
- Parallelize tests if your runner allows it; cache dependencies/builds.
79+
- Fail-fast: stop early when a change clearly demonstrates an assertion gap.
80+
81+
## Triage workflow for surviving mutants
82+
83+
1) Inspect the mutated line and behavior.
84+
- Reproduce locally by applying the mutated line and running a focused test.
85+
86+
2) Strengthen tests to assert state, not only return values.
87+
- Add equality-boundary checks (e.g., test threshold `==`).
88+
- Assert post-conditions: balances, total supply, authorization effects, and emitted events.
89+
90+
3) Replace overly permissive mocks with realistic behavior.
91+
- Ensure mocks enforce transfers, failure paths, and event emissions that occur on-chain.
92+
93+
4) Add invariants for fuzz tests.
94+
- E.g., conservation of value, non-negative balances, authorization invariants, monotonic supply where applicable.
95+
96+
5) Re-run slither-mutate until survivors are killed or explicitly justified.
97+
98+
## Case study: revealing missing state assertions (Arkis protocol)
99+
100+
A mutation campaign during an audit of the Arkis DeFi protocol surfaced survivors like:
101+
102+
```text
103+
INFO:Slither-Mutate:[CR] Line 33: 'cmdsToExecute.last().value = _cmd.value' ==> '//cmdsToExecute.last().value = _cmd.value' --> UNCAUGHT
104+
```
105+
106+
Commenting out the assignment didn’t break the tests, proving missing post-state assertions. Root cause: code trusted a user-controlled `_cmd.value` instead of validating actual token transfers. An attacker could desynchronize expected vs. actual transfers to drain funds. Result: high severity risk to protocol solvency.
107+
108+
Guidance: Treat survivors that affect value transfers, accounting, or access control as high-risk until killed.
109+
110+
## Practical checklist
111+
112+
- Run a targeted campaign:
113+
- `slither-mutate ./src/contracts --test-cmd="forge test"`
114+
- Triage survivors and write tests/invariants that would fail under the mutated behavior.
115+
- Assert balances, supply, authorizations, and events.
116+
- Add boundary tests (`==`, overflows/underflows, zero-address, zero-amount, empty arrays).
117+
- Replace unrealistic mocks; simulate failure modes.
118+
- Iterate until all mutants are killed or justified with comments and rationale.
119+
120+
## References
121+
122+
- [Use mutation testing to find the bugs your tests don't catch (Trail of Bits)](https://blog.trailofbits.com/2025/09/18/use-mutation-testing-to-find-the-bugs-your-tests-dont-catch/)
123+
- [Arkis DeFi Prime Brokerage Security Review (Appendix C)](https://github.com/trailofbits/publications/blob/master/reviews/2024-12-arkis-defi-prime-brokerage-securityreview.pdf)
124+
- [Slither (GitHub)](https://github.com/crytic/slither)
125+
126+
{{#include ../../../banners/hacktricks-training.md}}

0 commit comments

Comments
 (0)