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 753cc6ef1..889e31ba1 100644 --- a/validation/standard/init.go +++ b/validation/standard/init.go @@ -3,7 +3,6 @@ package standard import ( "embed" "io/fs" - "reflect" "github.com/BurntSushi/toml" "github.com/ethereum-optimism/superchain-registry/superchain" @@ -12,23 +11,6 @@ import ( //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), @@ -57,10 +39,17 @@ func init() { 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) @@ -70,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 NetworkVersions["mainnet"].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-mainnet.toml b/validation/standard/standard-versions-mainnet.toml index cb4d336a7..754e249dc 100644 --- a/validation/standard/standard-versions-mainnet.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 index ce33aaef1..277f9d096 100644 --- a/validation/standard/standard-versions-sepolia.toml +++ b/validation/standard/standard-versions-sepolia.toml @@ -1,5 +1,3 @@ -standard_release = "op-contracts/v1.6.0" - [releases] # Contracts which are diff --git a/validation/standard/versions.go b/validation/standard/versions.go index f30000c9b..196579168 100644 --- a/validation/standard/versions.go +++ b/validation/standard/versions.go @@ -8,17 +8,50 @@ 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 ( + 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 56128bc6c..5cd1e6a34 100644 --- a/validation/superchain-version_test.go +++ b/validation/superchain-version_test.go @@ -66,7 +66,7 @@ func getContractVersionsFromChain(list AddressList, client *ethclient.Client, ch wg := new(sync.WaitGroup) - contractsToCheckVersionOf := standard.NetworkVersions[chain.Superchain].Releases[standard.NetworkVersions[chain.Superchain].StandardRelease].GetNonEmpty() + contractsToCheckVersionOf := standard.NetworkVersions[chain.Superchain].Releases[standard.Release].GetNonEmpty() for _, contractName := range contractsToCheckVersionOf { a, err := list.AddressFor(contractName) @@ -124,7 +124,7 @@ func getContractBytecodeHashesFromChain(chainID uint64, list AddressList, client wg := new(sync.WaitGroup) - contractsToCheckBytecodeOf := standard.BytecodeHashes[standard.NetworkVersions[chain.Superchain].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) @@ -254,7 +255,7 @@ func getBytecodeHash(ctx context.Context, chainID uint64, contractName string, t } func requireStandardSemvers(t *testing.T, versions ContractVersions, isTestnet bool, chain *ChainConfig) { - standardVersions := standard.NetworkVersions[chain.Superchain].Releases[standard.NetworkVersions[chain.Superchain].StandardRelease] + 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.NetworkVersions[chain.Superchain].StandardRelease, diff) + standard.Release, diff) } } func requireStandardByteCodeHashes(t *testing.T, hashes standard.L1ContractBytecodeHashes, chain *ChainConfig) { - standardHashes := standard.BytecodeHashes[standard.NetworkVersions[chain.Superchain].StandardRelease] + 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.NetworkVersions[chain.Superchain].StandardRelease, diff) + standard.Release, diff) } }