Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

validation: decouple GasToken tests #803

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 0 additions & 90 deletions validation/gas-token_test.go

This file was deleted.

102 changes: 102 additions & 0 deletions validation/gas_token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package validation

import (
"context"
"errors"
"fmt"
"strings"
"testing"

"github.com/ethereum-optimism/superchain-registry/superchain"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/stretchr/testify/require"
)

func testGasToken(t *testing.T, chain *superchain.ChainConfig) {
l1Client, err := ethclient.Dial(superchain.Superchains[chain.Superchain].Config.L1.PublicRPC)
require.NoError(t, err, "Failed to connect to L1 EthClient at RPC url %s", superchain.Superchains[chain.Superchain].Config.L1.PublicRPC)
defer l1Client.Close()

l2Client, err := ethclient.Dial(chain.PublicRPC)
bitwiseguy marked this conversation as resolved.
Show resolved Hide resolved
require.NoError(t, err, "Failed to connect to L2 EthClient at RPC url %s", chain.PublicRPC)
defer l2Client.Close()

err = CheckGasToken(chain, l1Client, l2Client)
require.NoError(t, err)
}

func CheckGasToken(chain *superchain.ChainConfig, l1Client EthClient, l2Client EthClient) error {
weth9PredeployAddress := superchain.MustHexToAddress("0x4200000000000000000000000000000000000006")
want := "0000000000000000000000000000000000000000000000000000000000000020" + // offset
"000000000000000000000000000000000000000000000000000000000000000d" + // length
"5772617070656420457468657200000000000000000000000000000000000000" // "Wrapped Ether" padded to 32 bytes
gotName, err := getHexString("name()", weth9PredeployAddress, l2Client)
if err != nil {
return err
}
if want != gotName {
return fmt.Errorf("predeploy WETH9.name(): want=%s, got=%s", want, gotName)
}

l1BlockPredeployAddress := superchain.MustHexToAddress("0x4200000000000000000000000000000000000015")
isCustomGasToken, err := getBool("isCustomGasToken()", l1BlockPredeployAddress, l2Client)
if err != nil && !strings.Contains(err.Error(), "execution reverted") {
// Pre: reverting is acceptable
return err
} else {
// Post: must be set to false
if isCustomGasToken {
return fmt.Errorf("L1Block.isCustomGasToken() must return false")
}
}

isCustomGasToken, err = getBool("isCustomGasToken()", superchain.Addresses[chain.ChainID].SystemConfigProxy, l1Client)
if err != nil && !strings.Contains(err.Error(), "execution reverted") {
// Pre: reverting is acceptable
return err
} else {
// Post: must be set to false
if isCustomGasToken {
return fmt.Errorf("SystemConfigProxy.isCustomGasToken() must return false")
}
}
return nil
}

func getBytes(method string, contractAddress superchain.Address, client EthClient) ([]byte, error) {
bitwiseguy marked this conversation as resolved.
Show resolved Hide resolved
addr := (common.Address(contractAddress))
callMsg := ethereum.CallMsg{
To: &addr,
Data: crypto.Keccak256([]byte(method))[:4],
}

callContract := func(msg ethereum.CallMsg) ([]byte, error) {
return client.CallContract(context.Background(), msg, nil)
}

return Retry(callContract)(callMsg)
}

func getHexString(method string, contractAddress superchain.Address, client EthClient) (string, error) {
bitwiseguy marked this conversation as resolved.
Show resolved Hide resolved
result, err := getBytes(method, contractAddress, client)
return common.Bytes2Hex(result), err
}

func getBool(method string, contractAddress superchain.Address, client EthClient) (bool, error) {
result, err := getBytes(method, contractAddress, client)
if err != nil {
return false, err
}

switch common.HexToHash(string(result)) {
case common.Hash{1}:
return true, nil
case common.Hash{}:
return false, nil
default:
return false, errors.New("unexpected non-bool return value")
}
}
120 changes: 120 additions & 0 deletions validation/gas_token_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package validation

import (
"errors"
"math/big"
"testing"

"github.com/ethereum-optimism/superchain-registry/superchain"
"github.com/ethereum-optimism/superchain-registry/validation/testutils"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)

func TestGetBytes_Success(t *testing.T) {
t.Parallel()

mockClient := &testutils.MockEthClient{}
expected := []byte{0xde, 0xad, 0xbe, 0xef}
mockClient.On("CallContract", mock.Anything, mock.Anything, (*big.Int)(nil)).
Return(expected, nil).
Once()

result, err := getBytes("myMethod()", superchain.Address{}, mockClient)
require.NoError(t, err)
require.Equal(t, expected, result)

mockClient.AssertExpectations(t)
}

