Skip to content

Commit

Permalink
perf: next key poc for snapshots
Browse files Browse the repository at this point in the history
Signef-off-by: 0xRaccoon <[email protected]>
  • Loading branch information
0xRaccoon committed Jan 4, 2024
1 parent aedee3d commit f6b8f6b
Show file tree
Hide file tree
Showing 15 changed files with 553 additions and 680 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
"lint:sol-tests": "solhint 'solidity/test/**/*.sol'",
"prepare": "husky install",
"test": "forge test -vvv",
"test:e2e": "forge test --match-contract E2E -vvv",
"test:gas": "forge test --match-contract Integration -vvv --gas-report",
"test:e2e": "forge test --match-contract Integration -vvv",
"test:gas": "forge test --match-contract Gas -vvv --gas-report",
"test:integration": "forge test --match-contract Integration -vvv",
"test:unit": "forge test --match-contract Unit -vvv",
"test:unit:deep": "FOUNDRY_FUZZ_RUNS=5000 yarn test:unit"
Expand Down
38 changes: 26 additions & 12 deletions solidity/contracts/governance/WonderGovernor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ abstract contract WonderGovernor is
address account,
uint8 proposalType,
uint256 timepoint,
uint256 voteStart,
bytes memory params
) internal view virtual returns (uint256);

Expand Down Expand Up @@ -304,6 +305,7 @@ abstract contract WonderGovernor is
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
uint256 votesBlockNumber,
string memory description
) public virtual validProposalType(proposalType) returns (uint256) {
address proposer = _msgSender();
Expand All @@ -314,7 +316,7 @@ abstract contract WonderGovernor is
}

