Skip to content

Commit a08a148

Browse files
authored
Merge pull request #427 from crytic/update
fix coverage report bugs (source unit ID issues) and method selection process
2 parents 924247c + f4d710f commit a08a148

20 files changed

+547
-121
lines changed

.github/workflows/ci.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,6 @@ jobs:
8787
inputs: ./medusa-*.tar.gz
8888

8989
- name: Upload artifact
90-
if: github.ref == 'refs/heads/master' || (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/'))
9190
uses: actions/upload-artifact@v4
9291
with:
9392
name: medusa-${{ runner.os }}-${{ runner.arch }}

DEV.md

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# Debugging and Development
2+
3+
## Debugging
4+
5+
The following scripts are available for Medusa developers for debugging changes to the fuzzer.
6+
7+
### Corpus diff
8+
9+
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.
10+
11+
```shell
12+
python3 scripts/corpus_diff.py corpus1 corpus2
13+
```
14+
15+
```shell
16+
Methods only in ~/corpus1:
17+
- clampSplitWeight(uint32,uint32)
18+
19+
Methods only in ~/corpus2:
20+
<None>
21+
```
22+
23+
### Corpus stats
24+
25+
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.
26+
27+
```shell
28+
python3 scripts/corpus_stats.py corpus
29+
```
30+
31+
```shell
32+
Number of Sequences in ~/corpus: 130
33+
34+
Average Length of Transactions List: 43
35+
36+
Frequency of Methods Called:
37+
- testReceiversReceivedSplit(uint8): 280
38+
- setMaxEndHints(uint32,uint32): 174
39+
- setStreamBalanceWithdrawAll(uint8): 139
40+
- giveClampedAmount(uint8,uint8,uint128): 136
41+
- receiveStreamsSplitAndCollectToSelf(uint8): 133
42+
- testSqueezeViewVsActual(uint8,uint8): 128
43+
- testSqueeze(uint8,uint8): 128
44+
- testSetStreamBalance(uint8,int128): 128
45+
- addStreamWithClamping(uint8,uint8,uint160,uint32,uint32,int128): 125
46+
- removeAllSplits(uint8): 118
47+
- testSplittableAfterSplit(uint8): 113
48+
- testSqueezableVsReceived(uint8): 111
49+
- testBalanceAtInFuture(uint8,uint8,uint160): 108
50+
- testRemoveStreamShouldNotRevert(uint8,uint256): 103
51+
- invariantWithdrawAllTokensShouldNotRevert(): 103
52+
- collect(uint8,uint8): 101
53+
- invariantAmtPerSecVsMinAmtPerSec(uint8,uint256): 98
54+
- testSqueezableAmountCantBeWithdrawn(uint8,uint8): 97
55+
- split(uint8): 97
56+
- invariantWithdrawAllTokens(): 95
57+
- testReceiveStreams(uint8,uint32): 93
58+
- invariantAccountingVsTokenBalance(): 92
59+
- testSqueezeWithFuzzedHistoryShouldNotRevert(uint8,uint8,uint256,bytes32): 91
60+
- testSqueezableAmountCantBeUndone(uint8,uint8,uint160,uint32,uint32,int128): 87
61+
- testCollect(uint8,uint8): 86
62+
- testSetStreamBalanceWithdrawAllShouldNotRevert(uint8): 86
63+
- testAddStreamShouldNotRevert(uint8,uint8,uint160,uint32,uint32,int128): 85
64+
- testReceiveStreamsShouldNotRevert(uint8): 84
65+
- addSplitsReceiver(uint8,uint8,uint32): 84
66+
- setStreamBalanceWithClamping(uint8,int128): 82
67+
- addSplitsReceiverWithClamping(uint8,uint8,uint32): 80
68+
- testSetStreamBalanceShouldNotRevert(uint8,int128): 80
69+
- testSplitShouldNotRevert(uint8): 80
70+
- squeezeAllAndReceiveAndSplitAndCollectToSelf(uint8): 79
71+
- addStreamImmediatelySqueezable(uint8,uint8,uint160): 79
72+
- testSetSplitsShouldNotRevert(uint8,uint8,uint32): 78
73+
- invariantSumAmtDeltaIsZero(uint8): 78
74+
- testReceiveStreamsViewConsistency(uint8,uint32): 76
75+
- squeezeToSelf(uint8): 74
76+
- collectToSelf(uint8): 72
77+
- setStreams(uint8,uint8,uint160,uint32,uint32,int128): 70
78+
- receiveStreamsAllCycles(uint8): 69
79+
- invariantWithdrawShouldAlwaysFail(uint256): 68
80+
- addStream(uint8,uint8,uint160,uint32,uint32,int128): 68
81+
- squeezeWithFuzzedHistory(uint8,uint8,uint256,bytes32): 67
82+
- setStreamsWithClamping(uint8,uint8,uint160,uint32,uint32,int128): 67
83+
- splitAndCollectToSelf(uint8): 67
84+
- testSqueezeWithFullyHashedHistory(uint8,uint8): 65
85+
- give(uint8,uint8,uint128): 65
86+
- setSplits(uint8,uint8,uint32): 65
87+
- testSqueezeTwice(uint8,uint8,uint256,bytes32): 65
88+
- testSetStreamsShouldNotRevert(uint8,uint8,uint160,uint32,uint32,int128): 64
89+
- squeezeAllSenders(uint8): 63
90+
- removeStream(uint8,uint256): 62
91+
- testCollectableAfterSplit(uint8): 58
92+
- testCollectShouldNotRevert(uint8,uint8): 56
93+
- testReceiveStreamsViewVsActual(uint8,uint32): 55
94+
- receiveStreams(uint8,uint32): 55
95+
- setSplitsWithClamping(uint8,uint8,uint32): 55
96+
- testGiveShouldNotRevert(uint8,uint8,uint128): 47
97+
- setStreamBalance(uint8,int128): 47
98+
- squeezeWithDefaultHistory(uint8,uint8): 45
99+
- testSplitViewVsActual(uint8): 45
100+
- testAddSplitsShouldNotRevert(uint8,uint8,uint32): 30
101+
- testSqueezeWithDefaultHistoryShouldNotRevert(uint8,uint8): 23
102+
103+
Number of Unique Methods: 65
104+
```

chain/test_chain_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -215,15 +215,15 @@ func TestChainDynamicDeployments(t *testing.T) {
215215
compilations, _, err := cryticCompile.Compile()
216216
assert.NoError(t, err)
217217
assert.EqualValues(t, 1, len(compilations))
218-
assert.EqualValues(t, 1, len(compilations[0].Sources))
218+
assert.EqualValues(t, 1, len(compilations[0].SourcePathToArtifact))
219219

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

223223
// Deploy each contract that has no construct arguments.
224224
deployCount := 0
225225
for _, compilation := range compilations {
226-
for _, source := range compilation.Sources {
226+
for _, source := range compilation.SourcePathToArtifact {
227227
for _, contract := range source.Contracts {
228228
contract := contract
229229
if len(contract.Abi.Constructor.Inputs) == 0 {
@@ -329,7 +329,7 @@ func TestChainDeploymentWithArgs(t *testing.T) {
329329
compilations, _, err := cryticCompile.Compile()
330330
assert.NoError(t, err)
331331
assert.EqualValues(t, 1, len(compilations))
332-
assert.EqualValues(t, 1, len(compilations[0].Sources))
332+
assert.EqualValues(t, 1, len(compilations[0].SourcePathToArtifact))
333333

334334
// Obtain our chain and senders
335335
chain, senders := createChain(t)
@@ -346,7 +346,7 @@ func TestChainDeploymentWithArgs(t *testing.T) {
346346
// Deploy each contract
347347
deployCount := 0
348348
for _, compilation := range compilations {
349-
for _, source := range compilation.Sources {
349+
for _, source := range compilation.SourcePathToArtifact {
350350
for contractName, contract := range source.Contracts {
351351
contract := contract
352352

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

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

564564
// Deploy each contract that has no construct arguments 10 times.
565565
for _, compilation := range compilations {
566-
for _, source := range compilation.Sources {
566+
for _, source := range compilation.SourcePathToArtifact {
567567
for _, contract := range source.Contracts {
568568
contract := contract
569569
if len(contract.Abi.Constructor.Inputs) == 0 {

compilation/platforms/crytic_compile.go

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ func (c *CryticCompilationConfig) Compile() ([]types.Compilation, string, error)
143143
var compilationList []types.Compilation
144144

145145
// Define the structure of our crytic-compile export data.
146-
type solcExportSource struct {
146+
type solcSourceUnit struct {
147147
AST any `json:"AST"`
148148
}
149149
type solcExportContract struct {
@@ -154,9 +154,8 @@ func (c *CryticCompilationConfig) Compile() ([]types.Compilation, string, error)
154154
BinRuntime string `json:"bin-runtime"`
155155
}
156156
type solcExportData struct {
157-
Sources map[string]solcExportSource `json:"sources"`
158-
Contracts map[string]solcExportContract `json:"contracts"`
159-
SourceList []string `json:"sourceList"`
157+
Sources map[string]solcSourceUnit `json:"sources"`
158+
Contracts map[string]solcExportContract `json:"contracts"`
160159
}
161160

162161
// Loop through each .json file for compilation units.
@@ -176,14 +175,41 @@ func (c *CryticCompilationConfig) Compile() ([]types.Compilation, string, error)
176175

177176
// Create a compilation object that will store the contracts and source information.
178177
compilation := types.NewCompilation()
179-
compilation.SourceList = solcExport.SourceList
178+
179+
// Create a map of contract names to their kinds
180+
contractKinds := make(map[string]types.ContractKind)
180181

181182
// Loop through all sources and parse them into our types.
182183
for sourcePath, source := range solcExport.Sources {
183-
compilation.Sources[sourcePath] = types.CompiledSource{
184-
Ast: source.AST,
185-
Contracts: make(map[string]types.CompiledContract),
184+
// Convert the AST into our version of the AST (types.AST)
185+
var ast types.AST
186+
b, err = json.Marshal(source.AST)
187+
if err != nil {
188+
return nil, "", fmt.Errorf("could not encode AST from sources: %v", err)
189+
}
190+
err = json.Unmarshal(b, &ast)
191+
if err != nil {
192+
return nil, "", fmt.Errorf("could not parse AST from sources: %v", err)
193+
}
194+
195+
// From the AST, extract the contract kinds where the contract definition could be for a contract, library,
196+
// or interface
197+
for _, node := range ast.Nodes {
198+
if node.GetNodeType() == "ContractDefinition" {
199+
contractDefinition := node.(types.ContractDefinition)
200+
contractKinds[contractDefinition.CanonicalName] = contractDefinition.Kind
201+
}
202+
}
203+
204+
// Retrieve the source unit ID
205+
sourceUnitId := ast.GetSourceUnitID()
206+
compilation.SourcePathToArtifact[sourcePath] = types.SourceArtifact{
207+
// TODO: Our types.AST is not the same as the original AST but we could parse it and avoid using "any"
208+
Ast: source.AST,
209+
Contracts: make(map[string]types.CompiledContract),
210+
SourceUnitId: sourceUnitId,
186211
}
212+
compilation.SourceIdToPath[sourceUnitId] = sourcePath
187213
}
188214

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

199225
// Ensure a source exists for this, or create one if our path somehow differed from any
200226
// path not existing in the "sources" key at the root of the export.
201-
if _, ok := compilation.Sources[sourcePath]; !ok {
202-
parentSource := types.CompiledSource{
227+
if _, ok := compilation.SourcePathToArtifact[sourcePath]; !ok {
228+
parentSource := types.SourceArtifact{
203229
Ast: nil,
204230
Contracts: make(map[string]types.CompiledContract),
205231
}
206-
compilation.Sources[sourcePath] = parentSource
232+
compilation.SourcePathToArtifact[sourcePath] = parentSource
207233
}
208234

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

225251
// Add contract details
226-
compilation.Sources[sourcePath].Contracts[contractName] = types.CompiledContract{
252+
compilation.SourcePathToArtifact[sourcePath].Contracts[contractName] = types.CompiledContract{
227253
Abi: *contractAbi,
228254
InitBytecode: initBytecode,
229255
RuntimeBytecode: runtimeBytecode,
230256
SrcMapsInit: contract.SrcMap,
231257
SrcMapsRuntime: contract.SrcMapRuntime,
258+
Kind: contractKinds[contractName],
232259
}
233260
}
234261

compilation/platforms/crytic_compile_test.go

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
11
package platforms
22

33
import (
4-
"github.com/crytic/medusa/compilation/types"
5-
"github.com/crytic/medusa/utils"
6-
"github.com/crytic/medusa/utils/testutils"
7-
"github.com/stretchr/testify/assert"
84
"os"
95
"os/exec"
106
"path/filepath"
117
"strings"
128
"testing"
9+
10+
"github.com/crytic/medusa/compilation/types"
11+
"github.com/crytic/medusa/utils"
12+
"github.com/crytic/medusa/utils/testutils"
13+
"github.com/stretchr/testify/assert"
1314
)
1415

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

@@ -53,10 +54,10 @@ func TestCryticSingleFileAbsolutePath(t *testing.T) {
5354
// One compilation object
5455
assert.EqualValues(t, 1, len(compilations))
5556
// One source because we specified one file
56-
assert.EqualValues(t, 1, len(compilations[0].Sources))
57+
assert.EqualValues(t, 1, len(compilations[0].SourcePathToArtifact))
5758
// Two contracts in SimpleContract.sol
5859
contractCount := 0
59-
for _, source := range compilations[0].Sources {
60+
for _, source := range compilations[0].SourcePathToArtifact {
6061
contractCount += len(source.Contracts)
6162
}
6263
assert.EqualValues(t, 2, contractCount)
@@ -82,10 +83,10 @@ func TestCryticSingleFileRelativePathSameDirectory(t *testing.T) {
8283
// One compilation object
8384
assert.EqualValues(t, 1, len(compilations))
8485
// One source because we specified one file
85-
assert.EqualValues(t, 1, len(compilations[0].Sources))
86+
assert.EqualValues(t, 1, len(compilations[0].SourcePathToArtifact))
8687
// Two contracts in SimpleContract.sol
8788
contractCount := 0
88-
for _, source := range compilations[0].Sources {
89+
for _, source := range compilations[0].SourcePathToArtifact {
8990
contractCount += len(source.Contracts)
9091
}
9192
assert.EqualValues(t, 2, contractCount)
@@ -118,10 +119,10 @@ func TestCryticSingleFileRelativePathChildDirectory(t *testing.T) {
118119
// One compilation object
119120
assert.EqualValues(t, 1, len(compilations))
120121
// One source because we specified one file
121-
assert.EqualValues(t, 1, len(compilations[0].Sources))
122+
assert.EqualValues(t, 1, len(compilations[0].SourcePathToArtifact))
122123
// Two contracts in SimpleContract.sol
123124
contractCount := 0
124-
for _, source := range compilations[0].Sources {
125+
for _, source := range compilations[0].SourcePathToArtifact {
125126
contractCount += len(source.Contracts)
126127
}
127128
assert.EqualValues(t, 2, contractCount)
@@ -160,9 +161,9 @@ func TestCryticSingleFileBuildDirectoryArgRelativePath(t *testing.T) {
160161
// One compilation object
161162
assert.EqualValues(t, 1, len(compilations))
162163
// One source because we specified one file
163-
assert.EqualValues(t, 1, len(compilations[0].Sources))
164+
assert.EqualValues(t, 1, len(compilations[0].SourcePathToArtifact))
164165
// Two contracts in SimpleContract.sol.
165-
compiledSource := testCryticGetCompiledSourceByBaseName(compilations[0].Sources, contractName)
166+
compiledSource := testCryticGetCompiledSourceByBaseName(compilations[0].SourcePathToArtifact, contractName)
166167
assert.NotNil(t, compiledSource, "source file could not be resolved in compilation sources")
167168
assert.EqualValues(t, 2, len(compiledSource.Contracts))
168169
})
@@ -215,11 +216,11 @@ func TestCryticMultipleFiles(t *testing.T) {
215216
// Verify there is one compilation object
216217
assert.EqualValues(t, 1, len(compilations))
217218
// Verify there are two sources
218-
assert.EqualValues(t, 2, len(compilations[0].Sources))
219+
assert.EqualValues(t, 2, len(compilations[0].SourcePathToArtifact))
219220

220221
// Verify there are three contracts
221222
contractCount := 0
222-
for _, source := range compilations[0].Sources {
223+
for _, source := range compilations[0].SourcePathToArtifact {
223224
contractCount += len(source.Contracts)
224225
}
225226
assert.EqualValues(t, 3, contractCount)
@@ -247,16 +248,16 @@ func TestCryticDirectoryNoArgs(t *testing.T) {
247248
// Two compilation objects
248249
assert.EqualValues(t, 2, len(compilations))
249250
// One source per compilation unit
250-
assert.EqualValues(t, 1, len(compilations[0].Sources))
251-
assert.EqualValues(t, 1, len(compilations[1].Sources))
251+
assert.EqualValues(t, 1, len(compilations[0].SourcePathToArtifact))
252+
assert.EqualValues(t, 1, len(compilations[1].SourcePathToArtifact))
252253

253254
// Obtain the compiled source from both compilation units
254255
firstContractName := "FirstContract.sol"
255256
secondContractName := "SecondContract.sol"
256-
firstUnitFirstContractSource := testCryticGetCompiledSourceByBaseName(compilations[0].Sources, firstContractName)
257-
firstUnitSecondContractSource := testCryticGetCompiledSourceByBaseName(compilations[0].Sources, secondContractName)
258-
secondUnitFirstContractSource := testCryticGetCompiledSourceByBaseName(compilations[1].Sources, firstContractName)
259-
secondUnitSecondContractSource := testCryticGetCompiledSourceByBaseName(compilations[1].Sources, secondContractName)
257+
firstUnitFirstContractSource := testCryticGetCompiledSourceByBaseName(compilations[0].SourcePathToArtifact, firstContractName)
258+
firstUnitSecondContractSource := testCryticGetCompiledSourceByBaseName(compilations[0].SourcePathToArtifact, secondContractName)
259+
secondUnitFirstContractSource := testCryticGetCompiledSourceByBaseName(compilations[1].SourcePathToArtifact, firstContractName)
260+
secondUnitSecondContractSource := testCryticGetCompiledSourceByBaseName(compilations[1].SourcePathToArtifact, secondContractName)
260261

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

0 commit comments

Comments
 (0)