Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 11 additions & 13 deletions validation/immutable-references.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
}
}
}
Expand Down
59 changes: 17 additions & 42 deletions validation/standard/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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)
Expand All @@ -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
}
}
}
1 change: 1 addition & 0 deletions validation/standard/standard-releases.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
standard_release = "op-contracts/v1.6.0"
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
standard_release = "op-contracts/v1.6.0"

[releases]

# Contracts which are
Expand Down
23 changes: 23 additions & 0 deletions validation/standard/standard-versions-sepolia.toml
Original file line number Diff line number Diff line change
@@ -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" }
41 changes: 36 additions & 5 deletions validation/standard/versions.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
)
Expand Down
31 changes: 16 additions & 15 deletions validation/superchain-version_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
}
}

Expand Down