Skip to content

Commit

Permalink
Coverage report generation (#111)
Browse files Browse the repository at this point in the history
* Added support for source coverage analysis and coverage report generation
* Added support for parsing source maps, translating instruction index->offset
* Added support for reverted coverage collection
* Changed corpus to save different call sequences to different folders.
* Improved init bytecode and runtime bytecode matching
* Fix corpus initialization not measuring initial deployment coverage on fuzzer startup
* Miscellaneous fixes and improvements

---------

Co-authored-by: David Pokora <[email protected]>
Co-authored-by: anishnaik <[email protected]>
  • Loading branch information
3 people authored May 22, 2023
1 parent b7ea61b commit 184905f
Show file tree
Hide file tree
Showing 37 changed files with 1,762 additions and 722 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ jobs:
node-version: 18.15

- name: Install Node dependencies
run: npm install -g hardhat truffle
run: npm install -g hardhat

- name: Install Python dependencies
run: |
Expand Down
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ To better understand how to make responsible open source contributions, consider
When introducing changes to the project, note the following requirements:

- All changes to the main branch should be introduced via pull requests.
- All branches created for pull requests should follow the `dev/*` naming convention, e.g. `dev/coverage-reports`.
- Every pull request **must** be reviewed by at least one other peer prior to being merged into the main branch.
- Code **must** be supported on Linux, macOS, and Windows.
- Code **must** be sufficiently commented:
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ It provides parallelized fuzz testing of smart contracts through CLI, or its Go

### Precompiled binaries

To use `medusa`, first ensure you have [crytic-compile](https://github.com/crytic/crytic-compile) and a suitable compilation framework (e.g. `solc`, `truffle`, `hardhat`) installed on your machine.
To use `medusa`, first ensure you have [crytic-compile](https://github.com/crytic/crytic-compile) and a suitable compilation framework (e.g. `solc`, `hardhat`) installed on your machine.

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

Expand Down Expand Up @@ -85,7 +85,7 @@ This will use the `medusa.json` configuration in the current directory and begin

## 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), `truffle`, and `hardhat` available on your system.
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.
Expand Down
6 changes: 4 additions & 2 deletions compilation/platforms/crytic_compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,9 @@ func (c *CryticCompilationConfig) Compile() ([]types.Compilation, string, error)
BinRuntime string `json:"bin-runtime"`
}
type solcExportData struct {
Sources map[string]solcExportSource `json:"sources"`
Contracts map[string]solcExportContract `json:"contracts"`
Sources map[string]solcExportSource `json:"sources"`
Contracts map[string]solcExportContract `json:"contracts"`
SourceList []string `json:"sourceList"`
}

// Loop through each .json file for compilation units.
Expand All @@ -175,6 +176,7 @@ func (c *CryticCompilationConfig) Compile() ([]types.Compilation, string, error)

// Create a compilation object that will store the contracts and source information.
compilation := types.NewCompilation()
compilation.SourceList = solcExport.SourceList

// Loop through all sources and parse them into our types.
for sourcePath, source := range solcExport.Sources {
Expand Down
38 changes: 33 additions & 5 deletions compilation/platforms/crytic_compile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func testCryticGetCompiledSourceByBaseName(sources map[string]types.CompiledSour
// file path.
func TestCryticSingleFileAbsolutePath(t *testing.T) {
// Copy our testdata over to our testing directory
contractPath := testutils.CopyToTestDirectory(t, "testdata/solc/SimpleContract.sol")
contractPath := testutils.CopyToTestDirectory(t, "testdata/solc/basic/SimpleContract.sol")

// Execute our tests in the given test path
testutils.ExecuteInDirectory(t, contractPath, func() {
Expand Down Expand Up @@ -67,7 +67,7 @@ func TestCryticSingleFileAbsolutePath(t *testing.T) {
// file path in the working directory.
func TestCryticSingleFileRelativePathSameDirectory(t *testing.T) {
// Copy our testdata over to our testing directory
contractPath := testutils.CopyToTestDirectory(t, "testdata/solc/SimpleContract.sol")
contractPath := testutils.CopyToTestDirectory(t, "testdata/solc/basic/SimpleContract.sol")
contractName := filepath.Base(contractPath)

// Execute our tests in the given test path
Expand Down Expand Up @@ -96,7 +96,7 @@ func TestCryticSingleFileRelativePathSameDirectory(t *testing.T) {
// file path in a child directory of the working directory.
func TestCryticSingleFileRelativePathChildDirectory(t *testing.T) {
// Copy our testdata over to our testing directory
contractPath := testutils.CopyToTestDirectory(t, "testdata/solc/SimpleContract.sol")
contractPath := testutils.CopyToTestDirectory(t, "testdata/solc/basic/SimpleContract.sol")

// Move it to a subdirectory
contractDirectory := filepath.Dir(contractPath)
Expand Down Expand Up @@ -137,7 +137,7 @@ func TestCryticSingleFileRelativePathChildDirectory(t *testing.T) {
// a relative path provided.
func TestCryticSingleFileBuildDirectoryArgRelativePath(t *testing.T) {
// Copy our testdata over to our testing directory
contractPath := testutils.CopyToTestDirectory(t, "testdata/solc/SimpleContract.sol")
contractPath := testutils.CopyToTestDirectory(t, "testdata/solc/basic/SimpleContract.sol")

// Execute our tests in the given test path
testutils.ExecuteInDirectory(t, contractPath, func() {
Expand Down Expand Up @@ -172,7 +172,7 @@ func TestCryticSingleFileBuildDirectoryArgRelativePath(t *testing.T) {
// (e.g. export-dir, export-format)
func TestCryticSingleFileBadArgs(t *testing.T) {
// Copy our testdata over to our testing directory
contractPath := testutils.CopyToTestDirectory(t, "testdata/solc/SimpleContract.sol")
contractPath := testutils.CopyToTestDirectory(t, "testdata/solc/basic/SimpleContract.sol")

// Execute our tests in the given test path
testutils.ExecuteInDirectory(t, contractPath, func() {
Expand All @@ -198,6 +198,34 @@ func TestCryticSingleFileBadArgs(t *testing.T) {
})
}

// TestCryticMultipleFiles tests compilation of a single target that inherits from another file.
func TestCryticMultipleFiles(t *testing.T) {
// Copy our testdata over to our testing directory
contractPath := testutils.CopyToTestDirectory(t, "testdata/solc/basic/")

// Execute our tests in the given test path
testutils.ExecuteInDirectory(t, contractPath, func() {
// Create our platform configuration
config := NewCryticCompilationConfig("DerivedContract.sol")

// Compile the file
compilations, _, err := config.Compile()
assert.NoError(t, err)

// Verify there is one compilation object
assert.EqualValues(t, 1, len(compilations))
// Verify there are two sources
assert.EqualValues(t, 2, len(compilations[0].Sources))

// Verify there are three contracts
contractCount := 0
for _, source := range compilations[0].Sources {
contractCount += len(source.Contracts)
}
assert.EqualValues(t, 3, contractCount)
})
}

// TestCryticDirectoryNoArgs tests compilation of a hardhat directory with no addition arguments provided
func TestCryticDirectoryNoArgs(t *testing.T) {
// Copy our testdata over to our testing directory
Expand Down
13 changes: 13 additions & 0 deletions compilation/platforms/solc.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,18 @@ func (s *SolcCompilationConfig) Compile() ([]types.Compilation, string, error) {

// Create a compilation unit out of this.
compilation := types.NewCompilation()
if sourceList, ok := results["sourceList"]; ok {
if sourceListCasted, ok := sourceList.([]any); ok {
compilation.SourceList = make([]string, len(sourceListCasted))
for i := 0; i < len(sourceListCasted); i++ {
compilation.SourceList[i] = sourceListCasted[i].(string)
}
} else {
return nil, "", fmt.Errorf("could not parse compiled source artifact because 'sourcesList' was not a []string type")
}
} else {
return nil, "", fmt.Errorf("could not parse compiled source artifact because 'sourcesList' did not exist")
}

// Parse our sources from solc output
if sources, ok := results["sources"]; ok {
Expand Down Expand Up @@ -135,6 +147,7 @@ func (s *SolcCompilationConfig) Compile() ([]types.Compilation, string, error) {
if err != nil {
return nil, "", err
}

for name, contract := range contracts {
// Split our name which should be of form "filename:contractname"
nameSplit := strings.Split(name, ":")
Expand Down
15 changes: 7 additions & 8 deletions compilation/platforms/solc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ func TestSolcVersion(t *testing.T) {
// with an absolute target path in our platform config.
func TestSimpleSolcCompilationAbsolutePath(t *testing.T) {
// Copy our testdata over to our testing directory
contractPath := testutils.CopyToTestDirectory(t, "testdata/solc/SimpleContract.sol")
contractDirectory := testutils.CopyToTestDirectory(t, "testdata/solc/basic/")

// Execute our tests in the given test path
testutils.ExecuteInDirectory(t, contractPath, func() {
testutils.ExecuteInDirectory(t, contractDirectory, func() {
// Create a solc provider
solc := NewSolcCompilationConfig(contractPath)
solc := NewSolcCompilationConfig(filepath.Join(contractDirectory, "DerivedContract.sol"))

// Obtain our compilations and ensure we didn't encounter an error
compilations, _, err := solc.Compile()
Expand All @@ -37,13 +37,12 @@ func TestSimpleSolcCompilationAbsolutePath(t *testing.T) {
// with a relative target path in our platform config.
func TestSimpleSolcCompilationRelativePath(t *testing.T) {
// Copy our testdata over to our testing directory
contractPath := testutils.CopyToTestDirectory(t, "testdata/solc/SimpleContract.sol")
contractName := filepath.Base(contractPath)
contractDirectory := testutils.CopyToTestDirectory(t, "testdata/solc/basic/")

// Execute our tests in the given test path
testutils.ExecuteInDirectory(t, contractPath, func() {
testutils.ExecuteInDirectory(t, contractDirectory, func() {
// Create a solc provider
solc := NewSolcCompilationConfig(contractName)
solc := NewSolcCompilationConfig("DerivedContract.sol")

// Obtain our solc version and ensure we didn't encounter an error
compilations, _, err := solc.Compile()
Expand All @@ -55,7 +54,7 @@ func TestSimpleSolcCompilationRelativePath(t *testing.T) {
// TestFailedSolcCompilation tests that a single contract of invalid form should fail compilation.
func TestFailedSolcCompilation(t *testing.T) {
// Copy our testdata over to our testing directory
contractPath := testutils.CopyToTestDirectory(t, "testdata/solc/FailedCompilationContract.sol")
contractPath := testutils.CopyToTestDirectory(t, "testdata/solc/bad/FailedCompilationContract.sol")

// Execute our tests in the given test path
testutils.ExecuteInDirectory(t, contractPath, func() {
Expand Down
20 changes: 0 additions & 20 deletions compilation/platforms/testdata/solc/SimpleContract.sol

This file was deleted.

9 changes: 9 additions & 0 deletions compilation/platforms/testdata/solc/basic/DerivedContract.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import "./SimpleContract.sol";

contract DerivedContract is SimpleContract {
uint z;

function setZ(uint value) public {
z = value;
}
}
26 changes: 26 additions & 0 deletions compilation/platforms/testdata/solc/basic/SimpleContract.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
contract SimpleContract {
uint x;
uint y;

function setX(uint value) public {
x = value;
}

function setY(uint value) public {
y = value;
}
}

contract SimpleContract2 {
uint x;
uint y;

function setX(uint value) public returns (bool) {
x = value;
return true;
}

function setY(uint value) public {
y = value;
}
}

This file was deleted.

This file was deleted.

Empty file.
Loading

0 comments on commit 184905f

Please sign in to comment.