Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(op-challenger): add EventCall, TxCall and GetSubClaims #63

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions op-challenger/game/fault/contracts/faultdisputegame.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ var (
methodL2BlockNumberChallenged = "l2BlockNumberChallenged"
methodL2BlockNumberChallenger = "l2BlockNumberChallenger"
methodChallengeRootL2Block = "challengeRootL2Block"
subClaimField = "_claim"
)

var (
Expand Down Expand Up @@ -624,4 +625,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)
}
65 changes: 65 additions & 0 deletions op-challenger/game/fault/contracts/faultdisputegame2.go
Original file line number Diff line number Diff line change
@@ -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) {
dajuguan marked this conversation as resolved.
Show resolved Hide resolved
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
}
78 changes: 78 additions & 0 deletions op-challenger/game/fault/contracts/faultdisputegame_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -321,6 +322,83 @@ 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) {
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)))})

eventName := "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[eventName].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)
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)
blockchaindevsh marked this conversation as resolved.
Show resolved Hide resolved
packed, err := tx.MarshalBinary()
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
Expand Down
4 changes: 4 additions & 0 deletions op-service/sources/batching/bound.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,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...)
}
Expand Down
37 changes: 37 additions & 0 deletions op-service/sources/batching/event_call.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
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
}

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
}
75 changes: 75 additions & 0 deletions op-service/sources/batching/event_call_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
20 changes: 20 additions & 0 deletions op-service/sources/batching/multicall.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -80,3 +83,20 @@ func (m *MultiCaller) Call(ctx context.Context, block rpcblock.Block, calls ...C
}
return callResults, nil
}

// 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)
if err != nil {
return nil, err
}
var out []types.Log
results.GetStruct(0, &out)
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")
}
22 changes: 22 additions & 0 deletions op-service/sources/batching/test/abi_stub.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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{}{}
Expand All @@ -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{
Expand Down
Loading