API Overview (WIP) - Building Secure Contracts (original) (raw)

Building Secure Contracts

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 implemented 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 custom 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

Providers

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 or example). Similarly, interfacing with a Fuzzer requires a ProjectConfig object. After importing medusa into your Go project, you can create one like this:

// 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:

    // 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 off the bat:

The Fuzzer maintains event emitters for the following events under Fuzzer.Events.*:

The FuzzerWorker maintains event emiters for the following events under FuzzerWorker.Events.*:

The TestChain maintains event emitters for the following events under TestChain.Events.*:

Hooks

The Fuzzer maintains hooks for some of its functionality under Fuzzer.Hooks.*:

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) and its test cases (found here) 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:

    // 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
    }