@godzillaba/mutest 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.devcontainer/Dockerfile +117 -0
- package/.devcontainer/devcontainer.json +62 -0
- package/.devcontainer/init-firewall.sh +118 -0
- package/.github/workflows/test.yml +38 -0
- package/.gitmodules +3 -0
- package/CLAUDE.md +39 -0
- package/README.md +33 -0
- package/foundry.lock +8 -0
- package/foundry.toml +6 -0
- package/index.ts +97 -0
- package/lib/forge-std/.gitattributes +1 -0
- package/lib/forge-std/.github/CODEOWNERS +1 -0
- package/lib/forge-std/.github/dependabot.yml +6 -0
- package/lib/forge-std/.github/workflows/ci.yml +125 -0
- package/lib/forge-std/.github/workflows/sync.yml +36 -0
- package/lib/forge-std/CONTRIBUTING.md +193 -0
- package/lib/forge-std/LICENSE-APACHE +203 -0
- package/lib/forge-std/LICENSE-MIT +25 -0
- package/lib/forge-std/README.md +314 -0
- package/lib/forge-std/RELEASE_CHECKLIST.md +12 -0
- package/lib/forge-std/foundry.toml +18 -0
- package/lib/forge-std/package.json +16 -0
- package/lib/forge-std/scripts/vm.py +636 -0
- package/lib/forge-std/src/Base.sol +48 -0
- package/lib/forge-std/src/Config.sol +60 -0
- package/lib/forge-std/src/LibVariable.sol +477 -0
- package/lib/forge-std/src/Script.sol +28 -0
- package/lib/forge-std/src/StdAssertions.sol +779 -0
- package/lib/forge-std/src/StdChains.sol +303 -0
- package/lib/forge-std/src/StdCheats.sol +825 -0
- package/lib/forge-std/src/StdConfig.sol +632 -0
- package/lib/forge-std/src/StdConstants.sol +30 -0
- package/lib/forge-std/src/StdError.sol +15 -0
- package/lib/forge-std/src/StdInvariant.sol +140 -0
- package/lib/forge-std/src/StdJson.sol +275 -0
- package/lib/forge-std/src/StdMath.sol +47 -0
- package/lib/forge-std/src/StdStorage.sol +475 -0
- package/lib/forge-std/src/StdStyle.sol +333 -0
- package/lib/forge-std/src/StdToml.sol +275 -0
- package/lib/forge-std/src/StdUtils.sol +200 -0
- package/lib/forge-std/src/Test.sol +32 -0
- package/lib/forge-std/src/Vm.sol +2533 -0
- package/lib/forge-std/src/console.sol +1551 -0
- package/lib/forge-std/src/console2.sol +4 -0
- package/lib/forge-std/src/interfaces/IERC1155.sol +105 -0
- package/lib/forge-std/src/interfaces/IERC165.sol +12 -0
- package/lib/forge-std/src/interfaces/IERC20.sol +43 -0
- package/lib/forge-std/src/interfaces/IERC4626.sol +190 -0
- package/lib/forge-std/src/interfaces/IERC6909.sol +72 -0
- package/lib/forge-std/src/interfaces/IERC721.sol +164 -0
- package/lib/forge-std/src/interfaces/IERC7540.sol +144 -0
- package/lib/forge-std/src/interfaces/IERC7575.sol +241 -0
- package/lib/forge-std/src/interfaces/IMulticall3.sol +68 -0
- package/lib/forge-std/src/safeconsole.sol +13248 -0
- package/lib/forge-std/test/CommonBase.t.sol +44 -0
- package/lib/forge-std/test/Config.t.sol +381 -0
- package/lib/forge-std/test/LibVariable.t.sol +452 -0
- package/lib/forge-std/test/StdAssertions.t.sol +141 -0
- package/lib/forge-std/test/StdChains.t.sol +227 -0
- package/lib/forge-std/test/StdCheats.t.sol +638 -0
- package/lib/forge-std/test/StdConstants.t.sol +38 -0
- package/lib/forge-std/test/StdError.t.sol +119 -0
- package/lib/forge-std/test/StdJson.t.sol +49 -0
- package/lib/forge-std/test/StdMath.t.sol +202 -0
- package/lib/forge-std/test/StdStorage.t.sol +485 -0
- package/lib/forge-std/test/StdStyle.t.sol +110 -0
- package/lib/forge-std/test/StdToml.t.sol +49 -0
- package/lib/forge-std/test/StdUtils.t.sol +342 -0
- package/lib/forge-std/test/Vm.t.sol +18 -0
- package/lib/forge-std/test/compilation/CompilationScript.sol +8 -0
- package/lib/forge-std/test/compilation/CompilationScriptBase.sol +8 -0
- package/lib/forge-std/test/compilation/CompilationTest.sol +8 -0
- package/lib/forge-std/test/compilation/CompilationTestBase.sol +8 -0
- package/lib/forge-std/test/fixtures/broadcast.log.json +187 -0
- package/lib/forge-std/test/fixtures/config.toml +81 -0
- package/lib/forge-std/test/fixtures/test.json +8 -0
- package/lib/forge-std/test/fixtures/test.toml +6 -0
- package/package.json +10 -0
- package/script/Counter.s.sol +19 -0
- package/src/Counter.sol +14 -0
- package/test/Counter.t.sol +24 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
FROM node:20
|
|
2
|
+
|
|
3
|
+
ARG TZ
|
|
4
|
+
ENV TZ="$TZ"
|
|
5
|
+
|
|
6
|
+
ARG CLAUDE_CODE_VERSION=latest
|
|
7
|
+
|
|
8
|
+
# Install basic development tools and iptables/ipset
|
|
9
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
10
|
+
less \
|
|
11
|
+
git \
|
|
12
|
+
procps \
|
|
13
|
+
sudo \
|
|
14
|
+
fzf \
|
|
15
|
+
zsh \
|
|
16
|
+
man-db \
|
|
17
|
+
unzip \
|
|
18
|
+
gnupg2 \
|
|
19
|
+
gh \
|
|
20
|
+
iptables \
|
|
21
|
+
ipset \
|
|
22
|
+
iproute2 \
|
|
23
|
+
dnsutils \
|
|
24
|
+
aggregate \
|
|
25
|
+
jq \
|
|
26
|
+
nano \
|
|
27
|
+
vim \
|
|
28
|
+
&& apt-get clean && rm -rf /var/lib/apt/lists/*
|
|
29
|
+
|
|
30
|
+
# Install Go
|
|
31
|
+
ARG GO_VERSION=1.23.6
|
|
32
|
+
RUN ARCH=$(dpkg --print-architecture) && \
|
|
33
|
+
wget -q "https://go.dev/dl/go${GO_VERSION}.linux-${ARCH}.tar.gz" && \
|
|
34
|
+
tar -C /usr/local -xzf "go${GO_VERSION}.linux-${ARCH}.tar.gz" && \
|
|
35
|
+
rm "go${GO_VERSION}.linux-${ARCH}.tar.gz"
|
|
36
|
+
ENV PATH=$PATH:/usr/local/go/bin
|
|
37
|
+
ENV GOPATH=/home/node/go
|
|
38
|
+
ENV PATH=$PATH:/home/node/go/bin
|
|
39
|
+
|
|
40
|
+
# Ensure default node user has access to /usr/local/share
|
|
41
|
+
RUN mkdir -p /usr/local/share/npm-global && \
|
|
42
|
+
chown -R node:node /usr/local/share
|
|
43
|
+
|
|
44
|
+
ARG USERNAME=node
|
|
45
|
+
|
|
46
|
+
# Persist bash history.
|
|
47
|
+
RUN SNIPPET="export PROMPT_COMMAND='history -a' && export HISTFILE=/commandhistory/.bash_history" \
|
|
48
|
+
&& mkdir /commandhistory \
|
|
49
|
+
&& touch /commandhistory/.bash_history \
|
|
50
|
+
&& chown -R $USERNAME /commandhistory
|
|
51
|
+
|
|
52
|
+
# Set `DEVCONTAINER` environment variable to help with orientation
|
|
53
|
+
ENV DEVCONTAINER=true
|
|
54
|
+
|
|
55
|
+
# Create workspace and config directories and set permissions
|
|
56
|
+
RUN mkdir -p /workspace /home/node/.claude && \
|
|
57
|
+
chown -R node:node /workspace /home/node/.claude
|
|
58
|
+
|
|
59
|
+
WORKDIR /workspace
|
|
60
|
+
|
|
61
|
+
ARG GIT_DELTA_VERSION=0.18.2
|
|
62
|
+
RUN ARCH=$(dpkg --print-architecture) && \
|
|
63
|
+
wget "https://github.com/dandavison/delta/releases/download/${GIT_DELTA_VERSION}/git-delta_${GIT_DELTA_VERSION}_${ARCH}.deb" && \
|
|
64
|
+
sudo dpkg -i "git-delta_${GIT_DELTA_VERSION}_${ARCH}.deb" && \
|
|
65
|
+
rm "git-delta_${GIT_DELTA_VERSION}_${ARCH}.deb"
|
|
66
|
+
|
|
67
|
+
# Set up non-root user
|
|
68
|
+
USER node
|
|
69
|
+
|
|
70
|
+
# Install global packages
|
|
71
|
+
ENV NPM_CONFIG_PREFIX=/usr/local/share/npm-global
|
|
72
|
+
ENV PATH=$PATH:/usr/local/share/npm-global/bin
|
|
73
|
+
|
|
74
|
+
# Set the default shell to zsh rather than sh
|
|
75
|
+
ENV SHELL=/bin/zsh
|
|
76
|
+
|
|
77
|
+
# Set the default editor and visual
|
|
78
|
+
ENV EDITOR=nano
|
|
79
|
+
ENV VISUAL=nano
|
|
80
|
+
|
|
81
|
+
# Default powerline10k theme
|
|
82
|
+
ARG ZSH_IN_DOCKER_VERSION=1.2.0
|
|
83
|
+
RUN sh -c "$(wget -O- https://github.com/deluan/zsh-in-docker/releases/download/v${ZSH_IN_DOCKER_VERSION}/zsh-in-docker.sh)" -- \
|
|
84
|
+
-p git \
|
|
85
|
+
-p fzf \
|
|
86
|
+
-a "source /usr/share/doc/fzf/examples/key-bindings.zsh" \
|
|
87
|
+
-a "source /usr/share/doc/fzf/examples/completion.zsh" \
|
|
88
|
+
-a "export PROMPT_COMMAND='history -a' && export HISTFILE=/commandhistory/.bash_history" \
|
|
89
|
+
-x
|
|
90
|
+
|
|
91
|
+
# Install Gambit (Solidity mutation testing)
|
|
92
|
+
ARG GAMBIT_VERSION=v1.0.6
|
|
93
|
+
USER root
|
|
94
|
+
RUN wget -q "https://github.com/Certora/gambit/releases/download/${GAMBIT_VERSION}/gambit-linux-${GAMBIT_VERSION}" -O /usr/local/bin/gambit && \
|
|
95
|
+
chmod +x /usr/local/bin/gambit
|
|
96
|
+
USER node
|
|
97
|
+
|
|
98
|
+
# Install Foundry (forge, cast, anvil, chisel)
|
|
99
|
+
ENV PATH=$PATH:/home/node/.foundry/bin
|
|
100
|
+
RUN curl -L https://foundry.paradigm.xyz | bash && foundryup
|
|
101
|
+
|
|
102
|
+
# Symlink solc
|
|
103
|
+
USER root
|
|
104
|
+
RUN ln -s /home/node/.svm/0.8.33/solc-0.8.33 /usr/local/bin/solc
|
|
105
|
+
USER node
|
|
106
|
+
|
|
107
|
+
# Install Claude
|
|
108
|
+
RUN npm install -g @anthropic-ai/claude-code@${CLAUDE_CODE_VERSION}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# Copy and set up firewall script
|
|
112
|
+
COPY init-firewall.sh /usr/local/bin/
|
|
113
|
+
USER root
|
|
114
|
+
RUN chmod +x /usr/local/bin/init-firewall.sh && \
|
|
115
|
+
echo "node ALL=(root) NOPASSWD: /usr/local/bin/init-firewall.sh" > /etc/sudoers.d/node-firewall && \
|
|
116
|
+
chmod 0440 /etc/sudoers.d/node-firewall
|
|
117
|
+
USER node
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Claude Code Sandbox",
|
|
3
|
+
"build": {
|
|
4
|
+
"dockerfile": "Dockerfile",
|
|
5
|
+
"args": {
|
|
6
|
+
"TZ": "${localEnv:TZ:America/Los_Angeles}",
|
|
7
|
+
"CLAUDE_CODE_VERSION": "latest",
|
|
8
|
+
"GIT_DELTA_VERSION": "0.18.2",
|
|
9
|
+
"ZSH_IN_DOCKER_VERSION": "1.2.0"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"runArgs": [
|
|
13
|
+
"--cap-add=NET_ADMIN",
|
|
14
|
+
"--cap-add=NET_RAW"
|
|
15
|
+
],
|
|
16
|
+
"customizations": {
|
|
17
|
+
"vscode": {
|
|
18
|
+
"extensions": [
|
|
19
|
+
"anthropic.claude-code",
|
|
20
|
+
"dbaeumer.vscode-eslint",
|
|
21
|
+
"esbenp.prettier-vscode",
|
|
22
|
+
"eamodio.gitlens",
|
|
23
|
+
"golang.go"
|
|
24
|
+
],
|
|
25
|
+
"settings": {
|
|
26
|
+
"go.useLanguageServer": true,
|
|
27
|
+
"[go]": {
|
|
28
|
+
"editor.formatOnSave": true,
|
|
29
|
+
"editor.defaultFormatter": "golang.go",
|
|
30
|
+
"editor.codeActionsOnSave": {
|
|
31
|
+
"source.organizeImports": "explicit"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"terminal.integrated.defaultProfile.linux": "zsh",
|
|
35
|
+
"terminal.integrated.profiles.linux": {
|
|
36
|
+
"bash": {
|
|
37
|
+
"path": "bash",
|
|
38
|
+
"icon": "terminal-bash"
|
|
39
|
+
},
|
|
40
|
+
"zsh": {
|
|
41
|
+
"path": "zsh"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"remoteUser": "node",
|
|
48
|
+
"mounts": [
|
|
49
|
+
"source=claude-code-bashhistory-${devcontainerId},target=/commandhistory,type=volume",
|
|
50
|
+
"source=claude-code-config-${devcontainerId},target=/home/node/.claude,type=volume"
|
|
51
|
+
],
|
|
52
|
+
"containerEnv": {
|
|
53
|
+
"NODE_OPTIONS": "--max-old-space-size=4096",
|
|
54
|
+
"CLAUDE_CONFIG_DIR": "/home/node/.claude",
|
|
55
|
+
"GOPATH": "/home/node/go",
|
|
56
|
+
"POWERLEVEL9K_DISABLE_GITSTATUS": "true"
|
|
57
|
+
},
|
|
58
|
+
"workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=delegated",
|
|
59
|
+
"workspaceFolder": "/workspace",
|
|
60
|
+
"postStartCommand": "sudo /usr/local/bin/init-firewall.sh",
|
|
61
|
+
"waitFor": "postStartCommand"
|
|
62
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -euo pipefail # Exit on error, undefined vars, and pipeline failures
|
|
3
|
+
IFS=$'\n\t' # Stricter word splitting
|
|
4
|
+
|
|
5
|
+
# 1. Extract Docker DNS info BEFORE any flushing
|
|
6
|
+
DOCKER_DNS_RULES=$(iptables-save -t nat | grep "127\.0\.0\.11" || true)
|
|
7
|
+
|
|
8
|
+
# Flush existing rules and delete existing ipsets
|
|
9
|
+
iptables -F
|
|
10
|
+
iptables -X
|
|
11
|
+
iptables -t nat -F
|
|
12
|
+
iptables -t nat -X
|
|
13
|
+
iptables -t mangle -F
|
|
14
|
+
iptables -t mangle -X
|
|
15
|
+
ipset destroy allowed-domains 2>/dev/null || true
|
|
16
|
+
|
|
17
|
+
# 2. Selectively restore ONLY internal Docker DNS resolution
|
|
18
|
+
if [ -n "$DOCKER_DNS_RULES" ]; then
|
|
19
|
+
echo "Restoring Docker DNS rules..."
|
|
20
|
+
iptables -t nat -N DOCKER_OUTPUT 2>/dev/null || true
|
|
21
|
+
iptables -t nat -N DOCKER_POSTROUTING 2>/dev/null || true
|
|
22
|
+
echo "$DOCKER_DNS_RULES" | xargs -L 1 iptables -t nat
|
|
23
|
+
else
|
|
24
|
+
echo "No Docker DNS rules to restore"
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
# First allow DNS and localhost before any restrictions
|
|
28
|
+
# Allow outbound DNS
|
|
29
|
+
iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
|
|
30
|
+
# Allow inbound DNS responses
|
|
31
|
+
iptables -A INPUT -p udp --sport 53 -j ACCEPT
|
|
32
|
+
# Allow outbound SSH
|
|
33
|
+
iptables -A OUTPUT -p tcp --dport 22 -j ACCEPT
|
|
34
|
+
# Allow inbound SSH responses
|
|
35
|
+
iptables -A INPUT -p tcp --sport 22 -m state --state ESTABLISHED -j ACCEPT
|
|
36
|
+
# Allow localhost
|
|
37
|
+
iptables -A INPUT -i lo -j ACCEPT
|
|
38
|
+
iptables -A OUTPUT -o lo -j ACCEPT
|
|
39
|
+
|
|
40
|
+
# Create ipset with CIDR support
|
|
41
|
+
ipset create allowed-domains hash:net
|
|
42
|
+
|
|
43
|
+
# Resolve and add other allowed domains
|
|
44
|
+
for domain in \
|
|
45
|
+
"registry.npmjs.org" \
|
|
46
|
+
"api.anthropic.com" \
|
|
47
|
+
"sentry.io" \
|
|
48
|
+
"statsig.anthropic.com" \
|
|
49
|
+
"statsig.com" \
|
|
50
|
+
"marketplace.visualstudio.com" \
|
|
51
|
+
"vscode.blob.core.windows.net" \
|
|
52
|
+
"update.code.visualstudio.com" \
|
|
53
|
+
"proxy.golang.org" \
|
|
54
|
+
"sum.golang.org" \
|
|
55
|
+
"storage.googleapis.com" \
|
|
56
|
+
"binaries.soliditylang.org"; do
|
|
57
|
+
echo "Resolving $domain..."
|
|
58
|
+
ips=$(dig +noall +answer A "$domain" | awk '$4 == "A" {print $5}')
|
|
59
|
+
if [ -z "$ips" ]; then
|
|
60
|
+
echo "ERROR: Failed to resolve $domain"
|
|
61
|
+
exit 1
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
while read -r ip; do
|
|
65
|
+
if [[ ! "$ip" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
|
|
66
|
+
echo "ERROR: Invalid IP from DNS for $domain: $ip"
|
|
67
|
+
exit 1
|
|
68
|
+
fi
|
|
69
|
+
echo "Adding $ip for $domain"
|
|
70
|
+
ipset add allowed-domains "$ip" -exist
|
|
71
|
+
done < <(echo "$ips")
|
|
72
|
+
done
|
|
73
|
+
|
|
74
|
+
# Get host IP from default route
|
|
75
|
+
HOST_IP=$(ip route | grep default | cut -d" " -f3)
|
|
76
|
+
if [ -z "$HOST_IP" ]; then
|
|
77
|
+
echo "ERROR: Failed to detect host IP"
|
|
78
|
+
exit 1
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
HOST_NETWORK=$(echo "$HOST_IP" | sed "s/\.[0-9]*$/.0\/24/")
|
|
82
|
+
echo "Host network detected as: $HOST_NETWORK"
|
|
83
|
+
|
|
84
|
+
# Set up remaining iptables rules
|
|
85
|
+
iptables -A INPUT -s "$HOST_NETWORK" -j ACCEPT
|
|
86
|
+
iptables -A OUTPUT -d "$HOST_NETWORK" -j ACCEPT
|
|
87
|
+
|
|
88
|
+
# Set default policies to DROP first
|
|
89
|
+
iptables -P INPUT DROP
|
|
90
|
+
iptables -P FORWARD DROP
|
|
91
|
+
iptables -P OUTPUT DROP
|
|
92
|
+
|
|
93
|
+
# First allow established connections for already approved traffic
|
|
94
|
+
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
|
|
95
|
+
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
|
|
96
|
+
|
|
97
|
+
# Then allow only specific outbound traffic to allowed domains
|
|
98
|
+
iptables -A OUTPUT -m set --match-set allowed-domains dst -j ACCEPT
|
|
99
|
+
|
|
100
|
+
# Explicitly REJECT all other outbound traffic for immediate feedback
|
|
101
|
+
iptables -A OUTPUT -j REJECT --reject-with icmp-admin-prohibited
|
|
102
|
+
|
|
103
|
+
echo "Firewall configuration complete"
|
|
104
|
+
echo "Verifying firewall rules..."
|
|
105
|
+
if curl --connect-timeout 5 https://example.com >/dev/null 2>&1; then
|
|
106
|
+
echo "ERROR: Firewall verification failed - was able to reach https://example.com"
|
|
107
|
+
exit 1
|
|
108
|
+
else
|
|
109
|
+
echo "Firewall verification passed - unable to reach https://example.com as expected"
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
# Verify GitHub API access
|
|
113
|
+
if curl --connect-timeout 5 https://api.github.com/zen >/dev/null 2>&1; then
|
|
114
|
+
echo "ERROR: Firewall verification failed - was able to reach https://api.github.com"
|
|
115
|
+
exit 1
|
|
116
|
+
else
|
|
117
|
+
echo "Firewall verification passed - unable to reach https://api.github.com as expected"
|
|
118
|
+
fi
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
permissions: {}
|
|
4
|
+
|
|
5
|
+
on:
|
|
6
|
+
push:
|
|
7
|
+
pull_request:
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
env:
|
|
11
|
+
FOUNDRY_PROFILE: ci
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
check:
|
|
15
|
+
name: Foundry project
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
permissions:
|
|
18
|
+
contents: read
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v5
|
|
21
|
+
with:
|
|
22
|
+
persist-credentials: false
|
|
23
|
+
submodules: recursive
|
|
24
|
+
|
|
25
|
+
- name: Install Foundry
|
|
26
|
+
uses: foundry-rs/foundry-toolchain@v1
|
|
27
|
+
|
|
28
|
+
- name: Show Forge version
|
|
29
|
+
run: forge --version
|
|
30
|
+
|
|
31
|
+
- name: Run Forge fmt
|
|
32
|
+
run: forge fmt --check
|
|
33
|
+
|
|
34
|
+
- name: Run Forge build
|
|
35
|
+
run: forge build --sizes
|
|
36
|
+
|
|
37
|
+
- name: Run Forge tests
|
|
38
|
+
run: forge test -vvv
|
package/.gitmodules
ADDED
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
## Git
|
|
4
|
+
|
|
5
|
+
Always commit with `--no-gpg-sign` — GPG signing is not configured in this container.
|
|
6
|
+
|
|
7
|
+
## Solidity Toolchain
|
|
8
|
+
|
|
9
|
+
`solc` is symlinked into PATH via the Dockerfile. If `solc` is not found (e.g. after a container rebuild), run `forge clean && forge build` — Foundry will re-install solc via svm automatically.
|
|
10
|
+
|
|
11
|
+
## Development Environment
|
|
12
|
+
|
|
13
|
+
This project runs inside a devcontainer (`.devcontainer/`). The container is locked down with an iptables firewall that only allows traffic to a small allowlist of domains.
|
|
14
|
+
|
|
15
|
+
- **To install system packages or tools** — edit `.devcontainer/Dockerfile` and rebuild the container.
|
|
16
|
+
- **To allow network access to a new domain** — add it to the domain list in `.devcontainer/init-firewall.sh`.
|
|
17
|
+
|
|
18
|
+
## Tooling Philosophy
|
|
19
|
+
|
|
20
|
+
Always use the right tool for the job — install real libraries instead of reimplementing things with stdlib. If a dependency is missing and can't be installed due to the container/firewall setup, ask the user to help unblock it (e.g. add a domain to the firewall, add a package to the Dockerfile, rebuild the container). Don't silently work around missing tools with inferior hand-rolled alternatives.
|
|
21
|
+
|
|
22
|
+
## Documentation
|
|
23
|
+
|
|
24
|
+
When adding or modifying a feature, always update the relevant documentation if it exists. Keep docs in sync with code.
|
|
25
|
+
|
|
26
|
+
## Code Style
|
|
27
|
+
|
|
28
|
+
Inspired by NASA/JPL's "Power of 10" — code must be quickly and easily reviewable by a human.
|
|
29
|
+
|
|
30
|
+
- Write minimal, concise code. No unnecessary abstractions or indirection.
|
|
31
|
+
- Functions should be short enough to fit on a screen (~60 lines max). If longer, split by responsibility.
|
|
32
|
+
- Simple control flow. Minimal nesting, early returns over deep if/else chains.
|
|
33
|
+
- Smallest possible scope for all variables and data.
|
|
34
|
+
- No comments unless the logic is genuinely non-obvious. Never restate what the code already says.
|
|
35
|
+
- No JSDoc unless it's a public API. Skip @param/@returns that just repeat type signatures.
|
|
36
|
+
- Don't add error handling, validation, or fallbacks for cases that can't realistically happen.
|
|
37
|
+
- Prefer fewer lines. Three similar lines are better than a helper function used once.
|
|
38
|
+
- Don't refactor, rename, or "improve" code you weren't asked to change.
|
|
39
|
+
- No clever tricks. Code should be obvious, not impressive.
|
package/README.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# mutest
|
|
2
|
+
|
|
3
|
+
Mutation testing for Solidity. Uses [Gambit](https://github.com/Certora/gambit) to generate mutants and [Foundry](https://github.com/foundry-rs/foundry) to test them.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npx . src/Counter.sol
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Pass one or more Solidity files. Mutest will:
|
|
12
|
+
|
|
13
|
+
1. Create 10 parallel copies of your project
|
|
14
|
+
2. Generate mutants with Gambit (e.g. `++` -> `--`, assignments replaced)
|
|
15
|
+
3. Run `forge test` against each mutant across the worker copies
|
|
16
|
+
4. Report which mutants were killed (tests caught them) or survived (coverage gap)
|
|
17
|
+
|
|
18
|
+
Example output:
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
[KILLED] #1 DeleteExpressionMutation src/Counter.sol
|
|
22
|
+
[KILLED] #2 AssignmentMutation src/Counter.sol
|
|
23
|
+
[SURVIVED] #3 AssignmentMutation src/Counter.sol
|
|
24
|
+
|
|
25
|
+
3 mutants tested: 2 killed, 1 survived
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Requirements
|
|
29
|
+
|
|
30
|
+
- [Foundry](https://getfoundry.sh/) (`forge`)
|
|
31
|
+
- [Gambit](https://github.com/Certora/gambit) (`gambit`)
|
|
32
|
+
- `solc` in PATH
|
|
33
|
+
- Node.js >= 18
|
package/foundry.lock
ADDED
package/foundry.toml
ADDED
package/index.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env -S npx tsx
|
|
2
|
+
import { execFile as execFileCb } from "child_process";
|
|
3
|
+
import { readFile, cp, rm } from "fs/promises";
|
|
4
|
+
import { promisify } from "util";
|
|
5
|
+
|
|
6
|
+
const execFile = promisify(execFileCb);
|
|
7
|
+
|
|
8
|
+
interface Mutant {
|
|
9
|
+
id: string;
|
|
10
|
+
description: string;
|
|
11
|
+
name: string;
|
|
12
|
+
original: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function setupWorkers(workerCount: number): Promise<string> {
|
|
16
|
+
const { stdout: tempDir } = await execFile("mktemp", ["-d"]);
|
|
17
|
+
const root = tempDir.trim();
|
|
18
|
+
|
|
19
|
+
const workers = Array.from({ length: workerCount }, (_, i) => {
|
|
20
|
+
const dir = `${root}/worker-${i}`;
|
|
21
|
+
return execFile("bash", [
|
|
22
|
+
"-c",
|
|
23
|
+
`mkdir -p "${dir}" && git ls-files -z | tar -c --null -T - | tar -x -C "${dir}"`,
|
|
24
|
+
]);
|
|
25
|
+
});
|
|
26
|
+
await Promise.all(workers);
|
|
27
|
+
return root;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function runGambit(solFiles: string[]): Promise<Mutant[]> {
|
|
31
|
+
await rm("gambit_out", { recursive: true, force: true });
|
|
32
|
+
for (const file of solFiles) {
|
|
33
|
+
await execFile("gambit", ["mutate", "--filename", file]);
|
|
34
|
+
}
|
|
35
|
+
const raw = await readFile("gambit_out/gambit_results.json", "utf-8");
|
|
36
|
+
return JSON.parse(raw);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function processMutants(
|
|
40
|
+
tempDir: string,
|
|
41
|
+
mutants: Mutant[],
|
|
42
|
+
workerCount: number,
|
|
43
|
+
) {
|
|
44
|
+
const queues: Mutant[][] = Array.from({ length: workerCount }, () => []);
|
|
45
|
+
for (let i = 0; i < mutants.length; i++)
|
|
46
|
+
queues[i % workerCount].push(mutants[i]);
|
|
47
|
+
|
|
48
|
+
let killed = 0;
|
|
49
|
+
let survived = 0;
|
|
50
|
+
|
|
51
|
+
const workers = queues.map(async (queue, workerIdx) => {
|
|
52
|
+
const workerDir = `${tempDir}/worker-${workerIdx}`;
|
|
53
|
+
for (const mutant of queue) {
|
|
54
|
+
await cp(`gambit_out/${mutant.name}`, `${workerDir}/${mutant.original}`);
|
|
55
|
+
try {
|
|
56
|
+
await execFile("forge", ["test", "--root", workerDir]);
|
|
57
|
+
survived++;
|
|
58
|
+
console.log(
|
|
59
|
+
`[SURVIVED] #${mutant.id} ${mutant.description} ${mutant.original}`,
|
|
60
|
+
);
|
|
61
|
+
} catch {
|
|
62
|
+
killed++;
|
|
63
|
+
console.log(
|
|
64
|
+
`[KILLED] #${mutant.id} ${mutant.description} ${mutant.original}`,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
await Promise.all(workers);
|
|
71
|
+
console.log(
|
|
72
|
+
`\n${killed + survived} mutants tested: ${killed} killed, ${survived} survived`,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function main() {
|
|
77
|
+
const solFiles = process.argv.slice(2);
|
|
78
|
+
if (solFiles.length === 0) {
|
|
79
|
+
console.error("Usage: mutest <sol-file> [sol-file ...]");
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const workerCount = 10;
|
|
84
|
+
console.log(`Setting up ${workerCount} workers...`);
|
|
85
|
+
const tempDir = await setupWorkers(workerCount);
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
console.log(`Running gambit on ${solFiles.join(", ")}...`);
|
|
89
|
+
const mutants = await runGambit(solFiles);
|
|
90
|
+
console.log(`Generated ${mutants.length} mutants, running tests...\n`);
|
|
91
|
+
await processMutants(tempDir, mutants, workerCount);
|
|
92
|
+
} finally {
|
|
93
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
main();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
src/Vm.sol linguist-generated
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
* @danipopes @klkvr @mattsse @grandizzy @yash-atreya @zerosnacks @onbjerg @0xrusowsky
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
permissions: {}
|
|
4
|
+
|
|
5
|
+
on:
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
pull_request:
|
|
8
|
+
push:
|
|
9
|
+
branches:
|
|
10
|
+
- master
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
build:
|
|
14
|
+
name: build +${{ matrix.toolchain }} ${{ matrix.flags }}
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
timeout-minutes: 10
|
|
17
|
+
permissions:
|
|
18
|
+
contents: read
|
|
19
|
+
strategy:
|
|
20
|
+
fail-fast: false
|
|
21
|
+
matrix:
|
|
22
|
+
toolchain: [stable, nightly]
|
|
23
|
+
flags:
|
|
24
|
+
- ""
|
|
25
|
+
- --via-ir
|
|
26
|
+
- --use solc:0.8.33 --via-ir
|
|
27
|
+
- --use solc:0.8.33
|
|
28
|
+
- --use solc:0.8.13 --via-ir
|
|
29
|
+
- --use solc:0.8.13
|
|
30
|
+
steps:
|
|
31
|
+
- uses: actions/checkout@v6
|
|
32
|
+
with:
|
|
33
|
+
persist-credentials: false
|
|
34
|
+
- uses: foundry-rs/foundry-toolchain@v1
|
|
35
|
+
with:
|
|
36
|
+
version: ${{ matrix.toolchain }}
|
|
37
|
+
- run: forge --version
|
|
38
|
+
- run: forge build -vvvvv --skip test --deny warnings ${{ matrix.flags }} --contracts 'test/compilation/*'
|
|
39
|
+
|
|
40
|
+
test:
|
|
41
|
+
runs-on: ubuntu-latest
|
|
42
|
+
timeout-minutes: 10
|
|
43
|
+
permissions:
|
|
44
|
+
contents: read
|
|
45
|
+
strategy:
|
|
46
|
+
fail-fast: false
|
|
47
|
+
matrix:
|
|
48
|
+
toolchain: [stable, nightly]
|
|
49
|
+
steps:
|
|
50
|
+
- uses: actions/checkout@v6
|
|
51
|
+
with:
|
|
52
|
+
persist-credentials: false
|
|
53
|
+
- uses: foundry-rs/foundry-toolchain@v1
|
|
54
|
+
with:
|
|
55
|
+
version: ${{ matrix.toolchain }}
|
|
56
|
+
- run: forge --version
|
|
57
|
+
- run: forge test -vvv
|
|
58
|
+
|
|
59
|
+
fmt:
|
|
60
|
+
runs-on: ubuntu-latest
|
|
61
|
+
timeout-minutes: 10
|
|
62
|
+
permissions:
|
|
63
|
+
contents: read
|
|
64
|
+
steps:
|
|
65
|
+
- uses: actions/checkout@v6
|
|
66
|
+
with:
|
|
67
|
+
persist-credentials: false
|
|
68
|
+
- uses: foundry-rs/foundry-toolchain@v1
|
|
69
|
+
- run: forge --version
|
|
70
|
+
- run: forge fmt --check
|
|
71
|
+
|
|
72
|
+
typos:
|
|
73
|
+
runs-on: ubuntu-latest
|
|
74
|
+
timeout-minutes: 10
|
|
75
|
+
permissions:
|
|
76
|
+
contents: read
|
|
77
|
+
steps:
|
|
78
|
+
- uses: actions/checkout@v6
|
|
79
|
+
with:
|
|
80
|
+
persist-credentials: false
|
|
81
|
+
- uses: crate-ci/typos@78bc6fb2c0d734235d57a2d6b9de923cc325ebdd # v1
|
|
82
|
+
|
|
83
|
+
codeql:
|
|
84
|
+
name: Analyze (${{ matrix.language }})
|
|
85
|
+
runs-on: ubuntu-latest
|
|
86
|
+
permissions:
|
|
87
|
+
security-events: write
|
|
88
|
+
actions: read
|
|
89
|
+
contents: read
|
|
90
|
+
strategy:
|
|
91
|
+
fail-fast: false
|
|
92
|
+
matrix:
|
|
93
|
+
include:
|
|
94
|
+
- language: actions
|
|
95
|
+
build-mode: none
|
|
96
|
+
steps:
|
|
97
|
+
- name: Checkout repository
|
|
98
|
+
uses: actions/checkout@v6
|
|
99
|
+
with:
|
|
100
|
+
persist-credentials: false
|
|
101
|
+
- name: Initialize CodeQL
|
|
102
|
+
uses: github/codeql-action/init@v4
|
|
103
|
+
with:
|
|
104
|
+
languages: ${{ matrix.language }}
|
|
105
|
+
build-mode: ${{ matrix.build-mode }}
|
|
106
|
+
- name: Perform CodeQL Analysis
|
|
107
|
+
uses: github/codeql-action/analyze@v4
|
|
108
|
+
with:
|
|
109
|
+
category: "/language:${{matrix.language}}"
|
|
110
|
+
|
|
111
|
+
ci-success:
|
|
112
|
+
runs-on: ubuntu-latest
|
|
113
|
+
if: always()
|
|
114
|
+
needs:
|
|
115
|
+
- build
|
|
116
|
+
- test
|
|
117
|
+
- fmt
|
|
118
|
+
- typos
|
|
119
|
+
- codeql
|
|
120
|
+
timeout-minutes: 10
|
|
121
|
+
steps:
|
|
122
|
+
- name: Decide whether the needed jobs succeeded or failed
|
|
123
|
+
uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # release/v1
|
|
124
|
+
with:
|
|
125
|
+
jobs: ${{ toJSON(needs) }}
|