Skip to content
This repository has been archived by the owner on Aug 31, 2021. It is now read-only.

VDB-318 Allow for packed storage slots #121

Merged
merged 6 commits into from
Jul 23, 2019

Conversation

elizabethengelman
Copy link
Contributor

for storage values that are less than 32bytes the solidity compiler packs them into one storage slot, this PR starts to allow for that decoding

@elizabethengelman elizabethengelman force-pushed the allow-for-packed-storage-slots branch from 19ef7f2 to ebfb496 Compare July 18, 2019 21:22
@elizabethengelman elizabethengelman force-pushed the allow-for-packed-storage-slots branch 3 times, most recently from 376988a to b8e51a7 Compare July 19, 2019 15:03
@elizabethengelman elizabethengelman force-pushed the allow-for-packed-storage-slots branch 2 times, most recently from 83a6c76 to bf27858 Compare July 19, 2019 15:42
@elizabethengelman elizabethengelman force-pushed the allow-for-packed-storage-slots branch from bf27858 to 69ad521 Compare July 19, 2019 15:44
@elizabethengelman elizabethengelman requested review from rmulhol, i-norden, yaoandrew, Gslaughl and aaizuss and removed request for rmulhol and i-norden July 19, 2019 15:45
Copy link
Contributor

@rmulhol rmulhol left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM - nice catch! 🥇

@@ -43,6 +47,11 @@ func decodeUint256(raw []byte) string {
return n.String()
}

func decodeUint128(raw []byte) string {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if it might make sense to just have one decodeInteger function as long as the implementation's the same for every type 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking about that, and then also was wondering if it would make sense for each of these decode methods to do some sort of validation that the bytes passed in are the right length?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That seems like it would be cool, but do you know whether we can assume that input will be of a fixed length? Thinking that a uint128 could be 16 bytes if it's packed, or 32 if it's not. I suppose we could validate that the input is greater than the min required?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh yeah, that's a great point. validating a minimum requirement seems doable, but i'm not sure how useful that may be.

for now, i'll update this to have just one decodeInteger method.

return getMetadata(name, keys, valueType, packedNames, packedTypes)
}

func getMetadata(name string, keys map[Key]string, t ValueType, packedNames map[int]string, packedTypes map[int]ValueType) StorageValueMetadata {
return StorageValueMetadata{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is probably unnecessary but I'm wondering if we might want to put a guard here to check that if t == PackedSlot then packedNames != nil and packedTypes != nil (and vice versa if t != PackedSlot)

}

func getNumberOfBytes(valueType ValueType) int {
// 8 bits per byte
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe worth replacing with a var?

return decodedStorageSlotItems
}

func decodeIndividualItems(itemBytes []byte, valueType ValueType) string {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe this should be singular?

decodedStorageSlotItems := map[int]string{}
numberOfTypes := len(packedTypes)

for position := 0; position < numberOfTypes; position++ {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could maybe replace this with for k, v := range packedTypes

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maps also don't have a guaranteed iteration order (golang actually purposefully randomizes the intersection order 😆: https://blog.golang.org/go-maps-in-action), so if we iterate over them with for k, v := range packedTypes they won't necessarily decode the raw data in the right order.

I think an alternative to the current implementation could be to sort the packedTypes keys, and then do something like this

var keys []int
for k := range packedTypes {
    keys = append(keys, k)
}
sort.Ints(keys)
for _, k := range keys {
   // similar loop
}

Do you think that's more readable?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point! I'm fine to stick with the current implementation given that we need a guaranteed iteration order.

I am wondering how much it would muck things up to try an implementation that doesn't require a specific iteration order. Thinking that perhaps we could implement a function that returns the relevant slice for a given index provided the indexes and types of all values packed into the slot, but I'm also not sure whether or not that would be more readable 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, i need to think about this a bit more. 🤔 do you think it's worth adding a story in the back log to address this later?

Expect(repository.PassedMetadata).To(Equal(fakeMetadata))
expectedPassedValue := make(map[int]string)
expectedPassedValue[0] = "10800"
expectedPassedValue[1] = "172800"
Copy link

@aaizuss aaizuss Jul 19, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know that 2a300 is "172800" in decimal and 2a30 is "10800", which is why you're setting these expectedPassedValues, but I don't understand how you know where to get the 2 slots from "000000000000000000000000000000000000000000000002a300000000002a30" for the expected values (or how the 0 padding works - is that in the solidity documentation?). Why is 2a30 the first slot and 2a300 the second? how do you know the second isn't 2a3000

edit: I commented on this before seeing the decodePackedSlot function, which looks like it might help me understand

@@ -43,6 +47,11 @@ func decodeUint256(raw []byte) string {
return n.String()
}

func decodeUint128(raw []byte) string {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we have three decodeUintxx functions that have the same implementation? Would it make sense to have a single decodeUint function instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's a good point that maybe having just one decodeUint function makes sense. The one thing I'm wondering is if having some sort of validation would make sense - like if we call decodeUint128 that we make sure that the passed in bytes are a uint128. 🤔

}

func getNumberOfBytes(valueType ValueType) int {
// 8 bits per byte
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like we should have a constant for the number 8 that makes it clear a byte is 8 bits (ie ByteBits) - I imagine we use that a lot in the codebase. On the other hand that could be overkill and when I see computation/conversions with the number 8 I usually assume it's because it's the number of bits in a byte. 🤷‍♀

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i like the idea of having a constant

Copy link

@aaizuss aaizuss left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is good. I'm having a hard time internalizing how the storage slots work, so I think I need to read the solidity documentation again.

@@ -51,3 +60,52 @@ func decodeUint48(raw []byte) string {
func decodeAddress(raw []byte) string {
return common.BytesToAddress(raw).Hex()
}

func decodePackedSlot(raw []byte, packedTypes map[int]ValueType) map[int]string {
storageSlotData := raw
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this assignment is superfluous; could name the argument storageSlotData instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assigned the raw argument to storageSlotData because then on line 84 we're updating what is in storageSlotData by popping off the current item. Does that make sense?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yeah that makes perfect sense, sorry I missed that!

- factor out bitsPerByte constant
- panic if the necessary args aren't passed to GetStorageValueMetadataForPackedSlot
- only have one decode integer method in the deocoder file
@elizabethengelman elizabethengelman force-pushed the allow-for-packed-storage-slots branch from 7c8337b to 2c66afb Compare July 23, 2019 18:37
@elizabethengelman elizabethengelman merged commit 85aba29 into staging Jul 23, 2019
@elizabethengelman elizabethengelman deleted the allow-for-packed-storage-slots branch July 23, 2019 18:48
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants