Commit 16d65a68 authored by Adrian Sutton's avatar Adrian Sutton

Add more tests

parent 8c688bc1
......@@ -12,6 +12,15 @@ import (
"github.com/ethereum/go-ethereum/common"
)
const (
methodGameDuration = "GAME_DURATION"
methodMaxGameDepth = "MAX_GAME_DEPTH"
methodAbsolutePrestate = "ABSOLUTE_PRESTATE"
methodStatus = "status"
methodClaimCount = "claimDataLen"
methodClaim = "claimData"
)
type FaultDisputeGameContract struct {
multiCaller *batching.MultiCaller
contract *batching.BoundContract
......@@ -30,15 +39,15 @@ func NewFaultDisputeGameContract(addr common.Address, caller *batching.MultiCall
}
func (f *FaultDisputeGameContract) GetGameDuration(ctx context.Context) (uint64, error) {
result, err := f.multiCaller.SingleCallLatest(ctx, f.contract.Call("GAME_DURATION"))
result, err := f.multiCaller.SingleCallLatest(ctx, f.contract.Call(methodGameDuration))
if err != nil {
return 0, fmt.Errorf("failed to fetch game duration: %w", err)
}
return result.GetBigInt(0).Uint64(), nil
return result.GetUint64(0), nil
}
func (f *FaultDisputeGameContract) GetMaxGameDepth(ctx context.Context) (uint64, error) {
result, err := f.multiCaller.SingleCallLatest(ctx, f.contract.Call("MAX_GAME_DEPTH"))
result, err := f.multiCaller.SingleCallLatest(ctx, f.contract.Call(methodMaxGameDepth))
if err != nil {
return 0, fmt.Errorf("failed to fetch max game depth: %w", err)
}
......@@ -46,7 +55,7 @@ func (f *FaultDisputeGameContract) GetMaxGameDepth(ctx context.Context) (uint64,
}
func (f *FaultDisputeGameContract) GetAbsolutePrestateHash(ctx context.Context) (common.Hash, error) {
result, err := f.multiCaller.SingleCallLatest(ctx, f.contract.Call("ABSOLUTE_PRESTATE"))
result, err := f.multiCaller.SingleCallLatest(ctx, f.contract.Call(methodAbsolutePrestate))
if err != nil {
return common.Hash{}, fmt.Errorf("failed to fetch absolute prestate hash: %w", err)
}
......@@ -54,7 +63,7 @@ func (f *FaultDisputeGameContract) GetAbsolutePrestateHash(ctx context.Context)
}
func (f *FaultDisputeGameContract) GetStatus(ctx context.Context) (gameTypes.GameStatus, error) {
result, err := f.multiCaller.SingleCallLatest(ctx, f.contract.Call("status"))
result, err := f.multiCaller.SingleCallLatest(ctx, f.contract.Call(methodStatus))
if err != nil {
return 0, fmt.Errorf("failed to fetch status: %w", err)
}
......@@ -62,7 +71,7 @@ func (f *FaultDisputeGameContract) GetStatus(ctx context.Context) (gameTypes.Gam
}
func (f *FaultDisputeGameContract) GetClaimCount(ctx context.Context) (uint64, error) {
result, err := f.multiCaller.SingleCallLatest(ctx, f.contract.Call("claimDataLen"))
result, err := f.multiCaller.SingleCallLatest(ctx, f.contract.Call(methodClaimCount))
if err != nil {
return 0, fmt.Errorf("failed to fetch claim count: %w", err)
}
......@@ -70,7 +79,7 @@ func (f *FaultDisputeGameContract) GetClaimCount(ctx context.Context) (uint64, e
}
func (f *FaultDisputeGameContract) GetClaim(ctx context.Context, idx uint64) (types.Claim, error) {
result, err := f.multiCaller.SingleCallLatest(ctx, f.contract.Call("claimData", new(big.Int).SetUint64(idx)))
result, err := f.multiCaller.SingleCallLatest(ctx, f.contract.Call(methodClaim, new(big.Int).SetUint64(idx)))
if err != nil {
return types.Claim{}, fmt.Errorf("failed to fetch claim %v: %w", idx, err)
}
......@@ -85,7 +94,7 @@ func (f *FaultDisputeGameContract) GetAllClaims(ctx context.Context) ([]types.Cl
calls := make([]*batching.ContractCall, count)
for i := uint64(0); i < count; i++ {
calls[i] = f.contract.Call("claimData", new(big.Int).SetUint64(i))
calls[i] = f.contract.Call(methodClaim, new(big.Int).SetUint64(i))
}
results, err := f.multiCaller.CallLatest(ctx, calls...)
......
......@@ -2,7 +2,6 @@ package contracts
import (
"context"
"encoding/json"
"math/big"
"testing"
......@@ -10,19 +9,71 @@ import (
faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum-optimism/optimism/op-service/sources/batching/test"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/rpc"
"github.com/stretchr/testify/require"
)
func TestGetStatus(t *testing.T) {
func TestSimpleGetters(t *testing.T) {
tests := []struct {
method string
args []interface{}
result interface{}
expected interface{} // Defaults to expecting the same as result
call func(game *FaultDisputeGameContract) (any, error)
}{
{
method: methodStatus,
result: types.GameStatusChallengerWon,
call: func(game *FaultDisputeGameContract) (any, error) {
return game.GetStatus(context.Background())
},
},
{
method: methodGameDuration,
result: uint64(5566),
call: func(game *FaultDisputeGameContract) (any, error) {
return game.GetGameDuration(context.Background())
},
},
{
method: methodMaxGameDepth,
result: big.NewInt(128),
expected: uint64(128),
call: func(game *FaultDisputeGameContract) (any, error) {
return game.GetMaxGameDepth(context.Background())
},
},
{
method: methodAbsolutePrestate,
result: common.Hash{0xab},
call: func(game *FaultDisputeGameContract) (any, error) {
return game.GetAbsolutePrestateHash(context.Background())
},
},
{
method: methodClaimCount,
result: big.NewInt(9876),
expected: uint64(9876),
call: func(game *FaultDisputeGameContract) (any, error) {
return game.GetClaimCount(context.Background())
},
},
}
for _, test := range tests {
test := test
t.Run(test.method, func(t *testing.T) {
stubRpc, game := setup(t)
stubRpc.SetResponse("status", nil, []interface{}{types.GameStatusChallengerWon})
status, err := game.GetStatus(context.Background())
stubRpc.SetResponse(test.method, nil, []interface{}{test.result})
status, err := test.call(game)
require.NoError(t, err)
require.Equal(t, types.GameStatusChallengerWon, status)
expected := test.expected
if expected == nil {
expected = test.result
}
require.Equal(t, expected, status)
})
}
}
func TestGetClaim(t *testing.T) {
......@@ -33,7 +84,7 @@ func TestGetClaim(t *testing.T) {
value := common.Hash{0xab}
position := big.NewInt(2)
clock := big.NewInt(1234)
stubRpc.SetResponse("claimData", []interface{}{idx}, []interface{}{parentIndex, countered, value, position, clock})
stubRpc.SetResponse(methodClaim, []interface{}{idx}, []interface{}{parentIndex, countered, value, position, clock})
status, err := game.GetClaim(context.Background(), idx.Uint64())
require.NoError(t, err)
require.Equal(t, faultTypes.Claim{
......@@ -48,76 +99,14 @@ func TestGetClaim(t *testing.T) {
}, status)
}
type abiBasedRpc struct {
t *testing.T
abi *abi.ABI
addr common.Address
expectedArgs map[string][]interface{}
outputs map[string][]interface{}
}
func setup(t *testing.T) (*abiBasedRpc, *FaultDisputeGameContract) {
func setup(t *testing.T) (*test.AbiBasedRpc, *FaultDisputeGameContract) {
fdgAbi, err := bindings.FaultDisputeGameMetaData.GetAbi()
require.NoError(t, err)
address := common.HexToAddress("0x24112842371dFC380576ebb09Ae16Cb6B6caD7CB")
stubRpc := &abiBasedRpc{
t: t,
abi: fdgAbi,
addr: address,
expectedArgs: make(map[string][]interface{}),
outputs: make(map[string][]interface{}),
}
caller := batching.NewMultiCaller(stubRpc, 1)
stubRpc := test.NewAbiBasedRpc(t, fdgAbi, address)
caller := batching.NewMultiCaller(stubRpc, 100)
game, err := NewFaultDisputeGameContract(address, caller)
require.NoError(t, err)
return stubRpc, game
}
func (l *abiBasedRpc) SetResponse(method string, expected []interface{}, output []interface{}) {
if expected == nil {
expected = []interface{}{}
}
if output == nil {
output = []interface{}{}
}
l.expectedArgs[method] = expected
l.outputs[method] = output
}
func (l *abiBasedRpc) BatchCallContext(ctx context.Context, b []rpc.BatchElem) error {
panic("Not implemented")
}
func (l *abiBasedRpc) CallContext(ctx context.Context, out interface{}, method string, args ...interface{}) error {
require.Equal(l.t, "eth_call", method)
require.Len(l.t, args, 2)
require.Equal(l.t, "latest", args[1])
callOpts, ok := args[0].(map[string]any)
require.True(l.t, ok)
require.Equal(l.t, &l.addr, callOpts["to"])
data, ok := callOpts["data"].(hexutil.Bytes)
require.True(l.t, ok)
abiMethod, err := l.abi.MethodById(data[0:4])
require.NoError(l.t, err)
args, err = abiMethod.Inputs.Unpack(data[4:])
require.NoError(l.t, err)
require.Len(l.t, args, len(abiMethod.Inputs))
expectedArgs, ok := l.expectedArgs[abiMethod.Name]
require.True(l.t, ok)
require.EqualValues(l.t, expectedArgs, args, "Unexpected args")
outputs, ok := l.outputs[abiMethod.Name]
require.True(l.t, ok)
output, err := abiMethod.Outputs.Pack(outputs...)
require.NoError(l.t, err)
// I admit I do not understand Go reflection.
// So leverage json.Unmarshal to set the out value correctly.
j, err := json.Marshal(hexutil.Bytes(output))
require.NoError(l.t, err)
require.NoError(l.t, json.Unmarshal(j, out))
return nil
}
......@@ -74,6 +74,10 @@ func (c *CallResult) GetUint32(i int) uint32 {
return *abi.ConvertType(c.out[i], new(uint32)).(*uint32)
}
func (c *CallResult) GetUint64(i int) uint64 {
return *abi.ConvertType(c.out[i], new(uint64)).(*uint64)
}
func (c *CallResult) GetBool(i int) bool {
return *abi.ConvertType(c.out[i], new(bool)).(*bool)
}
......
package test
import (
"context"
"encoding/json"
"testing"
"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"
"github.com/stretchr/testify/require"
)
type AbiBasedRpc struct {
t *testing.T
abi *abi.ABI
addr common.Address
expectedArgs map[string][]interface{}
outputs map[string][]interface{}
}
func NewAbiBasedRpc(t *testing.T, contractAbi *abi.ABI, addr common.Address) *AbiBasedRpc {
return &AbiBasedRpc{
t: t,
abi: contractAbi,
addr: addr,
expectedArgs: make(map[string][]interface{}),
outputs: make(map[string][]interface{}),
}
}
func (l *AbiBasedRpc) SetResponse(method string, expected []interface{}, output []interface{}) {
if expected == nil {
expected = []interface{}{}
}
if output == nil {
output = []interface{}{}
}
l.expectedArgs[method] = expected
l.outputs[method] = output
}
func (l *AbiBasedRpc) BatchCallContext(_ context.Context, b []rpc.BatchElem) error {
panic("Not implemented")
}
func (l *AbiBasedRpc) CallContext(_ context.Context, out interface{}, method string, args ...interface{}) error {
require.Equal(l.t, "eth_call", method)
require.Len(l.t, args, 2)
require.Equal(l.t, "latest", args[1])
callOpts, ok := args[0].(map[string]any)
require.True(l.t, ok)
require.Equal(l.t, &l.addr, callOpts["to"])
data, ok := callOpts["data"].(hexutil.Bytes)
require.True(l.t, ok)
abiMethod, err := l.abi.MethodById(data[0:4])
require.NoError(l.t, err)
args, err = abiMethod.Inputs.Unpack(data[4:])
require.NoError(l.t, err)
require.Len(l.t, args, len(abiMethod.Inputs))
expectedArgs, ok := l.expectedArgs[abiMethod.Name]
require.Truef(l.t, ok, "Unexpected call to %v", abiMethod.Name)
require.EqualValues(l.t, expectedArgs, args, "Unexpected args")
outputs, ok := l.outputs[abiMethod.Name]
require.True(l.t, ok)
output, err := abiMethod.Outputs.Pack(outputs...)
require.NoError(l.t, err)
// I admit I do not understand Go reflection.
// So leverage json.Unmarshal to set the out value correctly.
j, err := json.Marshal(hexutil.Bytes(output))
require.NoError(l.t, err)
require.NoError(l.t, json.Unmarshal(j, out))
return nil
}
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