Commit 1bdd60d1 authored by Adrian Sutton's avatar Adrian Sutton

Merge remote-tracking branch 'origin/develop' into aj/split-providers

parents 2e4de94b c3b17448
......@@ -192,9 +192,13 @@ jobs:
description: Run the release script
type: boolean
default: false
resource_class:
description: Docker resoruce class
type: string
default: medium
machine:
image: ubuntu-2204:2022.07.1
resource_class: medium
resource_class: "<<parameters.resource_class>>"
docker_layer_caching: true # we rely on this for faster builds, and actively warm it up for builds with common stages
steps:
- checkout
......@@ -1668,6 +1672,7 @@ workflows:
docker_tags: <<pipeline.git.revision>>,latest
publish: true
release: true
resource_class: xlarge
context:
- oplabs-gcr
requires:
......
......@@ -7,7 +7,7 @@ toolchain go1.21.1
require github.com/ethereum-optimism/optimism v0.0.0
require (
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/crypto v0.15.0 // indirect
golang.org/x/sys v0.14.0 // indirect
)
......
......@@ -4,8 +4,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
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/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
......
......@@ -33,7 +33,7 @@ flag_management:
individual_flags:
- name: contracts-bedrock-tests
paths:
- packages/contracts-bedrock
- packages/contracts-bedrock/src
statuses:
- type: patch
target: 100%
......
......@@ -33,13 +33,13 @@ require (
github.com/multiformats/go-multiaddr v0.12.0
github.com/multiformats/go-multiaddr-dns v0.3.1
github.com/olekukonko/tablewriter v0.0.5
github.com/onsi/gomega v1.29.0
github.com/onsi/gomega v1.30.0
github.com/pkg/errors v0.9.1
github.com/pkg/profile v1.7.0
github.com/prometheus/client_golang v1.17.0
github.com/stretchr/testify v1.8.4
github.com/urfave/cli/v2 v2.25.7
golang.org/x/crypto v0.14.0
golang.org/x/crypto v0.15.0
golang.org/x/exp v0.0.0-20231006140011-7918f672742d
golang.org/x/sync v0.5.0
golang.org/x/term v0.14.0
......@@ -200,7 +200,7 @@ require (
golang.org/x/mod v0.13.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/sys v0.14.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.14.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
......
......@@ -569,8 +569,8 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/runtime-spec v1.1.0 h1:HHUyrt9mwHUjtasSbXSMvs4cyFxh+Bll4AjJ9odEGpg=
github.com/opencontainers/runtime-spec v1.1.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
......@@ -769,8 +769,8 @@ golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
......@@ -895,8 +895,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -13,7 +13,7 @@ const StorageSetterStorageLayoutJSON = "{\"storage\":null,\"types\":{}}"
var StorageSetterStorageLayout = new(solc.StorageLayout)
var StorageSetterDeployedBin = "0x608060405234801561001057600080fd5b506004361061007d5760003560e01c8063a6ed563e1161005b578063a6ed563e1461011c578063bd02d0f51461011c578063ca446dd914610138578063e2a4853a146100bf57600080fd5b806321f8a721146100825780634e91db08146100bf57806354fd4d50146100d3575b600080fd5b610095610090366004610156565b610146565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b6100d16100cd36600461016f565b9055565b005b61010f6040518060400160405280600581526020017f312e302e3000000000000000000000000000000000000000000000000000000081525081565b6040516100b69190610191565b61012a610090366004610156565b6040519081526020016100b6565b6100d16100cd366004610204565b6000610150825490565b92915050565b60006020828403121561016857600080fd5b5035919050565b6000806040838503121561018257600080fd5b50508035926020909101359150565b600060208083528351808285015260005b818110156101be578581018301518582016040015282016101a2565b818111156101d0576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b6000806040838503121561021757600080fd5b82359150602083013573ffffffffffffffffffffffffffffffffffffffff8116811461024257600080fd5b80915050925092905056fea164736f6c634300080f000a"
var StorageSetterDeployedBin = "0x608060405234801561001057600080fd5b50600436106100885760003560e01c8063a6ed563e1161005b578063a6ed563e1461013a578063bd02d0f51461013a578063ca446dd914610156578063e2a4853a146100df57600080fd5b80630528afe21461008d57806321f8a721146100a25780634e91db08146100df57806354fd4d50146100f1575b600080fd5b6100a061009b3660046101d7565b610164565b005b6100b56100b036600461024c565b6101c7565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b6100a06100ed366004610265565b9055565b61012d6040518060400160405280600581526020017f312e312e3000000000000000000000000000000000000000000000000000000081525081565b6040516100d69190610287565b6101486100b036600461024c565b6040519081526020016100d6565b6100a06100ed3660046102fa565b8060005b818110156101c1576101af84848381811061018557610185610343565b905060400201600001358585848181106101a1576101a1610343565b905060400201602001359055565b806101b981610372565b915050610168565b50505050565b60006101d1825490565b92915050565b600080602083850312156101ea57600080fd5b823567ffffffffffffffff8082111561020257600080fd5b818501915085601f83011261021657600080fd5b81358181111561022557600080fd5b8660208260061b850101111561023a57600080fd5b60209290920196919550909350505050565b60006020828403121561025e57600080fd5b5035919050565b6000806040838503121561027857600080fd5b50508035926020909101359150565b600060208083528351808285015260005b818110156102b457858101830151858201604001528201610298565b818111156102c6576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b6000806040838503121561030d57600080fd5b82359150602083013573ffffffffffffffffffffffffffffffffffffffff8116811461033857600080fd5b809150509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036103ca577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b506001019056fea164736f6c634300080f000a"
func init() {
if err := json.Unmarshal([]byte(StorageSetterStorageLayoutJSON), StorageSetterStorageLayout); err != nil {
......
......@@ -388,41 +388,28 @@ func (d *DeployConfig) SetDeployments(deployments *L1Deployments) {
}
// GetDeployedAddresses will get the deployed addresses of deployed L1 contracts
// required for the L2 genesis creation. Legacy systems use the `Proxy__` prefix
// while modern systems use the `Proxy` suffix. First check for the legacy
// deployments so that this works with upgrading a system.
// required for the L2 genesis creation.
func (d *DeployConfig) GetDeployedAddresses(hh *hardhat.Hardhat) error {
var err error
if d.L1StandardBridgeProxy == (common.Address{}) {
var l1StandardBridgeProxyDeployment *hardhat.Deployment
l1StandardBridgeProxyDeployment, err = hh.GetDeployment("Proxy__OVM_L1StandardBridge")
if errors.Is(err, hardhat.ErrCannotFindDeployment) {
l1StandardBridgeProxyDeployment, err = hh.GetDeployment("L1StandardBridgeProxy")
l1StandardBridgeProxyDeployment, err := hh.GetDeployment("L1StandardBridgeProxy")
if err != nil {
return err
}
return fmt.Errorf("cannot find L1StandardBridgeProxy artifact: %w", err)
}
d.L1StandardBridgeProxy = l1StandardBridgeProxyDeployment.Address
}
if d.L1CrossDomainMessengerProxy == (common.Address{}) {
var l1CrossDomainMessengerProxyDeployment *hardhat.Deployment
l1CrossDomainMessengerProxyDeployment, err = hh.GetDeployment("Proxy__OVM_L1CrossDomainMessenger")
if errors.Is(err, hardhat.ErrCannotFindDeployment) {
l1CrossDomainMessengerProxyDeployment, err = hh.GetDeployment("L1CrossDomainMessengerProxy")
l1CrossDomainMessengerProxyDeployment, err := hh.GetDeployment("L1CrossDomainMessengerProxy")
if err != nil {
return err
}
return fmt.Errorf("cannot find L1CrossDomainMessengerProxy artifact: %w", err)
}
d.L1CrossDomainMessengerProxy = l1CrossDomainMessengerProxyDeployment.Address
}
if d.L1ERC721BridgeProxy == (common.Address{}) {
// There is no legacy deployment of this contract
l1ERC721BridgeProxyDeployment, err := hh.GetDeployment("L1ERC721BridgeProxy")
if err != nil {
return err
return fmt.Errorf("cannot find L1ERC721BridgeProxy artifact: %w", err)
}
d.L1ERC721BridgeProxy = l1ERC721BridgeProxyDeployment.Address
}
......@@ -430,7 +417,7 @@ func (d *DeployConfig) GetDeployedAddresses(hh *hardhat.Hardhat) error {
if d.SystemConfigProxy == (common.Address{}) {
systemConfigProxyDeployment, err := hh.GetDeployment("SystemConfigProxy")
if err != nil {
return err
return fmt.Errorf("cannot find SystemConfigProxy artifact: %w", err)
}
d.SystemConfigProxy = systemConfigProxyDeployment.Address
}
......@@ -438,7 +425,7 @@ func (d *DeployConfig) GetDeployedAddresses(hh *hardhat.Hardhat) error {
if d.OptimismPortalProxy == (common.Address{}) {
optimismPortalProxyDeployment, err := hh.GetDeployment("OptimismPortalProxy")
if err != nil {
return err
return fmt.Errorf("cannot find OptimismPortalProxy artifact: %w", err)
}
d.OptimismPortalProxy = optimismPortalProxyDeployment.Address
}
......
......@@ -9,6 +9,7 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common"
)
......@@ -21,6 +22,11 @@ const (
methodClaim = "claimData"
methodL1Head = "l1Head"
methodProposals = "proposals"
methodResolve = "resolve"
methodResolveClaim = "resolveClaim"
methodAttack = "attack"
methodDefend = "defend"
methodStep = "step"
)
type FaultDisputeGameContract struct {
......@@ -138,6 +144,57 @@ func (f *FaultDisputeGameContract) GetAllClaims(ctx context.Context) ([]types.Cl
return claims, nil
}
func (f *FaultDisputeGameContract) AttackTx(parentContractIndex uint64, pivot common.Hash) (txmgr.TxCandidate, error) {
call := f.contract.Call(methodAttack, new(big.Int).SetUint64(parentContractIndex), pivot)
return call.ToTxCandidate()
}
func (f *FaultDisputeGameContract) DefendTx(parentContractIndex uint64, pivot common.Hash) (txmgr.TxCandidate, error) {
call := f.contract.Call(methodDefend, new(big.Int).SetUint64(parentContractIndex), pivot)
return call.ToTxCandidate()
}
func (f *FaultDisputeGameContract) StepTx(claimIdx uint64, isAttack bool, stateData []byte, proof []byte) (txmgr.TxCandidate, error) {
call := f.contract.Call(methodStep, new(big.Int).SetUint64(claimIdx), isAttack, stateData, proof)
return call.ToTxCandidate()
}
func (f *FaultDisputeGameContract) CallResolveClaim(ctx context.Context, claimIdx uint64) error {
call := f.resolveClaimCall(claimIdx)
_, err := f.multiCaller.SingleCall(ctx, batching.BlockLatest, call)
if err != nil {
return fmt.Errorf("failed to call resolve claim: %w", err)
}
return nil
}
func (f *FaultDisputeGameContract) ResolveClaimTx(claimIdx uint64) (txmgr.TxCandidate, error) {
call := f.resolveClaimCall(claimIdx)
return call.ToTxCandidate()
}
func (f *FaultDisputeGameContract) resolveClaimCall(claimIdx uint64) *batching.ContractCall {
return f.contract.Call(methodResolveClaim, new(big.Int).SetUint64(claimIdx))
}
func (f *FaultDisputeGameContract) CallResolve(ctx context.Context) (gameTypes.GameStatus, error) {
call := f.resolveCall()
result, err := f.multiCaller.SingleCall(ctx, batching.BlockLatest, call)
if err != nil {
return gameTypes.GameStatusInProgress, fmt.Errorf("failed to call resolve: %w", err)
}
return gameTypes.GameStatusFromUint8(result.GetUint8(0))
}
func (f *FaultDisputeGameContract) ResolveTx() (txmgr.TxCandidate, error) {
call := f.resolveCall()
return call.ToTxCandidate()
}
func (f *FaultDisputeGameContract) resolveCall() *batching.ContractCall {
return f.contract.Call(methodResolve)
}
func (f *FaultDisputeGameContract) decodeClaim(result *batching.CallResult, contractIndex int) types.Claim {
parentIndex := result.GetUint32(0)
countered := result.GetBool(1)
......
......@@ -67,6 +67,13 @@ func TestSimpleGetters(t *testing.T) {
return game.GetL1Head(context.Background())
},
},
{
method: methodResolve,
result: types.GameStatusInProgress,
call: func(game *FaultDisputeGameContract) (any, error) {
return game.CallResolve(context.Background())
},
},
}
for _, test := range tests {
test := test
......@@ -176,6 +183,57 @@ func TestGetAllClaims(t *testing.T) {
require.Equal(t, expectedClaims, claims)
}
func TestCallResolveClaim(t *testing.T) {
stubRpc, game := setup(t)
stubRpc.SetResponse(methodResolveClaim, batching.BlockLatest, []interface{}{big.NewInt(123)}, nil)
err := game.CallResolveClaim(context.Background(), 123)
require.NoError(t, err)
}
func TestResolveClaimTx(t *testing.T) {
stubRpc, game := setup(t)
stubRpc.SetResponse(methodResolveClaim, batching.BlockLatest, []interface{}{big.NewInt(123)}, nil)
tx, err := game.ResolveClaimTx(123)
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
}
func TestResolveTx(t *testing.T) {
stubRpc, game := setup(t)
stubRpc.SetResponse(methodResolve, batching.BlockLatest, nil, nil)
tx, err := game.ResolveTx()
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
}
func TestAttackTx(t *testing.T) {
stubRpc, game := setup(t)
value := common.Hash{0xaa}
stubRpc.SetResponse(methodAttack, batching.BlockLatest, []interface{}{big.NewInt(111), value}, nil)
tx, err := game.AttackTx(111, value)
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
}
func TestDefendTx(t *testing.T) {
stubRpc, game := setup(t)
value := common.Hash{0xaa}
stubRpc.SetResponse(methodDefend, batching.BlockLatest, []interface{}{big.NewInt(111), value}, nil)
tx, err := game.DefendTx(111, value)
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
}
func TestStepTx(t *testing.T) {
stubRpc, game := setup(t)
stateData := []byte{1, 2, 3}
proofData := []byte{4, 5, 6, 7, 8, 9}
stubRpc.SetResponse(methodStep, batching.BlockLatest, []interface{}{big.NewInt(111), true, stateData, proofData}, nil)
tx, err := game.StepTx(111, true, stateData, proofData)
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
}
func expectGetClaim(stubRpc *batchingTest.AbiBasedRpc, claim faultTypes.Claim) {
stubRpc.SetResponse(
methodClaim,
......
......@@ -8,7 +8,6 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/responder"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-challenger/metrics"
......@@ -26,6 +25,10 @@ type GameInfo interface {
GetClaimCount(context.Context) (uint64, error)
}
// gameValidator checks that the specific game instance is compatible with the configuration.
// Typically, this is done by verifying the absolute prestate of the game matches the local absolute prestate.
type gameValidator func(ctx context.Context, gameContract *contracts.FaultDisputeGameContract) error
type GamePlayer struct {
act actor
agreeWithProposedOutput bool
......@@ -34,7 +37,7 @@ type GamePlayer struct {
status gameTypes.GameStatus
}
type resourceCreator func(addr common.Address, contract *contracts.FaultDisputeGameContract, gameDepth uint64, dir string) (types.TraceProvider, types.OracleUpdater, error)
type resourceCreator func(addr common.Address, contract *contracts.FaultDisputeGameContract, gameDepth uint64, dir string) (types.TraceAccessor, types.OracleUpdater, gameValidator, error)
func NewGamePlayer(
ctx context.Context,
......@@ -77,21 +80,20 @@ func NewGamePlayer(
return nil, fmt.Errorf("failed to fetch the game depth: %w", err)
}
provider, updater, err := creator(addr, loader, gameDepth, dir)
accessor, updater, validator, err := creator(addr, loader, gameDepth, dir)
if err != nil {
return nil, fmt.Errorf("failed to create trace provider: %w", err)
return nil, fmt.Errorf("failed to create trace accessor: %w", err)
}
if err := ValidateAbsolutePrestate(ctx, provider, loader); err != nil {
if err := validator(ctx, loader); err != nil {
return nil, fmt.Errorf("failed to validate absolute prestate: %w", err)
}
responder, err := responder.NewFaultResponder(logger, txMgr, addr)
responder, err := responder.NewFaultResponder(logger, txMgr, loader)
if err != nil {
return nil, fmt.Errorf("failed to create the responder: %w", err)
}
accessor := trace.NewSimpleTraceAccessor(provider)
agent := NewAgent(m, loader, int(gameDepth), accessor, responder, updater, cfg.AgreeWithProposedOutput, logger)
return &GamePlayer{
act: agent.Act,
......
......@@ -6,6 +6,7 @@ import (
"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/trace"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/alphabet"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/cannon"
faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
......@@ -37,16 +38,19 @@ func RegisterGameTypes(
client *ethclient.Client,
) {
if cfg.TraceTypeEnabled(config.TraceTypeCannon) {
resourceCreator := func(addr common.Address, contract *contracts.FaultDisputeGameContract, gameDepth uint64, dir string) (faultTypes.TraceProvider, faultTypes.OracleUpdater, 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, contract, dir, gameDepth)
if err != nil {
return nil, nil, fmt.Errorf("create cannon trace provider: %w", err)
return nil, nil, nil, fmt.Errorf("create cannon trace provider: %w", err)
}
updater, err := cannon.NewOracleUpdater(ctx, logger, txMgr, addr, client)
if err != nil {
return nil, nil, fmt.Errorf("failed to create the cannon updater: %w", err)
return nil, nil, nil, fmt.Errorf("failed to create the cannon updater: %w", err)
}
return provider, updater, nil
validator := func(ctx context.Context, contract *contracts.FaultDisputeGameContract) error {
return ValidateAbsolutePrestate(ctx, provider, contract)
}
return trace.NewSimpleTraceAccessor(provider), updater, validator, nil
}
playerCreator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) {
return NewGamePlayer(ctx, logger, m, cfg, dir, game.Proxy, txMgr, client, resourceCreator)
......@@ -54,10 +58,13 @@ func RegisterGameTypes(
registry.RegisterGameType(cannonGameType, playerCreator)
}
if cfg.TraceTypeEnabled(config.TraceTypeAlphabet) {
resourceCreator := func(addr common.Address, contract *contracts.FaultDisputeGameContract, gameDepth uint64, dir string) (faultTypes.TraceProvider, faultTypes.OracleUpdater, 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)
updater := alphabet.NewOracleUpdater(logger)
return provider, updater, nil
validator := func(ctx context.Context, contract *contracts.FaultDisputeGameContract) error {
return ValidateAbsolutePrestate(ctx, provider, contract)
}
return trace.NewSimpleTraceAccessor(provider), updater, validator, nil
}
playerCreator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) {
return NewGamePlayer(ctx, logger, m, cfg, dir, game.Proxy, txMgr, client, resourceCreator)
......
package responder
import (
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"github.com/stretchr/testify/require"
)
// setupFaultDisputeGame deploys the FaultDisputeGame contract to a simulated backend
func setupFaultDisputeGame() (common.Address, *bind.TransactOpts, *backends.SimulatedBackend, *bindings.FaultDisputeGame, error) {
privateKey, err := crypto.GenerateKey()
from := crypto.PubkeyToAddress(privateKey.PublicKey)
if err != nil {
return common.Address{}, nil, nil, nil, err
}
opts, err := bind.NewKeyedTransactorWithChainID(privateKey, big.NewInt(1337))
if err != nil {
return common.Address{}, nil, nil, nil, err
}
backend := backends.NewSimulatedBackend(
core.GenesisAlloc{from: {Balance: big.NewInt(params.Ether)}},
50_000_000,
)
blockHashOracle, _, _, err := bindings.DeployBlockOracle(opts, backend)
if err != nil {
return common.Address{}, nil, nil, nil, err
}
_, _, contract, err := bindings.DeployFaultDisputeGame(
opts,
backend,
uint8(0), // Game Type ID
[32]byte{0x01}, // Absolute Prestate Claim
big.NewInt(15), // Max Game Depth
uint64(604800), // 7 days
common.Address{0xdd}, // VM
common.Address{0xee}, // L2OutputOracle (Not used in Alphabet Game)
blockHashOracle, // Block hash oracle
)
if err != nil {
return common.Address{}, nil, nil, nil, err
}
return from, opts, backend, contract, nil
}
// TestBuildFaultDefendData ensures that the manual ABI packing is the same as going through the bound contract.
func TestBuildFaultDefendData(t *testing.T) {
_, opts, _, contract, err := setupFaultDisputeGame()
require.NoError(t, err)
resp, _ := newTestFaultResponder(t)
data, err := resp.buildFaultDefendData(1, [32]byte{0x02, 0x03})
require.NoError(t, err)
opts.GasLimit = 100_000
tx, err := contract.Defend(opts, big.NewInt(1), [32]byte{0x02, 0x03})
require.NoError(t, err)
require.Equal(t, data, tx.Data())
}
// TestBuildFaultAttackData ensures that the manual ABI packing is the same as going through the bound contract.
func TestBuildFaultAttackData(t *testing.T) {
_, opts, _, contract, err := setupFaultDisputeGame()
require.NoError(t, err)
resp, _ := newTestFaultResponder(t)
data, err := resp.buildFaultAttackData(1, [32]byte{0x02, 0x03})
require.NoError(t, err)
opts.GasLimit = 100_000
tx, err := contract.Attack(opts, big.NewInt(1), [32]byte{0x02, 0x03})
require.NoError(t, err)
require.Equal(t, data, tx.Data())
}
// TestBuildFaultStepData ensures that the manual ABI packing is the same as going through the bound contract.
func TestBuildFaultStepData(t *testing.T) {
_, opts, _, contract, err := setupFaultDisputeGame()
require.NoError(t, err)
resp, _ := newTestFaultResponder(t)
data, err := resp.buildStepTxData(2, false, []byte{0x01}, []byte{0x02})
require.NoError(t, err)
opts.GasLimit = 100_000
tx, err := contract.Step(opts, big.NewInt(2), false, []byte{0x01}, []byte{0x02})
require.NoError(t, err)
require.Equal(t, data, tx.Data())
}
......@@ -2,153 +2,97 @@ package responder
import (
"context"
"math/big"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
)
type GameContract interface {
CallResolve(ctx context.Context) (gameTypes.GameStatus, error)
ResolveTx() (txmgr.TxCandidate, error)
CallResolveClaim(ctx context.Context, claimIdx uint64) error
ResolveClaimTx(claimIdx uint64) (txmgr.TxCandidate, error)
AttackTx(parentContractIndex uint64, pivot common.Hash) (txmgr.TxCandidate, error)
DefendTx(parentContractIndex uint64, pivot common.Hash) (txmgr.TxCandidate, error)
StepTx(claimIdx uint64, isAttack bool, stateData []byte, proof []byte) (txmgr.TxCandidate, error)
}
// FaultResponder implements the [Responder] interface to send onchain transactions.
type FaultResponder struct {
log log.Logger
txMgr txmgr.TxManager
fdgAddr common.Address
fdgAbi *abi.ABI
contract GameContract
}
// NewFaultResponder returns a new [FaultResponder].
func NewFaultResponder(logger log.Logger, txManagr txmgr.TxManager, fdgAddr common.Address) (*FaultResponder, error) {
fdgAbi, err := bindings.FaultDisputeGameMetaData.GetAbi()
if err != nil {
return nil, err
}
func NewFaultResponder(logger log.Logger, txMgr txmgr.TxManager, contract GameContract) (*FaultResponder, error) {
return &FaultResponder{
log: logger,
txMgr: txManagr,
fdgAddr: fdgAddr,
fdgAbi: fdgAbi,
txMgr: txMgr,
contract: contract,
}, nil
}
// buildFaultDefendData creates the transaction data for the Defend function.
func (r *FaultResponder) buildFaultDefendData(parentContractIndex int, pivot [32]byte) ([]byte, error) {
return r.fdgAbi.Pack(
"defend",
big.NewInt(int64(parentContractIndex)),
pivot,
)
}
// buildFaultAttackData creates the transaction data for the Attack function.
func (r *FaultResponder) buildFaultAttackData(parentContractIndex int, pivot [32]byte) ([]byte, error) {
return r.fdgAbi.Pack(
"attack",
big.NewInt(int64(parentContractIndex)),
pivot,
)
}
// buildResolveData creates the transaction data for the Resolve function.
func (r *FaultResponder) buildResolveData() ([]byte, error) {
return r.fdgAbi.Pack("resolve")
}
// CallResolve determines if the resolve function on the fault dispute game contract
// would succeed. Returns the game status if the call would succeed, errors otherwise.
func (r *FaultResponder) CallResolve(ctx context.Context) (gameTypes.GameStatus, error) {
txData, err := r.buildResolveData()
if err != nil {
return gameTypes.GameStatusInProgress, err
}
res, err := r.txMgr.Call(ctx, ethereum.CallMsg{
To: &r.fdgAddr,
Data: txData,
}, nil)
if err != nil {
return gameTypes.GameStatusInProgress, err
}
var status uint8
if err = r.fdgAbi.UnpackIntoInterface(&status, "resolve", res); err != nil {
return gameTypes.GameStatusInProgress, err
}
return gameTypes.GameStatusFromUint8(status)
return r.contract.CallResolve(ctx)
}
// Resolve executes a resolve transaction to resolve a fault dispute game.
func (r *FaultResponder) Resolve(ctx context.Context) error {
txData, err := r.buildResolveData()
candidate, err := r.contract.ResolveTx()
if err != nil {
return err
}
return r.sendTxAndWait(ctx, txData)
}
// buildResolveClaimData creates the transaction data for the ResolveClaim function.
func (r *FaultResponder) buildResolveClaimData(claimIdx uint64) ([]byte, error) {
return r.fdgAbi.Pack("resolveClaim", big.NewInt(int64(claimIdx)))
return r.sendTxAndWait(ctx, candidate)
}
// CallResolveClaim determines if the resolveClaim function on the fault dispute game contract
// would succeed.
func (r *FaultResponder) CallResolveClaim(ctx context.Context, claimIdx uint64) error {
txData, err := r.buildResolveClaimData(claimIdx)
if err != nil {
return err
}
_, err = r.txMgr.Call(ctx, ethereum.CallMsg{
To: &r.fdgAddr,
Data: txData,
}, nil)
return err
return r.contract.CallResolveClaim(ctx, claimIdx)
}
// ResolveClaim executes a resolveClaim transaction to resolve a fault dispute game.
func (r *FaultResponder) ResolveClaim(ctx context.Context, claimIdx uint64) error {
txData, err := r.buildResolveClaimData(claimIdx)
candidate, err := r.contract.ResolveClaimTx(claimIdx)
if err != nil {
return err
}
return r.sendTxAndWait(ctx, txData)
return r.sendTxAndWait(ctx, candidate)
}
func (r *FaultResponder) PerformAction(ctx context.Context, action types.Action) error {
var txData []byte
var candidate txmgr.TxCandidate
var err error
switch action.Type {
case types.ActionTypeMove:
if action.IsAttack {
txData, err = r.buildFaultAttackData(action.ParentIdx, action.Value)
candidate, err = r.contract.AttackTx(uint64(action.ParentIdx), action.Value)
} else {
txData, err = r.buildFaultDefendData(action.ParentIdx, action.Value)
candidate, err = r.contract.DefendTx(uint64(action.ParentIdx), action.Value)
}
case types.ActionTypeStep:
txData, err = r.buildStepTxData(uint64(action.ParentIdx), action.IsAttack, action.PreState, action.ProofData)
candidate, err = r.contract.StepTx(uint64(action.ParentIdx), action.IsAttack, action.PreState, action.ProofData)
}
if err != nil {
return err
}
return r.sendTxAndWait(ctx, txData)
return r.sendTxAndWait(ctx, candidate)
}
// sendTxAndWait sends a transaction through the [txmgr] and waits for a receipt.
// This sets the tx GasLimit to 0, performing gas estimation online through the [txmgr].
func (r *FaultResponder) sendTxAndWait(ctx context.Context, txData []byte) error {
receipt, err := r.txMgr.Send(ctx, txmgr.TxCandidate{
To: &r.fdgAddr,
TxData: txData,
GasLimit: 0,
})
func (r *FaultResponder) sendTxAndWait(ctx context.Context, candidate txmgr.TxCandidate) error {
receipt, err := r.txMgr.Send(ctx, candidate)
if err != nil {
return err
}
......@@ -159,14 +103,3 @@ func (r *FaultResponder) sendTxAndWait(ctx context.Context, txData []byte) error
}
return nil
}
// buildStepTxData creates the transaction data for the step function.
func (r *FaultResponder) buildStepTxData(claimIdx uint64, isAttack bool, stateData []byte, proof []byte) ([]byte, error) {
return r.fdgAbi.Pack(
"step",
big.NewInt(int64(claimIdx)),
isAttack,
stateData,
proof,
)
}
......@@ -3,7 +3,6 @@ package cannon
import (
"context"
"errors"
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
......@@ -11,7 +10,6 @@ import (
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/common"
......@@ -47,10 +45,6 @@ func (m *mockTxManager) Send(ctx context.Context, candidate txmgr.TxCandidate) (
), nil
}
func (m *mockTxManager) Call(_ context.Context, _ ethereum.CallMsg, _ *big.Int) ([]byte, error) {
panic("not implemented")
}
func (m *mockTxManager) BlockNumber(ctx context.Context) (uint64, error) {
panic("not implemented")
}
......
package trace
import (
"context"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum/go-ethereum/common"
)
type translatingProvider struct {
parentDepth uint64
provider types.TraceProvider
}
func Translate(provider types.TraceProvider, parentDepth uint64) types.TraceProvider {
return &translatingProvider{
parentDepth: parentDepth,
provider: provider,
}
}
func (p translatingProvider) Get(ctx context.Context, pos types.Position) (common.Hash, error) {
relativePos, err := pos.RelativeToAncestorAtDepth(p.parentDepth)
if err != nil {
return common.Hash{}, err
}
return p.provider.Get(ctx, relativePos)
}
func (p translatingProvider) GetStepData(ctx context.Context, pos types.Position) (prestate []byte, proofData []byte, preimageData *types.PreimageOracleData, err error) {
relativePos, err := pos.RelativeToAncestorAtDepth(p.parentDepth)
if err != nil {
return nil, nil, nil, err
}
return p.provider.GetStepData(ctx, relativePos)
}
func (p translatingProvider) AbsolutePreStateCommitment(ctx context.Context) (hash common.Hash, err error) {
return p.provider.AbsolutePreStateCommitment(ctx)
}
var _ types.TraceProvider = (*translatingProvider)(nil)
package trace
import (
"context"
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/alphabet"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/stretchr/testify/require"
)
func TestTranslate(t *testing.T) {
orig := alphabet.NewTraceProvider("abcdefghij", 4)
translated := Translate(orig, 3)
// All nodes on the first translated layer, map to GIndex 1
for i := int64(8); i <= 15; i++ {
requireSameValue(t, orig, 1, translated, i)
}
// Nodes on the second translated layer map to GIndex 2 and 3 alternately
for i := int64(16); i <= 31; i += 2 {
requireSameValue(t, orig, 2, translated, i)
requireSameValue(t, orig, 3, translated, i+1)
}
// Nodes on the third translated layer map to GIndex 4, 5, 6 and 7
for i := int64(32); i <= 61; i += 4 {
requireSameValue(t, orig, 4, translated, i)
requireSameValue(t, orig, 5, translated, i+1)
requireSameValue(t, orig, 6, translated, i+2)
requireSameValue(t, orig, 7, translated, i+3)
}
}
func requireSameValue(t *testing.T, a types.TraceProvider, aGIdx int64, b types.TraceProvider, bGIdx int64) {
// Check Get returns the same results
aValue, err := a.Get(context.Background(), types.NewPositionFromGIndex(big.NewInt(aGIdx)))
require.NoError(t, err)
bValue, err := b.Get(context.Background(), types.NewPositionFromGIndex(big.NewInt(bGIdx)))
require.NoError(t, err)
require.Equal(t, aValue, bValue)
// Check GetStepData returns the same results
aPrestate, aProofData, aPreimageData, err := a.GetStepData(context.Background(), types.NewPositionFromGIndex(big.NewInt(aGIdx)))
require.NoError(t, err)
bPrestate, bProofData, bPreimageData, err := b.GetStepData(context.Background(), types.NewPositionFromGIndex(big.NewInt(bGIdx)))
require.NoError(t, err)
require.Equal(t, aPrestate, bPrestate)
require.Equal(t, aProofData, bProofData)
require.Equal(t, aPreimageData, bPreimageData)
}
func TestTranslate_AbsolutePreStateCommitment(t *testing.T) {
orig := alphabet.NewTraceProvider("abcdefghij", 4)
translated := Translate(orig, 3)
origValue, err := orig.AbsolutePreStateCommitment(context.Background())
require.NoError(t, err)
translatedValue, err := translated.AbsolutePreStateCommitment(context.Background())
require.NoError(t, err)
require.Equal(t, origValue, translatedValue)
}
......@@ -48,9 +48,6 @@ type fakeTxMgr struct {
func (f fakeTxMgr) From() common.Address {
return f.from
}
func (f fakeTxMgr) Call(_ context.Context, _ ethereum.CallMsg, _ *big.Int) ([]byte, error) {
panic("unimplemented")
}
func (f fakeTxMgr) BlockNumber(_ context.Context) (uint64, error) {
panic("unimplemented")
}
......
......@@ -28,7 +28,7 @@ var Subcommands = cli.Commands{
Flags: []cli.Flag{
&cli.StringFlag{
Name: "deploy-config",
Usage: "Path to hardhat deploy config file",
Usage: "Path to deploy config file",
Required: true,
},
&cli.StringFlag{
......@@ -91,6 +91,9 @@ var Subcommands = cli.Commands{
{
Name: "l2",
Usage: "Generates an L2 genesis file and rollup config suitable for a deployed network",
Description: "Generating the L2 genesis depends on knowledge of L1 contract addresses for the bridge to be secure. " +
"A deploy config and either a deployment directory or an L1 deployments file are used to create the L2 genesis. " +
"The deploy directory and L1 deployments file are generated by the L1 contract deployments.",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "l1-rpc",
......@@ -99,10 +102,15 @@ var Subcommands = cli.Commands{
&cli.StringFlag{
Name: "deploy-config",
Usage: "Path to deploy config file",
Required: true,
},
&cli.StringFlag{
Name: "deployment-dir",
Usage: "Path to network deployment directory",
Usage: "Path to network deployment directory. Cannot be used with --l1-deployments",
},
&cli.StringFlag{
Name: "l1-deployments",
Usage: "Path to L1 deployments JSON file. Cannot be used with --deployment-dir",
},
&cli.StringFlag{
Name: "outfile.l2",
......@@ -122,10 +130,16 @@ var Subcommands = cli.Commands{
}
deployDir := ctx.String("deployment-dir")
if deployDir == "" {
return errors.New("Must specify --deployment-dir")
l1Deployments := ctx.String("l1-deployments")
if deployDir != "" && l1Deployments != "" {
return errors.New("cannot specify both --deployment-dir and --l1-deployments")
}
if deployDir == "" && l1Deployments == "" {
return errors.New("must specify either --deployment-dir or --l1-deployments")
}
if deployDir != "" {
log.Info("Deployment directory", "path", deployDir)
depPath, network := filepath.Split(deployDir)
hh, err := hardhat.New(network, nil, []string{depPath})
......@@ -137,6 +151,16 @@ var Subcommands = cli.Commands{
if err := config.GetDeployedAddresses(hh); err != nil {
return err
}
}
if l1Deployments != "" {
log.Info("L1 deployments", "path", l1Deployments)
deployments, err := genesis.NewL1Deployments(l1Deployments)
if err != nil {
return err
}
config.SetDeployments(deployments)
}
client, err := ethclient.Dial(ctx.String("l1-rpc"))
if err != nil {
......
......@@ -4,6 +4,7 @@ import (
"fmt"
"math/big"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
......@@ -86,6 +87,17 @@ func toCallArg(msg ethereum.CallMsg) interface{} {
return arg
}
func (c *ContractCall) ToTxCandidate() (txmgr.TxCandidate, error) {
data, err := c.Pack()
if err != nil {
return txmgr.TxCandidate{}, fmt.Errorf("failed to pack arguments: %w", err)
}
return txmgr.TxCandidate{
TxData: data,
To: &c.Addr,
}, nil
}
type CallResult struct {
out []interface{}
}
......
......@@ -30,6 +30,22 @@ func TestContractCall_ToCallArgs(t *testing.T) {
require.NotContains(t, argMap, "gasPrice")
}
func TestContractCall_ToTxCandidate(t *testing.T) {
addr := common.Address{0xbd}
testAbi, err := bindings.ERC20MetaData.GetAbi()
require.NoError(t, err)
call := NewContractCall(testAbi, addr, "approve", common.Address{0xcc}, big.NewInt(1234444))
candidate, err := call.ToTxCandidate()
require.NoError(t, err)
require.Equal(t, candidate.To, &addr)
expectedData, err := call.Pack()
require.NoError(t, err)
require.Equal(t, candidate.TxData, expectedData)
require.Nil(t, candidate.Value)
require.Zero(t, candidate.GasLimit)
}
func TestContractCall_Pack(t *testing.T) {
addr := common.Address{0xbd}
testAbi, err := bindings.ERC20MetaData.GetAbi()
......
......@@ -8,6 +8,7 @@ import (
"testing"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
......@@ -73,6 +74,11 @@ func (l *AbiBasedRpc) BatchCallContext(ctx context.Context, b []rpc.BatchElem) e
return errors.Join(errs...)
}
func (l *AbiBasedRpc) VerifyTxCandidate(candidate txmgr.TxCandidate) {
require.EqualValues(l.t, &l.addr, candidate.To, "Incorrect To address")
l.findExpectedCall(candidate.TxData, batching.BlockLatest.ArgValue())
}
func (l *AbiBasedRpc) CallContext(_ context.Context, out interface{}, method string, args ...interface{}) error {
require.Equal(l.t, "eth_call", method)
require.Len(l.t, args, 2)
......@@ -82,11 +88,27 @@ func (l *AbiBasedRpc) CallContext(_ context.Context, out interface{}, method str
require.Equal(l.t, &l.addr, callOpts["to"])
data, ok := callOpts["input"].(hexutil.Bytes)
require.True(l.t, ok)
call, abiMethod := l.findExpectedCall(data, actualBlockRef)
output, err := abiMethod.Outputs.Pack(call.outputs...)
require.NoErrorf(l.t, err, "Invalid outputs for method %v: %v", abiMethod.Name, call.outputs)
// I admit I do not understand Go reflection.
// So leverage json.Unmarshal to set the out value correctly.
j, err := json.Marshal(hexutil.Bytes(output))
require.NoError(l.t, err)
require.NoError(l.t, json.Unmarshal(j, out))
return nil
}
func (l *AbiBasedRpc) findExpectedCall(data []byte, actualBlockRef interface{}) (*expectedCall, *abi.Method) {
abiMethod, err := l.abi.MethodById(data[0:4])
require.NoError(l.t, err)
argData := data[4:]
args, err = abiMethod.Inputs.Unpack(argData)
args, err := abiMethod.Inputs.Unpack(argData)
require.NoError(l.t, err)
require.Len(l.t, args, len(abiMethod.Inputs))
......@@ -100,14 +122,5 @@ func (l *AbiBasedRpc) CallContext(_ context.Context, out interface{}, method str
}
}
require.NotNilf(l.t, call, "No expected calls to %v at block %v with arguments: %v\nExpected calls: %v", abiMethod.Name, actualBlockRef, args, expectedCalls)
output, err := abiMethod.Outputs.Pack(call.outputs...)
require.NoErrorf(l.t, err, "Invalid outputs for method %v: %v", abiMethod.Name, call.outputs)
// I admit I do not understand Go reflection.
// So leverage json.Unmarshal to set the out value correctly.
j, err := json.Marshal(hexutil.Bytes(output))
require.NoError(l.t, err)
require.NoError(l.t, json.Unmarshal(j, out))
return nil
return call, abiMethod
}
......@@ -51,20 +51,21 @@ func NewEngineClient(client client.RPC, log log.Logger, metrics caching.Metrics,
// 2. `error` as eth.InputError: the forkchoice state or attributes are not valid.
// 3. Other types of `error`: temporary RPC errors, like timeouts.
func (s *EngineClient) ForkchoiceUpdate(ctx context.Context, fc *eth.ForkchoiceState, attributes *eth.PayloadAttributes) (*eth.ForkchoiceUpdatedResult, error) {
e := s.log.New("state", fc, "attr", attributes)
e.Trace("Sharing forkchoice-updated signal")
llog := s.log.New("state", fc) // local logger
tlog := llog.New("attr", attributes) // trace logger
tlog.Trace("Sharing forkchoice-updated signal")
fcCtx, cancel := context.WithTimeout(ctx, time.Second*5)
defer cancel()
var result eth.ForkchoiceUpdatedResult
err := s.client.CallContext(fcCtx, &result, "engine_forkchoiceUpdatedV2", fc, attributes)
if err == nil {
e.Trace("Shared forkchoice-updated signal")
tlog.Trace("Shared forkchoice-updated signal")
if attributes != nil { // block building is optional, we only get a payload ID if we are building a block
e.Trace("Received payload id", "payloadId", result.PayloadID)
tlog.Trace("Received payload id", "payloadId", result.PayloadID)
}
return &result, nil
} else {
e.Warn("Failed to share forkchoice-updated signal", "err", err)
llog.Warn("Failed to share forkchoice-updated signal", "err", err)
if rpcErr, ok := err.(rpc.Error); ok {
code := eth.ErrorCode(rpcErr.ErrorCode())
switch code {
......
{"name":"post-shanghai-bad-receipts-hash","fail":true, "reason":"failed to verify block hash"}
{"name":"post-shanghai-bad-transactions-hash","fail":true, "reason":"failed to verify block hash"}
{"name":"post-shanghai-bad-transactions-nil","fail":true, "reason": "block tx 0 is nil"}
{"name":"post-shanghai-bad-transactions","fail":true}
{"name":"post-shanghai-bad-transactions","fail":true, "reason": "failed to verify transactions list"}
{"name":"post-shanghai-bad-withdrawals-hash","fail":true, "reason":"failed to verify block hash"}
{"name":"post-shanghai-bad-withdrawals-nil","fail":true, "reason": "block withdrawal 0 is null"}
{"name":"post-shanghai-bad-withdrawals","fail":true}
{"name":"post-shanghai-bad-withdrawals","fail":true,"reason": "failed to verify withdrawals list"}
{"name":"pre-shanghai-bad-receipts-hash","fail":true, "reason":"failed to verify block hash"}
{"name":"pre-shanghai-bad-transactions-hash","fail":true, "reason":"failed to verify block hash"}
{"name":"pre-shanghai-bad-transactions","fail":true}
{"name":"pre-shanghai-bad-transactions","fail":true,"reason": "failed to verify transactions list"}
......@@ -62,18 +62,8 @@ generate_test_vector() {
$mutation_func "$data_file" $metadata_file
}
mkdir -p data/blocks
mkdir -p data/headers
# Blocks
generate_test_vector "pre-shanghai-success" "0x9ef7cd2241202b919a0e51240818a8666c73f7ce4b908931e3ae6d26d30f7663" true success_case
generate_test_vector "pre-shanghai-bad-transactions" "0x9ef7cd2241202b919a0e51240818a8666c73f7ce4b908931e3ae6d26d30f7663" true bad_transactions_root
generate_test_vector "pre-shanghai-bad-receipts" "0x9ef7cd2241202b919a0e51240818a8666c73f7ce4b908931e3ae6d26d30f7663" true bad_receipts_root
generate_test_vector "post-shanghai-success" "0xa16c6bcda4fdca88b5761965c4d724f7afc6a6900d9051a204e544870adb3452" true success_case
generate_test_vector "post-shanghai-bad-withdrawals" "0xa16c6bcda4fdca88b5761965c4d724f7afc6a6900d9051a204e544870adb3452" true bad_withdrawals_root
generate_test_vector "post-shanghai-bad-transactions" "0xa16c6bcda4fdca88b5761965c4d724f7afc6a6900d9051a204e544870adb3452" true bad_transactions_root
generate_test_vector "post-shanghai-bad-receipts" "0xa16c6bcda4fdca88b5761965c4d724f7afc6a6900d9051a204e544870adb3452" true bad_receipts_root
# Headers
generate_test_vector "pre-shanghai-success" "0x9ef7cd2241202b919a0e51240818a8666c73f7ce4b908931e3ae6d26d30f7663" false success_case
generate_test_vector "pre-shanghai-bad-transactions" "0x9ef7cd2241202b919a0e51240818a8666c73f7ce4b908931e3ae6d26d30f7663" false bad_transactions_root
......
......@@ -15,6 +15,7 @@ var blocksTestdata embed.FS
type testMetadata struct {
Name string `json:"name"`
Fail bool `json:"fail,omitempty"`
Reason string `json:"reason,omitempty"`
}
func readJsonTestdata(t *testing.T, name string, dest any) {
......@@ -67,6 +68,7 @@ func TestBlockJSON(t *testing.T) {
err := block.verify()
if metadata.Fail {
require.NotNil(t, err, "expecting verification error")
require.ErrorContains(t, err, metadata.Reason, "validation failed for incorrect reason")
} else {
require.NoError(t, err, "verification should pass")
}
......
......@@ -4,12 +4,9 @@ package mocks
import (
context "context"
big "math/big"
common "github.com/ethereum/go-ethereum/common"
ethereum "github.com/ethereum/go-ethereum"
mock "github.com/stretchr/testify/mock"
txmgr "github.com/ethereum-optimism/optimism/op-service/txmgr"
......@@ -46,32 +43,6 @@ func (_m *TxManager) BlockNumber(ctx context.Context) (uint64, error) {
return r0, r1
}
// Call provides a mock function with given fields: ctx, msg, blockNumber
func (_m *TxManager) Call(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
ret := _m.Called(ctx, msg, blockNumber)
var r0 []byte
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, ethereum.CallMsg, *big.Int) ([]byte, error)); ok {
return rf(ctx, msg, blockNumber)
}
if rf, ok := ret.Get(0).(func(context.Context, ethereum.CallMsg, *big.Int) []byte); ok {
r0 = rf(ctx, msg, blockNumber)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]byte)
}
}
if rf, ok := ret.Get(1).(func(context.Context, ethereum.CallMsg, *big.Int) error); ok {
r1 = rf(ctx, msg, blockNumber)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// From provides a mock function with given fields:
func (_m *TxManager) From() common.Address {
ret := _m.Called()
......
......@@ -43,10 +43,6 @@ type TxManager interface {
// NOTE: Send can be called concurrently, the nonce will be managed internally.
Send(ctx context.Context, candidate TxCandidate) (*types.Receipt, error)
// Call is used to call a contract.
// Internally, it uses the [ethclient.Client.CallContract] method.
Call(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error)
// From returns the sending address associated with the instance of the transaction manager.
// It is static for a single instance of a TxManager.
From() common.Address
......@@ -169,12 +165,6 @@ func (m *SimpleTxManager) Send(ctx context.Context, candidate TxCandidate) (*typ
return receipt, err
}
// Call is used to call a contract.
// Internally, it uses the [ethclient.Client.CallContract] method.
func (m *SimpleTxManager) Call(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
return m.backend.CallContract(ctx, msg, blockNumber)
}
// send performs the actual transaction creation and sending.
func (m *SimpleTxManager) send(ctx context.Context, candidate TxCandidate) (*types.Receipt, error) {
if m.cfg.TxSendTimeout != 0 {
......
This diff is collapsed.
......@@ -6,8 +6,9 @@ import {
validators,
} from '@eth-optimism/common-ts'
import { Provider } from '@ethersproject/abstract-provider'
import { ethers } from 'ethers'
import { BigNumber, ethers } from 'ethers'
import Safe from '../abi/IGnosisSafe.0.8.19.json'
import { version } from '../../package.json'
type BalanceMonOptions = {
......@@ -17,11 +18,12 @@ type BalanceMonOptions = {
type BalanceMonMetrics = {
balances: Gauge
safeNonces: Gauge
unexpectedRpcErrors: Counter
}
type BalanceMonState = {
accounts: Array<{ address: string; nickname: string }>
accounts: Array<{ address: string; nickname: string; safe: boolean }>
}
export class BalanceMonService extends BaseServiceV2<
......@@ -45,7 +47,7 @@ export class BalanceMonService extends BaseServiceV2<
},
accounts: {
validator: validators.str,
desc: 'JSON array of [{ address, nickname }] to monitor balances of',
desc: 'JSON array of [{ address, nickname, safe }] to monitor balances and nonces of',
public: true,
},
},
......@@ -55,6 +57,11 @@ export class BalanceMonService extends BaseServiceV2<
desc: 'Balances of addresses',
labels: ['address', 'nickname'],
},
safeNonces: {
type: Gauge,
desc: 'Safe nonce',
labels: ['address', 'nickname'],
},
unexpectedRpcErrors: {
type: Counter,
desc: 'Number of unexpected RPC errors',
......@@ -70,9 +77,21 @@ export class BalanceMonService extends BaseServiceV2<
protected async main(): Promise<void> {
for (const account of this.state.accounts) {
let balance: ethers.BigNumber
try {
balance = await this.options.rpc.getBalance(account.address)
const balance = await this.options.rpc.getBalance(account.address)
this.logger.info(`got balance`, {
address: account.address,
nickname: account.nickname,
balance: balance.toString(),
})
// Parse the balance as an integer instead of via toNumber() to avoid ethers throwing an
// an error. We might get rounding errors but we don't need perfect precision here, just a
// generally accurate sense for what the current balance is.
this.metrics.balances.set(
{ address: account.address, nickname: account.nickname },
parseInt(balance.toString(), 10)
)
} catch (err) {
this.logger.info(`got unexpected RPC error`, {
section: 'balances',
......@@ -83,22 +102,39 @@ export class BalanceMonService extends BaseServiceV2<
section: 'balances',
name: 'getBalance',
})
continue
}
this.logger.info(`got balance`, {
// Get the safe nonce to report
if (account.safe) {
try {
const safeContract = new ethers.Contract(
account.address,
Safe.abi,
this.options.rpc
)
const safeNonce = BigNumber.from(await safeContract.nonce())
this.logger.info(`got nonce`, {
address: account.address,
nickname: account.nickname,
balance: balance.toString(),
nonce: safeNonce.toString(),
})
// Parse the balance as an integer instead of via toNumber() to avoid ethers throwing an
// an error. We might get rounding errors but we don't need perfect precision here, just a
// generally accurate sense for what the current balance is.
this.metrics.balances.set(
this.metrics.safeNonces.set(
{ address: account.address, nickname: account.nickname },
parseInt(balance.toString(), 10)
parseInt(safeNonce.toString(), 10)
)
} catch (err) {
this.logger.info(`got unexpected RPC error`, {
section: 'safeNonce',
name: 'getSafeNonce',
err,
})
this.metrics.unexpectedRpcErrors.inc({
section: 'safeNonce',
name: 'getSafeNonce',
})
}
}
}
}
}
......
......@@ -6,6 +6,7 @@
},
"include": [
"package.json",
"src/abi/IGnosisSafe.0.8.19.json",
"src/**/*"
]
}
......@@ -672,8 +672,8 @@ SequencerFeeVault_Test:test_withdraw_toL1_succeeds() (gas: 618343)
SetPrevBaseFee_Test:test_setPrevBaseFee_succeeds() (gas: 11595)
StandardBridge_Stateless_Test:test_isCorrectTokenPair_succeeds() (gas: 50149)
StandardBridge_Stateless_Test:test_isOptimismMintableERC20_succeeds() (gas: 33142)
Storage_Roundtrip_Test:test_setGetAddress_succeeds(bytes32,address) (runs: 64, μ: 31532, ~: 31843)
Storage_Roundtrip_Test:test_setGetBytes32_succeeds(bytes32,bytes32) (runs: 64, μ: 31620, ~: 31620)
Storage_Roundtrip_Test:test_setGetAddress_succeeds(bytes32,address) (runs: 64, μ: 31221, ~: 31843)
Storage_Roundtrip_Test:test_setGetBytes32_succeeds(bytes32,bytes32) (runs: 64, μ: 31654, ~: 31654)
Storage_Roundtrip_Test:test_setGetUint_succeeds(bytes32,uint256) (runs: 64, μ: 30731, ~: 31664)
SystemConfig_Initialize_Test:test_initialize_events_succeeds() (gas: 88186)
SystemConfig_Initialize_Test:test_initialize_startBlockNoop_reverts() (gas: 77216)
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { ProxyAdmin } from "src/universal/ProxyAdmin.sol";
import { ResourceMetering } from "src/L1/ResourceMetering.sol";
import { DeployConfig } from "scripts/DeployConfig.s.sol";
import { SystemConfig } from "src/L1/SystemConfig.sol";
import { Constants } from "src/libraries/Constants.sol";
import { L1StandardBridge } from "src/L1/L1StandardBridge.sol";
import { L2OutputOracle } from "src/L1/L2OutputOracle.sol";
import { ProtocolVersion, ProtocolVersions } from "src/L1/ProtocolVersions.sol";
import { OptimismPortal } from "src/L1/OptimismPortal.sol";
import { L1CrossDomainMessenger } from "src/L1/L1CrossDomainMessenger.sol";
import { OptimismMintableERC20Factory } from "src/universal/OptimismMintableERC20Factory.sol";
import { L1ERC721Bridge } from "src/L1/L1ERC721Bridge.sol";
import { Predeploys } from "src/libraries/Predeploys.sol";
import { Types } from "scripts/Types.sol";
import { Vm } from "forge-std/Vm.sol";
import { ISystemConfigV0 } from "scripts/interfaces/ISystemConfigV0.sol";
library ChainAssertions {
/// @notice Asserts the correctness of an L1 deployment
function postDeployAssertions(
Types.ContractSet memory prox,
DeployConfig cfg,
uint256 l2OutputOracleStartingTimestamp,
Vm vm
)
internal
view
{
ResourceMetering.ResourceConfig memory rcfg = SystemConfig(prox.SystemConfig).resourceConfig();
ResourceMetering.ResourceConfig memory dflt = Constants.DEFAULT_RESOURCE_CONFIG();
require(keccak256(abi.encode(rcfg)) == keccak256(abi.encode(dflt)));
checkSystemConfig(prox, cfg);
checkL1CrossDomainMessenger(prox, vm);
checkL1StandardBridge(prox, vm);
checkL2OutputOracle(prox, cfg, l2OutputOracleStartingTimestamp);
checkOptimismMintableERC20Factory(prox);
checkL1ERC721Bridge(prox);
checkOptimismPortal(prox, cfg);
checkProtocolVersions(prox, cfg);
}
/// @notice Asserts that the SystemConfig is setup correctly
function checkSystemConfig(Types.ContractSet memory proxies, DeployConfig cfg) internal view {
ISystemConfigV0 config = ISystemConfigV0(proxies.SystemConfig);
require(config.owner() == cfg.finalSystemOwner());
require(config.overhead() == cfg.gasPriceOracleOverhead());
require(config.scalar() == cfg.gasPriceOracleScalar());
require(config.batcherHash() == bytes32(uint256(uint160(cfg.batchSenderAddress()))));
require(config.unsafeBlockSigner() == cfg.p2pSequencerAddress());
ResourceMetering.ResourceConfig memory rconfig = Constants.DEFAULT_RESOURCE_CONFIG();
ResourceMetering.ResourceConfig memory resourceConfig = config.resourceConfig();
require(resourceConfig.maxResourceLimit == rconfig.maxResourceLimit);
require(resourceConfig.elasticityMultiplier == rconfig.elasticityMultiplier);
require(resourceConfig.baseFeeMaxChangeDenominator == rconfig.baseFeeMaxChangeDenominator);
require(resourceConfig.systemTxMaxGas == rconfig.systemTxMaxGas);
require(resourceConfig.minimumBaseFee == rconfig.minimumBaseFee);
require(resourceConfig.maximumBaseFee == rconfig.maximumBaseFee);
}
/// @notice Asserts that the L1CrossDomainMessenger is setup correctly
function checkL1CrossDomainMessenger(Types.ContractSet memory proxies, Vm vm) internal view {
L1CrossDomainMessenger messenger = L1CrossDomainMessenger(proxies.L1CrossDomainMessenger);
require(address(messenger.portal()) == proxies.OptimismPortal);
require(address(messenger.PORTAL()) == proxies.OptimismPortal);
bytes32 xdmSenderSlot = vm.load(address(messenger), bytes32(uint256(204)));
require(address(uint160(uint256(xdmSenderSlot))) == Constants.DEFAULT_L2_SENDER);
}
/// @notice Asserts that the L1StandardBridge is setup correctly
function checkL1StandardBridge(Types.ContractSet memory proxies, Vm vm) internal view {
L1StandardBridge bridge = L1StandardBridge(payable(proxies.L1StandardBridge));
require(address(bridge.MESSENGER()) == proxies.L1CrossDomainMessenger);
require(address(bridge.messenger()) == proxies.L1CrossDomainMessenger);
require(address(bridge.OTHER_BRIDGE()) == Predeploys.L2_STANDARD_BRIDGE);
require(address(bridge.otherBridge()) == Predeploys.L2_STANDARD_BRIDGE);
// Ensures that the legacy slot is modified correctly. This will fail
// during predeployment simulation on OP Mainnet if there is a bug.
bytes32 slot0 = vm.load(address(bridge), bytes32(uint256(0)));
require(slot0 == bytes32(uint256(Constants.INITIALIZER)));
}
/// @notice Asserts that the L2OutputOracle is setup correctly
function checkL2OutputOracle(
Types.ContractSet memory proxies,
DeployConfig cfg,
uint256 l2OutputOracleStartingTimestamp
)
internal
view
{
L2OutputOracle oracle = L2OutputOracle(proxies.L2OutputOracle);
require(oracle.SUBMISSION_INTERVAL() == cfg.l2OutputOracleSubmissionInterval());
require(oracle.submissionInterval() == cfg.l2OutputOracleSubmissionInterval());
require(oracle.L2_BLOCK_TIME() == cfg.l2BlockTime());
require(oracle.l2BlockTime() == cfg.l2BlockTime());
require(oracle.PROPOSER() == cfg.l2OutputOracleProposer());
require(oracle.proposer() == cfg.l2OutputOracleProposer());
require(oracle.CHALLENGER() == cfg.l2OutputOracleChallenger());
require(oracle.challenger() == cfg.l2OutputOracleChallenger());
require(oracle.FINALIZATION_PERIOD_SECONDS() == cfg.finalizationPeriodSeconds());
require(oracle.finalizationPeriodSeconds() == cfg.finalizationPeriodSeconds());
require(oracle.startingBlockNumber() == cfg.l2OutputOracleStartingBlockNumber());
require(oracle.startingTimestamp() == l2OutputOracleStartingTimestamp);
}
/// @notice Asserts that the OptimismMintableERC20Factory is setup correctly
function checkOptimismMintableERC20Factory(Types.ContractSet memory proxies) internal view {
OptimismMintableERC20Factory factory = OptimismMintableERC20Factory(proxies.OptimismMintableERC20Factory);
require(factory.BRIDGE() == proxies.L1StandardBridge);
require(factory.bridge() == proxies.L1StandardBridge);
}
/// @notice Asserts that the L1ERC721Bridge is setup correctly
function checkL1ERC721Bridge(Types.ContractSet memory proxies) internal view {
L1ERC721Bridge bridge = L1ERC721Bridge(proxies.L1ERC721Bridge);
require(address(bridge.MESSENGER()) == proxies.L1CrossDomainMessenger);
require(bridge.OTHER_BRIDGE() == Predeploys.L2_ERC721_BRIDGE);
}
/// @notice Asserts the OptimismPortal is setup correctly
function checkOptimismPortal(Types.ContractSet memory proxies, DeployConfig cfg) internal view {
OptimismPortal portal = OptimismPortal(payable(proxies.OptimismPortal));
require(address(portal.L2_ORACLE()) == proxies.L2OutputOracle);
require(portal.GUARDIAN() == cfg.portalGuardian());
require(address(portal.SYSTEM_CONFIG()) == proxies.SystemConfig);
require(portal.paused() == false);
}
/// @notice Asserts that the ProtocolVersions is setup correctly
function checkProtocolVersions(Types.ContractSet memory proxies, DeployConfig cfg) internal view {
ProtocolVersions versions = ProtocolVersions(proxies.ProtocolVersions);
require(versions.owner() == cfg.finalSystemOwner());
require(ProtocolVersion.unwrap(versions.required()) == cfg.requiredProtocolVersion());
require(ProtocolVersion.unwrap(versions.recommended()) == cfg.recommendedProtocolVersion());
}
}
......@@ -43,6 +43,8 @@ import { IBigStepper } from "src/dispute/interfaces/IBigStepper.sol";
import { IPreimageOracle } from "src/cannon/interfaces/IPreimageOracle.sol";
import { AlphabetVM } from "test/mocks/AlphabetVM.sol";
import "src/libraries/DisputeTypes.sol";
import { ChainAssertions } from "scripts/ChainAssertions.sol";
import { Types } from "scripts/Types.sol";
/// @title Deploy
/// @notice Script used to deploy a bedrock system. The entire system is deployed within the `run` function.
......@@ -642,33 +644,7 @@ contract Deploy is Deployer {
string memory version = config.version();
console.log("SystemConfig version: %s", version);
require(config.owner() == cfg.finalSystemOwner());
require(config.overhead() == cfg.gasPriceOracleOverhead());
require(config.scalar() == cfg.gasPriceOracleScalar());
require(config.unsafeBlockSigner() == cfg.p2pSequencerAddress());
require(config.batcherHash() == batcherHash);
ResourceMetering.ResourceConfig memory rconfig = Constants.DEFAULT_RESOURCE_CONFIG();
ResourceMetering.ResourceConfig memory resourceConfig = config.resourceConfig();
require(resourceConfig.maxResourceLimit == rconfig.maxResourceLimit);
require(resourceConfig.elasticityMultiplier == rconfig.elasticityMultiplier);
require(resourceConfig.baseFeeMaxChangeDenominator == rconfig.baseFeeMaxChangeDenominator);
require(resourceConfig.systemTxMaxGas == rconfig.systemTxMaxGas);
require(resourceConfig.minimumBaseFee == rconfig.minimumBaseFee);
require(resourceConfig.maximumBaseFee == rconfig.maximumBaseFee);
require(config.l1ERC721Bridge() == mustGetAddress("L1ERC721BridgeProxy"));
require(config.l1StandardBridge() == mustGetAddress("L1StandardBridgeProxy"));
require(config.l2OutputOracle() == mustGetAddress("L2OutputOracleProxy"));
require(config.optimismPortal() == mustGetAddress("OptimismPortalProxy"));
require(config.l1CrossDomainMessenger() == mustGetAddress("L1CrossDomainMessengerProxy"));
// A non zero start block is an override
if (startBlock != 0) {
require(config.startBlock() == startBlock);
} else {
require(config.startBlock() == block.number);
}
ChainAssertions.checkSystemConfig(_proxies(), cfg);
}
/// @notice Initialize the L1StandardBridge
......@@ -698,11 +674,7 @@ contract Deploy is Deployer {
string memory version = L1StandardBridge(payable(l1StandardBridgeProxy)).version();
console.log("L1StandardBridge version: %s", version);
L1StandardBridge bridge = L1StandardBridge(payable(l1StandardBridgeProxy));
require(address(bridge.MESSENGER()) == l1CrossDomainMessengerProxy);
require(address(bridge.messenger()) == l1CrossDomainMessengerProxy);
require(address(bridge.OTHER_BRIDGE()) == Predeploys.L2_STANDARD_BRIDGE);
require(address(bridge.otherBridge()) == Predeploys.L2_STANDARD_BRIDGE);
ChainAssertions.checkL1StandardBridge(_proxies(), vm);
}
/// @notice Initialize the L1ERC721Bridge
......@@ -721,8 +693,7 @@ contract Deploy is Deployer {
string memory version = bridge.version();
console.log("L1ERC721Bridge version: %s", version);
require(address(bridge.MESSENGER()) == l1CrossDomainMessengerProxy);
require(bridge.OTHER_BRIDGE() == Predeploys.L2_ERC721_BRIDGE);
ChainAssertions.checkL1ERC721Bridge(_proxies());
}
/// @notice Ininitialize the OptimismMintableERC20Factory
......@@ -741,8 +712,7 @@ contract Deploy is Deployer {
string memory version = factory.version();
console.log("OptimismMintableERC20Factory version: %s", version);
require(factory.BRIDGE() == l1StandardBridgeProxy);
require(factory.bridge() == l1StandardBridgeProxy);
ChainAssertions.checkOptimismMintableERC20Factory(_proxies());
}
/// @notice initializeL1CrossDomainMessenger
......@@ -786,10 +756,7 @@ contract Deploy is Deployer {
string memory version = messenger.version();
console.log("L1CrossDomainMessenger version: %s", version);
require(address(messenger.PORTAL()) == optimismPortalProxy);
require(address(messenger.portal()) == optimismPortalProxy);
bytes32 xdmSenderSlot = vm.load(address(messenger), bytes32(uint256(204)));
require(address(uint160(uint256(xdmSenderSlot))) == Constants.DEFAULT_L2_SENDER);
ChainAssertions.checkL1CrossDomainMessenger(_proxies(), vm);
}
/// @notice Initialize the L2OutputOracle
......@@ -815,18 +782,7 @@ contract Deploy is Deployer {
string memory version = oracle.version();
console.log("L2OutputOracle version: %s", version);
require(oracle.SUBMISSION_INTERVAL() == cfg.l2OutputOracleSubmissionInterval());
require(oracle.submissionInterval() == cfg.l2OutputOracleSubmissionInterval());
require(oracle.L2_BLOCK_TIME() == cfg.l2BlockTime());
require(oracle.l2BlockTime() == cfg.l2BlockTime());
require(oracle.PROPOSER() == cfg.l2OutputOracleProposer());
require(oracle.proposer() == cfg.l2OutputOracleProposer());
require(oracle.CHALLENGER() == cfg.l2OutputOracleChallenger());
require(oracle.challenger() == cfg.l2OutputOracleChallenger());
require(oracle.FINALIZATION_PERIOD_SECONDS() == cfg.finalizationPeriodSeconds());
require(oracle.finalizationPeriodSeconds() == cfg.finalizationPeriodSeconds());
require(oracle.startingBlockNumber() == cfg.l2OutputOracleStartingBlockNumber());
require(oracle.startingTimestamp() == cfg.l2OutputOracleStartingTimestamp());
ChainAssertions.checkL2OutputOracle(_proxies(), cfg, cfg.l2OutputOracleStartingTimestamp());
}
/// @notice Initialize the OptimismPortal
......@@ -854,10 +810,7 @@ contract Deploy is Deployer {
string memory version = portal.version();
console.log("OptimismPortal version: %s", version);
require(address(portal.L2_ORACLE()) == l2OutputOracleProxy);
require(portal.GUARDIAN() == cfg.portalGuardian());
require(address(portal.SYSTEM_CONFIG()) == systemConfigProxy);
require(portal.paused() == false);
ChainAssertions.checkOptimismPortal(_proxies(), cfg);
}
function initializeProtocolVersions() public broadcast {
......@@ -885,9 +838,7 @@ contract Deploy is Deployer {
string memory version = versions.version();
console.log("ProtocolVersions version: %s", version);
require(versions.owner() == finalSystemOwner);
require(ProtocolVersion.unwrap(versions.required()) == requiredProtocolVersion);
require(ProtocolVersion.unwrap(versions.recommended()) == recommendedProtocolVersion);
ChainAssertions.checkProtocolVersions(_proxies(), cfg);
}
/// @notice Transfer ownership of the ProxyAdmin contract to the final system owner
......@@ -1008,4 +959,18 @@ contract Deploy is Deployer {
console.log("StorageSetter version: %s", version);
addr_ = address(setter);
}
/// @notice Returns the proxy addresses
function _proxies() private view returns (Types.ContractSet memory proxies_) {
proxies_ = Types.ContractSet({
L1CrossDomainMessenger: mustGetAddress("L1CrossDomainMessengerProxy"),
L1StandardBridge: mustGetAddress("L1StandardBridgeProxy"),
L2OutputOracle: mustGetAddress("L2OutputOracleProxy"),
OptimismMintableERC20Factory: mustGetAddress("OptimismMintableERC20FactoryProxy"),
OptimismPortal: mustGetAddress("OptimismPortalProxy"),
SystemConfig: mustGetAddress("SystemConfigProxy"),
L1ERC721Bridge: mustGetAddress("L1ERC721BridgeProxy"),
ProtocolVersions: mustGetAddress("ProtocolVersionsProxy")
});
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
library Types {
/// @notice Represents a set of L1 contracts. Used to represent a set of proxies.
struct ContractSet {
address L1CrossDomainMessenger;
address L1StandardBridge;
address L2OutputOracle;
address OptimismMintableERC20Factory;
address OptimismPortal;
address SystemConfig;
address L1ERC721Bridge;
address ProtocolVersions;
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { ResourceMetering } from "src/L1/ResourceMetering.sol";
/// @title ISystemConfigV0
/// @notice Minimal interface of the Legacy SystemConfig containing only getters.
/// Based on
/// https://github.com/ethereum-optimism/optimism/blob/f54a2234f2f350795552011f35f704a3feb56a08/packages/contracts-bedrock/src/L1/SystemConfig.sol
interface ISystemConfigV0 {
function owner() external view returns (address);
function VERSION() external view returns (uint256);
function overhead() external view returns (uint256);
function scalar() external view returns (uint256);
function batcherHash() external view returns (bytes32);
function gasLimit() external view returns (uint64);
function resourceConfig() external view returns (ResourceMetering.ResourceConfig memory);
function unsafeBlockSigner() external view returns (address);
}
......@@ -34,5 +34,5 @@
"src/universal/OptimismMintableERC20Factory.sol": "0x8d4cbf4cc30a0bb72925b5d2e0386b8f91559f00933a9c7cf3dcc118e34fe61b",
"src/universal/OptimismMintableERC721.sol": "0x4c73bf8474fa7eb091796a4db7e57bc5f26d50a3d1cfcb78d5efa47ced5ced2b",
"src/universal/OptimismMintableERC721Factory.sol": "0x935fd97018b6ef10fa813d9d43ab7a77c80885f7a8d7feb430097645cb2abd2c",
"src/universal/StorageSetter.sol": "0x6372647d8a67d243bc2fb40d2c4bf5807022d94d52d9423cfed27a7d57918635"
"src/universal/StorageSetter.sol": "0x394ec39ef24b44f54549deec6183cace8eea2e5313cde8d5a6e0411a481c5953"
}
\ No newline at end of file
......@@ -9,9 +9,15 @@ import { Storage } from "src/libraries/Storage.sol";
/// WARNING: this contract is not safe to be called by untrusted parties.
/// It is only meant as an intermediate step during upgrades.
contract StorageSetter is ISemver {
/// @notice Represents a storage slot key value pair.
struct Slot {
bytes32 key;
bytes32 value;
}
/// @notice Semantic version.
/// @custom:semver 1.0.0
string public constant version = "1.0.0";
/// @custom:semver 1.1.0
string public constant version = "1.1.0";
/// @notice Stores a bytes32 `_value` at `_slot`. Any storage slots that
/// are packed should be set through this interface.
......@@ -19,6 +25,14 @@ contract StorageSetter is ISemver {
Storage.setBytes32(_slot, _value);
}
/// @notice Stores a bytes32 value at each key in `_slots`.
function setBytes32(Slot[] calldata slots) public {
uint256 length = slots.length;
for (uint256 i; i < length; i++) {
Storage.setBytes32(slots[i].key, slots[i].value);
}
}
/// @notice Retrieves a bytes32 value from `_slot`.
function getBytes32(bytes32 _slot) external view returns (bytes32) {
return Storage.getBytes32(_slot);
......
......@@ -13,6 +13,11 @@ import { Test } from "forge-std/Test.sol";
contract Storage_Roundtrip_Test is Test {
StorageSetter setter;
/// @notice A set of storage slots to pass to `setBytes32`.
StorageSetter.Slot[] slots;
/// @notice Used to deduplicate slots passed to `setBytes32`.
mapping(bytes32 => bool) keys;
function setUp() external {
setter = new StorageSetter();
}
......@@ -34,4 +39,21 @@ contract Storage_Roundtrip_Test is Test {
assertEq(setter.getBytes32(slot), hash);
assertEq(hash, vm.load(address(setter), slot));
}
/// @dev All keys must be unique in the input so deduplication is required.
function testFuzz_setGetBytes32Multi_succeeds(StorageSetter.Slot[] calldata _slots) external {
for (uint256 i; i < _slots.length; i++) {
if (keys[_slots[i].key]) {
continue;
}
slots.push(_slots[i]);
keys[_slots[i].key] = true;
}
setter.setBytes32(slots);
for (uint256 i; i < slots.length; i++) {
assertEq(setter.getBytes32(slots[i].key), slots[i].value);
assertEq(slots[i].value, vm.load(address(setter), slots[i].key));
}
}
}
......@@ -43,7 +43,7 @@
"@nomiclabs/hardhat-waffle": "^2.0.1",
"@types/chai": "^4.3.8",
"@types/chai-as-promised": "^7.1.5",
"@types/mocha": "^10.0.3",
"@types/mocha": "^10.0.4",
"@types/node": "^20.8.9",
"chai-as-promised": "^7.1.1",
"ethereum-waffle": "^4.0.10",
......
......@@ -28,8 +28,8 @@ importers:
specifier: ^7.1.4
version: 7.1.5
'@types/mocha':
specifier: ^10.0.3
version: 10.0.3
specifier: ^10.0.4
version: 10.0.4
'@types/node':
specifier: ^20.8.9
version: 20.8.9
......@@ -269,10 +269,10 @@ importers:
devDependencies:
'@typescript-eslint/eslint-plugin':
specifier: ^6.9.1
version: 6.9.1(@typescript-eslint/parser@6.9.1)(eslint@8.52.0)(typescript@5.2.2)
version: 6.9.1(@typescript-eslint/parser@6.9.1)(eslint@8.53.0)(typescript@5.2.2)
'@typescript-eslint/parser':
specifier: ^6.9.1
version: 6.9.1(eslint@8.52.0)(typescript@5.2.2)
version: 6.9.1(eslint@8.53.0)(typescript@5.2.2)
tsx:
specifier: ^3.14.0
version: 3.14.0
......@@ -490,8 +490,8 @@ importers:
specifier: ^7.1.5
version: 7.1.5
'@types/mocha':
specifier: ^10.0.3
version: 10.0.3
specifier: ^10.0.4
version: 10.0.4
'@types/node':
specifier: ^20.8.9
version: 20.8.9
......@@ -1801,16 +1801,6 @@ packages:
dev: true
optional: true
/@eslint-community/eslint-utils@4.4.0(eslint@8.52.0):
resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
dependencies:
eslint: 8.52.0
eslint-visitor-keys: 3.4.3
dev: true
/@eslint-community/eslint-utils@4.4.0(eslint@8.53.0):
resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
......@@ -1826,23 +1816,6 @@ packages:
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
dev: true
/@eslint/eslintrc@2.1.2:
resolution: {integrity: sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dependencies:
ajv: 6.12.6
debug: 4.3.4(supports-color@8.1.1)
espree: 9.6.1
globals: 13.21.0
ignore: 5.2.4
import-fresh: 3.3.0
js-yaml: 4.1.0
minimatch: 3.1.2
strip-json-comments: 3.1.1
transitivePeerDependencies:
- supports-color
dev: true
/@eslint/eslintrc@2.1.3:
resolution: {integrity: sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
......@@ -1860,11 +1833,6 @@ packages:
- supports-color
dev: true
/@eslint/js@8.52.0:
resolution: {integrity: sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: true
/@eslint/js@8.53.0:
resolution: {integrity: sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
......@@ -4033,8 +4001,8 @@ packages:
'@types/node': 20.8.9
dev: true
/@types/mocha@10.0.3:
resolution: {integrity: sha512-RsOPImTriV/OE4A9qKjMtk2MnXiuLLbcO3nCXK+kvq4nr0iMfFgpjaX3MPLb6f7+EL1FGSelYvuJMV6REH+ZPQ==}
/@types/mocha@10.0.4:
resolution: {integrity: sha512-xKU7bUjiFTIttpWaIZ9qvgg+22O1nmbA+HRxdlR+u6TWsGfmFdXrheJoK4fFxrHNVIOBDvDNKZG+LYBpMHpX3w==}
dev: true
/@types/morgan@1.9.7:
......@@ -4233,35 +4201,6 @@ packages:
'@types/node': 20.8.9
dev: false
/@typescript-eslint/eslint-plugin@6.9.1(@typescript-eslint/parser@6.9.1)(eslint@8.52.0)(typescript@5.2.2):
resolution: {integrity: sha512-w0tiiRc9I4S5XSXXrMHOWgHgxbrBn1Ro+PmiYhSg2ZVdxrAJtQgzU5o2m1BfP6UOn7Vxcc6152vFjQfmZR4xEg==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
'@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha
eslint: ^7.0.0 || ^8.0.0
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
dependencies:
'@eslint-community/regexpp': 4.6.2
'@typescript-eslint/parser': 6.9.1(eslint@8.52.0)(typescript@5.2.2)
'@typescript-eslint/scope-manager': 6.9.1
'@typescript-eslint/type-utils': 6.9.1(eslint@8.52.0)(typescript@5.2.2)
'@typescript-eslint/utils': 6.9.1(eslint@8.52.0)(typescript@5.2.2)
'@typescript-eslint/visitor-keys': 6.9.1
debug: 4.3.4(supports-color@8.1.1)
eslint: 8.52.0
graphemer: 1.4.0
ignore: 5.2.4
natural-compare: 1.4.0
semver: 7.5.4
ts-api-utils: 1.0.1(typescript@5.2.2)
typescript: 5.2.2
transitivePeerDependencies:
- supports-color
dev: true
/@typescript-eslint/eslint-plugin@6.9.1(@typescript-eslint/parser@6.9.1)(eslint@8.53.0)(typescript@5.2.2):
resolution: {integrity: sha512-w0tiiRc9I4S5XSXXrMHOWgHgxbrBn1Ro+PmiYhSg2ZVdxrAJtQgzU5o2m1BfP6UOn7Vxcc6152vFjQfmZR4xEg==}
engines: {node: ^16.0.0 || >=18.0.0}
......@@ -4291,27 +4230,6 @@ packages:
- supports-color
dev: true
/@typescript-eslint/parser@6.9.1(eslint@8.52.0)(typescript@5.2.2):
resolution: {integrity: sha512-C7AK2wn43GSaCUZ9do6Ksgi2g3mwFkMO3Cis96kzmgudoVaKyt62yNzJOktP0HDLb/iO2O0n2lBOzJgr6Q/cyg==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
eslint: ^7.0.0 || ^8.0.0
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
dependencies:
'@typescript-eslint/scope-manager': 6.9.1
'@typescript-eslint/types': 6.9.1
'@typescript-eslint/typescript-estree': 6.9.1(typescript@5.2.2)
'@typescript-eslint/visitor-keys': 6.9.1
debug: 4.3.4(supports-color@8.1.1)
eslint: 8.52.0
typescript: 5.2.2
transitivePeerDependencies:
- supports-color
dev: true
/@typescript-eslint/parser@6.9.1(eslint@8.53.0)(typescript@5.2.2):
resolution: {integrity: sha512-C7AK2wn43GSaCUZ9do6Ksgi2g3mwFkMO3Cis96kzmgudoVaKyt62yNzJOktP0HDLb/iO2O0n2lBOzJgr6Q/cyg==}
engines: {node: ^16.0.0 || >=18.0.0}
......@@ -4341,26 +4259,6 @@ packages:
'@typescript-eslint/visitor-keys': 6.9.1
dev: true
/@typescript-eslint/type-utils@6.9.1(eslint@8.52.0)(typescript@5.2.2):
resolution: {integrity: sha512-eh2oHaUKCK58qIeYp19F5V5TbpM52680sB4zNSz29VBQPTWIlE/hCj5P5B1AChxECe/fmZlspAWFuRniep1Skg==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
eslint: ^7.0.0 || ^8.0.0
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
dependencies:
'@typescript-eslint/typescript-estree': 6.9.1(typescript@5.2.2)
'@typescript-eslint/utils': 6.9.1(eslint@8.52.0)(typescript@5.2.2)
debug: 4.3.4(supports-color@8.1.1)
eslint: 8.52.0
ts-api-utils: 1.0.1(typescript@5.2.2)
typescript: 5.2.2
transitivePeerDependencies:
- supports-color
dev: true
/@typescript-eslint/type-utils@6.9.1(eslint@8.53.0)(typescript@5.2.2):
resolution: {integrity: sha512-eh2oHaUKCK58qIeYp19F5V5TbpM52680sB4zNSz29VBQPTWIlE/hCj5P5B1AChxECe/fmZlspAWFuRniep1Skg==}
engines: {node: ^16.0.0 || >=18.0.0}
......@@ -4407,25 +4305,6 @@ packages:
- supports-color
dev: true
/@typescript-eslint/utils@6.9.1(eslint@8.52.0)(typescript@5.2.2):
resolution: {integrity: sha512-L1T0A5nFdQrMVunpZgzqPL6y2wVreSyHhKGZryS6jrEN7bD9NplVAyMryUhXsQ4TWLnZmxc2ekar/lSGIlprCA==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
eslint: ^7.0.0 || ^8.0.0
dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@8.52.0)
'@types/json-schema': 7.0.12
'@types/semver': 7.5.0
'@typescript-eslint/scope-manager': 6.9.1
'@typescript-eslint/types': 6.9.1
'@typescript-eslint/typescript-estree': 6.9.1(typescript@5.2.2)
eslint: 8.52.0
semver: 7.5.4
transitivePeerDependencies:
- supports-color
- typescript
dev: true
/@typescript-eslint/utils@6.9.1(eslint@8.53.0)(typescript@5.2.2):
resolution: {integrity: sha512-L1T0A5nFdQrMVunpZgzqPL6y2wVreSyHhKGZryS6jrEN7bD9NplVAyMryUhXsQ4TWLnZmxc2ekar/lSGIlprCA==}
engines: {node: ^16.0.0 || >=18.0.0}
......@@ -7841,53 +7720,6 @@ packages:
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: true
/eslint@8.52.0:
resolution: {integrity: sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
hasBin: true
dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@8.52.0)
'@eslint-community/regexpp': 4.6.2
'@eslint/eslintrc': 2.1.2
'@eslint/js': 8.52.0
'@humanwhocodes/config-array': 0.11.13
'@humanwhocodes/module-importer': 1.0.1
'@nodelib/fs.walk': 1.2.8
'@ungap/structured-clone': 1.2.0
ajv: 6.12.6
chalk: 4.1.2
cross-spawn: 7.0.3
debug: 4.3.4(supports-color@8.1.1)
doctrine: 3.0.0
escape-string-regexp: 4.0.0
eslint-scope: 7.2.2
eslint-visitor-keys: 3.4.3
espree: 9.6.1
esquery: 1.5.0
esutils: 2.0.3
fast-deep-equal: 3.1.3
file-entry-cache: 6.0.1
find-up: 5.0.0
glob-parent: 6.0.2
globals: 13.21.0
graphemer: 1.4.0
ignore: 5.2.4
imurmurhash: 0.1.4
is-glob: 4.0.3
is-path-inside: 3.0.3
js-yaml: 4.1.0
json-stable-stringify-without-jsonify: 1.0.1
levn: 0.4.1
lodash.merge: 4.6.2
minimatch: 3.1.2
natural-compare: 1.4.0
optionator: 0.9.3
strip-ansi: 6.0.1
text-table: 0.2.0
transitivePeerDependencies:
- supports-color
dev: true
/eslint@8.53.0:
resolution: {integrity: sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
......
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