Skip to content

Commit 2a82b79

Browse files
geokneebitwiseguy
andauthored
Add consistency check for standard versions (#768)
* add standard-version-consistency_test.go * lint * enhance consistency check to cover bytecode hashes * pass tag down to getters * update test * update MIPS standard bytecodes and immutables * validation: refactor TestStandardVersionConsistency * validation: rename NetworkVersions to ContractVersions * validation: rename func GetVersion to GetContractVersion * validation: simplify struct in standard-versions-*.toml files * validation: remove unused VersionTags struct * just: remove -count=1 flag * validation: add L1ContractBytecodeHashes unit tests * update rc tags --------- Co-authored-by: Samuel Stokes <[email protected]>
1 parent 68e077b commit 2a82b79

12 files changed

+185
-69
lines changed

superchain/superchain.go

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -411,28 +411,30 @@ func (c ContractVersions) GetNonEmpty() []string {
411411
// VersionFor returns the version for the supplied contract name, if it exits
412412
// (and an error otherwise). Useful for slicing into the struct using a string.
413413
func (c ContractVersions) VersionFor(contractName string) (string, error) {
414+
vc, err := c.VersionedContractFor(contractName)
415+
if err != nil {
416+
return "", err
417+
}
418+
419+
if vc.Version == "" {
420+
return "", ErrEmptyVersion
421+
}
422+
return vc.Version, nil
423+
}
424+
425+
// VersionFor returns the version for the supplied contract name, if it exits
426+
// (and an error otherwise). Useful for slicing into the struct using a string.
427+
func (c ContractVersions) VersionedContractFor(contractName string) (VersionedContract, error) {
414428
// Use reflection to get the value of the struct
415429
val := reflect.ValueOf(c)
416430
// Get the field by name (contractName)
417431
field := val.FieldByName(contractName)
418432

419433
// Check if the field exists and is a struct
420434
if !field.IsValid() {
421-
return "", errors.New("no such contract name")
422-
}
423-
424-
// Check if the struct contains the "Version" field
425-
versionField := field.FieldByName("Version")
426-
if !versionField.IsValid() || versionField.String() == "" {
427-
return "", errors.New("no version specified")
435+
return VersionedContract{}, errors.New("no such contract name")
428436
}
429-
430-
// Return the version if it's a string
431-
if versionField.Kind() == reflect.String {
432-
return versionField.String(), nil
433-
}
434-
435-
return "", errors.New("version is not a string")
437+
return field.Interface().(VersionedContract), nil
436438
}
437439

438440
// Check will sanity check the validity of the semantic version strings

validation/standard/init.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,9 @@ func init() {
2828
Config.Params[network] = new(Params)
2929
decodeTOMLFileIntoConfig("standard-config-params-"+network+".toml", Config.Params[network])
3030

31-
var versions VersionTags = VersionTags{
32-
Releases: make(map[Tag]superchain.ContractVersions, 0),
33-
}
34-
31+
versions := make(map[Tag]superchain.ContractVersions)
3532
decodeTOMLFileIntoConfig("standard-versions-"+network+".toml", &versions)
36-
NetworkVersions[network] = versions
33+
ContractVersions[network] = versions
3734
}
3835

3936
decodeTOMLFileIntoConfig("standard-bytecodes.toml", &BytecodeHashes)
@@ -57,6 +54,9 @@ func decodeTOMLFileIntoConfig[
5754
if err != nil {
5855
panic(err)
5956
}
57+
if data == nil {
58+
panic("empty data")
59+
}
6060
err = toml.Unmarshal(data, config)
6161
if err != nil {
6262
panic(err)

validation/standard/init_test.go

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -36,40 +36,38 @@ func TestConfigInitialization(t *testing.T) {
3636
})
3737

3838
t.Run(fmt.Sprintf("MultisigRoles[%s]", network), func(t *testing.T) {
39-
roles := Config.MultisigRoles[network]
40-
4139
// Ensure network MultisigRoles are populated
40+
roles := Config.MultisigRoles[network]
4241
require.NotNil(t, roles, "Config.MultisigRoles[%s] should not be nil", network)
43-
4442
require.NotZero(t, roles, "Config.MultisigRoles[%s] should not be zero value", network)
4543

4644
l1Roles := roles.KeyHandover.L1.Universal
4745
require.NotNil(t, l1Roles, "Config.MultisigRoles[%s].KeyHandover.L1.Universal must be present", network)
4846
require.NotEmpty(t, l1Roles["ProxyAdmin"]["owner()"], "Config.MultisigRoles[%s].ProxyAdmin.\"owner()\" must be set", network)
4947
})
5048

51-
t.Run(fmt.Sprintf("NetworkVersions[%s]", network), func(t *testing.T) {
49+
t.Run(fmt.Sprintf("ContractVersions[%s]", network), func(t *testing.T) {
5250
// Ensure network Versions are populated
53-
versions, ok := NetworkVersions[network]
54-
require.True(t, ok, "NetworkVersions[%s] should exist", network)
55-
require.NotNil(t, versions, "NetworkVersions[%s] should not be nil", network)
56-
require.NotZero(t, len(versions.Releases), "NetworkVersions[%s].Releases should not be empty", network)
51+
versions, ok := ContractVersions[network]
52+
require.True(t, ok, "ContractVersions[%s] should exist", network)
53+
require.NotNil(t, versions, "ContractVersions[%s] should not be nil", network)
54+
require.NotZero(t, len(versions), "ContractVersions[%s] should not be empty", network)
5755

58-
_, ok = versions.Releases[Release]
59-
require.True(t, ok, "NetworkVersions[%s].Releases[%s] should exist", network, Release)
56+
_, ok = versions[Release]
57+
require.True(t, ok, "ContractVersions[%s][%s] should exist", network, Release)
6058

61-
// Ensure release.ImplementationAddress and release.Address are correctly set
62-
release, ok := versions.Releases["op-contracts/v1.6.0"]
63-
require.True(t, ok, "NetworkVersions[%s].Releases[%s] should exist", network, "op-contracts/v1.6.0")
59+
// Ensure ImplementationAddress/Address are correctly set
60+
release, ok := versions["op-contracts/v1.6.0"]
61+
require.True(t, ok, "ContractVersions[%s][%s] should exist", network, "op-contracts/v1.6.0")
6462
if network == "mainnet" {
6563
require.Equal(t, "0xe2F826324b2faf99E513D16D266c3F80aE87832B", release.OptimismPortal.ImplementationAddress.String(), "failed parsing release implementation_address")
6664
} else {
6765
require.Equal(t, "0x35028bAe87D71cbC192d545d38F960BA30B4B233", release.OptimismPortal.ImplementationAddress.String(), "failed parsing release implementation_address")
6866
}
6967
require.Nil(t, release.OptimismPortal.Address, "failed parsing release address")
7068

71-
_, ok = versions.Releases["fake-release"]
72-
require.False(t, ok, "NetworkVersions[%s].Releases[%s] should not exist", network, Release)
69+
_, ok = versions["fake-release"]
70+
require.False(t, ok, "ContractVersions[%s][%s] should not exist", network, Release)
7371
})
7472
}
7573
}

validation/standard/standard-bytecodes.toml

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
[releases."op-contracts/v1.8.0-rc.3"] # Holocene https://github.com/ethereum-optimism/optimism/releases/tag/op-contracts%2Fv1.8.0-rc.3
1+
["op-contracts/v1.8.0-rc.4"] # Holocene https://github.com/ethereum-optimism/optimism/releases/tag/op-contracts%2Fv1.8.0-rc.4
22
# Updated in this release
33
system_config = "0x165dece6291f3a071e602c804f4a3e89977a71ec866f46c85eb21448f22f6f9a" # version 2.3.0
44
fault_dispute_game = "0x1a81c5fe9fbbf68a0f75e987451e5181c4fc3e801f5c056d38f08942cefed349" # version "1.3.1"
@@ -15,6 +15,7 @@ preimage_oracle = "0x2109eb20fb34703fe7ab0231bf0e93d972fe78423b72e7fb1f797f16821
1515
# for the contracts below which contain immutables, the hash is calculated from the masked bytecode (not the deployed bytecode as-is).
1616
# sections of the bytecode that contain the immutables are masked (set to 0) before computing the hash
1717
anchor_state_registry = "0x46e8ecc46fd27f178ada1c5a2168c09512e7ed2f7d4ac9a7c74123af26fd850c" # version "2.0.0"
18+
dispute_game_factory = "0x0d25ffc72ede825443defd6dfb435f5ddeb2e9ce0e3da3e40af069f3ba6bef0f" # version "1.0.0"
1819
delayed_weth = "0xc4fb1a6fa54865937fda773735938b5374d23fcf556bb4c69686f23be96b3f97" # version "1.1.0"
1920

2021

@@ -31,6 +32,7 @@ permissioned_dispute_game = "0x666d50416294caefc6a0a9c7c3803d75dbb76e9b85384c4aa
3132
# for the contracts below which contain immutables, the hash is calculated from the masked bytecode (not the deployed bytecode as-is).
3233
# sections of the bytecode that contain the immutables are masked (set to 0) before computing the hash
3334
anchor_state_registry = "0x46e8ecc46fd27f178ada1c5a2168c09512e7ed2f7d4ac9a7c74123af26fd850c" # version "2.0.0"
35+
dispute_game_factory = "0x0d25ffc72ede825443defd6dfb435f5ddeb2e9ce0e3da3e40af069f3ba6bef0f" # version "1.0.0"
3436
delayed_weth = "0xc4fb1a6fa54865937fda773735938b5374d23fcf556bb4c69686f23be96b3f97" # version "1.1.0"
3537
mips = "0x8f1cb311156fdc848bd99f26b08d88f0bec2c419bbca97533049907794390b4b" # version "1.1.0"
3638
fault_dispute_game = "0x94ded5b66802fe6287d2105fad9ab6c4c8f25275db55d997e07e2284c830595a" # version "1.2.0"
@@ -49,6 +51,18 @@ permissioned_dispute_game = "0xb0a3aed0fb964b33445061d604e5fcfb952fb99dbff4d6f0c
4951
# for the contracts below which contain immutables, the hash is calculated from the masked bytecode (not the deployed bytecode as-is).
5052
# sections of the bytecode that contain the immutables are masked (set to 0) before computing the hash
5153
anchor_state_registry = "0x46e8ecc46fd27f178ada1c5a2168c09512e7ed2f7d4ac9a7c74123af26fd850c" # version "2.0.0"
52-
delayed_weth = "0x430b15595a625dc821f34b42c354676de32c2f112fbd89978b8196cf66d823d5" # version "1.0.0"
54+
dispute_game_factory = "0x0d25ffc72ede825443defd6dfb435f5ddeb2e9ce0e3da3e40af069f3ba6bef0f" # version "1.0.0"
55+
delayed_weth = "0x66cb5d2c98ecd6b1e8768bdee4abd7a66775beaa50d995af1ac24f0cd837d1a3" # version "1.0.0"
5356
mips = "0xce7e7ce9dc98d151ecbbb02a548534ab33a59aa4091ad52be0a703dc6c4f3d39" # version "1.0.0"
5457
fault_dispute_game = "0x875e36fd4039b580b20051d25336a7db1ea982613edcc7b83f9d6406f8c84959" # version "1.2.0"
58+
59+
# MCP https://github.com/ethereum-optimism/optimism/releases/tag/op-contracts%2Fv1.3.0
60+
["op-contracts/v1.3.0"]
61+
address_manager = ""
62+
l1_cross_domain_messenger = "0xc919b535f7bbbaa57f32b602f26000e3997c22c933efbb92a36e053b20b2b4b1" # version 2.3.0
63+
l1_erc721_bridge = "0xa1a3ea3996797cf5035dafff9335386f8b00c58ff22aaa4c34b02c4ae7cf5e8a" # version 2.1.0
64+
l1_standard_bridge = "0x6894839591ba2aa1f6ba43526113e25fd795ec5c5a48202e664c931cd170aaee" # version 2.1.0
65+
optimism_mintable_erc20_factory = "0xb8ec72533005496795f8fc0c71f0312578ff626f7b94699984e1add6dc2f3b88" # version 1.9.0
66+
optimism_portal = "0x3708314c65d3949911060cd82c167a5ace2921694c2bab2d9a60814bde3412d7" # version 3.10.0
67+
system_config = "0x72ba7f6fe9ce5e4bcc7145b3f0681112f2161b025618dedb74bb3ea15bbcbe90" # version 2.2.0
68+
l2_output_oracle = "0xc7a28a1f30fa6c5c10c7e164c4cb50015f99f91ccbbaa5c156ad5e4e20be80e2"

validation/standard/standard-immutables.toml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88
#
99
# "immutableReferences":{"85798":[{"start":178,"length":32},{"start":1771,"length":32}]}}
1010
#
11-
[releases."op-contracts/v1.8.0-rc.3"] # Holocene https://github.com/ethereum-optimism/optimism/releases/tag/op-contracts%2Fv1.8.0-rc.3
11+
["op-contracts/v1.8.0-rc.4"] # Holocene https://github.com/ethereum-optimism/optimism/releases/tag/op-contracts%2Fv1.8.0-rc.4
1212
# Updated in this release
1313
fault_dispute_game = '{"121231":[{"start":1926,"length":32},{"start":12628,"length":32}],"121234":[{"start":2891,"length":32},{"start":7484,"length":32},{"start":7822,"length":32},{"start":11526,"length":32},{"start":11660,"length":32},{"start":12171,"length":32},{"start":12470,"length":32}],"121237":[{"start":2724,"length":32},{"start":7283,"length":32},{"start":7577,"length":32},{"start":8183,"length":32},{"start":12437,"length":32},{"start":14230,"length":32},{"start":16139,"length":32},{"start":17960,"length":32},{"start":18262,"length":32},{"start":18519,"length":32},{"start":18732,"length":32}],"121241":[{"start":2673,"length":32},{"start":3936,"length":32},{"start":7722,"length":32},{"start":8329,"length":32},{"start":8424,"length":32},{"start":11254,"length":32},{"start":11320,"length":32}],"121245":[{"start":1204,"length":32},{"start":7866,"length":32},{"start":12866,"length":32},{"start":13719,"length":32}],"121249":[{"start":2189,"length":32},{"start":9639,"length":32},{"start":14792,"length":32}],"121253":[{"start":1319,"length":32},{"start":6476,"length":32},{"start":9344,"length":32},{"start":10730,"length":32},{"start":15892,"length":32}],"121257":[{"start":1555,"length":32},{"start":6026,"length":32},{"start":9704,"length":32}],"121260":[{"start":2590,"length":32},{"start":14649,"length":32}],"121264":[{"start":1723,"length":32},{"start":8123,"length":32},{"start":8230,"length":32},{"start":8281,"length":32}]}'
14-
mips = '{"92095":[{"start":178,"length":32},{"start":1869,"length":32}]}'
14+
mips = '{"106243":[{"start":178,"length":32},{"start":1869,"length":32}]}'
1515
# Unchanged in this release
1616
anchor_state_registry = '{"105981":[{"start":387,"length":32},{"start":828,"length":32},{"start":2296,"length":32}]}'
1717
delayed_weth = '{"110979":[{"start":831,"length":32},{"start":4133,"length":32}]}'
@@ -22,7 +22,6 @@ delayed_weth = '{"110979":[{"start":831,"length":32},{"start":4133,"length":32}]
2222
fault_dispute_game = '{"106803":[{"start":1926,"length":32},{"start":12628,"length":32}],"106806":[{"start":2891,"length":32},{"start":7484,"length":32},{"start":7822,"length":32},{"start":11526,"length":32},{"start":11660,"length":32},{"start":12171,"length":32},{"start":12470,"length":32}],"106809":[{"start":2724,"length":32},{"start":7283,"length":32},{"start":7577,"length":32},{"start":8183,"length":32},{"start":12437,"length":32},{"start":14230,"length":32},{"start":16139,"length":32},{"start":17960,"length":32},{"start":18262,"length":32},{"start":18519,"length":32},{"start":18732,"length":32}],"106813":[{"start":2673,"length":32},{"start":3936,"length":32},{"start":7722,"length":32},{"start":8329,"length":32},{"start":8424,"length":32},{"start":11254,"length":32},{"start":11320,"length":32}],"106817":[{"start":1204,"length":32},{"start":7866,"length":32},{"start":12866,"length":32},{"start":13719,"length":32}],"106821":[{"start":2189,"length":32},{"start":9639,"length":32},{"start":14792,"length":32}],"106825":[{"start":1319,"length":32},{"start":6476,"length":32},{"start":9344,"length":32},{"start":10730,"length":32},{"start":15892,"length":32}],"106829":[{"start":1555,"length":32},{"start":6026,"length":32},{"start":9704,"length":32}],"106832":[{"start":2590,"length":32},{"start":14649,"length":32}],"106836":[{"start":1723,"length":32},{"start":8123,"length":32},{"start":8230,"length":32},{"start":8281,"length":32}]}'
2323
mips = '{"92095":[{"start":178,"length":32},{"start":1771,"length":32}]}'
2424

25-
2625
["op-contracts/v1.4.0"] # Fault Proof contract AST https://github.com/ethereum-optimism/optimism/releases/tag/op-contracts%2Fv1.4.0
2726
anchor_state_registry = '{"92829":[{"start":387,"length":32},{"start":828,"length":32},{"start":2296,"length":32}]}'
2827
delayed_weth = '{"97827":[{"start":831,"length":32},{"start":4133,"length":32}]}'
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package standard_test
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
. "github.com/ethereum-optimism/superchain-registry/superchain"
8+
"github.com/ethereum-optimism/superchain-registry/validation"
9+
"github.com/ethereum-optimism/superchain-registry/validation/standard"
10+
"github.com/ethereum/go-ethereum/common"
11+
"github.com/ethereum/go-ethereum/ethclient"
12+
"github.com/stretchr/testify/require"
13+
)
14+
15+
// This test will check:
16+
// For each each superchain
17+
// For each contract release
18+
// For each contract version declaration which specifies an address
19+
// There is a contract at that address with the matching bytecode and semver.
20+
// This is a consistency check on the standard package itself, not any particular chain
21+
func TestStandardVersionConsistency(t *testing.T) {
22+
for _, superchain := range []string{"sepolia", "mainnet"} {
23+
rpcEndpoint := Superchains[superchain].Config.L1.PublicRPC
24+
require.NotEmpty(t, rpcEndpoint)
25+
26+
client, err := ethclient.Dial(rpcEndpoint)
27+
require.NoErrorf(t, err, "could not dial rpc endpoint %s", rpcEndpoint)
28+
29+
for tag, release := range standard.ContractVersions[superchain] {
30+
for _, contract := range release.GetNonEmpty() {
31+
vc, err := release.VersionedContractFor(contract)
32+
require.NoError(t, err)
33+
if vc.Address != nil && vc.ImplementationAddress != nil {
34+
require.FailNow(t, "should not specify both address and implementation address")
35+
}
36+
addressToCheck := vc.Address
37+
if vc.ImplementationAddress != nil {
38+
addressToCheck = vc.ImplementationAddress
39+
}
40+
if addressToCheck != nil {
41+
version, err := validation.GetContractVersion(context.Background(), common.Address(*addressToCheck), client)
42+
require.NoErrorf(t, err, "could not get version for address %s, contract %s, superchain %s, release %s", addressToCheck, contract, superchain, tag)
43+
require.Equal(t, vc.Version, version, "version mismatch for %s", addressToCheck)
44+
45+
dummyChainId := uint64(12345678) // we don't actually need a chain ID for this since we wouldn't ever pass a proxy here
46+
bytecodeHash, err := validation.GetBytecodeHash(context.Background(), dummyChainId, contract, common.Address(*addressToCheck), client, tag)
47+
require.NoError(t, err)
48+
49+
h, err := standard.BytecodeHashes[tag].GetBytecodeHashFor(contract)
50+
require.NoErrorf(t, err, "could not get hash for contract %s, release %s", contract, tag)
51+
require.Equal(t, h, bytecodeHash, "bytecode hash mismatch for ", contract, "release", tag, "superchain", superchain, "should be", bytecodeHash)
52+
}
53+
}
54+
}
55+
}
56+
}

validation/standard/standard-versions-mainnet.toml

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
1-
[releases]
2-
31
# Contracts which are
42
# * unproxied singletons: specify a standard "address"
53
# * proxied : specify a standard "implementation_address"
64
# * neither : specify neither a standard "address" nor "implementation_address"
75

8-
# Holocene https://github.com/ethereum-optimism/optimism/releases/tag/op-contracts%2Fv1.8.0-rc.3
9-
[releases."op-contracts/v1.8.0-rc.3"]
6+
# Holocene https://github.com/ethereum-optimism/optimism/releases/tag/op-contracts%2Fv1.8.0-rc.4
7+
["op-contracts/v1.8.0-rc.4"]
108
# Updated in this release
119
system_config = { version = "2.3.0", implementation_address = "0xAB9d6cB7A427c0765163A7f45BB91cAfe5f2D375" } # UPDATED IN THIS RELEASE
1210
fault_dispute_game = { version = "1.3.1" } # UPDATED IN THIS RELEASE
@@ -25,7 +23,7 @@ l1_standard_bridge = { version = "2.1.0", implementation_address = "0x64B5a5Ed26
2523
optimism_mintable_erc20_factory = { version = "1.9.0", implementation_address = "0xE01efbeb1089D1d1dB9c6c8b135C934C0734c846" }
2624

2725
# Fault Proofs https://github.com/ethereum-optimism/optimism/releases/tag/op-contracts%2Fv1.6.0
28-
[releases."op-contracts/v1.6.0"]
26+
["op-contracts/v1.6.0"]
2927
optimism_portal = { version = "3.10.0", implementation_address = "0xe2F826324b2faf99E513D16D266c3F80aE87832B" }
3028
system_config = { version = "2.2.0", implementation_address = "0xF56D96B2535B932656d3c04Ebf51baBff241D886" }
3129
anchor_state_registry = { version = "2.0.0" }
@@ -42,7 +40,7 @@ l1_standard_bridge = { version = "2.1.0", implementation_address = "0x64B5a5Ed26
4240
optimism_mintable_erc20_factory = { version = "1.9.0", implementation_address = "0xE01efbeb1089D1d1dB9c6c8b135C934C0734c846" }
4341

4442
# Fault Proofs https://github.com/ethereum-optimism/optimism/releases/tag/op-contracts%2Fv1.4.0
45-
[releases."op-contracts/v1.4.0"]
43+
["op-contracts/v1.4.0"]
4644
optimism_portal = { version = "3.10.0", implementation_address = "0xe2F826324b2faf99E513D16D266c3F80aE87832B" }
4745
system_config = { version = "2.2.0", implementation_address = "0xF56D96B2535B932656d3c04Ebf51baBff241D886" }
4846
anchor_state_registry = { version = "1.0.0" }
@@ -54,7 +52,7 @@ mips = { version = "1.0.1", address = "0x0f8EdFbDdD3c0256A80AD8C0F2560B1807873C9
5452
preimage_oracle = { version = "1.0.0", address = "0xD326E10B8186e90F4E2adc5c13a2d0C137ee8b34" }
5553

5654
# MCP https://github.com/ethereum-optimism/optimism/releases/tag/op-contracts%2Fv1.3.0
57-
[releases."op-contracts/v1.3.0"]
55+
["op-contracts/v1.3.0"]
5856
l1_cross_domain_messenger = { version = "2.3.0", implementation_address = "0xD3494713A5cfaD3F5359379DfA074E2Ac8C6Fd65" }
5957
l1_erc721_bridge = { version = "2.1.0", implementation_address = "0xAE2AF01232a6c4a4d3012C5eC5b1b35059caF10d" }
6058
l1_standard_bridge = { version = "2.1.0", implementation_address = "0x64B5a5Ed26DCb17370Ff4d33a8D503f0fbD06CfF" }

0 commit comments

Comments
 (0)