Skip to content

Add a parameter in optimization mode to maximize gas usage #165

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

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
6 changes: 5 additions & 1 deletion fuzzing/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/crytic/medusa/chain/config"
"os"

"github.com/crytic/medusa/chain/config"

"github.com/crytic/medusa/compilation"
"github.com/crytic/medusa/utils"
)
Expand Down Expand Up @@ -138,6 +139,9 @@ type OptimizationTestingConfig struct {

// TestPrefixes dictates what method name prefixes will determine if a contract method is an optimization test.
TestPrefixes []string `json:"testPrefixes"`

// GasOptimization describes whether gas optimization is used.
GasOptimization bool `json:"gasOptimization"`
}

// ReadProjectConfigFromFile reads a JSON-serialized ProjectConfig from a provided file path.
Expand Down
1 change: 1 addition & 0 deletions fuzzing/config/config_defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ func GetDefaultProjectConfig(platform string) (*ProjectConfig, error) {
TestPrefixes: []string{
"optimize_",
},
GasOptimization: false,
},
},
TestChainConfig: *chainConfig,
Expand Down
34 changes: 30 additions & 4 deletions fuzzing/test_case_optimization_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ package fuzzing

import (
"fmt"
"math/big"
"strings"
"sync"

"github.com/crytic/medusa/fuzzing/calls"
"github.com/crytic/medusa/fuzzing/contracts"
"github.com/crytic/medusa/fuzzing/executiontracer"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/core"
"math/big"
"strings"
"sync"
)

const MIN_INT = "-8000000000000000000000000000000000000000000000000000000000000000"
Expand Down Expand Up @@ -63,6 +64,10 @@ func attachOptimizationTestCaseProvider(fuzzer *Fuzzer) *OptimizationTestCasePro
// isOptimizationTest check whether the method is an optimization test given potential naming prefixes it must conform to
// and its underlying input/output arguments.
func (t *OptimizationTestCaseProvider) isOptimizationTest(method abi.Method) bool {
if t.fuzzer.Config().Fuzzing.Testing.OptimizationTesting.GasOptimization {
return true
}

// Loop through all enabled prefixes to find a match
for _, prefix := range t.fuzzer.Config().Fuzzing.Testing.OptimizationTesting.TestPrefixes {
if strings.HasPrefix(method.Name, prefix) {
Expand All @@ -78,6 +83,26 @@ func (t *OptimizationTestCaseProvider) isOptimizationTest(method abi.Method) boo
// runOptimizationTest executes a given optimization test method (w/ an optional execution trace) and returns the return value
// from the optimization test method. This is called after every call the Fuzzer makes when testing call sequences for each test case.
func (t *OptimizationTestCaseProvider) runOptimizationTest(worker *FuzzerWorker, optimizationTestMethod *contracts.DeployedContractMethod, trace bool) (*big.Int, *executiontracer.ExecutionTrace, error) {

if t.fuzzer.Config().Fuzzing.Testing.OptimizationTesting.GasOptimization {
pending := worker.Chain().PendingBlock()
var gasUsed uint64 = 0
if pending != nil {
messagesLen := len(pending.MessageResults)
if messagesLen > 0 {
gasUsed = pending.MessageResults[messagesLen-1].Receipt.GasUsed
}
} else {
blocks := worker.Chain().CommittedBlocks()
blocksLen := len(blocks)
lastBlock := blocks[blocksLen-1]
messagesLen := len(lastBlock.MessageResults)
gasUsed = lastBlock.MessageResults[messagesLen-1].Receipt.GasUsed
}
newValue := big.NewInt(0)
newValue.SetUint64(gasUsed)
return newValue, nil, nil
}
// Generate our ABI input data for the call. In this case, optimization test methods take no arguments, so the
// variadic argument list here is empty.
data, err := optimizationTestMethod.Contract.CompiledContract().Abi.Pack(optimizationTestMethod.Method.Name)
Expand Down Expand Up @@ -347,7 +372,8 @@ func (t *OptimizationTestCaseProvider) callSequencePostCallTest(worker *FuzzerWo
}

// If, for some reason, the shrunken sequence lowers the new max value, do not save anything and exit
if shrunkenSequenceNewValue.Cmp(newValue) < 0 {
isGasOptimization := t.fuzzer.Config().Fuzzing.Testing.OptimizationTesting.GasOptimization
if !isGasOptimization && shrunkenSequenceNewValue.Cmp(newValue) < 0 {
return fmt.Errorf("optimized call sequence failed to maximize value")
}

Expand Down
5 changes: 5 additions & 0 deletions fuzzing/testdata/contracts/optimizations/optimize.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
contract TestContract {
int256 input;

function expensive(uint8 n) public {
for (uint8 i = 0; i < n; i++)
keccak256(abi.encode(n));
}

function set(int256 _input) public {
input = _input;
}
Expand Down