Good or bad external storage patterns?

Hi there, I am currently using an external storage pattern similar to what is described in this post, and got a few questions:

In the pattern there, all keys in mappings are hashed, for example, for an ID to Value mapping, the ID is first hashed to a bytes32 key, and mapping(bytes32 => uint256) is used for setting, getting and deleting all (bytes32, uint256) pairs. I am wondering if this is necessary.

I could understand in that post, a mapping for a type of values can be taken care of by only one mapping for all types of keys, be it a string or an uint256, as long as the mapped value is an uint256, the same mapping can be used. But is it really a good pattern?

What could go wrong if a plain mapping(uint256 => uint256) is used instead? This allows more specific mappings, like mapping(uint256 => uint256) _IDToValue; mapping(uint256 => uint256) _BalanceToBalance, etc.

To come to a single question, is it also OK to use primitive types as keys instead of hashed values in an external storage pattern? Thanks.

@spalladino Could you please take a look at this question as well.

Hey @maxaero! You can certainly use a mapping with a different key type if it suits your use case, which would save you a hashing operation. Keep in mind though that the keys on a mapping are always hashed though (to determine the resulting position in storage), so you’ll always be paying the cost of at least one keccak.

The rationale for using bytes32 for keys is to minimize the number of different mappings you set up in external storage. For the sake of the argument, let’s say you have 4 primitive types (bytes, uint, bool, string), so you’d need 4 different mappings to store these different types in mapping(bytes32 => bytes), mapping(bytes32 => uint), etc. But if you want to also use different types for the keys (which, under the hood, remember that will always get hashed), you now need 4 x 4 mappings, which is more annoying to maintain.

That said, I’m not a fan of this storage pattern. I think it introduces an unnecessary complexity when coding your contracts, and prevents reusing existing contract libraries that do not follow this pattern.

1 Like

Thanks, @spalladino . Since it is a pattern, not a requirement, a hybrid approach can be taken in pratice.