Commit 176478d7 authored by Adrian Sutton's avatar Adrian Sutton Committed by GitHub

op-e2e: Add e2e test that checks steps that load local preimages. (#9687)

Co-authored-by: default avatarrefcell <abigger87@gmail.com>
parent beff72f8
...@@ -5,6 +5,9 @@ import ( ...@@ -5,6 +5,9 @@ import (
"fmt" "fmt"
"os" "os"
"os/exec" "os/exec"
"slices"
"strconv"
"strings"
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -65,6 +68,11 @@ var ( ...@@ -65,6 +68,11 @@ var (
Value: new(StepMatcherFlag), Value: new(StepMatcherFlag),
Required: false, Required: false,
} }
RunStopAtPreimageFlag = &cli.StringFlag{
Name: "stop-at-preimage",
Usage: "stop at the first preimage request matching this key",
Required: false,
}
RunStopAtPreimageTypeFlag = &cli.StringFlag{ RunStopAtPreimageTypeFlag = &cli.StringFlag{
Name: "stop-at-preimage-type", Name: "stop-at-preimage-type",
Usage: "stop at the first preimage request matching this type", Usage: "stop at the first preimage request matching this type",
...@@ -247,24 +255,41 @@ func Run(ctx *cli.Context) error { ...@@ -247,24 +255,41 @@ func Run(ctx *cli.Context) error {
errLog := &mipsevm.LoggingWriter{Name: "program std-err", Log: l} errLog := &mipsevm.LoggingWriter{Name: "program std-err", Log: l}
stopAtAnyPreimage := false stopAtAnyPreimage := false
var stopAtPreimageTypeByte preimage.KeyType var stopAtPreimageKeyPrefix []byte
switch ctx.String(RunStopAtPreimageTypeFlag.Name) { stopAtPreimageOffset := uint32(0)
case "local": if ctx.IsSet(RunStopAtPreimageFlag.Name) {
stopAtPreimageTypeByte = preimage.LocalKeyType val := ctx.String(RunStopAtPreimageFlag.Name)
case "keccak": parts := strings.Split(val, "@")
stopAtPreimageTypeByte = preimage.Keccak256KeyType if len(parts) > 2 {
case "sha256": return fmt.Errorf("invalid %v: %v", RunStopAtPreimageFlag.Name, val)
stopAtPreimageTypeByte = preimage.Sha256KeyType }
case "blob": stopAtPreimageKeyPrefix = common.FromHex(parts[0])
stopAtPreimageTypeByte = preimage.BlobKeyType if len(parts) == 2 {
case "precompile": x, err := strconv.ParseUint(parts[1], 10, 32)
stopAtPreimageTypeByte = preimage.PrecompileKeyType if err != nil {
case "any": return fmt.Errorf("invalid preimage offset: %w", err)
stopAtAnyPreimage = true }
case "": stopAtPreimageOffset = uint32(x)
// 0 preimage type is forbidden so will not stop at any preimage }
default: } else {
return fmt.Errorf("invalid preimage type %q", ctx.String(RunStopAtPreimageTypeFlag.Name)) switch ctx.String(RunStopAtPreimageTypeFlag.Name) {
case "local":
stopAtPreimageKeyPrefix = []byte{byte(preimage.LocalKeyType)}
case "keccak":
stopAtPreimageKeyPrefix = []byte{byte(preimage.Keccak256KeyType)}
case "sha256":
stopAtPreimageKeyPrefix = []byte{byte(preimage.Sha256KeyType)}
case "blob":
stopAtPreimageKeyPrefix = []byte{byte(preimage.BlobKeyType)}
case "precompile":
stopAtPreimageKeyPrefix = []byte{byte(preimage.PrecompileKeyType)}
case "any":
stopAtAnyPreimage = true
case "":
// 0 preimage type is forbidden so will not stop at any preimage
default:
return fmt.Errorf("invalid preimage type %q", ctx.String(RunStopAtPreimageTypeFlag.Name))
}
} }
stopAtPreimageLargerThan := ctx.Int(RunStopAtPreimageLargerThanFlag.Name) stopAtPreimageLargerThan := ctx.Int(RunStopAtPreimageLargerThanFlag.Name)
...@@ -362,8 +387,6 @@ func Run(ctx *cli.Context) error { ...@@ -362,8 +387,6 @@ func Run(ctx *cli.Context) error {
} }
} }
prevPreimageOffset := state.PreimageOffset
if proofAt(state) { if proofAt(state) {
preStateHash, err := state.EncodeWitness().StateHash() preStateHash, err := state.EncodeWitness().StateHash()
if err != nil { if err != nil {
...@@ -399,17 +422,21 @@ func Run(ctx *cli.Context) error { ...@@ -399,17 +422,21 @@ func Run(ctx *cli.Context) error {
} }
} }
if preimageRead := state.PreimageOffset > prevPreimageOffset; preimageRead { lastPreimageKey, lastPreimageValue, lastPreimageOffset := us.LastPreimage()
if lastPreimageOffset != ^uint32(0) {
if stopAtAnyPreimage { if stopAtAnyPreimage {
l.Info("Stopping at preimage read") l.Info("Stopping at preimage read")
break break
} }
if state.PreimageKey.Bytes()[0] == byte(stopAtPreimageTypeByte) { if len(stopAtPreimageKeyPrefix) > 0 &&
l.Info("Stopping at preimage read", "type", stopAtPreimageTypeByte) slices.Equal(lastPreimageKey[:len(stopAtPreimageKeyPrefix)], stopAtPreimageKeyPrefix) {
break if stopAtPreimageOffset == lastPreimageOffset {
l.Info("Stopping at preimage read", "keyPrefix", common.Bytes2Hex(stopAtPreimageKeyPrefix), "offset", lastPreimageOffset)
break
}
} }
if stopAtPreimageLargerThan != 0 && len(us.LastPreimage()) > stopAtPreimageLargerThan { if stopAtPreimageLargerThan != 0 && len(lastPreimageValue) > stopAtPreimageLargerThan {
l.Info("Stopping at preimage read", "size", len(us.LastPreimage()), "min", stopAtPreimageLargerThan) l.Info("Stopping at preimage read", "size", len(lastPreimageValue), "min", stopAtPreimageLargerThan)
break break
} }
} }
...@@ -435,6 +462,7 @@ var RunCommand = &cli.Command{ ...@@ -435,6 +462,7 @@ var RunCommand = &cli.Command{
RunSnapshotAtFlag, RunSnapshotAtFlag,
RunSnapshotFmtFlag, RunSnapshotFmtFlag,
RunStopAtFlag, RunStopAtFlag,
RunStopAtPreimageFlag,
RunStopAtPreimageTypeFlag, RunStopAtPreimageTypeFlag,
RunStopAtPreimageLargerThanFlag, RunStopAtPreimageLargerThanFlag,
RunMetaFlag, RunMetaFlag,
......
...@@ -81,6 +81,6 @@ func (m *InstrumentedState) Step(proof bool) (wit *StepWitness, err error) { ...@@ -81,6 +81,6 @@ func (m *InstrumentedState) Step(proof bool) (wit *StepWitness, err error) {
return return
} }
func (m *InstrumentedState) LastPreimage() []byte { func (m *InstrumentedState) LastPreimage() ([32]byte, []byte, uint32) {
return m.lastPreimage return m.lastPreimageKey, m.lastPreimage, m.lastPreimageOffset
} }
...@@ -13,6 +13,7 @@ import ( ...@@ -13,6 +13,7 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/config" "github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
preimage "github.com/ethereum-optimism/optimism/op-preimage"
"github.com/ethereum-optimism/optimism/op-program/host/kvstore" "github.com/ethereum-optimism/optimism/op-program/host/kvstore"
"github.com/ethereum-optimism/optimism/op-service/ioutil" "github.com/ethereum-optimism/optimism/op-service/ioutil"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -255,6 +256,12 @@ type preimageOpts []string ...@@ -255,6 +256,12 @@ type preimageOpts []string
type PreimageOpt func() preimageOpts type PreimageOpt func() preimageOpts
func PreimageLoad(key preimage.Key, offset uint32) PreimageOpt {
return func() preimageOpts {
return []string{"--stop-at-preimage", fmt.Sprintf("%v@%v", common.Hash(key.PreimageKey()).Hex(), offset)}
}
}
func FirstPreimageLoadOfType(preimageType string) PreimageOpt { func FirstPreimageLoadOfType(preimageType string) PreimageOpt {
return func() preimageOpts { return func() preimageOpts {
return []string{"--stop-at-preimage-type", preimageType} return []string{"--stop-at-preimage-type", preimageType}
...@@ -287,28 +294,20 @@ func NewTraceProviderForTest(logger log.Logger, m CannonMetricer, cfg *config.Co ...@@ -287,28 +294,20 @@ func NewTraceProviderForTest(logger log.Logger, m CannonMetricer, cfg *config.Co
return &CannonTraceProviderForTest{p} return &CannonTraceProviderForTest{p}
} }
func (p *CannonTraceProviderForTest) FindStep(ctx context.Context, start uint64, preimage PreimageOpt) (uint64, common.Hash, error) { func (p *CannonTraceProviderForTest) FindStep(ctx context.Context, start uint64, preimage PreimageOpt) (uint64, error) {
// First generate a snapshot of the starting state, so we can snap to it later for the full trace search // Run cannon to find the step that meets the preimage conditions
prestateProof, err := p.loadProof(ctx, start) if err := p.generator.(*Executor).generateProof(ctx, p.dir, start, math.MaxUint64, preimage()...); err != nil {
return 0, fmt.Errorf("generate cannon trace (until preimage read): %w", err)
}
// Load the step from the state cannon finished with
state, err := p.finalState()
if err != nil { if err != nil {
return 0, common.Hash{}, err return 0, fmt.Errorf("failed to load final state: %w", err)
} }
start += 1 // Check we didn't get to the end of the trace without finding the preimage read we were looking for
for { if state.Exited {
if err := p.generator.(*Executor).generateProof(ctx, p.dir, start, math.MaxUint64, preimage()...); err != nil { return 0, fmt.Errorf("preimage read not found: %w", io.EOF)
return 0, common.Hash{}, fmt.Errorf("generate cannon trace (until preimage read) with proof at %d: %w", start, err)
}
state, err := p.finalState()
if err != nil {
return 0, common.Hash{}, err
}
if state.Exited {
break
}
if state.PreimageOffset != 0 && state.PreimageOffset != prestateProof.OracleOffset {
return state.Step - 1, state.PreimageKey, nil
}
start = state.Step
} }
return 0, common.Hash{}, io.EOF // The state is the post-state so the step we want to execute to read the preimage is step - 1.
return state.Step - 1, nil
} }
...@@ -3,10 +3,14 @@ package disputegame ...@@ -3,10 +3,14 @@ package disputegame
import ( import (
"context" "context"
"crypto/ecdsa" "crypto/ecdsa"
"errors"
"io"
"math/big" "math/big"
"path/filepath" "path/filepath"
"time" "time"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-chain-ops/deployer"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/cannon" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/cannon"
...@@ -16,8 +20,10 @@ import ( ...@@ -16,8 +20,10 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/metrics" "github.com/ethereum-optimism/optimism/op-challenger/metrics"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait"
preimage "github.com/ethereum-optimism/optimism/op-preimage"
"github.com/ethereum-optimism/optimism/op-service/sources/batching" "github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
...@@ -124,8 +130,8 @@ func (g *OutputCannonGameHelper) CreateStepPreimageLoadCheck(ctx context.Context ...@@ -124,8 +130,8 @@ func (g *OutputCannonGameHelper) CreateStepPreimageLoadCheck(ctx context.Context
// This expects an odd execution game depth in order for the honest challenger to step on our leaf claim // This expects an odd execution game depth in order for the honest challenger to step on our leaf claim
func (g *OutputCannonGameHelper) ChallengeToPreimageLoad(ctx context.Context, outputRootClaim *ClaimHelper, challengerKey *ecdsa.PrivateKey, preimage cannon.PreimageOpt, preimageCheck PreimageLoadCheck, preloadPreimage bool) { func (g *OutputCannonGameHelper) ChallengeToPreimageLoad(ctx context.Context, outputRootClaim *ClaimHelper, challengerKey *ecdsa.PrivateKey, preimage cannon.PreimageOpt, preimageCheck PreimageLoadCheck, preloadPreimage bool) {
// Identifying the first state transition that loads a global preimage // Identifying the first state transition that loads a global preimage
provider := g.createCannonTraceProvider(ctx, "sequencer", outputRootClaim, challenger.WithPrivKey(challengerKey)) provider, _ := g.createCannonTraceProvider(ctx, "sequencer", outputRootClaim, challenger.WithPrivKey(challengerKey))
targetTraceIndex, _, err := provider.FindStep(ctx, 0, preimage) targetTraceIndex, err := provider.FindStep(ctx, 0, preimage)
g.require.NoError(err) g.require.NoError(err)
splitDepth := g.SplitDepth(ctx) splitDepth := g.SplitDepth(ctx)
...@@ -213,7 +219,70 @@ func (g *OutputCannonGameHelper) ChallengeToPreimageLoad(ctx context.Context, ou ...@@ -213,7 +219,70 @@ func (g *OutputCannonGameHelper) ChallengeToPreimageLoad(ctx context.Context, ou
g.LogGameData(ctx) g.LogGameData(ctx)
} }
func (g *OutputCannonGameHelper) createCannonTraceProvider(ctx context.Context, l2Node string, outputRootClaim *ClaimHelper, options ...challenger.Option) *cannon.CannonTraceProviderForTest { func (g *OutputCannonGameHelper) VerifyPreimage(ctx context.Context, outputRootClaim *ClaimHelper, preimageKey preimage.Key) {
execDepth := g.ExecDepth(ctx)
// Identifying the first state transition that loads a global preimage
provider, localContext := g.createCannonTraceProvider(ctx, "sequencer", outputRootClaim, challenger.WithPrivKey(deployer.TestKey))
start := uint64(0)
found := false
for offset := uint32(0); ; offset += 4 {
preimageOpt := cannon.PreimageLoad(preimageKey, offset)
g.t.Logf("Searching for step with key %x and offset %v", preimageKey.PreimageKey(), offset)
targetTraceIndex, err := provider.FindStep(ctx, start, preimageOpt)
if errors.Is(err, io.EOF) {
// Did not find any more reads
g.require.True(found, "Should have found at least one preimage read")
g.t.Logf("Searching for step with key %x and offset %v did not find another read", preimageKey.PreimageKey(), offset)
return
}
g.require.NoError(err, "Failed to find step that loads requested preimage")
start = targetTraceIndex
found = true
g.t.Logf("Target trace index: %v", targetTraceIndex)
pos := types.NewPosition(execDepth, new(big.Int).SetUint64(targetTraceIndex))
g.require.Equal(targetTraceIndex, pos.TraceIndex(execDepth).Uint64())
prestate, proof, oracleData, err := provider.GetStepData(ctx, pos)
g.require.NoError(err, "Failed to get step data")
g.require.NotNil(oracleData, "Should have had required preimage oracle data")
g.require.Equal(common.Hash(preimageKey.PreimageKey()).Bytes(), oracleData.OracleKey, "Must have correct preimage key")
tx, err := g.game.AddLocalData(g.opts,
oracleData.GetIdent(),
big.NewInt(outputRootClaim.index),
new(big.Int).SetUint64(uint64(oracleData.OracleOffset)))
g.require.NoError(err)
_, err = wait.ForReceiptOK(ctx, g.client, tx.Hash())
g.require.NoError(err)
expectedPostState, err := provider.Get(ctx, pos)
g.require.NoError(err, "Failed to get expected post state")
callOpts := &bind.CallOpts{Context: ctx}
vmAddr, err := g.game.Vm(callOpts)
g.require.NoError(err, "Failed to get VM address")
abi, err := bindings.MIPSMetaData.GetAbi()
g.require.NoError(err, "Failed to load MIPS ABI")
caller := batching.NewMultiCaller(g.client.Client(), batching.DefaultBatchSize)
result, err := caller.SingleCall(ctx, batching.BlockLatest, &batching.ContractCall{
Abi: abi,
Addr: vmAddr,
Method: "step",
Args: []interface{}{
prestate, proof, localContext,
},
From: g.addr,
})
g.require.NoError(err, "Failed to call step")
actualPostState := result.GetBytes32(0)
g.require.Equal(expectedPostState, common.Hash(actualPostState))
}
}
func (g *OutputCannonGameHelper) createCannonTraceProvider(ctx context.Context, l2Node string, outputRootClaim *ClaimHelper, options ...challenger.Option) (*cannon.CannonTraceProviderForTest, common.Hash) {
splitDepth := g.SplitDepth(ctx) splitDepth := g.SplitDepth(ctx)
g.require.EqualValues(outputRootClaim.Depth(), splitDepth+1, "outputRootClaim must be the root of an execution game") g.require.EqualValues(outputRootClaim.Depth(), splitDepth+1, "outputRootClaim must be the root of an execution game")
...@@ -234,13 +303,14 @@ func (g *OutputCannonGameHelper) createCannonTraceProvider(ctx context.Context, ...@@ -234,13 +303,14 @@ func (g *OutputCannonGameHelper) createCannonTraceProvider(ctx context.Context,
l1Head := g.getL1Head(ctx) l1Head := g.getL1Head(ctx)
outputProvider := outputs.NewTraceProvider(logger, prestateProvider, rollupClient, l1Head, splitDepth, prestateBlock, poststateBlock) outputProvider := outputs.NewTraceProvider(logger, prestateProvider, rollupClient, l1Head, splitDepth, prestateBlock, poststateBlock)
var localContext common.Hash
selector := split.NewSplitProviderSelector(outputProvider, splitDepth, func(ctx context.Context, depth types.Depth, pre types.Claim, post types.Claim) (types.TraceProvider, error) { selector := split.NewSplitProviderSelector(outputProvider, splitDepth, func(ctx context.Context, depth types.Depth, pre types.Claim, post types.Claim) (types.TraceProvider, error) {
agreed, disputed, err := outputs.FetchProposals(ctx, outputProvider, pre, post) agreed, disputed, err := outputs.FetchProposals(ctx, outputProvider, pre, post)
g.require.NoError(err) g.require.NoError(err)
g.t.Logf("Using trace between blocks %v and %v\n", agreed.L2BlockNumber, disputed.L2BlockNumber) g.t.Logf("Using trace between blocks %v and %v\n", agreed.L2BlockNumber, disputed.L2BlockNumber)
localInputs, err := cannon.FetchLocalInputsFromProposals(ctx, l1Head.Hash, l2Client, agreed, disputed) localInputs, err := cannon.FetchLocalInputsFromProposals(ctx, l1Head.Hash, l2Client, agreed, disputed)
g.require.NoError(err, "Failed to fetch local inputs") g.require.NoError(err, "Failed to fetch local inputs")
localContext := outputs.CreateLocalContext(pre, post) localContext = outputs.CreateLocalContext(pre, post)
dir := filepath.Join(cfg.Datadir, "cannon-trace") dir := filepath.Join(cfg.Datadir, "cannon-trace")
subdir := filepath.Join(dir, localContext.Hex()) subdir := filepath.Join(dir, localContext.Hex())
return cannon.NewTraceProviderForTest(logger, metrics.NoopMetrics, cfg, localInputs, subdir, g.MaxDepth(ctx)-splitDepth-1), nil return cannon.NewTraceProviderForTest(logger, metrics.NoopMetrics, cfg, localInputs, subdir, g.MaxDepth(ctx)-splitDepth-1), nil
...@@ -253,7 +323,7 @@ func (g *OutputCannonGameHelper) createCannonTraceProvider(ctx context.Context, ...@@ -253,7 +323,7 @@ func (g *OutputCannonGameHelper) createCannonTraceProvider(ctx context.Context,
provider, err := selector(ctx, game, game.Claims()[outputRootClaim.parentIndex], outputRootClaim.position) provider, err := selector(ctx, game, game.Claims()[outputRootClaim.parentIndex], outputRootClaim.position)
g.require.NoError(err) g.require.NoError(err)
translatingProvider := provider.(*trace.TranslatingProvider) translatingProvider := provider.(*trace.TranslatingProvider)
return translatingProvider.Original().(*cannon.CannonTraceProviderForTest) return translatingProvider.Original().(*cannon.CannonTraceProviderForTest), localContext
} }
func (g *OutputCannonGameHelper) defaultChallengerOptions(l2Node string) []challenger.Option { func (g *OutputCannonGameHelper) defaultChallengerOptions(l2Node string) []challenger.Option {
......
package faultproofs
import (
"context"
"fmt"
"testing"
op_e2e "github.com/ethereum-optimism/optimism/op-e2e"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/disputegame"
preimage "github.com/ethereum-optimism/optimism/op-preimage"
"github.com/ethereum-optimism/optimism/op-program/client"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)
func TestLocalPreimages(t *testing.T) {
op_e2e.InitParallel(t, op_e2e.UsesCannon)
tests := []struct {
key preimage.Key
}{
{key: client.L1HeadLocalIndex},
{key: client.L2OutputRootLocalIndex},
{key: client.L2ClaimLocalIndex},
{key: client.L2ClaimBlockNumberLocalIndex},
// We don't check client.L2ChainIDLocalIndex because e2e tests use a custom chain configuration
// which requires using a custom chain ID indicator so op-program will load the full rollup config and
// genesis from the preimage oracle
}
for _, test := range tests {
test := test
t.Run(fmt.Sprintf("preimage-%v", test.key), func(t *testing.T) {
op_e2e.InitParallel(t, op_e2e.UsesCannon)
ctx := context.Background()
sys, _ := startFaultDisputeSystem(t)
t.Cleanup(sys.Close)
disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys)
game := disputeGameFactory.StartOutputCannonGame(ctx, "sequencer", 3, common.Hash{0x01, 0xaa})
require.NotNil(t, game)
claim := game.DisputeLastBlock(ctx)
// Create the root of the cannon trace.
claim = claim.Attack(ctx, common.Hash{0x01})
game.LogGameData(ctx)
game.VerifyPreimage(ctx, claim, test.key)
game.LogGameData(ctx)
})
}
}
...@@ -5,7 +5,6 @@ import ( ...@@ -5,7 +5,6 @@ import (
"math/big" "math/big"
"github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
...@@ -16,6 +15,7 @@ type ContractCall struct { ...@@ -16,6 +15,7 @@ type ContractCall struct {
Addr common.Address Addr common.Address
Method string Method string
Args []interface{} Args []interface{}
From common.Address
} }
func NewContractCall(abi *abi.ABI, addr common.Address, method string, args ...interface{}) *ContractCall { func NewContractCall(abi *abi.ABI, addr common.Address, method string, args ...interface{}) *ContractCall {
...@@ -36,11 +36,13 @@ func (c *ContractCall) ToCallArgs() (interface{}, error) { ...@@ -36,11 +36,13 @@ func (c *ContractCall) ToCallArgs() (interface{}, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to pack arguments: %w", err) return nil, fmt.Errorf("failed to pack arguments: %w", err)
} }
msg := ethereum.CallMsg{
To: &c.Addr, arg := map[string]interface{}{
Data: data, "from": c.From,
"to": &c.Addr,
"input": hexutil.Bytes(data),
} }
return toCallArg(msg), nil return arg, nil
} }
func (c *ContractCall) Unpack(hex hexutil.Bytes) (*CallResult, error) { func (c *ContractCall) Unpack(hex hexutil.Bytes) (*CallResult, error) {
...@@ -51,26 +53,6 @@ func (c *ContractCall) Unpack(hex hexutil.Bytes) (*CallResult, error) { ...@@ -51,26 +53,6 @@ func (c *ContractCall) Unpack(hex hexutil.Bytes) (*CallResult, error) {
return &CallResult{out: out}, nil return &CallResult{out: out}, nil
} }
func toCallArg(msg ethereum.CallMsg) interface{} {
arg := map[string]interface{}{
"from": msg.From,
"to": msg.To,
}
if len(msg.Data) > 0 {
arg["input"] = hexutil.Bytes(msg.Data)
}
if msg.Value != nil {
arg["value"] = (*hexutil.Big)(msg.Value)
}
if msg.Gas != 0 {
arg["gas"] = hexutil.Uint64(msg.Gas)
}
if msg.GasPrice != nil {
arg["gasPrice"] = (*hexutil.Big)(msg.GasPrice)
}
return arg
}
func (c *ContractCall) ToTxCandidate() (txmgr.TxCandidate, error) { func (c *ContractCall) ToTxCandidate() (txmgr.TxCandidate, error) {
data, err := c.Pack() data, err := c.Pack()
if err != nil { if err != nil {
......
...@@ -15,11 +15,12 @@ func TestContractCall_ToCallArgs(t *testing.T) { ...@@ -15,11 +15,12 @@ func TestContractCall_ToCallArgs(t *testing.T) {
testAbi, err := bindings.ERC20MetaData.GetAbi() testAbi, err := bindings.ERC20MetaData.GetAbi()
require.NoError(t, err) require.NoError(t, err)
call := NewContractCall(testAbi, addr, "approve", common.Address{0xcc}, big.NewInt(1234444)) call := NewContractCall(testAbi, addr, "approve", common.Address{0xcc}, big.NewInt(1234444))
call.From = common.Address{0xab}
args, err := call.ToCallArgs() args, err := call.ToCallArgs()
require.NoError(t, err) require.NoError(t, err)
argMap, ok := args.(map[string]interface{}) argMap, ok := args.(map[string]interface{})
require.True(t, ok) require.True(t, ok)
require.Equal(t, argMap["from"], common.Address{}) require.Equal(t, argMap["from"], call.From)
require.Equal(t, argMap["to"], &addr) require.Equal(t, argMap["to"], &addr)
expectedData, err := call.Pack() expectedData, err := call.Pack()
require.NoError(t, err) require.NoError(t, err)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment