Commit 6436a1e2 authored by Adrian Sutton's avatar Adrian Sutton

Merge branch 'develop' into aj/register-output-game

parents c39737dc bfb62726
......@@ -1522,7 +1522,7 @@ workflows:
- cannon-build-test-vectors
- check-values-match:
pattern_file1: "uint8 internal constant INITIALIZER ="
pattern_file2: "const initializedValue ="
pattern_file2: "const InitializedValue ="
file1_path: "packages/contracts-bedrock/src/libraries/Constants.sol"
file2_path: "op-chain-ops/genesis/config.go"
release:
......@@ -1827,6 +1827,7 @@ workflows:
name: chain-mon-docker-publish
docker_name: chain-mon
docker_tags: <<pipeline.git.revision>>,<<pipeline.git.branch>>
resource_class: xlarge
publish: true
context:
- oplabs-gcr
......
b205b6add562c778206a9edba1c0676c04a709b1
1a2e2e071ef0a1b6f41fdcba773b04c30498752f
module.exports = {
$schema: 'http://json.schemastore.org/prettierrc',
plugins: ['prettier-plugin-solidity'],
plugins: [],
trailingComma: 'es5',
tabWidth: 2,
semi: false,
singleQuote: true,
arrowParens: 'always',
overrides: [
{
files: '*.sol',
options: {
// These options are native to Prettier.
printWidth: 100,
tabWidth: 4,
useTabs: false,
singleQuote: false,
bracketSpacing: true,
// These options are specific to the Solidity Plugin
explicitTypes: 'always',
compiler: '>=0.8.15',
},
},
],
overrides: [],
}
......@@ -7,12 +7,14 @@ import (
"fmt"
"math/big"
"os"
"strings"
"golang.org/x/sync/errgroup"
"github.com/mattn/go-isatty"
"github.com/urfave/cli/v2"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
......@@ -24,7 +26,15 @@ import (
"github.com/ethereum/go-ethereum/log"
)
var defaultCrossDomainMessageSender = common.HexToAddress("0x000000000000000000000000000000000000dead")
var (
defaultCrossDomainMessageSender = common.HexToAddress("0x000000000000000000000000000000000000dead")
// errInvalidInitialized represents when the initialized value is not set to the expected value.
// This is an assertion on `_initialized`. We do not care about the value of `_initializing`.
errInvalidInitialized = errors.New("invalid initialized value")
// errAlreadyInitialized represents a revert from when a contract is already initialized.
// This error is used to assert with `eth_call` on contracts that are `Initializable`
errAlreadyInitialized = errors.New("Initializable: contract is already initialized")
)
// Default script for checking that L2 has been configured correctly. This should be extended in the future
// to pull in L1 deploy artifacts and assert that the L2 state is consistent with the L1 state.
......@@ -138,7 +148,7 @@ func checkPredeployConfig(client *ethclient.Client, name string) error {
return err
}
if impl != standardImpl {
log.Warn("%s does not have the standard implementation", name)
log.Warn(name + " does not have the standard implementation")
}
implCode, err := client.CodeAt(context.Background(), impl, nil)
if err != nil {
......@@ -151,6 +161,8 @@ func checkPredeployConfig(client *ethclient.Client, name string) error {
})
// Ensure that the code is set to the proxy bytecode as expected
// This will not work against production networks where the bytecode
// has deviated from the current bytecode. We need a more reliable way to check for this.
g.Go(func() error {
proxyCode, err := client.CodeAt(context.Background(), p, nil)
if err != nil {
......@@ -172,97 +184,97 @@ func checkPredeployConfig(client *ethclient.Client, name string) error {
switch p {
case predeploys.LegacyMessagePasserAddr:
if err := checkLegacyMessagePasser(p, client); err != nil {
return err
return fmt.Errorf("LegacyMessagePasser: %w", err)
}
case predeploys.DeployerWhitelistAddr:
if err := checkDeployerWhitelist(p, client); err != nil {
return err
return fmt.Errorf("DeployerWhiteList: %w", err)
}
case predeploys.L2CrossDomainMessengerAddr:
if err := checkL2CrossDomainMessenger(p, client); err != nil {
return err
return fmt.Errorf("L2CrossDomainMessenger: %w", err)
}
case predeploys.GasPriceOracleAddr:
if err := checkGasPriceOracle(p, client); err != nil {
return err
return fmt.Errorf("GasPriceOracle: %w", err)
}
case predeploys.L2StandardBridgeAddr:
if err := checkL2StandardBridge(p, client); err != nil {
return err
return fmt.Errorf("L2StandardBridge: %w", err)
}
case predeploys.SequencerFeeVaultAddr:
if err := checkSequencerFeeVault(p, client); err != nil {
return err
return fmt.Errorf("SequencerFeeVault: %w", err)
}
case predeploys.OptimismMintableERC20FactoryAddr:
if err := checkOptimismMintableERC20Factory(p, client); err != nil {
return err
return fmt.Errorf("OptimismMintableERC20Factory: %w", err)
}
case predeploys.L1BlockNumberAddr:
if err := checkL1BlockNumber(p, client); err != nil {
return err
return fmt.Errorf("L1BlockNumber: %w", err)
}
case predeploys.L1BlockAddr:
if err := checkL1Block(p, client); err != nil {
return err
return fmt.Errorf("L1Block: %w", err)
}
case predeploys.WETH9Addr:
if err := checkWETH9(p, client); err != nil {
return err
return fmt.Errorf("WETH9: %w", err)
}
case predeploys.GovernanceTokenAddr:
if err := checkGovernanceToken(p, client); err != nil {
return err
return fmt.Errorf("GovernanceToken: %w", err)
}
case predeploys.L2ERC721BridgeAddr:
if err := checkL2ERC721Bridge(p, client); err != nil {
return err
return fmt.Errorf("L2ERC721Bridge: %w", err)
}
case predeploys.OptimismMintableERC721FactoryAddr:
if err := checkOptimismMintableERC721Factory(p, client); err != nil {
return err
return fmt.Errorf("OptimismMintableERC721Factory: %w", err)
}
case predeploys.ProxyAdminAddr:
if err := checkProxyAdmin(p, client); err != nil {
return err
return fmt.Errorf("ProxyAdmin: %w", err)
}
case predeploys.BaseFeeVaultAddr:
if err := checkBaseFeeVault(p, client); err != nil {
return err
return fmt.Errorf("BaseFeeVault: %w", err)
}
case predeploys.L1FeeVaultAddr:
if err := checkL1FeeVault(p, client); err != nil {
return err
return fmt.Errorf("L1FeeVault: %w", err)
}
case predeploys.L2ToL1MessagePasserAddr:
if err := checkL2ToL1MessagePasser(p, client); err != nil {
return err
return fmt.Errorf("L2ToL1MessagePasser: %w", err)
}
case predeploys.SchemaRegistryAddr:
if err := checkSchemaRegistry(p, client); err != nil {
return err
return fmt.Errorf("SchemaRegistry: %w", err)
}
case predeploys.EASAddr:
if err := checkEAS(p, client); err != nil {
return err
return fmt.Errorf("EAS: %w", err)
}
}
return nil
......@@ -429,6 +441,21 @@ func checkL2ERC721Bridge(addr common.Address, client *ethclient.Client) error {
return err
}
log.Info("L2ERC721Bridge", "_initialized", initialized)
if initialized.Uint64() != genesis.InitializedValue {
return fmt.Errorf("%w: %s", errInvalidInitialized, initialized)
}
abi, err := bindings.L2ERC721BridgeMetaData.GetAbi()
if err != nil {
return err
}
calldata, err := abi.Pack("initialize")
if err != nil {
return err
}
if err := checkAlreadyInitialized(addr, calldata, client); err != nil {
return err
}
initializing, err := getInitializing("L2ERC721Bridge", addr, client)
if err != nil {
......@@ -566,6 +593,21 @@ func checkOptimismMintableERC20Factory(addr common.Address, client *ethclient.Cl
return err
}
log.Info("OptimismMintableERC20Factory", "_initialized", initialized)
if initialized.Uint64() != genesis.InitializedValue {
return fmt.Errorf("%w: %s", errInvalidInitialized, initialized)
}
abi, err := bindings.OptimismMintableERC20FactoryMetaData.GetAbi()
if err != nil {
return err
}
calldata, err := abi.Pack("initialize", common.Address{})
if err != nil {
return err
}
if err := checkAlreadyInitialized(addr, calldata, client); err != nil {
return err
}
initializing, err := getInitializing("OptimismMintableERC20Factory", addr, client)
if err != nil {
......@@ -647,6 +689,21 @@ func checkL2StandardBridge(addr common.Address, client *ethclient.Client) error
return err
}
log.Info("L2StandardBridge", "_initialized", initialized)
if initialized.Uint64() != genesis.InitializedValue {
return fmt.Errorf("%w: %s", errInvalidInitialized, initialized)
}
abi, err := bindings.L2StandardBridgeMetaData.GetAbi()
if err != nil {
return err
}
calldata, err := abi.Pack("initialize")
if err != nil {
return err
}
if err := checkAlreadyInitialized(addr, calldata, client); err != nil {
return err
}
initializing, err := getInitializing("L2StandardBridge", addr, client)
if err != nil {
......@@ -766,6 +823,21 @@ func checkL2CrossDomainMessenger(addr common.Address, client *ethclient.Client)
return err
}
log.Info("L2CrossDomainMessenger", "_initialized", initialized)
if initialized.Uint64() != genesis.InitializedValue {
return fmt.Errorf("%w: %s", errInvalidInitialized, initialized)
}
abi, err := bindings.L2CrossDomainMessengerMetaData.GetAbi()
if err != nil {
return err
}
calldata, err := abi.Pack("initialize")
if err != nil {
return err
}
if err := checkAlreadyInitialized(addr, calldata, client); err != nil {
return err
}
initializing, err := getInitializing("L2CrossDomainMessenger", addr, client)
if err != nil {
......@@ -918,3 +990,16 @@ func getStorageValue(name, entryName string, addr common.Address, client *ethcli
}
return slice[entry.Offset : entry.Offset+typ.NumberOfBytes], nil
}
// checkAlreadyInitialized will check if a contract has already been initialized
// based on error message string matching.
func checkAlreadyInitialized(addr common.Address, calldata []byte, client *ethclient.Client) error {
msg := ethereum.CallMsg{
To: &addr,
Data: calldata,
}
if _, err := client.CallContract(context.Background(), msg, nil); !strings.Contains(err.Error(), errAlreadyInitialized.Error()) {
return err
}
return nil
}
......@@ -29,7 +29,7 @@ import (
// initialzedValue represents the `Initializable` contract value. It should be kept in
// sync with the constant in `Constants.sol`.
// https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/libraries/Constants.sol
const initializedValue = 3
const InitializedValue = 3
var (
ErrInvalidDeployConfig = errors.New("invalid deploy config")
......@@ -726,13 +726,13 @@ func NewL2StorageConfig(config *DeployConfig, block *types.Block) (state.Storage
"msgNonce": 0,
}
storage["L2CrossDomainMessenger"] = state.StorageValues{
"_initialized": initializedValue,
"_initialized": InitializedValue,
"_initializing": false,
"xDomainMsgSender": "0x000000000000000000000000000000000000dEaD",
"msgNonce": 0,
}
storage["L2StandardBridge"] = state.StorageValues{
"_initialized": initializedValue,
"_initialized": InitializedValue,
"_initializing": false,
"messenger": predeploys.L2CrossDomainMessengerAddr,
}
......@@ -767,12 +767,12 @@ func NewL2StorageConfig(config *DeployConfig, block *types.Block) (state.Storage
}
storage["L2ERC721Bridge"] = state.StorageValues{
"messenger": predeploys.L2CrossDomainMessengerAddr,
"_initialized": initializedValue,
"_initialized": InitializedValue,
"_initializing": false,
}
storage["OptimismMintableERC20Factory"] = state.StorageValues{
"bridge": predeploys.L2StandardBridgeAddr,
"_initialized": initializedValue,
"_initialized": InitializedValue,
"_initializing": false,
}
return storage, nil
......
......@@ -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")
}
......@@ -27,6 +27,8 @@ const (
methodAttack = "attack"
methodDefend = "defend"
methodStep = "step"
methodAddLocalData = "addLocalData"
methodVM = "VM"
)
type FaultDisputeGameContract struct {
......@@ -144,6 +146,15 @@ func (f *FaultDisputeGameContract) GetAllClaims(ctx context.Context) ([]types.Cl
return claims, nil
}
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 nil, fmt.Errorf("failed to fetch VM addr: %w", err)
}
vmAddr := result.GetAddress(0)
return NewVMContract(vmAddr, f.multiCaller)
}
func (f *FaultDisputeGameContract) AttackTx(parentContractIndex uint64, pivot common.Hash) (txmgr.TxCandidate, error) {
call := f.contract.Call(methodAttack, new(big.Int).SetUint64(parentContractIndex), pivot)
return call.ToTxCandidate()
......@@ -195,6 +206,35 @@ func (f *FaultDisputeGameContract) resolveCall() *batching.ContractCall {
return f.contract.Call(methodResolve)
}
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(),
new(big.Int).SetUint64(data.LocalContext),
new(big.Int).SetUint64(uint64(data.OracleOffset)),
)
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
......@@ -79,7 +85,7 @@ func TestSimpleGetters(t *testing.T) {
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
......@@ -109,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())
......@@ -126,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{
......@@ -174,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)
}
......@@ -185,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)
......@@ -200,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)
......@@ -209,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)
......@@ -218,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)
......@@ -228,14 +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 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))},
......@@ -251,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)
}
......@@ -37,7 +37,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,
......@@ -51,6 +51,7 @@ func NewGamePlayer(
creator resourceCreator,
) (*GamePlayer, error) {
logger = logger.New("game", addr)
loader, err := contracts.NewFaultDisputeGameContract(addr, batching.NewMultiCaller(client.Client(), batching.DefaultBatchSize))
if err != nil {
return nil, fmt.Errorf("failed to create fault dispute game contract wrapper: %w", err)
......@@ -80,7 +81,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)
}
......@@ -94,7 +95,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,
......
......@@ -58,25 +58,22 @@ func registerOutputCannon(
cfg *config.Config,
txMgr txmgr.TxManager,
client *ethclient.Client) {
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)
// TODO(client-pod#43): Updated contracts should expose this as the pre and post state blocks
agreed, disputed, err := contract.GetProposals(ctx)
if err != nil {
return nil, nil, nil, err
return nil, nil, err
}
accessor, err := outputs.NewOutputCannonTraceAccessor(ctx, logger, cfg.RollupRpc, gameDepth, agreed.L2BlockNumber.Uint64(), disputed.L2BlockNumber.Uint64())
if err != nil {
return nil, nil, nil, err
}
updater, err := cannon.NewOracleUpdater(ctx, logger, txMgr, addr, client)
if err != nil {
return nil, nil, nil, fmt.Errorf("failed to create the cannon updater: %w", err)
return nil, nil, err
}
// TODO(client-pod#44): Validate absolute pre-state for split games
noopValidator := func(ctx context.Context, gameContract *contracts.FaultDisputeGameContract) error {
return nil
}
return accessor, updater, noopValidator, nil
return accessor, noopValidator, nil
}
playerCreator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) {
return NewGamePlayer(ctx, logger, m, cfg, dir, game.Proxy, txMgr, client, resourceCreator)
......@@ -92,19 +89,16 @@ func registerCannon(
cfg *config.Config,
txMgr txmgr.TxManager,
client *ethclient.Client) {
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, addr, client)
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) {
return NewGamePlayer(ctx, logger, m, cfg, dir, game.Proxy, txMgr, client, resourceCreator)
......@@ -120,13 +114,12 @@ func registerAlphabet(
cfg *config.Config,
txMgr txmgr.TxManager,
client *ethclient.Client) {
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) {
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) {
return NewGamePlayer(ctx, logger, m, cfg, dir, game.Proxy, txMgr, client, resourceCreator)
......
......@@ -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"
)
// 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
fdgAbi abi.ABI
fdgAddr common.Address
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,
fdgAddr common.Address,
client bind.ContractCaller,
) (*cannonUpdater, error) {
gameCaller, err := bindings.NewFaultDisputeGameCaller(fdgAddr, client)
if err != nil {
return nil, fmt.Errorf("create caller for game %v: %w", fdgAddr, err)
}
opts := &bind.CallOpts{Context: ctx}
vm, err := gameCaller.VM(opts)
if err != nil {
return nil, fmt.Errorf("failed to load VM address from game %v: %w", fdgAddr, 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 from game %v: %w", fdgAddr, err)
}
return NewOracleUpdaterWithOracle(logger, txMgr, fdgAddr, oracleAddr)
}
// NewOracleUpdaterWithOracle returns a new updater using a specified pre-image oracle address.
func NewOracleUpdaterWithOracle(
logger log.Logger,
txMgr txmgr.TxManager,
fdgAddr common.Address,
preimageOracleAddr common.Address,
) (*cannonUpdater, error) {
fdgAbi, err := bindings.FaultDisputeGameMetaData.GetAbi()
if err != nil {
return nil, err
}
preimageOracleAbi, err := bindings.PreimageOracleMetaData.GetAbi()
if err != nil {
return nil, err
}
return &cannonUpdater{
log: logger,
txMgr: txMgr,
fdgAbi: *fdgAbi,
fdgAddr: fdgAddr,
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 {
txData, err := u.BuildLocalOracleData(data)
if err != nil {
return fmt.Errorf("local oracle tx data build: %w", err)
}
return u.sendTxAndWait(ctx, u.fdgAddr, txData)
}
// 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, u.fdgAddr, txData)
}
// BuildLocalOracleData takes the local preimage key and data
// and creates tx data to load the key, data pair into the
// PreimageOracle contract from the FaultDisputeGame contract call.
func (u *cannonUpdater) BuildLocalOracleData(data *types.PreimageOracleData) ([]byte, error) {
return u.fdgAbi.Pack(
"addLocalData",
data.GetIdent(),
big.NewInt(int64(data.LocalContext)),
big.NewInt(int64(data.OracleOffset)),
)
}
// 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, addr common.Address, txData []byte) error {
receipt, err := u.txMgr.Send(ctx, txmgr.TxCandidate{
To: &addr,
TxData: txData,
GasLimit: 0,
})
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-bindings/bindings"
"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 (
mockFdgAddress = common.HexToAddress("0x1234")
mockPreimageOracleAddress = common.HexToAddress("0x12345")
mockSendError = errors.New("mock send error")
)
type mockTxManager struct {
from common.Address
sends int
failedSends int
sendFails bool
}
func (m *mockTxManager) Send(ctx context.Context, candidate txmgr.TxCandidate) (*ethtypes.Receipt, error) {
if m.sendFails {
m.failedSends++
return nil, mockSendError
}
m.sends++
return ethtypes.NewReceipt(
[]byte{},
false,
0,
), 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) {
logger := testlog.Logger(t, log.LvlInfo)
txMgr := &mockTxManager{
from: mockFdgAddress,
sendFails: sendFails,
}
updater, err := NewOracleUpdaterWithOracle(logger, txMgr, mockFdgAddress, mockPreimageOracleAddress)
require.NoError(t, err)
return updater, txMgr
}
// TestCannonUpdater_UpdateOracle tests the [cannonUpdater]
// UpdateOracle function.
func TestCannonUpdater_UpdateOracle(t *testing.T) {
t.Run("succeeds", func(t *testing.T) {
updater, mockTxMgr := newTestCannonUpdater(t, false)
require.NoError(t, updater.UpdateOracle(context.Background(), &types.PreimageOracleData{
OracleKey: common.Hash{0xaa}.Bytes(),
OracleData: common.Hex2Bytes("cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"),
}))
require.Equal(t, 1, mockTxMgr.sends)
})
t.Run("send fails", func(t *testing.T) {
updater, mockTxMgr := newTestCannonUpdater(t, true)
require.Error(t, updater.UpdateOracle(context.Background(), &types.PreimageOracleData{
OracleKey: common.Hash{0xaa}.Bytes(),
OracleData: common.Hex2Bytes("cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"),
}))
require.Equal(t, 1, mockTxMgr.failedSends)
})
}
// TestCannonUpdater_BuildLocalOracleData tests the [cannonUpdater]
// builds a valid tx candidate for a local oracle update.
func TestCannonUpdater_BuildLocalOracleData(t *testing.T) {
updater, _ := newTestCannonUpdater(t, false)
oracleData := &types.PreimageOracleData{
OracleKey: common.Hex2Bytes("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
OracleData: common.Hex2Bytes("cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"),
OracleOffset: 7,
}
txData, err := updater.BuildLocalOracleData(oracleData)
require.NoError(t, err)
fdgAbi, err := bindings.FaultDisputeGameMetaData.GetAbi()
require.NoError(t, err)
addLocalDataBytes4 := fdgAbi.Methods["addLocalData"].ID[:4]
// Pack the tx data manually.
var expected []byte
expected = append(expected, addLocalDataBytes4...)
expected = append(expected, common.Hex2Bytes("00aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")...)
expected = append(expected, common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000000")...)
expected = append(expected, common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000007")...)
require.Equal(t, expected, txData)
}
// 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)
}
......@@ -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
}
......@@ -8,7 +8,7 @@ RUN apt-get update && \
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > rustup.sh && \
chmod +x ./rustup.sh && \
./rustup.sh -y
sh rustup.sh -y
# Install nightly toolchain
RUN source $HOME/.profile && rustup update nightly
......@@ -24,6 +24,10 @@ COPY ./ops/scripts/install-foundry.sh ./install-foundry.sh
RUN curl -L https://foundry.paradigm.xyz | bash
RUN source $HOME/.profile && ./install-foundry.sh
RUN strip /root/.foundry/bin/forge && \
strip /root/.foundry/bin/cast && \
strip /root/.foundry/bin/anvil
FROM --platform=linux/amd64 ghcr.io/crytic/echidna/echidna:v2.0.4 as echidna-test
FROM --platform=linux/amd64 debian:bullseye-slim as go-build
......
# @eth-optimism/drippie-mon
## 0.5.3
### Patch Changes
- Updated dependencies [[`2534eabb5`](https://github.com/ethereum-optimism/optimism/commit/2534eabb50afe76f176407f83cc1f1c606e6de69)]:
- @eth-optimism/sdk@3.1.5
## 0.5.2
### Patch Changes
......
{
"private": true,
"name": "@eth-optimism/chain-mon",
"version": "0.5.2",
"version": "0.5.3",
"description": "[Optimism] Chain monitoring services",
"main": "dist/index",
"types": "dist/index",
......@@ -16,7 +16,6 @@
"dev:replica-mon": "tsx watch ./src/replica-mon/service.ts",
"dev:wallet-mon": "tsx watch ./src/wallet-mon/service.ts",
"dev:wd-mon": "tsx watch ./src/wd-mon/service.ts",
"start:balance-mon": "tsx ./src/balance-mon/service.ts",
"start:drippie-mon": "tsx ./src/drippie-mon/service.ts",
"start:fault-mon": "tsx ./src/fault-mon/service.ts",
......@@ -24,7 +23,6 @@
"start:replica-mon": "tsx ./src/replica-mon/service.ts",
"start:wallet-mon": "tsx ./src/wallet-mon/service.ts",
"start:wd-mon": "tsx ./src/wd-mon/service.ts",
"test": "hardhat test",
"test:coverage": "nyc hardhat test && nyc merge .nyc_output coverage.json",
"build": "tsc -p ./tsconfig.json",
......@@ -65,6 +63,6 @@
"@nomiclabs/hardhat-waffle": "^2.0.6",
"hardhat": "^2.19.0",
"ts-node": "^10.9.1",
"tsx": "^3.14.0"
"tsx": "^4.1.1"
}
}
......@@ -35,7 +35,7 @@
},
"dependencies": {
"@eth-optimism/core-utils": "workspace:*",
"@sentry/node": "^7.77.0",
"@sentry/node": "^7.80.0",
"bcfg": "^0.2.1",
"body-parser": "^1.20.2",
"commander": "^11.1.0",
......
......@@ -44,9 +44,9 @@
"lint": "pnpm lint:fix && pnpm lint:check"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^6.10.0",
"@typescript-eslint/parser": "^6.9.1",
"tsx": "^3.14.0",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"@typescript-eslint/parser": "^6.11.0",
"tsx": "^4.1.1",
"typescript": "^5.2.2"
}
}
# @eth-optimism/sdk
## 3.1.5
### Patch Changes
- [#8155](https://github.com/ethereum-optimism/optimism/pull/8155) [`2534eabb5`](https://github.com/ethereum-optimism/optimism/commit/2534eabb50afe76f176407f83cc1f1c606e6de69) Thanks [@smartcontracts](https://github.com/smartcontracts)! - Fixed bug with tokenBridge checks throwing
## 3.1.4
### Patch Changes
......
{
"name": "@eth-optimism/sdk",
"version": "3.1.4",
"version": "3.1.5",
"description": "[Optimism] Tools for working with Optimism",
"main": "dist/index",
"types": "dist/index",
......
......@@ -156,46 +156,33 @@ export class StandardBridgeAdapter implements IBridgeAdapter {
l1Token: AddressLike,
l2Token: AddressLike
): Promise<boolean> {
try {
const contract = new Contract(
toAddress(l2Token),
optimismMintableERC20.abi,
this.messenger.l2Provider
)
// Don't support ETH deposits or withdrawals via this bridge.
if (
hexStringEquals(toAddress(l1Token), ethers.constants.AddressZero) ||
hexStringEquals(toAddress(l2Token), predeploys.OVM_ETH)
) {
return false
}
// Make sure the L1 token matches.
const remoteL1Token = await contract.l1Token()
const contract = new Contract(
toAddress(l2Token),
optimismMintableERC20.abi,
this.messenger.l2Provider
)
// Don't support ETH deposits or withdrawals via this bridge.
if (
hexStringEquals(toAddress(l1Token), ethers.constants.AddressZero) ||
hexStringEquals(toAddress(l2Token), predeploys.OVM_ETH)
) {
return false
}
if (!hexStringEquals(remoteL1Token, toAddress(l1Token))) {
return false
}
// Make sure the L1 token matches.
const remoteL1Token = await contract.l1Token()
// Make sure the L2 bridge matches.
const remoteL2Bridge = await contract.l2Bridge()
if (!hexStringEquals(remoteL2Bridge, this.l2Bridge.address)) {
return false
}
if (!hexStringEquals(remoteL1Token, toAddress(l1Token))) {
return false
}
return true
} catch (err) {
// If the L2 token is not an L2StandardERC20, it may throw an error. If there's a call
// exception then we assume that the token is not supported. Other errors are thrown. Since
// the JSON-RPC API is not well-specified, we need to handle multiple possible error codes.
if (
!err?.message?.toString().includes('CALL_EXCEPTION') &&
!err?.stack?.toString().includes('execution reverted')
) {
console.error('Unexpected error when checking bridge', err)
}
// Make sure the L2 bridge matches.
const remoteL2Bridge = await contract.l2Bridge()
if (!hexStringEquals(remoteL2Bridge, this.l2Bridge.address)) {
return false
}
return true
}
public async approval(
......
......@@ -490,8 +490,17 @@ export class CrossChainMessenger {
): Promise<IBridgeAdapter> {
const bridges: IBridgeAdapter[] = []
for (const bridge of Object.values(this.bridges)) {
if (await bridge.supportsTokenPair(l1Token, l2Token)) {
bridges.push(bridge)
try {
if (await bridge.supportsTokenPair(l1Token, l2Token)) {
bridges.push(bridge)
}
} catch (err) {
if (
!err?.message?.toString().includes('CALL_EXCEPTION') &&
!err?.stack?.toString().includes('execution reverted')
) {
console.error('Unexpected error when checking bridge', err)
}
}
}
......
This diff is collapsed.
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