diff --git a/.github/actions/medusa-action/Dockerfile b/.github/actions/medusa-action/Dockerfile new file mode 100644 index 00000000..1440343b --- /dev/null +++ b/.github/actions/medusa-action/Dockerfile @@ -0,0 +1,9 @@ +FROM trailofbits/eth-security-toolbox:latest + +COPY entrypoint.sh /entrypoint.sh +RUN foundryup +USER root +RUN cp -rT /home/ethsec/ /root +ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/root/.local/bin:/root/.vyper/bin:/root/.foundry/bin:/root/.vyper/bin + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/.github/actions/medusa-action/action.yml b/.github/actions/medusa-action/action.yml new file mode 100644 index 00000000..a9f8c262 --- /dev/null +++ b/.github/actions/medusa-action/action.yml @@ -0,0 +1,49 @@ +name: "medusa-action" + +description: "Run Medusa, the smart contract fuzzer" + +branding: + icon: 'shield' + color: 'red' + +inputs: + timeout: + description: "Time to run the campaign" + required: false + output-file: + description: "Capture medusa's output into this file. The path must be relative to the repository root." + required: false + negate-exit-status: + description: "Apply logical NOT to medusa-test's exit status (for testing the action)" + required: false + medusa-workdir: + description: "Path to run medusa-test from." + required: false + internal-github-workspace: + # Do not set manually. This is a hacky way to pass the host workspace to inside the action + # This is used to improve compatibility when using ignore-compile. + # GitHub rewrites the argument if it is passed directly, to we use toJSON to "transform" + # it and avoid the remapping done by GitHub Actions. + default: ${{ toJSON(github.workspace) }} + +outputs: + output-file: + description: "If produced, the file containing medusa-test's output, relative to the repository root." + value: ${{ steps.medusa.outputs.output-file }} + +runs: + using: "composite" + steps: + - run: | + docker build -t eth-security-toolbox:latest ${{ github.action_path }} + shell: bash + - id: medusa + run: | + # medusa campaign + ${{ github.action_path }}/launch.sh eth-security-toolbox:latest + shell: bash + env: + INPUT_NEGATE-EXIT-STATUS: ${{ inputs.negate-exit-status }} + INPUT_MEDUSA-WORKDIR: ${{ inputs.medusa-workdir }} + INPUT_OUTPUT-FILE: ${{ inputs.output-file }} + INPUT_INTERNAL-GITHUB-WORKSPACE: ${{ inputs.internal-github-workspace }} diff --git a/.github/actions/medusa-action/entrypoint.sh b/.github/actions/medusa-action/entrypoint.sh new file mode 100755 index 00000000..96d667ad --- /dev/null +++ b/.github/actions/medusa-action/entrypoint.sh @@ -0,0 +1,67 @@ +#! /bin/bash + +set -eu + +OPTIONS="contract config format corpus-dir test-limit test-mode shrink-limit \ +seq-len contract-addr deployer sender seed crytic-args solc-args" + +SWITCHES="multi-abi" + +# smoelius: `get` works for non-standard variable names like `INPUT_CORPUS-DIR`. +get() { + env | sed -n "s/^$1=\(.*\)/\1/;T;p" +} + +compatibility_link() +{ + HOST_GITHUB_WORKSPACE="$(get INPUT_INTERNAL-GITHUB-WORKSPACE | tr -d \")" + if [[ -d "$GITHUB_WORKSPACE" ]]; then + mkdir -p "$(dirname "$HOST_GITHUB_WORKSPACE")" + ln -s "$GITHUB_WORKSPACE" "$HOST_GITHUB_WORKSPACE" + echo "[-] Applied compatibility link: $HOST_GITHUB_WORKSPACE -> $GITHUB_WORKSPACE" + fi +} + +compatibility_link + +CMD=(medusa fuzz) + +for OPTION in $OPTIONS; do + NAME=INPUT_"${OPTION^^}" + VALUE="$(get "$NAME")" + if [[ -n "$VALUE" ]]; then + CMD+=(--"$OPTION" "$VALUE") + fi +done + +for SWITCH in $SWITCHES; do + NAME=INPUT_"${SWITCH^^}" + VALUE="$(get "$NAME")" + if [[ -n "$VALUE" ]]; then + CMD+=(--"$SWITCH") + fi +done + +echo "medusa version: $(medusa --version)" >&2 +echo "medusa command line: ${CMD[@]}" >&2 +echo 'PATH: ' "$PATH" >&2 +echo 'foundry version: ' "$(forge --version)" >&2 +echo >&2 + +OUTPUT_FILE="$(get 'INPUT_OUTPUT-FILE')" +if [[ -n "$OUTPUT_FILE" ]]; then + echo "::set-output name=output-file::$OUTPUT_FILE" + # tee stdout to $OUTPUT_FILE to capture medusa's output + exec > >(tee "$OUTPUT_FILE") +fi + +WORKDIR="$(get 'INPUT_MEDUSA-WORKDIR')" +if [[ -n "$WORKDIR" ]]; then + cd "$WORKDIR" +fi + +if [[ -n "$(get 'INPUT_NEGATE-EXIT-STATUS')" ]]; then + ! "${CMD[@]}" +else + "${CMD[@]}" +fi diff --git a/.github/actions/medusa-action/launch.sh b/.github/actions/medusa-action/launch.sh new file mode 100755 index 00000000..bb7d9c6b --- /dev/null +++ b/.github/actions/medusa-action/launch.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +IMAGE=$1 +INPUTS=$(env | cut -f1 -d= | grep '^INPUT_') + +CMD=(docker run --rm -v "$PWD:/github/workspace" --workdir /github/workspace -e GITHUB_WORKSPACE=/github/workspace) + +for VARNAME in $INPUTS; do + CMD+=(-e "$VARNAME") +done + +CMD+=("$IMAGE") + +"${CMD[@]}" diff --git a/.github/workflows/medusa.yml b/.github/workflows/medusa.yml new file mode 100644 index 00000000..730072d4 --- /dev/null +++ b/.github/workflows/medusa.yml @@ -0,0 +1,29 @@ +name: Run medusa +on: + push: + branches: [main, dev] + pull_request: + branches: + - "**" +jobs: + medusa-tests: + name: Medusa Test + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: 20.x + cache: yarn + + - name: Install dependencies + run: yarn --frozen-lockfile --network-concurrency 1 + + - name: Run Medusa + uses: ./.github/actions/medusa-action diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 18624462..2c1a9ace 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -64,42 +64,6 @@ jobs: - name: Run tests run: yarn test:integration - echidna-tests: - name: Echidna Test - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 - with: - version: nightly - - - name: Use Node.js - uses: actions/setup-node@v4 - with: - node-version: 20.x - cache: 'yarn' - - - name: Install dependencies - run: yarn --frozen-lockfile --network-concurrency 1 - - - name: Compile contracts - run: | - forge build --build-info - - - name: Run Echidna - uses: crytic/echidna-action@v2 - with: - files: . - contract: InvariantGreeter - test-mode: assertion - crytic-args: --ignore-compile - halmos-tests: name: Run symbolic execution tests runs-on: ubuntu-latest