Skip to content

Commit

Permalink
Merge branch 'master' into dev/coverage-collapsibility
Browse files Browse the repository at this point in the history
  • Loading branch information
Xenomega committed May 29, 2024
2 parents 85a7d35 + 6750032 commit a7635d0
Show file tree
Hide file tree
Showing 123 changed files with 4,087 additions and 495 deletions.
90 changes: 74 additions & 16 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ on:
push:
branches:
- master
tags:
- "v*"
pull_request:
branches:
- master
Expand All @@ -22,7 +24,10 @@ jobs:
needs: [lint, test]
strategy:
matrix:
environment: [ubuntu-latest, macos-latest, windows-latest]
environment: [ubuntu-latest, macos-12, macos-14, windows-latest]
permissions:
contents: read
id-token: write

runs-on: ${{ matrix.environment }}
timeout-minutes: 10
Expand All @@ -44,32 +49,76 @@ jobs:
printf 'TEMP=%s\\tmpdir\n' "$DIR" | tee -a "$GITHUB_ENV"
go env
- uses: actions/setup-go@v4
- uses: actions/setup-go@v5
with:
go-version: "^1.18.1"
# disable caching during release (tag) builds
cache: ${{ !startsWith(github.ref, 'refs/tags/') }}

- name: Build (Linux and macOS)
if: runner.os == 'Linux' || runner.os == 'macOS'
run: go build -o medusa -v .

- name: Compress (Linux and macOS)
if: runner.os == 'Linux' || runner.os == 'macOS'
run: tar -czvf medusa.tar.gz medusa
run: tar -czvf medusa-${{ runner.os }}-${{ runner.arch }}.tar.gz medusa

- name: Build (Windows)
if: runner.os == 'Windows'
run: go build -o medusa.exe -v .

- name: Compress (Windows)
if: runner.os == 'Windows'
run: tar -czvf medusa.tar.gz medusa.exe
run: tar -czvf medusa-${{ runner.os }}-${{ runner.arch }}.tar.gz medusa.exe

- name: Upload artifact on merge to master
if: github.ref == 'refs/heads/master'
uses: actions/upload-artifact@v3
- name: Rename for release
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
shell: bash
run: |
[ ! -f medusa-Linux-X64.tar.gz ] || mv medusa-Linux-X64.tar.gz medusa-linux-x64.tar.gz
[ ! -f medusa-macOS-X64.tar.gz ] || mv medusa-macOS-X64.tar.gz medusa-mac-x64.tar.gz
[ ! -f medusa-macOS-ARM64.tar.gz ] || mv medusa-macOS-ARM64.tar.gz medusa-mac-arm64.tar.gz
[ ! -f medusa-Windows-X64.tar.gz ] || mv medusa-Windows-X64.tar.gz medusa-win-x64.tar.gz
- name: Sign artifact
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
uses: sigstore/[email protected]
with:
inputs: ./medusa-*.tar.gz

