Skip to content

Commit

Permalink
op-bindings: Canonicalize bindings, regenerate
Browse files Browse the repository at this point in the history
  • Loading branch information
mslipper committed Nov 8, 2022
1 parent 3f1c1a7 commit e60e6c7
Show file tree
Hide file tree
Showing 25 changed files with 512 additions and 21 deletions.
97 changes: 97 additions & 0 deletions op-bindings/ast/canonicalize.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package ast

import (
"regexp"
"sort"
"strconv"
"strings"

"github.com/ethereum-optimism/optimism/op-bindings/solc"
)

var remapTypeRe = regexp.MustCompile(`^t_[\w_]+\([\w]+\)([\d]+)$`)

// CanonicalizeASTIDs canonicalizes AST IDs in storage layouts so that they
// don't cause unnecessary conflicts/diffs. The implementation is not
// particularly efficient, but is plenty fast enough for our purposes.
// It works in two passes:
//
// 1. First, it finds all AST IDs in storage and types, and builds a
// map to replace them in the second pass.
// 2. The second pass performs the replacement.
//
// This function returns a copy of the passed-in storage layout. The
// inefficiency comes from replaceType, which performs a linear
// search of all replacements when performing substring matches of
// composite types.
func CanonicalizeASTIDs(in *solc.StorageLayout) *solc.StorageLayout {
lastId := uint(1000)
astIDRemappings := make(map[uint]uint)
typeRemappings := make(map[string]string)

for _, slot := range in.Storage {
astIDRemappings[slot.AstId] = lastId
lastId++
}

// Go map iteration order is random, so we need to sort
// keys here in order to prevent non-determinism.
var sortedOldTypes sort.StringSlice
for oldType := range in.Types {
sortedOldTypes = append(sortedOldTypes, oldType)
}
sortedOldTypes.Sort()

for _, oldType := range sortedOldTypes {
matches := remapTypeRe.FindAllStringSubmatch(oldType, -1)
if len(matches) == 0 {
continue
}

replaceAstID := matches[0][1]
newType := strings.Replace(oldType, replaceAstID, strconv.Itoa(int(lastId)), 1)
typeRemappings[oldType] = newType
lastId++
}

outLayout := &solc.StorageLayout{
Types: make(map[string]solc.StorageLayoutType),
}
for _, slot := range in.Storage {
outLayout.Storage = append(outLayout.Storage, solc.StorageLayoutEntry{
AstId: astIDRemappings[slot.AstId],
Contract: slot.Contract,
Label: slot.Label,
Offset: slot.Offset,
Slot: slot.Slot,
Type: replaceType(typeRemappings, slot.Type),
})
}

for _, oldType := range sortedOldTypes {
value := in.Types[oldType]
newType := replaceType(typeRemappings, oldType)
outLayout.Types[newType] = solc.StorageLayoutType{
Encoding: value.Encoding,
Label: value.Label,
NumberOfBytes: value.NumberOfBytes,
Key: replaceType(typeRemappings, value.Key),
Value: replaceType(typeRemappings, value.Value),
}
}
return outLayout
}

func replaceType(typeRemappings map[string]string, in string) string {
if typeRemappings[in] != "" {
return typeRemappings[in]
}

for oldType, newType := range typeRemappings {
if strings.Contains(in, oldType) {
return strings.Replace(in, oldType, newType, 1)
}
}

return in
}
48 changes: 48 additions & 0 deletions op-bindings/ast/canonicalize_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package ast

import (
"encoding/json"
"os"
"path"
"testing"

"github.com/ethereum-optimism/optimism/op-bindings/solc"
"github.com/stretchr/testify/require"
)

type astIDTest struct {
In *solc.StorageLayout `json:"in"`
Out *solc.StorageLayout `json:"out"`
}

