Skip to content

Commit

Permalink
Merge pull request #427 from crytic/update
Browse files Browse the repository at this point in the history
fix coverage report bugs (source unit ID issues) and method selection process
  • Loading branch information
anishnaik authored Aug 2, 2024
2 parents 924247c + f4d710f commit a08a148
Show file tree
Hide file tree
Showing 20 changed files with 547 additions and 121 deletions.
1 change: 0 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ jobs:
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 }}-${{ runner.arch }}
Expand Down
104 changes: 104 additions & 0 deletions DEV.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Debugging and Development

## Debugging

The following scripts are available for Medusa developers for debugging changes to the fuzzer.

### Corpus diff

The corpus diff script is used to compare two corpora and identify the methods that are present in one but not the other. This is useful for identifying methods that are missing from a corpus that should be present.

```shell
python3 scripts/corpus_diff.py corpus1 corpus2
```

```shell
Methods only in ~/corpus1:
- clampSplitWeight(uint32,uint32)

Methods only in ~/corpus2:
<None>
```

### Corpus stats

The corpus stats script is used to generate statistics about a corpus. This includes the number of sequences, the average length of sequences, and the frequency of methods called.

```shell
python3 scripts/corpus_stats.py corpus
```

```shell
Number of Sequences in ~/corpus: 130

Average Length of Transactions List: 43

Frequency of Methods Called:
- testReceiversReceivedSplit(uint8): 280
- setMaxEndHints(uint32,uint32): 174
- setStreamBalanceWithdrawAll(uint8): 139
- giveClampedAmount(uint8,uint8,uint128): 136
- receiveStreamsSplitAndCollectToSelf(uint8): 133
- testSqueezeViewVsActual(uint8,uint8): 128
- testSqueeze(uint8,uint8): 128
- testSetStreamBalance(uint8,int128): 128
- addStreamWithClamping(uint8,uint8,uint160,uint32,uint32,int128): 125
- removeAllSplits(uint8): 118
- testSplittableAfterSplit(uint8): 113
- testSqueezableVsReceived(uint8): 111
- testBalanceAtInFuture(uint8,uint8,uint160): 108
- testRemoveStreamShouldNotRevert(uint8,uint256): 103
- invariantWithdrawAllTokensShouldNotRevert(): 103
- collect(uint8,uint8): 101
- invariantAmtPerSecVsMinAmtPerSec(uint8,uint256): 98
- testSqueezableAmountCantBeWithdrawn(uint8,uint8): 97
- split(uint8): 97
- invariantWithdrawAllTokens(): 95
- testReceiveStreams(uint8,uint32): 93
- invariantAccountingVsTokenBalance(): 92
- testSqueezeWithFuzzedHistoryShouldNotRevert(uint8,uint8,uint256,bytes32): 91
- testSqueezableAmountCantBeUndone(uint8,uint8,uint160,uint32,uint32,int128): 87
- testCollect(uint8,uint8): 86
- testSetStreamBalanceWithdrawAllShouldNotRevert(uint8): 86
- testAddStreamShouldNotRevert(uint8,uint8,uint160,uint32,uint32,int128): 85
- testReceiveStreamsShouldNotRevert(uint8): 84
- addSplitsReceiver(uint8,uint8,uint32): 84
- setStreamBalanceWithClamping(uint8,int128): 82
- addSplitsReceiverWithClamping(uint8,uint8,uint32): 80
- testSetStreamBalanceShouldNotRevert(uint8,int128): 80
- testSplitShouldNotRevert(uint8): 80
- squeezeAllAndReceiveAndSplitAndCollectToSelf(uint8): 79
- addStreamImmediatelySqueezable(uint8,uint8,uint160): 79
- testSetSplitsShouldNotRevert(uint8,uint8,uint32): 78
- invariantSumAmtDeltaIsZero(uint8): 78
- testReceiveStreamsViewConsistency(uint8,uint32): 76
- squeezeToSelf(uint8): 74
- collectToSelf(uint8): 72
- setStreams(uint8,uint8,uint160,uint32,uint32,int128): 70
- receiveStreamsAllCycles(uint8): 69
- invariantWithdrawShouldAlwaysFail(uint256): 68
- addStream(uint8,uint8,uint160,uint32,uint32,int128): 68
- squeezeWithFuzzedHistory(uint8,uint8,uint256,bytes32): 67
- setStreamsWithClamping(uint8,uint8,uint160,uint32,uint32,int128): 67
- splitAndCollectToSelf(uint8): 67
- testSqueezeWithFullyHashedHistory(uint8,uint8): 65
- give(uint8,uint8,uint128): 65
- setSplits(uint8,uint8,uint32): 65
- testSqueezeTwice(uint8,uint8,uint256,bytes32): 65
- testSetStreamsShouldNotRevert(uint8,uint8,uint160,uint32,uint32,int128): 64
- squeezeAllSenders(uint8): 63
- removeStream(uint8,uint256): 62
- testCollectableAfterSplit(uint8): 58
- testCollectShouldNotRevert(uint8,uint8): 56
- testReceiveStreamsViewVsActual(uint8,uint32): 55
- receiveStreams(uint8,uint32): 55
- setSplitsWithClamping(uint8,uint8,uint32): 55
- testGiveShouldNotRevert(uint8,uint8,uint128): 47
- setStreamBalance(uint8,int128): 47
- squeezeWithDefaultHistory(uint8,uint8): 45
- testSplitViewVsActual(uint8): 45
- testAddSplitsShouldNotRevert(uint8,uint8,uint32): 30
- testSqueezeWithDefaultHistoryShouldNotRevert(uint8,uint8): 23

Number of Unique Methods: 65
```
12 changes: 6 additions & 6 deletions chain/test_chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,15 +215,15 @@ func TestChainDynamicDeployments(t *testing.T) {
compilations, _, err := cryticCompile.Compile()
assert.NoError(t, err)
assert.EqualValues(t, 1, len(compilations))
assert.EqualValues(t, 1, len(compilations[0].Sources))
assert.EqualValues(t, 1, len(compilations[0].SourcePathToArtifact))

// Obtain our chain and senders
chain, senders := createChain(t)

// Deploy each contract that has no construct arguments.
deployCount := 0
for _, compilation := range compilations {
for _, source := range compilation.Sources {
for _, source := range compilation.SourcePathToArtifact {
for _, contract := range source.Contracts {
contract := contract
if len(contract.Abi.Constructor.Inputs) == 0 {
Expand Down Expand Up @@ -329,7 +329,7 @@ func TestChainDeploymentWithArgs(t *testing.T) {
compilations, _, err := cryticCompile.Compile()
assert.NoError(t, err)
assert.EqualValues(t, 1, len(compilations))
assert.EqualValues(t, 1, len(compilations[0].Sources))
assert.EqualValues(t, 1, len(compilations[0].SourcePathToArtifact))

// Obtain our chain and senders
chain, senders := createChain(t)
Expand All @@ -346,7 +346,7 @@ func TestChainDeploymentWithArgs(t *testing.T) {
// Deploy each contract
deployCount := 0
for _, compilation := range compilations {
for _, source := range compilation.Sources {
for _, source := range compilation.SourcePathToArtifact {
for contractName, contract := range source.Contracts {
contract := contract

Expand Down Expand Up @@ -467,7 +467,7 @@ func TestChainCloning(t *testing.T) {

// Deploy each contract that has no construct arguments 10 times.
for _, compilation := range compilations {
for _, source := range compilation.Sources {
for _, source := range compilation.SourcePathToArtifact {
for _, contract := range source.Contracts {
contract := contract
if len(contract.Abi.Constructor.Inputs) == 0 {
Expand Down Expand Up @@ -563,7 +563,7 @@ func TestChainCallSequenceReplayMatchSimple(t *testing.T) {

// Deploy each contract that has no construct arguments 10 times.
for _, compilation := range compilations {
for _, source := range compilation.Sources {
for _, source := range compilation.SourcePathToArtifact {
for _, contract := range source.Contracts {
contract := contract
if len(contract.Abi.Constructor.Inputs) == 0 {
Expand Down
51 changes: 39 additions & 12 deletions compilation/platforms/crytic_compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ func (c *CryticCompilationConfig) Compile() ([]types.Compilation, string, error)
var compilationList []types.Compilation

// Define the structure of our crytic-compile export data.
type solcExportSource struct {
type solcSourceUnit struct {
AST any `json:"AST"`
}
type solcExportContract struct {
Expand All @@ -154,9 +154,8 @@ 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"`
SourceList []string `json:"sourceList"`
Sources map[string]solcSourceUnit `json:"sources"`
Contracts map[string]solcExportContract `json:"contracts"`
}

// Loop through each .json file for compilation units.
Expand All @@ -176,14 +175,41 @@ 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

// Create a map of contract names to their kinds
contractKinds := make(map[string]types.ContractKind)

// Loop through all sources and parse them into our types.
for sourcePath, source := range solcExport.Sources {
compilation.Sources[sourcePath] = types.CompiledSource{
Ast: source.AST,
Contracts: make(map[string]types.CompiledContract),
// Convert the AST into our version of the AST (types.AST)
var ast types.AST
b, err = json.Marshal(source.AST)
if err != nil {
return nil, "", fmt.Errorf("could not encode AST from sources: %v", err)
}
err = json.Unmarshal(b, &ast)
if err != nil {
return nil, "", fmt.Errorf("could not parse AST from sources: %v", err)
}

// From the AST, extract the contract kinds where the contract definition could be for a contract, library,
// or interface
for _, node := range ast.Nodes {
if node.GetNodeType() == "ContractDefinition" {
contractDefinition := node.(types.ContractDefinition)
contractKinds[contractDefinition.CanonicalName] = contractDefinition.Kind
}
}

// Retrieve the source unit ID
sourceUnitId := ast.GetSourceUnitID()
compilation.SourcePathToArtifact[sourcePath] = types.SourceArtifact{
// TODO: Our types.AST is not the same as the original AST but we could parse it and avoid using "any"
Ast: source.AST,
Contracts: make(map[string]types.CompiledContract),
SourceUnitId: sourceUnitId,
}
compilation.SourceIdToPath[sourceUnitId] = sourcePath
}

// Loop through all contracts and parse them into our types.
Expand All @@ -198,12 +224,12 @@ func (c *CryticCompilationConfig) Compile() ([]types.Compilation, string, error)

// Ensure a source exists for this, or create one if our path somehow differed from any
// path not existing in the "sources" key at the root of the export.
if _, ok := compilation.Sources[sourcePath]; !ok {
parentSource := types.CompiledSource{
if _, ok := compilation.SourcePathToArtifact[sourcePath]; !ok {
parentSource := types.SourceArtifact{
Ast: nil,
Contracts: make(map[string]types.CompiledContract),
}
compilation.Sources[sourcePath] = parentSource
compilation.SourcePathToArtifact[sourcePath] = parentSource
}

// Parse the ABI
Expand All @@ -223,12 +249,13 @@ func (c *CryticCompilationConfig) Compile() ([]types.Compilation, string, error)
}

// Add contract details
compilation.Sources[sourcePath].Contracts[contractName] = types.CompiledContract{
compilation.SourcePathToArtifact[sourcePath].Contracts[contractName] = types.CompiledContract{
Abi: *contractAbi,
InitBytecode: initBytecode,
RuntimeBytecode: runtimeBytecode,
SrcMapsInit: contract.SrcMap,
SrcMapsRuntime: contract.SrcMapRuntime,
Kind: contractKinds[contractName],
}
}

Expand Down
43 changes: 22 additions & 21 deletions compilation/platforms/crytic_compile_test.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
package platforms

import (
"github.com/crytic/medusa/compilation/types"
"github.com/crytic/medusa/utils"
"github.com/crytic/medusa/utils/testutils"
"github.com/stretchr/testify/assert"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"

"github.com/crytic/medusa/compilation/types"
"github.com/crytic/medusa/utils"
"github.com/crytic/medusa/utils/testutils"
"github.com/stretchr/testify/assert"
)

// testCryticGetCompiledSourceByBaseName checks if a given source file exists in a given compilation's map of sources.
// The source file is the file name of a specific file. This function simply checks one of the paths ends with
// this name. Avoid including any directories in case the path separators differ per system.
// Returns the types.CompiledSource (mapping value) associated to the path if it is found. Returns nil otherwise.
func testCryticGetCompiledSourceByBaseName(sources map[string]types.CompiledSource, name string) *types.CompiledSource {
func testCryticGetCompiledSourceByBaseName(sources map[string]types.SourceArtifact, name string) *types.SourceArtifact {
// Obtain a lower case version of our name to search for
lowerName := strings.ToLower(name)

Expand Down Expand Up @@ -53,10 +54,10 @@ func TestCryticSingleFileAbsolutePath(t *testing.T) {
// One compilation object
assert.EqualValues(t, 1, len(compilations))
// One source because we specified one file
assert.EqualValues(t, 1, len(compilations[0].Sources))
assert.EqualValues(t, 1, len(compilations[0].SourcePathToArtifact))
// Two contracts in SimpleContract.sol
contractCount := 0
for _, source := range compilations[0].Sources {
for _, source := range compilations[0].SourcePathToArtifact {
contractCount += len(source.Contracts)
}
assert.EqualValues(t, 2, contractCount)
Expand All @@ -82,10 +83,10 @@ func TestCryticSingleFileRelativePathSameDirectory(t *testing.T) {
// One compilation object
assert.EqualValues(t, 1, len(compilations))
// One source because we specified one file
assert.EqualValues(t, 1, len(compilations[0].Sources))
assert.EqualValues(t, 1, len(compilations[0].SourcePathToArtifact))
// Two contracts in SimpleContract.sol
contractCount := 0
for _, source := range compilations[0].Sources {
for _, source := range compilations[0].SourcePathToArtifact {
contractCount += len(source.Contracts)
}
assert.EqualValues(t, 2, contractCount)
Expand Down Expand Up @@ -118,10 +119,10 @@ func TestCryticSingleFileRelativePathChildDirectory(t *testing.T) {
// One compilation object
assert.EqualValues(t, 1, len(compilations))
// One source because we specified one file
assert.EqualValues(t, 1, len(compilations[0].Sources))
assert.EqualValues(t, 1, len(compilations[0].SourcePathToArtifact))
// Two contracts in SimpleContract.sol
contractCount := 0
for _, source := range compilations[0].Sources {
for _, source := range compilations[0].SourcePathToArtifact {
contractCount += len(source.Contracts)
}
assert.EqualValues(t, 2, contractCount)
Expand Down Expand Up @@ -160,9 +161,9 @@ func TestCryticSingleFileBuildDirectoryArgRelativePath(t *testing.T) {
// One compilation object
assert.EqualValues(t, 1, len(compilations))
// One source because we specified one file
assert.EqualValues(t, 1, len(compilations[0].Sources))
assert.EqualValues(t, 1, len(compilations[0].SourcePathToArtifact))
// Two contracts in SimpleContract.sol.
compiledSource := testCryticGetCompiledSourceByBaseName(compilations[0].Sources, contractName)
compiledSource := testCryticGetCompiledSourceByBaseName(compilations[0].SourcePathToArtifact, contractName)
assert.NotNil(t, compiledSource, "source file could not be resolved in compilation sources")
assert.EqualValues(t, 2, len(compiledSource.Contracts))
})
Expand Down Expand Up @@ -215,11 +216,11 @@ func TestCryticMultipleFiles(t *testing.T) {
// 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))
assert.EqualValues(t, 2, len(compilations[0].SourcePathToArtifact))

// Verify there are three contracts
contractCount := 0
for _, source := range compilations[0].Sources {
for _, source := range compilations[0].SourcePathToArtifact {
contractCount += len(source.Contracts)
}
assert.EqualValues(t, 3, contractCount)
Expand Down Expand Up @@ -247,16 +248,16 @@ func TestCryticDirectoryNoArgs(t *testing.T) {
// Two compilation objects
assert.EqualValues(t, 2, len(compilations))
// One source per compilation unit
assert.EqualValues(t, 1, len(compilations[0].Sources))
assert.EqualValues(t, 1, len(compilations[1].Sources))
assert.EqualValues(t, 1, len(compilations[0].SourcePathToArtifact))
assert.EqualValues(t, 1, len(compilations[1].SourcePathToArtifact))

// Obtain the compiled source from both compilation units
firstContractName := "FirstContract.sol"
secondContractName := "SecondContract.sol"
firstUnitFirstContractSource := testCryticGetCompiledSourceByBaseName(compilations[0].Sources, firstContractName)
firstUnitSecondContractSource := testCryticGetCompiledSourceByBaseName(compilations[0].Sources, secondContractName)
secondUnitFirstContractSource := testCryticGetCompiledSourceByBaseName(compilations[1].Sources, firstContractName)
secondUnitSecondContractSource := testCryticGetCompiledSourceByBaseName(compilations[1].Sources, secondContractName)
firstUnitFirstContractSource := testCryticGetCompiledSourceByBaseName(compilations[0].SourcePathToArtifact, firstContractName)
firstUnitSecondContractSource := testCryticGetCompiledSourceByBaseName(compilations[0].SourcePathToArtifact, secondContractName)
secondUnitFirstContractSource := testCryticGetCompiledSourceByBaseName(compilations[1].SourcePathToArtifact, firstContractName)
secondUnitSecondContractSource := testCryticGetCompiledSourceByBaseName(compilations[1].SourcePathToArtifact, secondContractName)

// Assert that each compilation unit should have two contracts in it.
// Compilation unit ordering is non-deterministic in JSON output
Expand Down
Loading

0 comments on commit a08a148

Please sign in to comment.