- name: Upload artifact
if: github.ref == 'refs/heads/master' || (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/'))
uses: actions/upload-artifact@v4
with:
name: medusa-${{ runner.os }}
path: medusa.tar.gz
name: medusa-${{ runner.os }}-${{ runner.arch }}
path: |
./medusa-*.tar.gz
./medusa-*.tar.gz.sigstore
release:
needs: [build]
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
permissions:
contents: write

runs-on: ubuntu-latest
timeout-minutes: 10

steps:
- name: Download binaries
uses: actions/download-artifact@v4
with:
pattern: medusa-*
merge-multiple: true

- name: Create GitHub release and upload binaries
uses: softprops/action-gh-release@9d7c94cfd0a1f3ed45544c887983e9fa900f0564 # v2.0.4
with:
draft: true
name: "${{ github.ref_name }}"
files: |
./medusa-*.tar.gz
./medusa-*.tar.gz.sigstore
lint:
runs-on: ubuntu-latest
Expand All @@ -78,7 +127,7 @@ jobs:
steps:
- uses: actions/checkout@v4

- uses: actions/setup-go@v4
- uses: actions/setup-go@v5
with:
go-version: "^1.18.1"

Expand Down Expand Up @@ -110,14 +159,18 @@ jobs:
test:
strategy:
matrix:
environment: [ubuntu-latest, macos-latest, windows-latest]
environment: [ubuntu-latest, macos-12, macos-14, windows-latest]

runs-on: ${{ matrix.environment }}
timeout-minutes: 20

steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.10"

- name: Speed up Go, Python, Node (Windows)
if: runner.os == 'Windows'
run: |
Expand All @@ -142,11 +195,11 @@ jobs:
npm config set cache "$DIR\\npm-cache" --global
echo "::endgroup::"
- uses: actions/setup-go@v4
- uses: actions/setup-go@v5
with:
go-version: "^1.18.1"

- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version: 18.15

Expand All @@ -155,7 +208,7 @@ jobs:

- name: Install Python dependencies
run: |
pip3 install --no-cache-dir setuptools solc-select crytic-compile
pip3 install --no-cache-dir solc-select crytic-compile
- name: Install solc
run: |
Expand All @@ -165,9 +218,14 @@ jobs:
run: go test ./...

all-checks:
needs: [lint, test, build]
if: always()
needs: [lint, test, build, release]

runs-on: ubuntu-latest

steps:
- run: true
- name: Decide whether the needed jobs succeeded or failed
uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2
with:
allowed-skips: release
jobs: ${{ toJSON(needs) }}
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,7 @@
*node_modules/

# Medusa binary
medusa
medusa

# Medusa docs
docs/book
94 changes: 7 additions & 87 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,98 +17,18 @@ It provides parallelized fuzz testing of smart contracts through CLI, or its Go
- ✔️**Extensible low-level testing API** through events and hooks provided throughout the fuzzer, workers, and test chains.
-**Extensible high-level testing API** allowing for the addition of per-contract or global post call/event property tests with minimal effort.

## Installation
## Documentation

### Precompiled binaries
To learn more about how to install and use `medusa`, please refer to our [documentation](./docs/src/SUMMARY.md).

To use `medusa`, ensure you have:
For a better viewing experience, we recommend you install [mdbook](https://rust-lang.github.io/mdBook/guide/installation.html)
and then running the following steps from medusa's source directory:

- [crytic-compile](https://github.com/crytic/crytic-compile) (`pip3 install crytic-compile`)
- a suitable compilation framework (e.g. `solc`, `hardhat`) installed on your machine. We recommend [solc-select](https://github.com/crytic/solc-select) to quickly switch between Solidity compiler versions.

You can then fetch the latest binaries for your platform from our [GitHub Releases](https://github.com/crytic/medusa/releases) page.

### Building from source

#### Requirements

- You must have at least go 1.18 installed.
- [Windows only] The `go-ethereum` dependency may require [TDM-GCC](https://jmeubank.github.io/tdm-gcc/) to build.

#### Steps

- Clone the repository, then execute `go build` in the repository root.
- Go will automatically fetch all dependencies and build a binary for you in the same folder when completed.

## Usage

Although we recommend users run `medusa` in a configuration file driven format for more customizability, you can also run `medusa` through the CLI directly.
We provide instructions for both below.

We recommend you familiarize yourself with writing [assertion](https://github.com/crytic/building-secure-contracts/blob/master/program-analysis/echidna/basic/assertion-checking.md) and [property](https://github.com/crytic/building-secure-contracts/blob/master/program-analysis/echidna/introduction/how-to-test-a-property.md) tests for Echidna. `medusa` supports Echidna-like property testing with config-defined function prefixes (default: `fuzz_`) and assertion testing using Solidity `assert(...)` statements.

### Command-line only

You can use the following command to run `medusa` against a contract:

```console
medusa fuzz --target contract.sol --deployment-order ContractName
```bash
cd docs
mdbook serve
```

Where:

- `--target` specifies the path `crytic-compile` should use to compile contracts
- `--deployment-order` specifies comma-separated names of contracts to be deployed for testing.

**Note:** Check out the [command-line interface](https://github.com/crytic/medusa/wiki/Command-Line-Interface) wiki page, or run `medusa --help` for more information.

### Configuration file driven

The preferred method to use medusa is to enter your project directory (hardhat directory, or directory with your contracts),
then execute the following command:

```console
medusa init
```

This will create a `medusa.json` in your current folder. There are two required fields that should be set correctly:

- Set your `"target"` under `"compilation"` to point to the file/directory which `crytic-compile` should use to build your contracts.
- Put the names of any contracts you wish to deploy and run tests against in the `"deploymentOrder"` field. This must be non-empty.

After you have a configuration in place, you can execute:

```console
medusa fuzz
```

This will use the `medusa.json` configuration in the current directory and begin the fuzzing campaign.

**Note:** Check out the [project configuration](https://github.com/crytic/medusa/wiki/Project-Configuration) wiki page, or run `medusa --help` for more information.

## Running Unit Tests

First, install [crytic-compile](https://github.com/crytic/crytic-compile), [solc-select](https://github.com/crytic/solc-select), and ensure you have `solc` (version >=0.8.7), and `hardhat` available on your system.

- From the root of the repository, invoke `go test -v ./...` on through command-line to run tests from all packages at or below the root.
- Or enter each package directory to run `go test -v .` to test the immediate package.
- Note: the `-v` parameter provides verbose output.
- Otherwise, use an IDE like [GoLand](https://www.jetbrains.com/go/) to visualize the tests and logically separate output.

## FAQs

**Why create `medusa` if Echidna is already working just fine?**

With `medusa`, we are exploring a different EVM implementation and language for our smart contract fuzzer. We believe that
experimenting with a new fuzzer provides us with the following benefits:

- Since `medusa` is written in Go, we believe that this will **lower the barrier of entry for external contributions**.
We have taken great care in thoroughly commenting our code so that it is easy for new contributors to get up-to-speed and start contributing!
- The use of Go allows us to build an API to hook into the various parts of the fuzzer to build custom testing methodologies. See the [API Overview (WIP)](<https://github.com/crytic/medusa/wiki/API-Overview-(WIP)>) section in the Wiki for more details.
- Our forked version of go-ethereum, [`medusa-geth`](https://github.com/crytic/medusa-geth), exhibits behavior that is closer to that of the EVM in production environments.
- We can take the lessons we learned while developing Echidna to create a fuzzer that is just as feature-rich but with additional capabilities to
create powerful and unique testing methodologies.

## Contributing

For information about how to contribute to this project, check out the [CONTRIBUTING](./CONTRIBUTING.md) guidelines.
Expand Down
21 changes: 21 additions & 0 deletions chain/standard_cheat_code_contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,27 @@ func getStandardCheatCodeContract(tracer *cheatCodeTracer) (*CheatCodeContract,
},
)

// snapshot: Takes a snapshot of the current state of the evm and returns the id associated with the snapshot
contract.addMethod(
"snapshot", abi.Arguments{}, abi.Arguments{{Type: typeUint256}},
func(tracer *cheatCodeTracer, inputs []any) ([]any, *cheatCodeRawReturnData) {
snapshotID := tracer.evm.StateDB.Snapshot()

return []any{snapshotID}, nil
},
)

// revertTo(uint256): Revert the state of the evm to a previous snapshot. Takes the snapshot id to revert to.
contract.addMethod(
"revertTo", abi.Arguments{{Type: typeUint256}}, abi.Arguments{{Type: typeBool}},
func(tracer *cheatCodeTracer, inputs []any) ([]any, *cheatCodeRawReturnData) {
snapshotID := inputs[0].(*big.Int)
tracer.evm.StateDB.RevertToSnapshot(int(snapshotID.Int64()))

return []any{true}, nil
},
)

// FFI: Run arbitrary command on base OS
contract.addMethod(
"ffi", abi.Arguments{{Type: typeStringSlice}}, abi.Arguments{{Type: typeBytes}},
Expand Down
41 changes: 41 additions & 0 deletions cmd/exitcodes/error_with_exit_code.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package exitcodes

// ErrorWithExitCode is an `error` type that wraps an existing error and exit code, providing exit codes
// for a given error if they are bubbled up to the top-level.
type ErrorWithExitCode struct {
err error
exitCode int
}

// NewErrorWithExitCode creates a new error (ErrorWithExitCode) with the provided internal error and exit code.
func NewErrorWithExitCode(err error, exitCode int) *ErrorWithExitCode {
return &ErrorWithExitCode{
err: err,
exitCode: exitCode,
}
}

// Error returns the error message string, implementing the `error` interface.
func (e *ErrorWithExitCode) Error() string {
if e.err == nil {
return ""
}
return e.err.Error()
}

// GetInnerErrorAndExitCode checks the given exit code that the application should exit with, if this error is bubbled
// to the top-level. This will be 0 for a nil error, 1 for a generic error, or arbitrary if the error is of type
// ErrorWithExitCode.
// Returns the error (or inner error if it is an ErrorWithExitCode error type), along with the exit code associated
// with the error.
func GetInnerErrorAndExitCode(err error) (error, int) {
// If we have no error, return 0, if we have a generic error, return 1, if we have a custom error code, unwrap
// and return it.
if err == nil {
return nil, ExitCodeSuccess
} else if unwrappedErr, ok := err.(*ErrorWithExitCode); ok {
return unwrappedErr.err, unwrappedErr.exitCode
} else {
return err, ExitCodeGeneralError
}
}
25 changes: 25 additions & 0 deletions cmd/exitcodes/exit_codes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package exitcodes

const (
// ================================
// Platform-universal exit codes
// ================================

// ExitCodeSuccess indicates no errors or failures had occurred.
ExitCodeSuccess = 0

// ExitCodeGeneralError indicates some type of general error occurred.
ExitCodeGeneralError = 1

// ================================
// Application-specific exit codes
// ================================
// Note: Despite not being standardized, exit codes 2-5 are often used for common use cases, so we avoid them.

// ExitCodeHandledError indicates that there was an error that was logged already and does not need to be handled
// by main.
ExitCodeHandledError = 6

// ExitCodeTestFailed indicates a test case had failed.
ExitCodeTestFailed = 7
)
Loading

0 comments on commit a7635d0

Please sign in to comment.