Commit 9a5d3e4d authored by Adrian Sutton's avatar Adrian Sutton

Merge remote-tracking branch 'origin/develop' into aj/game-validator

parents 902bf190 ab402c42
##################################################
# Getting Started #
##################################################
# Admin account
export GS_ADMIN_ADDRESS=
export GS_ADMIN_PRIVATE_KEY=
# Batcher account
export GS_BATCHER_ADDRESS=
export GS_BATCHER_PRIVATE_KEY=
# Proposer account
export GS_PROPOSER_ADDRESS=
export GS_PROPOSER_PRIVATE_KEY=
# Sequencer account
export GS_SEQUENCER_ADDRESS=
export GS_SEQUENCER_PRIVATE_KEY=
##################################################
# op-node Configuration #
##################################################
# The kind of RPC provider, used to inform optimal transactions receipts
# fetching. Valid options: alchemy, quicknode, infura, parity, nethermind,
# debug_geth, erigon, basic, any.
export L1_RPC_KIND=
##################################################
# Contract Deployment #
##################################################
# RPC URL for the L1 network to interact with
export L1_RPC_URL=
# Salt used via CREATE2 to determine implementation addresses
# NOTE: If you want to deploy contracts from scratch you MUST reload this
# variable to ensure the salt is regenerated and the contracts are
# deployed to new addresses (otherwise deployment will fail)
export IMPL_SALT=$(openssl rand -hex 32)
# Name for the deployed network
export DEPLOYMENT_CONTEXT=getting-started
# Optional Tenderly details for simulation link during deployment
export TENDERLY_PROJECT=
export TENDERLY_USERNAME=
# Optional Etherscan API key for contract verification
export ETHERSCAN_API_KEY=
# Private key to use for contract deployments, you don't need to worry about
# this for the Getting Started guide.
export PRIVATE_KEY=
...@@ -30,6 +30,7 @@ packages/contracts-bedrock/deployments/anvil ...@@ -30,6 +30,7 @@ packages/contracts-bedrock/deployments/anvil
.env .env
.env* .env*
!.env.example !.env.example
!.envrc.example
*.log *.log
.devnet .devnet
......
...@@ -8,7 +8,7 @@ require github.com/ethereum-optimism/optimism v0.0.0 ...@@ -8,7 +8,7 @@ require github.com/ethereum-optimism/optimism v0.0.0
require ( require (
golang.org/x/crypto v0.14.0 // indirect golang.org/x/crypto v0.14.0 // indirect
golang.org/x/sys v0.13.0 // indirect golang.org/x/sys v0.14.0 // indirect
) )
replace github.com/ethereum-optimism/optimism v0.0.0 => ../../.. replace github.com/ethereum-optimism/optimism v0.0.0 => ../../..
...@@ -6,7 +6,7 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU ...@@ -6,7 +6,7 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
...@@ -42,7 +42,7 @@ require ( ...@@ -42,7 +42,7 @@ require (
golang.org/x/crypto v0.14.0 golang.org/x/crypto v0.14.0
golang.org/x/exp v0.0.0-20231006140011-7918f672742d golang.org/x/exp v0.0.0-20231006140011-7918f672742d
golang.org/x/sync v0.5.0 golang.org/x/sync v0.5.0
golang.org/x/term v0.13.0 golang.org/x/term v0.14.0
golang.org/x/time v0.4.0 golang.org/x/time v0.4.0
gorm.io/driver/postgres v1.5.4 gorm.io/driver/postgres v1.5.4
gorm.io/gorm v1.25.5 gorm.io/gorm v1.25.5
...@@ -199,7 +199,7 @@ require ( ...@@ -199,7 +199,7 @@ require (
go.uber.org/zap v1.26.0 // indirect go.uber.org/zap v1.26.0 // indirect
golang.org/x/mod v0.13.0 // indirect golang.org/x/mod v0.13.0 // indirect
golang.org/x/net v0.17.0 // indirect golang.org/x/net v0.17.0 // indirect
golang.org/x/sys v0.13.0 // indirect golang.org/x/sys v0.14.0 // indirect
golang.org/x/text v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect
golang.org/x/tools v0.14.0 // indirect golang.org/x/tools v0.14.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect google.golang.org/protobuf v1.31.0 // indirect
......
...@@ -879,13 +879,13 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= ...@@ -879,13 +879,13 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 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-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.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
......
...@@ -51,3 +51,7 @@ export interface WithdrawalResponse { ...@@ -51,3 +51,7 @@ export interface WithdrawalResponse {
hasNextPage: boolean; hasNextPage: boolean;
items: WithdrawalItem[]; items: WithdrawalItem[];
} }
export interface BridgeSupplyView {
l1DepositSum: number /* float64 */;
l2WithdrawalSum: number /* float64 */;
}
...@@ -35,6 +35,8 @@ const ( ...@@ -35,6 +35,8 @@ const (
HealthPath = "/healthz" HealthPath = "/healthz"
DepositsPath = "/api/v0/deposits/" DepositsPath = "/api/v0/deposits/"
WithdrawalsPath = "/api/v0/withdrawals/" WithdrawalsPath = "/api/v0/withdrawals/"
SupplyPath = "/api/v0/supply"
) )
// Api ... Indexer API struct // Api ... Indexer API struct
...@@ -140,15 +142,14 @@ func (a *APIService) initRouter(apiConfig config.ServerConfig) { ...@@ -140,15 +142,14 @@ func (a *APIService) initRouter(apiConfig config.ServerConfig) {
promRecorder := metrics.NewPromHTTPRecorder(a.metricsRegistry, MetricsNamespace) promRecorder := metrics.NewPromHTTPRecorder(a.metricsRegistry, MetricsNamespace)
// (2) Inject routing middleware
apiRouter.Use(chiMetricsMiddleware(promRecorder)) apiRouter.Use(chiMetricsMiddleware(promRecorder))
apiRouter.Use(middleware.Timeout(time.Duration(apiConfig.WriteTimeout) * time.Second)) apiRouter.Use(middleware.Timeout(time.Duration(apiConfig.WriteTimeout) * time.Second))
apiRouter.Use(middleware.Recoverer) apiRouter.Use(middleware.Recoverer)
apiRouter.Use(middleware.Heartbeat(HealthPath)) apiRouter.Use(middleware.Heartbeat(HealthPath))
// (3) Set GET routes
apiRouter.Get(fmt.Sprintf(DepositsPath+addressParam, ethereumAddressRegex), h.L1DepositsHandler) apiRouter.Get(fmt.Sprintf(DepositsPath+addressParam, ethereumAddressRegex), h.L1DepositsHandler)
apiRouter.Get(fmt.Sprintf(WithdrawalsPath+addressParam, ethereumAddressRegex), h.L2WithdrawalsHandler) apiRouter.Get(fmt.Sprintf(WithdrawalsPath+addressParam, ethereumAddressRegex), h.L2WithdrawalsHandler)
apiRouter.Get(SupplyPath, h.SupplyView)
a.router = apiRouter a.router = apiRouter
} }
......
...@@ -95,6 +95,14 @@ func (mbv *MockBridgeTransfersView) L2BridgeWithdrawalsByAddress(address common. ...@@ -95,6 +95,14 @@ func (mbv *MockBridgeTransfersView) L2BridgeWithdrawalsByAddress(address common.
}, },
}, nil }, nil
} }
func (mbv *MockBridgeTransfersView) L1BridgeDepositSum() (float64, error) {
return 69, nil
}
func (mbv *MockBridgeTransfersView) L2BridgeWithdrawalSum() (float64, error) {
return 420, nil
}
func TestHealthz(t *testing.T) { func TestHealthz(t *testing.T) {
logger := testlog.Logger(t, log.LvlInfo) logger := testlog.Logger(t, log.LvlInfo)
cfg := &Config{ cfg := &Config{
......
...@@ -49,6 +49,11 @@ type WithdrawalResponse struct { ...@@ -49,6 +49,11 @@ type WithdrawalResponse struct {
Items []WithdrawalItem `json:"items"` Items []WithdrawalItem `json:"items"`
} }
type BridgeSupplyView struct {
L1DepositSum float64 `json:"l1DepositSum"`
L2WithdrawalSum float64 `json:"l2WithdrawalSum"`
}
// FIXME make a pure function that returns a struct instead of newWithdrawalResponse // FIXME make a pure function that returns a struct instead of newWithdrawalResponse
// newWithdrawalResponse ... Converts a database.L2BridgeWithdrawalsResponse to an api.WithdrawalResponse // newWithdrawalResponse ... Converts a database.L2BridgeWithdrawalsResponse to an api.WithdrawalResponse
func CreateWithdrawalResponse(withdrawals *database.L2BridgeWithdrawalsResponse) WithdrawalResponse { func CreateWithdrawalResponse(withdrawals *database.L2BridgeWithdrawalsResponse) WithdrawalResponse {
......
package routes
import (
"net/http"
"github.com/ethereum-optimism/optimism/indexer/api/models"
)
// SupplyView ... Handles /api/v0/supply GET requests
func (h Routes) SupplyView(w http.ResponseWriter, r *http.Request) {
depositSum, err := h.view.L1BridgeDepositSum()
if err != nil {
http.Error(w, "internal server error reading deposits", http.StatusInternalServerError)
h.logger.Error("unable to read deposits from DB", "err", err.Error())
return
}
withdrawalSum, err := h.view.L2BridgeWithdrawalSum()
if err != nil {
http.Error(w, "internal server error reading withdrawals", http.StatusInternalServerError)
h.logger.Error("unable to read withdrawals from DB", "err", err.Error())
return
}
view := models.BridgeSupplyView{
L1DepositSum: depositSum,
L2WithdrawalSum: withdrawalSum,
}
err = jsonResponse(w, view, http.StatusOK)
if err != nil {
h.logger.Error("error writing response", "err", err)
}
}
...@@ -23,6 +23,7 @@ const ( ...@@ -23,6 +23,7 @@ const (
healthz = "get_health" healthz = "get_health"
deposits = "get_deposits" deposits = "get_deposits"
withdrawals = "get_withdrawals" withdrawals = "get_withdrawals"
sum = "get_sum"
) )
// Option ... Provides configuration through callback injection // Option ... Provides configuration through callback injection
...@@ -164,6 +165,25 @@ func (c *Client) GetAllDepositsByAddress(l1Address common.Address) ([]models.Dep ...@@ -164,6 +165,25 @@ func (c *Client) GetAllDepositsByAddress(l1Address common.Address) ([]models.Dep
} }
// GetSupplyAssessment ... Returns an assessment of the current supply
// on both L1 and L2. This includes the individual sums of
// (L1/L2) deposits and withdrawals
func (c *Client) GetSupplyAssessment() (*models.BridgeSupplyView, error) {
url := c.cfg.BaseURL + api.SupplyPath
resp, err := c.doRecordRequest(sum, url)
if err != nil {
return nil, err
}
var bsv *models.BridgeSupplyView
if err := json.Unmarshal(resp, &bsv); err != nil {
return nil, err
}
return bsv, nil
}
// GetAllWithdrawalsByAddress ... Gets all withdrawals provided a L2 address // GetAllWithdrawalsByAddress ... Gets all withdrawals provided a L2 address
func (c *Client) GetAllWithdrawalsByAddress(l2Address common.Address) ([]models.WithdrawalItem, error) { func (c *Client) GetAllWithdrawalsByAddress(l2Address common.Address) ([]models.WithdrawalItem, error) {
var withdrawals []models.WithdrawalItem var withdrawals []models.WithdrawalItem
......
...@@ -61,10 +61,12 @@ type L2BridgeWithdrawalWithTransactionHashes struct { ...@@ -61,10 +61,12 @@ type L2BridgeWithdrawalWithTransactionHashes struct {
type BridgeTransfersView interface { type BridgeTransfersView interface {
L1BridgeDeposit(common.Hash) (*L1BridgeDeposit, error) L1BridgeDeposit(common.Hash) (*L1BridgeDeposit, error)
L1BridgeDepositSum() (float64, error)
L1BridgeDepositWithFilter(BridgeTransfer) (*L1BridgeDeposit, error) L1BridgeDepositWithFilter(BridgeTransfer) (*L1BridgeDeposit, error)
L1BridgeDepositsByAddress(common.Address, string, int) (*L1BridgeDepositsResponse, error) L1BridgeDepositsByAddress(common.Address, string, int) (*L1BridgeDepositsResponse, error)
L2BridgeWithdrawal(common.Hash) (*L2BridgeWithdrawal, error) L2BridgeWithdrawal(common.Hash) (*L2BridgeWithdrawal, error)
L2BridgeWithdrawalSum() (float64, error)
L2BridgeWithdrawalWithFilter(BridgeTransfer) (*L2BridgeWithdrawal, error) L2BridgeWithdrawalWithFilter(BridgeTransfer) (*L2BridgeWithdrawal, error)
L2BridgeWithdrawalsByAddress(common.Address, string, int) (*L2BridgeWithdrawalsResponse, error) L2BridgeWithdrawalsByAddress(common.Address, string, int) (*L2BridgeWithdrawalsResponse, error)
} }
...@@ -136,6 +138,17 @@ type L1BridgeDepositsResponse struct { ...@@ -136,6 +138,17 @@ type L1BridgeDepositsResponse struct {
HasNextPage bool HasNextPage bool
} }
// L1BridgeDepositSum ... returns the sum of all l1 bridge deposit mints in gwei
func (db *bridgeTransfersDB) L1BridgeDepositSum() (float64, error) {
var sum float64
result := db.gorm.Model(&L1TransactionDeposit{}).Select("sum(amount)").Scan(&sum)
if result.Error != nil {
return 0, result.Error
}
return sum, nil
}
// L1BridgeDepositsByAddress retrieves a list of deposits initiated by the specified address, // L1BridgeDepositsByAddress retrieves a list of deposits initiated by the specified address,
// coupled with the L1/L2 transaction hashes that complete the bridge transaction. // coupled with the L1/L2 transaction hashes that complete the bridge transaction.
func (db *bridgeTransfersDB) L1BridgeDepositsByAddress(address common.Address, cursor string, limit int) (*L1BridgeDepositsResponse, error) { func (db *bridgeTransfersDB) L1BridgeDepositsByAddress(address common.Address, cursor string, limit int) (*L1BridgeDepositsResponse, error) {
...@@ -233,6 +246,16 @@ func (db *bridgeTransfersDB) L2BridgeWithdrawal(txWithdrawalHash common.Hash) (* ...@@ -233,6 +246,16 @@ func (db *bridgeTransfersDB) L2BridgeWithdrawal(txWithdrawalHash common.Hash) (*
return &withdrawal, nil return &withdrawal, nil
} }
func (db *bridgeTransfersDB) L2BridgeWithdrawalSum() (float64, error) {
var sum float64
result := db.gorm.Model(&L2TransactionWithdrawal{}).Select("sum(amount)").Scan(&sum)
if result.Error != nil {
return 0, result.Error
}
return sum, nil
}
// L2BridgeWithdrawalWithFilter queries for a bridge withdrawal with set fields in the `BridgeTransfer` filter // L2BridgeWithdrawalWithFilter queries for a bridge withdrawal with set fields in the `BridgeTransfer` filter
func (db *bridgeTransfersDB) L2BridgeWithdrawalWithFilter(filter BridgeTransfer) (*L2BridgeWithdrawal, error) { func (db *bridgeTransfersDB) L2BridgeWithdrawalWithFilter(filter BridgeTransfer) (*L2BridgeWithdrawal, error) {
var withdrawal L2BridgeWithdrawal var withdrawal L2BridgeWithdrawal
......
...@@ -8,6 +8,7 @@ import ( ...@@ -8,6 +8,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/ethereum-optimism/optimism/indexer/bigint"
e2etest_utils "github.com/ethereum-optimism/optimism/indexer/e2e_tests/utils" e2etest_utils "github.com/ethereum-optimism/optimism/indexer/e2e_tests/utils"
op_e2e "github.com/ethereum-optimism/optimism/op-e2e" op_e2e "github.com/ethereum-optimism/optimism/op-e2e"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/transactions" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/transactions"
...@@ -447,7 +448,7 @@ func TestE2EBridgeTransfersCursoredWithdrawals(t *testing.T) { ...@@ -447,7 +448,7 @@ func TestE2EBridgeTransfersCursoredWithdrawals(t *testing.T) {
} }
} }
func TestClientGetWithdrawals(t *testing.T) { func TestClientBridgeFunctions(t *testing.T) {
testSuite := createE2ETestSuite(t) testSuite := createE2ETestSuite(t)
// (1) Generate contract bindings for the L1 and L2 standard bridges // (1) Generate contract bindings for the L1 and L2 standard bridges
...@@ -459,12 +460,16 @@ func TestClientGetWithdrawals(t *testing.T) { ...@@ -459,12 +460,16 @@ func TestClientGetWithdrawals(t *testing.T) {
// (2) Create test actors that will deposit and withdraw using the standard bridge // (2) Create test actors that will deposit and withdraw using the standard bridge
aliceAddr := testSuite.OpCfg.Secrets.Addresses().Alice aliceAddr := testSuite.OpCfg.Secrets.Addresses().Alice
bobAddr := testSuite.OpCfg.Secrets.Addresses().Bob bobAddr := testSuite.OpCfg.Secrets.Addresses().Bob
malAddr := testSuite.OpCfg.Secrets.Addresses().Mallory
type actor struct { type actor struct {
addr common.Address addr common.Address
priv *ecdsa.PrivateKey priv *ecdsa.PrivateKey
} }
mintSum := bigint.Zero
withdrawSum := bigint.Zero
actors := []actor{ actors := []actor{
{ {
addr: aliceAddr, addr: aliceAddr,
...@@ -474,6 +479,10 @@ func TestClientGetWithdrawals(t *testing.T) { ...@@ -474,6 +479,10 @@ func TestClientGetWithdrawals(t *testing.T) {
addr: bobAddr, addr: bobAddr,
priv: testSuite.OpCfg.Secrets.Bob, priv: testSuite.OpCfg.Secrets.Bob,
}, },
{
addr: malAddr,
priv: testSuite.OpCfg.Secrets.Mallory,
},
} }
// (3) Iterate over each actor and deposit / withdraw // (3) Iterate over each actor and deposit / withdraw
...@@ -491,6 +500,8 @@ func TestClientGetWithdrawals(t *testing.T) { ...@@ -491,6 +500,8 @@ func TestClientGetWithdrawals(t *testing.T) {
_, err = wait.ForReceiptOK(context.Background(), testSuite.L1Client, depositTx.Hash()) _, err = wait.ForReceiptOK(context.Background(), testSuite.L1Client, depositTx.Hash())
require.NoError(t, err) require.NoError(t, err)
mintSum = new(big.Int).Add(mintSum, depositTx.Value())
// (3.b) Initiate withdrawal transaction via L2ToL1MessagePasser contract // (3.b) Initiate withdrawal transaction via L2ToL1MessagePasser contract
l2ToL1MessagePasserWithdrawTx, err := l2ToL1MessagePasser.Receive(l2Opts) l2ToL1MessagePasserWithdrawTx, err := l2ToL1MessagePasser.Receive(l2Opts)
require.NoError(t, err) require.NoError(t, err)
...@@ -503,6 +514,8 @@ func TestClientGetWithdrawals(t *testing.T) { ...@@ -503,6 +514,8 @@ func TestClientGetWithdrawals(t *testing.T) {
return l2Header != nil && l2Header.Number.Uint64() >= l2ToL1WithdrawReceipt.BlockNumber.Uint64(), nil return l2Header != nil && l2Header.Number.Uint64() >= l2ToL1WithdrawReceipt.BlockNumber.Uint64(), nil
})) }))
withdrawSum = new(big.Int).Add(withdrawSum, l2ToL1MessagePasserWithdrawTx.Value())
// (3.d) Ensure that withdrawal and deposit txs are retrievable via API // (3.d) Ensure that withdrawal and deposit txs are retrievable via API
deposits, err := testSuite.Client.GetAllDepositsByAddress(actor.addr) deposits, err := testSuite.Client.GetAllDepositsByAddress(actor.addr)
require.NoError(t, err) require.NoError(t, err)
...@@ -513,6 +526,17 @@ func TestClientGetWithdrawals(t *testing.T) { ...@@ -513,6 +526,17 @@ func TestClientGetWithdrawals(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Len(t, withdrawals, 1) require.Len(t, withdrawals, 1)
require.Equal(t, l2ToL1MessagePasserWithdrawTx.Hash().String(), withdrawals[0].TransactionHash) require.Equal(t, l2ToL1MessagePasserWithdrawTx.Hash().String(), withdrawals[0].TransactionHash)
} }
// (4) Ensure that supply assessment is correct
assessment, err := testSuite.Client.GetSupplyAssessment()
require.NoError(t, err)
mintFloat, _ := mintSum.Float64()
require.Equal(t, mintFloat, assessment.L1DepositSum)
withdrawFloat, _ := withdrawSum.Float64()
require.Equal(t, withdrawFloat, assessment.L2WithdrawalSum)
} }
...@@ -96,8 +96,8 @@ CREATE TABLE IF NOT EXISTS l1_transaction_deposits ( ...@@ -96,8 +96,8 @@ CREATE TABLE IF NOT EXISTS l1_transaction_deposits (
-- transaction data. NOTE: `to_address` is the recipient of funds transferred in value field of the -- transaction data. NOTE: `to_address` is the recipient of funds transferred in value field of the
-- L2 deposit transaction and not the amount minted on L1 from the source address. Hence the `amount` -- L2 deposit transaction and not the amount minted on L1 from the source address. Hence the `amount`
-- column in this table does NOT indiciate the amount transferred to the recipient but instead funds -- column in this table does NOT indicate the amount transferred to the recipient but instead funds
-- bridged from L1 into `from_address`. -- bridged from L1 by the `from_address`.
from_address VARCHAR NOT NULL, from_address VARCHAR NOT NULL,
to_address VARCHAR NOT NULL, to_address VARCHAR NOT NULL,
......
This diff is collapsed.
This diff is collapsed.
...@@ -19,6 +19,8 @@ const ( ...@@ -19,6 +19,8 @@ const (
methodStatus = "status" methodStatus = "status"
methodClaimCount = "claimDataLen" methodClaimCount = "claimDataLen"
methodClaim = "claimData" methodClaim = "claimData"
methodL1Head = "l1Head"
methodProposals = "proposals"
) )
type FaultDisputeGameContract struct { type FaultDisputeGameContract struct {
...@@ -26,6 +28,12 @@ type FaultDisputeGameContract struct { ...@@ -26,6 +28,12 @@ type FaultDisputeGameContract struct {
contract *batching.BoundContract contract *batching.BoundContract
} }
type Proposal struct {
Index *big.Int
L2BlockNumber *big.Int
OutputRoot common.Hash
}
func NewFaultDisputeGameContract(addr common.Address, caller *batching.MultiCaller) (*FaultDisputeGameContract, error) { func NewFaultDisputeGameContract(addr common.Address, caller *batching.MultiCaller) (*FaultDisputeGameContract, error) {
fdgAbi, err := bindings.FaultDisputeGameMetaData.GetAbi() fdgAbi, err := bindings.FaultDisputeGameMetaData.GetAbi()
if err != nil { if err != nil {
...@@ -62,6 +70,27 @@ func (f *FaultDisputeGameContract) GetAbsolutePrestateHash(ctx context.Context) ...@@ -62,6 +70,27 @@ func (f *FaultDisputeGameContract) GetAbsolutePrestateHash(ctx context.Context)
return result.GetHash(0), nil return result.GetHash(0), nil
} }
func (f *FaultDisputeGameContract) GetL1Head(ctx context.Context) (common.Hash, error) {
result, err := f.multiCaller.SingleCall(ctx, batching.BlockLatest, f.contract.Call(methodL1Head))
if err != nil {
return common.Hash{}, fmt.Errorf("failed to fetch L1 head: %w", err)
}
return result.GetHash(0), nil
}
// GetProposals returns the agreed and disputed proposals
func (f *FaultDisputeGameContract) GetProposals(ctx context.Context) (Proposal, Proposal, error) {
result, err := f.multiCaller.SingleCall(ctx, batching.BlockLatest, f.contract.Call(methodProposals))
if err != nil {
return Proposal{}, Proposal{}, fmt.Errorf("failed to fetch proposals: %w", err)
}
var agreed, disputed Proposal
result.GetStruct(0, &agreed)
result.GetStruct(1, &disputed)
return agreed, disputed, nil
}
func (f *FaultDisputeGameContract) GetStatus(ctx context.Context) (gameTypes.GameStatus, error) { func (f *FaultDisputeGameContract) GetStatus(ctx context.Context) (gameTypes.GameStatus, error) {
result, err := f.multiCaller.SingleCall(ctx, batching.BlockLatest, f.contract.Call(methodStatus)) result, err := f.multiCaller.SingleCall(ctx, batching.BlockLatest, f.contract.Call(methodStatus))
if err != nil { if err != nil {
......
...@@ -60,6 +60,13 @@ func TestSimpleGetters(t *testing.T) { ...@@ -60,6 +60,13 @@ func TestSimpleGetters(t *testing.T) {
return game.GetClaimCount(context.Background()) return game.GetClaimCount(context.Background())
}, },
}, },
{
method: methodL1Head,
result: common.Hash{0xdd, 0xbb},
call: func(game *FaultDisputeGameContract) (any, error) {
return game.GetL1Head(context.Background())
},
},
} }
for _, test := range tests { for _, test := range tests {
test := test test := test
...@@ -77,6 +84,33 @@ func TestSimpleGetters(t *testing.T) { ...@@ -77,6 +84,33 @@ func TestSimpleGetters(t *testing.T) {
} }
} }
func TestGetProposals(t *testing.T) {
stubRpc, game := setup(t)
agreedIndex := big.NewInt(5)
agreedBlockNum := big.NewInt(6)
agreedRoot := common.Hash{0xaa}
disputedIndex := big.NewInt(7)
disputedBlockNum := big.NewInt(8)
disputedRoot := common.Hash{0xdd}
agreed := Proposal{
Index: agreedIndex,
L2BlockNumber: agreedBlockNum,
OutputRoot: agreedRoot,
}
disputed := Proposal{
Index: disputedIndex,
L2BlockNumber: disputedBlockNum,
OutputRoot: disputedRoot,
}
stubRpc.SetResponse(methodProposals, batching.BlockLatest, []interface{}{}, []interface{}{
agreed, disputed,
})
actualAgreed, actualDisputed, err := game.GetProposals(context.Background())
require.NoError(t, err)
require.Equal(t, agreed, actualAgreed)
require.Equal(t, disputed, actualDisputed)
}
func TestGetClaim(t *testing.T) { func TestGetClaim(t *testing.T) {
stubRpc, game := setup(t) stubRpc, game := setup(t)
idx := big.NewInt(2) idx := big.NewInt(2)
......
...@@ -37,7 +37,7 @@ type GamePlayer struct { ...@@ -37,7 +37,7 @@ type GamePlayer struct {
status gameTypes.GameStatus status gameTypes.GameStatus
} }
type resourceCreator func(addr common.Address, gameDepth uint64, dir string) (types.TraceAccessor, types.OracleUpdater, gameValidator, error) type resourceCreator func(addr common.Address, contract *contracts.FaultDisputeGameContract, gameDepth uint64, dir string) (types.TraceAccessor, types.OracleUpdater, gameValidator, error)
func NewGamePlayer( func NewGamePlayer(
ctx context.Context, ctx context.Context,
...@@ -80,7 +80,7 @@ func NewGamePlayer( ...@@ -80,7 +80,7 @@ func NewGamePlayer(
return nil, fmt.Errorf("failed to fetch the game depth: %w", err) return nil, fmt.Errorf("failed to fetch the game depth: %w", err)
} }
accessor, updater, validator, err := creator(addr, gameDepth, dir) accessor, updater, validator, err := creator(addr, loader, gameDepth, dir)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create trace accessor: %w", err) return nil, fmt.Errorf("failed to create trace accessor: %w", err)
} }
......
...@@ -38,8 +38,8 @@ func RegisterGameTypes( ...@@ -38,8 +38,8 @@ func RegisterGameTypes(
client *ethclient.Client, client *ethclient.Client,
) { ) {
if cfg.TraceTypeEnabled(config.TraceTypeCannon) { if cfg.TraceTypeEnabled(config.TraceTypeCannon) {
resourceCreator := func(addr common.Address, gameDepth uint64, dir string) (faultTypes.TraceAccessor, faultTypes.OracleUpdater, gameValidator, error) { resourceCreator := func(addr common.Address, contract *contracts.FaultDisputeGameContract, gameDepth uint64, dir string) (faultTypes.TraceAccessor, faultTypes.OracleUpdater, gameValidator, error) {
provider, err := cannon.NewTraceProvider(ctx, logger, m, cfg, client, dir, addr, gameDepth) provider, err := cannon.NewTraceProvider(ctx, logger, m, cfg, contract, dir, gameDepth)
if err != nil { if err != nil {
return nil, nil, nil, fmt.Errorf("create cannon trace provider: %w", err) return nil, nil, nil, fmt.Errorf("create cannon trace provider: %w", err)
} }
...@@ -58,7 +58,7 @@ func RegisterGameTypes( ...@@ -58,7 +58,7 @@ func RegisterGameTypes(
registry.RegisterGameType(cannonGameType, playerCreator) registry.RegisterGameType(cannonGameType, playerCreator)
} }
if cfg.TraceTypeEnabled(config.TraceTypeAlphabet) { if cfg.TraceTypeEnabled(config.TraceTypeAlphabet) {
resourceCreator := func(addr common.Address, gameDepth uint64, dir string) (faultTypes.TraceAccessor, faultTypes.OracleUpdater, gameValidator, error) { resourceCreator := func(addr common.Address, contract *contracts.FaultDisputeGameContract, gameDepth uint64, dir string) (faultTypes.TraceAccessor, faultTypes.OracleUpdater, gameValidator, error) {
provider := alphabet.NewTraceProvider(cfg.AlphabetTrace, gameDepth) provider := alphabet.NewTraceProvider(cfg.AlphabetTrace, gameDepth)
updater := alphabet.NewOracleUpdater(logger) updater := alphabet.NewOracleUpdater(logger)
validator := func(ctx context.Context, contract *contracts.FaultDisputeGameContract) error { validator := func(ctx context.Context, contract *contracts.FaultDisputeGameContract) error {
......
...@@ -5,8 +5,7 @@ import ( ...@@ -5,8 +5,7 @@ import (
"fmt" "fmt"
"math/big" "math/big"
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types" ethtypes "github.com/ethereum/go-ethereum/core/types"
) )
...@@ -25,26 +24,20 @@ type L2DataSource interface { ...@@ -25,26 +24,20 @@ type L2DataSource interface {
} }
type GameInputsSource interface { type GameInputsSource interface {
L1Head(opts *bind.CallOpts) ([32]byte, error) GetL1Head(ctx context.Context) (common.Hash, error)
Proposals(opts *bind.CallOpts) (struct { GetProposals(ctx context.Context) (agreed contracts.Proposal, disputed contracts.Proposal, err error)
Starting bindings.IFaultDisputeGameOutputProposal
Disputed bindings.IFaultDisputeGameOutputProposal
}, error)
} }
func fetchLocalInputs(ctx context.Context, gameAddr common.Address, caller GameInputsSource, l2Client L2DataSource) (LocalGameInputs, error) { func fetchLocalInputs(ctx context.Context, caller GameInputsSource, l2Client L2DataSource) (LocalGameInputs, error) {
opts := &bind.CallOpts{Context: ctx} l1Head, err := caller.GetL1Head(ctx)
l1Head, err := caller.L1Head(opts)
if err != nil { if err != nil {
return LocalGameInputs{}, fmt.Errorf("fetch L1 head for game %v: %w", gameAddr, err) return LocalGameInputs{}, fmt.Errorf("fetch L1 head: %w", err)
} }
proposals, err := caller.Proposals(opts) agreedOutput, claimedOutput, err := caller.GetProposals(ctx)
if err != nil { if err != nil {
return LocalGameInputs{}, fmt.Errorf("fetch proposals: %w", err) return LocalGameInputs{}, fmt.Errorf("fetch proposals: %w", err)
} }
claimedOutput := proposals.Disputed
agreedOutput := proposals.Starting
agreedHeader, err := l2Client.HeaderByNumber(ctx, agreedOutput.L2BlockNumber) agreedHeader, err := l2Client.HeaderByNumber(ctx, agreedOutput.L2BlockNumber)
if err != nil { if err != nil {
return LocalGameInputs{}, fmt.Errorf("fetch L2 block header %v: %w", agreedOutput.L2BlockNumber, err) return LocalGameInputs{}, fmt.Errorf("fetch L2 block header %v: %w", agreedOutput.L2BlockNumber, err)
......
...@@ -5,9 +5,8 @@ import ( ...@@ -5,9 +5,8 @@ import (
"math/big" "math/big"
"testing" "testing"
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
"github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types" ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
...@@ -15,15 +14,14 @@ import ( ...@@ -15,15 +14,14 @@ import (
func TestFetchLocalInputs(t *testing.T) { func TestFetchLocalInputs(t *testing.T) {
ctx := context.Background() ctx := context.Background()
gameAddr := common.Address{0xab} contract := &mockGameInputsSource{
l1Client := &mockGameInputsSource{
l1Head: common.Hash{0xcc}, l1Head: common.Hash{0xcc},
starting: bindings.IFaultDisputeGameOutputProposal{ starting: contracts.Proposal{
Index: big.NewInt(6), Index: big.NewInt(6),
L2BlockNumber: big.NewInt(2222), L2BlockNumber: big.NewInt(2222),
OutputRoot: common.Hash{0xdd}, OutputRoot: common.Hash{0xdd},
}, },
disputed: bindings.IFaultDisputeGameOutputProposal{ disputed: contracts.Proposal{
Index: big.NewInt(7), Index: big.NewInt(7),
L2BlockNumber: big.NewInt(3333), L2BlockNumber: big.NewInt(3333),
OutputRoot: common.Hash{0xee}, OutputRoot: common.Hash{0xee},
...@@ -32,41 +30,32 @@ func TestFetchLocalInputs(t *testing.T) { ...@@ -32,41 +30,32 @@ func TestFetchLocalInputs(t *testing.T) {
l2Client := &mockL2DataSource{ l2Client := &mockL2DataSource{
chainID: big.NewInt(88422), chainID: big.NewInt(88422),
header: ethtypes.Header{ header: ethtypes.Header{
Number: l1Client.starting.L2BlockNumber, Number: contract.starting.L2BlockNumber,
}, },
} }
inputs, err := fetchLocalInputs(ctx, gameAddr, l1Client, l2Client) inputs, err := fetchLocalInputs(ctx, contract, l2Client)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, l1Client.l1Head, inputs.L1Head) require.Equal(t, contract.l1Head, inputs.L1Head)
require.Equal(t, l2Client.header.Hash(), inputs.L2Head) require.Equal(t, l2Client.header.Hash(), inputs.L2Head)
require.EqualValues(t, l1Client.starting.OutputRoot, inputs.L2OutputRoot) require.EqualValues(t, contract.starting.OutputRoot, inputs.L2OutputRoot)
require.EqualValues(t, l1Client.disputed.OutputRoot, inputs.L2Claim) require.EqualValues(t, contract.disputed.OutputRoot, inputs.L2Claim)
require.Equal(t, l1Client.disputed.L2BlockNumber, inputs.L2BlockNumber) require.Equal(t, contract.disputed.L2BlockNumber, inputs.L2BlockNumber)
} }
type mockGameInputsSource struct { type mockGameInputsSource struct {
l1Head common.Hash l1Head common.Hash
starting bindings.IFaultDisputeGameOutputProposal starting contracts.Proposal
disputed bindings.IFaultDisputeGameOutputProposal disputed contracts.Proposal
} }
func (s *mockGameInputsSource) L1Head(opts *bind.CallOpts) ([32]byte, error) { func (s *mockGameInputsSource) GetL1Head(_ context.Context) (common.Hash, error) {
return s.l1Head, nil return s.l1Head, nil
} }
func (s *mockGameInputsSource) Proposals(opts *bind.CallOpts) (struct { func (s *mockGameInputsSource) GetProposals(_ context.Context) (contracts.Proposal, contracts.Proposal, error) {
Starting bindings.IFaultDisputeGameOutputProposal return s.starting, s.disputed, nil
Disputed bindings.IFaultDisputeGameOutputProposal
}, error) {
return struct {
Starting bindings.IFaultDisputeGameOutputProposal
Disputed bindings.IFaultDisputeGameOutputProposal
}{
Starting: s.starting,
Disputed: s.disputed,
}, nil
} }
type mockL2DataSource struct { type mockL2DataSource struct {
......
...@@ -8,11 +8,10 @@ import ( ...@@ -8,11 +8,10 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/config" "github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-service/ioutil" "github.com/ethereum-optimism/optimism/op-service/ioutil"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
...@@ -56,17 +55,13 @@ type CannonTraceProvider struct { ...@@ -56,17 +55,13 @@ type CannonTraceProvider struct {
lastStep uint64 lastStep uint64
} }
func NewTraceProvider(ctx context.Context, logger log.Logger, m CannonMetricer, cfg *config.Config, l1Client bind.ContractCaller, dir string, gameAddr common.Address, gameDepth uint64) (*CannonTraceProvider, error) { func NewTraceProvider(ctx context.Context, logger log.Logger, m CannonMetricer, cfg *config.Config, gameContract *contracts.FaultDisputeGameContract, dir string, gameDepth uint64) (*CannonTraceProvider, error) {
l2Client, err := ethclient.DialContext(ctx, cfg.CannonL2) l2Client, err := ethclient.DialContext(ctx, cfg.CannonL2)
if err != nil { if err != nil {
return nil, fmt.Errorf("dial l2 client %v: %w", cfg.CannonL2, err) return nil, fmt.Errorf("dial l2 client %v: %w", cfg.CannonL2, err)
} }
defer l2Client.Close() // Not needed after fetching the inputs defer l2Client.Close() // Not needed after fetching the inputs
gameCaller, err := bindings.NewFaultDisputeGameCaller(gameAddr, l1Client) localInputs, err := fetchLocalInputs(ctx, gameContract, l2Client)
if err != nil {
return nil, fmt.Errorf("create caller for game %v: %w", gameAddr, err)
}
localInputs, err := fetchLocalInputs(ctx, gameAddr, gameCaller, l2Client)
if err != nil { if err != nil {
return nil, fmt.Errorf("fetch local game inputs: %w", err) return nil, fmt.Errorf("fetch local game inputs: %w", err)
} }
......
...@@ -4,13 +4,15 @@ import ( ...@@ -4,13 +4,15 @@ import (
"context" "context"
"path/filepath" "path/filepath"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/cannon" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/cannon"
"github.com/ethereum-optimism/optimism/op-challenger/metrics" "github.com/ethereum-optimism/optimism/op-challenger/metrics"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger"
"github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
...@@ -32,7 +34,7 @@ func (g *CannonGameHelper) StartChallenger(ctx context.Context, rollupCfg *rollu ...@@ -32,7 +34,7 @@ func (g *CannonGameHelper) StartChallenger(ctx context.Context, rollupCfg *rollu
return c return c
} }
func (g *CannonGameHelper) CreateHonestActor(ctx context.Context, rollupCfg *rollup.Config, l2Genesis *core.Genesis, l1Client bind.ContractCaller, l1Endpoint string, l2Endpoint string, options ...challenger.Option) *HonestHelper { func (g *CannonGameHelper) CreateHonestActor(ctx context.Context, rollupCfg *rollup.Config, l2Genesis *core.Genesis, l1Client *ethclient.Client, l1Endpoint string, l2Endpoint string, options ...challenger.Option) *HonestHelper {
opts := []challenger.Option{ opts := []challenger.Option{
challenger.WithCannon(g.t, rollupCfg, l2Genesis, l2Endpoint), challenger.WithCannon(g.t, rollupCfg, l2Genesis, l2Endpoint),
challenger.WithFactoryAddress(g.factoryAddr), challenger.WithFactoryAddress(g.factoryAddr),
...@@ -42,7 +44,9 @@ func (g *CannonGameHelper) CreateHonestActor(ctx context.Context, rollupCfg *rol ...@@ -42,7 +44,9 @@ func (g *CannonGameHelper) CreateHonestActor(ctx context.Context, rollupCfg *rol
cfg := challenger.NewChallengerConfig(g.t, l1Endpoint, opts...) cfg := challenger.NewChallengerConfig(g.t, l1Endpoint, opts...)
logger := testlog.Logger(g.t, log.LvlInfo).New("role", "CorrectTrace") logger := testlog.Logger(g.t, log.LvlInfo).New("role", "CorrectTrace")
maxDepth := g.MaxDepth(ctx) maxDepth := g.MaxDepth(ctx)
provider, err := cannon.NewTraceProvider(ctx, logger, metrics.NoopMetrics, cfg, l1Client, filepath.Join(cfg.Datadir, "honest"), g.addr, uint64(maxDepth)) gameContract, err := contracts.NewFaultDisputeGameContract(g.addr, batching.NewMultiCaller(l1Client.Client(), batching.DefaultBatchSize))
g.require.NoError(err, "Create game contract bindings")
provider, err := cannon.NewTraceProvider(ctx, logger, metrics.NoopMetrics, cfg, gameContract, filepath.Join(cfg.Datadir, "honest"), uint64(maxDepth))
g.require.NoError(err, "create cannon trace provider") g.require.NoError(err, "create cannon trace provider")
return &HonestHelper{ return &HonestHelper{
......
bin bin
# config files
genesis.json
jwt.txt
rollup.json
...@@ -2,6 +2,7 @@ package cli ...@@ -2,6 +2,7 @@ package cli
import ( import (
"fmt" "fmt"
"strings"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
...@@ -19,7 +20,7 @@ func LoadSignerSetup(ctx *cli.Context) (p2p.SignerSetup, error) { ...@@ -19,7 +20,7 @@ func LoadSignerSetup(ctx *cli.Context) (p2p.SignerSetup, error) {
if key != "" { if key != "" {
// Mnemonics are bad because they leak *all* keys when they leak. // Mnemonics are bad because they leak *all* keys when they leak.
// Unencrypted keys from file are bad because they are easy to leak (and we are not checking file permissions). // Unencrypted keys from file are bad because they are easy to leak (and we are not checking file permissions).
priv, err := crypto.HexToECDSA(key) priv, err := crypto.HexToECDSA(strings.TrimPrefix(key, "0x"))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to read batch submitter key: %w", err) return nil, fmt.Errorf("failed to read batch submitter key: %w", err)
} }
......
...@@ -117,3 +117,7 @@ func (c *CallResult) GetAddress(i int) common.Address { ...@@ -117,3 +117,7 @@ func (c *CallResult) GetAddress(i int) common.Address {
func (c *CallResult) GetBigInt(i int) *big.Int { func (c *CallResult) GetBigInt(i int) *big.Int {
return *abi.ConvertType(c.out[i], new(*big.Int)).(**big.Int) return *abi.ConvertType(c.out[i], new(*big.Int)).(**big.Int)
} }
func (c *CallResult) GetStruct(i int, target interface{}) {
abi.ConvertType(c.out[i], target)
}
...@@ -139,6 +139,24 @@ func TestCallResult_GetValues(t *testing.T) { ...@@ -139,6 +139,24 @@ func TestCallResult_GetValues(t *testing.T) {
}, },
expected: big.NewInt(2398423), expected: big.NewInt(2398423),
}, },
{
name: "GetStruct",
getter: func(result *CallResult, i int) interface{} {
out := struct {
a *big.Int
b common.Hash
}{}
result.GetStruct(i, &out)
return out
},
expected: struct {
a *big.Int
b common.Hash
}{
a: big.NewInt(6),
b: common.Hash{0xee},
},
},
} }
for _, test := range tests { for _, test := range tests {
......
# RPC for the network to deploy to
export ETH_RPC_URL=
# Sets the deployer's key to match the first default hardhat account
export PRIVATE_KEY=ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
# Name of the deployed network
export DEPLOYMENT_CONTEXT=getting-started
# Optional Tenderly details for a simulation link during deployment
export TENDERLY_PROJECT=
export TENDERLY_USERNAME=
export ETHERSCAN_API_KEY=
# Optional create2 salt for deterministic deployment of
# contract implementations
export IMPL_SALT=$(openssl rand -hex 32)
This diff is collapsed.
...@@ -26,3 +26,6 @@ deployments/1337 ...@@ -26,3 +26,6 @@ deployments/1337
# Devnet config which changes with each 'make devnet-up' # Devnet config which changes with each 'make devnet-up'
deploy-config/devnetL1.json deploy-config/devnetL1.json
# Getting Started guide deploy config
deploy-config/getting-started.json
# `SystemConfig` Invariants # `SystemConfig` Invariants
## The gas limit of the `SystemConfig` contract can never be lower than the hard-coded lower bound. ## The gas limit of the `SystemConfig` contract can never be lower than the hard-coded lower bound.
**Test:** [`SystemConfig.t.sol#L80`](../test/invariants/SystemConfig.t.sol#L80) **Test:** [`SystemConfig.t.sol#L70`](../test/invariants/SystemConfig.t.sol#L70)
Subproject commit e8a047e3f40f13fa37af6fe14e6e06283d9a060e Subproject commit 37a37ab73364d6644bfe11edf88a07880f99bd56
...@@ -284,7 +284,7 @@ abstract contract Deployer is Script { ...@@ -284,7 +284,7 @@ abstract contract Deployer is Script {
} }
/// @notice Returns the contract name from a deploy transaction. /// @notice Returns the contract name from a deploy transaction.
function _getContractNameFromDeployTransaction(string memory _deployTx) internal returns (string memory) { function _getContractNameFromDeployTransaction(string memory _deployTx) internal pure returns (string memory) {
return stdJson.readString(_deployTx, ".contractName"); return stdJson.readString(_deployTx, ".contractName");
} }
......
#!/usr/bin/env bash
# This script is used to generate the getting-started.json configuration file
# used in the Getting Started quickstart guide on the docs site. Avoids the
# need to have the getting-started.json committed to the repo since it's an
# invalid JSON file when not filled in, which is annoying.
reqenv() {
if [ -z "${!1}" ]; then
echo "Error: environment variable '$1' is undefined"
exit 1
fi
}
# Check required environment variables
reqenv "GS_ADMIN_ADDRESS"
reqenv "GS_BATCHER_ADDRESS"
reqenv "GS_PROPOSER_ADDRESS"
reqenv "GS_SEQUENCER_ADDRESS"
reqenv "L1_RPC_URL"
# Get the finalized block timestamp and hash
block=$(cast block finalized --rpc-url $L1_RPC_URL)
timestamp=$(echo "$block" | awk '/timestamp/ { print $2 }')
blockhash=$(echo "$block" | awk '/hash/ { print $2 }')
# Generate the config file
config=$(cat << EOL
{ {
"finalSystemOwner": "ADMIN", "finalSystemOwner": "$GS_ADMIN_ADDRESS",
"portalGuardian": "ADMIN", "portalGuardian": "$GS_ADMIN_ADDRESS",
"l1StartingBlockTag": "BLOCKHASH", "l1StartingBlockTag": "$blockhash",
"l1ChainID": 5, "l1ChainID": 11155111,
"l2ChainID": 42069, "l2ChainID": 42069,
"l2BlockTime": 2, "l2BlockTime": 2,
"l1BlockTime": 12, "l1BlockTime": 12,
...@@ -13,23 +41,23 @@ ...@@ -13,23 +41,23 @@
"sequencerWindowSize": 3600, "sequencerWindowSize": 3600,
"channelTimeout": 300, "channelTimeout": 300,
"p2pSequencerAddress": "SEQUENCER", "p2pSequencerAddress": "$GS_SEQUENCER_ADDRESS",
"batchInboxAddress": "0xff00000000000000000000000000000000042069", "batchInboxAddress": "0xff00000000000000000000000000000000042069",
"batchSenderAddress": "BATCHER", "batchSenderAddress": "$GS_BATCHER_ADDRESS",
"l2OutputOracleSubmissionInterval": 120, "l2OutputOracleSubmissionInterval": 120,
"l2OutputOracleStartingBlockNumber": 0, "l2OutputOracleStartingBlockNumber": 0,
"l2OutputOracleStartingTimestamp": TIMESTAMP, "l2OutputOracleStartingTimestamp": $timestamp,
"l2OutputOracleProposer": "PROPOSER", "l2OutputOracleProposer": "$GS_PROPOSER_ADDRESS",
"l2OutputOracleChallenger": "ADMIN", "l2OutputOracleChallenger": "$GS_ADMIN_ADDRESS",
"finalizationPeriodSeconds": 12, "finalizationPeriodSeconds": 12,
"proxyAdminOwner": "ADMIN", "proxyAdminOwner": "$GS_ADMIN_ADDRESS",
"baseFeeVaultRecipient": "ADMIN", "baseFeeVaultRecipient": "$GS_ADMIN_ADDRESS",
"l1FeeVaultRecipient": "ADMIN", "l1FeeVaultRecipient": "$GS_ADMIN_ADDRESS",
"sequencerFeeVaultRecipient": "ADMIN", "sequencerFeeVaultRecipient": "$GS_ADMIN_ADDRESS",
"baseFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", "baseFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000",
"l1FeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", "l1FeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000",
...@@ -44,7 +72,7 @@ ...@@ -44,7 +72,7 @@
"enableGovernance": true, "enableGovernance": true,
"governanceTokenSymbol": "OP", "governanceTokenSymbol": "OP",
"governanceTokenName": "Optimism", "governanceTokenName": "Optimism",
"governanceTokenOwner": "ADMIN", "governanceTokenOwner": "$GS_ADMIN_ADDRESS",
"l2GenesisBlockGasLimit": "0x1c9c380", "l2GenesisBlockGasLimit": "0x1c9c380",
"l2GenesisBlockBaseFeePerGas": "0x3b9aca00", "l2GenesisBlockBaseFeePerGas": "0x3b9aca00",
...@@ -59,3 +87,8 @@ ...@@ -59,3 +87,8 @@
"requiredProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000", "requiredProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000",
"recommendedProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000" "recommendedProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000"
} }
EOL
)
# Write the config file
echo "$config" > deploy-config/getting-started.json
#!/usr/bin/env bash
# This script prints out the versions of the various tools used in the Getting
# Started quickstart guide on the docs site. Simplifies things for users so
# they can easily see if they're using the right versions of everything.
version() {
local string=$1
local version_regex='([0-9]+(\.[0-9]+)+)'
if [[ $string =~ $version_regex ]]; then
echo "${BASH_REMATCH[1]}"
else
echo "No version found."
fi
}
# Grab versions
ver_git=$(version "$(git --version)")
ver_go=$(version "$(go version)")
ver_node=$(version "$(node --version)")
ver_pnpm=$(version "$(pnpm --version)")
ver_foundry=$(version "$(forge --version)")
ver_make=$(version "$(make --version)")
ver_jq=$(version "$(jq --version)")
ver_direnv=$(version "$(direnv --version)")
# Print versions
echo "Dependency | Minimum | Actual"
echo "git 2 $ver_git"
echo "go 1.21 $ver_go"
echo "node 20 $ver_node"
echo "pnpm 8 $ver_pnpm"
echo "foundry 0.2.0 $ver_foundry"
echo "make 3 $ver_make"
echo "jq 1.6 $ver_jq"
echo "direnv 2 $ver_direnv"
#!/usr/bin/env bash
# This script is used to generate the four wallets that are used in the Getting
# Started quickstart guide on the docs site. Simplifies things for users
# slightly while also avoiding the need for users to manually copy/paste a
# bunch of stuff over to the environment file.
# Generate wallets
wallet1=$(cast wallet new)
wallet2=$(cast wallet new)
wallet3=$(cast wallet new)
wallet4=$(cast wallet new)
# Grab wallet addresses
address1=$(echo "$wallet1" | awk '/Address/ { print $2 }')
address2=$(echo "$wallet2" | awk '/Address/ { print $2 }')
address3=$(echo "$wallet3" | awk '/Address/ { print $2 }')
address4=$(echo "$wallet4" | awk '/Address/ { print $2 }')
# Grab wallet private keys
key1=$(echo "$wallet1" | awk '/Private key/ { print $3 }')
key2=$(echo "$wallet2" | awk '/Private key/ { print $3 }')
key3=$(echo "$wallet3" | awk '/Private key/ { print $3 }')
key4=$(echo "$wallet4" | awk '/Private key/ { print $3 }')
# Print out the environment variables to copy
echo "Copy the following into your .envrc file:"
echo
echo "# Admin account"
echo "export GS_ADMIN_ADDRESS=$address1"
echo "export GS_ADMIN_PRIVATE_KEY=$key1"
echo
echo "# Batcher account"
echo "export GS_BATCHER_ADDRESS=$address2"
echo "export GS_BATCHER_PRIVATE_KEY=$key2"
echo
echo "# Proposer account"
echo "export GS_PROPOSER_ADDRESS=$address3"
echo "export GS_PROPOSER_PRIVATE_KEY=$key3"
echo
echo "# Sequencer account"
echo "export GS_SEQUENCER_ADDRESS=$address4"
echo "export GS_SEQUENCER_PRIVATE_KEY=$key4"
{ {
"src/EAS/EAS.sol": "0xe5e9700b94a88a2e1baabe4bfa66d5c1c94e811bcc5d2c64526afc649df59118", "src/EAS/EAS.sol": "0x850a0eb089d5a01f489c7239f5b9a1b09120afb1bc80239268215c2dfe1de26c",
"src/EAS/SchemaRegistry.sol": "0x5ee1a0c3b2bf1eb5edb53fb0967cf13856be546f0f16fe7acdc3e4f286db6831", "src/EAS/SchemaRegistry.sol": "0x5ee1a0c3b2bf1eb5edb53fb0967cf13856be546f0f16fe7acdc3e4f286db6831",
"src/L1/DelayedVetoable.sol": "0x276c6276292095e6aa37a70008cf4e0d1cbcc020dbc9107459bbc72ab5ed744f", "src/L1/DelayedVetoable.sol": "0x276c6276292095e6aa37a70008cf4e0d1cbcc020dbc9107459bbc72ab5ed744f",
"src/L1/L1CrossDomainMessenger.sol": "0x2aa4e06827bc48484212eb2bdc30fd604ffd23b37e401b78ef428c12fa9b8385", "src/L1/L1CrossDomainMessenger.sol": "0x2aa4e06827bc48484212eb2bdc30fd604ffd23b37e401b78ef428c12fa9b8385",
......
...@@ -80,11 +80,11 @@ contract EAS is IEAS, ISemver, EIP1271Verifier { ...@@ -80,11 +80,11 @@ contract EAS is IEAS, ISemver, EIP1271Verifier {
uint256[MAX_GAP - 3] private __gap; uint256[MAX_GAP - 3] private __gap;
/// @notice Semantic version. /// @notice Semantic version.
/// @custom:semver 1.3.0 /// @custom:semver 1.4.0
string public constant version = "1.3.0"; string public constant version = "1.4.0";
/// @dev Creates a new EAS instance. /// @dev Creates a new EAS instance.
constructor() EIP1271Verifier("EAS", "1.2.0") { } constructor() EIP1271Verifier("EAS", "1.3.0") { }
/// @inheritdoc IEAS /// @inheritdoc IEAS
function getSchemaRegistry() external pure returns (ISchemaRegistry) { function getSchemaRegistry() external pure returns (ISchemaRegistry) {
......
...@@ -30,13 +30,13 @@ abstract contract EIP1271Verifier is EIP712 { ...@@ -30,13 +30,13 @@ abstract contract EIP1271Verifier is EIP712 {
error InvalidNonce(); error InvalidNonce();
// The hash of the data type used to relay calls to the attest function. It's the value of // The hash of the data type used to relay calls to the attest function. It's the value of
// keccak256("Attest(bytes32 schema,address recipient,uint64 expirationTime,bool revocable,bytes32 refUID,bytes // keccak256("Attest(address attester,bytes32 schema,address recipient,uint64 expirationTime,bool revocable,bytes32
// data,uint256 value,uint256 nonce,uint64 deadline)"). // refUID,bytes data,uint256 value,uint256 nonce,uint64 deadline)").
bytes32 private constant ATTEST_TYPEHASH = 0xf83bb2b0ede93a840239f7e701a54d9bc35f03701f51ae153d601c6947ff3d3f; bytes32 private constant ATTEST_TYPEHASH = 0xfeb2925a02bae3dae48d424a0437a2b6ac939aa9230ddc55a1a76f065d988076;
// The hash of the data type used to relay calls to the revoke function. It's the value of // The hash of the data type used to relay calls to the revoke function. It's the value of
// keccak256("Revoke(bytes32 schema,bytes32 uid,uint256 value,uint256 nonce,uint64 deadline)"). // keccak256("Revoke(address revoker,bytes32 schema,bytes32 uid,uint256 value,uint256 nonce,uint64 deadline)").
bytes32 private constant REVOKE_TYPEHASH = 0x2d4116d8c9824e4c316453e5c2843a1885580374159ce8768603c49085ef424c; bytes32 private constant REVOKE_TYPEHASH = 0xb5d556f07587ec0f08cf386545cc4362c702a001650c2058002615ee5c9d1e75;
// The user readable name of the signing domain. // The user readable name of the signing domain.
bytes32 private immutable _name; bytes32 private immutable _name;
...@@ -116,6 +116,7 @@ abstract contract EIP1271Verifier is EIP712 { ...@@ -116,6 +116,7 @@ abstract contract EIP1271Verifier is EIP712 {
keccak256( keccak256(
abi.encode( abi.encode(
ATTEST_TYPEHASH, ATTEST_TYPEHASH,
request.attester,
request.schema, request.schema,
data.recipient, data.recipient,
data.expirationTime, data.expirationTime,
...@@ -150,7 +151,13 @@ abstract contract EIP1271Verifier is EIP712 { ...@@ -150,7 +151,13 @@ abstract contract EIP1271Verifier is EIP712 {
bytes32 hash = _hashTypedDataV4( bytes32 hash = _hashTypedDataV4(
keccak256( keccak256(
abi.encode( abi.encode(
REVOKE_TYPEHASH, request.schema, data.uid, data.value, _nonces[request.revoker]++, request.deadline REVOKE_TYPEHASH,
request.revoker,
request.schema,
data.uid,
data.value,
_nonces[request.revoker]++,
request.deadline
) )
) )
); );
......
...@@ -13,7 +13,7 @@ contract SafeCall_Test is Test { ...@@ -13,7 +13,7 @@ contract SafeCall_Test is Test {
vm.assume(from.balance == 0); vm.assume(from.balance == 0);
vm.assume(to.balance == 0); vm.assume(to.balance == 0);
// no precompiles (mainnet) // no precompiles (mainnet)
assumeNoPrecompiles(to, 1); assumeNotPrecompile(to);
// don't call the vm // don't call the vm
vm.assume(to != address(vm)); vm.assume(to != address(vm));
vm.assume(from != address(vm)); vm.assume(from != address(vm));
...@@ -47,7 +47,7 @@ contract SafeCall_Test is Test { ...@@ -47,7 +47,7 @@ contract SafeCall_Test is Test {
vm.assume(from.balance == 0); vm.assume(from.balance == 0);
vm.assume(to.balance == 0); vm.assume(to.balance == 0);
// no precompiles (mainnet) // no precompiles (mainnet)
assumeNoPrecompiles(to, 1); assumeNotPrecompile(to);
// don't call the vm // don't call the vm
vm.assume(to != address(vm)); vm.assume(to != address(vm));
vm.assume(from != address(vm)); vm.assume(from != address(vm));
...@@ -89,7 +89,7 @@ contract SafeCall_Test is Test { ...@@ -89,7 +89,7 @@ contract SafeCall_Test is Test {
vm.assume(from.balance == 0); vm.assume(from.balance == 0);
vm.assume(to.balance == 0); vm.assume(to.balance == 0);
// no precompiles (mainnet) // no precompiles (mainnet)
assumeNoPrecompiles(to, 1); assumeNotPrecompile(to);
// don't call the vm // don't call the vm
vm.assume(to != address(vm)); vm.assume(to != address(vm));
vm.assume(from != address(vm)); vm.assume(from != address(vm));
......
...@@ -8,11 +8,6 @@ import { ResourceMetering } from "src/L1/ResourceMetering.sol"; ...@@ -8,11 +8,6 @@ import { ResourceMetering } from "src/L1/ResourceMetering.sol";
import { Constants } from "src/libraries/Constants.sol"; import { Constants } from "src/libraries/Constants.sol";
contract SystemConfig_GasLimitLowerBound_Invariant is Test { contract SystemConfig_GasLimitLowerBound_Invariant is Test {
struct FuzzInterface {
address target;
string[] artifacts;
}
SystemConfig public config; SystemConfig public config;
function setUp() external { function setUp() external {
...@@ -59,19 +54,14 @@ contract SystemConfig_GasLimitLowerBound_Invariant is Test { ...@@ -59,19 +54,14 @@ contract SystemConfig_GasLimitLowerBound_Invariant is Test {
selectors[0] = config.setGasLimit.selector; selectors[0] = config.setGasLimit.selector;
FuzzSelector memory selector = FuzzSelector({ addr: address(config), selectors: selectors }); FuzzSelector memory selector = FuzzSelector({ addr: address(config), selectors: selectors });
targetSelector(selector); targetSelector(selector);
}
/// @dev Allows the SystemConfig contract to be the target of the invariant test
/// when it is behind a proxy. Foundry calls this function under the hood to
/// know the ABI to use when calling the target contract.
function targetInterfaces() public view returns (FuzzInterface[] memory) {
require(address(config) != address(0), "SystemConfig not initialized");
FuzzInterface[] memory targets = new FuzzInterface[](1); /// Allows the SystemConfig contract to be the target of the invariant test
/// when it is behind a proxy. Foundry calls this function under the hood to
/// know the ABI to use when calling the target contract.
string[] memory artifacts = new string[](1); string[] memory artifacts = new string[](1);
artifacts[0] = "SystemConfig"; artifacts[0] = "SystemConfig";
targets[0] = FuzzInterface(address(config), artifacts); FuzzInterface memory target = FuzzInterface(address(config), artifacts);
return targets; targetInterface(target);
} }
/// @custom:invariant The gas limit of the `SystemConfig` contract can never be lower /// @custom:invariant The gas limit of the `SystemConfig` contract can never be lower
......
...@@ -213,8 +213,9 @@ The RLP-encoded consensus-enforced fields are: ...@@ -213,8 +213,9 @@ The RLP-encoded consensus-enforced fields are:
- `bloom` (standard): bloom filter of the transaction logs. - `bloom` (standard): bloom filter of the transaction logs.
- `logs` (standard): log events emitted by the EVM processing. - `logs` (standard): log events emitted by the EVM processing.
- `depositNonce` (unique extension): Optional field. The deposit transaction persists the nonce used during execution. - `depositNonce` (unique extension): Optional field. The deposit transaction persists the nonce used during execution.
- Before Regolith, this `depositNonce` field must always be omitted. - `depositNonceVersion` (unique extension): Optional field. The value must be 1 if the field is present
- With Regolith, this `depositNonce` field must always be included. - Before Canyon, these `depositNonce` & `depositNonceVersion` fields must always be omitted.
- With Canyon, these `depositNonce` & `depositNonceVersion` fields must always be included.
Starting with Regolith, the receipt API responses utilize the receipt changes for more accurate response data: Starting with Regolith, the receipt API responses utilize the receipt changes for more accurate response data:
......
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