From 88441eb6a06a08d94652b11fcc58e390497b5d60 Mon Sep 17 00:00:00 2001 From: tre Date: Mon, 22 Jul 2024 15:10:32 -0700 Subject: [PATCH] feat: interop invariant checks --- README.md | 11 +- anvil/anvil.go | 18 ++++ config/chain.go | 34 +++++++ contracts/.gitignore | 2 + crossl2inbox/CrossL2Inbox.json | 170 +++++++++++++++++++++++++++++++ crossl2inbox/crossl2inbox.go | 134 ++++++++++++++++++++++++ go.mod | 2 +- opsimulator/opsimulator.go | 181 +++++++++++++++++++++++++++++++-- orchestrator/orchestrator.go | 2 +- testutils/mockchain.go | 9 ++ types/types.go | 11 ++ 11 files changed, 561 insertions(+), 13 deletions(-) create mode 100644 crossl2inbox/CrossL2Inbox.json create mode 100644 crossl2inbox/crossl2inbox.go create mode 100644 types/types.go diff --git a/README.md b/README.md index d4299b61..5693923f 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,15 @@ Supersim allows developers to start multiple local evm nodes with one command, a Supersim is a lightweight tool that simulates an interoperable Superchain environment locally. It does not require a complicated devnet setup and is run using cli commands with configuration options that fall back to sensible defaults if they are not specified. Each chain is an instance of [anvil](https://book.getfoundry.sh/reference/anvil/), though future versions may support other local testing tools. ## Getting started -### Installation -TODO +### Running Locally +1. build the binary by running: +``` +go build cmd/main.go +``` +2. start supersim in vanilla mode by running: +``` +./main +``` ## Features ### Vanilla mode diff --git a/anvil/anvil.go b/anvil/anvil.go index a693196f..031bfa87 100644 --- a/anvil/anvil.go +++ b/anvil/anvil.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "io" + "math/big" "os" "os/exec" "strconv" @@ -283,7 +284,24 @@ func (a *Anvil) EthSendTransaction(ctx context.Context, tx *types.Transaction) e return a.ethClient.SendTransaction(ctx, tx) } +func (a *Anvil) EthBlockByNumber(ctx context.Context, blockHeight *big.Int) (*types.Block, error) { + return a.ethClient.BlockByNumber(ctx, blockHeight) +} + // subscription API func (a *Anvil) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) { return a.ethClient.SubscribeFilterLogs(ctx, q, ch) } + +func (a *Anvil) DebugTraceCall(ctx context.Context, txArgs config.TransactionArgs) (config.TraceCallRaw, error) { + var result config.TraceCallRaw + if err := a.rpcClient.CallContext(ctx, &result, "debug_traceCall", txArgs, "latest", map[string]interface{}{ + "tracer": "callTracer", + "tracerConfig": map[string]interface{}{ + "withLog": true, + }, + }); err != nil { + return config.TraceCallRaw{}, err + } + return result, nil +} diff --git a/config/chain.go b/config/chain.go index 5fab2504..36aca5b8 100644 --- a/config/chain.go +++ b/config/chain.go @@ -3,6 +3,7 @@ package config import ( "context" "fmt" + "math/big" "strings" registry "github.com/ethereum-optimism/superchain-registry/superchain" @@ -12,6 +13,7 @@ import ( "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" ) @@ -96,6 +98,35 @@ type NetworkConfig struct { L2Configs []ChainConfig } +type TransactionArgs struct { + From common.Address `json:"from"` + To *common.Address `json:"to"` + Gas hexutil.Uint64 `json:"gas"` + GasPrice *hexutil.Big `json:"gasPrice"` + Data hexutil.Bytes `json:"data"` + Value *hexutil.Big `json:"value"` +} + +type TraceCallRaw struct { + Error *string `json:"error,omitempty"` + Type string `json:"type"` + From string `json:"from"` + To string `json:"to"` + Value string `json:"value"` + Gas string `json:"gas"` + GasUsed string `json:"gasUsed"` + Input string `json:"input"` + Output string `json:"output"` + Logs []*TraceCallRawLog `json:"logs"` + Calls []TraceCallRaw `json:"calls"` +} + +type TraceCallRawLog struct { + Address common.Address `json:"address"` + Topics []common.Hash `json:"topics"` + Data string `json:"data"` +} + type Chain interface { Name() string Endpoint() string @@ -109,7 +140,10 @@ type Chain interface { EthGetCode(ctx context.Context, account common.Address) ([]byte, error) EthGetLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) EthSendTransaction(ctx context.Context, tx *types.Transaction) error + EthBlockByNumber(ctx context.Context, blockHeight *big.Int) (*types.Block, error) + SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) + DebugTraceCall(ctx context.Context, txArgs TransactionArgs) (TraceCallRaw, error) } // Note: The default secrets config is used everywhere diff --git a/contracts/.gitignore b/contracts/.gitignore index 85198aaa..dc45b573 100644 --- a/contracts/.gitignore +++ b/contracts/.gitignore @@ -5,6 +5,8 @@ out/ # Ignores development broadcast logs !/broadcast /broadcast/*/31337/ +/broadcast/*/901/ +/broadcast/*/902/ /broadcast/**/dry-run/ # Docs diff --git a/crossl2inbox/CrossL2Inbox.json b/crossl2inbox/CrossL2Inbox.json new file mode 100644 index 00000000..b9a39784 --- /dev/null +++ b/crossl2inbox/CrossL2Inbox.json @@ -0,0 +1,170 @@ +[ + { + "inputs": [], + "name": "blockNumber", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "chainId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "origin", + "type": "address" + }, + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "logIndex", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "chainId", + "type": "uint256" + } + ], + "internalType": "struct ICrossL2Inbox.Identifier", + "name": "_id", + "type": "tuple" + }, + { + "internalType": "address", + "name": "_target", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_message", + "type": "bytes" + } + ], + "name": "executeMessage", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "logIndex", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "origin", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "timestamp", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes", + "name": "encodedId", + "type": "bytes" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "message", + "type": "bytes" + } + ], + "name": "ExecutingMessage", + "type": "event" + }, + { + "inputs": [], + "name": "InvalidChainId", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidTimestamp", + "type": "error" + }, + { + "inputs": [], + "name": "NotEntered", + "type": "error" + }, + { + "inputs": [], + "name": "TargetCallFailed", + "type": "error" + } + ] + \ No newline at end of file diff --git a/crossl2inbox/crossl2inbox.go b/crossl2inbox/crossl2inbox.go new file mode 100644 index 00000000..e7b6b3c0 --- /dev/null +++ b/crossl2inbox/crossl2inbox.go @@ -0,0 +1,134 @@ +package crossl2inbox + +import ( + "bytes" + _ "embed" + "errors" + "fmt" + "math/big" + + "github.com/ethereum-optimism/optimism/op-service/sources/batching" + "github.com/ethereum-optimism/supersim/types" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + ethTypes "github.com/ethereum/go-ethereum/core/types" + "github.com/holiman/uint256" +) + +const ( + eventExecutingMessage = "ExecutingMessage" +) + +var ( + ErrEventNotFound = errors.New("event not found") + crossL2InboxAddress = common.HexToAddress("0x4200000000000000000000000000000000000022") +) + +//go:embed CrossL2Inbox.json +var crossL2Inbox []byte + +type ExecutingMessage struct { + Message []byte + Identifier Identifier +} + +// Identifier struct to match the Solidity struct +type Identifier struct { + Origin common.Address + BlockNumber *big.Int + LogIndex *big.Int + Timestamp *big.Int + ChainId *big.Int +} + +type CrossL2Inbox struct { + contract *batching.BoundContract + crossL2InboxAbi abi.ABI +} + +func NewCrossL2Inbox() *CrossL2Inbox { + abi := loadCrossL2InboxABI() + return &CrossL2Inbox{ + crossL2InboxAbi: *abi, + contract: batching.NewBoundContract(abi, crossL2InboxAddress), + } +} + +func (i *CrossL2Inbox) DecodeExecutingMessageLog(l *ethTypes.Log) (*ExecutingMessage, error) { + if l.Address != i.contract.Addr() { + return nil, nil + } + name, result, err := i.contract.DecodeEvent(l) + if errors.Is(err, batching.ErrUnknownEvent) { + return nil, fmt.Errorf("%w: %v", ErrEventNotFound, err.Error()) + } else if err != nil { + return nil, fmt.Errorf("failed to decode event: %w", err) + } + if name != eventExecutingMessage { + return nil, nil + } + + encodedId := result.GetBytes(0) + message := result.GetBytes(1) + identifierArgs := abi.Arguments{ + {Name: "origin", Type: types.AddressType}, + {Name: "blockNumber", Type: types.Uint256Type}, + {Name: "logIndex", Type: types.Uint256Type}, + {Name: "timestamp", Type: types.Uint256Type}, + {Name: "chainId", Type: types.Uint256Type}, + } + + unpacked, unpackErr := identifierArgs.Unpack(encodedId) + if unpackErr != nil { + return nil, fmt.Errorf("failed to unpack message id: %w", err) + } + origin, ok := unpacked[0].(common.Address) + if !ok { + return nil, fmt.Errorf("failed to unpack id.origin: %w", err) + } + blockNumber, ok := unpacked[1].(*big.Int) + if !ok { + return nil, fmt.Errorf("failed to unpack id.blockNumber: %w", err) + } + logIndex, ok := unpacked[2].(*big.Int) + if !ok { + return nil, fmt.Errorf("failed to unpack id.logIndex: %w", err) + } + timestamp, ok := unpacked[3].(*big.Int) + if !ok { + return nil, fmt.Errorf("failed to unpack id.timestamp: %w", err) + } + chainId, ok := unpacked[4].(*big.Int) + if !ok { + return nil, fmt.Errorf("failed to unpack id.chainId: %w", err) + } + + return &ExecutingMessage{ + Message: message, + Identifier: Identifier{ + Origin: origin, + BlockNumber: blockNumber, + LogIndex: logIndex, + Timestamp: timestamp, + ChainId: chainId, + }, + }, nil +} + +func loadCrossL2InboxABI() *abi.ABI { + return loadABI(crossL2Inbox) +} + +type ChainID uint256.Int + +func ChainIDFromBig(chainID *big.Int) ChainID { + return ChainID(*uint256.MustFromBig(chainID)) +} + +func loadABI(json []byte) *abi.ABI { + if parsed, err := abi.JSON(bytes.NewReader(json)); err != nil { + panic(err) + } else { + return &parsed + } +} diff --git a/go.mod b/go.mod index 5fb4b0a4..59d3b89f 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/ethereum-optimism/optimism v1.7.7 github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20240626122407-496fe4119eab github.com/ethereum/go-ethereum v1.13.15 + github.com/holiman/uint256 v1.2.4 github.com/stretchr/testify v1.9.0 github.com/tyler-smith/go-bip39 v1.1.0 github.com/urfave/cli/v2 v2.27.1 @@ -53,7 +54,6 @@ require ( github.com/hashicorp/go-bexpr v0.1.11 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/holiman/bloomfilter/v2 v2.0.3 // indirect - github.com/holiman/uint256 v1.2.4 // indirect github.com/huin/goupnp v1.3.0 // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/klauspost/compress v1.17.8 // indirect diff --git a/opsimulator/opsimulator.go b/opsimulator/opsimulator.go index 7fcc7c96..38a33b4f 100644 --- a/opsimulator/opsimulator.go +++ b/opsimulator/opsimulator.go @@ -3,6 +3,8 @@ package opsimulator import ( "bytes" "context" + "encoding/hex" + "encoding/json" "errors" "fmt" "io" @@ -18,10 +20,16 @@ import ( ophttp "github.com/ethereum-optimism/optimism/op-service/httputil" "github.com/ethereum-optimism/optimism/op-service/tasks" + "github.com/ethereum-optimism/supersim/anvil" "github.com/ethereum-optimism/supersim/config" + "github.com/ethereum-optimism/supersim/crossl2inbox" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" + ethTypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" ) const ( @@ -40,6 +48,7 @@ type OpSimulator struct { bgTasks tasks.Group bgTasksCtx context.Context bgTasksCancel context.CancelFunc + clients map[uint64]*anvil.Anvil // One time tasks at startup startupTasks tasks.Group @@ -52,7 +61,13 @@ type OpSimulator struct { stopped atomic.Bool } -func New(log log.Logger, port uint64, l1Chain, l2Chain config.Chain, l2Config *config.L2Config) *OpSimulator { +type SimulatedLog struct { + Address common.Address + Topics []common.Hash + Data []byte +} + +func New(log log.Logger, port uint64, l1Chain, l2Chain config.Chain, l2Config *config.L2Config, clients map[uint64]*anvil.Anvil) *OpSimulator { bgTasksCtx, bgTasksCancel := context.WithCancel(context.Background()) startupTasksCtx, startupTasksCancel := context.WithCancel(context.Background()) @@ -78,6 +93,7 @@ func New(log log.Logger, port uint64, l1Chain, l2Chain config.Chain, l2Config *c log.Error("startup task failed", err) }, }, + clients: clients, } } @@ -88,7 +104,7 @@ func (opSim *OpSimulator) Start(ctx context.Context) error { } mux := http.NewServeMux() - mux.Handle("/", handler(proxy)) + mux.Handle("/", opSim.handler(proxy, ctx)) hs, err := ophttp.StartHTTPServer(net.JoinHostPort(host, fmt.Sprintf("%d", opSim.port)), mux) if err != nil { @@ -166,7 +182,7 @@ func (opSim *OpSimulator) startBackgroundTasks() { }) } -func handler(proxy *httputil.ReverseProxy) http.HandlerFunc { +func (opSim *OpSimulator) handler(proxy *httputil.ReverseProxy, ctx context.Context) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodOptions { // handle preflight requests @@ -189,11 +205,24 @@ func handler(proxy *httputil.ReverseProxy) http.HandlerFunc { return } - // TODO(https://github.com/ethereum-optimism/supersim/issues/55): support batch txs - for _, msg := range msgs { if msg.Method == "eth_sendRawTransaction" { - checkInteropInvariants() + var params []string + if err := json.Unmarshal(msg.Params, ¶ms); err != nil { + http.Error(w, fmt.Sprintf("Bad params sent to eth_sendRawTransaction: %v", err), http.StatusBadRequest) + return + } + if len(params) != 1 { + http.Error(w, "raw transaction request has invalid number of params", http.StatusBadRequest) + return + } + + err := opSim.checkInteropInvariants(ctx, params[0]) + if err != nil { + opSim.log.Error(fmt.Sprintf("interop invariants not met: %v", err)) + http.Error(w, fmt.Sprintf("interop invariants not met: %v", err), http.StatusBadRequest) + return + } } } @@ -216,9 +245,143 @@ func (opSim *OpSimulator) AddDependency(chainID uint64, depSet []uint64) error { return nil } -// TODO(https://github.com/ethereum-optimism/supersim/issues/19): add logic for checking that an interop transaction is valid. -func checkInteropInvariants() bool { - return true +func (opSim *OpSimulator) checkInteropInvariants(ctx context.Context, rawTx string) error { + tx, rawTxErr := opSim.decodeRawTransaction(rawTx) + if rawTxErr != nil { + return fmt.Errorf("failed to decode transaction: %w", rawTxErr) + } + + from, err := getFromAddress(tx) + + if err != nil { + return fmt.Errorf("failed to find sender of transaction: %w", err) + } + + result, err := opSim.l2Chain.DebugTraceCall(ctx, config.TransactionArgs{From: from, To: tx.To(), Gas: hexutil.Uint64(tx.Gas()), GasPrice: (*hexutil.Big)(tx.GasPrice()), Data: tx.Data(), Value: (*hexutil.Big)(tx.Value())}) + if err != nil { + return fmt.Errorf("failed to simulate transaction: %w", err) + } + if result.Error != nil { + return fmt.Errorf("tx trace error: %v", result.Error) + } + simulatedLogs := toSimulatedLogs(result) + + crossL2Inbox := crossl2inbox.NewCrossL2Inbox() + var executingMessages []crossl2inbox.ExecutingMessage + for _, log := range simulatedLogs { + executingMessage, err := crossL2Inbox.DecodeExecutingMessageLog(&log) + if err != nil { + return fmt.Errorf("failed to decode executing messages from transaction logs: %w", err) + } + + if executingMessage != nil { + executingMessages = append(executingMessages, *executingMessage) + } + } + + if len(executingMessages) >= 1 { + for _, executingMessage := range executingMessages { + client := opSim.clients[executingMessage.Identifier.ChainId.Uint64()] + if client == nil { + return fmt.Errorf("no client found for chainid: %v on executing message", executingMessage.Identifier.ChainId) + } + + block, err := client.EthBlockByNumber(ctx, executingMessage.Identifier.BlockNumber) + if err != nil { + return fmt.Errorf("failed to fetch executing message block: %w", err) + } + + if executingMessage.Identifier.Timestamp.Cmp(new(big.Int).SetUint64(block.Time())) != 0 { + return fmt.Errorf("executing message identifier does not match block timestamp: %w", err) + } + + logs, err := client.EthGetLogs( + ctx, + ethereum.FilterQuery{ + Addresses: []common.Address{executingMessage.Identifier.Origin}, + FromBlock: executingMessage.Identifier.BlockNumber, + ToBlock: executingMessage.Identifier.BlockNumber, + }, + ) + if err != nil { + return fmt.Errorf("failed to fetch initiating message logs: %w", err) + } + var initiatingMessageLogs []ethTypes.Log + for _, log := range logs { + logIndex := big.NewInt(int64(log.Index)) + if logIndex.Cmp(executingMessage.Identifier.LogIndex) == 0 { + initiatingMessageLogs = append(initiatingMessageLogs, log) + } + } + + if len(initiatingMessageLogs) == 0 { + return fmt.Errorf("initiating message not found") + } + + // Since we look for a log at a specific index, this should never be more than 1. + if len(initiatingMessageLogs) > 1 { + return fmt.Errorf("unexpected number of initiating messages found: %v", len(initiatingMessageLogs)) + } + + if !bytes.Equal(executingMessage.Message, messagePayloadBytes(&initiatingMessageLogs[0])) { + return fmt.Errorf("executing and initiating message fields are not equal") + } + } + } + + return nil +} + +func toSimulatedLogs(call config.TraceCallRaw) []ethTypes.Log { + var logs []ethTypes.Log + + // Recursively append the logs for each nested call + for _, call := range call.Calls { + logs = append(logs, toSimulatedLogs(call)...) + } + + for _, log := range call.Logs { + logs = append(logs, ethTypes.Log{ + Address: log.Address, + Topics: log.Topics, + Data: common.FromHex(log.Data), + }) + } + + return logs +} + +func (opSim *OpSimulator) decodeRawTransaction(rawTx string) (*ethTypes.Transaction, error) { + rawTxBytes, err := hex.DecodeString(rawTx[2:]) + if err != nil { + return nil, fmt.Errorf("failed to decode raw transaction: %w", err) + } + + var ethTx ethTypes.Transaction + if err := ethTx.UnmarshalBinary(rawTxBytes); err != nil { + // If UnmarshalBinary failed, we will try RLP in case user made mistake + e := rlp.DecodeBytes(rawTxBytes, ðTx) + if e != nil { + return nil, fmt.Errorf("could not decode Ethereum transaction: %w", e) + } + opSim.log.Warn("Ethereum transaction was in RLP format instead of binary," + + " transaction has been processed anyway, but it'd be best to use the Ethereum binary standard encoding") + } + return ðTx, nil +} + +func messagePayloadBytes(log *ethTypes.Log) []byte { + msg := []byte{} + for _, topic := range log.Topics { + msg = append(msg, topic.Bytes()...) + } + return append(msg, log.Data...) +} + +func getFromAddress(tx *ethTypes.Transaction) (common.Address, error) { + from, err := types.Sender(types.LatestSignerForChainID(tx.ChainId()), tx) + + return from, err } func (opSim *OpSimulator) createReverseProxy() (*httputil.ReverseProxy, error) { diff --git a/orchestrator/orchestrator.go b/orchestrator/orchestrator.go index a6664f53..8271eaa6 100644 --- a/orchestrator/orchestrator.go +++ b/orchestrator/orchestrator.go @@ -36,7 +36,7 @@ func NewOrchestrator(log log.Logger, networkConfig *config.NetworkConfig) (*Orch l2Anvil := anvil.New(log, &cfg) l2Anvils[cfg.ChainID] = l2Anvil - L2OpSims[cfg.ChainID] = opsimulator.New(log, nextL2Port, l1Anvil, l2Anvil, cfg.L2Config) + L2OpSims[cfg.ChainID] = opsimulator.New(log, nextL2Port, l1Anvil, l2Anvil, cfg.L2Config, l2Anvils) // only increment expected port if it has been specified if nextL2Port > 0 { diff --git a/testutils/mockchain.go b/testutils/mockchain.go index 42e221ff..c49c9200 100644 --- a/testutils/mockchain.go +++ b/testutils/mockchain.go @@ -2,6 +2,7 @@ package testutils import ( "context" + "math/big" "github.com/ethereum-optimism/supersim/config" @@ -64,6 +65,14 @@ func (c *MockChain) EthSendTransaction(ctx context.Context, tx *types.Transactio return nil } +func (c *MockChain) EthBlockByNumber(ctx context.Context, blockHeight *big.Int) (*types.Block, error) { + return &types.Block{}, nil +} + func (c *MockChain) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) { return &MockSubscription{}, nil } + +func (c *MockChain) DebugTraceCall(ctx context.Context, txArgs config.TransactionArgs) (config.TraceCallRaw, error) { + return config.TraceCallRaw{}, nil +} diff --git a/types/types.go b/types/types.go new file mode 100644 index 00000000..e41a0985 --- /dev/null +++ b/types/types.go @@ -0,0 +1,11 @@ +package types + +import "github.com/ethereum/go-ethereum/accounts/abi" + +var ( + // Standard ABI types + Uint256Type, _ = abi.NewType("uint256", "", nil) + BytesType, _ = abi.NewType("bytes", "", nil) + AddressType, _ = abi.NewType("address", "", nil) + Bytes32Type, _ = abi.NewType("bytes32", "", nil) +)