Commit 9dc2386a authored by mergify[bot]'s avatar mergify[bot] Committed by GitHub

Merge branch 'develop' into ctb/portal-immutables

parents 3c35aaeb d003f600
......@@ -5,8 +5,8 @@ go 1.20
require github.com/ethereum-optimism/optimism v0.0.0
require (
golang.org/x/crypto v0.8.0 // indirect
golang.org/x/sys v0.7.0 // indirect
golang.org/x/crypto v0.12.0 // indirect
golang.org/x/sys v0.11.0 // indirect
)
replace github.com/ethereum-optimism/optimism v0.0.0 => ../../..
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
......@@ -23,7 +23,7 @@ require (
github.com/ipfs/go-datastore v0.6.0
github.com/ipfs/go-ds-leveldb v0.5.0
github.com/jackc/pgtype v1.14.0
github.com/jackc/pgx/v5 v5.3.1
github.com/jackc/pgx/v5 v5.4.3
github.com/lib/pq v1.10.9
github.com/libp2p/go-libp2p v0.25.1
github.com/libp2p/go-libp2p-pubsub v0.9.3
......@@ -40,10 +40,10 @@ require (
github.com/stretchr/testify v1.8.4
github.com/urfave/cli v1.22.14
github.com/urfave/cli/v2 v2.25.7
golang.org/x/crypto v0.8.0
golang.org/x/crypto v0.12.0
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df
golang.org/x/sync v0.3.0
golang.org/x/term v0.7.0
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
......@@ -185,9 +185,9 @@ require (
go.uber.org/multierr v1.9.0 // indirect
go.uber.org/zap v1.24.0 // indirect
golang.org/x/mod v0.11.0 // indirect
golang.org/x/net v0.9.0 // indirect
golang.org/x/sys v0.7.0 // indirect
golang.org/x/text v0.9.0 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/sys v0.11.0 // indirect
golang.org/x/text v0.12.0 // indirect
golang.org/x/tools v0.7.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
......
......@@ -405,8 +405,8 @@ github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c h1:Dznn52SgVIVst9UyOT9brctYUgxs+CvVfPaC3jKrA50=
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU=
github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8=
github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY=
github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
......@@ -860,8 +860,8 @@ golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
......@@ -911,8 +911,8 @@ golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
......@@ -982,13 +982,13 @@ golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
......@@ -997,8 +997,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
......
......@@ -16,17 +16,15 @@ type PaginationResponse struct {
HasNextPage bool `json:"hasNextPage"`
}
func (a *Api) DepositsHandler(w http.ResponseWriter, r *http.Request) {
bv := a.bridgeView
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.DepositsByAddress(address)
deposits, err := bv.L1BridgeDepositsByAddress(address)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
......@@ -43,17 +41,15 @@ func (a *Api) DepositsHandler(w http.ResponseWriter, r *http.Request) {
jsonResponse(w, response, http.StatusOK)
}
func (a *Api) WithdrawalsHandler(w http.ResponseWriter, r *http.Request) {
bv := a.bridgeView
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.WithdrawalsByAddress(address)
withdrawals, err := bv.L2BridgeWithdrawalsByAddress(address)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
......@@ -83,22 +79,20 @@ func jsonResponse(w http.ResponseWriter, data interface{}, statusCode int) {
}
type Api struct {
Router *chi.Mux
bridgeView database.BridgeView
Router *chi.Mux
BridgeTransfersView database.BridgeTransfersView
}
func NewApi(bv database.BridgeView) *Api {
func NewApi(bv database.BridgeTransfersView) *Api {
r := chi.NewRouter()
api := &Api{
Router: r,
bridgeView: bv,
}
api := &Api{Router: r, BridgeTransfersView: 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.DepositsHandler)
r.Get("/api/v0/withdrawals/{address:.+}", api.WithdrawalsHandler)
r.Get("/api/v0/deposits/{address:.+}", api.L1DepositsHandler)
r.Get("/api/v0/withdrawals/{address:.+}", api.L2WithdrawalsHandler)
r.Get("/healthz", api.HealthzHandler)
return api
......
......@@ -12,89 +12,67 @@ import (
"github.com/stretchr/testify/assert"
)
// MockBridgeView mocks the BridgeView interface
type MockBridgeView struct{}
// MockBridgeTransfersView mocks the BridgeTransfersView interface
type MockBridgeTransfersView struct{}
const (
guid1 = "8408b6d2-7c90-4cfc-8604-b2204116cb6a"
guid2 = "8408b6d2-7c90-4cfc-8604-b2204116cb6b"
)
// DepositsByAddress mocks returning deposits by an address
func (mbv *MockBridgeView) DepositsByAddress(address common.Address) ([]*database.DepositWithTransactionHashes, error) {
return []*database.DepositWithTransactionHashes{
{
Deposit: database.Deposit{
GUID: uuid.MustParse(guid1),
InitiatedL1EventGUID: uuid.MustParse(guid2),
Tx: database.Transaction{},
TokenPair: database.TokenPair{},
},
L1TransactionHash: common.HexToHash("0x123"),
},
}, nil
}
// DepositsByAddress mocks returning deposits by an address
func (mbv *MockBridgeView) DepositByMessageNonce(nonce *big.Int) (*database.Deposit, error) {
return &database.Deposit{
var (
deposit = database.L1BridgeDeposit{
GUID: uuid.MustParse(guid1),
InitiatedL1EventGUID: uuid.MustParse(guid2),
Tx: database.Transaction{},
TokenPair: database.TokenPair{},
}, nil
}
withdrawal = database.L2BridgeWithdrawal{
GUID: uuid.MustParse(guid2),
InitiatedL2EventGUID: uuid.MustParse(guid1),
WithdrawalHash: common.HexToHash("0x456"),
Tx: database.Transaction{},
TokenPair: database.TokenPair{},
}
)
func (mbv *MockBridgeTransfersView) L1BridgeDeposit(hash common.Hash) (*database.L1BridgeDeposit, error) {
return &deposit, nil
}
// LatestDepositMessageNonce mocks returning the latest cross domain message nonce for a deposit
func (mbv *MockBridgeView) LatestDepositMessageNonce() (*big.Int, error) {
return big.NewInt(0), nil
func (mbv *MockBridgeTransfersView) L1BridgeDepositByCrossDomainMessengerNonce(nonce *big.Int) (*database.L1BridgeDeposit, error) {
return &deposit, nil
}
// WithdrawalsByAddress mocks returning withdrawals by an address
func (mbv *MockBridgeView) WithdrawalsByAddress(address common.Address) ([]*database.WithdrawalWithTransactionHashes, error) {
return []*database.WithdrawalWithTransactionHashes{
func (mbv *MockBridgeTransfersView) L1BridgeDepositsByAddress(address common.Address) ([]*database.L1BridgeDepositWithTransactionHashes, error) {
return []*database.L1BridgeDepositWithTransactionHashes{
{
Withdrawal: database.Withdrawal{
GUID: uuid.MustParse(guid2),
InitiatedL2EventGUID: uuid.MustParse(guid1),
WithdrawalHash: common.HexToHash("0x456"),
Tx: database.Transaction{},
TokenPair: database.TokenPair{},
},
L2TransactionHash: common.HexToHash("0x789"),
L1BridgeDeposit: deposit,
L1TransactionHash: common.HexToHash("0x123"),
},
}, nil
}
// WithdrawalsByMessageNonce mocks returning withdrawals by a withdrawal hash
func (mbv *MockBridgeView) WithdrawalByMessageNonce(nonce *big.Int) (*database.Withdrawal, error) {
return &database.Withdrawal{
GUID: uuid.MustParse(guid2),
InitiatedL2EventGUID: uuid.MustParse(guid1),
WithdrawalHash: common.HexToHash("0x456"),
Tx: database.Transaction{},
TokenPair: database.TokenPair{},
}, nil
func (mbv *MockBridgeTransfersView) L2BridgeWithdrawalByWithdrawalHash(address common.Hash) (*database.L2BridgeWithdrawal, error) {
return &withdrawal, nil
}
// WithdrawalsByHash mocks returning withdrawals by a withdrawal hash
func (mbv *MockBridgeView) WithdrawalByHash(address common.Hash) (*database.Withdrawal, error) {
return &database.Withdrawal{
GUID: uuid.MustParse(guid2),
InitiatedL2EventGUID: uuid.MustParse(guid1),
WithdrawalHash: common.HexToHash("0x456"),
Tx: database.Transaction{},
TokenPair: database.TokenPair{},
}, nil
func (mbv *MockBridgeTransfersView) L2BridgeWithdrawalByCrossDomainMessengerNonce(nonce *big.Int) (*database.L2BridgeWithdrawal, error) {
return &withdrawal, nil
}
// LatestWithdrawalMessageNonce mocks returning the latest cross domain message nonce for a withdrawal
func (mbv *MockBridgeView) LatestWithdrawalMessageNonce() (*big.Int, error) {
return big.NewInt(0), nil
func (mbv *MockBridgeTransfersView) L2BridgeWithdrawalsByAddress(address common.Address) ([]*database.L2BridgeWithdrawalWithTransactionHashes, error) {
return []*database.L2BridgeWithdrawalWithTransactionHashes{
{
L2BridgeWithdrawal: withdrawal,
L2TransactionHash: common.HexToHash("0x789"),
},
}, nil
}
func TestHealthz(t *testing.T) {
api := NewApi(&MockBridgeView{})
api := NewApi(&MockBridgeTransfersView{})
request, err := http.NewRequest("GET", "/healthz", nil)
assert.Nil(t, err)
......@@ -104,8 +82,8 @@ func TestHealthz(t *testing.T) {
assert.Equal(t, http.StatusOK, responseRecorder.Code)
}
func TestDepositsHandler(t *testing.T) {
api := NewApi(&MockBridgeView{})
func TestL1BridgeDepositsHandler(t *testing.T) {
api := NewApi(&MockBridgeTransfersView{})
request, err := http.NewRequest("GET", "/api/v0/deposits/0x123", nil)
assert.Nil(t, err)
......@@ -115,8 +93,8 @@ func TestDepositsHandler(t *testing.T) {
assert.Equal(t, http.StatusOK, responseRecorder.Code)
}
func TestWithdrawalsHandler(t *testing.T) {
api := NewApi(&MockBridgeView{})
func TestL2BridgeWithdrawalsByAddressHandler(t *testing.T) {
api := NewApi(&MockBridgeTransfersView{})
request, err := http.NewRequest("GET", "/api/v0/withdrawals/0x123", nil)
assert.Nil(t, err)
......
......@@ -23,15 +23,16 @@ type Cli struct {
}
func runIndexer(ctx *cli.Context) error {
logger := log.NewLogger(log.ReadCLIConfig(ctx))
configPath := ctx.String(ConfigFlag.Name)
cfg, err := config.LoadConfig(configPath)
if err != nil {
logger.Error("failed to load config", "err", err)
return err
}
// setup logger
cfg.Logger = log.NewLogger(log.ReadCLIConfig(ctx))
cfg.Logger = logger
indexer, err := indexer.NewIndexer(cfg)
if err != nil {
return err
......@@ -47,17 +48,20 @@ func runIndexer(ctx *cli.Context) error {
}
func runApi(ctx *cli.Context) error {
configPath := ctx.String(ConfigFlag.Name)
conf, err := config.LoadConfig(configPath)
fmt.Println(conf)
logger := log.NewLogger(log.ReadCLIConfig(ctx))
configPath := ctx.String(ConfigFlag.Name)
cfg, err := config.LoadConfig(configPath)
if err != nil {
panic(err)
logger.Error("failed to load config", "err", err)
return err
}
cfg.Logger = logger
fmt.Println(cfg)
// finish me
return nil
return err
}
var (
......
......@@ -10,9 +10,9 @@ import (
type DB struct {
gorm *gorm.DB
Blocks BlocksDB
ContractEvents ContractEventsDB
Bridge BridgeDB
Blocks BlocksDB
ContractEvents ContractEventsDB
BridgeTransfers BridgeTransfersDB
}
func NewDB(dsn string) (*DB, error) {
......@@ -31,10 +31,10 @@ func NewDB(dsn string) (*DB, error) {
}
db := &DB{
gorm: gorm,
Blocks: newBlocksDB(gorm),
ContractEvents: newContractEventsDB(gorm),
Bridge: newBridgeDB(gorm),
gorm: gorm,
Blocks: newBlocksDB(gorm),
ContractEvents: newContractEventsDB(gorm),
BridgeTransfers: newBridgeTransfersDB(gorm),
}
return db, nil
......@@ -59,9 +59,9 @@ func (db *DB) Close() error {
func dbFromGormTx(tx *gorm.DB) *DB {
return &DB{
gorm: tx,
Blocks: newBlocksDB(tx),
ContractEvents: newContractEventsDB(tx),
Bridge: newBridgeDB(tx),
gorm: tx,
Blocks: newBlocksDB(tx),
ContractEvents: newContractEventsDB(tx),
BridgeTransfers: newBridgeTransfersDB(tx),
}
}
package e2e_tests
import (
"context"
"math/big"
"testing"
"time"
"github.com/ethereum-optimism/optimism/indexer/processor"
"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"
op_e2e "github.com/ethereum-optimism/optimism/op-e2e"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"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/params"
"github.com/stretchr/testify/require"
)
func TestE2EBridge(t *testing.T) {
testSuite := createE2ETestSuite(t)
l1Client := testSuite.OpSys.Clients["l1"]
l2Client := testSuite.OpSys.Clients["sequencer"]
l1StandardBridge, err := bindings.NewL1StandardBridge(testSuite.OpCfg.L1Deployments.L1StandardBridgeProxy, l1Client)
require.NoError(t, err)
l2StandardBridge, err := bindings.NewL2StandardBridge(predeploys.L2StandardBridgeAddr, l2Client)
require.NoError(t, err)
// pre-emptively conduct a deposit & withdrawal to speed up the test
setupCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
aliceAddr := testSuite.OpCfg.Secrets.Addresses().Alice
l1Opts, err := bind.NewKeyedTransactorWithChainID(testSuite.OpCfg.Secrets.Alice, testSuite.OpCfg.L1ChainIDBig())
require.NoError(t, err)
l2Opts, err := bind.NewKeyedTransactorWithChainID(testSuite.OpCfg.Secrets.Alice, testSuite.OpCfg.L2ChainIDBig())
require.NoError(t, err)
l1Opts.Value = big.NewInt(params.Ether)
l2Opts.Value = big.NewInt(params.Ether)
depositTx, err := l1StandardBridge.DepositETH(l1Opts, 200_000, []byte{byte(1)})
require.NoError(t, err)
withdrawTx, err := l2StandardBridge.Withdraw(l2Opts, processor.EthAddress, big.NewInt(params.Ether), 200_000, []byte{byte(1)})
require.NoError(t, err)
depositReceipt, err := utils.WaitReceiptOK(setupCtx, l1Client, depositTx.Hash())
require.NoError(t, err)
withdrawalReceipt, err := utils.WaitReceiptOK(setupCtx, l2Client, withdrawTx.Hash())
require.NoError(t, err)
t.Run("indexes ETH deposits", func(t *testing.T) {
testCtx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
// Pause the L2Processor so that we can test for finalization separately. A pause is
// required since deposit inclusion is apart of the L2 block derivation process
testSuite.Indexer.L2Processor.PauseForTest()
// (1) Test Deposit Initiation
// wait for processor catchup
require.NoError(t, utils.WaitFor(testCtx, 500*time.Millisecond, func() (bool, error) {
l1Header := testSuite.Indexer.L1Processor.LatestProcessedHeader()
return l1Header != nil && l1Header.Number.Uint64() >= depositReceipt.BlockNumber.Uint64(), nil
}))
aliceDeposits, err := testSuite.DB.Bridge.DepositsByAddress(aliceAddr)
require.NoError(t, err)
require.Len(t, aliceDeposits, 1)
require.Equal(t, depositTx.Hash(), aliceDeposits[0].L1TransactionHash)
require.Empty(t, aliceDeposits[0].FinalizedL2TransactionHash)
deposit := aliceDeposits[0].Deposit
require.Nil(t, deposit.FinalizedL2EventGUID)
require.Equal(t, processor.EthAddress, deposit.TokenPair.L1TokenAddress)
require.Equal(t, processor.EthAddress, deposit.TokenPair.L2TokenAddress)
require.Equal(t, big.NewInt(params.Ether), deposit.Tx.Amount.Int)
require.Equal(t, aliceAddr, deposit.Tx.FromAddress)
require.Equal(t, aliceAddr, deposit.Tx.ToAddress)
require.Equal(t, byte(1), deposit.Tx.Data[0])
// (2) Test Deposit Finalization
testSuite.Indexer.L2Processor.ResumeForTest()
// finalization hash can be deterministically derived from TransactionDeposited log
var depositTxHash common.Hash
for _, log := range depositReceipt.Logs {
if log.Topics[0] == derive.DepositEventABIHash {
deposit, err := derive.UnmarshalDepositLogEvent(log)
require.NoError(t, err)
depositTxHash = types.NewTx(deposit).Hash()
break
}
}
// wait for the l2 processor to catch this deposit in the derivation process
_, err = utils.WaitReceiptOK(testCtx, l2Client, depositTxHash)
require.NoError(t, err)
l2Height, err := l2Client.BlockNumber(testCtx)
require.NoError(t, err)
require.NoError(t, utils.WaitFor(testCtx, 500*time.Millisecond, func() (bool, error) {
l2Header := testSuite.Indexer.L2Processor.LatestProcessedHeader()
return l2Header != nil && l2Header.Number.Uint64() >= l2Height, nil
}))
aliceDeposits, err = testSuite.DB.Bridge.DepositsByAddress(aliceAddr)
require.NoError(t, err)
require.Equal(t, depositTxHash, aliceDeposits[0].FinalizedL2TransactionHash)
require.NotNil(t, aliceDeposits[0].Deposit.FinalizedL2EventGUID)
})
t.Run("indexes ETH withdrawals", func(t *testing.T) {
testCtx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
defer cancel()
// (1) Test Withdrawal Initiation
// wait for processor catchup
require.NoError(t, utils.WaitFor(testCtx, 500*time.Millisecond, func() (bool, error) {
l2Header := testSuite.Indexer.L2Processor.LatestProcessedHeader()
return l2Header != nil && l2Header.Number.Uint64() >= withdrawalReceipt.BlockNumber.Uint64(), nil
}))
aliceWithdrawals, err := testSuite.DB.Bridge.WithdrawalsByAddress(aliceAddr)
require.NoError(t, err)
require.Len(t, aliceWithdrawals, 1)
require.Equal(t, withdrawTx.Hash(), aliceWithdrawals[0].L2TransactionHash)
require.Empty(t, aliceWithdrawals[0].ProvenL1TransactionHash)
require.Empty(t, aliceWithdrawals[0].FinalizedL1TransactionHash)
withdrawal := aliceWithdrawals[0].Withdrawal
require.Nil(t, withdrawal.ProvenL1EventGUID)
require.Nil(t, withdrawal.FinalizedL1EventGUID)
require.Equal(t, processor.EthAddress, withdrawal.TokenPair.L1TokenAddress)
require.Equal(t, processor.EthAddress, withdrawal.TokenPair.L2TokenAddress)
require.Equal(t, big.NewInt(params.Ether), withdrawal.Tx.Amount.Int)
require.Equal(t, aliceAddr, withdrawal.Tx.FromAddress)
require.Equal(t, aliceAddr, withdrawal.Tx.ToAddress)
require.Equal(t, byte(1), withdrawal.Tx.Data[0])
// (2) Test Withdrawal Proven
// prove & wait for processor catchup
withdrawParams, proveReceipt := op_e2e.ProveWithdrawal(t, *testSuite.OpCfg, l1Client, testSuite.OpSys.Nodes["sequencer"], testSuite.OpCfg.Secrets.Alice, withdrawalReceipt)
require.NoError(t, utils.WaitFor(testCtx, 500*time.Millisecond, func() (bool, error) {
l1Header := testSuite.Indexer.L1Processor.LatestProcessedHeader()
return l1Header != nil && l1Header.Number.Uint64() >= proveReceipt.BlockNumber.Uint64(), nil
}))
aliceWithdrawals, err = testSuite.DB.Bridge.WithdrawalsByAddress(aliceAddr)
require.NoError(t, err)
require.Empty(t, aliceWithdrawals[0].FinalizedL1TransactionHash)
require.Equal(t, proveReceipt.TxHash, aliceWithdrawals[0].ProvenL1TransactionHash)
// (3) Test Withdrawal Finalization
// finalize & wait for processor catchup
finalizeReceipt := op_e2e.FinalizeWithdrawal(t, *testSuite.OpCfg, l1Client, testSuite.OpCfg.Secrets.Alice, proveReceipt, withdrawParams)
require.NoError(t, utils.WaitFor(testCtx, 500*time.Millisecond, func() (bool, error) {
l1Header := testSuite.Indexer.L1Processor.LatestProcessedHeader()
return l1Header != nil && l1Header.Number.Uint64() >= finalizeReceipt.BlockNumber.Uint64(), nil
}))
aliceWithdrawals, err = testSuite.DB.Bridge.WithdrawalsByAddress(aliceAddr)
require.NoError(t, err)
require.Equal(t, finalizeReceipt.TxHash, aliceWithdrawals[0].FinalizedL1TransactionHash)
})
}
......@@ -23,25 +23,19 @@ import (
func TestE2EBlockHeaders(t *testing.T) {
testSuite := createE2ETestSuite(t)
l1Client := testSuite.OpSys.Clients["l1"]
l2Client := testSuite.OpSys.Clients["sequencer"]
l2OutputOracle, err := bindings.NewL2OutputOracleCaller(testSuite.OpCfg.L1Deployments.L2OutputOracleProxy, l1Client)
l2OutputOracle, err := bindings.NewL2OutputOracle(testSuite.OpCfg.L1Deployments.L2OutputOracleProxy, testSuite.L1Client)
require.NoError(t, err)
// a minute for total setup to finish
setupCtx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
// wait for at least 10 L2 blocks to be created & posted on L1
require.NoError(t, utils.WaitFor(setupCtx, time.Second, func() (bool, error) {
l2Height, err := l2OutputOracle.LatestBlockNumber(&bind.CallOpts{Context: setupCtx})
require.NoError(t, utils.WaitFor(context.Background(), time.Second, func() (bool, error) {
l2Height, err := l2OutputOracle.LatestBlockNumber(&bind.CallOpts{Context: context.Background()})
return l2Height != nil && l2Height.Uint64() >= 9, err
}))
// ensure the processors are caught up to this state
l1Height, err := l1Client.BlockNumber(setupCtx)
l1Height, err := testSuite.L1Client.BlockNumber(context.Background())
require.NoError(t, err)
require.NoError(t, utils.WaitFor(setupCtx, time.Second, func() (bool, error) {
require.NoError(t, utils.WaitFor(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
......@@ -60,7 +54,7 @@ func TestE2EBlockHeaders(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, indexedHeader)
header, err := l2Client.HeaderByNumber(context.Background(), height)
header, err := testSuite.L2Client.HeaderByNumber(context.Background(), height)
require.NoError(t, err)
require.NotNil(t, indexedHeader)
......@@ -93,7 +87,7 @@ func TestE2EBlockHeaders(t *testing.T) {
require.NotEmpty(t, output.L1ContractEventGUID)
// we may as well check the integrity of the output root
l2Block, err := l2Client.BlockByNumber(context.Background(), blockNumber)
l2Block, err := testSuite.L2Client.BlockByNumber(context.Background(), blockNumber)
require.NoError(t, err)
messagePasserStorageHash, err := l2EthClient.StorageHash(predeploys.L2ToL1MessagePasserAddr, blockNumber)
require.NoError(t, err)
......@@ -111,12 +105,10 @@ func TestE2EBlockHeaders(t *testing.T) {
testCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
devContracts := make([]common.Address, 0)
testSuite.OpCfg.L1Deployments.ForEach(func(name string, address common.Address) {
devContracts = append(devContracts, address)
})
logFilter := ethereum.FilterQuery{FromBlock: big.NewInt(0), ToBlock: big.NewInt(int64(l1Height)), Addresses: devContracts}
logs, err := l1Client.FilterLogs(testCtx, logFilter) // []types.Log
l1Contracts := []common.Address{}
testSuite.OpCfg.L1Deployments.ForEach(func(name string, addr common.Address) { l1Contracts = append(l1Contracts, addr) })
logFilter := ethereum.FilterQuery{FromBlock: big.NewInt(0), ToBlock: big.NewInt(int64(l1Height)), Addresses: l1Contracts}
logs, err := testSuite.L1Client.FilterLogs(testCtx, logFilter) // []types.Log
require.NoError(t, err)
for _, log := range logs {
......@@ -128,7 +120,7 @@ func TestE2EBlockHeaders(t *testing.T) {
require.Equal(t, log.Index, uint(contractEvent.LogIndex))
// ensure the block is also indexed
block, err := l1Client.BlockByNumber(testCtx, big.NewInt(int64(log.BlockNumber)))
block, err := testSuite.L1Client.BlockByNumber(testCtx, big.NewInt(int64(log.BlockNumber)))
require.NoError(t, err)
require.Equal(t, block.Time(), contractEvent.Timestamp)
......
package e2e_tests
import (
"context"
"math/big"
"testing"
"time"
e2etest_utils "github.com/ethereum-optimism/optimism/indexer/e2e_tests/utils"
op_e2e "github.com/ethereum-optimism/optimism/op-e2e"
"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"
"github.com/stretchr/testify/require"
)
func TestE2EBridgeTransfersStandardBridgeETHDeposit(t *testing.T) {
testSuite := createE2ETestSuite(t)
l1StandardBridge, err := bindings.NewL1StandardBridge(testSuite.OpCfg.L1Deployments.L1StandardBridgeProxy, testSuite.L1Client)
require.NoError(t, err)
// 1 ETH transfer
aliceAddr := testSuite.OpCfg.Secrets.Addresses().Alice
l1Opts, err := bind.NewKeyedTransactorWithChainID(testSuite.OpCfg.Secrets.Alice, testSuite.OpCfg.L1ChainIDBig())
require.NoError(t, err)
l1Opts.Value = big.NewInt(params.Ether)
// Pause the L2Processor so that we can test for finalization separately. A pause is
// required since deposit inclusion is apart of the L2 block derivation process
testSuite.Indexer.L2Processor.PauseForTest()
// (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())
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) {
l1Header := testSuite.Indexer.L1Processor.LatestProcessedHeader()
return l1Header != nil && l1Header.Number.Uint64() >= depositReceipt.BlockNumber.Uint64(), nil
}))
aliceDeposits, err := testSuite.DB.BridgeTransfers.L1BridgeDepositsByAddress(aliceAddr)
require.NoError(t, err)
require.Len(t, aliceDeposits, 1)
require.Equal(t, depositTx.Hash(), aliceDeposits[0].L1TransactionHash)
require.Empty(t, aliceDeposits[0].FinalizedL2TransactionHash)
deposit := aliceDeposits[0].L1BridgeDeposit
require.Equal(t, predeploys.LegacyERC20ETHAddr, deposit.TokenPair.L1TokenAddress)
require.Equal(t, predeploys.LegacyERC20ETHAddr, deposit.TokenPair.L2TokenAddress)
require.Equal(t, big.NewInt(params.Ether), deposit.Tx.Amount.Int)
require.Equal(t, aliceAddr, deposit.Tx.FromAddress)
require.Equal(t, aliceAddr, deposit.Tx.ToAddress)
require.Equal(t, byte(1), deposit.Tx.Data[0])
// (2) Test Deposit Finalization
require.Nil(t, deposit.FinalizedL2EventGUID)
testSuite.Indexer.L2Processor.ResumeForTest()
// wait for the l2 processor to catch this deposit in the derivation process
depositReceipt, err = utils.WaitReceiptOK(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) {
l2Header := testSuite.Indexer.L2Processor.LatestProcessedHeader()
return l2Header != nil && l2Header.Number.Uint64() >= depositReceipt.BlockNumber.Uint64(), nil
}))
aliceDeposits, err = testSuite.DB.BridgeTransfers.L1BridgeDepositsByAddress(aliceAddr)
require.NoError(t, err)
require.NotNil(t, aliceDeposits[0].L1BridgeDeposit.FinalizedL2EventGUID)
require.Equal(t, types.NewTx(depositInfo.DepositTx).Hash(), aliceDeposits[0].FinalizedL2TransactionHash)
}
func TestE2EBridgeTransfersStandardBridgeETHWithdrawal(t *testing.T) {
testSuite := createE2ETestSuite(t)
optimismPortal, err := bindings.NewOptimismPortal(testSuite.OpCfg.L1Deployments.OptimismPortalProxy, testSuite.L1Client)
require.NoError(t, err)
l2StandardBridge, err := bindings.NewL2StandardBridge(predeploys.L2StandardBridgeAddr, testSuite.L2Client)
require.NoError(t, err)
// 1 ETH transfer
aliceAddr := testSuite.OpCfg.Secrets.Addresses().Alice
l2Opts, err := bind.NewKeyedTransactorWithChainID(testSuite.OpCfg.Secrets.Alice, testSuite.OpCfg.L2ChainIDBig())
require.NoError(t, err)
l2Opts.Value = big.NewInt(params.Ether)
// Ensure L1 has enough funds for the withdrawal by depositing an equal amount into the OptimismPortal
l1Opts, err := bind.NewKeyedTransactorWithChainID(testSuite.OpCfg.Secrets.Alice, testSuite.OpCfg.L1ChainIDBig())
require.NoError(t, err)
l1Opts.Value = l2Opts.Value
depositTx, err := optimismPortal.Receive(l1Opts)
require.NoError(t, err)
_, err = utils.WaitReceiptOK(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())
require.NoError(t, err)
// wait for processor catchup
require.NoError(t, utils.WaitFor(context.Background(), 500*time.Millisecond, func() (bool, error) {
l2Header := testSuite.Indexer.L2Processor.LatestProcessedHeader()
return l2Header != nil && l2Header.Number.Uint64() >= withdrawReceipt.BlockNumber.Uint64(), nil
}))
aliceWithdrawals, err := testSuite.DB.BridgeTransfers.L2BridgeWithdrawalsByAddress(aliceAddr)
require.NoError(t, err)
require.Len(t, aliceWithdrawals, 1)
require.Equal(t, withdrawTx.Hash(), aliceWithdrawals[0].L2TransactionHash)
msgPassed, err := withdrawals.ParseMessagePassed(withdrawReceipt)
require.NoError(t, err)
withdrawalHash, err := withdrawals.WithdrawalHash(msgPassed)
require.NoError(t, err)
withdrawal := aliceWithdrawals[0].L2BridgeWithdrawal
require.Equal(t, withdrawalHash, withdrawal.WithdrawalHash)
require.Equal(t, predeploys.LegacyERC20ETHAddr, withdrawal.TokenPair.L1TokenAddress)
require.Equal(t, predeploys.LegacyERC20ETHAddr, withdrawal.TokenPair.L2TokenAddress)
require.Equal(t, big.NewInt(params.Ether), withdrawal.Tx.Amount.Int)
require.Equal(t, aliceAddr, withdrawal.Tx.FromAddress)
require.Equal(t, aliceAddr, withdrawal.Tx.ToAddress)
require.Equal(t, byte(1), withdrawal.Tx.Data[0])
// (2) Test Withdrawal Proven/Finalized. Test the sql join queries to populate the right transaction
require.Nil(t, withdrawal.ProvenL1EventGUID)
require.Nil(t, withdrawal.FinalizedL1EventGUID)
require.Empty(t, aliceWithdrawals[0].ProvenL1TransactionHash)
require.Empty(t, aliceWithdrawals[0].FinalizedL1TransactionHash)
// 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) {
l1Header := testSuite.Indexer.L1Processor.LatestProcessedHeader()
return l1Header != nil && l1Header.Number.Uint64() >= finalizeReceipt.BlockNumber.Uint64(), nil
}))
aliceWithdrawals, err = testSuite.DB.BridgeTransfers.L2BridgeWithdrawalsByAddress(aliceAddr)
require.NoError(t, err)
require.NotNil(t, aliceWithdrawals[0].L2BridgeWithdrawal.ProvenL1EventGUID)
require.NotNil(t, aliceWithdrawals[0].L2BridgeWithdrawal.FinalizedL1EventGUID)
require.Equal(t, proveReceipt.TxHash, aliceWithdrawals[0].ProvenL1TransactionHash)
require.Equal(t, finalizeReceipt.TxHash, aliceWithdrawals[0].FinalizedL1TransactionHash)
}
......@@ -17,6 +17,7 @@ import (
op_e2e "github.com/ethereum-optimism/optimism/op-e2e"
"github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
_ "github.com/jackc/pgx/v5/stdlib"
......@@ -33,6 +34,10 @@ type E2ETestSuite struct {
// Rollup
OpCfg *op_e2e.SystemConfig
OpSys *op_e2e.System
// Clients
L1Client *ethclient.Client
L2Client *ethclient.Client
}
func createE2ETestSuite(t *testing.T) E2ETestSuite {
......@@ -48,16 +53,9 @@ func createE2ETestSuite(t *testing.T) E2ETestSuite {
opSys, err := opCfg.Start()
require.NoError(t, err)
l1Contracts := processor.L1Contracts{
OptimismPortal: opCfg.L1Deployments.OptimismPortalProxy,
L2OutputOracle: opCfg.L1Deployments.L2OutputOracleProxy,
L1CrossDomainMessenger: opCfg.L1Deployments.L1CrossDomainMessengerProxy,
L1StandardBridge: opCfg.L1Deployments.L1StandardBridgeProxy,
L1ERC721Bridge: opCfg.L1Deployments.L1ERC721BridgeProxy,
}
// Indexer Configuration and Start
indexerCfg := config.Config{
Logger: logger,
DB: config.DBConfig{
Host: "127.0.0.1",
Port: 5432,
......@@ -68,9 +66,14 @@ func createE2ETestSuite(t *testing.T) E2ETestSuite {
L1RPC: opSys.Nodes["l1"].HTTPEndpoint(),
L2RPC: opSys.Nodes["sequencer"].HTTPEndpoint(),
},
Logger: logger,
Chain: config.ChainConfig{
L1Contracts: l1Contracts,
L1Contracts: processor.L1Contracts{
OptimismPortal: opCfg.L1Deployments.OptimismPortalProxy,
L2OutputOracle: opCfg.L1Deployments.L2OutputOracleProxy,
L1CrossDomainMessenger: opCfg.L1Deployments.L1CrossDomainMessengerProxy,
L1StandardBridge: opCfg.L1Deployments.L1StandardBridgeProxy,
L1ERC721Bridge: opCfg.L1Deployments.L1ERC721BridgeProxy,
},
},
}
......@@ -99,11 +102,13 @@ func createE2ETestSuite(t *testing.T) E2ETestSuite {
})
return E2ETestSuite{
t: t,
DB: db,
Indexer: indexer,
OpCfg: &opCfg,
OpSys: opSys,
t: t,
DB: db,
Indexer: indexer,
OpCfg: &opCfg,
OpSys: opSys,
L1Client: opSys.Clients["l1"],
L2Client: opSys.Clients["sequencer"],
}
}
......
package utils
import (
"errors"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
)
type DepositInfo struct {
*bindings.OptimismPortalTransactionDeposited
DepositTx *types.DepositTx
}
func ParseDepositInfo(depositReceipt *types.Receipt) (*DepositInfo, error) {
optimismPortal, err := bindings.NewOptimismPortal(common.Address{}, nil)
if err != nil {
return nil, err
}
for _, log := range depositReceipt.Logs {
if log.Topics[0] == derive.DepositEventABIHash {
portalTxDeposited, err := optimismPortal.ParseTransactionDeposited(*log)
if err != nil {
return nil, err
}
depositTx, err := derive.UnmarshalDepositLogEvent(log)
if err != nil {
return nil, err
}
return &DepositInfo{portalTxDeposited, depositTx}, nil
}
}
return nil, errors.New("cannot find deposit event in receipt")
}
......@@ -72,21 +72,30 @@ func NewIndexer(cfg config.Config) (*Indexer, error) {
// Start starts the indexing service on L1 and L2 chains
func (i *Indexer) Run(ctx context.Context) error {
var wg sync.WaitGroup
errCh := make(chan error)
errCh := make(chan error, 1)
// If either processor errors out, we stop
processorCtx, cancel := context.WithCancel(ctx)
run := func(start func(ctx context.Context) error) {
wg.Add(1)
defer wg.Done()
defer func() {
if err := recover(); err != nil {
i.log.Error("halting indexer on panic", "err", err)
errCh <- fmt.Errorf("panic: %v", err)
}
wg.Done()
}()
err := start(processorCtx)
if err != nil {
i.log.Error("halting indexer on error", "err", err)
cancel()
errCh <- err
}
// Send a value down regardless if we've received an error or halted
// via cancellation where err == nil
errCh <- err
}
// Kick off the processors
......
......@@ -67,12 +67,12 @@ CREATE TABLE IF NOT EXISTS output_proposals (
* BRIDGING DATA
*/
CREATE TABLE IF NOT EXISTS deposits (
CREATE TABLE IF NOT EXISTS l1_bridge_deposits (
guid VARCHAR PRIMARY KEY NOT NULL,
-- Event causing the deposit
initiated_l1_event_guid VARCHAR NOT NULL REFERENCES l1_contract_events(guid),
sent_message_nonce UINT256 UNIQUE,
initiated_l1_event_guid VARCHAR NOT NULL REFERENCES l1_contract_events(guid),
cross_domain_messenger_nonce UINT256 UNIQUE,
-- Finalization marker for the deposit
finalized_l2_event_guid VARCHAR REFERENCES l2_contract_events(guid),
......@@ -88,12 +88,12 @@ CREATE TABLE IF NOT EXISTS deposits (
timestamp INTEGER NOT NULL CHECK (timestamp > 0)
);
CREATE TABLE IF NOT EXISTS withdrawals (
CREATE TABLE IF NOT EXISTS l2_bridge_withdrawals (
guid VARCHAR PRIMARY KEY NOT NULL,
-- Event causing this withdrawal
initiated_l2_event_guid VARCHAR NOT NULL REFERENCES l2_contract_events(guid),
sent_message_nonce UINT256 UNIQUE,
initiated_l2_event_guid VARCHAR NOT NULL REFERENCES l2_contract_events(guid),
cross_domain_messenger_nonce UINT256 UNIQUE,
-- Multistep (bedrock) process of a withdrawal
withdrawal_hash VARCHAR NOT NULL,
......
......@@ -255,13 +255,13 @@ func l1BridgeProcessContractEvents(processLog log.Logger, db *database.DB, ethCl
return err
}
deposits := make([]*database.Deposit, len(initiatedDepositEvents))
deposits := make([]*database.L1BridgeDeposit, len(initiatedDepositEvents))
for i, initiatedBridgeEvent := range initiatedDepositEvents {
deposits[i] = &database.Deposit{
GUID: uuid.New(),
InitiatedL1EventGUID: initiatedBridgeEvent.RawEvent.GUID,
SentMessageNonce: database.U256{Int: initiatedBridgeEvent.CrossDomainMessengerNonce},
TokenPair: database.TokenPair{L1TokenAddress: initiatedBridgeEvent.LocalToken, L2TokenAddress: initiatedBridgeEvent.RemoteToken},
deposits[i] = &database.L1BridgeDeposit{
GUID: uuid.New(),
InitiatedL1EventGUID: initiatedBridgeEvent.RawEvent.GUID,
CrossDomainMessengerNonce: database.U256{Int: initiatedBridgeEvent.CrossDomainMessengerNonce},
TokenPair: database.TokenPair{L1TokenAddress: initiatedBridgeEvent.LocalToken, L2TokenAddress: initiatedBridgeEvent.RemoteToken},
Tx: database.Transaction{
FromAddress: initiatedBridgeEvent.From,
ToAddress: initiatedBridgeEvent.To,
......@@ -274,7 +274,7 @@ func l1BridgeProcessContractEvents(processLog log.Logger, db *database.DB, ethCl
if len(deposits) > 0 {
processLog.Info("detected L1StandardBridge deposits", "num", len(deposits))
err := db.Bridge.StoreDeposits(deposits)
err := db.BridgeTransfers.StoreL1BridgeDeposits(deposits)
if err != nil {
return err
}
......@@ -286,47 +286,39 @@ func l1BridgeProcessContractEvents(processLog log.Logger, db *database.DB, ethCl
return err
}
// we manually keep track since not every proven withdrawal is a standard bridge withdrawal
latestL2Header, err := db.Blocks.LatestL2BlockHeader()
if err != nil {
return err
} else if len(provenWithdrawalEvents) > 0 && latestL2Header == nil {
return errors.New("no indexed L2 state to process any proven L1 transactions")
}
numProvenWithdrawals := 0
for _, provenWithdrawalEvent := range provenWithdrawalEvents {
withdrawalHash := provenWithdrawalEvent.WithdrawalHash
withdrawal, err := db.Bridge.WithdrawalByHash(withdrawalHash)
withdrawal, err := db.BridgeTransfers.L2BridgeWithdrawalByWithdrawalHash(withdrawalHash)
if err != nil {
return err
}
// Check if the L2Processor is behind or really has missed an event. We can compare against the
// OptimismPortal#ProvenWithdrawal on-chain mapping relative to the latest indexed L2 height
if withdrawal == nil {
// This needs to be updated to read from config as well as correctly identify if the CrossDomainMessenger message is a standard
// bridge message. This will easier to do once we index passed messages separately which will include the right To/From fields
} else if withdrawal == nil {
// NOTE: This needs to be updated to identify if this CrossDomainMessenger message is a StandardBridge message. This
// will be easier to do once we index cross domain messages and track its lifecyle separately
if provenWithdrawalEvent.From != common.HexToAddress("0x4200000000000000000000000000000000000007") || provenWithdrawalEvent.To != l1Contracts.L1CrossDomainMessenger {
// non-bridge withdrawal
continue
}
// Query for the the proven withdrawal on-chain
provenWithdrawal, err := OptimismPortalQueryProvenWithdrawal(rawEthClient, l1Contracts.OptimismPortal, withdrawalHash)
if err != nil {
return err
}
latestL2Header, err := db.Blocks.LatestL2BlockHeader()
if err != nil {
return err
}
if latestL2Header == nil || provenWithdrawal.L2OutputIndex.Cmp(latestL2Header.Number.Int) > 0 {
processLog.Warn("behind on indexed L2 withdrawals")
// Check if the L2Processor is behind or really has missed an event. Since L2 timestamps
// are derived from L1, we can simply compare timestamps
if provenWithdrawalEvent.RawEvent.Timestamp > latestL2Header.Timestamp {
processLog.Warn("behind on indexed L2StandardBridge withdrawals")
return errors.New("waiting for L2Processor to catch up")
} else {
processLog.Crit("missing indexed withdrawal for this proven event")
processLog.Crit("missing indexed L2StandardBridge withdrawal for this proven event")
return errors.New("missing withdrawal message")
}
}
err = db.Bridge.MarkProvenWithdrawalEvent(withdrawal.GUID, provenWithdrawalEvent.RawEvent.GUID)
err = db.BridgeTransfers.MarkProvenL2BridgeWithdrawalEvent(withdrawal.GUID, provenWithdrawalEvent.RawEvent.GUID)
if err != nil {
return err
}
......@@ -346,7 +338,7 @@ func l1BridgeProcessContractEvents(processLog log.Logger, db *database.DB, ethCl
for _, finalizedBridgeEvent := range finalizedWithdrawalEvents {
nonce := finalizedBridgeEvent.CrossDomainMessengerNonce
withdrawal, err := db.Bridge.WithdrawalByMessageNonce(nonce)
withdrawal, err := db.BridgeTransfers.L2BridgeWithdrawalByCrossDomainMessengerNonce(nonce)
if err != nil {
processLog.Error("error querying associated withdrawal messsage using nonce", "cross_domain_messenger_nonce", nonce)
return err
......@@ -359,7 +351,7 @@ func l1BridgeProcessContractEvents(processLog log.Logger, db *database.DB, ethCl
return errors.New("missing withdrawal message")
}
err = db.Bridge.MarkFinalizedWithdrawalEvent(withdrawal.GUID, finalizedBridgeEvent.RawEvent.GUID)
err = db.BridgeTransfers.MarkFinalizedL2BridgeWithdrawalEvent(withdrawal.GUID, finalizedBridgeEvent.RawEvent.GUID)
if err != nil {
processLog.Error("error finalizing withdrawal", "err", err)
return err
......
......@@ -183,7 +183,7 @@ func l2BridgeProcessContractEvents(processLog log.Logger, db *database.DB, ethCl
return err
}
withdrawals := make([]*database.Withdrawal, len(initiatedWithdrawalEvents))
withdrawals := make([]*database.L2BridgeWithdrawal, len(initiatedWithdrawalEvents))
for i, initiatedBridgeEvent := range initiatedWithdrawalEvents {
log := events.eventLog[initiatedBridgeEvent.RawEvent.GUID]
......@@ -195,12 +195,12 @@ func l2BridgeProcessContractEvents(processLog log.Logger, db *database.DB, ethCl
return err
}
withdrawals[i] = &database.Withdrawal{
GUID: uuid.New(),
InitiatedL2EventGUID: initiatedBridgeEvent.RawEvent.GUID,
SentMessageNonce: database.U256{Int: initiatedBridgeEvent.CrossDomainMessengerNonce},
WithdrawalHash: msgPassedData.WithdrawalHash,
TokenPair: database.TokenPair{L1TokenAddress: initiatedBridgeEvent.LocalToken, L2TokenAddress: initiatedBridgeEvent.RemoteToken},
withdrawals[i] = &database.L2BridgeWithdrawal{
GUID: uuid.New(),
InitiatedL2EventGUID: initiatedBridgeEvent.RawEvent.GUID,
CrossDomainMessengerNonce: database.U256{Int: initiatedBridgeEvent.CrossDomainMessengerNonce},
WithdrawalHash: msgPassedData.WithdrawalHash,
TokenPair: database.TokenPair{L1TokenAddress: initiatedBridgeEvent.LocalToken, L2TokenAddress: initiatedBridgeEvent.RemoteToken},
Tx: database.Transaction{
FromAddress: initiatedBridgeEvent.From,
ToAddress: initiatedBridgeEvent.To,
......@@ -213,7 +213,7 @@ func l2BridgeProcessContractEvents(processLog log.Logger, db *database.DB, ethCl
if len(withdrawals) > 0 {
processLog.Info("detected L2StandardBridge withdrawals", "num", len(withdrawals))
err := db.Bridge.StoreWithdrawals(withdrawals)
err := db.BridgeTransfers.StoreL2BridgeWithdrawals(withdrawals)
if err != nil {
return err
}
......@@ -225,30 +225,33 @@ func l2BridgeProcessContractEvents(processLog log.Logger, db *database.DB, ethCl
return err
}
latestL1Header, err := db.Blocks.LatestL1BlockHeader()
if err != nil {
return err
} else if len(finalizationBridgeEvents) > 0 && latestL1Header == nil {
return errors.New("no indexed L1 state to process any L2 bridge finalizations")
}
for _, finalizedBridgeEvent := range finalizationBridgeEvents {
nonce := finalizedBridgeEvent.CrossDomainMessengerNonce
deposit, err := db.Bridge.DepositByMessageNonce(nonce)
deposit, err := db.BridgeTransfers.L1BridgeDepositByCrossDomainMessengerNonce(nonce)
if err != nil {
processLog.Error("error querying associated deposit messsage using nonce", "cross_domain_messenger_nonce", nonce)
return err
} else if deposit == nil {
latestNonce, err := db.Bridge.LatestDepositMessageNonce()
if err != nil {
return err
}
// Check if the L1Processor is behind or really has missed an event
if latestNonce == nil || nonce.Cmp(latestNonce) > 0 {
processLog.Warn("behind on indexed L1 deposits")
// Check if the L1Processor is behind or really has missed an event. Since L2 timestamps
// are derived from L1, we can simply compare timestamps
if finalizedBridgeEvent.RawEvent.Timestamp > latestL1Header.Timestamp {
processLog.Warn("behind on indexed L1StandardBridge deposits")
return errors.New("waiting for L1Processor to catch up")
} else {
processLog.Crit("missing indexed deposit for this finalization event")
processLog.Crit("missing indexed L1StandardBridge deposit for this finalization event")
return errors.New("missing deposit message")
}
}
err = db.Bridge.MarkFinalizedDepositEvent(deposit.GUID, finalizedBridgeEvent.RawEvent.GUID)
err = db.BridgeTransfers.MarkFinalizedL1BridgeDepositEvent(deposit.GUID, finalizedBridgeEvent.RawEvent.GUID)
if err != nil {
processLog.Error("error finalizing deposit", "err", err)
return err
......
This diff is collapsed.
......@@ -31,7 +31,7 @@ var (
// AlphabetVMMetaData contains all meta data concerning the AlphabetVM contract.
var AlphabetVMMetaData = &bind.MetaData{
ABI: "[{\"inputs\":[{\"internalType\":\"Claim\",\"name\":\"_absolutePrestate\",\"type\":\"bytes32\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"oracle\",\"outputs\":[{\"internalType\":\"contractIPreimageOracle\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"_stateData\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"name\":\"step\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"postState_\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]",
Bin: "0x60a060405234801561001057600080fd5b5060405161039838038061039883398101604081905261002f9161007a565b608081905261003c610062565b600080546001600160a01b0319166001600160a01b039290921691909117905550610093565b6460016000f36000908152600580601b83f091505090565b60006020828403121561008c57600080fd5b5051919050565b6080516102eb6100ad600039600060ad01526102eb6000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80637dc0d1d01461003b578063f8e0cb9614610085575b600080fd5b60005461005b9073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b6100986100933660046101a8565b6100a6565b60405190815260200161007c565b60008060007f000000000000000000000000000000000000000000000000000000000000000087876040516100dc929190610214565b60405180910390200361010057600091506100f986880188610224565b905061011f565b61010c8688018861023d565b90925090508161011b8161028e565b9250505b8161012b8260016102c6565b6040805160208101939093528201526060016040516020818303038152906040528051906020012092505050949350505050565b60008083601f84011261017157600080fd5b50813567ffffffffffffffff81111561018957600080fd5b6020830191508360208285010111156101a157600080fd5b9250929050565b600080600080604085870312156101be57600080fd5b843567ffffffffffffffff808211156101d657600080fd5b6101e28883890161015f565b909650945060208701359150808211156101fb57600080fd5b506102088782880161015f565b95989497509550505050565b8183823760009101908152919050565b60006020828403121561023657600080fd5b5035919050565b6000806040838503121561025057600080fd5b50508035926020909101359150565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036102bf576102bf61025f565b5060010190565b600082198211156102d9576102d961025f565b50019056fea164736f6c634300080f000a",
Bin: "0x60a060405234801561001057600080fd5b50604051610a73380380610a7383398101604081905261002f91610090565b608081905260405161004090610083565b604051809103906000f08015801561005c573d6000803e3d6000fd5b50600080546001600160a01b0319166001600160a01b0392909216919091179055506100a9565b6106c5806103ae83390190565b6000602082840312156100a257600080fd5b5051919050565b6080516102eb6100c3600039600060ad01526102eb6000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80637dc0d1d01461003b578063f8e0cb9614610085575b600080fd5b60005461005b9073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b6100986100933660046101a8565b6100a6565b60405190815260200161007c565b60008060007f000000000000000000000000000000000000000000000000000000000000000087876040516100dc929190610214565b60405180910390200361010057600091506100f986880188610224565b905061011f565b61010c8688018861023d565b90925090508161011b8161028e565b9250505b8161012b8260016102c6565b6040805160208101939093528201526060016040516020818303038152906040528051906020012092505050949350505050565b60008083601f84011261017157600080fd5b50813567ffffffffffffffff81111561018957600080fd5b6020830191508360208285010111156101a157600080fd5b9250929050565b600080600080604085870312156101be57600080fd5b843567ffffffffffffffff808211156101d657600080fd5b6101e28883890161015f565b909650945060208701359150808211156101fb57600080fd5b506102088782880161015f565b95989497509550505050565b8183823760009101908152919050565b60006020828403121561023657600080fd5b5035919050565b6000806040838503121561025057600080fd5b50508035926020909101359150565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036102bf576102bf61025f565b5060010190565b600082198211156102d9576102d961025f565b50019056fea164736f6c634300080f000a608060405234801561001057600080fd5b506106a5806100206000396000f3fe608060405234801561001057600080fd5b506004361061007d5760003560e01c8063e03110e11161005b578063e03110e114610111578063e159261114610139578063fe4ac08e1461014e578063fef2b4ed146101c357600080fd5b806361238bde146100825780638542cf50146100c05780639a1f5e7f146100fe575b600080fd5b6100ad610090366004610551565b600160209081526000928352604080842090915290825290205481565b6040519081526020015b60405180910390f35b6100ee6100ce366004610551565b600260209081526000928352604080842090915290825290205460ff1681565b60405190151581526020016100b7565b6100ad61010c366004610573565b6101e3565b61012461011f366004610551565b6102b6565b604080519283526020830191909152016100b7565b61014c6101473660046105a5565b6103a7565b005b61014c61015c366004610573565b6000838152600260209081526040808320878452825280832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660019081179091558684528252808320968352958152858220939093559283529082905291902055565b6100ad6101d1366004610621565b60006020819052908152604090205481565b60006101ee856104b0565b90506101fb836008610669565b8211806102085750602083115b1561023f576040517ffe25498700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000602081815260c085901b82526008959095528251828252600286526040808320858452875280832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600190811790915584845287528083209483529386528382205581815293849052922055919050565b6000828152600260209081526040808320848452909152812054819060ff1661033f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f7072652d696d616765206d757374206578697374000000000000000000000000604482015260640160405180910390fd5b506000838152602081815260409091205461035b816008610669565b610366856020610669565b106103845783610377826008610669565b6103819190610681565b91505b506000938452600160209081526040808620948652939052919092205492909150565b604435600080600883018611156103c65763fe2549876000526004601cfd5b60c083901b6080526088838682378087017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80151908490207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f02000000000000000000000000000000000000000000000000000000000000001760008181526002602090815260408083208b8452825280832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600190811790915584845282528083209a83529981528982209390935590815290819052959095209190915550505050565b7f01000000000000000000000000000000000000000000000000000000000000007effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82161761054b81600090815233602052604090207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f01000000000000000000000000000000000000000000000000000000000000001790565b92915050565b6000806040838503121561056457600080fd5b50508035926020909101359150565b6000806000806080858703121561058957600080fd5b5050823594602084013594506040840135936060013592509050565b6000806000604084860312156105ba57600080fd5b83359250602084013567ffffffffffffffff808211156105d957600080fd5b818601915086601f8301126105ed57600080fd5b8135818111156105fc57600080fd5b87602082850101111561060e57600080fd5b6020830194508093505050509250925092565b60006020828403121561063357600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000821982111561067c5761067c61063a565b500190565b6000828210156106935761069361063a565b50039056fea164736f6c634300080f000a",
}
// AlphabetVMABI is the input ABI used to generate the binding from.
......
This diff is collapsed.
This diff is collapsed.
......@@ -470,6 +470,7 @@ func NewDeployConfigWithNetwork(network, path string) (*DeployConfig, error) {
// L1Deployments represents a set of L1 contracts that are deployed.
type L1Deployments struct {
AddressManager common.Address `json:"AddressManager"`
BlockOracle common.Address `json:"BlockOracle"`
DisputeGameFactory common.Address `json:"DisputeGameFactory"`
DisputeGameFactoryProxy common.Address `json:"DisputeGameFactoryProxy"`
L1CrossDomainMessenger common.Address `json:"L1CrossDomainMessenger"`
......@@ -512,7 +513,7 @@ func (d *L1Deployments) Check() error {
for i := 0; i < val.NumField(); i++ {
name := val.Type().Field(i).Name
// Skip the non production ready contracts
if name == "DisputeGameFactory" || name == "DisputeGameFactoryProxy" {
if name == "DisputeGameFactory" || name == "DisputeGameFactoryProxy" || name == "BlockOracle" {
continue
}
if val.Field(i).Interface().(common.Address) == (common.Address{}) {
......
......@@ -13,17 +13,16 @@ import (
)
var (
l1EthRpc = "http://example.com:8545"
gameAddressValue = "0xaa00000000000000000000000000000000000000"
preimageOracleAddressValue = "0xbb00000000000000000000000000000000000000"
cannonBin = "./bin/cannon"
cannonServer = "./bin/op-program"
cannonPreState = "./pre.json"
cannonDatadir = "./test_data"
cannonL2 = "http://example.com:9545"
alphabetTrace = "abcdefghijz"
agreeWithProposedOutput = "true"
gameDepth = "4"
l1EthRpc = "http://example.com:8545"
gameAddressValue = "0xaa00000000000000000000000000000000000000"
cannonBin = "./bin/cannon"
cannonServer = "./bin/op-program"
cannonPreState = "./pre.json"
cannonDatadir = "./test_data"
cannonL2 = "http://example.com:9545"
alphabetTrace = "abcdefghijz"
agreeWithProposedOutput = "true"
gameDepth = "4"
)
func TestLogLevel(t *testing.T) {
......@@ -43,14 +42,14 @@ func TestLogLevel(t *testing.T) {
func TestDefaultCLIOptionsMatchDefaultConfig(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs(config.TraceTypeAlphabet))
defaultCfg := config.NewConfig(l1EthRpc, common.HexToAddress(gameAddressValue), common.HexToAddress(preimageOracleAddressValue), config.TraceTypeAlphabet, true, 4)
defaultCfg := config.NewConfig(l1EthRpc, common.HexToAddress(gameAddressValue), config.TraceTypeAlphabet, true, 4)
// Add in the extra CLI options required when using alphabet trace type
defaultCfg.AlphabetTrace = alphabetTrace
require.Equal(t, defaultCfg, cfg)
}
func TestDefaultConfigIsValid(t *testing.T) {
cfg := config.NewConfig(l1EthRpc, common.HexToAddress(gameAddressValue), common.HexToAddress(preimageOracleAddressValue), config.TraceTypeAlphabet, true, 4)
cfg := config.NewConfig(l1EthRpc, common.HexToAddress(gameAddressValue), config.TraceTypeAlphabet, true, 4)
// Add in options that are required based on the specific trace type
// To avoid needing to specify unused options, these aren't included in the params for NewConfig
cfg.AlphabetTrace = alphabetTrace
......@@ -140,26 +139,6 @@ func TestGameDepth(t *testing.T) {
})
}
func TestPreimageOracleAddress(t *testing.T) {
t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) {
configForArgs(t, addRequiredArgsExcept(config.TraceTypeAlphabet, "--preimage-oracle-address"))
})
t.Run("Required", func(t *testing.T) {
verifyArgsInvalid(t, "flag preimage-oracle-address is required", addRequiredArgsExcept(config.TraceTypeCannon, "--preimage-oracle-address"))
})
t.Run("Valid", func(t *testing.T) {
addr := common.Address{0xbb, 0xcc, 0xdd}
cfg := configForArgs(t, addRequiredArgsExcept(config.TraceTypeCannon, "--preimage-oracle-address", "--preimage-oracle-address="+addr.Hex()))
require.Equal(t, addr, cfg.PreimageOracleAddress)
})
t.Run("Invalid", func(t *testing.T) {
verifyArgsInvalid(t, "invalid address: foo", addRequiredArgsExcept(config.TraceTypeCannon, "--preimage-oracle-address", "--preimage-oracle-address=foo"))
})
}
func TestCannonBin(t *testing.T) {
t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) {
configForArgs(t, addRequiredArgsExcept(config.TraceTypeAlphabet, "--cannon-bin"))
......@@ -288,7 +267,6 @@ func requiredArgs(traceType config.TraceType) map[string]string {
"--agree-with-proposed-output": agreeWithProposedOutput,
"--l1-eth-rpc": l1EthRpc,
"--game-address": gameAddressValue,
"--preimage-oracle-address": preimageOracleAddressValue,
"--trace-type": traceType.String(),
}
switch traceType {
......
......@@ -61,7 +61,6 @@ const DefaultCannonSnapshotFreq = uint(10_000)
type Config struct {
L1EthRpc string // L1 RPC Url
GameAddress common.Address // Address of the fault game
PreimageOracleAddress common.Address // Address of the pre-image oracle
AgreeWithProposedOutput bool // Temporary config if we agree or disagree with the posted output
GameDepth int // Depth of the game tree
......@@ -84,15 +83,13 @@ type Config struct {
func NewConfig(
l1EthRpc string,
gameAddress common.Address,
preimageOracleAddress common.Address,
traceType TraceType,
agreeWithProposedOutput bool,
gameDepth int,
) Config {
return Config{
L1EthRpc: l1EthRpc,
GameAddress: gameAddress,
PreimageOracleAddress: preimageOracleAddress,
L1EthRpc: l1EthRpc,
GameAddress: gameAddress,
AgreeWithProposedOutput: agreeWithProposedOutput,
GameDepth: gameDepth,
......@@ -116,9 +113,6 @@ func (c Config) Check() error {
return ErrMissingTraceType
}
if c.TraceType == TraceTypeCannon {
if c.PreimageOracleAddress == (common.Address{}) {
return ErrMissingPreimageOracleAddress
}
if c.CannonBin == "" {
return ErrMissingCannonBin
}
......
......@@ -11,7 +11,6 @@ import (
var (
validL1EthRpc = "http://localhost:8545"
validGameAddress = common.HexToAddress("0x7bdd3b028C4796eF0EAf07d11394d0d9d8c24139")
validPreimageOracleAddress = common.HexToAddress("0x7bdd3b028C4796eF0EAf07d11394d0d9d8c24139")
validAlphabetTrace = "abcdefgh"
validCannonBin = "./bin/cannon"
validCannonOpProgramBin = "./bin/op-program"
......@@ -23,7 +22,7 @@ var (
)
func validConfig(traceType TraceType) Config {
cfg := NewConfig(validL1EthRpc, validGameAddress, validPreimageOracleAddress, traceType, agreeWithProposedOutput, gameDepth)
cfg := NewConfig(validL1EthRpc, validGameAddress, traceType, agreeWithProposedOutput, gameDepth)
switch traceType {
case TraceTypeAlphabet:
cfg.AlphabetTrace = validAlphabetTrace
......@@ -74,12 +73,6 @@ func TestAlphabetTraceRequired(t *testing.T) {
require.ErrorIs(t, config.Check(), ErrMissingAlphabetTrace)
}
func TestCannonPreimageOracleAddressRequired(t *testing.T) {
config := validConfig(TraceTypeCannon)
config.PreimageOracleAddress = common.Address{}
require.ErrorIs(t, config.Check(), ErrMissingPreimageOracleAddress)
}
func TestCannonBinRequired(t *testing.T) {
config := validConfig(TraceTypeCannon)
config.CannonBin = ""
......
......@@ -23,16 +23,18 @@ type Agent struct {
solver *solver.Solver
loader Loader
responder Responder
updater types.OracleUpdater
maxDepth int
agreeWithProposedOutput bool
log log.Logger
}
func NewAgent(loader Loader, maxDepth int, trace types.TraceProvider, responder Responder, agreeWithProposedOutput bool, log log.Logger) *Agent {
func NewAgent(loader Loader, maxDepth int, trace types.TraceProvider, responder Responder, updater types.OracleUpdater, agreeWithProposedOutput bool, log log.Logger) *Agent {
return &Agent{
solver: solver.NewSolver(maxDepth, trace),
loader: loader,
responder: responder,
updater: updater,
maxDepth: maxDepth,
agreeWithProposedOutput: agreeWithProposedOutput,
log: log,
......@@ -132,6 +134,17 @@ func (a *Agent) step(ctx context.Context, claim types.Claim, game types.Game) er
return nil
}
oracleData, err := a.solver.GetOracleData(ctx, claim)
if err != nil {
a.log.Debug("Failed to get oracle data", "err", err)
return nil
}
a.log.Info("Updating oracle data", "oracleKey", oracleData.OracleKey, "oracleData", oracleData.OracleData)
if err := a.updater.UpdateOracle(ctx, *oracleData); err != nil {
return fmt.Errorf("failed to load oracle data: %w", err)
}
a.log.Info("Attempting step", "claim_depth", claim.Depth(), "maxDepth", a.maxDepth)
step, err := a.solver.AttemptStep(ctx, claim, agreeWithClaimLevel)
if err != nil {
......
......@@ -56,31 +56,44 @@ func NewExecutor(logger log.Logger, cfg *config.Config, inputs localGameInputs)
}
func (e *Executor) GenerateProof(ctx context.Context, dir string, i uint64) error {
start, err := e.selectSnapshot(e.logger, filepath.Join(e.dataDir, snapsDir), e.absolutePreState, i)
snapshotDir := filepath.Join(dir, snapsDir)
start, err := e.selectSnapshot(e.logger, snapshotDir, e.absolutePreState, i)
if err != nil {
return fmt.Errorf("find starting snapshot: %w", err)
}
proofDir := filepath.Join(dir, proofsDir)
dataDir := filepath.Join(e.dataDir, preimagesDir)
args := []string{
"run",
"--input", start,
"--output", filepath.Join(dir, "out.json"),
"--meta", "",
"--proof-at", "=" + strconv.FormatUint(i, 10),
"--stop-at", "=" + strconv.FormatUint(i+1, 10),
"--proof-fmt", filepath.Join(dir, proofsDir, "%d.json"),
"--proof-fmt", filepath.Join(proofDir, "%d.json"),
"--snapshot-at", "%" + strconv.FormatUint(uint64(e.snapshotFreq), 10),
"--snapshot-fmt", filepath.Join(e.dataDir, snapsDir, "%d.json"),
"--snapshot-fmt", filepath.Join(snapshotDir, "%d.json"),
"--",
e.server,
"--l1", e.l1,
"--l2", e.l2,
"--datadir", filepath.Join(e.dataDir, preimagesDir),
"--datadir", dataDir,
"--l1.head", e.inputs.l1Head.Hex(),
"--l2.head", e.inputs.l2Head.Hex(),
"--l2.outputroot", e.inputs.l2OutputRoot.Hex(),
"--l2.claim", e.inputs.l2Claim.Hex(),
"--l2.blocknumber", e.inputs.l2BlockNumber.Text(10),
"--l2.chainid", e.inputs.l2ChainId.Text(10),
}
if err := os.MkdirAll(snapshotDir, 0755); err != nil {
return fmt.Errorf("could not create snapshot directory %v: %w", snapshotDir, err)
}
if err := os.MkdirAll(dataDir, 0755); err != nil {
return fmt.Errorf("could not create preimage cache directory %v: %w", dataDir, err)
}
if err := os.MkdirAll(proofDir, 0755); err != nil {
return fmt.Errorf("could not create proofs directory %v: %w", proofDir, err)
}
e.logger.Info("Generating trace", "proof", i, "cmd", e.cannon, "args", args)
return e.cmdExecutor(ctx, e.logger.New("proof", i), e.cannon, args...)
}
......
......@@ -20,7 +20,7 @@ const execTestCannonPrestate = "/foo/pre.json"
func TestGenerateProof(t *testing.T) {
input := "starting.json"
cfg := config.NewConfig("http://localhost:8888", common.Address{0xaa}, common.Address{0xbb}, config.TraceTypeCannon, true, 5)
cfg := config.NewConfig("http://localhost:8888", common.Address{0xaa}, config.TraceTypeCannon, true, 5)
cfg.CannonDatadir = t.TempDir()
cfg.CannonAbsolutePreState = "pre.json"
cfg.CannonBin = "./bin/cannon"
......@@ -30,7 +30,6 @@ func TestGenerateProof(t *testing.T) {
inputs := localGameInputs{
l1Head: common.Hash{0x11},
l2ChainId: big.NewInt(2342),
l2Head: common.Hash{0x22},
l2OutputRoot: common.Hash{0x33},
l2Claim: common.Hash{0x44},
......@@ -54,9 +53,15 @@ func TestGenerateProof(t *testing.T) {
}
err := executor.GenerateProof(context.Background(), cfg.CannonDatadir, 150_000_000)
require.NoError(t, err)
require.DirExists(t, filepath.Join(cfg.CannonDatadir, preimagesDir))
require.DirExists(t, filepath.Join(cfg.CannonDatadir, proofsDir))
require.DirExists(t, filepath.Join(cfg.CannonDatadir, snapsDir))
require.Equal(t, cfg.CannonBin, binary)
require.Equal(t, "run", subcommand)
require.Equal(t, input, args["--input"])
require.Contains(t, args, "--meta")
require.Equal(t, "", args["--meta"])
require.Equal(t, filepath.Join(cfg.CannonDatadir, "out.json"), args["--output"])
require.Equal(t, "=150000000", args["--proof-at"])
require.Equal(t, "=150000001", args["--stop-at"])
require.Equal(t, "%500", args["--snapshot-at"])
......@@ -73,7 +78,6 @@ func TestGenerateProof(t *testing.T) {
require.Equal(t, inputs.l2OutputRoot.Hex(), args["--l2.outputroot"])
require.Equal(t, inputs.l2Claim.Hex(), args["--l2.claim"])
require.Equal(t, "3333", args["--l2.blocknumber"])
require.Equal(t, "2342", args["--l2.chainid"])
}
func TestRunCmdLogsOutput(t *testing.T) {
......
......@@ -13,7 +13,6 @@ import (
type localGameInputs struct {
l1Head common.Hash
l2ChainId *big.Int
l2Head common.Hash
l2OutputRoot common.Hash
l2Claim common.Hash
......@@ -39,10 +38,6 @@ func fetchLocalInputs(ctx context.Context, gameAddr common.Address, caller GameI
if err != nil {
return localGameInputs{}, fmt.Errorf("fetch L1 head for game %v: %w", gameAddr, err)
}
l2ChainId, err := l2Client.ChainID(ctx)
if err != nil {
return localGameInputs{}, fmt.Errorf("fetch L2 chain ID: %w", err)
}
proposals, err := caller.Proposals(opts)
if err != nil {
......@@ -58,7 +53,6 @@ func fetchLocalInputs(ctx context.Context, gameAddr common.Address, caller GameI
return localGameInputs{
l1Head: l1Head,
l2ChainId: l2ChainId,
l2Head: l2Head,
l2OutputRoot: agreedOutput.OutputRoot,
l2Claim: claimedOutput.OutputRoot,
......
......@@ -40,7 +40,6 @@ func TestFetchLocalInputs(t *testing.T) {
require.NoError(t, err)
require.Equal(t, l1Client.l1Head, inputs.l1Head)
require.Equal(t, l2Client.chainID, inputs.l2ChainId)
require.Equal(t, l2Client.header.Hash(), inputs.l2Head)
require.EqualValues(t, l1Client.starting.OutputRoot, inputs.l2OutputRoot)
require.EqualValues(t, l1Client.disputed.OutputRoot, inputs.l2Claim)
......
......@@ -8,6 +8,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/txmgr"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
......@@ -28,8 +29,36 @@ type cannonUpdater struct {
preimageOracleAddr common.Address
}
// NewOracleUpdater returns a new updater.
// NewOracleUpdater returns a new updater. The pre-image oracle address is loaded from the fault dispute game.
func NewOracleUpdater(
ctx context.Context,
logger log.Logger,
txMgr txmgr.TxManager,
fdgAddr common.Address,
client bind.ContractCaller,
) (*cannonUpdater, error) {
gameCaller, err := bindings.NewFaultDisputeGameCaller(fdgAddr, client)
if err != nil {
return nil, fmt.Errorf("create caller for game %v: %w", fdgAddr, err)
}
opts := &bind.CallOpts{Context: ctx}
vm, err := gameCaller.VM(opts)
if err != nil {
return nil, fmt.Errorf("failed to load VM address from game %v: %w", fdgAddr, err)
}
mipsCaller, err := bindings.NewMIPSCaller(vm, client)
if err != nil {
return nil, fmt.Errorf("failed to create MIPS caller for address %v: %w", vm, err)
}
oracleAddr, err := mipsCaller.Oracle(opts)
if err != nil {
return nil, fmt.Errorf("failed to load pre-image oracle address from game %v: %w", fdgAddr, err)
}
return NewOracleUpdaterWithOracle(logger, txMgr, fdgAddr, oracleAddr)
}
// NewOracleUpdaterWithOracle returns a new updater using a specified pre-image oracle address.
func NewOracleUpdaterWithOracle(
logger log.Logger,
txMgr txmgr.TxManager,
fdgAddr common.Address,
......
......@@ -64,7 +64,7 @@ func newTestCannonUpdater(t *testing.T, sendFails bool) (*cannonUpdater, *mockTx
from: mockFdgAddress,
sendFails: sendFails,
}
updater, err := NewOracleUpdater(logger, txMgr, mockFdgAddress, mockPreimageOracleAddress)
updater, err := NewOracleUpdaterWithOracle(logger, txMgr, mockFdgAddress, mockPreimageOracleAddress)
require.NoError(t, err)
return updater, txMgr
}
......
......@@ -49,7 +49,7 @@ func NewService(ctx context.Context, logger log.Logger, cfg *config.Config) (*se
if err != nil {
return nil, fmt.Errorf("create cannon trace provider: %w", err)
}
updater, err = cannon.NewOracleUpdater(logger, txMgr, cfg.GameAddress, cfg.PreimageOracleAddress)
updater, err = cannon.NewOracleUpdater(ctx, logger, txMgr, cfg.GameAddress, client)
if err != nil {
return nil, fmt.Errorf("failed to create the cannon updater: %w", err)
}
......@@ -64,8 +64,7 @@ func NewService(ctx context.Context, logger log.Logger, cfg *config.Config) (*se
}
// newTypedService creates a new Service from a provided trace provider.
func newTypedService(ctx context.Context, logger log.Logger, cfg *config.Config, client *ethclient.Client, provider types.TraceProvider, uploader types.OracleUpdater, txMgr txmgr.TxManager) (*service, error) {
func newTypedService(ctx context.Context, logger log.Logger, cfg *config.Config, client *ethclient.Client, provider types.TraceProvider, updater types.OracleUpdater, txMgr txmgr.TxManager) (*service, error) {
contract, err := bindings.NewFaultDisputeGameCaller(cfg.GameAddress, client)
if err != nil {
return nil, fmt.Errorf("failed to bind the fault dispute game contract: %w", err)
......@@ -83,7 +82,7 @@ func newTypedService(ctx context.Context, logger log.Logger, cfg *config.Config,
return nil, fmt.Errorf("failed to bind the fault contract: %w", err)
}
agent := NewAgent(loader, cfg.GameDepth, provider, responder, cfg.AgreeWithProposedOutput, gameLogger)
agent := NewAgent(loader, cfg.GameDepth, provider, responder, updater, cfg.AgreeWithProposedOutput, gameLogger)
return &service{
agent: agent,
......
......@@ -28,6 +28,12 @@ func NewSolver(gameDepth int, traceProvider types.TraceProvider) *Solver {
}
}
// GetOracleData returns the oracle data for the provided claim.
// It passes through to the [TraceProvider] by finding the trace index for the claim.
func (s *Solver) GetOracleData(ctx context.Context, claim types.Claim) (*types.PreimageOracleData, error) {
return s.trace.GetOracleData(ctx, claim.TraceIndex(s.gameDepth))
}
// NextMove returns the next move to make given the current state of the game.
func (s *Solver) NextMove(ctx context.Context, claim types.Claim, agreeWithClaimLevel bool) (*types.Claim, error) {
if agreeWithClaimLevel {
......
......@@ -10,7 +10,6 @@ import (
oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common"
"github.com/urfave/cli/v2"
)
......@@ -59,11 +58,6 @@ var (
Usage: "Correct Alphabet Trace (alphabet trace type only)",
EnvVars: prefixEnvVars("ALPHABET"),
}
PreimageOracleAddressFlag = &cli.StringFlag{
Name: "preimage-oracle-address",
Usage: "Address of the Preimage Oracle contract (only required for cannon).",
EnvVars: prefixEnvVars("PREIMAGE_ORACLE_ADDRESS"),
}
CannonBinFlag = &cli.StringFlag{
Name: "cannon-bin",
Usage: "Path to cannon executable to use when generating trace data (cannon trace type only)",
......@@ -109,7 +103,6 @@ var requiredFlags = []cli.Flag{
// optionalFlags is a list of unchecked cli flags
var optionalFlags = []cli.Flag{
AlphabetFlag,
PreimageOracleAddressFlag,
CannonBinFlag,
CannonServerFlag,
CannonPreStateFlag,
......@@ -137,9 +130,6 @@ func CheckRequired(ctx *cli.Context) error {
gameType := config.TraceType(strings.ToLower(ctx.String(TraceTypeFlag.Name)))
switch gameType {
case config.TraceTypeCannon:
if !ctx.IsSet(PreimageOracleAddressFlag.Name) {
return fmt.Errorf("flag %s is required", PreimageOracleAddressFlag.Name)
}
if !ctx.IsSet(CannonBinFlag.Name) {
return fmt.Errorf("flag %s is required", CannonBinFlag.Name)
}
......@@ -179,21 +169,11 @@ func NewConfigFromCLI(ctx *cli.Context) (*config.Config, error) {
traceTypeFlag := config.TraceType(strings.ToLower(ctx.String(TraceTypeFlag.Name)))
preimageOracleAddress := common.Address{}
preimageOracleValue := ctx.String(PreimageOracleAddressFlag.Name)
if traceTypeFlag == config.TraceTypeCannon || preimageOracleValue != "" {
preimageOracleAddress, err = opservice.ParseAddress(preimageOracleValue)
if err != nil {
return nil, err
}
}
return &config.Config{
// Required Flags
L1EthRpc: ctx.String(L1EthRpcFlag.Name),
TraceType: traceTypeFlag,
GameAddress: dgfAddress,
PreimageOracleAddress: preimageOracleAddress,
AlphabetTrace: ctx.String(AlphabetFlag.Name),
CannonBin: ctx.String(CannonBinFlag.Name),
CannonServer: ctx.String(CannonServerFlag.Name),
......
package disputegame
import (
"context"
"math/big"
"time"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-chain-ops/deployer"
"github.com/ethereum-optimism/optimism/op-e2e/config"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/ethereum-optimism/optimism/op-service/client/utils"
"github.com/ethereum-optimism/optimism/op-service/clock"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/stretchr/testify/require"
)
// deployDisputeGameContracts deploys the DisputeGameFactory, AlphabetVM and FaultDisputeGame contracts
// It configures the alphabet fault game as game type 0 (faultGameType)
// If/when the dispute game factory becomes a predeployed contract this can be removed and just use the
// predeployed version
func deployDisputeGameContracts(require *require.Assertions, ctx context.Context, clock *clock.AdvancingClock, client *ethclient.Client, opts *bind.TransactOpts, gameDuration uint64) (*bindings.DisputeGameFactory, *big.Int) {
ctx, cancel := context.WithTimeout(ctx, 5*time.Minute)
defer cancel()
// Deploy the proxy
_, tx, proxy, err := bindings.DeployProxy(opts, client, deployer.TestAddress)
require.NoError(err)
proxyAddr, err := bind.WaitDeployed(ctx, client, tx)
require.NoError(err)
// Deploy the dispute game factory implementation
_, tx, _, err = bindings.DeployDisputeGameFactory(opts, client)
require.NoError(err)
factoryAddr, err := bind.WaitDeployed(ctx, client, tx)
require.NoError(err)
// Point the proxy at the implementation and create bindings going via the proxy
disputeGameFactoryAbi, err := bindings.DisputeGameFactoryMetaData.GetAbi()
require.NoError(err)
data, err := disputeGameFactoryAbi.Pack("initialize", deployer.TestAddress)
require.NoError(err)
tx, err = proxy.UpgradeToAndCall(opts, factoryAddr, data)
require.NoError(err)
_, err = utils.WaitReceiptOK(ctx, client, tx.Hash())
require.NoError(err)
factory, err := bindings.NewDisputeGameFactory(proxyAddr, client)
require.NoError(err)
// Now setup the fault dispute game type
// Start by deploying the AlphabetVM
_, tx, _, err = bindings.DeployAlphabetVM(opts, client, alphabetVMAbsolutePrestateClaim)
require.NoError(err)
alphaVMAddr, err := bind.WaitDeployed(ctx, client, tx)
require.NoError(err)
l2OutputOracle, err := bindings.NewL2OutputOracle(config.L1Deployments.L2OutputOracleProxy, client)
require.NoError(err)
// Deploy the block hash oracle
_, tx, _, err = bindings.DeployBlockOracle(opts, client)
require.NoError(err)
blockHashOracleAddr, err := bind.WaitDeployed(ctx, client, tx)
require.NoError(err)
blockHashOracle, err := bindings.NewBlockOracle(blockHashOracleAddr, client)
require.NoError(err)
// Deploy the fault dispute game implementation
_, tx, _, err = bindings.DeployFaultDisputeGame(
opts,
client,
uint8(0),
alphabetVMAbsolutePrestateClaim,
big.NewInt(alphabetGameDepth),
gameDuration,
alphaVMAddr,
config.L1Deployments.L2OutputOracleProxy,
blockHashOracleAddr,
)
require.NoError(err)
faultDisputeGameAddr, err := bind.WaitDeployed(ctx, client, tx)
require.NoError(err)
// Create a proposer transactor
secrets, err := e2eutils.DefaultMnemonicConfig.Secrets()
require.NoError(err)
chainId, err := client.ChainID(ctx)
require.NoError(err)
proposerOpts, err := bind.NewKeyedTransactorWithChainID(secrets.Proposer, chainId)
require.NoError(err)
// Propose 2 outputs
for i := uint8(0); i < 2; i++ {
nextBlockNumber, err := l2OutputOracle.NextBlockNumber(&bind.CallOpts{Pending: true, Context: ctx})
require.NoError(err)
block, err := client.BlockByNumber(ctx, big.NewInt(int64(i)))
require.NoError(err)
tx, err = l2OutputOracle.ProposeL2Output(proposerOpts, [32]byte{i + 1}, nextBlockNumber, block.Hash(), block.Number())
require.NoError(err)
_, err = utils.WaitReceiptOK(ctx, client, tx.Hash())
require.NoError(err)
}
// Set the fault game type implementation
tx, err = factory.SetImplementation(opts, faultGameType, faultDisputeGameAddr)
require.NoError(err)
_, err = utils.WaitReceiptOK(ctx, client, tx.Hash())
require.NoError(err, "wait for final transaction to be included and OK")
// Warp 15 seconds ahead for a diff in the timestamp.
clock.AdvanceTime(15 * time.Second)
// Store the current block in the oracle
tx, err = blockHashOracle.Checkpoint(opts)
require.NoError(err)
r, err := utils.WaitReceiptOK(ctx, client, tx.Hash())
require.NoError(err, "failed to store block in blockoracle")
return factory, new(big.Int).Sub(r.BlockNumber, big.NewInt(1))
}
......@@ -10,15 +10,14 @@ 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-challenger/fault/types"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger"
"github.com/ethereum-optimism/optimism/op-service/client/utils"
"github.com/ethereum-optimism/optimism/op-service/clock"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/stretchr/testify/require"
)
......@@ -35,48 +34,90 @@ const (
StatusDefenderWins
)
var alphabetVMAbsolutePrestate = common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000060")
var alphabetVMAbsolutePrestateClaim = crypto.Keccak256Hash(alphabetVMAbsolutePrestate)
func (s Status) String() string {
switch s {
case StatusInProgress:
return "In Progress"
case StatusChallengerWins:
return "Challenger Wins"
case StatusDefenderWins:
return "Defender Wins"
default:
return fmt.Sprintf("Unknown status: %v", int(s))
}
}
var CorrectAlphabet = "abcdefghijklmnop"
type FactoryHelper struct {
t *testing.T
require *require.Assertions
client *ethclient.Client
opts *bind.TransactOpts
factory *bindings.DisputeGameFactory
l1Head *big.Int
t *testing.T
require *require.Assertions
client *ethclient.Client
opts *bind.TransactOpts
factory *bindings.DisputeGameFactory
blockOracle *bindings.BlockOracle
l2oo *bindings.L2OutputOracleCaller
}
func NewFactoryHelper(t *testing.T, ctx context.Context, clock *clock.AdvancingClock, client *ethclient.Client, gameDuration uint64) *FactoryHelper {
func NewFactoryHelper(t *testing.T, ctx context.Context, deployments *genesis.L1Deployments, client *ethclient.Client) *FactoryHelper {
require := require.New(t)
chainID, err := client.ChainID(ctx)
require.NoError(err)
opts, err := bind.NewKeyedTransactorWithChainID(deployer.TestKey, chainID)
require.NoError(err)
factory, l1Head := deployDisputeGameContracts(require, ctx, clock, client, opts, gameDuration)
require.NotNil(deployments, "No deployments")
factory, err := bindings.NewDisputeGameFactory(deployments.DisputeGameFactoryProxy, client)
require.NoError(err)
blockOracle, err := bindings.NewBlockOracle(deployments.BlockOracle, client)
require.NoError(err)
l2oo, err := bindings.NewL2OutputOracleCaller(deployments.L2OutputOracleProxy, client)
require.NoError(err, "Error creating l2oo caller")
//factory, l1Head := deployDisputeGameContracts(require, ctx, clock, client, opts, gameDuration)
return &FactoryHelper{
t: t,
require: require,
client: client,
opts: opts,
factory: factory,
l1Head: l1Head,
t: t,
require: require,
client: client,
opts: opts,
factory: factory,
blockOracle: blockOracle,
l2oo: l2oo,
}
}
func (h *FactoryHelper) StartAlphabetGame(ctx context.Context, claimedAlphabet string) *FaultGameHelper {
ctx, cancel := context.WithTimeout(ctx, 1*time.Minute)
// Wait for two output proposals to be published
ctx, cancel := context.WithTimeout(ctx, 2*time.Minute)
defer cancel()
trace := alphabet.NewTraceProvider(claimedAlphabet, 4)
err := utils.WaitFor(ctx, time.Second, func() (bool, error) {
index, err := h.l2oo.LatestOutputIndex(&bind.CallOpts{Context: ctx})
if err != nil {
h.t.Logf("Could not get latest output index: %v", err.Error())
return false, nil
}
h.t.Logf("Latest output index: %v", index)
return index.Cmp(big.NewInt(1)) >= 0, nil
})
h.require.NoError(err, "Did not get two output roots")
ctx, cancel = context.WithTimeout(ctx, 1*time.Minute)
defer cancel()
// 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())
h.require.NoError(err, "failed to store block in blockoracle")
l1Head := new(big.Int).Sub(r.BlockNumber, big.NewInt(1))
trace := alphabet.NewTraceProvider(claimedAlphabet, alphabetGameDepth)
rootClaim, err := trace.Get(ctx, lastAlphabetTraceIndex)
h.require.NoError(err, "get root claim")
extraData := make([]byte, 64)
binary.BigEndian.PutUint64(extraData[24:], uint64(8))
binary.BigEndian.PutUint64(extraData[56:], h.l1Head.Uint64())
tx, err := h.factory.Create(h.opts, faultGameType, rootClaim, extraData)
binary.BigEndian.PutUint64(extraData[56:], l1Head.Uint64())
tx, err = h.factory.Create(h.opts, faultGameType, rootClaim, extraData)
h.require.NoError(err, "create fault dispute game")
rcpt, err := utils.WaitReceiptOK(ctx, h.client, tx.Hash())
h.require.NoError(err, "wait for create fault dispute game receipt to be OK")
......@@ -128,6 +169,12 @@ func (g *FaultGameHelper) StartChallenger(ctx context.Context, l1Endpoint string
return c
}
func (g *FaultGameHelper) GameDuration(ctx context.Context) time.Duration {
duration, err := g.game.GAMEDURATION(&bind.CallOpts{Context: ctx})
g.require.NoError(err, "failed to get game duration")
return time.Duration(duration) * time.Second
}
func (g *FaultGameHelper) WaitForClaimCount(ctx context.Context, count int64) {
ctx, cancel := context.WithTimeout(ctx, 1*time.Minute)
defer cancel()
......@@ -190,6 +237,7 @@ func (g *FaultGameHelper) Resolve(ctx context.Context) {
}
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, 1*time.Minute)
defer cancel()
err := utils.WaitFor(ctx, 1*time.Second, func() (bool, error) {
......@@ -199,7 +247,7 @@ func (g *FaultGameHelper) WaitForGameStatus(ctx context.Context, expected Status
if err != nil {
return false, fmt.Errorf("game status unavailable: %w", err)
}
g.t.Logf("Game %v has state %v, waiting for state %v", g.addr, Status(status), expected)
return expected == Status(status), nil
})
g.require.NoError(err, "wait for game status")
......
......@@ -3,7 +3,6 @@ package op_e2e
import (
"context"
"testing"
"time"
"github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
......@@ -17,13 +16,13 @@ func TestResolveDisputeGame(t *testing.T) {
InitParallel(t)
ctx := context.Background()
sys, l1Client := startL1OnlySystem(t)
sys, l1Client := startFaultDisputeSystem(t)
t.Cleanup(sys.Close)
gameDuration := 24 * time.Hour
disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys.TimeTravelClock, l1Client, uint64(gameDuration.Seconds()))
disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys.cfg.L1Deployments, l1Client)
game := disputeGameFactory.StartAlphabetGame(ctx, "zyxwvut")
require.NotNil(t, game)
gameDuration := game.GameDuration(ctx)
game.WaitForGameStatus(ctx, disputegame.StatusInProgress)
......@@ -115,13 +114,13 @@ func TestChallengerCompleteDisputeGame(t *testing.T) {
InitParallel(t)
ctx := context.Background()
sys, l1Client := startL1OnlySystem(t)
sys, l1Client := startFaultDisputeSystem(t)
t.Cleanup(sys.Close)
gameDuration := 24 * time.Hour
disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys.TimeTravelClock, l1Client, uint64(gameDuration.Seconds()))
disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys.cfg.L1Deployments, l1Client)
game := disputeGameFactory.StartAlphabetGame(ctx, test.rootClaimAlphabet)
require.NotNil(t, game)
gameDuration := game.GameDuration(ctx)
game.StartChallenger(ctx, sys.NodeEndpoint("l1"), "Defender", func(c *config.Config) {
c.TxMgrConfig.PrivateKey = e2eutils.EncodePrivKeyToString(sys.cfg.Secrets.Mallory)
......@@ -144,13 +143,13 @@ func TestChallengerCompleteDisputeGame(t *testing.T) {
}
}
func startL1OnlySystem(t *testing.T) (*System, *ethclient.Client) {
func startFaultDisputeSystem(t *testing.T) (*System, *ethclient.Client) {
cfg := DefaultSystemConfig(t)
cfg.DeployConfig.L1BlockTime = 1
delete(cfg.Nodes, "verifier")
delete(cfg.Nodes, "sequencer")
cfg.SupportL1TimeTravel = true
cfg.DeployConfig.L2OutputOracleSubmissionInterval = 2
cfg.NonFinalizedProposals = true // Submit output proposals asap
sys, err := cfg.Start()
require.Nil(t, err, "Error starting up system")
require.NoError(t, err, "Error starting up system")
return sys, sys.Clients["l1"]
}
......@@ -87,38 +87,39 @@ FaucetTest:test_nonAdmin_drip_fails() (gas: 262520)
FaucetTest:test_receive_succeeds() (gas: 17401)
FaucetTest:test_withdraw_nonAdmin_reverts() (gas: 13145)
FaucetTest:test_withdraw_succeeds() (gas: 78359)
FaultDisputeGame_ResolvesCorrectly_CorrectRoot1:test_resolvesCorrectly_succeeds() (gas: 498844)
FaultDisputeGame_ResolvesCorrectly_CorrectRoot2:test_resolvesCorrectly_succeeds() (gas: 505718)
FaultDisputeGame_ResolvesCorrectly_CorrectRoot3:test_resolvesCorrectly_succeeds() (gas: 502387)
FaultDisputeGame_ResolvesCorrectly_CorrectRoot4:test_resolvesCorrectly_succeeds() (gas: 505594)
FaultDisputeGame_ResolvesCorrectly_CorrectRoot5:test_resolvesCorrectly_succeeds() (gas: 504883)
FaultDisputeGame_ResolvesCorrectly_IncorrectRoot1:test_resolvesCorrectly_succeeds() (gas: 497609)
FaultDisputeGame_ResolvesCorrectly_IncorrectRoot2:test_resolvesCorrectly_succeeds() (gas: 504483)
FaultDisputeGame_ResolvesCorrectly_IncorrectRoot3:test_resolvesCorrectly_succeeds() (gas: 501152)
FaultDisputeGame_ResolvesCorrectly_IncorrectRoot4:test_resolvesCorrectly_succeeds() (gas: 502359)
FaultDisputeGame_ResolvesCorrectly_IncorrectRoot5:test_resolvesCorrectly_succeeds() (gas: 501648)
FaultDisputeGame_Test:test_extraData_succeeds() (gas: 32354)
FaultDisputeGame_Test:test_gameData_succeeds() (gas: 32806)
FaultDisputeGame_Test:test_gameStart_succeeds() (gas: 10388)
FaultDisputeGame_Test:test_gameType_succeeds() (gas: 8272)
FaultDisputeGame_Test:test_initialize_correctData_succeeds() (gas: 57716)
FaultDisputeGame_Test:test_initialize_firstOutput_reverts() (gas: 210532)
FaultDisputeGame_Test:test_initialize_l1HeadTooOld_reverts() (gas: 228402)
FaultDisputeGame_Test:test_move_clockCorrectness_succeeds() (gas: 415921)
FaultDisputeGame_Test:test_move_clockTimeExceeded_reverts() (gas: 26428)
FaultDisputeGame_Test:test_move_defendRoot_reverts() (gas: 13388)
FaultDisputeGame_Test:test_move_duplicateClaim_reverts() (gas: 103377)
FaultDisputeGame_Test:test_move_gameDepthExceeded_reverts() (gas: 408294)
FaultDisputeGame_ResolvesCorrectly_CorrectRoot1:test_resolvesCorrectly_succeeds() (gas: 498906)
FaultDisputeGame_ResolvesCorrectly_CorrectRoot2:test_resolvesCorrectly_succeeds() (gas: 505746)
FaultDisputeGame_ResolvesCorrectly_CorrectRoot3:test_resolvesCorrectly_succeeds() (gas: 502447)
FaultDisputeGame_ResolvesCorrectly_CorrectRoot4:test_resolvesCorrectly_succeeds() (gas: 505644)
FaultDisputeGame_ResolvesCorrectly_CorrectRoot5:test_resolvesCorrectly_succeeds() (gas: 504933)
FaultDisputeGame_ResolvesCorrectly_IncorrectRoot1:test_resolvesCorrectly_succeeds() (gas: 497671)
FaultDisputeGame_ResolvesCorrectly_IncorrectRoot2:test_resolvesCorrectly_succeeds() (gas: 504511)
FaultDisputeGame_ResolvesCorrectly_IncorrectRoot3:test_resolvesCorrectly_succeeds() (gas: 501212)
FaultDisputeGame_ResolvesCorrectly_IncorrectRoot4:test_resolvesCorrectly_succeeds() (gas: 502409)
FaultDisputeGame_ResolvesCorrectly_IncorrectRoot5:test_resolvesCorrectly_succeeds() (gas: 501698)
FaultDisputeGame_Test:test_addLocalData_static_succeeds() (gas: 640503)
FaultDisputeGame_Test:test_createdAt_succeeds() (gas: 10342)
FaultDisputeGame_Test:test_extraData_succeeds() (gas: 32377)
FaultDisputeGame_Test:test_gameData_succeeds() (gas: 32829)
FaultDisputeGame_Test:test_gameType_succeeds() (gas: 8250)
FaultDisputeGame_Test:test_initialize_correctData_succeeds() (gas: 57694)
FaultDisputeGame_Test:test_initialize_firstOutput_reverts() (gas: 210576)
FaultDisputeGame_Test:test_initialize_l1HeadTooOld_reverts() (gas: 228425)
FaultDisputeGame_Test:test_move_clockCorrectness_succeeds() (gas: 415993)
FaultDisputeGame_Test:test_move_clockTimeExceeded_reverts() (gas: 23219)
FaultDisputeGame_Test:test_move_defendRoot_reverts() (gas: 13366)
FaultDisputeGame_Test:test_move_duplicateClaim_reverts() (gas: 102920)
FaultDisputeGame_Test:test_move_gameDepthExceeded_reverts() (gas: 407913)
FaultDisputeGame_Test:test_move_gameNotInProgress_reverts() (gas: 11024)
FaultDisputeGame_Test:test_move_nonExistentParent_reverts() (gas: 24689)
FaultDisputeGame_Test:test_move_simpleAttack_succeeds() (gas: 107300)
FaultDisputeGame_Test:test_resolve_challengeContested_succeeds() (gas: 224869)
FaultDisputeGame_Test:test_move_nonExistentParent_reverts() (gas: 24732)
FaultDisputeGame_Test:test_move_simpleAttack_succeeds() (gas: 107341)
FaultDisputeGame_Test:test_resolve_challengeContested_succeeds() (gas: 224906)
FaultDisputeGame_Test:test_resolve_notInProgress_reverts() (gas: 9664)
FaultDisputeGame_Test:test_resolve_rootContested_succeeds() (gas: 109838)
FaultDisputeGame_Test:test_resolve_rootUncontestedClockNotExpired_succeeds() (gas: 21443)
FaultDisputeGame_Test:test_resolve_rootUncontested_succeeds() (gas: 27278)
FaultDisputeGame_Test:test_resolve_teamDeathmatch_succeeds() (gas: 395607)
FaultDisputeGame_Test:test_rootClaim_succeeds() (gas: 8253)
FaultDisputeGame_Test:test_resolve_rootContested_succeeds() (gas: 109856)
FaultDisputeGame_Test:test_resolve_rootUncontestedClockNotExpired_succeeds() (gas: 21421)
FaultDisputeGame_Test:test_resolve_rootUncontested_succeeds() (gas: 27256)
FaultDisputeGame_Test:test_resolve_teamDeathmatch_succeeds() (gas: 395635)
FaultDisputeGame_Test:test_rootClaim_succeeds() (gas: 8276)
FeeVault_Test:test_constructor_succeeds() (gas: 18185)
GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 352139)
GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_1() (gas: 2950346)
......
......@@ -43,7 +43,7 @@
"eip1559Elasticity": 6,
"l1GenesisBlockTimestamp": "0x64c811bf",
"l2GenesisRegolithTimeOffset": "0x0",
"faultGameAbsolutePrestate": 96,
"faultGameAbsolutePrestate": "0x41c7ae758795765c6664a5d39bf63841c71ff191e9189522bad8ebff5d4eca98",
"faultGameMaxDepth": 4,
"faultGameMaxDuration": 120
"faultGameMaxDuration": 300
}
......@@ -14,7 +14,7 @@
"src/L2/L2StandardBridge.sol": "0x73a4fea3dca8ac7d7ba32e38aadeb69bd344042666a40a75e8c28849f01999e5",
"src/L2/L2ToL1MessagePasser.sol": "0xed800b600cb3f67e18a1ab10750e3934a8b3e42178f422bcacfde770a6e8e8bd",
"src/L2/SequencerFeeVault.sol": "0xd57c143b1f042400430b991b806bf971628e6980406c751e82d19ae80eeb4e8d",
"src/dispute/FaultDisputeGame.sol": "0x01b3770e4d056550e71b4b9e9ada5401de98668d0a1e0ff336ed8f73a0127e99",
"src/dispute/FaultDisputeGame.sol": "0x2a7f367443752f841d734f339a596c4ad93e16f7df0230f1cdd5e6f0b4b58368",
"src/legacy/DeployerWhitelist.sol": "0x5e80f7b13ef73f06c63bd9b118a49da1ff06a5c0fcf8067b5a3365d731c23765",
"src/legacy/L1BlockNumber.sol": "0x84cc587148de5920dfcd19da44d28e769f0e4d08ca2bcc93f18aa78c6cc2ebe6",
"src/legacy/LegacyMessagePasser.sol": "0x2692b50b227e5f75a53439c0cf303498edfd4fc087555b3fc9bc4bceb518229b",
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import "../libraries/DisputeTypes.sol";
import "../libraries/DisputeErrors.sol";
import { ClonesWithImmutableArgs } from "@cwia/ClonesWithImmutableArgs.sol";
import {
OwnableUpgradeable
} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { Semver } from "../universal/Semver.sol";
import { Semver } from "src/universal/Semver.sol";
import { IDisputeGame } from "./interfaces/IDisputeGame.sol";
import { IDisputeGameFactory } from "./interfaces/IDisputeGameFactory.sol";
import "src/libraries/DisputeTypes.sol";
import "src/libraries/DisputeErrors.sol";
/// @title DisputeGameFactory
/// @notice A factory contract for creating `IDisputeGame` contracts. All created dispute games
/// are stored in both a mapping and an append only array. The timestamp of the creation
......@@ -37,7 +37,7 @@ contract DisputeGameFactory is OwnableUpgradeable, IDisputeGameFactory, Semver {
GameId[] internal _disputeGameList;
/// @notice constructs a new DisputeGameFactory contract.
constructor() OwnableUpgradeable() Semver(0, 0, 2) {
constructor() OwnableUpgradeable() Semver(0, 0, 3) {
initialize(address(0));
}
......@@ -88,9 +88,7 @@ contract DisputeGameFactory is OwnableUpgradeable, IDisputeGameFactory, Semver {
IDisputeGame impl = gameImpls[gameType];
// If there is no implementation to clone for the given `GameType`, revert.
if (address(impl) == address(0)) {
revert NoImplementation(gameType);
}
if (address(impl) == address(0)) revert NoImplementation(gameType);
// Clone the implementation contract and initialize it with the given parameters.
proxy = IDisputeGame(address(impl).clone(abi.encodePacked(rootClaim, extraData)));
......@@ -100,9 +98,7 @@ contract DisputeGameFactory is OwnableUpgradeable, IDisputeGameFactory, Semver {
Hash uuid = getGameUUID(gameType, rootClaim, extraData);
// If a dispute game with the same UUID already exists, revert.
if (GameId.unwrap(_disputeGames[uuid]) != bytes32(0)) {
revert GameAlreadyExists(uuid);
}
if (GameId.unwrap(_disputeGames[uuid]) != bytes32(0)) revert GameAlreadyExists(uuid);
GameId slot = _packSlot(address(proxy), block.timestamp);
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import { IPreimageOracle } from "../../cannon/interfaces/IPreimageOracle.sol";
import { IPreimageOracle } from "src/cannon/interfaces/IPreimageOracle.sol";
/// @title IBigStepper
/// @notice An interface for a contract with a state transition function that
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import "../../libraries/DisputeTypes.sol";
import { IBondManager } from "./IBondManager.sol";
import { IInitializable } from "./IInitializable.sol";
import "src/libraries/DisputeTypes.sol";
/// @title IDisputeGame
/// @notice The generic interface for a DisputeGame contract.
interface IDisputeGame is IInitializable {
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import "../../libraries/DisputeTypes.sol";
import { IDisputeGame } from "./IDisputeGame.sol";
import "src/libraries/DisputeTypes.sol";
/// @title IDisputeGameFactory
/// @notice The interface for a DisputeGameFactory contract.
interface IDisputeGameFactory {
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import "../../libraries/DisputeTypes.sol";
import { IDisputeGame } from "./IDisputeGame.sol";
import "src/libraries/DisputeTypes.sol";
/// @title IFaultDisputeGame
/// @notice The interface for a fault proof backed dispute game.
interface IFaultDisputeGame is IDisputeGame {
/// @notice The `ClaimData` struct represents the data associated with a Claim.
/// @dev TODO: Add bond ID information.
/// @dev TODO(clabby): Add bond ID information.
struct ClaimData {
uint32 parentIndex;
bool countered;
......@@ -26,7 +26,14 @@ interface IFaultDisputeGame is IDisputeGame {
Hash outputRoot;
}
/// @notice A container for two consecutive `OutputProposal`s.
/// @notice A container for two consecutive `OutputProposal`s, used to store the starting
/// and disputed output proposals for a given dispute game. The starting output
/// proposal will be used to determine where the off chain agents should begin
/// running their fault proof program, and the disputed output proposal will be
/// fed into the program and treated as disputed state. The program's exit code
/// expresses its opinion on the validity of the state transition from the starting,
/// trusted output proposal to the disputed output proposal, and ultimately resolves
/// the dispute.
struct OutputProposals {
OutputProposal starting;
OutputProposal disputed;
......@@ -39,27 +46,26 @@ interface IFaultDisputeGame is IDisputeGame {
event Move(uint256 indexed parentIndex, Claim indexed claim, address indexed claimant);
/// @notice Attack a disagreed upon `Claim`.
/// @param _parentIndex Index of the `Claim` to attack in `claimData`.
/// @param _parentIndex Index of the `Claim` to attack in the `claimData` array.
/// @param _claim The `Claim` at the relative attack position.
function attack(uint256 _parentIndex, Claim _claim) external payable;
/// @notice Defend an agreed upon `Claim`.
/// @param _parentIndex Index of the claim to defend in `claimData`.
/// @param _parentIndex Index of the claim to defend in the `claimData` array.
/// @param _claim The `Claim` at the relative defense position.
function defend(uint256 _parentIndex, Claim _claim) external payable;
/// @notice Perform the final step via an on-chain fault proof processor
/// @notice Perform an instruction step via an on-chain fault proof processor.
/// @dev This function should point to a fault proof processor in order to execute
/// a step in the fault proof program on-chain. The interface of the fault proof
/// processor contract should be generic enough such that we can use different
/// fault proof VMs (MIPS, RiscV5, etc.)
/// processor contract should adhere to the `IBigStepper` interface.
/// @param _claimIndex The index of the challenged claim within `claimData`.
/// @param _isAttack Whether or not the step is an attack or a defense.
/// @param _stateData The stateData of the step is the preimage of the claim at the given
/// prestate, which is at `_stateIndex` if the move is an attack and `_claimIndex` if
/// the move is a defense. If the step is an attack on the first instruction, it is
/// the absolute prestate of the fault proof VM.
/// @param _proof Proof to access memory leaf nodes in the VM.
/// @param _proof Proof to access memory nodes in the VM's merkle state tree.
function step(
uint256 _claimIndex,
bool _isAttack,
......@@ -72,11 +78,13 @@ interface IFaultDisputeGame is IDisputeGame {
/// @param _partOffset The offset of the data to post.
function addLocalData(uint256 _ident, uint256 _partOffset) external;
/// @notice Returns the L1 block hash at the time of the game's creation.
/// @notice An L1 block hash that contains the disputed output root, fetched from the
/// `BlockOracle` and verified by referencing the timestamp associated with the
/// first L2 Output Proposal in the `L2OutputOracle` that contains the disputed
/// L2 block number.
function l1Head() external view returns (Hash l1Head_);
/// @notice The l2BlockNumber that the `rootClaim` commits to. The trace being bisected within
/// the game is from `l2BlockNumber - 1` -> `l2BlockNumber`.
/// @notice The l2BlockNumber of the disputed output root in the `L2OutputOracle`.
function l2BlockNumber() external view returns (uint256 l2BlockNumber_);
/// @notice The l1BlockNumber that Cannon was ran from to generate the root claim.
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import "../../libraries/DisputeTypes.sol";
import "src/libraries/DisputeTypes.sol";
/// @title LibClock
/// @notice This library contains helper functions for working with the `Clock` type.
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import "../../libraries/DisputeTypes.sol";
import "src/libraries/DisputeTypes.sol";
/// @title Hashing
/// @notice This library contains all of the hashing utilities used in the Cannon contracts.
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import "../../libraries/DisputeTypes.sol";
import "src/libraries/DisputeTypes.sol";
/// @title LibPosition
/// @notice This library contains helper functions for working with the `Position` type.
......
......@@ -58,6 +58,9 @@ error ValidStep();
/// not contain the disputed output root.
error L1HeadTooOld();
/// @notice Thrown when an invalid local identifier is passed to the `addLocalData` function.
error InvalidLocalIdent();
////////////////////////////////////////////////////////////////
// `AttestationDisputeGame` Errors //
////////////////////////////////////////////////////////////////
......
......@@ -8,6 +8,8 @@ import { DisputeGameFactory } from "src/dispute/DisputeGameFactory.sol";
import { FaultDisputeGame } from "src/dispute/FaultDisputeGame.sol";
import { L2OutputOracle } from "src/L1/L2OutputOracle.sol";
import { BlockOracle } from "src/dispute/BlockOracle.sol";
import { PreimageOracle } from "src/cannon/PreimageOracle.sol";
import { PreimageKeyLib } from "src/cannon/PreimageKeyLib.sol";
import "src/libraries/DisputeTypes.sol";
import "src/libraries/DisputeErrors.sol";
......@@ -38,12 +40,7 @@ contract FaultDisputeGame_Init is DisputeGameFactory_Init {
// Propose 2 mock outputs
vm.startPrank(oracle.PROPOSER());
for (uint256 i; i < 2; i++) {
oracle.proposeL2Output(
bytes32(i + 1),
oracle.nextBlockNumber(),
blockhash(i),
i
);
oracle.proposeL2Output(bytes32(i + 1), oracle.nextBlockNumber(), blockhash(i), i);
// Advance 1 block
vm.roll(block.number + 1);
......@@ -102,9 +99,9 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init {
assertEq(gameProxy.extraData(), extraData);
}
/// @dev Tests that the game's status is set correctly.
function test_gameStart_succeeds() public {
assertEq(Timestamp.unwrap(gameProxy.gameStart()), block.timestamp);
/// @dev Tests that the game's starting timestamp is set correctly.
function test_createdAt_succeeds() public {
assertEq(Timestamp.unwrap(gameProxy.createdAt()), block.timestamp);
}
/// @dev Tests that the game's type is set correctly.
......@@ -129,7 +126,11 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init {
/// contain the disputed L2 output root.
function test_initialize_l1HeadTooOld_reverts() public {
// Store a mock block hash for the genesis block. The timestamp will default to 0.
vm.store(address(gameImpl.BLOCK_ORACLE()), keccak256(abi.encode(0, 0)), bytes32(uint256(1)));
vm.store(
address(gameImpl.BLOCK_ORACLE()),
keccak256(abi.encode(0, 0)),
bytes32(uint256(1))
);
bytes memory _extraData = abi.encode(oracle.SUBMISSION_INTERVAL() * 2, 0);
vm.expectRevert(L1HeadTooOld.selector);
......@@ -149,7 +150,10 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init {
/// @dev Tests that the game is initialized with the correct data.
function test_initialize_correctData_succeeds() public {
// Starting
(FaultDisputeGame.OutputProposal memory startingProp, FaultDisputeGame.OutputProposal memory disputedProp) = gameProxy.proposals();
(
FaultDisputeGame.OutputProposal memory startingProp,
FaultDisputeGame.OutputProposal memory disputedProp
) = gameProxy.proposals();
Types.OutputProposal memory starting = oracle.getL2Output(startingProp.index);
assertEq(startingProp.index, 0);
assertEq(startingProp.l2BlockNumber, starting.l2BlockNumber);
......@@ -421,6 +425,59 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init {
assertEq(uint8(status), uint8(GameStatus.CHALLENGER_WINS));
assertEq(uint8(gameProxy.status()), uint8(GameStatus.CHALLENGER_WINS));
}
/// @dev Tests that adding local data with an out of bounds identifier reverts.
function testFuzz_addLocalData_oob_reverts(uint256 _ident) public {
// [1, 5] are valid local data identifiers.
if (_ident <= 5) _ident = 0;
vm.expectRevert(InvalidLocalIdent.selector);
gameProxy.addLocalData(_ident, 0);
}
/// @dev Tests that local data is loaded into the preimage oracle correctly.
function test_addLocalData_static_succeeds() public {
IPreimageOracle oracle = IPreimageOracle(address(gameProxy.VM().oracle()));
(
FaultDisputeGame.OutputProposal memory starting,
FaultDisputeGame.OutputProposal memory disputed
) = gameProxy.proposals();
bytes32[5] memory data = [
Hash.unwrap(gameProxy.l1Head()),
Hash.unwrap(starting.outputRoot),
Hash.unwrap(disputed.outputRoot),
bytes32(uint256(starting.l2BlockNumber) << 0xC0),
bytes32(block.chainid << 0xC0)
];
for (uint256 i = 1; i <= 5; i++) {
uint256 expectedLen = i > 3 ? 8 : 32;
gameProxy.addLocalData(i, 0);
bytes32 key = _getKey(i);
(bytes32 dat, uint256 datLen) = oracle.readPreimage(key, 0);
assertEq(dat >> 0xC0, bytes32(expectedLen));
// Account for the length prefix if i > 3 (the data stored
// at identifiers i <= 3 are 32 bytes long, so the expected
// length is already correct. If i > 3, the data is only 8
// bytes long, so the length prefix + the data is 16 bytes
// total.)
assertEq(datLen, expectedLen + (i > 3 ? 8 : 0));
gameProxy.addLocalData(i, 8);
key = _getKey(i);
(dat, datLen) = oracle.readPreimage(key, 8);
assertEq(dat, data[i - 1]);
assertEq(datLen, expectedLen);
}
}
/// @dev Helper to get the localized key for an identifier in the context of the game proxy.
function _getKey(uint256 _ident) internal view returns (bytes32) {
bytes32 h = keccak256(abi.encode(_ident | (1 << 248), address(gameProxy)));
return bytes32((uint256(h) & ~uint256(0xFF << 248)) | (1 << 248));
}
}
/// @notice A generic game player actor with a configurable trace.
......@@ -963,7 +1020,7 @@ contract AlphabetVM is IBigStepper {
constructor(Claim _absolutePrestate) {
ABSOLUTE_PRESTATE = _absolutePrestate;
oracle = IPreimageOracle(deployNoop());
oracle = new PreimageOracle();
}
/// @inheritdoc IBigStepper
......@@ -987,16 +1044,3 @@ contract AlphabetVM is IBigStepper {
postState_ = keccak256(abi.encode(traceIndex, claim + 1));
}
}
////////////////////////////////////////////////////////////////
// HELPERS //
////////////////////////////////////////////////////////////////
/// @notice Deploys a noop contract.
function deployNoop() returns (address noop_) {
assembly {
mstore(0x00, 0x60016000F3)
let size := 5
noop_ := create(0, sub(0x20, size), size)
}
}
# @eth-optimism/fee-estimation
## 0.15.2
### Patch Changes
- [#6609](https://github.com/ethereum-optimism/optimism/pull/6609) [`0e83c4452`](https://github.com/ethereum-optimism/optimism/commit/0e83c44522e1a13e4d5c1395fd4dc9dbae8be08d) Thanks [@roninjin10](https://github.com/roninjin10)! - Fixed bug with 'estimateFees' not taking into account the l2 gas price
## 0.15.1
### Patch Changes
......
......@@ -37,11 +37,9 @@ yarn add @eth-optimism/fee-estimation
### Basic Usage
```ts
import {
estimateFees,
} from '@eth-optimism/fee-estimation'
import {optimistABI} from '@eth-optimism/contracts-ts'
import {viemClient} from './viem-client'
import { estimateFees } from '@eth-optimism/fee-estimation'
import { optimistABI } from '@eth-optimism/contracts-ts'
import { viemClient } from './viem-client'
const optimistOwnerAddress =
'0x77194aa25a06f932c10c0f25090f3046af2c85a6' as const
......
{
"name": "@eth-optimism/fee-estimation",
"version": "0.15.1",
"version": "0.15.2",
"description": "Lightweight library for doing OP-Chain gas estimation",
"license": "MIT",
"repository": {
......
......@@ -24,6 +24,19 @@ import {
} from '@eth-optimism/contracts-ts'
import { parseEther, parseGwei } from 'viem'
vi.mock('viem', async () => {
const _viem = (await vi.importActual('viem')) as any
return {
..._viem,
// no way to get historical gas price
createPublicClient: (...args: [any]) => {
const client = _viem.createPublicClient(...args)
client.getGasPrice = async () => parseGwei('0.00000042')
return client
},
}
})
// using this optimist https://optimistic.etherscan.io/tx/0xaa291efba7ea40b0742e5ff84a1e7831a2eb6c2fc35001fa03ba80fd3b609dc9
const blockNumber = BigInt(107028270)
const optimistOwnerAddress =
......@@ -95,44 +108,44 @@ beforeEach(() => {
test('estimateFees should return correct fees', async () => {
// burn
const res = await estimateFees({ ...paramsWithRpcUrl, ...functionDataBurn })
expect(res).toMatchInlineSnapshot('20573185261089n')
expect(formatEther(res)).toMatchInlineSnapshot('"0.000020573185261089"')
expect(res).toMatchInlineSnapshot('20573203833264n')
expect(formatEther(res)).toMatchInlineSnapshot('"0.000020573203833264"')
expect(
await estimateFees({ ...paramsWithRpcUrl, ...functionDataBurn })
).toMatchInlineSnapshot('20573185261089n')
).toMatchInlineSnapshot('20573203833264n')
expect(
await estimateFees({ ...paramsWithViemClient, ...functionDataBurn })
).toMatchInlineSnapshot('20573185261089n')
).toMatchInlineSnapshot('20573203833264n')
expect(
await estimateFees({
...paramsWithRpcUrl,
...functionDataBurnWithPriorityFees,
})
).toMatchInlineSnapshot('21536974118090n')
).toMatchInlineSnapshot('21536992690265n')
// what is the l2 and l1 part of the fees for reference?
const l1Fee = await getL1Fee({ ...paramsWithRpcUrl, ...functionDataBurn })
const l2Fee = res - l1Fee
expect(l1Fee).toMatchInlineSnapshot('20573185216764n')
expect(formatEther(l1Fee)).toMatchInlineSnapshot('"0.000020573185216764"')
expect(l2Fee).toMatchInlineSnapshot('44325n')
expect(formatEther(l2Fee)).toMatchInlineSnapshot('"0.000000000000044325"')
expect(l2Fee).toMatchInlineSnapshot('18616500n')
expect(formatEther(l2Fee)).toMatchInlineSnapshot('"0.0000000000186165"')
// withdraw
const res2 = await estimateFees({
...paramsWithRpcUrlWithdraw,
...functionDataWithdraw,
})
expect(res2).toMatchInlineSnapshot('62857039016380n')
expect(res2).toMatchInlineSnapshot('62857090247510n')
expect(
await estimateFees({ ...paramsWithRpcUrlWithdraw, ...functionDataWithdraw })
).toMatchInlineSnapshot('62857039016380n')
).toMatchInlineSnapshot('62857090247510n')
expect(
await estimateFees({ ...paramsWithRpcUrlWithdraw, ...functionDataWithdraw })
).toMatchInlineSnapshot('62857039016380n')
).toMatchInlineSnapshot('62857090247510n')
expect(
await estimateFees({ ...paramsWithRpcUrlWithdraw, ...functionDataWithdraw })
).toMatchInlineSnapshot('62857039016380n')
expect(formatEther(res2)).toMatchInlineSnapshot('"0.00006285703901638"')
).toMatchInlineSnapshot('62857090247510n')
expect(formatEther(res2)).toMatchInlineSnapshot('"0.00006285709024751"')
// what is the l2 and l1 part of the fees for reference?
const l1Fee2 = await getL1Fee({
...paramsWithRpcUrlWithdraw,
......@@ -141,8 +154,8 @@ test('estimateFees should return correct fees', async () => {
const l2Fee2 = res2 - l1Fee
expect(l1Fee2).toMatchInlineSnapshot('62857038894110n')
expect(formatEther(l1Fee2)).toMatchInlineSnapshot('"0.00006285703889411"')
expect(l2Fee2).toMatchInlineSnapshot('42283853799616n')
expect(formatEther(l2Fee2)).toMatchInlineSnapshot('"0.000042283853799616"')
expect(l2Fee2).toMatchInlineSnapshot('42283905030746n')
expect(formatEther(l2Fee2)).toMatchInlineSnapshot('"0.000042283905030746"')
})
test('baseFee should return the correct result', async () => {
......
......@@ -332,7 +332,7 @@ export const estimateFees: EstimateFees = async (options) => {
args: options.args,
functionName: options.functionName,
} as EncodeFunctionDataParameters)
const [l1Fee, l2Fee] = await Promise.all([
const [l1Fee, l2Gas, l2GasPrice] = await Promise.all([
getL1Fee({
...options,
// account must be undefined or else viem will return undefined
......@@ -347,6 +347,7 @@ export const estimateFees: EstimateFees = async (options) => {
data: encodedFunctionData,
value: options.value,
} as EstimateGasParameters<typeof chains.optimism>),
client.getGasPrice(),
])
return l1Fee + l2Fee
return l1Fee + l2Gas * l2GasPrice
}
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