-
Notifications
You must be signed in to change notification settings - Fork 47
/
Copy pathStakingGovernanceModule.sol
374 lines (337 loc) · 15.1 KB
/
StakingGovernanceModule.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
pragma solidity ^0.5.17;
pragma experimental ABIEncoderV2;
import "./shared/CheckpointsShared.sol";
import "../../../openzeppelin/Address.sol";
import "./shared/StakingShared.sol";
import "../../../proxy/modules/interfaces/IFunctionsList.sol";
import "../../../rsk/RSKAddrValidator.sol";
import "../../Vesting/IVesting.sol";
/**
* @title Staking Governance Module contract
* @notice Implements voting power and delegation functionality
* */
contract StakingGovernanceModule is IFunctionsList, StakingShared, CheckpointsShared {
using Address for address payable;
/************* TOTAL VOTING POWER COMPUTATION ************************/
/**
* @notice Compute the total voting power at a given time.
* @param blockNumber The block number, needed for checkpointing.
* @param time The timestamp for which to calculate the total voting power.
* @return The total voting power at the given time.
* */
function getPriorTotalVotingPower(
uint32 blockNumber,
uint256 time
) public view returns (uint96 totalVotingPower) {
/// @dev Start the computation with the exact or previous unlocking date (voting weight remians the same until the next break point).
uint256 start = _timestampToLockDate(time);
uint256 end = start + MAX_DURATION;
/// @dev Max 78 iterations.
for (uint256 i = start; i <= end; i += TWO_WEEKS) {
totalVotingPower = add96(
totalVotingPower,
_totalPowerByDate(i, start, blockNumber),
"arrays mismatch"
); // WS06
}
}
/**
* @notice Compute the voting power for a specific date.
* Power = stake * weight
* @param date The staking date to compute the power for.
* @param startDate The date for which we need to know the power of the stake.
* @param blockNumber The block number, needed for checkpointing.
* @return The stacking power.
* */
function _totalPowerByDate(
uint256 date,
uint256 startDate,
uint256 blockNumber
) internal view returns (uint96 power) {
uint96 weight = _computeWeightByDate(date, startDate);
uint96 staked = _getPriorTotalStakesForDate(date, blockNumber);
/// @dev weight is multiplied by some factor to allow decimals.
power = mul96(staked, weight, "mul overflow") / WEIGHT_FACTOR; // WS07
}
/****************************** DELEGATED VOTING POWER COMPUTATION ************************/
/**
* @notice Get the current votes balance for a user account.
* @param account The address to get votes balance.
* @dev This is a wrapper to simplify arguments. The actual computation is
* performed on WeightedStaking parent contract.
* @return The number of current votes for a user account.
* */
function getCurrentVotes(address account) external view returns (uint96) {
return getPriorVotes(account, block.number - 1, block.timestamp);
}
/**
* @notice Determine the prior number of votes for a delegatee as of a block number.
* Iterate through checkpoints adding up voting power.
* @dev Block number must be a finalized block or else this function will revert
* to prevent misinformation.
* Used for Voting, not for fee sharing.
* @param account The address of the account to check.
* @param blockNumber The block number to get the vote balance at.
* @param date The staking date to compute the power for.
* @return The number of votes the delegatee had as of the given block.
* */
function getPriorVotes(
address account,
uint256 blockNumber,
uint256 date
) public view returns (uint96 votes) {
/// @dev If date is not an exact break point, start weight computation from the previous break point (alternative would be the next).
uint256 start = _timestampToLockDate(date);
uint256 end = start + MAX_DURATION;
/// @dev Max 78 iterations.
for (uint256 i = start; i <= end; i += TWO_WEEKS) {
votes = add96(
votes,
_totalPowerByDateForDelegatee(account, i, start, blockNumber),
"overflow - total VP"
); // WS09
}
}
/**
* @notice Compute the voting power for a specific date.
* Power = stake * weight
* @param account The address of the account to check.
* @param date The staking date to compute the power for.
* @param startDate The date for which we need to know the power of the stake.
* @param blockNumber The block number, needed for checkpointing.
* @return The stacking power.
* */
function _totalPowerByDateForDelegatee(
address account,
uint256 date,
uint256 startDate,
uint256 blockNumber
) internal view returns (uint96 power) {
uint96 weight = _computeWeightByDate(date, startDate);
uint96 staked = _getPriorStakeByDateForDelegatee(account, date, blockNumber);
power = mul96(staked, weight, "mul overflow") / WEIGHT_FACTOR; // WS10
}
/**
* @notice Determine the prior number of stake for an account as of a block number.
* @dev Block number must be a finalized block or else this function will
* revert to prevent misinformation.
* @param account The address of the account to check.
* @param date The staking date to compute the power for. Adjusted to the next valid lock date, if necessary.
* @param blockNumber The block number to get the vote balance at.
* @return The number of votes the account had as of the given block.
* */
function getPriorStakeByDateForDelegatee(
address account,
uint256 date,
uint256 blockNumber
) external view returns (uint96) {
date = _adjustDateForOrigin(date);
return _getPriorStakeByDateForDelegatee(account, date, blockNumber);
}
/**
* @notice Determine the prior number of stake for an account as of a block number.
* @dev Block number must be a finalized block or else this function will
* revert to prevent misinformation.
* @param account The address of the account to check.
* @param date The staking date to compute the power for.
* @param blockNumber The block number to get the vote balance at.
* @return The number of votes the account had as of the given block.
* */
function _getPriorStakeByDateForDelegatee(
address account,
uint256 date,
uint256 blockNumber
) internal view returns (uint96) {
require(blockNumber < _getCurrentBlockNumber(), "not determined yet"); // WS11
uint32 nCheckpoints = numDelegateStakingCheckpoints[account][date];
if (nCheckpoints == 0) {
return 0;
}
/// @dev First check most recent balance.
if (delegateStakingCheckpoints[account][date][nCheckpoints - 1].fromBlock <= blockNumber) {
return delegateStakingCheckpoints[account][date][nCheckpoints - 1].stake;
}
/// @dev Next check implicit zero balance.
if (delegateStakingCheckpoints[account][date][0].fromBlock > blockNumber) {
return 0;
}
uint32 lower = 0;
uint32 upper = nCheckpoints - 1;
while (upper > lower) {
uint32 center = upper - (upper - lower) / 2; /// @dev ceil, avoiding overflow.
Checkpoint memory cp = delegateStakingCheckpoints[account][date][center];
if (cp.fromBlock == blockNumber) {
return cp.stake;
} else if (cp.fromBlock < blockNumber) {
lower = center;
} else {
upper = center - 1;
}
}
return delegateStakingCheckpoints[account][date][lower].stake;
}
/**************** SHARED FUNCTIONS *********************/
/**
* @notice Determine the prior number of stake for an unlocking date as of a block number.
* @dev Block number must be a finalized block or else this function will
* revert to prevent misinformation.
* @param date The date to check the stakes for. Adjusted to the next valid lock date, as necessary
* @param blockNumber The block number to get the vote balance at.
* @return The total number of votes as of the given block.
* */
function getPriorTotalStakesForDate(
uint256 date,
uint256 blockNumber
) public view returns (uint96) {
date = _adjustDateForOrigin(date);
return _getPriorTotalStakesForDate(date, blockNumber);
}
/**
* @notice Determine the prior number of stake for an unlocking date as of a block number.
* @dev Block number must be a finalized block or else this function will
* revert to prevent misinformation.
* @param date The date to check the stakes for.
* @param blockNumber The block number to get the vote balance at.
* @return The total number of votes as of the given block.
* */
function _getPriorTotalStakesForDate(
uint256 date,
uint256 blockNumber
) internal view returns (uint96) {
require(blockNumber < _getCurrentBlockNumber(), "not determined"); // WS08
uint32 nCheckpoints = numTotalStakingCheckpoints[date];
if (nCheckpoints == 0) {
return 0;
}
// First check most recent balance
if (totalStakingCheckpoints[date][nCheckpoints - 1].fromBlock <= blockNumber) {
return totalStakingCheckpoints[date][nCheckpoints - 1].stake;
}
// Next check implicit zero balance
if (totalStakingCheckpoints[date][0].fromBlock > blockNumber) {
return 0;
}
uint32 lower = 0;
uint32 upper = nCheckpoints - 1;
while (upper > lower) {
uint32 center = upper - (upper - lower) / 2; // ceil, avoiding overflow
Checkpoint memory cp = totalStakingCheckpoints[date][center];
if (cp.fromBlock == blockNumber) {
return cp.stake;
} else if (cp.fromBlock < blockNumber) {
lower = center;
} else {
upper = center - 1;
}
}
return totalStakingCheckpoints[date][lower].stake;
}
/**
* @notice Set new delegatee. Move from user's current delegate to a new
* delegatee the stake balance.
* @param delegator The user address to move stake balance from its current delegatee.
* @param delegatee The new delegatee. The address to move stake balance to.
* @param lockedTS The lock date.
* @dev Reverts if delegator balance or delegatee is not valid, unless the sender is a vesting contract.
* */
function _delegate(address delegator, address delegatee, uint256 lockedTS) internal {
address currentDelegate = delegates[delegator][lockedTS];
uint96 delegatorBalance = _currentBalance(delegator, lockedTS);
// vesting contracts will in multiple cases try to delegate a zero balance
// or to the existing delegatee
if (_isVestingContract(msg.sender)) {
if (delegatorBalance == 0 || currentDelegate == delegatee) {
return;
}
} else {
require(delegatorBalance > 0, "no stake to delegate");
require(currentDelegate != delegatee, "cannot delegate to the existing delegatee");
}
delegates[delegator][lockedTS] = delegatee;
emit DelegateChanged(delegator, lockedTS, currentDelegate, delegatee);
_moveDelegates(currentDelegate, delegatee, delegatorBalance, lockedTS);
}
// @dev delegates tokens for lock date 2 weeks later than given lock date
// if message sender is a contract
function _delegateNext(address delegator, address delegatee, uint256 lockedTS) internal {
if (_isVestingContract(msg.sender)) {
uint256 nextLock = lockedTS.add(TWO_WEEKS);
address currentDelegate = delegates[delegator][nextLock];
if (currentDelegate != delegatee) {
_delegate(delegator, delegatee, nextLock);
}
// @dev workaround for the issue with a delegation of the latest stake
uint256 endDate = IVesting(msg.sender).endDate();
nextLock = lockedTS.add(FOUR_WEEKS);
if (nextLock == endDate) {
currentDelegate = delegates[delegator][nextLock];
if (currentDelegate != delegatee) {
_delegate(delegator, delegatee, nextLock);
}
}
}
}
/**
* @notice Move an amount of delegate stake from a source address to a
* destination address.
* @param srcRep The address to get the staked amount from.
* @param dstRep The address to send the staked amount to.
* @param amount The staked amount to move.
* @param lockedTS The lock date.
* */
function _moveDelegates(
address srcRep,
address dstRep,
uint96 amount,
uint256 lockedTS
) internal {
if (srcRep != dstRep && amount > 0) {
if (srcRep != address(0)) _decreaseDelegateStake(srcRep, lockedTS, amount);
if (dstRep != address(0)) _increaseDelegateStake(dstRep, lockedTS, amount);
}
}
/**
* @notice Retrieve CHAIN_ID of the executing chain.
*
* Chain identifier (chainID) introduced in EIP-155 protects transaction
* included into one chain from being included into another chain.
* Basically, chain identifier is an integer number being used in the
* processes of signing transactions and verifying transaction signatures.
*
* @dev As of version 0.5.12, Solidity includes an assembly function
* chainid() that provides access to the new CHAINID opcode.
*
* TODO: chainId is included in block. So you can get chain id like
* block timestamp or block number: block.chainid;
* */
function _getChainId() internal pure returns (uint256) {
uint256 chainId;
assembly {
chainId := chainid()
}
return chainId;
}
/**
* @notice Delegate votes from `msg.sender` which are locked until lockDate to `delegatee`.
* @param delegatee The address to delegate votes to.
* @param lockDate the date if the position to delegate.
* */
function delegate(address delegatee, uint256 lockDate) external whenNotPaused {
require(delegatee != address(0), "cannot delegate to the zero address");
_notSameBlockAsStakingCheckpoint(lockDate, msg.sender);
_delegate(msg.sender, delegatee, lockDate);
// @dev delegates tokens for lock date 2 weeks later than given lock date
// if message sender is a contract
_delegateNext(msg.sender, delegatee, lockDate);
}
function getFunctionsList() external pure returns (bytes4[] memory) {
bytes4[] memory functionsList = new bytes4[](6);
functionsList[0] = this.getPriorTotalVotingPower.selector;
functionsList[1] = this.getCurrentVotes.selector;
functionsList[2] = this.getPriorVotes.selector;
functionsList[3] = this.getPriorStakeByDateForDelegatee.selector;
functionsList[4] = this.getPriorTotalStakesForDate.selector;
functionsList[5] = this.delegate.selector;
return functionsList;
}
}