-
Notifications
You must be signed in to change notification settings - Fork 11.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adds EnumerableMap. #2072
Adds EnumerableMap. #2072
Conversation
I'm not sure what the best way to recommend a library to OZ contracts is, so I figured I would start with a PR and see what happens. This is code is _strongly_ based on the OZ EnumerableSet. The biggest usability difference is that you have to instantiate manually and then call `.initialize` rather than doing `EnumerableMap.newMap()`. This is because struct arrays aren't supported in the ways necessary to make `EnumerableMap.newMap()` work. The `require` statements are basically invariants to protect against bugs (and provide an early warning during testing) but could be removed as they should never be hit (really they are assertions with useful error messages). I also changed the array to be 1-indexed, which differs from `EnumerableSet` which has a 0-indexed array and then treats the index values as all off-by-one. I personally found the code easier to understand with a placeholder value at the first entry position rather than having to shift the indexes by 1. Since the placeholder is just a 0 value, I suspect there is no meaningful gas difference between the strategies and it is just a matter of style. The biggest problem here, of course (same as `EnumerableSet`) is that Solidity doesn't contain generics, so the user will need to change the key and value types to match their code. The nice thing here though is that the key can be any valid mapping key and the value can be anything including structs. The user only needs to change the type in a few places. I chose to use `address` and `uint8` because you can do a find/replace as neither type is used outside the key/value types and it is as reasonable of a choice as any.
Compile failure is due to a change in Solidity 0.6.x (not sure when exactly). There is now a |
Also added a `tryGet` for when you _want_ a sentinel value instead of throwing. Also allows someone who is copying this code to customize the sentinel value to something besides `0` if desired (e.g., INT_MAX).
Hello @MicahZoltu, thank you for this suggestion! Sorry I took so long to review this. To clarify for people with less context, While I think this would be a useful primitive to have (a true enumerable Solidity
While I agree that shifting indexes makes the code harder to read (because you need this extra context), I'd argue 1-indexing also has similar added complexity. There's one big difference between the two though: by shifting indexes we can remove the need for the This is significant enough to make me prefer the shifting approach, even if it makes the code slightly more obtuse. |
I inquired with Solidity dev team about generics and while they are on the roadmap, they are fairly low priority, and a pretty big task, so I think we (dev community) should proceed for the time being under the assumption that they won't happen anytime soon. Given that, the question then becomes is it better to have an implementation that requires a find/replace to change the type, or is it better to have nothing at all. While in some cases I would argue that nothing at all is better, in this case I think this is a widely enough desired data structure that I believe it may be worth having the less-than-ideal version rather than nothing. In my own projects, I have wanted it on several occassions (which is what finally caused me to generalize the solution and type this up). I would rather people in a similar situation not write their own, and instead re-use something that is well established as a "good pattern" (whatever that ends up looking like). Your point about not requiring initialization is convincing enough to me to switch this over to 0-indexed. |
Thanks a lot for the contribution! The issue that we have is that so far we've highly discouraged copy-pasting code and making changes to it, with the argument that it's an error-prone procedure. We added For |
Another option, which isn't great but maybe better than all of the other options, would be to create a folder full of Yet another option would be to author a code-generator script that will create an |
Hi all! |
It looks like we may end up implementing a |
Hi all! |
I think we have to add the specific @nventuro Any news about the enumerable map for ERC721? |
I'm not sure what the best way to recommend a library to OZ contracts is, so I figured I would start with a PR and see what happens. This is code is strongly based on the OZ EnumerableSet. The biggest usability difference is that you have to instantiate manually and then call
.initialize
rather than doingEnumerableMap.newMap()
. This is because struct arrays aren't supported in the ways necessary to makeEnumerableMap.newMap()
work.The
require
statements are basically invariants to protect against bugs (and provide an early warning during testing) but could be removed as they should never be hit (really they are assertions with useful error messages).I also changed the array to be 1-indexed, which differs from
EnumerableSet
which has a 0-indexed array and then treats the index values as all off-by-one. I personally found the code easier to understand with a placeholder value at the first entry position rather than having to shift the indexes by 1. Since the placeholder is just a 0 value, I suspect there is no meaningful gas difference between the strategies and it is just a matter of style.The biggest problem here, of course (same as
EnumerableSet
) is that Solidity doesn't contain generics, so the user will need to change the key and value types to match their code. The nice thing here though is that the key can be any valid mapping key and the value can be anything including structs. The user only needs to change the type in a few places. I chose to useaddress
anduint8
because you can do a find/replace as neither type is used outside the key/value types and it is as reasonable of a choice as any.Fixes #