-
Notifications
You must be signed in to change notification settings - Fork 30
Votes Confidential #40
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
Conversation
✅ Deploy Preview for confidential-tokens ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
| * @dev Returns the current total supply of votes as an encrypted uint64 (euint64). Must be implemented | ||
| * by the derived contract. | ||
| */ | ||
| function totalSupply() public view virtual returns (euint64); |
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.
I'm confused by the distinction between this and getCurrentTotalSupply.
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.
should it be internal ?
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.
I would agree on making it internal to avoid specifying override in derived contracts
| function delegateBySig( | ||
| address delegator, | ||
| address delegatee, | ||
| uint256 nonce, | ||
| uint256 expiry, | ||
| bytes memory signature | ||
| ) public virtual { |
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.
I understand the rational for this function having delegator as an argument, and using bytes signatures (instead of v,r,s) ... But I'm wondering if we shouldn't keep the traditional votes function here.
Lets discuss that in a call.
| */ | ||
| function _transferVotingUnits(address from, address to, euint64 amount) internal virtual { | ||
| if (from == address(0) || to == address(0)) { | ||
| _push(_totalCheckpoints, totalSupply()); |
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.
This is a VERY different behavior from the non-confidential Votes.sol.
Is that on purpose? Is there a security issue in replicating Votes's behavior ?
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.
It's quite expensive to perform the total supply math twice (external calls), as is done in normal votes. I prefer to inherit the total supply from the token contract.
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.
Is the logic that instead of adding and subtracting the supply, we just push the latest totalSupply because it's assumed to be correct?
I think that makes sense, but now the order of calling _transferVotingUnits matter. While I would agree on keeping it like this, I'm worried that calling this function before an _update wouldn't be caught easily
| } | ||
| if (to != address(0)) { | ||
| store = _delegateCheckpoints[to]; | ||
| euint64 newValue = store.latest().add(amount); |
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.
store.latest() might be 0 as in "was never created/allocated". These should have special treatment
I believe https://github.com/OpenZeppelin/openzeppelin-confidential-contracts/blob/master/contracts/utils/TFHESafeMath.sol#L16-L25 would be usefull here.
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 makes me think the Votes.sol construction, with a _push function that takes a fnPointer, would probably work fine with the functions in THFESafeMath.sol
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.
Didn't use this method due to the reason here #40 (comment)
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 linked comment is about totalSupply(). AFAIK, this has nothing to do with the _push method that uses a fnPointer. Are you maybe pointing to the wrong comment ?
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.
And my original comment still stand !
If the trace is empty (that is the case for all addresses that never received any delegation), then store.latest() does
function latest(TraceEuint64 storage self) internal view returns (euint64) {
return euint64.wrap(bytes32(self._inner.latest()));
}with self._inner.latest() returning 0.
My question was: is euint64.wrap(0) guaranteed to represent an encrypted 0, and is it ok to use it in FHE.add ???
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.
Ah I posted the wrong response here. The FHE library by Zama always handles euint64.wrap(0) correctly since 0.7.0.
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.
what about other FHE beside zama, should we expect them to do the same ?
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.
Right now we are tightly coupled to the FHE library produced by Zama. If we were to decouple, we'd have to rethink all operations--any precautions we take here would need to be handled on a per library basis which isn't necessary for Zama.
ernestognw
left a comment
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.
I understand the main discussion point is about calculating the totalSupply (or using expensive methods from CheckpointsConfidential). Is that correct? I would encourage generating some quick benchmark to understand the actual cost, that would help us form a better opinion
| * @dev Returns the current total supply of votes as an encrypted uint64 (euint64). Must be implemented | ||
| * by the derived contract. | ||
| */ | ||
| function totalSupply() public view virtual returns (euint64); |
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.
I would agree on making it internal to avoid specifying override in derived contracts
| } | ||
|
|
||
| /// @dev Delegates votes from to `delegatee` by sig. | ||
| function delegateBySig( |
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.
Maybe we should note that it only supports ECDSA purposely, as any other smart account should rely on another mechanism for sponsoring
| */ | ||
| function _transferVotingUnits(address from, address to, euint64 amount) internal virtual { | ||
| if (from == address(0) || to == address(0)) { | ||
| _push(_totalCheckpoints, totalSupply()); |
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.
Is the logic that instead of adding and subtracting the supply, we just push the latest totalSupply because it's assumed to be correct?
I think that makes sense, but now the order of calling _transferVotingUnits matter. While I would agree on keeping it like this, I'm worried that calling this function before an _update wouldn't be caught easily
|
@ernestognw can't reply above but in reference to #40 (comment). Yes exactly. Duplicating the math (as is done in vanilla) is very expensive so I want to reuse it. And yes the order matters now for when overriding |
Co-authored-by: Ernesto García <ernestognw@gmail.com>
Co-authored-by: Ernesto García <ernestognw@gmail.com>
ernestognw
left a comment
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.
LGTM. I agree with the tradeoffs about reusing storage rather than recomputing
| */ | ||
| function _moveDelegateVotes(address from, address to, euint64 amount) internal virtual { | ||
| CheckpointsConfidential.TraceEuint64 storage store; | ||
| if (from != to && euint64.unwrap(amount) != 0) { |
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.
if (from != to && amount.isInitialized()) { (& everywhere).
I can create a PR @arr00 if you agree with that (?)
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.
Feel free to create a PR. Seems like isInitialized does the same thing but is a nicer semantic.
No description provided.