Commit ba939af9 authored by Adrian Sutton's avatar Adrian Sutton

Merge branch 'develop' into jg/challenger_metrics

# Conflicts:
#	op-challenger/fault/service.go
parents 532d8ce6 7b30659f
---
'@eth-optimism/common-ts': patch
---
Updated npm dependencies of common-ts
......@@ -415,6 +415,10 @@ jobs:
pnpm autogen:invariant-docs
git diff --exit-code ./invariant-docs/*.md || echo "export INVARIANT_DOCS_STATUS=1" >> "$BASH_ENV"
working_directory: packages/contracts-bedrock
- run:
name: check deploy configs || echo "export DEPLOY_CONFIGS_STATUS=1" >> "$BASH_ENV"
command: pnpm validate-deploy-configs
working_directory: packages/contracts-bedrock
- run:
name: check statuses
command: |
......@@ -430,12 +434,16 @@ jobs:
echo "Storage snapshot failed, see job output for details."
FAILED=1
fi
if [[ "$SEMVER_LOCK_STATUS" -ne 0 ]]; then
echo "Semver lock failed, see job output for details."
FAILED=1
fi
if [[ "$INVARIANT_DOCS_STATUS" -ne 0 ]]; then
echo "Invariant docs failed, see job output for details."
FAILED=1
fi
if [[ "$SEMVER_LOCK_STATUS" -ne 0 ]]; then
echo "Semver lock failed, see job output for details."
if [[ "$DEPLOY_CONFIGS_STATUS" -ne 0 ]]; then
echo "Deploy config check failed, see job output for details."
FAILED=1
fi
if [[ "$FAILED" -ne 0 ]]; then
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -13,7 +13,7 @@ const ProxyStorageLayoutJSON = "{\"storage\":null,\"types\":{}}"
var ProxyStorageLayout = new(solc.StorageLayout)
var ProxyDeployedBin = "0x60806040526004361061005e5760003560e01c80635c60da1b116100435780635c60da1b146100be5780638f283970146100f8578063f851a440146101185761006d565b80633659cfe6146100755780634f1ef286146100955761006d565b3661006d5761006b61012d565b005b61006b61012d565b34801561008157600080fd5b5061006b6100903660046106d9565b610224565b6100a86100a33660046106f4565b610296565b6040516100b59190610777565b60405180910390f35b3480156100ca57600080fd5b506100d3610419565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016100b5565b34801561010457600080fd5b5061006b6101133660046106d9565b6104b0565b34801561012457600080fd5b506100d3610517565b60006101577f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5490565b905073ffffffffffffffffffffffffffffffffffffffff8116610201576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f50726f78793a20696d706c656d656e746174696f6e206e6f7420696e6974696160448201527f6c697a656400000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b3660008037600080366000845af43d6000803e8061021e573d6000fd5b503d6000f35b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16148061027d575033155b1561028e5761028b816105a3565b50565b61028b61012d565b60606102c07fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806102f7575033155b1561040a57610305846105a3565b6000808573ffffffffffffffffffffffffffffffffffffffff16858560405161032f9291906107ea565b600060405180830381855af49150503d806000811461036a576040519150601f19603f3d011682016040523d82523d6000602084013e61036f565b606091505b509150915081610401576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603960248201527f50726f78793a2064656c656761746563616c6c20746f206e657720696d706c6560448201527f6d656e746174696f6e20636f6e7472616374206661696c65640000000000000060648201526084016101f8565b91506104129050565b61041261012d565b9392505050565b60006104437fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16148061047a575033155b156104a557507f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5490565b6104ad61012d565b90565b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161480610509575033155b1561028e5761028b8161060b565b60006105417fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161480610578575033155b156104a557507fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc81905560405173ffffffffffffffffffffffffffffffffffffffff8216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b60006106357fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61038390556040805173ffffffffffffffffffffffffffffffffffffffff8084168252851660208201529192507f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f910160405180910390a15050565b803573ffffffffffffffffffffffffffffffffffffffff811681146106d457600080fd5b919050565b6000602082840312156106eb57600080fd5b610412826106b0565b60008060006040848603121561070957600080fd5b610712846106b0565b9250602084013567ffffffffffffffff8082111561072f57600080fd5b818601915086601f83011261074357600080fd5b81358181111561075257600080fd5b87602082850101111561076457600080fd5b6020830194508093505050509250925092565b600060208083528351808285015260005b818110156107a457858101830151858201604001528201610788565b818111156107b6576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b818382376000910190815291905056fea164736f6c634300080f000a"
var ProxyDeployedBin = "0x60806040526004361061005e5760003560e01c80635c60da1b116100435780635c60da1b146100be5780638f283970146100f8578063f851a440146101185761006d565b80633659cfe6146100755780634f1ef286146100955761006d565b3661006d5761006b61012d565b005b61006b61012d565b34801561008157600080fd5b5061006b6100903660046106dd565b610224565b6100a86100a33660046106f8565b610296565b6040516100b5919061077b565b60405180910390f35b3480156100ca57600080fd5b506100d3610419565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016100b5565b34801561010457600080fd5b5061006b6101133660046106dd565b6104b0565b34801561012457600080fd5b506100d3610517565b60006101577f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5490565b905073ffffffffffffffffffffffffffffffffffffffff8116610201576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f50726f78793a20696d706c656d656e746174696f6e206e6f7420696e6974696160448201527f6c697a656400000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b3660008037600080366000845af43d6000803e8061021e573d6000fd5b503d6000f35b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16148061027d575033155b1561028e5761028b816105a3565b50565b61028b61012d565b60606102c07fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806102f7575033155b1561040a57610305846105a3565b6000808573ffffffffffffffffffffffffffffffffffffffff16858560405161032f9291906107ee565b600060405180830381855af49150503d806000811461036a576040519150601f19603f3d011682016040523d82523d6000602084013e61036f565b606091505b509150915081610401576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603960248201527f50726f78793a2064656c656761746563616c6c20746f206e657720696d706c6560448201527f6d656e746174696f6e20636f6e7472616374206661696c65640000000000000060648201526084016101f8565b91506104129050565b61041261012d565b9392505050565b60006104437fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16148061047a575033155b156104a557507f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5490565b6104ad61012d565b90565b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161480610509575033155b1561028e5761028b8161060c565b60006105417fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161480610578575033155b156104a557507fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc81815560405173ffffffffffffffffffffffffffffffffffffffff8316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a25050565b60006106367fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61038381556040805173ffffffffffffffffffffffffffffffffffffffff80851682528616602082015292935090917f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f910160405180910390a1505050565b803573ffffffffffffffffffffffffffffffffffffffff811681146106d857600080fd5b919050565b6000602082840312156106ef57600080fd5b610412826106b4565b60008060006040848603121561070d57600080fd5b610716846106b4565b9250602084013567ffffffffffffffff8082111561073357600080fd5b818601915086601f83011261074757600080fd5b81358181111561075657600080fd5b87602082850101111561076857600080fd5b6020830194508093505050509250925092565b600060208083528351808285015260005b818110156107a85785810183015185820160400152820161078c565b818111156107ba576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b818382376000910190815291905056fea164736f6c634300080f000a"
func init() {
if err := json.Unmarshal([]byte(ProxyStorageLayoutJSON), ProxyStorageLayout); err != nil {
......
package main
import (
"os"
"path/filepath"
"strings"
"github.com/mattn/go-isatty"
"github.com/urfave/cli/v2"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
"github.com/ethereum/go-ethereum/log"
)
func main() {
log.Root().SetHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(isatty.IsTerminal(os.Stderr.Fd()))))
app := &cli.App{
Name: "check-deploy-config",
Usage: "Check that a deploy config is valid",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "path",
Required: true,
Usage: "File system path to the deploy config",
},
},
Action: entrypoint,
}
if err := app.Run(os.Args); err != nil {
log.Crit("error checking deploy config", "err", err)
}
}
func entrypoint(ctx *cli.Context) error {
path := ctx.String("path")
name := strings.TrimSuffix(filepath.Base(path), filepath.Ext(path))
log.Info("Checking deploy config", "name", name, "path", path)
config, err := genesis.NewDeployConfig(path)
if err != nil {
return err
}
// Check the config, no need to call `CheckAddresses()`
if err := config.Check(); err != nil {
return err
}
log.Info("Valid deploy config")
return nil
}
package genesis
import (
"bytes"
"encoding/json"
"errors"
"fmt"
......@@ -31,6 +32,7 @@ var (
)
// DeployConfig represents the deployment configuration for an OP Stack chain.
// It is used to deploy the L1 contracts as well as create the L2 genesis state.
type DeployConfig struct {
// L1StartingBlockTag is used to fill in the storage of the L1Block info predeploy. The rollup
// config script uses this to fill the L1 genesis info for the rollup. The Output oracle deploy
......@@ -172,10 +174,10 @@ type DeployConfig struct {
EIP1559Elasticity uint64 `json:"eip1559Elasticity"`
// EIP1559Denominator is the denominator of EIP1559 base fee market.
EIP1559Denominator uint64 `json:"eip1559Denominator"`
// FundDevAccounts configures whether or not to fund the dev accounts. Should only be used
// during devnet deployments.
FundDevAccounts bool `json:"fundDevAccounts"`
// SystemConfigStartBlock represents the block at which the op-node should start syncing
// from. It is an override to set this value on legacy networks where it is not set by
// default. It can be removed once all networks have this value set in their storage.
SystemConfigStartBlock uint64 `json:"systemConfigStartBlock"`
// FaultGameAbsolutePrestate is the absolute prestate of Cannon. This is computed
// by generating a proof from the 0th -> 1st instruction and grabbing the prestate from
// the output JSON. All honest challengers should agree on the setup state of the program.
......@@ -193,6 +195,9 @@ type DeployConfig struct {
// game can run for before it is ready to be resolved. Each side receives half of this value
// on their chess clock at the inception of the dispute.
FaultGameMaxDuration uint64 `json:"faultGameMaxDuration"`
// FundDevAccounts configures whether or not to fund the dev accounts. Should only be used
// during devnet deployments.
FundDevAccounts bool `json:"fundDevAccounts"`
}
// Copy will deeply copy the DeployConfig. This does a JSON roundtrip to copy
......@@ -293,21 +298,6 @@ func (d *DeployConfig) Check() error {
if d.GasPriceOracleScalar == 0 {
return fmt.Errorf("%w: GasPriceOracleScalar cannot be 0", ErrInvalidDeployConfig)
}
if d.L1StandardBridgeProxy == (common.Address{}) {
return fmt.Errorf("%w: L1StandardBridgeProxy cannot be address(0)", ErrInvalidDeployConfig)
}
if d.L1CrossDomainMessengerProxy == (common.Address{}) {
return fmt.Errorf("%w: L1CrossDomainMessengerProxy cannot be address(0)", ErrInvalidDeployConfig)
}
if d.L1ERC721BridgeProxy == (common.Address{}) {
return fmt.Errorf("%w: L1ERC721BridgeProxy cannot be address(0)", ErrInvalidDeployConfig)
}
if d.SystemConfigProxy == (common.Address{}) {
return fmt.Errorf("%w: SystemConfigProxy cannot be address(0)", ErrInvalidDeployConfig)
}
if d.OptimismPortalProxy == (common.Address{}) {
return fmt.Errorf("%w: OptimismPortalProxy cannot be address(0)", ErrInvalidDeployConfig)
}
if d.EIP1559Denominator == 0 {
return fmt.Errorf("%w: EIP1559Denominator cannot be 0", ErrInvalidDeployConfig)
}
......@@ -343,6 +333,29 @@ func (d *DeployConfig) Check() error {
return nil
}
// CheckAddresses will return an error if the addresses are not set.
// These values are required to create the L2 genesis state and are present in the deploy config
// even though the deploy config is required to deploy the contracts on L1. This creates a
// circular dependency that should be resolved in the future.
func (d *DeployConfig) CheckAddresses() error {
if d.L1StandardBridgeProxy == (common.Address{}) {
return fmt.Errorf("%w: L1StandardBridgeProxy cannot be address(0)", ErrInvalidDeployConfig)
}
if d.L1CrossDomainMessengerProxy == (common.Address{}) {
return fmt.Errorf("%w: L1CrossDomainMessengerProxy cannot be address(0)", ErrInvalidDeployConfig)
}
if d.L1ERC721BridgeProxy == (common.Address{}) {
return fmt.Errorf("%w: L1ERC721BridgeProxy cannot be address(0)", ErrInvalidDeployConfig)
}
if d.SystemConfigProxy == (common.Address{}) {
return fmt.Errorf("%w: SystemConfigProxy cannot be address(0)", ErrInvalidDeployConfig)
}
if d.OptimismPortalProxy == (common.Address{}) {
return fmt.Errorf("%w: OptimismPortalProxy cannot be address(0)", ErrInvalidDeployConfig)
}
return nil
}
// SetDeployments will merge a Deployments into a DeployConfig.
func (d *DeployConfig) SetDeployments(deployments *L1Deployments) {
d.L1StandardBridgeProxy = deployments.L1StandardBridgeProxy
......@@ -469,8 +482,11 @@ func NewDeployConfig(path string) (*DeployConfig, error) {
return nil, fmt.Errorf("deploy config at %s not found: %w", path, err)
}
dec := json.NewDecoder(bytes.NewReader(file))
dec.DisallowUnknownFields()
var config DeployConfig
if err := json.Unmarshal(file, &config); err != nil {
if err := dec.Decode(&config); err != nil {
return nil, fmt.Errorf("cannot unmarshal deploy config: %w", err)
}
......
......@@ -66,5 +66,6 @@
"fundDevAccounts": true,
"faultGameAbsolutePrestate": "0x0000000000000000000000000000000000000000000000000000000000000000",
"faultGameMaxDepth": 63,
"faultGameMaxDuration": 604800
"faultGameMaxDuration": 604800,
"systemConfigStartBlock": 0
}
......@@ -50,17 +50,17 @@ type CannonTraceProvider struct {
lastProof *proofData
}
func NewTraceProvider(ctx context.Context, logger log.Logger, cfg *config.Config, l1Client bind.ContractCaller) (*CannonTraceProvider, error) {
func NewTraceProvider(ctx context.Context, logger log.Logger, cfg *config.Config, l1Client bind.ContractCaller, gameAddr common.Address) (*CannonTraceProvider, error) {
l2Client, err := ethclient.DialContext(ctx, cfg.CannonL2)
if err != nil {
return nil, fmt.Errorf("dial l2 client %v: %w", cfg.CannonL2, err)
}
defer l2Client.Close() // Not needed after fetching the inputs
gameCaller, err := bindings.NewFaultDisputeGameCaller(cfg.GameAddress, l1Client)
gameCaller, err := bindings.NewFaultDisputeGameCaller(gameAddr, l1Client)
if err != nil {
return nil, fmt.Errorf("create caller for game %v: %w", cfg.GameAddress, err)
return nil, fmt.Errorf("create caller for game %v: %w", gameAddr, err)
}
localInputs, err := fetchLocalInputs(ctx, cfg.GameAddress, gameCaller, l2Client)
localInputs, err := fetchLocalInputs(ctx, gameAddr, gameCaller, l2Client)
if err != nil {
return nil, fmt.Errorf("fetch local game inputs: %w", err)
}
......
......@@ -31,11 +31,6 @@ type FaultDisputeGame struct {
Proxy common.Address
}
// GameLoader is a minimal interface for fetching on chain dispute games.
type GameLoader interface {
FetchAllGamesAtBlock(ctx context.Context) ([]FaultDisputeGame, error)
}
type gameLoader struct {
caller MinimalDisputeGameFactoryCaller
}
......
package fault
import (
"context"
"fmt"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/fault/alphabet"
"github.com/ethereum-optimism/optimism/op-challenger/fault/cannon"
"github.com/ethereum-optimism/optimism/op-challenger/fault/types"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
)
type Actor interface {
Act(ctx context.Context) error
}
type GameInfo interface {
GetGameStatus(context.Context) (types.GameStatus, error)
LogGameInfo(ctx context.Context)
}
type GamePlayer struct {
agent Actor
agreeWithProposedOutput bool
caller GameInfo
logger log.Logger
}
func NewGamePlayer(
ctx context.Context,
logger log.Logger,
cfg *config.Config,
addr common.Address,
txMgr txmgr.TxManager,
client *ethclient.Client,
) (*GamePlayer, error) {
logger = logger.New("game", addr)
contract, err := bindings.NewFaultDisputeGameCaller(addr, client)
if err != nil {
return nil, fmt.Errorf("failed to bind the fault dispute game contract: %w", err)
}
loader := NewLoader(contract)
gameDepth, err := loader.FetchGameDepth(ctx)
if err != nil {
return nil, fmt.Errorf("failed to fetch the game depth: %w", err)
}
var provider types.TraceProvider
var updater types.OracleUpdater
switch cfg.TraceType {
case config.TraceTypeCannon:
provider, err = cannon.NewTraceProvider(ctx, logger, cfg, client, addr)
if err != nil {
return nil, fmt.Errorf("create cannon trace provider: %w", err)
}
updater, err = cannon.NewOracleUpdater(ctx, logger, txMgr, addr, client)
if err != nil {
return nil, fmt.Errorf("failed to create the cannon updater: %w", err)
}
case config.TraceTypeAlphabet:
provider = alphabet.NewTraceProvider(cfg.AlphabetTrace, gameDepth)
updater = alphabet.NewOracleUpdater(logger)
default:
return nil, fmt.Errorf("unsupported trace type: %v", cfg.TraceType)
}
if err := ValidateAbsolutePrestate(ctx, provider, loader); err != nil {
return nil, fmt.Errorf("failed to validate absolute prestate: %w", err)
}
responder, err := NewFaultResponder(logger, txMgr, addr)
if err != nil {
return nil, fmt.Errorf("failed to create the responder: %w", err)
}
caller, err := NewFaultCallerFromBindings(addr, client, logger)
if err != nil {
return nil, fmt.Errorf("failed to bind the fault contract: %w", err)
}
return &GamePlayer{
agent: NewAgent(loader, int(gameDepth), provider, responder, updater, cfg.AgreeWithProposedOutput, logger),
agreeWithProposedOutput: cfg.AgreeWithProposedOutput,
caller: caller,
logger: logger,
}, nil
}
func (g *GamePlayer) ProgressGame(ctx context.Context) bool {
g.logger.Trace("Checking if actions are required")
if err := g.agent.Act(ctx); err != nil {
g.logger.Error("Error when acting on game", "err", err)
}
if status, err := g.caller.GetGameStatus(ctx); err != nil {
g.logger.Warn("Unable to retrieve game status", "err", err)
} else if status != 0 {
var expectedStatus types.GameStatus
if g.agreeWithProposedOutput {
expectedStatus = types.GameStatusChallengerWon
} else {
expectedStatus = types.GameStatusDefenderWon
}
if expectedStatus == status {
g.logger.Info("Game won", "status", status)
} else {
g.logger.Error("Game lost", "status", status)
}
return true
} else {
g.caller.LogGameInfo(ctx)
}
return false
}
package fault
import (
"context"
"errors"
"testing"
"github.com/ethereum-optimism/optimism/op-challenger/fault/types"
"github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
)
func TestProgressGameAndLogState(t *testing.T) {
_, game, actor, gameInfo := setupProgressGameTest(t, true)
done := game.ProgressGame(context.Background())
require.False(t, done, "should not be done")
require.Equal(t, 1, actor.callCount, "should perform next actions")
require.Equal(t, 1, gameInfo.logCount, "should log latest game state")
}
func TestProgressGame_LogErrorFromAct(t *testing.T) {
handler, game, actor, gameInfo := setupProgressGameTest(t, true)
actor.err = errors.New("boom")
done := game.ProgressGame(context.Background())
require.False(t, done, "should not be done")
require.Equal(t, 1, actor.callCount, "should perform next actions")
require.Equal(t, 1, gameInfo.logCount, "should log latest game state")
errLog := handler.FindLog(log.LvlError, "Error when acting on game")
require.NotNil(t, errLog, "should log error")
require.Equal(t, actor.err, errLog.GetContextValue("err"))
}
func TestProgressGame_LogErrorWhenGameLost(t *testing.T) {
tests := []struct {
name string
status types.GameStatus
agreeWithOutput bool
logLevel log.Lvl
logMsg string
}{
{
name: "GameLostAsDefender",
status: types.GameStatusChallengerWon,
agreeWithOutput: false,
logLevel: log.LvlError,
logMsg: "Game lost",
},
{
name: "GameLostAsChallenger",
status: types.GameStatusDefenderWon,
agreeWithOutput: true,
logLevel: log.LvlError,
logMsg: "Game lost",
},
{
name: "GameWonAsDefender",
status: types.GameStatusDefenderWon,
agreeWithOutput: false,
logLevel: log.LvlInfo,
logMsg: "Game won",
},
{
name: "GameWonAsChallenger",
status: types.GameStatusChallengerWon,
agreeWithOutput: true,
logLevel: log.LvlInfo,
logMsg: "Game won",
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
handler, game, _, gameInfo := setupProgressGameTest(t, test.agreeWithOutput)
gameInfo.status = test.status
done := game.ProgressGame(context.Background())
require.True(t, done, "should be done")
require.Equal(t, 0, gameInfo.logCount, "should not log latest game state")
errLog := handler.FindLog(test.logLevel, test.logMsg)
require.NotNil(t, errLog, "should log game result")
require.Equal(t, test.status, errLog.GetContextValue("status"))
})
}
}
func setupProgressGameTest(t *testing.T, agreeWithProposedRoot bool) (*testlog.CapturingHandler, *GamePlayer, *stubActor, *stubGameInfo) {
logger := testlog.Logger(t, log.LvlDebug)
handler := &testlog.CapturingHandler{
Delegate: logger.GetHandler(),
}
logger.SetHandler(handler)
actor := &stubActor{}
gameInfo := &stubGameInfo{}
game := &GamePlayer{
agent: actor,
agreeWithProposedOutput: agreeWithProposedRoot,
caller: gameInfo,
logger: logger,
}
return handler, game, actor, gameInfo
}
type stubActor struct {
callCount int
err error
}
func (a *stubActor) Act(ctx context.Context) error {
a.callCount++
return a.err
}
type stubGameInfo struct {
status types.GameStatus
err error
logCount int
}
func (s *stubGameInfo) GetGameStatus(ctx context.Context) (types.GameStatus, error) {
return s.status, s.err
}
func (s *stubGameInfo) LogGameInfo(ctx context.Context) {
s.logCount++
}
......@@ -2,63 +2,95 @@ package fault
import (
"context"
"fmt"
"math/big"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-challenger/fault/types"
)
type GameInfo interface {
GetGameStatus(context.Context) (types.GameStatus, error)
LogGameInfo(ctx context.Context)
type gamePlayer interface {
ProgressGame(ctx context.Context) bool
}
type Actor interface {
Act(ctx context.Context) error
type playerCreator func(address common.Address) (gamePlayer, error)
type blockNumberFetcher func(ctx context.Context) (uint64, error)
// gameSource loads information about the games available to play
type gameSource interface {
FetchAllGamesAtBlock(ctx context.Context, blockNumber *big.Int) ([]FaultDisputeGame, error)
}
func MonitorGame(ctx context.Context, logger log.Logger, agreeWithProposedOutput bool, actor Actor, caller GameInfo) error {
logger.Info("Monitoring fault dispute game", "agreeWithOutput", agreeWithProposedOutput)
type gameMonitor struct {
logger log.Logger
source gameSource
createPlayer playerCreator
fetchBlockNumber blockNumberFetcher
allowedGame common.Address
players map[common.Address]gamePlayer
}
for {
done := progressGame(ctx, logger, agreeWithProposedOutput, actor, caller)
if done {
return nil
func newGameMonitor(logger log.Logger, fetchBlockNumber blockNumberFetcher, allowedGame common.Address, source gameSource, createGame playerCreator) *gameMonitor {
return &gameMonitor{
logger: logger,
source: source,
createPlayer: createGame,
fetchBlockNumber: fetchBlockNumber,
allowedGame: allowedGame,
players: make(map[common.Address]gamePlayer),
}
}
func (m *gameMonitor) progressGames(ctx context.Context) error {
blockNum, err := m.fetchBlockNumber(ctx)
if err != nil {
return fmt.Errorf("failed to load current block number: %w", err)
}
games, err := m.source.FetchAllGamesAtBlock(ctx, new(big.Int).SetUint64(blockNum))
if err != nil {
return fmt.Errorf("failed to load games: %w", err)
}
for _, game := range games {
if m.allowedGame != (common.Address{}) && m.allowedGame != game.Proxy {
m.logger.Debug("Skipping game not on allow list", "game", game.Proxy)
continue
}
select {
case <-time.After(300 * time.Millisecond):
// Continue
case <-ctx.Done():
return ctx.Err()
player, err := m.fetchOrCreateGamePlayer(game)
if err != nil {
m.logger.Error("Error while progressing game", "game", game.Proxy, "err", err)
continue
}
player.ProgressGame(ctx)
}
return nil
}
// progressGame checks the current state of the game, and attempts to progress it by performing moves, steps or resolving
// Returns true if the game is complete or false if it needs to be monitored further
func progressGame(ctx context.Context, logger log.Logger, agreeWithProposedOutput bool, actor Actor, caller GameInfo) bool {
logger.Trace("Checking if actions are required")
if err := actor.Act(ctx); err != nil {
logger.Error("Error when acting on game", "err", err)
func (m *gameMonitor) fetchOrCreateGamePlayer(gameData FaultDisputeGame) (gamePlayer, error) {
if player, ok := m.players[gameData.Proxy]; ok {
return player, nil
}
player, err := m.createPlayer(gameData.Proxy)
if err != nil {
return nil, fmt.Errorf("failed to create game player %v: %w", gameData.Proxy, err)
}
if status, err := caller.GetGameStatus(ctx); err != nil {
logger.Warn("Unable to retrieve game status", "err", err)
} else if status != 0 {
var expectedStatus types.GameStatus
if agreeWithProposedOutput {
expectedStatus = types.GameStatusChallengerWon
} else {
expectedStatus = types.GameStatusDefenderWon
m.players[gameData.Proxy] = player
return player, nil
}
func (m *gameMonitor) MonitorGames(ctx context.Context) error {
m.logger.Info("Monitoring fault dispute games")
for {
err := m.progressGames(ctx)
if err != nil {
m.logger.Error("Failed to progress games", "err", err)
}
if expectedStatus == status {
logger.Info("Game won", "status", status)
} else {
logger.Error("Game lost", "status", status)
select {
case <-time.After(300 * time.Millisecond):
// Continue
case <-ctx.Done():
return ctx.Err()
}
return true
} else {
caller.LogGameInfo(ctx)
}
return false
}
......@@ -2,130 +2,123 @@ package fault
import (
"context"
"errors"
"math/big"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/op-challenger/fault/types"
"github.com/ethereum-optimism/optimism/op-node/testlog"
)
func TestMonitorExitsWhenContextDone(t *testing.T) {
logger := testlog.Logger(t, log.LvlDebug)
actor := &stubActor{}
gameInfo := &stubGameInfo{}
monitor, _, _ := setupMonitorTest(t, common.Address{})
ctx, cancel := context.WithCancel(context.Background())
cancel()
err := MonitorGame(ctx, logger, true, actor, gameInfo)
err := monitor.MonitorGames(ctx)
require.ErrorIs(t, err, context.Canceled)
}
func TestProgressGameAndLogState(t *testing.T) {
logger, _, actor, gameInfo := setupProgressGameTest(t)
done := progressGame(context.Background(), logger, true, actor, gameInfo)
require.False(t, done, "should not be done")
require.Equal(t, 1, actor.callCount, "should perform next actions")
require.Equal(t, 1, gameInfo.logCount, "should log latest game state")
}
func TestMonitorCreateAndProgressGameAgents(t *testing.T) {
monitor, source, games := setupMonitorTest(t, common.Address{})
func TestProgressGame_LogErrorFromAct(t *testing.T) {
logger, handler, actor, gameInfo := setupProgressGameTest(t)
actor.err = errors.New("Boom")
done := progressGame(context.Background(), logger, true, actor, gameInfo)
require.False(t, done, "should not be done")
require.Equal(t, 1, actor.callCount, "should perform next actions")
require.Equal(t, 1, gameInfo.logCount, "should log latest game state")
errLog := handler.FindLog(log.LvlError, "Error when acting on game")
require.NotNil(t, errLog, "should log error")
require.Equal(t, actor.err, errLog.GetContextValue("err"))
}
func TestProgressGame_LogErrorWhenGameLost(t *testing.T) {
tests := []struct {
name string
status types.GameStatus
agreeWithOutput bool
logLevel log.Lvl
logMsg string
}{
addr1 := common.Address{0xaa}
addr2 := common.Address{0xbb}
source.games = []FaultDisputeGame{
{
name: "GameLostAsDefender",
status: types.GameStatusChallengerWon,
agreeWithOutput: false,
logLevel: log.LvlError,
logMsg: "Game lost",
Proxy: addr1,
Timestamp: 9999,
},
{
name: "GameLostAsChallenger",
status: types.GameStatusDefenderWon,
agreeWithOutput: true,
logLevel: log.LvlError,
logMsg: "Game lost",
Proxy: addr2,
Timestamp: 9999,
},
}
err := monitor.progressGames(context.Background())
require.NoError(t, err)
require.Len(t, games.created, 2, "should create game agents")
require.Contains(t, games.created, addr1)
require.Contains(t, games.created, addr2)
require.Equal(t, 1, games.created[addr1].progressCount)
require.Equal(t, 1, games.created[addr2].progressCount)
// The stub will fail the test if a game is created with the same address multiple times
require.NoError(t, monitor.progressGames(context.Background()), "should only create games once")
require.Equal(t, 2, games.created[addr1].progressCount)
require.Equal(t, 2, games.created[addr2].progressCount)
}
func TestMonitorOnlyCreateSpecifiedGame(t *testing.T) {
addr1 := common.Address{0xaa}
addr2 := common.Address{0xbb}
monitor, source, games := setupMonitorTest(t, addr2)
source.games = []FaultDisputeGame{
{
name: "GameWonAsDefender",
status: types.GameStatusDefenderWon,
agreeWithOutput: false,
logLevel: log.LvlInfo,
logMsg: "Game won",
Proxy: addr1,
Timestamp: 9999,
},
{
name: "GameWonAsChallenger",
status: types.GameStatusChallengerWon,
agreeWithOutput: true,
logLevel: log.LvlInfo,
logMsg: "Game won",
Proxy: addr2,
Timestamp: 9999,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
logger, handler, actor, gameInfo := setupProgressGameTest(t)
gameInfo.status = test.status
done := progressGame(context.Background(), logger, test.agreeWithOutput, actor, gameInfo)
require.True(t, done, "should be done")
require.Equal(t, 0, gameInfo.logCount, "should not log latest game state")
errLog := handler.FindLog(test.logLevel, test.logMsg)
require.NotNil(t, errLog, "should log game result")
require.Equal(t, test.status, errLog.GetContextValue("status"))
})
}
err := monitor.progressGames(context.Background())
require.NoError(t, err)
require.Len(t, games.created, 1, "should only create allowed game")
require.Contains(t, games.created, addr2)
require.NotContains(t, games.created, addr1)
require.Equal(t, 1, games.created[addr2].progressCount)
}
func setupProgressGameTest(t *testing.T) (log.Logger, *testlog.CapturingHandler, *stubActor, *stubGameInfo) {
func setupMonitorTest(t *testing.T, allowedGame common.Address) (*gameMonitor, *stubGameSource, *createdGames) {
logger := testlog.Logger(t, log.LvlDebug)
handler := &testlog.CapturingHandler{
Delegate: logger.GetHandler(),
source := &stubGameSource{}
games := &createdGames{
t: t,
created: make(map[common.Address]*stubGame),
}
logger.SetHandler(handler)
actor := &stubActor{}
gameInfo := &stubGameInfo{}
return logger, handler, actor, gameInfo
fetchBlockNum := func(ctx context.Context) (uint64, error) {
return 1234, nil
}
monitor := newGameMonitor(logger, fetchBlockNum, allowedGame, source, games.CreateGame)
return monitor, source, games
}
type stubGameSource struct {
games []FaultDisputeGame
}
type stubActor struct {
callCount int
err error
func (s *stubGameSource) FetchAllGamesAtBlock(ctx context.Context, blockNumber *big.Int) ([]FaultDisputeGame, error) {
return s.games, nil
}
func (a *stubActor) Act(ctx context.Context) error {
a.callCount++
return a.err
type stubGame struct {
addr common.Address
progressCount int
done bool
}
type stubGameInfo struct {
status types.GameStatus
err error
logCount int
func (g *stubGame) ProgressGame(ctx context.Context) bool {
g.progressCount++
return g.done
}
func (s *stubGameInfo) GetGameStatus(ctx context.Context) (types.GameStatus, error) {
return s.status, s.err
type createdGames struct {
t *testing.T
created map[common.Address]*stubGame
}
func (s *stubGameInfo) LogGameInfo(ctx context.Context) {
s.logCount++
func (c *createdGames) CreateGame(addr common.Address) (gamePlayer, error) {
if _, exists := c.created[addr]; exists {
c.t.Fatalf("game %v already exists", addr)
}
game := &stubGame{addr: addr}
c.created[addr] = game
return game, nil
}
......@@ -7,16 +7,14 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"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/cannon"
"github.com/ethereum-optimism/optimism/op-challenger/fault/types"
"github.com/ethereum-optimism/optimism/op-challenger/metrics"
"github.com/ethereum-optimism/optimism/op-challenger/version"
"github.com/ethereum-optimism/optimism/op-service/client"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
)
......@@ -28,11 +26,9 @@ type Service interface {
}
type service struct {
agent *Agent
agreeWithProposedOutput bool
caller *FaultCaller
logger log.Logger
metrics metrics.Metricer
logger log.Logger
metrics metrics.Metricer
monitor *gameMonitor
}
// NewService creates a new Service.
......@@ -69,78 +65,23 @@ func NewService(ctx context.Context, logger log.Logger, cfg *config.Config) (*se
m.StartBalanceMetrics(ctx, logger, client, txMgr.From())
}
contract, err := bindings.NewFaultDisputeGameCaller(cfg.GameAddress, client)
factory, err := bindings.NewDisputeGameFactory(cfg.GameFactoryAddress, client)
if err != nil {
return nil, fmt.Errorf("failed to bind the fault dispute game contract: %w", err)
return nil, fmt.Errorf("failed to bind the fault dispute game factory contract: %w", err)
}
loader := NewGameLoader(factory)
loader := NewLoader(contract)
monitor := newGameMonitor(logger, client.BlockNumber, cfg.GameAddress, loader, func(addr common.Address) (gamePlayer, error) {
return NewGamePlayer(ctx, logger, cfg, addr, txMgr, client)
})
gameDepth, err := loader.FetchGameDepth(ctx)
if err != nil {
return nil, fmt.Errorf("failed to fetch the game depth: %w", err)
}
gameDepth = uint64(gameDepth)
var trace types.TraceProvider
var updater types.OracleUpdater
switch cfg.TraceType {
case config.TraceTypeCannon:
trace, err = cannon.NewTraceProvider(ctx, logger, cfg, client)
if err != nil {
return nil, fmt.Errorf("create cannon trace provider: %w", err)
}
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)
}
case config.TraceTypeAlphabet:
trace = alphabet.NewTraceProvider(cfg.AlphabetTrace, gameDepth)
updater = alphabet.NewOracleUpdater(logger)
default:
return nil, fmt.Errorf("unsupported trace type: %v", cfg.TraceType)
}
return newTypedService(ctx, logger, cfg, loader, gameDepth, client, trace, updater, txMgr, m)
}
// newTypedService creates a new Service from a provided trace provider.
func newTypedService(ctx context.Context,
logger log.Logger,
cfg *config.Config,
loader Loader,
gameDepth uint64,
client *ethclient.Client,
provider types.TraceProvider,
updater types.OracleUpdater,
txMgr txmgr.TxManager,
metrics metrics.Metricer,
) (*service, error) {
if err := ValidateAbsolutePrestate(ctx, provider, loader); err != nil {
return nil, fmt.Errorf("failed to validate absolute prestate: %w", err)
}
gameLogger := logger.New("game", cfg.GameAddress)
responder, err := NewFaultResponder(gameLogger, txMgr, cfg.GameAddress)
if err != nil {
return nil, fmt.Errorf("failed to create the responder: %w", err)
}
caller, err := NewFaultCallerFromBindings(cfg.GameAddress, client, gameLogger)
if err != nil {
return nil, fmt.Errorf("failed to bind the fault contract: %w", err)
}
metrics.RecordInfo(version.SimpleWithMeta)
metrics.RecordUp()
m.RecordInfo(version.SimpleWithMeta)
m.RecordUp()
return &service{
agent: NewAgent(loader, int(gameDepth), provider, responder, updater, cfg.AgreeWithProposedOutput, gameLogger),
agreeWithProposedOutput: cfg.AgreeWithProposedOutput,
caller: caller,
logger: gameLogger,
metrics: metrics,
logger: logger,
metrics: m,
monitor: monitor,
}, nil
}
......@@ -163,5 +104,5 @@ func ValidateAbsolutePrestate(ctx context.Context, trace types.TraceProvider, lo
// MonitorGame monitors the fault dispute game and attempts to progress it.
func (s *service) MonitorGame(ctx context.Context) error {
return MonitorGame(ctx, s.logger, s.agreeWithProposedOutput, s.agent, s.caller)
return s.monitor.MonitorGames(ctx)
}
......@@ -38,7 +38,7 @@ func (g *CannonGameHelper) CreateHonestActor(ctx context.Context, rollupCfg *rol
}
opts = append(opts, options...)
cfg := challenger.NewChallengerConfig(g.t, l1Endpoint, opts...)
provider, err := cannon.NewTraceProvider(ctx, testlog.Logger(g.t, log.LvlInfo).New("role", "CorrectTrace"), cfg, l1Client)
provider, err := cannon.NewTraceProvider(ctx, testlog.Logger(g.t, log.LvlInfo).New("role", "CorrectTrace"), cfg, l1Client, g.addr)
g.require.NoError(err, "create cannon trace provider")
return &HonestHelper{
......
......@@ -12,8 +12,7 @@ import (
"github.com/stretchr/testify/require"
)
func TestCannonMultipleGames(t *testing.T) {
t.Skip("Challenger doesn't yet support multiple games")
func TestMultipleAlphabetGames(t *testing.T) {
InitParallel(t)
ctx := context.Background()
......@@ -41,6 +40,13 @@ func TestCannonMultipleGames(t *testing.T) {
game2.WaitForClaimCount(ctx, 4)
game1.Defend(ctx, 1, common.Hash{0xaa})
game1.WaitForClaimCount(ctx, 4)
gameDuration := game1.GameDuration(ctx)
sys.TimeTravelClock.AdvanceTime(gameDuration)
require.NoError(t, wait.ForNextBlock(ctx, l1Client))
game1.WaitForGameStatus(ctx, disputegame.StatusChallengerWins)
game2.WaitForGameStatus(ctx, disputegame.StatusChallengerWins)
}
func TestMultipleCannonGames(t *testing.T) {
......
......@@ -63,11 +63,15 @@ var Subcommands = cli.Commands{
config.SetDeployments(deployments)
}
// Do the check after setting the deployments
if err := config.Check(); err != nil {
return fmt.Errorf("deploy config at %s invalid: %w", deployConfig, err)
}
// Check the addresses after setting the deployments
if err := config.CheckAddresses(); err != nil {
return fmt.Errorf("deploy config at %s invalid: %w", deployConfig, err)
}
var dump *state.Dump
if l1Allocs := ctx.String("l1-allocs"); l1Allocs != "" {
dump, err = genesis.NewStateDump(l1Allocs)
......
......@@ -34,31 +34,31 @@
"url": "https://github.com/ethereum-optimism/optimism.git"
},
"dependencies": {
"@eth-optimism/core-utils": "0.12.3",
"@sentry/node": "^6.3.1",
"bcfg": "^0.1.7",
"body-parser": "^1.20.0",
"commander": "^9.0.0",
"dotenv": "^16.0.0",
"envalid": "^7.2.2",
"ethers": "^5.7.0",
"express": "^4.17.1",
"express-prom-bundle": "^6.4.1",
"@eth-optimism/core-utils": "workspace:*",
"@sentry/node": "^7.64.0",
"bcfg": "^0.2.1",
"body-parser": "^1.20.2",
"commander": "^11.0.0",
"dotenv": "^16.3.1",
"envalid": "^7.3.1",
"ethers": "^5.7.2",
"express": "^4.18.2",
"express-prom-bundle": "^6.6.0",
"lodash": "^4.17.21",
"morgan": "^1.10.0",
"pino": "^8.15.0",
"pino-multi-stream": "^5.3.0",
"pino-multi-stream": "^6.0.0",
"pino-sentry": "^0.14.0",
"prom-client": "^13.1.0"
"prom-client": "^14.2.0"
},
"devDependencies": {
"@ethersproject/abstract-provider": "^5.7.0",
"@ethersproject/abstract-signer": "^5.7.0",
"@types/express": "^4.17.13",
"@types/morgan": "^1.9.3",
"@types/pino": "^6.3.6",
"@types/pino-multi-stream": "^5.1.1",
"chai": "^4.3.4",
"supertest": "^6.1.4"
"@types/express": "^4.17.17",
"@types/morgan": "^1.9.4",
"@types/pino": "^7.0.5",
"@types/pino-multi-stream": "^5.1.3",
"chai": "^4.3.7",
"supertest": "^6.3.3"
}
}
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist"
"outDir": "./dist",
"skipLibCheck": true
},
"include": [
......
......@@ -25,6 +25,7 @@ Bytes_slice_Test:test_slice_fromZeroIdx_works() (gas: 20804)
Bytes_toNibbles_Test:test_toNibbles_expectedResult128Bytes_works() (gas: 78882)
Bytes_toNibbles_Test:test_toNibbles_expectedResult5Bytes_works() (gas: 3992)
Bytes_toNibbles_Test:test_toNibbles_zeroLengthInput_works() (gas: 823)
Constants_Test:test_eip1967Constants_succeeds() (gas: 453)
CrossDomainMessenger_BaseGas_Test:test_baseGas_succeeds() (gas: 20435)
CrossDomainOwnable2_Test:test_onlyOwner_notMessenger_reverts() (gas: 8461)
CrossDomainOwnable2_Test:test_onlyOwner_notOwner2_reverts() (gas: 57515)
......@@ -239,7 +240,7 @@ L2OutputOracleUpgradeable_Test:test_initValuesOnImpl_succeeds() (gas: 23902)
L2OutputOracleUpgradeable_Test:test_initValuesOnProxy_succeeds() (gas: 46800)
L2OutputOracleUpgradeable_Test:test_initializeImpl_alreadyInitialized_reverts() (gas: 15216)
L2OutputOracleUpgradeable_Test:test_initializeProxy_alreadyInitialized_reverts() (gas: 20216)
L2OutputOracleUpgradeable_Test:test_upgrading_succeeds() (gas: 191346)
L2OutputOracleUpgradeable_Test:test_upgrading_succeeds() (gas: 191348)
L2OutputOracle_constructor_Test:test_constructor_l2BlockTimeZero_reverts() (gas: 39212)
L2OutputOracle_constructor_Test:test_constructor_submissionInterval_reverts() (gas: 39222)
L2OutputOracle_constructor_Test:test_constructor_succeeds() (gas: 51777)
......@@ -430,7 +431,7 @@ OptimismMintableTokenFactory_Test:test_createStandardL2Token_succeeds() (gas: 12
OptimismPortalUpgradeable_Test:test_initialize_cannotInitImpl_reverts() (gas: 11178)
OptimismPortalUpgradeable_Test:test_initialize_cannotInitProxy_reverts() (gas: 16111)
OptimismPortalUpgradeable_Test:test_params_initValuesOnProxy_succeeds() (gas: 26781)
OptimismPortalUpgradeable_Test:test_upgradeToAndCall_upgrading_succeeds() (gas: 191412)
OptimismPortalUpgradeable_Test:test_upgradeToAndCall_upgrading_succeeds() (gas: 191414)
OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_ifOutputRootChanges_reverts() (gas: 178667)
OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_ifOutputTimestampIsNotFinalized_reverts() (gas: 182306)
OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_ifWithdrawalNotProven_reverts() (gas: 41780)
......@@ -533,11 +534,11 @@ ProxyAdmin_Test:test_delegateResolvedGetProxyAdmin_succeeds() (gas: 17691)
ProxyAdmin_Test:test_delegateResolvedGetProxyImplementation_succeeds() (gas: 62028)
ProxyAdmin_Test:test_delegateResolvedUpgradeAndCall_succeeds() (gas: 98039)
ProxyAdmin_Test:test_delegateResolvedUpgrade_succeeds() (gas: 58482)
ProxyAdmin_Test:test_erc1967ChangeProxyAdmin_succeeds() (gas: 33812)
ProxyAdmin_Test:test_erc1967ChangeProxyAdmin_succeeds() (gas: 33820)
ProxyAdmin_Test:test_erc1967GetProxyAdmin_succeeds() (gas: 15616)
ProxyAdmin_Test:test_erc1967GetProxyImplementation_succeeds() (gas: 52071)
ProxyAdmin_Test:test_erc1967UpgradeAndCall_succeeds() (gas: 78969)
ProxyAdmin_Test:test_erc1967Upgrade_succeeds() (gas: 50078)
ProxyAdmin_Test:test_erc1967GetProxyImplementation_succeeds() (gas: 52073)
ProxyAdmin_Test:test_erc1967UpgradeAndCall_succeeds() (gas: 78971)
ProxyAdmin_Test:test_erc1967Upgrade_succeeds() (gas: 50080)
ProxyAdmin_Test:test_isUpgrading_succeeds() (gas: 19442)
ProxyAdmin_Test:test_onlyOwner_notOwner_reverts() (gas: 22767)
ProxyAdmin_Test:test_owner_succeeds() (gas: 9738)
......@@ -547,16 +548,16 @@ ProxyAdmin_Test:test_setImplementationName_notOwner_reverts() (gas: 11111)
ProxyAdmin_Test:test_setImplementationName_succeeds() (gas: 38945)
ProxyAdmin_Test:test_setProxyType_notOwner_reverts() (gas: 10814)
Proxy_Test:test_delegatesToImpl_succeeds() (gas: 45207)
Proxy_Test:test_implementationKey_succeeds() (gas: 20909)
Proxy_Test:test_implementation_isZeroAddress_reverts() (gas: 47626)
Proxy_Test:test_implementationKey_succeeds() (gas: 20911)
Proxy_Test:test_implementation_isZeroAddress_reverts() (gas: 47628)
Proxy_Test:test_implementation_zeroAddressCaller_succeeds() (gas: 14752)
Proxy_Test:test_ownerKey_succeeds() (gas: 19059)
Proxy_Test:test_ownerProxyCall_notAdmin_succeeds() (gas: 34615)
Proxy_Test:test_proxyCallToImp_notAdmin_succeeds() (gas: 30008)
Proxy_Test:test_upgradeToAndCall_functionDoesNotExist_reverts() (gas: 104565)
Proxy_Test:test_upgradeToAndCall_isPayable_succeeds() (gas: 53742)
Proxy_Test:test_upgradeToAndCall_succeeds() (gas: 125190)
Proxy_Test:test_upgradeTo_clashingFunctionSignatures_succeeds() (gas: 101359)
Proxy_Test:test_ownerKey_succeeds() (gas: 19067)
Proxy_Test:test_ownerProxyCall_notAdmin_succeeds() (gas: 34623)
Proxy_Test:test_proxyCallToImp_notAdmin_succeeds() (gas: 30010)
Proxy_Test:test_upgradeToAndCall_functionDoesNotExist_reverts() (gas: 104567)
Proxy_Test:test_upgradeToAndCall_isPayable_succeeds() (gas: 53744)
Proxy_Test:test_upgradeToAndCall_succeeds() (gas: 125192)
Proxy_Test:test_upgradeTo_clashingFunctionSignatures_succeeds() (gas: 101363)
RLPReader_readBytes_Test:test_readBytes_bytestring00_succeeds() (gas: 1863)
RLPReader_readBytes_Test:test_readBytes_bytestring01_succeeds() (gas: 1840)
RLPReader_readBytes_Test:test_readBytes_bytestring7f_succeeds() (gas: 1861)
......@@ -631,7 +632,7 @@ ResourceMetering_Test:test_meter_useMax_succeeds() (gas: 20020816)
ResourceMetering_Test:test_meter_useMoreThanMax_reverts() (gas: 19549)
SafeCall_Test:test_callWithMinGas_noLeakageHigh_succeeds() (gas: 1021670598)
SafeCall_Test:test_callWithMinGas_noLeakageLow_succeeds() (gas: 1095190710)
Semver_Test:test_behindProxy_succeeds() (gas: 506748)
Semver_Test:test_behindProxy_succeeds() (gas: 507558)
Semver_Test:test_version_succeeds() (gas: 9418)
SequencerFeeVault_L2Withdrawal_Test:test_withdraw_toL2_succeeds() (gas: 78333)
SequencerFeeVault_L2Withdrawal_Test:test_withdraw_toL2recipientReverts_fails() (gas: 46486)
......@@ -643,11 +644,11 @@ SequencerFeeVault_Test:test_withdraw_toL1_succeeds() (gas: 171357)
SetPrevBaseFee_Test:test_setPrevBaseFee_succeeds() (gas: 11549)
StandardBridge_Stateless_Test:test_isCorrectTokenPair_succeeds() (gas: 49936)
StandardBridge_Stateless_Test:test_isOptimismMintableERC20_succeeds() (gas: 33072)
SystemConfig_Initialize_Test:test_initialize_events_succeeds() (gas: 72059)
SystemConfig_Initialize_Test:test_initialize_startBlockOverride_succeeds() (gas: 65240)
SystemConfig_Initialize_Test:test_initialize_events_succeeds() (gas: 72060)
SystemConfig_Initialize_Test:test_initialize_startBlockOverride_succeeds() (gas: 65242)
SystemConfig_Initialize_Test:test_initialize_values_succeeds() (gas: 64946)
SystemConfig_Initialize_TestFail:test_initialize_lowGasLimit_reverts() (gas: 55653)
SystemConfig_Initialize_TestFail:test_initialize_startBlock_reverts() (gas: 78350)
SystemConfig_Initialize_TestFail:test_initialize_lowGasLimit_reverts() (gas: 55655)
SystemConfig_Initialize_TestFail:test_initialize_startBlock_reverts() (gas: 78352)
SystemConfig_Setters_TestFail:test_setBatcherHash_notOwner_reverts() (gas: 15607)
SystemConfig_Setters_TestFail:test_setGasConfig_notOwner_reverts() (gas: 15577)
SystemConfig_Setters_TestFail:test_setGasLimit_notOwner_reverts() (gas: 15676)
......
......@@ -5,7 +5,8 @@
"l1StartingBlockTag": "earliest",
"l1ChainID": 900,
"l2ChainID": 901,
"l2BlockTime": 2,
"l2BlockTime": 1,
"l1BlockTime": 2,
"maxSequencerDrift": 300,
"sequencerWindowSize": 15,
"channelTimeout": 40,
......@@ -35,7 +36,6 @@
"governanceTokenSymbol": "OP",
"governanceTokenOwner": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc",
"finalizationPeriodSeconds": 2,
"numDeployConfirmations": 1,
"eip1559Denominator": 50,
"eip1559Elasticity": 10,
"l2GenesisRegolithTimeOffset": "0x0",
......
{
"finalSystemOwner": "0x858F0751ef8B4067f0d2668C076BDB50a8549fbF",
"portalGuardian": "0x858F0751ef8B4067f0d2668C076BDB50a8549fbF",
"controller": "0x2d30335B0b807bBa1682C487BaAFD2Ad6da5D675",
"l1StartingBlockTag": "0x19c7e6b18fe156e45f4cfef707294fd8f079fa9c30a7b7cd6ec1ce3682ec6a2e",
"l1ChainID": 5,
"l2ChainID": 998,
"l2BlockTime": 2,
"l1BlockTime": 12,
"maxSequencerDrift": 1200,
"sequencerWindowSize": 3600,
......
......@@ -5,6 +5,7 @@
"l1ChainID": 1,
"l2ChainID": 10,
"l2BlockTime": 2,
"l1BlockTime": 12,
"maxSequencerDrift": 600,
"sequencerWindowSize": 3600,
"channelTimeout": 300,
......@@ -32,7 +33,6 @@
"governanceTokenSymbol": "OP",
"governanceTokenOwner": "0x5C4e7Ba1E219E47948e6e3F55019A647bA501005",
"l2GenesisBlockGasLimit": "0x1c9c380",
"l2GenesisBlockCoinbase": "0x4200000000000000000000000000000000000011",
"l2GenesisBlockBaseFeePerGas": "0x3b9aca00",
"gasPriceOracleOverhead": 188,
"gasPriceOracleScalar": 684000,
......
......@@ -23,7 +23,7 @@
"gas-snapshot": "pnpm build:differential && pnpm build:fuzz && forge snapshot --no-match-test 'testDiff|testFuzz|invariant|generateArtifact'",
"storage-snapshot": "./scripts/storage-snapshot.sh",
"semver-lock": "forge script scripts/SemverLock.s.sol",
"validate-deploy-configs": "./scripts/validate-deploy-configs.sh",
"validate-deploy-configs": "./scripts/check-deploy-configs.sh",
"validate-spacers": "pnpm build && npx tsx scripts/validate-spacers.ts",
"slither": "./scripts/slither.sh",
"slither:triage": "TRIAGE_MODE=1 ./scripts/slither.sh",
......
#!/usr/bin/env bash
# This script is used to check for valid deploy configs.
# It should check all configs and return a non-zero exit code if any of them are invalid.
# getting-started.json isn't valid JSON so its skipped.
code=$?
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
CONTRACTS_BASE=$(dirname $SCRIPT_DIR)
MONOREPO_BASE=$(dirname $(dirname $CONTRACTS_BASE))
for config in $CONTRACTS_BASE/deploy-config/*.json; do
if grep -q "getting-started" <<< "$config"; then
echo "Skipping getting-started.json"
continue
fi
go run $MONOREPO_BASE/op-chain-ops/cmd/check-deploy-config/main.go --path $config
[ $? -eq 0 ] || code=$?
done
exit $code
......@@ -83,7 +83,7 @@ contract DisputeGameFactory is OwnableUpgradeable, IDisputeGameFactory, Semver {
bytes calldata _extraData
)
external
returns (IDisputeGame proxy)
returns (IDisputeGame proxy_)
{
// Grab the implementation contract for the given `GameType`.
IDisputeGame impl = gameImpls[_gameType];
......@@ -92,8 +92,8 @@ contract DisputeGameFactory is OwnableUpgradeable, IDisputeGameFactory, Semver {
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)));
proxy.initialize();
proxy_ = IDisputeGame(address(impl).clone(abi.encodePacked(_rootClaim, _extraData)));
proxy_.initialize();
// Compute the unique identifier for the dispute game.
Hash uuid = getGameUUID(_gameType, _rootClaim, _extraData);
......@@ -101,12 +101,12 @@ contract DisputeGameFactory is OwnableUpgradeable, IDisputeGameFactory, Semver {
// If a dispute game with the same UUID already exists, revert.
if (GameId.unwrap(_disputeGames[uuid]) != bytes32(0)) revert GameAlreadyExists(uuid);
GameId id = LibGameId.pack(_gameType, Timestamp.wrap(uint64(block.timestamp)), proxy);
GameId id = LibGameId.pack(_gameType, Timestamp.wrap(uint64(block.timestamp)), proxy_);
// Store the dispute game id in the mapping & emit the `DisputeGameCreated` event.
_disputeGames[uuid] = id;
_disputeGameList.push(id);
emit DisputeGameCreated(address(proxy), _gameType, _rootClaim);
emit DisputeGameCreated(address(proxy_), _gameType, _rootClaim);
}
/// @inheritdoc IDisputeGameFactory
......@@ -117,7 +117,7 @@ contract DisputeGameFactory is OwnableUpgradeable, IDisputeGameFactory, Semver {
)
public
pure
returns (Hash _uuid)
returns (Hash uuid_)
{
assembly {
// Grab the offsets of the other memory locations we will need to temporarily overwrite.
......@@ -141,7 +141,7 @@ contract DisputeGameFactory is OwnableUpgradeable, IDisputeGameFactory, Semver {
let hashLen := and(add(mload(_extraData), 0x9F), not(0x1F))
// Hash the memory to produce the UUID digest
_uuid := keccak256(gameTypeOffset, hashLen)
uuid_ := keccak256(gameTypeOffset, hashLen)
// Restore the memory prior to `extraData`
mstore(gameTypeOffset, tempA)
......
......@@ -26,16 +26,16 @@ interface IDisputeGameFactory {
/// @notice `games` queries an internal mapping that maps the hash of
/// `gameType ++ rootClaim ++ extraData` to the deployed `DisputeGame` clone.
/// @dev `++` equates to concatenation.
/// @param gameType The type of the DisputeGame - used to decide the proxy implementation
/// @param rootClaim The root claim of the DisputeGame.
/// @param extraData Any extra data that should be provided to the created dispute game.
/// @param _gameType The type of the DisputeGame - used to decide the proxy implementation
/// @param _rootClaim The root claim of the DisputeGame.
/// @param _extraData Any extra data that should be provided to the created dispute game.
/// @return proxy_ The clone of the `DisputeGame` created with the given parameters.
/// Returns `address(0)` if nonexistent.
/// @return timestamp_ The timestamp of the creation of the dispute game.
function games(
GameType gameType,
Claim rootClaim,
bytes calldata extraData
GameType _gameType,
Claim _rootClaim,
bytes calldata _extraData
)
external
view
......@@ -56,9 +56,9 @@ interface IDisputeGameFactory {
/// @notice `gameImpls` is a mapping that maps `GameType`s to their respective
/// `IDisputeGame` implementations.
/// @param _gameType The type of the dispute game.
/// @return _impl The address of the implementation of the game type.
/// @return impl_ The address of the implementation of the game type.
/// Will be cloned on creation of a new dispute game with the given `gameType`.
function gameImpls(GameType _gameType) external view returns (IDisputeGame _impl);
function gameImpls(GameType _gameType) external view returns (IDisputeGame impl_);
/// @notice Creates a new DisputeGame proxy contract.
/// @param _gameType The type of the DisputeGame - used to decide the proxy implementation.
......@@ -85,7 +85,7 @@ interface IDisputeGameFactory {
/// @param _gameType The type of the DisputeGame.
/// @param _rootClaim The root claim of the DisputeGame.
/// @param _extraData Any extra data that should be provided to the created dispute game.
/// @return _uuid The unique identifier for the given dispute game parameters.
/// @return uuid_ The unique identifier for the given dispute game parameters.
function getGameUUID(
GameType _gameType,
Claim _rootClaim,
......@@ -93,5 +93,5 @@ interface IDisputeGameFactory {
)
external
pure
returns (Hash _uuid);
returns (Hash uuid_);
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { Constants } from "../libraries/Constants.sol";
/// @title IL1ChugSplashDeployer
interface IL1ChugSplashDeployer {
function isUpgrading() external view returns (bool);
......@@ -21,12 +23,6 @@ contract L1ChugSplashProxy {
/// contract, the appended bytecode will be deployed as given.
bytes13 internal constant DEPLOY_CODE_PREFIX = 0x600D380380600D6000396000f3;
/// @notice bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1)
bytes32 internal constant IMPLEMENTATION_KEY = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
/// @notice bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1)
bytes32 internal constant OWNER_KEY = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
/// @notice Blocks a function from being called when the parent signals that the system should
/// be paused via an isUpgrading function.
modifier onlyWhenNotPaused() {
......@@ -160,16 +156,18 @@ contract L1ChugSplashProxy {
/// @notice Sets the implementation address.
/// @param _implementation New implementation address.
function _setImplementation(address _implementation) internal {
bytes32 proxyImplementation = Constants.PROXY_IMPLEMENTATION_ADDRESS;
assembly {
sstore(IMPLEMENTATION_KEY, _implementation)
sstore(proxyImplementation, _implementation)
}
}
/// @notice Changes the owner of the proxy contract.
/// @param _owner New owner of the proxy contract.
function _setOwner(address _owner) internal {
bytes32 proxyOwner = Constants.PROXY_OWNER_ADDRESS;
assembly {
sstore(OWNER_KEY, _owner)
sstore(proxyOwner, _owner)
}
}
......@@ -203,8 +201,9 @@ contract L1ChugSplashProxy {
/// @return Implementation address.
function _getImplementation() internal view returns (address) {
address implementation;
bytes32 proxyImplementation = Constants.PROXY_IMPLEMENTATION_ADDRESS;
assembly {
implementation := sload(IMPLEMENTATION_KEY)
implementation := sload(proxyImplementation)
}
return implementation;
}
......@@ -213,8 +212,9 @@ contract L1ChugSplashProxy {
/// @return Owner address.
function _getOwner() internal view returns (address) {
address owner;
bytes32 proxyOwner = Constants.PROXY_OWNER_ADDRESS;
assembly {
owner := sload(OWNER_KEY)
owner := sload(proxyOwner)
}
return owner;
}
......
......@@ -21,6 +21,15 @@ library Constants {
/// non-zero to reduce the gas cost of message passing transactions.
address internal constant DEFAULT_L2_SENDER = 0x000000000000000000000000000000000000dEaD;
/// @notice The storage slot that holds the address of a proxy implementation.
/// @dev `bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1)`
bytes32 internal constant PROXY_IMPLEMENTATION_ADDRESS =
0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
/// @notice The storage slot that holds the address of the owner.
/// @dev `bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1)`
bytes32 internal constant PROXY_OWNER_ADDRESS = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
/// @notice Returns the default values for the ResourceConfig. These are the recommended values
/// for a production network.
function DEFAULT_RESOURCE_CONFIG() internal pure returns (ResourceMetering.ResourceConfig memory) {
......
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { Constants } from "../libraries/Constants.sol";
/// @title Proxy
/// @notice Proxy is a transparent proxy that passes through the call if the caller is the owner or
/// if the caller is address(0), meaning that the call originated from an off-chain
/// simulation.
contract Proxy {
/// @notice The storage slot that holds the address of the implementation.
/// bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1)
bytes32 internal constant IMPLEMENTATION_KEY = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
/// @notice The storage slot that holds the address of the owner.
/// bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1)
bytes32 internal constant OWNER_KEY = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
/// @notice An event that is emitted each time the implementation is changed. This event is part
/// of the EIP-1967 specification.
/// @param implementation The address of the implementation contract
......@@ -107,8 +101,9 @@ contract Proxy {
/// @notice Sets the implementation address.
/// @param _implementation New implementation address.
function _setImplementation(address _implementation) internal {
bytes32 proxyImplementation = Constants.PROXY_IMPLEMENTATION_ADDRESS;
assembly {
sstore(IMPLEMENTATION_KEY, _implementation)
sstore(proxyImplementation, _implementation)
}
emit Upgraded(_implementation);
}
......@@ -117,8 +112,9 @@ contract Proxy {
/// @param _admin New owner of the proxy contract.
function _changeAdmin(address _admin) internal {
address previous = _getAdmin();
bytes32 proxyOwner = Constants.PROXY_OWNER_ADDRESS;
assembly {
sstore(OWNER_KEY, _admin)
sstore(proxyOwner, _admin)
}
emit AdminChanged(previous, _admin);
}
......@@ -152,8 +148,9 @@ contract Proxy {
/// @return Implementation address.
function _getImplementation() internal view returns (address) {
address impl;
bytes32 proxyImplementation = Constants.PROXY_IMPLEMENTATION_ADDRESS;
assembly {
impl := sload(IMPLEMENTATION_KEY)
impl := sload(proxyImplementation)
}
return impl;
}
......@@ -162,8 +159,9 @@ contract Proxy {
/// @return Owner address.
function _getAdmin() internal view returns (address) {
address owner;
bytes32 proxyOwner = Constants.PROXY_OWNER_ADDRESS;
assembly {
owner := sload(OWNER_KEY)
owner := sload(proxyOwner)
}
return owner;
}
......
......@@ -5,6 +5,7 @@ import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { Proxy } from "./Proxy.sol";
import { AddressManager } from "../legacy/AddressManager.sol";
import { L1ChugSplashProxy } from "../legacy/L1ChugSplashProxy.sol";
import { Constants } from "../libraries/Constants.sol";
/// @title IStaticERC1967Proxy
/// @notice IStaticERC1967Proxy is a static version of the ERC1967 proxy interface.
......@@ -163,9 +164,7 @@ contract ProxyAdmin is Ownable {
Proxy(_proxy).upgradeTo(_implementation);
} else if (ptype == ProxyType.CHUGSPLASH) {
L1ChugSplashProxy(_proxy).setStorage(
// bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1)
0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc,
bytes32(uint256(uint160(_implementation)))
Constants.PROXY_IMPLEMENTATION_ADDRESS, bytes32(uint256(uint160(_implementation)))
);
} else if (ptype == ProxyType.RESOLVED) {
string memory name = implementationName[_proxy];
......
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol";
import { Constants } from "src/libraries/Constants.sol";
contract Constants_Test is Test {
/// @notice Check EIP1967 related constants.
function test_eip1967Constants_succeeds() external {
assertEq(
bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1), Constants.PROXY_IMPLEMENTATION_ADDRESS
);
assertEq(bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1), Constants.PROXY_OWNER_ADDRESS);
}
}
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment