Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update #427

Merged
merged 15 commits into from
Aug 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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