func TestGetBytes_Error(t *testing.T) {
t.Parallel()

mockClient := &testutils.MockEthClient{}
mockClient.On("CallContract", mock.Anything, mock.Anything, (*big.Int)(nil)).
Return([]byte{}, errors.New("some call error")).
Times(DefaultMaxRetries)

_, err := getBytes("failingMethod()", superchain.Address{}, mockClient)
require.Error(t, err)
require.Contains(t, err.Error(), "some call error")

mockClient.AssertExpectations(t)
}

func TestGetHexString_Success(t *testing.T) {
t.Parallel()

mockClient := &testutils.MockEthClient{}
expected := []byte{0x00, 0x11, 0x22, 0x33}
mockClient.On("CallContract", mock.Anything, mock.Anything, (*big.Int)(nil)).
Return(expected, nil).
Once()

hexVal, err := getHexString("hexMethod()", superchain.Address{}, mockClient)
require.NoError(t, err)
require.Equal(t, "00112233", hexVal)

mockClient.AssertExpectations(t)
}

func TestGetHexString_Error(t *testing.T) {
t.Parallel()

mockClient := &testutils.MockEthClient{}
mockClient.On("CallContract", mock.Anything, mock.Anything, (*big.Int)(nil)).
Return([]byte{}, errors.New("getHexString error")).
Times(DefaultMaxRetries)

_, err := getHexString("hexMethod()", superchain.Address{}, mockClient)
require.Error(t, err)
require.Contains(t, err.Error(), "getHexString error")

mockClient.AssertExpectations(t)
}

func TestGetBool_True(t *testing.T) {
t.Parallel()

// Returning bytes that correspond to `true` value
mockClient := &testutils.MockEthClient{}
mockClient.On("CallContract", mock.Anything, mock.Anything, (*big.Int)(nil)).
Return([]byte("0x0100000000000000000000000000000000000000000000000000000000000000"), nil).
Once()

val, err := getBool("boolMethod()", superchain.Address{}, mockClient)
require.NoError(t, err)
require.True(t, val)

mockClient.AssertExpectations(t)
}

func TestGetBool_False(t *testing.T) {
t.Parallel()

mockClient := &testutils.MockEthClient{}
mockClient.On("CallContract", mock.Anything, mock.Anything, (*big.Int)(nil)).
Return([]byte(""), nil).
Once()

val, err := getBool("boolMethod()", superchain.Address{}, mockClient)
require.NoError(t, err)
require.False(t, val)

mockClient.AssertExpectations(t)
}

func TestGetBool_ErrorUnexpectedValue(t *testing.T) {
t.Parallel()

mockClient := &testutils.MockEthClient{}
mockClient.On("CallContract", mock.Anything, mock.Anything, (*big.Int)(nil)).
Return([]byte("0xabcdef"), nil).
Once()

_, err := getBool("boolMethod()", superchain.Address{}, mockClient)
require.Error(t, err)
require.Contains(t, err.Error(), "unexpected non-bool return value")

mockClient.AssertExpectations(t)
}
1 change: 1 addition & 0 deletions validation/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ require (
github.com/rs/cors v1.11.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/supranational/blst v0.3.13 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
Expand Down
18 changes: 18 additions & 0 deletions validation/testutils/mock_ethclient.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package testutils

import (
"context"
"math/big"

"github.com/ethereum/go-ethereum"
"github.com/stretchr/testify/mock"
)

type MockEthClient struct {
mock.Mock
}

func (m *MockEthClient) CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
args := m.Called(ctx, msg, blockNumber)
return args.Get(0).([]byte), args.Error(1)
}
5 changes: 5 additions & 0 deletions validation/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,14 @@ import (

"github.com/ethereum-optimism/optimism/op-service/retry"
"github.com/ethereum-optimism/superchain-registry/superchain"
"github.com/ethereum/go-ethereum"
"github.com/stretchr/testify/assert"
)

type EthClient interface {
CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error)
}

// isBigIntWithinBounds returns true if actual is within bounds, where the bounds are [lower bound, upper bound] and are inclusive.
var isBigIntWithinBounds = func(actual *big.Int, bounds [2]*big.Int) bool {
if (bounds[1].Cmp(bounds[0])) < 0 {
Expand Down