-
Notifications
You must be signed in to change notification settings - Fork 358
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
Erc721 votes and general Votes component #1114
Conversation
Hey @ggonzalez94. I have some small comments, but since the design can still change, I won't submit those to avoid unnecessary noise. I will leave my thoughts on this comment: TLDR: Using traits to pass custom functionality to component implementations is a powerful feature, that we've been using to provide some capabilities hard to implement otherwise, like Hooks for token transfers. With this said, abusing this feature is an antipattern IMO, since users of the library need to define/import extra code that is called in a different context, which can be both confusing and easy to misuse. In this context, requiring an extra implementation ( Regarding the proposed design, I'm concerned about the UX for users of the library. The flow looks like this: Current design:
This can get quite verbose and somehow not as simple as it could be, I think we should favor having two separate components even if it includes some code repetition, just to improve the UX, since the user would only need to:
With this said, I think we should be able to reuse code in a separate module (not a component), where we can have the common storage and the common embeddable implementation defined using I recommend we separate the components now even if we have to reuse the Storage struct and the common logic in the embeddable implementation. PD: When proposed to use two different implementations of the same component, I was thinking of something like Ownable, where the only thing that needs to change in the contract is the embedded implementations |
Hey @ericnordelo! Thanks for the detailed feedback. I see your point, so I've been playing a bit with this and I don't think the DEX is that different between two different components vs single component with two different #[starknet::contract]
pub mod ERC721VotesMock {
component!(path: VotesComponent, storage: erc721_votes, event: ERC721VotesEvent);
component!(path: ERC721Component, storage: erc721, event: ERC721Event);
component!(path: SRC5Component, storage: src5, event: SRC5Event);
component!(path: NoncesComponent, storage: nonces, event: NoncesEvent);
//Votes and ERC721Votes
// Here's the only major difference - where instead of having two `impl`(internal and external)
// We have three(common internal and external) + ERC721 specific
#[abi(embed_v0)]
impl VotesImpl = VotesComponent::VotesImpl<ContractState>;
impl VotesInternalImpl = VotesComponent::InternalImpl<ContractState>;
impl ERC721VotesImpl = VotesComponent::ERC721VotesImpl<ContractState>;
// ERC721
#[abi(embed_v0)]
impl ERC721MixinImpl = ERC721Component::ERC721MixinImpl<ContractState>;
impl ERC721InternalImpl = ERC721Component::InternalImpl<ContractState>;
// Nonces
#[abi(embed_v0)]
impl NoncesImpl = NoncesComponent::NoncesImpl<ContractState>;
#[storage]
struct Storage {
#[substorage(v0)]
erc721_votes: VotesComponent::Storage,
#[substorage(v0)]
erc721: ERC721Component::Storage,
#[substorage(v0)]
src5: SRC5Component::Storage,
#[substorage(v0)]
nonces: NoncesComponent::Storage
}
#[event]
#[derive(Drop, starknet::Event)]
enum Event {
#[flat]
ERC721VotesEvent: VotesComponent::Event,
#[flat]
ERC721Event: ERC721Component::Event,
#[flat]
SRC5Event: SRC5Component::Event,
#[flat]
NoncesEvent: NoncesComponent::Event
}
/// Required for hash computation.
pub(crate) impl SNIP12MetadataImpl of SNIP12Metadata {
fn name() -> felt252 {
'DAPP_NAME'
}
fn version() -> felt252 {
'DAPP_VERSION'
}
}
//
// Hooks and ctor not included to make the example shorter
//
} As you can see the only big difference vs how this looks on a contract that uses ERC20Votes is only one |
My main concern was the extra impl, because while it is true we want to avoid code repetition, I'm hesitant regarding adding trait requirements in component impls, when the implementation of the trait doesn't need to be modified by the user. With this said, I realized now that with the approach you propose users won't even need to explicitly add the extra ERC721VotesImpl in the contract, because as you mentioned, one implementation will depend on the ERC721Component, and the other one on the ERC20Component, then the compiler can automatically get the right impl as long as the parent module (VotesComponent) is in scope. The preset can look like this:
Notice that it is enough to add just the VotesImpl as long as VotesComponent is in scope. And an ERC20Votes preset would only need to change the ERC721Component, the VotesImpl would work out of the box if the ERC20Votes impl is defined correctly in the VotesComponent. I have nothing against this approach of one component then. Let's add the ERC20Votes impl to the PR too. Even if we are not removing the Legacy ERC20Votes yet, we can mark it in the CHANGELOG as deprecated, and we can start favoring the new VotesComponent. |
I think the overall design looks really good! Some notes:
Great point
One case to consider is if a contract wanted to declare both ERC20 and ERC721 components. The contract can't expose both interfaces, but they could have the both InternalImpls in scope. The current design can't support that, but maybe we could leverage negative traits in the token votes impls? This was briefly discussed offline, but I'll repeat it here: I think we should remove the
|
Now that we settled on the design, I cleaned up the component and added a bunch of tests. I still need to finish the tests, add documentation and implement checkpoints using Vec(so far I'm reusing the current implementation that uses StorageArray). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good, sir! I left some comments and questions
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1114 +/- ##
==========================================
+ Coverage 91.76% 91.89% +0.13%
==========================================
Files 48 47 -1
Lines 1202 1197 -5
==========================================
- Hits 1103 1100 -3
+ Misses 99 97 -2
Continue to review full report in Codecov by Sentry.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The improvements look great and we're def almost there! I left a few minor comments, questions, and suggestions
#[test] | ||
fn test_delegate_to_recipient_updates_votes() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Were these added? Forgive me if I'm missing them
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One extra small suggestion and it looks good to me!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Phew! I left one final suggestion. Otherwise, this looks good to me 🎉🎉 great work!
Also, we're 2 comments away from tying the comment record (ERC1155 @ 197 comments)!!! |
Co-authored-by: Eric Nordelo <[email protected]>
@ericnordelo @andrew-fleming last few suggestions have been incorporated. Thanks for your feedback during the process!
Looks like we stayed one comment away from the record 😮💨 |
Fixes #984
This PR introduces a generic
Votes
component that contains reusable logic for other token voting mechanisms. When we implemented ERC20Votes we did it in a single component that used storage variable names specific to ERC20 and it also usesStorageArray
behind the scenes(Vec
wasn't available at that moment), so the design is suboptimal and we would like to move away from it at least forERC721Votes
and future voting contracts.This PR contains:
Votes
component that holds all the common logic for voting.impl
inside that component that has logic specific toERC721Votes
andERC20Votes
. If we want to add another form of voting we just need to add a newimpl
block to the same component that implements theget_voting_units
function from theTokenVotesTrait
trait.governance
module.ERC20Votes
.Vec
forTrace
and removeStorageArray
PR Checklist