Commit a490ddea authored by protolambda's avatar protolambda

Merge branch 'develop' into indexer-lifecycle

parents b58f6b44 07d22c8f
......@@ -31,7 +31,7 @@ golang-docker:
# We don't use a buildx builder here, and just load directly into regular docker, for convenience.
GIT_COMMIT=$$(git rev-parse HEAD) \
GIT_DATE=$$(git show -s --format='%ct') \
IMAGE_TAGS=$$GIT_COMMIT,latest \
IMAGE_TAGS=$$(git rev-parse HEAD),latest \
docker buildx bake \
--progress plain \
--load \
......@@ -189,4 +189,3 @@ install-geth:
go install -v github.com/ethereum/go-ethereum/cmd/geth@$(shell cat .gethrc); \
echo "Installed geth!"; true)
.PHONY: install-geth
......@@ -288,21 +288,23 @@ def debug_dumpBlock(url):
def wait_for_rpc_server(url):
log.info(f'Waiting for RPC server at {url}')
conn = http.client.HTTPConnection(url)
headers = {'Content-type': 'application/json'}
body = '{"id":1, "jsonrpc":"2.0", "method": "eth_chainId", "params":[]}'
while True:
try:
conn = http.client.HTTPConnection(url)
conn.request('POST', '/', body, headers)
response = conn.getresponse()
conn.close()
if response.status < 300:
log.info(f'RPC server at {url} ready')
return
except Exception as e:
log.info(f'Waiting for RPC server at {url}')
time.sleep(1)
finally:
if conn:
conn.close()
CommandPreset = namedtuple('Command', ['name', 'args', 'cwd', 'timeout'])
......
......@@ -12,6 +12,7 @@ ignore:
- "op-bindings/bindings/*.go"
- "**/*.t.sol"
- "packages/contracts-bedrock/test/**/*.sol"
- "packages/contracts-bedrock/scripts/**/*.sol"
- "packages/contracts-bedrock/contracts/vendor/WETH9.sol"
- 'packages/contracts-bedrock/contracts/EAS/**/*.sol'
coverage:
......
......@@ -34,12 +34,12 @@ export interface WithdrawalItem {
from: string;
to: string;
transactionHash: string;
messageHash: string;
crossDomainMessageHash: string;
timestamp: number /* uint64 */;
l2BlockHash: string;
amount: string;
proofTransactionHash: string;
claimTransactionHash: string;
l1ProvenTxHash: string;
l1FinalizedTxHash: string;
l1TokenAddress: string;
l2TokenAddress: string;
}
......
......@@ -8,6 +8,7 @@ import (
"net/http"
"strconv"
"sync/atomic"
"time"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
......@@ -76,7 +77,7 @@ func (a *APIService) initFromConfig(ctx context.Context, cfg *Config) error {
if err := a.startMetricsServer(cfg.MetricsServer); err != nil {
return fmt.Errorf("failed to start metrics server: %w", err)
}
a.initRouter()
a.initRouter(cfg.HTTPServer)
if err := a.startServer(cfg.HTTPServer); err != nil {
return fmt.Errorf("failed to start API server: %w", err)
}
......@@ -133,7 +134,7 @@ func (a *APIService) initDB(ctx context.Context, connector DBConnector) error {
return nil
}
func (a *APIService) initRouter() {
func (a *APIService) initRouter(apiConfig config.ServerConfig) {
apiRouter := chi.NewRouter()
h := routes.NewRoutes(a.log, a.bv, apiRouter)
......@@ -141,6 +142,7 @@ func (a *APIService) initRouter() {
// (2) Inject routing middleware
apiRouter.Use(chiMetricsMiddleware(promRecorder))
apiRouter.Use(middleware.Timeout(time.Duration(apiConfig.WriteTimeout) * time.Second))
apiRouter.Use(middleware.Recoverer)
apiRouter.Use(middleware.Heartbeat(HealthPath))
......
......@@ -169,8 +169,8 @@ func TestL2BridgeWithdrawalsByAddressHandler(t *testing.T) {
assert.Equal(t, resp.Items[0].To, withdrawal.Tx.ToAddress.String())
assert.Equal(t, resp.Items[0].TransactionHash, common.HexToHash("0x789").String())
assert.Equal(t, resp.Items[0].Amount, withdrawal.Tx.Amount.String())
assert.Equal(t, resp.Items[0].ProofTransactionHash, common.HexToHash("0x123").String())
assert.Equal(t, resp.Items[0].ClaimTransactionHash, common.HexToHash("0x123").String())
assert.Equal(t, resp.Items[0].L1ProvenTxHash, common.HexToHash("0x123").String())
assert.Equal(t, resp.Items[0].L1FinalizedTxHash, common.HexToHash("0x123").String())
assert.Equal(t, resp.Items[0].L1TokenAddress, withdrawal.TokenPair.RemoteTokenAddress.String())
assert.Equal(t, resp.Items[0].L2TokenAddress, withdrawal.TokenPair.LocalTokenAddress.String())
assert.Equal(t, resp.Items[0].Timestamp, withdrawal.Tx.Timestamp)
......
package models
import (
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum/go-ethereum/common"
)
// DepositItem ... Deposit item model for API responses
type DepositItem struct {
Guid string `json:"guid"`
......@@ -23,18 +28,18 @@ type DepositResponse struct {
// WithdrawalItem ... Data model for API JSON response
type WithdrawalItem struct {
Guid string `json:"guid"`
From string `json:"from"`
To string `json:"to"`
TransactionHash string `json:"transactionHash"`
MessageHash string `json:"messageHash"`
Timestamp uint64 `json:"timestamp"`
L2BlockHash string `json:"l2BlockHash"`
Amount string `json:"amount"`
ProofTransactionHash string `json:"proofTransactionHash"`
ClaimTransactionHash string `json:"claimTransactionHash"`
L1TokenAddress string `json:"l1TokenAddress"`
L2TokenAddress string `json:"l2TokenAddress"`
Guid string `json:"guid"`
From string `json:"from"`
To string `json:"to"`
TransactionHash string `json:"transactionHash"`
CrossDomainMessageHash string `json:"crossDomainMessageHash"`
Timestamp uint64 `json:"timestamp"`
L2BlockHash string `json:"l2BlockHash"`
Amount string `json:"amount"`
L1ProvenTxHash string `json:"l1ProvenTxHash"`
L1FinalizedTxHash string `json:"l1FinalizedTxHash"`
L1TokenAddress string `json:"l1TokenAddress"`
L2TokenAddress string `json:"l2TokenAddress"`
}
// WithdrawalResponse ... Data model for API JSON response
......@@ -43,3 +48,38 @@ type WithdrawalResponse struct {
HasNextPage bool `json:"hasNextPage"`
Items []WithdrawalItem `json:"items"`
}
// FIXME make a pure function that returns a struct instead of newWithdrawalResponse
// newWithdrawalResponse ... Converts a database.L2BridgeWithdrawalsResponse to an api.WithdrawalResponse
func CreateWithdrawalResponse(withdrawals *database.L2BridgeWithdrawalsResponse) WithdrawalResponse {
items := make([]WithdrawalItem, len(withdrawals.Withdrawals))
for i, withdrawal := range withdrawals.Withdrawals {
cdh := withdrawal.L2BridgeWithdrawal.CrossDomainMessageHash
if cdh == nil { // Zero value indicates that the withdrawal didn't have a cross domain message
cdh = &common.Hash{0}
}
item := WithdrawalItem{
Guid: withdrawal.L2BridgeWithdrawal.TransactionWithdrawalHash.String(),
L2BlockHash: withdrawal.L2BlockHash.String(),
Timestamp: withdrawal.L2BridgeWithdrawal.Tx.Timestamp,
From: withdrawal.L2BridgeWithdrawal.Tx.FromAddress.String(),
To: withdrawal.L2BridgeWithdrawal.Tx.ToAddress.String(),
TransactionHash: withdrawal.L2TransactionHash.String(),
Amount: withdrawal.L2BridgeWithdrawal.Tx.Amount.String(),
CrossDomainMessageHash: cdh.String(),
L1ProvenTxHash: withdrawal.ProvenL1TransactionHash.String(),
L1FinalizedTxHash: withdrawal.FinalizedL1TransactionHash.String(),
L1TokenAddress: withdrawal.L2BridgeWithdrawal.TokenPair.RemoteTokenAddress.String(),
L2TokenAddress: withdrawal.L2BridgeWithdrawal.TokenPair.LocalTokenAddress.String(),
}
items[i] = item
}
return WithdrawalResponse{
Cursor: withdrawals.Cursor,
HasNextPage: withdrawals.HasNextPage,
Items: items,
}
}
package models_test
import (
"fmt"
"reflect"
"testing"
"github.com/ethereum-optimism/optimism/indexer/api/models"
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)
func TestCreateWithdrawal(t *testing.T) {
// (1) Create a dummy database response object
cdh := common.HexToHash("0x2")
dbWithdrawals := &database.L2BridgeWithdrawalsResponse{
Withdrawals: []database.L2BridgeWithdrawalWithTransactionHashes{
{
L2BridgeWithdrawal: database.L2BridgeWithdrawal{
TransactionWithdrawalHash: common.HexToHash("0x1"),
BridgeTransfer: database.BridgeTransfer{
CrossDomainMessageHash: &cdh,
Tx: database.Transaction{
FromAddress: common.HexToAddress("0x3"),
ToAddress: common.HexToAddress("0x4"),
Timestamp: 5,
},
TokenPair: database.TokenPair{
LocalTokenAddress: common.HexToAddress("0x6"),
RemoteTokenAddress: common.HexToAddress("0x7"),
},
},
},
},
},
}
// (2) Create and validate response object
response := models.CreateWithdrawalResponse(dbWithdrawals)
require.NotEmpty(t, response.Items)
require.Len(t, response.Items, 1)
// (3) Use reflection to check that all fields in WithdrawalItem are populated correctly
item := response.Items[0]
structType := reflect.TypeOf(item)
structVal := reflect.ValueOf(item)
fieldNum := structVal.NumField()
for i := 0; i < fieldNum; i++ {
field := structVal.Field(i)
fieldName := structType.Field(i).Name
isSet := field.IsValid() && !field.IsZero()
require.True(t, isSet, fmt.Sprintf("%s in not set", fieldName))
}
}
......@@ -4,38 +4,9 @@ import (
"net/http"
"github.com/ethereum-optimism/optimism/indexer/api/models"
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/go-chi/chi/v5"
)
// FIXME make a pure function that returns a struct instead of newWithdrawalResponse
// newWithdrawalResponse ... Converts a database.L2BridgeWithdrawalsResponse to an api.WithdrawalResponse
func newWithdrawalResponse(withdrawals *database.L2BridgeWithdrawalsResponse) models.WithdrawalResponse {
items := make([]models.WithdrawalItem, len(withdrawals.Withdrawals))
for i, withdrawal := range withdrawals.Withdrawals {
item := models.WithdrawalItem{
Guid: withdrawal.L2BridgeWithdrawal.TransactionWithdrawalHash.String(),
L2BlockHash: withdrawal.L2BlockHash.String(),
Timestamp: withdrawal.L2BridgeWithdrawal.Tx.Timestamp,
From: withdrawal.L2BridgeWithdrawal.Tx.FromAddress.String(),
To: withdrawal.L2BridgeWithdrawal.Tx.ToAddress.String(),
TransactionHash: withdrawal.L2TransactionHash.String(),
Amount: withdrawal.L2BridgeWithdrawal.Tx.Amount.String(),
ProofTransactionHash: withdrawal.ProvenL1TransactionHash.String(),
ClaimTransactionHash: withdrawal.FinalizedL1TransactionHash.String(),
L1TokenAddress: withdrawal.L2BridgeWithdrawal.TokenPair.RemoteTokenAddress.String(),
L2TokenAddress: withdrawal.L2BridgeWithdrawal.TokenPair.LocalTokenAddress.String(),
}
items[i] = item
}
return models.WithdrawalResponse{
Cursor: withdrawals.Cursor,
HasNextPage: withdrawals.HasNextPage,
Items: items,
}
}
// L2WithdrawalsHandler ... Handles /api/v0/withdrawals/{address} GET requests
func (h Routes) L2WithdrawalsHandler(w http.ResponseWriter, r *http.Request) {
addressValue := chi.URLParam(r, "address")
......@@ -69,7 +40,7 @@ func (h Routes) L2WithdrawalsHandler(w http.ResponseWriter, r *http.Request) {
h.logger.Error("Unable to read withdrawals from DB", "err", err.Error())
return
}
response := newWithdrawalResponse(withdrawals)
response := models.CreateWithdrawalResponse(withdrawals)
err = jsonResponse(w, response, http.StatusOK)
if err != nil {
......
......@@ -134,10 +134,11 @@ type DBConfig struct {
Password string `toml:"password"`
}
// Configures the a server
// Configures the server
type ServerConfig struct {
Host string `toml:"host"`
Port int `toml:"port"`
Host string `toml:"host"`
Port int `toml:"port"`
WriteTimeout int `toml:"timeout"`
}
// LoadConfig loads the `indexer.toml` config file from a given path
......
......@@ -29,6 +29,7 @@ name = "$INDEXER_DB_NAME"
[http]
host = "127.0.0.1"
port = 8080
timeout = 10
[metrics]
host = "127.0.0.1"
......
......@@ -290,8 +290,10 @@ func (bs *BatcherService) Stop(ctx context.Context) error {
bs.Log.Info("Stopping batcher")
var result error
if err := bs.driver.StopBatchSubmittingIfRunning(ctx); err != nil {
result = errors.Join(result, fmt.Errorf("failed to stop batch submitting: %w", err))
if bs.driver != nil {
if err := bs.driver.StopBatchSubmittingIfRunning(ctx); err != nil {
result = errors.Join(result, fmt.Errorf("failed to stop batch submitting: %w", err))
}
}
if bs.rpcServer != nil {
......@@ -328,7 +330,7 @@ func (bs *BatcherService) Stop(ctx context.Context) error {
if result == nil {
bs.stopped.Store(true)
bs.driver.Log.Info("Batch Submitter stopped")
bs.Log.Info("Batch Submitter stopped")
}
return result
}
......
......@@ -9,7 +9,7 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/solc"
)
const AlphabetVMStorageLayoutJSON = "{\"storage\":[{\"astId\":1000,\"contract\":\"test/FaultDisputeGame.t.sol:AlphabetVM\",\"label\":\"oracle\",\"offset\":0,\"slot\":\"0\",\"type\":\"t_contract(IPreimageOracle)1001\"}],\"types\":{\"t_contract(IPreimageOracle)1001\":{\"encoding\":\"inplace\",\"label\":\"contract IPreimageOracle\",\"numberOfBytes\":\"20\"}}}"
const AlphabetVMStorageLayoutJSON = "{\"storage\":[{\"astId\":1000,\"contract\":\"test/mocks/AlphabetVM.sol:AlphabetVM\",\"label\":\"oracle\",\"offset\":0,\"slot\":\"0\",\"type\":\"t_contract(IPreimageOracle)1001\"}],\"types\":{\"t_contract(IPreimageOracle)1001\":{\"encoding\":\"inplace\",\"label\":\"contract IPreimageOracle\",\"numberOfBytes\":\"20\"}}}"
var AlphabetVMStorageLayout = new(solc.StorageLayout)
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -112,7 +112,7 @@ func entrypoint(ctx *cli.Context) error {
name, _ := toDeployConfigName(chainConfig)
config, err := genesis.NewDeployConfigWithNetwork(name, deployConfig)
if err != nil {
log.Warn("Cannot find deploy config for network", "name", chainConfig.Name, "deploy-config-name", name, "path", deployConfig)
log.Warn("Cannot find deploy config for network", "name", chainConfig.Name, "deploy-config-name", name, "path", deployConfig, "err", err)
}
if config != nil {
......
......@@ -25,7 +25,7 @@ type Responder interface {
}
type ClaimLoader interface {
FetchClaims(ctx context.Context) ([]types.Claim, error)
GetAllClaims(ctx context.Context) ([]types.Claim, error)
}
type Agent struct {
......@@ -136,7 +136,7 @@ func (a *Agent) tryResolve(ctx context.Context) bool {
var errNoResolvableClaims = errors.New("no resolvable claims")
func (a *Agent) tryResolveClaims(ctx context.Context) error {
claims, err := a.loader.FetchClaims(ctx)
claims, err := a.loader.GetAllClaims(ctx)
if err != nil {
return fmt.Errorf("failed to fetch claims: %w", err)
}
......@@ -189,7 +189,7 @@ func (a *Agent) resolveClaims(ctx context.Context) error {
// newGameFromContracts initializes a new game state from the state in the contract
func (a *Agent) newGameFromContracts(ctx context.Context) (types.Game, error) {
claims, err := a.loader.FetchClaims(ctx)
claims, err := a.loader.GetAllClaims(ctx)
if err != nil {
return nil, fmt.Errorf("failed to fetch claims: %w", err)
}
......
......@@ -124,7 +124,7 @@ type stubClaimLoader struct {
claims []types.Claim
}
func (s *stubClaimLoader) FetchClaims(ctx context.Context) ([]types.Claim, error) {
func (s *stubClaimLoader) GetAllClaims(ctx context.Context) ([]types.Claim, error) {
s.callCount++
return s.claims, nil
}
......
package contracts
import (
"context"
"fmt"
"math/big"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum/go-ethereum/common"
)
const (
methodGameDuration = "GAME_DURATION"
methodMaxGameDepth = "MAX_GAME_DEPTH"
methodAbsolutePrestate = "ABSOLUTE_PRESTATE"
methodStatus = "status"
methodClaimCount = "claimDataLen"
methodClaim = "claimData"
)
type FaultDisputeGameContract struct {
multiCaller *batching.MultiCaller
contract *batching.BoundContract
}
func NewFaultDisputeGameContract(addr common.Address, caller *batching.MultiCaller) (*FaultDisputeGameContract, error) {
fdgAbi, err := bindings.FaultDisputeGameMetaData.GetAbi()
if err != nil {
return nil, fmt.Errorf("failed to load fault dispute game ABI: %w", err)
}
return &FaultDisputeGameContract{
multiCaller: caller,
contract: batching.NewBoundContract(fdgAbi, addr),
}, nil
}
func (f *FaultDisputeGameContract) GetGameDuration(ctx context.Context) (uint64, error) {
result, err := f.multiCaller.SingleCallLatest(ctx, f.contract.Call(methodGameDuration))
if err != nil {
return 0, fmt.Errorf("failed to fetch game duration: %w", err)
}
return result.GetUint64(0), nil
}
func (f *FaultDisputeGameContract) GetMaxGameDepth(ctx context.Context) (uint64, error) {
result, err := f.multiCaller.SingleCallLatest(ctx, f.contract.Call(methodMaxGameDepth))
if err != nil {
return 0, fmt.Errorf("failed to fetch max game depth: %w", err)
}
return result.GetBigInt(0).Uint64(), nil
}
func (f *FaultDisputeGameContract) GetAbsolutePrestateHash(ctx context.Context) (common.Hash, error) {
result, err := f.multiCaller.SingleCallLatest(ctx, f.contract.Call(methodAbsolutePrestate))
if err != nil {
return common.Hash{}, fmt.Errorf("failed to fetch absolute prestate hash: %w", err)
}
return result.GetHash(0), nil
}
func (f *FaultDisputeGameContract) GetStatus(ctx context.Context) (gameTypes.GameStatus, error) {
result, err := f.multiCaller.SingleCallLatest(ctx, f.contract.Call(methodStatus))
if err != nil {
return 0, fmt.Errorf("failed to fetch status: %w", err)
}
return gameTypes.GameStatusFromUint8(result.GetUint8(0))
}
func (f *FaultDisputeGameContract) GetClaimCount(ctx context.Context) (uint64, error) {
result, err := f.multiCaller.SingleCallLatest(ctx, f.contract.Call(methodClaimCount))
if err != nil {
return 0, fmt.Errorf("failed to fetch claim count: %w", err)
}
return result.GetBigInt(0).Uint64(), nil
}
func (f *FaultDisputeGameContract) GetClaim(ctx context.Context, idx uint64) (types.Claim, error) {
result, err := f.multiCaller.SingleCallLatest(ctx, f.contract.Call(methodClaim, new(big.Int).SetUint64(idx)))
if err != nil {
return types.Claim{}, fmt.Errorf("failed to fetch claim %v: %w", idx, err)
}
return f.decodeClaim(result, int(idx)), nil
}
func (f *FaultDisputeGameContract) GetAllClaims(ctx context.Context) ([]types.Claim, error) {
count, err := f.GetClaimCount(ctx)
if err != nil {
return nil, fmt.Errorf("failed to load claim count: %w", err)
}
calls := make([]*batching.ContractCall, count)
for i := uint64(0); i < count; i++ {
calls[i] = f.contract.Call(methodClaim, new(big.Int).SetUint64(i))
}
results, err := f.multiCaller.CallLatest(ctx, calls...)
if err != nil {
return nil, fmt.Errorf("failed to fetch claim data: %w", err)
}
var claims []types.Claim
for idx, result := range results {
claims = append(claims, f.decodeClaim(result, idx))
}
return claims, nil
}
func (f *FaultDisputeGameContract) decodeClaim(result *batching.CallResult, contractIndex int) types.Claim {
parentIndex := result.GetUint32(0)
countered := result.GetBool(1)
claim := result.GetHash(2)
position := result.GetBigInt(3)
clock := result.GetBigInt(4)
return types.Claim{
ClaimData: types.ClaimData{
Value: claim,
Position: types.NewPositionFromGIndex(position),
},
Countered: countered,
Clock: clock.Uint64(),
ContractIndex: contractIndex,
ParentContractIndex: int(parentIndex),
}
}
package contracts
import (
"context"
"math"
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
batchingTest "github.com/ethereum-optimism/optimism/op-service/sources/batching/test"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)
func TestSimpleGetters(t *testing.T) {
tests := []struct {
method string
args []interface{}
result interface{}
expected interface{} // Defaults to expecting the same as result
call func(game *FaultDisputeGameContract) (any, error)
}{
{
method: methodStatus,
result: types.GameStatusChallengerWon,
call: func(game *FaultDisputeGameContract) (any, error) {
return game.GetStatus(context.Background())
},
},
{
method: methodGameDuration,
result: uint64(5566),
call: func(game *FaultDisputeGameContract) (any, error) {
return game.GetGameDuration(context.Background())
},
},
{
method: methodMaxGameDepth,
result: big.NewInt(128),
expected: uint64(128),
call: func(game *FaultDisputeGameContract) (any, error) {
return game.GetMaxGameDepth(context.Background())
},
},
{
method: methodAbsolutePrestate,
result: common.Hash{0xab},
call: func(game *FaultDisputeGameContract) (any, error) {
return game.GetAbsolutePrestateHash(context.Background())
},
},
{
method: methodClaimCount,
result: big.NewInt(9876),
expected: uint64(9876),
call: func(game *FaultDisputeGameContract) (any, error) {
return game.GetClaimCount(context.Background())
},
},
}
for _, test := range tests {
test := test
t.Run(test.method, func(t *testing.T) {
stubRpc, game := setup(t)
stubRpc.SetResponse(test.method, nil, []interface{}{test.result})
status, err := test.call(game)
require.NoError(t, err)
expected := test.expected
if expected == nil {
expected = test.result
}
require.Equal(t, expected, status)
})
}
}
func TestGetClaim(t *testing.T) {
stubRpc, game := setup(t)
idx := big.NewInt(2)
parentIndex := uint32(1)
countered := true
value := common.Hash{0xab}
position := big.NewInt(2)
clock := big.NewInt(1234)
stubRpc.SetResponse(methodClaim, []interface{}{idx}, []interface{}{parentIndex, countered, value, position, clock})
status, err := game.GetClaim(context.Background(), idx.Uint64())
require.NoError(t, err)
require.Equal(t, faultTypes.Claim{
ClaimData: faultTypes.ClaimData{
Value: value,
Position: faultTypes.NewPositionFromGIndex(position),
},
Countered: true,
Clock: 1234,
ContractIndex: int(idx.Uint64()),
ParentContractIndex: 1,
}, status)
}
func TestGetAllClaims(t *testing.T) {
stubRpc, game := setup(t)
claim0 := faultTypes.Claim{
ClaimData: faultTypes.ClaimData{
Value: common.Hash{0xaa},
Position: faultTypes.NewPositionFromGIndex(big.NewInt(1)),
},
Countered: true,
Clock: 1234,
ContractIndex: 0,
ParentContractIndex: math.MaxUint32,
}
claim1 := faultTypes.Claim{
ClaimData: faultTypes.ClaimData{
Value: common.Hash{0xab},
Position: faultTypes.NewPositionFromGIndex(big.NewInt(2)),
},
Countered: true,
Clock: 4455,
ContractIndex: 1,
ParentContractIndex: 0,
}
claim2 := faultTypes.Claim{
ClaimData: faultTypes.ClaimData{
Value: common.Hash{0xbb},
Position: faultTypes.NewPositionFromGIndex(big.NewInt(6)),
},
Countered: false,
Clock: 7777,
ContractIndex: 2,
ParentContractIndex: 1,
}
expectedClaims := []faultTypes.Claim{claim0, claim1, claim2}
stubRpc.SetResponse(methodClaimCount, nil, []interface{}{big.NewInt(int64(len(expectedClaims)))})
for _, claim := range expectedClaims {
expectGetClaim(stubRpc, claim)
}
claims, err := game.GetAllClaims(context.Background())
require.NoError(t, err)
require.Equal(t, expectedClaims, claims)
}
func expectGetClaim(stubRpc *batchingTest.AbiBasedRpc, claim faultTypes.Claim) {
stubRpc.SetResponse(
methodClaim,
[]interface{}{big.NewInt(int64(claim.ContractIndex))},
[]interface{}{
uint32(claim.ParentContractIndex),
claim.Countered,
claim.Value,
claim.Position.ToGIndex(),
big.NewInt(int64(claim.Clock)),
})
}
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)
require.NoError(t, err)
return stubRpc, game
}
......@@ -5,22 +5,23 @@ import (
"context"
"fmt"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/responder"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-challenger/metrics"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
)
type actor func(ctx context.Context) error
type GameInfo interface {
GetGameStatus(context.Context) (gameTypes.GameStatus, error)
GetStatus(context.Context) (gameTypes.GameStatus, error)
GetClaimCount(context.Context) (uint64, error)
}
......@@ -42,18 +43,16 @@ func NewGamePlayer(
dir string,
addr common.Address,
txMgr txmgr.TxManager,
client bind.ContractCaller,
client *ethclient.Client,
creator resourceCreator,
) (*GamePlayer, error) {
logger = logger.New("game", addr)
contract, err := bindings.NewFaultDisputeGameCaller(addr, client)
loader, err := contracts.NewFaultDisputeGameContract(addr, batching.NewMultiCaller(client.Client(), 100))
if err != nil {
return nil, fmt.Errorf("failed to bind the fault dispute game contract: %w", err)
return nil, fmt.Errorf("failed to create fault dispute game contract wrapper: %w", err)
}
loader := NewLoader(contract)
status, err := loader.GetGameStatus(ctx)
status, err := loader.GetStatus(ctx)
if err != nil {
return nil, fmt.Errorf("failed to fetch game status: %w", err)
}
......@@ -72,7 +71,7 @@ func NewGamePlayer(
}, nil
}
gameDepth, err := loader.FetchGameDepth(ctx)
gameDepth, err := loader.GetMaxGameDepth(ctx)
if err != nil {
return nil, fmt.Errorf("failed to fetch the game depth: %w", err)
}
......@@ -114,7 +113,7 @@ func (g *GamePlayer) ProgressGame(ctx context.Context) gameTypes.GameStatus {
if err := g.act(ctx); err != nil {
g.logger.Error("Error when acting on game", "err", err)
}
status, err := g.loader.GetGameStatus(ctx)
status, err := g.loader.GetStatus(ctx)
if err != nil {
g.logger.Warn("Unable to retrieve game status", "err", err)
return gameTypes.GameStatusInProgress
......@@ -148,7 +147,7 @@ func (g *GamePlayer) logGameStatus(ctx context.Context, status gameTypes.GameSta
}
type PrestateLoader interface {
FetchAbsolutePrestateHash(ctx context.Context) (common.Hash, error)
GetAbsolutePrestateHash(ctx context.Context) (common.Hash, error)
}
// ValidateAbsolutePrestate validates the absolute prestate of the fault game.
......@@ -157,7 +156,7 @@ func ValidateAbsolutePrestate(ctx context.Context, trace types.TraceProvider, lo
if err != nil {
return fmt.Errorf("failed to get the trace provider's absolute prestate: %w", err)
}
onchainPrestate, err := loader.FetchAbsolutePrestateHash(ctx)
onchainPrestate, err := loader.GetAbsolutePrestateHash(ctx)
if err != nil {
return fmt.Errorf("failed to get the onchain absolute prestate: %w", err)
}
......
......@@ -181,7 +181,7 @@ func (s *stubGameState) Act(ctx context.Context) error {
return s.actErr
}
func (s *stubGameState) GetGameStatus(ctx context.Context) (gameTypes.GameStatus, error) {
func (s *stubGameState) GetStatus(ctx context.Context) (gameTypes.GameStatus, error) {
return s.status, nil
}
......@@ -234,7 +234,7 @@ func newMockPrestateLoader(prestateError bool, prestate common.Hash) *mockLoader
prestate: prestate,
}
}
func (m *mockLoader) FetchAbsolutePrestateHash(ctx context.Context) (common.Hash, error) {
func (m *mockLoader) GetAbsolutePrestateHash(ctx context.Context) (common.Hash, error) {
if m.prestateError {
return common.Hash{}, mockLoaderError
}
......
......@@ -12,8 +12,8 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-challenger/metrics"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
)
......@@ -33,7 +33,7 @@ func RegisterGameTypes(
m metrics.Metricer,
cfg *config.Config,
txMgr txmgr.TxManager,
client bind.ContractCaller,
client *ethclient.Client,
) {
if cfg.TraceTypeEnabled(config.TraceTypeCannon) {
resourceCreator := func(addr common.Address, gameDepth uint64, dir string) (faultTypes.TraceProvider, faultTypes.OracleUpdater, error) {
......
......@@ -203,6 +203,9 @@ type SystemConfig struct {
// Target L1 tx size for the batcher transactions
BatcherTargetL1TxSizeBytes uint64
// Max L1 tx size for the batcher transactions
BatcherMaxL1TxSizeBytes uint64
// SupportL1TimeTravel determines if the L1 node supports quickly skipping forward in time
SupportL1TimeTravel bool
}
......@@ -684,13 +687,17 @@ func (cfg SystemConfig) Start(t *testing.T, _opts ...SystemConfigOption) (*Syste
if os.Getenv("OP_E2E_USE_SPAN_BATCH") == "true" {
batchType = derive.SpanBatchType
}
batcherMaxL1TxSizeBytes := cfg.BatcherMaxL1TxSizeBytes
if batcherMaxL1TxSizeBytes == 0 {
batcherMaxL1TxSizeBytes = 240_000
}
batcherCLIConfig := &bss.CLIConfig{
L1EthRpc: sys.EthInstances["l1"].WSEndpoint(),
L2EthRpc: sys.EthInstances["sequencer"].WSEndpoint(),
RollupRpc: sys.RollupNodes["sequencer"].HTTPEndpoint(),
MaxPendingTransactions: 0,
MaxChannelDuration: 1,
MaxL1TxSize: 240_000,
MaxL1TxSize: batcherMaxL1TxSizeBytes,
CompressorConfig: compressor.CLIConfig{
TargetL1TxSizeBytes: cfg.BatcherTargetL1TxSizeBytes,
TargetNumFrames: 1,
......
......@@ -1553,3 +1553,14 @@ func TestRequiredProtocolVersionChangeAndHalt(t *testing.T) {
require.NoError(t, err)
t.Log("verified that op-geth closed!")
}
func TestIncorrectBatcherConfiguration(t *testing.T) {
InitParallel(t)
cfg := DefaultSystemConfig(t)
// make the batcher configuration invalid
cfg.BatcherMaxL1TxSizeBytes = 1
_, err := cfg.Start(t)
require.Error(t, err, "Expected error on invalid batcher configuration")
}
......@@ -199,18 +199,18 @@ func checkSpanBatch(ctx context.Context, cfg *rollup.Config, log log.Logger, l1B
if batch.GetTimestamp() < nextTimestamp {
if batch.GetTimestamp() > l2SafeHead.Time {
// batch timestamp cannot be between safe head and next timestamp
log.Warn("batch has misaligned timestamp")
log.Warn("batch has misaligned timestamp, block time is too short")
return BatchDrop
}
if (l2SafeHead.Time-batch.GetTimestamp())%cfg.BlockTime != 0 {
log.Warn("batch has misaligned timestamp")
log.Warn("batch has misaligned timestamp, not overlapped exactly")
return BatchDrop
}
parentNum = l2SafeHead.Number - (l2SafeHead.Time-batch.GetTimestamp())/cfg.BlockTime - 1
var err error
parentBlock, err = l2Fetcher.L2BlockRefByNumber(ctx, parentNum)
if err != nil {
log.Error("failed to fetch L2 block", "number", parentNum, "err", err)
log.Warn("failed to fetch L2 block", "number", parentNum, "err", err)
// unable to validate the batch for now. retry later.
return BatchUndecided
}
......@@ -332,7 +332,7 @@ func checkSpanBatch(ctx context.Context, cfg *rollup.Config, log log.Logger, l1B
safeBlockNum := parentNum + i + 1
safeBlockPayload, err := l2Fetcher.PayloadByNumber(ctx, safeBlockNum)
if err != nil {
log.Error("failed to fetch L2 block payload", "number", parentNum, "err", err)
log.Warn("failed to fetch L2 block payload", "number", parentNum, "err", err)
// unable to validate the batch for now. retry later.
return BatchUndecided
}
......
This diff is collapsed.
package batching
import (
"fmt"
"math/big"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
type BoundContract struct {
abi *abi.ABI
addr common.Address
}
func NewBoundContract(abi *abi.ABI, addr common.Address) *BoundContract {
return &BoundContract{
abi: abi,
addr: addr,
}
}
func (b *BoundContract) Call(method string, args ...interface{}) *ContractCall {
return NewContractCall(b.abi, b.addr, method, args...)
}
type ContractCall struct {
Abi *abi.ABI
Addr common.Address
Method string
Args []interface{}
}
func NewContractCall(abi *abi.ABI, addr common.Address, method string, args ...interface{}) *ContractCall {
return &ContractCall{
Abi: abi,
Addr: addr,
Method: method,
Args: args,
}
}
func (c *ContractCall) Pack() ([]byte, error) {
return c.Abi.Pack(c.Method, c.Args...)
}
func (c *ContractCall) ToCallArgs() (interface{}, error) {
data, err := c.Pack()
if err != nil {
return nil, fmt.Errorf("failed to pack arguments: %w", err)
}
msg := ethereum.CallMsg{
To: &c.Addr,
Data: data,
}
return toCallArg(msg), nil
}
func (c *ContractCall) Unpack(hex hexutil.Bytes) (*CallResult, error) {
out, err := c.Abi.Unpack(c.Method, hex)
if err != nil {
return nil, fmt.Errorf("failed to unpack data: %w", err)
}
return &CallResult{out: out}, nil
}
func toCallArg(msg ethereum.CallMsg) interface{} {
arg := map[string]interface{}{
"from": msg.From,
"to": msg.To,
}
if len(msg.Data) > 0 {
arg["input"] = hexutil.Bytes(msg.Data)
}
if msg.Value != nil {
arg["value"] = (*hexutil.Big)(msg.Value)
}
if msg.Gas != 0 {
arg["gas"] = hexutil.Uint64(msg.Gas)
}
if msg.GasPrice != nil {
arg["gasPrice"] = (*hexutil.Big)(msg.GasPrice)
}
return arg
}
type CallResult struct {
out []interface{}
}
func (c *CallResult) GetUint8(i int) uint8 {
return *abi.ConvertType(c.out[i], new(uint8)).(*uint8)
}
func (c *CallResult) GetUint32(i int) uint32 {
return *abi.ConvertType(c.out[i], new(uint32)).(*uint32)
}
func (c *CallResult) GetUint64(i int) uint64 {
return *abi.ConvertType(c.out[i], new(uint64)).(*uint64)
}
func (c *CallResult) GetBool(i int) bool {
return *abi.ConvertType(c.out[i], new(bool)).(*bool)
}
func (c *CallResult) GetHash(i int) common.Hash {
return *abi.ConvertType(c.out[i], new([32]byte)).(*[32]byte)
}
func (c *CallResult) GetBigInt(i int) *big.Int {
return *abi.ConvertType(c.out[i], new(*big.Int)).(**big.Int)
}
package batching
import (
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/stretchr/testify/require"
)
func TestContractCall_ToCallArgs(t *testing.T) {
addr := common.Address{0xbd}
testAbi, err := bindings.ERC20MetaData.GetAbi()
require.NoError(t, err)
call := NewContractCall(testAbi, addr, "approve", common.Address{0xcc}, big.NewInt(1234444))
args, err := call.ToCallArgs()
require.NoError(t, err)
argMap, ok := args.(map[string]interface{})
require.True(t, ok)
require.Equal(t, argMap["from"], common.Address{})
require.Equal(t, argMap["to"], &addr)
expectedData, err := call.Pack()
require.NoError(t, err)
require.Equal(t, argMap["input"], hexutil.Bytes(expectedData))
require.NotContains(t, argMap, "value")
require.NotContains(t, argMap, "gas")
require.NotContains(t, argMap, "gasPrice")
}
func TestContractCall_Pack(t *testing.T) {
addr := common.Address{0xbd}
testAbi, err := bindings.ERC20MetaData.GetAbi()
require.NoError(t, err)
sender := common.Address{0xcc}
amount := big.NewInt(1234444)
call := NewContractCall(testAbi, addr, "approve", sender, amount)
actual, err := call.Pack()
require.NoError(t, err)
expected, err := testAbi.Pack("approve", sender, amount)
require.NoError(t, err)
require.Equal(t, actual, expected)
}
func TestContractCall_PackInvalid(t *testing.T) {
addr := common.Address{0xbd}
testAbi, err := bindings.ERC20MetaData.GetAbi()
require.NoError(t, err)
// Second arg should be a *big.Int so packing should fail
call := NewContractCall(testAbi, addr, "approve", common.Address{0xcc}, uint32(123))
_, err = call.Pack()
require.Error(t, err)
}
func TestContractCall_Unpack(t *testing.T) {
addr := common.Address{0xbd}
testAbi, err := bindings.ERC20MetaData.GetAbi()
require.NoError(t, err)
call := NewContractCall(testAbi, addr, "balanceOf", common.Address{0xcc})
outputs := testAbi.Methods["balanceOf"].Outputs
expected := big.NewInt(1234)
packed, err := outputs.Pack(expected)
require.NoError(t, err)
unpacked, err := call.Unpack(packed)
require.NoError(t, err)
require.Equal(t, unpacked.GetBigInt(0), expected)
}
func TestContractCall_UnpackInvalid(t *testing.T) {
addr := common.Address{0xbd}
testAbi, err := bindings.ERC20MetaData.GetAbi()
require.NoError(t, err)
call := NewContractCall(testAbi, addr, "balanceOf", common.Address{0xcc})
// Input data is the wrong format and won't unpack successfully
inputPacked, err := call.Pack()
require.NoError(t, err)
_, err = call.Unpack(inputPacked)
require.Error(t, err)
}
func TestCallResult_GetValues(t *testing.T) {
tests := []struct {
name string
getter func(result *CallResult, i int) interface{}
expected interface{}
}{
{
name: "GetUint8",
getter: func(result *CallResult, i int) interface{} {
return result.GetUint8(i)
},
expected: uint8(12),
},
{
name: "GetUint32",
getter: func(result *CallResult, i int) interface{} {
return result.GetUint32(i)
},
expected: uint32(12346),
},
{
name: "GetUint64",
getter: func(result *CallResult, i int) interface{} {
return result.GetUint64(i)
},
expected: uint64(12346),
},
{
name: "GetBool",
getter: func(result *CallResult, i int) interface{} {
return result.GetBool(i)
},
expected: true,
},
{
name: "GetHash",
getter: func(result *CallResult, i int) interface{} {
return result.GetHash(i)
},
expected: ([32]byte)(common.Hash{0xaa, 0xbb, 0xcc}),
},
{
name: "GetBigInt",
getter: func(result *CallResult, i int) interface{} {
return result.GetBigInt(i)
},
expected: big.NewInt(2398423),
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
callResult := &CallResult{[]interface{}{nil, 0, "abc", test.expected, "xyz", 3, nil}}
actual := test.getter(callResult, 3)
require.EqualValues(t, test.expected, actual)
})
}
}
package batching
import (
"context"
"fmt"
"io"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/rpc"
)
type EthRpc interface {
CallContext(ctx context.Context, out interface{}, method string, args ...interface{}) error
BatchCallContext(ctx context.Context, b []rpc.BatchElem) error
}
type MultiCaller struct {
rpc EthRpc
batchSize int
}
func NewMultiCaller(rpc EthRpc, batchSize int) *MultiCaller {
return &MultiCaller{
rpc: rpc,
batchSize: batchSize,
}
}
func (m *MultiCaller) SingleCallLatest(ctx context.Context, call *ContractCall) (*CallResult, error) {
results, err := m.CallLatest(ctx, call)
if err != nil {
return nil, err
}
return results[0], nil
}
func (m *MultiCaller) CallLatest(ctx context.Context, calls ...*ContractCall) ([]*CallResult, error) {
keys := make([]interface{}, len(calls))
for i := 0; i < len(calls); i++ {
args, err := calls[i].ToCallArgs()
if err != nil {
return nil, err
}
keys[i] = args
}
fetcher := NewIterativeBatchCall[interface{}, *hexutil.Bytes](
keys,
func(args interface{}) (*hexutil.Bytes, rpc.BatchElem) {
out := new(hexutil.Bytes)
return out, rpc.BatchElem{
Method: "eth_call",
Args: []interface{}{args, "latest"},
Result: &out,
}
},
m.rpc.BatchCallContext,
m.rpc.CallContext,
m.batchSize)
for {
if err := fetcher.Fetch(ctx); err == io.EOF {
break
} else if err != nil {
return nil, fmt.Errorf("failed to fetch claims: %w", err)
}
}
results, err := fetcher.Result()
if err != nil {
return nil, fmt.Errorf("failed to get batch call results: %w", err)
}
callResults := make([]*CallResult, len(results))
for i, result := range results {
call := calls[i]
out, err := call.Unpack(*result)
if err != nil {
return nil, fmt.Errorf("failed to unpack result: %w", err)
}
callResults[i] = out
}
return callResults, nil
}
package test
import (
"context"
"encoding/json"
"errors"
"fmt"
"testing"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/rpc"
"github.com/stretchr/testify/require"
"golang.org/x/exp/slices"
)
type expectedCall struct {
args []interface{}
packedArgs []byte
outputs []interface{}
}
func (e *expectedCall) String() string {
return fmt.Sprintf("{args: %v, outputs: %v}", e.args, e.outputs)
}
type AbiBasedRpc struct {
t *testing.T
abi *abi.ABI
addr common.Address
expectedCalls map[string][]*expectedCall
}
func NewAbiBasedRpc(t *testing.T, contractAbi *abi.ABI, addr common.Address) *AbiBasedRpc {
return &AbiBasedRpc{
t: t,
abi: contractAbi,
addr: addr,
expectedCalls: make(map[string][]*expectedCall),
}
}
func (l *AbiBasedRpc) SetResponse(method string, expected []interface{}, output []interface{}) {
if expected == nil {
expected = []interface{}{}
}
if output == nil {
output = []interface{}{}
}
abiMethod, ok := l.abi.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{
args: expected,
packedArgs: packedArgs,
outputs: output,
})
}
func (l *AbiBasedRpc) BatchCallContext(ctx context.Context, b []rpc.BatchElem) error {
var errs []error
for _, elem := range b {
elem.Error = l.CallContext(ctx, elem.Result, elem.Method, elem.Args...)
errs = append(errs, elem.Error)
}
return errors.Join(errs...)
}
func (l *AbiBasedRpc) CallContext(_ context.Context, out interface{}, method string, args ...interface{}) error {
require.Equal(l.t, "eth_call", method)
require.Len(l.t, args, 2)
require.Equal(l.t, "latest", args[1])
callOpts, ok := args[0].(map[string]any)
require.True(l.t, ok)
require.Equal(l.t, &l.addr, callOpts["to"])
data, ok := callOpts["input"].(hexutil.Bytes)
require.True(l.t, ok)
abiMethod, err := l.abi.MethodById(data[0:4])
require.NoError(l.t, err)
argData := data[4:]
args, err = abiMethod.Inputs.Unpack(argData)
require.NoError(l.t, err)
require.Len(l.t, args, len(abiMethod.Inputs))
expectedCalls, ok := l.expectedCalls[abiMethod.Name]
require.Truef(l.t, ok, "Unexpected call to %v", abiMethod.Name)
var call *expectedCall
for _, candidate := range expectedCalls {
if slices.Equal(candidate.packedArgs, argData) {
call = candidate
break
}
}
require.NotNilf(l.t, call, "No expected calls to %v with arguments: %v\nExpected calls: %v", abiMethod.Name, args, expectedCalls)
output, err := abiMethod.Outputs.Pack(call.outputs...)
require.NoErrorf(l.t, err, "Invalid outputs for method %v: %v", abiMethod.Name, call.outputs)
// I admit I do not understand Go reflection.
// So leverage json.Unmarshal to set the out value correctly.
j, err := json.Marshal(hexutil.Bytes(output))
require.NoError(l.t, err)
require.NoError(l.t, json.Unmarshal(j, out))
return nil
}
package batching
import (
"context"
"github.com/ethereum/go-ethereum/rpc"
)
type BatchCallContextFn func(ctx context.Context, b []rpc.BatchElem) error
type CallContextFn func(ctx context.Context, result any, method string, args ...any) error
......@@ -4,16 +4,17 @@ import (
"context"
"fmt"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/rawdb"
)
type DebugClient struct {
callContext CallContextFn
callContext batching.CallContextFn
}
func NewDebugClient(callContext CallContextFn) *DebugClient {
func NewDebugClient(callContext batching.CallContextFn) *DebugClient {
return &DebugClient{callContext}
}
......
......@@ -6,6 +6,7 @@ import (
"io"
"sync"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
......@@ -391,7 +392,7 @@ type receiptsFetchingJob struct {
receiptHash common.Hash
txHashes []common.Hash
fetcher *IterativeBatchCall[common.Hash, *types.Receipt]
fetcher *batching.IterativeBatchCall[common.Hash, *types.Receipt]
// [OPTIONAL] RethDB path to fetch receipts from
rethDbPath string
......@@ -424,7 +425,7 @@ type ReceiptsRequester interface {
func (job *receiptsFetchingJob) runFetcher(ctx context.Context) error {
if job.fetcher == nil {
// start new work
job.fetcher = NewIterativeBatchCall[common.Hash, *types.Receipt](
job.fetcher = batching.NewIterativeBatchCall[common.Hash, *types.Receipt](
job.txHashes,
makeReceiptRequest,
job.client.BatchCallContext,
......
package sources
import (
"context"
"fmt"
"math/big"
"strings"
......@@ -18,10 +17,6 @@ import (
"github.com/ethereum-optimism/optimism/op-service/eth"
)
type BatchCallContextFn func(ctx context.Context, b []rpc.BatchElem) error
type CallContextFn func(ctx context.Context, result any, method string, args ...any) error
// Note: these types are used, instead of the geth types, to enable:
// - batched calls of many block requests (standard bindings do extra uncle-header fetches, cannot be batched nicely)
// - ignore uncle data (does not even exist anymore post-Merge)
......
......@@ -35,7 +35,7 @@
},
"dependencies": {
"@eth-optimism/core-utils": "workspace:*",
"@sentry/node": "^7.75.0",
"@sentry/node": "^7.77.0",
"bcfg": "^0.2.1",
"body-parser": "^1.20.2",
"commander": "^11.1.0",
......
This diff is collapsed.
......@@ -40,6 +40,6 @@
"eip1559DenominatorCanyon": 250,
"eip1559Elasticity": 10,
"systemConfigStartBlock": 8300214,
"requiredProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000",
"recommendedProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000"
"requiredProtocolVersion": "0x0000000000000000000000000000000000000003000000010000000000000000",
"recommendedProtocolVersion": "0x0000000000000000000000000000000000000003000000010000000000000000"
}
......@@ -5,8 +5,8 @@
"l1StartingBlockTag": "earliest",
"l1ChainID": 900,
"l2ChainID": 901,
"l2BlockTime": 1,
"l1BlockTime": 2,
"l2BlockTime": 2,
"l1BlockTime": 12,
"maxSequencerDrift": 300,
"sequencerWindowSize": 15,
"channelTimeout": 40,
......@@ -14,8 +14,8 @@
"batchInboxAddress": "0xff00000000000000000000000000000000000000",
"batchSenderAddress": "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC",
"l2OutputOracleSubmissionInterval": 6,
"l2OutputOracleStartingTimestamp": 0,
"l2OutputOracleStartingBlockNumber": 0,
"l2OutputOracleStartingTimestamp": 1,
"l2OutputOracleStartingBlockNumber": 1,
"gasPriceOracleOverhead": 2100,
"gasPriceOracleScalar": 1000000,
"l2OutputOracleProposer": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
......@@ -35,7 +35,7 @@
"governanceTokenName": "Optimism",
"governanceTokenSymbol": "OP",
"governanceTokenOwner": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc",
"finalizationPeriodSeconds": 2,
"finalizationPeriodSeconds": 36,
"eip1559Denominator": 50,
"eip1559DenominatorCanyon": 250,
"eip1559Elasticity": 10,
......
......@@ -44,8 +44,8 @@
"lint": "pnpm lint:fix && pnpm lint:check"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^6.9.0",
"@typescript-eslint/parser": "^6.9.0",
"@typescript-eslint/eslint-plugin": "^6.9.1",
"@typescript-eslint/parser": "^6.9.1",
"tsx": "^3.14.0",
"typescript": "^5.2.2"
}
......
{
"faucetAdmin": "0x212E789D4523D4BAF464f8Fb2A9B9dff2B36e5A6",
"faucetDrippieOwner": "0x10ab157483dd308f8B38aCF2ad823dfD255F56b5",
"faucetDripV1Value": 20000000000000000000,
"faucetDripV1Interval": 3600,
"faucetDripV1Threshold": 100000000000000000000,
"faucetDripV2Interval": 604800,
"faucetDripV2Threshold": 20000000000000000000,
"faucetDripV2Value": 500000000000000000000,
"faucetAdminDripV1Interval": 86400,
"faucetAdminDripV1Threshold": 100000000000000000,
"faucetAdminDripV1Value": 1000000000000000000,
"faucetGelatoTreasury": "0x644CB00854EDC55FE8CCC9c1967BABb22F08Ad2f",
"faucetGelatoRecipient": "0x0E9b4649eB0760A4F01646636E032D68cFDe58FF",
"faucetGelatoBalanceV1DripInterval": 86400,
"faucetGelatoBalanceV1Value": 1000000000000000000,
"faucetGelatoThreshold": 100000000000000000,
"faucetOnchainAuthModuleAdmin": "0xFe44Ae787A632c45ACea658492dDBebE39f002aC",
"faucetOnchainAuthModuleTtl": 86400,
"faucetOnchainAuthModuleAmount": 1000000000000000000,
"faucetOffchainAuthModuleAdmin": "0xFe44Ae787A632c45ACea658492dDBebE39f002aC",
"faucetOffchainAuthModuleTtl": 86400,
"faucetOffchainAuthModuleAmount": 50000000000000000
}
{
"faucetAdmin": "0x212E789D4523D4BAF464f8Fb2A9B9dff2B36e5A6",
"faucetDrippieOwner": "0x10ab157483dd308f8B38aCF2ad823dfD255F56b5",
"faucetDripV1Value": 20000000000000000000,
"faucetDripV1Interval": 3600,
"faucetDripV1Threshold": 100000000000000000000,
"faucetDripV2Interval": 604800,
"faucetDripV2Threshold": 20000000000000000000,
"faucetDripV2Value": 500000000000000000000,
"faucetAdminDripV1Interval": 86400,
"faucetAdminDripV1Threshold": 100000000000000000,
"faucetAdminDripV1Value": 1000000000000000000,
"faucetGelatoTreasury": "0x644CB00854EDC55FE8CCC9c1967BABb22F08Ad2f",
"faucetGelatoRecipient": "0x0E9b4649eB0760A4F01646636E032D68cFDe58FF",
"faucetGelatoBalanceV1DripInterval": 86400,
"faucetGelatoBalanceV1Value": 1000000000000000000,
"faucetGelatoThreshold": 100000000000000000,
"faucetOnchainAuthModuleAdmin": "0xFe44Ae787A632c45ACea658492dDBebE39f002aC",
"faucetOnchainAuthModuleTtl": 86400,
"faucetOnchainAuthModuleAmount": 1000000000000000000,
"faucetOffchainAuthModuleAdmin": "0xFe44Ae787A632c45ACea658492dDBebE39f002aC",
"faucetOffchainAuthModuleTtl": 86400,
"faucetOffchainAuthModuleAmount": 50000000000000000
}
{
"faucetAdmin": "0x212E789D4523D4BAF464f8Fb2A9B9dff2B36e5A6",
"faucetDrippieOwner": "0x10ab157483dd308f8B38aCF2ad823dfD255F56b5",
"faucetDripV1Value": 20000000000000000000,
"faucetDripV1Interval": 3600,
"faucetDripV1Threshold": 100000000000000000000,
"faucetDripV2Interval": 604800,
"faucetDripV2Threshold": 20000000000000000000,
"faucetDripV2Value": 500000000000000000000,
"faucetAdminDripV1Interval": 86400,
"faucetAdminDripV1Threshold": 100000000000000000,
"faucetAdminDripV1Value": 1000000000000000000,
"faucetGelatoTreasury": "0x644CB00854EDC55FE8CCC9c1967BABb22F08Ad2f",
"faucetGelatoRecipient": "0x0E9b4649eB0760A4F01646636E032D68cFDe58FF",
"faucetGelatoBalanceV1DripInterval": 86400,
"faucetGelatoBalanceV1Value": 1000000000000000000,
"faucetGelatoThreshold": 100000000000000000,
"faucetOnchainAuthModuleAdmin": "0xFe44Ae787A632c45ACea658492dDBebE39f002aC",
"faucetOnchainAuthModuleTtl": 86400,
"faucetOnchainAuthModuleAmount": 1000000000000000000,
"faucetOffchainAuthModuleAdmin": "0xFe44Ae787A632c45ACea658492dDBebE39f002aC",
"faucetOffchainAuthModuleTtl": 86400,
"faucetOffchainAuthModuleAmount": 50000000000000000
}
{
"faucetAdmin": "0x212E789D4523D4BAF464f8Fb2A9B9dff2B36e5A6",
"faucetDrippieOwner": "0x10ab157483dd308f8B38aCF2ad823dfD255F56b5",
"faucetDripV1Value": 20000000000000000000,
"faucetDripV1Interval": 3600,
"faucetDripV1Threshold": 100000000000000000000,
"faucetDripV2Interval": 604800,
"faucetDripV2Threshold": 20000000000000000000,
"faucetDripV2Value": 500000000000000000000,
"faucetAdminDripV1Interval": 86400,
"faucetAdminDripV1Threshold": 100000000000000000,
"faucetAdminDripV1Value": 1000000000000000000,
"faucetGelatoTreasury": "0x644CB00854EDC55FE8CCC9c1967BABb22F08Ad2f",
"faucetGelatoRecipient": "0x0E9b4649eB0760A4F01646636E032D68cFDe58FF",
"faucetGelatoBalanceV1DripInterval": 86400,
"faucetGelatoBalanceV1Value": 1000000000000000000,
"faucetGelatoThreshold": 100000000000000000,
"faucetOnchainAuthModuleAdmin": "0xFe44Ae787A632c45ACea658492dDBebE39f002aC",
"faucetOnchainAuthModuleTtl": 86400,
"faucetOnchainAuthModuleAmount": 1000000000000000000,
"faucetOffchainAuthModuleAdmin": "0xFe44Ae787A632c45ACea658492dDBebE39f002aC",
"faucetOffchainAuthModuleTtl": 86400,
"faucetOffchainAuthModuleAmount": 50000000000000000
}
{
"faucetAdmin": "0x212E789D4523D4BAF464f8Fb2A9B9dff2B36e5A6",
"faucetDrippieOwner": "0x10ab157483dd308f8B38aCF2ad823dfD255F56b5",
"faucetDripV1Value": 20000000000000000000,
"faucetDripV1Interval": 3600,
"faucetDripV1Threshold": 100000000000000000000,
"faucetDripV2Interval": 604800,
"faucetDripV2Threshold": 20000000000000000000,
"faucetDripV2Value": 500000000000000000000,
"faucetAdminDripV1Interval": 86400,
"faucetAdminDripV1Threshold": 100000000000000000,
"faucetAdminDripV1Value": 1000000000000000000,
"faucetGelatoTreasury": "0x644CB00854EDC55FE8CCC9c1967BABb22F08Ad2f",
"faucetGelatoRecipient": "0x0E9b4649eB0760A4F01646636E032D68cFDe58FF",
"faucetGelatoBalanceV1DripInterval": 86400,
"faucetGelatoBalanceV1Value": 1000000000000000000,
"faucetGelatoThreshold": 100000000000000000,
"faucetOnchainAuthModuleAdmin": "0xFe44Ae787A632c45ACea658492dDBebE39f002aC",
"faucetOnchainAuthModuleTtl": 86400,
"faucetOnchainAuthModuleAmount": 1000000000000000000,
"faucetOffchainAuthModuleAdmin": "0xFe44Ae787A632c45ACea658492dDBebE39f002aC",
"faucetOffchainAuthModuleTtl": 86400,
"faucetOffchainAuthModuleAmount": 50000000000000000
}
{
"faucetAdmin": "0x212E789D4523D4BAF464f8Fb2A9B9dff2B36e5A6",
"faucetDrippieOwner": "0x10ab157483dd308f8B38aCF2ad823dfD255F56b5",
"faucetDripV1Value": 20000000000000000000,
"faucetDripV1Interval": 3600,
"faucetDripV1Threshold": 100000000000000000000,
"faucetDripV2Interval": 604800,
"faucetDripV2Threshold": 20000000000000000000,
"faucetDripV2Value": 500000000000000000000,
"faucetAdminDripV1Interval": 86400,
"faucetAdminDripV1Threshold": 100000000000000000,
"faucetAdminDripV1Value": 1000000000000000000,
"faucetGelatoTreasury": "0x644CB00854EDC55FE8CCC9c1967BABb22F08Ad2f",
"faucetGelatoRecipient": "0x0E9b4649eB0760A4F01646636E032D68cFDe58FF",
"faucetGelatoBalanceV1DripInterval": 86400,
"faucetGelatoBalanceV1Value": 1000000000000000000,
"faucetGelatoThreshold": 100000000000000000,
"faucetOnchainAuthModuleAdmin": "0xFe44Ae787A632c45ACea658492dDBebE39f002aC",
"faucetOnchainAuthModuleTtl": 86400,
"faucetOnchainAuthModuleAmount": 1000000000000000000,
"faucetOffchainAuthModuleAdmin": "0xFe44Ae787A632c45ACea658492dDBebE39f002aC",
"faucetOffchainAuthModuleTtl": 86400,
"faucetOffchainAuthModuleAmount": 50000000000000000
}
{
"faucetAdmin": "0x8F0EBDaA1cF7106bE861753B0f9F5c0250fE0819",
"faucetDrippieOwner": "0xEa193Fd9565284E7534dDDA15b07B119e7792644",
"faucetAdmin": "0x212E789D4523D4BAF464f8Fb2A9B9dff2B36e5A6",
"faucetDrippieOwner": "0x10ab157483dd308f8B38aCF2ad823dfD255F56b5",
"faucetDripV1Value": 20000000000000000000,
"faucetDripV1Interval": 3600,
"faucetDripV1Threshold": 100000000000000000000,
......@@ -11,14 +11,14 @@
"faucetAdminDripV1Threshold": 100000000000000000,
"faucetAdminDripV1Value": 1000000000000000000,
"faucetGelatoTreasury": "0x644CB00854EDC55FE8CCC9c1967BABb22F08Ad2f",
"faucetGelatoRecipient": "0x789e58a4B08A23a7f60141959C6ABbdC0D0C4Aba",
"faucetGelatoRecipient": "0x0E9b4649eB0760A4F01646636E032D68cFDe58FF",
"faucetGelatoBalanceV1DripInterval": 86400,
"faucetGelatoBalanceV1Value": 1000000000000000000,
"faucetGelatoThreshold": 100000000000000000,
"faucetOnchainAuthModuleAdmin": "0x8F0EBDaA1cF7106bE861753B0f9F5c0250fE0819",
"faucetOnchainAuthModuleAdmin": "0xFe44Ae787A632c45ACea658492dDBebE39f002aC",
"faucetOnchainAuthModuleTtl": 86400,
"faucetOnchainAuthModuleAmount": 1000000000000000000,
"faucetOffchainAuthModuleAdmin": "0x8F0EBDaA1cF7106bE861753B0f9F5c0250fE0819",
"faucetOffchainAuthModuleAdmin": "0xFe44Ae787A632c45ACea658492dDBebE39f002aC",
"faucetOffchainAuthModuleTtl": 86400,
"faucetOffchainAuthModuleAmount": 50000000000000000
}
{
"faucetAdmin": "0x212E789D4523D4BAF464f8Fb2A9B9dff2B36e5A6",
"faucetDrippieOwner": "0x10ab157483dd308f8B38aCF2ad823dfD255F56b5",
"faucetDripV1Value": 20000000000000000000,
"faucetDripV1Interval": 3600,
"faucetDripV1Threshold": 100000000000000000000,
"faucetDripV2Interval": 604800,
"faucetDripV2Threshold": 20000000000000000000,
"faucetDripV2Value": 500000000000000000000,
"faucetAdminDripV1Interval": 86400,
"faucetAdminDripV1Threshold": 100000000000000000,
"faucetAdminDripV1Value": 1000000000000000000,
"faucetGelatoTreasury": "0x644CB00854EDC55FE8CCC9c1967BABb22F08Ad2f",
"faucetGelatoRecipient": "0x0E9b4649eB0760A4F01646636E032D68cFDe58FF",
"faucetGelatoBalanceV1DripInterval": 86400,
"faucetGelatoBalanceV1Value": 1000000000000000000,
"faucetGelatoThreshold": 100000000000000000,
"faucetOnchainAuthModuleAdmin": "0xFe44Ae787A632c45ACea658492dDBebE39f002aC",
"faucetOnchainAuthModuleTtl": 86400,
"faucetOnchainAuthModuleAmount": 1000000000000000000,
"faucetOffchainAuthModuleAdmin": "0xFe44Ae787A632c45ACea658492dDBebE39f002aC",
"faucetOffchainAuthModuleTtl": 86400,
"faucetOffchainAuthModuleAmount": 50000000000000000
}
......@@ -41,7 +41,7 @@ import { Chains } from "scripts/Chains.sol";
import { IBigStepper } from "src/dispute/interfaces/IBigStepper.sol";
import { IPreimageOracle } from "src/cannon/interfaces/IPreimageOracle.sol";
import { AlphabetVM } from "../test/FaultDisputeGame.t.sol";
import { AlphabetVM } from "test/mocks/AlphabetVM.sol";
import "src/libraries/DisputeTypes.sol";
/// @title Deploy
......@@ -52,13 +52,12 @@ import "src/libraries/DisputeTypes.sol";
contract Deploy is Deployer {
DeployConfig cfg;
/// @notice The name of the script, used to ensure the right deploy artifacts
/// are used.
/// @inheritdoc Deployer
function name() public pure override returns (string memory name_) {
name_ = "Deploy";
}
function setUp() public override {
function setUp() public virtual override {
super.setUp();
string memory path = string.concat(vm.projectRoot(), "/deploy-config/", deploymentContext, ".json");
......@@ -97,7 +96,7 @@ contract Deploy is Deployer {
/// @notice The create2 salt used for deployment of the contract implementations.
/// Using this helps to reduce config across networks as the implementation
/// addresses will be the same across networks when deployed with create2.
function implSalt() public returns (bytes32) {
function implSalt() internal returns (bytes32) {
return keccak256(bytes(vm.envOr("IMPL_SALT", string("ethers phoenix"))));
}
......
......@@ -4,8 +4,8 @@ pragma solidity ^0.8.0;
import { Script } from "forge-std/Script.sol";
import { console2 as console } from "forge-std/console2.sol";
import { stdJson } from "forge-std/StdJson.sol";
import { Executables } from "./Executables.sol";
import { Chains } from "./Chains.sol";
import { Executables } from "scripts/Executables.sol";
import { Chains } from "scripts/Chains.sol";
/// @title DeployConfig
/// @notice Represents the configuration required to deploy the system. It is expected
......@@ -33,8 +33,11 @@ contract DeployConfig is Script {
uint256 public finalizationPeriodSeconds;
address public proxyAdminOwner;
address public baseFeeVaultRecipient;
uint256 public baseFeeVaultMinimumWithdrawalAmount;
address public l1FeeVaultRecipient;
uint256 public l1FeeVaultMinimumWithdrawalAmount;
address public sequencerFeeVaultRecipient;
uint256 public sequencerFeeVaultMinimumWithdrawalAmount;
string public governanceTokenName;
string public governanceTokenSymbol;
address public governanceTokenOwner;
......@@ -79,8 +82,11 @@ contract DeployConfig is Script {
finalizationPeriodSeconds = stdJson.readUint(_json, "$.finalizationPeriodSeconds");
proxyAdminOwner = stdJson.readAddress(_json, "$.proxyAdminOwner");
baseFeeVaultRecipient = stdJson.readAddress(_json, "$.baseFeeVaultRecipient");
baseFeeVaultMinimumWithdrawalAmount = stdJson.readUint(_json, "$.baseFeeVaultMinimumWithdrawalAmount");
l1FeeVaultRecipient = stdJson.readAddress(_json, "$.l1FeeVaultRecipient");
l1FeeVaultMinimumWithdrawalAmount = stdJson.readUint(_json, "$.l1FeeVaultMinimumWithdrawalAmount");
sequencerFeeVaultRecipient = stdJson.readAddress(_json, "$.sequencerFeeVaultRecipient");
sequencerFeeVaultMinimumWithdrawalAmount = stdJson.readUint(_json, "$.sequencerFeeVaultMinimumWithdrawalAmount");
governanceTokenName = stdJson.readString(_json, "$.governanceTokenName");
governanceTokenSymbol = stdJson.readString(_json, "$.governanceTokenSymbol");
governanceTokenOwner = stdJson.readAddress(_json, "$.governanceTokenOwner");
......
......@@ -12,7 +12,6 @@ import { Proxy } from "src/universal/Proxy.sol";
import { Faucet } from "src/periphery/faucet/Faucet.sol";
import { Drippie } from "src/periphery/drippie/Drippie.sol";
import { CheckGelatoLow } from "src/periphery/drippie/dripchecks/CheckGelatoLow.sol";
import { CheckBalanceHigh } from "src/periphery/drippie/dripchecks/CheckBalanceHigh.sol";
import { CheckBalanceLow } from "src/periphery/drippie/dripchecks/CheckBalanceLow.sol";
import { CheckTrue } from "src/periphery/drippie/dripchecks/CheckTrue.sol";
import { AdminFaucetAuthModule } from "src/periphery/faucet/authmodules/AdminFaucetAuthModule.sol";
......@@ -62,7 +61,6 @@ contract DeployPeriphery is Deployer {
deployFaucetDrippie();
deployCheckTrue();
deployCheckBalanceLow();
deployCheckBalanceHigh();
deployCheckGelatoLow();
deployOnChainAuthModule();
deployOffChainAuthModule();
......@@ -199,25 +197,6 @@ contract DeployPeriphery is Deployer {
}
}
/// @notice Deploy CheckBalanceHigh contract.
function deployCheckBalanceHigh() public broadcast returns (address addr_) {
bytes32 salt = keccak256(bytes("CheckBalanceHigh"));
bytes32 initCodeHash = keccak256(abi.encodePacked(type(CheckBalanceHigh).creationCode));
address preComputedAddress = computeCreate2Address(salt, initCodeHash);
if (preComputedAddress.code.length > 0) {
console.log("CheckBalanceHigh already deployed at %s", preComputedAddress);
save("CheckBalanceHigh", preComputedAddress);
addr_ = preComputedAddress;
} else {
CheckBalanceHigh checkBalanceHigh = new CheckBalanceHigh{ salt: salt }();
save("CheckBalanceHigh", address(checkBalanceHigh));
console.log("CheckBalanceHigh deployed at %s", address(checkBalanceHigh));
addr_ = address(checkBalanceHigh);
}
}
/// @notice Deploy CheckGelatoLow contract.
function deployCheckGelatoLow() public broadcast returns (address addr_) {
bytes32 salt = keccak256(bytes("CheckGelatoLow"));
......@@ -468,7 +447,7 @@ contract DeployPeriphery is Deployer {
/// @notice installs the OnChain AuthModule on the Faucet contract.
function installOnChainAuthModule() public broadcast {
string memory moduleName = "OnChainAuthModule";
Faucet faucet = Faucet(mustGetAddress("Faucet"));
Faucet faucet = Faucet(mustGetAddress("FaucetProxy"));
AdminFaucetAuthModule onChainAuthModule = AdminFaucetAuthModule(mustGetAddress(moduleName));
if (faucet.isModuleEnabled(onChainAuthModule)) {
console.log("%s already installed.", moduleName);
......@@ -488,7 +467,7 @@ contract DeployPeriphery is Deployer {
/// @notice installs the OffChain AuthModule on the Faucet contract.
function installOffChainAuthModule() public broadcast {
string memory moduleName = "OffChainAuthModule";
Faucet faucet = Faucet(mustGetAddress("Faucet"));
Faucet faucet = Faucet(mustGetAddress("FaucetProxy"));
AdminFaucetAuthModule offChainAuthModule = AdminFaucetAuthModule(mustGetAddress(moduleName));
if (faucet.isModuleEnabled(offChainAuthModule)) {
console.log("%s already installed.", moduleName);
......@@ -507,7 +486,7 @@ contract DeployPeriphery is Deployer {
/// @notice installs all of the auth module in the faucet contract.
function installFaucetAuthModulesConfigs() public {
Faucet faucet = Faucet(mustGetAddress("Faucet"));
Faucet faucet = Faucet(mustGetAddress("FaucetProxy"));
console.log("Installing auth modules at %s", address(faucet));
installOnChainAuthModule();
installOffChainAuthModule();
......
......@@ -163,6 +163,8 @@ abstract contract Deployer is Script {
/// @notice Returns the name of the deployment script. Children contracts
/// must implement this to ensure that the deploy artifacts can be found.
/// This should be the same as the name of the script and is used as the file
/// name inside of the `broadcast` directory when looking up deployment artifacts.
function name() public pure virtual returns (string memory);
/// @notice Returns all of the deployments done in the current context.
......
......@@ -3,16 +3,16 @@ pragma solidity 0.8.15;
import { console } from "forge-std/console.sol";
import { Script } from "forge-std/Script.sol";
import { Semver } from "../../src/universal/Semver.sol";
import { ISemver } from "../../src/universal/ISemver.sol";
/// @title EnhancedScript
/// @notice Enhances forge-std' Script.sol with some additional application-specific functionality.
/// Logs simulation links using Tenderly.
abstract contract EnhancedScript is Script {
/// @notice Helper function used to compute the hash of Semver's version string to be used in a
/// @notice Helper function used to compute the hash of ISemver's version string to be used in a
/// comparison.
function _versionHash(address _addr) internal view returns (bytes32) {
return keccak256(bytes(Semver(_addr).version()));
return keccak256(bytes(ISemver(_addr).version()));
}
/// @notice Log a tenderly simulation link. The TENDERLY_USERNAME and TENDERLY_PROJECT
......
......@@ -10,7 +10,6 @@ import { ProxyAdmin } from "src/universal/ProxyAdmin.sol";
import { Constants } from "src/libraries/Constants.sol";
import { SystemConfig } from "src/L1/SystemConfig.sol";
import { ResourceMetering } from "src/L1/ResourceMetering.sol";
import { Semver } from "src/universal/Semver.sol";
import { DeployConfig } from "scripts/DeployConfig.s.sol";
import { SystemConfig } from "src/L1/SystemConfig.sol";
......
{
"src/EAS/EAS.sol": "0xea04b7a9ec170ce9cbd466ede87650f9e9ffe3d725d7951cef5777a98a840173",
"src/EAS/EAS.sol": "0xe5e9700b94a88a2e1baabe4bfa66d5c1c94e811bcc5d2c64526afc649df59118",
"src/EAS/SchemaRegistry.sol": "0x5ee1a0c3b2bf1eb5edb53fb0967cf13856be546f0f16fe7acdc3e4f286db6831",
"src/L1/DelayedVetoable.sol": "0x276c6276292095e6aa37a70008cf4e0d1cbcc020dbc9107459bbc72ab5ed744f",
"src/L1/L1CrossDomainMessenger.sol": "0x2aa4e06827bc48484212eb2bdc30fd604ffd23b37e401b78ef428c12fa9b8385",
......@@ -26,12 +26,12 @@
"src/legacy/DeployerWhitelist.sol": "0x0a6840074734c9d167321d3299be18ef911a415e4c471fa92af7d6cfaa8336d4",
"src/legacy/L1BlockNumber.sol": "0x20d83a636c5e2067fca8c0ed505b295174e6eddb25960d8705e6b6fea8e77fa6",
"src/legacy/LegacyMessagePasser.sol": "0x80f355c9710af586f58cf6a86d1925e0073d1e504d0b3d814284af1bafe4dece",
"src/periphery/op-nft/AttestationStation.sol": "0x9cf6f2fd909f9bcff229a137198186749a839cfa3d11ddbb3021fe71c30a2fa9",
"src/periphery/op-nft/Optimist.sol": "0x38407f766aa9d394403e9da388dd0265b48901789f3e8a28af50014f9f5251d9",
"src/periphery/op-nft/OptimistAllowlist.sol": "0x53e9a9dfecbae036fd468e8f34c80c7d9c35bd8908c8a6483db44dbc5128ad69",
"src/periphery/op-nft/OptimistInviter.sol": "0xfdd5b9d45205ef9372ba37f7a6394724695e676d27a47cb154ee6e4148490013",
"src/universal/OptimismMintableERC20.sol": "0x716db294648fce1bb41c5f95a20f92445f165a568886f21edd922d5c9b2cb0b5",
"src/universal/OptimismMintableERC20Factory.sol": "0xfcf2eb56777478f47f3bf2f1111aa2e3769d5ed28a6f5fceff4517683447131a",
"src/periphery/op-nft/AttestationStation.sol": "0x067b29fe24734c121469c1cb2e9b2602ddabb9e07792338b766cab341776cd78",
"src/periphery/op-nft/Optimist.sol": "0x128113cd97433987220f25b59d883d5ee7e70ff2214a536c0df3d892e13287fc",
"src/periphery/op-nft/OptimistAllowlist.sol": "0x12e5d0a79c8c05cfd41be8a1bcbd5c889652182a723aeb5eccb35e0ee2a5b6c0",
"src/periphery/op-nft/OptimistInviter.sol": "0xe5353f882475396ca8c3c0f8905baf3450697612ee6ac1d7053a80f6e1ecdd3b",
"src/universal/OptimismMintableERC20.sol": "0x099bea9f5d2f0a827f87485a4e51b8055981f6d84a0e974d226ba6d8ed5ba73d",
"src/universal/OptimismMintableERC20Factory.sol": "0x8d4cbf4cc30a0bb72925b5d2e0386b8f91559f00933a9c7cf3dcc118e34fe61b",
"src/universal/OptimismMintableERC721.sol": "0x4c73bf8474fa7eb091796a4db7e57bc5f26d50a3d1cfcb78d5efa47ced5ced2b",
"src/universal/OptimismMintableERC721Factory.sol": "0x935fd97018b6ef10fa813d9d43ab7a77c80885f7a8d7feb430097645cb2abd2c",
"src/universal/StorageSetter.sol": "0x6372647d8a67d243bc2fb40d2c4bf5807022d94d52d9423cfed27a7d57918635"
......
......@@ -16,7 +16,7 @@ import {
NotFound,
NO_EXPIRATION_TIME,
uncheckedInc
} from "./Common.sol";
} from "src/EAS/Common.sol";
import {
Attestation,
......@@ -31,9 +31,9 @@ import {
MultiRevocationRequest,
RevocationRequest,
RevocationRequestData
} from "./IEAS.sol";
} from "src/EAS/IEAS.sol";
import { ISchemaRegistry, SchemaRecord } from "./ISchemaRegistry.sol";
import { ISchemaRegistry, SchemaRecord } from "src/EAS/ISchemaRegistry.sol";
struct AttestationsResult {
uint256 usedValue; // Total ETH amount that was sent to resolvers.
......
......@@ -10,7 +10,7 @@ import {
DelegatedAttestationRequest,
DelegatedRevocationRequest,
RevocationRequestData
} from "../IEAS.sol";
} from "src/EAS/IEAS.sol";
import {
DeadlineExpired,
......@@ -20,7 +20,7 @@ import {
MAX_GAP,
stringToBytes32,
bytes32ToString
} from "../Common.sol";
} from "src/EAS/Common.sol";
/// @title EIP1271Verifier
/// @notice EIP1271Verifier typed signatures verifier for EAS delegated attestations.
......
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
import { Semver } from "../../universal/Semver.sol";
import { IEAS, Attestation } from "src/EAS/IEAS.sol";
import { AccessDenied, InvalidEAS, InvalidLength, uncheckedInc } from "src/EAS/Common.sol";
import { IEAS, Attestation } from "../IEAS.sol";
import { AccessDenied, InvalidEAS, InvalidLength, uncheckedInc } from "../Common.sol";
import { ISchemaResolver } from "./ISchemaResolver.sol";
import { ISchemaResolver } from "src/EAS/resolver/ISchemaResolver.sol";
/// @title SchemaResolver
/// @notice The base schema resolver contract.
abstract contract SchemaResolver is ISchemaResolver, Semver {
abstract contract SchemaResolver is ISchemaResolver {
error InsufficientValue();
error NotPayable();
......@@ -19,7 +17,7 @@ abstract contract SchemaResolver is ISchemaResolver, Semver {
/// @dev Creates a new resolver.
/// @param eas The address of the global EAS contract.
constructor(IEAS eas) Semver(1, 2, 0) {
constructor(IEAS eas) {
if (address(eas) == address(0)) {
revert InvalidEAS();
}
......
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { IDripCheck } from "../IDripCheck.sol";
/// @title CheckBalanceHigh
/// @notice DripCheck for checking if an account's balance is above a given threshold.
contract CheckBalanceHigh is IDripCheck {
struct Params {
address target;
uint256 threshold;
}
/// @notice External event used to help client-side tooling encode parameters.
/// @param params Parameters to encode.
event _EventToExposeStructInABI__Params(Params params);
/// @inheritdoc IDripCheck
function check(bytes memory _params) external view returns (bool execute_) {
Params memory params = abi.decode(_params, (Params));
// Check target balance is above threshold.
execute_ = params.target.balance > params.threshold;
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { Semver } from "../../universal/Semver.sol";
import { ISemver } from "src/universal/ISemver.sol";
/// @title AttestationStation
/// @author Optimism Collective
/// @author Gitcoin
/// @notice Where attestations live.
contract AttestationStation is Semver {
contract AttestationStation is ISemver {
/// @notice Struct representing data that is being attested.
/// @custom:field about Address for which the attestation is about.
/// @custom:field key A bytes32 key for the attestation.
......@@ -28,8 +28,9 @@ contract AttestationStation is Semver {
/// @param val Value of the attestation.
event AttestationCreated(address indexed creator, address indexed about, bytes32 indexed key, bytes val);
/// @custom:semver 1.1.2
constructor() Semver(1, 1, 2) { }
/// @notice Semantic version.
/// @custom:semver 1.2.0
string public constant version = "1.2.0";
/// @notice Allows anyone to create an attestation.
/// @param _about Address that the attestation is about.
......
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { Semver } from "../../universal/Semver.sol";
import { ISemver } from "src/universal/ISemver.sol";
import { ERC721BurnableUpgradeable } from
"@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721BurnableUpgradeable.sol";
import { AttestationStation } from "./AttestationStation.sol";
import { OptimistAllowlist } from "./OptimistAllowlist.sol";
import { AttestationStation } from "src/periphery/op-nft/AttestationStation.sol";
import { OptimistAllowlist } from "src/periphery/op-nft/OptimistAllowlist.sol";
import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
/// @author Optimism Collective
/// @author Gitcoin
/// @title Optimist
/// @notice A Soul Bound Token for real humans only(tm).
contract Optimist is ERC721BurnableUpgradeable, Semver {
contract Optimist is ERC721BurnableUpgradeable, ISemver {
/// @notice Attestation key used by the attestor to attest the baseURI.
bytes32 public constant BASE_URI_ATTESTATION_KEY = bytes32("optimist.base-uri");
......@@ -25,7 +25,10 @@ contract Optimist is ERC721BurnableUpgradeable, Semver {
/// @notice Address of the OptimistAllowlist contract.
OptimistAllowlist public immutable OPTIMIST_ALLOWLIST;
/// @custom:semver 2.0.2
/// @notice Semantic version.
/// @custom:semver 2.1.0
string public constant version = "2.1.0";
/// @param _name Token name.
/// @param _symbol Token symbol.
/// @param _baseURIAttestor Address of the baseURI attestor.
......@@ -37,9 +40,7 @@ contract Optimist is ERC721BurnableUpgradeable, Semver {
address _baseURIAttestor,
AttestationStation _attestationStation,
OptimistAllowlist _optimistAllowlist
)
Semver(2, 0, 2)
{
) {
BASE_URI_ATTESTOR = _baseURIAttestor;
ATTESTATION_STATION = _attestationStation;
OPTIMIST_ALLOWLIST = _optimistAllowlist;
......
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { Semver } from "../../universal/Semver.sol";
import { AttestationStation } from "./AttestationStation.sol";
import { OptimistConstants } from "./libraries/OptimistConstants.sol";
import { ISemver } from "src/universal/ISemver.sol";
import { AttestationStation } from "src/periphery/op-nft/AttestationStation.sol";
import { OptimistConstants } from "src/periphery/op-nft/libraries/OptimistConstants.sol";
/// @title OptimistAllowlist
/// @notice Source of truth for whether an address is able to mint an Optimist NFT.
/// isAllowedToMint function checks various signals to return boolean value
/// for whether an address is eligible or not.
contract OptimistAllowlist is Semver {
contract OptimistAllowlist is ISemver {
/// @notice Attestation key used by the AllowlistAttestor to manually add addresses to the
/// allowlist.
bytes32 public constant OPTIMIST_CAN_MINT_ATTESTATION_KEY = bytes32("optimist.can-mint");
......@@ -30,7 +30,10 @@ contract OptimistAllowlist is Semver {
/// attestations.
address public immutable OPTIMIST_INVITER;
/// @custom:semver 1.0.2
/// @notice Semantic version.
/// @custom:semver 1.1.0
string public constant version = "1.1.0";
/// @param _attestationStation Address of the AttestationStation contract.
/// @param _allowlistAttestor Address of the allowlist attestor.
/// @param _coinbaseQuestAttestor Address of the Coinbase Quest attestor.
......@@ -40,9 +43,7 @@ contract OptimistAllowlist is Semver {
address _allowlistAttestor,
address _coinbaseQuestAttestor,
address _optimistInviter
)
Semver(1, 0, 2)
{
) {
ATTESTATION_STATION = _attestationStation;
ALLOWLIST_ATTESTOR = _allowlistAttestor;
COINBASE_QUEST_ATTESTOR = _coinbaseQuestAttestor;
......
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { OptimistConstants } from "./libraries/OptimistConstants.sol";
import { Semver } from "../../universal/Semver.sol";
import { AttestationStation } from "./AttestationStation.sol";
import { OptimistConstants } from "src/periphery/op-nft/libraries/OptimistConstants.sol";
import { ISemver } from "src/universal/ISemver.sol";
import { AttestationStation } from "src/periphery/op-nft/AttestationStation.sol";
import { SignatureChecker } from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
import { EIP712Upgradeable } from "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol";
......@@ -32,7 +32,7 @@ import { EIP712Upgradeable } from "@openzeppelin/contracts-upgradeable/utils/cry
/// 6) claimer waits for the MIN_COMMITMENT_PERIOD to pass.
/// 7) claimer reveals the plaintext ClaimableInvite and the signature using the
/// claimInvite function, receiving the "optimist.can-mint-from-invite" attestation
contract OptimistInviter is Semver, EIP712Upgradeable {
contract OptimistInviter is ISemver, EIP712Upgradeable {
/// @notice Emitted when an invite is claimed.
/// @param issuer Address that issued the signature.
/// @param claimer Address that claimed the invite.
......@@ -87,10 +87,13 @@ contract OptimistInviter is Semver, EIP712Upgradeable {
/// @notice Maps from addresses to number of invites they have.
mapping(address => uint256) public inviteCounts;
/// @custom:semver 1.0.2
/// @notice Semantic version.
/// @custom:semver 1.1.0
string public constant version = "1.1.0";
/// @param _inviteGranter Address of the invite granter.
/// @param _attestationStation Address of the AttestationStation contract.
constructor(address _inviteGranter, AttestationStation _attestationStation) Semver(1, 0, 2) {
constructor(address _inviteGranter, AttestationStation _attestationStation) {
INVITE_GRANTER = _inviteGranter;
ATTESTATION_STATION = _attestationStation;
}
......
......@@ -4,7 +4,7 @@ pragma solidity 0.8.15;
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import { ILegacyMintableERC20, IOptimismMintableERC20 } from "src/universal/IOptimismMintableERC20.sol";
import { Semver } from "src/universal/Semver.sol";
import { ISemver } from "src/universal/ISemver.sol";
/// @title OptimismMintableERC20
/// @notice OptimismMintableERC20 is a standard extension of the base ERC20 token contract designed
......@@ -12,7 +12,7 @@ import { Semver } from "src/universal/Semver.sol";
/// use an OptimismMintablERC20 as the L2 representation of an L1 token, or vice-versa.
/// Designed to be backwards compatible with the older StandardL2ERC20 token which was only
/// meant for use on L2.
contract OptimismMintableERC20 is IOptimismMintableERC20, ILegacyMintableERC20, ERC20, Semver {
contract OptimismMintableERC20 is IOptimismMintableERC20, ILegacyMintableERC20, ERC20, ISemver {
/// @notice Address of the corresponding version of this token on the remote chain.
address public immutable REMOTE_TOKEN;
......@@ -38,7 +38,10 @@ contract OptimismMintableERC20 is IOptimismMintableERC20, ILegacyMintableERC20,
_;
}
/// @custom:semver 1.2.1
/// @notice Semantic version.
/// @custom:semver 1.3.0
string public constant version = "1.3.0";
/// @param _bridge Address of the L2 standard bridge.
/// @param _remoteToken Address of the corresponding L1 token.
/// @param _name ERC20 name.
......@@ -51,7 +54,6 @@ contract OptimismMintableERC20 is IOptimismMintableERC20, ILegacyMintableERC20,
uint8 _decimals
)
ERC20(_name, _symbol)
Semver(1, 2, 1)
{
REMOTE_TOKEN = _remoteToken;
BRIDGE = _bridge;
......
......@@ -32,8 +32,8 @@ contract OptimismMintableERC20Factory is ISemver, Initializable {
event OptimismMintableERC20Created(address indexed localToken, address indexed remoteToken, address deployer);
/// @notice Semantic version.
/// @custom:semver 1.6.1
string public constant version = "1.6.1";
/// @custom:semver 1.7.0
string public constant version = "1.7.0";
/// @notice The semver MUST be bumped any time that there is a change in
/// the OptimismMintableERC20 token contract since this contract
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
/// @title Semver
/// @notice Semver is a simple contract for managing contract versions.
contract Semver {
/// @notice Contract version number (major).
uint256 private immutable MAJOR_VERSION;
/// @notice Contract version number (minor).
uint256 private immutable MINOR_VERSION;
/// @notice Contract version number (patch).
uint256 private immutable PATCH_VERSION;
/// @param _major Version number (major).
/// @param _minor Version number (minor).
/// @param _patch Version number (patch).
constructor(uint256 _major, uint256 _minor, uint256 _patch) {
MAJOR_VERSION = _major;
MINOR_VERSION = _minor;
PATCH_VERSION = _patch;
}
/// @notice Returns the full semver contract version.
/// @return Semver contract version as a string.
function version() public view returns (string memory) {
return string(
abi.encodePacked(
Strings.toString(MAJOR_VERSION),
".",
Strings.toString(MINOR_VERSION),
".",
Strings.toString(PATCH_VERSION)
)
);
}
}
......@@ -4,7 +4,7 @@ pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol";
import { AdminFaucetAuthModule } from "src/periphery/faucet/authmodules/AdminFaucetAuthModule.sol";
import { Faucet } from "src/periphery/faucet/Faucet.sol";
import { FaucetHelper } from "test/Helpers.sol";
import { FaucetHelper } from "test/mocks/FaucetHelper.sol";
/// @title AdminFaucetAuthModuleTest
/// @notice Tests the AdminFaucetAuthModule contract.
......
......@@ -3,8 +3,8 @@ pragma solidity 0.8.15;
// Testing utilities
import { Test } from "forge-std/Test.sol";
import { TestERC20 } from "test/Helpers.sol";
import { TestERC721 } from "test/Helpers.sol";
import { TestERC20 } from "test/mocks/TestERC20.sol";
import { TestERC721 } from "test/mocks/TestERC721.sol";
import { AssetReceiver } from "src/periphery/AssetReceiver.sol";
contract AssetReceiver_Initializer is Test {
......
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol";
import { CheckBalanceHigh } from "src/periphery/drippie/dripchecks/CheckBalanceHigh.sol";
/// @title CheckBalanceHighTest
/// @notice Tests the CheckBalanceHigh contract via fuzzing both the success case
/// and the failure case.
contract CheckBalanceHighTest is Test {
/// @notice An instance of the CheckBalanceHigh contract.
CheckBalanceHigh c;
/// @notice Deploy the `CheckTrue` contract.
function setUp() external {
c = new CheckBalanceHigh();
}
/// @notice Fuzz the `check` function and assert that it always returns true
/// when the target's balance is larger than the threshold.
function testFuzz_check_succeeds(address _target, uint256 _threshold) external {
CheckBalanceHigh.Params memory p = CheckBalanceHigh.Params({ target: _target, threshold: _threshold });
// prevent overflows
vm.assume(_threshold != type(uint256).max);
vm.deal(_target, _threshold + 1);
assertEq(c.check(abi.encode(p)), true);
}
/// @notice Fuzz the `check` function and assert that it always returns false
/// when the target's balance is smaller than the threshold.
function testFuzz_check_lowBalance_fails(address _target, uint256 _threshold) external {
CheckBalanceHigh.Params memory p = CheckBalanceHigh.Params({ target: _target, threshold: _threshold });
vm.assume(_target.balance < _threshold);
assertEq(c.check(abi.encode(p)), false);
}
}
......@@ -20,7 +20,7 @@ contract MockGelatoTreasury is IGelatoTreasury {
}
/// @title CheckGelatoLowTest
/// @notice Tests the CheckBalanceHigh contract via fuzzing both the success case
/// @notice Tests the CheckGelatoLow contract via fuzzing both the success case
/// and the failure case.
contract CheckGelatoLowTest is Test {
/// @notice An instance of the CheckGelatoLow contract.
......
......@@ -731,99 +731,3 @@ contract FFIInterface is Test {
return (memRoot, proof);
}
}
library EIP1967Helper {
Vm internal constant vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);
function getAdmin(address _proxy) internal view returns (address) {
return address(uint160(uint256(vm.load(address(_proxy), Constants.PROXY_OWNER_ADDRESS))));
}
function getImplementation(address _proxy) internal view returns (address) {
return address(uint160(uint256(vm.load(address(_proxy), Constants.PROXY_IMPLEMENTATION_ADDRESS))));
}
}
// Used for testing a future upgrade beyond the current implementations.
// We include some variables so that we can sanity check accessing storage values after an upgrade.
contract NextImpl is Initializable {
// Initializable occupies the zero-th slot.
bytes32 slot1;
bytes32[19] __gap;
bytes32 slot21;
bytes32 public constant slot21Init = bytes32(hex"1337");
function initialize(uint8 _init) public reinitializer(_init) {
// Slot21 is unused by an of our upgradeable contracts.
// This is used to verify that we can access this value after an upgrade.
slot21 = slot21Init;
}
}
contract Reverter {
fallback() external {
revert();
}
}
// Useful for testing reentrancy guards
contract CallerCaller {
event WhatHappened(bool success, bytes returndata);
fallback() external {
(bool success, bytes memory returndata) = msg.sender.call(msg.data);
emit WhatHappened(success, returndata);
assembly {
switch success
case 0 { revert(add(returndata, 0x20), mload(returndata)) }
default { return(add(returndata, 0x20), mload(returndata)) }
}
}
}
// Used for testing the `CrossDomainMessenger`'s per-message reentrancy guard.
contract ConfigurableCaller {
bool doRevert = true;
address target;
bytes payload;
event WhatHappened(bool success, bytes returndata);
/// @notice Call the configured target with the configured payload OR revert.
function call() external {
if (doRevert) {
revert("ConfigurableCaller: revert");
} else {
(bool success, bytes memory returndata) = address(target).call(payload);
emit WhatHappened(success, returndata);
assembly {
switch success
case 0 { revert(add(returndata, 0x20), mload(returndata)) }
default { return(add(returndata, 0x20), mload(returndata)) }
}
}
}
/// @notice Set whether or not to have `call` revert.
function setDoRevert(bool _doRevert) external {
doRevert = _doRevert;
}
/// @notice Set the target for the call made in `call`.
function setTarget(address _target) external {
target = _target;
}
/// @notice Set the payload for the call made in `call`.
function setPayload(bytes calldata _payload) external {
payload = _payload;
}
/// @notice Fallback function that reverts if `doRevert` is true.
/// Otherwise, it does nothing.
fallback() external {
if (doRevert) {
revert("ConfigurableCaller: revert");
}
}
}
......@@ -2,13 +2,16 @@
pragma solidity 0.8.15;
// Testing utilities
import { Messenger_Initializer, Reverter, CallerCaller, CommonTest } from "test/CommonTest.t.sol";
import { Test } from "forge-std/Test.sol";
import { Messenger_Initializer, Test } from "test/CommonTest.t.sol";
import { L1CrossDomainMessenger } from "src/L1/L1CrossDomainMessenger.sol";
import { Reverter, CallerCaller } from "test/mocks/Callers.sol";
// Libraries
import { Predeploys } from "../src/libraries/Predeploys.sol";
import { Hashing } from "../src/libraries/Hashing.sol";
import { Encoding } from "../src/libraries/Encoding.sol";
import { Predeploys } from "src/libraries/Predeploys.sol";
import { Hashing } from "src/libraries/Hashing.sol";
import { Encoding } from "src/libraries/Encoding.sol";
// CrossDomainMessenger_Test is for testing functionality which is common to both the L1 and L2
// CrossDomainMessenger contracts. For simplicity, we use the L1 Messenger as the test contract.
......@@ -39,7 +42,7 @@ contract CrossDomainMessenger_BaseGas_Test is Messenger_Initializer {
/// @title ExternalRelay
/// @notice A mock external contract called via the SafeCall inside
/// the CrossDomainMessenger's `relayMessage` function.
contract ExternalRelay is CommonTest {
contract ExternalRelay is Test {
address internal op;
address internal fuzzedSender;
L1CrossDomainMessenger internal L1Messenger;
......
......@@ -5,7 +5,7 @@ import { Test } from "forge-std/Test.sol";
import { Drippie } from "src/periphery/drippie/Drippie.sol";
import { IDripCheck } from "src/periphery/drippie/IDripCheck.sol";
import { CheckTrue } from "src/periphery/drippie/dripchecks/CheckTrue.sol";
import { SimpleStorage } from "test/Helpers.sol";
import { SimpleStorage } from "test/mocks/SimpleStorage.sol";
/// @title TestDrippie
/// @notice This is a wrapper contract around Drippie used for testing.
......
......@@ -4,7 +4,7 @@ pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol";
import { Faucet } from "src/periphery/faucet/Faucet.sol";
import { AdminFaucetAuthModule } from "src/periphery/faucet/authmodules/AdminFaucetAuthModule.sol";
import { FaucetHelper } from "test/Helpers.sol";
import { FaucetHelper } from "test/mocks/FaucetHelper.sol";
contract Faucet_Initializer is Test {
event Drip(string indexed authModule, bytes32 indexed userId, uint256 amount, address indexed recipient);
......
......@@ -17,6 +17,7 @@ import { Types } from "src/libraries/Types.sol";
import { LibClock } from "src/dispute/lib/LibClock.sol";
import { LibPosition } from "src/dispute/lib/LibPosition.sol";
import { IBigStepper, IPreimageOracle } from "src/dispute/interfaces/IBigStepper.sol";
import { AlphabetVM } from "test/mocks/AlphabetVM.sol";
contract FaultDisputeGame_Init is DisputeGameFactory_Init {
/// @dev The type of the game being tested.
......@@ -1093,37 +1094,3 @@ contract VariableDivergentPlayer is GamePlayer {
trace = _trace;
}
}
////////////////////////////////////////////////////////////////
// MOCK VMS //
////////////////////////////////////////////////////////////////
contract AlphabetVM is IBigStepper {
Claim internal immutable ABSOLUTE_PRESTATE;
IPreimageOracle public oracle;
constructor(Claim _absolutePrestate) {
ABSOLUTE_PRESTATE = _absolutePrestate;
oracle = new PreimageOracle();
}
/// @inheritdoc IBigStepper
function step(bytes calldata _stateData, bytes calldata, uint256) external view returns (bytes32 postState_) {
uint256 traceIndex;
uint256 claim;
if ((keccak256(_stateData) << 8) == (Claim.unwrap(ABSOLUTE_PRESTATE) << 8)) {
// If the state data is empty, then the absolute prestate is the claim.
traceIndex = 0;
(claim) = abi.decode(_stateData, (uint256));
} else {
// Otherwise, decode the state data.
(traceIndex, claim) = abi.decode(_stateData, (uint256, uint256));
traceIndex++;
}
// STF: n -> n + 1
postState_ = keccak256(abi.encode(traceIndex, claim + 1));
assembly {
postState_ := or(and(postState_, not(shl(248, 0xFF))), shl(248, 1))
}
}
}
......@@ -2,8 +2,9 @@
pragma solidity 0.8.15;
// Testing utilities
import { Messenger_Initializer, Reverter, ConfigurableCaller } from "test/CommonTest.t.sol";
import { Messenger_Initializer } from "test/CommonTest.t.sol";
import { L2OutputOracle_Initializer } from "test/L2OutputOracle.t.sol";
import { Reverter, ConfigurableCaller } from "test/mocks/Callers.sol";
// Libraries
import { AddressAliasHelper } from "src/vendor/AddressAliasHelper.sol";
......@@ -12,12 +13,8 @@ import { Hashing } from "src/libraries/Hashing.sol";
import { Encoding } from "src/libraries/Encoding.sol";
// Target contract dependencies
import { L2OutputOracle } from "src/L1/L2OutputOracle.sol";
import { OptimismPortal } from "src/L1/OptimismPortal.sol";
// Target contract
import { L1CrossDomainMessenger } from "src/L1/L1CrossDomainMessenger.sol";
contract L1CrossDomainMessenger_Test is Messenger_Initializer {
/// @dev The receiver address
address recipient = address(0xabbaacdc);
......
......@@ -2,7 +2,8 @@
pragma solidity 0.8.15;
// Testing utilities
import { Messenger_Initializer, Reverter, ConfigurableCaller } from "test/CommonTest.t.sol";
import { Messenger_Initializer } from "test/CommonTest.t.sol";
import { Reverter, ConfigurableCaller } from "test/mocks/Callers.sol";
// Libraries
import { Hashing } from "src/libraries/Hashing.sol";
......@@ -12,10 +13,6 @@ import { Types } from "src/libraries/Types.sol";
// Target contract dependencies
import { L2ToL1MessagePasser } from "src/L2/L2ToL1MessagePasser.sol";
import { AddressAliasHelper } from "src/vendor/AddressAliasHelper.sol";
import { L1CrossDomainMessenger } from "src/L1/L1CrossDomainMessenger.sol";
// Target contract
import { L2CrossDomainMessenger } from "src/L2/L2CrossDomainMessenger.sol";
contract L2CrossDomainMessenger_Test is Messenger_Initializer {
/// @dev Receiver address for testing
......
......@@ -3,7 +3,9 @@ pragma solidity 0.8.15;
// Testing utilities
import { stdError } from "forge-std/Test.sol";
import { L2OutputOracle_Initializer, NextImpl, EIP1967Helper } from "test/CommonTest.t.sol";
import { L2OutputOracle_Initializer } from "test/CommonTest.t.sol";
import { NextImpl } from "test/mocks/NextImpl.sol";
import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol";
// Libraries
import { Types } from "src/libraries/Types.sol";
......
......@@ -72,37 +72,56 @@ contract L2ToL1MessagePasserTest is CommonTest {
/// @dev Tests that `initiateWithdrawal` succeeds and emits the correct MessagePassed
/// log when called by a contract.
function test_initiateWithdrawal_fromContract_succeeds() external {
function testFuzz_initiateWithdrawal_fromContract_succeeds(
address _target,
uint256 _gasLimit,
uint256 _value,
bytes memory _data
)
external
{
bytes32 withdrawalHash = Hashing.hashWithdrawal(
Types.WithdrawalTransaction(messagePasser.messageNonce(), address(this), address(4), 100, 64000, hex"")
Types.WithdrawalTransaction({
nonce: messagePasser.messageNonce(),
sender: address(this),
target: _target,
value: _value,
gasLimit: _gasLimit,
data: _data
})
);
vm.expectEmit(true, true, true, true);
emit MessagePassed(messagePasser.messageNonce(), address(this), address(4), 100, 64000, hex"", withdrawalHash);
vm.expectEmit(address(messagePasser));
emit MessagePassed(
messagePasser.messageNonce(), address(this), _target, _value, _gasLimit, _data, withdrawalHash
);
vm.deal(address(this), 2 ** 64);
messagePasser.initiateWithdrawal{ value: 100 }(address(4), 64000, hex"");
vm.deal(address(this), _value);
messagePasser.initiateWithdrawal{ value: _value }(_target, _gasLimit, _data);
}
/// @dev Tests that `initiateWithdrawal` succeeds and emits the correct MessagePassed
/// log when called by an EOA.
function test_initiateWithdrawal_fromEOA_succeeds() external {
uint256 gasLimit = 64000;
address target = address(4);
uint256 value = 100;
bytes memory data = hex"ff";
function testFuzz_initiateWithdrawal_fromEOA_succeeds(
uint256 _gasLimit,
address _target,
uint256 _value,
bytes memory _data
)
external
{
uint256 nonce = messagePasser.messageNonce();
// EOA emulation
vm.prank(alice, alice);
vm.deal(alice, 2 ** 64);
vm.deal(alice, _value);
bytes32 withdrawalHash =
Hashing.hashWithdrawal(Types.WithdrawalTransaction(nonce, alice, target, value, gasLimit, data));
Hashing.hashWithdrawal(Types.WithdrawalTransaction(nonce, alice, _target, _value, _gasLimit, _data));
vm.expectEmit(true, true, true, true);
emit MessagePassed(nonce, alice, target, value, gasLimit, data, withdrawalHash);
vm.expectEmit(address(messagePasser));
emit MessagePassed(nonce, alice, _target, _value, _gasLimit, _data, withdrawalHash);
messagePasser.initiateWithdrawal{ value: value }(target, gasLimit, data);
messagePasser.initiateWithdrawal{ value: _value }({ _target: _target, _gasLimit: _gasLimit, _data: _data });
// the sent messages mapping is filled
assertEq(messagePasser.sentMessages(withdrawalHash), true);
......@@ -111,12 +130,14 @@ contract L2ToL1MessagePasserTest is CommonTest {
}
/// @dev Tests that `burn` succeeds and destroys the ETH held in the contract.
function test_burn_succeeds() external {
messagePasser.initiateWithdrawal{ value: NON_ZERO_VALUE }(NON_ZERO_ADDRESS, NON_ZERO_GASLIMIT, NON_ZERO_DATA);
function testFuzz_burn_succeeds(uint256 _value, address _target, uint256 _gasLimit, bytes memory _data) external {
vm.deal(address(this), _value);
messagePasser.initiateWithdrawal{ value: _value }({ _target: _target, _gasLimit: _gasLimit, _data: _data });
assertEq(address(messagePasser).balance, NON_ZERO_VALUE);
assertEq(address(messagePasser).balance, _value);
vm.expectEmit(true, false, false, false);
emit WithdrawerBalanceBurnt(NON_ZERO_VALUE);
emit WithdrawerBalanceBurnt(_value);
messagePasser.burn();
// The Withdrawer should have no balance
......
......@@ -47,7 +47,7 @@ contract LivenessGuard_Constructor_Test is LivenessGuard_TestInit {
function test_constructor_works() external {
address[] memory owners = safeInstance.owners;
livenessGuard = new WrappedGuard(safeInstance.safe);
for (uint256 i = 0; i < owners.length; i++) {
for (uint256 i; i < owners.length; i++) {
assertEq(livenessGuard.lastLive(owners[i]), initTime);
}
}
......@@ -242,7 +242,7 @@ contract LivenessGuard_FuzzOwnerManagement_Test is StdCheats, StdUtils, Liveness
(address[] memory ownerAddrs, uint256[] memory ownerkeys) =
SafeTestLib.makeAddrsAndKeys("safeTest", initialOwners);
// record the private keys for later use
for (uint256 i = 0; i < ownerAddrs.length; i++) {
for (uint256 i; i < ownerAddrs.length; i++) {
privateKeys[ownerAddrs[i]] = ownerkeys[i];
}
......@@ -251,7 +251,7 @@ contract LivenessGuard_FuzzOwnerManagement_Test is StdCheats, StdUtils, Liveness
livenessGuard = new WrappedGuard(safeInstance.safe);
safeInstance.setGuard(address(livenessGuard));
for (uint256 i = 0; i < changes.length; i++) {
for (uint256 i; i < changes.length; i++) {
vm.warp(block.timestamp + changes[i].timeDelta);
OwnerChange memory change = changes[i];
address[] memory currentOwners = safeInstance.safe.getOwners();
......@@ -312,16 +312,16 @@ contract LivenessGuard_FuzzOwnerManagement_Test is StdCheats, StdUtils, Liveness
// Looks up the private key for each owner
uint256[] memory unsortedOwnerPKs = new uint256[](instance.owners.length);
for (uint256 j = 0; j < instance.owners.length; j++) {
unsortedOwnerPKs[j] = privateKeys[instance.owners[j]];
for (uint256 i; i < instance.owners.length; i++) {
unsortedOwnerPKs[i] = privateKeys[instance.owners[i]];
}
// Sort the keys by address and store them in the SafeInstance
instance.ownerPKs = SafeTestLib.sortPKsByComputedAddress(unsortedOwnerPKs);
// Overwrite the SafeInstances owners array with the computed addresses from the ownerPKs array
for (uint256 k; k < instance.owners.length; k++) {
instance.owners[k] = SafeTestLib.getAddr(instance.ownerPKs[k]);
for (uint256 i; i < instance.owners.length; i++) {
instance.owners[i] = SafeTestLib.getAddr(instance.ownerPKs[i]);
}
}
}
......@@ -199,7 +199,7 @@ contract LivenessModule_RemoveOwners_TestFail is LivenessModule_TestInit {
uint256 numOwners = safeInstance.owners.length;
address[] memory ownersToRemove = new address[](numOwners);
for (uint256 i = 0; i < numOwners; i++) {
for (uint256 i; i < numOwners; i++) {
ownersToRemove[i] = safeInstance.owners[i];
}
address[] memory prevOwners = safeInstance.getPrevOwners(ownersToRemove);
......@@ -218,7 +218,7 @@ contract LivenessModule_RemoveOwners_TestFail is LivenessModule_TestInit {
uint256 numOwners = safeInstance.owners.length - 2;
address[] memory ownersToRemove = new address[](numOwners);
for (uint256 i = 0; i < numOwners; i++) {
for (uint256 i; i < numOwners; i++) {
ownersToRemove[i] = safeInstance.owners[i];
}
address[] memory prevOwners = safeInstance.getPrevOwners(ownersToRemove);
......@@ -236,7 +236,7 @@ contract LivenessModule_RemoveOwners_TestFail is LivenessModule_TestInit {
uint256 numOwners = safeInstance.owners.length - 1;
address[] memory ownersToRemove = new address[](numOwners);
for (uint256 i = 0; i < numOwners; i++) {
for (uint256 i; i < numOwners; i++) {
ownersToRemove[i] = safeInstance.owners[i];
}
address[] memory prevOwners = safeInstance.getPrevOwners(ownersToRemove);
......@@ -297,7 +297,7 @@ contract LivenessModule_RemoveOwners_Test is LivenessModule_TestInit {
uint256 numOwners = safeInstance.owners.length;
address[] memory ownersToRemove = new address[](numOwners);
for (uint256 i = 0; i < numOwners; i++) {
for (uint256 i; i < numOwners; i++) {
ownersToRemove[i] = safeInstance.owners[i];
}
address[] memory prevOwners = safeInstance.getPrevOwners(ownersToRemove);
......@@ -348,15 +348,12 @@ contract LivenessModule_RemoveOwnersFuzz_Test is LivenessModule_TestInit {
//
// _numOwners must be at least 4, so that _minOwners can be set to at least 3 by the following bound() call.
// Limiting the owner set to 20 helps to keep the runtime of the test reasonable.
console.log("bounding numOwners");
numOwners_ = bound(_numOwners, 4, 20);
// _minOwners must be at least 3, otherwise we don't have any range below _minOwners in which to test all of the
// ShutDownBehavior options.
console.log("bounding minOwners");
minOwners_ = bound(_minOwners, 3, numOwners_ - 1);
// Ensure that _numLiveOwners is less than _numOwners so that we can remove at least one owner.
console.log("bounding numLiveOwners");
numLiveOwners_ = bound(_numLiveOwners, 0, numOwners_ - 1);
// The above bounds are a bit tricky, so we assert that the resulting parameters enable us to test all possible
......@@ -402,20 +399,19 @@ contract LivenessModule_RemoveOwnersFuzz_Test is LivenessModule_TestInit {
// Create an array of live owners, and call showLiveness for each of them
address[] memory liveOwners = new address[](numLiveOwners);
for (uint256 i = 0; i < numLiveOwners; i++) {
for (uint256 i; i < numLiveOwners; i++) {
liveOwners[i] = safeInstance.owners[i];
vm.prank(safeInstance.owners[i]);
livenessGuard.showLiveness();
}
address[] memory nonLiveOwners = new address[](numOwners - numLiveOwners);
for (uint256 i = 0; i < numOwners - numLiveOwners; i++) {
for (uint256 i; i < numOwners - numLiveOwners; i++) {
nonLiveOwners[i] = safeInstance.owners[i + numLiveOwners];
}
address[] memory prevOwners;
if (numLiveOwners >= minOwners) {
console.log("No shutdown");
// The safe will remain above the minimum number of owners, so we can remove only those owners which are not
// live.
prevOwners = safeInstance.getPrevOwners(nonLiveOwners);
......@@ -424,10 +420,10 @@ contract LivenessModule_RemoveOwnersFuzz_Test is LivenessModule_TestInit {
// Validate the resulting state of the Safe
assertEq(safeInstance.safe.getOwners().length, numLiveOwners);
assertEq(safeInstance.safe.getThreshold(), get75PercentThreshold(numLiveOwners));
for (uint256 i = 0; i < numLiveOwners; i++) {
for (uint256 i; i < numLiveOwners; i++) {
assertTrue(safeInstance.safe.isOwner(liveOwners[i]));
}
for (uint256 i = 0; i < nonLiveOwners.length; i++) {
for (uint256 i; i < nonLiveOwners.length; i++) {
assertFalse(safeInstance.safe.isOwner(nonLiveOwners[i]));
}
} else {
......@@ -439,14 +435,10 @@ contract LivenessModule_RemoveOwnersFuzz_Test is LivenessModule_TestInit {
// The safe is below the minimum number of owners.
// The ShutDownBehavior enum determines how we handle this case.
if (shutDownBehavior == ShutDownBehavior.Correct) {
console.log("Correct Shutdown");
// We remove all owners, and transfer ownership to the shutDown owner.
// but we need to do remove the non-live owners first, so we reverse the owners array, since
// the first owners in the array were the ones to call showLiveness.
// ownersToRemove = new address[](numOwners);
for (uint256 i = 0; i < numOwners; i++) {
// ownersToRemove[numOwners - i - 1] = safeInstance.owners[i];
// ownersToRemove[i] = safeInstance.owners[numOwners - i - 1];
for (uint256 i; i < numOwners; i++) {
ownersToRemove.push(safeInstance.owners[numOwners - i - 1]);
}
prevOwners = safeInstance.getPrevOwners(ownersToRemove);
......@@ -461,14 +453,11 @@ contract LivenessModule_RemoveOwnersFuzz_Test is LivenessModule_TestInit {
// trigger that behavior. We initialize that value here then set it in the if statements below.
uint256 numOwnersToRemoveinShutDown;
if (shutDownBehavior == ShutDownBehavior.DoesNotRemoveAllOwners) {
console.log("Shutdown DoesNotRemoveAllOwners");
// In the DoesNotRemoveAllOwners case, we should have more than 1 of the pre-existing owners
// remaining
console.log("bounding numOwnersToRemoveinShutDown");
numOwnersToRemoveinShutDown =
bound(_numOwnersToRemoveinShutDown, numOwners - minOwners + 1, numOwners - 2);
uint256 i = 0;
for (i; i < numOwnersToRemoveinShutDown; i++) {
for (uint256 i; i < numOwnersToRemoveinShutDown; i++) {
// Add non-live owners to remove first
if (i < nonLiveOwners.length) {
ownersToRemove.push(nonLiveOwners[i]);
......@@ -483,11 +472,9 @@ contract LivenessModule_RemoveOwnersFuzz_Test is LivenessModule_TestInit {
);
livenessModule.removeOwners(prevOwners, ownersToRemove);
} else if (shutDownBehavior == ShutDownBehavior.DoesNotTransferToFallbackOwner) {
console.log("Shutdown DoesNotTransferToFallbackOwner");
// In the DoesNotRemoveAllOwners case, we should have exactly 1 pre-existing owners remaining
numOwnersToRemoveinShutDown = numOwners - 1;
uint256 i = 0;
for (i; i < numOwnersToRemoveinShutDown; i++) {
for (uint256 i; i < numOwnersToRemoveinShutDown; i++) {
// Add non-live owners to remove first
if (i < nonLiveOwners.length) {
ownersToRemove.push(nonLiveOwners[i]);
......@@ -503,8 +490,8 @@ contract LivenessModule_RemoveOwnersFuzz_Test is LivenessModule_TestInit {
// For both of the incorrect behaviors, verify no change to the Safe state
assertEq(safeInstance.safe.getOwners().length, numOwners);
assertEq(safeInstance.safe.getThreshold(), get75PercentThreshold(numOwners));
for (uint256 j = 0; j < numOwners; j++) {
assertTrue(safeInstance.safe.isOwner(safeInstance.owners[j]));
for (uint256 i; i < numOwners; i++) {
assertTrue(safeInstance.safe.isOwner(safeInstance.owners[i]));
}
}
}
......
......@@ -3,7 +3,8 @@ pragma solidity 0.8.15;
// Testing utilities
import { stdError } from "forge-std/Test.sol";
import { Portal_Initializer, CommonTest, NextImpl } from "test/CommonTest.t.sol";
import { Portal_Initializer } from "test/CommonTest.t.sol";
import { NextImpl } from "test/mocks/NextImpl.sol";
// Libraries
import { Types } from "src/libraries/Types.sol";
......
......@@ -7,7 +7,7 @@ import { AttestationStation } from "src/periphery/op-nft/AttestationStation.sol"
import { Optimist } from "src/periphery/op-nft/Optimist.sol";
import { OptimistAllowlist } from "src/periphery/op-nft/OptimistAllowlist.sol";
import { OptimistInviter } from "src/periphery/op-nft/OptimistInviter.sol";
import { OptimistInviterHelper } from "test/Helpers.sol";
import { OptimistInviterHelper } from "test/mocks/OptimistInviterHelper.sol";
import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
......
......@@ -6,7 +6,7 @@ import { Test } from "forge-std/Test.sol";
import { AttestationStation } from "src/periphery/op-nft/AttestationStation.sol";
import { OptimistAllowlist } from "src/periphery/op-nft/OptimistAllowlist.sol";
import { OptimistInviter } from "src/periphery/op-nft/OptimistInviter.sol";
import { OptimistInviterHelper } from "test/Helpers.sol";
import { OptimistInviterHelper } from "test/mocks/OptimistInviterHelper.sol";
import { OptimistConstants } from "src/periphery/op-nft/libraries/OptimistConstants.sol";
contract OptimistAllowlist_Initializer is Test {
......
......@@ -7,8 +7,8 @@ import { AttestationStation } from "src/periphery/op-nft/AttestationStation.sol"
import { OptimistInviter } from "src/periphery/op-nft/OptimistInviter.sol";
import { Optimist } from "src/periphery/op-nft/Optimist.sol";
import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
import { TestERC1271Wallet } from "test/Helpers.sol";
import { OptimistInviterHelper } from "test/Helpers.sol";
import { TestERC1271Wallet } from "test/mocks/TestERC1271Wallet.sol";
import { OptimistInviterHelper } from "test/mocks/OptimistInviterHelper.sol";
import { OptimistConstants } from "src/periphery/op-nft/libraries/OptimistConstants.sol";
contract OptimistInviter_Initializer is Test {
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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