forked from MiloTruck/evm-ctf-challenges
-
Notifications
You must be signed in to change notification settings - Fork 0
/
VotingVault.sol
157 lines (122 loc) · 5.13 KB
/
VotingVault.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
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.15;
import {IERC20} from "./lib/IERC20.sol";
import {History} from "./History.sol";
/// @title VotingVault
/// @notice The VotingVault contract.
contract VotingVault {
using History for History.CheckpointHistory;
struct Deposit {
uint256 cumulativeAmount;
uint256 unlockTimestamp;
}
struct UserData {
Deposit[] deposits;
uint256 front;
address delegatee;
}
uint256 public constant VOTE_MULTIPLIER = 1.3e18;
uint256 public constant LOCK_DURATION = 30 days;
IERC20 public immutable GREY;
History.CheckpointHistory internal history;
mapping(address => UserData) public userData;
/**
* @param grey The GREY token contract.
*/
constructor(address grey) {
GREY = IERC20(grey);
}
// ========================================= MUTATIVE FUNCTIONS ========================================
/**
* @notice Allows users to stake and lock GREY for votes.
*
* @param amount The amount of GREY the user wishes to stake.
* @return Index of the deposit in the deposits array.
*/
function lock(uint256 amount) external returns (uint256) {
(UserData storage data, address delegatee) = _getUserData(msg.sender);
Deposit[] storage deposits = data.deposits;
if (deposits.length == 0) {
deposits.push(Deposit(0, 0));
}
uint256 previousAmount = deposits[deposits.length - 1].cumulativeAmount;
deposits.push(
Deposit({cumulativeAmount: previousAmount + amount, unlockTimestamp: block.timestamp + LOCK_DURATION})
);
uint256 votes = _calculateVotes(amount);
_addVotingPower(delegatee, votes);
GREY.transferFrom(msg.sender, address(this), amount);
return deposits.length - 1;
}
/**
* @notice Withdraws staked GREY that is unlocked.
*
* @param end The index of the last deposit to unlock.
* @return amount The amount of GREY unlocked.
*/
function unlock(uint256 end) external returns (uint256 amount) {
(UserData storage data, address delegatee) = _getUserData(msg.sender);
Deposit[] storage deposits = data.deposits;
uint256 front = data.front;
require(front < end, "already unlocked");
Deposit memory lastUnlockedDeposit = deposits[front];
Deposit memory depositToUnlock = deposits[end];
require(block.timestamp > depositToUnlock.unlockTimestamp, "still locked");
amount = depositToUnlock.cumulativeAmount - lastUnlockedDeposit.cumulativeAmount;
data.front = end;
uint256 votes = _calculateVotes(amount);
_subtractVotingPower(delegatee, votes);
GREY.transfer(msg.sender, amount);
}
/**
* @notice Transfer voting power from one address to another.
*
* @param newDelegatee The new address which gets voting power.
*/
function delegate(address newDelegatee) external {
require(newDelegatee != address(0), "cannot delegate to zero address");
(UserData storage data, address delegatee) = _getUserData(msg.sender);
Deposit[] storage deposits = data.deposits;
data.delegatee = newDelegatee;
uint256 length = deposits.length;
if (length == 0) return;
Deposit storage lastUnlockedDeposit = deposits[data.front];
Deposit storage lastDeposit = deposits[length - 1];
uint256 amount = lastDeposit.cumulativeAmount - lastUnlockedDeposit.cumulativeAmount;
uint256 votes = _calculateVotes(amount);
_subtractVotingPower(delegatee, votes);
_addVotingPower(newDelegatee, votes);
}
// ============================================ VIEW FUNCTIONS ===========================================
/**
* @notice Fetch the voting power of a user.
*
* @param user The address we want to load the voting power from.
* @param blockNumber The block number we want the user's voting power at.
*
* @return The number of votes.
*/
function votingPower(address user, uint256 blockNumber) external view returns (uint256) {
return history.getVotingPower(user, blockNumber);
}
// ============================================== HELPERS ===============================================
function _addVotingPower(address delegatee, uint256 votes) internal {
uint256 oldVotes = history.getLatestVotingPower(delegatee);
unchecked {
history.push(delegatee, oldVotes + votes);
}
}
function _subtractVotingPower(address delegatee, uint256 votes) internal {
uint256 oldVotes = history.getLatestVotingPower(delegatee);
unchecked {
history.push(delegatee, oldVotes - votes);
}
}
function _calculateVotes(uint256 amount) internal pure returns (uint256) {
return amount * VOTE_MULTIPLIER / 1e18;
}
function _getUserData(address user) internal view returns (UserData storage data, address delegatee) {
data = userData[user];
delegatee = data.delegatee == address(0) ? user : data.delegatee;
}
}