From a73b45bb585dcd5caa783efce4fb19a2501ed547 Mon Sep 17 00:00:00 2001 From: Po Date: Wed, 18 Sep 2024 08:08:01 +0800 Subject: [PATCH 1/7] Test: decoding tx calldata --- .../game/fault/contracts/faultdisputegame.go | 34 ++++++++++ .../sources/batching/test/generic_stub.go | 8 +++ op-service/sources/batching/tx_call.go | 65 +++++++++++++++++++ op-service/sources/batching/tx_call_test.go | 58 +++++++++++++++++ 4 files changed, 165 insertions(+) create mode 100644 op-service/sources/batching/tx_call.go create mode 100644 op-service/sources/batching/tx_call_test.go diff --git a/op-challenger/game/fault/contracts/faultdisputegame.go b/op-challenger/game/fault/contracts/faultdisputegame.go index e48f06549dfb..456ffaaf41cf 100644 --- a/op-challenger/game/fault/contracts/faultdisputegame.go +++ b/op-challenger/game/fault/contracts/faultdisputegame.go @@ -13,6 +13,7 @@ import ( "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts/metrics" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" + "github.com/ethereum-optimism/optimism/op-e2e/bindings" "github.com/ethereum-optimism/optimism/op-service/sources/batching" "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" "github.com/ethereum-optimism/optimism/op-service/txmgr" @@ -173,6 +174,10 @@ func (f *FaultDisputeGameContractLatest) GetBlockRange(ctx context.Context) (pre return } +func (f *FaultDisputeGameContractLatest) GetContract() *batching.BoundContract { + return f.contract +} + type GameMetadata struct { L1Head common.Hash L2BlockNum uint64 @@ -426,6 +431,35 @@ func (f *FaultDisputeGameContractLatest) GetClaim(ctx context.Context, idx uint6 return f.decodeClaim(result, int(idx)), nil } +func (f *FaultDisputeGameContractLatest) GetSubClaims(ctx context.Context, idx uint64) ([]types.Claim, error) { + defer f.metrics.StartContractRequest("GetClaim")() + var subClaims []types.Claim + result, err := f.multiCaller.SingleCall(ctx, rpcblock.Latest, f.contract.Call(methodClaim, new(big.Int).SetUint64(idx))) + if err != nil { + return append(subClaims, types.Claim{}), fmt.Errorf("failed to fetch claim %v: %w", idx, err) + } + aggClaim := f.decodeClaim(result, int(idx)) + subClaims = append(subClaims, aggClaim) + // findMoveTransaction + filter, err := bindings.NewFaultDisputeGameFilterer(f.contract.Addr(), nil) + if err != nil { + return nil, err + } + + parentIndex := [...]*big.Int{big.NewInt(int64(aggClaim.ParentContractIndex))} + claim := [...][32]byte{aggClaim.ClaimData.ValueBytes()} + claimant := [...]common.Address{aggClaim.Claimant} + moveIter, err := filter.FilterMove(nil, parentIndex[:], claim[:], claimant[:]) + if err != nil { + return nil, err + } + moveIter.Next() + txHash := moveIter.Event.Raw.TxHash + // decode calldata + + return subClaims, nil +} + func (f *FaultDisputeGameContractLatest) GetAllClaims(ctx context.Context, block rpcblock.Block) ([]types.Claim, error) { defer f.metrics.StartContractRequest("GetAllClaims")() results, err := batching.ReadArray(ctx, f.multiCaller, block, f.contract.Call(methodClaimCount), func(i *big.Int) *batching.ContractCall { diff --git a/op-service/sources/batching/test/generic_stub.go b/op-service/sources/batching/test/generic_stub.go index c269eb100d6b..d79d121d4363 100644 --- a/op-service/sources/batching/test/generic_stub.go +++ b/op-service/sources/batching/test/generic_stub.go @@ -80,6 +80,14 @@ func NewGetBalanceCall(addr common.Address, block rpcblock.Block, balance *big.I } } +func NewGetTxCall(txHash common.Hash, block rpcblock.Block, out *[]byte) ExpectedRpcCall { + return &GenericExpectedCall{ + method: "eth_getTransactionByHash", + args: []interface{}{txHash, block.ArgValue()}, + result: hexutil.Encode(*out), + } +} + func (c *GenericExpectedCall) Matches(rpcMethod string, args ...interface{}) error { if rpcMethod != c.method { return fmt.Errorf("expected method %v but was %v", c.method, rpcMethod) diff --git a/op-service/sources/batching/tx_call.go b/op-service/sources/batching/tx_call.go new file mode 100644 index 000000000000..4b4ad02fb4ff --- /dev/null +++ b/op-service/sources/batching/tx_call.go @@ -0,0 +1,65 @@ +package batching + +import ( + "fmt" + + "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/rpc" +) + +type TxCall struct { + Abi *abi.ABI + TxHash common.Hash + Method string +} + +var _ Call = (*TxCall)(nil) + +func NewTxCall(abi *abi.ABI, txhash common.Hash, method string) *TxCall { + return &TxCall{ + Abi: abi, + TxHash: txhash, + Method: method, + } +} + +func (b *TxCall) ToBatchElemCreator() (BatchElementCreator, error) { + return func(block rpcblock.Block) (any, rpc.BatchElem) { + out := new(hexutil.Bytes) + return out, rpc.BatchElem{ + Method: "eth_getTransactionByHash", + Args: []interface{}{b.TxHash, block.ArgValue()}, + Result: &out, + } + }, nil +} + +func (c *TxCall) HandleResult(result interface{}) (*CallResult, error) { + out, err := c.Unpack(*result.(*hexutil.Bytes)) + return out, err +} + +func (c *TxCall) DecodeTxParams(data []byte) (map[string]interface{}, error) { + m, err := c.Abi.MethodById(data[:4]) + v := map[string]interface{}{} + if err != nil { + return map[string]interface{}{}, err + } + if err := m.Inputs.UnpackIntoMap(v, data[4:]); err != nil { + return map[string]interface{}{}, err + } + return v, nil +} + +func (c *TxCall) Unpack(hex hexutil.Bytes) (*CallResult, error) { + inputs := c.Abi.Methods[c.Method].Inputs + + out, err := inputs.UnpackValues(hex[4:]) + if err != nil { + return nil, fmt.Errorf("failed to unpack inputs: %w", err) + } + return &CallResult{out: out}, nil +} diff --git a/op-service/sources/batching/tx_call_test.go b/op-service/sources/batching/tx_call_test.go new file mode 100644 index 000000000000..af67f6597934 --- /dev/null +++ b/op-service/sources/batching/tx_call_test.go @@ -0,0 +1,58 @@ +package batching + +import ( + "context" + "fmt" + "math/big" + "testing" + + "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" + "github.com/ethereum-optimism/optimism/op-service/sources/batching/test" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +func TestTxCall_ToCallArgs(t *testing.T) { + addr := common.Address{0xbd} + testAbi, err := test.ERC20MetaData.GetAbi() + require.NoError(t, err) + call := NewTxCall(testAbi, common.Hash{0xcc}, "approve") + expectedAmount := big.NewInt(1234444) + expectedSpender := common.Address{0xcc} + contractCall := NewContractCall(testAbi, addr, "approve", expectedSpender, expectedAmount) + packed, err := contractCall.Pack() + require.NoError(t, err) + + unpackedMap, err := call.DecodeTxParams(packed) + require.NoError(t, err) + require.Equal(t, expectedAmount, unpackedMap["amount"]) + require.Equal(t, expectedSpender, unpackedMap["spender"]) + + unpacked, err := call.Unpack(packed) + require.NoError(t, err) + require.Equal(t, expectedSpender, unpacked.GetAddress(0)) + require.Equal(t, expectedAmount, unpacked.GetBigInt(1)) +} + +func TestGetTxCalldata(t *testing.T) { + expectedSpender := common.Address{0xcc} + expectedAmount := big.NewInt(1234444) + txHash := common.Hash{0x11} + addr := common.Address{0xbd} + + testAbi, err := test.ERC20MetaData.GetAbi() + require.NoError(t, err) + contractCall := NewContractCall(testAbi, addr, "approve", expectedSpender, expectedAmount) + packed, err := contractCall.Pack() + + stub := test.NewRpcStub(t) + stub.AddExpectedCall(test.NewGetTxCall(txHash, rpcblock.Latest, &packed)) + + caller := NewMultiCaller(stub, DefaultBatchSize) + txCall := NewTxCall(testAbi, txHash, "approve") + result, err := caller.SingleCall(context.Background(), rpcblock.Latest, txCall) + require.NoError(t, err) + fmt.Println() + require.Equal(t, expectedSpender, result.GetAddress(0)) + require.Equal(t, expectedAmount, result.GetBigInt(1)) +} From 15e66c13bcb679211ab548f3d18b83b30b2fc149 Mon Sep 17 00:00:00 2001 From: Po Date: Sat, 28 Sep 2024 00:44:02 +0800 Subject: [PATCH 2/7] Test: add event stub --- .../game/fault/contracts/delayed_weth.go | 2 +- .../game/fault/contracts/faultdisputegame.go | 67 ++++++++++------- .../fault/contracts/faultdisputegame_test.go | 65 ++++++++++++++++ .../game/fault/contracts/gamefactory.go | 2 +- op-challenger/game/fault/contracts/oracle.go | 2 +- op-challenger/game/fault/contracts/vm.go | 2 +- op-service/sources/batching/bound.go | 17 +++-- op-service/sources/batching/bound_test.go | 4 +- op-service/sources/batching/event_call.go | 39 ++++++++++ .../sources/batching/event_call_test.go | 75 +++++++++++++++++++ op-service/sources/batching/multicall.go | 20 +++++ op-service/sources/batching/test/abi_stub.go | 22 ++++++ .../sources/batching/test/event_stub.go | 63 ++++++++++++++++ op-service/sources/batching/test/tx_stub.go | 42 +++++++++++ op-service/sources/batching/tx_call_test.go | 3 +- 15 files changed, 383 insertions(+), 42 deletions(-) create mode 100644 op-service/sources/batching/event_call.go create mode 100644 op-service/sources/batching/event_call_test.go create mode 100644 op-service/sources/batching/test/event_stub.go create mode 100644 op-service/sources/batching/test/tx_stub.go diff --git a/op-challenger/game/fault/contracts/delayed_weth.go b/op-challenger/game/fault/contracts/delayed_weth.go index f093ecb3ff3b..36d2bb692e42 100644 --- a/op-challenger/game/fault/contracts/delayed_weth.go +++ b/op-challenger/game/fault/contracts/delayed_weth.go @@ -32,7 +32,7 @@ func NewDelayedWETHContract(metrics metrics.ContractMetricer, addr common.Addres return &DelayedWETHContract{ metrics: metrics, multiCaller: caller, - contract: batching.NewBoundContract(contractAbi, addr), + contract: batching.NewBoundContract(contractAbi, addr, caller), } } diff --git a/op-challenger/game/fault/contracts/faultdisputegame.go b/op-challenger/game/fault/contracts/faultdisputegame.go index 456ffaaf41cf..5a6a66a2f014 100644 --- a/op-challenger/game/fault/contracts/faultdisputegame.go +++ b/op-challenger/game/fault/contracts/faultdisputegame.go @@ -97,7 +97,7 @@ func NewFaultDisputeGameContract(ctx context.Context, metrics metrics.ContractMe FaultDisputeGameContractLatest: FaultDisputeGameContractLatest{ metrics: metrics, multiCaller: caller, - contract: batching.NewBoundContract(legacyAbi, addr), + contract: batching.NewBoundContract(legacyAbi, addr, caller), }, }, nil } else if strings.HasPrefix(version, "0.18.") || strings.HasPrefix(version, "1.0.") { @@ -107,7 +107,7 @@ func NewFaultDisputeGameContract(ctx context.Context, metrics metrics.ContractMe FaultDisputeGameContractLatest: FaultDisputeGameContractLatest{ metrics: metrics, multiCaller: caller, - contract: batching.NewBoundContract(legacyAbi, addr), + contract: batching.NewBoundContract(legacyAbi, addr, caller), }, }, nil } else if strings.HasPrefix(version, "1.1.") { @@ -117,14 +117,14 @@ func NewFaultDisputeGameContract(ctx context.Context, metrics metrics.ContractMe FaultDisputeGameContractLatest: FaultDisputeGameContractLatest{ metrics: metrics, multiCaller: caller, - contract: batching.NewBoundContract(legacyAbi, addr), + contract: batching.NewBoundContract(legacyAbi, addr, caller), }, }, nil } else { return &FaultDisputeGameContractLatest{ metrics: metrics, multiCaller: caller, - contract: batching.NewBoundContract(contractAbi, addr), + contract: batching.NewBoundContract(contractAbi, addr, caller), }, nil } } @@ -431,17 +431,27 @@ func (f *FaultDisputeGameContractLatest) GetClaim(ctx context.Context, idx uint6 return f.decodeClaim(result, int(idx)), nil } -func (f *FaultDisputeGameContractLatest) GetSubClaims(ctx context.Context, idx uint64) ([]types.Claim, error) { - defer f.metrics.StartContractRequest("GetClaim")() - var subClaims []types.Claim - result, err := f.multiCaller.SingleCall(ctx, rpcblock.Latest, f.contract.Call(methodClaim, new(big.Int).SetUint64(idx))) +func (f *FaultDisputeGameContractLatest) GetAllClaims(ctx context.Context, block rpcblock.Block) ([]types.Claim, error) { + defer f.metrics.StartContractRequest("GetAllClaims")() + results, err := batching.ReadArray(ctx, f.multiCaller, block, f.contract.Call(methodClaimCount), func(i *big.Int) *batching.ContractCall { + return f.contract.Call(methodClaim, i) + }) if err != nil { - return append(subClaims, types.Claim{}), fmt.Errorf("failed to fetch claim %v: %w", idx, err) + return nil, fmt.Errorf("failed to load claims: %w", err) } - aggClaim := f.decodeClaim(result, int(idx)) - subClaims = append(subClaims, aggClaim) + + var claims []types.Claim + for idx, result := range results { + claims = append(claims, f.decodeClaim(result, idx)) + } + return claims, nil +} + +func (f *FaultDisputeGameContractLatest) GetSubClaims(ctx context.Context, block rpcblock.Block, aggClaim *types.Claim) ([]common.Hash, error) { + defer f.metrics.StartContractRequest("GetAllSubClaims")() + // findMoveTransaction - filter, err := bindings.NewFaultDisputeGameFilterer(f.contract.Addr(), nil) + filter, err := bindings.NewFaultDisputeGameFilterer(f.contract.Addr(), f.multiCaller) if err != nil { return nil, err } @@ -451,29 +461,27 @@ func (f *FaultDisputeGameContractLatest) GetSubClaims(ctx context.Context, idx u claimant := [...]common.Address{aggClaim.Claimant} moveIter, err := filter.FilterMove(nil, parentIndex[:], claim[:], claimant[:]) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to filter move event log: %w", err) + } + ok := moveIter.Next() + if !ok { + return nil, fmt.Errorf("failed to get move event log: %w", moveIter.Error()) } - moveIter.Next() txHash := moveIter.Event.Raw.TxHash - // decode calldata - - return subClaims, nil -} -func (f *FaultDisputeGameContractLatest) GetAllClaims(ctx context.Context, block rpcblock.Block) ([]types.Claim, error) { - defer f.metrics.StartContractRequest("GetAllClaims")() - results, err := batching.ReadArray(ctx, f.multiCaller, block, f.contract.Call(methodClaimCount), func(i *big.Int) *batching.ContractCall { - return f.contract.Call(methodClaim, i) - }) + // todo: replace hardcoded nary, method name + nary := 1 + result, err := f.multiCaller.SingleCall(ctx, rpcblock.Latest, batching.NewTxCall(f.contract.Abi(), txHash, "move")) if err != nil { - return nil, fmt.Errorf("failed to load claims: %w", err) + return nil, fmt.Errorf("failed to load claim calldata: %w", err) } - - var claims []types.Claim - for idx, result := range results { - claims = append(claims, f.decodeClaim(result, idx)) + var subClaims []common.Hash + // We should start from 2 du to the signature of move(Claim _disputed, uint256 _challengeIndex, Claim _claim) + for i := 2; i < nary+2; i++ { + subClaims = append(subClaims, result.GetHash(i)) } - return claims, nil + + return subClaims, nil } func (f *FaultDisputeGameContractLatest) IsResolved(ctx context.Context, block rpcblock.Block, claims ...types.Claim) ([]bool, error) { @@ -658,4 +666,5 @@ type FaultDisputeGameContract interface { ResolveClaimTx(claimIdx uint64) (txmgr.TxCandidate, error) CallResolve(ctx context.Context) (gameTypes.GameStatus, error) ResolveTx() (txmgr.TxCandidate, error) + GetSubClaims(ctx context.Context, block rpcblock.Block, aggClaim *types.Claim) ([]common.Hash, error) } diff --git a/op-challenger/game/fault/contracts/faultdisputegame_test.go b/op-challenger/game/fault/contracts/faultdisputegame_test.go index 4ea7d9c3d0bf..863a9480f66a 100644 --- a/op-challenger/game/fault/contracts/faultdisputegame_test.go +++ b/op-challenger/game/fault/contracts/faultdisputegame_test.go @@ -23,6 +23,7 @@ import ( "github.com/ethereum-optimism/optimism/packages/contracts-bedrock/snapshots" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" + coreTypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rlp" "github.com/stretchr/testify/require" ) @@ -321,6 +322,70 @@ func TestGetAllClaims(t *testing.T) { } } +func TestGetSubClaims(t *testing.T) { + for _, version := range versions { + if version.Is("1.2.0") { + version := version + t.Run(version.version, func(t *testing.T) { + stubRpc, game := setupFaultDisputeGameTest(t, version) + claim0 := faultTypes.Claim{ + ClaimData: faultTypes.ClaimData{ + Value: common.Hash{0xaa}, + Position: faultTypes.NewPositionFromGIndex(big.NewInt(1)), + Bond: big.NewInt(5), + }, + CounteredBy: common.Address{0x01}, + Claimant: common.Address{0x02}, + Clock: decodeClock(big.NewInt(1234)), + ContractIndex: 0, + ParentContractIndex: math.MaxUint32, + } + expectedClaims := []faultTypes.Claim{claim0} + block := rpcblock.ByNumber(42) + stubRpc.SetResponse(fdgAddr, methodClaimCount, block, nil, []interface{}{big.NewInt(int64(len(expectedClaims)))}) + + name := "Move" + fdgAbi := version.loadAbi() + + var challgenIndex []interface{} + challgenIndex = append(challgenIndex, big.NewInt(int64(claim0.ParentContractIndex))) + claim := []interface{}{claim0.ClaimData.ValueBytes()} + address := []interface{}{claim0.Claimant} + query := [][]interface{}{challgenIndex, claim, address} + txHash := common.Hash{0xff} + + query = append([][]interface{}{{fdgAbi.Events[name].ID}}, query...) + + topics, err := abi.MakeTopics(query...) + var queryTopics []common.Hash + for _, item := range topics { + queryTopics = append(queryTopics, item[0]) + } + require.NoError(t, err) + out := []coreTypes.Log{ + { + Address: fdgAddr, + Topics: queryTopics, + Data: []byte{}, + TxHash: txHash, + }, + } + stubRpc.SetFilterLogResponse(topics, fdgAddr, block, out) + + contractCall := batching.NewContractCall(fdgAbi, fdgAddr, "move", claim0.ClaimData.Value, challgenIndex[0], claim0.ClaimData.Value, true) + packed, err := contractCall.Pack() + require.NoError(t, err) + stubRpc.SetTxResponse(txHash, packed) + + claims, err := game.GetSubClaims(context.Background(), block, &claim0) + require.NoError(t, err) + require.Equal(t, 1, len(claims)) + require.Equal(t, claim0.ClaimData.Value, claims[0]) + }) + } + } +} + func TestGetBalance(t *testing.T) { for _, version := range versions { version := version diff --git a/op-challenger/game/fault/contracts/gamefactory.go b/op-challenger/game/fault/contracts/gamefactory.go index d67a4688c422..6f7b97a9ae1c 100644 --- a/op-challenger/game/fault/contracts/gamefactory.go +++ b/op-challenger/game/fault/contracts/gamefactory.go @@ -44,7 +44,7 @@ func NewDisputeGameFactoryContract(m metrics.ContractMetricer, addr common.Addre return &DisputeGameFactoryContract{ metrics: m, multiCaller: caller, - contract: batching.NewBoundContract(factoryAbi, addr), + contract: batching.NewBoundContract(factoryAbi, addr, caller), abi: factoryAbi, } } diff --git a/op-challenger/game/fault/contracts/oracle.go b/op-challenger/game/fault/contracts/oracle.go index be2520bef8b5..5bf5ddc65d58 100644 --- a/op-challenger/game/fault/contracts/oracle.go +++ b/op-challenger/game/fault/contracts/oracle.go @@ -89,7 +89,7 @@ func NewPreimageOracleContract(addr common.Address, caller *batching.MultiCaller return &PreimageOracleContract{ addr: addr, multiCaller: caller, - contract: batching.NewBoundContract(oracleAbi, addr), + contract: batching.NewBoundContract(oracleAbi, addr, caller), } } diff --git a/op-challenger/game/fault/contracts/vm.go b/op-challenger/game/fault/contracts/vm.go index 1d1e22632cfe..8b50a5eff512 100644 --- a/op-challenger/game/fault/contracts/vm.go +++ b/op-challenger/game/fault/contracts/vm.go @@ -25,7 +25,7 @@ func NewVMContract(addr common.Address, caller *batching.MultiCaller) *VMContrac return &VMContract{ multiCaller: caller, - contract: batching.NewBoundContract(mipsAbi, addr), + contract: batching.NewBoundContract(mipsAbi, addr, caller), } } diff --git a/op-service/sources/batching/bound.go b/op-service/sources/batching/bound.go index 33e19c3dfbfa..1eec6ea40110 100644 --- a/op-service/sources/batching/bound.go +++ b/op-service/sources/batching/bound.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" ) @@ -17,14 +18,16 @@ var ( ) type BoundContract struct { - abi *abi.ABI - addr common.Address + abi *abi.ABI + addr common.Address + filter bind.ContractFilterer } -func NewBoundContract(abi *abi.ABI, addr common.Address) *BoundContract { +func NewBoundContract(abi *abi.ABI, addr common.Address, filter bind.ContractFilterer) *BoundContract { return &BoundContract{ - abi: abi, - addr: addr, + abi: abi, + addr: addr, + filter: filter, } } @@ -32,6 +35,10 @@ func (b *BoundContract) Addr() common.Address { return b.addr } +func (b *BoundContract) Abi() *abi.ABI { + return b.abi +} + func (b *BoundContract) Call(method string, args ...interface{}) *ContractCall { return NewContractCall(b.abi, b.addr, method, args...) } diff --git a/op-service/sources/batching/bound_test.go b/op-service/sources/batching/bound_test.go index 201fa2bdf48d..56868d9f4723 100644 --- a/op-service/sources/batching/bound_test.go +++ b/op-service/sources/batching/bound_test.go @@ -19,7 +19,7 @@ func TestDecodeCall(t *testing.T) { validData, err := testAbi.Pack(method, spender, amount) require.NoError(t, err) - contract := NewBoundContract(testAbi, common.Address{0xaa}) + contract := NewBoundContract(testAbi, common.Address{0xaa}, nil) t.Run("TooShort", func(t *testing.T) { _, _, err := contract.DecodeCall([]byte{1, 2, 3}) require.ErrorIs(t, err, ErrUnknownMethod) @@ -60,7 +60,7 @@ func TestDecodeEvent(t *testing.T) { // event Transfer(address indexed from, address indexed to, uint256 amount); event := testAbi.Events["Transfer"] - contract := NewBoundContract(testAbi, common.Address{0xaa}) + contract := NewBoundContract(testAbi, common.Address{0xaa}, nil) t.Run("NoTopics", func(t *testing.T) { log := &types.Log{} _, _, err := contract.DecodeEvent(log) diff --git a/op-service/sources/batching/event_call.go b/op-service/sources/batching/event_call.go new file mode 100644 index 000000000000..b537cfd003df --- /dev/null +++ b/op-service/sources/batching/event_call.go @@ -0,0 +1,39 @@ +package batching + +import ( + "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rpc" +) + +type EventCall struct { + topics [][]common.Hash + to []common.Address +} + +var _ Call = (*EventCall)(nil) + +func NewEventCall(q ethereum.FilterQuery) *EventCall { + return &EventCall{ + topics: q.Topics, + to: q.Addresses, + } +} + +func (b *EventCall) ToBatchElemCreator() (BatchElementCreator, error) { + return func(block rpcblock.Block) (any, rpc.BatchElem) { + out := new([]types.Log) + return out, rpc.BatchElem{ + Method: "eth_getFilterLogs", + Args: []interface{}{b.topics, b.to[0], block.ArgValue()}, + Result: &out, + } + }, nil +} + +func (c *EventCall) HandleResult(result interface{}) (*CallResult, error) { + res := result.(*[]types.Log) + return &CallResult{out: []interface{}{*res}}, nil +} diff --git a/op-service/sources/batching/event_call_test.go b/op-service/sources/batching/event_call_test.go new file mode 100644 index 000000000000..6144e34bd402 --- /dev/null +++ b/op-service/sources/batching/event_call_test.go @@ -0,0 +1,75 @@ +package batching + +import ( + "math/big" + "testing" + + "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" + batchingTest "github.com/ethereum-optimism/optimism/op-service/sources/batching/test" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/require" +) + +func TestEventLogFilter(t *testing.T) { + addr := common.Address{0xbd} + stub := batchingTest.NewRpcStub(t) + owner := []common.Address{{0xaa}} + spender := []common.Address{{0xbb}} + + testAbi, err := batchingTest.ERC20MetaData.GetAbi() + require.NoError(t, err) + name := "Approval" + var ownerRule []interface{} + for _, ownerItem := range owner { + ownerRule = append(ownerRule, ownerItem) + } + var spenderRule []interface{} + for _, spenderItem := range spender { + spenderRule = append(spenderRule, spenderItem) + } + query := [][]interface{}{ownerRule, spenderRule} + query = append([][]interface{}{{testAbi.Events[name].ID}}, query...) + + topics, err := abi.MakeTopics(query...) + require.NoError(t, err) + + txHash := common.Hash{0x11} + block := rpcblock.Latest + require.NoError(t, err) + event := testAbi.Events[name] + inputs := event.Inputs + + amount := big.NewInt(3) + packedData, err := inputs.NonIndexed().Pack(amount) + require.NoError(t, err) + _out := []types.Log{ + { + Address: addr, + Topics: []common.Hash{topics[0][0], topics[1][0], topics[2][0]}, + Data: packedData, + TxHash: txHash, + }, + } + out := make([]interface{}, len(_out)) + for i, r := range _out { + out[i] = r + } + + stub.SetFilterLogResponse(topics, addr, block, _out) + caller := NewMultiCaller(stub, DefaultBatchSize) + + filter, err := batchingTest.NewERC20Filterer(addr, caller) + require.NoError(t, err) + + iterator, err := filter.FilterApproval(nil, owner, spender) + require.NoError(t, err) + + res := iterator.Next() + require.True(t, res, iterator.Error()) + require.Equal(t, _out[0].Address, iterator.Event.Raw.Address) + require.Equal(t, _out[0].Topics, iterator.Event.Raw.Topics) + require.Equal(t, _out[0].Data, iterator.Event.Raw.Data) + require.Equal(t, txHash, iterator.Event.Raw.TxHash) +} diff --git a/op-service/sources/batching/multicall.go b/op-service/sources/batching/multicall.go index 2a02ce774f73..e2d708764960 100644 --- a/op-service/sources/batching/multicall.go +++ b/op-service/sources/batching/multicall.go @@ -2,10 +2,13 @@ package batching import ( "context" + "errors" "fmt" "io" "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rpc" ) @@ -80,3 +83,20 @@ func (m *MultiCaller) Call(ctx context.Context, block rpcblock.Block, calls ...C } return callResults, nil } + +// FilterLogs filters contract logs for past blocks, returning the necessary +// channels to construct a strongly typed bound iterator on top of them. +func (m *MultiCaller) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) { + call := NewEventCall(q) + results, err := m.SingleCall(ctx, rpcblock.ByNumber(q.FromBlock.Uint64()), call) + if err != nil { + return nil, err + } + var out []types.Log + results.GetStruct(0, &out) + return out, nil +} + +func (m *MultiCaller) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) { + return nil, errors.New("unimplemented") +} diff --git a/op-service/sources/batching/test/abi_stub.go b/op-service/sources/batching/test/abi_stub.go index 3c834896a800..5c90b4ff10ad 100644 --- a/op-service/sources/batching/test/abi_stub.go +++ b/op-service/sources/batching/test/abi_stub.go @@ -12,6 +12,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -127,6 +128,7 @@ func (l *AbiBasedRpc) SetError(to common.Address, method string, block rpcblock. err: callErr, }) } + func (l *AbiBasedRpc) SetResponse(to common.Address, method string, block rpcblock.Block, expected []interface{}, output []interface{}) { if expected == nil { expected = []interface{}{} @@ -148,6 +150,26 @@ func (l *AbiBasedRpc) SetResponse(to common.Address, method string, block rpcblo }) } +func (l *AbiBasedRpc) SetFilterLogResponse(topics [][]common.Hash, to common.Address, block rpcblock.Block, output []types.Log) { + if output == nil { + output = []types.Log{} + } + + l.AddExpectedCall(&expectedFilterLogsCall{ + topics: topics, + to: to, + block: block, + outputs: output, + }) +} + +func (l *AbiBasedRpc) SetTxResponse(txHash common.Hash, output []byte) { + if output == nil { + output = []byte{} + } + l.AddExpectedCall(&expectedTxCall{txHash: txHash, outputs: output}) +} + func (l *AbiBasedRpc) VerifyTxCandidate(candidate txmgr.TxCandidate) { require.NotNil(l.t, candidate.To) l.findExpectedCall("eth_call", map[string]any{ diff --git a/op-service/sources/batching/test/event_stub.go b/op-service/sources/batching/test/event_stub.go new file mode 100644 index 000000000000..c09f2f257fd9 --- /dev/null +++ b/op-service/sources/batching/test/event_stub.go @@ -0,0 +1,63 @@ +package test + +import ( + "encoding/json" + "fmt" + "reflect" + "testing" + + "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/require" +) + +type expectedFilterLogsCall struct { + topics [][]common.Hash + to common.Address + block rpcblock.Block + outputs []types.Log + err error +} + +func (c *expectedFilterLogsCall) Matches(rpcMethod string, args ...interface{}) error { + if rpcMethod != "eth_getFilterLogs" { + return fmt.Errorf("expected rpcMethod eth_getFilterLogs but was %v", rpcMethod) + } + + topics := args[0].([][]common.Hash) + + if !reflect.DeepEqual(topics, c.topics) { + return fmt.Errorf("expected topics %v but was %v", c.topics, topics) + } + + to := args[1].(common.Address) + if to != c.to { + return fmt.Errorf("expected contract address %v but was %v", c.topics, topics) + } + return c.err +} + +func (c *expectedFilterLogsCall) Execute(t *testing.T, out interface{}) error { + j, err := json.Marshal((c.outputs)) + require.NoError(t, err) + json.Unmarshal(j, out) + return c.err +} + +func (c *expectedFilterLogsCall) String() string { + return fmt.Sprintf("{to: %v, block: %v, outputs: %v}", c.to, c.block, c.outputs) +} + +func (l *RpcStub) SetFilterLogResponse(topics [][]common.Hash, to common.Address, block rpcblock.Block, output []types.Log) { + if output == nil { + output = []types.Log{} + } + + l.AddExpectedCall(&expectedFilterLogsCall{ + topics: topics, + to: to, + block: block, + outputs: output, + }) +} diff --git a/op-service/sources/batching/test/tx_stub.go b/op-service/sources/batching/test/tx_stub.go new file mode 100644 index 000000000000..b6507104620c --- /dev/null +++ b/op-service/sources/batching/test/tx_stub.go @@ -0,0 +1,42 @@ +package test + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/stretchr/testify/require" +) + +type expectedTxCall struct { + txHash common.Hash + outputs []byte + err error +} + +func (c *expectedTxCall) Matches(rpcMethod string, args ...interface{}) error { + if rpcMethod != "eth_getTransactionByHash" { + return fmt.Errorf("expected rpcMethod eth_getTransactionByHash but was %v", rpcMethod) + } + + txhash := args[0].(common.Hash) + + if txhash != c.txHash { + return fmt.Errorf("expected txHash %v but was %v", c.txHash, txhash) + } + + return c.err +} + +func (c *expectedTxCall) Execute(t *testing.T, out interface{}) error { + j, err := json.Marshal(hexutil.Bytes(c.outputs)) + require.NoError(t, err) + json.Unmarshal(j, out) + return c.err +} + +func (c *expectedTxCall) String() string { + return fmt.Sprintf("{txHash: %v, outputs: %v}", c.txHash, c.outputs) +} diff --git a/op-service/sources/batching/tx_call_test.go b/op-service/sources/batching/tx_call_test.go index af67f6597934..1154266376c9 100644 --- a/op-service/sources/batching/tx_call_test.go +++ b/op-service/sources/batching/tx_call_test.go @@ -2,7 +2,6 @@ package batching import ( "context" - "fmt" "math/big" "testing" @@ -44,6 +43,7 @@ func TestGetTxCalldata(t *testing.T) { require.NoError(t, err) contractCall := NewContractCall(testAbi, addr, "approve", expectedSpender, expectedAmount) packed, err := contractCall.Pack() + require.NoError(t, err) stub := test.NewRpcStub(t) stub.AddExpectedCall(test.NewGetTxCall(txHash, rpcblock.Latest, &packed)) @@ -52,7 +52,6 @@ func TestGetTxCalldata(t *testing.T) { txCall := NewTxCall(testAbi, txHash, "approve") result, err := caller.SingleCall(context.Background(), rpcblock.Latest, txCall) require.NoError(t, err) - fmt.Println() require.Equal(t, expectedSpender, result.GetAddress(0)) require.Equal(t, expectedAmount, result.GetBigInt(1)) } From 3d9e698315c5f907202eda54a93d39761ea2e5a7 Mon Sep 17 00:00:00 2001 From: Po Date: Sat, 28 Sep 2024 03:05:29 +0800 Subject: [PATCH 3/7] Test: decode tx from TxCall --- .../game/fault/contracts/faultdisputegame.go | 32 ++++++++++++------- .../fault/contracts/faultdisputegame_test.go | 19 +++++++++-- op-service/sources/batching/event_call.go | 2 -- op-service/sources/batching/multicall.go | 4 +-- .../sources/batching/test/event_stub.go | 4 +-- op-service/sources/batching/tx_call.go | 26 ++++++++------- op-service/sources/batching/tx_call_test.go | 31 ++++++++++++------ 7 files changed, 76 insertions(+), 42 deletions(-) diff --git a/op-challenger/game/fault/contracts/faultdisputegame.go b/op-challenger/game/fault/contracts/faultdisputegame.go index 5a6a66a2f014..6618b02bd9d1 100644 --- a/op-challenger/game/fault/contracts/faultdisputegame.go +++ b/op-challenger/game/fault/contracts/faultdisputegame.go @@ -55,6 +55,7 @@ var ( methodL2BlockNumberChallenged = "l2BlockNumberChallenged" methodL2BlockNumberChallenger = "l2BlockNumberChallenger" methodChallengeRootL2Block = "challengeRootL2Block" + subClaimField = "_claim" ) var ( @@ -174,10 +175,6 @@ func (f *FaultDisputeGameContractLatest) GetBlockRange(ctx context.Context) (pre return } -func (f *FaultDisputeGameContractLatest) GetContract() *batching.BoundContract { - return f.contract -} - type GameMetadata struct { L1Head common.Hash L2BlockNum uint64 @@ -450,7 +447,6 @@ func (f *FaultDisputeGameContractLatest) GetAllClaims(ctx context.Context, block func (f *FaultDisputeGameContractLatest) GetSubClaims(ctx context.Context, block rpcblock.Block, aggClaim *types.Claim) ([]common.Hash, error) { defer f.metrics.StartContractRequest("GetAllSubClaims")() - // findMoveTransaction filter, err := bindings.NewFaultDisputeGameFilterer(f.contract.Addr(), f.multiCaller) if err != nil { return nil, err @@ -469,16 +465,30 @@ func (f *FaultDisputeGameContractLatest) GetSubClaims(ctx context.Context, block } txHash := moveIter.Event.Raw.TxHash - // todo: replace hardcoded nary, method name - nary := 1 - result, err := f.multiCaller.SingleCall(ctx, rpcblock.Latest, batching.NewTxCall(f.contract.Abi(), txHash, "move")) + // todo: replace hardcoded method name + txCall := batching.NewTxCall(f.contract.Abi(), txHash, "move") + result, err := f.multiCaller.SingleCall(ctx, rpcblock.Latest, txCall) if err != nil { return nil, fmt.Errorf("failed to load claim calldata: %w", err) } + + txn, err := txCall.DecodeToTx(result) + if err != nil { + return nil, fmt.Errorf("failed to decode tx: %w", err) + } + var subClaims []common.Hash - // We should start from 2 du to the signature of move(Claim _disputed, uint256 _challengeIndex, Claim _claim) - for i := 2; i < nary+2; i++ { - subClaims = append(subClaims, result.GetHash(i)) + + if len(txn.BlobHashes()) > 0 { + // todo: fetch Blobs and unpack it into subClaims + } else { + inputMap, err := txCall.UnpackCallData(txn) + if err != nil { + return nil, fmt.Errorf("failed to unpack tx resp: %w", err) + } + // todo: replace claim with nary-subclaims + claim := *abi.ConvertType(inputMap[subClaimField], new([32]byte)).(*[32]byte) + subClaims = append(subClaims, claim) } return subClaims, nil diff --git a/op-challenger/game/fault/contracts/faultdisputegame_test.go b/op-challenger/game/fault/contracts/faultdisputegame_test.go index 863a9480f66a..387b34b84175 100644 --- a/op-challenger/game/fault/contracts/faultdisputegame_test.go +++ b/op-challenger/game/fault/contracts/faultdisputegame_test.go @@ -324,6 +324,7 @@ func TestGetAllClaims(t *testing.T) { func TestGetSubClaims(t *testing.T) { for _, version := range versions { + // todo: backward and forward support if version.Is("1.2.0") { version := version t.Run(version.version, func(t *testing.T) { @@ -344,7 +345,7 @@ func TestGetSubClaims(t *testing.T) { block := rpcblock.ByNumber(42) stubRpc.SetResponse(fdgAddr, methodClaimCount, block, nil, []interface{}{big.NewInt(int64(len(expectedClaims)))}) - name := "Move" + eventName := "Move" fdgAbi := version.loadAbi() var challgenIndex []interface{} @@ -354,7 +355,7 @@ func TestGetSubClaims(t *testing.T) { query := [][]interface{}{challgenIndex, claim, address} txHash := common.Hash{0xff} - query = append([][]interface{}{{fdgAbi.Events[name].ID}}, query...) + query = append([][]interface{}{{fdgAbi.Events[eventName].ID}}, query...) topics, err := abi.MakeTopics(query...) var queryTopics []common.Hash @@ -373,7 +374,19 @@ func TestGetSubClaims(t *testing.T) { stubRpc.SetFilterLogResponse(topics, fdgAddr, block, out) contractCall := batching.NewContractCall(fdgAbi, fdgAddr, "move", claim0.ClaimData.Value, challgenIndex[0], claim0.ClaimData.Value, true) - packed, err := contractCall.Pack() + inputData, err := contractCall.Pack() + require.NoError(t, err) + + tx := coreTypes.NewTx(&coreTypes.LegacyTx{ + Nonce: 0, + GasPrice: big.NewInt(11111), + Gas: 1111, + To: &claim0.Claimant, + Value: big.NewInt(111), + Data: inputData, + }) + require.NoError(t, err) + packed, err := tx.MarshalBinary() require.NoError(t, err) stubRpc.SetTxResponse(txHash, packed) diff --git a/op-service/sources/batching/event_call.go b/op-service/sources/batching/event_call.go index b537cfd003df..01e09b9fc359 100644 --- a/op-service/sources/batching/event_call.go +++ b/op-service/sources/batching/event_call.go @@ -13,8 +13,6 @@ type EventCall struct { to []common.Address } -var _ Call = (*EventCall)(nil) - func NewEventCall(q ethereum.FilterQuery) *EventCall { return &EventCall{ topics: q.Topics, diff --git a/op-service/sources/batching/multicall.go b/op-service/sources/batching/multicall.go index e2d708764960..88b500791c7a 100644 --- a/op-service/sources/batching/multicall.go +++ b/op-service/sources/batching/multicall.go @@ -84,8 +84,7 @@ func (m *MultiCaller) Call(ctx context.Context, block rpcblock.Block, calls ...C return callResults, nil } -// FilterLogs filters contract logs for past blocks, returning the necessary -// channels to construct a strongly typed bound iterator on top of them. +// implment LogFilterer interface func (m *MultiCaller) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) { call := NewEventCall(q) results, err := m.SingleCall(ctx, rpcblock.ByNumber(q.FromBlock.Uint64()), call) @@ -97,6 +96,7 @@ func (m *MultiCaller) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([ return out, nil } +// implment LogFilterer interface func (m *MultiCaller) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) { return nil, errors.New("unimplemented") } diff --git a/op-service/sources/batching/test/event_stub.go b/op-service/sources/batching/test/event_stub.go index c09f2f257fd9..970ec7ced092 100644 --- a/op-service/sources/batching/test/event_stub.go +++ b/op-service/sources/batching/test/event_stub.go @@ -33,13 +33,13 @@ func (c *expectedFilterLogsCall) Matches(rpcMethod string, args ...interface{}) to := args[1].(common.Address) if to != c.to { - return fmt.Errorf("expected contract address %v but was %v", c.topics, topics) + return fmt.Errorf("expected contract address %v but was %v", c.to, to) } return c.err } func (c *expectedFilterLogsCall) Execute(t *testing.T, out interface{}) error { - j, err := json.Marshal((c.outputs)) + j, err := json.Marshal(c.outputs) require.NoError(t, err) json.Unmarshal(j, out) return c.err diff --git a/op-service/sources/batching/tx_call.go b/op-service/sources/batching/tx_call.go index 4b4ad02fb4ff..b1a4b60bcdf9 100644 --- a/op-service/sources/batching/tx_call.go +++ b/op-service/sources/batching/tx_call.go @@ -1,12 +1,11 @@ package batching import ( - "fmt" - "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" "github.com/ethereum/go-ethereum/accounts/abi" "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/rpc" ) @@ -16,8 +15,6 @@ type TxCall struct { Method string } -var _ Call = (*TxCall)(nil) - func NewTxCall(abi *abi.ABI, txhash common.Hash, method string) *TxCall { return &TxCall{ Abi: abi, @@ -38,8 +35,8 @@ func (b *TxCall) ToBatchElemCreator() (BatchElementCreator, error) { } func (c *TxCall) HandleResult(result interface{}) (*CallResult, error) { - out, err := c.Unpack(*result.(*hexutil.Bytes)) - return out, err + res := result.(*hexutil.Bytes) + return &CallResult{out: []interface{}{*res}}, nil } func (c *TxCall) DecodeTxParams(data []byte) (map[string]interface{}, error) { @@ -54,12 +51,17 @@ func (c *TxCall) DecodeTxParams(data []byte) (map[string]interface{}, error) { return v, nil } -func (c *TxCall) Unpack(hex hexutil.Bytes) (*CallResult, error) { - inputs := c.Abi.Methods[c.Method].Inputs - - out, err := inputs.UnpackValues(hex[4:]) +func (c *TxCall) DecodeToTx(res *CallResult) (*types.Transaction, error) { + txn := new(types.Transaction) + hex := res.out[0].(hexutil.Bytes) + err := txn.UnmarshalBinary(hex) if err != nil { - return nil, fmt.Errorf("failed to unpack inputs: %w", err) + return nil, err } - return &CallResult{out: out}, nil + return txn, nil +} + +func (c *TxCall) UnpackCallData(txn *types.Transaction) (map[string]interface{}, error) { + input := txn.Data() + return c.DecodeTxParams(input) } diff --git a/op-service/sources/batching/tx_call_test.go b/op-service/sources/batching/tx_call_test.go index 1154266376c9..a3ba4eebe4f8 100644 --- a/op-service/sources/batching/tx_call_test.go +++ b/op-service/sources/batching/tx_call_test.go @@ -8,10 +8,11 @@ import ( "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" "github.com/ethereum-optimism/optimism/op-service/sources/batching/test" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" "github.com/stretchr/testify/require" ) -func TestTxCall_ToCallArgs(t *testing.T) { +func TestDecodeTxCall(t *testing.T) { addr := common.Address{0xbd} testAbi, err := test.ERC20MetaData.GetAbi() require.NoError(t, err) @@ -26,14 +27,9 @@ func TestTxCall_ToCallArgs(t *testing.T) { require.NoError(t, err) require.Equal(t, expectedAmount, unpackedMap["amount"]) require.Equal(t, expectedSpender, unpackedMap["spender"]) - - unpacked, err := call.Unpack(packed) - require.NoError(t, err) - require.Equal(t, expectedSpender, unpacked.GetAddress(0)) - require.Equal(t, expectedAmount, unpacked.GetBigInt(1)) } -func TestGetTxCalldata(t *testing.T) { +func TestUnpackTxCalldata(t *testing.T) { expectedSpender := common.Address{0xcc} expectedAmount := big.NewInt(1234444) txHash := common.Hash{0x11} @@ -42,7 +38,17 @@ func TestGetTxCalldata(t *testing.T) { testAbi, err := test.ERC20MetaData.GetAbi() require.NoError(t, err) contractCall := NewContractCall(testAbi, addr, "approve", expectedSpender, expectedAmount) - packed, err := contractCall.Pack() + inputData, err := contractCall.Pack() + tx := types.NewTx(&types.LegacyTx{ + Nonce: 0, + GasPrice: big.NewInt(11111), + Gas: 1111, + To: &addr, + Value: big.NewInt(111), + Data: inputData, + }) + require.NoError(t, err) + packed, err := tx.MarshalBinary() require.NoError(t, err) stub := test.NewRpcStub(t) @@ -52,6 +58,11 @@ func TestGetTxCalldata(t *testing.T) { txCall := NewTxCall(testAbi, txHash, "approve") result, err := caller.SingleCall(context.Background(), rpcblock.Latest, txCall) require.NoError(t, err) - require.Equal(t, expectedSpender, result.GetAddress(0)) - require.Equal(t, expectedAmount, result.GetBigInt(1)) + + decodedTx, err := txCall.DecodeToTx(result) + require.NoError(t, err) + unpackedMap, err := txCall.UnpackCallData(decodedTx) + require.NoError(t, err) + require.Equal(t, expectedSpender, unpackedMap["spender"]) + require.Equal(t, expectedAmount, unpackedMap["amount"]) } From 8db70b8e72789209b2371a1bd43dac1b71d56c83 Mon Sep 17 00:00:00 2001 From: Po Date: Wed, 9 Oct 2024 15:05:31 +0800 Subject: [PATCH 4/7] address comments --- .../game/fault/contracts/faultdisputegame.go | 3 ++- op-service/sources/batching/tx_call.go | 16 ++++++++-------- op-service/sources/batching/tx_call_test.go | 6 +++--- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/op-challenger/game/fault/contracts/faultdisputegame.go b/op-challenger/game/fault/contracts/faultdisputegame.go index 6618b02bd9d1..7af1ee2d4a0f 100644 --- a/op-challenger/game/fault/contracts/faultdisputegame.go +++ b/op-challenger/game/fault/contracts/faultdisputegame.go @@ -466,7 +466,7 @@ func (f *FaultDisputeGameContractLatest) GetSubClaims(ctx context.Context, block txHash := moveIter.Event.Raw.TxHash // todo: replace hardcoded method name - txCall := batching.NewTxCall(f.contract.Abi(), txHash, "move") + txCall := batching.NewTxGetByHash(f.contract.Abi(), txHash, "move") result, err := f.multiCaller.SingleCall(ctx, rpcblock.Latest, txCall) if err != nil { return nil, fmt.Errorf("failed to load claim calldata: %w", err) @@ -481,6 +481,7 @@ func (f *FaultDisputeGameContractLatest) GetSubClaims(ctx context.Context, block if len(txn.BlobHashes()) > 0 { // todo: fetch Blobs and unpack it into subClaims + return nil, fmt.Errorf("blob tx hasn't been supported") } else { inputMap, err := txCall.UnpackCallData(txn) if err != nil { diff --git a/op-service/sources/batching/tx_call.go b/op-service/sources/batching/tx_call.go index b1a4b60bcdf9..9f5487a68f4e 100644 --- a/op-service/sources/batching/tx_call.go +++ b/op-service/sources/batching/tx_call.go @@ -9,21 +9,21 @@ import ( "github.com/ethereum/go-ethereum/rpc" ) -type TxCall struct { +type TxGetByHashCall struct { Abi *abi.ABI TxHash common.Hash Method string } -func NewTxCall(abi *abi.ABI, txhash common.Hash, method string) *TxCall { - return &TxCall{ +func NewTxGetByHash(abi *abi.ABI, txhash common.Hash, method string) *TxGetByHashCall { + return &TxGetByHashCall{ Abi: abi, TxHash: txhash, Method: method, } } -func (b *TxCall) ToBatchElemCreator() (BatchElementCreator, error) { +func (b *TxGetByHashCall) ToBatchElemCreator() (BatchElementCreator, error) { return func(block rpcblock.Block) (any, rpc.BatchElem) { out := new(hexutil.Bytes) return out, rpc.BatchElem{ @@ -34,12 +34,12 @@ func (b *TxCall) ToBatchElemCreator() (BatchElementCreator, error) { }, nil } -func (c *TxCall) HandleResult(result interface{}) (*CallResult, error) { +func (c *TxGetByHashCall) HandleResult(result interface{}) (*CallResult, error) { res := result.(*hexutil.Bytes) return &CallResult{out: []interface{}{*res}}, nil } -func (c *TxCall) DecodeTxParams(data []byte) (map[string]interface{}, error) { +func (c *TxGetByHashCall) DecodeTxParams(data []byte) (map[string]interface{}, error) { m, err := c.Abi.MethodById(data[:4]) v := map[string]interface{}{} if err != nil { @@ -51,7 +51,7 @@ func (c *TxCall) DecodeTxParams(data []byte) (map[string]interface{}, error) { return v, nil } -func (c *TxCall) DecodeToTx(res *CallResult) (*types.Transaction, error) { +func (c *TxGetByHashCall) DecodeToTx(res *CallResult) (*types.Transaction, error) { txn := new(types.Transaction) hex := res.out[0].(hexutil.Bytes) err := txn.UnmarshalBinary(hex) @@ -61,7 +61,7 @@ func (c *TxCall) DecodeToTx(res *CallResult) (*types.Transaction, error) { return txn, nil } -func (c *TxCall) UnpackCallData(txn *types.Transaction) (map[string]interface{}, error) { +func (c *TxGetByHashCall) UnpackCallData(txn *types.Transaction) (map[string]interface{}, error) { input := txn.Data() return c.DecodeTxParams(input) } diff --git a/op-service/sources/batching/tx_call_test.go b/op-service/sources/batching/tx_call_test.go index a3ba4eebe4f8..2b4440df33e4 100644 --- a/op-service/sources/batching/tx_call_test.go +++ b/op-service/sources/batching/tx_call_test.go @@ -12,11 +12,11 @@ import ( "github.com/stretchr/testify/require" ) -func TestDecodeTxCall(t *testing.T) { +func TestDecodeTxGetByHash(t *testing.T) { addr := common.Address{0xbd} testAbi, err := test.ERC20MetaData.GetAbi() require.NoError(t, err) - call := NewTxCall(testAbi, common.Hash{0xcc}, "approve") + call := NewTxGetByHash(testAbi, common.Hash{0xcc}, "approve") expectedAmount := big.NewInt(1234444) expectedSpender := common.Address{0xcc} contractCall := NewContractCall(testAbi, addr, "approve", expectedSpender, expectedAmount) @@ -55,7 +55,7 @@ func TestUnpackTxCalldata(t *testing.T) { stub.AddExpectedCall(test.NewGetTxCall(txHash, rpcblock.Latest, &packed)) caller := NewMultiCaller(stub, DefaultBatchSize) - txCall := NewTxCall(testAbi, txHash, "approve") + txCall := NewTxGetByHash(testAbi, txHash, "approve") result, err := caller.SingleCall(context.Background(), rpcblock.Latest, txCall) require.NoError(t, err) From 94a61338fb885628c390d579d14413d18d7556ed Mon Sep 17 00:00:00 2001 From: Po Date: Sat, 26 Oct 2024 15:40:10 +0800 Subject: [PATCH 5/7] chore(op-challenger): address comments(remove unused filter) --- .../game/fault/contracts/delayed_weth.go | 2 +- .../game/fault/contracts/faultdisputegame.go | 60 ++--------------- .../game/fault/contracts/faultdisputegame2.go | 65 +++++++++++++++++++ .../game/fault/contracts/gamefactory.go | 2 +- op-challenger/game/fault/contracts/oracle.go | 2 +- op-challenger/game/fault/contracts/vm.go | 2 +- op-service/sources/batching/bound.go | 13 ++-- op-service/sources/batching/bound_test.go | 4 +- 8 files changed, 80 insertions(+), 70 deletions(-) create mode 100644 op-challenger/game/fault/contracts/faultdisputegame2.go diff --git a/op-challenger/game/fault/contracts/delayed_weth.go b/op-challenger/game/fault/contracts/delayed_weth.go index 36d2bb692e42..f093ecb3ff3b 100644 --- a/op-challenger/game/fault/contracts/delayed_weth.go +++ b/op-challenger/game/fault/contracts/delayed_weth.go @@ -32,7 +32,7 @@ func NewDelayedWETHContract(metrics metrics.ContractMetricer, addr common.Addres return &DelayedWETHContract{ metrics: metrics, multiCaller: caller, - contract: batching.NewBoundContract(contractAbi, addr, caller), + contract: batching.NewBoundContract(contractAbi, addr), } } diff --git a/op-challenger/game/fault/contracts/faultdisputegame.go b/op-challenger/game/fault/contracts/faultdisputegame.go index 7af1ee2d4a0f..61e3a09674f9 100644 --- a/op-challenger/game/fault/contracts/faultdisputegame.go +++ b/op-challenger/game/fault/contracts/faultdisputegame.go @@ -13,7 +13,6 @@ import ( "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts/metrics" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" - "github.com/ethereum-optimism/optimism/op-e2e/bindings" "github.com/ethereum-optimism/optimism/op-service/sources/batching" "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" "github.com/ethereum-optimism/optimism/op-service/txmgr" @@ -98,7 +97,7 @@ func NewFaultDisputeGameContract(ctx context.Context, metrics metrics.ContractMe FaultDisputeGameContractLatest: FaultDisputeGameContractLatest{ metrics: metrics, multiCaller: caller, - contract: batching.NewBoundContract(legacyAbi, addr, caller), + contract: batching.NewBoundContract(legacyAbi, addr), }, }, nil } else if strings.HasPrefix(version, "0.18.") || strings.HasPrefix(version, "1.0.") { @@ -108,7 +107,7 @@ func NewFaultDisputeGameContract(ctx context.Context, metrics metrics.ContractMe FaultDisputeGameContractLatest: FaultDisputeGameContractLatest{ metrics: metrics, multiCaller: caller, - contract: batching.NewBoundContract(legacyAbi, addr, caller), + contract: batching.NewBoundContract(legacyAbi, addr), }, }, nil } else if strings.HasPrefix(version, "1.1.") { @@ -118,14 +117,14 @@ func NewFaultDisputeGameContract(ctx context.Context, metrics metrics.ContractMe FaultDisputeGameContractLatest: FaultDisputeGameContractLatest{ metrics: metrics, multiCaller: caller, - contract: batching.NewBoundContract(legacyAbi, addr, caller), + contract: batching.NewBoundContract(legacyAbi, addr), }, }, nil } else { return &FaultDisputeGameContractLatest{ metrics: metrics, multiCaller: caller, - contract: batching.NewBoundContract(contractAbi, addr, caller), + contract: batching.NewBoundContract(contractAbi, addr), }, nil } } @@ -444,57 +443,6 @@ func (f *FaultDisputeGameContractLatest) GetAllClaims(ctx context.Context, block return claims, nil } -func (f *FaultDisputeGameContractLatest) GetSubClaims(ctx context.Context, block rpcblock.Block, aggClaim *types.Claim) ([]common.Hash, error) { - defer f.metrics.StartContractRequest("GetAllSubClaims")() - - filter, err := bindings.NewFaultDisputeGameFilterer(f.contract.Addr(), f.multiCaller) - if err != nil { - return nil, err - } - - parentIndex := [...]*big.Int{big.NewInt(int64(aggClaim.ParentContractIndex))} - claim := [...][32]byte{aggClaim.ClaimData.ValueBytes()} - claimant := [...]common.Address{aggClaim.Claimant} - moveIter, err := filter.FilterMove(nil, parentIndex[:], claim[:], claimant[:]) - if err != nil { - return nil, fmt.Errorf("failed to filter move event log: %w", err) - } - ok := moveIter.Next() - if !ok { - return nil, fmt.Errorf("failed to get move event log: %w", moveIter.Error()) - } - txHash := moveIter.Event.Raw.TxHash - - // todo: replace hardcoded method name - txCall := batching.NewTxGetByHash(f.contract.Abi(), txHash, "move") - result, err := f.multiCaller.SingleCall(ctx, rpcblock.Latest, txCall) - if err != nil { - return nil, fmt.Errorf("failed to load claim calldata: %w", err) - } - - txn, err := txCall.DecodeToTx(result) - if err != nil { - return nil, fmt.Errorf("failed to decode tx: %w", err) - } - - var subClaims []common.Hash - - if len(txn.BlobHashes()) > 0 { - // todo: fetch Blobs and unpack it into subClaims - return nil, fmt.Errorf("blob tx hasn't been supported") - } else { - inputMap, err := txCall.UnpackCallData(txn) - if err != nil { - return nil, fmt.Errorf("failed to unpack tx resp: %w", err) - } - // todo: replace claim with nary-subclaims - claim := *abi.ConvertType(inputMap[subClaimField], new([32]byte)).(*[32]byte) - subClaims = append(subClaims, claim) - } - - return subClaims, nil -} - func (f *FaultDisputeGameContractLatest) IsResolved(ctx context.Context, block rpcblock.Block, claims ...types.Claim) ([]bool, error) { defer f.metrics.StartContractRequest("IsResolved")() calls := make([]batching.Call, 0, len(claims)) diff --git a/op-challenger/game/fault/contracts/faultdisputegame2.go b/op-challenger/game/fault/contracts/faultdisputegame2.go new file mode 100644 index 000000000000..bad905dd24d6 --- /dev/null +++ b/op-challenger/game/fault/contracts/faultdisputegame2.go @@ -0,0 +1,65 @@ +package contracts + +import ( + "context" + "fmt" + "math/big" + + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + "github.com/ethereum-optimism/optimism/op-e2e/bindings" + "github.com/ethereum-optimism/optimism/op-service/sources/batching" + "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" +) + +func (f *FaultDisputeGameContractLatest) GetSubClaims(ctx context.Context, block rpcblock.Block, aggClaim *types.Claim) ([]common.Hash, error) { + defer f.metrics.StartContractRequest("GetAllSubClaims")() + + filter, err := bindings.NewFaultDisputeGameFilterer(f.contract.Addr(), f.multiCaller) + if err != nil { + return nil, err + } + + parentIndex := [...]*big.Int{big.NewInt(int64(aggClaim.ParentContractIndex))} + claim := [...][32]byte{aggClaim.ClaimData.ValueBytes()} + claimant := [...]common.Address{aggClaim.Claimant} + moveIter, err := filter.FilterMove(nil, parentIndex[:], claim[:], claimant[:]) + if err != nil { + return nil, fmt.Errorf("failed to filter move event log: %w", err) + } + ok := moveIter.Next() + if !ok { + return nil, fmt.Errorf("failed to get move event log: %w", moveIter.Error()) + } + txHash := moveIter.Event.Raw.TxHash + + // todo: replace hardcoded method name + txCall := batching.NewTxGetByHash(f.contract.Abi(), txHash, "move") + result, err := f.multiCaller.SingleCall(ctx, rpcblock.Latest, txCall) + if err != nil { + return nil, fmt.Errorf("failed to load claim calldata: %w", err) + } + + txn, err := txCall.DecodeToTx(result) + if err != nil { + return nil, fmt.Errorf("failed to decode tx: %w", err) + } + + var subClaims []common.Hash + + if len(txn.BlobHashes()) > 0 { + // todo: fetch Blobs and unpack it into subClaims + return nil, fmt.Errorf("blob tx hasn't been supported") + } else { + inputMap, err := txCall.UnpackCallData(txn) + if err != nil { + return nil, fmt.Errorf("failed to unpack tx resp: %w", err) + } + // todo: replace claim with nary-subclaims + claim := *abi.ConvertType(inputMap[subClaimField], new([32]byte)).(*[32]byte) + subClaims = append(subClaims, claim) + } + + return subClaims, nil +} diff --git a/op-challenger/game/fault/contracts/gamefactory.go b/op-challenger/game/fault/contracts/gamefactory.go index 6f7b97a9ae1c..d67a4688c422 100644 --- a/op-challenger/game/fault/contracts/gamefactory.go +++ b/op-challenger/game/fault/contracts/gamefactory.go @@ -44,7 +44,7 @@ func NewDisputeGameFactoryContract(m metrics.ContractMetricer, addr common.Addre return &DisputeGameFactoryContract{ metrics: m, multiCaller: caller, - contract: batching.NewBoundContract(factoryAbi, addr, caller), + contract: batching.NewBoundContract(factoryAbi, addr), abi: factoryAbi, } } diff --git a/op-challenger/game/fault/contracts/oracle.go b/op-challenger/game/fault/contracts/oracle.go index 5bf5ddc65d58..be2520bef8b5 100644 --- a/op-challenger/game/fault/contracts/oracle.go +++ b/op-challenger/game/fault/contracts/oracle.go @@ -89,7 +89,7 @@ func NewPreimageOracleContract(addr common.Address, caller *batching.MultiCaller return &PreimageOracleContract{ addr: addr, multiCaller: caller, - contract: batching.NewBoundContract(oracleAbi, addr, caller), + contract: batching.NewBoundContract(oracleAbi, addr), } } diff --git a/op-challenger/game/fault/contracts/vm.go b/op-challenger/game/fault/contracts/vm.go index 8b50a5eff512..1d1e22632cfe 100644 --- a/op-challenger/game/fault/contracts/vm.go +++ b/op-challenger/game/fault/contracts/vm.go @@ -25,7 +25,7 @@ func NewVMContract(addr common.Address, caller *batching.MultiCaller) *VMContrac return &VMContract{ multiCaller: caller, - contract: batching.NewBoundContract(mipsAbi, addr, caller), + contract: batching.NewBoundContract(mipsAbi, addr), } } diff --git a/op-service/sources/batching/bound.go b/op-service/sources/batching/bound.go index 1eec6ea40110..e3aff050f130 100644 --- a/op-service/sources/batching/bound.go +++ b/op-service/sources/batching/bound.go @@ -5,7 +5,6 @@ import ( "fmt" "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" ) @@ -18,16 +17,14 @@ var ( ) type BoundContract struct { - abi *abi.ABI - addr common.Address - filter bind.ContractFilterer + abi *abi.ABI + addr common.Address } -func NewBoundContract(abi *abi.ABI, addr common.Address, filter bind.ContractFilterer) *BoundContract { +func NewBoundContract(abi *abi.ABI, addr common.Address) *BoundContract { return &BoundContract{ - abi: abi, - addr: addr, - filter: filter, + abi: abi, + addr: addr, } } diff --git a/op-service/sources/batching/bound_test.go b/op-service/sources/batching/bound_test.go index 56868d9f4723..201fa2bdf48d 100644 --- a/op-service/sources/batching/bound_test.go +++ b/op-service/sources/batching/bound_test.go @@ -19,7 +19,7 @@ func TestDecodeCall(t *testing.T) { validData, err := testAbi.Pack(method, spender, amount) require.NoError(t, err) - contract := NewBoundContract(testAbi, common.Address{0xaa}, nil) + contract := NewBoundContract(testAbi, common.Address{0xaa}) t.Run("TooShort", func(t *testing.T) { _, _, err := contract.DecodeCall([]byte{1, 2, 3}) require.ErrorIs(t, err, ErrUnknownMethod) @@ -60,7 +60,7 @@ func TestDecodeEvent(t *testing.T) { // event Transfer(address indexed from, address indexed to, uint256 amount); event := testAbi.Events["Transfer"] - contract := NewBoundContract(testAbi, common.Address{0xaa}, nil) + contract := NewBoundContract(testAbi, common.Address{0xaa}) t.Run("NoTopics", func(t *testing.T) { log := &types.Log{} _, _, err := contract.DecodeEvent(log) From 8d03dda9a9ba5afbfa591672bbb5a80bbd5118cb Mon Sep 17 00:00:00 2001 From: Po Date: Sun, 27 Oct 2024 17:39:10 +0800 Subject: [PATCH 6/7] chore(op-challenger): address comment & cosmetic --- .../game/fault/contracts/faultdisputegame_test.go | 3 +-- op-service/sources/batching/test/abi_stub.go | 4 ++-- op-service/sources/batching/test/event_stub.go | 2 +- op-service/sources/batching/test/tx_stub.go | 10 +++++----- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/op-challenger/game/fault/contracts/faultdisputegame_test.go b/op-challenger/game/fault/contracts/faultdisputegame_test.go index 387b34b84175..877ff31820cf 100644 --- a/op-challenger/game/fault/contracts/faultdisputegame_test.go +++ b/op-challenger/game/fault/contracts/faultdisputegame_test.go @@ -385,10 +385,9 @@ func TestGetSubClaims(t *testing.T) { Value: big.NewInt(111), Data: inputData, }) - require.NoError(t, err) packed, err := tx.MarshalBinary() require.NoError(t, err) - stubRpc.SetTxResponse(txHash, packed) + stubRpc.SetGetTxByHashResponse(txHash, packed) claims, err := game.GetSubClaims(context.Background(), block, &claim0) require.NoError(t, err) diff --git a/op-service/sources/batching/test/abi_stub.go b/op-service/sources/batching/test/abi_stub.go index 5c90b4ff10ad..2a67fe2738a7 100644 --- a/op-service/sources/batching/test/abi_stub.go +++ b/op-service/sources/batching/test/abi_stub.go @@ -163,11 +163,11 @@ func (l *AbiBasedRpc) SetFilterLogResponse(topics [][]common.Hash, to common.Add }) } -func (l *AbiBasedRpc) SetTxResponse(txHash common.Hash, output []byte) { +func (l *AbiBasedRpc) SetGetTxByHashResponse(txHash common.Hash, output []byte) { if output == nil { output = []byte{} } - l.AddExpectedCall(&expectedTxCall{txHash: txHash, outputs: output}) + l.AddExpectedCall(&expectedGetTxByHashCall{txHash: txHash, outputs: output}) } func (l *AbiBasedRpc) VerifyTxCandidate(candidate txmgr.TxCandidate) { diff --git a/op-service/sources/batching/test/event_stub.go b/op-service/sources/batching/test/event_stub.go index 970ec7ced092..4c08284dba2b 100644 --- a/op-service/sources/batching/test/event_stub.go +++ b/op-service/sources/batching/test/event_stub.go @@ -41,7 +41,7 @@ func (c *expectedFilterLogsCall) Matches(rpcMethod string, args ...interface{}) func (c *expectedFilterLogsCall) Execute(t *testing.T, out interface{}) error { j, err := json.Marshal(c.outputs) require.NoError(t, err) - json.Unmarshal(j, out) + require.NoError(t, json.Unmarshal(j, out)) return c.err } diff --git a/op-service/sources/batching/test/tx_stub.go b/op-service/sources/batching/test/tx_stub.go index b6507104620c..a513a215e68f 100644 --- a/op-service/sources/batching/test/tx_stub.go +++ b/op-service/sources/batching/test/tx_stub.go @@ -10,13 +10,13 @@ import ( "github.com/stretchr/testify/require" ) -type expectedTxCall struct { +type expectedGetTxByHashCall struct { txHash common.Hash outputs []byte err error } -func (c *expectedTxCall) Matches(rpcMethod string, args ...interface{}) error { +func (c *expectedGetTxByHashCall) Matches(rpcMethod string, args ...interface{}) error { if rpcMethod != "eth_getTransactionByHash" { return fmt.Errorf("expected rpcMethod eth_getTransactionByHash but was %v", rpcMethod) } @@ -30,13 +30,13 @@ func (c *expectedTxCall) Matches(rpcMethod string, args ...interface{}) error { return c.err } -func (c *expectedTxCall) Execute(t *testing.T, out interface{}) error { +func (c *expectedGetTxByHashCall) Execute(t *testing.T, out interface{}) error { j, err := json.Marshal(hexutil.Bytes(c.outputs)) require.NoError(t, err) - json.Unmarshal(j, out) + require.NoError(t, json.Unmarshal(j, out)) return c.err } -func (c *expectedTxCall) String() string { +func (c *expectedGetTxByHashCall) String() string { return fmt.Sprintf("{txHash: %v, outputs: %v}", c.txHash, c.outputs) } From 0fbfd260ebfd7cbb03f110ccf8a7b9a5c2f8425b Mon Sep 17 00:00:00 2001 From: Po Date: Sun, 27 Oct 2024 22:08:34 +0800 Subject: [PATCH 7/7] op-challenger: address comments --- .../game/fault/contracts/faultdisputegame2.go | 24 ++++++++++---- op-service/sources/batching/call.go | 5 +++ .../sources/batching/test/event_stub.go | 5 ++- op-service/sources/batching/test/tx_stub.go | 5 ++- op-service/sources/batching/tx_call.go | 33 +++++++++---------- op-service/sources/batching/tx_call_test.go | 22 ++----------- 6 files changed, 47 insertions(+), 47 deletions(-) diff --git a/op-challenger/game/fault/contracts/faultdisputegame2.go b/op-challenger/game/fault/contracts/faultdisputegame2.go index bad905dd24d6..e447eba9e615 100644 --- a/op-challenger/game/fault/contracts/faultdisputegame2.go +++ b/op-challenger/game/fault/contracts/faultdisputegame2.go @@ -10,7 +10,9 @@ import ( "github.com/ethereum-optimism/optimism/op-service/sources/batching" "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rpc" ) func (f *FaultDisputeGameContractLatest) GetSubClaims(ctx context.Context, block rpcblock.Block, aggClaim *types.Claim) ([]common.Hash, error) { @@ -24,7 +26,18 @@ func (f *FaultDisputeGameContractLatest) GetSubClaims(ctx context.Context, block parentIndex := [...]*big.Int{big.NewInt(int64(aggClaim.ParentContractIndex))} claim := [...][32]byte{aggClaim.ClaimData.ValueBytes()} claimant := [...]common.Address{aggClaim.Claimant} - moveIter, err := filter.FilterMove(nil, parentIndex[:], claim[:], claimant[:]) + var end *uint64 + if block.ArgValue() == rpcblock.Latest { + end = nil + } else { + blockNumber, ok := block.ArgValue().(rpc.BlockNumber) + if !ok { + return nil, fmt.Errorf("block number is not lastest or int64") + } + blockNumberU64 := uint64(blockNumber) + end = &blockNumberU64 + } + moveIter, err := filter.FilterMove(&bind.FilterOpts{End: end, Context: ctx}, parentIndex[:], claim[:], claimant[:]) if err != nil { return nil, fmt.Errorf("failed to filter move event log: %w", err) } @@ -36,15 +49,12 @@ func (f *FaultDisputeGameContractLatest) GetSubClaims(ctx context.Context, block // todo: replace hardcoded method name txCall := batching.NewTxGetByHash(f.contract.Abi(), txHash, "move") - result, err := f.multiCaller.SingleCall(ctx, rpcblock.Latest, txCall) + result, err := f.multiCaller.SingleCall(ctx, block, txCall) if err != nil { return nil, fmt.Errorf("failed to load claim calldata: %w", err) } - txn, err := txCall.DecodeToTx(result) - if err != nil { - return nil, fmt.Errorf("failed to decode tx: %w", err) - } + txn := result.GetTx() var subClaims []common.Hash @@ -52,7 +62,7 @@ func (f *FaultDisputeGameContractLatest) GetSubClaims(ctx context.Context, block // todo: fetch Blobs and unpack it into subClaims return nil, fmt.Errorf("blob tx hasn't been supported") } else { - inputMap, err := txCall.UnpackCallData(txn) + inputMap, err := txCall.UnpackCallData(&txn) if err != nil { return nil, fmt.Errorf("failed to unpack tx resp: %w", err) } diff --git a/op-service/sources/batching/call.go b/op-service/sources/batching/call.go index db4563b64258..6784d8ba04cb 100644 --- a/op-service/sources/batching/call.go +++ b/op-service/sources/batching/call.go @@ -6,6 +6,7 @@ import ( "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rpc" ) @@ -67,3 +68,7 @@ func (c *CallResult) GetBytes32Slice(i int) [][32]byte { func (c *CallResult) GetString(i int) string { return *abi.ConvertType(c.out[i], new(string)).(*string) } + +func (c *CallResult) GetTx() types.Transaction { + return *abi.ConvertType(c.out[0], new(types.Transaction)).(*types.Transaction) +} diff --git a/op-service/sources/batching/test/event_stub.go b/op-service/sources/batching/test/event_stub.go index 4c08284dba2b..6b46d385f819 100644 --- a/op-service/sources/batching/test/event_stub.go +++ b/op-service/sources/batching/test/event_stub.go @@ -25,7 +25,10 @@ func (c *expectedFilterLogsCall) Matches(rpcMethod string, args ...interface{}) return fmt.Errorf("expected rpcMethod eth_getFilterLogs but was %v", rpcMethod) } - topics := args[0].([][]common.Hash) + topics, ok := args[0].([][]common.Hash) + if !ok { + return fmt.Errorf("arg 0 is not [][]common.Hash") + } if !reflect.DeepEqual(topics, c.topics) { return fmt.Errorf("expected topics %v but was %v", c.topics, topics) diff --git a/op-service/sources/batching/test/tx_stub.go b/op-service/sources/batching/test/tx_stub.go index a513a215e68f..4b14a7592781 100644 --- a/op-service/sources/batching/test/tx_stub.go +++ b/op-service/sources/batching/test/tx_stub.go @@ -21,7 +21,10 @@ func (c *expectedGetTxByHashCall) Matches(rpcMethod string, args ...interface{}) return fmt.Errorf("expected rpcMethod eth_getTransactionByHash but was %v", rpcMethod) } - txhash := args[0].(common.Hash) + txhash, ok := args[0].(common.Hash) + if !ok { + return fmt.Errorf("arg 0 is not common.Hash") + } if txhash != c.txHash { return fmt.Errorf("expected txHash %v but was %v", c.txHash, txhash) diff --git a/op-service/sources/batching/tx_call.go b/op-service/sources/batching/tx_call.go index 9f5487a68f4e..de3d223e2e5d 100644 --- a/op-service/sources/batching/tx_call.go +++ b/op-service/sources/batching/tx_call.go @@ -1,6 +1,8 @@ package batching import ( + "fmt" + "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" @@ -35,11 +37,21 @@ func (b *TxGetByHashCall) ToBatchElemCreator() (BatchElementCreator, error) { } func (c *TxGetByHashCall) HandleResult(result interface{}) (*CallResult, error) { - res := result.(*hexutil.Bytes) - return &CallResult{out: []interface{}{*res}}, nil + res, ok := result.(*hexutil.Bytes) + if !ok { + return nil, fmt.Errorf("result is not hexutil.Bytes") + } + + txn := new(types.Transaction) + err := txn.UnmarshalBinary(*res) + if err != nil { + return nil, err + } + return &CallResult{out: []interface{}{txn}}, nil } -func (c *TxGetByHashCall) DecodeTxParams(data []byte) (map[string]interface{}, error) { +func (c *TxGetByHashCall) UnpackCallData(txn *types.Transaction) (map[string]interface{}, error) { + data := txn.Data() m, err := c.Abi.MethodById(data[:4]) v := map[string]interface{}{} if err != nil { @@ -50,18 +62,3 @@ func (c *TxGetByHashCall) DecodeTxParams(data []byte) (map[string]interface{}, e } return v, nil } - -func (c *TxGetByHashCall) DecodeToTx(res *CallResult) (*types.Transaction, error) { - txn := new(types.Transaction) - hex := res.out[0].(hexutil.Bytes) - err := txn.UnmarshalBinary(hex) - if err != nil { - return nil, err - } - return txn, nil -} - -func (c *TxGetByHashCall) UnpackCallData(txn *types.Transaction) (map[string]interface{}, error) { - input := txn.Data() - return c.DecodeTxParams(input) -} diff --git a/op-service/sources/batching/tx_call_test.go b/op-service/sources/batching/tx_call_test.go index 2b4440df33e4..7dfdefb88335 100644 --- a/op-service/sources/batching/tx_call_test.go +++ b/op-service/sources/batching/tx_call_test.go @@ -12,23 +12,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestDecodeTxGetByHash(t *testing.T) { - addr := common.Address{0xbd} - testAbi, err := test.ERC20MetaData.GetAbi() - require.NoError(t, err) - call := NewTxGetByHash(testAbi, common.Hash{0xcc}, "approve") - expectedAmount := big.NewInt(1234444) - expectedSpender := common.Address{0xcc} - contractCall := NewContractCall(testAbi, addr, "approve", expectedSpender, expectedAmount) - packed, err := contractCall.Pack() - require.NoError(t, err) - - unpackedMap, err := call.DecodeTxParams(packed) - require.NoError(t, err) - require.Equal(t, expectedAmount, unpackedMap["amount"]) - require.Equal(t, expectedSpender, unpackedMap["spender"]) -} - func TestUnpackTxCalldata(t *testing.T) { expectedSpender := common.Address{0xcc} expectedAmount := big.NewInt(1234444) @@ -59,9 +42,8 @@ func TestUnpackTxCalldata(t *testing.T) { result, err := caller.SingleCall(context.Background(), rpcblock.Latest, txCall) require.NoError(t, err) - decodedTx, err := txCall.DecodeToTx(result) - require.NoError(t, err) - unpackedMap, err := txCall.UnpackCallData(decodedTx) + decodedTx := result.GetTx() + unpackedMap, err := txCall.UnpackCallData(&decodedTx) require.NoError(t, err) require.Equal(t, expectedSpender, unpackedMap["spender"]) require.Equal(t, expectedAmount, unpackedMap["amount"])