Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 36 additions & 2 deletions contracts/governance/utils/VotesConfidential.sol
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ abstract contract VotesConfidential is Nonces, EIP712, IERC6372 {
return _delegateCheckpoints[account].latest();
}

/// @dev Get access to the current amount of votes the `account` has.
function getVotesAccess(address account) public virtual returns (euint64 votes) {
address authorized = _validateVotesAccess(account);
votes = getVotes(account);
FHE.allow(votes, authorized);
}

/**
* @dev Returns the amount of votes that `account` had at a specific moment in the past. If the {clock} is
* configured to use block numbers, this will return the value at the end of the corresponding block.
Expand All @@ -75,6 +82,13 @@ abstract contract VotesConfidential is Nonces, EIP712, IERC6372 {
return _delegateCheckpoints[account].upperLookupRecent(_validateTimepoint(timepoint));
}

/// @dev Get access to the amount of votes the `account` had at a specific moment in the past.
function getPastVotesAccess(address account, uint256 timepoint) public virtual returns (euint64 votes) {
address authorized = _validateVotesAccess(account);
votes = getPastVotes(account, timepoint);
FHE.allow(votes, authorized);
}

/**
* @dev Returns the total supply of votes available at a specific moment in the past. If the {clock} is
* configured to use block numbers, this will return the value at the end of the corresponding block.
Expand All @@ -91,12 +105,26 @@ abstract contract VotesConfidential is Nonces, EIP712, IERC6372 {
return _totalCheckpoints.upperLookupRecent(_validateTimepoint(timepoint));
}

/// @dev Get access to the total supply of votes available at a specific moment in the past.
function getPastTotalSupplyAccess(uint256 timepoint) public virtual returns (euint64 totalSupply) {
address authorized = _validateTotalSupplyAccess();
totalSupply = getPastTotalSupply(timepoint);
FHE.allow(totalSupply, authorized);
}

/**
* @dev Returns the current total supply of votes as an encrypted uint64 (euint64). Must be implemented
* by the derived contract.
*/
function confidentialTotalSupply() public view virtual returns (euint64);

/// @dev Get access to the total supply of votes.
function confidentialTotalSupplyAccess() public virtual returns (euint64 totalSupply) {
address authorized = _validateTotalSupplyAccess();
totalSupply = confidentialTotalSupply();
FHE.allow(totalSupply, authorized);
}

