diff --git a/contracts/governance/ConfidentialERC20Votes.sol b/contracts/governance/ConfidentialERC20Votes.sol index 3edd158..5363c3d 100644 --- a/contracts/governance/ConfidentialERC20Votes.sol +++ b/contracts/governance/ConfidentialERC20Votes.sol @@ -80,9 +80,7 @@ abstract contract ConfidentialERC20Votes is IConfidentialERC20Votes, Confidentia /// @notice Constant for zero using TFHE. /// @dev Since it is expensive to compute 0, it is stored instead. - /// However, is not possible to define it as constant due to TFHE constraints. - /* solhint-disable var-name-mixedcase*/ - euint64 private _EUINT64_ZERO; + euint64 private immutable _EUINT64_ZERO; /** * @param owner_ Owner address. @@ -168,11 +166,7 @@ abstract contract ConfidentialERC20Votes is IConfidentialERC20Votes, Confidentia revert GovernorInvalid(); } - if (blockNumber >= block.number) { - revert BlockNumberEqualOrHigherThanCurrentBlock(); - } - - votes = _getPriorVote(account, blockNumber); + votes = getPriorVotes(account, blockNumber); TFHE.allow(votes, msg.sender); } @@ -223,26 +217,24 @@ abstract contract ConfidentialERC20Votes is IConfidentialERC20Votes, Confidentia _moveDelegates(currentDelegate, delegatee, delegatorBalance); } - function _getPriorVote(address account, uint256 blockNumber) internal view returns (euint64 votes) { + function _getPriorVote(address account, uint256 blockNumber) internal view virtual returns (euint64 votes) { uint32 nCheckpoints = numCheckpoints[account]; if (nCheckpoints == 0) { - /// If there is no checkpoint for the `account`, return encrypted zero. - /// @dev It will not be possible to reencrypt it by the `account`. - votes = _EUINT64_ZERO; + /// @dev If there is no checkpoint for the `account`, return empty handle. + return votes; } else if (_checkpoints[account][nCheckpoints - 1].fromBlock <= blockNumber) { - /// First, check the most recent balance. - votes = _checkpoints[account][nCheckpoints - 1].votes; + /// @dev First, check the most recent balance. + return _checkpoints[account][nCheckpoints - 1].votes; } else if (_checkpoints[account][0].fromBlock > blockNumber) { - /// Then, check if there is zero balance. - /// @dev It will not be possible to reencrypt it by the `account`. - votes = _EUINT64_ZERO; + /// @dev Then, check if there is zero balance. If so, return empty handle. + return votes; } else { - /// Else, search for the voting power at the `blockNumber`. + /// @dev Else, search for the voting power at the `blockNumber`. uint32 lower = 0; uint32 upper = nCheckpoints - 1; while (upper > lower) { - /// Ceil to avoid overflow. + /// @dev Ceil to avoid overflow. uint32 center = upper - (upper - lower) / 2; Checkpoint memory cp = _checkpoints[account][center]; @@ -254,7 +246,7 @@ abstract contract ConfidentialERC20Votes is IConfidentialERC20Votes, Confidentia upper = center - 1; } } - votes = _checkpoints[account][lower].votes; + return _checkpoints[account][lower].votes; } } diff --git a/contracts/governance/ConfidentialGovernorAlpha.sol b/contracts/governance/ConfidentialGovernorAlpha.sol index df83372..7935516 100644 --- a/contracts/governance/ConfidentialGovernorAlpha.sol +++ b/contracts/governance/ConfidentialGovernorAlpha.sol @@ -227,16 +227,12 @@ abstract contract ConfidentialGovernorAlpha is Ownable2Step, GatewayCaller { ICompoundTimelock public immutable TIMELOCK; /// @notice Constant for zero using TFHE. - /// @dev Since it is expensive to compute 0, it is stored instead. - /// However, is not possible to define it as constant due to TFHE constraints. - /* solhint-disable var-name-mixedcase*/ - euint64 private _EUINT64_ZERO; + /// @dev Since it is expensive to compute 0, it is stored once instead. + euint64 private immutable _EUINT64_ZERO; /// @notice Constant for PROPOSAL_THRESHOLD using TFHE. - /// @dev Since it is expensive to compute 0, it is stored instead. - /// However, is not possible to define it as constant due to TFHE constraints. - /* solhint-disable var-name-mixedcase*/ - euint64 private _EUINT64_PROPOSAL_THRESHOLD; + /// @dev Since it is expensive to compute the PROPOSAL_THRESHOLD, it is stored once instead. + euint64 private immutable _EUINT64_PROPOSAL_THRESHOLD; /// @notice The total number of proposals made. /// It includes all proposals, including the ones that @@ -465,10 +461,8 @@ abstract contract ConfidentialGovernorAlpha is Ownable2Step, GatewayCaller { description ); - ebool canPropose = TFHE.lt( - _EUINT64_PROPOSAL_THRESHOLD, - CONFIDENTIAL_ERC20_VOTES.getPriorVotesForGovernor(msg.sender, block.number - 1) - ); + euint64 priorVotes = CONFIDENTIAL_ERC20_VOTES.getPriorVotesForGovernor(msg.sender, block.number - 1); + ebool canPropose = TFHE.lt(_EUINT64_PROPOSAL_THRESHOLD, priorVotes); uint256[] memory cts = new uint256[](1); cts[0] = Gateway.toUint256(canPropose); diff --git a/hardhat.config.ts b/hardhat.config.ts index 9c1ac97..a053fe2 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -99,6 +99,7 @@ const config: HardhatUserConfig = { }, networks: { hardhat: { + gas: "auto", accounts: { count: 10, mnemonic, diff --git a/package.json b/package.json index 5ac6a4d..126238b 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,7 @@ }, "dependencies": { "fhevm": "^0.6.1", - "@openzeppelin/contracts": "^5.0.2", - "@openzeppelin/contracts-upgradeable": "^5.0.2" + "@openzeppelin/contracts": "^5.1.0", + "@openzeppelin/contracts-upgradeable": "^5.1.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 888a206..936b31e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,11 +9,11 @@ importers: .: dependencies: '@openzeppelin/contracts': - specifier: ^5.0.2 - version: 5.0.2 + specifier: ^5.1.0 + version: 5.1.0 '@openzeppelin/contracts-upgradeable': - specifier: ^5.0.2 - version: 5.0.2(@openzeppelin/contracts@5.0.2) + specifier: ^5.1.0 + version: 5.1.0(@openzeppelin/contracts@5.1.0) fhevm: specifier: ^0.6.1 version: 0.6.1 @@ -784,13 +784,13 @@ packages: engines: {node: '>=10'} deprecated: This functionality has been moved to @npmcli/fs - '@openzeppelin/contracts-upgradeable@5.0.2': - resolution: {integrity: sha512-0MmkHSHiW2NRFiT9/r5Lu4eJq5UJ4/tzlOgYXNAIj/ONkQTVnz22pLxDvp4C4uZ9he7ZFvGn3Driptn1/iU7tQ==} + '@openzeppelin/contracts-upgradeable@5.1.0': + resolution: {integrity: sha512-AIElwP5Ck+cslNE+Hkemf5SxjJoF4wBvvjxc27Rp+9jaPs/CLIaUBMYe1FNzhdiN0cYuwGRmYaRHmmntuiju4Q==} peerDependencies: - '@openzeppelin/contracts': 5.0.2 + '@openzeppelin/contracts': 5.1.0 - '@openzeppelin/contracts@5.0.2': - resolution: {integrity: sha512-ytPc6eLGcHHnapAZ9S+5qsdomhjo6QBHTDRRBFfTxXIpsicMhVPouPgmUPebZZZGX7vt9USA+Z+0M0dSVtSUEA==} + '@openzeppelin/contracts@5.1.0': + resolution: {integrity: sha512-p1ULhl7BXzjjbha5aqst+QMLY+4/LCWADXOCsmLHRM77AqiPjnd9vvUN9sosUfhL9JGKpZ0TjEGxgvnizmWGSA==} '@openzeppelin/defender-sdk-base-client@1.15.2': resolution: {integrity: sha512-N3ZTeH8TXyklL7yNPMLUv0dxQwT78DTkOEDhzMS2/QE2FxbXrclSseoeeXxl6UYI61RBtZKn+okbSsbwiB5QWQ==} @@ -4733,11 +4733,11 @@ snapshots: rimraf: 3.0.2 optional: true - '@openzeppelin/contracts-upgradeable@5.0.2(@openzeppelin/contracts@5.0.2)': + '@openzeppelin/contracts-upgradeable@5.1.0(@openzeppelin/contracts@5.1.0)': dependencies: - '@openzeppelin/contracts': 5.0.2 + '@openzeppelin/contracts': 5.1.0 - '@openzeppelin/contracts@5.0.2': {} + '@openzeppelin/contracts@5.1.0': {} '@openzeppelin/defender-sdk-base-client@1.15.2(encoding@0.1.13)': dependencies: @@ -6110,7 +6110,7 @@ snapshots: fhevm@0.6.1: dependencies: - '@openzeppelin/contracts': 5.0.2 + '@openzeppelin/contracts': 5.1.0 extra-bigint: 1.2.0 sqlite3: 5.1.7 transitivePeerDependencies: diff --git a/test/governance/ConfidentialERC20Votes.test.ts b/test/governance/ConfidentialERC20Votes.test.ts index a53f967..09908dd 100644 --- a/test/governance/ConfidentialERC20Votes.test.ts +++ b/test/governance/ConfidentialERC20Votes.test.ts @@ -389,12 +389,12 @@ describe("ConfidentialERC20Votes", function () { .connect(this.signers.bob) .getPriorVotes(this.signers.bob.address, latestBlockNumber); - // It is an encrypted constant that is not reencryptable by Bob. - expect(currentVoteHandle).not.to.be.eq(0n); + // The handle is not set. + expect(currentVoteHandle).to.be.eq(0n); await expect( reencryptEuint64(this.signers.bob, this.instance, currentVoteHandle, this.confidentialERC20Votes), - ).to.be.rejectedWith("Invalid contract address."); + ).to.be.rejectedWith("Handle is not initialized"); // 3. If a checkpoint exists using getPriorVotes but block.number < block of first checkpoint latestBlockNumber = await ethers.provider.getBlockNumber(); @@ -408,11 +408,11 @@ describe("ConfidentialERC20Votes", function () { .getPriorVotes(this.signers.bob.address, latestBlockNumber); // It is an encrypted constant that is not reencryptable by Bob. - expect(currentVoteHandle).not.to.be.eq(0n); + expect(currentVoteHandle).to.eq(0n); await expect( reencryptEuint64(this.signers.bob, this.instance, currentVoteHandle, this.confidentialERC20Votes), - ).to.be.rejectedWith("Invalid contract address."); + ).to.be.rejectedWith("Handle is not initialized"); }); it("can do multiple checkpoints and access the values when needed", async function () { @@ -541,18 +541,16 @@ describe("ConfidentialERC20Votes", function () { ); }); - // TODO: fix issue with mining - it.skip("number of checkpoints is incremented once per block, even when written multiple times in same block", async function () { + it("number of checkpoints is incremented once per block, even when written multiple times in same block", async function () { await network.provider.send("evm_setAutomine", [false]); await network.provider.send("evm_setIntervalMining", [0]); - // do two checkpoints in same block - const tx1 = this.confidentialERC20Votes.connect(this.signers.alice).delegate(this.signers.bob); - const tx2 = this.confidentialERC20Votes.connect(this.signers.alice).delegate(this.signers.carol); + // @dev There are two checkpoints in the same block. + await this.confidentialERC20Votes.connect(this.signers.alice).delegate(this.signers.bob); + await this.confidentialERC20Votes.connect(this.signers.alice).delegate(this.signers.carol); await network.provider.send("evm_mine"); await network.provider.send("evm_setAutomine", [true]); - await Promise.all([tx1, tx2]); expect(await this.confidentialERC20Votes.numCheckpoints(this.signers.alice.address)).to.be.equal(0n); expect(await this.confidentialERC20Votes.numCheckpoints(this.signers.bob.address)).to.be.equal(1n); diff --git a/test/governance/ConfidentialGovernorAlpha.test.ts b/test/governance/ConfidentialGovernorAlpha.test.ts index 14d1137..98c4e24 100644 --- a/test/governance/ConfidentialGovernorAlpha.test.ts +++ b/test/governance/ConfidentialGovernorAlpha.test.ts @@ -1123,6 +1123,8 @@ describe("ConfidentialGovernorAlpha", function () { "TransactionTooLateForExecution", ); + await mineNBlocks(1); + proposalInfo = await this.governor.getProposalInfo(proposalId); // 9 ==> Expired expect(proposalInfo.state).to.equal(9);