func TestCanonicalize(t *testing.T) {
tests := []struct {
name string
filename string
}{
{
"simple",
"simple.json",
},
{
"remap public variables",
"public-variables.json",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
f, err := os.Open(path.Join("testdata", tt.filename))
require.NoError(t, err)
dec := json.NewDecoder(f)
var testData astIDTest
require.NoError(t, dec.Decode(&testData))
require.NoError(t, f.Close())

// Run 100 times to make sure that we aren't relying
// on random map iteration order.
for i := 0; i < 100; i++ {
require.Equal(t, testData.Out, CanonicalizeASTIDs(testData.In))
}
})
}
}
172 changes: 172 additions & 0 deletions op-bindings/ast/testdata/public-variables.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
{
"in": {
"storage": [
{
"astId": 37343,
"contract": "contracts/universal/ProxyAdmin.sol:ProxyAdmin",
"label": "owner",
"offset": 0,
"slot": "0",
"type": "t_address"
},
{
"astId": 27905,
"contract": "contracts/universal/ProxyAdmin.sol:ProxyAdmin",
"label": "proxyType",
"offset": 0,
"slot": "1",
"type": "t_mapping(t_address,t_enum(ProxyType)27899)"
},
{
"astId": 27910,
"contract": "contracts/universal/ProxyAdmin.sol:ProxyAdmin",
"label": "implementationName",
"offset": 0,
"slot": "2",
"type": "t_mapping(t_address,t_string_storage)"
},
{
"astId": 27914,
"contract": "contracts/universal/ProxyAdmin.sol:ProxyAdmin",
"label": "addressManager",
"offset": 0,
"slot": "3",
"type": "t_contract(AddressManager)4431"
},
{
"astId": 27918,
"contract": "contracts/universal/ProxyAdmin.sol:ProxyAdmin",
"label": "upgrading",
"offset": 20,
"slot": "3",
"type": "t_bool"
}
],
"types": {
"t_address": {
"encoding": "inplace",
"label": "address",
"numberOfBytes": "20"
},
"t_bool": {
"encoding": "inplace",
"label": "bool",
"numberOfBytes": "1"
},
"t_contract(AddressManager)4431": {
"encoding": "inplace",
"label": "contract AddressManager",
"numberOfBytes": "20"
},
"t_enum(ProxyType)27899": {
"encoding": "inplace",
"label": "enum ProxyAdmin.ProxyType",
"numberOfBytes": "1"
},
"t_mapping(t_address,t_enum(ProxyType)27899)": {
"encoding": "mapping",
"key": "t_address",
"label": "mapping(address => enum ProxyAdmin.ProxyType)",
"numberOfBytes": "32",
"value": "t_enum(ProxyType)27899"
},
"t_mapping(t_address,t_string_storage)": {
"encoding": "mapping",
"key": "t_address",
"label": "mapping(address => string)",
"numberOfBytes": "32",
"value": "t_string_storage"
},
"t_string_storage": {
"encoding": "bytes",
"label": "string",
"numberOfBytes": "32"
}
}
},
"out": {
"storage": [
{
"astId": 1000,
"contract": "contracts/universal/ProxyAdmin.sol:ProxyAdmin",
"label": "owner",
"offset": 0,
"slot": "0",
"type": "t_address"
},
{
"astId": 1001,
"contract": "contracts/universal/ProxyAdmin.sol:ProxyAdmin",
"label": "proxyType",
"offset": 0,
"slot": "1",
"type": "t_mapping(t_address,t_enum(ProxyType)1006)"
},
{
"astId": 1002,
"contract": "contracts/universal/ProxyAdmin.sol:ProxyAdmin",
"label": "implementationName",
"offset": 0,
"slot": "2",
"type": "t_mapping(t_address,t_string_storage)"
},
{
"astId": 1003,
"contract": "contracts/universal/ProxyAdmin.sol:ProxyAdmin",
"label": "addressManager",
"offset": 0,
"slot": "3",
"type": "t_contract(AddressManager)1005"
},
{
"astId": 1004,
"contract": "contracts/universal/ProxyAdmin.sol:ProxyAdmin",
"label": "upgrading",
"offset": 20,
"slot": "3",
"type": "t_bool"
}
],
"types": {
"t_address": {
"encoding": "inplace",
"label": "address",
"numberOfBytes": "20"
},
"t_bool": {
"encoding": "inplace",
"label": "bool",
"numberOfBytes": "1"
},
"t_contract(AddressManager)1005": {
"encoding": "inplace",
"label": "contract AddressManager",
"numberOfBytes": "20"
},
"t_enum(ProxyType)1006": {
"encoding": "inplace",
"label": "enum ProxyAdmin.ProxyType",
"numberOfBytes": "1"
},
"t_mapping(t_address,t_enum(ProxyType)1006)": {
"encoding": "mapping",
"key": "t_address",
"label": "mapping(address => enum ProxyAdmin.ProxyType)",
"numberOfBytes": "32",
"value": "t_enum(ProxyType)1006"
},
"t_mapping(t_address,t_string_storage)": {
"encoding": "mapping",
"key": "t_address",
"label": "mapping(address => string)",
"numberOfBytes": "32",
"value": "t_string_storage"
},
"t_string_storage": {
"encoding": "bytes",
"label": "string",
"numberOfBytes": "32"
}
}
}
}
Loading

0 comments on commit e60e6c7

Please sign in to comment.