/// @dev Returns the delegate that `account` has chosen.
function delegates(address account) public view virtual returns (address) {
return _delegatee[account];
Expand Down Expand Up @@ -167,15 +195,15 @@ abstract contract VotesConfidential is Nonces, EIP712, IERC6372 {
store = _delegateCheckpoints[from];
euint64 newValue = store.latest().sub(amount);
newValue.allowThis();
newValue.allow(from);
// not allowing value for delegatee to avoid delegator balance deduction
euint64 oldValue = _push(store, newValue);
emit DelegateVotesChanged(from, oldValue, newValue);
}
if (to != address(0)) {
store = _delegateCheckpoints[to];
euint64 newValue = store.latest().add(amount);
newValue.allowThis();
newValue.allow(to);
// not allowing value for delegatee
euint64 oldValue = _push(store, newValue);
emit DelegateVotesChanged(to, oldValue, newValue);
}
Expand All @@ -194,6 +222,12 @@ abstract contract VotesConfidential is Nonces, EIP712, IERC6372 {
*/
function _getVotingUnits(address) internal view virtual returns (euint64);

/// Validate access to votes handle. Returns authorized account.
function _validateVotesAccess(address account) internal view virtual returns (address authorized);

/// Validate access to total supply handle. Returns authorized account.
function _validateTotalSupplyAccess() internal view virtual returns (address authorized);

function _push(CheckpointsConfidential.TraceEuint64 storage store, euint64 value) private returns (euint64) {
(euint64 oldValue, ) = store.push(clock(), value);
return oldValue;
Expand Down
43 changes: 39 additions & 4 deletions contracts/mocks/token/ConfidentialFungibleTokenVotesMock.sol
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
pragma solidity ^0.8.26;

import {SepoliaConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
import {FHE, externalEuint64, euint64} from "@fhevm/solidity/lib/FHE.sol";
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import {ConfidentialFungibleTokenVotes, ConfidentialFungibleToken, VotesConfidential} from "../../token/extensions/ConfidentialFungibleTokenVotes.sol";
import {ConfidentialFungibleTokenMock} from "./ConfidentialFungibleTokenMock.sol";

abstract contract ConfidentialFungibleTokenVotesMock is ConfidentialFungibleTokenMock, ConfidentialFungibleTokenVotes {
// solhint-disable func-name-mixedcase
abstract contract ConfidentialFungibleTokenVotesMock is
ConfidentialFungibleToken,
ConfidentialFungibleTokenVotes,
SepoliaConfig
{
address private immutable _OWNER;

uint48 private _clockOverrideVal;

error InvalidAccess();

constructor(
string memory name_,
string memory symbol_,
string memory tokenURI_
) ConfidentialFungibleTokenMock(name_, symbol_, tokenURI_) EIP712(name_, "1.0.0") {
) ConfidentialFungibleToken(name_, symbol_, tokenURI_) EIP712(name_, "1.0.0") {
_OWNER = msg.sender;
}

Expand All @@ -26,6 +34,14 @@ abstract contract ConfidentialFungibleTokenVotesMock is ConfidentialFungibleToke
return super.clock();
}

function $_mint(
address to,
externalEuint64 encryptedAmount,
bytes calldata inputProof
) public returns (euint64 transferred) {
return _mint(to, FHE.fromExternal(encryptedAmount, inputProof));
}

function confidentialTotalSupply()
public
view
Expand All @@ -40,11 +56,30 @@ abstract contract ConfidentialFungibleTokenVotesMock is ConfidentialFungibleToke
address from,
address to,
euint64 amount
) internal virtual override(ConfidentialFungibleTokenMock, ConfidentialFungibleTokenVotes) returns (euint64) {
) internal virtual override(ConfidentialFungibleToken, ConfidentialFungibleTokenVotes) returns (euint64) {
return super._update(from, to, amount);
}

function _setClockOverride(uint48 val) external {
_clockOverrideVal = val;
}

/**
* @dev Decision of how delegatees can see their votes without revaling balance
* of their delegators is up to the final contract.
* One approach is allowing a delegatee to see their aggregated number of votes.
*/
function _validateVotesAccess(address account) internal view override returns (address) {
if (msg.sender == account) {
return account;
}
revert InvalidAccess();
}

function _validateTotalSupplyAccess() internal view override returns (address) {
if (msg.sender == _OWNER) {
return _OWNER;
}
revert InvalidAccess();
}
}
46 changes: 46 additions & 0 deletions test/token/extensions/ConfidentialFungibleTokenVotes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ describe('ConfidentialFungibleTokenVotes', function () {
await this.token.connect(this.holder).delegate(this.holder);

const votesHandle = await this.token.getVotes(this.holder);
await this.token.connect(this.holder).getVotesAccess(this.holder);
await expect(
fhevm.userDecryptEuint(FhevmType.euint64, votesHandle, this.token.target, this.holder),
).to.eventually.equal(1000);
Expand All @@ -128,6 +129,13 @@ describe('ConfidentialFungibleTokenVotes', function () {
await expect(fhevm.userDecryptEuint(FhevmType.euint64, votesHandle, this.token.target, this.operator)).to
.eventually.rejected;
});

it('should not get votes access', async function () {
await expect(this.token.connect(this.recipient).getVotesAccess(this.holder)).to.be.revertedWithCustomError(
this.token,
'InvalidAccess',
);
});
});

describe('getPastVotes', async function () {
Expand Down Expand Up @@ -194,11 +202,13 @@ describe('ConfidentialFungibleTokenVotes', function () {
).to.eventually.equal(1000);

const afterTransferVotesHandle = await this.token.getPastVotes(this.holder, afterTransferBlock);
await this.token.connect(this.holder).getPastVotesAccess(this.holder, afterTransferBlock);
await expect(
fhevm.userDecryptEuint(FhevmType.euint64, afterTransferVotesHandle, this.token.target, this.holder),
).to.eventually.equal(800);

const afterBurnVotesHandle = await this.token.getPastVotes(this.holder, afterBurnBlock);
await this.token.connect(this.holder).getPastVotesAccess(this.holder, afterBurnBlock);
await expect(
fhevm.userDecryptEuint(FhevmType.euint64, afterBurnVotesHandle, this.token.target, this.holder),
).to.eventually.equal(0);
Expand All @@ -209,6 +219,12 @@ describe('ConfidentialFungibleTokenVotes', function () {
.to.be.revertedWithCustomError(this.token, 'ERC5805FutureLookup')
.withArgs(this.blockNumber + 10, this.blockNumber);
});

it('should not get past votes access', async function () {
await expect(
this.token.connect(this.recipient).getPastVotesAccess(this.holder, this.blockNumber),
).to.be.revertedWithCustomError(this.token, 'InvalidAccess');
});
});

describe('getPastTotalSupply', function () {
Expand Down Expand Up @@ -256,17 +272,47 @@ describe('ConfidentialFungibleTokenVotes', function () {

// Check total supply for each block
const afterFirstMintSupplyHandle = await this.token.getPastTotalSupply(afterFirstMintBlock);
await this.token.connect(this.holder).getPastTotalSupplyAccess(afterFirstMintBlock);
await expect(
fhevm.userDecryptEuint(FhevmType.euint64, afterFirstMintSupplyHandle, this.token.target, this.holder),
).to.eventually.equal(1000);

await expect(this.token.getPastTotalSupply(afterTransferBlock)).to.eventually.eq(afterFirstMintSupplyHandle);

const afterSecondMintSupplyHandle = await this.token.getPastTotalSupply(afterSecondMintBlock);
await this.token.connect(this.holder).getPastTotalSupplyAccess(afterSecondMintBlock);
await expect(
fhevm.userDecryptEuint(FhevmType.euint64, afterSecondMintSupplyHandle, this.token.target, this.holder),
).to.eventually.equal(2000);
});

it('should not get past total supply access', async function () {
await expect(
this.token.connect(this.recipient).getPastTotalSupplyAccess(this.blockNumber),
).to.be.revertedWithCustomError(this.token, 'InvalidAccess');
});
});

describe('Total supply', async function () {
it('should get confidential total supply', async function () {
await this.token['$_mint(address,bytes32,bytes)'](
this.holder,
this.encryptedInput.handles[0],
this.encryptedInput.inputProof,
);
const totalSupply = await this.token.confidentialTotalSupply();
await this.token.connect(this.holder).confidentialTotalSupplyAccess();
await expect(
fhevm.userDecryptEuint(FhevmType.euint64, totalSupply, this.token.target, this.holder),
).to.eventually.equal(1000);
});

it('should not get confidential total supply access', async function () {
await expect(this.token.connect(this.recipient).confidentialTotalSupplyAccess()).to.be.revertedWithCustomError(
this.token,
'InvalidAccess',
);
});
});

describe('Clock', async function () {
Expand Down