diff --git a/validation/immutable-references.go b/validation/immutable-references.go index 4cf389751..e4d4ee893 100644 --- a/validation/immutable-references.go +++ b/validation/immutable-references.go @@ -22,9 +22,10 @@ type BytecodeAndImmutableReferences struct { } // initBytecodeImmutableMask returns the struct with coordinates of the immutable references in the deployed bytecode, if present -func initBytecodeImmutableMask(bytecode []byte, contractName string) (*BytecodeAndImmutableReferences, error) { +func initBytecodeImmutableMask(bytecode []byte, tag standard.Tag, contractName string) (*BytecodeAndImmutableReferences, error) { parsedImmutables := map[string][]ImmutableReference{} - refs, exists := standard.ContractASTsWithImmutableReferences[contractName] + + refs, exists := standard.BytecodeImmutables[tag].ForContractWithName(contractName) if exists { err := json.Unmarshal([]byte(refs), &parsedImmutables) if err != nil { @@ -34,19 +35,16 @@ func initBytecodeImmutableMask(bytecode []byte, contractName string) (*BytecodeA return &BytecodeAndImmutableReferences{Bytecode: bytecode, ImmutableReferences: parsedImmutables}, nil } -// maskBytecode checks for the presence of immutables in the contract, as indicated by the stored config and if present, -// masks the sections of the bytecode where immutables are stored. If immutables aren't present, the stored bytecode in the receiver is unaltered +// maskBytecode masks the sections of the bytecode where immutables are stored. +// If immutables aren't present, the stored bytecode in the receiver is unaltered func (deployed *BytecodeAndImmutableReferences) maskBytecode(contractName string) error { - _, exists := standard.ContractASTsWithImmutableReferences[contractName] - if exists { - for _, v := range deployed.ImmutableReferences { - for _, r := range v { - for i := r.Start; i < r.Start+r.Length; i++ { - if i >= len(deployed.Bytecode) { - return fmt.Errorf("immutable reference for contract %s [start:%d, length: %d] extends beyond bytecode", contractName, r.Start, r.Length) - } - deployed.Bytecode[i] = 0 + for _, v := range deployed.ImmutableReferences { + for _, r := range v { + for i := r.Start; i < r.Start+r.Length; i++ { + if i >= len(deployed.Bytecode) { + return fmt.Errorf("immutable reference for contract %s [start:%d, length: %d] extends beyond bytecode", contractName, r.Start, r.Length) } + deployed.Bytecode[i] = 0 } } } diff --git a/validation/standard/init.go b/validation/standard/init.go index 9ee9339f0..889e31ba1 100644 --- a/validation/standard/init.go +++ b/validation/standard/init.go @@ -3,31 +3,14 @@ package standard import ( "embed" "io/fs" - "reflect" "github.com/BurntSushi/toml" + "github.com/ethereum-optimism/superchain-registry/superchain" ) //go:embed *.toml var standardConfigFile embed.FS -// ContractASTsWithImmutableReferences caches the `immutableReferences` after parsing it from the config file. -// -// The config file is generated from the compiled contract AST (from the combined JSON -// -// artifact from the monorepo. We do this because the contracts and compiled artifacts are not available in the superchain -// registry. Ex: ethereum-optimism/optimism/packages/contracts-bedrock/forge-artifacts/MIPS.sol/MIPS.json -var ContractASTsWithImmutableReferences = map[string]string{} - -// ContractBytecodeImmutables stores the immutable references as a raw stringified JSON string in a TOML config. -// it is stored this way because it can be plucked out of the contract compilation output as is and pasted into the TOML config file. -type ContractBytecodeImmutables struct { - AnchorStateRegistry string `toml:"anchor_state_registry,omitempty"` - DelayedWETH string `toml:"delayed_weth,omitempty"` - FaultDisputeGame string `toml:"fault_dispute_game,omitempty"` - MIPS string `toml:"mips,omitempty"` -} - func init() { Config = ConfigType{ Params: make(map[string]*Params), @@ -44,16 +27,29 @@ func init() { Config.Params[network] = new(Params) decodeTOMLFileIntoConfig("standard-config-params-"+network+".toml", Config.Params[network]) + + var versions VersionTags = VersionTags{ + Releases: make(map[Tag]superchain.ContractVersions, 0), + } + + decodeTOMLFileIntoConfig("standard-versions-"+network+".toml", &versions) + NetworkVersions[network] = versions } - decodeTOMLFileIntoConfig("standard-versions.toml", &Versions) decodeTOMLFileIntoConfig("standard-bytecodes.toml", &BytecodeHashes) decodeTOMLFileIntoConfig("standard-immutables.toml", &BytecodeImmutables) - LoadImmutableReferences() + // Get the single standard release Tag (universal across superchain targets) + // and store in the standard.Release + temp := new(struct { + sr Tag `toml:"standard_release,omitempty"` + }) + decodeTOMLFileIntoConfig("standard-releases.toml", temp) + Release = temp.sr } -func decodeTOMLFileIntoConfig[T Params | Roles | MultisigRoles | VersionTags | BytecodeHashTags | BytecodeImmutablesTags](filename string, config *T) { +func decodeTOMLFileIntoConfig[ + T any](filename string, config *T) { data, err := fs.ReadFile(standardConfigFile, filename) if err != nil { panic(err) @@ -63,24 +59,3 @@ func decodeTOMLFileIntoConfig[T Params | Roles | MultisigRoles | VersionTags | B panic(err) } } - -// LoadImmutableReferences parses standard-immutables.toml and stores it in a map. Needs to be invoked one-time only. -func LoadImmutableReferences() { - var bytecodeImmutables *ContractBytecodeImmutables - for tag := range Versions.Releases { - for contractVersion, immutables := range BytecodeImmutables { - if tag == contractVersion { - bytecodeImmutables = &immutables - break - } - } - } - if bytecodeImmutables != nil { - s := reflect.ValueOf(bytecodeImmutables).Elem() - for i := 0; i < s.NumField(); i++ { - name := s.Type().Field(i).Name - value := string(reflect.ValueOf(*bytecodeImmutables).Field(i).String()) - ContractASTsWithImmutableReferences[name] = value - } - } -} diff --git a/validation/standard/standard-releases.toml b/validation/standard/standard-releases.toml new file mode 100644 index 000000000..498b3dbd1 --- /dev/null +++ b/validation/standard/standard-releases.toml @@ -0,0 +1 @@ +standard_release = "op-contracts/v1.6.0" diff --git a/validation/standard/standard-versions.toml b/validation/standard/standard-versions-mainnet.toml similarity index 98% rename from validation/standard/standard-versions.toml rename to validation/standard/standard-versions-mainnet.toml index cb4d336a7..754e249dc 100644 --- a/validation/standard/standard-versions.toml +++ b/validation/standard/standard-versions-mainnet.toml @@ -1,5 +1,3 @@ -standard_release = "op-contracts/v1.6.0" - [releases] # Contracts which are diff --git a/validation/standard/standard-versions-sepolia.toml b/validation/standard/standard-versions-sepolia.toml new file mode 100644 index 000000000..277f9d096 --- /dev/null +++ b/validation/standard/standard-versions-sepolia.toml @@ -0,0 +1,23 @@ +[releases] + +# Contracts which are +# * unproxied singletons: specify a standard "address" +# * proxied : specify a standard "implementation_address" +# * neither : specify neither a standard "address" nor "implementation_address" + +# Fault Proofs https://github.com/ethereum-optimism/optimism/releases/tag/op-contracts%2Fv1.6.0 +[releases."op-contracts/v1.6.0"] +optimism_portal = { version = "3.10.0", implementation_address = "0x35028bae87d71cbc192d545d38f960ba30b4b233" } +system_config = { version = "2.2.0", implementation_address = "0xCcdd86d581e40fb5a1C77582247BC493b6c8B169" } +anchor_state_registry = { version = "2.0.0" } +delayed_weth = { version = "1.1.0", implementation_address = "0x07f69b19532476c6cd03056d6bc3f1b110ab7538" } +dispute_game_factory = { version = "1.0.0", implementation_address = "0xa51bea7e4d34206c0bcb04a776292f2f19f0beec" } +fault_dispute_game = { version = "1.3.0" } +permissioned_dispute_game = { version = "1.3.0" } +mips = { version = "1.1.0", address = "0x47B0E34C1054009e696BaBAAd56165e1e994144d" } +preimage_oracle = { version = "1.1.2", address = "0x92240135b46fc1142dA181f550aE8f595B858854" } +l1_cross_domain_messenger = { version = "2.3.0", implementation_address = "0xD3494713A5cfaD3F5359379DfA074E2Ac8C6Fd65" } +l1_erc721_bridge = { version = "2.1.0", implementation_address = "0xae2af01232a6c4a4d3012c5ec5b1b35059caf10d" } +l1_standard_bridge = { version = "2.1.0", implementation_address = "0x64b5a5ed26dcb17370ff4d33a8d503f0fbd06cff" } +# l2_output_oracle -- This contract not used in fault proofs +optimism_mintable_erc20_factory = { version = "1.9.0", implementation_address = "0xe01efbeb1089d1d1db9c6c8b135c934c0734c846" } diff --git a/validation/standard/versions.go b/validation/standard/versions.go index 9722ef338..196579168 100644 --- a/validation/standard/versions.go +++ b/validation/standard/versions.go @@ -8,20 +8,51 @@ import ( type Tag string +// ContractBytecodeImmutables stores the immutable references as a raw stringified JSON string in a TOML config. +// it is stored this way because it can be plucked out of the contract compilation output as is and pasted into the TOML config file. +type ContractBytecodeImmutables struct { + AnchorStateRegistry string `toml:"anchor_state_registry,omitempty"` + DelayedWETH string `toml:"delayed_weth,omitempty"` + FaultDisputeGame string `toml:"fault_dispute_game,omitempty"` + MIPS string `toml:"mips,omitempty"` +} + +func (c ContractBytecodeImmutables) ForContractWithName(name string) (string, bool) { + // Use reflection to get the struct value and type + v := reflect.ValueOf(c) + + // Try to find the field by name + field := v.FieldByName(name) + if !field.IsValid() { + return "", false + } + + // Check if the field is of type string + if field.Type() != reflect.TypeOf("") { + return "", false + } + + // Check if the string is empty + s := field.Interface().(string) + if s == "" { + return "", false + } + + return s, true +} + type ( BytecodeHashTags = map[Tag]L1ContractBytecodeHashes BytecodeImmutablesTags = map[Tag]ContractBytecodeImmutables ) type VersionTags struct { - Releases map[Tag]superchain.ContractVersions `toml:"releases"` - StandardRelease Tag `toml:"standard_release,omitempty"` + Releases map[Tag]superchain.ContractVersions `toml:"releases"` } var ( - Versions VersionTags = VersionTags{ - Releases: make(map[Tag]superchain.ContractVersions, 0), - } + Release Tag + NetworkVersions = make(map[string]VersionTags) BytecodeHashes BytecodeHashTags = make(BytecodeHashTags, 0) BytecodeImmutables BytecodeImmutablesTags = make(BytecodeImmutablesTags, 0) ) diff --git a/validation/superchain-version_test.go b/validation/superchain-version_test.go index 991da10f5..5cd1e6a34 100644 --- a/validation/superchain-version_test.go +++ b/validation/superchain-version_test.go @@ -36,21 +36,21 @@ func checkForStandardVersions(t *testing.T, chain *ChainConfig) { // than the versions specified in the standard config isTestnet := (chain.Superchain == "sepolia" || chain.Superchain == "sepolia-dev-0") - versions, err := getContractVersionsFromChain(*Addresses[chain.ChainID], client) + versions, err := getContractVersionsFromChain(*Addresses[chain.ChainID], client, chain) require.NoError(t, err) - requireStandardSemvers(t, versions, isTestnet) + requireStandardSemvers(t, versions, isTestnet, chain) // don't perform bytecode checking for testnets if !isTestnet { - bytecodeHashes, err := getContractBytecodeHashesFromChain(chain.ChainID, *Addresses[chain.ChainID], client) + bytecodeHashes, err := getContractBytecodeHashesFromChain(chain.ChainID, *Addresses[chain.ChainID], client, chain) require.NoError(t, err) - requireStandardByteCodeHashes(t, bytecodeHashes) + requireStandardByteCodeHashes(t, bytecodeHashes, chain) } } // getContractVersionsFromChain pulls the appropriate contract versions from chain // using the supplied client (calling the version() method for each contract). It does this concurrently. -func getContractVersionsFromChain(list AddressList, client *ethclient.Client) (ContractVersions, error) { +func getContractVersionsFromChain(list AddressList, client *ethclient.Client, chain *ChainConfig) (ContractVersions, error) { // Prepare a concurrency-safe object to store version information in, and // spin up a goroutine for each contract we are checking (to speed things up). results := new(sync.Map) @@ -66,7 +66,7 @@ func getContractVersionsFromChain(list AddressList, client *ethclient.Client) (C wg := new(sync.WaitGroup) - contractsToCheckVersionOf := standard.Versions.Releases[standard.Versions.StandardRelease].GetNonEmpty() + contractsToCheckVersionOf := standard.NetworkVersions[chain.Superchain].Releases[standard.Release].GetNonEmpty() for _, contractName := range contractsToCheckVersionOf { a, err := list.AddressFor(contractName) @@ -108,7 +108,7 @@ func getContractVersionsFromChain(list AddressList, client *ethclient.Client) (C // getContractBytecodeHashesFromChain pulls the appropriate bytecode from chain // using the supplied client, concurrently. -func getContractBytecodeHashesFromChain(chainID uint64, list AddressList, client *ethclient.Client) (standard.L1ContractBytecodeHashes, error) { +func getContractBytecodeHashesFromChain(chainID uint64, list AddressList, client *ethclient.Client, chain *ChainConfig) (standard.L1ContractBytecodeHashes, error) { // Prepare a concurrency-safe object to store bytecode information in, and // spin up a goroutine for each contract we are checking (to speed things up). results := new(sync.Map) @@ -124,7 +124,7 @@ func getContractBytecodeHashesFromChain(chainID uint64, list AddressList, client wg := new(sync.WaitGroup) - contractsToCheckBytecodeOf := standard.BytecodeHashes[standard.Versions.StandardRelease].GetNonEmpty() + contractsToCheckBytecodeOf := standard.BytecodeHashes[standard.Release].GetNonEmpty() for _, contractName := range contractsToCheckBytecodeOf { contractAddress, err := list.AddressFor(contractName) @@ -238,7 +238,8 @@ func getBytecodeHash(ctx context.Context, chainID uint64, contractName string, t } // if the contract is known to have immutables, setup the filterer to mask the bytes which contain the variable's value - bytecodeImmutableFilterer, err := initBytecodeImmutableMask(code, contractName) + tag := standard.Release + bytecodeImmutableFilterer, err := initBytecodeImmutableMask(code, tag, contractName) // error indicates that the contract _does_ have immutables, but we weren't able to determine the coordinates of the immutables in the bytecode if err != nil { return "", fmt.Errorf("unable to check for presence of immutables in bytecode: %w", err) @@ -253,8 +254,8 @@ func getBytecodeHash(ctx context.Context, chainID uint64, contractName string, t return crypto.Keccak256Hash(bytecodeImmutableFilterer.Bytecode).Hex(), nil } -func requireStandardSemvers(t *testing.T, versions ContractVersions, isTestnet bool) { - standardVersions := standard.Versions.Releases[standard.Versions.StandardRelease] +func requireStandardSemvers(t *testing.T, versions ContractVersions, isTestnet bool, chain *ChainConfig) { + standardVersions := standard.NetworkVersions[chain.Superchain].Releases[standard.Release] s := reflect.ValueOf(standardVersions) c := reflect.ValueOf(versions) matches := checkMatchOrTestnet(s, c, isTestnet) @@ -265,12 +266,12 @@ func requireStandardSemvers(t *testing.T, versions ContractVersions, isTestnet b }, cmp.Ignore())) require.Truef(t, matches, "contract versions do not match the standard versions for the %s release \n (-removed from standard / +added to actual):\n %s", - standard.Versions.StandardRelease, diff) + standard.Release, diff) } } -func requireStandardByteCodeHashes(t *testing.T, hashes standard.L1ContractBytecodeHashes) { - standardHashes := standard.BytecodeHashes[standard.Versions.StandardRelease] +func requireStandardByteCodeHashes(t *testing.T, hashes standard.L1ContractBytecodeHashes, chain *ChainConfig) { + standardHashes := standard.BytecodeHashes[standard.Release] s := reflect.ValueOf(standardHashes) c := reflect.ValueOf(hashes) matches := checkMatch(s, c) @@ -279,7 +280,7 @@ func requireStandardByteCodeHashes(t *testing.T, hashes standard.L1ContractBytec diff := cmp.Diff(standardHashes, hashes) require.Truef(t, matches, "contract bytecode hashes do not match the standard bytecode hashes for the %s release \n (-removed from standard / +added to actual):\n %s", - standard.Versions.StandardRelease, diff) + standard.Release, diff) } }