Commit be4d2f2e authored by Mark Tyneway's avatar Mark Tyneway Committed by GitHub

Merge pull request #3254 from ethereum-optimism/state-surger/bytes32

state-surgery: implement bytes32 handling
parents 815b6779 2d933448
...@@ -41,6 +41,12 @@ func EncodeStorageKeyValue(value any, entry solc.StorageLayoutEntry, storageType ...@@ -41,6 +41,12 @@ func EncodeStorageKeyValue(value any, entry solc.StorageLayoutEntry, storageType
encoded = append(encoded, &EncodedStorage{key, val}) encoded = append(encoded, &EncodedStorage{key, val})
case "bytes": case "bytes":
return nil, fmt.Errorf("%w: %s", errUnimplemented, label) return nil, fmt.Errorf("%w: %s", errUnimplemented, label)
case "bytes32":
val, err := EncodeBytes32Value(value, entry.Offset)
if err != nil {
return nil, err
}
encoded = append(encoded, &EncodedStorage{key, val})
default: default:
switch true { switch true {
case strings.HasPrefix(label, "contract"): case strings.HasPrefix(label, "contract"):
...@@ -137,6 +143,42 @@ func getElementEncoder(kind string) (ElementEncoder, error) { ...@@ -137,6 +143,42 @@ func getElementEncoder(kind string) (ElementEncoder, error) {
return nil, fmt.Errorf("unsupported type: %s", kind) return nil, fmt.Errorf("unsupported type: %s", kind)
} }
// EncodeBytes32Value will encode a bytes32 value. The offset
// is included so that it can implement the ElementEncoder
// interface, but the offset must always be 0.
func EncodeBytes32Value(value any, offset uint) (common.Hash, error) {
if offset != 0 {
return common.Hash{}, errors.New("offset must be 0")
}
return encodeBytes32Value(value)
}
// encodeBytes32Value implements the encoding of a bytes32
// value into a common.Hash that is suitable for storage.
func encodeBytes32Value(value any) (common.Hash, error) {
name := reflect.TypeOf(value).Name()
switch name {
case "string":
str, ok := value.(string)
if !ok {
return common.Hash{}, errInvalidType
}
val, err := hexutil.Decode(str)
if err != nil {
return common.Hash{}, err
}
return common.BytesToHash(val), nil
case "Hash":
hash, ok := value.(common.Hash)
if !ok {
return common.Hash{}, errInvalidType
}
return hash, nil
default:
return common.Hash{}, errInvalidType
}
}
// EncodeBoolValue will encode a boolean value given a storage // EncodeBoolValue will encode a boolean value given a storage
// offset. // offset.
func EncodeBoolValue(value any, offset uint) (common.Hash, error) { func EncodeBoolValue(value any, offset uint) (common.Hash, error) {
......
...@@ -2,6 +2,7 @@ package state_test ...@@ -2,6 +2,7 @@ package state_test
import ( import (
"encoding/json" "encoding/json"
"fmt"
"math/big" "math/big"
"os" "os"
"testing" "testing"
...@@ -51,6 +52,7 @@ func TestSetAndGetStorageSlots(t *testing.T) { ...@@ -51,6 +52,7 @@ func TestSetAndGetStorageSlots(t *testing.T) {
values["offset3"] = uint32(0xf33d35) values["offset3"] = uint32(0xf33d35)
values["offset4"] = uint64(0xd34dd34d00) values["offset4"] = uint64(0xd34dd34d00)
values["offset5"] = new(big.Int).SetUint64(0x43ad0043ad0043ad) values["offset5"] = new(big.Int).SetUint64(0x43ad0043ad0043ad)
values["_bytes32"] = common.Hash{0xff}
addresses := make(map[any]any) addresses := make(map[any]any)
addresses[big.NewInt(1)] = common.Address{19: 0xff} addresses[big.NewInt(1)] = common.Address{19: 0xff}
...@@ -120,6 +122,13 @@ OUTER: ...@@ -120,6 +122,13 @@ OUTER:
res, err = contract.Offset4(&bind.CallOpts{}) res, err = contract.Offset4(&bind.CallOpts{})
case "offset5": case "offset5":
res, err = contract.Offset5(&bind.CallOpts{}) res, err = contract.Offset5(&bind.CallOpts{})
case "_bytes32":
res, err = contract.Bytes32(&bind.CallOpts{})
result, ok := res.([32]uint8)
require.Equal(t, ok, true)
require.Nil(t, err)
require.Equal(t, common.BytesToHash(result[:]), value)
continue OUTER
case "addresses": case "addresses":
addrs, ok := value.(map[any]any) addrs, ok := value.(map[any]any)
require.Equal(t, ok, true) require.Equal(t, ok, true)
...@@ -130,7 +139,7 @@ OUTER: ...@@ -130,7 +139,7 @@ OUTER:
continue OUTER continue OUTER
} }
default: default:
require.Fail(t, "Unknown variable label", key) require.Fail(t, fmt.Sprintf("Unknown variable label: %s", key))
} }
require.Nil(t, err) require.Nil(t, err)
require.Equal(t, res, value) require.Equal(t, res, value)
...@@ -173,6 +182,10 @@ func testContractStateValuesAreEmpty(t *testing.T, contract *testdata.Testdata) ...@@ -173,6 +182,10 @@ func testContractStateValuesAreEmpty(t *testing.T, contract *testdata.Testdata)
offset5, err := contract.Offset5(&bind.CallOpts{}) offset5, err := contract.Offset5(&bind.CallOpts{})
require.Nil(t, err) require.Nil(t, err)
require.Equal(t, offset5.Uint64(), uint64(0)) require.Equal(t, offset5.Uint64(), uint64(0))
bytes32, err := contract.Bytes32(&bind.CallOpts{})
require.Nil(t, err)
require.Equal(t, common.BytesToHash(bytes32[:]), common.Hash{})
} }
func TestMergeStorage(t *testing.T) { func TestMergeStorage(t *testing.T) {
...@@ -403,3 +416,25 @@ func TestEncodeAddressValue(t *testing.T) { ...@@ -403,3 +416,25 @@ func TestEncodeAddressValue(t *testing.T) {
require.Equal(t, got, test.expect) require.Equal(t, got, test.expect)
} }
} }
func TestEncodeBytes32Value(t *testing.T) {
cases := []struct {
bytes32 any
expect common.Hash
}{
{
bytes32: common.Hash{0xff},
expect: common.Hash{0xff},
},
{
bytes32: "0x11ffffff00000000000000000000000000000000000000000000000000000000",
expect: common.HexToHash("0x11ffffff00000000000000000000000000000000000000000000000000000000"),
},
}
for _, test := range cases {
got, err := state.EncodeBytes32Value(test.bytes32, 0)
require.Nil(t, err)
require.Equal(t, got, test.expect)
}
}
This diff is collapsed.
...@@ -79,6 +79,22 @@ ...@@ -79,6 +79,22 @@
"offset": 16, "offset": 16,
"slot": "4", "slot": "4",
"type": "t_uint128" "type": "t_uint128"
},
{
"astId": 25,
"contract": "contracts/HelloWorld.sol:HelloWorld",
"label": "_bytes32",
"offset": 0,
"slot": "5",
"type": "t_bytes32"
},
{
"astId": 27,
"contract": "contracts/HelloWorld.sol:HelloWorld",
"label": "_string",
"offset": 0,
"slot": "6",
"type": "t_string_storage"
} }
], ],
"types": { "types": {
...@@ -92,6 +108,11 @@ ...@@ -92,6 +108,11 @@
"label": "bool", "label": "bool",
"numberOfBytes": "1" "numberOfBytes": "1"
}, },
"t_bytes32": {
"encoding": "inplace",
"label": "bytes32",
"numberOfBytes": "32"
},
"t_mapping(t_uint256,t_address)": { "t_mapping(t_uint256,t_address)": {
"encoding": "mapping", "encoding": "mapping",
"key": "t_uint256", "key": "t_uint256",
...@@ -99,6 +120,11 @@ ...@@ -99,6 +120,11 @@
"numberOfBytes": "32", "numberOfBytes": "32",
"value": "t_address" "value": "t_address"
}, },
"t_string_storage": {
"encoding": "bytes",
"label": "string",
"numberOfBytes": "32"
},
"t_uint128": { "t_uint128": {
"encoding": "inplace", "encoding": "inplace",
"label": "uint128", "label": "uint128",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment