Skip to content

Commit 38c114a

Browse files
committed
Revert "Is the problem in /src?"
This reverts commit 0caff70.
1 parent 0caff70 commit 38c114a

File tree

6 files changed

+644
-0
lines changed

6 files changed

+644
-0
lines changed

src/FlexVotingBase.sol

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.20;
3+
4+
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
5+
import {Checkpoints} from "@openzeppelin/contracts/utils/structs/Checkpoints.sol";
6+
import {IFractionalGovernor} from "src/interfaces/IFractionalGovernor.sol";
7+
import {IVotingToken} from "src/interfaces/IVotingToken.sol";
8+
9+
/// @notice This is an abstract contract designed to make it easy to build
10+
/// clients for governance systems that inherit from GovernorCountingFractional,
11+
/// a.k.a. Flexible Voting governors.
12+
///
13+
/// A "client" in this sense is a contract that:
14+
15+
/// - (a) receives deposits of governance tokens from its users,
16+
/// - (b) gives said depositors the ability to express their voting preferences
17+
/// on governance proposals, and
18+
/// - (c) casts votes on said proposals to flexible voting governors according
19+
/// to the expressed preferences of its depositors.
20+
///
21+
/// This contract assumes that a child contract will implement a mechanism for
22+
/// receiving and storing deposit balances, part (a). With that in place, this
23+
/// contract supplies features (b) and (c).
24+
///
25+
/// A key concept here is that of a user's "raw balance". The raw balance is the
26+
/// system's internal representation of a user's claim on the governance tokens
27+
/// that it custodies. Since different systems might represent such claims in
28+
/// different ways, this contract leaves the implementation of the `_rawBalance`
29+
/// function to the child contract.
30+
///
31+
/// The simplest such representation would be to directly store the cumulative
32+
/// balance of the governance token that the user has deposited. In such a
33+
/// system, the amount that the user deposits is the amount that the user has
34+
/// claim to. If the user has claim to 1e18 governance tokens, the internal
35+
/// representation is just 1e18.
36+
///
37+
/// In many systems, however, the raw balance will not be equivalent to the
38+
/// amount of governance tokens the user has claim to. In Aave, for example,
39+
/// deposit amounts are scaled down by an ever-increasing index that represents
40+
/// the cumulative amount of interest earned over the lifetime of deposits. The
41+
/// "raw balance" of a user in Aave's case is this scaled down amount, since it
42+
/// is the value that represents the user's claim on deposits. Thus for Aave, a
43+
/// users's raw balance will always be less than the actual amount they have
44+
/// claim to.
45+
///
46+
/// If the raw balance can be identified and defined for a system, and
47+
/// `_rawBalance` can be implemented for it, then this contract will take care
48+
/// of the rest.
49+
abstract contract FlexVotingBase {
50+
using SafeCast for uint256;
51+
52+
// @dev Trace208 is used instead of Trace224 because the former allocates 48
53+
// bits to its _key. We need at least 48 bits because the _key is going to be
54+
// a timepoint. Timepoints in the context of ERC20Votes and ERC721Votes
55+
// conform to the EIP-6372 standard, which specifies they be uint48s.
56+
using Checkpoints for Checkpoints.Trace208;
57+
58+
/// @notice The governor contract associated with this governance token. It
59+
/// must be one that supports fractional voting, e.g. GovernorCountingFractional.
60+
IFractionalGovernor public immutable GOVERNOR;
61+
62+
/// @dev Mapping from address to the checkpoint history of internal voting
63+
/// weight for that address, i.e. how much weight they can call `expressVote`
64+
/// with at a given time.
65+
mapping(address => Checkpoints.Trace208) internal voteWeightCheckpoints;
66+
67+
/// @dev History of the sum total of voting weight in the system. May or may
68+
/// not be equivalent to this contract's balance of `GOVERNOR`s token at a
69+
/// given time.
70+
Checkpoints.Trace208 internal totalVoteWeightCheckpoints;
71+
72+
/// @param _governor The address of the flex-voting-compatible governance contract.
73+
constructor(address _governor) {
74+
GOVERNOR = IFractionalGovernor(_governor);
75+
}
76+
77+
/// @dev Returns a representation of the current amount of `GOVERNOR`s
78+
/// token that `_user` has claim to in this system. It may or may not be
79+
/// equivalent to the withdrawable balance of `GOVERNOR`s token for `user`,
80+
/// e.g. if the internal representation of balance has been scaled down.
81+
function _rawBalanceOf(address _user) internal view virtual returns (uint208);
82+
83+
// TODO Should we rename this function to avoid collision with FlexVotingDelegable?
84+
// https://github.com/ScopeLift/flexible-voting/issues/88
85+
/// @dev Delegates the present contract's voting rights with `GOVERNOR` to itself.
86+
function _selfDelegate() internal {
87+
IVotingToken(GOVERNOR.token()).delegate(address(this));
88+
}
89+
90+
function _applyDeltaToCheckpoint(Checkpoints.Trace208 storage _checkpoint, int256 _delta)
91+
internal
92+
returns (uint208 _prevTotal, uint208 _newTotal)
93+
{
94+
// The casting in this function is safe since:
95+
// - if oldTotal + delta > int256.max it will panic and revert.
96+
// - if |delta| <= oldTotal there is no risk of wrapping
97+
// - if |delta| > oldTotal
98+
// * uint256(oldTotal + delta) will wrap but the wrapped value will
99+
// necessarily be greater than uint208.max, so SafeCast will revert.
100+
// * the lowest that oldTotal + delta can be is int256.min (when
101+
// oldTotal is 0 and delta is int256.min). The wrapped value of a
102+
// negative signed integer is:
103+
// wrapped(integer) = uint256.max + integer
104+
// Substituting:
105+
// wrapped(int256.min) = uint256.max + int256.min
106+
// But:
107+
// uint256.max + int256.min > uint208.max
108+
// Substituting again:
109+
// wrapped(int256.min) > uint208.max, which will revert when safecast.
110+
_prevTotal = _checkpoint.latest();
111+
int256 _castTotal = int256(uint256(_prevTotal));
112+
_newTotal = SafeCast.toUint208(uint256(_castTotal + _delta));
113+
114+
uint48 _timepoint = IVotingToken(GOVERNOR.token()).clock();
115+
_checkpoint.push(_timepoint, _newTotal);
116+
}
117+
118+
/// @dev Checkpoints internal voting weight of `user` after applying `_delta`.
119+
function _checkpointVoteWeightOf(address _user, int256 _delta) internal virtual {
120+
_applyDeltaToCheckpoint(voteWeightCheckpoints[_user], _delta);
121+
}
122+
123+
/// @dev Checkpoints the total vote weight after applying `_delta`.
124+
function _checkpointTotalVoteWeight(int256 _delta) internal virtual {
125+
_applyDeltaToCheckpoint(totalVoteWeightCheckpoints, _delta);
126+
}
127+
}

src/FlexVotingClient.sol

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.20;
3+
4+
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
5+
import {Checkpoints} from "@openzeppelin/contracts/utils/structs/Checkpoints.sol";
6+
import {IFractionalGovernor} from "src/interfaces/IFractionalGovernor.sol";
7+
import {IVotingToken} from "src/interfaces/IVotingToken.sol";
8+
import {FlexVotingBase} from "src/FlexVotingBase.sol";
9+
10+
/// @notice This is an abstract contract designed to make it easy to build clients
11+
/// for governance systems that inherit from GovernorCountingFractional, a.k.a.
12+
/// Flexible Voting governors.
13+
///
14+
/// This contract extends FlexVotingBase, adding two features:
15+
/// (a) the ability for depositors to express voting preferences on
16+
/// {Governor}'s proposals, and
17+
/// (b) the ability to cast fractional, rolled up votes on behalf of depositors.
18+
abstract contract FlexVotingClient is FlexVotingBase {
19+
using Checkpoints for Checkpoints.Trace208;
20+
using SafeCast for uint256;
21+
22+
/// @notice The voting options. The order of options should match that of the
23+
/// voting options in the corresponding {Governor} contract.
24+
enum VoteType {
25+
Against,
26+
For,
27+
Abstain
28+
}
29+
30+
/// @notice Data structure to store vote preferences expressed by depositors.
31+
struct ProposalVote {
32+
uint128 againstVotes;
33+
uint128 forVotes;
34+
uint128 abstainVotes;
35+
}
36+
37+
/// @dev Map proposalId to an address to whether they have voted on this proposal.
38+
mapping(uint256 => mapping(address => bool)) private proposalVoterHasVoted;
39+
40+
/// @notice Map proposalId to vote totals expressed on this proposal.
41+
mapping(uint256 => ProposalVote) public proposalVotes;
42+
43+
/// Constant used by OZ's implementation of {GovernorCountingFractional} to
44+
/// signal fractional voting.
45+
/// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/7b74442c5e87ea51dde41c7f18a209fa5154f1a4/contracts/governance/extensions/GovernorCountingFractional.sol#L37
46+
uint8 internal constant VOTE_TYPE_FRACTIONAL = 255;
47+
48+
error FlexVotingClient__NoVotingWeight();
49+
error FlexVotingClient__AlreadyVoted();
50+
error FlexVotingClient__InvalidSupportValue();
51+
error FlexVotingClient__NoVotesExpressed();
52+
53+
/// @dev Used as the `reason` param when submitting a vote to `GOVERNOR`.
54+
function _castVoteReasonString() internal virtual returns (string memory) {
55+
return "rolled-up vote from governance token holders";
56+
}
57+
58+
/// @notice Allow the caller to express their voting preference for a given
59+
/// proposal. Their preference is recorded internally but not moved to the
60+
/// Governor until `castVote` is called.
61+
/// @param proposalId The proposalId in the associated Governor
62+
/// @param support The depositor's vote preferences in accordance with the `VoteType` enum.
63+
function expressVote(uint256 proposalId, uint8 support) external virtual {
64+
address voter = msg.sender;
65+
uint256 weight = getPastVoteWeight(voter, GOVERNOR.proposalSnapshot(proposalId));
66+
if (weight == 0) revert FlexVotingClient__NoVotingWeight();
67+
68+
if (proposalVoterHasVoted[proposalId][voter]) revert FlexVotingClient__AlreadyVoted();
69+
proposalVoterHasVoted[proposalId][voter] = true;
70+
71+
if (support == uint8(VoteType.Against)) {
72+
proposalVotes[proposalId].againstVotes += SafeCast.toUint128(weight);
73+
} else if (support == uint8(VoteType.For)) {
74+
proposalVotes[proposalId].forVotes += SafeCast.toUint128(weight);
75+
} else if (support == uint8(VoteType.Abstain)) {
76+
proposalVotes[proposalId].abstainVotes += SafeCast.toUint128(weight);
77+
} else {
78+
// Support value must be included in VoteType enum.
79+
revert FlexVotingClient__InvalidSupportValue();
80+
}
81+
}
82+
83+
/// @notice Causes this contract to cast a vote to the Governor for all of the
84+
/// accumulated votes expressed by users. Uses the total internal vote weight
85+
/// to proportionally split weight among expressed votes. Can be called by
86+
/// anyone. It is idempotent and can be called multiple times during the
87+
/// lifecycle of a given proposal.
88+
/// @param proposalId The ID of the proposal which the FlexVotingClient will
89+
/// now vote on.
90+
function castVote(uint256 proposalId) external {
91+
ProposalVote storage _proposalVote = proposalVotes[proposalId];
92+
if (_proposalVote.forVotes + _proposalVote.againstVotes + _proposalVote.abstainVotes == 0) {
93+
revert FlexVotingClient__NoVotesExpressed();
94+
}
95+
96+
uint256 _proposalSnapshot = GOVERNOR.proposalSnapshot(proposalId);
97+
98+
// We use the snapshot of total vote weight to determine the weight with
99+
// which to vote. We do this for two reasons:
100+
// (1) We cannot use the proposalVote numbers alone, since some people with
101+
// balances at the snapshot might never express their preferences. If a
102+
// large holder never expressed a preference, but this contract nevertheless
103+
// cast votes to the governor with all of its weight, then other users may
104+
// effectively have *increased* their voting weight because someone else
105+
// didn't participate, which creates all kinds of bad incentives.
106+
// (2) Other people might have already expressed their preferences on this
107+
// proposal and had those preferences submitted to the governor by an
108+
// earlier call to this function. The weight of those preferences
109+
// should still be taken into consideration when determining how much
110+
// weight to vote with this time.
111+
// Using the total vote weight to proportion votes in this way means that in
112+
// many circumstances this function will not cast votes with all of its
113+
// weight.
114+
uint256 _totalVotesInternal = getPastTotalVoteWeight(_proposalSnapshot);
115+
116+
// We need 256 bits because of the multiplication we're about to do.
117+
uint256 _totalTokenWeight =
118+
IVotingToken(address(GOVERNOR.token())).getPastVotes(address(this), _proposalSnapshot);
119+
120+
// userVotesInternal userVoteWeight
121+
// ------------------------- = --------------------
122+
// totalVotesInternal totalTokenWeight
123+
//
124+
// userVoteWeight = userVotesInternal * totalTokenWeight / totalVotesInternal
125+
uint128 _forVotesToCast =
126+
SafeCast.toUint128((_totalTokenWeight * _proposalVote.forVotes) / _totalVotesInternal);
127+
uint128 _againstVotesToCast =
128+
SafeCast.toUint128((_totalTokenWeight * _proposalVote.againstVotes) / _totalVotesInternal);
129+
uint128 _abstainVotesToCast =
130+
SafeCast.toUint128((_totalTokenWeight * _proposalVote.abstainVotes) / _totalVotesInternal);
131+
132+
// Clear the stored votes so that we don't double-cast them.
133+
delete proposalVotes[proposalId];
134+
135+
bytes memory fractionalizedVotes =
136+
abi.encodePacked(_againstVotesToCast, _forVotesToCast, _abstainVotesToCast);
137+
GOVERNOR.castVoteWithReasonAndParams(
138+
proposalId, VOTE_TYPE_FRACTIONAL, _castVoteReasonString(), fractionalizedVotes
139+
);
140+
}
141+
142+
/// @notice Returns the `_user`'s internal voting weight at `_timepoint`.
143+
/// @param _user The account that's historical vote weight will be looked up.
144+
/// @param _timepoint The timepoint at which to lookup the _user's internal
145+
/// voting weight, either a block number or a timestamp as determined by
146+
/// {GOVERNOR.token().clock()}.
147+
function getPastVoteWeight(address _user, uint256 _timepoint) public view returns (uint256) {
148+
uint48 key = SafeCast.toUint48(_timepoint);
149+
return voteWeightCheckpoints[_user].upperLookup(key);
150+
}
151+
152+
/// @notice Returns the total internal voting weight of all users at `_timepoint`.
153+
/// @param _timepoint The timepoint at which to lookup the total weight,
154+
/// either a block number or a timestamp as determined by
155+
/// {GOVERNOR.token().clock()}.
156+
function getPastTotalVoteWeight(uint256 _timepoint) public view returns (uint256) {
157+
uint48 key = SafeCast.toUint48(_timepoint);
158+
return totalVoteWeightCheckpoints.upperLookup(key);
159+
}
160+
}

