Skip to content

Commit

Permalink
Merge branch 'master' into dev/multi-error-log-support
Browse files Browse the repository at this point in the history
  • Loading branch information
anishnaik authored Aug 16, 2023
2 parents 5829bda + 7d2e16c commit 5636013
Show file tree
Hide file tree
Showing 28 changed files with 2,220 additions and 319 deletions.
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
18 changes: 13 additions & 5 deletions chain/test_chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,14 @@ func NewTestChain(genesisAlloc core.GenesisAlloc, testChainConfig *config.TestCh
return nil, err
}

// TODO: go-ethereum doesn't set shanghai start time for THEIR test `ChainConfig` struct.
// Note: We have our own `TestChainConfig` definition that is different (second argument in this function).
// We should allow the user to provide a go-ethereum `ChainConfig` to do custom fork selection, inside of our
// `TestChainConfig` definition. Or we should wrap it in our own struct to simplify the options and not pollute
// our overall medusa project config.
shanghaiTime := uint64(0)
chainConfig.ShanghaiTime = &shanghaiTime

// Create our genesis definition with our default chain config.
genesisDefinition := &core.Genesis{
Config: chainConfig,
Expand Down Expand Up @@ -527,7 +535,7 @@ func (t *TestChain) RevertToBlockNumber(blockNumber uint64) error {
// It takes an optional state argument, which is the state to execute the message over. If not provided, the
// current pending state (or committed state if none is pending) will be used instead.
// The state executed over may be a pending block state.
func (t *TestChain) CallContract(msg core.Message, state *state.StateDB, additionalTracers ...vm.EVMLogger) (*core.ExecutionResult, error) {
func (t *TestChain) CallContract(msg *core.Message, state *state.StateDB, additionalTracers ...vm.EVMLogger) (*core.ExecutionResult, error) {
// If our provided state is nil, use our current chain state.
if state == nil {
state = t.state
Expand All @@ -537,7 +545,7 @@ func (t *TestChain) CallContract(msg core.Message, state *state.StateDB, additio
snapshot := state.Snapshot()

// Set infinite balance to the fake caller account
from := state.GetOrNewStateObject(msg.From())
from := state.GetOrNewStateObject(msg.From)
from.SetBalance(math.MaxBig256)

// Create our transaction and block contexts for the vm
Expand All @@ -552,7 +560,7 @@ func (t *TestChain) CallContract(msg core.Message, state *state.StateDB, additio

// Create our EVM instance.
evm := vm.NewEVM(blockContext, txContext, state, t.chainConfig, vm.Config{
Debug: true,
//Debug: true,
Tracer: extendedTracerRouter,
NoBaseFee: true,
ConfigExtensions: t.vmConfigExtensions,
Expand Down Expand Up @@ -672,7 +680,7 @@ func (t *TestChain) PendingBlockCreateWithParameters(blockNumber uint64, blockTi
// PendingBlockAddTx takes a message (internal txs) and adds it to the current pending block, updating the header
// with relevant execution information. If a pending block was not created, an error is returned.
// Returns the constructed block, or an error if one occurred.
func (t *TestChain) PendingBlockAddTx(message core.Message) error {
func (t *TestChain) PendingBlockAddTx(message *core.Message) error {
// If we don't have a pending block, return an error
if t.pendingBlock == nil {
return errors.New("could not add tx to the chain's pending block because no pending block was created")
Expand All @@ -692,7 +700,7 @@ func (t *TestChain) PendingBlockAddTx(message core.Message) error {

// Create our EVM instance.
evm := vm.NewEVM(blockContext, core.NewEVMTxContext(message), t.state, t.chainConfig, vm.Config{
Debug: true,
//Debug: true,
Tracer: t.transactionTracerRouter,
NoBaseFee: true,
ConfigExtensions: t.vmConfigExtensions,
Expand Down
64 changes: 56 additions & 8 deletions chain/test_chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,14 +241,26 @@ func TestChainDynamicDeployments(t *testing.T) {
// Deploy the currently indexed contract next

// Create a message to represent our contract deployment.
msg := types.NewMessage(senders[0], nil, chain.State().GetNonce(senders[0]), big.NewInt(0), chain.BlockGasLimit, big.NewInt(1), big.NewInt(0), big.NewInt(0), contract.InitBytecode, nil, false)
msg := core.Message{
To: nil,
From: senders[0],
Nonce: chain.State().GetNonce(senders[0]),
Value: big.NewInt(0),
GasLimit: chain.BlockGasLimit,
GasPrice: big.NewInt(1),
GasFeeCap: big.NewInt(0),
GasTipCap: big.NewInt(0),
Data: contract.InitBytecode,
AccessList: nil,
SkipAccountChecks: false,
}

// Create a new pending block we'll commit to chain
block, err := chain.PendingBlockCreate()
assert.NoError(t, err)

// Add our transaction to the block
err = chain.PendingBlockAddTx(msg)
err = chain.PendingBlockAddTx(&msg)
assert.NoError(t, err)

// Commit the pending block to the chain, so it becomes the new head.
Expand Down Expand Up @@ -354,14 +366,26 @@ func TestChainDeploymentWithArgs(t *testing.T) {
assert.NoError(t, err)

// Create a message to represent our contract deployment.
msg := types.NewMessage(senders[0], nil, chain.State().GetNonce(senders[0]), big.NewInt(0), chain.BlockGasLimit, big.NewInt(1), big.NewInt(0), big.NewInt(0), msgData, nil, false)
msg := core.Message{
To: nil,
From: senders[0],
Nonce: chain.State().GetNonce(senders[0]),
Value: big.NewInt(0),
GasLimit: chain.BlockGasLimit,
GasPrice: big.NewInt(1),
GasFeeCap: big.NewInt(0),
GasTipCap: big.NewInt(0),
Data: msgData,
AccessList: nil,
SkipAccountChecks: false,
}

// Create a new pending block we'll commit to chain
block, err := chain.PendingBlockCreate()
assert.NoError(t, err)

// Add our transaction to the block
err = chain.PendingBlockAddTx(msg)
err = chain.PendingBlockAddTx(&msg)
assert.NoError(t, err)

// Commit the pending block to the chain, so it becomes the new head.
Expand Down Expand Up @@ -451,14 +475,26 @@ func TestChainCloning(t *testing.T) {
// Deploy the currently indexed contract next

// Create a message to represent our contract deployment.
msg := types.NewMessage(senders[0], nil, chain.State().GetNonce(senders[0]), big.NewInt(0), chain.BlockGasLimit, big.NewInt(1), big.NewInt(0), big.NewInt(0), contract.InitBytecode, nil, false)
msg := core.Message{
To: nil,
From: senders[0],
Nonce: chain.State().GetNonce(senders[0]),
Value: big.NewInt(0),
GasLimit: chain.BlockGasLimit,
GasPrice: big.NewInt(1),
GasFeeCap: big.NewInt(0),
GasTipCap: big.NewInt(0),
Data: contract.InitBytecode,
AccessList: nil,
SkipAccountChecks: false,
}

// Create a new pending block we'll commit to chain
block, err := chain.PendingBlockCreate()
assert.NoError(t, err)

// Add our transaction to the block
err = chain.PendingBlockAddTx(msg)
err = chain.PendingBlockAddTx(&msg)
assert.NoError(t, err)

// Commit the pending block to the chain, so it becomes the new head.
Expand Down Expand Up @@ -533,14 +569,26 @@ func TestChainCallSequenceReplayMatchSimple(t *testing.T) {
if len(contract.Abi.Constructor.Inputs) == 0 {
for i := 0; i < 10; i++ {
// Create a message to represent our contract deployment.
msg := types.NewMessage(senders[0], nil, chain.State().GetNonce(senders[0]), big.NewInt(0), chain.BlockGasLimit, big.NewInt(1), big.NewInt(0), big.NewInt(0), contract.InitBytecode, nil, false)
msg := core.Message{
To: nil,
From: senders[0],
Nonce: chain.State().GetNonce(senders[0]),
Value: big.NewInt(0),
GasLimit: chain.BlockGasLimit,
GasPrice: big.NewInt(1),
GasFeeCap: big.NewInt(0),
GasTipCap: big.NewInt(0),
Data: contract.InitBytecode,
AccessList: nil,
SkipAccountChecks: false,
}

// Create a new pending block we'll commit to chain
block, err := chain.PendingBlockCreate()
assert.NoError(t, err)

// Add our transaction to the block
err = chain.PendingBlockAddTx(msg)
err = chain.PendingBlockAddTx(&msg)
assert.NoError(t, err)

// Commit the pending block to the chain, so it becomes the new head.
Expand Down
Loading

0 comments on commit 5636013

Please sign in to comment.