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

console.log go brrr #193

Merged
merged 16 commits into from
Aug 15, 2023
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
27 changes: 25 additions & 2 deletions chain/cheat_code_contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,30 @@ type cheatCodeRawReturnData struct {
Err error
}

// getCheatCodeProviders obtains a cheatCodeTracer (used to power cheat code analysis) and associated CheatCodeContract
// objects linked to the tracer (providing on-chain callable methods as an entry point). These objects are attached to
// the TestChain to enable cheat code functionality.
// Returns the tracer and associated pre-compile contracts, or an error, if one occurred.
func getCheatCodeProviders() (*cheatCodeTracer, []*CheatCodeContract, error) {
// Create a cheat code tracer and attach it to the chain.
tracer := newCheatCodeTracer()

// Obtain our standard cheat code pre-compile
stdCheatCodeContract, err := getStandardCheatCodeContract(tracer)
if err != nil {
return nil, nil, err
}

// Obtain the console.log pre-compile
consoleCheatCodeContract, err := getConsoleLogCheatCodeContract(tracer)
if err != nil {
return nil, nil, err
}

// Return the tracer and precompiles
return tracer, []*CheatCodeContract{stdCheatCodeContract, consoleCheatCodeContract}, nil
}

// newCheatCodeContract returns a new precompiledContract which uses the attached cheatCodeTracer for execution
// context.
func newCheatCodeContract(tracer *cheatCodeTracer, address common.Address, name string) *CheatCodeContract {
Expand Down Expand Up @@ -98,7 +122,7 @@ func (c *CheatCodeContract) Abi() *abi.ABI {
}

// addMethod adds a new method to the precompiled contract.
// Returns an error if one occurred.
// Throws a panic if either the name is the empty string or the handler is nil.
func (c *CheatCodeContract) addMethod(name string, inputs abi.Arguments, outputs abi.Arguments, handler cheatCodeMethodHandler) {
// Verify a method name was provided
if name == "" {
Expand All @@ -117,7 +141,6 @@ func (c *CheatCodeContract) addMethod(name string, inputs abi.Arguments, outputs
method: method,
handler: handler,
}

// Add the method to the ABI.
// Note: Normally the key here should be the method name, not sig. But cheat code contracts have duplicate
// method names with different parameter types, so we use this so they don't override.
Expand Down
124 changes: 124 additions & 0 deletions chain/console_log_cheat_code_contract.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package chain

import (
"github.com/crytic/medusa/utils"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"strconv"
)

// ConsoleLogContractAddress is the address for the console.log precompile contract
var ConsoleLogContractAddress = common.HexToAddress("0x000000000000000000636F6e736F6c652e6c6f67")

// getConsoleLogCheatCodeContract obtains a CheatCodeContract which implements the console.log functions.
// Returns the precompiled contract, or an error if there is one.
func getConsoleLogCheatCodeContract(tracer *cheatCodeTracer) (*CheatCodeContract, error) {
// Create a new precompile to add methods to.
contract := newCheatCodeContract(tracer, ConsoleLogContractAddress, "Console")

// Define all the ABI types needed for console.log functions
typeUint256, err := abi.NewType("uint256", "", nil)
if err != nil {
return nil, err
}
typeInt256, err := abi.NewType("int256", "", nil)
if err != nil {
return nil, err
}
typeString, err := abi.NewType("string", "", nil)
if err != nil {
return nil, err
}
typeBool, err := abi.NewType("bool", "", nil)
if err != nil {
return nil, err
}
typeAddress, err := abi.NewType("address", "", nil)
if err != nil {
return nil, err
}
typeBytes, err := abi.NewType("bytes", "", nil)
if err != nil {
return nil, err
}

// We will store all the fixed byte (e.g. byte1, byte2) in a mapping
const numFixedByteTypes = 32
fixedByteTypes := make(map[int]abi.Type, numFixedByteTypes)
for i := 1; i <= numFixedByteTypes; i++ {
byteString := "bytes" + strconv.FormatInt(int64(i), 10)
fixedByteTypes[i], err = abi.NewType(byteString, "", nil)
if err != nil {
return nil, err
}
}

// We have a few special log function signatures outside all the permutations of (string, uint256, bool, address).
// These include log(int256), log(bytes), log(bytesX), and log(string, uint256). So, we will manually create these
// signatures and then programmatically iterate through all the permutations.

// Note that none of the functions actually do anything - they just have to be callable so that the execution
// traces can show the arguments that the user wants to log!

// log(int256): Log an int256
contract.addMethod("log", abi.Arguments{{Type: typeInt256}}, abi.Arguments{},
func(tracer *cheatCodeTracer, inputs []any) ([]any, *cheatCodeRawReturnData) {
return nil, nil
},
)

// log(bytes): Log bytes
contract.addMethod("log", abi.Arguments{{Type: typeBytes}}, abi.Arguments{},
func(tracer *cheatCodeTracer, inputs []any) ([]any, *cheatCodeRawReturnData) {
return nil, nil
},
)

// Now, we will add the logBytes1, logBytes2, and so on in a loop
for i := 1; i <= numFixedByteTypes; i++ {
// Create local copy of abi argument
fixedByteType := fixedByteTypes[i]

// Add the method
contract.addMethod("log", abi.Arguments{{Type: fixedByteType}}, abi.Arguments{},
func(tracer *cheatCodeTracer, inputs []any) ([]any, *cheatCodeRawReturnData) {
return nil, nil
},
)
}

// log(string, int256): Log string with an int where the string could be formatted
contract.addMethod("log", abi.Arguments{{Type: typeString}, {Type: typeInt256}}, abi.Arguments{},
func(tracer *cheatCodeTracer, inputs []any) ([]any, *cheatCodeRawReturnData) {
return nil, nil
},
)

// These are the four parameter types that console.log() accepts
choices := abi.Arguments{{Type: typeUint256}, {Type: typeString}, {Type: typeBool}, {Type: typeAddress}}

// Create all possible permutations (with repetition) where the number of choices increases from 1...len(choices)
permutations := make([]abi.Arguments, 0)
for n := 1; n <= len(choices); n++ {
nextSetOfPermutations := utils.PermutationsWithRepetition(choices, n)
for _, permutation := range nextSetOfPermutations {
permutations = append(permutations, permutation)
}
}

// Iterate across each permutation to add their associated event and function handler
for i := 0; i < len(permutations); i++ {
// Make a local copy of the current permutation
permutation := permutations[i]

// Create the function handler
contract.addMethod("log", permutation, abi.Arguments{},
func(tracer *cheatCodeTracer, inputs []any) ([]any, *cheatCodeRawReturnData) {
return nil, nil
},
)
}

// Return our precompile contract information.
return contract, nil
}
24 changes: 4 additions & 20 deletions chain/cheat_codes.go → chain/standard_cheat_code_contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,14 @@ import (
"strings"
)

// getCheatCodeProviders obtains a cheatCodeTracer (used to power cheat code analysis) and associated CheatCodeContract
// objects linked to the tracer (providing on-chain callable methods as an entry point). These objects are attached to
// the TestChain to enable cheat code functionality.
// Returns the tracer and associated pre-compile contracts, or an error, if one occurred.
func getCheatCodeProviders() (*cheatCodeTracer, []*CheatCodeContract, error) {
// Create a cheat code tracer and attach it to the chain.
tracer := newCheatCodeTracer()

// Obtain our cheat code pre-compiles
stdCheatCodeContract, err := getStandardCheatCodeContract(tracer)
if err != nil {
return nil, nil, err
}

// Return the tracer and precompiles
return tracer, []*CheatCodeContract{stdCheatCodeContract}, nil
}
// StandardCheatcodeContractAddress is the address for the standard cheatcode contract
var StandardCheatcodeContractAddress = common.HexToAddress("0x7109709ECfa91a80626fF3989D68f67F5b1DD12D")

// getStandardCheatCodeContract obtains a CheatCodeContract which implements common cheat codes.
// Returns the precompiled contract, or an error if one occurs.
func getStandardCheatCodeContract(tracer *cheatCodeTracer) (*CheatCodeContract, error) {
// Define our address for this precompile contract, then create a new precompile to add methods to.
contractAddress := common.HexToAddress("0x7109709ECfa91a80626fF3989D68f67F5b1DD12D")
contract := newCheatCodeContract(tracer, contractAddress, "StdCheats")
// Create a new precompile to add methods to.
contract := newCheatCodeContract(tracer, StandardCheatcodeContractAddress, "StdCheats")

// Define some basic ABI argument types
typeAddress, err := abi.NewType("address", "", nil)
Expand Down
20 changes: 10 additions & 10 deletions compilation/abiutils/solidity_errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,25 +113,25 @@ func GetPanicReason(panicCode uint64) string {
// Switch on panic code
switch panicCode {
case PanicCodeCompilerInserted:
return "compiler inserted panic"
return "panic: compiler inserted panic"
case PanicCodeAssertFailed:
return "assertion failed"
return "panic: assertion failed"
case PanicCodeArithmeticUnderOverflow:
return "arithmetic underflow"
return "panic: arithmetic underflow"
case PanicCodeDivideByZero:
return "division by zero"
return "panic: division by zero"
case PanicCodeEnumTypeConversionOutOfBounds:
return "enum access out of bounds"
return "panic: enum access out of bounds"
case PanicCodeIncorrectStorageAccess:
return "incorrect storage access"
return "panic: incorrect storage access"
case PanicCodePopEmptyArray:
return "pop on empty array"
return "panic: pop on empty array"
case PanicCodeOutOfBoundsArrayAccess:
return "out of bounds array access"
return "panic: out of bounds array access"
case PanicCodeAllocateTooMuchMemory:
return "overallocation of memory"
return "panic; overallocation of memory"
case PanicCodeCallUninitializedVariable:
return "call on uninitialized variable"
return "panic: call on uninitialized variable"
default:
return fmt.Sprintf("unknown panic code(%v)", panicCode)
}
Expand Down
Loading