src/FlexVotingDelegable.sol

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.20;
3+
4+
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
5+
import {Context} from "@openzeppelin/contracts/utils/Context.sol";
6+
import {Checkpoints} from "@openzeppelin/contracts/utils/structs/Checkpoints.sol";
7+
8+
import {FlexVotingBase} from "src/FlexVotingBase.sol";
9+
10+
/// @notice This is an abstract contract designed to make it easy to build
11+
/// clients for governance systems that inherit from GovernorCountingFractional,
12+
/// a.k.a. Flexible Voting governors.
13+
///
14+
/// This contract extends FlexVotingBase, adding the ability to subdelegate one's
15+
/// internal voting weight. It is meant to be inherited from in conjunction with
16+
/// FlexVotingClient. Doing so makes the following usecase possible:
17+
/// - user A deposits 100 governance tokens in a FlexVotingClient
18+
/// - user B deposits 50 governance tokens into the same client
19+
/// - user A delegates voting weight to user B
20+
/// - a proposal is created in the Governor contract
21+
/// - user B expresses a voting preference P on the proposal to the client
22+
/// - the client casts its votes on the proposal to the Governor contract
23+
/// - user B's voting weight is combined with user A's voting weight so that
24+
/// 150 tokens are effectively cast with voting preference P on behalf of
25+
/// users A and B.
26+
abstract contract FlexVotingDelegable is Context, FlexVotingBase {
27+
using Checkpoints for Checkpoints.Trace208;
28+
29+
// @dev Emitted when an account changes its delegate.
30+
event DelegateChanged(
31+
address indexed delegator, address indexed fromDelegate, address indexed toDelegate
32+
);
33+
34+
// @dev Emitted when a delegate change results in changes to a delegate's
35+
// number of voting weight.
36+
event DelegateWeightChanged(address indexed delegate, uint256 previousVotes, uint256 newVotes);
37+
38+
mapping(address account => address) private _delegatee;
39+
40+
// @dev Delegates votes from the sender to `delegatee`.
41+
function delegate(address delegatee) public virtual {
42+
address account = _msgSender();
43+
_delegate(account, delegatee);
44+
}
45+
46+
// @dev Returns the delegate that `account` has chosen. Assumes
47+
// self-delegation if no delegate has been chosen.
48+
function delegates(address _account) public view virtual returns (address) {
49+
address _proxy = _delegatee[_account];
50+
if (_proxy == address(0)) return _account;
51+
return _proxy;
52+
}
53+
54+
// @dev Delegate all of `account`'s voting units to `delegatee`.
55+
//
56+
// Emits events {DelegateChanged} and {DelegateWeightChanged}.
57+
function _delegate(address account, address delegatee) internal virtual {
58+
address oldDelegate = delegates(account);
59+
_delegatee[account] = delegatee;
60+
61+
int256 _delta = int256(uint256(_rawBalanceOf(account)));
62+
emit DelegateChanged(account, oldDelegate, delegatee);
63+
_updateDelegateBalance(oldDelegate, delegatee, _delta);
64+
}
65+
66+
function _checkpointVoteWeightOf(address _user, int256 _delta) internal virtual override {
67+
address _proxy = delegates(_user);
68+
_applyDeltaToCheckpoint(voteWeightCheckpoints[_proxy], _delta);
69+
}
70+
71+
// @dev Moves delegated votes from one delegate to another.
72+
function _updateDelegateBalance(address from, address to, int256 _delta) internal virtual {
73+
if (from == to || _delta == 0) return;
74+
75+
// Decrement old delegate's weight.
76+
(uint208 _oldFrom, uint208 _newFrom) =
77+
_applyDeltaToCheckpoint(voteWeightCheckpoints[from], -_delta);
78+
emit DelegateWeightChanged(from, _oldFrom, _newFrom);
79+
80+
// Increment new delegate's weight.
81+
(uint208 _oldTo, uint208 _newTo) = _applyDeltaToCheckpoint(voteWeightCheckpoints[to], _delta);
82+
emit DelegateWeightChanged(to, _oldTo, _newTo);
83+
}
84+
}

0 commit comments

Comments
 (0)