Commit d8927939 authored by Adrian Sutton's avatar Adrian Sutton

op-challenger: Replace OracleUpdater with calls to new contract bindings in responder.

parent c04fdd7a
......@@ -33,19 +33,17 @@ type Agent struct {
solver *solver.GameSolver
loader ClaimLoader
responder Responder
updater types.OracleUpdater
maxDepth int
agreeWithProposedOutput bool
log log.Logger
}
func NewAgent(m metrics.Metricer, loader ClaimLoader, maxDepth int, trace types.TraceAccessor, responder Responder, updater types.OracleUpdater, agreeWithProposedOutput bool, log log.Logger) *Agent {
func NewAgent(m metrics.Metricer, loader ClaimLoader, maxDepth int, trace types.TraceAccessor, responder Responder, agreeWithProposedOutput bool, log log.Logger) *Agent {
return &Agent{
metrics: m,
solver: solver.NewGameSolver(maxDepth, trace),
loader: loader,
responder: responder,
updater: updater,
maxDepth: maxDepth,
agreeWithProposedOutput: agreeWithProposedOutput,
log: log,
......@@ -77,13 +75,6 @@ func (a *Agent) Act(ctx context.Context) error {
log = log.New("value", action.Value)
}
if action.OracleData != nil {
a.log.Info("Updating oracle data", "oracleKey", action.OracleData.OracleKey, "oracleData", action.OracleData.OracleData)
if err := a.updater.UpdateOracle(ctx, action.OracleData); err != nil {
return fmt.Errorf("failed to load oracle data: %w", err)
}
}
switch action.Type {
case types.ActionTypeMove:
a.metrics.RecordGameMove()
......
......@@ -115,8 +115,7 @@ func setupTestAgent(t *testing.T, agreeWithProposedOutput bool) (*Agent, *stubCl
depth := 4
provider := alphabet.NewTraceProvider("abcd", uint64(depth))
responder := &stubResponder{}
updater := &stubUpdater{}
agent := NewAgent(metrics.NoopMetrics, claimLoader, depth, trace.NewSimpleTraceAccessor(provider), responder, updater, agreeWithProposedOutput, logger)
agent := NewAgent(metrics.NoopMetrics, claimLoader, depth, trace.NewSimpleTraceAccessor(provider), responder, agreeWithProposedOutput, logger)
return agent, claimLoader, responder
}
......@@ -166,10 +165,3 @@ func (s *stubResponder) ResolveClaim(ctx context.Context, clainIdx uint64) error
func (s *stubResponder) PerformAction(ctx context.Context, response types.Action) error {
return nil
}
type stubUpdater struct {
}
func (s *stubUpdater) UpdateOracle(ctx context.Context, data *types.PreimageOracleData) error {
panic("Not implemented")
}
......@@ -146,12 +146,13 @@ func (f *FaultDisputeGameContract) GetAllClaims(ctx context.Context) ([]types.Cl
return claims, nil
}
func (f *FaultDisputeGameContract) VMAddr(ctx context.Context) (common.Address, error) {
func (f *FaultDisputeGameContract) vm(ctx context.Context) (*VMContract, error) {
result, err := f.multiCaller.SingleCall(ctx, batching.BlockLatest, f.contract.Call(methodVM))
if err != nil {
return common.Address{}, fmt.Errorf("failed to fetch VM addr: %w", err)
return nil, fmt.Errorf("failed to fetch VM addr: %w", err)
}
return result.GetAddress(0), nil
vmAddr := result.GetAddress(0)
return NewVMContract(vmAddr, f.multiCaller)
}
func (f *FaultDisputeGameContract) AttackTx(parentContractIndex uint64, pivot common.Hash) (txmgr.TxCandidate, error) {
......@@ -205,7 +206,14 @@ func (f *FaultDisputeGameContract) resolveCall() *batching.ContractCall {
return f.contract.Call(methodResolve)
}
func (f *FaultDisputeGameContract) AddLocalDataTx(data *types.PreimageOracleData) (txmgr.TxCandidate, error) {
func (f *FaultDisputeGameContract) UpdateOracleTx(ctx context.Context, data *types.PreimageOracleData) (txmgr.TxCandidate, error) {
if data.IsLocal {
return f.addLocalDataTx(data)
}
return f.addGlobalDataTx(ctx, data)
}
func (f *FaultDisputeGameContract) addLocalDataTx(data *types.PreimageOracleData) (txmgr.TxCandidate, error) {
call := f.contract.Call(
methodAddLocalData,
data.GetIdent(),
......@@ -215,6 +223,18 @@ func (f *FaultDisputeGameContract) AddLocalDataTx(data *types.PreimageOracleData
return call.ToTxCandidate()
}
func (f *FaultDisputeGameContract) addGlobalDataTx(ctx context.Context, data *types.PreimageOracleData) (txmgr.TxCandidate, error) {
vm, err := f.vm(ctx)
if err != nil {
return txmgr.TxCandidate{}, err
}
oracle, err := vm.Oracle(ctx)
if err != nil {
return txmgr.TxCandidate{}, err
}
return oracle.AddGlobalDataTx(data)
}
func (f *FaultDisputeGameContract) decodeClaim(result *batching.CallResult, contractIndex int) types.Claim {
parentIndex := result.GetUint32(0)
countered := result.GetBool(1)
......
......@@ -15,6 +15,12 @@ import (
"github.com/stretchr/testify/require"
)
var (
fdgAddr = common.HexToAddress("0x24112842371dFC380576ebb09Ae16Cb6B6caD7CB")
vmAddr = common.HexToAddress("0x33332842371dFC380576ebb09Ae16Cb6B6c3333")
oracleAddr = common.HexToAddress("0x44442842371dFC380576ebb09Ae16Cb6B6ca4444")
)
func TestSimpleGetters(t *testing.T) {
tests := []struct {
method string
......@@ -74,19 +80,12 @@ func TestSimpleGetters(t *testing.T) {
return game.CallResolve(context.Background())
},
},
{
method: methodVM,
result: common.Address{0xab, 0xbc},
call: func(game *FaultDisputeGameContract) (any, error) {
return game.VMAddr(context.Background())
},
},
}
for _, test := range tests {
test := test
t.Run(test.method, func(t *testing.T) {
stubRpc, game := setup(t)
stubRpc.SetResponse(test.method, batching.BlockLatest, nil, []interface{}{test.result})
stubRpc.SetResponse(fdgAddr, test.method, batching.BlockLatest, nil, []interface{}{test.result})
status, err := test.call(game)
require.NoError(t, err)
expected := test.expected
......@@ -116,7 +115,7 @@ func TestGetProposals(t *testing.T) {
L2BlockNumber: disputedBlockNum,
OutputRoot: disputedRoot,
}
stubRpc.SetResponse(methodProposals, batching.BlockLatest, []interface{}{}, []interface{}{
stubRpc.SetResponse(fdgAddr, methodProposals, batching.BlockLatest, []interface{}{}, []interface{}{
agreed, disputed,
})
actualAgreed, actualDisputed, err := game.GetProposals(context.Background())
......@@ -133,7 +132,7 @@ func TestGetClaim(t *testing.T) {
value := common.Hash{0xab}
position := big.NewInt(2)
clock := big.NewInt(1234)
stubRpc.SetResponse(methodClaim, batching.BlockLatest, []interface{}{idx}, []interface{}{parentIndex, countered, value, position, clock})
stubRpc.SetResponse(fdgAddr, methodClaim, batching.BlockLatest, []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{
......@@ -181,7 +180,7 @@ func TestGetAllClaims(t *testing.T) {
ParentContractIndex: 1,
}
expectedClaims := []faultTypes.Claim{claim0, claim1, claim2}
stubRpc.SetResponse(methodClaimCount, batching.BlockLatest, nil, []interface{}{big.NewInt(int64(len(expectedClaims)))})
stubRpc.SetResponse(fdgAddr, methodClaimCount, batching.BlockLatest, nil, []interface{}{big.NewInt(int64(len(expectedClaims)))})
for _, claim := range expectedClaims {
expectGetClaim(stubRpc, claim)
}
......@@ -192,14 +191,14 @@ func TestGetAllClaims(t *testing.T) {
func TestCallResolveClaim(t *testing.T) {
stubRpc, game := setup(t)
stubRpc.SetResponse(methodResolveClaim, batching.BlockLatest, []interface{}{big.NewInt(123)}, nil)
stubRpc.SetResponse(fdgAddr, methodResolveClaim, batching.BlockLatest, []interface{}{big.NewInt(123)}, nil)
err := game.CallResolveClaim(context.Background(), 123)
require.NoError(t, err)
}
func TestResolveClaimTx(t *testing.T) {
stubRpc, game := setup(t)
stubRpc.SetResponse(methodResolveClaim, batching.BlockLatest, []interface{}{big.NewInt(123)}, nil)
stubRpc.SetResponse(fdgAddr, methodResolveClaim, batching.BlockLatest, []interface{}{big.NewInt(123)}, nil)
tx, err := game.ResolveClaimTx(123)
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
......@@ -207,7 +206,7 @@ func TestResolveClaimTx(t *testing.T) {
func TestResolveTx(t *testing.T) {
stubRpc, game := setup(t)
stubRpc.SetResponse(methodResolve, batching.BlockLatest, nil, nil)
stubRpc.SetResponse(fdgAddr, methodResolve, batching.BlockLatest, nil, nil)
tx, err := game.ResolveTx()
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
......@@ -216,7 +215,7 @@ func TestResolveTx(t *testing.T) {
func TestAttackTx(t *testing.T) {
stubRpc, game := setup(t)
value := common.Hash{0xaa}
stubRpc.SetResponse(methodAttack, batching.BlockLatest, []interface{}{big.NewInt(111), value}, nil)
stubRpc.SetResponse(fdgAddr, methodAttack, batching.BlockLatest, []interface{}{big.NewInt(111), value}, nil)
tx, err := game.AttackTx(111, value)
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
......@@ -225,7 +224,7 @@ func TestAttackTx(t *testing.T) {
func TestDefendTx(t *testing.T) {
stubRpc, game := setup(t)
value := common.Hash{0xaa}
stubRpc.SetResponse(methodDefend, batching.BlockLatest, []interface{}{big.NewInt(111), value}, nil)
stubRpc.SetResponse(fdgAddr, methodDefend, batching.BlockLatest, []interface{}{big.NewInt(111), value}, nil)
tx, err := game.DefendTx(111, value)
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
......@@ -235,33 +234,55 @@ func TestStepTx(t *testing.T) {
stubRpc, game := setup(t)
stateData := []byte{1, 2, 3}
proofData := []byte{4, 5, 6, 7, 8, 9}
stubRpc.SetResponse(methodStep, batching.BlockLatest, []interface{}{big.NewInt(111), true, stateData, proofData}, nil)
stubRpc.SetResponse(fdgAddr, methodStep, batching.BlockLatest, []interface{}{big.NewInt(111), true, stateData, proofData}, nil)
tx, err := game.StepTx(111, true, stateData, proofData)
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
}
func TestAddLocalDataTx(t *testing.T) {
stubRpc, game := setup(t)
data := &faultTypes.PreimageOracleData{
IsLocal: true,
LocalContext: 2,
OracleKey: common.Hash{0xbc}.Bytes(),
OracleData: []byte{1, 2, 3, 4, 5, 6, 7},
OracleOffset: 16,
}
stubRpc.SetResponse(methodAddLocalData, batching.BlockLatest, []interface{}{
data.GetIdent(),
new(big.Int).SetUint64(data.LocalContext),
new(big.Int).SetUint64(uint64(data.OracleOffset)),
}, nil)
tx, err := game.AddLocalDataTx(data)
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
func TestUpdateOracleTx(t *testing.T) {
t.Run("Local", func(t *testing.T) {
stubRpc, game := setup(t)
data := &faultTypes.PreimageOracleData{
IsLocal: true,
LocalContext: 2,
OracleKey: common.Hash{0xbc}.Bytes(),
OracleData: []byte{1, 2, 3, 4, 5, 6, 7},
OracleOffset: 16,
}
stubRpc.SetResponse(fdgAddr, methodAddLocalData, batching.BlockLatest, []interface{}{
data.GetIdent(),
new(big.Int).SetUint64(data.LocalContext),
new(big.Int).SetUint64(uint64(data.OracleOffset)),
}, nil)
tx, err := game.UpdateOracleTx(context.Background(), data)
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
})
t.Run("Global", func(t *testing.T) {
stubRpc, game := setup(t)
data := &faultTypes.PreimageOracleData{
IsLocal: false,
OracleKey: common.Hash{0xbc}.Bytes(),
OracleData: []byte{1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15},
OracleOffset: 16,
}
stubRpc.SetResponse(fdgAddr, methodVM, batching.BlockLatest, nil, []interface{}{vmAddr})
stubRpc.SetResponse(vmAddr, methodOracle, batching.BlockLatest, nil, []interface{}{oracleAddr})
stubRpc.SetResponse(oracleAddr, methodLoadKeccak256PreimagePart, batching.BlockLatest, []interface{}{
new(big.Int).SetUint64(uint64(data.OracleOffset)),
data.GetPreimageWithoutSize(),
}, nil)
tx, err := game.UpdateOracleTx(context.Background(), data)
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
})
}
func expectGetClaim(stubRpc *batchingTest.AbiBasedRpc, claim faultTypes.Claim) {
stubRpc.SetResponse(
fdgAddr,
methodClaim,
batching.BlockLatest,
[]interface{}{big.NewInt(int64(claim.ContractIndex))},
......@@ -277,11 +298,17 @@ func expectGetClaim(stubRpc *batchingTest.AbiBasedRpc, claim faultTypes.Claim) {
func setup(t *testing.T) (*batchingTest.AbiBasedRpc, *FaultDisputeGameContract) {
fdgAbi, err := bindings.FaultDisputeGameMetaData.GetAbi()
require.NoError(t, err)
address := common.HexToAddress("0x24112842371dFC380576ebb09Ae16Cb6B6caD7CB")
stubRpc := batchingTest.NewAbiBasedRpc(t, fdgAbi, address)
caller := batching.NewMultiCaller(stubRpc, 100)
game, err := NewFaultDisputeGameContract(address, caller)
vmAbi, err := bindings.MIPSMetaData.GetAbi()
require.NoError(t, err)
oracleAbi, err := bindings.PreimageOracleMetaData.GetAbi()
require.NoError(t, err)
stubRpc := batchingTest.NewAbiBasedRpc(t, fdgAddr, fdgAbi)
stubRpc.AddContract(vmAddr, vmAbi)
stubRpc.AddContract(oracleAddr, oracleAbi)
caller := batching.NewMultiCaller(stubRpc, batching.DefaultBatchSize)
game, err := NewFaultDisputeGameContract(fdgAddr, caller)
require.NoError(t, err)
return stubRpc, game
}
......@@ -13,6 +13,10 @@ import (
"github.com/stretchr/testify/require"
)
var (
factoryAddr = common.HexToAddress("0x24112842371dFC380576ebb09Ae16Cb6B6caD7CB")
)
func TestDisputeGameFactorySimpleGetters(t *testing.T) {
blockNum := uint64(23)
tests := []struct {
......@@ -35,7 +39,7 @@ func TestDisputeGameFactorySimpleGetters(t *testing.T) {
test := test
t.Run(test.method, func(t *testing.T) {
stubRpc, factory := setupDisputeGameFactoryTest(t)
stubRpc.SetResponse(test.method, batching.BlockByNumber(blockNum), nil, []interface{}{test.result})
stubRpc.SetResponse(factoryAddr, test.method, batching.BlockByNumber(blockNum), nil, []interface{}{test.result})
status, err := test.call(factory)
require.NoError(t, err)
expected := test.expected
......@@ -76,6 +80,7 @@ func TestLoadGame(t *testing.T) {
func expectGetGame(stubRpc *batchingTest.AbiBasedRpc, idx int, blockNum uint64, game types.GameMetadata) {
stubRpc.SetResponse(
factoryAddr,
methodGameAtIndex,
batching.BlockByNumber(blockNum),
[]interface{}{big.NewInt(int64(idx))},
......@@ -89,11 +94,10 @@ func expectGetGame(stubRpc *batchingTest.AbiBasedRpc, idx int, blockNum uint64,
func setupDisputeGameFactoryTest(t *testing.T) (*batchingTest.AbiBasedRpc, *DisputeGameFactoryContract) {
fdgAbi, err := bindings.DisputeGameFactoryMetaData.GetAbi()
require.NoError(t, err)
address := common.HexToAddress("0x24112842371dFC380576ebb09Ae16Cb6B6caD7CB")
stubRpc := batchingTest.NewAbiBasedRpc(t, fdgAbi, address)
stubRpc := batchingTest.NewAbiBasedRpc(t, factoryAddr, fdgAbi)
caller := batching.NewMultiCaller(stubRpc, 100)
factory, err := NewDisputeGameFactoryContract(address, caller)
factory, err := NewDisputeGameFactoryContract(factoryAddr, caller)
require.NoError(t, err)
return stubRpc, factory
}
package contracts
import (
"fmt"
"math/big"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common"
)
const (
methodLoadKeccak256PreimagePart = "loadKeccak256PreimagePart"
)
// PreimageOracleContract is a binding that works with contracts implementing the IPreimageOracle interface
type PreimageOracleContract struct {
multiCaller *batching.MultiCaller
contract *batching.BoundContract
}
func NewPreimageOracleContract(addr common.Address, caller *batching.MultiCaller) (*PreimageOracleContract, error) {
mipsAbi, err := bindings.PreimageOracleMetaData.GetAbi()
if err != nil {
return nil, fmt.Errorf("failed to load preimage oracle ABI: %w", err)
}
return &PreimageOracleContract{
multiCaller: caller,
contract: batching.NewBoundContract(mipsAbi, addr),
}, nil
}
func (c PreimageOracleContract) AddGlobalDataTx(data *types.PreimageOracleData) (txmgr.TxCandidate, error) {
call := c.contract.Call(methodLoadKeccak256PreimagePart, new(big.Int).SetUint64(uint64(data.OracleOffset)), data.GetPreimageWithoutSize())
return call.ToTxCandidate()
}
package contracts
import (
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
batchingTest "github.com/ethereum-optimism/optimism/op-service/sources/batching/test"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)
func TestPreimageOracleContract_LoadKeccak256(t *testing.T) {
oracleAbi, err := bindings.PreimageOracleMetaData.GetAbi()
require.NoError(t, err)
stubRpc := batchingTest.NewAbiBasedRpc(t, oracleAddr, oracleAbi)
oracleContract, err := NewPreimageOracleContract(oracleAddr, batching.NewMultiCaller(stubRpc, batching.DefaultBatchSize))
require.NoError(t, err)
data := &types.PreimageOracleData{
OracleKey: common.Hash{0xcc}.Bytes(),
OracleData: make([]byte, 20),
OracleOffset: 545,
}
stubRpc.SetResponse(oracleAddr, methodLoadKeccak256PreimagePart, batching.BlockLatest, []interface{}{
new(big.Int).SetUint64(uint64(data.OracleOffset)),
data.GetPreimageWithoutSize(),
}, nil)
tx, err := oracleContract.AddGlobalDataTx(data)
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
}
package contracts
import (
"context"
"fmt"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum/go-ethereum/common"
)
const (
methodOracle = "oracle"
)
// VMContract is a binding that works with contracts implementing the IBigStepper interface
type VMContract struct {
multiCaller *batching.MultiCaller
contract *batching.BoundContract
}
func NewVMContract(addr common.Address, caller *batching.MultiCaller) (*VMContract, error) {
mipsAbi, err := bindings.MIPSMetaData.GetAbi()
if err != nil {
return nil, fmt.Errorf("failed to load VM ABI: %w", err)
}
return &VMContract{
multiCaller: caller,
contract: batching.NewBoundContract(mipsAbi, addr),
}, nil
}
func (c *VMContract) Oracle(ctx context.Context) (*PreimageOracleContract, error) {
results, err := c.multiCaller.SingleCall(ctx, batching.BlockLatest, c.contract.Call(methodOracle))
if err != nil {
return nil, fmt.Errorf("failed to load oracle address: %w", err)
}
return NewPreimageOracleContract(results.GetAddress(0), c.multiCaller)
}
package contracts
import (
"context"
"testing"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
batchingTest "github.com/ethereum-optimism/optimism/op-service/sources/batching/test"
"github.com/stretchr/testify/require"
)
func TestVMContract_Oracle(t *testing.T) {
vmAbi, err := bindings.MIPSMetaData.GetAbi()
require.NoError(t, err)
stubRpc := batchingTest.NewAbiBasedRpc(t, vmAddr, vmAbi)
vmContract, err := NewVMContract(vmAddr, batching.NewMultiCaller(stubRpc, batching.DefaultBatchSize))
require.NoError(t, err)
stubRpc.SetResponse(vmAddr, methodOracle, batching.BlockLatest, nil, []interface{}{oracleAddr})
oracleContract, err := vmContract.Oracle(context.Background())
require.NoError(t, err)
tx, err := oracleContract.AddGlobalDataTx(&types.PreimageOracleData{
OracleData: make([]byte, 20),
})
require.NoError(t, err)
// This test doesn't care about all the tx details, we just want to confirm the contract binding is using the
// correct address
require.Equal(t, &oracleAddr, tx.To)
}
......@@ -35,7 +35,7 @@ type GamePlayer struct {
status gameTypes.GameStatus
}
type resourceCreator func(addr common.Address, contract *contracts.FaultDisputeGameContract, gameDepth uint64, dir string) (types.TraceAccessor, types.OracleUpdater, gameValidator, error)
type resourceCreator func(addr common.Address, contract *contracts.FaultDisputeGameContract, gameDepth uint64, dir string) (types.TraceAccessor, gameValidator, error)
func NewGamePlayer(
ctx context.Context,
......@@ -74,7 +74,7 @@ func NewGamePlayer(
return nil, fmt.Errorf("failed to fetch the game depth: %w", err)
}
accessor, updater, validator, err := creator(addr, loader, gameDepth, dir)
accessor, validator, err := creator(addr, loader, gameDepth, dir)
if err != nil {
return nil, fmt.Errorf("failed to create trace accessor: %w", err)
}
......@@ -88,7 +88,7 @@ func NewGamePlayer(
return nil, fmt.Errorf("failed to create the responder: %w", err)
}
agent := NewAgent(m, loader, int(gameDepth), accessor, responder, updater, cfg.AgreeWithProposedOutput, logger)
agent := NewAgent(m, loader, int(gameDepth), accessor, responder, cfg.AgreeWithProposedOutput, logger)
return &GamePlayer{
act: agent.Act,
agreeWithProposedOutput: cfg.AgreeWithProposedOutput,
......
......@@ -41,20 +41,16 @@ func RegisterGameTypes(
multiCaller := batching.NewMultiCaller(client.Client(), batching.DefaultBatchSize)
if cfg.TraceTypeEnabled(config.TraceTypeCannon) {
resourceCreator := func(addr common.Address, contract *contracts.FaultDisputeGameContract, gameDepth uint64, dir string) (faultTypes.TraceAccessor, faultTypes.OracleUpdater, gameValidator, error) {
resourceCreator := func(addr common.Address, contract *contracts.FaultDisputeGameContract, gameDepth uint64, dir string) (faultTypes.TraceAccessor, gameValidator, error) {
logger := logger.New("game", addr)
provider, err := cannon.NewTraceProvider(ctx, logger, m, cfg, contract, dir, gameDepth)
if err != nil {
return nil, nil, nil, fmt.Errorf("create cannon trace provider: %w", err)
}
updater, err := cannon.NewOracleUpdater(ctx, logger, txMgr, client, contract)
if err != nil {
return nil, nil, nil, fmt.Errorf("failed to create the cannon updater: %w", err)
return nil, nil, fmt.Errorf("create cannon trace provider: %w", err)
}
validator := func(ctx context.Context, contract *contracts.FaultDisputeGameContract) error {
return ValidateAbsolutePrestate(ctx, provider, contract)
}
return trace.NewSimpleTraceAccessor(provider), updater, validator, nil
return trace.NewSimpleTraceAccessor(provider), validator, nil
}
playerCreator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) {
gameContract, err := contracts.NewFaultDisputeGameContract(game.Proxy, multiCaller)
......@@ -66,14 +62,12 @@ func RegisterGameTypes(
registry.RegisterGameType(cannonGameType, playerCreator)
}
if cfg.TraceTypeEnabled(config.TraceTypeAlphabet) {
resourceCreator := func(addr common.Address, contract *contracts.FaultDisputeGameContract, gameDepth uint64, dir string) (faultTypes.TraceAccessor, faultTypes.OracleUpdater, gameValidator, error) {
logger := logger.New("game", addr)
resourceCreator := func(addr common.Address, contract *contracts.FaultDisputeGameContract, gameDepth uint64, dir string) (faultTypes.TraceAccessor, gameValidator, error) {
provider := alphabet.NewTraceProvider(cfg.AlphabetTrace, gameDepth)
updater := alphabet.NewOracleUpdater(logger)
validator := func(ctx context.Context, contract *contracts.FaultDisputeGameContract) error {
return ValidateAbsolutePrestate(ctx, provider, contract)
}
return trace.NewSimpleTraceAccessor(provider), updater, validator, nil
return trace.NewSimpleTraceAccessor(provider), validator, nil
}
playerCreator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) {
gameContract, err := contracts.NewFaultDisputeGameContract(game.Proxy, multiCaller)
......
......@@ -2,6 +2,7 @@ package responder
import (
"context"
"fmt"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types"
......@@ -20,6 +21,7 @@ type GameContract interface {
AttackTx(parentContractIndex uint64, pivot common.Hash) (txmgr.TxCandidate, error)
DefendTx(parentContractIndex uint64, pivot common.Hash) (txmgr.TxCandidate, error)
StepTx(claimIdx uint64, isAttack bool, stateData []byte, proof []byte) (txmgr.TxCandidate, error)
UpdateOracleTx(ctx context.Context, data *types.PreimageOracleData) (txmgr.TxCandidate, error)
}
// FaultResponder implements the [Responder] interface to send onchain transactions.
......@@ -71,6 +73,16 @@ func (r *FaultResponder) ResolveClaim(ctx context.Context, claimIdx uint64) erro
}
func (r *FaultResponder) PerformAction(ctx context.Context, action types.Action) error {
if action.OracleData != nil {
r.log.Info("Updating oracle data", "key", action.OracleData.OracleKey)
candidate, err := r.contract.UpdateOracleTx(ctx, action.OracleData)
if err != nil {
return fmt.Errorf("failed to create pre-image oracle tx: %w", err)
}
if err := r.sendTxAndWait(ctx, candidate); err != nil {
return fmt.Errorf("failed to populate pre-image oracle: %w", err)
}
}
var candidate txmgr.TxCandidate
var err error
switch action.Type {
......
......@@ -169,6 +169,30 @@ func TestPerformAction(t *testing.T) {
require.EqualValues(t, []interface{}{uint64(action.ParentIdx), action.IsAttack, action.PreState, action.ProofData}, contract.stepArgs)
require.Equal(t, ([]byte)("step"), mockTxMgr.sent[0].TxData)
})
t.Run("stepWithOracleData", func(t *testing.T) {
responder, mockTxMgr, contract := newTestFaultResponder(t)
action := types.Action{
Type: types.ActionTypeStep,
ParentIdx: 123,
IsAttack: true,
PreState: []byte{1, 2, 3},
ProofData: []byte{4, 5, 6},
OracleData: &types.PreimageOracleData{
IsLocal: true,
LocalContext: 6,
},
}
err := responder.PerformAction(context.Background(), action)
require.NoError(t, err)
require.Len(t, mockTxMgr.sent, 2)
require.EqualValues(t, action.OracleData, contract.updateOracleArgs)
require.EqualValues(t, []interface{}{uint64(action.ParentIdx), action.IsAttack, action.PreState, action.ProofData}, contract.stepArgs)
// Important that the oracle is updated first
require.Equal(t, ([]byte)("updateOracle"), mockTxMgr.sent[0].TxData)
require.Equal(t, ([]byte)("step"), mockTxMgr.sent[1].TxData)
})
}
func newTestFaultResponder(t *testing.T) (*FaultResponder, *mockTxManager, *mockContract) {
......@@ -209,11 +233,12 @@ func (m *mockTxManager) From() common.Address {
}
type mockContract struct {
calls int
callFails bool
attackArgs []interface{}
defendArgs []interface{}
stepArgs []interface{}
calls int
callFails bool
attackArgs []interface{}
defendArgs []interface{}
stepArgs []interface{}
updateOracleArgs *types.PreimageOracleData
}
func (m *mockContract) CallResolve(_ context.Context) (gameTypes.GameStatus, error) {
......@@ -254,3 +279,8 @@ func (m *mockContract) StepTx(claimIdx uint64, isAttack bool, stateData []byte,
m.stepArgs = []interface{}{claimIdx, isAttack, stateData, proofData}
return txmgr.TxCandidate{TxData: ([]byte)("step")}, nil
}
func (m *mockContract) UpdateOracleTx(_ context.Context, data *types.PreimageOracleData) (txmgr.TxCandidate, error) {
m.updateOracleArgs = data
return txmgr.TxCandidate{TxData: ([]byte)("updateOracle")}, nil
}
package alphabet
import (
"context"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum/go-ethereum/log"
)
// alphabetUpdater is a [types.OracleUpdater] that exposes a
// method to update onchain oracles with required data.
type alphabetUpdater struct {
logger log.Logger
}
// NewOracleUpdater returns a new updater.
func NewOracleUpdater(logger log.Logger) *alphabetUpdater {
return &alphabetUpdater{
logger: logger,
}
}
// UpdateOracle updates the oracle with the given data.
func (u *alphabetUpdater) UpdateOracle(ctx context.Context, data *types.PreimageOracleData) error {
u.logger.Info("alphabet oracle updater called")
return nil
}
package alphabet
import (
"context"
"testing"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
)
// TestAlphabetUpdater tests the [alphabetUpdater].
func TestAlphabetUpdater(t *testing.T) {
logger := testlog.Logger(t, log.LvlInfo)
updater := NewOracleUpdater(logger)
require.Nil(t, updater.UpdateOracle(context.Background(), &types.PreimageOracleData{}))
}
package cannon
import (
"context"
"fmt"
"math/big"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
)
type GameContract interface {
VMAddr(ctx context.Context) (common.Address, error)
AddLocalDataTx(data *types.PreimageOracleData) (txmgr.TxCandidate, error)
}
// cannonUpdater is a [types.OracleUpdater] that exposes a method
// to update onchain cannon oracles with required data.
type cannonUpdater struct {
log log.Logger
txMgr txmgr.TxManager
gameContract GameContract
preimageOracleAbi abi.ABI
preimageOracleAddr common.Address
}
// NewOracleUpdater returns a new updater. The pre-image oracle address is loaded from the fault dispute game.
func NewOracleUpdater(
ctx context.Context,
logger log.Logger,
txMgr txmgr.TxManager,
client bind.ContractCaller,
gameContract GameContract,
) (*cannonUpdater, error) {
opts := &bind.CallOpts{Context: ctx}
vm, err := gameContract.VMAddr(ctx)
if err != nil {
return nil, fmt.Errorf("failed to load VM address: %w", err)
}
mipsCaller, err := bindings.NewMIPSCaller(vm, client)
if err != nil {
return nil, fmt.Errorf("failed to create MIPS caller for address %v: %w", vm, err)
}
oracleAddr, err := mipsCaller.Oracle(opts)
if err != nil {
return nil, fmt.Errorf("failed to load pre-image oracle address: %w", err)
}
return NewOracleUpdaterWithOracle(logger, txMgr, gameContract, oracleAddr)
}
// NewOracleUpdaterWithOracle returns a new updater using a specified pre-image oracle address.
func NewOracleUpdaterWithOracle(
logger log.Logger,
txMgr txmgr.TxManager,
gameContract GameContract,
preimageOracleAddr common.Address,
) (*cannonUpdater, error) {
preimageOracleAbi, err := bindings.PreimageOracleMetaData.GetAbi()
if err != nil {
return nil, err
}
return &cannonUpdater{
log: logger,
txMgr: txMgr,
gameContract: gameContract,
preimageOracleAbi: *preimageOracleAbi,
preimageOracleAddr: preimageOracleAddr,
}, nil
}
// UpdateOracle updates the oracle with the given data.
func (u *cannonUpdater) UpdateOracle(ctx context.Context, data *types.PreimageOracleData) error {
if data.IsLocal {
return u.sendLocalOracleData(ctx, data)
}
return u.sendGlobalOracleData(ctx, data)
}
// sendLocalOracleData sends the local oracle data to the [txmgr].
func (u *cannonUpdater) sendLocalOracleData(ctx context.Context, data *types.PreimageOracleData) error {
tx, err := u.gameContract.AddLocalDataTx(data)
if err != nil {
return fmt.Errorf("local oracle tx data build: %w", err)
}
return u.sendTxAndWait(ctx, tx)
}
// sendGlobalOracleData sends the global oracle data to the [txmgr].
func (u *cannonUpdater) sendGlobalOracleData(ctx context.Context, data *types.PreimageOracleData) error {
txData, err := u.BuildGlobalOracleData(data)
if err != nil {
return fmt.Errorf("global oracle tx data build: %w", err)
}
return u.sendTxAndWait(ctx, txmgr.TxCandidate{
To: &u.preimageOracleAddr,
TxData: txData,
GasLimit: 0,
})
}
// BuildGlobalOracleData takes the global preimage key and data
// and creates tx data to load the key, data pair into the
// PreimageOracle contract.
func (u *cannonUpdater) BuildGlobalOracleData(data *types.PreimageOracleData) ([]byte, error) {
return u.preimageOracleAbi.Pack(
"loadKeccak256PreimagePart",
big.NewInt(int64(data.OracleOffset)),
data.GetPreimageWithoutSize(),
)
}
// sendTxAndWait sends a transaction through the [txmgr] and waits for a receipt.
// This sets the tx GasLimit to 0, performing gas estimation online through the [txmgr].
func (u *cannonUpdater) sendTxAndWait(ctx context.Context, tx txmgr.TxCandidate) error {
receipt, err := u.txMgr.Send(ctx, tx)
if err != nil {
return err
}
if receipt.Status == ethtypes.ReceiptStatusFailed {
u.log.Error("Responder tx successfully published but reverted", "tx_hash", receipt.TxHash)
} else {
u.log.Debug("Responder tx successfully published", "tx_hash", receipt.TxHash)
}
return nil
}
package cannon
import (
"context"
"errors"
"testing"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
)
var (
mockPreimageOracleAddress = common.HexToAddress("0x12345")
mockSendError = errors.New("mock send error")
)
type mockTxManager struct {
from common.Address
sent []txmgr.TxCandidate
failedSends int
sendFails bool
}
func (m *mockTxManager) Send(ctx context.Context, candidate txmgr.TxCandidate) (*ethtypes.Receipt, error) {
m.sent = append(m.sent, candidate)
if m.sendFails {
m.failedSends++
return nil, mockSendError
}
return &ethtypes.Receipt{
Type: ethtypes.LegacyTxType,
PostState: []byte{},
CumulativeGasUsed: 0,
Status: ethtypes.ReceiptStatusSuccessful,
}, nil
}
func (m *mockTxManager) BlockNumber(ctx context.Context) (uint64, error) {
panic("not implemented")
}
func (m *mockTxManager) From() common.Address {
return m.from
}
func newTestCannonUpdater(t *testing.T, sendFails bool) (*cannonUpdater, *mockTxManager, *mockGameContract) {
logger := testlog.Logger(t, log.LvlInfo)
txMgr := &mockTxManager{
from: common.HexToAddress("0x1234"),
sendFails: sendFails,
}
gameContract := &mockGameContract{}
updater, err := NewOracleUpdaterWithOracle(logger, txMgr, gameContract, mockPreimageOracleAddress)
require.NoError(t, err)
return updater, txMgr, gameContract
}
// TestCannonUpdater_UpdateOracle tests the [cannonUpdater]
// UpdateOracle function.
func TestCannonUpdater_UpdateOracle(t *testing.T) {
t.Run("local_succeeds", func(t *testing.T) {
updater, mockTxMgr, gameContract := newTestCannonUpdater(t, false)
gameContract.tx = txmgr.TxCandidate{
TxData: []byte{5, 6, 7, 8},
}
require.NoError(t, updater.UpdateOracle(context.Background(), &types.PreimageOracleData{
IsLocal: true,
LocalContext: 3,
OracleKey: common.Hash{0xaa}.Bytes(),
OracleData: common.Hex2Bytes("cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"),
}))
require.Len(t, mockTxMgr.sent, 1)
require.Equal(t, gameContract.tx, mockTxMgr.sent[0])
})
t.Run("local_fails", func(t *testing.T) {
updater, mockTxMgr, gameContract := newTestCannonUpdater(t, true)
gameContract.tx = txmgr.TxCandidate{
TxData: []byte{5, 6, 7, 8},
}
require.Error(t, updater.UpdateOracle(context.Background(), &types.PreimageOracleData{
IsLocal: true,
LocalContext: 3,
OracleKey: common.Hash{0xaa}.Bytes(),
OracleData: common.Hex2Bytes("cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"),
}))
require.Len(t, mockTxMgr.sent, 1)
require.Equal(t, gameContract.tx, mockTxMgr.sent[0])
require.Equal(t, 1, mockTxMgr.failedSends)
})
t.Run("global_succeeds", func(t *testing.T) {
updater, mockTxMgr, _ := newTestCannonUpdater(t, false)
require.NoError(t, updater.UpdateOracle(context.Background(), &types.PreimageOracleData{
IsLocal: false,
OracleKey: common.Hash{0xaa}.Bytes(),
OracleData: common.Hex2Bytes("cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"),
}))
require.Len(t, mockTxMgr.sent, 1)
require.Equal(t, mockPreimageOracleAddress, *mockTxMgr.sent[0].To)
})
t.Run("local_fails", func(t *testing.T) {
updater, mockTxMgr, _ := newTestCannonUpdater(t, true)
require.Error(t, updater.UpdateOracle(context.Background(), &types.PreimageOracleData{
IsLocal: false,
OracleKey: common.Hash{0xaa}.Bytes(),
OracleData: common.Hex2Bytes("cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"),
}))
require.Len(t, mockTxMgr.sent, 1)
require.Equal(t, mockPreimageOracleAddress, *mockTxMgr.sent[0].To)
require.Equal(t, 1, mockTxMgr.failedSends)
})
}
// TestCannonUpdater_BuildGlobalOracleData tests the [cannonUpdater]
// builds a valid tx candidate for a global oracle update.
func TestCannonUpdater_BuildGlobalOracleData(t *testing.T) {
updater, _, _ := newTestCannonUpdater(t, false)
oracleData := &types.PreimageOracleData{
OracleKey: common.Hex2Bytes("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
OracleData: common.Hex2Bytes("cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"),
OracleOffset: 7,
}
txData, err := updater.BuildGlobalOracleData(oracleData)
require.NoError(t, err)
var loadKeccak256PreimagePartBytes4 = crypto.Keccak256([]byte("loadKeccak256PreimagePart(uint256,bytes)"))[:4]
// Pack the tx data manually.
var expected []byte
expected = append(expected, loadKeccak256PreimagePartBytes4...)
expected = append(expected, common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000007")...)
expected = append(expected, common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040")...)
expected = append(expected, common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000018")...)
expected = append(expected, common.Hex2Bytes("cccccccccccccccccccccccccccccccccccccccccccccccc0000000000000000")...)
require.Equal(t, expected, txData)
}
type mockGameContract struct {
tx txmgr.TxCandidate
err error
}
func (m *mockGameContract) VMAddr(_ context.Context) (common.Address, error) {
return common.Address{0xcc}, nil
}
func (m *mockGameContract) AddLocalDataTx(_ *types.PreimageOracleData) (txmgr.TxCandidate, error) {
return m.tx, m.err
}
......@@ -51,12 +51,6 @@ type StepCallData struct {
Proof []byte
}
// OracleUpdater is a generic interface for updating oracles.
type OracleUpdater interface {
// UpdateOracle updates the oracle with the given data.
UpdateOracle(ctx context.Context, data *PreimageOracleData) error
}
// TraceAccessor defines an interface to request data from a TraceProvider with additional context for the game position.
// This can be used to implement split games where lower layers of the game may have different values depending on claims
// at higher levels in the game.
......
......@@ -19,6 +19,7 @@ import (
)
type expectedCall struct {
to common.Address
block batching.Block
args []interface{}
packedArgs []byte
......@@ -26,38 +27,49 @@ type expectedCall struct {
}
func (e *expectedCall) String() string {
return fmt.Sprintf("{block: %v, args: %v, outputs: %v}", e.block, e.args, e.outputs)
return fmt.Sprintf("{to: %v, block: %v, args: %v, outputs: %v}", e.to, e.block, e.args, e.outputs)
}
type AbiBasedRpc struct {
t *testing.T
abi *abi.ABI
addr common.Address
abis map[common.Address]*abi.ABI
expectedCalls map[string][]*expectedCall
}
func NewAbiBasedRpc(t *testing.T, contractAbi *abi.ABI, addr common.Address) *AbiBasedRpc {
func NewAbiBasedRpc(t *testing.T, to common.Address, contractAbi *abi.ABI) *AbiBasedRpc {
abis := make(map[common.Address]*abi.ABI)
abis[to] = contractAbi
return &AbiBasedRpc{
t: t,
abi: contractAbi,
addr: addr,
abis: abis,
expectedCalls: make(map[string][]*expectedCall),
}
}
func (l *AbiBasedRpc) SetResponse(method string, block batching.Block, expected []interface{}, output []interface{}) {
func (l *AbiBasedRpc) AddContract(to common.Address, contractAbi *abi.ABI) {
l.abis[to] = contractAbi
}
func (l *AbiBasedRpc) abi(to common.Address) *abi.ABI {
abi, ok := l.abis[to]
require.Truef(l.t, ok, "Missing ABI for %v", to)
return abi
}
func (l *AbiBasedRpc) SetResponse(to common.Address, method string, block batching.Block, expected []interface{}, output []interface{}) {
if expected == nil {
expected = []interface{}{}
}
if output == nil {
output = []interface{}{}
}
abiMethod, ok := l.abi.Methods[method]
abiMethod, ok := l.abi(to).Methods[method]
require.Truef(l.t, ok, "No method: %v", method)
packedArgs, err := abiMethod.Inputs.Pack(expected...)
require.NoErrorf(l.t, err, "Invalid expected arguments for method %v: %v", method, expected)
l.expectedCalls[method] = append(l.expectedCalls[method], &expectedCall{
to: to,
block: block,
args: expected,
packedArgs: packedArgs,
......@@ -75,8 +87,8 @@ func (l *AbiBasedRpc) BatchCallContext(ctx context.Context, b []rpc.BatchElem) e
}
func (l *AbiBasedRpc) VerifyTxCandidate(candidate txmgr.TxCandidate) {
require.EqualValues(l.t, &l.addr, candidate.To, "Incorrect To address")
l.findExpectedCall(candidate.TxData, batching.BlockLatest.ArgValue())
require.NotNil(l.t, candidate.To)
l.findExpectedCall(*candidate.To, candidate.TxData, batching.BlockLatest.ArgValue())
}
func (l *AbiBasedRpc) CallContext(_ context.Context, out interface{}, method string, args ...interface{}) error {
......@@ -85,11 +97,13 @@ func (l *AbiBasedRpc) CallContext(_ context.Context, out interface{}, method str
actualBlockRef := args[1]
callOpts, ok := args[0].(map[string]any)
require.True(l.t, ok)
require.Equal(l.t, &l.addr, callOpts["to"])
to, ok := callOpts["to"].(*common.Address)
require.True(l.t, ok)
require.NotNil(l.t, to)
data, ok := callOpts["input"].(hexutil.Bytes)
require.True(l.t, ok)
call, abiMethod := l.findExpectedCall(data, actualBlockRef)
call, abiMethod := l.findExpectedCall(*to, data, actualBlockRef)
output, err := abiMethod.Outputs.Pack(call.outputs...)
require.NoErrorf(l.t, err, "Invalid outputs for method %v: %v", abiMethod.Name, call.outputs)
......@@ -102,9 +116,9 @@ func (l *AbiBasedRpc) CallContext(_ context.Context, out interface{}, method str
return nil
}
func (l *AbiBasedRpc) findExpectedCall(data []byte, actualBlockRef interface{}) (*expectedCall, *abi.Method) {
func (l *AbiBasedRpc) findExpectedCall(to common.Address, data []byte, actualBlockRef interface{}) (*expectedCall, *abi.Method) {
abiMethod, err := l.abi.MethodById(data[0:4])
abiMethod, err := l.abi(to).MethodById(data[0:4])
require.NoError(l.t, err)
argData := data[4:]
......@@ -116,11 +130,14 @@ func (l *AbiBasedRpc) findExpectedCall(data []byte, actualBlockRef interface{})
require.Truef(l.t, ok, "Unexpected call to %v", abiMethod.Name)
var call *expectedCall
for _, candidate := range expectedCalls {
if slices.Equal(candidate.packedArgs, argData) && assert.ObjectsAreEqualValues(candidate.block.ArgValue(), actualBlockRef) {
if to == candidate.to &&
slices.Equal(candidate.packedArgs, argData) &&
assert.ObjectsAreEqualValues(candidate.block.ArgValue(), actualBlockRef) {
call = candidate
break
}
}
require.NotNilf(l.t, call, "No expected calls to %v at block %v with arguments: %v\nExpected calls: %v", abiMethod.Name, actualBlockRef, args, expectedCalls)
require.NotNilf(l.t, call, "No expected calls to %v at block %v with to: %v, arguments: %v\nExpected calls: %v",
to, abiMethod.Name, actualBlockRef, args, expectedCalls)
return call, abiMethod
}
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