Commit 2f776d90 authored by Ethen Pociask's avatar Ethen Pociask

[indexer.docs] Fix merge conflicts

parents 8af6726b 75182608
---
'@eth-optimism/core-utils': patch
---
Upgraded npm dependencies to latest
---
'@eth-optimism/chain-mon': patch
---
Upgraded npm dependencies to latest
......@@ -115,20 +115,23 @@ jobs:
name: Restore PNPM Package Cache
keys:
- pnpm-packages-v2-{{ checksum "pnpm-lock.yaml" }}
- run:
name: Install dependencies
command: pnpm install --frozen-lockfile
# Fetch node_modules into the pnpm store
# This will cache node_modules based on pnpm-lock so other steps can instantly install them with `pnpm install --prefer-offline`
# --prefer-offline installs node_modules instantly by just reading from cache if it exists rather than fetching from network
# when installing node_modules pnpm simply adds symlinks instead of copying the files which is why it is pretty much instant to run --prefer-offline
# this allows a caching strategy of only checking pnpm-lockfile so we don't have to keep it in sync with our packages
# For more information see https://pnpm.io/cli/fetch
- run:
name: Fetch dependencies
command: pnpm fetch --frozen-lockfile --prefer-offline
- save_cache:
name: Save PNPM Package Cache
key: pnpm-packages-v2-{{ checksum "pnpm-lock.yaml" }}
paths:
- "node_modules"
- "packages/chain-mon/node_modules"
- "packages/common-ts/node_modules"
- "packages/contracts-bedrock/node_modules"
- "packages/core-utils/node_modules"
- "packages/sdk/node_modules"
- "packages/contracts-ts/node_modules"
- run:
name: Install dependencies
command: pnpm install --frozen-lockfile --offline
- run:
name: print forge version
command: forge --version
......@@ -469,6 +472,10 @@ jobs:
name: Restore PNPM Package Cache
keys:
- pnpm-packages-v2-{{ checksum "pnpm-lock.yaml" }}
# populate node modules from the cache
- run:
name: Install dependencies
command: pnpm install --frozen-lockfile --prefer-offline
- check-changed:
patterns: contracts-bedrock
- run:
......@@ -476,51 +483,6 @@ jobs:
command: pnpm validate-spacers
working_directory: packages/contracts-bedrock
bedrock-echidna-build:
docker:
- image: us-docker.pkg.dev/oplabs-tools-artifacts/images/ci-builder:latest
steps:
- checkout
- attach_workspace: { at: "." }
- check-changed:
patterns: contracts-bedrock
- run:
name: Compile with metadata hash
command: pnpm clean && pnpm build:with-metadata
working_directory: packages/contracts-bedrock
- persist_to_workspace:
root: .
paths:
- "node_modules"
- packages/contracts-bedrock
bedrock-echidna-run:
docker:
- image: us-docker.pkg.dev/oplabs-tools-artifacts/images/ci-builder:latest
parameters:
echidna_target:
description: Which echidna fuzz contract to run
type: string
size:
description: Custom resource class size for the run
type: string
default: large
resource_class: <<parameters.size>>
steps:
- checkout
- attach_workspace: { at: "." }
- restore_cache:
name: Restore PNPM Package Cache
keys:
- pnpm-packages-v2-{{ checksum "pnpm-lock.yaml" }}
- check-changed:
patterns: contracts-bedrock
- run:
name: Echidna Fuzz <<parameters.echidna_target>>
command: pnpm echidna:<<parameters.echidna_target>>
working_directory: packages/contracts-bedrock
no_output_timeout: 15m
op-bindings-build:
docker:
- image: us-docker.pkg.dev/oplabs-tools-artifacts/images/ci-builder:latest
......@@ -556,6 +518,10 @@ jobs:
- pnpm-packages-v2-{{ checksum "pnpm-lock.yaml" }}
- check-changed:
patterns: <<parameters.package_name>>,<<parameters.dependencies>>
# populate node modules from the cache
- run:
name: Install dependencies
command: pnpm install --frozen-lockfile --prefer-offline
- run:
name: Lint
command: pnpm lint && git diff --exit-code
......@@ -581,6 +547,10 @@ jobs:
- pnpm-packages-v2-{{ checksum "pnpm.lock.yaml" }}
- check-changed:
patterns: sdk,contracts-bedrock,contracts
# populate node modules from the cache
- run:
name: Install dependencies
command: pnpm install --frozen-lockfile --prefer-offline
- run:
name: Check generated and build
command: pnpm generate:check
......@@ -599,6 +569,10 @@ jobs:
- pnpm-packages-v2-{{ checksum "pnpm.lock.yaml" }}
- check-changed:
patterns: sdk,contracts-bedrock,contracts
# populate node modules from the cache
- run:
name: Install dependencies
command: pnpm install --frozen-lockfile --prefer-offline
- run:
name: anvil-l1
background: true
......@@ -643,22 +617,13 @@ jobs:
- checkout
- check-changed:
patterns: specs/(.*)\.md$
# TODO remove me after ci builder updated
- run:
name: Install pnpm package manager
command: |
npm i pnpm --global
# TODO remove me after ci builder updated
# A github dep clones-with-immutable-args is installed via github
# packages installed via npm via github automatically run postpack scripts
# their postpack script happens to use yarn so we need it here
- run:
name: Install yarn package manager
command: |
npm i yarn@1 --global
- run:
name: pnpm dev deps
command: pnpm install
command: pnpm install --frozen-lockfile --prefer-offline
- run:
name: specs toc
command: pnpm lint:specs:toc && git diff --exit-code ./specs
......@@ -671,7 +636,6 @@ jobs:
image: ubuntu-2204:2022.07.1
steps:
- checkout
# TODO remove me after ci builder updated
- run:
name: Install pnpm package manager
command: |
......@@ -679,7 +643,7 @@ jobs:
- run:
name: Install node_modules
command: |
pnpm install
pnpm install --frozen-lockfile --prefer-offline
- run:
name: Lint check
command: |
......@@ -981,7 +945,7 @@ jobs:
- run:
name: Install and build
command: |
pnpm install && pnpm build
pnpm install --frozen-lockfile --prefer-offline && pnpm build
- run:
name: generate cannon prestate
command: make cannon-prestate
......
......@@ -42,7 +42,7 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Fetch tags
run: git fetch --tags origin
run: git fetch --tags origin --force
- name: Setup Python 3.10
uses: actions/setup-python@v4
with:
......
......@@ -165,6 +165,43 @@ func TestEVM(t *testing.T) {
}
}
func TestEVMSingleStep(t *testing.T) {
contracts, addrs := testContractsSetup(t)
var tracer vm.EVMLogger
//tracer = SourceMapTracer(t, contracts, addrs)
type testInput struct {
name string
pc uint32
nextPC uint32
insn uint32
}
cases := []testInput{
{"j MSB set target", 0, 4, 0x0A_00_00_02}, // j 0x02_00_00_02
{"j non-zero PC region", 0x10000000, 0x10000004, 0x08_00_00_02}, // j 0x2
{"jal MSB set target", 0, 4, 0x0E_00_00_02}, // jal 0x02_00_00_02
{"jal non-zero PC region", 0x10000000, 0x10000004, 0x0C_00_00_02}, // jal 0x2
}
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
state := &State{PC: tt.pc, NextPC: tt.nextPC, Memory: NewMemory()}
state.Memory.SetMemory(tt.pc, tt.insn)
us := NewInstrumentedState(state, nil, os.Stdout, os.Stderr)
stepWitness, err := us.Step(true)
require.NoError(t, err)
evm := NewMIPSEVM(contracts, addrs)
evm.SetTracer(tracer)
evmPost := evm.Step(t, stepWitness)
goPost := us.state.EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
}
}
func TestEVMFault(t *testing.T) {
contracts, addrs := testContractsSetup(t)
var tracer vm.EVMLogger // no-tracer by default, but see SourceMapTracer and MarkdownTracer
......
......@@ -285,13 +285,13 @@ func (m *InstrumentedState) mipsStep() error {
// j-type j/jal
if opcode == 2 || opcode == 3 {
// TODO likely bug in original code: MIPS spec says this should be in the "current" region;
// a 256 MB aligned region (i.e. use top 4 bits of branch delay slot (pc+4))
linkReg := uint32(0)
if opcode == 3 {
linkReg = 31
}
return m.handleJump(linkReg, SE(insn&0x03FFFFFF, 26)<<2)
// Take top 4 bits of the next PC (its 256 MB region), and concatenate with the 26-bit offset
target := (m.state.NextPC & 0xF0000000) | ((insn & 0x03FFFFFF) << 2)
return m.handleJump(linkReg, target)
}
// register fetch
......@@ -396,7 +396,6 @@ func (m *InstrumentedState) mipsStep() error {
func execute(insn uint32, rs uint32, rt uint32, mem uint32) uint32 {
opcode := insn >> 26 // 6-bits
fun := insn & 0x3f // 6-bits
// TODO(CLI-4136): deref the immed into a register
if opcode < 0x20 {
// transform ArithLogI
......
......@@ -43,7 +43,7 @@ require (
golang.org/x/term v0.11.0
golang.org/x/time v0.3.0
gorm.io/driver/postgres v1.5.2
gorm.io/gorm v1.25.2
gorm.io/gorm v1.25.3
)
require (
......@@ -170,7 +170,7 @@ require (
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/status-im/keycard-go v0.2.0 // indirect
github.com/stretchr/objx v0.5.0 // indirect
github.com/supranational/blst v0.3.11-0.20230406105308-e9dfc5ee724b // indirect
github.com/supranational/blst v0.3.11 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a // indirect
github.com/tklauser/go-sysconf v0.3.10 // indirect
github.com/tklauser/numcpus v0.5.0 // indirect
......
......@@ -764,8 +764,8 @@ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1F
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/supranational/blst v0.3.11-0.20230406105308-e9dfc5ee724b h1:u49mjRnygnB34h8OKbnNJFVUtWSKIKb1KukdV8bILUM=
github.com/supranational/blst v0.3.11-0.20230406105308-e9dfc5ee724b/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw=
github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4=
github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a h1:1ur3QoCqvE5fl+nylMaIr9PVV1w343YRDtsy+Rwu7XI=
github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48=
......@@ -1111,8 +1111,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0=
gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBpCgl8=
gorm.io/gorm v1.25.2 h1:gs1o6Vsa+oVKG/a9ElL3XgyGfghFfkKA2SInQaCyMho=
gorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
gorm.io/gorm v1.25.3 h1:zi4rHZj1anhZS2EuEODMhDisGy+Daq9jtPrNGgbQYD8=
gorm.io/gorm v1.25.3/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
......
package api
import (
"encoding/json"
"fmt"
"net/http"
"github.com/ethereum-optimism/optimism/indexer/api/routes"
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/go-chi/chi/v5"
)
type PaginationResponse struct {
// TODO type this better
Data interface{} `json:"data"`
Cursor string `json:"cursor"`
HasNextPage bool `json:"hasNextPage"`
}
func (a *Api) L1DepositsHandler(w http.ResponseWriter, r *http.Request) {
bv := a.BridgeTransfersView
address := common.HexToAddress(chi.URLParam(r, "address"))
// limit := getIntFromQuery(r, "limit", 10)
// cursor := r.URL.Query().Get("cursor")
// sortDirection := r.URL.Query().Get("sortDirection")
deposits, err := bv.L1BridgeDepositsByAddress(address)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// This is not the shape of the response we want!!!
// will add in the individual features in future prs 1 by 1
response := PaginationResponse{
Data: deposits,
// Cursor: nextCursor,
HasNextPage: false,
}
jsonResponse(w, response, http.StatusOK)
}
func (a *Api) L2WithdrawalsHandler(w http.ResponseWriter, r *http.Request) {
bv := a.BridgeTransfersView
address := common.HexToAddress(chi.URLParam(r, "address"))
// limit := getIntFromQuery(r, "limit", 10)
// cursor := r.URL.Query().Get("cursor")
// sortDirection := r.URL.Query().Get("sortDirection")
withdrawals, err := bv.L2BridgeWithdrawalsByAddress(address)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// This is not the shape of the response we want!!!
// will add in the individual features in future prs 1 by 1
response := PaginationResponse{
Data: withdrawals,
// Cursor: nextCursor,
HasNextPage: false,
}
jsonResponse(w, response, http.StatusOK)
}
func (a *Api) HealthzHandler(w http.ResponseWriter, r *http.Request) {
jsonResponse(w, "ok", http.StatusOK)
}
func jsonResponse(w http.ResponseWriter, data interface{}, statusCode int) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(statusCode)
if err := json.NewEncoder(w).Encode(data); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
const ethereumAddressRegex = `^0x[a-fA-F0-9]{40}$`
type Api struct {
Router *chi.Mux
BridgeTransfersView database.BridgeTransfersView
Router *chi.Mux
}
func NewApi(bv database.BridgeTransfersView) *Api {
func NewApi(bv database.BridgeTransfersView, logger log.Logger) *Api {
logger.Info("Initializing API...")
r := chi.NewRouter()
api := &Api{Router: r, BridgeTransfersView: bv}
h := routes.NewRoutes(logger, bv)
// these regex are .+ because I wasn't sure what they should be
// don't want a regex for addresses because would prefer to validate the address
// with go-ethereum and throw a friendly error message
r.Get("/api/v0/deposits/{address:.+}", api.L1DepositsHandler)
r.Get("/api/v0/withdrawals/{address:.+}", api.L2WithdrawalsHandler)
r.Get("/healthz", api.HealthzHandler)
api := &Api{Router: r}
return api
r.Get("/healthz", h.HealthzHandler)
r.Get(fmt.Sprintf("/api/v0/deposits/{address:%s}", ethereumAddressRegex), h.L1DepositsHandler)
r.Get(fmt.Sprintf("/api/v0/withdrawals/{address:%s}", ethereumAddressRegex), h.L2WithdrawalsHandler)
return api
}
func (a *Api) Listen(port string) error {
......
package api
import (
"fmt"
"math/big"
"net/http"
"net/http/httptest"
"testing"
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/assert"
)
// MockBridgeTransfersView mocks the BridgeTransfersView interface
type MockBridgeTransfersView struct{}
var mockAddress = "0x4204204204204204204204204204204204204204"
var (
deposit = database.L1BridgeDeposit{
TransactionSourceHash: common.HexToHash("abc"),
......@@ -23,7 +28,7 @@ var (
}
withdrawal = database.L2BridgeWithdrawal{
TransactionWithdrawalHash: common.HexToHash("0x456"),
TransactionWithdrawalHash: common.HexToHash("0x420"),
CrossDomainMessengerNonce: &database.U256{Int: big.NewInt(0)},
Tx: database.Transaction{},
TokenPair: database.TokenPair{},
......@@ -65,7 +70,8 @@ func (mbv *MockBridgeTransfersView) L2BridgeWithdrawalsByAddress(address common.
}
func TestHealthz(t *testing.T) {
api := NewApi(&MockBridgeTransfersView{})
logger := testlog.Logger(t, log.LvlInfo)
api := NewApi(&MockBridgeTransfersView{}, logger)
request, err := http.NewRequest("GET", "/healthz", nil)
assert.Nil(t, err)
......@@ -76,8 +82,9 @@ func TestHealthz(t *testing.T) {
}
func TestL1BridgeDepositsHandler(t *testing.T) {
api := NewApi(&MockBridgeTransfersView{})
request, err := http.NewRequest("GET", "/api/v0/deposits/0x123", nil)
logger := testlog.Logger(t, log.LvlInfo)
api := NewApi(&MockBridgeTransfersView{}, logger)
request, err := http.NewRequest("GET", fmt.Sprintf("/api/v0/deposits/%s", mockAddress), nil)
assert.Nil(t, err)
responseRecorder := httptest.NewRecorder()
......@@ -87,8 +94,9 @@ func TestL1BridgeDepositsHandler(t *testing.T) {
}
func TestL2BridgeWithdrawalsByAddressHandler(t *testing.T) {
api := NewApi(&MockBridgeTransfersView{})
request, err := http.NewRequest("GET", "/api/v0/withdrawals/0x123", nil)
logger := testlog.Logger(t, log.LvlInfo)
api := NewApi(&MockBridgeTransfersView{}, logger)
request, err := http.NewRequest("GET", fmt.Sprintf("/api/v0/withdrawals/%s", mockAddress), nil)
assert.Nil(t, err)
responseRecorder := httptest.NewRecorder()
......
package routes
import (
"encoding/json"
"net/http"
"github.com/ethereum/go-ethereum/log"
)
// lazily typing numbers fixme
type Transaction struct {
Timestamp uint64 `json:"timestamp"`
TransactionHash string `json:"transactionHash"`
}
type Block struct {
BlockNumber int64 `json:"number"`
BlockHash string `json:"hash"`
// ParentBlockHash string `json:"parentHash"`
}
type Extensions struct {
OptimismBridgeAddress string `json:"OptimismBridgeAddress"`
}
type TokenInfo struct {
// TODO lazily typing ints go through them all with fine tooth comb once api is up
ChainId int `json:"chainId"`
Address string `json:"address"`
Name string `json:"name"`
Symbol string `json:"symbol"`
Decimals int `json:"decimals"`
Extensions Extensions `json:"extensions"`
}
func jsonResponse(w http.ResponseWriter, logger log.Logger, data interface{}, statusCode int) {
w.Header().Set("Content-Type", "application/json")
jsonData, err := json.Marshal(data)
if err != nil {
http.Error(w, "Internal server error", http.StatusInternalServerError)
logger.Error("Failed to marshal JSON: %v", err)
return
}
w.WriteHeader(statusCode)
_, err = w.Write(jsonData)
if err != nil {
http.Error(w, "Internal server error", http.StatusInternalServerError)
logger.Error("Failed to write JSON data", err)
return
}
}
package routes
import (
"net/http"
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum/go-ethereum/common"
"github.com/go-chi/chi/v5"
)
type DepositItem struct {
Guid string `json:"guid"`
From string `json:"from"`
To string `json:"to"`
// TODO could consider OriginTx to be more generic to handling L2 to L2 deposits
// this seems more clear today though
Tx Transaction `json:"Tx"`
Block Block `json:"Block"`
Amount string `json:"amount"`
L1Token TokenInfo `json:"l1Token"`
L2Token TokenInfo `json:"l2Token"`
}
type DepositResponse struct {
Cursor string `json:"cursor"`
HasNextPage bool `json:"hasNextPage"`
Items []DepositItem `json:"items"`
}
// TODO this is original spec but maybe include the l2 block info too for the relayed tx
// FIXME make a pure function that returns a struct instead of newWithdrawalResponse
func newDepositResponse(deposits []*database.L1BridgeDepositWithTransactionHashes) DepositResponse {
items := make([]DepositItem, len(deposits))
for _, deposit := range deposits {
item := DepositItem{
Guid: deposit.L1BridgeDeposit.TransactionSourceHash.String(),
Block: Block{
BlockNumber: 420420, // TODO
BlockHash: "0x420", // TODO
},
Tx: Transaction{
TransactionHash: "0x420", // TODO
Timestamp: deposit.L1BridgeDeposit.Tx.Timestamp,
},
From: deposit.L1BridgeDeposit.Tx.FromAddress.String(),
To: deposit.L1BridgeDeposit.Tx.ToAddress.String(),
Amount: deposit.L1BridgeDeposit.Tx.Amount.Int.String(),
L1Token: TokenInfo{
ChainId: 1,
Address: deposit.L1BridgeDeposit.TokenPair.L1TokenAddress.String(),
Name: "TODO",
Symbol: "TODO",
Decimals: 420,
Extensions: Extensions{
OptimismBridgeAddress: "0x420", // TODO
},
},
L2Token: TokenInfo{
ChainId: 10,
Address: deposit.L1BridgeDeposit.TokenPair.L2TokenAddress.String(),
Name: "TODO",
Symbol: "TODO",
Decimals: 420,
Extensions: Extensions{
OptimismBridgeAddress: "0x420", // TODO
},
},
}
items = append(items, item)
}
return DepositResponse{
Cursor: "42042042-4204-4204-4204-420420420420", // TODO
HasNextPage: false, // TODO
Items: items,
}
}
func (h Routes) L1DepositsHandler(w http.ResponseWriter, r *http.Request) {
address := common.HexToAddress(chi.URLParam(r, "address"))
deposits, err := h.BridgeTransfersView.L1BridgeDepositsByAddress(address)
if err != nil {
http.Error(w, "Internal server error reading deposits", http.StatusInternalServerError)
h.Logger.Error("Unable to read deposits from DB")
h.Logger.Error(err.Error())
return
}
response := newDepositResponse(deposits)
jsonResponse(w, h.Logger, response, http.StatusOK)
}
package routes
import (
"net/http"
)
func (h Routes) HealthzHandler(w http.ResponseWriter, r *http.Request) {
jsonResponse(w, h.Logger, "ok", http.StatusOK)
}
package routes
import (
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum/go-ethereum/log"
)
type Routes struct {
Logger log.Logger
BridgeTransfersView database.BridgeTransfersView
}
func NewRoutes(logger log.Logger, bv database.BridgeTransfersView) Routes {
return Routes{
Logger: logger,
BridgeTransfersView: bv,
}
}
package routes
import (
"net/http"
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum/go-ethereum/common"
"github.com/go-chi/chi/v5"
)
type Proof struct {
TransactionHash string `json:"transactionHash"`
BlockTimestamp uint64 `json:"blockTimestamp"`
BlockNumber int `json:"blockNumber"`
}
type Claim struct {
TransactionHash string `json:"transactionHash"`
BlockTimestamp uint64 `json:"blockTimestamp"`
BlockNumber int `json:"blockNumber"`
}
type WithdrawalItem struct {
Guid string `json:"guid"`
Tx Transaction `json:"Tx"`
Block Block `json:"Block"`
From string `json:"from"`
To string `json:"to"`
TransactionHash string `json:"transactionHash"`
Amount string `json:"amount"`
Proof Proof `json:"proof"`
Claim Claim `json:"claim"`
WithdrawalState string `json:"withdrawalState"`
L1Token TokenInfo `json:"l1Token"`
L2Token TokenInfo `json:"l2Token"`
}
type WithdrawalResponse struct {
Cursor string `json:"cursor"`
HasNextPage bool `json:"hasNextPage"`
Items []WithdrawalItem `json:"items"`
}
// FIXME make a pure function that returns a struct instead of newWithdrawalResponse
func newWithdrawalResponse(withdrawals []*database.L2BridgeWithdrawalWithTransactionHashes) WithdrawalResponse {
items := make([]WithdrawalItem, len(withdrawals))
for _, withdrawal := range withdrawals {
item := WithdrawalItem{
Guid: withdrawal.L2BridgeWithdrawal.TransactionWithdrawalHash.String(),
Block: Block{
BlockNumber: 420420, // TODO
BlockHash: "0x420", // TODO
},
Tx: Transaction{
TransactionHash: "0x420", // TODO
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.Int.String(),
Proof: Proof{
TransactionHash: withdrawal.ProvenL1TransactionHash.String(),
BlockTimestamp: withdrawal.L2BridgeWithdrawal.Tx.Timestamp,
BlockNumber: 420, // TODO Block struct instead
},
Claim: Claim{
TransactionHash: withdrawal.FinalizedL1TransactionHash.String(),
BlockTimestamp: withdrawal.L2BridgeWithdrawal.Tx.Timestamp, // Using L2 timestamp for now, might need adjustment
BlockNumber: 420, // TODO block struct
},
WithdrawalState: "COMPLETE", // TODO
L1Token: TokenInfo{
ChainId: 1,
Address: withdrawal.L2BridgeWithdrawal.TokenPair.L1TokenAddress.String(),
Name: "Example", // TODO
Symbol: "EXAMPLE", // TODO
Decimals: 18, // TODO
Extensions: Extensions{
OptimismBridgeAddress: "0x636Af16bf2f682dD3109e60102b8E1A089FedAa8",
},
},
L2Token: TokenInfo{
ChainId: 10,
Address: withdrawal.L2BridgeWithdrawal.TokenPair.L2TokenAddress.String(),
Name: "Example", // TODO
Symbol: "EXAMPLE", // TODO
Decimals: 18, // TODO
Extensions: Extensions{
OptimismBridgeAddress: "0x36Af16bf2f682dD3109e60102b8E1A089FedAa86",
},
},
}
items = append(items, item)
}
return WithdrawalResponse{
Cursor: "42042042-0420-4204-2042-420420420420", // TODO
HasNextPage: true, // TODO
Items: items,
}
}
func (h Routes) L2WithdrawalsHandler(w http.ResponseWriter, r *http.Request) {
address := common.HexToAddress(chi.URLParam(r, "address"))
withdrawals, err := h.BridgeTransfersView.L2BridgeWithdrawalsByAddress(address)
if err != nil {
http.Error(w, "Internal server error fetching withdrawals", http.StatusInternalServerError)
h.Logger.Error("Unable to read deposits from DB")
h.Logger.Error(err.Error())
return
}
response := newWithdrawalResponse(withdrawals)
jsonResponse(w, h.Logger, response, http.StatusOK)
}
......@@ -3,12 +3,14 @@ package cli
import (
"context"
"fmt"
"strconv"
"github.com/ethereum-optimism/optimism/indexer"
"github.com/ethereum-optimism/optimism/indexer/api"
"github.com/ethereum-optimism/optimism/indexer/config"
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum-optimism/optimism/op-service/opio"
"github.com/ethereum/go-ethereum/params"
"github.com/urfave/cli/v2"
......@@ -26,14 +28,19 @@ func runIndexer(ctx *cli.Context) error {
logger := log.NewLogger(log.ReadCLIConfig(ctx))
configPath := ctx.String(ConfigFlag.Name)
cfg, err := config.LoadConfig(configPath)
cfg, err := config.LoadConfig(logger, configPath)
if err != nil {
logger.Error("failed to load config", "err", err)
return err
}
cfg.Logger = logger
indexer, err := indexer.NewIndexer(cfg)
db, err := database.NewDB(cfg.DB)
if err != nil {
return err
}
indexer, err := indexer.NewIndexer(cfg.Chain, cfg.RPCs, db, logger)
if err != nil {
return err
}
......@@ -51,17 +58,21 @@ func runApi(ctx *cli.Context) error {
logger := log.NewLogger(log.ReadCLIConfig(ctx))
configPath := ctx.String(ConfigFlag.Name)
cfg, err := config.LoadConfig(configPath)
cfg, err := config.LoadConfig(logger, configPath)
if err != nil {
logger.Error("failed to load config", "err", err)
return err
}
cfg.Logger = logger
fmt.Println(cfg)
db, err := database.NewDB(cfg.DB)
if err != nil {
logger.Crit("Failed to connect to database", "err", err)
}
server := api.NewApi(db.BridgeTransfers, logger)
// finish me
return err
return server.Listen(strconv.Itoa(cfg.API.Port))
}
var (
......@@ -81,7 +92,8 @@ func (c *Cli) Run(args []string) error {
}
func NewCli(GitVersion string, GitCommit string, GitDate string) *Cli {
flags := append([]cli.Flag{ConfigFlag}, log.CLIFlags("INDEXER")...)
flags := []cli.Flag{ConfigFlag}
flags = append(flags, log.CLIFlags("INDEXER")...)
app := &cli.App{
Version: fmt.Sprintf("%s-%s", GitVersion, params.VersionWithCommit(GitCommit, GitDate)),
Description: "An indexer of all optimism events with a serving api layer",
......
package config
import (
"fmt"
"os"
"reflect"
"github.com/BurntSushi/toml"
"github.com/ethereum-optimism/optimism/indexer/processor"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/common"
geth_log "github.com/ethereum/go-ethereum/log"
"github.com/joho/godotenv"
)
// in future presets can just be onchain config and fetched on initialization
// Config represents the `indexer.toml` file used to configure the indexer
type Config struct {
Chain ChainConfig
......@@ -17,7 +20,33 @@ type Config struct {
DB DBConfig
API APIConfig
Metrics MetricsConfig
Logger log.Logger `toml:"-"`
}
// fetch this via onchain config from RPCsConfig and remove from config in future
type L1Contracts struct {
OptimismPortal common.Address
L2OutputOracle common.Address
L1CrossDomainMessenger common.Address
L1StandardBridge common.Address
L1ERC721Bridge common.Address
// Some more contracts -- ProxyAdmin, SystemConfig, etcc
// Ignore the auxiliary contracts?
// Legacy contracts? We'll add this in to index the legacy chain.
// Remove afterwards?
}
func (c L1Contracts) ToSlice() []common.Address {
fields := reflect.VisibleFields(reflect.TypeOf(c))
v := reflect.ValueOf(c)
contracts := make([]common.Address, len(fields))
for i, field := range fields {
contracts[i] = (v.FieldByName(field.Name).Interface()).(common.Address)
}
return contracts
}
// ChainConfig configures of the chain being indexed
......@@ -25,7 +54,7 @@ type ChainConfig struct {
// Configure known chains with the l2 chain id
// NOTE - This currently performs no lookups to extract known L1 contracts by l2 chain id
Preset int
L1Contracts processor.L1Contracts `toml:"l1-contracts"`
L1Contracts L1Contracts `toml:"l1-contracts"`
// L1StartingHeight is the block height to start indexing from
// NOTE - This is currently unimplemented
L1StartingHeight int
......@@ -59,32 +88,38 @@ type MetricsConfig struct {
}
// LoadConfig loads the `indexer.toml` config file from a given path
func LoadConfig(path string) (Config, error) {
func LoadConfig(logger geth_log.Logger, path string) (Config, error) {
if err := godotenv.Load(); err != nil {
log.Warn("Unable to load .env file", err)
log.Info("Continuing without .env file")
logger.Warn("Unable to load .env file", err)
logger.Info("Continuing without .env file")
} else {
log.Info("Loaded .env file")
logger.Info("Loaded .env file")
}
var conf Config
// Read the config file.
data, err := os.ReadFile(path)
if err != nil {
return conf, err
}
// Replace environment variables.
data = []byte(os.ExpandEnv(string(data)))
// Decode the TOML data.
if _, err := toml.Decode(string(data), &conf); err != nil {
log.Info("Failed to decode config file", "message", err)
logger.Info("Failed to decode config file", "message", err)
return conf, err
}
log.Debug("Loaded config file", conf)
if conf.Chain.Preset != 0 {
knownContracts, ok := presetL1Contracts[conf.Chain.Preset]
if ok {
conf.Chain.L1Contracts = knownContracts
} else {
return conf, fmt.Errorf("unknown preset: %d", conf.Chain.Preset)
}
}
logger.Debug("Loaded config file", conf)
return conf, nil
}
package config
import (
"fmt"
"os"
"testing"
"github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
)
func TestLoadConfig(t *testing.T) {
logger := testlog.Logger(t, log.LvlInfo)
tmpfile, err := os.CreateTemp("", "test.toml")
require.NoError(t, err)
defer os.Remove(tmpfile.Name())
......@@ -15,7 +20,7 @@ func TestLoadConfig(t *testing.T) {
testData := `
[chain]
preset = 1234
preset = 420
[rpcs]
l1-rpc = "https://l1.example.com"
......@@ -45,10 +50,15 @@ func TestLoadConfig(t *testing.T) {
err = tmpfile.Close()
require.NoError(t, err)
conf, err := LoadConfig(tmpfile.Name())
conf, err := LoadConfig(logger, tmpfile.Name())
require.NoError(t, err)
require.Equal(t, conf.Chain.Preset, 1234)
require.Equal(t, conf.Chain.Preset, 420)
require.Equal(t, conf.Chain.L1Contracts.OptimismPortal.String(), presetL1Contracts[420].OptimismPortal.String())
require.Equal(t, conf.Chain.L1Contracts.L1CrossDomainMessenger.String(), presetL1Contracts[420].L1CrossDomainMessenger.String())
require.Equal(t, conf.Chain.L1Contracts.L1ERC721Bridge.String(), presetL1Contracts[420].L1ERC721Bridge.String())
require.Equal(t, conf.Chain.L1Contracts.L1StandardBridge.String(), presetL1Contracts[420].L1StandardBridge.String())
require.Equal(t, conf.Chain.L1Contracts.L2OutputOracle.String(), presetL1Contracts[420].L2OutputOracle.String())
require.Equal(t, conf.RPCs.L1RPC, "https://l1.example.com")
require.Equal(t, conf.RPCs.L2RPC, "https://l2.example.com")
require.Equal(t, conf.DB.Host, "127.0.0.1")
......@@ -61,3 +71,69 @@ func TestLoadConfig(t *testing.T) {
require.Equal(t, conf.Metrics.Host, "127.0.0.1")
require.Equal(t, conf.Metrics.Port, 7300)
}
func TestLoadConfig_WithoutPreset(t *testing.T) {
tmpfile, err := os.CreateTemp("", "test_without_preset.toml")
require.NoError(t, err)
defer os.Remove(tmpfile.Name())
defer tmpfile.Close()
testData := `
[chain]
l1contracts = { OptimismPortal = "0x4205Fc579115071764c7423A4f12eDde41f106Ed", L2OutputOracle = "0x42097868233d1aa22e815a266982f2cf17685a27", L1CrossDomainMessenger = "0x420ce71c97B33Cc4729CF772ae268934F7ab5fA1", L1StandardBridge = "0x4209fc46f92E8a1c0deC1b1747d010903E884bE1", L1ERC721Bridge ="0x420749f83b81B301cAb5f48EB8516B986DAef23D" }
[rpcs]
l1-rpc = "https://l1.example.com"
l2-rpc = "https://l2.example.com"
`
data := []byte(testData)
err = os.WriteFile(tmpfile.Name(), data, 0644)
require.NoError(t, err)
defer os.Remove(tmpfile.Name())
err = tmpfile.Close()
require.NoError(t, err)
logger := testlog.Logger(t, log.LvlInfo)
conf, err := LoadConfig(logger, tmpfile.Name())
require.NoError(t, err)
require.Equal(t, conf.Chain.L1Contracts.OptimismPortal.String(), common.HexToAddress("0x4205Fc579115071764c7423A4f12eDde41f106Ed").String())
require.Equal(t, conf.Chain.L1Contracts.L2OutputOracle.String(), common.HexToAddress("0x42097868233d1aa22e815a266982f2cf17685a27").String())
require.Equal(t, conf.Chain.L1Contracts.L1CrossDomainMessenger.String(), common.HexToAddress("0x420ce71c97B33Cc4729CF772ae268934F7ab5fA1").String())
require.Equal(t, conf.Chain.L1Contracts.L1StandardBridge.String(), common.HexToAddress("0x4209fc46f92E8a1c0deC1b1747d010903E884bE1").String())
require.Equal(t, conf.Chain.L1Contracts.L1ERC721Bridge.String(), common.HexToAddress("0x420749f83b81B301cAb5f48EB8516B986DAef23D").String())
require.Equal(t, conf.Chain.Preset, 0)
}
func TestLoadConfig_WithUnknownPreset(t *testing.T) {
tmpfile, err := os.CreateTemp("", "test_bad_preset.toml")
require.NoError(t, err)
defer os.Remove(tmpfile.Name())
defer tmpfile.Close()
testData := `
[chain]
preset = 1234567890 # this preset doesn't exist
[rpcs]
l1-rpc = "https://l1.example.com"
l2-rpc = "https://l2.example.com"
`
data := []byte(testData)
err = os.WriteFile(tmpfile.Name(), data, 0644)
require.NoError(t, err)
defer os.Remove(tmpfile.Name())
err = tmpfile.Close()
require.NoError(t, err)
logger := testlog.Logger(t, log.LvlInfo)
conf, err := LoadConfig(logger, tmpfile.Name())
var faultyPreset = 1234567890
require.Equal(t, conf.Chain.Preset, faultyPreset)
require.Error(t, err)
require.Equal(t, fmt.Sprintf("unknown preset: %d", faultyPreset), err.Error())
}
package config
import (
"github.com/ethereum/go-ethereum/common"
)
// in future presets can just be onchain config and fetched on initialization
// Mapping of l2 chain ids to their preset chain configurations
var presetL1Contracts = map[int]L1Contracts{
// OP Mainnet
10: {
OptimismPortal: common.HexToAddress("0xbEb5Fc579115071764c7423A4f12eDde41f106Ed"),
L2OutputOracle: common.HexToAddress("0xdfe97868233d1aa22e815a266982f2cf17685a27"),
L1CrossDomainMessenger: common.HexToAddress("0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1"),
L1StandardBridge: common.HexToAddress("0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1"),
L1ERC721Bridge: common.HexToAddress("0x5a7749f83b81B301cAb5f48EB8516B986DAef23D"),
},
// OP Goerli
420: {
OptimismPortal: common.HexToAddress("0x5b47E1A08Ea6d985D6649300584e6722Ec4B1383"),
L2OutputOracle: common.HexToAddress("0xE6Dfba0953616Bacab0c9A8ecb3a9BBa77FC15c0"),
L1CrossDomainMessenger: common.HexToAddress("0x5086d1eEF304eb5284A0f6720f79403b4e9bE294"),
L1StandardBridge: common.HexToAddress("0x636Af16bf2f682dD3109e60102b8E1A089FedAa8"),
L1ERC721Bridge: common.HexToAddress("0x8DD330DdE8D9898d43b4dc840Da27A07dF91b3c9"),
},
// Base Mainnet
8453: {
OptimismPortal: common.HexToAddress("0x49048044D57e1C92A77f79988d21Fa8fAF74E97e"),
L2OutputOracle: common.HexToAddress("0x56315b90c40730925ec5485cf004d835058518A0"),
L1CrossDomainMessenger: common.HexToAddress("0x866E82a600A1414e583f7F13623F1aC5d58b0Afa"),
L1StandardBridge: common.HexToAddress("0x3154Cf16ccdb4C6d922629664174b904d80F2C35"),
// FIXME update this to the correct address
L1ERC721Bridge: common.HexToAddress("0x0000000000000000000000000000000000000000"),
},
// Base Goerli
84531: {
OptimismPortal: common.HexToAddress("0xe93c8cD0D409341205A592f8c4Ac1A5fe5585cfA"),
L2OutputOracle: common.HexToAddress("0x2A35891ff30313CcFa6CE88dcf3858bb075A2298"),
L1CrossDomainMessenger: common.HexToAddress("0x8e5693140eA606bcEB98761d9beB1BC87383706D"),
L1StandardBridge: common.HexToAddress("0xfA6D8Ee5BE770F84FC001D098C4bD604Fe01284a"),
// FIXME update this to the correct address
L1ERC721Bridge: common.HexToAddress("0x0000000000000000000000000000000000000000"),
},
// Zora mainnet
7777777: {
OptimismPortal: common.HexToAddress("0x1a0ad011913A150f69f6A19DF447A0CfD9551054"),
L2OutputOracle: common.HexToAddress("0x9E6204F750cD866b299594e2aC9eA824E2e5f95c"),
L1CrossDomainMessenger: common.HexToAddress("0xdC40a14d9abd6F410226f1E6de71aE03441ca506"),
L1StandardBridge: common.HexToAddress("0x3e2Ea9B92B7E48A52296fD261dc26fd995284631"),
// FIXME update this to the correct address
L1ERC721Bridge: common.HexToAddress("0x0000000000000000000000000000000000000000"),
},
// Zora goerli
999: {
OptimismPortal: common.HexToAddress("0xDb9F51790365e7dc196e7D072728df39Be958ACe"),
L2OutputOracle: common.HexToAddress("0xdD292C9eEd00f6A32Ff5245d0BCd7f2a15f24e00"),
L1CrossDomainMessenger: common.HexToAddress("0xD87342e16352D33170557A7dA1e5fB966a60FafC"),
L1StandardBridge: common.HexToAddress("0x7CC09AC2452D6555d5e0C213Ab9E2d44eFbFc956"),
// FIXME update this to the correct address
L1ERC721Bridge: common.HexToAddress("0x0000000000000000000000000000000000000000"),
},
}
......@@ -2,6 +2,9 @@
package database
import (
"fmt"
"github.com/ethereum-optimism/optimism/indexer/config"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"gorm.io/gorm/logger"
......@@ -17,7 +20,14 @@ type DB struct {
BridgeTransactions BridgeTransactionsDB
}
func NewDB(dsn string) (*DB, error) {
func NewDB(dbConfig config.DBConfig) (*DB, error) {
dsn := fmt.Sprintf("host=%s port=%d dbname=%s sslmode=disable", dbConfig.Host, dbConfig.Port, dbConfig.Name)
if dbConfig.User != "" {
dsn += fmt.Sprintf(" user=%s", dbConfig.User)
}
if dbConfig.Password != "" {
dsn += fmt.Sprintf(" password=%s", dbConfig.Password)
}
gorm, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
// The indexer will explicitly manage the transaction
// flow processing blocks
......
......@@ -17,6 +17,29 @@ services:
- postgres_data:/data/postgres
indexer:
build:
context: ..
dockerfile: indexer/Dockerfile.refresh
command: ["indexer-refresh", "processor"]
# healthcheck:
# Add healthcheck once figure out good way how
# maybe after we add metrics?
ports:
- 8080:8080
environment:
- INDEXER_DB_PORT=5432
- INDEXER_DB_USER=db_username
- INDEXER_DB_PASSWORD=db_password
- INDEXER_DB_NAME=db_name
- INDEXER_DB_HOST=postgres
- INDEXER_CONFIG=/configs/indexer.toml
volumes:
- ./indexer.toml:/configs/indexer.toml
depends_on:
postgres:
condition: service_healthy
api:
build:
context: ..
dockerfile: indexer/Dockerfile
......@@ -75,5 +98,121 @@ services:
postgres:
condition: service_healthy
gateway-frontend:
command: pnpm nx start @gateway/frontend --host 0.0.0.0 --port 5173
# Change tag to `latest` after https://github.com/ethereum-optimism/gateway/pull/2541 merges
image: ethereumoptimism/gateway-frontend:latest
ports:
- 5173:5173
healthcheck:
test: curl http://0.0.0.0:5173
environment:
- VITE_GROWTHBOOK=${VITE_GROWTHBOOK:-https://cdn.growthbook.io/api/features/dev_iGoAbSwtGOtEJONeHdVTosV0BD3TvTPttAccGyRxqsk}
- VITE_ENABLE_DEVNET=true
- VITE_RPC_URL_ETHEREUM_MAINNET=$VITE_RPC_URL_ETHEREUM_MAINNET
- VITE_RPC_URL_ETHEREUM_OPTIMISM_MAINNET=$VITE_RPC_URL_OPTIMISM_MAINNET
- VITE_RPC_URL_ETHEREUM_GOERLI=$VITE_RPC_URL_ETHEREUM_GOERLI
- VITE_RPC_URL_ETHEREUM_OPTIMISM_GOERLI=$VITE_RPC_URL_OPTIMISM_GOERLI
- VITE_BACKEND_URL_MAINNET=http://localhost:7421
- VITE_BACKEND_URL_GOERLI=http://localhost:7422
- VITE_ENABLE_ALL_FEATURES=true
backend-mainnet:
image: ethereumoptimism/gateway-backend:latest
environment:
# this enables the backend to proxy history requests to the indexer
- BRIDGE_INDEXER_URI=http://api
- HOST=0.0.0.0
- PORT=7300
- MIGRATE_APP_DB_USER=${MIGRATE_APP_DB_USER:-postgres}
- MIGRATE_APP_DB_PASSWORD=${MIGRATE_APP_DB_PASSWORD:-db_password}
- APP_DB_HOST=${APP_DB_HOST:-postgres-app}
- APP_DB_USER=${APP_DB_USER:-gateway-backend-mainnet@oplabs-local-web.iam}
- APP_DB_NAME=${APP_DB_NAME:-gateway}
- APP_DB_PORT=${APP_DB_PORT:-5432}
# THis is for the legacy indexer which won't be used but the env variable is still required
- INDEXER_DB_HOST=postgres-mainnet
# THis is for the legacy indexer which won't be used but the env variable is still required
- INDEXER_DB_USER=db_username
# THis is for the legacy indexer which won't be used but the env variable is still required
- INDEXER_DB_PASS=db_password
# THis is for the legacy indexer which won't be used but the env variable is still required
- INDEXER_DB_NAME=db_name
# THis is for the legacy indexer which won't be used but the env variable is still required
- INDEXER_DB_PORT=5432
# THis is for the legacy indexer which won't be used but the env variable is still required
- DATABASE_URL=postgres://db_username:db_password@postgres-mainnet:5432/db_name
- JSON_RPC_URLS_L1=$JSON_RPC_URLS_L1_MAINNET
- JSON_RPC_URLS_L2=$JSON_RPC_URLS_L2_MAINNET
- JSON_RPC_URLS_L2_GOERLI=$JSON_RPC_URLS_L2_GOERLI
# anvil[0] privater key as placeholder
- FAUCET_AUTH_ADMIN_WALLET_PRIVATE_KEY=${$FAUCET_AUTH_ADMIN_WALLET_PRIVATE_KEY:-0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80}
- IRON_SESSION_SECRET=${IRON_SESSION_SECRET:-UNKNOWN_IRON_SESSION_PASSWORD_32}
- CHAIN_ID_L1=1
- CHAIN_ID_L2=10
- FLEEK_BUCKET_ADDRESS=34a609661-6774-441f-9fdb-453fdbb89931-bucket
- FLEEK_API_SECRET=$FLEEK_API_SECRET
- FLEEK_API_KEY=$FLEEK_API_KEY
- MOCK_MERKLE_PROOF=true
- LOOP_INTERVAL_MINUTES=.1
- GITHUB_CLIENT_ID=$GITHUB_CLIENT_ID
- GITHUB_SECRET=$GITHUB_SECRET
- MAINNET_BEDROCK=$MAINNET_BEDROCK
- TRM_API_KEY=$TRM_API_KEY
- GOOGLE_CLOUD_STORAGE_BUCKET_NAME=oplabs-dev-web-content
# Recommened to uncomment for local dev unless you need it
#- BYPASS_EVENT_LOG_POLLER_BOOTSTRAP=true
ports:
- 7421:7300
# overrides command in Dockerfile so we can hot reload the server in docker while developing
#command: ['pnpm', 'nx', 'run', '@gateway/backend:docker:watch']
healthcheck:
test: curl http://0.0.0.0:7300/api/v0/healthz
backend-goerli:
image: ethereumoptimism/gateway-backend:latest
environment:
# this enables the backend to proxy history requests to the indexer
- BRIDGE_INDEXER_URI=http://api
- HOST=0.0.0.0
- PORT=7300
- MIGRATE_APP_DB_USER=${MIGRATE_APP_DB_USER:-postgres}
- MIGRATE_APP_DB_PASSWORD=${MIGRATE_APP_DB_PASSWORD:-db_password}
- APP_DB_HOST=${APP_DB_HOST:-postgres-app}
- APP_DB_USER=${APP_DB_USER:-gateway-backend-goerli@oplabs-local-web.iam}
- APP_DB_NAME=${APP_DB_NAME:-gateway}
- APP_DB_PORT=${APP_DB_PORT:-5432}
- INDEXER_DB_HOST=${INDEXER_DB_HOST_GOERLI:-postgres-goerli}
- INDEXER_DB_USER=${INDEXER_DB_USER_GOERLI:-db_username}
- INDEXER_DB_PASS=${INDEXER_DB_PASSWORD_GOERLI:-db_password}
- INDEXER_DB_NAME=${INDEXER_DB_NAME_GOERLI:-db_name}
- INDEXER_DB_PORT=${INDEXER_DB_PORT_GOERLI:-5432}
- DATABASE_URL=${DATABASE_URL_GOERLI:-postgres://db_username:db_password@postgres-goerli:5432/db_name}
- JSON_RPC_URLS_L1=$JSON_RPC_URLS_L1_GOERLI
- JSON_RPC_URLS_L2=$JSON_RPC_URLS_L2_GOERLI
- JSON_RPC_URLS_L2_GOERLI=$JSON_RPC_URLS_L2_GOERLI
- FAUCET_AUTH_ADMIN_WALLET_PRIVATE_KEY=$FAUCET_AUTH_ADMIN_WALLET_PRIVATE_KEY
- IRON_SESSION_SECRET=${IRON_SESSION_SECRET:-UNKNOWN_IRON_SESSION_PASSWORD_32}
- CHAIN_ID_L1=5
- CHAIN_ID_L2=420
- FLEEK_BUCKET_ADDRESS=34a609661-6774-441f-9fdb-453fdbb89931-bucket
- FLEEK_API_SECRET=$FLEEK_API_SECRET
- FLEEK_API_KEY=$FLEEK_API_KEY
- MOCK_MERKLE_PROOF=true
- LOOP_INTERVAL_MINUTES=.1
- GITHUB_CLIENT_ID=$GITHUB_CLIENT_ID
- GITHUB_SECRET=$GITHUB_SECRET
- MAINNET_BEDROCK=true
- TRM_API_KEY=$TRM_API_KEY
- GOOGLE_CLOUD_STORAGE_BUCKET_NAME=oplabs-dev-web-content
# Recommened to uncomment for local dev unless you need it
#- BYPASS_EVENT_LOG_POLLER_BOOTSTRAP=true
ports:
- 7422:7300
# overrides command in Dockerfile so we can hot reload the server in docker while developing
#command: ['pnpm', 'nx', 'run', '@gateway/backend:docker:watch']
healthcheck:
test: curl http://0.0.0.0:7300/api/v0/healthz
volumes:
postgres_data:
......@@ -7,10 +7,9 @@ import (
"time"
"github.com/ethereum-optimism/optimism/indexer/node"
"github.com/ethereum-optimism/optimism/op-service/client/utils"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
......@@ -28,7 +27,7 @@ func TestE2EBlockHeaders(t *testing.T) {
require.NoError(t, err)
// wait for at least 10 L2 blocks to be created & posted on L1
require.NoError(t, utils.WaitFor(context.Background(), time.Second, func() (bool, error) {
require.NoError(t, wait.For(context.Background(), time.Second, func() (bool, error) {
l2Height, err := l2OutputOracle.LatestBlockNumber(&bind.CallOpts{Context: context.Background()})
return l2Height != nil && l2Height.Uint64() >= 9, err
}))
......@@ -36,7 +35,7 @@ func TestE2EBlockHeaders(t *testing.T) {
// ensure the processors are caught up to this state
l1Height, err := testSuite.L1Client.BlockNumber(context.Background())
require.NoError(t, err)
require.NoError(t, utils.WaitFor(context.Background(), time.Second, func() (bool, error) {
require.NoError(t, wait.For(context.Background(), time.Second, func() (bool, error) {
l1Header := testSuite.Indexer.L1Processor.LatestProcessedHeader()
l2Header := testSuite.Indexer.L2Processor.LatestProcessedHeader()
return (l1Header != nil && l1Header.Number.Uint64() >= l1Height) && (l2Header != nil && l2Header.Number.Uint64() >= 9), nil
......
......@@ -10,9 +10,8 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
op_e2e "github.com/ethereum-optimism/optimism/op-e2e"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait"
"github.com/ethereum-optimism/optimism/op-node/withdrawals"
"github.com/ethereum-optimism/optimism/op-service/client/utils"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/params"
......@@ -39,14 +38,14 @@ func TestE2EBridgeL1CrossDomainMessenger(t *testing.T) {
// (1) Send the Message
sentMsgTx, err := l1CrossDomainMessenger.SendMessage(l1Opts, aliceAddr, calldata, 100_000)
require.NoError(t, err)
sentMsgReceipt, err := utils.WaitReceiptOK(context.Background(), testSuite.L1Client, sentMsgTx.Hash())
sentMsgReceipt, err := wait.ForReceiptOK(context.Background(), testSuite.L1Client, sentMsgTx.Hash())
require.NoError(t, err)
depositInfo, err := e2etest_utils.ParseDepositInfo(sentMsgReceipt)
require.NoError(t, err)
// wait for processor catchup
require.NoError(t, utils.WaitFor(context.Background(), 500*time.Millisecond, func() (bool, error) {
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l1Header := testSuite.Indexer.L1Processor.LatestProcessedHeader()
return l1Header != nil && l1Header.Number.Uint64() >= sentMsgReceipt.BlockNumber.Uint64(), nil
}))
......@@ -77,9 +76,9 @@ func TestE2EBridgeL1CrossDomainMessenger(t *testing.T) {
require.NoError(t, err)
// wait for processor catchup
depositReceipt, err := utils.WaitReceiptOK(context.Background(), testSuite.L2Client, transaction.L2TransactionHash)
depositReceipt, err := wait.ForReceiptOK(context.Background(), testSuite.L2Client, transaction.L2TransactionHash)
require.NoError(t, err)
require.NoError(t, utils.WaitFor(context.Background(), 500*time.Millisecond, func() (bool, error) {
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l2Header := testSuite.Indexer.L2Processor.LatestProcessedHeader()
return l2Header != nil && l2Header.Number.Uint64() >= depositReceipt.BlockNumber.Uint64(), nil
}))
......@@ -117,13 +116,13 @@ func TestE2EBridgeL2CrossDomainMessenger(t *testing.T) {
l1Opts.Value = l2Opts.Value
depositTx, err := optimismPortal.Receive(l1Opts)
require.NoError(t, err)
_, err = utils.WaitReceiptOK(context.Background(), testSuite.L1Client, depositTx.Hash())
_, err = wait.ForReceiptOK(context.Background(), testSuite.L1Client, depositTx.Hash())
require.NoError(t, err)
// (1) Send the Message
sentMsgTx, err := l2CrossDomainMessenger.SendMessage(l2Opts, aliceAddr, calldata, 100_000)
require.NoError(t, err)
sentMsgReceipt, err := utils.WaitReceiptOK(context.Background(), testSuite.L2Client, sentMsgTx.Hash())
sentMsgReceipt, err := wait.ForReceiptOK(context.Background(), testSuite.L2Client, sentMsgTx.Hash())
require.NoError(t, err)
msgPassed, err := withdrawals.ParseMessagePassed(sentMsgReceipt)
......@@ -132,7 +131,7 @@ func TestE2EBridgeL2CrossDomainMessenger(t *testing.T) {
require.NoError(t, err)
// wait for processor catchup
require.NoError(t, utils.WaitFor(context.Background(), 500*time.Millisecond, func() (bool, error) {
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l2Header := testSuite.Indexer.L2Processor.LatestProcessedHeader()
return l2Header != nil && l2Header.Number.Uint64() >= sentMsgReceipt.BlockNumber.Uint64(), nil
}))
......@@ -161,7 +160,7 @@ func TestE2EBridgeL2CrossDomainMessenger(t *testing.T) {
_, finalizedReceipt := op_e2e.ProveAndFinalizeWithdrawal(t, *testSuite.OpCfg, testSuite.L1Client, testSuite.OpSys.Nodes["sequencer"], testSuite.OpCfg.Secrets.Alice, sentMsgReceipt)
// wait for processor catchup
require.NoError(t, utils.WaitFor(context.Background(), 500*time.Millisecond, func() (bool, error) {
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l1Header := testSuite.Indexer.L1Processor.LatestProcessedHeader()
return l1Header != nil && l1Header.Number.Uint64() >= finalizedReceipt.BlockNumber.Uint64(), nil
}))
......
......@@ -7,13 +7,12 @@ import (
"time"
e2etest_utils "github.com/ethereum-optimism/optimism/indexer/e2e_tests/utils"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
op_e2e "github.com/ethereum-optimism/optimism/op-e2e"
"github.com/ethereum-optimism/optimism/op-node/withdrawals"
"github.com/ethereum-optimism/optimism/op-service/client/utils"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"
......@@ -37,7 +36,7 @@ func TestE2EBridgeTransactionsOptimismPortalDeposits(t *testing.T) {
depositTx, err := optimismPortal.DepositTransaction(l1Opts, aliceAddr, l1Opts.Value, 100_000, false, calldata)
require.NoError(t, err)
depositReceipt, err := utils.WaitReceiptOK(context.Background(), testSuite.L1Client, depositTx.Hash())
depositReceipt, err := wait.ForReceiptOK(context.Background(), testSuite.L1Client, depositTx.Hash())
require.NoError(t, err)
depositInfo, err := e2etest_utils.ParseDepositInfo(depositReceipt)
......@@ -46,7 +45,7 @@ func TestE2EBridgeTransactionsOptimismPortalDeposits(t *testing.T) {
depositL2TxHash := types.NewTx(depositInfo.DepositTx).Hash()
// wait for processor catchup
require.NoError(t, utils.WaitFor(context.Background(), 500*time.Millisecond, func() (bool, error) {
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l1Header := testSuite.Indexer.L1Processor.LatestProcessedHeader()
return l1Header != nil && l1Header.Number.Uint64() >= depositReceipt.BlockNumber.Uint64(), nil
}))
......@@ -94,16 +93,16 @@ func TestE2EBridgeTransactionsL2ToL1MessagePasserWithdrawal(t *testing.T) {
l1Opts.Value = l2Opts.Value
depositTx, err := optimismPortal.Receive(l1Opts)
require.NoError(t, err)
_, err = utils.WaitReceiptOK(context.Background(), testSuite.L1Client, depositTx.Hash())
_, err = wait.ForReceiptOK(context.Background(), testSuite.L1Client, depositTx.Hash())
require.NoError(t, err)
withdrawTx, err := l2ToL1MessagePasser.InitiateWithdrawal(l2Opts, aliceAddr, big.NewInt(100_000), calldata)
require.NoError(t, err)
withdrawReceipt, err := utils.WaitReceiptOK(context.Background(), testSuite.L2Client, withdrawTx.Hash())
withdrawReceipt, err := wait.ForReceiptOK(context.Background(), testSuite.L2Client, withdrawTx.Hash())
require.NoError(t, err)
// wait for processor catchup
require.NoError(t, utils.WaitFor(context.Background(), 500*time.Millisecond, func() (bool, error) {
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l2Header := testSuite.Indexer.L2Processor.LatestProcessedHeader()
return l2Header != nil && l2Header.Number.Uint64() >= withdrawReceipt.BlockNumber.Uint64(), nil
}))
......@@ -132,7 +131,7 @@ func TestE2EBridgeTransactionsL2ToL1MessagePasserWithdrawal(t *testing.T) {
require.Nil(t, withdraw.FinalizedL1EventGUID)
withdrawParams, proveReceipt := op_e2e.ProveWithdrawal(t, *testSuite.OpCfg, testSuite.L1Client, testSuite.OpSys.Nodes["sequencer"], testSuite.OpCfg.Secrets.Alice, withdrawReceipt)
require.NoError(t, utils.WaitFor(context.Background(), 500*time.Millisecond, func() (bool, error) {
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l1Header := testSuite.Indexer.L1Processor.LatestProcessedHeader()
return l1Header != nil && l1Header.Number.Uint64() >= proveReceipt.BlockNumber.Uint64(), nil
}))
......@@ -150,7 +149,7 @@ func TestE2EBridgeTransactionsL2ToL1MessagePasserWithdrawal(t *testing.T) {
require.Nil(t, withdraw.FinalizedL1EventGUID)
finalizeReceipt := op_e2e.FinalizeWithdrawal(t, *testSuite.OpCfg, testSuite.L1Client, testSuite.OpCfg.Secrets.Alice, proveReceipt, withdrawParams)
require.NoError(t, utils.WaitFor(context.Background(), 500*time.Millisecond, func() (bool, error) {
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l1Header := testSuite.Indexer.L1Processor.LatestProcessedHeader()
return l1Header != nil && l1Header.Number.Uint64() >= finalizeReceipt.BlockNumber.Uint64(), nil
}))
......@@ -182,7 +181,7 @@ func TestE2EBridgeTransactionsL2ToL1MessagePasserFailedWithdrawal(t *testing.T)
withdrawTx, err := l2ToL1MessagePasser.InitiateWithdrawal(l2Opts, aliceAddr, big.NewInt(100_000), nil)
require.NoError(t, err)
withdrawReceipt, err := utils.WaitReceiptOK(context.Background(), testSuite.L2Client, withdrawTx.Hash())
withdrawReceipt, err := wait.ForReceiptOK(context.Background(), testSuite.L2Client, withdrawTx.Hash())
require.NoError(t, err)
msgPassed, err := withdrawals.ParseMessagePassed(withdrawReceipt)
......@@ -192,7 +191,7 @@ func TestE2EBridgeTransactionsL2ToL1MessagePasserFailedWithdrawal(t *testing.T)
// Prove&Finalize withdrawal
_, finalizeReceipt := op_e2e.ProveAndFinalizeWithdrawal(t, *testSuite.OpCfg, testSuite.L1Client, testSuite.OpSys.Nodes["sequencer"], testSuite.OpCfg.Secrets.Alice, withdrawReceipt)
require.NoError(t, utils.WaitFor(context.Background(), 500*time.Millisecond, func() (bool, error) {
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l1Header := testSuite.Indexer.L1Processor.LatestProcessedHeader()
return l1Header != nil && l1Header.Number.Uint64() >= finalizeReceipt.BlockNumber.Uint64(), nil
}))
......
......@@ -9,12 +9,11 @@ import (
e2etest_utils "github.com/ethereum-optimism/optimism/indexer/e2e_tests/utils"
"github.com/ethereum-optimism/optimism/indexer/processor"
op_e2e "github.com/ethereum-optimism/optimism/op-e2e"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait"
"github.com/ethereum-optimism/optimism/op-node/withdrawals"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-service/client/utils"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"
......@@ -37,14 +36,14 @@ func TestE2EBridgeTransfersStandardBridgeETHDeposit(t *testing.T) {
// (1) Test Deposit Initiation
depositTx, err := l1StandardBridge.DepositETH(l1Opts, 200_000, []byte{byte(1)})
require.NoError(t, err)
depositReceipt, err := utils.WaitReceiptOK(context.Background(), testSuite.L1Client, depositTx.Hash())
depositReceipt, err := wait.ForReceiptOK(context.Background(), testSuite.L1Client, depositTx.Hash())
require.NoError(t, err)
depositInfo, err := e2etest_utils.ParseDepositInfo(depositReceipt)
require.NoError(t, err)
// wait for processor catchup
require.NoError(t, utils.WaitFor(context.Background(), 500*time.Millisecond, func() (bool, error) {
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l1Header := testSuite.Indexer.L1Processor.LatestProcessedHeader()
return l1Header != nil && l1Header.Number.Uint64() >= depositReceipt.BlockNumber.Uint64(), nil
}))
......@@ -71,9 +70,9 @@ func TestE2EBridgeTransfersStandardBridgeETHDeposit(t *testing.T) {
require.Zero(t, nonce.Uint64())
// (2) Test Deposit Finalization via CrossDomainMessenger relayed message
depositReceipt, err = utils.WaitReceiptOK(context.Background(), testSuite.L2Client, types.NewTx(depositInfo.DepositTx).Hash())
depositReceipt, err = wait.ForReceiptOK(context.Background(), testSuite.L2Client, types.NewTx(depositInfo.DepositTx).Hash())
require.NoError(t, err)
require.NoError(t, utils.WaitFor(context.Background(), 500*time.Millisecond, func() (bool, error) {
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l2Header := testSuite.Indexer.L2Processor.LatestProcessedHeader()
return l2Header != nil && l2Header.Number.Uint64() >= depositReceipt.BlockNumber.Uint64(), nil
}))
......@@ -99,14 +98,14 @@ func TestE2EBridgeTransfersOptimismPortalETHReceive(t *testing.T) {
// (1) Test Deposit Initiation
portalDepositTx, err := optimismPortal.Receive(l1Opts)
require.NoError(t, err)
portalDepositReceipt, err := utils.WaitReceiptOK(context.Background(), testSuite.L1Client, portalDepositTx.Hash())
portalDepositReceipt, err := wait.ForReceiptOK(context.Background(), testSuite.L1Client, portalDepositTx.Hash())
require.NoError(t, err)
depositInfo, err := e2etest_utils.ParseDepositInfo(portalDepositReceipt)
require.NoError(t, err)
// wait for processor catchup
require.NoError(t, utils.WaitFor(context.Background(), 500*time.Millisecond, func() (bool, error) {
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l1Header := testSuite.Indexer.L1Processor.LatestProcessedHeader()
return l1Header != nil && l1Header.Number.Uint64() >= portalDepositReceipt.BlockNumber.Uint64(), nil
}))
......@@ -152,17 +151,17 @@ func TestE2EBridgeTransfersStandardBridgeETHWithdrawal(t *testing.T) {
l1Opts.Value = l2Opts.Value
depositTx, err := optimismPortal.Receive(l1Opts)
require.NoError(t, err)
_, err = utils.WaitReceiptOK(context.Background(), testSuite.L1Client, depositTx.Hash())
_, err = wait.ForReceiptOK(context.Background(), testSuite.L1Client, depositTx.Hash())
require.NoError(t, err)
// (1) Test Withdrawal Initiation
withdrawTx, err := l2StandardBridge.Withdraw(l2Opts, predeploys.LegacyERC20ETHAddr, l2Opts.Value, 200_000, []byte{byte(1)})
require.NoError(t, err)
withdrawReceipt, err := utils.WaitReceiptOK(context.Background(), testSuite.L2Client, withdrawTx.Hash())
withdrawReceipt, err := wait.ForReceiptOK(context.Background(), testSuite.L2Client, withdrawTx.Hash())
require.NoError(t, err)
// wait for processor catchup
require.NoError(t, utils.WaitFor(context.Background(), 500*time.Millisecond, func() (bool, error) {
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l2Header := testSuite.Indexer.L2Processor.LatestProcessedHeader()
return l2Header != nil && l2Header.Number.Uint64() >= withdrawReceipt.BlockNumber.Uint64(), nil
}))
......@@ -198,7 +197,7 @@ func TestE2EBridgeTransfersStandardBridgeETHWithdrawal(t *testing.T) {
// wait for processor catchup
proveReceipt, finalizeReceipt := op_e2e.ProveAndFinalizeWithdrawal(t, *testSuite.OpCfg, testSuite.L1Client, testSuite.OpSys.Nodes["sequencer"], testSuite.OpCfg.Secrets.Alice, withdrawReceipt)
require.NoError(t, utils.WaitFor(context.Background(), 500*time.Millisecond, func() (bool, error) {
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l1Header := testSuite.Indexer.L1Processor.LatestProcessedHeader()
return l1Header != nil && l1Header.Number.Uint64() >= finalizeReceipt.BlockNumber.Uint64(), nil
}))
......@@ -229,17 +228,17 @@ func TestE2EBridgeTransfersL2ToL1MessagePasserReceive(t *testing.T) {
l1Opts.Value = l2Opts.Value
depositTx, err := optimismPortal.Receive(l1Opts)
require.NoError(t, err)
_, err = utils.WaitReceiptOK(context.Background(), testSuite.L1Client, depositTx.Hash())
_, err = wait.ForReceiptOK(context.Background(), testSuite.L1Client, depositTx.Hash())
require.NoError(t, err)
// (1) Test Withdrawal Initiation
l2ToL1MessagePasserWithdrawTx, err := l2ToL1MessagePasser.Receive(l2Opts)
require.NoError(t, err)
l2ToL1WithdrawReceipt, err := utils.WaitReceiptOK(context.Background(), testSuite.L2Client, l2ToL1MessagePasserWithdrawTx.Hash())
l2ToL1WithdrawReceipt, err := wait.ForReceiptOK(context.Background(), testSuite.L2Client, l2ToL1MessagePasserWithdrawTx.Hash())
require.NoError(t, err)
// wait for processor catchup
require.NoError(t, utils.WaitFor(context.Background(), 500*time.Millisecond, func() (bool, error) {
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l2Header := testSuite.Indexer.L2Processor.LatestProcessedHeader()
return l2Header != nil && l2Header.Number.Uint64() >= l2ToL1WithdrawReceipt.BlockNumber.Uint64(), nil
}))
......@@ -271,7 +270,7 @@ func TestE2EBridgeTransfersL2ToL1MessagePasserReceive(t *testing.T) {
// wait for processor catchup
proveReceipt, finalizeReceipt := op_e2e.ProveAndFinalizeWithdrawal(t, *testSuite.OpCfg, testSuite.L1Client, testSuite.OpSys.Nodes["sequencer"], testSuite.OpCfg.Secrets.Alice, l2ToL1WithdrawReceipt)
require.NoError(t, utils.WaitFor(context.Background(), 500*time.Millisecond, func() (bool, error) {
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l1Header := testSuite.Indexer.L1Processor.LatestProcessedHeader()
return l1Header != nil && l1Header.Number.Uint64() >= finalizeReceipt.BlockNumber.Uint64(), nil
}))
......
......@@ -13,7 +13,6 @@ import (
"github.com/ethereum-optimism/optimism/indexer"
"github.com/ethereum-optimism/optimism/indexer/config"
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum-optimism/optimism/indexer/processor"
op_e2e "github.com/ethereum-optimism/optimism/op-e2e"
"github.com/ethereum-optimism/optimism/op-node/testlog"
......@@ -59,7 +58,7 @@ func createE2ETestSuite(t *testing.T) E2ETestSuite {
// Indexer Configuration and Start
indexerCfg := config.Config{
Logger: logger,
DB: config.DBConfig{
Host: "127.0.0.1",
Port: 5432,
......@@ -71,7 +70,7 @@ func createE2ETestSuite(t *testing.T) E2ETestSuite {
L2RPC: opSys.Nodes["sequencer"].HTTPEndpoint(),
},
Chain: config.ChainConfig{
L1Contracts: processor.L1Contracts{
L1Contracts: config.L1Contracts{
OptimismPortal: opCfg.L1Deployments.OptimismPortalProxy,
L2OutputOracle: opCfg.L1Deployments.L2OutputOracleProxy,
L1CrossDomainMessenger: opCfg.L1Deployments.L1CrossDomainMessengerProxy,
......@@ -81,9 +80,14 @@ func createE2ETestSuite(t *testing.T) E2ETestSuite {
},
}
db, err := database.NewDB(fmt.Sprintf("postgres://%s@localhost:5432/%s?sslmode=disable", dbUser, dbName))
db, err := database.NewDB(indexerCfg.DB)
require.NoError(t, err)
indexer, err := indexer.NewIndexer(indexerCfg)
indexer, err := indexer.NewIndexer(
indexerCfg.Chain,
indexerCfg.RPCs,
db,
logger,
)
require.NoError(t, err)
indexerStoppedCh := make(chan interface{}, 1)
......
......@@ -24,44 +24,31 @@ type Indexer struct {
}
// NewIndexer initializes an instance of the Indexer
func NewIndexer(cfg config.Config) (*Indexer, error) {
dsn := fmt.Sprintf("host=%s port=%d dbname=%s sslmode=disable", cfg.DB.Host, cfg.DB.Port, cfg.DB.Name)
if cfg.DB.User != "" {
dsn += fmt.Sprintf(" user=%s", cfg.DB.User)
}
if cfg.DB.Password != "" {
dsn += fmt.Sprintf(" password=%s", cfg.DB.Password)
}
db, err := database.NewDB(dsn)
if err != nil {
return nil, err
}
l1Contracts := cfg.Chain.L1Contracts
l1EthClient, err := node.DialEthClient(cfg.RPCs.L1RPC)
func NewIndexer(chainConfig config.ChainConfig, rpcsConfig config.RPCsConfig, db *database.DB, logger log.Logger) (*Indexer, error) {
l1Contracts := chainConfig.L1Contracts
l1EthClient, err := node.DialEthClient(rpcsConfig.L1RPC)
if err != nil {
return nil, err
}
l1Processor, err := processor.NewL1Processor(cfg.Logger, l1EthClient, db, l1Contracts)
l1Processor, err := processor.NewL1Processor(logger, l1EthClient, db, l1Contracts)
if err != nil {
return nil, err
}
// L2Processor (predeploys). Although most likely the right setting, make this configurable?
l2Contracts := processor.L2ContractPredeploys()
l2EthClient, err := node.DialEthClient(cfg.RPCs.L2RPC)
l2EthClient, err := node.DialEthClient(rpcsConfig.L2RPC)
if err != nil {
return nil, err
}
l2Processor, err := processor.NewL2Processor(cfg.Logger, l2EthClient, db, l2Contracts)
l2Processor, err := processor.NewL2Processor(logger, l2EthClient, db, l2Contracts)
if err != nil {
return nil, err
}
indexer := &Indexer{
db: db,
log: cfg.Logger,
log: logger,
L1Processor: l1Processor,
L2Processor: l2Processor,
}
......
# Chain configures l1 chain addresses
# Can configure them manually or use a preset l2 ChainId for known chains including OP Mainnet, OP Goerli, Base, Base Goerli, Zora, and Zora goerli
[chain]
# OP Goerli
preset = 420
[chain.l1-contracts]
......
......@@ -6,8 +6,8 @@ import (
"errors"
"fmt"
"math/big"
"reflect"
"github.com/ethereum-optimism/optimism/indexer/config"
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum-optimism/optimism/indexer/node"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
......@@ -23,35 +23,6 @@ import (
"github.com/ethereum/go-ethereum/log"
)
// L1Contracts contains all L1 system contract addresses
// NOTE - There are no active validations to ensure that the
// addresses provided are not nil or duplicated
type L1Contracts struct {
OptimismPortal common.Address `toml:"optimism-portal"`
L2OutputOracle common.Address `toml:"l2-output-oracle"`
L1CrossDomainMessenger common.Address `toml:"l1-cross-domain-messenger"`
L1StandardBridge common.Address `toml:"l1-standard-bridge"`
L1ERC721Bridge common.Address `toml:"l1-erc721-bridge"`
// Some more contracts -- ProxyAdmin, SystemConfig, etc
// Ignore the auxiliary contracts?
// Legacy contracts? We'll add this in to index the legacy chain.
// Remove afterwards?
}
func (c L1Contracts) ToSlice() []common.Address {
fields := reflect.VisibleFields(reflect.TypeOf(c))
v := reflect.ValueOf(c)
contracts := make([]common.Address, len(fields))
for i, field := range fields {
contracts[i] = (v.FieldByName(field.Name).Interface()).(common.Address)
}
return contracts
}
type checkpointAbi struct {
l2OutputOracle *abi.ABI
legacyStateCommitmentChain *abi.ABI
......@@ -61,7 +32,7 @@ type L1Processor struct {
processor
}
func NewL1Processor(logger log.Logger, ethClient node.EthClient, db *database.DB, l1Contracts L1Contracts) (*L1Processor, error) {
func NewL1Processor(logger log.Logger, ethClient node.EthClient, db *database.DB, l1Contracts config.L1Contracts) (*L1Processor, error) {
l1ProcessLog := logger.New("processor", "l1")
l1ProcessLog.Info("initializing processor")
......@@ -110,7 +81,7 @@ func NewL1Processor(logger log.Logger, ethClient node.EthClient, db *database.DB
return l1Processor, nil
}
func l1ProcessFn(processLog log.Logger, ethClient node.EthClient, l1Contracts L1Contracts, checkpointAbi checkpointAbi) ProcessFn {
func l1ProcessFn(processLog log.Logger, ethClient node.EthClient, l1Contracts config.L1Contracts, checkpointAbi checkpointAbi) ProcessFn {
rawEthClient := ethclient.NewClient(ethClient.RawRpcClient())
contractAddrs := l1Contracts.ToSlice()
......@@ -264,7 +235,7 @@ func l1ProcessFn(processLog log.Logger, ethClient node.EthClient, l1Contracts L1
}
}
func l1ProcessContractEventsBridgeTransactions(processLog log.Logger, db *database.DB, l1Contracts L1Contracts, events *ProcessedContractEvents) error {
func l1ProcessContractEventsBridgeTransactions(processLog log.Logger, db *database.DB, l1Contracts config.L1Contracts, events *ProcessedContractEvents) error {
// (1) Process New Deposits
portalDeposits, err := OptimismPortalTransactionDepositEvents(events)
if err != nil {
......@@ -297,6 +268,7 @@ func l1ProcessContractEventsBridgeTransactions(processLog log.Logger, db *databa
TransactionSourceHash: depositTx.SourceHash,
Tx: transactionDeposits[i].Tx,
TokenPair: database.TokenPair{
// TODO index eth token if it doesn't exist
L1TokenAddress: predeploys.LegacyERC20ETHAddr,
L2TokenAddress: predeploys.LegacyERC20ETHAddr,
},
......@@ -495,7 +467,8 @@ func l1ProcessContractEventsStandardBridge(processLog log.Logger, db *database.D
deposits[i] = &database.L1BridgeDeposit{
TransactionSourceHash: depositTx.SourceHash,
CrossDomainMessengerNonce: &database.U256{Int: initiatedBridgeEvent.CrossDomainMessengerNonce},
TokenPair: database.TokenPair{L1TokenAddress: initiatedBridgeEvent.LocalToken, L2TokenAddress: initiatedBridgeEvent.RemoteToken},
// TODO index the tokens pairs if they don't exist
TokenPair: database.TokenPair{L1TokenAddress: initiatedBridgeEvent.LocalToken, L2TokenAddress: initiatedBridgeEvent.RemoteToken},
Tx: database.Transaction{
FromAddress: initiatedBridgeEvent.From,
ToAddress: initiatedBridgeEvent.To,
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -9,7 +9,7 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/fault/types"
"github.com/ethereum-optimism/optimism/op-service/client/utils"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
......@@ -34,7 +34,7 @@ func (g *FaultGameHelper) GameDuration(ctx context.Context) time.Duration {
func (g *FaultGameHelper) WaitForClaimCount(ctx context.Context, count int64) {
ctx, cancel := context.WithTimeout(ctx, 2*time.Minute)
defer cancel()
err := utils.WaitFor(ctx, time.Second, func() (bool, error) {
err := wait.For(ctx, time.Second, func() (bool, error) {
actual, err := g.game.ClaimDataLen(&bind.CallOpts{Context: ctx})
if err != nil {
return false, err
......@@ -62,7 +62,7 @@ func (g *FaultGameHelper) MaxDepth(ctx context.Context) int64 {
func (g *FaultGameHelper) WaitForClaim(ctx context.Context, predicate func(claim ContractClaim) bool) {
ctx, cancel := context.WithTimeout(ctx, time.Minute)
defer cancel()
err := utils.WaitFor(ctx, time.Second, func() (bool, error) {
err := wait.For(ctx, time.Second, func() (bool, error) {
count, err := g.game.ClaimDataLen(&bind.CallOpts{Context: ctx})
if err != nil {
return false, fmt.Errorf("retrieve number of claims: %w", err)
......@@ -105,7 +105,7 @@ func (g *FaultGameHelper) Resolve(ctx context.Context) {
defer cancel()
tx, err := g.game.Resolve(g.opts)
g.require.NoError(err)
_, err = utils.WaitReceiptOK(ctx, g.client, tx.Hash())
_, err = wait.ForReceiptOK(ctx, g.client, tx.Hash())
g.require.NoError(err)
}
......@@ -113,7 +113,7 @@ func (g *FaultGameHelper) WaitForGameStatus(ctx context.Context, expected Status
g.t.Logf("Waiting for game %v to have status %v", g.addr, expected)
ctx, cancel := context.WithTimeout(ctx, time.Minute)
defer cancel()
err := utils.WaitFor(ctx, time.Second, func() (bool, error) {
err := wait.For(ctx, time.Second, func() (bool, error) {
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
status, err := g.game.Status(&bind.CallOpts{Context: ctx})
......@@ -129,14 +129,14 @@ func (g *FaultGameHelper) WaitForGameStatus(ctx context.Context, expected Status
func (g *FaultGameHelper) Attack(ctx context.Context, claimIdx int64, claim common.Hash) {
tx, err := g.game.Attack(g.opts, big.NewInt(claimIdx), claim)
g.require.NoError(err, "Attack transaction did not send")
_, err = utils.WaitReceiptOK(ctx, g.client, tx.Hash())
_, err = wait.ForReceiptOK(ctx, g.client, tx.Hash())
g.require.NoError(err, "Attack transaction was not OK")
}
func (g *FaultGameHelper) Defend(ctx context.Context, claimIdx int64, claim common.Hash) {
tx, err := g.game.Defend(g.opts, big.NewInt(claimIdx), claim)
g.require.NoError(err, "Defend transaction did not send")
_, err = utils.WaitReceiptOK(ctx, g.client, tx.Hash())
_, err = wait.ForReceiptOK(ctx, g.client, tx.Hash())
g.require.NoError(err, "Defend transaction was not OK")
}
......
......@@ -11,8 +11,10 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-chain-ops/deployer"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
"github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/fault/alphabet"
"github.com/ethereum-optimism/optimism/op-service/client/utils"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
......@@ -52,6 +54,7 @@ type FactoryHelper struct {
require *require.Assertions
client *ethclient.Client
opts *bind.TransactOpts
factoryAddr common.Address
factory *bindings.DisputeGameFactory
blockOracle *bindings.BlockOracle
l2oo *bindings.L2OutputOracleCaller
......@@ -65,7 +68,8 @@ func NewFactoryHelper(t *testing.T, ctx context.Context, deployments *genesis.L1
require.NoError(err)
require.NotNil(deployments, "No deployments")
factory, err := bindings.NewDisputeGameFactory(deployments.DisputeGameFactoryProxy, client)
factoryAddr := deployments.DisputeGameFactoryProxy
factory, err := bindings.NewDisputeGameFactory(factoryAddr, client)
require.NoError(err)
blockOracle, err := bindings.NewBlockOracle(deployments.BlockOracle, client)
require.NoError(err)
......@@ -78,6 +82,7 @@ func NewFactoryHelper(t *testing.T, ctx context.Context, deployments *genesis.L1
client: client,
opts: opts,
factory: factory,
factoryAddr: factoryAddr,
blockOracle: blockOracle,
l2oo: l2oo,
}
......@@ -98,7 +103,7 @@ func (h *FactoryHelper) StartAlphabetGame(ctx context.Context, claimedAlphabet s
binary.BigEndian.PutUint64(extraData[56:], l1Head.Uint64())
tx, err := h.factory.Create(h.opts, alphabetGameType, rootClaim, extraData)
h.require.NoError(err, "create fault dispute game")
rcpt, err := utils.WaitReceiptOK(ctx, h.client, tx.Hash())
rcpt, err := wait.ForReceiptOK(ctx, h.client, tx.Hash())
h.require.NoError(err, "wait for create fault dispute game receipt to be OK")
h.require.Len(rcpt.Logs, 1, "should have emitted a single DisputeGameCreated event")
createdEvent, err := h.factory.ParseDisputeGameCreated(*rcpt.Logs[0])
......@@ -131,7 +136,7 @@ func (h *FactoryHelper) StartCannonGame(ctx context.Context, rootClaim common.Ha
binary.BigEndian.PutUint64(extraData[56:], l1Head.Uint64())
tx, err := h.factory.Create(h.opts, cannonGameType, rootClaim, extraData)
h.require.NoError(err, "create fault dispute game")
rcpt, err := utils.WaitReceiptOK(ctx, h.client, tx.Hash())
rcpt, err := wait.ForReceiptOK(ctx, h.client, tx.Hash())
h.require.NoError(err, "wait for create fault dispute game receipt to be OK")
h.require.Len(rcpt.Logs, 1, "should have emitted a single DisputeGameCreated event")
createdEvent, err := h.factory.ParseDisputeGameCreated(*rcpt.Logs[0])
......@@ -150,6 +155,21 @@ func (h *FactoryHelper) StartCannonGame(ctx context.Context, rootClaim common.Ha
},
}
}
func (h *FactoryHelper) StartChallenger(ctx context.Context, l1Endpoint string, name string, options ...challenger.Option) *challenger.Helper {
opts := []challenger.Option{
func(c *config.Config) {
// Uncomment when challenger actually supports setting the game factory address
//c.FactoryAddress = h.factoryAddr
c.TraceType = config.TraceTypeAlphabet
},
}
opts = append(opts, options...)
c := challenger.NewChallenger(h.t, ctx, l1Endpoint, name, opts...)
h.t.Cleanup(func() {
_ = c.Close()
})
return c
}
// waitForProposals waits until there are at least two proposals in the output oracle
// This is the minimum required for creating a game.
......@@ -158,7 +178,7 @@ func (h *FactoryHelper) waitForProposals(ctx context.Context) uint64 {
ctx, cancel := context.WithTimeout(ctx, 2*time.Minute)
defer cancel()
opts := &bind.CallOpts{Context: ctx}
latestOutputIndex, err := utils.WaitAndGet(
latestOutputIndex, err := wait.AndGet(
ctx,
time.Second,
func() (*big.Int, error) {
......@@ -187,7 +207,7 @@ func (h *FactoryHelper) checkpointL1Block(ctx context.Context) *big.Int {
// Store the current block in the oracle
tx, err := h.blockOracle.Checkpoint(h.opts)
h.require.NoError(err)
r, err := utils.WaitReceiptOK(ctx, h.client, tx.Hash())
r, err := wait.ForReceiptOK(ctx, h.client, tx.Hash())
h.require.NoError(err, "failed to store block in block oracle")
return new(big.Int).Sub(r.BlockNumber, big.NewInt(1))
}
package utils
package wait
import (
"context"
......@@ -8,19 +8,20 @@ import (
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
)
func WaitReceiptOK(ctx context.Context, client *ethclient.Client, hash common.Hash) (*types.Receipt, error) {
return WaitReceipt(ctx, client, hash, types.ReceiptStatusSuccessful)
func ForReceiptOK(ctx context.Context, client *ethclient.Client, hash common.Hash) (*types.Receipt, error) {
return ForReceipt(ctx, client, hash, types.ReceiptStatusSuccessful)
}
func WaitReceiptFail(ctx context.Context, client *ethclient.Client, hash common.Hash) (*types.Receipt, error) {
return WaitReceipt(ctx, client, hash, types.ReceiptStatusFailed)
func ForReceiptFail(ctx context.Context, client *ethclient.Client, hash common.Hash) (*types.Receipt, error) {
return ForReceipt(ctx, client, hash, types.ReceiptStatusFailed)
}
func WaitReceipt(ctx context.Context, client *ethclient.Client, hash common.Hash, status uint64) (*types.Receipt, error) {
func ForReceipt(ctx context.Context, client *ethclient.Client, hash common.Hash, status uint64) (*types.Receipt, error) {
ticker := time.NewTicker(100 * time.Millisecond)
defer ticker.Stop()
for {
......@@ -34,16 +35,35 @@ func WaitReceipt(ctx context.Context, client *ethclient.Client, hash common.Hash
}
}
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to get receipt: %w", err)
}
if receipt.Status != status {
return receipt, fmt.Errorf("expected status %d, but got %d", status, receipt.Status)
return receipt, addDebugTrace(ctx, client, hash, fmt.Errorf("expected status %d, but got %d", status, receipt.Status))
}
return receipt, nil
}
}
func WaitBlock(ctx context.Context, client *ethclient.Client, n uint64) error {
type jsonRawString string
func (s *jsonRawString) UnmarshalJSON(input []byte) error {
str := jsonRawString(input)
*s = str
return nil
}
// addDebugTrace adds debug_traceTransaction output to the original error to make debugging
func addDebugTrace(ctx context.Context, client *ethclient.Client, txHash common.Hash, origErr error) error {
var result jsonRawString
options := map[string]string{}
err := client.Client().CallContext(ctx, &result, "debug_traceTransaction", hexutil.Bytes(txHash.Bytes()), options)
if err != nil {
return errors.Join(origErr, fmt.Errorf("tx trace unavailable: %w", err))
}
return fmt.Errorf("%w\nTxTrace: %v", origErr, result)
}
func ForBlock(ctx context.Context, client *ethclient.Client, n uint64) error {
for {
height, err := client.BlockNumber(ctx)
if err != nil {
......@@ -59,15 +79,15 @@ func WaitBlock(ctx context.Context, client *ethclient.Client, n uint64) error {
return nil
}
func WaitNextBlock(ctx context.Context, client *ethclient.Client) error {
func ForNextBlock(ctx context.Context, client *ethclient.Client) error {
current, err := client.BlockNumber(ctx)
if err != nil {
return fmt.Errorf("get starting block number: %w", err)
}
return WaitBlock(ctx, client, current+1)
return ForBlock(ctx, client, current+1)
}
func WaitFor(ctx context.Context, rate time.Duration, cb func() (bool, error)) error {
func For(ctx context.Context, rate time.Duration, cb func() (bool, error)) error {
tick := time.NewTicker(rate)
defer tick.Stop()
......@@ -87,7 +107,7 @@ func WaitFor(ctx context.Context, rate time.Duration, cb func() (bool, error)) e
}
}
func WaitAndGet[T interface{}](ctx context.Context, pollRate time.Duration, get func() (T, error), predicate func(T) bool) (T, error) {
func AndGet[T interface{}](ctx context.Context, pollRate time.Duration, get func() (T, error), predicate func(T) bool) (T, error) {
tick := time.NewTicker(pollRate)
defer tick.Stop()
......
package wait
import (
"context"
"fmt"
"math/big"
"time"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
)
// ForOutputRootPublished waits until there is an output published for an L2 block number larger than the supplied l2BlockNumber
// This function polls and can block for a very long time if used on mainnet.
// This returns the block number to use for proof generation.
func ForOutputRootPublished(ctx context.Context, client *ethclient.Client, l2OutputOracleAddr common.Address, l2BlockNumber *big.Int) (uint64, error) {
l2BlockNumber = new(big.Int).Set(l2BlockNumber) // Don't clobber caller owned l2BlockNumber
opts := &bind.CallOpts{Context: ctx}
l2OO, err := bindings.NewL2OutputOracleCaller(l2OutputOracleAddr, client)
if err != nil {
return 0, err
}
getL2BlockFromLatestOutput := func() (*big.Int, error) { return l2OO.LatestBlockNumber(opts) }
outputBlockNum, err := AndGet(ctx, time.Second, getL2BlockFromLatestOutput, func(latest *big.Int) bool {
return latest.Cmp(l2BlockNumber) >= 0
})
if err != nil {
return 0, err
}
return outputBlockNum.Uint64(), nil
}
// ForFinalizationPeriod waits until the L1 chain has progressed far enough that l1ProvingBlockNum has completed
// the finalization period.
// This functions polls and can block for a very long time if used on mainnet.
func ForFinalizationPeriod(ctx context.Context, client *ethclient.Client, l1ProvingBlockNum *big.Int, l2OutputOracleAddr common.Address) error {
l1ProvingBlockNum = new(big.Int).Set(l1ProvingBlockNum) // Don't clobber caller owned l1ProvingBlockNum
opts := &bind.CallOpts{Context: ctx}
// Load finalization period
l2OO, err := bindings.NewL2OutputOracleCaller(l2OutputOracleAddr, client)
if err != nil {
return fmt.Errorf("create L2OOCaller: %w", err)
}
finalizationPeriod, err := l2OO.FINALIZATIONPERIODSECONDS(opts)
if err != nil {
return fmt.Errorf("get finalization period: %w", err)
}
provingHeader, err := client.HeaderByNumber(ctx, l1ProvingBlockNum)
if err != nil {
return fmt.Errorf("retrieve proving header: %w", err)
}
targetTimestamp := new(big.Int).Add(new(big.Int).SetUint64(provingHeader.Time), finalizationPeriod)
targetTime := time.Unix(targetTimestamp.Int64(), 0)
// Assume clock is relatively correct
time.Sleep(time.Until(targetTime))
// Poll for L1 Block to have a time greater than the target time
return For(ctx, time.Second, func() (bool, error) {
header, err := client.HeaderByNumber(ctx, nil)
if err != nil {
return false, fmt.Errorf("retrieve latest header: %w", err)
}
return header.Time > targetTimestamp.Uint64(), nil
})
}
......@@ -7,12 +7,43 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/disputegame"
"github.com/ethereum-optimism/optimism/op-service/client/utils"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/stretchr/testify/require"
)
func TestCannonMultipleGames(t *testing.T) {
t.Skip("Challenger doesn't yet support multiple games")
InitParallel(t)
ctx := context.Background()
sys, l1Client := startFaultDisputeSystem(t)
t.Cleanup(sys.Close)
gameFactory := disputegame.NewFactoryHelper(t, ctx, sys.cfg.L1Deployments, l1Client)
// Start a challenger with the correct alphabet trace
gameFactory.StartChallenger(ctx, sys.NodeEndpoint("l1"), "TowerDefense", func(c *config.Config) {
c.AgreeWithProposedOutput = true
c.AlphabetTrace = "abcdefg"
c.TxMgrConfig.PrivateKey = e2eutils.EncodePrivKeyToString(sys.cfg.Secrets.Alice)
})
game1 := gameFactory.StartAlphabetGame(ctx, "abcxyz")
// Wait for the challenger to respond to the first game
game1.WaitForClaimCount(ctx, 2)
game2 := gameFactory.StartAlphabetGame(ctx, "zyxabc")
// Wait for the challenger to respond to the second game
game2.WaitForClaimCount(ctx, 2)
// Challenger should respond to new claims
game2.Attack(ctx, 1, common.Hash{0xaa})
game2.WaitForClaimCount(ctx, 4)
game1.Defend(ctx, 1, common.Hash{0xaa})
game1.WaitForClaimCount(ctx, 4)
}
func TestResolveDisputeGame(t *testing.T) {
InitParallel(t)
......@@ -37,7 +68,7 @@ func TestResolveDisputeGame(t *testing.T) {
game.WaitForClaimCount(ctx, 2)
sys.TimeTravelClock.AdvanceTime(gameDuration)
require.NoError(t, utils.WaitNextBlock(ctx, l1Client))
require.NoError(t, wait.ForNextBlock(ctx, l1Client))
// Challenger should resolve the game now that the clocks have expired.
game.WaitForGameStatus(ctx, disputegame.StatusChallengerWins)
......@@ -138,7 +169,7 @@ func TestChallengerCompleteDisputeGame(t *testing.T) {
game.WaitForClaimAtMaxDepth(ctx, test.expectStep)
sys.TimeTravelClock.AdvanceTime(gameDuration)
require.NoError(t, utils.WaitNextBlock(ctx, l1Client))
require.NoError(t, wait.ForNextBlock(ctx, l1Client))
game.WaitForGameStatus(ctx, test.expectedResult)
})
......@@ -198,7 +229,7 @@ func TestCannonDisputeGame(t *testing.T) {
game.WaitForClaimAtMaxDepth(ctx, true)
sys.TimeTravelClock.AdvanceTime(game.GameDuration(ctx))
require.NoError(t, utils.WaitNextBlock(ctx, l1Client))
require.NoError(t, wait.ForNextBlock(ctx, l1Client))
game.WaitForGameStatus(ctx, disputegame.StatusChallengerWins)
game.LogGameData(ctx)
......@@ -253,7 +284,7 @@ func TestCannonDefendStep(t *testing.T) {
game.WaitForClaimAtMaxDepth(ctx, true)
sys.TimeTravelClock.AdvanceTime(game.GameDuration(ctx))
require.NoError(t, utils.WaitNextBlock(ctx, l1Client))
require.NoError(t, wait.ForNextBlock(ctx, l1Client))
game.WaitForGameStatus(ctx, disputegame.StatusChallengerWins)
game.LogGameData(ctx)
......
......@@ -13,6 +13,7 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait"
"github.com/ethereum-optimism/optimism/op-node/testutils/fuzzerutils"
"github.com/ethereum-optimism/optimism/op-node/withdrawals"
"github.com/ethereum/go-ethereum/accounts"
......@@ -534,7 +535,7 @@ func TestMixedWithdrawalValidity(t *testing.T) {
// Wait for the finalization period, then we can finalize this withdrawal.
ctx, cancel = context.WithTimeout(context.Background(), 40*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second)
require.NotEqual(t, cfg.L1Deployments.L2OutputOracleProxy, common.Address{})
blockNumber, err := withdrawals.WaitForOutputRootPublished(ctx, l1Client, cfg.L1Deployments.L2OutputOracleProxy, receipt.BlockNumber)
blockNumber, err := wait.ForOutputRootPublished(ctx, l1Client, cfg.L1Deployments.L2OutputOracleProxy, receipt.BlockNumber)
cancel()
require.Nil(t, err)
......@@ -662,7 +663,7 @@ func TestMixedWithdrawalValidity(t *testing.T) {
// Wait for finalization and then create the Finalized Withdrawal Transaction
ctx, cancel = context.WithTimeout(context.Background(), 45*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second)
defer cancel()
err = withdrawals.WaitForFinalizationPeriod(ctx, l1Client, header.Number, cfg.L1Deployments.L2OutputOracleProxy)
err = wait.ForFinalizationPeriod(ctx, l1Client, header.Number, cfg.L1Deployments.L2OutputOracleProxy)
require.Nil(t, err)
// Finalize withdrawal
......
......@@ -10,6 +10,7 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-e2e/config"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait"
"github.com/ethereum-optimism/optimism/op-node/withdrawals"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
......@@ -89,7 +90,7 @@ func ProveWithdrawal(t *testing.T, cfg SystemConfig, l1Client *ethclient.Client,
ctx, cancel := context.WithTimeout(context.Background(), 40*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second)
defer cancel()
blockNumber, err := withdrawals.WaitForOutputRootPublished(ctx, l1Client, config.L1Deployments.L2OutputOracleProxy, l2WithdrawalReceipt.BlockNumber)
blockNumber, err := wait.ForOutputRootPublished(ctx, l1Client, config.L1Deployments.L2OutputOracleProxy, l2WithdrawalReceipt.BlockNumber)
require.Nil(t, err)
rpcClient, err := rpc.Dial(l2Node.WSEndpoint())
......@@ -145,7 +146,7 @@ func FinalizeWithdrawal(t *testing.T, cfg SystemConfig, l1Client *ethclient.Clie
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second)
defer cancel()
err := withdrawals.WaitForFinalizationPeriod(ctx, l1Client, withdrawalProofReceipt.BlockNumber, config.L1Deployments.L2OutputOracleProxy)
err := wait.ForFinalizationPeriod(ctx, l1Client, withdrawalProofReceipt.BlockNumber, config.L1Deployments.L2OutputOracleProxy)
require.Nil(t, err)
opts, err := bind.NewKeyedTransactorWithChainID(privKey, cfg.L1ChainIDBig())
......
......@@ -9,7 +9,7 @@ import (
"github.com/stretchr/testify/require"
)
func TestIsURLAvailable(t *testing.T) {
func TestIsURLAvailableLocal(t *testing.T) {
listener, err := net.Listen("tcp4", ":0")
require.NoError(t, err)
defer listener.Close()
......@@ -18,6 +18,39 @@ func TestIsURLAvailable(t *testing.T) {
parts := strings.Split(a, ":")
addr := fmt.Sprintf("http://localhost:%s", parts[1])
// True & False with ports
require.True(t, IsURLAvailable(addr))
require.False(t, IsURLAvailable("http://localhost:0"))
// Fail open if we don't recognize the scheme
require.True(t, IsURLAvailable("mailto://example.com"))
}
func TestIsURLAvailableNonLocal(t *testing.T) {
if !IsURLAvailable("http://example.com") {
t.Skip("No internet connection found, skipping this test")
}
// True without ports. http & https
require.True(t, IsURLAvailable("http://example.com"))
require.True(t, IsURLAvailable("http://example.com/hello"))
require.True(t, IsURLAvailable("https://example.com"))
require.True(t, IsURLAvailable("https://example.com/hello"))
// True without ports. ws & wss
require.True(t, IsURLAvailable("ws://example.com"))
require.True(t, IsURLAvailable("ws://example.com/hello"))
require.True(t, IsURLAvailable("wss://example.com"))
require.True(t, IsURLAvailable("wss://example.com/hello"))
// False without ports
require.False(t, IsURLAvailable("http://fakedomainnamethatdoesnotexistandshouldneverexist.com"))
require.False(t, IsURLAvailable("http://fakedomainnamethatdoesnotexistandshouldneverexist.com/hello"))
require.False(t, IsURLAvailable("https://fakedomainnamethatdoesnotexistandshouldneverexist.com"))
require.False(t, IsURLAvailable("https://fakedomainnamethatdoesnotexistandshouldneverexist.com/hello"))
require.False(t, IsURLAvailable("ws://fakedomainnamethatdoesnotexistandshouldneverexist.com"))
require.False(t, IsURLAvailable("ws://fakedomainnamethatdoesnotexistandshouldneverexist.com/hello"))
require.False(t, IsURLAvailable("wss://fakedomainnamethatdoesnotexistandshouldneverexist.com"))
require.False(t, IsURLAvailable("wss://fakedomainnamethatdoesnotexistandshouldneverexist.com/hello"))
}
......@@ -122,7 +122,19 @@ func IsURLAvailable(address string) bool {
if err != nil {
return false
}
conn, err := net.DialTimeout("tcp", u.Host, 5*time.Second)
addr := u.Host
if u.Port() == "" {
switch u.Scheme {
case "http", "ws":
addr += ":80"
case "https", "wss":
addr += ":443"
default:
// Fail open if we can't figure out what the port should be
return true
}
}
conn, err := net.DialTimeout("tcp", addr, 5*time.Second)
if err != nil {
return false
}
......
......@@ -6,81 +6,20 @@ import (
"errors"
"fmt"
"math/big"
"time"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/ethclient/gethclient"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-service/client/utils"
)
var MessagePassedTopic = crypto.Keccak256Hash([]byte("MessagePassed(uint256,address,address,uint256,uint256,bytes,bytes32)"))
// WaitForOutputRootPublished waits until there is an output published for an L2 block number larger than the supplied l2BlockNumber
// This function polls and can block for a very long time if used on mainnet.
// This returns the block number to use for proof generation.
func WaitForOutputRootPublished(ctx context.Context, client *ethclient.Client, l2OutputOracleAddr common.Address, l2BlockNumber *big.Int) (uint64, error) {
l2BlockNumber = new(big.Int).Set(l2BlockNumber) // Don't clobber caller owned l2BlockNumber
opts := &bind.CallOpts{Context: ctx}
l2OO, err := bindings.NewL2OutputOracleCaller(l2OutputOracleAddr, client)
if err != nil {
return 0, err
}
getL2BlockFromLatestOutput := func() (*big.Int, error) { return l2OO.LatestBlockNumber(opts) }
outputBlockNum, err := utils.WaitAndGet(ctx, time.Second, getL2BlockFromLatestOutput, func(latest *big.Int) bool {
return latest.Cmp(l2BlockNumber) >= 0
})
if err != nil {
return 0, err
}
return outputBlockNum.Uint64(), nil
}
// WaitForFinalizationPeriod waits until the L1 chain has progressed far enough that l1ProvingBlockNum has completed
// the finalization period.
// This functions polls and can block for a very long time if used on mainnet.
func WaitForFinalizationPeriod(ctx context.Context, client *ethclient.Client, l1ProvingBlockNum *big.Int, l2OutputOracleAddr common.Address) error {
l1ProvingBlockNum = new(big.Int).Set(l1ProvingBlockNum) // Don't clobber caller owned l1ProvingBlockNum
opts := &bind.CallOpts{Context: ctx}
// Load finalization period
l2OO, err := bindings.NewL2OutputOracleCaller(l2OutputOracleAddr, client)
if err != nil {
return fmt.Errorf("create L2OOCaller: %w", err)
}
finalizationPeriod, err := l2OO.FINALIZATIONPERIODSECONDS(opts)
if err != nil {
return fmt.Errorf("get finalization period: %w", err)
}
provingHeader, err := client.HeaderByNumber(ctx, l1ProvingBlockNum)
if err != nil {
return fmt.Errorf("retrieve proving header: %w", err)
}
targetTimestamp := new(big.Int).Add(new(big.Int).SetUint64(provingHeader.Time), finalizationPeriod)
targetTime := time.Unix(targetTimestamp.Int64(), 0)
// Assume clock is relatively correct
time.Sleep(time.Until(targetTime))
// Poll for L1 Block to have a time greater than the target time
return utils.WaitFor(ctx, time.Second, func() (bool, error) {
header, err := client.HeaderByNumber(ctx, nil)
if err != nil {
return false, fmt.Errorf("retrieve latest header: %w", err)
}
return header.Time > targetTimestamp.Uint64(), nil
})
}
type ProofClient interface {
GetProof(context.Context, common.Address, []string, *big.Int) (*gethclient.AccountResult, error)
}
......
......@@ -17,6 +17,10 @@ func (e *ErrFailedPermanently) Error() string {
return fmt.Sprintf("operation failed permanently after %d attempts: %v", e.attempts, e.LastErr)
}
func (e *ErrFailedPermanently) Unwrap() error {
return e.LastErr
}
type pair[T, U any] struct {
a T
b U
......@@ -35,37 +39,27 @@ func Do2[T, U any](ctx context.Context, maxAttempts int, strategy Strategy, op f
// with delays in between each retry according to the provided
// Strategy.
func Do[T any](ctx context.Context, maxAttempts int, strategy Strategy, op func() (T, error)) (T, error) {
var empty T
var empty, ret T
var err error
if maxAttempts < 1 {
return empty, fmt.Errorf("need at least 1 attempt to run op, but have %d max attempts", maxAttempts)
}
var attempt int
reattemptCh := make(chan struct{}, 1)
doReattempt := func() {
reattemptCh <- struct{}{}
}
doReattempt()
for {
select {
case <-ctx.Done():
for i := 0; i < maxAttempts; i++ {
if ctx.Err() != nil {
return empty, ctx.Err()
case <-reattemptCh:
attempt++
ret, err := op()
if err == nil {
return ret, nil
}
if attempt == maxAttempts {
return empty, &ErrFailedPermanently{
attempts: maxAttempts,
LastErr: err,
}
}
time.AfterFunc(strategy.Duration(attempt-1), doReattempt)
}
ret, err = op()
if err == nil {
return ret, nil
}
// Don't sleep when we are about to exit the loop & return ErrFailedPermanently
if i != maxAttempts-1 {
time.Sleep(strategy.Duration(i))
}
}
return empty, &ErrFailedPermanently{
attempts: maxAttempts,
LastErr: err,
}
}
......@@ -159,7 +159,7 @@ func TestSend(t *testing.T) {
{sendErr: true},
{},
},
nonces: []uint64{0, 1, 1},
nonces: []uint64{0, 1},
total: 3 * time.Second,
},
}
......
......@@ -17,6 +17,7 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-service/backoff"
"github.com/ethereum-optimism/optimism/op-service/txmgr/metrics"
)
......@@ -175,7 +176,13 @@ func (m *SimpleTxManager) send(ctx context.Context, candidate TxCandidate) (*typ
ctx, cancel = context.WithTimeout(ctx, m.cfg.TxSendTimeout)
defer cancel()
}
tx, err := m.craftTx(ctx, candidate)
tx, err := backoff.Do(ctx, 30, backoff.Fixed(2*time.Second), func() (*types.Transaction, error) {
tx, err := m.craftTx(ctx, candidate)
if err != nil {
m.l.Warn("Failed to create a transaction, will retry", "err", err)
}
return tx, err
})
if err != nil {
return nil, fmt.Errorf("failed to create the tx: %w", err)
}
......
......@@ -68,7 +68,7 @@
"markdownlint-cli2": "0.4.0",
"mkdirp": "^1.0.4",
"mocha": "^10.2.0",
"nx": "15.6.0",
"nx": "16.6.0",
"nyc": "^15.1.0",
"patch-package": "^6.4.7",
"prettier": "^2.8.0",
......
......@@ -9,12 +9,12 @@
"dist/*"
],
"scripts": {
"start:balance-mon": "ts-node ./src/balance-mon/service.ts",
"start:wallet-mon": "ts-node ./src/wallet-mon/service.ts",
"start:drippie-mon": "ts-node ./src/drippie-mon/service.ts",
"start:wd-mon": "ts-node ./src/wd-mon/service.ts",
"start:fault-mon": "ts-node ./src/fault-mon/service.ts",
"start:replica-mon": "ts-node ./src/replica-mon/service.ts",
"start:balance-mon": "tsx ./src/balance-mon/service.ts",
"start:wallet-mon": "tsx ./src/wallet-mon/service.ts",
"start:drippie-mon": "tsx ./src/drippie-mon/service.ts",
"start:wd-mon": "tsx ./src/wd-mon/service.ts",
"start:fault-mon": "tsx ./src/fault-mon/service.ts",
"start:replica-mon": "tsx ./src/replica-mon/service.ts",
"test": "hardhat test",
"test:coverage": "nyc hardhat test && nyc merge .nyc_output coverage.json",
"build": "tsc -p ./tsconfig.json",
......@@ -38,22 +38,23 @@
"url": "https://github.com/ethereum-optimism/optimism.git"
},
"dependencies": {
"@eth-optimism/common-ts": "0.8.3",
"@eth-optimism/common-ts": "workspace:*",
"@eth-optimism/contracts-bedrock": "workspace:*",
"@eth-optimism/contracts-periphery": "1.0.8",
"@eth-optimism/contracts-bedrock": "0.16.0",
"@eth-optimism/core-utils": "0.12.2",
"@eth-optimism/sdk": "3.1.0",
"ethers": "^5.7.0",
"dotenv": "^16.1.4",
"@eth-optimism/core-utils": "workspace:*",
"@eth-optimism/sdk": "workspace:*",
"@types/dateformat": "^5.0.0",
"chai-as-promised": "^7.1.1",
"dateformat": "^4.5.1"
"dateformat": "^4.5.1",
"dotenv": "^16.3.1",
"ethers": "^5.7.2"
},
"devDependencies": {
"@ethersproject/abstract-provider": "^5.7.0",
"@nomiclabs/hardhat-ethers": "^2.0.6",
"@nomiclabs/hardhat-waffle": "^2.0.3",
"@nomiclabs/hardhat-waffle": "^2.0.6",
"hardhat": "^2.9.6",
"ts-node": "^10.9.1"
"ts-node": "^10.9.1",
"tsx": "^3.12.7"
}
}
......@@ -48,7 +48,7 @@ Check the list of available metrics via `pnpm start --help`:
```sh
> pnpm start --help
$ ts-node ./src/service.ts --help
$ tsx ./src/service.ts --help
Usage: service [options]
Options:
......
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist"
"outDir": "./dist",
"skipLibCheck": true
},
"include": [
"package.json",
......
......@@ -48,7 +48,7 @@
"morgan": "^1.10.0",
"pino": "^6.11.3",
"pino-multi-stream": "^5.3.0",
"pino-sentry": "^0.7.0",
"pino-sentry": "^0.14.0",
"prom-client": "^13.1.0"
},
"devDependencies": {
......
......@@ -156,7 +156,7 @@ export abstract class BaseServiceV2<
}
// Use commander as a way to communicate info about the service. We don't actually *use*
// commander for anything besides the ability to run `ts-node ./service.ts --help`.
// commander for anything besides the ability to run `tsx ./service.ts --help`.
const program = new Command().allowUnknownOption(true)
for (const [optionName, optionSpec] of Object.entries(params.optionsSpec)) {
// Skip options that are not meant to be used by the user.
......
......@@ -298,77 +298,79 @@ LegacyERC20ETH_Test:test_transfer_doesNotExist_reverts() (gas: 10755)
LegacyMessagePasser_Test:test_passMessageToL1_succeeds() (gas: 34524)
LibPosition_Test:test_pos_correctness_succeeds() (gas: 38689)
MIPS_Test:test_add_succeeds() (gas: 121593)
MIPS_Test:test_addi_succeeds() (gas: 121896)
MIPS_Test:test_addu_succeeds() (gas: 121645)
MIPS_Test:test_addui_succeeds() (gas: 121953)
MIPS_Test:test_and_succeeds() (gas: 121628)
MIPS_Test:test_andi_succeeds() (gas: 121770)
MIPS_Test:test_addi_succeeds() (gas: 121918)
MIPS_Test:test_addu_succeeds() (gas: 121601)
MIPS_Test:test_addui_succeeds() (gas: 121975)
MIPS_Test:test_and_succeeds() (gas: 121650)
MIPS_Test:test_andi_succeeds() (gas: 121792)
MIPS_Test:test_beq_succeeds() (gas: 202355)
MIPS_Test:test_bgez_succeeds() (gas: 121507)
MIPS_Test:test_bgtz_succeeds() (gas: 121428)
MIPS_Test:test_blez_succeeds() (gas: 121406)
MIPS_Test:test_bltz_succeeds() (gas: 121482)
MIPS_Test:test_bne_succeeds() (gas: 121548)
MIPS_Test:test_branch_inDelaySlot_fails() (gas: 85977)
MIPS_Test:test_brk_succeeds() (gas: 121509)
MIPS_Test:test_bgez_succeeds() (gas: 121484)
MIPS_Test:test_bgtz_succeeds() (gas: 121405)
MIPS_Test:test_blez_succeeds() (gas: 121361)
MIPS_Test:test_bltz_succeeds() (gas: 121504)
MIPS_Test:test_bne_succeeds() (gas: 121570)
MIPS_Test:test_branch_inDelaySlot_fails() (gas: 85999)
MIPS_Test:test_brk_succeeds() (gas: 121531)
MIPS_Test:test_clo_succeeds() (gas: 121991)
MIPS_Test:test_clone_succeeds() (gas: 121484)
MIPS_Test:test_clz_succeeds() (gas: 122440)
MIPS_Test:test_clz_succeeds() (gas: 122462)
MIPS_Test:test_div_succeeds() (gas: 121806)
MIPS_Test:test_divu_succeeds() (gas: 121806)
MIPS_Test:test_exit_succeeds() (gas: 121386)
MIPS_Test:test_fcntl_succeeds() (gas: 203171)
MIPS_Test:test_illegal_instruction_fails() (gas: 91153)
MIPS_Test:test_invalid_root_fails() (gas: 435656)
MIPS_Test:test_jal_succeeds() (gas: 117399)
MIPS_Test:test_divu_succeeds() (gas: 121762)
MIPS_Test:test_exit_succeeds() (gas: 121408)
MIPS_Test:test_fcntl_succeeds() (gas: 203129)
MIPS_Test:test_illegal_instruction_fails() (gas: 91175)
MIPS_Test:test_invalid_root_fails() (gas: 435678)
MIPS_Test:test_jal_nonzeroRegion_succeeds() (gas: 120492)
MIPS_Test:test_jal_succeeds() (gas: 120481)
MIPS_Test:test_jalr_succeeds() (gas: 121349)
MIPS_Test:test_jr_succeeds() (gas: 121138)
MIPS_Test:test_jump_inDelaySlot_fails() (gas: 85512)
MIPS_Test:test_jump_succeeds() (gas: 120353)
MIPS_Test:test_jr_succeeds() (gas: 121094)
MIPS_Test:test_jump_inDelaySlot_fails() (gas: 85345)
MIPS_Test:test_jump_nonzeroRegion_succeeds() (gas: 120236)
MIPS_Test:test_jump_succeeds() (gas: 120188)
MIPS_Test:test_lb_succeeds() (gas: 127346)
MIPS_Test:test_lbu_succeeds() (gas: 127266)
MIPS_Test:test_lh_succeeds() (gas: 127345)
MIPS_Test:test_lhu_succeeds() (gas: 127262)
MIPS_Test:test_ll_succeeds() (gas: 127282)
MIPS_Test:test_lui_succeeds() (gas: 121531)
MIPS_Test:test_lbu_succeeds() (gas: 127222)
MIPS_Test:test_lh_succeeds() (gas: 127367)
MIPS_Test:test_lhu_succeeds() (gas: 127284)
MIPS_Test:test_ll_succeeds() (gas: 127304)
MIPS_Test:test_lui_succeeds() (gas: 121488)
MIPS_Test:test_lw_succeeds() (gas: 127158)
MIPS_Test:test_lwl_succeeds() (gas: 241457)
MIPS_Test:test_lwr_succeeds() (gas: 241767)
MIPS_Test:test_lwl_succeeds() (gas: 241434)
MIPS_Test:test_lwr_succeeds() (gas: 241722)
MIPS_Test:test_mfhi_succeeds() (gas: 121458)
MIPS_Test:test_mflo_succeeds() (gas: 121484)
MIPS_Test:test_mmap_succeeds() (gas: 118492)
MIPS_Test:test_mmap_succeeds() (gas: 118451)
MIPS_Test:test_movn_succeeds() (gas: 202409)
MIPS_Test:test_movz_succeeds() (gas: 202313)
MIPS_Test:test_mthi_succeeds() (gas: 121428)
MIPS_Test:test_mthi_succeeds() (gas: 121450)
MIPS_Test:test_mtlo_succeeds() (gas: 121478)
MIPS_Test:test_mul_succeeds() (gas: 121541)
MIPS_Test:test_mult_succeeds() (gas: 121645)
MIPS_Test:test_multu_succeeds() (gas: 121698)
MIPS_Test:test_nor_succeeds() (gas: 121739)
MIPS_Test:test_or_succeeds() (gas: 121635)
MIPS_Test:test_mul_succeeds() (gas: 121563)
MIPS_Test:test_mult_succeeds() (gas: 121667)
MIPS_Test:test_multu_succeeds() (gas: 121675)
MIPS_Test:test_nor_succeeds() (gas: 121697)
MIPS_Test:test_or_succeeds() (gas: 121657)
MIPS_Test:test_ori_succeeds() (gas: 121865)
MIPS_Test:test_preimage_read_succeeds() (gas: 233825)
MIPS_Test:test_preimage_read_succeeds() (gas: 233847)
MIPS_Test:test_preimage_write_succeeds() (gas: 126473)
MIPS_Test:test_prestate_exited_succeeds() (gas: 112970)
MIPS_Test:test_prestate_exited_succeeds() (gas: 112992)
MIPS_Test:test_sb_succeeds() (gas: 159993)
MIPS_Test:test_sc_succeeds() (gas: 160187)
MIPS_Test:test_sh_succeeds() (gas: 160096)
MIPS_Test:test_sll_succeeds() (gas: 121434)
MIPS_Test:test_sllv_succeeds() (gas: 121624)
MIPS_Test:test_slt_succeeds() (gas: 203244)
MIPS_Test:test_sh_succeeds() (gas: 160052)
MIPS_Test:test_sll_succeeds() (gas: 121456)
MIPS_Test:test_sllv_succeeds() (gas: 121646)
MIPS_Test:test_slt_succeeds() (gas: 203266)
MIPS_Test:test_sltu_succeeds() (gas: 121871)
MIPS_Test:test_sra_succeeds() (gas: 121719)
MIPS_Test:test_sra_succeeds() (gas: 121763)
MIPS_Test:test_srav_succeeds() (gas: 121959)
MIPS_Test:test_srl_succeeds() (gas: 121514)
MIPS_Test:test_srlv_succeeds() (gas: 121707)
MIPS_Test:test_step_abi_succeeds() (gas: 57876)
MIPS_Test:test_sub_succeeds() (gas: 121674)
MIPS_Test:test_subu_succeeds() (gas: 121682)
MIPS_Test:test_sw_succeeds() (gas: 160050)
MIPS_Test:test_swl_succeeds() (gas: 160066)
MIPS_Test:test_swr_succeeds() (gas: 160141)
MIPS_Test:test_sub_succeeds() (gas: 121696)
MIPS_Test:test_subu_succeeds() (gas: 121659)
MIPS_Test:test_sw_succeeds() (gas: 160027)
MIPS_Test:test_swl_succeeds() (gas: 160088)
MIPS_Test:test_swr_succeeds() (gas: 160163)
MIPS_Test:test_xor_succeeds() (gas: 121685)
MIPS_Test:test_xori_succeeds() (gas: 121894)
MIPS_Test:test_xori_succeeds() (gas: 121916)
MerkleTrie_get_Test:test_get_corruptedProof_reverts() (gas: 5733)
MerkleTrie_get_Test:test_get_extraProofElements_reverts() (gas: 58889)
MerkleTrie_get_Test:test_get_invalidDataRemainder_reverts() (gas: 35845)
......@@ -410,7 +412,8 @@ OptimismMintableERC20_Test:test_mint_notBridge_reverts() (gas: 11165)
OptimismMintableERC20_Test:test_mint_succeeds() (gas: 63544)
OptimismMintableERC20_Test:test_remoteToken_succeeds() (gas: 7667)
OptimismMintableERC721Factory_Test:test_constructor_succeeds() (gas: 8351)
OptimismMintableERC721Factory_Test:test_createOptimismMintableERC721_succeeds() (gas: 2321820)
OptimismMintableERC721Factory_Test:test_createOptimismMintableERC721_sameTwice_reverts() (gas: 8937393460516801476)
OptimismMintableERC721Factory_Test:test_createOptimismMintableERC721_succeeds() (gas: 2363100)
OptimismMintableERC721Factory_Test:test_createOptimismMintableERC721_zeroRemoteToken_reverts() (gas: 9418)
OptimismMintableERC721_Test:test_burn_notBridge_reverts() (gas: 136966)
OptimismMintableERC721_Test:test_burn_succeeds() (gas: 118832)
......@@ -419,10 +422,10 @@ OptimismMintableERC721_Test:test_safeMint_notBridge_reverts() (gas: 11121)
OptimismMintableERC721_Test:test_safeMint_succeeds() (gas: 140547)
OptimismMintableERC721_Test:test_supportsInterfaces_succeeds() (gas: 9005)
OptimismMintableERC721_Test:test_tokenURI_succeeds() (gas: 163441)
OptimismMintableTokenFactory_Test:test_bridge_succeeds() (gas: 7580)
OptimismMintableTokenFactory_Test:test_createStandardL2Token_remoteIsZero_succeeds() (gas: 9390)
OptimismMintableTokenFactory_Test:test_createStandardL2Token_sameTwice_succeeds() (gas: 2523203)
OptimismMintableTokenFactory_Test:test_createStandardL2Token_succeeds() (gas: 1268564)
OptimismMintableTokenFactory_Test:test_bridge_succeeds() (gas: 7646)
OptimismMintableTokenFactory_Test:test_createStandardL2Token_remoteIsZero_reverts() (gas: 9401)
OptimismMintableTokenFactory_Test:test_createStandardL2Token_sameTwice_reverts() (gas: 8937393460516769032)
OptimismMintableTokenFactory_Test:test_createStandardL2Token_succeeds() (gas: 1293594)
OptimismPortalUpgradeable_Test:test_initialize_cannotInitImpl_reverts() (gas: 11178)
OptimismPortalUpgradeable_Test:test_initialize_cannotInitProxy_reverts() (gas: 16111)
OptimismPortalUpgradeable_Test:test_params_initValuesOnProxy_succeeds() (gas: 26781)
......
# Contributing to CONTRIBUTING.md
First off, thanks for taking the time to contribute!
We welcome and appreciate all kinds of contributions. We ask that before contributing you please review the procedures for each type of contribution available in the [Table of Contents](#table-of-contents). This will streamline the process for both maintainers and contributors. To find ways to contribute, view the [I Want To Contribute](#i-want-to-contribute) section below. Larger contributions should [open an issue](https://github.com/ethereum-optimism/optimism/issues/new) before implementation to ensure changes don't go to waste.
We're excited to work with you and your contributions to scaling Ethereum!
## Table of Contents
- [I Have a Question](#i-have-a-question)
- [I Want To Contribute](#i-want-to-contribute)
- [Reporting Bugs](#reporting-bugs)
- [Suggesting Enhancements](#suggesting-enhancements)
- [Your First Code Contribution](#your-first-code-contribution)
- [Improving The Documentation](#improving-the-documentation)
- [Deploying on Devnet](#deploying-on-devnet)
- [Tools](#tools)
## I Have a Question
> **Note**
> Before making an issue, please read the documentation and search the issues to see if your question has already been answered.
If you have any questions about the smart contracts, please feel free to ask them in the Optimism discord developer channels or create a new detailed issue.
## I Want To Contribute
### Reporting Bugs
**Any and all bug reports on production smart contract code should be submitted privately to the Optimism team so that we can mitigate the issue before it is exploited. Please see our security policy document [here](https://github.com/ethereum-optimism/.github/blob/master/SECURITY.md).**
### Suggesting Enhancements
#### Before Submitting an Enhancement
- Read the documentation and the smart contracts themselves to see if the feature already exists.
- Perform a search in the issues to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one.
#### How Do I Submit a Good Enhancement Suggestion?
Enhancement suggestions are tracked as [GitHub issues](/issues).
- Use a **clear and descriptive title** for the issue to identify the suggestion.
- Provide a **step-by-step** description of the suggested enhancement in as many details as possible.
- Describe the **current** behavior and why the **intended** behavior you expected to see differs. At this point you can also tell which alternatives do not work for you.
- Explain why this enhancement would be useful in Optimism's smart contracts. You may also want to point out the other projects that solved it better and which could serve as inspiration.
### Your First Code Contribution
The best place to begin contributing is by looking through the issues with the `good first issue` label. These are issues that are relatively easy to implement and are a great way to get familiar with the codebase.
Optimism's smart contracts are written in Solidity and we use [foundry](https://github.com/foundry-rs/foundry) as our development framework. To get started, you'll need to install several dependencies:
1. [pnpm](https://pnpm.io)
1. Make sure to `pnpm install`
1. [foundry](https://getfoundry.sh)
1. Foundry is built with [rust](https://www.rust-lang.org/tools/install), and this project uses a pinned version of foundry. Install the rust toolchain with `rustup`.
1. Make sure to install the version of foundry used by `ci-builder`, defined in the `.foundryrc` file in the root of this repo. Once you have `foundryup` installed, there is a helper to do this: `pnpm install:foundry`
1. [golang](https://golang.org/doc/install)
1. [python](https://www.python.org/downloads/)
Our [Style Guide](STYLE_GUIDE.md) contains information about the project structure, syntax preferences, naming conventions, and more. Please take a look at it before submitting a PR, and let us know if you spot inconcistencies!
Once you've read the styleguide and are ready to work on your PR, there are a plethora of useful `pnpm` scripts to know about that will help you with development:
1. `pnpm build` Builds the smart contracts.
1. `pnpm test` Runs the full `forge` test suite.
1 `pnpm gas-snapshot` Generates the gas snapshot for the smart contracts.
1. `pnpm semver-lock` Generates the semver lockfile.
1. `pnpm storage-snapshot` Generates the storage lockfile.
1. `pnpm autogen:invariant-docs` Generates the invariant test documentation.
1. `pnpm clean` Removes all build artifacts for `forge` and `go` compilations.
1. `pnpm validate-spacers` Validates the positions of the storage slot spacers.
1. `pnpm validate-deploy-configs` Validates the deployment configurations in `deploy-config`
1. `pnpm slither` Runs the slither static analysis tool on the smart contracts.
1. `pnpm lint` Runs the linter on the smart contracts and scripts.
1. `pnpm pre-pr` Runs most checks, generators, and linters prior to a PR. For most PRs, this is sufficient to pass CI if everything is in order.
1. `pnpm pre-pr:full` Runs all checks, generators, and linters prior to a PR.
### Improving The Documentation
Documentation improvements are more than welcome! If you see a typo or feel that a code comment describes something poorly or incorrectly, please submit a PR with a fix.
### Deploying on Devnet
To deploy the smart contracts on a local devnet, run `make devnet-up` in the monorepo root. For more information on the local devnet, see [devnet.md](../../specs/meta/devnet.md).
### Tools
#### Layout Locking
We use a system called "layout locking" as a safety mechanism to prevent certain contract variables from being moved to different storage slots accidentally.
To lock a contract variable, add it to the `layout-lock.json` file which has the following format:
```json
{
"MyContractName": {
"myVariableName": {
"slot": 1,
"offset": 0,
"length": 32
}
}
}
```
With the above config, the `validate-spacers` script will check that we have a contract called `MyContractName`, that the contract has a variable named `myVariableName`, and that the variable is in the correct position as defined in the lock file.
You should add things to the `layout-lock.json` file when you want those variables to **never** change.
Layout locking should be used in combination with diffing the `.storage-layout` file in CI.
#### Gas Snapshots
We use forge's `gas-snapshot` subcommand to produce a gas snapshot for most tests within our suite. CI will check that the gas snapshot has been updated properly when it runs, so make sure to run `pnpm gas-snapshot`!
#### Semver Locking
Many of our smart contracts are semantically versioned. To make sure that changes are not made to a contract without deliberately bumping its version, we commit to the source code and the creation bytecode of its dependencies in a lockfile. Consult the [Style Guide](./STYLE_GUIDE.md#Versioning) for more information about how our contracts are versioned.
#### Storage Snapshots
Due to the many proxied contracts in Optimism's protocol, we automate tracking the diff to storage layouts of the contracts in the project. This is to ensure that we don't break a proxy by upgrading its implementation to a contract with a different storage layout. To generate the storage lockfile, run `pnpm storage-snapshot`.
......@@ -50,78 +50,29 @@ We export contract ABIs, contract source code, and contract deployment informati
npm install @eth-optimism/contracts-bedrock
```
## Development
## Contributing
### Dependencies
For all information about working on and contributing to Optimism's smart contracts, please see [CONTRIBUTING.md](./CONTRIBUTING.md)
We work on this repository with a combination of [Hardhat](https://hardhat.org) and [Foundry](https://getfoundry.sh/).
1. Install node modules with pnpm (v8) and Node.js (16+):
```shell
pnpm install
```
2. Install the correct version of foundry (defined in the .foundryrc file in the root of this repo.
```shell
pnpm install:foundry
```
### Build
```shell
pnpm build
```
### Tests
```shell
pnpm test
```
### Deployment
## Deployment
The smart contracts are deployed using `foundry` with a `hardhat-deploy` compatibility layer. When the contracts are deployed,
they will write a temp file to disk that can then be formatted into a `hardhat-deploy` style artifact by calling another script.
#### Configuration
### Configuration
Create or modify a file `<network-name>.json` inside of the [`deploy-config`](./deploy-config/) folder.
By default, the network name will be selected automatically based on the chainid. Alternatively, the `DEPLOYMENT_CONTEXT` env var can be used to override the network name.
The spec for the deploy config is defined by the `deployConfigSpec` located inside of the [`hardhat.config.ts`](./hardhat.config.ts).
#### Execution
### Execution
1. Set the env vars `ETH_RPC_URL`, `PRIVATE_KEY` and `ETHERSCAN_API_KEY` if contract verification is desired
1. Deploy the contracts with `forge script -vvv scripts/Deploy.s.sol:Deploy --rpc-url $ETH_RPC_URL --broadcast --private-key $PRIVATE_KEY`
Pass the `--verify` flag to verify the deployments automatically with Etherscan.
1. Generate the hardhat deploy artifacts with `forge script -vvv scripts/Deploy.s.sol:Deploy --sig 'sync()' --rpc-url $ETH_RPC_URL --broadcast --private-key $PRIVATE_KEY`
#### Deploying a single contract
### Deploying a single contract
All of the functions for deploying a single contract are `public` meaning that the `--sig` argument to `forge script` can be used to
target the deployment of a single contract.
## Tools
### Layout Locking
We use a system called "layout locking" as a safety mechanism to prevent certain contract variables from being moved to different storage slots accidentally.
To lock a contract variable, add it to the `layout-lock.json` file which has the following format:
```json
{
"MyContractName": {
"myVariableName": {
"slot": 1,
"offset": 0,
"length": 32
}
}
}
```
With the above config, the `validate-spacers` hardhat task will check that we have a contract called `MyContractName`, that the contract has a variable named `myVariableName`, and that the variable is in the correct position as defined in the lock file.
You should add things to the `layout-lock.json` file when you want those variables to **never** change.
Layout locking should be used in combination with diffing the `.storage-layout` file in CI.
......@@ -102,7 +102,7 @@ All test contracts and functions should be organized and named according to the
These guidelines are also encoded in a script which can be run with:
```
ts-node scripts/forge-test-names.ts
tsx scripts/forge-test-names.ts
```
_Note: This is a work in progress, not all test files are compliant with these guidelines._
......
......@@ -52,5 +52,7 @@
"l2GenesisRegolithTimeOffset": "0x0",
"eip1559Denominator": 50,
"eip1559Elasticity": 10
"eip1559Elasticity": 10,
"systemConfigStartBlock": 0
}
......@@ -5,6 +5,7 @@
"l1ChainID": 5,
"l2ChainID": 420,
"l2BlockTime": 2,
"l1BlockTime": 12,
"maxSequencerDrift": 600,
"sequencerWindowSize": 3600,
"channelTimeout": 300,
......@@ -36,5 +37,6 @@
"l2GenesisBlockGasLimit": "0x17D7840",
"l2GenesisBlockBaseFeePerGas": "0x3b9aca00",
"eip1559Denominator": 50,
"eip1559Elasticity": 10
"eip1559Elasticity": 10,
"systemConfigStartBlock": 8300214
}
......@@ -16,7 +16,7 @@
"prebuild": "./scripts/verify-foundry-install.sh",
"build:differential": "go build -o ./scripts/differential-testing/differential-testing ./scripts/differential-testing",
"build:fuzz": "(cd test-case-generator && go build ./cmd/fuzz.go)",
"autogen:invariant-docs": "ts-node scripts/invariant-doc-gen.ts",
"autogen:invariant-docs": "tsx scripts/invariant-doc-gen.ts",
"test": "pnpm build:differential && pnpm build:fuzz && forge test",
"coverage": "pnpm build:differential && pnpm build:fuzz && forge coverage",
"coverage:lcov": "pnpm build:differential && pnpm build:fuzz && forge coverage --report lcov",
......@@ -24,13 +24,15 @@
"storage-snapshot": "./scripts/storage-snapshot.sh",
"semver-lock": "forge script scripts/SemverLock.s.sol",
"validate-deploy-configs": "./scripts/validate-deploy-configs.sh",
"validate-spacers": "pnpm build && npx ts-node scripts/validate-spacers.ts",
"validate-spacers": "pnpm build && npx tsx scripts/validate-spacers.ts",
"slither": "./scripts/slither.sh",
"slither:triage": "TRIAGE_MODE=1 ./scripts/slither.sh",
"clean": "rm -rf ./artifacts ./forge-artifacts ./cache ./tsconfig.tsbuildinfo ./tsconfig.build.tsbuildinfo ./test-case-generator/fuzz ./scripts/differential-testing/differential-testing",
"preinstall": "npx only-allow pnpm",
"pre-pr": "pnpm clean && pnpm gas-snapshot && pnpm storage-snapshot && pnpm semver-lock && pnpm autogen:invariant-docs && pnpm lint && (cd ../../op-bindings && make)",
"pre-pr:full": "pnpm test && pnpm slither && pnpm validate-deploy-configs && pnpm validate-spacers && pnpm pre-pr",
"lint:ts:check": "eslint . --max-warnings=0",
"lint:forge-tests:check": "ts-node scripts/forge-test-names.ts",
"lint:forge-tests:check": "tsx scripts/forge-test-names.ts",
"lint:contracts:check": "pnpm lint:fix && git diff --exit-code",
"lint:check": "pnpm lint:contracts:check && pnpm lint:ts:check",
"lint:ts:fix": "eslint --fix .",
......@@ -39,9 +41,9 @@
"lint": "pnpm lint:fix && pnpm lint:check"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.60.1",
"@typescript-eslint/parser": "^5.60.1",
"ts-node": "^10.9.1",
"@typescript-eslint/eslint-plugin": "^6.4.0",
"@typescript-eslint/parser": "^6.4.0",
"tsx": "^3.12.7",
"typescript": "^5.1.6"
}
}
......@@ -2,13 +2,16 @@ import fs from 'fs'
import path from 'path'
import { execSync } from 'child_process'
type Check = (parts: string[]) => boolean
type Checks = Array<{
check: Check
error: string
}>
/**
* Series of function name checks.
*/
const checks: Array<{
check: (parts: string[]) => boolean
error: string
}> = [
const checks: Checks = [
{
error: 'test name parts should be in camelCase',
check: (parts: string[]): boolean => {
......
......@@ -24,8 +24,8 @@
"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": "0xddb1f0047ee90db724405ad6a775fd2b26882cbc31d464c3def2c20c88aaf750",
"src/universal/OptimismMintableERC20Factory.sol": "0x1c77e31f624d0a7306dec8ffc3947a72f8e1e93a01cd911b4561c038732ab7fe",
"src/universal/OptimismMintableERC721.sol": "0x4eaa5ea0d923ce967f299c2ded84c2a53431159c47c0a344a9b8f534a25b7e1f",
"src/universal/OptimismMintableERC721Factory.sol": "0x6c19bc51bf9184aafad46f292bf9c23cc87c3406f31cccf57d4e3e096b887a8d"
"src/universal/OptimismMintableERC20.sol": "0xa0b4f168802d0f9eca9ddc54347ca66c34ad7aa0fd84b01e0d7e99a9f86f46d6",
"src/universal/OptimismMintableERC20Factory.sol": "0x8afb6b634f40e8ac8c60ec7e2ca3bcd610b020d09b3cae70a3d4995722cab3ce",
"src/universal/OptimismMintableERC721.sol": "0x49dc863caf3e002bf0c90b3af3873990fb282771f4c63735fd61a885b7873983",
"src/universal/OptimismMintableERC721Factory.sol": "0x502438b009bfaf0ab187914662468261f10446fd85d44a79b29cdaef7b4982ba"
}
\ No newline at end of file
......@@ -667,9 +667,9 @@ contract MIPS {
// j-type j/jal
if (opcode == 2 || opcode == 3) {
// TODO(CLI-4136): likely bug in original code: MIPS spec says this should be in the "current" region;
// a 256 MB aligned region (i.e. use top 4 bits of branch delay slot (pc+4))
return handleJump(opcode == 2 ? 0 : 31, SE(insn & 0x03FFFFFF, 26) << 2);
// Take top 4 bits of the next PC (its 256 MB region), and concatenate with the 26-bit offset
uint32 target = (state.nextPC & 0xF0000000) | (insn & 0x03FFFFFF) << 2;
return handleJump(opcode == 2 ? 0 : 31, target);
}
// register fetch
......@@ -775,7 +775,6 @@ contract MIPS {
unchecked {
uint32 opcode = insn >> 26; // 6-bits
uint32 func = insn & 0x3f; // 6-bits
// TODO(CLI-4136): deref the immed into a register
if (opcode < 0x20) {
// transform ArithLogI
......
......@@ -35,7 +35,7 @@ contract OptimismMintableERC20 is IOptimismMintableERC20, ILegacyMintableERC20,
_;
}
/// @custom:semver 1.0.3
/// @custom:semver 1.1.0
/// @param _bridge Address of the L2 standard bridge.
/// @param _remoteToken Address of the corresponding L1 token.
/// @param _name ERC20 name.
......@@ -47,7 +47,7 @@ contract OptimismMintableERC20 is IOptimismMintableERC20, ILegacyMintableERC20,
string memory _symbol
)
ERC20(_name, _symbol)
Semver(1, 0, 3)
Semver(1, 1, 0)
{
REMOTE_TOKEN = _remoteToken;
BRIDGE = _bridge;
......
......@@ -31,7 +31,7 @@ contract OptimismMintableERC721 is ERC721Enumerable, IOptimismMintableERC721, Se
_;
}
/// @custom:semver 1.1.2
/// @custom:semver 1.2.0
/// @param _bridge Address of the bridge on this network.
/// @param _remoteChainId Chain ID where the remote token is deployed.
/// @param _remoteToken Address of the corresponding token on the other network.
......@@ -45,7 +45,7 @@ contract OptimismMintableERC721 is ERC721Enumerable, IOptimismMintableERC721, Se
string memory _symbol
)
ERC721(_name, _symbol)
Semver(1, 1, 2)
Semver(1, 2, 0)
{
require(_bridge != address(0), "OptimismMintableERC721: bridge cannot be address(0)");
require(_remoteChainId != 0, "OptimismMintableERC721: remote chain id cannot be zero");
......
This diff is collapsed.
......@@ -43,13 +43,13 @@
"@ethersproject/keccak256": "^5.7.0",
"@ethersproject/properties": "^5.7.0",
"@ethersproject/rlp": "^5.7.0",
"@ethersproject/web": "^5.7.0",
"chai": "^4.3.4",
"ethers": "^5.7.0",
"@ethersproject/web": "^5.7.1",
"chai": "^4.3.7",
"ethers": "^5.7.2",
"node-fetch": "^2.6.7"
},
"devDependencies": {
"@types/node": "^12.12.6",
"mocha": "^10.0.0"
"@types/node": "^20.5.0",
"mocha": "^10.2.0"
}
}
......@@ -70,7 +70,7 @@ export class Etherscan {
url.searchParams.append('apikey', this.apiKey)
const response = await fetch(url)
const result = await response.json()
return result.result[0]
return (result as { result: number[] }).result[0]
}
public async getContractABI(address: string): Promise<any> {
......
......@@ -10,5 +10,6 @@ ignores: [
"eslint-config-prettier",
"eslint-plugin-prettier",
"chai",
"ts-node",
"typedoc"
]
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