-
Notifications
You must be signed in to change notification settings - Fork 11
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
Replace ballots with indexed map #114
Conversation
330fc50
to
e964d5c
Compare
e964d5c
to
af211cd
Compare
These are in the primary key. I understand you need
|
That's right, I kept it mostly for consistency. Good point, removed.
Cool, that's why I asked. Anyway, I removed |
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.
Looks good to me.
This flagged a number of limitations with the current storage-plus muti-index impl, that we can address in follow-up issues.
.ballots | ||
.idx | ||
.voter | ||
.prefix(voter_addr) | ||
.range(deps.storage, start, None, Order::Ascending) |
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.
You can use range_raw
here, as you don't use / need the pk. That, or stick with range
, and get proposal_id
from the (deserialised) pk, below.
That way you can avoid the need for storing the proposal_id
as part the value.
I'm now thinking that it would be good that the MultiIndex
index function signature, instead of being fn(&T) -> IK
, were fn(&PK, &T) -> IK
instead. That way, the pk or parts of it can be used for indexing.
That will avoid having to store the voter address for the value as well. Will create an issue for it.
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.
See CosmWasm/cw-plus#670.
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.
That way you can avoid the need for storing the proposal_id as part the value.
So this is already done.
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.
Yes. After CosmWasm/cw-plus#670 (if we ever decide to do it), you can avoid the need for storing owner
in the value also. As the index function will have that from the pk passed to it, and can take it from there.
This is just an idea at the moment. It will be a breaking change, but I think it may be worth it.
|
||
pub struct BallotIndexes<'a> { | ||
// This PrimaryKey allows quering over all proposal ids for given voter address | ||
pub voter: MultiIndex<'a, Addr, Ballot, (u64, Addr)>, |
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.
That last type parameter is crucial, as it now serves not only for signalling the pk deserialization type, but also and more important, to define the type for typed bounds over the multi-index.
Let's create an issue for updating documentation about it. And, making that parameter mandatory, instead of optional.
Also, if we finish CosmWasm/cw-plus#531, that parameter could be tied to the IndexedMap
pk definition directly, or automatically.
Which would be great, and will solve the issues with type-less bounds / ranges once and for all.
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.
See CosmWasm/cw-plus#669.
impl<'a> Ballots<'a> { | ||
pub fn new(storage_key: &'a str, release_subkey: &'a str) -> Self { | ||
let indexes = BallotIndexes { | ||
voter: MultiIndex::new(|ballot| ballot.voter.clone(), storage_key, release_subkey), |
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.
Here, if we go ahead and change the index function signature for MultiIndex
, we could instead have
voter: MultiIndex::new(|ballot| ballot.voter.clone(), storage_key, release_subkey), | |
voter: MultiIndex::new(|(pk, _ballot)| pk.1, storage_key, release_subkey), |
and get rid of the need for adding extra fields to the values just for indexing.
Update: See CosmWasm/cw-plus#670.
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.
+1 for pulling that code out into the ballots.rs
file. Awesome.
Now that I think about it, shouldn't we mark this as breaking-state
?
.range(deps.storage, start, None, Order::Ascending) | ||
.take(limit) | ||
.map(|item| { | ||
let (proposal_id, ballot) = item?; | ||
let ((proposal_id, _), ballot) = item?; |
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.
👍🏼
|
||
let votes: StdResult<Vec<_>> = BALLOTS_BY_VOTER | ||
.prefix(&voter_addr) | ||
// PrimaryKey of that IndexMap is (proposal_id, voter_address) -> (u64, Addr) |
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.
Also, pk is (the last) part of a (multi-)index key. That's why, after fixing the voter addr with prefix()
, you still need to range over the rest of the index key (which matches / corresponds to the pk, in this case).
let votes: StdResult<Vec<_>> = BALLOTS_BY_VOTER | ||
.prefix(&voter_addr) | ||
// PrimaryKey of that IndexMap is (proposal_id, voter_address) -> (u64, Addr) | ||
let start = start_after.map(|m| Bound::exclusive((m, voter_addr.clone()))); |
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.
Another way to do this (just for fun. The current impl is better / clearer):
let start = start_after.map(|m| Bound::exclusive((m, voter_addr.clone()))); | |
let start = start_after.map(|proposal_id| Bound::inclusive((proposal_id+1, Addr::unchecked(""))); |
An inclusive
bound over proposal_id+1
is needed, because we currently don't have something like Addr::MAX
(but we could).
This works basically because the (proposal_id, voter)
tuple is unique (guaranteed, because it's the pk). So, this will not miss any keys (for any given voter, he could have voted (at most) only once on any given proposal).
I actually removed "breaking" label, since all changes are done internally. And although technically |
Yeah, but if there are live voting contracts out there that have already stored some ballots on the blockchain, they will probably break when migrating since their stored data is structured differently now ( |
closes #83
I expanded
Ballot
structure, so now it contains also address of voter and proposal id.It got me thinking - now this is basically the same structure as
VoteInfo
link, BUT... it is usingString
instead ofAddr
type as inBallot
now.Options:
Ballot
structure and useVoteInfo
in voting-contract everywhere instead2a) but check for
Addr
correctness before each query.2b) replace
String
withAddr
inVoteInfo
structure