// check proposal threshold
uint256 proposerVotes = getVotes(proposer, proposalType, clock() - 1);
uint256 proposerVotes = getVotes(proposer, proposalType, votesBlockNumber, clock() - 1);
uint256 votesThreshold = proposalThreshold(proposalType);
if (proposerVotes < votesThreshold) {
revert GovernorInsufficientProposerVotes(proposer, proposerVotes, votesThreshold);
Expand Down Expand Up @@ -533,8 +535,13 @@ abstract contract WonderGovernor is
/**
* @dev See {IWonderGovernor-getVotes}.
*/
function getVotes(address account, uint8 proposalType, uint256 timepoint) public view virtual returns (uint256) {
return _getVotes(account, proposalType, timepoint, _defaultParams());
function getVotes(
address account,
uint8 proposalType,
uint256 timepoint,
uint256 voteStart
) public view virtual returns (uint256) {
return _getVotes(account, proposalType, timepoint, voteStart, _defaultParams());
}

/**
Expand All @@ -544,17 +551,18 @@ abstract contract WonderGovernor is
address account,
uint8 proposalType,
uint256 timepoint,
uint256 voteStart,
bytes memory params
) public view virtual returns (uint256) {
return _getVotes(account, proposalType, timepoint, params);
return _getVotes(account, proposalType, timepoint, voteStart, params);
}

/**
* @dev See {IWonderGovernor-castVote}.
*/
function castVote(uint256 proposalId, uint8 support) public virtual returns (uint256) {
function castVote(uint256 proposalId, uint8 support, uint256 votesBlockNumber) public virtual returns (uint256) {
address voter = _msgSender();
return _castVote(proposalId, voter, support, '');
return _castVote(proposalId, voter, support, votesBlockNumber, '');
}

/**
Expand All @@ -563,10 +571,11 @@ abstract contract WonderGovernor is
function castVoteWithReason(
uint256 proposalId,
uint8 support,
uint256 votesBlockNumber,
string calldata reason
) public virtual returns (uint256) {
address voter = _msgSender();
return _castVote(proposalId, voter, support, reason);
return _castVote(proposalId, voter, support, votesBlockNumber, reason);
}

/**
Expand All @@ -575,11 +584,12 @@ abstract contract WonderGovernor is
function castVoteWithReasonAndParams(
uint256 proposalId,
uint8 support,
uint256 votesBlockNumber,
string calldata reason,
bytes memory params
) public virtual returns (uint256) {
address voter = _msgSender();
return _castVote(proposalId, voter, support, reason, params);
return _castVote(proposalId, voter, support, reason, votesBlockNumber, params);
}

/**
Expand All @@ -589,6 +599,7 @@ abstract contract WonderGovernor is
uint256 proposalId,
uint8 support,
address voter,
uint256 votesBlockNumber,
bytes memory signature
) public virtual returns (uint256) {
bool valid = SignatureChecker.isValidSignatureNow(
Expand All @@ -601,7 +612,7 @@ abstract contract WonderGovernor is
revert GovernorInvalidSignature(voter);
}

return _castVote(proposalId, voter, support, '');
return _castVote(proposalId, voter, support, votesBlockNumber, '');
}

/**
Expand All @@ -611,6 +622,7 @@ abstract contract WonderGovernor is
uint256 proposalId,
uint8 support,
address voter,
uint256 votesBlockNumber,
string calldata reason,
bytes memory params,
bytes memory signature
Expand All @@ -637,7 +649,7 @@ abstract contract WonderGovernor is
revert GovernorInvalidSignature(voter);
}

return _castVote(proposalId, voter, support, reason, params);
return _castVote(proposalId, voter, support, reason, votesBlockNumber, params);
}

/**
Expand All @@ -650,9 +662,10 @@ abstract contract WonderGovernor is
uint256 proposalId,
address account,
uint8 support,
uint256 votesBlockNumber,
string memory reason
) internal virtual returns (uint256) {
return _castVote(proposalId, account, support, reason, _defaultParams());
return _castVote(proposalId, account, support, reason, votesBlockNumber, _defaultParams());
}

/**
Expand All @@ -666,13 +679,14 @@ abstract contract WonderGovernor is
address account,
uint8 support,
string memory reason,
uint256 votesBlockNumber,
bytes memory params
) internal virtual returns (uint256) {
_validateStateBitmap(proposalId, _encodeStateBitmap(ProposalState.Active));

uint8 _proposalType = _proposals[proposalId].proposalType;

uint256 weight = _getVotes(account, _proposalType, proposalSnapshot(proposalId), params);
uint256 weight = _getVotes(account, _proposalType, votesBlockNumber, proposalSnapshot(proposalId), params);
_countVote(proposalId, account, support, weight, params);

if (params.length == 0) {
Expand Down
110 changes: 56 additions & 54 deletions solidity/contracts/governance/utils/WonderVotes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {IERC6372} from '@openzeppelin/contracts/interfaces/IERC6372.sol';
import {Context} from '@openzeppelin/contracts/utils/Context.sol';
import {Nonces} from '@openzeppelin/contracts/utils/Nonces.sol';
import {EIP712} from '@openzeppelin/contracts/utils/cryptography/EIP712.sol';
import {Checkpoints} from '@openzeppelin/contracts/utils/structs/Checkpoints.sol';
import {SafeCast} from '@openzeppelin/contracts/utils/math/SafeCast.sol';
import {ECDSA} from '@openzeppelin/contracts/utils/cryptography/ECDSA.sol';
import {Time} from '@openzeppelin/contracts/utils/types/Time.sol';
Expand All @@ -31,16 +30,19 @@ import {IWonderVotes} from 'interfaces/governance/utils/IWonderVotes.sol';
* previous example, it would be included in {ERC721-_update}).
*/
abstract contract WonderVotes is Context, EIP712, Nonces, IERC6372, IWonderVotes {
using Checkpoints for Checkpoints.Trace208;

bytes32 private constant DELEGATION_TYPEHASH =
keccak256('Delegation(uint8 proposalType, Delegate[] delegatees,uint256 nonce,uint256 expiry)');

mapping(address account => mapping(uint8 proposalType => Delegate[])) private _delegatees;

mapping(address delegatee => mapping(uint8 proposalType => Checkpoints.Trace208)) private _delegateCheckpoints;
mapping(address delegatee => mapping(uint8 proposalType => mapping(uint48 key => Checkpoint))) private
_delegateCheckpoints;

mapping(address delegatee => mapping(uint8 proposalType => uint48 lastUpdate)) private _delegateCheckpointsLastUpdate;

mapping(uint48 key => uint208 value) private _totalCheckpoints;

Checkpoints.Trace208 private _totalCheckpoints;
uint48 private _totalCheckpointsLastUpdate;

mapping(address account => bool) private _nonDelegableAddresses;

Expand Down Expand Up @@ -78,7 +80,7 @@ abstract contract WonderVotes is Context, EIP712, Nonces, IERC6372, IWonderVotes
* @dev Returns the current amount of votes that `account` has for the given `proposalType`.
*/
function getVotes(address account, uint8 proposalType) public view virtual returns (uint256) {
return _delegateCheckpoints[account][proposalType].latest();
return _delegateCheckpoints[account][proposalType][_delegateCheckpointsLastUpdate[account][proposalType]]._value;
}

/**
Expand All @@ -89,12 +91,15 @@ abstract contract WonderVotes is Context, EIP712, Nonces, IERC6372, IWonderVotes
*
* - `timepoint` must be in the past. If operating using block numbers, the block must be already mined.
*/
function getPastVotes(address account, uint8 proposalType, uint256 timepoint) public view virtual returns (uint256) {
uint48 currentTimepoint = clock();
if (timepoint >= currentTimepoint) {
revert ERC5805FutureLookup(timepoint, currentTimepoint);
}
return _delegateCheckpoints[account][proposalType].upperLookupRecent(SafeCast.toUint48(timepoint));
function getSnapshotVotes(
address account,
uint8 proposalType,
uint256 timepoint,
uint256 voteStart
) public view virtual returns (uint256) {
Checkpoint memory _checkPoint = _delegateCheckpoints[account][proposalType][uint48(timepoint)];
if (_checkPoint._nextKey < voteStart && _checkPoint._nextKey > 0) revert InvalidVotesClock(uint48(timepoint));
return _checkPoint._value;
}

/**
Expand All @@ -114,14 +119,14 @@ abstract contract WonderVotes is Context, EIP712, Nonces, IERC6372, IWonderVotes
if (timepoint >= currentTimepoint) {
revert ERC5805FutureLookup(timepoint, currentTimepoint);
}
return _totalCheckpoints.upperLookupRecent(SafeCast.toUint48(timepoint));
return _totalCheckpoints[uint48(timepoint)];
}

/**
* @dev Returns the current total supply of votes for a given `proposalType`.
*/
function _getTotalSupply() internal view virtual returns (uint256) {
return _totalCheckpoints.latest();
return _totalCheckpoints[_totalCheckpointsLastUpdate];
}

/**
Expand Down Expand Up @@ -292,11 +297,12 @@ abstract contract WonderVotes is Context, EIP712, Nonces, IERC6372, IWonderVotes
*/
function _transferVotingUnits(address from, address to, uint256 amount) internal virtual {
if (from == address(0)) {
_push(_totalCheckpoints, _add, SafeCast.toUint208(amount));
_totalCheckpoints[clock()] = uint208(_uncheckedAdd(_totalCheckpoints[_totalCheckpointsLastUpdate], amount));
}
if (to == address(0)) {
_push(_totalCheckpoints, _subtract, SafeCast.toUint208(amount));
_totalCheckpoints[clock()] = uint208(_uncheckedSub(_totalCheckpoints[_totalCheckpointsLastUpdate], amount));
}
_totalCheckpointsLastUpdate = clock();

uint8[] memory _proposalTypes = _getProposalTypes();

Expand All @@ -317,55 +323,39 @@ abstract contract WonderVotes is Context, EIP712, Nonces, IERC6372, IWonderVotes
if (from[i].account != address(0)) {
_weight = from[i].weight;
uint256 _votingUnits = amount * _weight / _weightSum;
(uint256 oldValue, uint256 newValue) =
_push(_delegateCheckpoints[from[i].account][proposalType], _subtract, SafeCast.toUint208(_votingUnits));
emit DelegateVotesChanged(from[i].account, proposalType, oldValue, newValue);

Checkpoint storage _previousCheckpoint = _delegateCheckpoints[from[i].account][proposalType][_delegateCheckpointsLastUpdate[from[i]
.account][proposalType]];
_previousCheckpoint._nextKey = clock();

uint256 _oldValue = _previousCheckpoint._value;
uint256 _newValue = _uncheckedSub(_oldValue, _votingUnits);

_delegateCheckpoints[from[i].account][proposalType][clock()]._value = uint208(_newValue);

_delegateCheckpointsLastUpdate[from[i].account][proposalType] = clock();
emit DelegateVotesChanged(from[i].account, proposalType, _oldValue, _newValue);
}
}

for (uint256 i = 0; i < to.length; i++) {
if (to[i].account != address(0)) {
_weight = to[i].weight;
uint256 _votingUnits = amount * _weight / _weightSum;
(uint256 oldValue, uint256 newValue) =
_push(_delegateCheckpoints[to[i].account][proposalType], _add, SafeCast.toUint208(_votingUnits));
emit DelegateVotesChanged(to[i].account, proposalType, oldValue, newValue);
}
}
}

/**
* @dev Get number of checkpoints for `account` given a `proposalType`.
*/
function _numCheckpoints(address account, uint8 proposalType) internal view virtual returns (uint32) {
return SafeCast.toUint32(_delegateCheckpoints[account][proposalType].length());
}
Checkpoint storage _previousCheckpoint =
_delegateCheckpoints[to[i].account][proposalType][_delegateCheckpointsLastUpdate[to[i].account][proposalType]];
_previousCheckpoint._nextKey = clock();

/**
* @dev Get the `pos`-th checkpoint for `account` given a `proposalType`.
*/
function _checkpoints(
address account,
uint8 proposalType,
uint32 pos
) internal view virtual returns (Checkpoints.Checkpoint208 memory) {
return _delegateCheckpoints[account][proposalType].at(pos);
}

function _push(
Checkpoints.Trace208 storage store,
function(uint208, uint208) view returns (uint208) op,
uint208 delta
) private returns (uint208, uint208) {
return store.push(clock(), op(store.latest(), delta));
}
uint256 _oldValue = _previousCheckpoint._value;
uint256 _newValue = _uncheckedAdd(_oldValue, _votingUnits);

function _add(uint208 a, uint208 b) private pure returns (uint208) {
return a + b;
}
_delegateCheckpoints[to[i].account][proposalType][clock()]._value = uint208(_newValue);

function _subtract(uint208 a, uint208 b) private pure returns (uint208) {
return a - b;
_delegateCheckpointsLastUpdate[to[i].account][proposalType] = clock();
emit DelegateVotesChanged(to[i].account, proposalType, _oldValue, _newValue);
}
}
}

/**
Expand Down Expand Up @@ -418,4 +408,16 @@ abstract contract WonderVotes is Context, EIP712, Nonces, IERC6372, IWonderVotes
}
_;
}

function _uncheckedAdd(uint256 a, uint256 b) internal pure returns (uint256) {
unchecked {
return a + b;
}
}

function _uncheckedSub(uint256 a, uint256 b) internal pure returns (uint256) {
unchecked {
return a - b;
}
}
}
21 changes: 1 addition & 20 deletions solidity/contracts/token/ERC20/extensions/WonderERC20Votes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
pragma solidity ^0.8.20;

import {ERC20} from '@openzeppelin/contracts/token/ERC20/ERC20.sol';
import {Checkpoints} from '@openzeppelin/contracts/utils/structs/Checkpoints.sol';
import {WonderVotes} from 'contracts/governance/utils/WonderVotes.sol';

/**
Expand All @@ -15,7 +14,7 @@ import {WonderVotes} from 'contracts/governance/utils/WonderVotes.sol';
*
* This extension keeps a history (checkpoints) of each account's vote power. Vote power can be delegated either
* by calling the {delegate} function directly, or by providing a signature to be used with {delegateBySig}. Voting
* power can be queried through the public accessors {getVotes} and {getPastVotes}.
* power can be queried through the public accessors {getVotes} and {getSnapshotVotes}.
*
* By default, token balance does not account for voting power. This makes transfers cheaper. The downside is that it
* requires users to delegate to themselves in order to activate checkpoints and have their voting power tracked.
Expand Down Expand Up @@ -66,22 +65,4 @@ abstract contract WonderERC20Votes is ERC20, WonderVotes {
function _getVotingUnits(address account) internal view virtual override returns (uint256) {
return balanceOf(account);
}

/**
* @dev Get number of checkpoints for `account` given a `proposalType`.
*/
function numCheckpoints(address account, uint8 proposalType) public view virtual returns (uint32) {
return _numCheckpoints(account, proposalType);
}

/**
* @dev Get the `pos`-th checkpoint for `account` given a `proposalType`.
*/
function checkpoints(
address account,
uint8 proposalType,
uint32 pos
) public view virtual returns (Checkpoints.Checkpoint208 memory) {
return _checkpoints(account, proposalType, pos);
}
}
Loading

0 comments on commit f6b8f6b

Please sign in to comment.