Skip to content

Commit 18e1ae1

Browse files
committed
Confidential Votes WIP
1 parent 2800100 commit 18e1ae1

File tree

3 files changed

+328
-0
lines changed

3 files changed

+328
-0
lines changed
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.24;
3+
4+
import { TFHE, einput, euint64 } from "fhevm/lib/TFHE.sol";
5+
import { Time } from "@openzeppelin/contracts/utils/types/Time.sol";
6+
import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
7+
8+
import { CheckpointConfidential } from "../../utils/structs/CheckpointConfidential.sol";
9+
10+
abstract contract VotesConfidential {
11+
using TFHE for *;
12+
using CheckpointConfidential for CheckpointConfidential.TraceEuint64;
13+
14+
bytes32 private constant DELEGATION_TYPEHASH =
15+
keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)");
16+
17+
mapping(address account => address) private _delegatee;
18+
19+
mapping(address delegatee => CheckpointConfidential.TraceEuint64) private _delegateCheckpoints;
20+
21+
CheckpointConfidential.TraceEuint64 private _totalCheckpoints;
22+
23+
event DelegateVotesChanged(address indexed delegate, euint64 previousVotes, euint64 newVotes);
24+
25+
/**
26+
* @dev Lookup to future votes is not available.
27+
*/
28+
error ERC5805FutureLookup(uint256 timepoint, uint48 clock);
29+
30+
function _validateTimepoint(uint256 timepoint) internal view returns (uint48) {
31+
uint48 currentTimepoint = clock();
32+
if (timepoint >= currentTimepoint) revert ERC5805FutureLookup(timepoint, currentTimepoint);
33+
return SafeCast.toUint48(timepoint);
34+
}
35+
36+
/**
37+
* @dev Returns the current amount of votes that `account` has.
38+
*/
39+
function getVotes(address account) public view virtual returns (euint64) {
40+
return _delegateCheckpoints[account].latest();
41+
}
42+
43+
function clock() public view virtual returns (uint48) {
44+
return Time.blockNumber();
45+
}
46+
47+
function getPastVotes(address account, uint256 timepoint) public view virtual returns (euint64) {
48+
return _delegateCheckpoints[account].upperLookupRecent(_validateTimepoint(timepoint));
49+
}
50+
51+
function getPastTotalSupply(uint256 timepoint) public view virtual returns (euint64) {
52+
return _totalCheckpoints.upperLookupRecent(_validateTimepoint(timepoint));
53+
}
54+
55+
function delegates(address account) public view virtual returns (address) {
56+
return _delegatee[account];
57+
}
58+
59+
function _transferVotingUnits(address from, address to, euint64 amount) internal virtual {
60+
if (from == address(0)) {
61+
_push(_totalCheckpoints, _add, amount);
62+
}
63+
if (to == address(0)) {
64+
_push(_totalCheckpoints, _subtract, amount);
65+
}
66+
_moveDelegateVotes(delegates(from), delegates(to), amount);
67+
}
68+
69+
function _moveDelegateVotes(address from, address to, euint64 amount) internal virtual {
70+
if (from != to && euint64.unwrap(amount) != 0) {
71+
if (from != address(0)) {
72+
(euint64 oldValue, euint64 newValue) = _push(_delegateCheckpoints[from], _subtract, amount);
73+
emit DelegateVotesChanged(from, oldValue, newValue);
74+
}
75+
if (to != address(0)) {
76+
(euint64 oldValue, euint64 newValue) = _push(_delegateCheckpoints[to], _add, amount);
77+
emit DelegateVotesChanged(to, oldValue, newValue);
78+
}
79+
}
80+
}
81+
82+
function _push(
83+
CheckpointConfidential.TraceEuint64 storage store,
84+
function(euint64, euint64) returns (euint64) op,
85+
euint64 delta
86+
) private returns (euint64 oldValue, euint64 newValue) {
87+
return store.push(clock(), op(store.latest(), delta));
88+
}
89+
90+
function _add(euint64 a, euint64 b) private returns (euint64) {
91+
return a.add(b);
92+
}
93+
94+
function _subtract(euint64 a, euint64 b) private returns (euint64) {
95+
return a.sub(b);
96+
}
97+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.24;
3+
4+
import { euint64 } from "fhevm/lib/TFHE.sol";
5+
6+
import { ConfidentialFungibleToken } from "../ConfidentialFungibleToken.sol";
7+
import { VotesConfidential } from "../../governance/utils/VotesConfidential.sol";
8+
9+
abstract contract ConfidentialFungibleTokenVotes is ConfidentialFungibleToken, VotesConfidential {
10+
function _update(address from, address to, euint64 amount) internal virtual override returns (euint64 transferred) {
11+
transferred = super._update(from, to, amount);
12+
13+
_transferVotingUnits(from, to, transferred);
14+
}
15+
}
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.24;
3+
4+
import { TFHE, einput, euint64 } from "fhevm/lib/TFHE.sol";
5+
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
6+
7+
library CheckpointConfidential {
8+
error CheckpointUnorderedInsertion();
9+
10+
struct TraceEuint64 {
11+
CheckpointEuint64[] _checkpoints;
12+
}
13+
14+
struct CheckpointEuint64 {
15+
uint48 _key;
16+
euint64 _value;
17+
}
18+
19+
euint64 private constant ENCRYPTED_ZERO = euint64.wrap(0);
20+
21+
/**
22+
* @dev Pushes a (`key`, `value`) pair into a TraceEuint64 so that it is stored as the checkpoint.
23+
*
24+
* Returns previous value and new value.
25+
*
26+
* IMPORTANT: Never accept `key` as a user input, since an arbitrary `type(uint96).max` key set will disable the
27+
* library.
28+
*/
29+
function push(
30+
TraceEuint64 storage self,
31+
uint48 key,
32+
euint64 value
33+
) internal returns (euint64 oldValue, euint64 newValue) {
34+
return _insert(self._checkpoints, key, value);
35+
}
36+
37+
/**
38+
* @dev Returns the value in the first (oldest) checkpoint with key greater or equal than the search key, or zero if
39+
* there is none.
40+
*/
41+
function lowerLookup(TraceEuint64 storage self, uint96 key) internal view returns (euint64) {
42+
uint256 len = self._checkpoints.length;
43+
uint256 pos = _lowerBinaryLookup(self._checkpoints, key, 0, len);
44+
return pos == len ? ENCRYPTED_ZERO : _unsafeAccess(self._checkpoints, pos)._value;
45+
}
46+
47+
/**
48+
* @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero
49+
* if there is none.
50+
*/
51+
function upperLookup(TraceEuint64 storage self, uint96 key) internal view returns (euint64) {
52+
uint256 len = self._checkpoints.length;
53+
uint256 pos = _upperBinaryLookup(self._checkpoints, key, 0, len);
54+
return pos == 0 ? ENCRYPTED_ZERO : _unsafeAccess(self._checkpoints, pos - 1)._value;
55+
}
56+
57+
/**
58+
* @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero
59+
* if there is none.
60+
*
61+
* NOTE: This is a variant of {upperLookup} that is optimized to find "recent" checkpoint (checkpoints with high
62+
* keys).
63+
*/
64+
function upperLookupRecent(TraceEuint64 storage self, uint96 key) internal view returns (euint64) {
65+
uint256 len = self._checkpoints.length;
66+
67+
uint256 low = 0;
68+
uint256 high = len;
69+
70+
if (len > 5) {
71+
uint256 mid = len - Math.sqrt(len);
72+
if (key < _unsafeAccess(self._checkpoints, mid)._key) {
73+
high = mid;
74+
} else {
75+
low = mid + 1;
76+
}
77+
}
78+
79+
uint256 pos = _upperBinaryLookup(self._checkpoints, key, low, high);
80+
81+
return pos == 0 ? ENCRYPTED_ZERO : _unsafeAccess(self._checkpoints, pos - 1)._value;
82+
}
83+
84+
/**
85+
* @dev Returns the value in the most recent checkpoint, or zero if there are no checkpoints.
86+
*/
87+
function latest(TraceEuint64 storage self) internal view returns (euint64) {
88+
uint256 pos = self._checkpoints.length;
89+
return pos == 0 ? ENCRYPTED_ZERO : _unsafeAccess(self._checkpoints, pos - 1)._value;
90+
}
91+
92+
/**
93+
* @dev Returns whether there is a checkpoint in the structure (i.e. it is not empty), and if so the key and value
94+
* in the most recent checkpoint.
95+
*/
96+
function latestCheckpoint(
97+
TraceEuint64 storage self
98+
) internal view returns (bool exists, uint96 _key, euint64 _value) {
99+
uint256 pos = self._checkpoints.length;
100+
if (pos == 0) {
101+
return (false, 0, ENCRYPTED_ZERO);
102+
} else {
103+
CheckpointEuint64 storage ckpt = _unsafeAccess(self._checkpoints, pos - 1);
104+
return (true, ckpt._key, ckpt._value);
105+
}
106+
}
107+
108+
/**
109+
* @dev Returns the number of checkpoints.
110+
*/
111+
function length(TraceEuint64 storage self) internal view returns (uint256) {
112+
return self._checkpoints.length;
113+
}
114+
115+
/**
116+
* @dev Returns checkpoint at given position.
117+
*/
118+
function at(TraceEuint64 storage self, uint32 pos) internal view returns (CheckpointEuint64 memory) {
119+
return self._checkpoints[pos];
120+
}
121+
122+
/**
123+
* @dev Pushes a (`key`, `value`) pair into an ordered list of checkpoints, either by inserting a new checkpoint,
124+
* or by updating the last one.
125+
*/
126+
function _insert(
127+
CheckpointEuint64[] storage self,
128+
uint48 key,
129+
euint64 value
130+
) private returns (euint64 oldValue, euint64 newValue) {
131+
uint256 pos = self.length;
132+
133+
if (pos > 0) {
134+
CheckpointEuint64 storage last = _unsafeAccess(self, pos - 1);
135+
uint96 lastKey = last._key;
136+
euint64 lastValue = last._value;
137+
138+
// Checkpoint keys must be non-decreasing.
139+
if (lastKey > key) {
140+
revert CheckpointUnorderedInsertion();
141+
}
142+
143+
// Update or push new checkpoint
144+
if (lastKey == key) {
145+
last._value = value;
146+
} else {
147+
self.push(CheckpointEuint64({ _key: key, _value: value }));
148+
}
149+
return (lastValue, value);
150+
} else {
151+
self.push(CheckpointEuint64({ _key: key, _value: value }));
152+
return (TFHE.asEuint64(0), value);
153+
}
154+
}
155+
156+
/**
157+
* @dev Return the index of the first (oldest) checkpoint with key strictly bigger than the search key, or `high`
158+
* if there is none. `low` and `high` define a section where to do the search, with inclusive `low` and exclusive
159+
* `high`.
160+
*
161+
* WARNING: `high` should not be greater than the array's length.
162+
*/
163+
function _upperBinaryLookup(
164+
CheckpointEuint64[] storage self,
165+
uint96 key,
166+
uint256 low,
167+
uint256 high
168+
) private view returns (uint256) {
169+
while (low < high) {
170+
uint256 mid = Math.average(low, high);
171+
if (_unsafeAccess(self, mid)._key > key) {
172+
high = mid;
173+
} else {
174+
low = mid + 1;
175+
}
176+
}
177+
return high;
178+
}
179+
180+
/**
181+
* @dev Return the index of the first (oldest) checkpoint with key greater or equal than the search key, or `high`
182+
* if there is none. `low` and `high` define a section where to do the search, with inclusive `low` and exclusive
183+
* `high`.
184+
*
185+
* WARNING: `high` should not be greater than the array's length.
186+
*/
187+
function _lowerBinaryLookup(
188+
CheckpointEuint64[] storage self,
189+
uint96 key,
190+
uint256 low,
191+
uint256 high
192+
) private view returns (uint256) {
193+
while (low < high) {
194+
uint256 mid = Math.average(low, high);
195+
if (_unsafeAccess(self, mid)._key < key) {
196+
low = mid + 1;
197+
} else {
198+
high = mid;
199+
}
200+
}
201+
return high;
202+
}
203+
204+
/**
205+
* @dev Access an element of the array without performing bounds check. The position is assumed to be within bounds.
206+
*/
207+
function _unsafeAccess(
208+
CheckpointEuint64[] storage self,
209+
uint256 pos
210+
) private pure returns (CheckpointEuint64 storage result) {
211+
assembly {
212+
mstore(0, self.slot)
213+
result.slot := add(keccak256(0, 0x20), pos)
214+
}
215+
}
216+
}

0 commit comments

Comments
 (0)