Skip to content

Conversation

@arr00
Copy link
Contributor

@arr00 arr00 commented May 19, 2025

No description provided.

@arr00 arr00 closed this Jun 5, 2025
@arr00 arr00 reopened this Jun 5, 2025
@netlify
Copy link

netlify bot commented Jun 5, 2025

Deploy Preview for confidential-tokens ready!

Name Link
🔨 Latest commit 1aa2eec
🔍 Latest deploy log https://app.netlify.com/projects/confidential-tokens/deploys/6866d5f64b8ede00082afccf
😎 Deploy Preview https://deploy-preview-40--confidential-tokens.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@arr00 arr00 closed this Jun 9, 2025
@arr00 arr00 reopened this Jun 9, 2025
@arr00 arr00 requested a review from a team as a code owner June 11, 2025 14:32
@arr00 arr00 removed the request for review from a team June 11, 2025 14:32
@arr00 arr00 changed the base branch from master to feat/checkpoints June 18, 2025 20:43
@arr00 arr00 force-pushed the feat/checkpoints branch from bfe2b05 to 616bc78 Compare June 25, 2025 18:16
Base automatically changed from feat/checkpoints to master June 26, 2025 12:46
@arr00 arr00 changed the title WIP: Confidential Votes Votes Confidential Jun 26, 2025
* @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);
Copy link
Contributor

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should it be internal ?

Copy link
Member

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

Comment on lines 121 to 122
function delegateBySig(
address delegator,
address delegatee,
uint256 nonce,
uint256 expiry,
bytes memory signature
) public virtual {
Copy link
Contributor

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());
Copy link
Contributor

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 ?

Copy link
Contributor Author

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.

Copy link
Member

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);
Copy link
Contributor

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.

Copy link
Contributor

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

Copy link
Contributor Author

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)

Copy link
Contributor

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 ?

Copy link
Contributor

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 ???

Copy link
Contributor Author

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.

Copy link
Contributor

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 ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/zama-ai/fhevm/blob/724b52d65f919d50ca22093adbf141bd38554814/library-solidity/lib/FHE.sol#L3665-L3673

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.

@arr00 arr00 requested a review from ernestognw July 2, 2025 19:29
Copy link
Member

@ernestognw ernestognw left a 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);
Copy link
Member

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(
Copy link
Member

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());
Copy link
Member

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

@arr00
Copy link
Contributor Author

arr00 commented Jul 3, 2025

@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 _update.

arr00 and others added 3 commits July 3, 2025 10:47
ernestognw
ernestognw previously approved these changes Jul 3, 2025
Copy link
Member

@ernestognw ernestognw left a 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

@arr00 arr00 merged commit b7d8fc2 into master Jul 3, 2025
12 checks passed
@arr00 arr00 deleted the feat/votes branch July 3, 2025 19:37
@james-toussaint james-toussaint self-requested a review July 4, 2025 12:33
*/
function _moveDelegateVotes(address from, address to, euint64 amount) internal virtual {
CheckpointsConfidential.TraceEuint64 storage store;
if (from != to && euint64.unwrap(amount) != 0) {
Copy link
Contributor

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 (?)

Copy link
Contributor Author

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.

This was referenced Jul 12, 2025
@github-actions github-actions bot mentioned this pull request Aug 5, 2025
@github-actions github-actions bot mentioned this pull request Oct 9, 2025
@github-actions github-actions bot mentioned this pull request Nov 12, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants