Commit 626c3432 authored by Mark Tyneway's avatar Mark Tyneway Committed by GitHub

state-surgery: add string serialization support (#3258)

Adds the ability to serialize strings in state.
It only supports strings less than 32 bytes long
which would require major refactoring and we do
not currently need that functionality.

Main test cases come from mainnet WETH contract
Co-authored-by: default avatarmergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
parent a1a73e64
...@@ -23,9 +23,10 @@ func EncodeStorageKeyValue(value any, entry solc.StorageLayoutEntry, storageType ...@@ -23,9 +23,10 @@ func EncodeStorageKeyValue(value any, entry solc.StorageLayoutEntry, storageType
label := storageType.Label label := storageType.Label
encoded := make([]*EncodedStorage, 0) encoded := make([]*EncodedStorage, 0)
key := encodeSlotKey(entry)
switch storageType.Encoding { switch storageType.Encoding {
case "inplace": case "inplace":
key := encodeSlotKey(entry)
switch label { switch label {
case "bool": case "bool":
val, err := EncodeBoolValue(value, entry.Offset) val, err := EncodeBoolValue(value, entry.Offset)
...@@ -68,7 +69,16 @@ func EncodeStorageKeyValue(value any, entry solc.StorageLayoutEntry, storageType ...@@ -68,7 +69,16 @@ func EncodeStorageKeyValue(value any, entry solc.StorageLayoutEntry, storageType
} }
case "dynamic_array": case "dynamic_array":
case "bytes": case "bytes":
return nil, fmt.Errorf("%w: %s", errUnimplemented, label) switch label {
case "string":
val, err := EncodeStringValue(value, entry.Offset)
if err != nil {
return nil, err
}
encoded = append(encoded, &EncodedStorage{key, val})
default:
return nil, fmt.Errorf("%w: %s", errUnimplemented, label)
}
case "mapping": case "mapping":
if strings.HasPrefix(storageType.Value, "mapping") { if strings.HasPrefix(storageType.Value, "mapping") {
return nil, fmt.Errorf("%w: %s", errUnimplemented, "nested mappings") return nil, fmt.Errorf("%w: %s", errUnimplemented, "nested mappings")
...@@ -179,6 +189,45 @@ func encodeBytes32Value(value any) (common.Hash, error) { ...@@ -179,6 +189,45 @@ func encodeBytes32Value(value any) (common.Hash, error) {
} }
} }
// EncodeStringValue will encode a string to a type suitable
// for storage in state. The offset must be 0.
func EncodeStringValue(value any, offset uint) (common.Hash, error) {
if offset != 0 {
return common.Hash{}, errors.New("offset must be 0")
}
return encodeStringValue(value)
}
// encodeStringValue implements the string encoding. Values larger
// than 31 bytes are not supported because they will be stored
// in multiple storage slots.
func encodeStringValue(value any) (common.Hash, error) {
name := reflect.TypeOf(value).Name()
switch name {
case "string":
str, ok := value.(string)
if !ok {
return common.Hash{}, errInvalidType
}
data := []byte(str)
// Values that are 32 bytes or longer are not supported
if len(data) >= 32 {
return common.Hash{}, errors.New("string value too long")
}
// The data is right padded with 2 * the length
// of the data in the final byte
padded := common.RightPadBytes(data, 32)
padded[len(padded)-1] = byte(len(data) * 2)
return common.BytesToHash(padded), 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) {
......
...@@ -53,6 +53,7 @@ func TestSetAndGetStorageSlots(t *testing.T) { ...@@ -53,6 +53,7 @@ func TestSetAndGetStorageSlots(t *testing.T) {
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} values["_bytes32"] = common.Hash{0xff}
values["_string"] = "foobar"
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}
...@@ -129,6 +130,8 @@ OUTER: ...@@ -129,6 +130,8 @@ OUTER:
require.Nil(t, err) require.Nil(t, err)
require.Equal(t, common.BytesToHash(result[:]), value) require.Equal(t, common.BytesToHash(result[:]), value)
continue OUTER continue OUTER
case "_string":
res, err = contract.String(&bind.CallOpts{})
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)
...@@ -438,3 +441,30 @@ func TestEncodeBytes32Value(t *testing.T) { ...@@ -438,3 +441,30 @@ func TestEncodeBytes32Value(t *testing.T) {
require.Equal(t, got, test.expect) require.Equal(t, got, test.expect)
} }
} }
func TestEncodeStringValue(t *testing.T) {
cases := []struct {
str any
expect common.Hash
}{
{
str: "foo",
expect: common.Hash{0x66, 0x6f, 0x6f, 31: 6},
},
// Taken from mainnet WETH at 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
{
str: "Wrapped Ether",
expect: common.HexToHash("0x577261707065642045746865720000000000000000000000000000000000001a"),
},
{
str: "WETH",
expect: common.HexToHash("0x5745544800000000000000000000000000000000000000000000000000000008"),
},
}
for _, test := range cases {
got, err := state.EncodeStringValue(test.str, 0)
require.Nil(t, err)
require.Equal(t, got, test.expect)
}
}
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