diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c5c97e7d..ea20f899 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,6 +5,8 @@ on: push: branches: - master + tags: + - "v*" pull_request: branches: - master @@ -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 @@ -44,9 +49,11 @@ 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' @@ -54,7 +61,7 @@ jobs: - 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' @@ -62,14 +69,56 @@ jobs: - 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/gh-action-sigstore-python@v2.1.1 + 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 @@ -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" @@ -110,7 +159,7 @@ 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 @@ -118,6 +167,10 @@ jobs: 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: | @@ -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 @@ -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: | @@ -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) }} diff --git a/.gitignore b/.gitignore index 051bf3b0..b1251402 100644 --- a/.gitignore +++ b/.gitignore @@ -20,4 +20,7 @@ *node_modules/ # Medusa binary -medusa \ No newline at end of file +medusa + +# Medusa docs +docs/book diff --git a/README.md b/README.md index c4597293..18f154b5 100644 --- a/README.md +++ b/README.md @@ -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)]() 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. diff --git a/chain/standard_cheat_code_contract.go b/chain/standard_cheat_code_contract.go index 79c2d3ad..144c7e77 100644 --- a/chain/standard_cheat_code_contract.go +++ b/chain/standard_cheat_code_contract.go @@ -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}}, diff --git a/cmd/exitcodes/error_with_exit_code.go b/cmd/exitcodes/error_with_exit_code.go new file mode 100644 index 00000000..96102f7f --- /dev/null +++ b/cmd/exitcodes/error_with_exit_code.go @@ -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 + } +} diff --git a/cmd/exitcodes/exit_codes.go b/cmd/exitcodes/exit_codes.go new file mode 100644 index 00000000..5bbed621 --- /dev/null +++ b/cmd/exitcodes/exit_codes.go @@ -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 +) diff --git a/cmd/fuzz.go b/cmd/fuzz.go index 3fc22fd3..188f5d95 100644 --- a/cmd/fuzz.go +++ b/cmd/fuzz.go @@ -2,6 +2,7 @@ package cmd import ( "fmt" + "github.com/crytic/medusa/cmd/exitcodes" "github.com/crytic/medusa/logging/colors" "os" "os/signal" @@ -144,9 +145,9 @@ func cmdRunFuzz(cmd *cobra.Command, args []string) error { } // Create our fuzzing - fuzzer, err := fuzzing.NewFuzzer(*projectConfig) - if err != nil { - return err + fuzzer, fuzzErr := fuzzing.NewFuzzer(*projectConfig) + if fuzzErr != nil { + return exitcodes.NewErrorWithExitCode(fuzzErr, exitcodes.ExitCodeHandledError) } // Stop our fuzzing on keyboard interrupts @@ -158,7 +159,15 @@ func cmdRunFuzz(cmd *cobra.Command, args []string) error { }() // Start the fuzzing process with our cancellable context. - err = fuzzer.Start() + fuzzErr = fuzzer.Start() + if fuzzErr != nil { + return exitcodes.NewErrorWithExitCode(fuzzErr, exitcodes.ExitCodeHandledError) + } + + // If we have no error and failed test cases, we'll want to return a special exit code + if fuzzErr == nil && len(fuzzer.TestCasesWithStatus(fuzzing.TestCaseStatusFailed)) > 0 { + return exitcodes.NewErrorWithExitCode(fuzzErr, exitcodes.ExitCodeTestFailed) + } - return err + return fuzzErr } diff --git a/cmd/fuzz_flags.go b/cmd/fuzz_flags.go index e1999804..9e1c9d37 100644 --- a/cmd/fuzz_flags.go +++ b/cmd/fuzz_flags.go @@ -21,8 +21,8 @@ func addFuzzFlags() error { // Config file fuzzCmd.Flags().String("config", "", "path to config file") - // Target - fuzzCmd.Flags().String("target", "", TargetFlagDescription) + // Compilation Target + fuzzCmd.Flags().String("compilation-target", "", TargetFlagDescription) // Number of workers fuzzCmd.Flags().Int("workers", 0, @@ -40,14 +40,13 @@ func addFuzzFlags() error { fuzzCmd.Flags().Int("seq-len", 0, fmt.Sprintf("maximum transactions to run in sequence (unless a config file is provided, default is %d)", defaultConfig.Fuzzing.CallSequenceLength)) - // Deployment order - fuzzCmd.Flags().StringSlice("deployment-order", []string{}, - fmt.Sprintf("order in which to deploy target contracts (unless a config file is provided, default is %v)", defaultConfig.Fuzzing.DeploymentOrder)) + // Target contracts + fuzzCmd.Flags().StringSlice("target-contracts", []string{}, + fmt.Sprintf("target contracts for fuzz testing (unless a config file is provided, default is %v)", defaultConfig.Fuzzing.TargetContracts)) // Corpus directory - // TODO: Update description when we add "coverage reports" feature fuzzCmd.Flags().String("corpus-dir", "", - fmt.Sprintf("directory path for corpus items (unless a config file is provided, default is %q)", defaultConfig.Fuzzing.CorpusDirectory)) + fmt.Sprintf("directory path for corpus items and coverage reports (unless a config file is provided, default is %q)", defaultConfig.Fuzzing.CorpusDirectory)) // Senders fuzzCmd.Flags().StringSlice("senders", []string{}, @@ -57,14 +56,6 @@ func addFuzzFlags() error { fuzzCmd.Flags().String("deployer", "", "account address used to deploy contracts") - // Assertion mode - fuzzCmd.Flags().Bool("assertion-mode", false, - fmt.Sprintf("enable assertion mode (unless a config file is provided, default is %t)", defaultConfig.Fuzzing.Testing.AssertionTesting.Enabled)) - - // Optimization mode - fuzzCmd.Flags().Bool("optimization-mode", false, - fmt.Sprintf("enable optimization mode (unless a config file is provided, default is %t)", defaultConfig.Fuzzing.Testing.OptimizationTesting.Enabled)) - // Trace all fuzzCmd.Flags().Bool("trace-all", false, fmt.Sprintf("print the execution trace for every element in a shrunken call sequence instead of only the last element (unless a config file is provided, default is %t)", defaultConfig.Fuzzing.Testing.TraceAll)) @@ -79,10 +70,10 @@ func addFuzzFlags() error { func updateProjectConfigWithFuzzFlags(cmd *cobra.Command, projectConfig *config.ProjectConfig) error { var err error - // If --target was used - if cmd.Flags().Changed("target") { + // If --compilation-target was used + if cmd.Flags().Changed("compilation-target") { // Get the new target - newTarget, err := cmd.Flags().GetString("target") + newTarget, err := cmd.Flags().GetString("compilation-target") if err != nil { return err } @@ -125,9 +116,9 @@ func updateProjectConfigWithFuzzFlags(cmd *cobra.Command, projectConfig *config. } } - // Update deployment order - if cmd.Flags().Changed("deployment-order") { - projectConfig.Fuzzing.DeploymentOrder, err = cmd.Flags().GetStringSlice("deployment-order") + // Update target contracts + if cmd.Flags().Changed("target-contracts") { + projectConfig.Fuzzing.TargetContracts, err = cmd.Flags().GetStringSlice("target-contracts") if err != nil { return err } @@ -157,22 +148,6 @@ func updateProjectConfigWithFuzzFlags(cmd *cobra.Command, projectConfig *config. } } - // Update assertion mode enablement - if cmd.Flags().Changed("assertion-mode") { - projectConfig.Fuzzing.Testing.AssertionTesting.Enabled, err = cmd.Flags().GetBool("assertion-mode") - if err != nil { - return err - } - } - - // Update optimization mode enablement - if cmd.Flags().Changed("optimization-mode") { - projectConfig.Fuzzing.Testing.OptimizationTesting.Enabled, err = cmd.Flags().GetBool("optimization-mode") - if err != nil { - return err - } - } - // Update trace all enablement if cmd.Flags().Changed("trace-all") { projectConfig.Fuzzing.Testing.TraceAll, err = cmd.Flags().GetBool("trace-all") diff --git a/cmd/init_flags.go b/cmd/init_flags.go index 40c3499a..dfe6d8d6 100644 --- a/cmd/init_flags.go +++ b/cmd/init_flags.go @@ -10,18 +10,18 @@ func addInitFlags() error { // Output path for configuration initCmd.Flags().String("out", "", "output path for the new project configuration file") - // Target file / directory - initCmd.Flags().String("target", "", TargetFlagDescription) + // Target file / directory for compilation + initCmd.Flags().String("compilation-target", "", TargetFlagDescription) return nil } // updateProjectConfigWithInitFlags will update the given projectConfig with any CLI arguments that were provided to the init command func updateProjectConfigWithInitFlags(cmd *cobra.Command, projectConfig *config.ProjectConfig) error { - // If --target was used - if cmd.Flags().Changed("target") { + // If --compilation-target was used + if cmd.Flags().Changed("compilation-target") { // Get the new target - newTarget, err := cmd.Flags().GetString("target") + newTarget, err := cmd.Flags().GetString("compilation-target") if err != nil { return err } diff --git a/cmd/root.go b/cmd/root.go index ae03d15d..6dd04a3d 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -7,7 +7,7 @@ import ( "os" ) -const version = "0.1.2" +const version = "0.1.3" // rootCmd represents the root CLI command object which all other commands stem from. var rootCmd = &cobra.Command{ diff --git a/docs/book.toml b/docs/book.toml new file mode 100644 index 00000000..c09f7b92 --- /dev/null +++ b/docs/book.toml @@ -0,0 +1,17 @@ +[book] +title = "medusa" +authors = ["Trail of Bits"] +language = "en" +multilingual = false +src = "src" +description = "This repository, brought to you by Trail of Bits, contains the documentation files for the medusa fuzzer." + +[output.html] +git-repository-url = "https://github.com/crytic/medusa" +edit-url-template = "https://github.com/crytic/medusa/edit/master/docs/{path}" +additional-css = ["src/static/custom.css"] +default-theme = "light" + +[output.html.fold] +enable = true +level = 1 \ No newline at end of file diff --git a/docs/src/README.md b/docs/src/README.md new file mode 100644 index 00000000..e9ceb477 --- /dev/null +++ b/docs/src/README.md @@ -0,0 +1,18 @@ +![medusa_logo](./static/medusa_logo.png) + +`medusa` is a cross-platform go-ethereum-based smart contract fuzzer inspired by Echidna. It provides parallelized fuzz +testing of smart contracts through CLI, or its Go API that allows custom user-extended testing methodology. + +## Table of Contents + +- [Getting Started](./getting_started/installation.md): Learn how to install `medusa` and how to set it up for your first project. +- [Project Configuration](./project_configuration/overview.md): Learn how to set up `medusa` for your project as well as + the vast number of configuration options that can be set up based on your project needs. +- [Command Line Interface](./cli/overview.md): Learn how to use `medusa`'s CLI. +- [Writing Tests](./testing/overview.md): Learn how to write tests with `medusa` +- [API (WIP)](./api/api_overview.md): Learn about `medusa`'s Go API that can be used to perform advanced testing + methodologies and extend `medusa`'s capabilities. +- Appendices + - [Cheatcodes](./cheatcodes/cheatcodes_overview.md): Learn about the various cheatcodes that are supported by `medusa`. + - [Console Logging](./console_logging.md): Learn about how to use `console.log` with `medusa`. + - [FAQ](./faq.md) diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md new file mode 100644 index 00000000..383a71ef --- /dev/null +++ b/docs/src/SUMMARY.md @@ -0,0 +1,69 @@ +# Summary + +[Introduction](./README.md) + +# Getting Started + +- [Installation](./getting_started/installation.md) +- [First Steps](./getting_started/first_steps.md) + +# Project Configuration + +- [Configuration Overview](project_configuration/overview.md) +- [Fuzzing Configuration](project_configuration/fuzzing_config.md) +- [Testing Configuration](project_configuration/testing_config.md) +- [Chain Configuration](project_configuration/chain_config.md) +- [Compilation Configuration](project_configuration/compilation_config.md) +- [Logging Configuration](project_configuration/logging_config.md) + +# Command Line Interface (CLI) + +- [CLI Overview](./cli/overview.md) +- [init](./cli/init.md) +- [fuzz](./cli/fuzz.md) +- [completion](./cli/completion.md) + +# Writing Tests + +- [Testing Overview](./testing/overview.md) +- [The Fuzzing Lifecycle](./testing/fuzzing_lifecycle.md) +- [Types of Invariants](./testing/invariants.md) +- [Writing Function-Level Invariants](./testing/writing-function-level-invariants.md) +- [Writing System-Level Invariants (WIP)](./testing/writing-system-level-invariants.md) +- [Coverage Reports (WIP)](./testing/coverage_reports.md) + +# API + +- [API Overview (WIP)](api/api_overview.md) + +# Appendices + +- [Cheatcodes](cheatcodes/cheatcodes_overview.md) + - [warp](./cheatcodes/warp.md) + - [roll](./cheatcodes/roll.md) + - [fee](./cheatcodes/fee.md) + - [difficulty](./cheatcodes/difficulty.md) + - [chainId](./cheatcodes/chain_id.md) + - [store](./cheatcodes/store.md) + - [load](./cheatcodes/load.md) + - [etch](./cheatcodes/etch.md) + - [deal](./cheatcodes/deal.md) + - [snapshot](./cheatcodes/snapshot.md) + - [getNonce](./cheatcodes/get_nonce.md) + - [setNonce](./cheatcodes/set_nonce.md) + - [coinbase](./cheatcodes/coinbase.md) + - [prank](./cheatcodes/prank.md) + - [prankHere](./cheatcodes/prank_here.md) + - [ffi](./cheatcodes/ffi.md) + - [addr](./cheatcodes/addr.md) + - [sign](./cheatcodes/sign.md) + - [toString](./cheatcodes/to_string.md) + - [parseBytes](./cheatcodes/parse_bytes.md) + - [parseBytes32](./cheatcodes/parse_bytes32.md) + - [parseInt](./cheatcodes/parse_int.md) + - [parseUint](./cheatcodes/parse_uint.md) + - [parseBool](./cheatcodes/parse_bool.md) + - [parseAddress](./cheatcodes/parse_address.md) +- [Console Logging](./console_logging.md) + +[FAQ](./faq.md) diff --git a/docs/src/advanced.md b/docs/src/advanced.md new file mode 100644 index 00000000..a4a1f87f --- /dev/null +++ b/docs/src/advanced.md @@ -0,0 +1,16 @@ +> **Definition**: Stateful fuzzing is the process of maintaining EVM state across multiple fuzzed transactions. + +Stateful fuzzing is an incredibly powerful feature because it allows medusa to test your system **end-to-end**. Let's +take, for example, a staking system where you have the ability to `deposit`, `stake`, `unstake`, and `withdraw`. Because +medusa can execute an array of transactions, medusa can call [`deposit`, `stake`, `unstake`, `withdraw`] inorder and test the +whole system in one fell swoop. It is very important to note that medusa was not _forced_ to call those functions in +sequence. Medusa, over time, will identify that calling deposit allows it to stake tokens and having a staked balance +allows it to unstake, and so on. + +In contrast, having a call sequence length of 1 is called **stateless fuzzing**. + +> **Definition**: Stateless fuzzing is the process of executing a single transaction before resetting the EVM state. + +Stateless fuzzing is useful for arithmetic libraries or isolated functions where state does not need to be maintained +across transactions. Stateless fuzzing, although faster, is not useful for larger systems that have many code paths with +nuanced and complex invariants. diff --git a/docs/src/api/api_overview.md b/docs/src/api/api_overview.md new file mode 100644 index 00000000..ab3e6dee --- /dev/null +++ b/docs/src/api/api_overview.md @@ -0,0 +1,185 @@ +# API Overview (WIP) + +`medusa` offers a lower level API to hook into various parts of the fuzzer, its workers, and underlying chains. Although assertion and property testing are two built-in testing providers, they are implementing using events and hooks offered throughout the `Fuzzer`, `FuzzerWorker`(s), and underlying `TestChain`. These same hooks can be used by external developers wishing to implement their own customing testing methodology. In the sections below, we explore some of the relevant components throughout `medusa`, their events/hooks, an example of creating custom testing methodology with it. + +## Component overview + +A rudimentary description of the objects/providers and their roles are explained below. + +### Data types + +- `ProjectConfig`: This defines the configuration for the Fuzzer, including the targets to compile, deploy, and how to fuzz or test them. + +- `ValueSet`: This is an object that acts as a dictionary of values, used in mutation operations. It is populated at compilation time with some rudimentary static analysis. + +- `Contract`: Can be thought of as a "contract definition", it is a data type which stores the name of the contract, and a reference to the underlying `CompiledContract`, a definition derived from compilation, containing the bytecode, source maps, ABI, etc. + +- `CallSequence`: This represents a list of `CallSequenceElement`s, which define a transaction to send, the suggested block number and timestamp delay to use, and stores a reference to the block/transaction/results when it is executed (for later querying in tests). They are used to generate and execute transaction sequences in the fuzzer. + +- `CoverageMaps` define a list of `CoverageMap` objects, which record all instruction offsets executed for a given contract address and code hash. + +- `TestCase` defines the interface for a test that the `Fuzzer` will track. It simply defines a name, ID, status (not started, running, passed, failed) and message for the `Fuzzer`. + +### Providers + +- `ValueGenerator`: This is an object that provides methods to generate values of different kinds for transactions. Examples include the `RandomValueGenerator` and superceding `MutationalValueGenerator`. They are provided a `ValueSet` by their worker, which they may use in generation operations. + +- `TestChain`: This is a fake chain that operates on fake block structures created for the purpose of testing. Rather than operating on `types.Transaction` (which requires signing), it operates on `core.Message`s, which are derived from transactions and simply allow you to set the `sender` field. It is responsible for: + + - Maintaining state of the chain (blocks, transactions in them, results/receipts) + - Providing methods to create blocks, add transactions to them, commit them to chain, revert to previous block numbers. + - Allowing spoofing of block number and timestamp (commiting block number 1, then 50, jumping 49 blocks ahead), while simulating the existence of intermediate blocks. + - Provides methods to add tracers such as `evm.Logger` (standard go-ethereum tracers) or extend them with an additional interface (`TestChainTracer`) to also store any captured traced information in the execution results. This allows you to trace EVM execution for certain conditions, store results, and query them at a later time for testing. + +- `Fuzzer`: This is the main provider for the fuzzing process. It takes a `ProjectConfig` and is responsible for: + + - Housing data shared between the `FuzzerWorker`s such as contract definitions, a `ValueSet` derived from compilation to use in value generation, the reference to `Corpus`, the `CoverageMaps` representing all coverage achieved, as well as maintaining `TestCase`s registered to it and printing their results. + - Compiling the targets defined by the project config and setting up state. + - Provides methods to start/stop the fuzzing process, add additional compilation targets, access the initial value set prior to fuzzing start, access corpus, config, register new test cases and report them finished. + - Starts the fuzzing process by creating a "base" `TestChain`, deploys compiled contracts, replays all corpus sequences to measure existing coverage from previous fuzzing campaign, then spawns as many `FuzzerWorker`s as configured on their own goroutines ("threads") and passes them the "base" `TestChain` (which they clone) to begin the fuzzing operation. + - Respawns `FuzzerWorker`s when they hit a config-defined reset limit for the amount of transaction sequences they should process before destroying themselves and freeing memory. + - Maintains the context for when fuzzing should stop, which all workers track. + +- `FuzzerWorker`: This describes an object spawned by the `Fuzzer` with a given "base" `TestChain` with target contracts already deployed, ready to be fuzzed. It clones this chain, then is called upon to begin creating fuzz transactions. It is responsible for: + - Maintaining a reference to the parent `Fuzzer` for any shared information between it and other workers (`Corpus`, total `CoverageMaps`, contract definitions to match deployment's bytecode, etc) + - Maintaining its own `TestChain` to run fuzzed transaction sequences. + - Maintaining its own `ValueSet` which derives from the `Fuzzer`'s `ValueSet` (populated by compilation or user-provided values through API), as each `FuzzerWorker` may populate its `ValueSet` with different runtime values depending on their own chain state. + - Spawning a `ValueGenerator` which uses the `ValueSet`, to generate values used to construct fuzzed transaction sequences. + - Most importantly, it continuously: + - Generates `CallSequence`s (a series of transactions), plays them on its `TestChain`, records the results of in each `CallSequenceElement`, and calls abstract/hookable "test functions" to indicate they should perform post-tx tests (for which they can return requests for a shrunk test sequence). + - Updates the total `CoverageMaps` and `Corpus` with the current `CallSequence` if the most recent call increased coverage. + - Processes any shrink requests from the previous step (shrink requests can define arbitrary criteria for shrinking). + - Eventually, hits the config-defined reset limit for how many sequences it should process, and destroys itself to free all memory, expecting the `Fuzzer` to respawn another in its place. + +## Creating a project configuration + +`medusa` is config-driven. To begin a fuzzing campaign on an API level, you must first define a project configuration so the fuzzer knows what contracts to compile, deploy, and how it should operate. + +When using `medusa` over command-line, it operates a project config similarly (see [docs](https://github.com/trailofbits/medusa/wiki/Project-Configuration) or [example](https://github.com/trailofbits/medusa/wiki/Example-Project-Configuration-File)). Similarly, interfacing with a `Fuzzer` requires a `ProjectConfig` object. After importing `medusa` into your Go project, you can create one like this: + +```go +// Initialize a default project config with using crytic-compile as a compilation platform, and set the target it should compile. +projectConfig := config.GetDefaultProjectConfig("crytic-compile") +err := projectConfig.Compilation.SetTarget("contract.sol") +if err != nil { + return err +} + +// You can edit any of the values as you please. +projectConfig.Fuzzing.Workers = 20 +projectConfig.Fuzzing.DeploymentOrder = []string{"TestContract1", "TestContract2"} +``` + +You may also instantiate the whole config in-line with all the fields you'd like, setting the underlying platform config yourself. + +> **NOTE**: The `CompilationConfig` and `PlatformConfig` WILL BE deprecated and replaced with something more intuitive in the future, as the `compilation` package has not been updated since the project's inception, prior to the release of generics in go 1.18. + +## Creating and starting the fuzzer + +After you have created a `ProjectConfig`, you can create a new `Fuzzer` with it, and tell it to start: + +```go + // Create our fuzzer + fuzzer, err := fuzzing.NewFuzzer(*projectConfig) + if err != nil { + return err + } + + // Start the fuzzer + err = fuzzer.Start() + if err != nil { + return err + } + + // Fetch test cases results + testCases := fuzzer.TestCases() +[...] +``` + +> **Note**: `Fuzzer.Start()` is a blocking operation. If you wish to stop, you must define a TestLimit or Timeout in your config. Otherwise start it on another goroutine and call `Fuzzer.Stop()` to stop it. + +## Events/Hooks + +### Events + +Now it may be the case that you wish to hook the `Fuzzer`, `FuzzerWorker`, or `TestChain` to provide your own functionality. You can add your own testing methodology, and even power it with your own low-level EVM execution tracers to store and query results about each call. + +There are a few events/hooks that may be useful of the bat: + +The `Fuzzer` maintains event emitters for the following events under `Fuzzer.Events.*`: + +- `FuzzerStartingEvent`: Indicates a `Fuzzer` is starting and provides a reference to it. + +- `FuzzerStoppingEvent`: Indicates a `Fuzzer` has just stopped all workers and is about to print results and exit. + +- `FuzzerWorkerCreatedEvent`: Indicates a `FuzzerWorker` was created by a `Fuzzer`. It provides a reference to the `FuzzerWorker` spawned. The parent `Fuzzer` can be accessed through `FuzzerWorker.Fuzzer()`. +- `FuzzerWorkerDestroyedEvent`: Indicates a `FuzzerWorker` was destroyed. This can happen either due to hitting the config-defined worker reset limit or the fuzzing operation stopping. It provides a reference to the destroyed worker (for reference, though this should not be stored, to allow memory to free). + +The `FuzzerWorker` maintains event emiters for the following events under `FuzzerWorker.Events.*`: + +- `FuzzerWorkerChainCreatedEvent`: This indicates the `FuzzerWorker` is about to begin working and has created its chain (but not yet copied data from the "base" `TestChain` the `Fuzzer` provided). This offers an opportunity to attach tracers for calls made during chain setup. It provides a reference to the `FuzzerWorker` and its underlying `TestChain`. + +- `FuzzerWorkerChainSetupEvent`: This indicates the `FuzzerWorker` is about to begin working and has both created its chain, and copied data from the "base" `TestChain`, so the initial deployment of contracts is complete and fuzzing is ready to begin. It provides a reference to the `FuzzerWorker` and its underlying `TestChain`. + +- `CallSequenceTesting`: This indicates a new `CallSequence` is about to be generated and tested by the `FuzzerWorker`. It provides a reference to the `FuzzerWorker`. + +- `CallSequenceTested`: This indicates a `CallSequence` was just tested by the `FuzzerWorker`. It provides a reference to the `FuzzerWorker`. + +- `FuzzerWorkerContractAddedEvent`: This indicates a contract was added on the `FuzzerWorker`'s underlying `TestChain`. This event is emitted when the contract byte code is resolved to a `Contract` definition known by the `Fuzzer`. It may be emitted due to a contract deployment, or the reverting of a block which caused a SELFDESTRUCT. It provides a reference to the `FuzzerWorker`, the deployed contract address, and the `Contract` definition that it was matched to. + +- `FuzzerWorkerContractDeletedEvent`: This indicates a contract was removed on the `FuzzerWorker`'s underlying `TestChain`. It may be emitted due to a contract deployment which was reverted, or a SELFDESTRUCT operation. It provides a reference to the `FuzzerWorker`, the deployed contract address, and the `Contract` definition that it was matched to. + +The `TestChain` maintains event emitters for the following events under `TestChain.Events.*`: + +- `PendingBlockCreatedEvent`: This indicates a new block is being created but has not yet been committed to the chain. The block is empty at this point but will likely be populated. It provides a reference to the `Block` and `TestChain`. + +- `PendingBlockAddedTxEvent`: This indicates a pending block which has not yet been commited to chain has added a transaction to it, as it is being constructed. It provides a reference to the `Block`, `TestChain`, and index of the transaction in the `Block`. + +- `PendingBlockCommittedEvent`: This indicates a pending block was committed to chain as the new head. It provides a reference to the `Block` and `TestChain`. + +- `PendingBlockDiscardedEvent`: This indicates a pending block was not committed to chain and was instead discarded. + +- `BlocksRemovedEvent`: This indicates blocks were removed from the chain. This happens when a chain revert to a previous block number is invoked. It provides a reference to the `Block` and `TestChain`. + +- `ContractDeploymentsAddedEvent`: This indicates a new contract deployment was detected on chain. It provides a reference to the `TestChain`, as well as information captured about the bytecode. This may be triggered on contract deployment, or the reverting of a SELFDESTRUCT operation. + +- `ContractDeploymentsRemovedEvent`: This indicates a previously deployed contract deployment was removed from chain. It provides a reference to the `TestChain`, as well as information captured about the bytecode. This may be triggered on revert of a contract deployment, or a SELFDESTRUCT operation. + +### Hooks + +The `Fuzzer` maintains hooks for some of its functionality under `Fuzzer.Hooks.*`: + +- `NewValueGeneratorFunc`: This method is used to create a `ValueGenerator` for each `FuzzerWorker`. By default, this uses a `MutationalValueGenerator` constructed with the provided `ValueSet`. It can be replaced to provide a custom `ValueGenerator`. + +- `TestChainSetupFunc`: This method is used to set up a chain's initial state before fuzzing. By default, this method deploys all contracts compiled and marked for deployment in the `ProjectConfig` provided to the `Fuzzer`. It only deploys contracts if they have no constructor arguments. This can be replaced with your own method to do custom deployments. + + - **Note**: We do not recommend replacing this for now, as the `Contract` definitions may not be known to the `Fuzzer`. Additionally, `SenderAddresses` and `DeployerAddress` are the only addresses funded at genesis. This will be updated at a later time. + +- `CallSequenceTestFuncs`: This is a list of functions which are called after each `FuzzerWorker` executed another call in its current `CallSequence`. It takes the `FuzzerWorker` and `CallSequence` as input, and is expected to return a list of `ShinkRequest`s if some interesting result was found and we wish for the `FuzzerWorker` to shrink the sequence. You can add a function here as part of custom post-call testing methodology to check if some property was violated, then request a shrunken sequence for it with arbitrary criteria to verify the shrunk sequence satisfies your requirements (e.g. violating the same property again). + +### Extending testing methodology + +Although we will build out guidance on how you can solve different challenges or employ different tests with this lower level API, we intend to wrap some of this into a higher level API that allows testing complex post-call/event conditions with just a few lines of code externally. The lower level API will serve for more granular control across the system, and fine tuned optimizations. + +To ensure testing methodology was agnostic and extensible in `medusa`, we note that both assertion and property testing is implemented through the abovementioned events and hooks. When a higher level API is introduced, we intend to migrate these test case providers to that API. + +For now, the built-in `AssertionTestCaseProvider` (found [here](https://github.com/trailofbits/medusa/blob/8036697794481b7bf9fa78c922ec7fa6a8a3005c/fuzzing/test_case_assertion_provider.go)) and its test cases (found [here](https://github.com/trailofbits/medusa/blob/8036697794481b7bf9fa78c922ec7fa6a8a3005c/fuzzing/test_case_assertion.go)) are an example of code that _could_ exist externally outside of `medusa`, but plug into it to offer extended testing methodology. Although it makes use of some private variables, they can be replaced with public getter functions that are available. As such, if assertion testing didn't exist in `medusa` natively, you could've implemented it yourself externally! + +In the end, using it would look something like this: + +```go + // Create our fuzzer + fuzzer, err := fuzzing.NewFuzzer(*projectConfig) + if err != nil { + return err + } + + // Attach our custom test case provider + attachAssertionTestCaseProvider(fuzzer) + + // Start the fuzzer + err = fuzzer.Start() + if err != nil { + return err + } +``` diff --git a/docs/src/cheatcodes/addr.md b/docs/src/cheatcodes/addr.md new file mode 100644 index 00000000..5fe04c04 --- /dev/null +++ b/docs/src/cheatcodes/addr.md @@ -0,0 +1,24 @@ +# `addr` + +## Description + +The `addr` cheatcode will compute the address for a given private key. + +## Example + +```solidity +// Obtain our cheat code contract reference. +IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + +// Test with random private key +uint256 pkOne = 0x6df21769a2082e03f7e21f6395561279e9a7feb846b2bf740798c794ad196e00; +address addrOne = 0xdf8Ef652AdE0FA4790843a726164df8cf8649339; +address result = cheats.addr(pkOne); +assert(result == addrOne); +``` + +## Function Signature + +```solidity +function addr(uint256 privateKey) external returns (address); +``` diff --git a/docs/src/cheatcodes/chain_id.md b/docs/src/cheatcodes/chain_id.md new file mode 100644 index 00000000..ae8fedeb --- /dev/null +++ b/docs/src/cheatcodes/chain_id.md @@ -0,0 +1,22 @@ +# `chainId` + +## Description + +The `chainId` cheatcode will set the `block.chainid` + +## Example + +```solidity +// Obtain our cheat code contract reference. +IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + +// Change value and verify. +cheats.chainId(777123); +assert(block.chainid == 777123); +``` + +## Function Signature + +```solidity +function chainId(uint256) external; +``` diff --git a/docs/src/cheatcodes/cheatcodes_overview.md b/docs/src/cheatcodes/cheatcodes_overview.md new file mode 100644 index 00000000..ec2dcf46 --- /dev/null +++ b/docs/src/cheatcodes/cheatcodes_overview.md @@ -0,0 +1,118 @@ +# Cheatcodes Overview + +Cheatcodes allow users to manipulate EVM state, blockchain behavior, provide easy ways to manipulate data, and much more. +The cheatcode contract is deployed at `0x7109709ECfa91a80626fF3989D68f67F5b1DD12D`. + +## Cheatcode Interface + +The following interface must be added to your Solidity project if you wish to use cheatcodes. Note that if you use Foundry +as your compilation platform that the cheatcode interface is already provided [here](https://book.getfoundry.sh/reference/forge-std/#forge-stds-test). +However, it is important to note that medusa does not support all the cheatcodes provided out-of-box +by Foundry (see below for supported cheatcodes). + +```solidity +interface StdCheats { + // Set block.timestamp + function warp(uint256) external; + + // Set block.number + function roll(uint256) external; + + // Set block.basefee + function fee(uint256) external; + + // Set block.difficulty and block.prevrandao + function difficulty(uint256) external; + + // Set block.chainid + function chainId(uint256) external; + + // Sets the block.coinbase + function coinbase(address) external; + + // Loads a storage slot from an address + function load(address account, bytes32 slot) external returns (bytes32); + + // Stores a value to an address' storage slot + function store(address account, bytes32 slot, bytes32 value) external; + + // Sets the *next* call's msg.sender to be the input address + function prank(address) external; + + // Set msg.sender to the input address until the current call exits + function prankHere(address) external; + + // Sets an address' balance + function deal(address who, uint256 newBalance) external; + + // Sets an address' code + function etch(address who, bytes calldata code) external; + + // Signs data + function sign(uint256 privateKey, bytes32 digest) + external + returns (uint8 v, bytes32 r, bytes32 s); + + // Computes address for a given private key + function addr(uint256 privateKey) external returns (address); + + // Gets the nonce of an account + function getNonce(address account) external returns (uint64); + + // Sets the nonce of an account + // The new nonce must be higher than the current nonce of the account + function setNonce(address account, uint64 nonce) external; + + // Performs a foreign function call via terminal + function ffi(string[] calldata) external returns (bytes memory); + + // Take a snapshot of the current state of the EVM + function snapshot() external returns (uint256); + + // Revert state back to a snapshot + function revertTo(uint256) external returns (bool); + + // Convert Solidity types to strings + function toString(address) external returns(string memory); + function toString(bytes calldata) external returns(string memory); + function toString(bytes32) external returns(string memory); + function toString(bool) external returns(string memory); + function toString(uint256) external returns(string memory); + function toString(int256) external returns(string memory); + + // Convert strings into Solidity types + function parseBytes(string memory) external returns(bytes memory); + function parseBytes32(string memory) external returns(bytes32); + function parseAddress(string memory) external returns(address); + function parseUint(string memory)external returns(uint256); + function parseInt(string memory) external returns(int256); + function parseBool(string memory) external returns(bool); +} +``` + +# Using cheatcodes + +Below is an example snippet of how you would import the cheatcode interface into your project and use it. + +```solidity +// Assuming cheatcode interface is in the same directory +import "./IStdCheats.sol"; + +// MyContract will utilize the cheatcode interface +contract MyContract { + // Set up reference to cheatcode contract + IStdCheats cheats = IStdCheats(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + + // This is a test function that will set the msg.sender's nonce to the provided input argument + function testFunc(uint256 _x) public { + // Ensure that the input argument is greater than msg.sender's current nonce + require(_x > cheats.getNonce(msg.sender)); + + // Set sender's nonce + cheats.setNonce(msg.sender, x); + + // Assert that the nonce has been correctly updated + assert(cheats.getNonce(msg.sender) == x); + } +} +``` diff --git a/docs/src/cheatcodes/coinbase.md b/docs/src/cheatcodes/coinbase.md new file mode 100644 index 00000000..a0ab068e --- /dev/null +++ b/docs/src/cheatcodes/coinbase.md @@ -0,0 +1,22 @@ +# `coinbase` + +## Description + +The `coinbase` cheatcode will set the `block.coinbase` + +## Example + +```solidity +// Obtain our cheat code contract reference. +IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + +// Change value and verify. +cheats.coinbase(address(7)); +assert(block.coinbase == address(7)); +``` + +## Function Signature + +```solidity +function coinbase(address) external; +``` diff --git a/docs/src/cheatcodes/deal.md b/docs/src/cheatcodes/deal.md new file mode 100644 index 00000000..4518f61f --- /dev/null +++ b/docs/src/cheatcodes/deal.md @@ -0,0 +1,23 @@ +# `deal` + +## Description + +The `deal` cheatcode will set the ETH balance of address `who` to `newBalance` + +## Example + +```solidity +// Obtain our cheat code contract reference. +IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + +// Change value and verify. +address acc = address(777); +cheats.deal(acc, x); +assert(acc.balance == x); +``` + +## Function Signature + +```solidity +function deal(address who, uint256 newBalance) external; +``` diff --git a/docs/src/cheatcodes/difficulty.md b/docs/src/cheatcodes/difficulty.md new file mode 100644 index 00000000..fa901849 --- /dev/null +++ b/docs/src/cheatcodes/difficulty.md @@ -0,0 +1,25 @@ +# `difficulty` + +## Description + +The `difficulty` cheatcode will set the `block.difficulty` and the `block.prevrandao` value. At the moment, both values +are changed since the cheatcode does not check what EVM version is running. + +Note that this behavior will change in the future. + +## Example + +```solidity +// Obtain our cheat code contract reference. +IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + +// Change value and verify. +cheats.difficulty(x); +assert(block.difficulty == x); +``` + +## Function Signature + +```solidity +function difficulty(uint256) external; +``` diff --git a/docs/src/cheatcodes/etch.md b/docs/src/cheatcodes/etch.md new file mode 100644 index 00000000..4ba045bf --- /dev/null +++ b/docs/src/cheatcodes/etch.md @@ -0,0 +1,29 @@ +# `etch` + +## Description + +The `etch` cheatcode will set the `who` address's bytecode to `code`. + +## Example + +```solidity +// Obtain our cheat code contract reference. +IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + +// Obtain our original code hash for an account. +address acc = address(777); +bytes32 originalCodeHash; +assembly { originalCodeHash := extcodehash(acc) } + +// Change value and verify. +cheats.etch(acc, address(someContract).code); +bytes32 updatedCodeHash; +assembly { updatedCodeHash := extcodehash(acc) } +assert(originalCodeHash != updatedCodeHash); +``` + +## Function Signature + +```solidity +function etch(address who, bytes calldata code) external; +``` diff --git a/docs/src/cheatcodes/fee.md b/docs/src/cheatcodes/fee.md new file mode 100644 index 00000000..a4c6f115 --- /dev/null +++ b/docs/src/cheatcodes/fee.md @@ -0,0 +1,22 @@ +# `fee` + +## Description + +The `fee` cheatcode will set the `block.basefee`. + +## Example + +```solidity +// Obtain our cheat code contract reference. +IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + +// Change value and verify. +cheats.fee(7); +assert(block.basefee == 7); +``` + +## Function Signature + +```solidity +function fee(uint256) external; +``` diff --git a/docs/src/cheatcodes/ffi.md b/docs/src/cheatcodes/ffi.md new file mode 100644 index 00000000..562528ad --- /dev/null +++ b/docs/src/cheatcodes/ffi.md @@ -0,0 +1,58 @@ +# `ffi` + +## Description + +The `ffi` cheatcode is used to call an arbitrary command on your host OS. Note that `ffi` must be enabled via the project +configuration file by setting `fuzzing.chainConfig.cheatCodes.enableFFI` to `true`. + +Note that enabling `ffi` allows anyone to execute arbitrary commands on devices that run the fuzz tests which may +become a security risk. + +Please review [Foundry's documentation on the `ffi` cheatcode](https://book.getfoundry.sh/cheatcodes/ffi#tips) for general tips. + +## Example with ABI-encoded hex + +```solidity +// Obtain our cheat code contract reference. +IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + +// Create command +string[] memory inputs = new string[](3); +inputs[0] = "echo"; +inputs[1] = "-n"; +// Encoded "hello" +inputs[2] = "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000568656C6C6F000000000000000000000000000000000000000000000000000000"; + +// Call cheats.ffi +bytes memory res = cheats.ffi(inputs); + +// ABI decode +string memory output = abi.decode(res, (string)); +assert(keccak256(abi.encodePacked(output)) == keccak256(abi.encodePacked("hello"))); +``` + +## Example with UTF8 encoding + +```solidity +// Obtain our cheat code contract reference. +IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + +// Create command +string[] memory inputs = new string[](3); +inputs[0] = "echo"; +inputs[1] = "-n"; +inputs[2] = "hello"; + +// Call cheats.ffi +bytes memory res = cheats.ffi(inputs); + +// Convert to UTF-8 string +string memory output = string(res); +assert(keccak256(abi.encodePacked(output)) == keccak256(abi.encodePacked("hello"))); +``` + +## Function Signature + +```solidity +function ffi(string[] calldata) external returns (bytes memory); +``` diff --git a/docs/src/cheatcodes/get_nonce.md b/docs/src/cheatcodes/get_nonce.md new file mode 100644 index 00000000..b94f3773 --- /dev/null +++ b/docs/src/cheatcodes/get_nonce.md @@ -0,0 +1,22 @@ +# `getNonce` + +## Description + +The `getNonce` cheatcode will get the current nonce of `account`. + +## Example + +```solidity +// Obtain our cheat code contract reference. +IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + +// Get nonce and verify that the sender has sent at least one transaction +address acc = address(msg.sender); +assert(cheats.getNonce(acc) > 0); +``` + +## Function Signature + +```solidity +function getNonce(address account) external returns (uint64); +``` diff --git a/docs/src/cheatcodes/load.md b/docs/src/cheatcodes/load.md new file mode 100644 index 00000000..488891b4 --- /dev/null +++ b/docs/src/cheatcodes/load.md @@ -0,0 +1,27 @@ +# `load` + +## Description + +The `load` cheatcode will load storage slot `slot` for `account` + +## Example + +```solidity +contract TestContract { + uint x = 123; + function test() public { + // Obtain our cheat code contract reference. + IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + + // Load and verify x + bytes32 value = cheats.load(address(this), bytes32(uint(0))); + assert(value == bytes32(uint(123))); + } +} +``` + +## Function Signature + +```solidity +function load(address account, bytes32 slot) external returns (bytes32); +``` diff --git a/docs/src/cheatcodes/parse_address.md b/docs/src/cheatcodes/parse_address.md new file mode 100644 index 00000000..335a7eb0 --- /dev/null +++ b/docs/src/cheatcodes/parse_address.md @@ -0,0 +1,30 @@ +# `parseAddress` + +## Description + +The `parseAddress` cheatcode will parse the input string into an address + +## Example + +```solidity +contract TestContract { + uint x = 123; + function test() public { + // Obtain our cheat code contract reference. + IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + + address expectedAddress = 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D; + string memory test = "0x7109709ECfa91a80626fF3989D68f67F5b1DD12D"; + + // Call cheats.parseAddress + address result = cheats.parseAddress(test); + assert(expectedAddress == result); + } +} +``` + +## Function Signature + +```solidity +function parseAddress(string calldata) external returns (address); +``` diff --git a/docs/src/cheatcodes/parse_bool.md b/docs/src/cheatcodes/parse_bool.md new file mode 100644 index 00000000..dbbc7241 --- /dev/null +++ b/docs/src/cheatcodes/parse_bool.md @@ -0,0 +1,30 @@ +# `parseBool` + +## Description + +The `parseBool` cheatcode will parse the input string into a boolean + +## Example + +```solidity +contract TestContract { + uint x = 123; + function test() public { + // Obtain our cheat code contract reference. + IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + + bool expectedBool = true; + string memory test = "true"; + + // Call cheats.parseBool + bool result = cheats.parseBool(test); + assert(expectedBool == result); + } +} +``` + +## Function Signature + +```solidity +function parseBool(string calldata) external returns (bool); +``` diff --git a/docs/src/cheatcodes/parse_bytes.md b/docs/src/cheatcodes/parse_bytes.md new file mode 100644 index 00000000..4612116e --- /dev/null +++ b/docs/src/cheatcodes/parse_bytes.md @@ -0,0 +1,30 @@ +# `parseBytes` + +## Description + +The `parseBytes` cheatcode will parse the input string into bytes + +## Example + +```solidity +contract TestContract { + uint x = 123; + function test() public { + // Obtain our cheat code contract reference. + IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + + bytes memory expectedBytes = "medusa"; + string memory test = "medusa"; + + // Call cheats.parseBytes + bytes memory result = cheats.parseBytes(test); + assert(keccak256(expectedBytes) == keccak256(result)); + } +} +``` + +## Function Signature + +```solidity +function parseBytes(string calldata) external returns (bytes memory); +``` diff --git a/docs/src/cheatcodes/parse_bytes32.md b/docs/src/cheatcodes/parse_bytes32.md new file mode 100644 index 00000000..6fb0ab2a --- /dev/null +++ b/docs/src/cheatcodes/parse_bytes32.md @@ -0,0 +1,30 @@ +# `parseBytes32` + +## Description + +The `parseBytes32` cheatcode will parse the input string into bytes32 + +## Example + +```solidity +contract TestContract { + uint x = 123; + function test() public { + // Obtain our cheat code contract reference. + IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + + int256 expectedInt = -12345; + string memory test = "-12345"; + + // Call cheats.parseInt + int256 result = cheats.parseInt(test); + assert(expectedInt == result); + } +} +``` + +## Function Signature + +```solidity +function parseBytes32(string calldata) external returns (bytes32); +``` diff --git a/docs/src/cheatcodes/parse_int.md b/docs/src/cheatcodes/parse_int.md new file mode 100644 index 00000000..6a820a3c --- /dev/null +++ b/docs/src/cheatcodes/parse_int.md @@ -0,0 +1,30 @@ +# `parseInt` + +## Description + +The `parseInt` cheatcode will parse the input string into a int256 + +## Example + +```solidity +contract TestContract { + uint x = 123; + function test() public { + // Obtain our cheat code contract reference. + IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + + address expectedAddress = 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D; + string memory test = "0x7109709ECfa91a80626fF3989D68f67F5b1DD12D"; + + // Call cheats.parseAddress + address result = cheats.parseAddress(test); + assert(expectedAddress == result); + } +} +``` + +## Function Signature + +```solidity +function parseInt(string calldata) external returns (int256); +``` diff --git a/docs/src/cheatcodes/parse_uint.md b/docs/src/cheatcodes/parse_uint.md new file mode 100644 index 00000000..8c9f2e46 --- /dev/null +++ b/docs/src/cheatcodes/parse_uint.md @@ -0,0 +1,30 @@ +# `parseUint` + +## Description + +The `parseUint` cheatcode will parse the input string into a uint256 + +## Example + +```solidity +contract TestContract { + uint x = 123; + function test() public { + // Obtain our cheat code contract reference. + IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + + uint256 expectedUint = 12345; + string memory test = "12345"; + + // Call cheats.parseUint + uint256 result = cheats.parseUint(test); + assert(expectedUint == result); + } +} +``` + +## Function Signature + +```solidity +function parseUint(string calldata) external returns (uint256); +``` diff --git a/docs/src/cheatcodes/prank.md b/docs/src/cheatcodes/prank.md new file mode 100644 index 00000000..f309ba3f --- /dev/null +++ b/docs/src/cheatcodes/prank.md @@ -0,0 +1,38 @@ +# `prank` + +## Description + +The `prank` cheatcode will set the `msg.sender` for _only the next call_ to the specified input address. Note that, +contrary to [`prank` in Foundry](https://book.getfoundry.sh/cheatcodes/prank#description), calling the cheatcode contract will count as a +valid "next call" + +## Example + +```solidity +contract TestContract { + address owner = address(123); + function transferOwnership(address _newOwner) public { + require(msg.sender == owner); + + // Change ownership + owner = _newOwner; + } + + function test() public { + // Obtain our cheat code contract reference. + IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + + // Prank, change ownership, and verify + address newOwner = address(456); + cheats.prank(owner); + transferOwnership(newOwner); + assert(owner == newOwner); + } + } +``` + +## Function Signature + +```solidity +function prank(address) external; +``` diff --git a/docs/src/cheatcodes/prank_here.md b/docs/src/cheatcodes/prank_here.md new file mode 100644 index 00000000..5723cfb8 --- /dev/null +++ b/docs/src/cheatcodes/prank_here.md @@ -0,0 +1,49 @@ +# `prankHere` + +## Description + +The `prankHere` cheatcode will set the `msg.sender` to the specified input address until the current call exits. Compared +to `prank`, `prankHere` can persist for multiple calls. + +## Example + +```solidity +contract TestContract { + address owner = address(123); + uint256 x = 0; + uint256 y = 0; + + function updateX() public { + require(msg.sender == owner); + + // Update x + x = 1; + } + + function updateY() public { + require(msg.sender == owner); + + // Update y + y = 1; + } + + function test() public { + // Obtain our cheat code contract reference. + IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + + // Prank, update variables, and verify + cheats.prank(owner); + updateX(); + updateY(); + assert((x == 1) && (y == 1)); + + // Once this function returns, the `msg.sender` is reset + } +} +``` + +## Function Signature + +```solidity +function prankHere(address) external; +``` diff --git a/docs/src/cheatcodes/roll.md b/docs/src/cheatcodes/roll.md new file mode 100644 index 00000000..57b901e0 --- /dev/null +++ b/docs/src/cheatcodes/roll.md @@ -0,0 +1,24 @@ +# `roll` + +## Description + +The `roll` cheatcode sets the `block.number` + +## Example + +```solidity +// Obtain our cheat code contract reference. +IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + +// Change value and verify. +cheats.roll(7); +assert(block.number == 7); +cheats.roll(9); +assert(block.number == 9); +``` + +## Function Signature + +```solidity +function roll(uint256) external; +``` diff --git a/docs/src/cheatcodes/set_nonce.md b/docs/src/cheatcodes/set_nonce.md new file mode 100644 index 00000000..8d949c8e --- /dev/null +++ b/docs/src/cheatcodes/set_nonce.md @@ -0,0 +1,24 @@ +# setNonce + +## Description + +The `setNonce` cheatcode will set the nonce of `account` to `nonce`. Note that the `nonce` must be strictly greater than +the current nonce + +## Example + +```solidity +// Obtain our cheat code contract reference. +IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + +// Set nonce and verify (assume nonce before `setNonce` was less than 7) +address acc = address(msg.sender); +cheats.setNonce(acc, 7); +assert(cheats.getNonce(acc) == 7); +``` + +## Function Signature + +```solidity +function setNonce(address account, uint64 nonce) external; +``` diff --git a/docs/src/cheatcodes/sign.md b/docs/src/cheatcodes/sign.md new file mode 100644 index 00000000..dea23c2c --- /dev/null +++ b/docs/src/cheatcodes/sign.md @@ -0,0 +1,28 @@ +# `sign` + +## Description + +The `sign` cheatcode will take in a private key `privateKey` and a hash digest `digest` to generate a `(v, r, s)` +signature + +## Example + +```solidity +// Obtain our cheat code contract reference. +IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + +bytes32 digest = keccak256("Data To Sign"); + +// Call cheats.sign +(uint8 v, bytes32 r, bytes32 s) = cheats.sign(0x6df21769a2082e03f7e21f6395561279e9a7feb846b2bf740798c794ad196e00, digest); +address signer = ecrecover(digest, v, r, s); +assert(signer == 0xdf8Ef652AdE0FA4790843a726164df8cf8649339); +``` + +## Function Signature + +```solidity +function sign(uint256 privateKey, bytes32 digest) +external +returns (uint8 v, bytes32 r, bytes32 s); +``` diff --git a/docs/src/cheatcodes/snapshot.md b/docs/src/cheatcodes/snapshot.md new file mode 100644 index 00000000..27298f6b --- /dev/null +++ b/docs/src/cheatcodes/snapshot.md @@ -0,0 +1,68 @@ +# `snapshot` and `revertTo` + +## Description + +The `snapshot` cheatcode will take a snapshot of the current state of the blockchain and return an identifier for the +snapshot. + +On the flipside, the `revertTo` cheatcode will revert the EVM state back based on the provided identifier. + +## Example + +```solidity +interface CheatCodes { + function warp(uint256) external; + + function deal(address, uint256) external; + + function snapshot() external returns (uint256); + + function revertTo(uint256) external returns (bool); +} + +struct Storage { + uint slot0; + uint slot1; +} + +contract TestContract { + Storage store; + uint256 timestamp; + + function test() public { + // Obtain our cheat code contract reference. + CheatCodes cheats = CheatCodes( + 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D + ); + + store.slot0 = 10; + store.slot1 = 20; + timestamp = block.timestamp; + cheats.deal(address(this), 5 ether); + + // Save state + uint256 snapshot = cheats.snapshot(); + + // Change state + store.slot0 = 300; + store.slot1 = 400; + cheats.deal(address(this), 500 ether); + cheats.warp(12345); + + // Assert that state has been changed + assert(store.slot0 == 300); + assert(store.slot1 == 400); + assert(address(this).balance == 500 ether); + assert(block.timestamp == 12345); + + // Revert to snapshot + cheats.revertTo(snapshot); + + // Ensure state has been reset + assert(store.slot0 == 10); + assert(store.slot1 == 20); + assert(address(this).balance == 5 ether); + assert(block.timestamp == timestamp); + } +} +``` diff --git a/docs/src/cheatcodes/store.md b/docs/src/cheatcodes/store.md new file mode 100644 index 00000000..d3fb580d --- /dev/null +++ b/docs/src/cheatcodes/store.md @@ -0,0 +1,27 @@ +# `store` + +## Description + +The `store` cheatcode will store `value` in storage slot `slot` for `account` + +## Example + +```solidity +contract TestContract { + uint x = 123; + function test() public { + // Obtain our cheat code contract reference. + IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + + // Store into x, verify it. + cheats.store(address(this), bytes32(uint(0)), bytes32(uint(456))); + assert(y == 456); + } +} +``` + +## Function Signature + +```solidity +function store(address account, bytes32 slot, bytes32 value) external; +``` diff --git a/docs/src/cheatcodes/to_string.md b/docs/src/cheatcodes/to_string.md new file mode 100644 index 00000000..75e4182f --- /dev/null +++ b/docs/src/cheatcodes/to_string.md @@ -0,0 +1,84 @@ +# `toString` + +## Description + +The `toString` cheatcodes aid in converting primitive Solidity types into strings. Similar to +[Foundry's behavior](https://book.getfoundry.sh/cheatcodes/to-string?highlight=toStr#description), bytes are converted +to a hex-encoded string with `0x` prefixed. + +## Example + +```solidity +contract TestContract { + IStdCheats cheats; + + constructor() { + cheats = IStdCheats(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + } + + function testAddress() public { + address test = 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D; + string memory expectedString = "0x7109709ECfa91a80626fF3989D68f67F5b1DD12D"; + + // Call cheats.toString + string memory result = cheats.toString(test); + assert(keccak256(abi.encodePacked(result)) == keccak256(abi.encodePacked(expectedString))); + } + + function testBool() public { + bool test = true; + string memory expectedString = "true"; + + // Call cheats.toString + string memory result = cheats.toString(test); + assert(keccak256(abi.encodePacked(result)) == keccak256(abi.encodePacked(expectedString))); + } + + function testUint256() public { + uint256 test = 12345; + string memory expectedString = "12345"; + + // Call cheats.toString + string memory result = cheats.toString(test); + assert(keccak256(abi.encodePacked(result)) == keccak256(abi.encodePacked(expectedString))); + } + + function testInt256() public { + int256 test = -12345; + string memory expectedString = "-12345"; + + // Call cheats.toString + string memory result = cheats.toString(test); + assert(keccak256(abi.encodePacked(result)) == keccak256(abi.encodePacked(expectedString))); + } + + function testBytes32() public { + bytes32 test = "medusa"; + string memory expectedString = "0x6d65647573610000000000000000000000000000000000000000000000000000"; + + // Call cheats.toString + string memory result = cheats.toString(test); + assert(keccak256(abi.encodePacked(result)) == keccak256(abi.encodePacked(expectedString))); + } + + function testBytes() public { + bytes memory test = "medusa"; + string memory expectedString = "0x6d6564757361"; + + // Call cheats.toString + string memory result = cheats.toString(test); + assert(keccak256(abi.encodePacked(result)) == keccak256(abi.encodePacked(expectedString))); + } +} +``` + +## Function Signatures + +```solidity +function toString(address) external returns (string memory); +function toString(bool) external returns (string memory); +function toString(uint256) external returns (string memory); +function toString(int256) external returns (string memory); +function toString(bytes32) external returns (string memory); +function toString(bytes) external returns (string memory); +``` diff --git a/docs/src/cheatcodes/warp.md b/docs/src/cheatcodes/warp.md new file mode 100644 index 00000000..22830645 --- /dev/null +++ b/docs/src/cheatcodes/warp.md @@ -0,0 +1,24 @@ +# warp + +## Description + +The `warp` cheatcode sets the `block.timestamp` + +## Example + +```solidity +// Obtain our cheat code contract reference. +IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + +// Change value and verify. +cheats.warp(7); +assert(block.timestamp == 7); +cheats.warp(9); +assert(block.timestamp == 9); +``` + +## Function Signature + +```solidity +function warp(uint256) external; +``` diff --git a/docs/src/cli/completion.md b/docs/src/cli/completion.md new file mode 100644 index 00000000..242355db --- /dev/null +++ b/docs/src/cli/completion.md @@ -0,0 +1,21 @@ +# `completion` + +`medusa` provides the ability to generate autocompletion scripts for a given shell. +Once the autocompletion script is ran for a given shell, `medusa`'s commands and flags can be tab-autocompleted. +The following shells are supported: + +1. `bash` +2. `zsh` +3. `Powershell` + +To understand how to run the autocompletion script for a given shell, run the following command: + +```shell +medusa completion --help +``` + +Once you know how to run the autocompletion script, retrieve the script for that given shell using the following command: + +```shell +medusa completion +``` diff --git a/docs/src/cli/fuzz.md b/docs/src/cli/fuzz.md new file mode 100644 index 00000000..ed70d15a --- /dev/null +++ b/docs/src/cli/fuzz.md @@ -0,0 +1,131 @@ +# `fuzz` + +The `fuzz` command will initiate a fuzzing campaign: + +```shell +medusa fuzz [flags] +``` + +## Supported Flags + +### `--config` + +The `--config` flag allows you to specify the path for your [project configuration](../project_configuration/overview.md) +file. If the `--config` flag is not used, `medusa` will look for a [`medusa.json`](../static/medusa.json) file in the +current working directory. + +```shell +# Set config file path +medusa fuzz --out myConfig.json +``` + +### `--compilation-target` + +The `--compilation-target` flag allows you to specify the compilation target. If you are using `crytic-compile`, please review the +warning [here](../project_configuration/compilation_config.md#target) about changing the compilation target. + +```shell +# Set compilation target +medusa fuzz --target TestMyContract.sol +``` + +### `--workers` + +The `--workers` flag allows you to update the number of threads that will perform parallelized fuzzing (equivalent to +[`fuzzing.workers`](../project_configuration/fuzzing_config.md#workers)) + +```shell +# Set workers +medusa fuzz --workers 20 +``` + +### `--timeout` + +The `--timeout` flag allows you to update the duration of the fuzzing campaign (equivalent to +[`fuzzing.timeout`](../project_configuration/fuzzing_config.md#timeout)) + +```shell +# Set timeout +medusa fuzz --timeout 100 +``` + +### `--test-limit` + +The `--test-limit` flag allows you to update the number of transactions to run before stopping the fuzzing campaign +(equivalent to [`fuzzing.testLimit`](../project_configuration/fuzzing_config.md#testlimit)) + +```shell +# Set test limit +medusa fuzz --test-limit 100000 +``` + +### `--seq-len` + +The `--seq-len` flag allows you to update the length of a call sequence (equivalent to +[`fuzzing.callSequenceLength`](../project_configuration/fuzzing_config.md#callsequencelength)) + +```shell +# Set sequence length +medusa fuzz --seq-len 50 +``` + +### `--target-contracts` + +The `--target-contracts` flag allows you to update the target contracts for fuzzing (equivalent to +[`fuzzing.targetContracts`](../project_configuration/fuzzing_config.md#targetcontracts)) + +```shell +# Set target contracts +medusa fuzz --target-contracts "TestMyContract, TestMyOtherContract" +``` + +### `--corpus-dir` + +The `--corpus-dir` flag allows you to set the path for the corpus directory (equivalent to +[`fuzzing.corpusDirectory`](../project_configuration/fuzzing_config.md#corpusdirectory)) + +```shell +# Set corpus directory +medusa fuzz --corpus-dir corpus +``` + +### `--senders` + +The `--senders` flag allows you to update `medusa`'s senders (equivalent to +[`fuzzing.senderAddresses`](../project_configuration/fuzzing_config.md#senderaddresses)) + +```shell +# Set sender addresses +medusa fuzz --senders "0x50000,0x60000,0x70000" +``` + +### `--deployer` + +The `--deployer` flag allows you to update `medusa`'s contract deployer (equivalent to +[`fuzzing.deployerAddress`](../project_configuration/fuzzing_config.md#deployeraddress)) + +```shell +# Set deployer address +medusa fuzz --deployer "0x40000" +``` + +### `--trace-all` + +The `--trace-all` flag allows you to retrieve an execution trace for each element of a call sequence that triggered a test +failure (equivalent to +[`testing.traceAll`](../project_configuration/testing_config.md#traceall) + +```shell +# Trace each call +medusa fuzz --trace-all +``` + +### `--no-color` + +The `--no-color` flag disables colored console output (equivalent to +[`logging.NoColor`](../project_configuration/logging_config.md#nocolor)) + +```shell +# Disable colored output +medusa fuzz --no-color +``` diff --git a/docs/src/cli/init.md b/docs/src/cli/init.md new file mode 100644 index 00000000..e6f13cac --- /dev/null +++ b/docs/src/cli/init.md @@ -0,0 +1,36 @@ +# `init` + +The `init` command will generate the project configuration file within your current working directory: + +```shell +medusa init [platform] [flags] +``` + +By default, the project configuration file will be named `medusa.json`. You can learn more about `medusa`'s project +configuration [here](../project_configuration/overview.md) and also view an [example project configuration file](../static/medusa.json). + +Invoking this command without a `platform` argument will result in `medusa` using `crytic-compile` as the default compilation platform. +Currently, the only other supported platform is `solc`. If you are using a compilation platform such as Foundry or Hardhat, +it is best to use `crytic-compile`. + +## Supported Flags + +### `--out` + +The `--out` flag allows you to specify the output path for the project configuration file. Thus, you can name the file +something different from `medusa.json` or have the configuration file be placed elsewhere in your filesystem. + +```shell +# Set config file path +medusa init --out myConfig.json +``` + +### `--compilation-target` + +The `--compilation-target` flag allows you to specify the compilation target. If you are using `crytic-compile`, please review the +warning [here](../project_configuration/compilation_config.md#target) about changing the compilation target. + +```shell +# Set compilation target +medusa init --compilation-target TestMyContract.sol +``` diff --git a/docs/src/cli/overview.md b/docs/src/cli/overview.md new file mode 100644 index 00000000..b35fb74d --- /dev/null +++ b/docs/src/cli/overview.md @@ -0,0 +1,10 @@ +# CLI Overview + +The `medusa` CLI is used to perform parallelized fuzz testing of smart contracts. After you have `medusa` +[installed](../getting_started/installation.md), you can run `medusa help` in your terminal to view the available commands. + +The CLI supports three main commands with each command having a variety of flags: + +- [`medusa init`](./init.md) +- [`medusa fuzz`](./fuzz.md) +- [`medusa completion`](./completion.md) diff --git a/docs/src/console_logging.md b/docs/src/console_logging.md new file mode 100644 index 00000000..63d69510 --- /dev/null +++ b/docs/src/console_logging.md @@ -0,0 +1,55 @@ +# Console Logging + +Console logging in medusa is similar to the functionality found in Foundry or Hardhat (except for string formatting, +see [below](#differences-in-consolelogformatargs)). Note that if you are not using +Foundry or Hardhat as your compilation platform, you can retrieve the necessary `console.sol` library +[here](https://github.com/foundry-rs/forge-std/blob/master/src/console.sol). + +For more information on the available function signatures and general tips on console logging, please review [Foundry's +documentation](https://book.getfoundry.sh/reference/forge-std/console-log#console-logging). + +## Differences in `console.log(format[,...args])` + +The core functionality of string formatting is the same. If you want to string format an `int256`, the only supported function signature is: +`function log(string memory, int256) external;`. Otherwise, the supported argument types are `string`, `bool`, `address`, +and `uint256`. This capability is the same as in Foundry. + +The core difference in medusa's string formatting is the specifiers that are allowed for the +formatted string. The supported specifiers are as follows: + +- `%v`: The value will be printed in its default format. This will work for `uint256`, `int256`, `address`, + `bool`, and `string`. Using `%v` is the **recommended** specifier for all argument types. +- `%s`: The values will be converted into a human-readable string. This will work for `uint256`, `int256`, `address`, and + `string`. Contrary to Foundry or Hardhat, `%s` will not work for `bool`. Additionally, `uint256` and `int256` will _not_ + be provided in their hex-encoded format. This is the **recommended** specifier for projects that wish to maintain + compatibility with an existing fuzz test suite from Foundry. Special exceptions will need to be made for `bool` arguments. + For example, you could use the `console.logBool(bool)` function to separately log the `bool`. +- `%d`: This can be used for `uint256` and `int256`. +- `%i`: This specifier is not supported by medusa for `int256` and `uint256` +- `%e`: This specifier is not supported by medusa for `int256` and `uint256`. +- `%x`: This provides the hexadecimal representation of `int256` and `uint256`. +- `%o`: This specifier is not supported by medusa. `%o` in medusa will provide the base-8 representation of `int256` and + `uint256`. +- `%t`: This can be used for `bool`. +- `%%`: This will print out "%" and not consume an argument. + +If a specifier does not have a corresponding argument, the following is returned: + +```solidity +console.log("My name is %s %s", "medusa"); +// Returns: "My name is medusa %!s(MISSING)" +``` + +If there are more arguments than specifiers, the following is returned: + +```solidity +console.log("My name is %s", "medusa", "fuzzer"); +// Returns: "My name is medusa%!(EXTRA string=fuzzer)" +``` + +If only a format string with no arguments is provided, the string is returned with no formatting: + +```solidity +console.log("%% %s"); +// Returns: "%% %s" +``` diff --git a/docs/src/coverage_reports.md b/docs/src/coverage_reports.md new file mode 100644 index 00000000..cd24b564 --- /dev/null +++ b/docs/src/coverage_reports.md @@ -0,0 +1,3 @@ +# Coverage Reports + +WIP diff --git a/docs/src/faq.md b/docs/src/faq.md new file mode 100644 index 00000000..4d5ea1b9 --- /dev/null +++ b/docs/src/faq.md @@ -0,0 +1,16 @@ +# Frequently Asked Questions + +**Why create a new fuzzer if Echidna is already a great fuzzer?** + +With medusa, we are exploring a different EVM implementation and language for our smart contract fuzzer. While Echidna is already doing an amazing job, medusa offers the following advantages: + +- It is written in Go, easing the maintenance and allowing the creation of a native API for future integration into other projects. +- It uses geth as a base, ensuring the EVM equivalence. + +**Should I switch to medusa right away?** + +We do not recommend switching to medusa until it is extensively tested. However we encourage you to try it, and [let us know your experience](https://github.com/trailofbits/medusa/issues). In that sense, Echidna is our robust and well tested fuzzer, while medusa is our new exploratory fuzzer. [Follow us](https://twitter.com/trailofbits/) to hear updates about medusa as it grows in maturity. + +**Will all the previous available documentation from [secure-contracts.com](https://secure-contracts.com/) will apply to medusa?** + +In general, yes. All the information on testing approaches and techniques will apply for medusa. There are, however, different configuration options names and a few missing or different features in medusa from Echidna that we will be updating over time. diff --git a/docs/src/getting_started/first_steps.md b/docs/src/getting_started/first_steps.md new file mode 100644 index 00000000..7cd8456d --- /dev/null +++ b/docs/src/getting_started/first_steps.md @@ -0,0 +1,36 @@ +# First Steps + +After installation, you are ready to use `medusa` on your first codebase. This chapter will walk you through initializing +`medusa` for a project and then starting to fuzz. + +To initialize medusa for a project, `cd` into your project and run [`medusa init`](../cli/init.md): + +```shell +# Change working directory +cd my_project + +# Initialize medusa +medusa init +``` + +This will create a `medusa.json` file which holds a large number of [configuration options](../project_configuration/overview.md). +`medusa` will use this configuration file to determine how and what to fuzz. + +All there is left to do now is to run `medusa` on some fuzz tests: + +```shell +medusa fuzz --target-contracts "TestContract" --test-limit 10_000 +``` + +The `--target-contracts` flag tells `medusa` which contracts to run fuzz tests on. You can specify more than one +contract to fuzz test at once (e.g. `--target-contracts "TestContract, TestOtherContract"`). The `--test-limit` flag +tells `medusa` to execute `10_000` transactions before stopping the fuzzing campaign. + +> Note: The target contracts and the test limit can also be configured via the project configuration file, which is the +> **recommended** route. The `--target-contracts` flag is equivalent to the +> [`fuzzing.targetContracts`](../project_configuration/fuzzing_config.md#targetcontracts) configuration option and the +> `-test-limit` flag is equivalent to the [`fuzzing.testLimit`](../project_configuration/fuzzing_config.md#testlimit) +> configuration option. + +It is recommended to review the [Configuration Overview](../project_configuration/overview.md) next and learn more about +[`medusa`'s CLI](../cli/overview.md). diff --git a/docs/src/getting_started/installation.md b/docs/src/getting_started/installation.md new file mode 100644 index 00000000..531758ec --- /dev/null +++ b/docs/src/getting_started/installation.md @@ -0,0 +1,64 @@ +# Installation + +There are three main ways to install `medusa` at the moment. The first is using Homebrew, +building from source, or installing a precompiled binary. + +If you have any difficulty with installing `medusa`, please [open an issue](https://github.com/crytic/medusa/issues) on GitHub. + +## Installing with Homebrew + +Note that using Homebrew is only viable (and recommended) for macOS and Linux users. For Windows users, you must +[build from source](#building-from-source) or [install a precompiled binary](#precompiled-binaries). + +### Prerequisites + +Installation instructions for Homebrew can be found [here](https://brew.sh/). + +### Install `medusa` + +Run the following command to install `medusa`: + +```shell +brew install medusa +``` + +## Building from source + +### Prerequisites + +Before downloading `medusa`, you will need to download Golang and `crytic-compile`. + +- Installation instructions for Golang can be found [here](https://go.dev/doc/install) +- Installation instructions for `crytic-compile` can be found [here](https://github.com/crytic/crytic-compile#installation) + - Note that `crytic-compile` requires a Python environment. Installation instructions for Python can be found + [here](https://www.python.org/downloads/). + +### Build `medusa` + +Run the following commands to build `medusa` (this should work on all OSes): + +```shell +# Clone the repository +git clone https://github.com/crytic/medusa + +# Build medusa +cd medusa +go build -trimpath +``` + +You will now need to move the binary (`medusa` or `medusa.exe`) to somewhere in your `PATH` environment variable so that +it is accessible via the command line. Please review the instructions +[here](https://zwbetz.com/how-to-add-a-binary-to-your-path-on-macos-linux-windows/) (if you are a Windows user, we +recommend using the Windows GUI). + +## Precompiled binaries + +The precompiled binaries can be downloaded on `medusa`'s [GitHub releases page](https://github.com/crytic/medusa/releases). + +> **_NOTE:_** macOS may set the [quarantine extended attribute](https://superuser.com/questions/28384/what-should-i-do-about-com-apple-quarantine) +> on the downloaded zip file. To remove this attribute, run the following command: +> `sudo xattr -rd com.apple.quarantine `. + +Once installed, you will need to unzip the file and move the binary to somewhere in your `$PATH`. Please review the instructions +[here](https://zwbetz.com/how-to-add-a-binary-to-your-path-on-macos-linux-windows/) (if you are a Windows user, we +recommend using the Windows GUI). diff --git a/docs/src/project_configuration/chain_config.md b/docs/src/project_configuration/chain_config.md new file mode 100644 index 00000000..b101b56d --- /dev/null +++ b/docs/src/project_configuration/chain_config.md @@ -0,0 +1,25 @@ +# Chain Configuration + +The chain configuration defines the parameters for setting up `medusa`'s underlying blockchain. + +### `codeSizeCheckDisabled` + +- **Type**: Boolean +- **Description**: If `true`, the maximum code size check of 24576 bytes in `go-ethereum` is disabled. +- > 🚩 Setting `codeSizeCheckDisabled` to `false` is not recommended since it complicates the fuzz testing process. +- **Default**: `true` + +## Cheatcode Configuration + +### `cheatCodesEnabled` + +- **Type**: Boolean +- **Description**: Determines whether cheatcodes are enabled. +- **Default**: `true` + +### `enableFFI` + +- **Type**: Boolean +- **Description**: Determines whether the `ffi` cheatcode is enabled. + > 🚩 Enabling the `ffi` cheatcode may allow for arbitrary code execution on your machine. +- **Default**: `false` diff --git a/docs/src/project_configuration/compilation_config.md b/docs/src/project_configuration/compilation_config.md new file mode 100644 index 00000000..4e298fdf --- /dev/null +++ b/docs/src/project_configuration/compilation_config.md @@ -0,0 +1,59 @@ +# Compilation Configuration + +The compilation configuration defines the parameters to use while compiling a target file or project. + +### `platform` + +- **Type**: String +- **Description**: Refers to the type of platform to be used to compile the underlying target. Currently, + `crytic-compile` or `solc` can be used as the compilation platform. +- **Default**: `crytic-compile` + +### `platformConfig` + +- **Type**: Struct +- **Description**: This struct is a platform-dependent structure which offers parameters for compiling the underlying project. + See below for the structure of `platformConfig` for each compilation platform. +- **Default**: The `platformConfig` for `crytic-compile` is the default value for this struct. + +### `platformConfig` for `crytic-compile` + +#### `target` + +- **Type**: String +- **Description**: Refers to the target that is being compiled. + > 🚩 Note that if you are using a compilation platform, such as Foundry or Hardhat, the default value for `target`, `.`, + > should **not** be changed. The `.` is equivalent to telling `crytic-compile` that the entire project needs to compiled, + > including any dependencies and remappings. In fact, unless you want to compile a single file, that has no third-party + > imports from, for example, OpenZeppelin, the default value should not be changed. +- **Default**: `.` + +#### `solcVersion` + +- **Type**: String +- **Description**: Describes the version of `solc` that will be installed and then used for compilation. Note that if you + are using a compilation platform, such as Foundry or Hardhat, this option does not need to be set. +- **Default**: "" + +#### `exportDirectory` + +- **Type**: String +- **Description**: Describes the directory where all compilation artifacts should be stored after compilation. Leaving it + empty will lead to the compilation artifacts being stored in `crytic-export/`. +- **Default**: "" + +#### `args` + +- **Type**: [String] +- **Description**: Refers to any additional args that one may want to provide to `crytic-compile`. Run `crytic-compile --help` + to view all of its supported flags. For example, if you would like to specify `--compile-force-framework foundry`, the + `args` value will be `"args": ["--compile-force-framework", "foundry"]`. + > 🚩 The `--export-format` and `--export-dir` are already used during compilation with `crytic-compile`. + > Re-using these flags in `args` will cause the compilation to fail. + +### `platformConfig` for `solc` + +#### `target` + +- **Type**: String +- **Description**: Refers to the target that is being compiled. The target must be a single `.sol` file. diff --git a/docs/src/project_configuration/fuzzing_config.md b/docs/src/project_configuration/fuzzing_config.md new file mode 100644 index 00000000..0fa1f1e5 --- /dev/null +++ b/docs/src/project_configuration/fuzzing_config.md @@ -0,0 +1,205 @@ +# Fuzzing Configuration + +The fuzzing configuration defines the parameters for the fuzzing campaign. + +### `workers` + +- **Type**: Integer +- **Description**: The number of worker threads to parallelize fuzzing operations on. +- **Default**: 10 workers + +### `workerResetLimit` + +- **Type**: Integer +- **Description**: The number of call sequences a worker should process on its underlying chain before being fully reset, + freeing memory. After resetting, the worker will be re-created and continue processing of call sequences. + > 🚩 This setting, along with `workers` influence the speed and memory consumption of the fuzzer. Setting this value + > higher will result in greater memory consumption per worker. Setting it too high will result in the in-memory + > chain's database growing to a size that is slower to process. Setting it too low may result in frequent worker resets + > that are computationally expensive for complex contract deployments that need to be replayed during worker reconstruction. +- **Default**: 50 sequences + +### `timeout` + +- **Type**: Integer +- **Description**: The number of seconds before the fuzzing campaign should be terminated. If a zero value is provided, + the timeout will not be enforced. The timeout begins after compilation succeeds and the fuzzing campaign has started. +- **Default**: 0 seconds + +### `testLimit` + +- **Type**: Integer +- **Description**: The number of function calls to make before the fuzzing campaign should be terminated. If a zero value + is provided, no test limit will be enforced. +- **Default**: 0 calls + +### `callSequenceLength` + +- **Type**: Integer +- **Description**: The maximum number of function calls to generate in a single call sequence in the attempt to violate + properties. After every `callSequenceLength` function calls, the blockchain is reset for the next sequence of transactions. +- **Default**: 100 calls/sequence + +### `coverageEnabled` + +- **Type**: Boolean +- **Description**: Whether coverage-increasing call sequences should be saved for the fuzzer to mutate/re-use. + Enabling coverage allows for improved code exploration. +- **Default**: `true` + +### `corpusDirectory` + +- **Type**: String +- **Description**: The file path where the corpus should be saved. The corpus collects sequences during a fuzzing campaign + that help drive fuzzer features (e.g. a call sequence that increases code coverage is stored in the corpus). These sequences + can then be re-used/mutated by the fuzzer during the next fuzzing campaign. +- **Default**: "" + +### `targetContracts` + +- **Type**: [String] (e.g. `[FirstContract, SecondContract, ThirdContract]`) +- **Description**: The list of contracts that will be deployed on the blockchain and then targeted for fuzzing by `medusa`. + For single-contract compilations, this value can be left as `[]`. This, however, is rare since most projects are multi-contract compilations. + > 🚩 Note that the order specified in the array is the _order_ in which the contracts are deployed to the blockchain. + > Thus, if you have a `corpusDirectory` set up, and you change the order of the contracts in the array, the corpus may no + > longer work since the contract addresses of the target contracts will change. This may render the entire corpus useless. +- **Default**: `[]` + +### `targetContractBalances` + +- **Type**: [Base-16 Strings] (e.g. `[0x123, 0x456, 0x789]`) +- **Description**: The starting balance for each contract in `targetContracts`. If the `constructor` for a target contract + is marked `payable`, this configuration option can be used to send ether during contract deployment. Note that this array + has a one-to-one mapping to `targetContracts`. Thus, if `targetContracts` is `[A, B, C]` and `targetContractsBalances` is + `["0", "0xff", "0"]`, then `B` will have a starting balance of 255 wei and `A` and `C` will have zero wei. Note that the wei-value + has to be hex-encoded and _cannot_ have leading zeros. For an improved user-experience, the balances may be encoded as base-10 + format strings in the future. +- **Default**: `[]` + +### `constructorArgs` + +- **Type**: `{"contractName": {"variableName": _value}}` +- **Description**: If a contract in the `targetContracts` has a `constructor` that takes in variables, these can be specified here. + An example can be found [here](#using-constructorargs). +- **Default**: `{}` + +### `deployerAddress` + +- **Type**: Address +- **Description**: The address used to deploy contracts on startup, represented as a hex string. + > 🚩 Changing this address may render entries in the corpus invalid since the addresses of the target contracts will change. +- **Default**: `0x30000` + +### `senderAddresses` + +- **Type**: [Address] +- **Description**: Defines the account addresses used to send function calls to deployed contracts in the fuzzing campaign. + > 🚩 Changing these addresses may render entries in the corpus invalid since the sender(s) of corpus transactions may no + > longer be valid. +- **Default**: `[0x10000, 0x20000, 0x30000]` + +### `blockNumberDelayMax` + +- **Type**: Integer +- **Description**: Defines the maximum block number jump the fuzzer should make between test transactions. The fuzzer + will use this value to make the next block's `block.number` between `[1, blockNumberDelayMax]` more than that of the previous + block. Jumping `block.number` allows `medusa` to enter code paths that require a given number of blocks to pass. +- **Default**: `60_480` + +### `blockTimestampDelayMax` + +- **Type**: Integer +- **Description**: The number of the maximum block timestamp jump the fuzzer should make between test transactions. + The fuzzer will use this value to make the next block's `block.timestamp` between `[1, blockTimestampDelayMax]` more + than that of the previous block. Jumping `block.timestamp`time allows `medusa` to enter code paths that require a given amount of time to pass. +- **Default**: `604_800` + +### `blockGasLimit` + +- **Type**: Integer +- **Description**: The maximum amount of gas a block's transactions can use in total (thus defining max transactions per block). + > 🚩 It is advised not to change this naively, as a minimum must be set for the chain to operate. +- **Default**: `125_000_000` + +### `transactionGasLimit` + +- **Type**: Integer +- **Description**: Defines the amount of gas sent with each fuzzer-generated transaction. + > 🚩 It is advised not to change this naively, as a minimum must be set for the chain to operate. +- **Default**: `12_500_000` + +## Using `constructorArgs` + +There might be use cases where contracts in `targetContracts` have constructors that accept arguments. The `constructorArgs` +configuration option allows you to specify those arguments. `constructorArgs` is a nested dictionary that maps +contract name -> variable name -> variable value. Let's look at an example below: + +```solidity +// This contract is used to test deployment of contracts with constructor arguments. +contract TestContract { + struct Abc { + uint a; + bytes b; + } + + uint x; + bytes2 y; + Abc z; + + constructor(uint _x, bytes2 _y, Abc memory _z) { + x = _x; + y = _y; + z = _z; + } +} + +contract DependentOnTestContract { + address deployed; + + constructor(address _deployed) { + deployed = _deployed; + } +} +``` + +In the example above, we have two contracts `TestContract` and `DependentOnTestContract`. You will note that +`DependentOnTestContract` requires the deployment of `TestContract` _first_ so that it can accept the address of where +`TestContract` was deployed. On the other hand, `TestContract` requires `_x`, `_y`, and `_z`. Here is what the +`constructorArgs` value would look like for the above deployment: + +> **Note**: The example below has removed all the other project configuration options outside of `targetContracts` and +> `constructorArgs` + +```json +{ + "fuzzing": { + "targetContracts": ["TestContract", "DependentOnTestContract"], + "constructorArgs": { + "TestContract": { + "_x": "123456789", + "_y": "0x5465", + "_z": { + "a": "0x4d2", + "b": "0x54657374206465706c6f796d656e74207769746820617267756d656e7473" + } + }, + "DependentOnTestContract": { + "_deployed": "DeployedContract:TestContract" + } + } + } +} +``` + +First, let us look at `targetContracts`. As mentioned in the [documentation for `targetContracts`](#targetcontracts), +the order of the contracts in the array determine the order of deployment. This means that `TestContract` will be +deployed first, which is what we want. + +Now, let us look at `constructorArgs`. `TestContract`'s dictionary specifies the _exact name_ of the constructor argument +(e.g. `_x` or `_y`) with their associated value. Since `_z` is of type `TestContract.Abc`, `_z` is also a dictionary +that specifies each field in the `TestContract.Abc` struct. + +For `DependentOnTestContract`, the `_deployed` key has +a value of `DeployedContract:TestContract`. This tells `medusa` to look for a deployed contract that has the name +`TestContract` and provide its address as the value for `_deployed`. Thus, whenever you need a deployed contract's +address as an argument for another contract, you must follow the format `DeployedContract:`. diff --git a/docs/src/project_configuration/logging_config.md b/docs/src/project_configuration/logging_config.md new file mode 100644 index 00000000..fb0af649 --- /dev/null +++ b/docs/src/project_configuration/logging_config.md @@ -0,0 +1,25 @@ +# Logging Configuration + +The logging configuration defines the parameters for logging to console and/or file. + +### `level` + +- **Type**: String +- **Description**: The log level will determine which logs are emitted or discarded. If `level` is "info" then all logs + with informational level or higher will be logged. The supported values for `level` are "trace", "debug", "info", "warn", "error", + and "panic". +- **Default**: "info" + +### `logDirectory` + +- **Type**: String +- **Description**: Describes what directory log files should be outputted. Have a non-empty `logDirectory` value will + enable "file logging" which will result in logs to be output to both console and file. Note that the directory path is + _relative_ to the directory containing the project configuration file. +- **Default**: "" + +### `noColor` + +- **Type**: Boolean +- **Description**: Disables colored output to console. +- **Default**: `false` diff --git a/docs/src/project_configuration/overview.md b/docs/src/project_configuration/overview.md new file mode 100644 index 00000000..af7d3a46 --- /dev/null +++ b/docs/src/project_configuration/overview.md @@ -0,0 +1,49 @@ +# Configuration Overview + +`medusa`'s project configuration provides extensive and granular control over the execution of the fuzzer. The project +configuration is a `.json` file that is broken down into five core components. + +- [Fuzzing Configuration](./fuzzing_config.md): The fuzzing configuration dictates the parameters with which the fuzzer will execute. +- [Testing Configuration](./testing_config.md): The testing configuration dictates how and what `medusa` should fuzz test. +- [Chain Configuration](./chain_config.md): The chain configuration dictates how `medusa`'s underlying blockchain should be configured. +- [Compilation Configuration](./compilation_config.md): The compilation configuration dictates how to compile the fuzzing target. +- [Logging Configuration](./logging_config.md): The logging configuration dictates when and where to log events. + +To generate a project configuration file, run [`medusa init`](../cli/init.md). + +You can also view this [example project configuration file](../static/medusa.json) for visualization. + +## Recommended Configuration + +A common issue that first-time users face is identifying which configuration options to change. `medusa` provides an +incredible level of flexibility on how the fuzzer should run but this comes with a tradeoff of understanding the nuances +of what configuration options control what feature. Outlined below is a list of configuration options that we recommend +you become familiar with and change before starting to fuzz test. + +> **Note:** Having an [example project configuration file](../static/medusa.json) open will aid in visualizing which +> configuration options to change. + +### `fuzzing.targetContracts` + +Updating this configuration option is **required**! The `targetContracts` configuration option tells `medusa` which contracts +to fuzz test. You can specify one or more contracts for this option which is why it accepts an array +of strings. Let's say you have a fuzz testing contract called `TestStakingContract` that you want to test. +Then, you would set the value of `targetContracts` to `["TestStakingContract"]`. +You can learn more about this option [here](./fuzzing_config.md#targetcontracts). + +### `fuzzing.testLimit` + +Updating test limit is optional but recommended. Test limit determines how many transactions `medusa` will execute before +stopping the fuzzing campaign. By default, the `testLimit` is set to 0. This means that `medusa` will run indefinitely. +While you iterate over your fuzz tests, it is beneficial to have a non-zero value. Thus, it is recommended to update this +value to `10_000` or `100_000` depending on the use case. You can learn more about this option [here](./fuzzing_config.md#testlimit). + +### `fuzzing.corpusDirectory` + +Updating the corpus directory is optional but recommended. The corpus directory determines where corpus items should be +stored on disk. A corpus item is a sequence of transactions that increased `medusa`'s coverage of the system. Thus, these +corpus items are valuable to store so that they can be re-used for the next fuzzing campaign. Additionally, the directory +will also hold [coverage reports](TODO) which is a valuable tool for debugging and validation. For most cases, you may set +`corpusDirectory`'s value to "corpus". This will create a `corpus/` directory in the same directory as the `medusa.json` +file. +You can learn more about this option [here](./fuzzing_config.md#corpusdirectory). diff --git a/docs/src/project_configuration/testing_config.md b/docs/src/project_configuration/testing_config.md new file mode 100644 index 00000000..3adfe08c --- /dev/null +++ b/docs/src/project_configuration/testing_config.md @@ -0,0 +1,178 @@ +# Testing Configuration + +The testing configuration can be broken down into a few subcomponents: + +- **High-level configuration**: Configures global testing parameters, regardless of the type of testing. +- **Assertion testing configuration**: Configures what kind of EVM panics should be treated as a failing fuzz test. +- **Property testing configuration**: Configures what kind of function signatures should be treated as property tests. +- **Optimization testing configuration**: Configures what kind of function signatures should be treated as optimization tests. + +We will go over each subcomponent one-by-one: + +## High-level Configuration + +### `stopOnFailedTest` + +- **Type**: Boolean +- **Description**: Determines whether the fuzzer should stop execution after the first _failed_ test. If `false`, `medusa` + will continue fuzzing until either the [`testLimit`](./fuzzing_config.md#testlimit) is hit, the [`timeout`](./fuzzing_config.md#timeout) + is hit, or the user manually stops execution. +- **Default**: `true` + +### `stopOnFailedContractMatching` + +- **Type**: Boolean +- **Description**: Determines whether the fuzzer should stop execution if it is unable to match the bytecode of a dynamically + deployed contract. A dynamically deployed contract is one that is created during the fuzzing campaign + (versus one that is specified in the [`fuzzing.targetContracts`](./fuzzing_config.md#targetcontracts)). + Here is an example of a dynamically deployed contract: + +```solidity + +contract MyContract { + OtherContract otherContract; + constructor() { + // This is a dynamically deployed contract + otherContract = new otherContract(); + } +} +``` + +- **Default**: `false` + +### `stopOnNoTests` + +- **Type**: Boolean +- **Description**: Determines whether the fuzzer should stop execution if no tests are found + (property tests, assertion tests, optimization tests, or custom API-level tests). If `false` and no tests are found, + `medusa` will continue fuzzing until either the [`testLimit`](./fuzzing_config.md#testlimit) is hit, + the [`timeout`](./fuzzing_config.md#timeout) is hit, or the user manually stops execution. +- **Default**: `true` + +### `testAllContracts` + +- **Type**: Boolean +- **Description**: Determines whether all contracts should be tested (including dynamically deployed ones), rather than + just the contracts specified in the project configuration's [`fuzzing.targetContracts`](./fuzzing_config.md#targetcontracts). +- **Default**: `false` + +### `traceAll`: + +- **Type**: Boolean +- **Description**: Determines whether an [execution trace](TODO) should be attached to each element of a call sequence + that triggered a test failure. +- **Default**: `false` + +## Assertion Testing Configuration + +### `enabled` + +- **Type**: Boolean +- **Description**: Enable or disable assertion testing +- **Default**: `true` + +### `testViewMethods` + +- **Type**: Boolean +- **Description**: Whether `pure` / `view` functions should be tested for assertion failures. + > 🚩 Fuzzing `pure` and `view` functions is not currently implemented. Thus, enabling this option to `true` does not + > update the fuzzer's behavior. +- **Default**: `false` + +### `panicCodeConfig` + +- **Type**: Struct +- **Description**: This struct describes the various types of EVM-level panics that should be considered a "failing case". + By default, only an `assert(false)` is considered a failing case. However, these configuration options would allow a user + to treat arithmetic overflows or division by zero as failing cases as well. + +#### `failOnAssertion` + +- **Type**: Boolean +- **Description**: Triggering an assertion failure (e.g. `assert(false)`) should be treated as a failing case. +- **Default**: `true` + +#### `failOnCompilerInsertedPanic` + +- **Type**: Boolean +- **Description**: Triggering a compiler-inserted panic should be treated as a failing case. +- **Default**: `false` + +#### `failOnArithmeticUnderflow` + +- **Type**: Boolean +- **Description**: Arithmetic underflow or overflow should be treated as a failing case +- **Default**: `false` + +#### `failOnDivideByZero` + +- **Type**: Boolean +- **Description**: Dividing by zero should be treated as a failing case +- **Default**: `false` + +#### `failOnEnumTypeConversionOutOfBounds` + +- **Type**: Boolean +- **Description**: An out-of-bounds enum access should be treated as a failing case +- **Default**: `false` + +#### `failOnIncorrectStorageAccess` + +- **Type**: Boolean +- **Description**: An out-of-bounds storage access should be treated as a failing case +- **Default**: `false` + +#### `failOnPopEmptyArray` + +- **Type**: Boolean +- **Description**: A `pop()` operation on an empty array should be treated as a failing case +- **Default**: `false` + +#### `failOnOutOfBoundsArrayAccess` + +- **Type**: Boolean +- **Description**: An out-of-bounds array access should be treated as a failing case +- **Default**: `false` + +#### `failOnAllocateTooMuchMemory` + +- **Type**: Boolean +- **Description**: Overallocation/excessive memory usage should be treated as a failing case +- **Default**: `false` + +#### `failOnCallUninitializedVariable` + +- **Type**: Boolean +- **Description**: Calling an uninitialized variable should be treated as a failing case +- **Default**: `false` + +## Property Testing Configuration + +### `enabled` + +- **Type**: Boolean +- **Description**: Enable or disable property testing. +- **Default**: `true` + +### `testPrefixes` + +- **Type**: [String] +- **Description**: The list of prefixes that the fuzzer will use to determine whether a given function is a property test or not. + For example, if `property_` is a test prefix, then any function name in the form `property_*` may be a property test. + > **Note**: If you are moving over from Echidna, you can add `echidna_` as a test prefix to quickly port over the property tests from it. +- **Default**: `[property_]` + +## Optimization Testing Configuration + +### `enabled` + +- **Type**: Boolean +- **Description**: Enable or disable optimization testing. +- **Default**: `true` + +### `testPrefixes` + +- **Type**: [String] +- **Description**: The list of prefixes that the fuzzer will use to determine whether a given function is an optimization + test or not. For example, if `optimize_` is a test prefix, then any function name in the form `optimize_*` may be a property test. +- **Default**: `[optimize_]` diff --git a/docs/src/static/contract_deployment.png b/docs/src/static/contract_deployment.png new file mode 100644 index 00000000..a021b01c Binary files /dev/null and b/docs/src/static/contract_deployment.png differ diff --git a/docs/src/static/coverage.png b/docs/src/static/coverage.png new file mode 100644 index 00000000..f15676ea Binary files /dev/null and b/docs/src/static/coverage.png differ diff --git a/docs/src/static/custom.css b/docs/src/static/custom.css new file mode 100644 index 00000000..e4565d1a --- /dev/null +++ b/docs/src/static/custom.css @@ -0,0 +1,6 @@ +img[alt="medusa_logo"] { + width: 60%; + margin-left: auto; + margin-right: auto; + display: block; +} diff --git a/docs/src/static/function_level_testing_medusa.json b/docs/src/static/function_level_testing_medusa.json new file mode 100644 index 00000000..79f2aa21 --- /dev/null +++ b/docs/src/static/function_level_testing_medusa.json @@ -0,0 +1,72 @@ +{ + "fuzzing": { + "workers": 10, + "workerResetLimit": 50, + "timeout": 0, + "testLimit": 1000, + "callSequenceLength": 1, + "corpusDirectory": "", + "coverageEnabled": true, + "targetContracts": ["TestDepositContract"], + "targetContractsBalances": ["0xfffffffffffffffffffffffffffffff"], + "constructorArgs": {}, + "deployerAddress": "0x30000", + "senderAddresses": ["0x10000", "0x20000", "0x30000"], + "blockNumberDelayMax": 60480, + "blockTimestampDelayMax": 604800, + "blockGasLimit": 125000000, + "transactionGasLimit": 12500000, + "testing": { + "stopOnFailedTest": true, + "stopOnFailedContractMatching": false, + "stopOnNoTests": true, + "testAllContracts": false, + "traceAll": false, + "assertionTesting": { + "enabled": true, + "testViewMethods": false, + "panicCodeConfig": { + "failOnCompilerInsertedPanic": false, + "failOnAssertion": true, + "failOnArithmeticUnderflow": false, + "failOnDivideByZero": false, + "failOnEnumTypeConversionOutOfBounds": false, + "failOnIncorrectStorageAccess": false, + "failOnPopEmptyArray": false, + "failOnOutOfBoundsArrayAccess": false, + "failOnAllocateTooMuchMemory": false, + "failOnCallUninitializedVariable": false + } + }, + "propertyTesting": { + "enabled": true, + "testPrefixes": ["property_"] + }, + "optimizationTesting": { + "enabled": true, + "testPrefixes": ["optimize_"] + } + }, + "chainConfig": { + "codeSizeCheckDisabled": true, + "cheatCodes": { + "cheatCodesEnabled": true, + "enableFFI": false + } + } + }, + "compilation": { + "platform": "crytic-compile", + "platformConfig": { + "target": "test.sol", + "solcVersion": "", + "exportDirectory": "", + "args": [] + } + }, + "logging": { + "level": "info", + "logDirectory": "", + "noColor": false + } +} diff --git a/docs/src/static/medusa.json b/docs/src/static/medusa.json new file mode 100644 index 00000000..2e8644b6 --- /dev/null +++ b/docs/src/static/medusa.json @@ -0,0 +1,72 @@ +{ + "fuzzing": { + "workers": 10, + "workerResetLimit": 50, + "timeout": 0, + "testLimit": 0, + "callSequenceLength": 100, + "corpusDirectory": "", + "coverageEnabled": true, + "targetContracts": [], + "targetContractsBalances": [], + "constructorArgs": {}, + "deployerAddress": "0x30000", + "senderAddresses": ["0x10000", "0x20000", "0x30000"], + "blockNumberDelayMax": 60480, + "blockTimestampDelayMax": 604800, + "blockGasLimit": 125000000, + "transactionGasLimit": 12500000, + "testing": { + "stopOnFailedTest": true, + "stopOnFailedContractMatching": false, + "stopOnNoTests": true, + "testAllContracts": false, + "traceAll": false, + "assertionTesting": { + "enabled": true, + "testViewMethods": false, + "panicCodeConfig": { + "failOnCompilerInsertedPanic": false, + "failOnAssertion": true, + "failOnArithmeticUnderflow": false, + "failOnDivideByZero": false, + "failOnEnumTypeConversionOutOfBounds": false, + "failOnIncorrectStorageAccess": false, + "failOnPopEmptyArray": false, + "failOnOutOfBoundsArrayAccess": false, + "failOnAllocateTooMuchMemory": false, + "failOnCallUninitializedVariable": false + } + }, + "propertyTesting": { + "enabled": true, + "testPrefixes": ["property_"] + }, + "optimizationTesting": { + "enabled": true, + "testPrefixes": ["optimize_"] + } + }, + "chainConfig": { + "codeSizeCheckDisabled": true, + "cheatCodes": { + "cheatCodesEnabled": true, + "enableFFI": false + } + } + }, + "compilation": { + "platform": "crytic-compile", + "platformConfig": { + "target": ".", + "solcVersion": "", + "exportDirectory": "", + "args": [] + } + }, + "logging": { + "level": "info", + "logDirectory": "", + "noColor": false + } +} diff --git a/docs/src/static/medusa_logo.png b/docs/src/static/medusa_logo.png new file mode 100755 index 00000000..11c6061e Binary files /dev/null and b/docs/src/static/medusa_logo.png differ diff --git a/docs/src/testing/coverage_reports.md b/docs/src/testing/coverage_reports.md new file mode 100644 index 00000000..408f8e18 --- /dev/null +++ b/docs/src/testing/coverage_reports.md @@ -0,0 +1 @@ +## Coverage Reports diff --git a/docs/src/testing/fuzzing_lifecycle.md b/docs/src/testing/fuzzing_lifecycle.md new file mode 100644 index 00000000..278930e1 --- /dev/null +++ b/docs/src/testing/fuzzing_lifecycle.md @@ -0,0 +1,136 @@ +# The Fuzzing Lifecycle + +Understanding what `medusa` is doing under-the-hood significantly aids in understanding how to fuzz smart contracts +and also in writing fuzz tests. This chapter will walk you through the process of deploying the target contracts, +generating and executing call sequences, using the corpus, updating coverage, and resetting the blockchain. + +## Contract deployment + +The contract deployment process will deploy each contract specified in the +[`fuzzing.targetContracts`](../project_configuration/fuzzing_config.md#targetcontracts) one-by-one. Any contracts that +are dynamically deployed during the _construction_ of a target contract are also deployed. The deployment of these +contracts are done by the configurable +[`fuzzing.deployerAddress`](../project_configuration/fuzzing_config.md#deployeraddress) address. + +We will call the state of the blockchain after all the target contracts are deployed as the **"initial deployment state"**. +This is the state of the blockchain before any transactions have been executed by the fuzzer. Here is what the underlying +blockchain would look like after the deployment of contracts `A`, `B`, `C` (assuming no other dynamic deployments). + +![Contract Deployment Diagram](../static/contract_deployment.png) + +Now that we have our target contracts deployed, we can start executing call sequences! + +## Call sequence execution + +Call sequence execution is the crux of the fuzzing lifecycle. At a high-level, call sequence execution is the **iterative +process of executing fuzzed transactions on the initial deployment state** in hopes of violating (or validating) an invariant. +Before continuing, it is important to understand what a call sequence is. + +### Defining a call sequence + +A call sequence is an **array of individual transactions**. The length of the array is governed by the +[`fuzzing.callSequenceLength`](../project_configuration/fuzzing_config.md#callsequencelength) configuration parameter. +The fuzzer will maintain EVM state for the duration of a call sequence before [resetting the state](#resetting-the-blockchain). +Thus, if you have a call sequence length of 10, `medusa` will execute 10 transactions, maintain state throughout that +process, and then wipe the EVM state. Having a call sequence length of 1 means that `medusa` will wipe the state after +each transaction. This is useful for fuzz testing arithmetic libraries or isolated functions. + +Now that you know what a call sequence is, let's discuss how to generate a call sequence. + +### Generating a call sequence + +Call sequence generation can happen in two main ways: + +1. Generate a completely random call sequence +2. Mutate an existing call sequence from the corpus + +Let's talk about each possibility. Generating a completely random call sequence is straightforward. If we have a +`fuzzing.callSequenceLength` of 50, we will generate 50 random transactions. + +> **Definition**: A random transaction is a call to a random method in one of the target contracts. Any input arguments +> to the method are fuzzed values. + +The second possibility is more nuanced. To understand how to mutate an existing call sequence from the corpus, we need +to first discuss the idea of coverage and what a corpus is. + +### Coverage and the corpus + +Tracking coverage is one of the most powerful features of `medusa`. + +> **Definition**: Coverage is a measure of what parts of the code have been executed by the fuzzer + +Coverage is tracked in a rather simple fashion. For each target contract, we maintain a byte array where the length of the +byte array is equal to the length of that contract's bytecode. If a certain transaction caused us to execute an opcode +that we had not executed before, we increased coverage of that contract. + +![Coverage Tracking Diagram](../static/coverage.png) + +As shown in the figure above, the `CALL` opcode was just executed causing the coverage array's value to be updated at that +index. The next natural question is, how do we harness this information to improve the fuzzer? + +This is where the idea of a **corpus** comes in. + +> **Definition**: The corpus is a structure that holds "interesting" or "coverage-increasing" call sequences. + +Thus, when `medusa` runs, if it finds a call sequence that increased its coverage of the system, it will add it to the corpus. +These call sequences are invaluable to `medusa` because they allowed it to explore a larger portion of the system. This is +what makes `medusa` a **coverage-guided fuzzer**. + +> **Definition**: A coverage-guided fuzzer is one that aims to maximize its coverage of the system. + +Tracking coverage and storing coverage-increasing sequences in the corpus also allows `medusa` to re-use these sequences. +This takes us back to the second possibility when generating call sequences: mutating an existing sequence from the corpus. + +The reason we re-use call sequences is that we know that the call sequence in question improved our coverage. So, we +might as well re-use it, **mutate** it, and then execute that mutated call sequence in hopes of further increasing our coverage. +There are a variety of mutational strategies that `medusa` employs. For example, `medusa` can take a call sequence from the corpus and append a new random +transaction at the end of it. This is called **mutational fuzzing**. + +> **Definition**: Mutational fuzzing is the practice of taking existing data samples and generating new variants of them +> (mutants). + +Now that we know what a call sequence is, how to generate them, and how to track coverage, we can finally discuss how +these call sequences are executed. + +### Executing the call sequence + +Call sequence execution happens in an _iterative_ fashion. Here is some pseudocode on how it happens: + +``` +# Generate a new call sequence or mutate one from the corpus +sequence = generator.NewCallSequence() + +# Iteratively execute each call in the call sequence +for i < len(sequence) { + # Retrieve the i-th element in the sequence + tx = sequence[i] + + # Run the transaction on the blockchain and retrieve the result + result = blockchain.executeTransaction(tx) + + # Update coverage + increasedCoverage = coverageTracker.updateCoverage() + + # If coverage increased, add sequence[:i+1] to the corpus + if increasedCoveraged { + corpus.addCallSequence(tx[:i+1]) + } + + # Check for invariant failures + encounteredFailure = tester.checkForInvariantFailures(result) + + # Let user know we had a failing test case + if encounteredFailure { + reportFailedTestCase() + } +} +``` + +The one portion of the above pseudocode that we did not discuss is checking for invariant failures. We will discuss +the different types of invariants and what an invariant failure means in the [next chapter](./invariants.md). + +## Resetting the blockchain + +The final step in the fuzzing lifecycle is resetting the blockchain. Resetting the blockchain is as simple as reverting +to the "initial deployment state" of the blockchain. Once we reset back to the "initial deployment state", we can now generate and execute +another call sequence! diff --git a/docs/src/testing/invariants.md b/docs/src/testing/invariants.md new file mode 100644 index 00000000..4994c81b --- /dev/null +++ b/docs/src/testing/invariants.md @@ -0,0 +1,69 @@ +# Types of Invariants + +As discussed in the [testing overview](./overview.md) chapter, invariants describe the "truths" of your system. These +are unchanging properties that arise from the design of a codebase. + +> **Note**: We will interchange the use of the word property and invariant often. For all intents and purposes, they +> mean the same thing. + +Defining and testing your invariants is critical to assessing the **expected system behavior**. + +We like to break down invariants into two general categories: function-level invariants and system-level invariants. +Note that there are other ways of defining and scoping invariants, but this distinction is generally sufficient to +start fuzz testing even the most complex systems. + +## Function-level invariants + +A function-level invariant can be defined as follows: + +> **Definition**: A function-level invariant is a property that arises from the execution of a specific function. + +Let's take the following function from a smart contract: + +```solidity +function deposit() public payable { + // Make sure that the total deposited amount does not exceed the limit + uint256 amount = msg.value; + require(totalDeposited + amount <= MAX_DEPOSIT_AMOUNT); + + // Update the user balance and total deposited + balances[msg.sender] += amount; + totalDeposited += amount; + + emit Deposit(msg.sender, amount, totalDeposited); +} +``` + +The `deposit` function has the following function-level invariants: + +1. The ETH balance of `msg.sender` must decrease by `amount`. +2. The ETH of `address(this)` must increase by `amount`. +3. `balances[msg.sender]` should increase by `amount`. +4. The `totalDeposited` value should increase by `amount`. + +Note that there other properties that can also be tested for but the above should highlight what a function-level +invariant is. In general, function-level invariants can be identified by assessing what must be true _before_ the execution +of a function and what must be true _after_ the execution of that same function. In the next chapter, we will write a +fuzz test to test the `deposit` function and how to use medusa to run that test. + +Let's now look at system-level invariants. + +## System-level invariants + +A system-level invariant can be defined as follows: + +> **Definition**: A system-level invariant is a property that holds true across the _entire_ execution of a system + +Thus, a system-level invariant is a lot more generalized than a function-level invariant. Here are two common examples +of a function-level invariant: + +1. The `xy=k` constant product formula should always hold for Uniswap pools +2. No user's balance should ever exceed the total supply for an ERC20 token. + +In the `deposit` function above, we also see the presence of a system-level invariant: + +**The `totalDeposited` amount should always be less than or equal to the `MAX_DEPOSIT_AMOUNT`**. + +Since the `totalDeposited` value can be affected by the presence of other functions in the system +(e.g. `withdraw` or `stake`), it is best tested at the system level instead of the function level. We will look at how +to write system-level invariants in the [Writing System-Level Invariants](./writing-system-level-invariants.md) chapter. diff --git a/docs/src/testing/overview.md b/docs/src/testing/overview.md new file mode 100644 index 00000000..0ee8181c --- /dev/null +++ b/docs/src/testing/overview.md @@ -0,0 +1,26 @@ +# Testing Overview + +This chapter discusses the overarching goal of smart contract fuzzing. + +Traditional fuzz testing (e.g. with [`AFL`](https://lcamtuf.coredump.cx/afl/)) aims to generally explore a binary by providing +random inputs in an effort to identify new system states or crash the program (please note that this is a pretty crude generalization). +This model, however, does not translate to the smart contract ecosystem since you cannot cause a smart contract to "crash". +A transaction that reverts, for example, is not equivalent to a binary crashing or panicking. + +Thus, with smart contracts, we have to change the fuzzing paradigm. When you hear of "fuzzing smart contracts", you are +not trying to crash the program but, instead, you are trying to validate the **invariants** of the program. + +> **Definition**: An invariant is a property that remains unchanged after one or more operations are applied to it. + +More generally, an invariant is a "truth" about some system. For smart contracts, this can take many faces. + +1. **Mathematical invariants**: `a + b = b + a`. The commutative property is an invariant and any Solidity math library + should uphold this property. +2. **ERC20 tokens**: The sum of all user balances should never exceed the total supply of the token. +3. **Automated market maker (e.g. Uniswap)**: `xy = k`. The constant-product formula is an invariant that maintains the + economic guarantees of AMMs such as Uniswap. + +> **Definition**: Smart contract fuzzing uses random sequences of transactions to test the invariants of the smart contract system. + +Before we explore how to identify, write, and test invariants, it is beneficial to understand how smart contract fuzzing +works under-the-hood. diff --git a/docs/src/testing/tips.md b/docs/src/testing/tips.md new file mode 100644 index 00000000..a2de2928 --- /dev/null +++ b/docs/src/testing/tips.md @@ -0,0 +1,32 @@ +## Tips for Testing with Medusa + +### General + +- **Use multiple testing modes:** Medusa supports property testing, assertion testing, and optimization testing. Use a combination of modes to thoroughly test your contracts. +- **Write clear and concise tests:** Your tests should be easy to read and understand. Avoid complex logic or unnecessary code. +- **Test edge cases:** Consider testing extreme values and unusual inputs to ensure your contracts handle them correctly. +- **Use a variety of test inputs:** Generate a diverse set of test inputs to cover a wide range of scenarios. +- **Monitor gas consumption:** Medusa can track gas consumption during testing. Use this information to identify areas where your contracts can be optimized. + +### Property Testing + +- **Choose meaningful properties:** The properties you test should be important invariants of your contract. + +### Assertion Testing + +- **Use assertions judiciously:** Assertions can be useful for catching errors, but they can also slow down testing. Use them only when necessary. +- **Test for both valid and invalid inputs:** Ensure your assertions check for both valid and invalid inputs to thoroughly test your contract's behavior. +- **Use pre-conditions and post-conditions to verify the state of the contract before and after a function call.:** Pre-conditions and post-conditions are assertions that can be used to verify the state of the contract before and after a function call. This can help to ensure that the function is called with the correct inputs, that it produces the expected outputs, and that the state of the contract is valid. + +### Optimization Testing + +- **Choose a meaningful optimization goal:** The goal of your optimization test should be to maximize a specific metric, such as the return value of a function. +- **Use a variety of optimization techniques:** Medusa supports multiple optimization techniques, such as genetic algorithms and simulated annealing. Consider using different techniques to find the best solution. + +### Additional Tips + +- **Use a configuration file:** A configuration file allows you to customize Medusa's behavior and specify additional testing parameters. +- **Use corpus and coverage information to improve the effectiveness of your fuzzing campaigns:** Corpus and coverage information can be used to improve the effectiveness of your fuzzing campaigns by providing feedback on the quality of the test inputs. +- **Run Medusa in parallel:** Medusa can run tests in parallel to speed up the testing process. +- **Review the test results carefully:** Medusa provides detailed test results. Take the time to review them carefully and identify any potential issues. +- **Use Medusa as part of your development process:** Integrate Medusa into your development workflow to regularly test your contracts and identify potential bugs early on. diff --git a/docs/src/testing/writing-function-level-invariants.md b/docs/src/testing/writing-function-level-invariants.md new file mode 100644 index 00000000..ebe4e856 --- /dev/null +++ b/docs/src/testing/writing-function-level-invariants.md @@ -0,0 +1,145 @@ +## Writing Function-Level Invariants + +This chapter will walk you through writing function-level fuzz tests for the `deposit` function that we saw in the [previous chapter](./invariants.md#function-level-invariants). + +Before we write the fuzz tests, let's look into how we would write a unit test for the `deposit` function: + +```solidity +function testDeposit() public { + // The amount of tokens to deposit + uint256 amount = 10 ether; + + // Retrieve balance of user before deposit + preBalance = depositContract.balances(address(this)); + + // Call the deposit contract (let's assume this contract has 10 ether) + depositContract.deposit{value: amount}(); + + // Assert post-conditions + assert(depositContract.balances(msg.sender) == preBalance + amount); + // Add other assertions here +} +``` + +What we will notice about the test above is that it _fixes_ the value that is being sent. It is unable to test how the +`deposit` function behaves across a variety of input spaces. Thus, a function-level fuzz test can be thought of as a +"unit test on steroids". Instead of fixing the `amount`, we let the fuzzer control the `amount` value to any number between +`[0, type(uint256).max]` and see how the system behaves to that. + +> **Note**: One of the core differences between a traditional unit test versus a fuzz test is that a fuzz test accepts input arguments that the fuzzer can control. + +### Writing a Fuzz Test for the `deposit` Function + +Here is what a fuzz test for the `deposit` function would look like: + +```solidity +function testDeposit(uint256 _amount) public { + // Let's bound the input to be _at most_ the ETH balance of this contract + // The amount value will now in between [0, address(this).balance] + uint256 amount = clampLte(_amount, address(this).balance); + + // Retrieve balance of user before deposit + uint256 preBalance = depositContract.balances(address(this)); + + // Call the deposit contract with a variable amount + depositContract.deposit{value: _amount}(); + + // Assert post-conditions + assert(depositContract.balances(address(this)) == preBalance + amount); + // Add other assertions here +} +``` + +Notice that we bounded the `_amount` variable to be less than or equal to the test contract's ETH balance. +This type of bounding is very common when writing fuzz tests. Bounding allows you to only test values that are reasonable. +If `address(this)` doesn't have enough ETH, it does not make sense to try and call the `deposit` function. Additionally, +although we only tested one of the function-level invariants from the [previous chapter](./invariants.md), writing the remaining +would follow a similar pattern as the one written above. + +## Running a function-level test with medusa + +Let's now run the above example with medusa. Here is the test code: + +```solidity +contract DepositContract { + // @notice MAX_DEPOSIT_AMOUNT is the maximum amount that can be deposited into this contract + uint256 public constant MAX_DEPOSIT_AMOUNT = 1_000_000e18; + + // @notice balances holds user balances + mapping(address => uint256) public balances; + + // @notice totalDeposited represents the current deposited amount across all users + uint256 public totalDeposited; + + // @notice Deposit event is emitted after a deposit occurs + event Deposit(address depositor, uint256 amount, uint256 totalDeposited); + + // @notice deposit allows user to deposit into the system + function deposit() public payable { + // Make sure that the total deposited amount does not exceed the limit + uint256 amount = msg.value; + require(totalDeposited + amount <= MAX_DEPOSIT_AMOUNT); + + // Update the user balance and total deposited + balances[msg.sender] += amount; + totalDeposited += amount; + + emit Deposit(msg.sender, amount, totalDeposited); + } +} + +contract TestDepositContract { + + // @notice depositContract is an instance of DepositContract + DepositContract depositContract; + + constructor() payable { + // Deploy the deposit contract + depositContract = new DepositContract(); + } + + // @notice testDeposit tests the DepositContract.deposit function + function testDeposit(uint256 _amount) public { + // Let's bound the input to be _at most_ the ETH balance of this contract + // The amount value will now in between [0, address(this).balance] + uint256 amount = clampLte(_amount, address(this).balance); + + // Retrieve balance of user before deposit + uint256 preBalance = depositContract.balances(address(this)); + + // Call the deposit contract with a variable amount + depositContract.deposit{value: _amount}(); + + // Assert post-conditions + assert(depositContract.balances(address(this)) == preBalance + amount); + // Add other assertions here + } + + // @notice clampLte returns a value between [a, b] + function clampLte(uint256 a, uint256 b) internal returns (uint256) { + if (!(a <= b)) { + uint256 value = a % (b + 1); + return value; + } + return a; + } + +} +``` + +To run this test contract, download the project configuration file [here](../static/function_level_testing_medusa.json), +rename it to `medusa.json`, and run: + +``` +medusa fuzz --config medusa.json +``` + +The following changes were made to the default project configuration file to allow this test to run: + +- `fuzzing.targetContracts`: The `fuzzing.targetContracts` value was updated to `["TestDepositContract"]`. +- `fuzzing.targetContractsBalances`: The `fuzzing.targetContractsBalances` was updated to `["0xfffffffffffffffffffffffffffffff"]` + to allow the `TestDepositContract` contract to have an ETH balance allowing the fuzzer to correctly deposit funds into the + `DepositContract`. +- `fuzzing.testLimit`: The `fuzzing.testLimit` was set to `1_000` to shorten the duration of the fuzzing campign. +- `fuzzing.callSequenceLength`: The `fuzzing.callSequenceLength` was set to `1` so that the `TestDepositContract` can be + reset with its full ETH balance after each transaction. diff --git a/docs/src/testing/writing-system-level-invariants.md b/docs/src/testing/writing-system-level-invariants.md new file mode 100644 index 00000000..5cf2b0e1 --- /dev/null +++ b/docs/src/testing/writing-system-level-invariants.md @@ -0,0 +1,3 @@ +## Writing System-Level Invariants with Medusa + +WIP diff --git a/docs/src/testing/writing-tests.md b/docs/src/testing/writing-tests.md new file mode 100644 index 00000000..86da3ec6 --- /dev/null +++ b/docs/src/testing/writing-tests.md @@ -0,0 +1,189 @@ +# Testing with `medusa` + +`medusa`, like Echidna, supports the following testing modes: + +1. [Property Mode](https://secure-contracts.com/program-analysis/echidna/introduction/how-to-test-a-property.html) +2. [Assertion Mode](https://secure-contracts.com/program-analysis/echidna/basic/assertion-checking.html) +3. [Optimization Mode](https://secure-contracts.com/program-analysis/echidna/advanced/optimization_mode.html) + +For more advanced information and documentation on how the various modes work and their pros/cons, check out [secure-contracts.com](https://secure-contracts.com/program-analysis/echidna/index.html) + +## Writing property tests + +Property tests are represented as functions within a Solidity contract whose names are prefixed with a prefix specified by the `testPrefixes` configuration option (`fuzz_` is the default test prefix). Additionally, they must take no arguments and return a `bool` indicating if the test succeeded. + +```solidity +contract TestXY { + uint x; + uint y; + + function setX(uint value) public { + x = value + 3; + } + + function setY(uint value) public { + y = value + 9; + } + + function fuzz_never_specific_values() public returns (bool) { + // ASSERTION: x should never be 10 at the same time y is 80 + return !(x == 10 && y == 80); + } +} +``` + +`medusa` deploys your contract containing property tests and generates a sequence of calls to execute against all publicly accessible methods. After each function call, it calls upon your property tests to ensure they return a `true` (success) status. + +### Testing in property-mode + +To begin a fuzzing campaign in property-mode, you can run `medusa fuzz` or `medusa fuzz --config [config_path]`. + +> **Note**: Learn more about running `medusa` with its CLI [here](./Command-Line-Interface.md). + +Invoking this fuzzing campaign, `medusa` will: + +- Compile the given targets +- Start the configured number of worker threads, each with their own local Ethereum test chain. +- Deploy all contracts to each worker's test chain. +- Begin to generate and send call sequences to update contract state. +- Check property tests all succeed after each call executed. + +Upon discovery of a failed property test, `medusa` will halt, reporting the call sequence used to violate any property test(s): + +``` +[FAILED] Property Test: TestXY.fuzz_never_specific_values() +Test "TestXY.fuzz_never_specific_values()" failed after the following call sequence: +1) TestXY.setY([71]) (gas=4712388, gasprice=1, value=0, sender=0x2222222222222222222222222222222222222222) +2) TestXY.setX([7]) (gas=4712388, gasprice=1, value=0, sender=0x3333333333333333333333333333333333333333) +``` + +## Writing assertion tests + +Although both property-mode and assertion-mode try to validate / invalidate invariants of the system, they do so in different ways. In property-mode, `medusa` will look for functions with a specific test prefix (e.g. `fuzz_`) and test those. In assertion-mode, `medusa` will test to see if a given call sequence can cause the Ethereum Virtual Machine (EVM) to "panic". The EVM has a variety of panic codes for different scenarios. For example, there is a unique panic code when an `assert(x)` statement returns `false` or when a division by zero is encountered. In assertion mode, which panics should or should not be treated as "failing test cases" can be toggled by updating the [Project Configuration](./Project-Configuration.md#fuzzing-configuration). By default, only `FailOnAssertion` is enabled. Check out the [Example Project Configuration File](https://github.com/crytic/medusa/wiki/Example-Project-Configuration-File) for a visualization of the various panic codes that can be enabled. An explanation of the various panic codes can be found in the [Solidity documentation](https://docs.soliditylang.org/en/latest/control-structures.html#panic-via-assert-and-error-via-require). + +Please note that the behavior of assertion mode is different between `medusa` and Echidna. Echidna will only test for `assert(x)` statements while `medusa` provides additional flexibility. + +```solidity +contract TestContract { + uint x; + uint y; + + function setX(uint value) public { + x = value; + + // ASSERTION: x should be an even number + assert(x % 2 == 0); + } +} +``` + +During a call sequence, if `setX` is called with a `value` that breaks the assertion (e.g. `value = 3`), `medusa` will treat this as a failing property and report it back to the user. + +### Testing in assertion-mode + +To begin a fuzzing campaign in assertion-mode, you can run `medusa fuzz --assertion-mode` or `medusa fuzz --config [config_path] --assertion-mode`. + +> **Note**: Learn more about running `medusa` with its CLI [here](./Command-Line-Interface.md). + +Invoking this fuzzing campaign, `medusa` will: + +- Compile the given targets +- Start the configured number of worker threads, each with their own local Ethereum test chain. +- Deploy all contracts to each worker's test chain. +- Begin to generate and send call sequences to update contract state. +- Check to see if there any failing assertions after each call executed. + +Upon discovery of a failed assertion, `medusa` will halt, reporting the call sequence used to violate any assertions: + +``` +Fuzzer stopped, test results follow below ... +[FAILED] Assertion Test: TestContract.setX(uint256) +Test for method "TestContract.setX(uint256)" failed after the following call sequence resulted in an assertion: +1) TestContract.setX([102552480437485684723695021980667056378352338398148431990087576385563741034353]) (block=2, time=4, gas=12500000, gasprice=1, value=0, sender=0x1111111111111111111111111111111111111111) +``` + +## Writing optimization tests + +Optimization mode's goal is not to validate/invalidate properties but instead to maximize the return value of a function. Similar to property mode, these functions must be prefixed with a prefix specified by the `testPrefixes` configuration option (`optimize_` is the default test prefix). Additionally, they must take no arguments and return an `int256`. A good use case for optimization mode is to try to quantify the impact of a bug (e.g. a rounding error). + +```solidity +contract TestContract { + int256 input; + + function set(int256 _input) public { + input = _input; + } + + function optimize_opt_linear() public view returns (int256) { + if (input > -4242) return -input; + else return 0; + } +} +``` + +`medusa` deploys your contract containing optimization tests and generates a sequence of calls to execute against all publicly accessible methods. After each function call, it calls upon your otpimization tests to identify whether the return value of those tests are greater than the currently stored values. + +### Testing in optimization-mode + +To begin a fuzzing campaign in optimization-mode, you can run `medusa fuzz --optimization-mode` or `medusa fuzz --config [config_path] --optimization-mode`. + +> **Note**: Learn more about running `medusa` with its CLI [here](./Command-Line-Interface.md). + +Invoking this fuzzing campaign, `medusa` will: + +- Compile the given targets +- Start the configured number of worker threads, each with their own local Ethereum test chain. +- Deploy all contracts to each worker's test chain. +- Begin to generate and send call sequences to update contract state. +- Check to see if the return value of the optimization test is greater than the cached value. + - If the value is greater, update the cached value. + +Once the test limit or timeout for the fuzzing campaign has been reached, `medusa` will halt and report the call sequence that maximized the return value of the function: + +``` +Fuzzer stopped, test results follow below ... +[PASSED] Optimization Test: TestContract.optimize_opt_linear() +Optimization test "TestContract.optimize_opt_linear()" resulted in the maximum value: 4241 with the following sequence: +1) TestContract.set(-4241) (block=2, time=3, gas=12500000, gasprice=1, value=0, sender=0x0000000000000000000000000000000000010000) +``` + +## Testing with multiple modes + +Note that we can run `medusa` with one, many, or no modes enabled. Running `medusa fuzz --assertion-mode --optimization-mode` will run all three modes at the same time, since property-mode is enabled by default. If a project configuration file is used, any combination of the three modes can be toggled. In fact, all three modes can be disabled and `medusa` will still run. Please review the [Project Configuration](./Project-Configuration.md) wiki page and the [Project Configuration Example](/Example-Project-Configuration-File.md) for more information. + +```solidity +contract TestContract { + int256 input; + + function set(int256 _input) public { + input = _input; + } + + function failing_assert_method(uint value) public { + // ASSERTION: We always fail when you call this function. + assert(false); + } + + function fuzz_failing_property() public view returns (bool) { + // ASSERTION: fail immediately. + return false; + } + + function optimize_opt_linear() public view returns (int256) { + if (input > -4242) return -input; + else return 0; + } +} +``` + +Invoking a fuzzing campaign with `medusa fuzz --assertion-mode --optimization-mode` (note all three modes are enabled), `medusa` will: + +- Compile the given targets +- Start the configured number of worker threads, each with their own local Ethereum test chain. +- Deploy all contracts to each worker's test chain. +- Begin to generate and send call sequences to update contract state. +- Check to see: + - If property tests all succeed after each call executed. + - If a panic (which was enabled in the project configuration) has been triggered after each call. + - Whether the return value of the optimization test is greater than the cached value. + - Update the cached value if it is greater. diff --git a/docs/theme/favicon.png b/docs/theme/favicon.png new file mode 100755 index 00000000..a72d998f Binary files /dev/null and b/docs/theme/favicon.png differ diff --git a/docs/theme/favicon.svg b/docs/theme/favicon.svg new file mode 100755 index 00000000..78259cf9 --- /dev/null +++ b/docs/theme/favicon.svg @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/theme/highlight.js b/docs/theme/highlight.js new file mode 100644 index 00000000..8d926897 --- /dev/null +++ b/docs/theme/highlight.js @@ -0,0 +1,6 @@ +/* + Highlight.js 10.1.1 (93fd0d73) + License: BSD-3-Clause + Copyright (c) 2006-2020, Ivan Sagalaev +*/ +var hljs=function(){"use strict";function e(n){Object.freeze(n);var t="function"==typeof n;return Object.getOwnPropertyNames(n).forEach((function(r){!Object.hasOwnProperty.call(n,r)||null===n[r]||"object"!=typeof n[r]&&"function"!=typeof n[r]||t&&("caller"===r||"callee"===r||"arguments"===r)||Object.isFrozen(n[r])||e(n[r])})),n}class n{constructor(e){void 0===e.data&&(e.data={}),this.data=e.data}ignoreMatch(){this.ignore=!0}}function t(e){return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function r(e,...n){var t={};for(const n in e)t[n]=e[n];return n.forEach((function(e){for(const n in e)t[n]=e[n]})),t}function a(e){return e.nodeName.toLowerCase()}var i=Object.freeze({__proto__:null,escapeHTML:t,inherit:r,nodeStream:function(e){var n=[];return function e(t,r){for(var i=t.firstChild;i;i=i.nextSibling)3===i.nodeType?r+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:r,node:i}),r=e(i,r),a(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:r,node:i}));return r}(e,0),n},mergeStreams:function(e,n,r){var i=0,s="",o=[];function l(){return e.length&&n.length?e[0].offset!==n[0].offset?e[0].offset"}function u(e){s+=""}function d(e){("start"===e.event?c:u)(e.node)}for(;e.length||n.length;){var g=l();if(s+=t(r.substring(i,g[0].offset)),i=g[0].offset,g===e){o.reverse().forEach(u);do{d(g.splice(0,1)[0]),g=l()}while(g===e&&g.length&&g[0].offset===i);o.reverse().forEach(c)}else"start"===g[0].event?o.push(g[0].node):o.pop(),d(g.splice(0,1)[0])}return s+t(r.substr(i))}});const s="",o=e=>!!e.kind;class l{constructor(e,n){this.buffer="",this.classPrefix=n.classPrefix,e.walk(this)}addText(e){this.buffer+=t(e)}openNode(e){if(!o(e))return;let n=e.kind;e.sublanguage||(n=`${this.classPrefix}${n}`),this.span(n)}closeNode(e){o(e)&&(this.buffer+=s)}value(){return this.buffer}span(e){this.buffer+=``}}class c{constructor(){this.rootNode={children:[]},this.stack=[this.rootNode]}get top(){return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){this.top.children.push(e)}openNode(e){const n={kind:e,children:[]};this.add(n),this.stack.push(n)}closeNode(){if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,n){return"string"==typeof n?e.addText(n):n.children&&(e.openNode(n),n.children.forEach(n=>this._walk(e,n)),e.closeNode(n)),e}static _collapse(e){"string"!=typeof e&&e.children&&(e.children.every(e=>"string"==typeof e)?e.children=[e.children.join("")]:e.children.forEach(e=>{c._collapse(e)}))}}class u extends c{constructor(e){super(),this.options=e}addKeyword(e,n){""!==e&&(this.openNode(n),this.addText(e),this.closeNode())}addText(e){""!==e&&this.add(e)}addSublanguage(e,n){const t=e.root;t.kind=n,t.sublanguage=!0,this.add(t)}toHTML(){return new l(this,this.options).value()}finalize(){return!0}}function d(e){return e?"string"==typeof e?e:e.source:null}const g="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",h={begin:"\\\\[\\s\\S]",relevance:0},f={className:"string",begin:"'",end:"'",illegal:"\\n",contains:[h]},p={className:"string",begin:'"',end:'"',illegal:"\\n",contains:[h]},b={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},m=function(e,n,t={}){var a=r({className:"comment",begin:e,end:n,contains:[]},t);return a.contains.push(b),a.contains.push({className:"doctag",begin:"(?:TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):",relevance:0}),a},v=m("//","$"),x=m("/\\*","\\*/"),E=m("#","$");var _=Object.freeze({__proto__:null,IDENT_RE:"[a-zA-Z]\\w*",UNDERSCORE_IDENT_RE:"[a-zA-Z_]\\w*",NUMBER_RE:"\\b\\d+(\\.\\d+)?",C_NUMBER_RE:g,BINARY_NUMBER_RE:"\\b(0b[01]+)",RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",SHEBANG:(e={})=>{const n=/^#![ ]*\//;return e.binary&&(e.begin=function(...e){return e.map(e=>d(e)).join("")}(n,/.*\b/,e.binary,/\b.*/)),r({className:"meta",begin:n,end:/$/,relevance:0,"on:begin":(e,n)=>{0!==e.index&&n.ignoreMatch()}},e)},BACKSLASH_ESCAPE:h,APOS_STRING_MODE:f,QUOTE_STRING_MODE:p,PHRASAL_WORDS_MODE:b,COMMENT:m,C_LINE_COMMENT_MODE:v,C_BLOCK_COMMENT_MODE:x,HASH_COMMENT_MODE:E,NUMBER_MODE:{className:"number",begin:"\\b\\d+(\\.\\d+)?",relevance:0},C_NUMBER_MODE:{className:"number",begin:g,relevance:0},BINARY_NUMBER_MODE:{className:"number",begin:"\\b(0b[01]+)",relevance:0},CSS_NUMBER_MODE:{className:"number",begin:"\\b\\d+(\\.\\d+)?(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},REGEXP_MODE:{begin:/(?=\/[^/\n]*\/)/,contains:[{className:"regexp",begin:/\//,end:/\/[gimuy]*/,illegal:/\n/,contains:[h,{begin:/\[/,end:/\]/,relevance:0,contains:[h]}]}]},TITLE_MODE:{className:"title",begin:"[a-zA-Z]\\w*",relevance:0},UNDERSCORE_TITLE_MODE:{className:"title",begin:"[a-zA-Z_]\\w*",relevance:0},METHOD_GUARD:{begin:"\\.\\s*[a-zA-Z_]\\w*",relevance:0},END_SAME_AS_BEGIN:function(e){return Object.assign(e,{"on:begin":(e,n)=>{n.data._beginMatch=e[1]},"on:end":(e,n)=>{n.data._beginMatch!==e[1]&&n.ignoreMatch()}})}}),N="of and for in not or if then".split(" ");function w(e,n){return n?+n:function(e){return N.includes(e.toLowerCase())}(e)?0:1}const R=t,y=r,{nodeStream:k,mergeStreams:O}=i,M=Symbol("nomatch");return function(t){var a=[],i={},s={},o=[],l=!0,c=/(^(<[^>]+>|\t|)+|\n)/gm,g="Could not find the language '{}', did you forget to load/include a language module?";const h={disableAutodetect:!0,name:"Plain text",contains:[]};var f={noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:null,__emitter:u};function p(e){return f.noHighlightRe.test(e)}function b(e,n,t,r){var a={code:n,language:e};S("before:highlight",a);var i=a.result?a.result:m(a.language,a.code,t,r);return i.code=a.code,S("after:highlight",i),i}function m(e,t,a,s){var o=t;function c(e,n){var t=E.case_insensitive?n[0].toLowerCase():n[0];return Object.prototype.hasOwnProperty.call(e.keywords,t)&&e.keywords[t]}function u(){null!=y.subLanguage?function(){if(""!==A){var e=null;if("string"==typeof y.subLanguage){if(!i[y.subLanguage])return void O.addText(A);e=m(y.subLanguage,A,!0,k[y.subLanguage]),k[y.subLanguage]=e.top}else e=v(A,y.subLanguage.length?y.subLanguage:null);y.relevance>0&&(I+=e.relevance),O.addSublanguage(e.emitter,e.language)}}():function(){if(!y.keywords)return void O.addText(A);let e=0;y.keywordPatternRe.lastIndex=0;let n=y.keywordPatternRe.exec(A),t="";for(;n;){t+=A.substring(e,n.index);const r=c(y,n);if(r){const[e,a]=r;O.addText(t),t="",I+=a,O.addKeyword(n[0],e)}else t+=n[0];e=y.keywordPatternRe.lastIndex,n=y.keywordPatternRe.exec(A)}t+=A.substr(e),O.addText(t)}(),A=""}function h(e){return e.className&&O.openNode(e.className),y=Object.create(e,{parent:{value:y}})}function p(e){return 0===y.matcher.regexIndex?(A+=e[0],1):(L=!0,0)}var b={};function x(t,r){var i=r&&r[0];if(A+=t,null==i)return u(),0;if("begin"===b.type&&"end"===r.type&&b.index===r.index&&""===i){if(A+=o.slice(r.index,r.index+1),!l){const n=Error("0 width match regex");throw n.languageName=e,n.badRule=b.rule,n}return 1}if(b=r,"begin"===r.type)return function(e){var t=e[0],r=e.rule;const a=new n(r),i=[r.__beforeBegin,r["on:begin"]];for(const n of i)if(n&&(n(e,a),a.ignore))return p(t);return r&&r.endSameAsBegin&&(r.endRe=RegExp(t.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"),"m")),r.skip?A+=t:(r.excludeBegin&&(A+=t),u(),r.returnBegin||r.excludeBegin||(A=t)),h(r),r.returnBegin?0:t.length}(r);if("illegal"===r.type&&!a){const e=Error('Illegal lexeme "'+i+'" for mode "'+(y.className||"")+'"');throw e.mode=y,e}if("end"===r.type){var s=function(e){var t=e[0],r=o.substr(e.index),a=function e(t,r,a){let i=function(e,n){var t=e&&e.exec(n);return t&&0===t.index}(t.endRe,a);if(i){if(t["on:end"]){const e=new n(t);t["on:end"](r,e),e.ignore&&(i=!1)}if(i){for(;t.endsParent&&t.parent;)t=t.parent;return t}}if(t.endsWithParent)return e(t.parent,r,a)}(y,e,r);if(!a)return M;var i=y;i.skip?A+=t:(i.returnEnd||i.excludeEnd||(A+=t),u(),i.excludeEnd&&(A=t));do{y.className&&O.closeNode(),y.skip||y.subLanguage||(I+=y.relevance),y=y.parent}while(y!==a.parent);return a.starts&&(a.endSameAsBegin&&(a.starts.endRe=a.endRe),h(a.starts)),i.returnEnd?0:t.length}(r);if(s!==M)return s}if("illegal"===r.type&&""===i)return 1;if(B>1e5&&B>3*r.index)throw Error("potential infinite loop, way more iterations than matches");return A+=i,i.length}var E=T(e);if(!E)throw console.error(g.replace("{}",e)),Error('Unknown language: "'+e+'"');var _=function(e){function n(n,t){return RegExp(d(n),"m"+(e.case_insensitive?"i":"")+(t?"g":""))}class t{constructor(){this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}addRule(e,n){n.position=this.position++,this.matchIndexes[this.matchAt]=n,this.regexes.push([n,e]),this.matchAt+=function(e){return RegExp(e.toString()+"|").exec("").length-1}(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null);const e=this.regexes.map(e=>e[1]);this.matcherRe=n(function(e,n="|"){for(var t=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./,r=0,a="",i=0;i0&&(a+=n),a+="(";o.length>0;){var l=t.exec(o);if(null==l){a+=o;break}a+=o.substring(0,l.index),o=o.substring(l.index+l[0].length),"\\"===l[0][0]&&l[1]?a+="\\"+(+l[1]+s):(a+=l[0],"("===l[0]&&r++)}a+=")"}return a}(e),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex;const n=this.matcherRe.exec(e);if(!n)return null;const t=n.findIndex((e,n)=>n>0&&void 0!==e),r=this.matchIndexes[t];return n.splice(0,t),Object.assign(n,r)}}class a{constructor(){this.rules=[],this.multiRegexes=[],this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){if(this.multiRegexes[e])return this.multiRegexes[e];const n=new t;return this.rules.slice(e).forEach(([e,t])=>n.addRule(e,t)),n.compile(),this.multiRegexes[e]=n,n}considerAll(){this.regexIndex=0}addRule(e,n){this.rules.push([e,n]),"begin"===n.type&&this.count++}exec(e){const n=this.getMatcher(this.regexIndex);n.lastIndex=this.lastIndex;const t=n.exec(e);return t&&(this.regexIndex+=t.position+1,this.regexIndex===this.count&&(this.regexIndex=0)),t}}function i(e,n){const t=e.input[e.index-1],r=e.input[e.index+e[0].length];"."!==t&&"."!==r||n.ignoreMatch()}if(e.contains&&e.contains.includes("self"))throw Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");return function t(s,o){const l=s;if(s.compiled)return l;s.compiled=!0,s.__beforeBegin=null,s.keywords=s.keywords||s.beginKeywords;let c=null;if("object"==typeof s.keywords&&(c=s.keywords.$pattern,delete s.keywords.$pattern),s.keywords&&(s.keywords=function(e,n){var t={};return"string"==typeof e?r("keyword",e):Object.keys(e).forEach((function(n){r(n,e[n])})),t;function r(e,r){n&&(r=r.toLowerCase()),r.split(" ").forEach((function(n){var r=n.split("|");t[r[0]]=[e,w(r[0],r[1])]}))}}(s.keywords,e.case_insensitive)),s.lexemes&&c)throw Error("ERR: Prefer `keywords.$pattern` to `mode.lexemes`, BOTH are not allowed. (see mode reference) ");return l.keywordPatternRe=n(s.lexemes||c||/\w+/,!0),o&&(s.beginKeywords&&(s.begin="\\b("+s.beginKeywords.split(" ").join("|")+")(?=\\b|\\s)",s.__beforeBegin=i),s.begin||(s.begin=/\B|\b/),l.beginRe=n(s.begin),s.endSameAsBegin&&(s.end=s.begin),s.end||s.endsWithParent||(s.end=/\B|\b/),s.end&&(l.endRe=n(s.end)),l.terminator_end=d(s.end)||"",s.endsWithParent&&o.terminator_end&&(l.terminator_end+=(s.end?"|":"")+o.terminator_end)),s.illegal&&(l.illegalRe=n(s.illegal)),void 0===s.relevance&&(s.relevance=1),s.contains||(s.contains=[]),s.contains=[].concat(...s.contains.map((function(e){return function(e){return e.variants&&!e.cached_variants&&(e.cached_variants=e.variants.map((function(n){return r(e,{variants:null},n)}))),e.cached_variants?e.cached_variants:function e(n){return!!n&&(n.endsWithParent||e(n.starts))}(e)?r(e,{starts:e.starts?r(e.starts):null}):Object.isFrozen(e)?r(e):e}("self"===e?s:e)}))),s.contains.forEach((function(e){t(e,l)})),s.starts&&t(s.starts,o),l.matcher=function(e){const n=new a;return e.contains.forEach(e=>n.addRule(e.begin,{rule:e,type:"begin"})),e.terminator_end&&n.addRule(e.terminator_end,{type:"end"}),e.illegal&&n.addRule(e.illegal,{type:"illegal"}),n}(l),l}(e)}(E),N="",y=s||_,k={},O=new f.__emitter(f);!function(){for(var e=[],n=y;n!==E;n=n.parent)n.className&&e.unshift(n.className);e.forEach(e=>O.openNode(e))}();var A="",I=0,S=0,B=0,L=!1;try{for(y.matcher.considerAll();;){B++,L?L=!1:(y.matcher.lastIndex=S,y.matcher.considerAll());const e=y.matcher.exec(o);if(!e)break;const n=x(o.substring(S,e.index),e);S=e.index+n}return x(o.substr(S)),O.closeAllNodes(),O.finalize(),N=O.toHTML(),{relevance:I,value:N,language:e,illegal:!1,emitter:O,top:y}}catch(n){if(n.message&&n.message.includes("Illegal"))return{illegal:!0,illegalBy:{msg:n.message,context:o.slice(S-100,S+100),mode:n.mode},sofar:N,relevance:0,value:R(o),emitter:O};if(l)return{illegal:!1,relevance:0,value:R(o),emitter:O,language:e,top:y,errorRaised:n};throw n}}function v(e,n){n=n||f.languages||Object.keys(i);var t=function(e){const n={relevance:0,emitter:new f.__emitter(f),value:R(e),illegal:!1,top:h};return n.emitter.addText(e),n}(e),r=t;return n.filter(T).filter(I).forEach((function(n){var a=m(n,e,!1);a.language=n,a.relevance>r.relevance&&(r=a),a.relevance>t.relevance&&(r=t,t=a)})),r.language&&(t.second_best=r),t}function x(e){return f.tabReplace||f.useBR?e.replace(c,e=>"\n"===e?f.useBR?"
":e:f.tabReplace?e.replace(/\t/g,f.tabReplace):e):e}function E(e){let n=null;const t=function(e){var n=e.className+" ";n+=e.parentNode?e.parentNode.className:"";const t=f.languageDetectRe.exec(n);if(t){var r=T(t[1]);return r||(console.warn(g.replace("{}",t[1])),console.warn("Falling back to no-highlight mode for this block.",e)),r?t[1]:"no-highlight"}return n.split(/\s+/).find(e=>p(e)||T(e))}(e);if(p(t))return;S("before:highlightBlock",{block:e,language:t}),f.useBR?(n=document.createElement("div")).innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n"):n=e;const r=n.textContent,a=t?b(t,r,!0):v(r),i=k(n);if(i.length){const e=document.createElement("div");e.innerHTML=a.value,a.value=O(i,k(e),r)}a.value=x(a.value),S("after:highlightBlock",{block:e,result:a}),e.innerHTML=a.value,e.className=function(e,n,t){var r=n?s[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),e.includes(r)||a.push(r),a.join(" ").trim()}(e.className,t,a.language),e.result={language:a.language,re:a.relevance,relavance:a.relevance},a.second_best&&(e.second_best={language:a.second_best.language,re:a.second_best.relevance,relavance:a.second_best.relevance})}const N=()=>{if(!N.called){N.called=!0;var e=document.querySelectorAll("pre code");a.forEach.call(e,E)}};function T(e){return e=(e||"").toLowerCase(),i[e]||i[s[e]]}function A(e,{languageName:n}){"string"==typeof e&&(e=[e]),e.forEach(e=>{s[e]=n})}function I(e){var n=T(e);return n&&!n.disableAutodetect}function S(e,n){var t=e;o.forEach((function(e){e[t]&&e[t](n)}))}Object.assign(t,{highlight:b,highlightAuto:v,fixMarkup:x,highlightBlock:E,configure:function(e){f=y(f,e)},initHighlighting:N,initHighlightingOnLoad:function(){window.addEventListener("DOMContentLoaded",N,!1)},registerLanguage:function(e,n){var r=null;try{r=n(t)}catch(n){if(console.error("Language definition for '{}' could not be registered.".replace("{}",e)),!l)throw n;console.error(n),r=h}r.name||(r.name=e),i[e]=r,r.rawDefinition=n.bind(null,t),r.aliases&&A(r.aliases,{languageName:e})},listLanguages:function(){return Object.keys(i)},getLanguage:T,registerAliases:A,requireLanguage:function(e){var n=T(e);if(n)return n;throw Error("The '{}' language is required, but not loaded.".replace("{}",e))},autoDetection:I,inherit:y,addPlugin:function(e){o.push(e)}}),t.debugMode=function(){l=!1},t.safeMode=function(){l=!0},t.versionString="10.1.1";for(const n in _)"object"==typeof _[n]&&e(_[n]);return Object.assign(t,_),t}({})}();"object"==typeof exports&&"undefined"!=typeof module&&(module.exports=hljs);hljs.registerLanguage("php",function(){"use strict";return function(e){var r={begin:"\\$+[a-zA-Z_-ÿ][a-zA-Z0-9_-ÿ]*"},t={className:"meta",variants:[{begin:/<\?php/,relevance:10},{begin:/<\?[=]?/},{begin:/\?>/}]},a={className:"string",contains:[e.BACKSLASH_ESCAPE,t],variants:[{begin:'b"',end:'"'},{begin:"b'",end:"'"},e.inherit(e.APOS_STRING_MODE,{illegal:null}),e.inherit(e.QUOTE_STRING_MODE,{illegal:null})]},n={variants:[e.BINARY_NUMBER_MODE,e.C_NUMBER_MODE]},i={keyword:"__CLASS__ __DIR__ __FILE__ __FUNCTION__ __LINE__ __METHOD__ __NAMESPACE__ __TRAIT__ die echo exit include include_once print require require_once array abstract and as binary bool boolean break callable case catch class clone const continue declare default do double else elseif empty enddeclare endfor endforeach endif endswitch endwhile eval extends final finally float for foreach from global goto if implements instanceof insteadof int integer interface isset iterable list new object or private protected public real return string switch throw trait try unset use var void while xor yield",literal:"false null true",built_in:"Error|0 AppendIterator ArgumentCountError ArithmeticError ArrayIterator ArrayObject AssertionError BadFunctionCallException BadMethodCallException CachingIterator CallbackFilterIterator CompileError Countable DirectoryIterator DivisionByZeroError DomainException EmptyIterator ErrorException Exception FilesystemIterator FilterIterator GlobIterator InfiniteIterator InvalidArgumentException IteratorIterator LengthException LimitIterator LogicException MultipleIterator NoRewindIterator OutOfBoundsException OutOfRangeException OuterIterator OverflowException ParentIterator ParseError RangeException RecursiveArrayIterator RecursiveCachingIterator RecursiveCallbackFilterIterator RecursiveDirectoryIterator RecursiveFilterIterator RecursiveIterator RecursiveIteratorIterator RecursiveRegexIterator RecursiveTreeIterator RegexIterator RuntimeException SeekableIterator SplDoublyLinkedList SplFileInfo SplFileObject SplFixedArray SplHeap SplMaxHeap SplMinHeap SplObjectStorage SplObserver SplObserver SplPriorityQueue SplQueue SplStack SplSubject SplSubject SplTempFileObject TypeError UnderflowException UnexpectedValueException ArrayAccess Closure Generator Iterator IteratorAggregate Serializable Throwable Traversable WeakReference Directory __PHP_Incomplete_Class parent php_user_filter self static stdClass"};return{aliases:["php","php3","php4","php5","php6","php7"],case_insensitive:!0,keywords:i,contains:[e.HASH_COMMENT_MODE,e.COMMENT("//","$",{contains:[t]}),e.COMMENT("/\\*","\\*/",{contains:[{className:"doctag",begin:"@[A-Za-z]+"}]}),e.COMMENT("__halt_compiler.+?;",!1,{endsWithParent:!0,keywords:"__halt_compiler"}),{className:"string",begin:/<<<['"]?\w+['"]?$/,end:/^\w+;?$/,contains:[e.BACKSLASH_ESCAPE,{className:"subst",variants:[{begin:/\$\w+/},{begin:/\{\$/,end:/\}/}]}]},t,{className:"keyword",begin:/\$this\b/},r,{begin:/(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/},{className:"function",beginKeywords:"fn function",end:/[;{]/,excludeEnd:!0,illegal:"[$%\\[]",contains:[e.UNDERSCORE_TITLE_MODE,{className:"params",begin:"\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0,keywords:i,contains:["self",r,e.C_BLOCK_COMMENT_MODE,a,n]}]},{className:"class",beginKeywords:"class interface",end:"{",excludeEnd:!0,illegal:/[:\(\$"]/,contains:[{beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"namespace",end:";",illegal:/[\.']/,contains:[e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"use",end:";",contains:[e.UNDERSCORE_TITLE_MODE]},{begin:"=>"},a,n]}}}());hljs.registerLanguage("nginx",function(){"use strict";return function(e){var n={className:"variable",variants:[{begin:/\$\d+/},{begin:/\$\{/,end:/}/},{begin:"[\\$\\@]"+e.UNDERSCORE_IDENT_RE}]},a={endsWithParent:!0,keywords:{$pattern:"[a-z/_]+",literal:"on off yes no true false none blocked debug info notice warn error crit select break last permanent redirect kqueue rtsig epoll poll /dev/poll"},relevance:0,illegal:"=>",contains:[e.HASH_COMMENT_MODE,{className:"string",contains:[e.BACKSLASH_ESCAPE,n],variants:[{begin:/"/,end:/"/},{begin:/'/,end:/'/}]},{begin:"([a-z]+):/",end:"\\s",endsWithParent:!0,excludeEnd:!0,contains:[n]},{className:"regexp",contains:[e.BACKSLASH_ESCAPE,n],variants:[{begin:"\\s\\^",end:"\\s|{|;",returnEnd:!0},{begin:"~\\*?\\s+",end:"\\s|{|;",returnEnd:!0},{begin:"\\*(\\.[a-z\\-]+)+"},{begin:"([a-z\\-]+\\.)+\\*"}]},{className:"number",begin:"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b"},{className:"number",begin:"\\b\\d+[kKmMgGdshdwy]*\\b",relevance:0},n]};return{name:"Nginx config",aliases:["nginxconf"],contains:[e.HASH_COMMENT_MODE,{begin:e.UNDERSCORE_IDENT_RE+"\\s+{",returnBegin:!0,end:"{",contains:[{className:"section",begin:e.UNDERSCORE_IDENT_RE}],relevance:0},{begin:e.UNDERSCORE_IDENT_RE+"\\s",end:";|{",returnBegin:!0,contains:[{className:"attribute",begin:e.UNDERSCORE_IDENT_RE,starts:a}],relevance:0}],illegal:"[^\\s\\}]"}}}());hljs.registerLanguage("csharp",function(){"use strict";return function(e){var n={keyword:"abstract as base bool break byte case catch char checked const continue decimal default delegate do double enum event explicit extern finally fixed float for foreach goto if implicit in int interface internal is lock long object operator out override params private protected public readonly ref sbyte sealed short sizeof stackalloc static string struct switch this try typeof uint ulong unchecked unsafe ushort using virtual void volatile while add alias ascending async await by descending dynamic equals from get global group into join let nameof on orderby partial remove select set value var when where yield",literal:"null false true"},i=e.inherit(e.TITLE_MODE,{begin:"[a-zA-Z](\\.?\\w)*"}),a={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},s={className:"string",begin:'@"',end:'"',contains:[{begin:'""'}]},t=e.inherit(s,{illegal:/\n/}),l={className:"subst",begin:"{",end:"}",keywords:n},r=e.inherit(l,{illegal:/\n/}),c={className:"string",begin:/\$"/,end:'"',illegal:/\n/,contains:[{begin:"{{"},{begin:"}}"},e.BACKSLASH_ESCAPE,r]},o={className:"string",begin:/\$@"/,end:'"',contains:[{begin:"{{"},{begin:"}}"},{begin:'""'},l]},g=e.inherit(o,{illegal:/\n/,contains:[{begin:"{{"},{begin:"}}"},{begin:'""'},r]});l.contains=[o,c,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.C_BLOCK_COMMENT_MODE],r.contains=[g,c,t,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.inherit(e.C_BLOCK_COMMENT_MODE,{illegal:/\n/})];var d={variants:[o,c,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},E={begin:"<",end:">",contains:[{beginKeywords:"in out"},i]},_=e.IDENT_RE+"(<"+e.IDENT_RE+"(\\s*,\\s*"+e.IDENT_RE+")*>)?(\\[\\])?",b={begin:"@"+e.IDENT_RE,relevance:0};return{name:"C#",aliases:["cs","c#"],keywords:n,illegal:/::/,contains:[e.COMMENT("///","$",{returnBegin:!0,contains:[{className:"doctag",variants:[{begin:"///",relevance:0},{begin:"\x3c!--|--\x3e"},{begin:""}]}]}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"meta",begin:"#",end:"$",keywords:{"meta-keyword":"if else elif endif define undef warning error line region endregion pragma checksum"}},d,a,{beginKeywords:"class interface",end:/[{;=]/,illegal:/[^\s:,]/,contains:[{beginKeywords:"where class"},i,E,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{beginKeywords:"namespace",end:/[{;=]/,illegal:/[^\s:]/,contains:[i,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"meta",begin:"^\\s*\\[",excludeBegin:!0,end:"\\]",excludeEnd:!0,contains:[{className:"meta-string",begin:/"/,end:/"/}]},{beginKeywords:"new return throw await else",relevance:0},{className:"function",begin:"("+_+"\\s+)+"+e.IDENT_RE+"\\s*(\\<.+\\>)?\\s*\\(",returnBegin:!0,end:/\s*[{;=]/,excludeEnd:!0,keywords:n,contains:[{begin:e.IDENT_RE+"\\s*(\\<.+\\>)?\\s*\\(",returnBegin:!0,contains:[e.TITLE_MODE,E],relevance:0},{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:n,relevance:0,contains:[d,a,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},b]}}}());hljs.registerLanguage("perl",function(){"use strict";return function(e){var n={$pattern:/[\w.]+/,keyword:"getpwent getservent quotemeta msgrcv scalar kill dbmclose undef lc ma syswrite tr send umask sysopen shmwrite vec qx utime local oct semctl localtime readpipe do return format read sprintf dbmopen pop getpgrp not getpwnam rewinddir qq fileno qw endprotoent wait sethostent bless s|0 opendir continue each sleep endgrent shutdown dump chomp connect getsockname die socketpair close flock exists index shmget sub for endpwent redo lstat msgctl setpgrp abs exit select print ref gethostbyaddr unshift fcntl syscall goto getnetbyaddr join gmtime symlink semget splice x|0 getpeername recv log setsockopt cos last reverse gethostbyname getgrnam study formline endhostent times chop length gethostent getnetent pack getprotoent getservbyname rand mkdir pos chmod y|0 substr endnetent printf next open msgsnd readdir use unlink getsockopt getpriority rindex wantarray hex system getservbyport endservent int chr untie rmdir prototype tell listen fork shmread ucfirst setprotoent else sysseek link getgrgid shmctl waitpid unpack getnetbyname reset chdir grep split require caller lcfirst until warn while values shift telldir getpwuid my getprotobynumber delete and sort uc defined srand accept package seekdir getprotobyname semop our rename seek if q|0 chroot sysread setpwent no crypt getc chown sqrt write setnetent setpriority foreach tie sin msgget map stat getlogin unless elsif truncate exec keys glob tied closedir ioctl socket readlink eval xor readline binmode setservent eof ord bind alarm pipe atan2 getgrent exp time push setgrent gt lt or ne m|0 break given say state when"},t={className:"subst",begin:"[$@]\\{",end:"\\}",keywords:n},s={begin:"->{",end:"}"},r={variants:[{begin:/\$\d/},{begin:/[\$%@](\^\w\b|#\w+(::\w+)*|{\w+}|\w+(::\w*)*)/},{begin:/[\$%@][^\s\w{]/,relevance:0}]},i=[e.BACKSLASH_ESCAPE,t,r],a=[r,e.HASH_COMMENT_MODE,e.COMMENT("^\\=\\w","\\=cut",{endsWithParent:!0}),s,{className:"string",contains:i,variants:[{begin:"q[qwxr]?\\s*\\(",end:"\\)",relevance:5},{begin:"q[qwxr]?\\s*\\[",end:"\\]",relevance:5},{begin:"q[qwxr]?\\s*\\{",end:"\\}",relevance:5},{begin:"q[qwxr]?\\s*\\|",end:"\\|",relevance:5},{begin:"q[qwxr]?\\s*\\<",end:"\\>",relevance:5},{begin:"qw\\s+q",end:"q",relevance:5},{begin:"'",end:"'",contains:[e.BACKSLASH_ESCAPE]},{begin:'"',end:'"'},{begin:"`",end:"`",contains:[e.BACKSLASH_ESCAPE]},{begin:"{\\w+}",contains:[],relevance:0},{begin:"-?\\w+\\s*\\=\\>",contains:[],relevance:0}]},{className:"number",begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{begin:"(\\/\\/|"+e.RE_STARTERS_RE+"|\\b(split|return|print|reverse|grep)\\b)\\s*",keywords:"split return print reverse grep",relevance:0,contains:[e.HASH_COMMENT_MODE,{className:"regexp",begin:"(s|tr|y)/(\\\\.|[^/])*/(\\\\.|[^/])*/[a-z]*",relevance:10},{className:"regexp",begin:"(m|qr)?/",end:"/[a-z]*",contains:[e.BACKSLASH_ESCAPE],relevance:0}]},{className:"function",beginKeywords:"sub",end:"(\\s*\\(.*?\\))?[;{]",excludeEnd:!0,relevance:5,contains:[e.TITLE_MODE]},{begin:"-\\w\\b",relevance:0},{begin:"^__DATA__$",end:"^__END__$",subLanguage:"mojolicious",contains:[{begin:"^@@.*",end:"$",className:"comment"}]}];return t.contains=a,s.contains=a,{name:"Perl",aliases:["pl","pm"],keywords:n,contains:a}}}());hljs.registerLanguage("swift",function(){"use strict";return function(e){var i={keyword:"#available #colorLiteral #column #else #elseif #endif #file #fileLiteral #function #if #imageLiteral #line #selector #sourceLocation _ __COLUMN__ __FILE__ __FUNCTION__ __LINE__ Any as as! as? associatedtype associativity break case catch class continue convenience default defer deinit didSet do dynamic dynamicType else enum extension fallthrough false fileprivate final for func get guard if import in indirect infix init inout internal is lazy left let mutating nil none nonmutating open operator optional override postfix precedence prefix private protocol Protocol public repeat required rethrows return right self Self set static struct subscript super switch throw throws true try try! try? Type typealias unowned var weak where while willSet",literal:"true false nil",built_in:"abs advance alignof alignofValue anyGenerator assert assertionFailure bridgeFromObjectiveC bridgeFromObjectiveCUnconditional bridgeToObjectiveC bridgeToObjectiveCUnconditional c compactMap contains count countElements countLeadingZeros debugPrint debugPrintln distance dropFirst dropLast dump encodeBitsAsWords enumerate equal fatalError filter find getBridgedObjectiveCType getVaList indices insertionSort isBridgedToObjectiveC isBridgedVerbatimToObjectiveC isUniquelyReferenced isUniquelyReferencedNonObjC join lazy lexicographicalCompare map max maxElement min minElement numericCast overlaps partition posix precondition preconditionFailure print println quickSort readLine reduce reflect reinterpretCast reverse roundUpToAlignment sizeof sizeofValue sort split startsWith stride strideof strideofValue swap toString transcode underestimateCount unsafeAddressOf unsafeBitCast unsafeDowncast unsafeUnwrap unsafeReflect withExtendedLifetime withObjectAtPlusZero withUnsafePointer withUnsafePointerToObject withUnsafeMutablePointer withUnsafeMutablePointers withUnsafePointer withUnsafePointers withVaList zip"},n=e.COMMENT("/\\*","\\*/",{contains:["self"]}),t={className:"subst",begin:/\\\(/,end:"\\)",keywords:i,contains:[]},a={className:"string",contains:[e.BACKSLASH_ESCAPE,t],variants:[{begin:/"""/,end:/"""/},{begin:/"/,end:/"/}]},r={className:"number",begin:"\\b([\\d_]+(\\.[\\deE_]+)?|0x[a-fA-F0-9_]+(\\.[a-fA-F0-9p_]+)?|0b[01_]+|0o[0-7_]+)\\b",relevance:0};return t.contains=[r],{name:"Swift",keywords:i,contains:[a,e.C_LINE_COMMENT_MODE,n,{className:"type",begin:"\\b[A-Z][\\wÀ-ʸ']*[!?]"},{className:"type",begin:"\\b[A-Z][\\wÀ-ʸ']*",relevance:0},r,{className:"function",beginKeywords:"func",end:"{",excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/[A-Za-z$_][0-9A-Za-z$_]*/}),{begin://},{className:"params",begin:/\(/,end:/\)/,endsParent:!0,keywords:i,contains:["self",r,a,e.C_BLOCK_COMMENT_MODE,{begin:":"}],illegal:/["']/}],illegal:/\[|%/},{className:"class",beginKeywords:"struct protocol class extension enum",keywords:i,end:"\\{",excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/[A-Za-z$_][\u00C0-\u02B80-9A-Za-z$_]*/})]},{className:"meta",begin:"(@discardableResult|@warn_unused_result|@exported|@lazy|@noescape|@NSCopying|@NSManaged|@objc|@objcMembers|@convention|@required|@noreturn|@IBAction|@IBDesignable|@IBInspectable|@IBOutlet|@infix|@prefix|@postfix|@autoclosure|@testable|@available|@nonobjc|@NSApplicationMain|@UIApplicationMain|@dynamicMemberLookup|@propertyWrapper)\\b"},{beginKeywords:"import",end:/$/,contains:[e.C_LINE_COMMENT_MODE,n]}]}}}());hljs.registerLanguage("makefile",function(){"use strict";return function(e){var i={className:"variable",variants:[{begin:"\\$\\("+e.UNDERSCORE_IDENT_RE+"\\)",contains:[e.BACKSLASH_ESCAPE]},{begin:/\$[@%`]+/}]}]}]};return{name:"HTML, XML",aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],case_insensitive:!0,contains:[{className:"meta",begin:"",relevance:10,contains:[a,i,t,s,{begin:"\\[",end:"\\]",contains:[{className:"meta",begin:"",contains:[a,s,i,t]}]}]},e.COMMENT("\x3c!--","--\x3e",{relevance:10}),{begin:"<\\!\\[CDATA\\[",end:"\\]\\]>",relevance:10},n,{className:"meta",begin:/<\?xml/,end:/\?>/,relevance:10},{className:"tag",begin:")",end:">",keywords:{name:"style"},contains:[c],starts:{end:"",returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag",begin:")",end:">",keywords:{name:"script"},contains:[c],starts:{end:"<\/script>",returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{className:"tag",begin:"",contains:[{className:"name",begin:/[^\/><\s]+/,relevance:0},c]}]}}}());hljs.registerLanguage("bash",function(){"use strict";return function(e){const s={};Object.assign(s,{className:"variable",variants:[{begin:/\$[\w\d#@][\w\d_]*/},{begin:/\$\{/,end:/\}/,contains:[{begin:/:-/,contains:[s]}]}]});const t={className:"subst",begin:/\$\(/,end:/\)/,contains:[e.BACKSLASH_ESCAPE]},n={className:"string",begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,s,t]};t.contains.push(n);const a={begin:/\$\(\(/,end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},e.NUMBER_MODE,s]},i=e.SHEBANG({binary:"(fish|bash|zsh|sh|csh|ksh|tcsh|dash|scsh)",relevance:10}),c={className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0};return{name:"Bash",aliases:["sh","zsh"],keywords:{$pattern:/\b-?[a-z\._]+\b/,keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",_:"-ne -eq -lt -gt -f -d -e -s -l -a"},contains:[i,e.SHEBANG(),c,a,e.HASH_COMMENT_MODE,n,{className:"",begin:/\\"/},{className:"string",begin:/'/,end:/'/},s]}}}());hljs.registerLanguage("c-like",function(){"use strict";return function(e){function t(e){return"(?:"+e+")?"}var n="(decltype\\(auto\\)|"+t("[a-zA-Z_]\\w*::")+"[a-zA-Z_]\\w*"+t("<.*?>")+")",r={className:"keyword",begin:"\\b[a-z\\d_]*_t\\b"},a={className:"string",variants:[{begin:'(u8?|U|L)?"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{begin:"(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)",end:"'",illegal:"."},e.END_SAME_AS_BEGIN({begin:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\(/,end:/\)([^()\\ ]{0,16})"/})]},i={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},s={className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{"meta-keyword":"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include"},contains:[{begin:/\\\n/,relevance:0},e.inherit(a,{className:"meta-string"}),{className:"meta-string",begin:/<.*?>/,end:/$/,illegal:"\\n"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},o={className:"title",begin:t("[a-zA-Z_]\\w*::")+e.IDENT_RE,relevance:0},c=t("[a-zA-Z_]\\w*::")+e.IDENT_RE+"\\s*\\(",l={keyword:"int float while private char char8_t char16_t char32_t catch import module export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using asm case typeid wchar_t short reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignas alignof constexpr consteval constinit decltype concept co_await co_return co_yield requires noexcept static_assert thread_local restrict final override atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong new throw return and and_eq bitand bitor compl not not_eq or or_eq xor xor_eq",built_in:"std string wstring cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set pair bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap priority_queue make_pair array shared_ptr abort terminate abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf future isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr _Bool complex _Complex imaginary _Imaginary",literal:"true false nullptr NULL"},d=[r,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,i,a],_={variants:[{begin:/=/,end:/;/},{begin:/\(/,end:/\)/},{beginKeywords:"new throw return else",end:/;/}],keywords:l,contains:d.concat([{begin:/\(/,end:/\)/,keywords:l,contains:d.concat(["self"]),relevance:0}]),relevance:0},u={className:"function",begin:"("+n+"[\\*&\\s]+)+"+c,returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:l,illegal:/[^\w\s\*&:<>]/,contains:[{begin:"decltype\\(auto\\)",keywords:l,relevance:0},{begin:c,returnBegin:!0,contains:[o],relevance:0},{className:"params",begin:/\(/,end:/\)/,keywords:l,relevance:0,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,i,r,{begin:/\(/,end:/\)/,keywords:l,relevance:0,contains:["self",e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,i,r]}]},r,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,s]};return{aliases:["c","cc","h","c++","h++","hpp","hh","hxx","cxx"],keywords:l,disableAutodetect:!0,illegal:"",keywords:l,contains:["self",r]},{begin:e.IDENT_RE+"::",keywords:l},{className:"class",beginKeywords:"class struct",end:/[{;:]/,contains:[{begin://,contains:["self"]},e.TITLE_MODE]}]),exports:{preprocessor:s,strings:a,keywords:l}}}}());hljs.registerLanguage("coffeescript",function(){"use strict";const e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],n=["true","false","null","undefined","NaN","Infinity"],a=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);return function(r){var t={keyword:e.concat(["then","unless","until","loop","by","when","and","or","is","isnt","not"]).filter((e=>n=>!e.includes(n))(["var","const","let","function","static"])).join(" "),literal:n.concat(["yes","no","on","off"]).join(" "),built_in:a.concat(["npm","print"]).join(" ")},i="[A-Za-z$_][0-9A-Za-z$_]*",s={className:"subst",begin:/#\{/,end:/}/,keywords:t},o=[r.BINARY_NUMBER_MODE,r.inherit(r.C_NUMBER_MODE,{starts:{end:"(\\s*/)?",relevance:0}}),{className:"string",variants:[{begin:/'''/,end:/'''/,contains:[r.BACKSLASH_ESCAPE]},{begin:/'/,end:/'/,contains:[r.BACKSLASH_ESCAPE]},{begin:/"""/,end:/"""/,contains:[r.BACKSLASH_ESCAPE,s]},{begin:/"/,end:/"/,contains:[r.BACKSLASH_ESCAPE,s]}]},{className:"regexp",variants:[{begin:"///",end:"///",contains:[s,r.HASH_COMMENT_MODE]},{begin:"//[gim]{0,3}(?=\\W)",relevance:0},{begin:/\/(?![ *]).*?(?![\\]).\/[gim]{0,3}(?=\W)/}]},{begin:"@"+i},{subLanguage:"javascript",excludeBegin:!0,excludeEnd:!0,variants:[{begin:"```",end:"```"},{begin:"`",end:"`"}]}];s.contains=o;var c=r.inherit(r.TITLE_MODE,{begin:i}),l={className:"params",begin:"\\([^\\(]",returnBegin:!0,contains:[{begin:/\(/,end:/\)/,keywords:t,contains:["self"].concat(o)}]};return{name:"CoffeeScript",aliases:["coffee","cson","iced"],keywords:t,illegal:/\/\*/,contains:o.concat([r.COMMENT("###","###"),r.HASH_COMMENT_MODE,{className:"function",begin:"^\\s*"+i+"\\s*=\\s*(\\(.*\\))?\\s*\\B[-=]>",end:"[-=]>",returnBegin:!0,contains:[c,l]},{begin:/[:\(,=]\s*/,relevance:0,contains:[{className:"function",begin:"(\\(.*\\))?\\s*\\B[-=]>",end:"[-=]>",returnBegin:!0,contains:[l]}]},{className:"class",beginKeywords:"class",end:"$",illegal:/[:="\[\]]/,contains:[{beginKeywords:"extends",endsWithParent:!0,illegal:/[:="\[\]]/,contains:[c]},c]},{begin:i+":",end:":",returnBegin:!0,returnEnd:!0,relevance:0}])}}}());hljs.registerLanguage("ruby",function(){"use strict";return function(e){var n="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",a={keyword:"and then defined module in return redo if BEGIN retry end for self when next until do begin unless END rescue else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor",literal:"true false nil"},s={className:"doctag",begin:"@[A-Za-z]+"},i={begin:"#<",end:">"},r=[e.COMMENT("#","$",{contains:[s]}),e.COMMENT("^\\=begin","^\\=end",{contains:[s],relevance:10}),e.COMMENT("^__END__","\\n$")],c={className:"subst",begin:"#\\{",end:"}",keywords:a},t={className:"string",contains:[e.BACKSLASH_ESCAPE,c],variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/`/,end:/`/},{begin:"%[qQwWx]?\\(",end:"\\)"},{begin:"%[qQwWx]?\\[",end:"\\]"},{begin:"%[qQwWx]?{",end:"}"},{begin:"%[qQwWx]?<",end:">"},{begin:"%[qQwWx]?/",end:"/"},{begin:"%[qQwWx]?%",end:"%"},{begin:"%[qQwWx]?-",end:"-"},{begin:"%[qQwWx]?\\|",end:"\\|"},{begin:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/},{begin:/<<[-~]?'?(\w+)(?:.|\n)*?\n\s*\1\b/,returnBegin:!0,contains:[{begin:/<<[-~]?'?/},e.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/,contains:[e.BACKSLASH_ESCAPE,c]})]}]},b={className:"params",begin:"\\(",end:"\\)",endsParent:!0,keywords:a},d=[t,i,{className:"class",beginKeywords:"class module",end:"$|;",illegal:/=/,contains:[e.inherit(e.TITLE_MODE,{begin:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{begin:"<\\s*",contains:[{begin:"("+e.IDENT_RE+"::)?"+e.IDENT_RE}]}].concat(r)},{className:"function",beginKeywords:"def",end:"$|;",contains:[e.inherit(e.TITLE_MODE,{begin:n}),b].concat(r)},{begin:e.IDENT_RE+"::"},{className:"symbol",begin:e.UNDERSCORE_IDENT_RE+"(\\!|\\?)?:",relevance:0},{className:"symbol",begin:":(?!\\s)",contains:[t,{begin:n}],relevance:0},{className:"number",begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{begin:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{className:"params",begin:/\|/,end:/\|/,keywords:a},{begin:"("+e.RE_STARTERS_RE+"|unless)\\s*",keywords:"unless",contains:[i,{className:"regexp",contains:[e.BACKSLASH_ESCAPE,c],illegal:/\n/,variants:[{begin:"/",end:"/[a-z]*"},{begin:"%r{",end:"}[a-z]*"},{begin:"%r\\(",end:"\\)[a-z]*"},{begin:"%r!",end:"![a-z]*"},{begin:"%r\\[",end:"\\][a-z]*"}]}].concat(r),relevance:0}].concat(r);c.contains=d,b.contains=d;var g=[{begin:/^\s*=>/,starts:{end:"$",contains:d}},{className:"meta",begin:"^([>?]>|[\\w#]+\\(\\w+\\):\\d+:\\d+>|(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>)",starts:{end:"$",contains:d}}];return{name:"Ruby",aliases:["rb","gemspec","podspec","thor","irb"],keywords:a,illegal:/\/\*/,contains:r.concat(g).concat(d)}}}());hljs.registerLanguage("yaml",function(){"use strict";return function(e){var n="true false yes no null",a="[\\w#;/?:@&=+$,.~*\\'()[\\]]+",s={className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/\S+/}],contains:[e.BACKSLASH_ESCAPE,{className:"template-variable",variants:[{begin:"{{",end:"}}"},{begin:"%{",end:"}"}]}]},i=e.inherit(s,{variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/[^\s,{}[\]]+/}]}),l={end:",",endsWithParent:!0,excludeEnd:!0,contains:[],keywords:n,relevance:0},t={begin:"{",end:"}",contains:[l],illegal:"\\n",relevance:0},g={begin:"\\[",end:"\\]",contains:[l],illegal:"\\n",relevance:0},b=[{className:"attr",variants:[{begin:"\\w[\\w :\\/.-]*:(?=[ \t]|$)"},{begin:'"\\w[\\w :\\/.-]*":(?=[ \t]|$)'},{begin:"'\\w[\\w :\\/.-]*':(?=[ \t]|$)"}]},{className:"meta",begin:"^---s*$",relevance:10},{className:"string",begin:"[\\|>]([0-9]?[+-])?[ ]*\\n( *)[\\S ]+\\n(\\2[\\S ]+\\n?)*"},{begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:"!\\w+!"+a},{className:"type",begin:"!<"+a+">"},{className:"type",begin:"!"+a},{className:"type",begin:"!!"+a},{className:"meta",begin:"&"+e.UNDERSCORE_IDENT_RE+"$"},{className:"meta",begin:"\\*"+e.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"\\-(?=[ ]|$)",relevance:0},e.HASH_COMMENT_MODE,{beginKeywords:n,keywords:{literal:n}},{className:"number",begin:"\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\.[0-9]*)?([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\b"},{className:"number",begin:e.C_NUMBER_RE+"\\b"},t,g,s],c=[...b];return c.pop(),c.push(i),l.contains=c,{name:"YAML",case_insensitive:!0,aliases:["yml","YAML"],contains:b}}}());hljs.registerLanguage("d",function(){"use strict";return function(e){var a={$pattern:e.UNDERSCORE_IDENT_RE,keyword:"abstract alias align asm assert auto body break byte case cast catch class const continue debug default delete deprecated do else enum export extern final finally for foreach foreach_reverse|10 goto if immutable import in inout int interface invariant is lazy macro mixin module new nothrow out override package pragma private protected public pure ref return scope shared static struct super switch synchronized template this throw try typedef typeid typeof union unittest version void volatile while with __FILE__ __LINE__ __gshared|10 __thread __traits __DATE__ __EOF__ __TIME__ __TIMESTAMP__ __VENDOR__ __VERSION__",built_in:"bool cdouble cent cfloat char creal dchar delegate double dstring float function idouble ifloat ireal long real short string ubyte ucent uint ulong ushort wchar wstring",literal:"false null true"},d="((0|[1-9][\\d_]*)|0[bB][01_]+|0[xX]([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*))",n="\\\\(['\"\\?\\\\abfnrtv]|u[\\dA-Fa-f]{4}|[0-7]{1,3}|x[\\dA-Fa-f]{2}|U[\\dA-Fa-f]{8})|&[a-zA-Z\\d]{2,};",t={className:"number",begin:"\\b"+d+"(L|u|U|Lu|LU|uL|UL)?",relevance:0},_={className:"number",begin:"\\b(((0[xX](([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*)\\.([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*)|\\.?([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*))[pP][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d))|((0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)(\\.\\d*|([eE][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)))|\\d+\\.(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)|\\.(0|[1-9][\\d_]*)([eE][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d))?))([fF]|L|i|[fF]i|Li)?|"+d+"(i|[fF]i|Li))",relevance:0},r={className:"string",begin:"'("+n+"|.)",end:"'",illegal:"."},i={className:"string",begin:'"',contains:[{begin:n,relevance:0}],end:'"[cwd]?'},s=e.COMMENT("\\/\\+","\\+\\/",{contains:["self"],relevance:10});return{name:"D",keywords:a,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,s,{className:"string",begin:'x"[\\da-fA-F\\s\\n\\r]*"[cwd]?',relevance:10},i,{className:"string",begin:'[rq]"',end:'"[cwd]?',relevance:5},{className:"string",begin:"`",end:"`[cwd]?"},{className:"string",begin:'q"\\{',end:'\\}"'},_,t,r,{className:"meta",begin:"^#!",end:"$",relevance:5},{className:"meta",begin:"#(line)",end:"$",relevance:5},{className:"keyword",begin:"@[a-zA-Z_][a-zA-Z_\\d]*"}]}}}());hljs.registerLanguage("properties",function(){"use strict";return function(e){var n="[ \\t\\f]*",t="("+n+"[:=]"+n+"|[ \\t\\f]+)",a="([^\\\\:= \\t\\f\\n]|\\\\.)+",s={end:t,relevance:0,starts:{className:"string",end:/$/,relevance:0,contains:[{begin:"\\\\\\n"}]}};return{name:".properties",case_insensitive:!0,illegal:/\S/,contains:[e.COMMENT("^\\s*[!#]","$"),{begin:"([^\\\\\\W:= \\t\\f\\n]|\\\\.)+"+t,returnBegin:!0,contains:[{className:"attr",begin:"([^\\\\\\W:= \\t\\f\\n]|\\\\.)+",endsParent:!0,relevance:0}],starts:s},{begin:a+t,returnBegin:!0,relevance:0,contains:[{className:"meta",begin:a,endsParent:!0,relevance:0}],starts:s},{className:"attr",relevance:0,begin:a+n+"$"}]}}}());hljs.registerLanguage("http",function(){"use strict";return function(e){var n="HTTP/[0-9\\.]+";return{name:"HTTP",aliases:["https"],illegal:"\\S",contains:[{begin:"^"+n,end:"$",contains:[{className:"number",begin:"\\b\\d{3}\\b"}]},{begin:"^[A-Z]+ (.*?) "+n+"$",returnBegin:!0,end:"$",contains:[{className:"string",begin:" ",end:" ",excludeBegin:!0,excludeEnd:!0},{begin:n},{className:"keyword",begin:"[A-Z]+"}]},{className:"attribute",begin:"^\\w",end:": ",excludeEnd:!0,illegal:"\\n|\\s|=",starts:{end:"$",relevance:0}},{begin:"\\n\\n",starts:{subLanguage:[],endsWithParent:!0}}]}}}());hljs.registerLanguage("haskell",function(){"use strict";return function(e){var n={variants:[e.COMMENT("--","$"),e.COMMENT("{-","-}",{contains:["self"]})]},i={className:"meta",begin:"{-#",end:"#-}"},a={className:"meta",begin:"^#",end:"$"},s={className:"type",begin:"\\b[A-Z][\\w']*",relevance:0},l={begin:"\\(",end:"\\)",illegal:'"',contains:[i,a,{className:"type",begin:"\\b[A-Z][\\w]*(\\((\\.\\.|,|\\w+)\\))?"},e.inherit(e.TITLE_MODE,{begin:"[_a-z][\\w']*"}),n]};return{name:"Haskell",aliases:["hs"],keywords:"let in if then else case of where do module import hiding qualified type data newtype deriving class instance as default infix infixl infixr foreign export ccall stdcall cplusplus jvm dotnet safe unsafe family forall mdo proc rec",contains:[{beginKeywords:"module",end:"where",keywords:"module where",contains:[l,n],illegal:"\\W\\.|;"},{begin:"\\bimport\\b",end:"$",keywords:"import qualified as hiding",contains:[l,n],illegal:"\\W\\.|;"},{className:"class",begin:"^(\\s*)?(class|instance)\\b",end:"where",keywords:"class family instance where",contains:[s,l,n]},{className:"class",begin:"\\b(data|(new)?type)\\b",end:"$",keywords:"data family type newtype deriving",contains:[i,s,l,{begin:"{",end:"}",contains:l.contains},n]},{beginKeywords:"default",end:"$",contains:[s,l,n]},{beginKeywords:"infix infixl infixr",end:"$",contains:[e.C_NUMBER_MODE,n]},{begin:"\\bforeign\\b",end:"$",keywords:"foreign import export ccall stdcall cplusplus jvm dotnet safe unsafe",contains:[s,e.QUOTE_STRING_MODE,n]},{className:"meta",begin:"#!\\/usr\\/bin\\/env runhaskell",end:"$"},i,a,e.QUOTE_STRING_MODE,e.C_NUMBER_MODE,s,e.inherit(e.TITLE_MODE,{begin:"^[_a-z][\\w']*"}),n,{begin:"->|<-"}]}}}());hljs.registerLanguage("handlebars",function(){"use strict";function e(...e){return e.map(e=>(function(e){return e?"string"==typeof e?e:e.source:null})(e)).join("")}return function(n){const a={"builtin-name":"action bindattr collection component concat debugger each each-in get hash if in input link-to loc log lookup mut outlet partial query-params render template textarea unbound unless view with yield"},t=/\[.*?\]/,s=/[^\s!"#%&'()*+,.\/;<=>@\[\\\]^`{|}~]+/,i=e("(",/'.*?'/,"|",/".*?"/,"|",t,"|",s,"|",/\.|\//,")+"),r=e("(",t,"|",s,")(?==)"),l={begin:i,lexemes:/[\w.\/]+/},c=n.inherit(l,{keywords:{literal:"true false undefined null"}}),o={begin:/\(/,end:/\)/},m={className:"attr",begin:r,relevance:0,starts:{begin:/=/,end:/=/,starts:{contains:[n.NUMBER_MODE,n.QUOTE_STRING_MODE,n.APOS_STRING_MODE,c,o]}}},d={contains:[n.NUMBER_MODE,n.QUOTE_STRING_MODE,n.APOS_STRING_MODE,{begin:/as\s+\|/,keywords:{keyword:"as"},end:/\|/,contains:[{begin:/\w+/}]},m,c,o],returnEnd:!0},g=n.inherit(l,{className:"name",keywords:a,starts:n.inherit(d,{end:/\)/})});o.contains=[g];const u=n.inherit(l,{keywords:a,className:"name",starts:n.inherit(d,{end:/}}/})}),b=n.inherit(l,{keywords:a,className:"name"}),h=n.inherit(l,{className:"name",keywords:a,starts:n.inherit(d,{end:/}}/})});return{name:"Handlebars",aliases:["hbs","html.hbs","html.handlebars","htmlbars"],case_insensitive:!0,subLanguage:"xml",contains:[{begin:/\\\{\{/,skip:!0},{begin:/\\\\(?=\{\{)/,skip:!0},n.COMMENT(/\{\{!--/,/--\}\}/),n.COMMENT(/\{\{!/,/\}\}/),{className:"template-tag",begin:/\{\{\{\{(?!\/)/,end:/\}\}\}\}/,contains:[u],starts:{end:/\{\{\{\{\//,returnEnd:!0,subLanguage:"xml"}},{className:"template-tag",begin:/\{\{\{\{\//,end:/\}\}\}\}/,contains:[b]},{className:"template-tag",begin:/\{\{#/,end:/\}\}/,contains:[u]},{className:"template-tag",begin:/\{\{(?=else\}\})/,end:/\}\}/,keywords:"else"},{className:"template-tag",begin:/\{\{\//,end:/\}\}/,contains:[b]},{className:"template-variable",begin:/\{\{\{/,end:/\}\}\}/,contains:[h]},{className:"template-variable",begin:/\{\{/,end:/\}\}/,contains:[h]}]}}}());hljs.registerLanguage("rust",function(){"use strict";return function(e){var n="([ui](8|16|32|64|128|size)|f(32|64))?",t="drop i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize f32 f64 str char bool Box Option Result String Vec Copy Send Sized Sync Drop Fn FnMut FnOnce ToOwned Clone Debug PartialEq PartialOrd Eq Ord AsRef AsMut Into From Default Iterator Extend IntoIterator DoubleEndedIterator ExactSizeIterator SliceConcatExt ToString assert! assert_eq! bitflags! bytes! cfg! col! concat! concat_idents! debug_assert! debug_assert_eq! env! panic! file! format! format_args! include_bin! include_str! line! local_data_key! module_path! option_env! print! println! select! stringify! try! unimplemented! unreachable! vec! write! writeln! macro_rules! assert_ne! debug_assert_ne!";return{name:"Rust",aliases:["rs"],keywords:{$pattern:e.IDENT_RE+"!?",keyword:"abstract as async await become box break const continue crate do dyn else enum extern false final fn for if impl in let loop macro match mod move mut override priv pub ref return self Self static struct super trait true try type typeof unsafe unsized use virtual where while yield",literal:"true false Some None Ok Err",built_in:t},illegal:""}]}}}());hljs.registerLanguage("cpp",function(){"use strict";return function(e){var t=e.getLanguage("c-like").rawDefinition();return t.disableAutodetect=!1,t.name="C++",t.aliases=["cc","c++","h++","hpp","hh","hxx","cxx"],t}}());hljs.registerLanguage("ini",function(){"use strict";function e(e){return e?"string"==typeof e?e:e.source:null}function n(...n){return n.map(n=>e(n)).join("")}return function(a){var s={className:"number",relevance:0,variants:[{begin:/([\+\-]+)?[\d]+_[\d_]+/},{begin:a.NUMBER_RE}]},i=a.COMMENT();i.variants=[{begin:/;/,end:/$/},{begin:/#/,end:/$/}];var t={className:"variable",variants:[{begin:/\$[\w\d"][\w\d_]*/},{begin:/\$\{(.*?)}/}]},r={className:"literal",begin:/\bon|off|true|false|yes|no\b/},l={className:"string",contains:[a.BACKSLASH_ESCAPE],variants:[{begin:"'''",end:"'''",relevance:10},{begin:'"""',end:'"""',relevance:10},{begin:'"',end:'"'},{begin:"'",end:"'"}]},c={begin:/\[/,end:/\]/,contains:[i,r,t,l,s,"self"],relevance:0},g="("+[/[A-Za-z0-9_-]+/,/"(\\"|[^"])*"/,/'[^']*'/].map(n=>e(n)).join("|")+")";return{name:"TOML, also INI",aliases:["toml"],case_insensitive:!0,illegal:/\S/,contains:[i,{className:"section",begin:/\[+/,end:/\]+/},{begin:n(g,"(\\s*\\.\\s*",g,")*",n("(?=",/\s*=\s*[^#\s]/,")")),className:"attr",starts:{end:/$/,contains:[i,c,r,t,l,s]}}]}}}());hljs.registerLanguage("objectivec",function(){"use strict";return function(e){var n=/[a-zA-Z@][a-zA-Z0-9_]*/,_={$pattern:n,keyword:"@interface @class @protocol @implementation"};return{name:"Objective-C",aliases:["mm","objc","obj-c"],keywords:{$pattern:n,keyword:"int float while char export sizeof typedef const struct for union unsigned long volatile static bool mutable if do return goto void enum else break extern asm case short default double register explicit signed typename this switch continue wchar_t inline readonly assign readwrite self @synchronized id typeof nonatomic super unichar IBOutlet IBAction strong weak copy in out inout bycopy byref oneway __strong __weak __block __autoreleasing @private @protected @public @try @property @end @throw @catch @finally @autoreleasepool @synthesize @dynamic @selector @optional @required @encode @package @import @defs @compatibility_alias __bridge __bridge_transfer __bridge_retained __bridge_retain __covariant __contravariant __kindof _Nonnull _Nullable _Null_unspecified __FUNCTION__ __PRETTY_FUNCTION__ __attribute__ getter setter retain unsafe_unretained nonnull nullable null_unspecified null_resettable class instancetype NS_DESIGNATED_INITIALIZER NS_UNAVAILABLE NS_REQUIRES_SUPER NS_RETURNS_INNER_POINTER NS_INLINE NS_AVAILABLE NS_DEPRECATED NS_ENUM NS_OPTIONS NS_SWIFT_UNAVAILABLE NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END NS_REFINED_FOR_SWIFT NS_SWIFT_NAME NS_SWIFT_NOTHROW NS_DURING NS_HANDLER NS_ENDHANDLER NS_VALUERETURN NS_VOIDRETURN",literal:"false true FALSE TRUE nil YES NO NULL",built_in:"BOOL dispatch_once_t dispatch_queue_t dispatch_sync dispatch_async dispatch_once"},illegal:"/,end:/$/,illegal:"\\n"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"class",begin:"("+_.keyword.split(" ").join("|")+")\\b",end:"({|$)",excludeEnd:!0,keywords:_,contains:[e.UNDERSCORE_TITLE_MODE]},{begin:"\\."+e.UNDERSCORE_IDENT_RE,relevance:0}]}}}());hljs.registerLanguage("apache",function(){"use strict";return function(e){var n={className:"number",begin:"\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?"};return{name:"Apache config",aliases:["apacheconf"],case_insensitive:!0,contains:[e.HASH_COMMENT_MODE,{className:"section",begin:"",contains:[n,{className:"number",begin:":\\d{1,5}"},e.inherit(e.QUOTE_STRING_MODE,{relevance:0})]},{className:"attribute",begin:/\w+/,relevance:0,keywords:{nomarkup:"order deny allow setenv rewriterule rewriteengine rewritecond documentroot sethandler errordocument loadmodule options header listen serverroot servername"},starts:{end:/$/,relevance:0,keywords:{literal:"on off all deny allow"},contains:[{className:"meta",begin:"\\s\\[",end:"\\]$"},{className:"variable",begin:"[\\$%]\\{",end:"\\}",contains:["self",{className:"number",begin:"[\\$%]\\d+"}]},n,{className:"number",begin:"\\d+"},e.QUOTE_STRING_MODE]}}],illegal:/\S/}}}());hljs.registerLanguage("java",function(){"use strict";function e(e){return e?"string"==typeof e?e:e.source:null}function n(e){return a("(",e,")?")}function a(...n){return n.map(n=>e(n)).join("")}function s(...n){return"("+n.map(n=>e(n)).join("|")+")"}return function(e){var t="false synchronized int abstract float private char boolean var static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private module requires exports do",i={className:"meta",begin:"@[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*",contains:[{begin:/\(/,end:/\)/,contains:["self"]}]},r=e=>a("[",e,"]+([",e,"_]*[",e,"]+)?"),c={className:"number",variants:[{begin:`\\b(0[bB]${r("01")})[lL]?`},{begin:`\\b(0${r("0-7")})[dDfFlL]?`},{begin:a(/\b0[xX]/,s(a(r("a-fA-F0-9"),/\./,r("a-fA-F0-9")),a(r("a-fA-F0-9"),/\.?/),a(/\./,r("a-fA-F0-9"))),/([pP][+-]?(\d+))?/,/[fFdDlL]?/)},{begin:a(/\b/,s(a(/\d*\./,r("\\d")),r("\\d")),/[eE][+-]?[\d]+[dDfF]?/)},{begin:a(/\b/,r(/\d/),n(/\.?/),n(r(/\d/)),/[dDfFlL]?/)}],relevance:0};return{name:"Java",aliases:["jsp"],keywords:t,illegal:/<\/|#/,contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{begin:/\w+@/,relevance:0},{className:"doctag",begin:"@[A-Za-z]+"}]}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:"class",beginKeywords:"class interface",end:/[{;=]/,excludeEnd:!0,keywords:"class interface",illegal:/[:"\[\]]/,contains:[{beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"new throw return else",relevance:0},{className:"function",begin:"([À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(<[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(\\s*,\\s*[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*)*>)?\\s+)+"+e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:t,contains:[{begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,contains:[e.UNDERSCORE_TITLE_MODE]},{className:"params",begin:/\(/,end:/\)/,keywords:t,relevance:0,contains:[i,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},c,i]}}}());hljs.registerLanguage("x86asm",function(){"use strict";return function(s){return{name:"Intel x86 Assembly",case_insensitive:!0,keywords:{$pattern:"[.%]?"+s.IDENT_RE,keyword:"lock rep repe repz repne repnz xaquire xrelease bnd nobnd aaa aad aam aas adc add and arpl bb0_reset bb1_reset bound bsf bsr bswap bt btc btr bts call cbw cdq cdqe clc cld cli clts cmc cmp cmpsb cmpsd cmpsq cmpsw cmpxchg cmpxchg486 cmpxchg8b cmpxchg16b cpuid cpu_read cpu_write cqo cwd cwde daa das dec div dmint emms enter equ f2xm1 fabs fadd faddp fbld fbstp fchs fclex fcmovb fcmovbe fcmove fcmovnb fcmovnbe fcmovne fcmovnu fcmovu fcom fcomi fcomip fcomp fcompp fcos fdecstp fdisi fdiv fdivp fdivr fdivrp femms feni ffree ffreep fiadd ficom ficomp fidiv fidivr fild fimul fincstp finit fist fistp fisttp fisub fisubr fld fld1 fldcw fldenv fldl2e fldl2t fldlg2 fldln2 fldpi fldz fmul fmulp fnclex fndisi fneni fninit fnop fnsave fnstcw fnstenv fnstsw fpatan fprem fprem1 fptan frndint frstor fsave fscale fsetpm fsin fsincos fsqrt fst fstcw fstenv fstp fstsw fsub fsubp fsubr fsubrp ftst fucom fucomi fucomip fucomp fucompp fxam fxch fxtract fyl2x fyl2xp1 hlt ibts icebp idiv imul in inc incbin insb insd insw int int01 int1 int03 int3 into invd invpcid invlpg invlpga iret iretd iretq iretw jcxz jecxz jrcxz jmp jmpe lahf lar lds lea leave les lfence lfs lgdt lgs lidt lldt lmsw loadall loadall286 lodsb lodsd lodsq lodsw loop loope loopne loopnz loopz lsl lss ltr mfence monitor mov movd movq movsb movsd movsq movsw movsx movsxd movzx mul mwait neg nop not or out outsb outsd outsw packssdw packsswb packuswb paddb paddd paddsb paddsiw paddsw paddusb paddusw paddw pand pandn pause paveb pavgusb pcmpeqb pcmpeqd pcmpeqw pcmpgtb pcmpgtd pcmpgtw pdistib pf2id pfacc pfadd pfcmpeq pfcmpge pfcmpgt pfmax pfmin pfmul pfrcp pfrcpit1 pfrcpit2 pfrsqit1 pfrsqrt pfsub pfsubr pi2fd pmachriw pmaddwd pmagw pmulhriw pmulhrwa pmulhrwc pmulhw pmullw pmvgezb pmvlzb pmvnzb pmvzb pop popa popad popaw popf popfd popfq popfw por prefetch prefetchw pslld psllq psllw psrad psraw psrld psrlq psrlw psubb psubd psubsb psubsiw psubsw psubusb psubusw psubw punpckhbw punpckhdq punpckhwd punpcklbw punpckldq punpcklwd push pusha pushad pushaw pushf pushfd pushfq pushfw pxor rcl rcr rdshr rdmsr rdpmc rdtsc rdtscp ret retf retn rol ror rdm rsdc rsldt rsm rsts sahf sal salc sar sbb scasb scasd scasq scasw sfence sgdt shl shld shr shrd sidt sldt skinit smi smint smintold smsw stc std sti stosb stosd stosq stosw str sub svdc svldt svts swapgs syscall sysenter sysexit sysret test ud0 ud1 ud2b ud2 ud2a umov verr verw fwait wbinvd wrshr wrmsr xadd xbts xchg xlatb xlat xor cmove cmovz cmovne cmovnz cmova cmovnbe cmovae cmovnb cmovb cmovnae cmovbe cmovna cmovg cmovnle cmovge cmovnl cmovl cmovnge cmovle cmovng cmovc cmovnc cmovo cmovno cmovs cmovns cmovp cmovpe cmovnp cmovpo je jz jne jnz ja jnbe jae jnb jb jnae jbe jna jg jnle jge jnl jl jnge jle jng jc jnc jo jno js jns jpo jnp jpe jp sete setz setne setnz seta setnbe setae setnb setnc setb setnae setcset setbe setna setg setnle setge setnl setl setnge setle setng sets setns seto setno setpe setp setpo setnp addps addss andnps andps cmpeqps cmpeqss cmpleps cmpless cmpltps cmpltss cmpneqps cmpneqss cmpnleps cmpnless cmpnltps cmpnltss cmpordps cmpordss cmpunordps cmpunordss cmpps cmpss comiss cvtpi2ps cvtps2pi cvtsi2ss cvtss2si cvttps2pi cvttss2si divps divss ldmxcsr maxps maxss minps minss movaps movhps movlhps movlps movhlps movmskps movntps movss movups mulps mulss orps rcpps rcpss rsqrtps rsqrtss shufps sqrtps sqrtss stmxcsr subps subss ucomiss unpckhps unpcklps xorps fxrstor fxrstor64 fxsave fxsave64 xgetbv xsetbv xsave xsave64 xsaveopt xsaveopt64 xrstor xrstor64 prefetchnta prefetcht0 prefetcht1 prefetcht2 maskmovq movntq pavgb pavgw pextrw pinsrw pmaxsw pmaxub pminsw pminub pmovmskb pmulhuw psadbw pshufw pf2iw pfnacc pfpnacc pi2fw pswapd maskmovdqu clflush movntdq movnti movntpd movdqa movdqu movdq2q movq2dq paddq pmuludq pshufd pshufhw pshuflw pslldq psrldq psubq punpckhqdq punpcklqdq addpd addsd andnpd andpd cmpeqpd cmpeqsd cmplepd cmplesd cmpltpd cmpltsd cmpneqpd cmpneqsd cmpnlepd cmpnlesd cmpnltpd cmpnltsd cmpordpd cmpordsd cmpunordpd cmpunordsd cmppd comisd cvtdq2pd cvtdq2ps cvtpd2dq cvtpd2pi cvtpd2ps cvtpi2pd cvtps2dq cvtps2pd cvtsd2si cvtsd2ss cvtsi2sd cvtss2sd cvttpd2pi cvttpd2dq cvttps2dq cvttsd2si divpd divsd maxpd maxsd minpd minsd movapd movhpd movlpd movmskpd movupd mulpd mulsd orpd shufpd sqrtpd sqrtsd subpd subsd ucomisd unpckhpd unpcklpd xorpd addsubpd addsubps haddpd haddps hsubpd hsubps lddqu movddup movshdup movsldup clgi stgi vmcall vmclear vmfunc vmlaunch vmload vmmcall vmptrld vmptrst vmread vmresume vmrun vmsave vmwrite vmxoff vmxon invept invvpid pabsb pabsw pabsd palignr phaddw phaddd phaddsw phsubw phsubd phsubsw pmaddubsw pmulhrsw pshufb psignb psignw psignd extrq insertq movntsd movntss lzcnt blendpd blendps blendvpd blendvps dppd dpps extractps insertps movntdqa mpsadbw packusdw pblendvb pblendw pcmpeqq pextrb pextrd pextrq phminposuw pinsrb pinsrd pinsrq pmaxsb pmaxsd pmaxud pmaxuw pminsb pminsd pminud pminuw pmovsxbw pmovsxbd pmovsxbq pmovsxwd pmovsxwq pmovsxdq pmovzxbw pmovzxbd pmovzxbq pmovzxwd pmovzxwq pmovzxdq pmuldq pmulld ptest roundpd roundps roundsd roundss crc32 pcmpestri pcmpestrm pcmpistri pcmpistrm pcmpgtq popcnt getsec pfrcpv pfrsqrtv movbe aesenc aesenclast aesdec aesdeclast aesimc aeskeygenassist vaesenc vaesenclast vaesdec vaesdeclast vaesimc vaeskeygenassist vaddpd vaddps vaddsd vaddss vaddsubpd vaddsubps vandpd vandps vandnpd vandnps vblendpd vblendps vblendvpd vblendvps vbroadcastss vbroadcastsd vbroadcastf128 vcmpeq_ospd vcmpeqpd vcmplt_ospd vcmpltpd vcmple_ospd vcmplepd vcmpunord_qpd vcmpunordpd vcmpneq_uqpd vcmpneqpd vcmpnlt_uspd vcmpnltpd vcmpnle_uspd vcmpnlepd vcmpord_qpd vcmpordpd vcmpeq_uqpd vcmpnge_uspd vcmpngepd vcmpngt_uspd vcmpngtpd vcmpfalse_oqpd vcmpfalsepd vcmpneq_oqpd vcmpge_ospd vcmpgepd vcmpgt_ospd vcmpgtpd vcmptrue_uqpd vcmptruepd vcmplt_oqpd vcmple_oqpd vcmpunord_spd vcmpneq_uspd vcmpnlt_uqpd vcmpnle_uqpd vcmpord_spd vcmpeq_uspd vcmpnge_uqpd vcmpngt_uqpd vcmpfalse_ospd vcmpneq_ospd vcmpge_oqpd vcmpgt_oqpd vcmptrue_uspd vcmppd vcmpeq_osps vcmpeqps vcmplt_osps vcmpltps vcmple_osps vcmpleps vcmpunord_qps vcmpunordps vcmpneq_uqps vcmpneqps vcmpnlt_usps vcmpnltps vcmpnle_usps vcmpnleps vcmpord_qps vcmpordps vcmpeq_uqps vcmpnge_usps vcmpngeps vcmpngt_usps vcmpngtps vcmpfalse_oqps vcmpfalseps vcmpneq_oqps vcmpge_osps vcmpgeps vcmpgt_osps vcmpgtps vcmptrue_uqps vcmptrueps vcmplt_oqps vcmple_oqps vcmpunord_sps vcmpneq_usps vcmpnlt_uqps vcmpnle_uqps vcmpord_sps vcmpeq_usps vcmpnge_uqps vcmpngt_uqps vcmpfalse_osps vcmpneq_osps vcmpge_oqps vcmpgt_oqps vcmptrue_usps vcmpps vcmpeq_ossd vcmpeqsd vcmplt_ossd vcmpltsd vcmple_ossd vcmplesd vcmpunord_qsd vcmpunordsd vcmpneq_uqsd vcmpneqsd vcmpnlt_ussd vcmpnltsd vcmpnle_ussd vcmpnlesd vcmpord_qsd vcmpordsd vcmpeq_uqsd vcmpnge_ussd vcmpngesd vcmpngt_ussd vcmpngtsd vcmpfalse_oqsd vcmpfalsesd vcmpneq_oqsd vcmpge_ossd vcmpgesd vcmpgt_ossd vcmpgtsd vcmptrue_uqsd vcmptruesd vcmplt_oqsd vcmple_oqsd vcmpunord_ssd vcmpneq_ussd vcmpnlt_uqsd vcmpnle_uqsd vcmpord_ssd vcmpeq_ussd vcmpnge_uqsd vcmpngt_uqsd vcmpfalse_ossd vcmpneq_ossd vcmpge_oqsd vcmpgt_oqsd vcmptrue_ussd vcmpsd vcmpeq_osss vcmpeqss vcmplt_osss vcmpltss vcmple_osss vcmpless vcmpunord_qss vcmpunordss vcmpneq_uqss vcmpneqss vcmpnlt_usss vcmpnltss vcmpnle_usss vcmpnless vcmpord_qss vcmpordss vcmpeq_uqss vcmpnge_usss vcmpngess vcmpngt_usss vcmpngtss vcmpfalse_oqss vcmpfalsess vcmpneq_oqss vcmpge_osss vcmpgess vcmpgt_osss vcmpgtss vcmptrue_uqss vcmptruess vcmplt_oqss vcmple_oqss vcmpunord_sss vcmpneq_usss vcmpnlt_uqss vcmpnle_uqss vcmpord_sss vcmpeq_usss vcmpnge_uqss vcmpngt_uqss vcmpfalse_osss vcmpneq_osss vcmpge_oqss vcmpgt_oqss vcmptrue_usss vcmpss vcomisd vcomiss vcvtdq2pd vcvtdq2ps vcvtpd2dq vcvtpd2ps vcvtps2dq vcvtps2pd vcvtsd2si vcvtsd2ss vcvtsi2sd vcvtsi2ss vcvtss2sd vcvtss2si vcvttpd2dq vcvttps2dq vcvttsd2si vcvttss2si vdivpd vdivps vdivsd vdivss vdppd vdpps vextractf128 vextractps vhaddpd vhaddps vhsubpd vhsubps vinsertf128 vinsertps vlddqu vldqqu vldmxcsr vmaskmovdqu vmaskmovps vmaskmovpd vmaxpd vmaxps vmaxsd vmaxss vminpd vminps vminsd vminss vmovapd vmovaps vmovd vmovq vmovddup vmovdqa vmovqqa vmovdqu vmovqqu vmovhlps vmovhpd vmovhps vmovlhps vmovlpd vmovlps vmovmskpd vmovmskps vmovntdq vmovntqq vmovntdqa vmovntpd vmovntps vmovsd vmovshdup vmovsldup vmovss vmovupd vmovups vmpsadbw vmulpd vmulps vmulsd vmulss vorpd vorps vpabsb vpabsw vpabsd vpacksswb vpackssdw vpackuswb vpackusdw vpaddb vpaddw vpaddd vpaddq vpaddsb vpaddsw vpaddusb vpaddusw vpalignr vpand vpandn vpavgb vpavgw vpblendvb vpblendw vpcmpestri vpcmpestrm vpcmpistri vpcmpistrm vpcmpeqb vpcmpeqw vpcmpeqd vpcmpeqq vpcmpgtb vpcmpgtw vpcmpgtd vpcmpgtq vpermilpd vpermilps vperm2f128 vpextrb vpextrw vpextrd vpextrq vphaddw vphaddd vphaddsw vphminposuw vphsubw vphsubd vphsubsw vpinsrb vpinsrw vpinsrd vpinsrq vpmaddwd vpmaddubsw vpmaxsb vpmaxsw vpmaxsd vpmaxub vpmaxuw vpmaxud vpminsb vpminsw vpminsd vpminub vpminuw vpminud vpmovmskb vpmovsxbw vpmovsxbd vpmovsxbq vpmovsxwd vpmovsxwq vpmovsxdq vpmovzxbw vpmovzxbd vpmovzxbq vpmovzxwd vpmovzxwq vpmovzxdq vpmulhuw vpmulhrsw vpmulhw vpmullw vpmulld vpmuludq vpmuldq vpor vpsadbw vpshufb vpshufd vpshufhw vpshuflw vpsignb vpsignw vpsignd vpslldq vpsrldq vpsllw vpslld vpsllq vpsraw vpsrad vpsrlw vpsrld vpsrlq vptest vpsubb vpsubw vpsubd vpsubq vpsubsb vpsubsw vpsubusb vpsubusw vpunpckhbw vpunpckhwd vpunpckhdq vpunpckhqdq vpunpcklbw vpunpcklwd vpunpckldq vpunpcklqdq vpxor vrcpps vrcpss vrsqrtps vrsqrtss vroundpd vroundps vroundsd vroundss vshufpd vshufps vsqrtpd vsqrtps vsqrtsd vsqrtss vstmxcsr vsubpd vsubps vsubsd vsubss vtestps vtestpd vucomisd vucomiss vunpckhpd vunpckhps vunpcklpd vunpcklps vxorpd vxorps vzeroall vzeroupper pclmullqlqdq pclmulhqlqdq pclmullqhqdq pclmulhqhqdq pclmulqdq vpclmullqlqdq vpclmulhqlqdq vpclmullqhqdq vpclmulhqhqdq vpclmulqdq vfmadd132ps vfmadd132pd vfmadd312ps vfmadd312pd vfmadd213ps vfmadd213pd vfmadd123ps vfmadd123pd vfmadd231ps vfmadd231pd vfmadd321ps vfmadd321pd vfmaddsub132ps vfmaddsub132pd vfmaddsub312ps vfmaddsub312pd vfmaddsub213ps vfmaddsub213pd vfmaddsub123ps vfmaddsub123pd vfmaddsub231ps vfmaddsub231pd vfmaddsub321ps vfmaddsub321pd vfmsub132ps vfmsub132pd vfmsub312ps vfmsub312pd vfmsub213ps vfmsub213pd vfmsub123ps vfmsub123pd vfmsub231ps vfmsub231pd vfmsub321ps vfmsub321pd vfmsubadd132ps vfmsubadd132pd vfmsubadd312ps vfmsubadd312pd vfmsubadd213ps vfmsubadd213pd vfmsubadd123ps vfmsubadd123pd vfmsubadd231ps vfmsubadd231pd vfmsubadd321ps vfmsubadd321pd vfnmadd132ps vfnmadd132pd vfnmadd312ps vfnmadd312pd vfnmadd213ps vfnmadd213pd vfnmadd123ps vfnmadd123pd vfnmadd231ps vfnmadd231pd vfnmadd321ps vfnmadd321pd vfnmsub132ps vfnmsub132pd vfnmsub312ps vfnmsub312pd vfnmsub213ps vfnmsub213pd vfnmsub123ps vfnmsub123pd vfnmsub231ps vfnmsub231pd vfnmsub321ps vfnmsub321pd vfmadd132ss vfmadd132sd vfmadd312ss vfmadd312sd vfmadd213ss vfmadd213sd vfmadd123ss vfmadd123sd vfmadd231ss vfmadd231sd vfmadd321ss vfmadd321sd vfmsub132ss vfmsub132sd vfmsub312ss vfmsub312sd vfmsub213ss vfmsub213sd vfmsub123ss vfmsub123sd vfmsub231ss vfmsub231sd vfmsub321ss vfmsub321sd vfnmadd132ss vfnmadd132sd vfnmadd312ss vfnmadd312sd vfnmadd213ss vfnmadd213sd vfnmadd123ss vfnmadd123sd vfnmadd231ss vfnmadd231sd vfnmadd321ss vfnmadd321sd vfnmsub132ss vfnmsub132sd vfnmsub312ss vfnmsub312sd vfnmsub213ss vfnmsub213sd vfnmsub123ss vfnmsub123sd vfnmsub231ss vfnmsub231sd vfnmsub321ss vfnmsub321sd rdfsbase rdgsbase rdrand wrfsbase wrgsbase vcvtph2ps vcvtps2ph adcx adox rdseed clac stac xstore xcryptecb xcryptcbc xcryptctr xcryptcfb xcryptofb montmul xsha1 xsha256 llwpcb slwpcb lwpval lwpins vfmaddpd vfmaddps vfmaddsd vfmaddss vfmaddsubpd vfmaddsubps vfmsubaddpd vfmsubaddps vfmsubpd vfmsubps vfmsubsd vfmsubss vfnmaddpd vfnmaddps vfnmaddsd vfnmaddss vfnmsubpd vfnmsubps vfnmsubsd vfnmsubss vfrczpd vfrczps vfrczsd vfrczss vpcmov vpcomb vpcomd vpcomq vpcomub vpcomud vpcomuq vpcomuw vpcomw vphaddbd vphaddbq vphaddbw vphadddq vphaddubd vphaddubq vphaddubw vphaddudq vphadduwd vphadduwq vphaddwd vphaddwq vphsubbw vphsubdq vphsubwd vpmacsdd vpmacsdqh vpmacsdql vpmacssdd vpmacssdqh vpmacssdql vpmacsswd vpmacssww vpmacswd vpmacsww vpmadcsswd vpmadcswd vpperm vprotb vprotd vprotq vprotw vpshab vpshad vpshaq vpshaw vpshlb vpshld vpshlq vpshlw vbroadcasti128 vpblendd vpbroadcastb vpbroadcastw vpbroadcastd vpbroadcastq vpermd vpermpd vpermps vpermq vperm2i128 vextracti128 vinserti128 vpmaskmovd vpmaskmovq vpsllvd vpsllvq vpsravd vpsrlvd vpsrlvq vgatherdpd vgatherqpd vgatherdps vgatherqps vpgatherdd vpgatherqd vpgatherdq vpgatherqq xabort xbegin xend xtest andn bextr blci blcic blsi blsic blcfill blsfill blcmsk blsmsk blsr blcs bzhi mulx pdep pext rorx sarx shlx shrx tzcnt tzmsk t1mskc valignd valignq vblendmpd vblendmps vbroadcastf32x4 vbroadcastf64x4 vbroadcasti32x4 vbroadcasti64x4 vcompresspd vcompressps vcvtpd2udq vcvtps2udq vcvtsd2usi vcvtss2usi vcvttpd2udq vcvttps2udq vcvttsd2usi vcvttss2usi vcvtudq2pd vcvtudq2ps vcvtusi2sd vcvtusi2ss vexpandpd vexpandps vextractf32x4 vextractf64x4 vextracti32x4 vextracti64x4 vfixupimmpd vfixupimmps vfixupimmsd vfixupimmss vgetexppd vgetexpps vgetexpsd vgetexpss vgetmantpd vgetmantps vgetmantsd vgetmantss vinsertf32x4 vinsertf64x4 vinserti32x4 vinserti64x4 vmovdqa32 vmovdqa64 vmovdqu32 vmovdqu64 vpabsq vpandd vpandnd vpandnq vpandq vpblendmd vpblendmq vpcmpltd vpcmpled vpcmpneqd vpcmpnltd vpcmpnled vpcmpd vpcmpltq vpcmpleq vpcmpneqq vpcmpnltq vpcmpnleq vpcmpq vpcmpequd vpcmpltud vpcmpleud vpcmpnequd vpcmpnltud vpcmpnleud vpcmpud vpcmpequq vpcmpltuq vpcmpleuq vpcmpnequq vpcmpnltuq vpcmpnleuq vpcmpuq vpcompressd vpcompressq vpermi2d vpermi2pd vpermi2ps vpermi2q vpermt2d vpermt2pd vpermt2ps vpermt2q vpexpandd vpexpandq vpmaxsq vpmaxuq vpminsq vpminuq vpmovdb vpmovdw vpmovqb vpmovqd vpmovqw vpmovsdb vpmovsdw vpmovsqb vpmovsqd vpmovsqw vpmovusdb vpmovusdw vpmovusqb vpmovusqd vpmovusqw vpord vporq vprold vprolq vprolvd vprolvq vprord vprorq vprorvd vprorvq vpscatterdd vpscatterdq vpscatterqd vpscatterqq vpsraq vpsravq vpternlogd vpternlogq vptestmd vptestmq vptestnmd vptestnmq vpxord vpxorq vrcp14pd vrcp14ps vrcp14sd vrcp14ss vrndscalepd vrndscaleps vrndscalesd vrndscaless vrsqrt14pd vrsqrt14ps vrsqrt14sd vrsqrt14ss vscalefpd vscalefps vscalefsd vscalefss vscatterdpd vscatterdps vscatterqpd vscatterqps vshuff32x4 vshuff64x2 vshufi32x4 vshufi64x2 kandnw kandw kmovw knotw kortestw korw kshiftlw kshiftrw kunpckbw kxnorw kxorw vpbroadcastmb2q vpbroadcastmw2d vpconflictd vpconflictq vplzcntd vplzcntq vexp2pd vexp2ps vrcp28pd vrcp28ps vrcp28sd vrcp28ss vrsqrt28pd vrsqrt28ps vrsqrt28sd vrsqrt28ss vgatherpf0dpd vgatherpf0dps vgatherpf0qpd vgatherpf0qps vgatherpf1dpd vgatherpf1dps vgatherpf1qpd vgatherpf1qps vscatterpf0dpd vscatterpf0dps vscatterpf0qpd vscatterpf0qps vscatterpf1dpd vscatterpf1dps vscatterpf1qpd vscatterpf1qps prefetchwt1 bndmk bndcl bndcu bndcn bndmov bndldx bndstx sha1rnds4 sha1nexte sha1msg1 sha1msg2 sha256rnds2 sha256msg1 sha256msg2 hint_nop0 hint_nop1 hint_nop2 hint_nop3 hint_nop4 hint_nop5 hint_nop6 hint_nop7 hint_nop8 hint_nop9 hint_nop10 hint_nop11 hint_nop12 hint_nop13 hint_nop14 hint_nop15 hint_nop16 hint_nop17 hint_nop18 hint_nop19 hint_nop20 hint_nop21 hint_nop22 hint_nop23 hint_nop24 hint_nop25 hint_nop26 hint_nop27 hint_nop28 hint_nop29 hint_nop30 hint_nop31 hint_nop32 hint_nop33 hint_nop34 hint_nop35 hint_nop36 hint_nop37 hint_nop38 hint_nop39 hint_nop40 hint_nop41 hint_nop42 hint_nop43 hint_nop44 hint_nop45 hint_nop46 hint_nop47 hint_nop48 hint_nop49 hint_nop50 hint_nop51 hint_nop52 hint_nop53 hint_nop54 hint_nop55 hint_nop56 hint_nop57 hint_nop58 hint_nop59 hint_nop60 hint_nop61 hint_nop62 hint_nop63",built_in:"ip eip rip al ah bl bh cl ch dl dh sil dil bpl spl r8b r9b r10b r11b r12b r13b r14b r15b ax bx cx dx si di bp sp r8w r9w r10w r11w r12w r13w r14w r15w eax ebx ecx edx esi edi ebp esp eip r8d r9d r10d r11d r12d r13d r14d r15d rax rbx rcx rdx rsi rdi rbp rsp r8 r9 r10 r11 r12 r13 r14 r15 cs ds es fs gs ss st st0 st1 st2 st3 st4 st5 st6 st7 mm0 mm1 mm2 mm3 mm4 mm5 mm6 mm7 xmm0 xmm1 xmm2 xmm3 xmm4 xmm5 xmm6 xmm7 xmm8 xmm9 xmm10 xmm11 xmm12 xmm13 xmm14 xmm15 xmm16 xmm17 xmm18 xmm19 xmm20 xmm21 xmm22 xmm23 xmm24 xmm25 xmm26 xmm27 xmm28 xmm29 xmm30 xmm31 ymm0 ymm1 ymm2 ymm3 ymm4 ymm5 ymm6 ymm7 ymm8 ymm9 ymm10 ymm11 ymm12 ymm13 ymm14 ymm15 ymm16 ymm17 ymm18 ymm19 ymm20 ymm21 ymm22 ymm23 ymm24 ymm25 ymm26 ymm27 ymm28 ymm29 ymm30 ymm31 zmm0 zmm1 zmm2 zmm3 zmm4 zmm5 zmm6 zmm7 zmm8 zmm9 zmm10 zmm11 zmm12 zmm13 zmm14 zmm15 zmm16 zmm17 zmm18 zmm19 zmm20 zmm21 zmm22 zmm23 zmm24 zmm25 zmm26 zmm27 zmm28 zmm29 zmm30 zmm31 k0 k1 k2 k3 k4 k5 k6 k7 bnd0 bnd1 bnd2 bnd3 cr0 cr1 cr2 cr3 cr4 cr8 dr0 dr1 dr2 dr3 dr8 tr3 tr4 tr5 tr6 tr7 r0 r1 r2 r3 r4 r5 r6 r7 r0b r1b r2b r3b r4b r5b r6b r7b r0w r1w r2w r3w r4w r5w r6w r7w r0d r1d r2d r3d r4d r5d r6d r7d r0h r1h r2h r3h r0l r1l r2l r3l r4l r5l r6l r7l r8l r9l r10l r11l r12l r13l r14l r15l db dw dd dq dt ddq do dy dz resb resw resd resq rest resdq reso resy resz incbin equ times byte word dword qword nosplit rel abs seg wrt strict near far a32 ptr",meta:"%define %xdefine %+ %undef %defstr %deftok %assign %strcat %strlen %substr %rotate %elif %else %endif %if %ifmacro %ifctx %ifidn %ifidni %ifid %ifnum %ifstr %iftoken %ifempty %ifenv %error %warning %fatal %rep %endrep %include %push %pop %repl %pathsearch %depend %use %arg %stacksize %local %line %comment %endcomment .nolist __FILE__ __LINE__ __SECT__ __BITS__ __OUTPUT_FORMAT__ __DATE__ __TIME__ __DATE_NUM__ __TIME_NUM__ __UTC_DATE__ __UTC_TIME__ __UTC_DATE_NUM__ __UTC_TIME_NUM__ __PASS__ struc endstruc istruc at iend align alignb sectalign daz nodaz up down zero default option assume public bits use16 use32 use64 default section segment absolute extern global common cpu float __utf16__ __utf16le__ __utf16be__ __utf32__ __utf32le__ __utf32be__ __float8__ __float16__ __float32__ __float64__ __float80m__ __float80e__ __float128l__ __float128h__ __Infinity__ __QNaN__ __SNaN__ Inf NaN QNaN SNaN float8 float16 float32 float64 float80m float80e float128l float128h __FLOAT_DAZ__ __FLOAT_ROUND__ __FLOAT__"},contains:[s.COMMENT(";","$",{relevance:0}),{className:"number",variants:[{begin:"\\b(?:([0-9][0-9_]*)?\\.[0-9_]*(?:[eE][+-]?[0-9_]+)?|(0[Xx])?[0-9][0-9_]*\\.?[0-9_]*(?:[pP](?:[+-]?[0-9_]+)?)?)\\b",relevance:0},{begin:"\\$[0-9][0-9A-Fa-f]*",relevance:0},{begin:"\\b(?:[0-9A-Fa-f][0-9A-Fa-f_]*[Hh]|[0-9][0-9_]*[DdTt]?|[0-7][0-7_]*[QqOo]|[0-1][0-1_]*[BbYy])\\b"},{begin:"\\b(?:0[Xx][0-9A-Fa-f_]+|0[DdTt][0-9_]+|0[QqOo][0-7_]+|0[BbYy][0-1_]+)\\b"}]},s.QUOTE_STRING_MODE,{className:"string",variants:[{begin:"'",end:"[^\\\\]'"},{begin:"`",end:"[^\\\\]`"}],relevance:0},{className:"symbol",variants:[{begin:"^\\s*[A-Za-z._?][A-Za-z0-9_$#@~.?]*(:|\\s+label)"},{begin:"^\\s*%%[A-Za-z0-9_$#@~.?]*:"}],relevance:0},{className:"subst",begin:"%[0-9]+",relevance:0},{className:"subst",begin:"%!S+",relevance:0},{className:"meta",begin:/^\s*\.[\w_-]+/}]}}}());hljs.registerLanguage("kotlin",function(){"use strict";return function(e){var n={keyword:"abstract as val var vararg get set class object open private protected public noinline crossinline dynamic final enum if else do while for when throw try catch finally import package is in fun override companion reified inline lateinit init interface annotation data sealed internal infix operator out by constructor super tailrec where const inner suspend typealias external expect actual trait volatile transient native default",built_in:"Byte Short Char Int Long Boolean Float Double Void Unit Nothing",literal:"true false null"},a={className:"symbol",begin:e.UNDERSCORE_IDENT_RE+"@"},i={className:"subst",begin:"\\${",end:"}",contains:[e.C_NUMBER_MODE]},s={className:"variable",begin:"\\$"+e.UNDERSCORE_IDENT_RE},t={className:"string",variants:[{begin:'"""',end:'"""(?=[^"])',contains:[s,i]},{begin:"'",end:"'",illegal:/\n/,contains:[e.BACKSLASH_ESCAPE]},{begin:'"',end:'"',illegal:/\n/,contains:[e.BACKSLASH_ESCAPE,s,i]}]};i.contains.push(t);var r={className:"meta",begin:"@(?:file|property|field|get|set|receiver|param|setparam|delegate)\\s*:(?:\\s*"+e.UNDERSCORE_IDENT_RE+")?"},l={className:"meta",begin:"@"+e.UNDERSCORE_IDENT_RE,contains:[{begin:/\(/,end:/\)/,contains:[e.inherit(t,{className:"meta-string"})]}]},c=e.COMMENT("/\\*","\\*/",{contains:[e.C_BLOCK_COMMENT_MODE]}),o={variants:[{className:"type",begin:e.UNDERSCORE_IDENT_RE},{begin:/\(/,end:/\)/,contains:[]}]},d=o;return d.variants[1].contains=[o],o.variants[1].contains=[d],{name:"Kotlin",aliases:["kt"],keywords:n,contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{className:"doctag",begin:"@[A-Za-z]+"}]}),e.C_LINE_COMMENT_MODE,c,{className:"keyword",begin:/\b(break|continue|return|this)\b/,starts:{contains:[{className:"symbol",begin:/@\w+/}]}},a,r,l,{className:"function",beginKeywords:"fun",end:"[(]|$",returnBegin:!0,excludeEnd:!0,keywords:n,illegal:/fun\s+(<.*>)?[^\s\(]+(\s+[^\s\(]+)\s*=/,relevance:5,contains:[{begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,contains:[e.UNDERSCORE_TITLE_MODE]},{className:"type",begin://,keywords:"reified",relevance:0},{className:"params",begin:/\(/,end:/\)/,endsParent:!0,keywords:n,relevance:0,contains:[{begin:/:/,end:/[=,\/]/,endsWithParent:!0,contains:[o,e.C_LINE_COMMENT_MODE,c],relevance:0},e.C_LINE_COMMENT_MODE,c,r,l,t,e.C_NUMBER_MODE]},c]},{className:"class",beginKeywords:"class interface trait",end:/[:\{(]|$/,excludeEnd:!0,illegal:"extends implements",contains:[{beginKeywords:"public protected internal private constructor"},e.UNDERSCORE_TITLE_MODE,{className:"type",begin://,excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:/[,:]\s*/,end:/[<\(,]|$/,excludeBegin:!0,returnEnd:!0},r,l]},t,{className:"meta",begin:"^#!/usr/bin/env",end:"$",illegal:"\n"},{className:"number",begin:"\\b(0[bB]([01]+[01_]+[01]+|[01]+)|0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)|(([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?|\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))([eE][-+]?\\d+)?)[lLfF]?",relevance:0}]}}}());hljs.registerLanguage("armasm",function(){"use strict";return function(s){const e={variants:[s.COMMENT("^[ \\t]*(?=#)","$",{relevance:0,excludeBegin:!0}),s.COMMENT("[;@]","$",{relevance:0}),s.C_LINE_COMMENT_MODE,s.C_BLOCK_COMMENT_MODE]};return{name:"ARM Assembly",case_insensitive:!0,aliases:["arm"],keywords:{$pattern:"\\.?"+s.IDENT_RE,meta:".2byte .4byte .align .ascii .asciz .balign .byte .code .data .else .end .endif .endm .endr .equ .err .exitm .extern .global .hword .if .ifdef .ifndef .include .irp .long .macro .rept .req .section .set .skip .space .text .word .arm .thumb .code16 .code32 .force_thumb .thumb_func .ltorg ALIAS ALIGN ARM AREA ASSERT ATTR CN CODE CODE16 CODE32 COMMON CP DATA DCB DCD DCDU DCDO DCFD DCFDU DCI DCQ DCQU DCW DCWU DN ELIF ELSE END ENDFUNC ENDIF ENDP ENTRY EQU EXPORT EXPORTAS EXTERN FIELD FILL FUNCTION GBLA GBLL GBLS GET GLOBAL IF IMPORT INCBIN INCLUDE INFO KEEP LCLA LCLL LCLS LTORG MACRO MAP MEND MEXIT NOFP OPT PRESERVE8 PROC QN READONLY RELOC REQUIRE REQUIRE8 RLIST FN ROUT SETA SETL SETS SN SPACE SUBT THUMB THUMBX TTL WHILE WEND ",built_in:"r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12 r13 r14 r15 pc lr sp ip sl sb fp a1 a2 a3 a4 v1 v2 v3 v4 v5 v6 v7 v8 f0 f1 f2 f3 f4 f5 f6 f7 p0 p1 p2 p3 p4 p5 p6 p7 p8 p9 p10 p11 p12 p13 p14 p15 c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 c10 c11 c12 c13 c14 c15 q0 q1 q2 q3 q4 q5 q6 q7 q8 q9 q10 q11 q12 q13 q14 q15 cpsr_c cpsr_x cpsr_s cpsr_f cpsr_cx cpsr_cxs cpsr_xs cpsr_xsf cpsr_sf cpsr_cxsf spsr_c spsr_x spsr_s spsr_f spsr_cx spsr_cxs spsr_xs spsr_xsf spsr_sf spsr_cxsf s0 s1 s2 s3 s4 s5 s6 s7 s8 s9 s10 s11 s12 s13 s14 s15 s16 s17 s18 s19 s20 s21 s22 s23 s24 s25 s26 s27 s28 s29 s30 s31 d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 d10 d11 d12 d13 d14 d15 d16 d17 d18 d19 d20 d21 d22 d23 d24 d25 d26 d27 d28 d29 d30 d31 {PC} {VAR} {TRUE} {FALSE} {OPT} {CONFIG} {ENDIAN} {CODESIZE} {CPU} {FPU} {ARCHITECTURE} {PCSTOREOFFSET} {ARMASM_VERSION} {INTER} {ROPI} {RWPI} {SWST} {NOSWST} . @"},contains:[{className:"keyword",begin:"\\b(adc|(qd?|sh?|u[qh]?)?add(8|16)?|usada?8|(q|sh?|u[qh]?)?(as|sa)x|and|adrl?|sbc|rs[bc]|asr|b[lx]?|blx|bxj|cbn?z|tb[bh]|bic|bfc|bfi|[su]bfx|bkpt|cdp2?|clz|clrex|cmp|cmn|cpsi[ed]|cps|setend|dbg|dmb|dsb|eor|isb|it[te]{0,3}|lsl|lsr|ror|rrx|ldm(([id][ab])|f[ds])?|ldr((s|ex)?[bhd])?|movt?|mvn|mra|mar|mul|[us]mull|smul[bwt][bt]|smu[as]d|smmul|smmla|mla|umlaal|smlal?([wbt][bt]|d)|mls|smlsl?[ds]|smc|svc|sev|mia([bt]{2}|ph)?|mrr?c2?|mcrr2?|mrs|msr|orr|orn|pkh(tb|bt)|rbit|rev(16|sh)?|sel|[su]sat(16)?|nop|pop|push|rfe([id][ab])?|stm([id][ab])?|str(ex)?[bhd]?|(qd?)?sub|(sh?|q|u[qh]?)?sub(8|16)|[su]xt(a?h|a?b(16)?)|srs([id][ab])?|swpb?|swi|smi|tst|teq|wfe|wfi|yield)(eq|ne|cs|cc|mi|pl|vs|vc|hi|ls|ge|lt|gt|le|al|hs|lo)?[sptrx]?(?=\\s)"},e,s.QUOTE_STRING_MODE,{className:"string",begin:"'",end:"[^\\\\]'",relevance:0},{className:"title",begin:"\\|",end:"\\|",illegal:"\\n",relevance:0},{className:"number",variants:[{begin:"[#$=]?0x[0-9a-f]+"},{begin:"[#$=]?0b[01]+"},{begin:"[#$=]\\d+"},{begin:"\\b\\d+"}],relevance:0},{className:"symbol",variants:[{begin:"^[ \\t]*[a-z_\\.\\$][a-z0-9_\\.\\$]+:"},{begin:"^[a-z_\\.\\$][a-z0-9_\\.\\$]+"},{begin:"[=#]\\w+"}],relevance:0}]}}}());hljs.registerLanguage("go",function(){"use strict";return function(e){var n={keyword:"break default func interface select case map struct chan else goto package switch const fallthrough if range type continue for import return var go defer bool byte complex64 complex128 float32 float64 int8 int16 int32 int64 string uint8 uint16 uint32 uint64 int uint uintptr rune",literal:"true false iota nil",built_in:"append cap close complex copy imag len make new panic print println real recover delete"};return{name:"Go",aliases:["golang"],keywords:n,illegal:">>|\.\.\.) /},i={className:"subst",begin:/\{/,end:/\}/,keywords:n,illegal:/#/},s={begin:/\{\{/,relevance:0},r={className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{begin:/(u|b)?r?'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE,a],relevance:10},{begin:/(u|b)?r?"""/,end:/"""/,contains:[e.BACKSLASH_ESCAPE,a],relevance:10},{begin:/(fr|rf|f)'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE,a,s,i]},{begin:/(fr|rf|f)"""/,end:/"""/,contains:[e.BACKSLASH_ESCAPE,a,s,i]},{begin:/(u|r|ur)'/,end:/'/,relevance:10},{begin:/(u|r|ur)"/,end:/"/,relevance:10},{begin:/(b|br)'/,end:/'/},{begin:/(b|br)"/,end:/"/},{begin:/(fr|rf|f)'/,end:/'/,contains:[e.BACKSLASH_ESCAPE,s,i]},{begin:/(fr|rf|f)"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,s,i]},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},l={className:"number",relevance:0,variants:[{begin:e.BINARY_NUMBER_RE+"[lLjJ]?"},{begin:"\\b(0o[0-7]+)[lLjJ]?"},{begin:e.C_NUMBER_RE+"[lLjJ]?"}]},t={className:"params",variants:[{begin:/\(\s*\)/,skip:!0,className:null},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:["self",a,l,r,e.HASH_COMMENT_MODE]}]};return i.contains=[r,l,a],{name:"Python",aliases:["py","gyp","ipython"],keywords:n,illegal:/(<\/|->|\?)|=>/,contains:[a,l,{beginKeywords:"if",relevance:0},r,e.HASH_COMMENT_MODE,{variants:[{className:"function",beginKeywords:"def"},{className:"class",beginKeywords:"class"}],end:/:/,illegal:/[${=;\n,]/,contains:[e.UNDERSCORE_TITLE_MODE,t,{begin:/->/,endsWithParent:!0,keywords:"None"}]},{className:"meta",begin:/^[\t ]*@/,end:/$/},{begin:/\b(print|exec)\(/}]}}}());hljs.registerLanguage("shell",function(){"use strict";return function(s){return{name:"Shell Session",aliases:["console"],contains:[{className:"meta",begin:"^\\s{0,3}[/\\w\\d\\[\\]()@-]*[>%$#]",starts:{end:"$",subLanguage:"bash"}}]}}}());hljs.registerLanguage("scala",function(){"use strict";return function(e){var n={className:"subst",variants:[{begin:"\\$[A-Za-z0-9_]+"},{begin:"\\${",end:"}"}]},a={className:"string",variants:[{begin:'"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{begin:'"""',end:'"""',relevance:10},{begin:'[a-z]+"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE,n]},{className:"string",begin:'[a-z]+"""',end:'"""',contains:[n],relevance:10}]},s={className:"type",begin:"\\b[A-Z][A-Za-z0-9_]*",relevance:0},t={className:"title",begin:/[^0-9\n\t "'(),.`{}\[\]:;][^\n\t "'(),.`{}\[\]:;]+|[^0-9\n\t "'(),.`{}\[\]:;=]/,relevance:0},i={className:"class",beginKeywords:"class object trait type",end:/[:={\[\n;]/,excludeEnd:!0,contains:[{beginKeywords:"extends with",relevance:10},{begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[s]},{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[s]},t]},l={className:"function",beginKeywords:"def",end:/[:={\[(\n;]/,excludeEnd:!0,contains:[t]};return{name:"Scala",keywords:{literal:"true false null",keyword:"type yield lazy override def with val var sealed abstract private trait object if forSome for while throw finally protected extends import final return else break new catch super class case package default try this match continue throws implicit"},contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,{className:"symbol",begin:"'\\w[\\w\\d_]*(?!')"},s,l,i,e.C_NUMBER_MODE,{className:"meta",begin:"@[A-Za-z]+"}]}}}());hljs.registerLanguage("julia",function(){"use strict";return function(e){var r="[A-Za-z_\\u00A1-\\uFFFF][A-Za-z_0-9\\u00A1-\\uFFFF]*",t={$pattern:r,keyword:"in isa where baremodule begin break catch ccall const continue do else elseif end export false finally for function global if import importall let local macro module quote return true try using while type immutable abstract bitstype typealias ",literal:"true false ARGS C_NULL DevNull ENDIAN_BOM ENV I Inf Inf16 Inf32 Inf64 InsertionSort JULIA_HOME LOAD_PATH MergeSort NaN NaN16 NaN32 NaN64 PROGRAM_FILE QuickSort RoundDown RoundFromZero RoundNearest RoundNearestTiesAway RoundNearestTiesUp RoundToZero RoundUp STDERR STDIN STDOUT VERSION catalan e|0 eu|0 eulergamma golden im nothing pi γ π φ ",built_in:"ANY AbstractArray AbstractChannel AbstractFloat AbstractMatrix AbstractRNG AbstractSerializer AbstractSet AbstractSparseArray AbstractSparseMatrix AbstractSparseVector AbstractString AbstractUnitRange AbstractVecOrMat AbstractVector Any ArgumentError Array AssertionError Associative Base64DecodePipe Base64EncodePipe Bidiagonal BigFloat BigInt BitArray BitMatrix BitVector Bool BoundsError BufferStream CachingPool CapturedException CartesianIndex CartesianRange Cchar Cdouble Cfloat Channel Char Cint Cintmax_t Clong Clonglong ClusterManager Cmd CodeInfo Colon Complex Complex128 Complex32 Complex64 CompositeException Condition ConjArray ConjMatrix ConjVector Cptrdiff_t Cshort Csize_t Cssize_t Cstring Cuchar Cuint Cuintmax_t Culong Culonglong Cushort Cwchar_t Cwstring DataType Date DateFormat DateTime DenseArray DenseMatrix DenseVecOrMat DenseVector Diagonal Dict DimensionMismatch Dims DirectIndexString Display DivideError DomainError EOFError EachLine Enum Enumerate ErrorException Exception ExponentialBackOff Expr Factorization FileMonitor Float16 Float32 Float64 Function Future GlobalRef GotoNode HTML Hermitian IO IOBuffer IOContext IOStream IPAddr IPv4 IPv6 IndexCartesian IndexLinear IndexStyle InexactError InitError Int Int128 Int16 Int32 Int64 Int8 IntSet Integer InterruptException InvalidStateException Irrational KeyError LabelNode LinSpace LineNumberNode LoadError LowerTriangular MIME Matrix MersenneTwister Method MethodError MethodTable Module NTuple NewvarNode NullException Nullable Number ObjectIdDict OrdinalRange OutOfMemoryError OverflowError Pair ParseError PartialQuickSort PermutedDimsArray Pipe PollingFileWatcher ProcessExitedException Ptr QuoteNode RandomDevice Range RangeIndex Rational RawFD ReadOnlyMemoryError Real ReentrantLock Ref Regex RegexMatch RemoteChannel RemoteException RevString RoundingMode RowVector SSAValue SegmentationFault SerializationState Set SharedArray SharedMatrix SharedVector Signed SimpleVector Slot SlotNumber SparseMatrixCSC SparseVector StackFrame StackOverflowError StackTrace StepRange StepRangeLen StridedArray StridedMatrix StridedVecOrMat StridedVector String SubArray SubString SymTridiagonal Symbol Symmetric SystemError TCPSocket Task Text TextDisplay Timer Tridiagonal Tuple Type TypeError TypeMapEntry TypeMapLevel TypeName TypeVar TypedSlot UDPSocket UInt UInt128 UInt16 UInt32 UInt64 UInt8 UndefRefError UndefVarError UnicodeError UniformScaling Union UnionAll UnitRange Unsigned UpperTriangular Val Vararg VecElement VecOrMat Vector VersionNumber Void WeakKeyDict WeakRef WorkerConfig WorkerPool "},a={keywords:t,illegal:/<\//},n={className:"subst",begin:/\$\(/,end:/\)/,keywords:t},o={className:"variable",begin:"\\$"+r},i={className:"string",contains:[e.BACKSLASH_ESCAPE,n,o],variants:[{begin:/\w*"""/,end:/"""\w*/,relevance:10},{begin:/\w*"/,end:/"\w*/}]},l={className:"string",contains:[e.BACKSLASH_ESCAPE,n,o],begin:"`",end:"`"},s={className:"meta",begin:"@"+r};return a.name="Julia",a.contains=[{className:"number",begin:/(\b0x[\d_]*(\.[\d_]*)?|0x\.\d[\d_]*)p[-+]?\d+|\b0[box][a-fA-F0-9][a-fA-F0-9_]*|(\b\d[\d_]*(\.[\d_]*)?|\.\d[\d_]*)([eEfF][-+]?\d+)?/,relevance:0},{className:"string",begin:/'(.|\\[xXuU][a-zA-Z0-9]+)'/},i,l,s,{className:"comment",variants:[{begin:"#=",end:"=#",relevance:10},{begin:"#",end:"$"}]},e.HASH_COMMENT_MODE,{className:"keyword",begin:"\\b(((abstract|primitive)\\s+)type|(mutable\\s+)?struct)\\b"},{begin:/<:/}],n.contains=a.contains,a}}());hljs.registerLanguage("php-template",function(){"use strict";return function(n){return{name:"PHP template",subLanguage:"xml",contains:[{begin:/<\?(php|=)?/,end:/\?>/,subLanguage:"php",contains:[{begin:"/\\*",end:"\\*/",skip:!0},{begin:'b"',end:'"',skip:!0},{begin:"b'",end:"'",skip:!0},n.inherit(n.APOS_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0}),n.inherit(n.QUOTE_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0})]}]}}}());hljs.registerLanguage("scss",function(){"use strict";return function(e){var t={className:"variable",begin:"(\\$[a-zA-Z-][a-zA-Z0-9_-]*)\\b"},i={className:"number",begin:"#[0-9A-Fa-f]+"};return e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,e.C_BLOCK_COMMENT_MODE,{name:"SCSS",case_insensitive:!0,illegal:"[=/|']",contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"selector-id",begin:"\\#[A-Za-z0-9_-]+",relevance:0},{className:"selector-class",begin:"\\.[A-Za-z0-9_-]+",relevance:0},{className:"selector-attr",begin:"\\[",end:"\\]",illegal:"$"},{className:"selector-tag",begin:"\\b(a|abbr|acronym|address|area|article|aside|audio|b|base|big|blockquote|body|br|button|canvas|caption|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|div|dl|dt|em|embed|fieldset|figcaption|figure|footer|form|frame|frameset|(h[1-6])|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|map|mark|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|samp|script|section|select|small|span|strike|strong|style|sub|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|tt|ul|var|video)\\b",relevance:0},{className:"selector-pseudo",begin:":(visited|valid|root|right|required|read-write|read-only|out-range|optional|only-of-type|only-child|nth-of-type|nth-last-of-type|nth-last-child|nth-child|not|link|left|last-of-type|last-child|lang|invalid|indeterminate|in-range|hover|focus|first-of-type|first-line|first-letter|first-child|first|enabled|empty|disabled|default|checked|before|after|active)"},{className:"selector-pseudo",begin:"::(after|before|choices|first-letter|first-line|repeat-index|repeat-item|selection|value)"},t,{className:"attribute",begin:"\\b(src|z-index|word-wrap|word-spacing|word-break|width|widows|white-space|visibility|vertical-align|unicode-bidi|transition-timing-function|transition-property|transition-duration|transition-delay|transition|transform-style|transform-origin|transform|top|text-underline-position|text-transform|text-shadow|text-rendering|text-overflow|text-indent|text-decoration-style|text-decoration-line|text-decoration-color|text-decoration|text-align-last|text-align|tab-size|table-layout|right|resize|quotes|position|pointer-events|perspective-origin|perspective|page-break-inside|page-break-before|page-break-after|padding-top|padding-right|padding-left|padding-bottom|padding|overflow-y|overflow-x|overflow-wrap|overflow|outline-width|outline-style|outline-offset|outline-color|outline|orphans|order|opacity|object-position|object-fit|normal|none|nav-up|nav-right|nav-left|nav-index|nav-down|min-width|min-height|max-width|max-height|mask|marks|margin-top|margin-right|margin-left|margin-bottom|margin|list-style-type|list-style-position|list-style-image|list-style|line-height|letter-spacing|left|justify-content|initial|inherit|ime-mode|image-orientation|image-resolution|image-rendering|icon|hyphens|height|font-weight|font-variant-ligatures|font-variant|font-style|font-stretch|font-size-adjust|font-size|font-language-override|font-kerning|font-feature-settings|font-family|font|float|flex-wrap|flex-shrink|flex-grow|flex-flow|flex-direction|flex-basis|flex|filter|empty-cells|display|direction|cursor|counter-reset|counter-increment|content|column-width|column-span|column-rule-width|column-rule-style|column-rule-color|column-rule|column-gap|column-fill|column-count|columns|color|clip-path|clip|clear|caption-side|break-inside|break-before|break-after|box-sizing|box-shadow|box-decoration-break|bottom|border-width|border-top-width|border-top-style|border-top-right-radius|border-top-left-radius|border-top-color|border-top|border-style|border-spacing|border-right-width|border-right-style|border-right-color|border-right|border-radius|border-left-width|border-left-style|border-left-color|border-left|border-image-width|border-image-source|border-image-slice|border-image-repeat|border-image-outset|border-image|border-color|border-collapse|border-bottom-width|border-bottom-style|border-bottom-right-radius|border-bottom-left-radius|border-bottom-color|border-bottom|border|background-size|background-repeat|background-position|background-origin|background-image|background-color|background-clip|background-attachment|background-blend-mode|background|backface-visibility|auto|animation-timing-function|animation-play-state|animation-name|animation-iteration-count|animation-fill-mode|animation-duration|animation-direction|animation-delay|animation|align-self|align-items|align-content)\\b",illegal:"[^\\s]"},{begin:"\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b"},{begin:":",end:";",contains:[t,i,e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,{className:"meta",begin:"!important"}]},{begin:"@(page|font-face)",lexemes:"@[a-z-]+",keywords:"@page @font-face"},{begin:"@",end:"[{;]",returnBegin:!0,keywords:"and or not only",contains:[{begin:"@[a-z-]+",className:"keyword"},t,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,i,e.CSS_NUMBER_MODE]}]}}}());hljs.registerLanguage("r",function(){"use strict";return function(e){var n="([a-zA-Z]|\\.[a-zA-Z.])[a-zA-Z0-9._]*";return{name:"R",contains:[e.HASH_COMMENT_MODE,{begin:n,keywords:{$pattern:n,keyword:"function if in break next repeat else for return switch while try tryCatch stop warning require library attach detach source setMethod setGeneric setGroupGeneric setClass ...",literal:"NULL NA TRUE FALSE T F Inf NaN NA_integer_|10 NA_real_|10 NA_character_|10 NA_complex_|10"},relevance:0},{className:"number",begin:"0[xX][0-9a-fA-F]+[Li]?\\b",relevance:0},{className:"number",begin:"\\d+(?:[eE][+\\-]?\\d*)?L\\b",relevance:0},{className:"number",begin:"\\d+\\.(?!\\d)(?:i\\b)?",relevance:0},{className:"number",begin:"\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d*)?i?\\b",relevance:0},{className:"number",begin:"\\.\\d+(?:[eE][+\\-]?\\d*)?i?\\b",relevance:0},{begin:"`",end:"`",relevance:0},{className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{begin:'"',end:'"'},{begin:"'",end:"'"}]}]}}}());hljs.registerLanguage("sql",function(){"use strict";return function(e){var t=e.COMMENT("--","$");return{name:"SQL",case_insensitive:!0,illegal:/[<>{}*]/,contains:[{beginKeywords:"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup revoke comment values with",end:/;/,endsWithParent:!0,keywords:{$pattern:/[\w\.]+/,keyword:"as abort abs absolute acc acce accep accept access accessed accessible account acos action activate add addtime admin administer advanced advise aes_decrypt aes_encrypt after agent aggregate ali alia alias all allocate allow alter always analyze ancillary and anti any anydata anydataset anyschema anytype apply archive archived archivelog are as asc ascii asin assembly assertion associate asynchronous at atan atn2 attr attri attrib attribu attribut attribute attributes audit authenticated authentication authid authors auto autoallocate autodblink autoextend automatic availability avg backup badfile basicfile before begin beginning benchmark between bfile bfile_base big bigfile bin binary_double binary_float binlog bit_and bit_count bit_length bit_or bit_xor bitmap blob_base block blocksize body both bound bucket buffer_cache buffer_pool build bulk by byte byteordermark bytes cache caching call calling cancel capacity cascade cascaded case cast catalog category ceil ceiling chain change changed char_base char_length character_length characters characterset charindex charset charsetform charsetid check checksum checksum_agg child choose chr chunk class cleanup clear client clob clob_base clone close cluster_id cluster_probability cluster_set clustering coalesce coercibility col collate collation collect colu colum column column_value columns columns_updated comment commit compact compatibility compiled complete composite_limit compound compress compute concat concat_ws concurrent confirm conn connec connect connect_by_iscycle connect_by_isleaf connect_by_root connect_time connection consider consistent constant constraint constraints constructor container content contents context contributors controlfile conv convert convert_tz corr corr_k corr_s corresponding corruption cos cost count count_big counted covar_pop covar_samp cpu_per_call cpu_per_session crc32 create creation critical cross cube cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime customdatum cycle data database databases datafile datafiles datalength date_add date_cache date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts day day_to_second dayname dayofmonth dayofweek dayofyear days db_role_change dbtimezone ddl deallocate declare decode decompose decrement decrypt deduplicate def defa defau defaul default defaults deferred defi defin define degrees delayed delegate delete delete_all delimited demand dense_rank depth dequeue des_decrypt des_encrypt des_key_file desc descr descri describ describe descriptor deterministic diagnostics difference dimension direct_load directory disable disable_all disallow disassociate discardfile disconnect diskgroup distinct distinctrow distribute distributed div do document domain dotnet double downgrade drop dumpfile duplicate duration each edition editionable editions element ellipsis else elsif elt empty enable enable_all enclosed encode encoding encrypt end end-exec endian enforced engine engines enqueue enterprise entityescaping eomonth error errors escaped evalname evaluate event eventdata events except exception exceptions exchange exclude excluding execu execut execute exempt exists exit exp expire explain explode export export_set extended extent external external_1 external_2 externally extract failed failed_login_attempts failover failure far fast feature_set feature_value fetch field fields file file_name_convert filesystem_like_logging final finish first first_value fixed flash_cache flashback floor flush following follows for forall force foreign form forma format found found_rows freelist freelists freepools fresh from from_base64 from_days ftp full function general generated get get_format get_lock getdate getutcdate global global_name globally go goto grant grants greatest group group_concat group_id grouping grouping_id groups gtid_subtract guarantee guard handler hash hashkeys having hea head headi headin heading heap help hex hierarchy high high_priority hosts hour hours http id ident_current ident_incr ident_seed identified identity idle_time if ifnull ignore iif ilike ilm immediate import in include including increment index indexes indexing indextype indicator indices inet6_aton inet6_ntoa inet_aton inet_ntoa infile initial initialized initially initrans inmemory inner innodb input insert install instance instantiable instr interface interleaved intersect into invalidate invisible is is_free_lock is_ipv4 is_ipv4_compat is_not is_not_null is_used_lock isdate isnull isolation iterate java join json json_exists keep keep_duplicates key keys kill language large last last_day last_insert_id last_value lateral lax lcase lead leading least leaves left len lenght length less level levels library like like2 like4 likec limit lines link list listagg little ln load load_file lob lobs local localtime localtimestamp locate locator lock locked log log10 log2 logfile logfiles logging logical logical_reads_per_call logoff logon logs long loop low low_priority lower lpad lrtrim ltrim main make_set makedate maketime managed management manual map mapping mask master master_pos_wait match matched materialized max maxextents maximize maxinstances maxlen maxlogfiles maxloghistory maxlogmembers maxsize maxtrans md5 measures median medium member memcompress memory merge microsecond mid migration min minextents minimum mining minus minute minutes minvalue missing mod mode model modification modify module monitoring month months mount move movement multiset mutex name name_const names nan national native natural nav nchar nclob nested never new newline next nextval no no_write_to_binlog noarchivelog noaudit nobadfile nocheck nocompress nocopy nocycle nodelay nodiscardfile noentityescaping noguarantee nokeep nologfile nomapping nomaxvalue nominimize nominvalue nomonitoring none noneditionable nonschema noorder nopr nopro noprom nopromp noprompt norely noresetlogs noreverse normal norowdependencies noschemacheck noswitch not nothing notice notnull notrim novalidate now nowait nth_value nullif nulls num numb numbe nvarchar nvarchar2 object ocicoll ocidate ocidatetime ociduration ociinterval ociloblocator ocinumber ociref ocirefcursor ocirowid ocistring ocitype oct octet_length of off offline offset oid oidindex old on online only opaque open operations operator optimal optimize option optionally or oracle oracle_date oradata ord ordaudio orddicom orddoc order ordimage ordinality ordvideo organization orlany orlvary out outer outfile outline output over overflow overriding package pad parallel parallel_enable parameters parent parse partial partition partitions pascal passing password password_grace_time password_lock_time password_reuse_max password_reuse_time password_verify_function patch path patindex pctincrease pctthreshold pctused pctversion percent percent_rank percentile_cont percentile_disc performance period period_add period_diff permanent physical pi pipe pipelined pivot pluggable plugin policy position post_transaction pow power pragma prebuilt precedes preceding precision prediction prediction_cost prediction_details prediction_probability prediction_set prepare present preserve prior priority private private_sga privileges procedural procedure procedure_analyze processlist profiles project prompt protection public publishingservername purge quarter query quick quiesce quota quotename radians raise rand range rank raw read reads readsize rebuild record records recover recovery recursive recycle redo reduced ref reference referenced references referencing refresh regexp_like register regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy reject rekey relational relative relaylog release release_lock relies_on relocate rely rem remainder rename repair repeat replace replicate replication required reset resetlogs resize resource respect restore restricted result result_cache resumable resume retention return returning returns reuse reverse revoke right rlike role roles rollback rolling rollup round row row_count rowdependencies rowid rownum rows rtrim rules safe salt sample save savepoint sb1 sb2 sb4 scan schema schemacheck scn scope scroll sdo_georaster sdo_topo_geometry search sec_to_time second seconds section securefile security seed segment select self semi sequence sequential serializable server servererror session session_user sessions_per_user set sets settings sha sha1 sha2 share shared shared_pool short show shrink shutdown si_averagecolor si_colorhistogram si_featurelist si_positionalcolor si_stillimage si_texture siblings sid sign sin size size_t sizes skip slave sleep smalldatetimefromparts smallfile snapshot some soname sort soundex source space sparse spfile split sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_small_result sql_variant_property sqlcode sqldata sqlerror sqlname sqlstate sqrt square standalone standby start starting startup statement static statistics stats_binomial_test stats_crosstab stats_ks_test stats_mode stats_mw_test stats_one_way_anova stats_t_test_ stats_t_test_indep stats_t_test_one stats_t_test_paired stats_wsr_test status std stddev stddev_pop stddev_samp stdev stop storage store stored str str_to_date straight_join strcmp strict string struct stuff style subdate subpartition subpartitions substitutable substr substring subtime subtring_index subtype success sum suspend switch switchoffset switchover sync synchronous synonym sys sys_xmlagg sysasm sysaux sysdate sysdatetimeoffset sysdba sysoper system system_user sysutcdatetime table tables tablespace tablesample tan tdo template temporary terminated tertiary_weights test than then thread through tier ties time time_format time_zone timediff timefromparts timeout timestamp timestampadd timestampdiff timezone_abbr timezone_minute timezone_region to to_base64 to_date to_days to_seconds todatetimeoffset trace tracking transaction transactional translate translation treat trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse type ub1 ub2 ub4 ucase unarchived unbounded uncompress under undo unhex unicode uniform uninstall union unique unix_timestamp unknown unlimited unlock unnest unpivot unrecoverable unsafe unsigned until untrusted unusable unused update updated upgrade upped upper upsert url urowid usable usage use use_stored_outlines user user_data user_resources users using utc_date utc_timestamp uuid uuid_short validate validate_password_strength validation valist value values var var_samp varcharc vari varia variab variabl variable variables variance varp varraw varrawc varray verify version versions view virtual visible void wait wallet warning warnings week weekday weekofyear wellformed when whene whenev wheneve whenever where while whitespace window with within without work wrapped xdb xml xmlagg xmlattributes xmlcast xmlcolattval xmlelement xmlexists xmlforest xmlindex xmlnamespaces xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltype xor year year_to_month years yearweek",literal:"true false null unknown",built_in:"array bigint binary bit blob bool boolean char character date dec decimal float int int8 integer interval number numeric real record serial serial8 smallint text time timestamp tinyint varchar varchar2 varying void"},contains:[{className:"string",begin:"'",end:"'",contains:[{begin:"''"}]},{className:"string",begin:'"',end:'"',contains:[{begin:'""'}]},{className:"string",begin:"`",end:"`"},e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE,t,e.HASH_COMMENT_MODE]},e.C_BLOCK_COMMENT_MODE,t,e.HASH_COMMENT_MODE]}}}());hljs.registerLanguage("c",function(){"use strict";return function(e){var n=e.getLanguage("c-like").rawDefinition();return n.name="C",n.aliases=["c","h"],n}}());hljs.registerLanguage("json",function(){"use strict";return function(n){var e={literal:"true false null"},i=[n.C_LINE_COMMENT_MODE,n.C_BLOCK_COMMENT_MODE],t=[n.QUOTE_STRING_MODE,n.C_NUMBER_MODE],a={end:",",endsWithParent:!0,excludeEnd:!0,contains:t,keywords:e},l={begin:"{",end:"}",contains:[{className:"attr",begin:/"/,end:/"/,contains:[n.BACKSLASH_ESCAPE],illegal:"\\n"},n.inherit(a,{begin:/:/})].concat(i),illegal:"\\S"},s={begin:"\\[",end:"\\]",contains:[n.inherit(a)],illegal:"\\S"};return t.push(l,s),i.forEach((function(n){t.push(n)})),{name:"JSON",contains:t,keywords:e,illegal:"\\S"}}}());hljs.registerLanguage("python-repl",function(){"use strict";return function(n){return{aliases:["pycon"],contains:[{className:"meta",starts:{end:/ |$/,starts:{end:"$",subLanguage:"python"}},variants:[{begin:/^>>>(?=[ ]|$)/},{begin:/^\.\.\.(?=[ ]|$)/}]}]}}}());hljs.registerLanguage("markdown",function(){"use strict";return function(n){const e={begin:"<",end:">",subLanguage:"xml",relevance:0},a={begin:"\\[.+?\\][\\(\\[].*?[\\)\\]]",returnBegin:!0,contains:[{className:"string",begin:"\\[",end:"\\]",excludeBegin:!0,returnEnd:!0,relevance:0},{className:"link",begin:"\\]\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0},{className:"symbol",begin:"\\]\\[",end:"\\]",excludeBegin:!0,excludeEnd:!0}],relevance:10},i={className:"strong",contains:[],variants:[{begin:/_{2}/,end:/_{2}/},{begin:/\*{2}/,end:/\*{2}/}]},s={className:"emphasis",contains:[],variants:[{begin:/\*(?!\*)/,end:/\*/},{begin:/_(?!_)/,end:/_/,relevance:0}]};i.contains.push(s),s.contains.push(i);var c=[e,a];return i.contains=i.contains.concat(c),s.contains=s.contains.concat(c),{name:"Markdown",aliases:["md","mkdown","mkd"],contains:[{className:"section",variants:[{begin:"^#{1,6}",end:"$",contains:c=c.concat(i,s)},{begin:"(?=^.+?\\n[=-]{2,}$)",contains:[{begin:"^[=-]*$"},{begin:"^",end:"\\n",contains:c}]}]},e,{className:"bullet",begin:"^[ \t]*([*+-]|(\\d+\\.))(?=\\s+)",end:"\\s+",excludeEnd:!0},i,s,{className:"quote",begin:"^>\\s+",contains:c,end:"$"},{className:"code",variants:[{begin:"(`{3,})(.|\\n)*?\\1`*[ ]*"},{begin:"(~{3,})(.|\\n)*?\\1~*[ ]*"},{begin:"```",end:"```+[ ]*$"},{begin:"~~~",end:"~~~+[ ]*$"},{begin:"`.+?`"},{begin:"(?=^( {4}|\\t))",contains:[{begin:"^( {4}|\\t)",end:"(\\n)$"}],relevance:0}]},{begin:"^[-\\*]{3,}",end:"$"},a,{begin:/^\[[^\n]+\]:/,returnBegin:!0,contains:[{className:"symbol",begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0},{className:"link",begin:/:\s*/,end:/$/,excludeBegin:!0}]}]}}}());hljs.registerLanguage("javascript",function(){"use strict";const e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],n=["true","false","null","undefined","NaN","Infinity"],a=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);function s(e){return r("(?=",e,")")}function r(...e){return e.map(e=>(function(e){return e?"string"==typeof e?e:e.source:null})(e)).join("")}return function(t){var i="[A-Za-z$_][0-9A-Za-z$_]*",c={begin:/<[A-Za-z0-9\\._:-]+/,end:/\/[A-Za-z0-9\\._:-]+>|\/>/},o={$pattern:"[A-Za-z$_][0-9A-Za-z$_]*",keyword:e.join(" "),literal:n.join(" "),built_in:a.join(" ")},l={className:"number",variants:[{begin:"\\b(0[bB][01]+)n?"},{begin:"\\b(0[oO][0-7]+)n?"},{begin:t.C_NUMBER_RE+"n?"}],relevance:0},E={className:"subst",begin:"\\$\\{",end:"\\}",keywords:o,contains:[]},d={begin:"html`",end:"",starts:{end:"`",returnEnd:!1,contains:[t.BACKSLASH_ESCAPE,E],subLanguage:"xml"}},g={begin:"css`",end:"",starts:{end:"`",returnEnd:!1,contains:[t.BACKSLASH_ESCAPE,E],subLanguage:"css"}},u={className:"string",begin:"`",end:"`",contains:[t.BACKSLASH_ESCAPE,E]};E.contains=[t.APOS_STRING_MODE,t.QUOTE_STRING_MODE,d,g,u,l,t.REGEXP_MODE];var b=E.contains.concat([{begin:/\(/,end:/\)/,contains:["self"].concat(E.contains,[t.C_BLOCK_COMMENT_MODE,t.C_LINE_COMMENT_MODE])},t.C_BLOCK_COMMENT_MODE,t.C_LINE_COMMENT_MODE]),_={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:b};return{name:"JavaScript",aliases:["js","jsx","mjs","cjs"],keywords:o,contains:[t.SHEBANG({binary:"node",relevance:5}),{className:"meta",relevance:10,begin:/^\s*['"]use (strict|asm)['"]/},t.APOS_STRING_MODE,t.QUOTE_STRING_MODE,d,g,u,t.C_LINE_COMMENT_MODE,t.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{className:"doctag",begin:"@[A-Za-z]+",contains:[{className:"type",begin:"\\{",end:"\\}",relevance:0},{className:"variable",begin:i+"(?=\\s*(-)|$)",endsParent:!0,relevance:0},{begin:/(?=[^\n])\s/,relevance:0}]}]}),t.C_BLOCK_COMMENT_MODE,l,{begin:r(/[{,\n]\s*/,s(r(/(((\/\/.*)|(\/\*(.|\n)*\*\/))\s*)*/,i+"\\s*:"))),relevance:0,contains:[{className:"attr",begin:i+s("\\s*:"),relevance:0}]},{begin:"("+t.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",contains:[t.C_LINE_COMMENT_MODE,t.C_BLOCK_COMMENT_MODE,t.REGEXP_MODE,{className:"function",begin:"(\\([^(]*(\\([^(]*(\\([^(]*\\))?\\))?\\)|"+t.UNDERSCORE_IDENT_RE+")\\s*=>",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:t.UNDERSCORE_IDENT_RE},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:o,contains:b}]}]},{begin:/,/,relevance:0},{className:"",begin:/\s/,end:/\s*/,skip:!0},{variants:[{begin:"<>",end:""},{begin:c.begin,end:c.end}],subLanguage:"xml",contains:[{begin:c.begin,end:c.end,skip:!0,contains:["self"]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/\{/,excludeEnd:!0,contains:[t.inherit(t.TITLE_MODE,{begin:i}),_],illegal:/\[|%/},{begin:/\$[(.]/},t.METHOD_GUARD,{className:"class",beginKeywords:"class",end:/[{;=]/,excludeEnd:!0,illegal:/[:"\[\]]/,contains:[{beginKeywords:"extends"},t.UNDERSCORE_TITLE_MODE]},{beginKeywords:"constructor",end:/\{/,excludeEnd:!0},{begin:"(get|set)\\s+(?="+i+"\\()",end:/{/,keywords:"get set",contains:[t.inherit(t.TITLE_MODE,{begin:i}),{begin:/\(\)/},_]}],illegal:/#(?!!)/}}}());hljs.registerLanguage("typescript",function(){"use strict";const e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],n=["true","false","null","undefined","NaN","Infinity"],a=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);return function(r){var t={$pattern:"[A-Za-z$_][0-9A-Za-z$_]*",keyword:e.concat(["type","namespace","typedef","interface","public","private","protected","implements","declare","abstract","readonly"]).join(" "),literal:n.join(" "),built_in:a.concat(["any","void","number","boolean","string","object","never","enum"]).join(" ")},s={className:"meta",begin:"@[A-Za-z$_][0-9A-Za-z$_]*"},i={className:"number",variants:[{begin:"\\b(0[bB][01]+)n?"},{begin:"\\b(0[oO][0-7]+)n?"},{begin:r.C_NUMBER_RE+"n?"}],relevance:0},o={className:"subst",begin:"\\$\\{",end:"\\}",keywords:t,contains:[]},c={begin:"html`",end:"",starts:{end:"`",returnEnd:!1,contains:[r.BACKSLASH_ESCAPE,o],subLanguage:"xml"}},l={begin:"css`",end:"",starts:{end:"`",returnEnd:!1,contains:[r.BACKSLASH_ESCAPE,o],subLanguage:"css"}},E={className:"string",begin:"`",end:"`",contains:[r.BACKSLASH_ESCAPE,o]};o.contains=[r.APOS_STRING_MODE,r.QUOTE_STRING_MODE,c,l,E,i,r.REGEXP_MODE];var d={begin:"\\(",end:/\)/,keywords:t,contains:["self",r.QUOTE_STRING_MODE,r.APOS_STRING_MODE,r.NUMBER_MODE]},u={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:t,contains:[r.C_LINE_COMMENT_MODE,r.C_BLOCK_COMMENT_MODE,s,d]};return{name:"TypeScript",aliases:["ts"],keywords:t,contains:[r.SHEBANG(),{className:"meta",begin:/^\s*['"]use strict['"]/},r.APOS_STRING_MODE,r.QUOTE_STRING_MODE,c,l,E,r.C_LINE_COMMENT_MODE,r.C_BLOCK_COMMENT_MODE,i,{begin:"("+r.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",contains:[r.C_LINE_COMMENT_MODE,r.C_BLOCK_COMMENT_MODE,r.REGEXP_MODE,{className:"function",begin:"(\\([^(]*(\\([^(]*(\\([^(]*\\))?\\))?\\)|"+r.UNDERSCORE_IDENT_RE+")\\s*=>",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:r.UNDERSCORE_IDENT_RE},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:t,contains:d.contains}]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/[\{;]/,excludeEnd:!0,keywords:t,contains:["self",r.inherit(r.TITLE_MODE,{begin:"[A-Za-z$_][0-9A-Za-z$_]*"}),u],illegal:/%/,relevance:0},{beginKeywords:"constructor",end:/[\{;]/,excludeEnd:!0,contains:["self",u]},{begin:/module\./,keywords:{built_in:"module"},relevance:0},{beginKeywords:"module",end:/\{/,excludeEnd:!0},{beginKeywords:"interface",end:/\{/,excludeEnd:!0,keywords:"interface extends"},{begin:/\$[(.]/},{begin:"\\."+r.IDENT_RE,relevance:0},s,d]}}}());hljs.registerLanguage("plaintext",function(){"use strict";return function(t){return{name:"Plain text",aliases:["text","txt"],disableAutodetect:!0}}}());hljs.registerLanguage("less",function(){"use strict";return function(e){var n="([\\w-]+|@{[\\w-]+})",a=[],s=[],t=function(e){return{className:"string",begin:"~?"+e+".*?"+e}},r=function(e,n,a){return{className:e,begin:n,relevance:a}},i={begin:"\\(",end:"\\)",contains:s,relevance:0};s.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,t("'"),t('"'),e.CSS_NUMBER_MODE,{begin:"(url|data-uri)\\(",starts:{className:"string",end:"[\\)\\n]",excludeEnd:!0}},r("number","#[0-9A-Fa-f]+\\b"),i,r("variable","@@?[\\w-]+",10),r("variable","@{[\\w-]+}"),r("built_in","~?`[^`]*?`"),{className:"attribute",begin:"[\\w-]+\\s*:",end:":",returnBegin:!0,excludeEnd:!0},{className:"meta",begin:"!important"});var c=s.concat({begin:"{",end:"}",contains:a}),l={beginKeywords:"when",endsWithParent:!0,contains:[{beginKeywords:"and not"}].concat(s)},o={begin:n+"\\s*:",returnBegin:!0,end:"[;}]",relevance:0,contains:[{className:"attribute",begin:n,end:":",excludeEnd:!0,starts:{endsWithParent:!0,illegal:"[<=$]",relevance:0,contains:s}}]},g={className:"keyword",begin:"@(import|media|charset|font-face|(-[a-z]+-)?keyframes|supports|document|namespace|page|viewport|host)\\b",starts:{end:"[;{}]",returnEnd:!0,contains:s,relevance:0}},d={className:"variable",variants:[{begin:"@[\\w-]+\\s*:",relevance:15},{begin:"@[\\w-]+"}],starts:{end:"[;}]",returnEnd:!0,contains:c}},b={variants:[{begin:"[\\.#:&\\[>]",end:"[;{}]"},{begin:n,end:"{"}],returnBegin:!0,returnEnd:!0,illegal:"[<='$\"]",relevance:0,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,l,r("keyword","all\\b"),r("variable","@{[\\w-]+}"),r("selector-tag",n+"%?",0),r("selector-id","#"+n),r("selector-class","\\."+n,0),r("selector-tag","&",0),{className:"selector-attr",begin:"\\[",end:"\\]"},{className:"selector-pseudo",begin:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{begin:"\\(",end:"\\)",contains:c},{begin:"!important"}]};return a.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,g,d,o,b),{name:"Less",case_insensitive:!0,illegal:"[=>'/<($\"]",contains:a}}}());hljs.registerLanguage("lua",function(){"use strict";return function(e){var t={begin:"\\[=*\\[",end:"\\]=*\\]",contains:["self"]},a=[e.COMMENT("--(?!\\[=*\\[)","$"),e.COMMENT("--\\[=*\\[","\\]=*\\]",{contains:[t],relevance:10})];return{name:"Lua",keywords:{$pattern:e.UNDERSCORE_IDENT_RE,literal:"true false nil",keyword:"and break do else elseif end for goto if in local not or repeat return then until while",built_in:"_G _ENV _VERSION __index __newindex __mode __call __metatable __tostring __len __gc __add __sub __mul __div __mod __pow __concat __unm __eq __lt __le assert collectgarbage dofile error getfenv getmetatable ipairs load loadfile loadstring module next pairs pcall print rawequal rawget rawset require select setfenv setmetatable tonumber tostring type unpack xpcall arg self coroutine resume yield status wrap create running debug getupvalue debug sethook getmetatable gethook setmetatable setlocal traceback setfenv getinfo setupvalue getlocal getregistry getfenv io lines write close flush open output type read stderr stdin input stdout popen tmpfile math log max acos huge ldexp pi cos tanh pow deg tan cosh sinh random randomseed frexp ceil floor rad abs sqrt modf asin min mod fmod log10 atan2 exp sin atan os exit setlocale date getenv difftime remove time clock tmpname rename execute package preload loadlib loaded loaders cpath config path seeall string sub upper len gfind rep find match char dump gmatch reverse byte format gsub lower table setn insert getn foreachi maxn foreach concat sort remove"},contains:a.concat([{className:"function",beginKeywords:"function",end:"\\)",contains:[e.inherit(e.TITLE_MODE,{begin:"([_a-zA-Z]\\w*\\.)*([_a-zA-Z]\\w*:)?[_a-zA-Z]\\w*"}),{className:"params",begin:"\\(",endsWithParent:!0,contains:a}].concat(a)},e.C_NUMBER_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:"string",begin:"\\[=*\\[",end:"\\]=*\\]",contains:[t],relevance:5}])}}}());hljs.registerLanguage("solidity",(()=>{"use strict";function e(){try{return!0}catch(e){return!1}}var a=/-?(\b0[xX]([a-fA-F0-9]_?)*[a-fA-F0-9]|(\b[1-9](_?\d)*(\.((\d_?)*\d)?)?|\.\d(_?\d)*)([eE][-+]?\d(_?\d)*)?|\b0)(?!\w|\$)/;e()&&(a=a.source.replace(/\\b/g,"(?{var a=r(e),o=l(e),c=/[A-Za-z_$][A-Za-z_$0-9.]*/,d=e.inherit(e.TITLE_MODE,{begin:/[A-Za-z$_][0-9A-Za-z$_]*/,lexemes:c,keywords:n}),u={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,lexemes:c,keywords:n,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,o,s]},_={className:"operator",begin:/:=|->/};return{keywords:n,lexemes:c,contains:[a,o,i,t,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,s,_,{className:"function",lexemes:c,beginKeywords:"function",end:"{",excludeEnd:!0,contains:[d,u,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,_]}]}},solAposStringMode:r,solQuoteStringMode:l,HEX_APOS_STRING_MODE:i,HEX_QUOTE_STRING_MODE:t,SOL_NUMBER:s,isNegativeLookbehindAvailable:e};const{baseAssembly:c,solAposStringMode:d,solQuoteStringMode:u,HEX_APOS_STRING_MODE:_,HEX_QUOTE_STRING_MODE:m,SOL_NUMBER:b,isNegativeLookbehindAvailable:g}=o;return e=>{for(var a=d(e),s=u(e),n=[],i=0;i<32;i++)n[i]=i+1;var t=n.map((e=>8*e)),r=[];for(i=0;i<=80;i++)r[i]=i;var l=n.map((e=>"bytes"+e)).join(" ")+" ",o=t.map((e=>"uint"+e)).join(" ")+" ",E=t.map((e=>"int"+e)).join(" ")+" ",M=[].concat.apply([],t.map((e=>r.map((a=>e+"x"+a))))),p={keyword:"var bool string int uint "+E+o+"byte bytes "+l+"fixed ufixed "+M.map((e=>"fixed"+e)).join(" ")+" "+M.map((e=>"ufixed"+e)).join(" ")+" enum struct mapping address new delete if else for while continue break return throw emit try catch revert unchecked _ function modifier event constructor fallback receive error virtual override constant immutable anonymous indexed storage memory calldata external public internal payable pure view private returns import from as using global pragma contract interface library is abstract type assembly",literal:"true false wei gwei szabo finney ether seconds minutes hours days weeks years",built_in:"self this super selfdestruct suicide now msg block tx abi blockhash gasleft assert require Error Panic sha3 sha256 keccak256 ripemd160 ecrecover addmod mulmod log0 log1 log2 log3 log4"},O={className:"operator",begin:/[+\-!~*\/%<>&^|=]/},C=/[A-Za-z_$][A-Za-z_$0-9]*/,N={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,lexemes:C,keywords:p,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,s,b,"self"]},f={begin:/\.\s*/,end:/[^A-Za-z0-9$_\.]/,excludeBegin:!0,excludeEnd:!0,keywords:{built_in:"gas value selector address length push pop send transfer call callcode delegatecall staticcall balance code codehash wrap unwrap name creationCode runtimeCode interfaceId min max"},relevance:2},y=e.inherit(e.TITLE_MODE,{begin:/[A-Za-z$_][0-9A-Za-z$_]*/,lexemes:C,keywords:p}),w={className:"built_in",begin:(g()?"(?{"use strict";function e(){try{return!0}catch(e){return!1}}var a=/-?(\b0[xX]([a-fA-F0-9]_?)*[a-fA-F0-9]|(\b[1-9](_?\d)*(\.((\d_?)*\d)?)?|\.\d(_?\d)*)([eE][-+]?\d(_?\d)*)?|\b0)(?!\w|\$)/;e()&&(a=a.source.replace(/\\b/g,"(?{var a=d(e),n=r(e),o=/[A-Za-z_$][A-Za-z_$0-9.]*/,c=e.inherit(e.TITLE_MODE,{begin:/[A-Za-z$_][0-9A-Za-z$_]*/,lexemes:o,keywords:t}),u={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,lexemes:o,keywords:t,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,n,s]},p={className:"operator",begin:/:=|->/};return{keywords:t,lexemes:o,contains:[a,n,i,l,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,s,p,{className:"function",lexemes:o,beginKeywords:"function",end:"{",excludeEnd:!0,contains:[c,u,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,p]}]}},solAposStringMode:d,solQuoteStringMode:r,HEX_APOS_STRING_MODE:i,HEX_QUOTE_STRING_MODE:l,SOL_NUMBER:s,isNegativeLookbehindAvailable:e};const{SOL_ASSEMBLY_KEYWORDS:o,baseAssembly:c,isNegativeLookbehindAvailable:u}=n;return e=>{var a={keyword:o.keyword+" object code data",built_in:o.built_in+" datasize dataoffset datacopy setimmutable loadimmutable linkersymbol memoryguard",literal:o.literal},s=/\bverbatim_[1-9]?[0-9]i_[1-9]?[0-9]o\b(?!\$)/;u()&&(s=s.source.replace(/\\b/,"(?" if err == nil && method != nil { - methodName = method.Name + methodName = method.Sig } // Next decode our arguments (we jump four bytes to skip the function selector) diff --git a/fuzzing/config/config.go b/fuzzing/config/config.go index eac6c371..2fbb1e64 100644 --- a/fuzzing/config/config.go +++ b/fuzzing/config/config.go @@ -3,15 +3,21 @@ package config import ( "encoding/json" "errors" - "os" - "github.com/crytic/medusa/chain/config" - "github.com/rs/zerolog" - "github.com/crytic/medusa/compilation" + "github.com/crytic/medusa/logging" "github.com/crytic/medusa/utils" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/rs/zerolog" + "math/big" + "os" ) +// The following directives will be picked up by the `go generate` command to generate JSON marshaling code from +// templates defined below. They should be preserved for re-use in case we change our structures. +//go:generate go get github.com/fjl/gencodec +//go:generate go run github.com/fjl/gencodec -type FuzzingConfig -field-override fuzzingConfigMarshaling -out gen_fuzzing_config.go + type ProjectConfig struct { // Fuzzing describes the configuration used in fuzzing campaigns. Fuzzing FuzzingConfig `json:"fuzzing"` @@ -32,14 +38,17 @@ type FuzzingConfig struct { // so that memory from its underlying chain is freed. WorkerResetLimit int `json:"workerResetLimit"` - // Timeout describes a time in seconds for which the fuzzing operation should run. Providing negative or zero value - // will result in no timeout. + // Timeout describes a time threshold in seconds for which the fuzzing operation should run. Providing negative or + // zero value will result in no timeout. Timeout int `json:"timeout"` // TestLimit describes a threshold for the number of transactions to test, after which it will exit. This number // must be non-negative. A zero value indicates the test limit should not be enforced. TestLimit uint64 `json:"testLimit"` + // ShrinkLimit describes a threshold for the iterations (call sequence tests) which shrinking should perform. + ShrinkLimit uint64 `json:"shrinkLimit"` + // CallSequenceLength describes the maximum length a transaction sequence can be generated as. CallSequenceLength int `json:"callSequenceLength"` @@ -50,10 +59,15 @@ type FuzzingConfig struct { // CoverageEnabled describes whether to use coverage-guided fuzzing CoverageEnabled bool `json:"coverageEnabled"` - // DeploymentOrder determines the order in which the contracts should be deployed - DeploymentOrder []string `json:"deploymentOrder"` + // TargetContracts are the target contracts for fuzz testing + TargetContracts []string `json:"targetContracts"` + + // TargetContractsBalances holds the amount of wei that should be sent during deployment for one or more contracts in + // TargetContracts + TargetContractsBalances []*big.Int `json:"targetContractsBalances"` - // Constructor arguments for contracts deployment. It is available only in init mode + // ConstructorArgs holds the constructor arguments for TargetContracts deployments. It is available via the project + // configuration ConstructorArgs map[string]map[string]any `json:"constructorArgs"` // DeployerAddress describe the account address to be used to deploy contracts. @@ -85,6 +99,13 @@ type FuzzingConfig struct { TestChainConfig config.TestChainConfig `json:"chainConfig"` } +// fuzzingConfigMarshaling is a structure that overrides field types during JSON marshaling. It allows FuzzingConfig to +// have its custom marshaling methods auto-generated and will handle type conversions for serialization purposes. +// For example, this enables serialization of big.Int but specifying a different field type to control serialization. +type fuzzingConfigMarshaling struct { + TargetContractsBalances []*hexutil.Big +} + // TestingConfig describes the configuration options used for testing type TestingConfig struct { // StopOnFailedTest describes whether the fuzzing.Fuzzer should stop after detecting the first failed test. @@ -111,7 +132,7 @@ type TestingConfig struct { AssertionTesting AssertionTestingConfig `json:"assertionTesting"` // PropertyTesting describes the configuration used for property testing. - PropertyTesting PropertyTestConfig `json:"propertyTesting"` + PropertyTesting PropertyTestingConfig `json:"propertyTesting"` // OptimizationTesting describes the configuration used for optimization testing. OptimizationTesting OptimizationTestingConfig `json:"optimizationTesting"` @@ -125,13 +146,12 @@ type AssertionTestingConfig struct { // TestViewMethods dictates whether constant/pure/view methods should be tested. TestViewMethods bool `json:"testViewMethods"` - // AssertionModes describes the various panic codes that can be enabled and be treated as a "failing case" - AssertionModes AssertionModesConfig `json:"assertionModes"` + // PanicCodeConfig describes the various panic codes that can be enabled and be treated as a "failing case" + PanicCodeConfig PanicCodeConfig `json:"panicCodeConfig"` } -// AssertionModesConfig describes the configuration options for the various modes that can be enabled for assertion -// testing -type AssertionModesConfig struct { +// PanicCodeConfig describes the various panic codes that can be enabled and be treated as a failing assertion test +type PanicCodeConfig struct { // FailOnCompilerInsertedPanic describes whether a generic compiler inserted panic should be treated as a failing case FailOnCompilerInsertedPanic bool `json:"failOnCompilerInsertedPanic"` @@ -163,8 +183,8 @@ type AssertionModesConfig struct { FailOnCallUninitializedVariable bool `json:"failOnCallUninitializedVariable"` } -// PropertyTestConfig describes the configuration options used for property testing -type PropertyTestConfig struct { +// PropertyTestingConfig describes the configuration options used for property testing +type PropertyTestingConfig struct { // Enabled describes whether testing is enabled. Enabled bool `json:"enabled"` @@ -255,6 +275,12 @@ func (p *ProjectConfig) WriteToFile(path string) error { // Validate validates that the ProjectConfig meets certain requirements. // Returns an error if one occurs. func (p *ProjectConfig) Validate() error { + // Create logger instance if global logger is available + logger := logging.NewLogger(zerolog.Disabled) + if logging.GlobalLogger != nil { + logger = logging.GlobalLogger.NewSubLogger("module", "fuzzer config") + } + // Verify the worker count is a positive number. if p.Fuzzing.Workers <= 0 { return errors.New("project configuration must specify a positive number for the worker count") @@ -262,7 +288,7 @@ func (p *ProjectConfig) Validate() error { // Verify that the sequence length is a positive number if p.Fuzzing.CallSequenceLength <= 0 { - return errors.New("project configuration must specify a positive number for the transaction sequence length") + return errors.New("project configuration must specify a positive number for the transaction sequence lengt") } // Verify the worker reset limit is a positive number @@ -270,12 +296,30 @@ func (p *ProjectConfig) Validate() error { return errors.New("project configuration must specify a positive number for the worker reset limit") } + // Verify timeout + if p.Fuzzing.Timeout < 0 { + return errors.New("project configuration must specify a positive number for the timeout") + } + // Verify gas limits are appropriate if p.Fuzzing.BlockGasLimit < p.Fuzzing.TransactionGasLimit { return errors.New("project configuration must specify a block gas limit which is not less than the transaction gas limit") } if p.Fuzzing.BlockGasLimit == 0 || p.Fuzzing.TransactionGasLimit == 0 { - return errors.New("project configuration must specify a block and transaction gas limit which is non-zero") + return errors.New("project configuration must specify a block and transaction gas limit which are non-zero") + } + + // Log warning if max block delay is zero + if p.Fuzzing.MaxBlockNumberDelay == 0 { + logger.Warn("The maximum block number delay is set to zero. Please be aware that transactions will " + + "always be fit in the same block until the block gas limit is reached and that the block number will always " + + "increment by one.") + } + + // Log warning if max timestamp delay is zero + if p.Fuzzing.MaxBlockTimestampDelay == 0 { + logger.Warn("The maximum timestamp delay is set to zero. Please be aware that block time jumps will " + + "always be exactly one.") } // Verify that senders are well-formed addresses @@ -288,17 +332,10 @@ func (p *ProjectConfig) Validate() error { return errors.New("project configuration must specify only a well-formed deployer address") } - // Verify property testing fields. - if p.Fuzzing.Testing.PropertyTesting.Enabled { - // Test prefixes must be supplied if property testing is enabled. - if len(p.Fuzzing.Testing.PropertyTesting.TestPrefixes) == 0 { - return errors.New("project configuration must specify test name prefixes if property testing is enabled") - } - } - // Ensure that the log level is a valid one - if _, err := zerolog.ParseLevel(p.Logging.Level.String()); err != nil { - return err + level, err := zerolog.ParseLevel(p.Logging.Level.String()) + if err != nil || level == zerolog.FatalLevel { + return errors.New("project config must specify a valid log level (trace, debug, info, warn, error, or panic)") } return nil diff --git a/fuzzing/config/config_defaults.go b/fuzzing/config/config_defaults.go index 74cd76f4..1fed1120 100644 --- a/fuzzing/config/config_defaults.go +++ b/fuzzing/config/config_defaults.go @@ -4,6 +4,7 @@ import ( testChainConfig "github.com/crytic/medusa/chain/config" "github.com/crytic/medusa/compilation" "github.com/rs/zerolog" + "math/big" ) // GetDefaultProjectConfig obtains a default configuration for a project. It populates a default compilation config @@ -32,15 +33,17 @@ func GetDefaultProjectConfig(platform string) (*ProjectConfig, error) { // Create a project configuration projectConfig := &ProjectConfig{ Fuzzing: FuzzingConfig{ - Workers: 10, - WorkerResetLimit: 50, - Timeout: 0, - TestLimit: 0, - CallSequenceLength: 100, - DeploymentOrder: []string{}, - ConstructorArgs: map[string]map[string]any{}, - CorpusDirectory: "", - CoverageEnabled: true, + Workers: 10, + WorkerResetLimit: 50, + Timeout: 0, + TestLimit: 0, + ShrinkLimit: 5_000, + CallSequenceLength: 100, + TargetContracts: []string{}, + TargetContractsBalances: []*big.Int{}, + ConstructorArgs: map[string]map[string]any{}, + CorpusDirectory: "", + CoverageEnabled: true, SenderAddresses: []string{ "0x10000", "0x20000", @@ -58,20 +61,20 @@ func GetDefaultProjectConfig(platform string) (*ProjectConfig, error) { TestAllContracts: false, TraceAll: false, AssertionTesting: AssertionTestingConfig{ - Enabled: false, + Enabled: true, TestViewMethods: false, - AssertionModes: AssertionModesConfig{ + PanicCodeConfig: PanicCodeConfig{ FailOnAssertion: true, }, }, - PropertyTesting: PropertyTestConfig{ + PropertyTesting: PropertyTestingConfig{ Enabled: true, TestPrefixes: []string{ - "fuzz_", + "property_", }, }, OptimizationTesting: OptimizationTestingConfig{ - Enabled: false, + Enabled: true, TestPrefixes: []string{ "optimize_", }, diff --git a/fuzzing/config/gen_fuzzing_config.go b/fuzzing/config/gen_fuzzing_config.go new file mode 100644 index 00000000..5f1de304 --- /dev/null +++ b/fuzzing/config/gen_fuzzing_config.go @@ -0,0 +1,148 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package config + +import ( + "encoding/json" + "math/big" + + "github.com/crytic/medusa/chain/config" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +var _ = (*fuzzingConfigMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (f FuzzingConfig) MarshalJSON() ([]byte, error) { + type FuzzingConfig struct { + Workers int `json:"workers"` + WorkerResetLimit int `json:"workerResetLimit"` + Timeout int `json:"timeout"` + TestLimit uint64 `json:"testLimit"` + CallSequenceLength int `json:"callSequenceLength"` + CorpusDirectory string `json:"corpusDirectory"` + CoverageEnabled bool `json:"coverageEnabled"` + TargetContracts []string `json:"targetContracts"` + TargetContractsBalances []*hexutil.Big `json:"targetContractsBalances"` + ConstructorArgs map[string]map[string]any `json:"constructorArgs"` + DeployerAddress string `json:"deployerAddress"` + SenderAddresses []string `json:"senderAddresses"` + MaxBlockNumberDelay uint64 `json:"blockNumberDelayMax"` + MaxBlockTimestampDelay uint64 `json:"blockTimestampDelayMax"` + BlockGasLimit uint64 `json:"blockGasLimit"` + TransactionGasLimit uint64 `json:"transactionGasLimit"` + Testing TestingConfig `json:"testing"` + TestChainConfig config.TestChainConfig `json:"chainConfig"` + } + var enc FuzzingConfig + enc.Workers = f.Workers + enc.WorkerResetLimit = f.WorkerResetLimit + enc.Timeout = f.Timeout + enc.TestLimit = f.TestLimit + enc.CallSequenceLength = f.CallSequenceLength + enc.CorpusDirectory = f.CorpusDirectory + enc.CoverageEnabled = f.CoverageEnabled + enc.TargetContracts = f.TargetContracts + if f.TargetContractsBalances != nil { + enc.TargetContractsBalances = make([]*hexutil.Big, len(f.TargetContractsBalances)) + for k, v := range f.TargetContractsBalances { + enc.TargetContractsBalances[k] = (*hexutil.Big)(v) + } + } + enc.ConstructorArgs = f.ConstructorArgs + enc.DeployerAddress = f.DeployerAddress + enc.SenderAddresses = f.SenderAddresses + enc.MaxBlockNumberDelay = f.MaxBlockNumberDelay + enc.MaxBlockTimestampDelay = f.MaxBlockTimestampDelay + enc.BlockGasLimit = f.BlockGasLimit + enc.TransactionGasLimit = f.TransactionGasLimit + enc.Testing = f.Testing + enc.TestChainConfig = f.TestChainConfig + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (f *FuzzingConfig) UnmarshalJSON(input []byte) error { + type FuzzingConfig struct { + Workers *int `json:"workers"` + WorkerResetLimit *int `json:"workerResetLimit"` + Timeout *int `json:"timeout"` + TestLimit *uint64 `json:"testLimit"` + CallSequenceLength *int `json:"callSequenceLength"` + CorpusDirectory *string `json:"corpusDirectory"` + CoverageEnabled *bool `json:"coverageEnabled"` + TargetContracts []string `json:"targetContracts"` + TargetContractsBalances []*hexutil.Big `json:"targetContractsBalances"` + ConstructorArgs map[string]map[string]any `json:"constructorArgs"` + DeployerAddress *string `json:"deployerAddress"` + SenderAddresses []string `json:"senderAddresses"` + MaxBlockNumberDelay *uint64 `json:"blockNumberDelayMax"` + MaxBlockTimestampDelay *uint64 `json:"blockTimestampDelayMax"` + BlockGasLimit *uint64 `json:"blockGasLimit"` + TransactionGasLimit *uint64 `json:"transactionGasLimit"` + Testing *TestingConfig `json:"testing"` + TestChainConfig *config.TestChainConfig `json:"chainConfig"` + } + var dec FuzzingConfig + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.Workers != nil { + f.Workers = *dec.Workers + } + if dec.WorkerResetLimit != nil { + f.WorkerResetLimit = *dec.WorkerResetLimit + } + if dec.Timeout != nil { + f.Timeout = *dec.Timeout + } + if dec.TestLimit != nil { + f.TestLimit = *dec.TestLimit + } + if dec.CallSequenceLength != nil { + f.CallSequenceLength = *dec.CallSequenceLength + } + if dec.CorpusDirectory != nil { + f.CorpusDirectory = *dec.CorpusDirectory + } + if dec.CoverageEnabled != nil { + f.CoverageEnabled = *dec.CoverageEnabled + } + if dec.TargetContracts != nil { + f.TargetContracts = dec.TargetContracts + } + if dec.TargetContractsBalances != nil { + f.TargetContractsBalances = make([]*big.Int, len(dec.TargetContractsBalances)) + for k, v := range dec.TargetContractsBalances { + f.TargetContractsBalances[k] = (*big.Int)(v) + } + } + if dec.ConstructorArgs != nil { + f.ConstructorArgs = dec.ConstructorArgs + } + if dec.DeployerAddress != nil { + f.DeployerAddress = *dec.DeployerAddress + } + if dec.SenderAddresses != nil { + f.SenderAddresses = dec.SenderAddresses + } + if dec.MaxBlockNumberDelay != nil { + f.MaxBlockNumberDelay = *dec.MaxBlockNumberDelay + } + if dec.MaxBlockTimestampDelay != nil { + f.MaxBlockTimestampDelay = *dec.MaxBlockTimestampDelay + } + if dec.BlockGasLimit != nil { + f.BlockGasLimit = *dec.BlockGasLimit + } + if dec.TransactionGasLimit != nil { + f.TransactionGasLimit = *dec.TransactionGasLimit + } + if dec.Testing != nil { + f.Testing = *dec.Testing + } + if dec.TestChainConfig != nil { + f.TestChainConfig = *dec.TestChainConfig + } + return nil +} diff --git a/fuzzing/corpus/corpus.go b/fuzzing/corpus/corpus.go index 9cc4cda6..819da24a 100644 --- a/fuzzing/corpus/corpus.go +++ b/fuzzing/corpus/corpus.go @@ -189,6 +189,7 @@ func (c *Corpus) initializeSequences(sequenceFiles *corpusDirectory[calls.CallSe if callAbiValues != nil { sequenceInvalidError = callAbiValues.Resolve(currentSequenceElement.Contract.CompiledContract().Abi) if sequenceInvalidError != nil { + sequenceInvalidError = fmt.Errorf("error resolving method in contract '%v': %v", currentSequenceElement.Contract.Name(), sequenceInvalidError) return nil, nil } } @@ -236,7 +237,9 @@ func (c *Corpus) initializeSequences(sequenceFiles *corpusDirectory[calls.CallSe // Initialize initializes any runtime data needed for a Corpus on startup. Call sequences are replayed on the post-setup // (deployment) test chain to calculate coverage, while resolving references to compiled contracts. -func (c *Corpus) Initialize(baseTestChain *chain.TestChain, contractDefinitions contracts.Contracts) error { +// Returns the active number of corpus items, total number of corpus items, or an error if one occurred. If an error +// is returned, then the corpus counts returned will always be zero. +func (c *Corpus) Initialize(baseTestChain *chain.TestChain, contractDefinitions contracts.Contracts) (int, int, error) { // Acquire our call sequences lock during the duration of this method. c.callSequencesLock.Lock() defer c.callSequencesLock.Unlock() @@ -273,7 +276,7 @@ func (c *Corpus) Initialize(baseTestChain *chain.TestChain, contractDefinitions return nil }) if err != nil { - return fmt.Errorf("failed to initialize coverage maps, base test chain cloning encountered error: %v", err) + return 0, 0, fmt.Errorf("failed to initialize coverage maps, base test chain cloning encountered error: %v", err) } // Set our coverage maps to those collected when replaying all blocks when cloning. @@ -283,27 +286,37 @@ func (c *Corpus) Initialize(baseTestChain *chain.TestChain, contractDefinitions covMaps := coverage.GetCoverageTracerResults(messageResults) _, _, covErr := c.coverageMaps.Update(covMaps) if covErr != nil { - return err + return 0, 0, err } } } // Next we replay every call sequence, checking its validity on this chain and measuring coverage. Valid sequences // are added to the corpus for mutations, re-execution, etc. - err = c.initializeSequences(c.mutableSequenceFiles, testChain, deployedContracts, true) + // + // The order of initializations here is important, as it determines the order of "unexecuted sequences" to replay + // when the fuzzer's worker starts up. We want to replay test results first, so that other corpus items + // do not trigger the same test failures instead. + err = c.initializeSequences(c.testResultSequenceFiles, testChain, deployedContracts, false) if err != nil { - return err + return 0, 0, err } - err = c.initializeSequences(c.immutableSequenceFiles, testChain, deployedContracts, false) + + err = c.initializeSequences(c.mutableSequenceFiles, testChain, deployedContracts, true) if err != nil { - return err + return 0, 0, err } - err = c.initializeSequences(c.testResultSequenceFiles, testChain, deployedContracts, false) + + err = c.initializeSequences(c.immutableSequenceFiles, testChain, deployedContracts, false) if err != nil { - return err + return 0, 0, err } - return nil + // Calculate corpus health metrics + corpusSequencesTotal := len(c.mutableSequenceFiles.files) + len(c.immutableSequenceFiles.files) + len(c.testResultSequenceFiles.files) + corpusSequencesActive := len(c.unexecutedCallSequences) + + return corpusSequencesActive, corpusSequencesTotal, nil } // addCallSequence adds a call sequence to the corpus in a given corpus directory. diff --git a/fuzzing/coverage/coverage_maps.go b/fuzzing/coverage/coverage_maps.go index 71ed9852..1e8a48d6 100644 --- a/fuzzing/coverage/coverage_maps.go +++ b/fuzzing/coverage/coverage_maps.go @@ -2,7 +2,6 @@ package coverage import ( "bytes" - "fmt" compilationTypes "github.com/crytic/medusa/compilation/types" "github.com/crytic/medusa/utils" "github.com/ethereum/go-ethereum/common" @@ -345,10 +344,9 @@ func (cm *CoverageMapBytecodeData) update(coverageMap *CoverageMapBytecodeData) return true, nil } - // Update each byte which represents a position in the bytecode which was covered. We ignore any size - // differences as init bytecode can have arbitrary length arguments appended. + // Update each byte which represents a position in the bytecode which was covered. changed := false - for i := 0; i < len(cm.executedFlags) || i < len(coverageMap.executedFlags); i++ { + for i := 0; i < len(cm.executedFlags) && i < len(coverageMap.executedFlags); i++ { if cm.executedFlags[i] == 0 && coverageMap.executedFlags[i] != 0 { cm.executedFlags[i] = 1 changed = true @@ -373,5 +371,8 @@ func (cm *CoverageMapBytecodeData) setCoveredAt(codeSize int, pc uint64) (bool, } return false, nil } - return false, fmt.Errorf("tried to set coverage map out of bounds (pc: %d, code size %d)", pc, len(cm.executedFlags)) + + // Since it is possible that the program counter is larger than the code size (e.g., malformed bytecode), we will + // simply return false with no error + return false, nil } diff --git a/fuzzing/coverage/report_generation.go b/fuzzing/coverage/report_generation.go index 184038f7..c9bc2da1 100644 --- a/fuzzing/coverage/report_generation.go +++ b/fuzzing/coverage/report_generation.go @@ -62,9 +62,9 @@ func exportCoverageReport(sourceAnalysis *SourceAnalysis, outputPath string) err // Determine our precision string formatStr := "%." + strconv.Itoa(decimals) + "f" - // If no lines are active and none are covered, show 100% coverage + // If no lines are active and none are covered, show 0% coverage if x == 0 && y == 0 { - return fmt.Sprintf(formatStr, float64(100)) + return fmt.Sprintf(formatStr, float64(0)) } return fmt.Sprintf(formatStr, (float64(x)/float64(y))*100) }, diff --git a/fuzzing/executiontracer/execution_trace.go b/fuzzing/executiontracer/execution_trace.go index 287ce52c..e85306bf 100644 --- a/fuzzing/executiontracer/execution_trace.go +++ b/fuzzing/executiontracer/execution_trace.go @@ -3,6 +3,9 @@ package executiontracer import ( "encoding/hex" "fmt" + "regexp" + "strings" + "github.com/crytic/medusa/chain" "github.com/crytic/medusa/compilation/abiutils" "github.com/crytic/medusa/fuzzing/contracts" @@ -12,8 +15,6 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" coreTypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" - "regexp" - "strings" ) // ExecutionTrace contains information recorded by an ExecutionTracer. It contains information about each call @@ -77,7 +78,7 @@ func (t *ExecutionTrace) generateCallFrameEnterElements(callFrame *CallFrame) ([ } else { method, err = callFrame.CodeContractAbi.MethodById(callFrame.InputData) if err == nil { - methodName = method.Name + methodName = method.Sig } } } @@ -107,10 +108,11 @@ func (t *ExecutionTrace) generateCallFrameEnterElements(callFrame *CallFrame) ([ // If the call was made to the console log precompile address, let's retrieve the log and format it if callFrame.ToAddress == chain.ConsoleLogContractAddress { - // First, attempt to do string formatting if the first element is a string and has a percent sign in it + // First, attempt to do string formatting if the first element is a string, has a percent sign in it, + // and there is at least one argument provided for formatting. exp := regexp.MustCompile(`%`) stringInput, isString := inputValues[0].(string) - if isString && exp.MatchString(stringInput) { + if isString && exp.MatchString(stringInput) && len(inputValues) > 1 { // Format the string and add it to the list of logs consoleLogString = fmt.Sprintf(inputValues[0].(string), inputValues[1:]...) } else { @@ -195,14 +197,19 @@ func (t *ExecutionTrace) generateCallFrameExitElements(callFrame *CallFrame) []a // If we could not correctly obtain the unpacked arguments in a nice display string (due to not having a resolved // contract or method definition, or failure to unpack), we display as raw data in the worst case. - if outputArgumentsDisplayText == nil { + // TODO: Fix if return data is empty len byte array + if outputArgumentsDisplayText == nil && len(callFrame.ReturnData) > 0 { temp := fmt.Sprintf("return_data=%v", hex.EncodeToString(callFrame.ReturnData)) outputArgumentsDisplayText = &temp } // Wrap our return message and output it at the end. if callFrame.ReturnError == nil { - elements = append(elements, colors.GreenBold, fmt.Sprintf("[return (%v)]", *outputArgumentsDisplayText), colors.Reset, "\n") + if outputArgumentsDisplayText != nil { + elements = append(elements, colors.GreenBold, fmt.Sprintf("[return (%v)]", *outputArgumentsDisplayText), colors.Reset, "\n") + } else { + elements = append(elements, colors.GreenBold, "[return]", colors.Reset, "\n") + } return elements } diff --git a/fuzzing/executiontracer/execution_tracer.go b/fuzzing/executiontracer/execution_tracer.go index ed96dbe1..17ec57fe 100644 --- a/fuzzing/executiontracer/execution_tracer.go +++ b/fuzzing/executiontracer/execution_tracer.go @@ -1,6 +1,8 @@ package executiontracer import ( + "math/big" + "github.com/crytic/medusa/chain" "github.com/crytic/medusa/fuzzing/contracts" "github.com/ethereum/go-ethereum/common" @@ -8,7 +10,6 @@ import ( "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/vm" "golang.org/x/exp/slices" - "math/big" ) // CallWithExecutionTrace obtains an execution trace for a given call, on the provided chain, using the state @@ -118,6 +119,12 @@ func (t *ExecutionTracer) resolveCallFrameContractDefinitions(callFrame *CallFra callFrame.ToContractName = toContract.Name() callFrame.ToContractAbi = &toContract.CompiledContract().Abi t.resolveCallFrameConstructorArgs(callFrame, toContract) + + // If this is a contract creation, set the code address to the address of the contract we just deployed. + if callFrame.IsContractCreation() { + callFrame.CodeContractName = toContract.Name() + callFrame.CodeContractAbi = &toContract.CompiledContract().Abi + } } } } diff --git a/fuzzing/fuzzer.go b/fuzzing/fuzzer.go index 4686eba8..5697d1cf 100644 --- a/fuzzing/fuzzer.go +++ b/fuzzing/fuzzer.go @@ -2,7 +2,9 @@ package fuzzing import ( "context" + "errors" "fmt" + "github.com/crytic/medusa/fuzzing/executiontracer" "math/big" "math/rand" "os" @@ -93,7 +95,9 @@ func NewFuzzer(config config.ProjectConfig) (*Fuzzer, error) { if config.Logging.NoColor { colors.DisableColor() } + // Create the global logger and add stdout as an unstructured output stream + // Note that we are not using the project config's log level because we have not validated it yet logging.GlobalLogger = logging.NewLogger(config.Logging.Level) logging.GlobalLogger.AddWriter(os.Stdout, logging.UNSTRUCTURED, !config.Logging.NoColor) @@ -110,16 +114,19 @@ func NewFuzzer(config config.ProjectConfig) (*Fuzzer, error) { logging.GlobalLogger.AddWriter(file, logging.UNSTRUCTURED, false) } - // Get the fuzzer's custom sub-logger - logger := logging.GlobalLogger.NewSubLogger("module", "fuzzer") - // Validate our provided config err := config.Validate() if err != nil { - logger.Error("Invalid configuration", err) + logging.GlobalLogger.Error("Invalid configuration", err) return nil, err } + // Update the log level of the global logger now + logging.GlobalLogger.SetLevel(config.Logging.Level) + + // Get the fuzzer's custom sub-logger + logger := logging.GlobalLogger.NewSubLogger("module", "fuzzer") + // Parse the senders addresses from our account config. senders, err := utils.HexStringsToAddresses(config.Fuzzing.SenderAddresses) if err != nil { @@ -329,20 +336,21 @@ func (f *Fuzzer) createTestChain() (*chain.TestChain, error) { // all compiled contract definitions. This includes any successful compilations as a result of the Fuzzer.config // definitions, as well as those added by Fuzzer.AddCompilationTargets. The contract deployment order is defined by // the Fuzzer.config. -func chainSetupFromCompilations(fuzzer *Fuzzer, testChain *chain.TestChain) error { - // Verify contract deployment order is not empty. If it's empty, but we only have one contract definition, - // we can infer the deployment order. Otherwise, we report an error. - if len(fuzzer.config.Fuzzing.DeploymentOrder) == 0 { +func chainSetupFromCompilations(fuzzer *Fuzzer, testChain *chain.TestChain) (error, *executiontracer.ExecutionTrace) { + // Verify that target contracts is not empty. If it's empty, but we only have one contract definition, + // we can infer the target contracts. Otherwise, we report an error. + if len(fuzzer.config.Fuzzing.TargetContracts) == 0 { if len(fuzzer.contractDefinitions) == 1 { - fuzzer.config.Fuzzing.DeploymentOrder = []string{fuzzer.contractDefinitions[0].Name()} + fuzzer.config.Fuzzing.TargetContracts = []string{fuzzer.contractDefinitions[0].Name()} } else { - return fmt.Errorf("you must specify a contract deployment order within your project configuration") + return fmt.Errorf("missing target contracts (update fuzzing.targetContracts in the project config " + + "or use the --target-contracts CLI flag)"), nil } } // Loop for all contracts to deploy deployedContractAddr := make(map[string]common.Address) - for _, contractName := range fuzzer.config.Fuzzing.DeploymentOrder { + for i, contractName := range fuzzer.config.Fuzzing.TargetContracts { // Look for a contract in our compiled contract definitions that matches this one found := false for _, contract := range fuzzer.contractDefinitions { @@ -352,49 +360,75 @@ func chainSetupFromCompilations(fuzzer *Fuzzer, testChain *chain.TestChain) erro if len(contract.CompiledContract().Abi.Constructor.Inputs) > 0 { jsonArgs, ok := fuzzer.config.Fuzzing.ConstructorArgs[contractName] if !ok { - return fmt.Errorf("constructor arguments for contract %s not provided", contractName) + return fmt.Errorf("constructor arguments for contract %s not provided", contractName), nil } decoded, err := valuegeneration.DecodeJSONArgumentsFromMap(contract.CompiledContract().Abi.Constructor.Inputs, jsonArgs, deployedContractAddr) if err != nil { - return err + return err, nil } args = decoded } - // Constructor our deployment message/tx data field + // Construct our deployment message/tx data field msgData, err := contract.CompiledContract().GetDeploymentMessageData(args) if err != nil { - return fmt.Errorf("initial contract deployment failed for contract \"%v\", error: %v", contractName, err) + return fmt.Errorf("initial contract deployment failed for contract \"%v\", error: %v", contractName, err), nil + } + + // If our project config has a non-zero balance for this target contract, retrieve it + contractBalance := big.NewInt(0) + if len(fuzzer.config.Fuzzing.TargetContractsBalances) > i { + contractBalance = new(big.Int).Set(fuzzer.config.Fuzzing.TargetContractsBalances[i]) } // Create a message to represent our contract deployment (we let deployments consume the whole block // gas limit rather than use tx gas limit) - msg := calls.NewCallMessage(fuzzer.deployer, nil, 0, big.NewInt(0), fuzzer.config.Fuzzing.BlockGasLimit, nil, nil, nil, msgData) + msg := calls.NewCallMessage(fuzzer.deployer, nil, 0, contractBalance, fuzzer.config.Fuzzing.BlockGasLimit, nil, nil, nil, msgData) msg.FillFromTestChainProperties(testChain) // Create a new pending block we'll commit to chain block, err := testChain.PendingBlockCreate() if err != nil { - return err + return err, nil } // Add our transaction to the block // Add our transaction to the block err = testChain.PendingBlockAddTx(msg.ToCoreMessage()) if err != nil { - return err + return err, nil } // Commit the pending block to the chain, so it becomes the new head. err = testChain.PendingBlockCommit() if err != nil { - return err + return err, nil } - // Ensure our transaction succeeded + // Ensure our transaction succeeded and, if it did not, attach an execution trace to it and re-run it. + // The execution trace will be returned so that it can be provided to the user for debugging if block.MessageResults[0].Receipt.Status != types.ReceiptStatusSuccessful { - return fmt.Errorf("contract deployment tx returned a failed status: %v", block.MessageResults[0].ExecutionResult.Err) + // Create a call sequence element to represent the failed contract deployment tx + cse := calls.NewCallSequenceElement(nil, msg, 0, 0) + cse.ChainReference = &calls.CallSequenceElementChainReference{ + Block: block, + TransactionIndex: len(block.Messages) - 1, + } + + // Replay the execution trace for the failed contract deployment tx + err = cse.AttachExecutionTrace(testChain, fuzzer.contractDefinitions) + + // Throw an error if execution tracing threw an error or the trace is nil + if err != nil { + return fmt.Errorf("failed to attach execution trace to failed contract deployment tx: %v", err), nil + } + if cse.ExecutionTrace == nil { + return fmt.Errorf("contract deployment tx returned a failed status: %v", block.MessageResults[0].ExecutionResult.Err), nil + } + + // Return the execution error and the execution trace + return fmt.Errorf("contract deployment tx returned a failed status: %v", block.MessageResults[0].ExecutionResult.Err), cse.ExecutionTrace } // Record our deployed contract so the next config-specified constructor args can reference this @@ -410,10 +444,10 @@ func chainSetupFromCompilations(fuzzer *Fuzzer, testChain *chain.TestChain) erro // If we did not find a contract corresponding to this item in the deployment order, we throw an error. if !found { - return fmt.Errorf("DeploymentOrder specified a contract name which was not found in the compilation: %v\n", contractName) + return fmt.Errorf("%v was specified in the target contracts but was not found in the compilation artifacts", contractName), nil } } - return nil + return nil, nil } // defaultCallSequenceGeneratorConfigFunc is a NewCallSequenceGeneratorConfigFunc which creates a @@ -502,8 +536,7 @@ func (f *Fuzzer) spawnWorkersLoop(baseTestChain *chain.TestChain) error { // Define a flag that indicates whether we have not cancelled o working := !utils.CheckContextDone(f.ctx) - // Log that we are about to create the workers and start fuzzing - f.logger.Info("Creating ", colors.Bold, f.config.Fuzzing.Workers, colors.Reset, " workers...") + // Create workers and start fuzzing. var err error for err == nil && working { // Send an item into our channel to queue up a spot. This will block us if we hit capacity until a worker @@ -606,6 +639,7 @@ func (f *Fuzzer) Start() error { } // Set up the corpus + f.logger.Info("Initializing corpus") f.corpus, err = corpus.NewCorpus(f.config.Fuzzing.CorpusDirectory) if err != nil { f.logger.Error("Failed to create the corpus", err) @@ -629,19 +663,38 @@ func (f *Fuzzer) Start() error { } // Set it up with our deployment/setup strategy defined by the fuzzer. - err = f.Hooks.ChainSetupFunc(f, baseTestChain) + f.logger.Info("Setting up base chain") + err, trace := f.Hooks.ChainSetupFunc(f, baseTestChain) if err != nil { - f.logger.Error("Failed to initialize the test chain", err) + if trace != nil { + f.logger.Error("Failed to initialize the test chain", err, errors.New(trace.Log().ColorString())) + } else { + f.logger.Error("Failed to initialize the test chain", err) + } return err } // Initialize our coverage maps by measuring the coverage we get from the corpus. - err = f.corpus.Initialize(baseTestChain, f.contractDefinitions) + var corpusActiveSequences, corpusTotalSequences int + f.logger.Info("Initializing and validating corpus call sequences") + corpusActiveSequences, corpusTotalSequences, err = f.corpus.Initialize(baseTestChain, f.contractDefinitions) if err != nil { f.logger.Error("Failed to initialize the corpus", err) return err } + // Log corpus health statistics, if we have any existing sequences. + if corpusTotalSequences > 0 { + f.logger.Info( + colors.Bold, "corpus: ", colors.Reset, + "health: ", colors.Bold, int(float32(corpusActiveSequences)/float32(corpusTotalSequences)*100.0), "%", colors.Reset, ", ", + "sequences: ", colors.Bold, corpusTotalSequences, " (", corpusActiveSequences, " valid, ", corpusTotalSequences-corpusActiveSequences, " invalid)", colors.Reset, + ) + } + + // Log the start of our fuzzing campaign. + f.logger.Info("Fuzzing with ", colors.Bold, f.config.Fuzzing.Workers, colors.Reset, " workers") + // Start our printing loop now that we're about to begin fuzzing. go f.printMetricsLoop() @@ -723,6 +776,7 @@ func (f *Fuzzer) printMetricsLoop() { callsTested := f.metrics.CallsTested() sequencesTested := f.metrics.SequencesTested() workerStartupCount := f.metrics.WorkerStartupCount() + workersShrinking := f.metrics.WorkersShrinkingCount() // Calculate time elapsed since the last update secondsSinceLastUpdate := time.Since(lastPrintedTime).Seconds() @@ -741,6 +795,7 @@ func (f *Fuzzer) printMetricsLoop() { logBuffer.Append(", seq/s: ", colors.Bold, fmt.Sprintf("%d", uint64(float64(new(big.Int).Sub(sequencesTested, lastSequencesTested).Uint64())/secondsSinceLastUpdate)), colors.Reset) logBuffer.Append(", coverage: ", colors.Bold, fmt.Sprintf("%d", f.corpus.ActiveMutableSequenceCount()), colors.Reset) if f.logger.Level() <= zerolog.DebugLevel { + logBuffer.Append(", shrinking: ", colors.Bold, fmt.Sprintf("%v", workersShrinking), colors.Reset) logBuffer.Append(", mem: ", colors.Bold, fmt.Sprintf("%v/%v MB", memoryUsedMB, memoryTotalMB), colors.Reset) logBuffer.Append(", resets/s: ", colors.Bold, fmt.Sprintf("%d", uint64(float64(new(big.Int).Sub(workerStartupCount, lastWorkerStartupCount).Uint64())/secondsSinceLastUpdate)), colors.Reset) } @@ -797,7 +852,7 @@ func (f *Fuzzer) printExitingResults() { // Print the results of each individual test case. f.logger.Info("Fuzzer stopped, test results follow below ...") for _, testCase := range f.testCases { - f.logger.Info(testCase.LogMessage().Elements()...) + f.logger.Info(testCase.LogMessage().ColorString()) // Tally our pass/fail count. if testCase.Status() == TestCaseStatusPassed { diff --git a/fuzzing/fuzzer_hooks.go b/fuzzing/fuzzer_hooks.go index ea2d8486..cf458192 100644 --- a/fuzzing/fuzzer_hooks.go +++ b/fuzzing/fuzzer_hooks.go @@ -1,6 +1,7 @@ package fuzzing import ( + "github.com/crytic/medusa/fuzzing/executiontracer" "math/rand" "github.com/crytic/medusa/chain" @@ -41,7 +42,8 @@ type NewShrinkingValueMutatorFunc func(fuzzer *Fuzzer, valueSet *valuegeneration type NewCallSequenceGeneratorConfigFunc func(fuzzer *Fuzzer, valueSet *valuegeneration.ValueSet, randomProvider *rand.Rand) (*CallSequenceGeneratorConfig, error) // TestChainSetupFunc describes a function which sets up a test chain's initial state prior to fuzzing. -type TestChainSetupFunc func(fuzzer *Fuzzer, testChain *chain.TestChain) error +// An execution trace can also be returned in case of a deployment error for an improved debugging experience +type TestChainSetupFunc func(fuzzer *Fuzzer, testChain *chain.TestChain) (error, *executiontracer.ExecutionTrace) // CallSequenceTestFunc defines a method called after a fuzzing.FuzzerWorker sends another call in a types.CallSequence // during a fuzzing campaign. It returns a ShrinkCallSequenceRequest set, which represents a set of requests for diff --git a/fuzzing/fuzzer_metrics.go b/fuzzing/fuzzer_metrics.go index 9d64c91a..70fc3788 100644 --- a/fuzzing/fuzzer_metrics.go +++ b/fuzzing/fuzzer_metrics.go @@ -19,6 +19,9 @@ type fuzzerWorkerMetrics struct { // workerStartupCount describes the amount of times the worker was generated, or re-generated for this index. workerStartupCount *big.Int + + // shrinking indicates whether the fuzzer worker is currently shrinking. + shrinking bool } // newFuzzerMetrics obtains a new FuzzerMetrics struct for a given number of workers specified by workerCount. @@ -63,3 +66,14 @@ func (m *FuzzerMetrics) WorkerStartupCount() *big.Int { } return workerStartupCount } + +// WorkersShrinkingCount returns the amount of workers currently performing shrinking operations. +func (m *FuzzerMetrics) WorkersShrinkingCount() uint64 { + shrinkingCount := uint64(0) + for _, workerMetrics := range m.workerMetrics { + if workerMetrics.shrinking { + shrinkingCount++ + } + } + return shrinkingCount +} diff --git a/fuzzing/fuzzer_test.go b/fuzzing/fuzzer_test.go index 7fdc4f47..860ebd93 100644 --- a/fuzzing/fuzzer_test.go +++ b/fuzzing/fuzzer_test.go @@ -2,15 +2,17 @@ package fuzzing import ( "encoding/hex" + "github.com/crytic/medusa/fuzzing/executiontracer" + "math/big" + "math/rand" + "testing" + "github.com/crytic/medusa/chain" "github.com/crytic/medusa/events" "github.com/crytic/medusa/fuzzing/calls" "github.com/crytic/medusa/fuzzing/valuegeneration" "github.com/crytic/medusa/utils" "github.com/ethereum/go-ethereum/common" - "math/big" - "math/rand" - "testing" "github.com/crytic/medusa/fuzzing/config" "github.com/stretchr/testify/assert" @@ -21,9 +23,9 @@ func TestFuzzerHooks(t *testing.T) { runFuzzerTest(t, &fuzzerSolcFileTest{ filePath: "testdata/contracts/assertions/assert_immediate.sol", configUpdates: func(config *config.ProjectConfig) { - config.Fuzzing.DeploymentOrder = []string{"TestContract"} + config.Fuzzing.TargetContracts = []string{"TestContract"} config.Fuzzing.Testing.PropertyTesting.Enabled = false - config.Fuzzing.Testing.AssertionTesting.Enabled = true + config.Fuzzing.Testing.OptimizationTesting.Enabled = false }, method: func(f *fuzzerTestContext) { // Attach to fuzzer hooks which simply set a success state. @@ -34,7 +36,7 @@ func TestFuzzerHooks(t *testing.T) { return existingSeqGenConfigFunc(fuzzer, valueSet, randomProvider) } existingChainSetupFunc := f.fuzzer.Hooks.ChainSetupFunc - f.fuzzer.Hooks.ChainSetupFunc = func(fuzzer *Fuzzer, testChain *chain.TestChain) error { + f.fuzzer.Hooks.ChainSetupFunc = func(fuzzer *Fuzzer, testChain *chain.TestChain) (error, *executiontracer.ExecutionTrace) { chainSetupOk = true return existingChainSetupFunc(fuzzer, testChain) } @@ -76,18 +78,18 @@ func TestAssertionMode(t *testing.T) { runFuzzerTest(t, &fuzzerSolcFileTest{ filePath: filePath, configUpdates: func(config *config.ProjectConfig) { - config.Fuzzing.DeploymentOrder = []string{"TestContract"} + config.Fuzzing.TargetContracts = []string{"TestContract"} + config.Fuzzing.Testing.AssertionTesting.PanicCodeConfig.FailOnAssertion = true + config.Fuzzing.Testing.AssertionTesting.PanicCodeConfig.FailOnAllocateTooMuchMemory = true + config.Fuzzing.Testing.AssertionTesting.PanicCodeConfig.FailOnArithmeticUnderflow = true + config.Fuzzing.Testing.AssertionTesting.PanicCodeConfig.FailOnCallUninitializedVariable = true + config.Fuzzing.Testing.AssertionTesting.PanicCodeConfig.FailOnEnumTypeConversionOutOfBounds = true + config.Fuzzing.Testing.AssertionTesting.PanicCodeConfig.FailOnDivideByZero = true + config.Fuzzing.Testing.AssertionTesting.PanicCodeConfig.FailOnIncorrectStorageAccess = true + config.Fuzzing.Testing.AssertionTesting.PanicCodeConfig.FailOnOutOfBoundsArrayAccess = true + config.Fuzzing.Testing.AssertionTesting.PanicCodeConfig.FailOnPopEmptyArray = true config.Fuzzing.Testing.PropertyTesting.Enabled = false - config.Fuzzing.Testing.AssertionTesting.Enabled = true - config.Fuzzing.Testing.AssertionTesting.AssertionModes.FailOnAssertion = true - config.Fuzzing.Testing.AssertionTesting.AssertionModes.FailOnAllocateTooMuchMemory = true - config.Fuzzing.Testing.AssertionTesting.AssertionModes.FailOnArithmeticUnderflow = true - config.Fuzzing.Testing.AssertionTesting.AssertionModes.FailOnCallUninitializedVariable = true - config.Fuzzing.Testing.AssertionTesting.AssertionModes.FailOnEnumTypeConversionOutOfBounds = true - config.Fuzzing.Testing.AssertionTesting.AssertionModes.FailOnDivideByZero = true - config.Fuzzing.Testing.AssertionTesting.AssertionModes.FailOnIncorrectStorageAccess = true - config.Fuzzing.Testing.AssertionTesting.AssertionModes.FailOnOutOfBoundsArrayAccess = true - config.Fuzzing.Testing.AssertionTesting.AssertionModes.FailOnPopEmptyArray = true + config.Fuzzing.Testing.OptimizationTesting.Enabled = false }, method: func(f *fuzzerTestContext) { // Start the fuzzer @@ -107,10 +109,10 @@ func TestAssertionsNotRequire(t *testing.T) { runFuzzerTest(t, &fuzzerSolcFileTest{ filePath: "testdata/contracts/assertions/assert_not_require.sol", configUpdates: func(config *config.ProjectConfig) { - config.Fuzzing.DeploymentOrder = []string{"TestContract"} + config.Fuzzing.TargetContracts = []string{"TestContract"} config.Fuzzing.TestLimit = 500 config.Fuzzing.Testing.PropertyTesting.Enabled = false - config.Fuzzing.Testing.AssertionTesting.Enabled = true + config.Fuzzing.Testing.OptimizationTesting.Enabled = false }, method: func(f *fuzzerTestContext) { // Start the fuzzer @@ -129,11 +131,10 @@ func TestAssertionsAndProperties(t *testing.T) { runFuzzerTest(t, &fuzzerSolcFileTest{ filePath: "testdata/contracts/assertions/assert_and_property_test.sol", configUpdates: func(config *config.ProjectConfig) { - config.Fuzzing.DeploymentOrder = []string{"TestContract"} + config.Fuzzing.TargetContracts = []string{"TestContract"} config.Fuzzing.TestLimit = 500 config.Fuzzing.Testing.StopOnFailedTest = false - config.Fuzzing.Testing.PropertyTesting.Enabled = true - config.Fuzzing.Testing.AssertionTesting.Enabled = true + config.Fuzzing.Testing.OptimizationTesting.Enabled = false }, method: func(f *fuzzerTestContext) { // Start the fuzzer @@ -155,11 +156,10 @@ func TestOptimizationMode(t *testing.T) { runFuzzerTest(t, &fuzzerSolcFileTest{ filePath: filePath, configUpdates: func(config *config.ProjectConfig) { - config.Fuzzing.DeploymentOrder = []string{"TestContract"} + config.Fuzzing.TargetContracts = []string{"TestContract"} + config.Fuzzing.TestLimit = 10_000 // this test should expose a failure quickly. config.Fuzzing.Testing.PropertyTesting.Enabled = false config.Fuzzing.Testing.AssertionTesting.Enabled = false - config.Fuzzing.Testing.OptimizationTesting.Enabled = true - config.Fuzzing.TestLimit = 10_000 // this test should expose a failure quickly. }, method: func(f *fuzzerTestContext) { // Start the fuzzer @@ -168,11 +168,10 @@ func TestOptimizationMode(t *testing.T) { // Check the value found for optimization test var testCases = f.fuzzer.TestCasesWithStatus(TestCaseStatusPassed) - switch v := testCases[0].(type) { - case *OptimizationTestCase: - assert.EqualValues(t, v.Value().Cmp(big.NewInt(4241)), 0) - default: - t.Errorf("invalid test case found %T", v) + for _, testCase := range testCases { + if optimizationTestCase, ok := testCase.(*OptimizationTestCase); ok { + assert.EqualValues(t, optimizationTestCase.Value().Cmp(big.NewInt(4241)), 0) + } } }, }) @@ -185,11 +184,13 @@ func TestChainBehaviour(t *testing.T) { runFuzzerTest(t, &fuzzerSolcFileTest{ filePath: "testdata/contracts/chain/tx_out_of_gas.sol", configUpdates: func(config *config.ProjectConfig) { - config.Fuzzing.DeploymentOrder = []string{"TestContract"} + config.Fuzzing.TargetContracts = []string{"TestContract"} config.Fuzzing.Workers = 1 config.Fuzzing.TestLimit = uint64(config.Fuzzing.CallSequenceLength) // we just need a few oog txs to test config.Fuzzing.Timeout = 10 // to be safe, we set a 10s timeout config.Fuzzing.TransactionGasLimit = 500000 // we set this low, so contract execution runs out of gas earlier. + config.Fuzzing.Testing.AssertionTesting.Enabled = false + config.Fuzzing.Testing.OptimizationTesting.Enabled = false }, method: func(f *fuzzerTestContext) { // Start the fuzzer @@ -209,6 +210,7 @@ func TestCheatCodes(t *testing.T) { "testdata/contracts/cheat_codes/utils/to_string.sol", "testdata/contracts/cheat_codes/utils/sign.sol", "testdata/contracts/cheat_codes/utils/parse.sol", + "testdata/contracts/cheat_codes/vm/snapshot_and_revert_to.sol", "testdata/contracts/cheat_codes/vm/coinbase.sol", "testdata/contracts/cheat_codes/vm/chain_id.sol", "testdata/contracts/cheat_codes/vm/deal.sol", @@ -237,7 +239,7 @@ func TestCheatCodes(t *testing.T) { runFuzzerTest(t, &fuzzerSolcFileTest{ filePath: filePath, configUpdates: func(config *config.ProjectConfig) { - config.Fuzzing.DeploymentOrder = []string{"TestContract"} + config.Fuzzing.TargetContracts = []string{"TestContract"} // some tests require full sequence + revert to test fully config.Fuzzing.Workers = 3 @@ -266,8 +268,8 @@ func TestConsoleLog(t *testing.T) { // These are the logs that should show up in the execution trace expectedLogs := []string{ "2", - "hello world", - "byte", + "68656c6c6f20776f726c64", // This is "hello world" in hex + "62797465", // This is "byte" in hex "i is 2", "% bool is true, addr is 0x0000000000000000000000000000000000000000, u is 100", } @@ -279,11 +281,10 @@ func TestConsoleLog(t *testing.T) { runFuzzerTest(t, &fuzzerSolcFileTest{ filePath: filePath, configUpdates: func(config *config.ProjectConfig) { - config.Fuzzing.DeploymentOrder = []string{"TestContract"} + config.Fuzzing.TargetContracts = []string{"TestContract"} config.Fuzzing.TestLimit = 10000 - // enable assertion testing only - config.Fuzzing.Testing.PropertyTesting.Enabled = true - config.Fuzzing.Testing.AssertionTesting.Enabled = true + config.Fuzzing.Testing.PropertyTesting.Enabled = false + config.Fuzzing.Testing.OptimizationTesting.Enabled = false }, method: func(f *fuzzerTestContext) { // Start the fuzzer @@ -326,10 +327,12 @@ func TestDeploymentsInnerDeployments(t *testing.T) { runFuzzerTest(t, &fuzzerSolcFileTest{ filePath: filePath, configUpdates: func(config *config.ProjectConfig) { - config.Fuzzing.DeploymentOrder = []string{"InnerDeploymentFactory"} + config.Fuzzing.TargetContracts = []string{"InnerDeploymentFactory"} config.Fuzzing.TestLimit = 1_000 // this test should expose a failure quickly. config.Fuzzing.Testing.StopOnFailedContractMatching = true config.Fuzzing.Testing.TestAllContracts = true // test dynamically deployed contracts + config.Fuzzing.Testing.AssertionTesting.Enabled = false + config.Fuzzing.Testing.OptimizationTesting.Enabled = false }, method: func(f *fuzzerTestContext) { // Start the fuzzer @@ -347,10 +350,12 @@ func TestDeploymentsInnerDeployments(t *testing.T) { runFuzzerTest(t, &fuzzerSolcFileTest{ filePath: "testdata/contracts/deployments/inner_deployment_on_construction.sol", configUpdates: func(config *config.ProjectConfig) { - config.Fuzzing.DeploymentOrder = []string{"InnerDeploymentFactory"} + config.Fuzzing.TargetContracts = []string{"InnerDeploymentFactory"} config.Fuzzing.TestLimit = 1_000 // this test should expose a failure quickly. config.Fuzzing.Testing.StopOnFailedContractMatching = true config.Fuzzing.Testing.TestAllContracts = true // test dynamically deployed contracts + config.Fuzzing.Testing.AssertionTesting.Enabled = false + config.Fuzzing.Testing.OptimizationTesting.Enabled = false }, method: func(f *fuzzerTestContext) { // Start the fuzzer @@ -368,8 +373,33 @@ func TestDeploymentsInternalLibrary(t *testing.T) { runFuzzerTest(t, &fuzzerSolcFileTest{ filePath: "testdata/contracts/deployments/internal_library.sol", configUpdates: func(config *config.ProjectConfig) { - config.Fuzzing.DeploymentOrder = []string{"TestInternalLibrary"} + config.Fuzzing.TargetContracts = []string{"TestInternalLibrary"} config.Fuzzing.TestLimit = 100 // this test should expose a failure quickly. + config.Fuzzing.Testing.AssertionTesting.Enabled = false + config.Fuzzing.Testing.OptimizationTesting.Enabled = false + }, + method: func(f *fuzzerTestContext) { + // Start the fuzzer + err := f.fuzzer.Start() + assert.NoError(t, err) + + // Check for any failed tests and verify coverage was captured + assertFailedTestsExpected(f, false) + assertCorpusCallSequencesCollected(f, true) + }, + }) +} + +// TestDeploymentsWithPayableConstructor runs a test to ensure that we can send ether to payable constructors +func TestDeploymentsWithPayableConstructors(t *testing.T) { + runFuzzerTest(t, &fuzzerSolcFileTest{ + filePath: "testdata/contracts/deployments/deploy_payable_constructors.sol", + configUpdates: func(config *config.ProjectConfig) { + config.Fuzzing.TargetContracts = []string{"FirstContract", "SecondContract"} + config.Fuzzing.TargetContractsBalances = []*big.Int{big.NewInt(0), big.NewInt(1e18)} + config.Fuzzing.TestLimit = 1 // this should happen immediately + config.Fuzzing.Testing.AssertionTesting.Enabled = false + config.Fuzzing.Testing.OptimizationTesting.Enabled = false }, method: func(f *fuzzerTestContext) { // Start the fuzzer @@ -395,9 +425,11 @@ func TestDeploymentsSelfDestruct(t *testing.T) { runFuzzerTest(t, &fuzzerSolcFileTest{ filePath: filePath, configUpdates: func(config *config.ProjectConfig) { - config.Fuzzing.DeploymentOrder = []string{"InnerDeploymentFactory"} + config.Fuzzing.TargetContracts = []string{"InnerDeploymentFactory"} config.Fuzzing.TestLimit = 500 // this test should expose a failure quickly. config.Fuzzing.Testing.StopOnNoTests = false + config.Fuzzing.Testing.AssertionTesting.Enabled = false + config.Fuzzing.Testing.OptimizationTesting.Enabled = false config.Fuzzing.Testing.TestAllContracts = true }, method: func(f *fuzzerTestContext) { @@ -431,7 +463,7 @@ func TestDeploymentsSelfDestruct(t *testing.T) { func TestExecutionTraces(t *testing.T) { expectedMessagesPerTest := map[string][]string{ "testdata/contracts/execution_tracing/call_and_deployment_args.sol": {"Hello from deployment args!", "Hello from call args!"}, - "testdata/contracts/execution_tracing/cheatcodes.sol": {"StdCheats.toString(true)"}, + "testdata/contracts/execution_tracing/cheatcodes.sol": {"StdCheats.toString(bool)(true)"}, "testdata/contracts/execution_tracing/event_emission.sol": {"TestEvent", "TestIndexedEvent", "TestMixedEvent", "Hello from event args!", "Hello from library event args!"}, "testdata/contracts/execution_tracing/proxy_call.sol": {"TestContract -> InnerDeploymentContract.setXY", "Hello from proxy call args!"}, "testdata/contracts/execution_tracing/revert_custom_error.sol": {"CustomError", "Hello from a custom error!"}, @@ -442,9 +474,9 @@ func TestExecutionTraces(t *testing.T) { runFuzzerTest(t, &fuzzerSolcFileTest{ filePath: filePath, configUpdates: func(config *config.ProjectConfig) { - config.Fuzzing.DeploymentOrder = []string{"TestContract"} + config.Fuzzing.TargetContracts = []string{"TestContract"} config.Fuzzing.Testing.PropertyTesting.Enabled = false - config.Fuzzing.Testing.AssertionTesting.Enabled = true + config.Fuzzing.Testing.OptimizationTesting.Enabled = false }, method: func(f *fuzzerTestContext) { // Start the fuzzer @@ -482,12 +514,11 @@ func TestTestingScope(t *testing.T) { runFuzzerTest(t, &fuzzerSolcFileTest{ filePath: "testdata/contracts/deployments/testing_scope.sol", configUpdates: func(config *config.ProjectConfig) { - config.Fuzzing.DeploymentOrder = []string{"TestContract"} + config.Fuzzing.TargetContracts = []string{"TestContract"} config.Fuzzing.TestLimit = 1_000 // this test should expose a failure quickly. config.Fuzzing.Testing.TestAllContracts = testingAllContracts config.Fuzzing.Testing.StopOnFailedTest = false - config.Fuzzing.Testing.AssertionTesting.Enabled = true - config.Fuzzing.Testing.PropertyTesting.Enabled = true + config.Fuzzing.Testing.OptimizationTesting.Enabled = false }, method: func(f *fuzzerTestContext) { // Start the fuzzer @@ -517,7 +548,7 @@ func TestDeploymentsWithArgs(t *testing.T) { runFuzzerTest(t, &fuzzerSolcFileTest{ filePath: "testdata/contracts/deployments/deployment_with_args.sol", configUpdates: func(config *config.ProjectConfig) { - config.Fuzzing.DeploymentOrder = []string{"DeploymentWithArgs", "Dependent"} + config.Fuzzing.TargetContracts = []string{"DeploymentWithArgs", "Dependent"} config.Fuzzing.ConstructorArgs = map[string]map[string]any{ "DeploymentWithArgs": { "_x": "123456789", @@ -533,6 +564,8 @@ func TestDeploymentsWithArgs(t *testing.T) { } config.Fuzzing.Testing.StopOnFailedTest = false config.Fuzzing.TestLimit = 500 // this test should expose a failure quickly. + config.Fuzzing.Testing.AssertionTesting.Enabled = false + config.Fuzzing.Testing.OptimizationTesting.Enabled = false }, method: func(f *fuzzerTestContext) { // Start the fuzzer @@ -550,8 +583,10 @@ func TestValueGenerationGenerateAllTypes(t *testing.T) { runFuzzerTest(t, &fuzzerSolcFileTest{ filePath: "testdata/contracts/value_generation/generate_all_types.sol", configUpdates: func(config *config.ProjectConfig) { - config.Fuzzing.DeploymentOrder = []string{"GenerateAllTypes"} + config.Fuzzing.TargetContracts = []string{"GenerateAllTypes"} config.Fuzzing.TestLimit = 10_000 + config.Fuzzing.Testing.AssertionTesting.Enabled = false + config.Fuzzing.Testing.OptimizationTesting.Enabled = false }, method: func(f *fuzzerTestContext) { // Start the fuzzer @@ -583,7 +618,9 @@ func TestValueGenerationSolving(t *testing.T) { runFuzzerTest(t, &fuzzerSolcFileTest{ filePath: filePath, configUpdates: func(config *config.ProjectConfig) { - config.Fuzzing.DeploymentOrder = []string{"TestContract"} + config.Fuzzing.TargetContracts = []string{"TestContract"} + config.Fuzzing.Testing.AssertionTesting.Enabled = false + config.Fuzzing.Testing.OptimizationTesting.Enabled = false }, method: func(f *fuzzerTestContext) { // Start the fuzzer @@ -639,8 +676,9 @@ func TestASTValueExtraction(t *testing.T) { filePath: "testdata/contracts/value_generation/ast_value_extraction.sol", configUpdates: func(config *config.ProjectConfig) { config.Fuzzing.TestLimit = 1 // stop immediately to simply see what values were mined. - config.Fuzzing.Testing.AssertionTesting.Enabled = true config.Fuzzing.Testing.PropertyTesting.Enabled = false + config.Fuzzing.Testing.OptimizationTesting.Enabled = false + config.Fuzzing.TargetContracts = []string{"TestContract"} }, method: func(f *fuzzerTestContext) { // Start the fuzzer @@ -674,9 +712,11 @@ func TestVMCorrectness(t *testing.T) { runFuzzerTest(t, &fuzzerSolcFileTest{ filePath: "testdata/contracts/vm_tests/block_number_increasing.sol", configUpdates: func(config *config.ProjectConfig) { - config.Fuzzing.DeploymentOrder = []string{"TestContract"} + config.Fuzzing.TargetContracts = []string{"TestContract"} config.Fuzzing.MaxBlockTimestampDelay = 1 // this contract require calls every block config.Fuzzing.MaxBlockNumberDelay = 1 // this contract require calls every block + config.Fuzzing.Testing.AssertionTesting.Enabled = false + config.Fuzzing.Testing.OptimizationTesting.Enabled = false }, method: func(f *fuzzerTestContext) { // Start the fuzzer @@ -693,7 +733,7 @@ func TestVMCorrectness(t *testing.T) { runFuzzerTest(t, &fuzzerSolcFileTest{ filePath: "testdata/contracts/vm_tests/block_number_increasing.sol", configUpdates: func(config *config.ProjectConfig) { - config.Fuzzing.DeploymentOrder = []string{"TestContract"} + config.Fuzzing.TargetContracts = []string{"TestContract"} config.Fuzzing.MaxBlockTimestampDelay = 1 // this contract require calls every block config.Fuzzing.MaxBlockNumberDelay = 1 // this contract require calls every block }, @@ -712,7 +752,7 @@ func TestVMCorrectness(t *testing.T) { runFuzzerTest(t, &fuzzerSolcFileTest{ filePath: "testdata/contracts/vm_tests/block_hash_store_check.sol", configUpdates: func(config *config.ProjectConfig) { - config.Fuzzing.DeploymentOrder = []string{"TestContract"} + config.Fuzzing.TargetContracts = []string{"TestContract"} config.Fuzzing.TestLimit = 1_000 // this test should expose a failure quickly. config.Fuzzing.MaxBlockTimestampDelay = 1 // this contract require calls every block config.Fuzzing.MaxBlockNumberDelay = 1 // this contract require calls every block @@ -737,8 +777,10 @@ func TestCorpusReplayability(t *testing.T) { runFuzzerTest(t, &fuzzerSolcFileTest{ filePath: "testdata/contracts/value_generation/match_uints_xy.sol", configUpdates: func(config *config.ProjectConfig) { - config.Fuzzing.DeploymentOrder = []string{"TestContract"} + config.Fuzzing.TargetContracts = []string{"TestContract"} config.Fuzzing.CorpusDirectory = "corpus" + config.Fuzzing.Testing.AssertionTesting.Enabled = false + config.Fuzzing.Testing.OptimizationTesting.Enabled = false }, method: func(f *fuzzerTestContext) { // Setup checks for event emissions @@ -776,14 +818,16 @@ func TestCorpusReplayability(t *testing.T) { }) } -// TestDeploymentOrderWithCoverage will ensure that changing the deployment order does not lead to the same coverage -// This is also proof that changing the order changes the addresses of the contracts leading to the coverage not being -// useful. +// TestDeploymentOrderWithCoverage will ensure that changing the order of deployment for the target contracts does not +// lead to the same coverage. This is also proof that changing the order changes the addresses of the contracts leading +// to the coverage not being useful. func TestDeploymentOrderWithCoverage(t *testing.T) { runFuzzerTest(t, &fuzzerSolcFileTest{ filePath: "testdata/contracts/deployments/deployment_order.sol", configUpdates: func(config *config.ProjectConfig) { - config.Fuzzing.DeploymentOrder = []string{"InheritedFirstContract", "InheritedSecondContract"} + config.Fuzzing.TargetContracts = []string{"InheritedFirstContract", "InheritedSecondContract"} + config.Fuzzing.Testing.AssertionTesting.Enabled = false + config.Fuzzing.Testing.OptimizationTesting.Enabled = false }, method: func(f *fuzzerTestContext) { // Setup checks for event emissions @@ -806,8 +850,8 @@ func TestDeploymentOrderWithCoverage(t *testing.T) { return nil }) - // Update the deployment order - f.fuzzer.config.Fuzzing.DeploymentOrder = []string{"InheritedSecondContract", "InheritedFirstContract"} + // Update the order of target contracts + f.fuzzer.config.Fuzzing.TargetContracts = []string{"InheritedSecondContract", "InheritedFirstContract"} // Note that the fuzzer won't spin up any workers or fuzz anything. We just want to test that the coverage // maps don't populate due to deployment order changes diff --git a/fuzzing/fuzzer_worker.go b/fuzzing/fuzzer_worker.go index a7ac3f4d..7ee4e93a 100644 --- a/fuzzing/fuzzer_worker.go +++ b/fuzzing/fuzzer_worker.go @@ -315,8 +315,8 @@ func (fw *FuzzerWorker) testNextCallSequence() (calls.CallSequence, []ShrinkCall // If this was not a new call sequence, indicate not to save the shrunken result to the corpus again. if !isNewSequence { - for _, shrinkRequest := range shrinkCallSequenceRequests { - shrinkRequest.RecordResultInCorpus = false + for i := 0; i < len(shrinkCallSequenceRequests); i++ { + shrinkCallSequenceRequests[i].RecordResultInCorpus = false } } @@ -391,73 +391,95 @@ func (fw *FuzzerWorker) testShrunkenCallSequence(possibleShrunkSequence calls.Ca // shrinkCallSequence takes a provided call sequence and attempts to shrink it by looking for redundant // calls which can be removed, and values which can be minimized, while continuing to satisfy the provided shrink // verifier. +// +// This function should *always* be called if there are shrink requests, and should always report a result, +// even if it is the original sequence provided. +// // Returns a call sequence that was optimized to include as little calls as possible to trigger the // expected conditions, or an error if one occurred. func (fw *FuzzerWorker) shrinkCallSequence(callSequence calls.CallSequence, shrinkRequest ShrinkCallSequenceRequest) (calls.CallSequence, error) { // Define a variable to track our most optimized sequence across all optimization iterations. optimizedSequence := callSequence - // First try to remove any calls we can. We go from start to end to avoid index shifting. - for i := 0; i < len(optimizedSequence); { - // If our fuzzer context is done, exit out immediately without results. - if utils.CheckContextDone(fw.fuzzer.ctx) { - return nil, nil - } - - // Recreate our current optimized sequence without the item at this index - possibleShrunkSequence, err := optimizedSequence.Clone() - if err != nil { - return nil, err - } - possibleShrunkSequence = append(possibleShrunkSequence[:i], possibleShrunkSequence[i+1:]...) - - // Test the shrunken sequence. - validShrunkSequence, err := fw.testShrunkenCallSequence(possibleShrunkSequence, shrinkRequest) - if err != nil { - return nil, err - } - - // If this current sequence satisfied our conditions, set it as our optimized sequence. - if validShrunkSequence { - optimizedSequence = possibleShrunkSequence - } else { - // We didn't remove an item at this index, so we'll iterate to the next one. - i++ - } + // Obtain our shrink limits and begin shrinking. + shrinkIteration := uint64(0) + shrinkLimit := fw.fuzzer.config.Fuzzing.ShrinkLimit + shrinkingEnded := func() bool { + return shrinkIteration >= shrinkLimit || utils.CheckContextDone(fw.fuzzer.ctx) } + if shrinkLimit > 0 { + // The first pass of shrinking is greedy towards trying to remove any unnecessary calls. + // For each call in the sequence, the following removal strategies are used: + // 1) Plain removal (lower block/time gap between surrounding blocks, maintain properties of max delay) + // 2) Add block/time delay to previous call (retain original block/time, possibly exceed max delays) + // At worst, this costs `2 * len(callSequence)` shrink iterations. + fw.workerMetrics().shrinking = true + for removalStrategy := 0; removalStrategy < 2 && !shrinkingEnded(); removalStrategy++ { + for i := len(optimizedSequence) - 1; i >= 0 && !shrinkingEnded(); i-- { + // Recreate our current optimized sequence without the item at this index + possibleShrunkSequence, err := optimizedSequence.Clone() + removedCall := possibleShrunkSequence[i] + if err != nil { + return nil, err + } + possibleShrunkSequence = append(possibleShrunkSequence[:i], possibleShrunkSequence[i+1:]...) + + // Exercise the next removal strategy for this call. + if removalStrategy == 0 { + // Case 1: Plain removal. + } else if removalStrategy == 1 { + // Case 2: Add block/time delay to previous call. + if i > 0 { + possibleShrunkSequence[i-1].BlockNumberDelay += removedCall.BlockNumberDelay + possibleShrunkSequence[i-1].BlockTimestampDelay += removedCall.BlockTimestampDelay + } + } - // Next try to shrink our values of every transaction a given number of rounds. - for i := 0; i < len(optimizedSequence); i++ { - for optimizationRound := 0; optimizationRound < 200; optimizationRound++ { - // If our fuzzer context is done, exit out immediately without results. - if utils.CheckContextDone(fw.fuzzer.ctx) { - return nil, nil + // Test the shrunken sequence. + validShrunkSequence, err := fw.testShrunkenCallSequence(possibleShrunkSequence, shrinkRequest) + shrinkIteration++ + if err != nil { + return nil, err + } + + // If the current sequence satisfied our conditions, set it as our optimized sequence. + if validShrunkSequence { + optimizedSequence = possibleShrunkSequence + } } + } - // Clone the optimized sequence. - possibleShrunkSequence, _ := optimizedSequence.Clone() + // The second pass of shrinking attempts to shrink values for each call in our call sequence. + // This is performed exhaustively in a round-robin fashion for each call, until the shrink limit is hit. + for !shrinkingEnded() { + for i := len(optimizedSequence) - 1; i >= 0 && !shrinkingEnded(); i-- { + // Clone the optimized sequence. + possibleShrunkSequence, _ := optimizedSequence.Clone() + + // Loop for each argument in the currently indexed call to mutate it. + abiValuesMsgData := possibleShrunkSequence[i].Call.DataAbiValues + for j := 0; j < len(abiValuesMsgData.InputValues); j++ { + mutatedInput, err := valuegeneration.MutateAbiValue(fw.sequenceGenerator.config.ValueGenerator, fw.shrinkingValueMutator, &abiValuesMsgData.Method.Inputs[j].Type, abiValuesMsgData.InputValues[j]) + if err != nil { + return nil, fmt.Errorf("error when shrinking call sequence input argument: %v", err) + } + abiValuesMsgData.InputValues[j] = mutatedInput + } - // Loop for each argument in the currently indexed call to mutate it. - abiValuesMsgData := possibleShrunkSequence[i].Call.DataAbiValues - for j := 0; j < len(abiValuesMsgData.InputValues); j++ { - mutatedInput, err := valuegeneration.MutateAbiValue(fw.sequenceGenerator.config.ValueGenerator, fw.shrinkingValueMutator, &abiValuesMsgData.Method.Inputs[j].Type, abiValuesMsgData.InputValues[j]) + // Test the shrunken sequence. + validShrunkSequence, err := fw.testShrunkenCallSequence(possibleShrunkSequence, shrinkRequest) + shrinkIteration++ if err != nil { - return nil, fmt.Errorf("error when shrinking call sequence input argument: %v", err) + return nil, err } - abiValuesMsgData.InputValues[j] = mutatedInput - } - // Test the shrunken sequence. - validShrunkSequence, err := fw.testShrunkenCallSequence(possibleShrunkSequence, shrinkRequest) - if err != nil { - return nil, err - } - - // If this current sequence satisfied our conditions, set it as our optimized sequence. - if validShrunkSequence { - optimizedSequence = possibleShrunkSequence + // If this current sequence satisfied our conditions, set it as our optimized sequence. + if validShrunkSequence { + optimizedSequence = possibleShrunkSequence + } } } + fw.workerMetrics().shrinking = false } // If the shrink request wanted the sequence recorded in the corpus, do so now. diff --git a/fuzzing/fuzzer_worker_sequence_generator.go b/fuzzing/fuzzer_worker_sequence_generator.go index 2b9358a4..a931b04a 100644 --- a/fuzzing/fuzzer_worker_sequence_generator.go +++ b/fuzzing/fuzzer_worker_sequence_generator.go @@ -2,11 +2,12 @@ package fuzzing import ( "fmt" + "math/big" + "github.com/crytic/medusa/fuzzing/calls" "github.com/crytic/medusa/fuzzing/valuegeneration" "github.com/crytic/medusa/utils" "github.com/crytic/medusa/utils/randomutils" - "math/big" ) // CallSequenceGenerator generates call sequences iteratively per element, for use in fuzzing campaigns. It is attached @@ -192,7 +193,7 @@ func (g *CallSequenceGenerator) InitializeNextSequence() (bool, error) { g.fetchIndex = 0 g.prefetchModifyCallFunc = nil - // Check if there are any previously une-xecuted corpus call sequences. If there are, the fuzzer should execute + // Check if there are any previously un-executed corpus call sequences. If there are, the fuzzer should execute // those first. unexecutedSequence := g.worker.fuzzer.corpus.UnexecutedCallSequence() if unexecutedSequence != nil { @@ -336,10 +337,10 @@ func callSeqGenFuncCorpusHead(sequenceGenerator *CallSequenceGenerator, sequence // Obtain a call sequence from the corpus corpusSequence, err := sequenceGenerator.worker.fuzzer.corpus.RandomMutationTargetSequence() if err != nil { - return fmt.Errorf("could not obtain corpus call sequence for tail mutation: %v", err) + return fmt.Errorf("could not obtain corpus call sequence for head mutation: %v", err) } - // Determine a random position to slice the call sequence. + // Determine the length of the slice to be copied in the head. maxLength := utils.Min(len(sequence), len(corpusSequence)) copy(sequence, corpusSequence[:maxLength]) diff --git a/fuzzing/test_case_assertion_provider.go b/fuzzing/test_case_assertion_provider.go index 300d97dc..af1a0b47 100644 --- a/fuzzing/test_case_assertion_provider.go +++ b/fuzzing/test_case_assertion_provider.go @@ -5,6 +5,7 @@ import ( "github.com/crytic/medusa/fuzzing/calls" "github.com/crytic/medusa/fuzzing/config" "github.com/crytic/medusa/fuzzing/contracts" + "github.com/crytic/medusa/fuzzing/utils" "github.com/ethereum/go-ethereum/accounts/abi" "golang.org/x/exp/slices" "sync" @@ -44,6 +45,16 @@ func attachAssertionTestCaseProvider(fuzzer *Fuzzer) *AssertionTestCaseProvider // isTestableMethod checks whether the method is configured by the attached fuzzer to be a target of assertion testing. // Returns true if this target should be tested, false otherwise. func (t *AssertionTestCaseProvider) isTestableMethod(method abi.Method) bool { + // Do not test optimization tests + if utils.IsOptimizationTest(method, t.fuzzer.config.Fuzzing.Testing.OptimizationTesting.TestPrefixes) { + return false + } + + // Do not test property tests + if utils.IsPropertyTest(method, t.fuzzer.config.Fuzzing.Testing.PropertyTesting.TestPrefixes) { + return false + } + // Only test constant methods (pure/view) if we are configured to. return !method.IsConstant() || t.fuzzer.config.Fuzzing.Testing.AssertionTesting.TestViewMethods } @@ -73,7 +84,7 @@ func (t *AssertionTestCaseProvider) checkAssertionFailures(callSequence calls.Ca panicCode := abiutils.GetSolidityPanicCode(lastExecutionResult.Err, lastExecutionResult.ReturnData, true) failure := false if panicCode != nil { - failure = encounteredAssertionFailure(panicCode.Uint64(), t.fuzzer.config.Fuzzing.Testing.AssertionTesting.AssertionModes) + failure = encounteredAssertionFailure(panicCode.Uint64(), t.fuzzer.config.Fuzzing.Testing.AssertionTesting.PanicCodeConfig) } return &methodId, failure, nil @@ -87,8 +98,8 @@ func (t *AssertionTestCaseProvider) onFuzzerStarting(event FuzzerStartingEvent) // Create a test case for every test method. for _, contract := range t.fuzzer.ContractDefinitions() { - // If we're not testing all contracts, verify the current contract is one we specified in our deployment order. - if !t.fuzzer.config.Fuzzing.Testing.TestAllContracts && !slices.Contains(t.fuzzer.config.Fuzzing.DeploymentOrder, contract.Name()) { + // If we're not testing all contracts, verify the current contract is one we specified in our target contracts + if !t.fuzzer.config.Fuzzing.Testing.TestAllContracts && !slices.Contains(t.fuzzer.config.Fuzzing.TargetContracts, contract.Name()) { continue } @@ -241,7 +252,7 @@ func (t *AssertionTestCaseProvider) callSequencePostCallTest(worker *FuzzerWorke // code was enabled in the config. Note that the panic codes are defined in the abiutils package and that this function // panic if it is provided a panic code that is not defined in the abiutils package. // TODO: This is a terrible design and a future PR should be made to maintain assertion and panic logic correctly -func encounteredAssertionFailure(panicCode uint64, conf config.AssertionModesConfig) bool { +func encounteredAssertionFailure(panicCode uint64, conf config.PanicCodeConfig) bool { // Switch on panic code switch panicCode { case abiutils.PanicCodeCompilerInserted: diff --git a/fuzzing/test_case_optimization_provider.go b/fuzzing/test_case_optimization_provider.go index 424db7e5..93c7e536 100644 --- a/fuzzing/test_case_optimization_provider.go +++ b/fuzzing/test_case_optimization_provider.go @@ -5,10 +5,10 @@ import ( "github.com/crytic/medusa/fuzzing/calls" "github.com/crytic/medusa/fuzzing/contracts" "github.com/crytic/medusa/fuzzing/executiontracer" - "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/crytic/medusa/fuzzing/utils" "github.com/ethereum/go-ethereum/core" + "golang.org/x/exp/slices" "math/big" - "strings" "sync" ) @@ -45,6 +45,11 @@ type optimizationTestCaseProviderWorkerState struct { // attachOptimizationTestCaseProvider attaches a new OptimizationTestCaseProvider to the Fuzzer and returns it. func attachOptimizationTestCaseProvider(fuzzer *Fuzzer) *OptimizationTestCaseProvider { + // If there are no testing prefixes, then there is no reason to attach a test case provider and subscribe to events + if len(fuzzer.config.Fuzzing.Testing.OptimizationTesting.TestPrefixes) == 0 { + return nil + } + // Create a test case provider t := &OptimizationTestCaseProvider{ fuzzer: fuzzer, @@ -60,21 +65,6 @@ func attachOptimizationTestCaseProvider(fuzzer *Fuzzer) *OptimizationTestCasePro return t } -// isOptimizationTest check whether the method is an optimization test given potential naming prefixes it must conform to -// and its underlying input/output arguments. -func (t *OptimizationTestCaseProvider) isOptimizationTest(method abi.Method) bool { - // Loop through all enabled prefixes to find a match - for _, prefix := range t.fuzzer.Config().Fuzzing.Testing.OptimizationTesting.TestPrefixes { - if strings.HasPrefix(method.Name, prefix) { - // An optimization test must take no inputs and return an int256 - if len(method.Inputs) == 0 && len(method.Outputs) == 1 && method.Outputs[0].Type.T == abi.IntTy && method.Outputs[0].Type.Size == 256 { - return true - } - } - } - return false -} - // runOptimizationTest executes a given optimization test method (w/ an optional execution trace) and returns the return value // from the optimization test method. This is called after every call the Fuzzer makes when testing call sequences for each test case. func (t *OptimizationTestCaseProvider) runOptimizationTest(worker *FuzzerWorker, optimizationTestMethod *contracts.DeployedContractMethod, trace bool) (*big.Int, *executiontracer.ExecutionTrace, error) { @@ -141,9 +131,14 @@ func (t *OptimizationTestCaseProvider) onFuzzerStarting(event FuzzerStartingEven // Create a test case for every optimization test method. for _, contract := range t.fuzzer.ContractDefinitions() { + // If we're not testing all contracts, verify the current contract is one we specified in our target contracts + if !t.fuzzer.config.Fuzzing.Testing.TestAllContracts && !slices.Contains(t.fuzzer.config.Fuzzing.TargetContracts, contract.Name()) { + continue + } + for _, method := range contract.CompiledContract().Abi.Methods { // Verify this method is an optimization test method - if !t.isOptimizationTest(method) { + if !utils.IsOptimizationTest(method, t.fuzzer.config.Fuzzing.Testing.OptimizationTesting.TestPrefixes) { continue } // Create local variables to avoid pointer types in the loop being overridden. diff --git a/fuzzing/test_case_property_provider.go b/fuzzing/test_case_property_provider.go index 10399cc5..6d85db17 100644 --- a/fuzzing/test_case_property_provider.go +++ b/fuzzing/test_case_property_provider.go @@ -2,15 +2,15 @@ package fuzzing import ( "fmt" + "math/big" + "sync" + "github.com/crytic/medusa/fuzzing/calls" "github.com/crytic/medusa/fuzzing/contracts" "github.com/crytic/medusa/fuzzing/executiontracer" - "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/crytic/medusa/fuzzing/utils" "github.com/ethereum/go-ethereum/core" "golang.org/x/exp/slices" - "math/big" - "strings" - "sync" ) // PropertyTestCaseProvider is a provider for on-chain property tests. @@ -47,6 +47,11 @@ type propertyTestCaseProviderWorkerState struct { // attachPropertyTestCaseProvider attaches a new PropertyTestCaseProvider to the Fuzzer and returns it. func attachPropertyTestCaseProvider(fuzzer *Fuzzer) *PropertyTestCaseProvider { + // If there are no testing prefixes, then there is no reason to attach a test case provider and subscribe to events + if len(fuzzer.config.Fuzzing.Testing.PropertyTesting.TestPrefixes) == 0 { + return nil + } + // Create a test case provider t := &PropertyTestCaseProvider{ fuzzer: fuzzer, @@ -62,20 +67,6 @@ func attachPropertyTestCaseProvider(fuzzer *Fuzzer) *PropertyTestCaseProvider { return t } -// isPropertyTest check whether the method is a property test given potential naming prefixes it must conform to -// and its underlying input/output arguments. -func (t *PropertyTestCaseProvider) isPropertyTest(method abi.Method) bool { - // Loop through all enabled prefixes to find a match - for _, prefix := range t.fuzzer.Config().Fuzzing.Testing.PropertyTesting.TestPrefixes { - if strings.HasPrefix(method.Name, prefix) { - if len(method.Inputs) == 0 && len(method.Outputs) == 1 && method.Outputs[0].Type.T == abi.BoolTy { - return true - } - } - } - return false -} - // checkPropertyTestFailed executes a given property test method to see if it returns a failed status. This is used to // facilitate testing of property test methods after every call the Fuzzer makes when testing call sequences. // A boolean indicating whether an execution trace should be captured and returned is provided to the method. @@ -110,7 +101,7 @@ func (t *PropertyTestCaseProvider) checkPropertyTestFailed(worker *FuzzerWorker, // If our property test method call failed, we flag a failed test. if executionResult.Failed() { - return true, nil, nil + return true, executionTrace, nil } // Decode our ABI outputs @@ -143,14 +134,14 @@ func (t *PropertyTestCaseProvider) onFuzzerStarting(event FuzzerStartingEvent) e // Create a test case for every property test method. for _, contract := range t.fuzzer.ContractDefinitions() { - // If we're not testing all contracts, verify the current contract is one we specified in our deployment order. - if !t.fuzzer.config.Fuzzing.Testing.TestAllContracts && !slices.Contains(t.fuzzer.config.Fuzzing.DeploymentOrder, contract.Name()) { + // If we're not testing all contracts, verify the current contract is one we specified in our target contracts. + if !t.fuzzer.config.Fuzzing.Testing.TestAllContracts && !slices.Contains(t.fuzzer.config.Fuzzing.TargetContracts, contract.Name()) { continue } for _, method := range contract.CompiledContract().Abi.Methods { // Verify this method is a property test method - if !t.isPropertyTest(method) { + if !utils.IsPropertyTest(method, t.fuzzer.config.Fuzzing.Testing.PropertyTesting.TestPrefixes) { continue } diff --git a/fuzzing/testdata/contracts/assertions/assert_and_property_test.sol b/fuzzing/testdata/contracts/assertions/assert_and_property_test.sol index 90051ba5..03100042 100644 --- a/fuzzing/testdata/contracts/assertions/assert_and_property_test.sol +++ b/fuzzing/testdata/contracts/assertions/assert_and_property_test.sol @@ -5,7 +5,7 @@ contract TestContract { assert(false); } - function fuzz_failing_property() public view returns (bool) { + function property_failing_property() public view returns (bool) { // ASSERTION: fail immediately. return false; } diff --git a/fuzzing/testdata/contracts/chain/tx_out_of_gas.sol b/fuzzing/testdata/contracts/chain/tx_out_of_gas.sol index 96fb55d0..6054a7d8 100644 --- a/fuzzing/testdata/contracts/chain/tx_out_of_gas.sol +++ b/fuzzing/testdata/contracts/chain/tx_out_of_gas.sol @@ -10,7 +10,7 @@ contract TestContract { } } - function fuzz_never_apply_state_when_oog() public view returns (bool) { + function property_never_apply_state_when_oog() public view returns (bool) { // ASSERTION: this state should never be applied, as our out of gas error should revert changes. return x == 0; } diff --git a/fuzzing/testdata/contracts/cheat_codes/vm/snapshot_and_revert_to.sol b/fuzzing/testdata/contracts/cheat_codes/vm/snapshot_and_revert_to.sol new file mode 100644 index 00000000..577ff194 --- /dev/null +++ b/fuzzing/testdata/contracts/cheat_codes/vm/snapshot_and_revert_to.sol @@ -0,0 +1,58 @@ +// This test ensures that we can take a snapshot of the current state of the testchain and revert to the state at that snapshot using the snapshot and revertTo cheatcodes +pragma solidity ^0.8.0; + +interface CheatCodes { + function warp(uint256) external; + + function deal(address, uint256) external; + + function snapshot() external returns (uint256); + + function revertTo(uint256) external returns (bool); +} + +struct Storage { + uint slot0; + uint slot1; +} + +contract TestContract { + Storage store; + uint256 timestamp; + + function test() public { + // Obtain our cheat code contract reference. + CheatCodes cheats = CheatCodes( + 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D + ); + + store.slot0 = 10; + store.slot1 = 20; + timestamp = block.timestamp; + cheats.deal(address(this), 5 ether); + + // Save state + uint256 snapshot = cheats.snapshot(); + + // Change state + store.slot0 = 300; + store.slot1 = 400; + cheats.deal(address(this), 500 ether); + cheats.warp(12345); + + // Assert that state has been changed + assert(store.slot0 == 300); + assert(store.slot1 == 400); + assert(address(this).balance == 500 ether); + assert(block.timestamp == 12345); + + // Revert to snapshot + cheats.revertTo(snapshot); + + // Ensure state has been reset + assert(store.slot0 == 10); + assert(store.slot1 == 20); + assert(address(this).balance == 5 ether); + assert(block.timestamp == timestamp); + } +} diff --git a/fuzzing/testdata/contracts/corpus_mutation/specific_call_sequence.sol b/fuzzing/testdata/contracts/corpus_mutation/specific_call_sequence.sol index e66c3771..b4356dac 100644 --- a/fuzzing/testdata/contracts/corpus_mutation/specific_call_sequence.sol +++ b/fuzzing/testdata/contracts/corpus_mutation/specific_call_sequence.sol @@ -29,7 +29,7 @@ contract TestContract { } } - function fuzz_solve_me() public view returns (bool) { + function property_solve_me() public view returns (bool) { // ASSERTION: The fuzzer should be able to fail this test case and solve all challenges. return index < 7; } diff --git a/fuzzing/testdata/contracts/deployments/deploy_payable_constructors.sol b/fuzzing/testdata/contracts/deployments/deploy_payable_constructors.sol new file mode 100644 index 00000000..223d85c4 --- /dev/null +++ b/fuzzing/testdata/contracts/deployments/deploy_payable_constructors.sol @@ -0,0 +1,18 @@ +// This source file provides two contracts to test whether we are able to send ether to payable constructors. FirstContract +// should get no ether and while SecondContract should receive 1 ether. +contract FirstContract { + constructor() payable {} + + function property_contract_has_no_balance() public returns(bool) { + return address(this).balance == 0; + } +} + + +contract SecondContract { + constructor() payable {} + + function property_contract_has_balance() public returns(bool) { + return address(this).balance == 1 ether; + } +} diff --git a/fuzzing/testdata/contracts/deployments/deployment_order.sol b/fuzzing/testdata/contracts/deployments/deployment_order.sol index efdff8bc..f7c6f3ed 100644 --- a/fuzzing/testdata/contracts/deployments/deployment_order.sol +++ b/fuzzing/testdata/contracts/deployments/deployment_order.sol @@ -15,7 +15,7 @@ contract InheritedFirstContract is FirstContract { y = value + 9; } - function fuzz_never_specific_values() public view returns (bool) { + function property_never_specific_values() public view returns (bool) { // ASSERTION: x should never be 10 at the same time y is 80 return !(x == 10 && y == 80); } @@ -41,7 +41,7 @@ contract InheritedSecondContract is SecondContract { c = value + 7; } - function fuzz_never_specific_values() public view returns (bool) { + function property_never_specific_values() public view returns (bool) { // ASSERTION: a should never be 10 at the same time b is 80 at the same time c is 14 return !(a == 10 && b == 80 && c == 14); } diff --git a/fuzzing/testdata/contracts/deployments/deployment_with_args.sol b/fuzzing/testdata/contracts/deployments/deployment_with_args.sol index 11e62d12..05991266 100644 --- a/fuzzing/testdata/contracts/deployments/deployment_with_args.sol +++ b/fuzzing/testdata/contracts/deployments/deployment_with_args.sol @@ -15,15 +15,15 @@ contract DeploymentWithArgs { z = _z; } - function fuzz_checkX() public returns (bool) { + function property_checkX() public returns (bool) { return x != 123456789; } - function fuzz_checkY() public returns (bool) { + function property_checkY() public returns (bool) { return y != 0x5465; } - function fuzz_checkZ() public returns (bool) { + function property_checkZ() public returns (bool) { return z.a != 0x4d2; } @@ -40,7 +40,7 @@ contract Dependent { deployed = _deployed; } - function fuzz_checkDeployed() public returns (bool) { + function property_checkDeployed() public returns (bool) { return deployed == 0x0000000000000000000000000000000000000000; } diff --git a/fuzzing/testdata/contracts/deployments/inner_deployment.sol b/fuzzing/testdata/contracts/deployments/inner_deployment.sol index d853687e..dc26b5b7 100644 --- a/fuzzing/testdata/contracts/deployments/inner_deployment.sol +++ b/fuzzing/testdata/contracts/deployments/inner_deployment.sol @@ -1,7 +1,7 @@ // InnerDeploymentFactory deploys InnerDeployment when a method is called after deployment, and verifies the fuzzer can // match bytecode and fail the test appropriately. contract InnerDeployment { - function fuzz_inner_deployment() public view returns (bool) { + function property_inner_deployment() public view returns (bool) { // ASSERTION: Fail immediately. return false; } diff --git a/fuzzing/testdata/contracts/deployments/inner_deployment_on_construction.sol b/fuzzing/testdata/contracts/deployments/inner_deployment_on_construction.sol index 15aec27c..4e08a111 100644 --- a/fuzzing/testdata/contracts/deployments/inner_deployment_on_construction.sol +++ b/fuzzing/testdata/contracts/deployments/inner_deployment_on_construction.sol @@ -6,7 +6,7 @@ contract InnerDeployment { x = 7; } - function fuzz_inner_deployment() public view returns (bool) { + function property_inner_deployment() public view returns (bool) { // ASSERTION: Fail immediately. return false; } diff --git a/fuzzing/testdata/contracts/deployments/inner_inner_deployment.sol b/fuzzing/testdata/contracts/deployments/inner_inner_deployment.sol index 8a1b1140..7718fc82 100644 --- a/fuzzing/testdata/contracts/deployments/inner_inner_deployment.sol +++ b/fuzzing/testdata/contracts/deployments/inner_inner_deployment.sol @@ -2,7 +2,7 @@ // deployed, a method can be used to deploy an InnerInnerDeployment. We verify we can violate an invariant // in a two-layer deep dynamic deployment. contract InnerInnerDeployment { - function fuzz_inner_inner_deployment() public view returns (bool) { + function property_inner_inner_deployment() public view returns (bool) { // ASSERTION: Fail immediately. return false; } diff --git a/fuzzing/testdata/contracts/deployments/internal_library.sol b/fuzzing/testdata/contracts/deployments/internal_library.sol index ba03a08a..27201d43 100644 --- a/fuzzing/testdata/contracts/deployments/internal_library.sol +++ b/fuzzing/testdata/contracts/deployments/internal_library.sol @@ -28,7 +28,7 @@ contract TestInternalLibrary { return a + b; } - function fuzz_library_linking_broken() public view returns (bool) { + function property_library_linking_broken() public view returns (bool) { // ASSERTION: We should always be able to compute correctly. return !failedTest; } diff --git a/fuzzing/testdata/contracts/deployments/testing_scope.sol b/fuzzing/testdata/contracts/deployments/testing_scope.sol index af4f6605..e98aef38 100644 --- a/fuzzing/testdata/contracts/deployments/testing_scope.sol +++ b/fuzzing/testdata/contracts/deployments/testing_scope.sol @@ -6,7 +6,7 @@ contract TestContractChild { assert(false); } - function fuzz_failing_property_test_method_child() public view returns (bool) { + function property_failing_property_test_method_child() public view returns (bool) { return false; } } @@ -22,7 +22,7 @@ contract TestContract { assert(false); } - function fuzz_failing_property_test_method() public view returns (bool) { + function property_failing_property_test_method() public view returns (bool) { return false; } } diff --git a/fuzzing/testdata/contracts/value_generation/generate_all_types.sol b/fuzzing/testdata/contracts/value_generation/generate_all_types.sol index a4d0114c..e9a6fa75 100644 --- a/fuzzing/testdata/contracts/value_generation/generate_all_types.sol +++ b/fuzzing/testdata/contracts/value_generation/generate_all_types.sol @@ -24,7 +24,7 @@ contract GenerateAllTypes { s = ""; } - function fuzz_never_fail() public view returns (bool) { + function property_never_fail() public view returns (bool) { // ASSERTION: never fail, to keep testing value generation return true; } diff --git a/fuzzing/testdata/contracts/value_generation/match_addr_contract.sol b/fuzzing/testdata/contracts/value_generation/match_addr_contract.sol index 27654a16..9a5cdb7e 100644 --- a/fuzzing/testdata/contracts/value_generation/match_addr_contract.sol +++ b/fuzzing/testdata/contracts/value_generation/match_addr_contract.sol @@ -6,7 +6,7 @@ contract TestContract { a = value; } - function fuzz_never_specific_values() public view returns (bool) { + function property_never_specific_values() public view returns (bool) { // ASSERTION: a should not be the contract's address itself. return !(a == address(this)); } diff --git a/fuzzing/testdata/contracts/value_generation/match_addr_exact.sol b/fuzzing/testdata/contracts/value_generation/match_addr_exact.sol index 385e2bfb..b46d594e 100644 --- a/fuzzing/testdata/contracts/value_generation/match_addr_exact.sol +++ b/fuzzing/testdata/contracts/value_generation/match_addr_exact.sol @@ -12,7 +12,7 @@ contract TestContract { y = value; } - function fuzz_never_specific_values() public view returns (bool) { + function property_never_specific_values() public view returns (bool) { // ASSERTION: x and y should not equal the exact addresses below. return !(x == address(0x12345) && y == address(7)); } diff --git a/fuzzing/testdata/contracts/value_generation/match_addr_sender.sol b/fuzzing/testdata/contracts/value_generation/match_addr_sender.sol index 6be91398..b67d80e4 100644 --- a/fuzzing/testdata/contracts/value_generation/match_addr_sender.sol +++ b/fuzzing/testdata/contracts/value_generation/match_addr_sender.sol @@ -8,7 +8,7 @@ contract TestContract { sender = msg.sender; } - function fuzz_never_specific_values() public view returns (bool) { + function property_never_specific_values() public view returns (bool) { // ASSERTION: a should not be sender's address who set it. return a != sender; } diff --git a/fuzzing/testdata/contracts/value_generation/match_ints_xy.sol b/fuzzing/testdata/contracts/value_generation/match_ints_xy.sol index 2cb5ca2b..3aeff78e 100644 --- a/fuzzing/testdata/contracts/value_generation/match_ints_xy.sol +++ b/fuzzing/testdata/contracts/value_generation/match_ints_xy.sol @@ -12,7 +12,7 @@ contract TestContract { } - function fuzz_never_specific_values() public view returns (bool) { + function property_never_specific_values() public view returns (bool) { // ASSERTION: x should never be -10 at the same time y is -62 return !(x == -10 && y == -62); } diff --git a/fuzzing/testdata/contracts/value_generation/match_payable_xy.sol b/fuzzing/testdata/contracts/value_generation/match_payable_xy.sol index 68221d87..b885c42e 100644 --- a/fuzzing/testdata/contracts/value_generation/match_payable_xy.sol +++ b/fuzzing/testdata/contracts/value_generation/match_payable_xy.sol @@ -11,7 +11,7 @@ contract TestContract { paidAmount2 = msg.value; } - function fuzz_never_pay_exact_amounts() public view returns (bool) { + function property_never_pay_exact_amounts() public view returns (bool) { // ASSERTION: paid amounts should never equal the exact numbers below. return !(paidAmount == 7777 && paidAmount2 == 8888); } diff --git a/fuzzing/testdata/contracts/value_generation/match_string_exact.sol b/fuzzing/testdata/contracts/value_generation/match_string_exact.sol index 6dda6770..80f15fae 100644 --- a/fuzzing/testdata/contracts/value_generation/match_string_exact.sol +++ b/fuzzing/testdata/contracts/value_generation/match_string_exact.sol @@ -6,7 +6,7 @@ contract TestContract { s = value; } - function fuzz_never_specific_values() public view returns (bool) { + function property_never_specific_values() public view returns (bool) { // ASSERTION: s should not be the MAGIC_STRING return keccak256(abi.encodePacked((s))) != keccak256(abi.encodePacked((MAGIC_STRING))); } diff --git a/fuzzing/testdata/contracts/value_generation/match_structs_xy.sol b/fuzzing/testdata/contracts/value_generation/match_structs_xy.sol index cc6d8495..817e079b 100644 --- a/fuzzing/testdata/contracts/value_generation/match_structs_xy.sol +++ b/fuzzing/testdata/contracts/value_generation/match_structs_xy.sol @@ -23,7 +23,7 @@ contract TestContract { s = ts; } - function fuzz_never_specific_values() public view returns (bool) { + function property_never_specific_values() public view returns (bool) { // ASSERTION: x should never be 10 at the same time y is 80 return !(s.x == 10 && s.i.y == 80); } diff --git a/fuzzing/testdata/contracts/value_generation/match_uints_xy.sol b/fuzzing/testdata/contracts/value_generation/match_uints_xy.sol index a064f423..d465708a 100644 --- a/fuzzing/testdata/contracts/value_generation/match_uints_xy.sol +++ b/fuzzing/testdata/contracts/value_generation/match_uints_xy.sol @@ -12,7 +12,7 @@ contract TestContract { } - function fuzz_never_specific_values() public view returns (bool) { + function property_never_specific_values() public view returns (bool) { // ASSERTION: x should never be 10 at the same time y is 80 return !(x == 10 && y == 80); } diff --git a/fuzzing/testdata/contracts/vm_tests/block_hash_store_check.sol b/fuzzing/testdata/contracts/vm_tests/block_hash_store_check.sol index d9f3d8df..cbc757b8 100644 --- a/fuzzing/testdata/contracts/vm_tests/block_hash_store_check.sol +++ b/fuzzing/testdata/contracts/vm_tests/block_hash_store_check.sol @@ -49,7 +49,7 @@ contract TestContract { lastBlockNumber = block.number; } - function fuzz_violate_block_hash_continuity() public view returns (bool) { + function property_violate_block_hash_continuity() public view returns (bool) { // ASSERTION: we fail if our blockHash works as expected so our fuzzer will catch it. return !failedTest; } diff --git a/fuzzing/testdata/contracts/vm_tests/block_number_increasing.sol b/fuzzing/testdata/contracts/vm_tests/block_number_increasing.sol index cbcecab4..b8a6450c 100644 --- a/fuzzing/testdata/contracts/vm_tests/block_number_increasing.sol +++ b/fuzzing/testdata/contracts/vm_tests/block_number_increasing.sol @@ -10,7 +10,7 @@ contract TestContract { // This method does nothing but is left exposed so it can be called by the fuzzer to advance block.number } - function fuzz_increase_block_number_by_10() public view returns (bool) { + function property_increase_block_number_by_10() public view returns (bool) { // ASSERTION: block number should never increase more than 10 (we expect failure) return !(block.number - startingBlockNumber >= 10); } diff --git a/fuzzing/testdata/contracts/vm_tests/block_timestamp_increasing.sol b/fuzzing/testdata/contracts/vm_tests/block_timestamp_increasing.sol index f14bbaa7..7d6b0961 100644 --- a/fuzzing/testdata/contracts/vm_tests/block_timestamp_increasing.sol +++ b/fuzzing/testdata/contracts/vm_tests/block_timestamp_increasing.sol @@ -10,7 +10,7 @@ contract TestContract { // This method does nothing but is left exposed so it can be called by the fuzzer to advance blocks/timestamps. } - function fuzz_increase_block_timestamp() public view returns (bool) { + function property_increase_block_timestamp() public view returns (bool) { // ASSERTION: block timestamp should never increase more than 10 (we expect failure) return !(block.timestamp - startingBlockTimestamp >= 10); } diff --git a/fuzzing/utils/fuzz_method_utils.go b/fuzzing/utils/fuzz_method_utils.go new file mode 100644 index 00000000..1174246b --- /dev/null +++ b/fuzzing/utils/fuzz_method_utils.go @@ -0,0 +1,34 @@ +package utils + +import ( + "github.com/ethereum/go-ethereum/accounts/abi" + "strings" +) + +// IsOptimizationTest checks whether the method is an optimization test given potential naming prefixes it must conform to +// and its underlying input/output arguments. +func IsOptimizationTest(method abi.Method, prefixes []string) bool { + // Loop through all enabled prefixes to find a match + for _, prefix := range prefixes { + if strings.HasPrefix(method.Name, prefix) { + // An optimization test must take no inputs and return an int256 + if len(method.Inputs) == 0 && len(method.Outputs) == 1 && method.Outputs[0].Type.T == abi.IntTy && method.Outputs[0].Type.Size == 256 { + return true + } + } + } + return false +} + +// IsPropertyTest checks whether the method is a property test given potential naming prefixes it must conform to +// and its underlying input/output arguments. +func IsPropertyTest(method abi.Method, prefixes []string) bool { + // Loop through all enabled prefixes to find a match + for _, prefix := range prefixes { + // The property test must simply have the right prefix and take no inputs + if strings.HasPrefix(method.Name, prefix) && len(method.Inputs) == 0 { + return true + } + } + return false +} diff --git a/fuzzing/valuegeneration/abi_values.go b/fuzzing/valuegeneration/abi_values.go index 56232e9d..c0610efc 100644 --- a/fuzzing/valuegeneration/abi_values.go +++ b/fuzzing/valuegeneration/abi_values.go @@ -443,21 +443,17 @@ func encodeABIArgumentToString(inputType *abi.Type, value any) (string, error) { } return strconv.QuoteToASCII(str), nil case abi.BytesTy: - // Prepare a byte array. Return as a string enclosed with "". The returned string uses Go escape - // sequences (\t, \n, \xFF, \u0100) for non-ASCII characters and non-printable characters. b, ok := value.([]byte) if !ok { return "", fmt.Errorf("could not encode dynamic-sized bytes as the value provided is not of the correct type") } - return strconv.QuoteToASCII(string(b)), nil + // Convert the fixed byte array to a hex string + return hex.EncodeToString(b), nil case abi.FixedBytesTy: - // Prepare a fixed-size byte array. Return as a string enclosed with "". The returned string uses Go escape - // sequences (\t, \n, \xFF, \u0100) for non-ASCII characters and non-printable characters. - // TODO: Error checking to ensure `value` is of the correct type. b := reflectionutils.ArrayToSlice(reflect.ValueOf(value)).([]byte) - // Convert the byte array to a string and use the QuoteToASCII method to format the string with Go escape sequences. - return strconv.QuoteToASCII(string(b)), nil + // Convert the byte array to a hex string + return hex.EncodeToString(b), nil case abi.ArrayTy: // Prepare an array. Return as a string enclosed with [], where specific elements are comma-separated. reflectedArray := reflect.ValueOf(value) @@ -679,7 +675,7 @@ func DecodeJSONArgumentsFromMap(inputs abi.Arguments, values map[string]any, dep for i, input := range inputs { value, ok := values[input.Name] if !ok { - err := fmt.Errorf("constructor argument not provided for: name: %v", input.Name) + err := fmt.Errorf("value not not provided for argument: name: %v", input.Name) return nil, err } arg, err := decodeJSONArgument(&input.Type, value, deployedContractAddr) diff --git a/go.mod b/go.mod index 27b9f68c..16fa2f1b 100644 --- a/go.mod +++ b/go.mod @@ -12,11 +12,11 @@ require ( github.com/shopspring/decimal v1.3.1 github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 - github.com/stretchr/testify v1.8.4 - golang.org/x/crypto v0.19.0 + github.com/stretchr/testify v1.9.0 + golang.org/x/crypto v0.22.0 golang.org/x/exp v0.0.0-20230206171751-46f607a40771 golang.org/x/net v0.21.0 - golang.org/x/sys v0.17.0 + golang.org/x/sys v0.19.0 ) require ( @@ -65,7 +65,7 @@ require ( github.com/x448/float16 v0.8.4 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect golang.org/x/text v0.14.0 // indirect - google.golang.org/protobuf v1.28.1 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 4c98ad4e..ff4eb54b 100644 --- a/go.sum +++ b/go.sum @@ -280,8 +280,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM= @@ -321,8 +321,8 @@ golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20230206171751-46f607a40771 h1:xP7rWLUr1e1n2xkK5YB4LI0hPEy3LJC6Wk+D4pGlOJg= golang.org/x/exp v0.0.0-20230206171751-46f607a40771/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= @@ -398,8 +398,8 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -452,8 +452,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/logging/log_buffer.go b/logging/log_buffer.go index 70abf4a4..761d7ab5 100644 --- a/logging/log_buffer.go +++ b/logging/log_buffer.go @@ -28,6 +28,12 @@ func (l *LogBuffer) Elements() []any { // String provides the non-colorized string representation of the LogBuffer func (l LogBuffer) String() string { - _, msg, _, _ := buildMsgs(l.elements) + _, msg, _, _ := buildMsgs(l.elements...) + return msg +} + +// ColorString provides the colorized string representation of the LogBuffer +func (l LogBuffer) ColorString() string { + msg, _, _, _ := buildMsgs(l.elements...) return msg } diff --git a/logging/logger.go b/logging/logger.go index 48d5c539..e52f4414 100644 --- a/logging/logger.go +++ b/logging/logger.go @@ -5,7 +5,6 @@ import ( "github.com/crytic/medusa/logging/colors" "github.com/rs/zerolog" "io" - "os" "strings" ) @@ -57,11 +56,11 @@ type StructuredLogInfo map[string]any func NewLogger(level zerolog.Level) *Logger { return &Logger{ level: level, - structuredLogger: zerolog.New(os.Stdout).Level(zerolog.Disabled), + structuredLogger: zerolog.New(nil).Level(level), structuredWriters: make([]io.Writer, 0), - unstructuredLogger: zerolog.New(os.Stdout).Level(zerolog.Disabled), + unstructuredLogger: zerolog.New(nil).Level(level), unstructuredWriters: make([]io.Writer, 0), - unstructuredColorLogger: zerolog.New(os.Stdout).Level(zerolog.Disabled), + unstructuredColorLogger: zerolog.New(nil).Level(level), unstructuredColorWriters: make([]io.Writer, 0), } } @@ -206,6 +205,7 @@ func (l *Logger) SetLevel(level zerolog.Level) { l.structuredLogger = l.structuredLogger.Level(level) l.unstructuredColorLogger = l.unstructuredColorLogger.Level(level) l.unstructuredLogger = l.unstructuredLogger.Level(level) + } // Trace is a wrapper function that will log a trace event @@ -343,22 +343,6 @@ func chainStructuredLogInfoErrorsAndMsgs(structuredLog *zerolog.Event, unstructu // First, we need to create a formatted error string for unstructured output var errStr string for _, err := range errs { - // To make the formatting a little nicer, we will add a tab after each new line in the error so that - // errors can be better differentiated on unstructured channels - lines := make([]string, 0) - for i, line := range strings.Split(err.Error(), "\n") { - // Add a tab to the line only after the first new line in the error message - if i != 0 { - line = "\t" + line - } - lines = append(lines, line) - } - - // Update the error string to be based on the tabbed lines array - if len(lines) > 0 { - err = fmt.Errorf("%v", strings.Join(lines, "\n")) - } - // Append a bullet point and the formatted error to the error string errStr += "\n" + colors.BULLET_POINT + " " + err.Error() } diff --git a/main.go b/main.go index 4902eaeb..b0bb8eaf 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,9 @@ package main import ( + "fmt" "github.com/crytic/medusa/cmd" + "github.com/crytic/medusa/cmd/exitcodes" "os" ) @@ -9,7 +11,17 @@ func main() { // Run our root CLI command, which contains all underlying command logic and will handle parsing/invocation. err := cmd.Execute() - if err != nil { - os.Exit(1) + // Obtain the actual error and exit code from the error, if any. + var exitCode int + err, exitCode = exitcodes.GetInnerErrorAndExitCode(err) + + // If we have an error, print it. + if err != nil && exitCode != exitcodes.ExitCodeHandledError { + fmt.Println(err) + } + + // If we have a non-success exit code, exit with it. + if exitCode != exitcodes.ExitCodeSuccess { + os.Exit(exitCode) } }