-
Notifications
You must be signed in to change notification settings - Fork 13
/
ExtraordinaryFunding.sol
309 lines (251 loc) · 12.1 KB
/
ExtraordinaryFunding.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
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
import { IERC20 } from "@oz/token/ERC20/IERC20.sol";
import { SafeCast } from "@oz/utils/math/SafeCast.sol";
import { Funding } from "./Funding.sol";
import { IExtraordinaryFunding } from "../interfaces/IExtraordinaryFunding.sol";
import { Maths } from "../libraries/Maths.sol";
abstract contract ExtraordinaryFunding is Funding, IExtraordinaryFunding {
/*****************/
/*** Constants ***/
/*****************/
/**
* @notice The maximum length of a proposal's voting period, in blocks.
*/
uint256 internal constant MAX_EFM_PROPOSAL_LENGTH = 216_000; // number of blocks in one month
/**
* @notice Keccak hash of a prefix string for extraordinary funding mechanism
*/
bytes32 internal constant DESCRIPTION_PREFIX_HASH_EXTRAORDINARY = keccak256(bytes("Extraordinary Funding: "));
/***********************/
/*** State Variables ***/
/***********************/
/**
* @notice Mapping of extant extraordinary funding proposals.
* @dev proposalId => ExtraordinaryFundingProposal.
*/
mapping (uint256 => ExtraordinaryFundingProposal) internal _extraordinaryFundingProposals;
/**
* @notice The list of extraordinary funding proposalIds that have been executed.
*/
uint256[] internal _fundedExtraordinaryProposals;
/**
* @notice Mapping checking if a voter has voted on a given proposal.
* @dev proposalId => address => bool.
*/
mapping(uint256 => mapping(address => bool)) public hasVotedExtraordinary;
/**************************/
/*** Proposal Functions ***/
/**************************/
/// @inheritdoc IExtraordinaryFunding
function executeExtraordinary(
address[] memory targets_,
uint256[] memory values_,
bytes[] memory calldatas_,
bytes32 descriptionHash_
) external nonReentrant override returns (uint256 proposalId_) {
proposalId_ = _hashProposal(targets_, values_, calldatas_, keccak256(abi.encode(DESCRIPTION_PREFIX_HASH_EXTRAORDINARY, descriptionHash_)));
ExtraordinaryFundingProposal storage proposal = _extraordinaryFundingProposals[proposalId_];
// since we are casting from uint128 to uint256, we can safely assume that the value will not overflow
uint256 tokensRequested = uint256(proposal.tokensRequested);
// check proposal is succesful and hasn't already been executed
if (proposal.executed || !_extraordinaryProposalSucceeded(proposalId_, tokensRequested)) revert ExecuteExtraordinaryProposalInvalid();
_fundedExtraordinaryProposals.push(proposalId_);
// update proposal state
proposal.executed = true;
// update treasury
treasury -= tokensRequested;
// execute proposal's calldata
_execute(proposalId_, targets_, values_, calldatas_);
}
/// @inheritdoc IExtraordinaryFunding
function proposeExtraordinary(
uint256 endBlock_,
address[] memory targets_,
uint256[] memory values_,
bytes[] memory calldatas_,
string memory description_) external override returns (uint256 proposalId_) {
proposalId_ = _hashProposal(targets_, values_, calldatas_, keccak256(abi.encode(DESCRIPTION_PREFIX_HASH_EXTRAORDINARY, keccak256(bytes(description_)))));
ExtraordinaryFundingProposal storage newProposal = _extraordinaryFundingProposals[proposalId_];
// check if proposal already exists (proposal id not 0)
if (newProposal.proposalId != 0) revert ProposalAlreadyExists();
// check proposal length is within limits of 1 month maximum
if (block.number + MAX_EFM_PROPOSAL_LENGTH < endBlock_) revert InvalidProposal();
uint128 totalTokensRequested = _validateCallDatas(targets_, values_, calldatas_);
// check tokens requested are available for claiming from the treasury
if (uint256(totalTokensRequested) > _getSliceOfTreasury(Maths.WAD - _getMinimumThresholdPercentage())) revert InvalidProposal();
// store newly created proposal
newProposal.proposalId = proposalId_;
newProposal.startBlock = SafeCast.toUint128(block.number);
newProposal.endBlock = SafeCast.toUint128(endBlock_);
newProposal.tokensRequested = totalTokensRequested;
emit ProposalCreated(
proposalId_,
msg.sender,
targets_,
values_,
new string[](targets_.length),
calldatas_,
block.number,
endBlock_,
description_
);
}
/************************/
/*** Voting Functions ***/
/************************/
/// @inheritdoc IExtraordinaryFunding
function voteExtraordinary(
uint256 proposalId_
) external override returns (uint256 votesCast_) {
// revert if msg.sender already voted on proposal
if (hasVotedExtraordinary[proposalId_][msg.sender]) revert AlreadyVoted();
ExtraordinaryFundingProposal storage proposal = _extraordinaryFundingProposals[proposalId_];
// revert if proposal is inactive
if (proposal.startBlock > block.number || proposal.endBlock < block.number || proposal.executed) {
revert ExtraordinaryFundingProposalInactive();
}
// check voting power at snapshot block and update proposal votes
votesCast_ = _getVotesExtraordinary(msg.sender, proposalId_);
proposal.votesReceived += SafeCast.toUint120(votesCast_);
// record that voter has voted on this extraordinary funding proposal
hasVotedExtraordinary[proposalId_][msg.sender] = true;
emit VoteCast(
msg.sender,
proposalId_,
1,
votesCast_,
""
);
}
/**
* @notice Check if a proposal for extraordinary funding has succeeded.
* @param proposalId_ The ID of the proposal being checked.
* @return Boolean indicating whether the proposal has succeeded.
*/
function _extraordinaryProposalSucceeded(
uint256 proposalId_,
uint256 tokensRequested_
) internal view returns (bool) {
uint256 votesReceived = uint256(_extraordinaryFundingProposals[proposalId_].votesReceived);
uint256 minThresholdPercentage = _getMinimumThresholdPercentage();
return
// succeeded if proposal's votes received doesn't exceed the minimum threshold required
(votesReceived >= tokensRequested_ + _getSliceOfNonTreasury(minThresholdPercentage))
&&
// succeeded if tokens requested are available for claiming from the treasury
(tokensRequested_ <= _getSliceOfTreasury(Maths.WAD - minThresholdPercentage))
;
}
/********************************/
/*** Internal View Functions ****/
/********************************/
/**
* @notice Get the current ProposalState of a given proposal.
* @dev Used by GrantFund.state() for analytics compatability purposes.
* @param proposalId_ The ID of the proposal being checked.
* @return The proposals status in the ProposalState enum.
*/
function _getExtraordinaryProposalState(uint256 proposalId_) internal view returns (ProposalState) {
ExtraordinaryFundingProposal memory proposal = _extraordinaryFundingProposals[proposalId_];
bool voteSucceeded = _extraordinaryProposalSucceeded(proposalId_, uint256(proposal.tokensRequested));
if (proposal.executed) return ProposalState.Executed;
else if (proposal.endBlock >= block.number && !voteSucceeded) return ProposalState.Active;
else if (voteSucceeded) return ProposalState.Succeeded;
else return ProposalState.Defeated;
}
/**
* @notice Get the minimum percentage of ajna tokens required for a proposal to pass.
* @dev The minimum threshold increases according to the number of funded EFM proposals.
* @return The minimum threshold percentage, as a WAD.
*/
function _getMinimumThresholdPercentage() internal view returns (uint256) {
// default minimum threshold is 50
if (_fundedExtraordinaryProposals.length == 0) {
return 0.5 * 1e18;
}
// minimum threshold increases according to the number of funded EFM proposals
else {
return 0.5 * 1e18 + (_fundedExtraordinaryProposals.length * (0.05 * 1e18));
}
}
/**
* @notice Get the number of ajna tokens equivalent to a given percentage.
* @param percentage_ The percentage of the Non treasury to retrieve, in WAD.
* @return The number of tokens, in WAD.
*/
function _getSliceOfNonTreasury(
uint256 percentage_
) internal view returns (uint256) {
uint256 totalAjnaSupply = IERC20(ajnaTokenAddress).totalSupply();
return Maths.wmul(totalAjnaSupply - treasury, percentage_);
}
/**
* @notice Get the number of ajna tokens equivalent to a given percentage.
* @param percentage_ The percentage of the treasury to retrieve, in WAD.
* @return The number of tokens, in WAD.
*/
function _getSliceOfTreasury(
uint256 percentage_
) internal view returns (uint256) {
return Maths.wmul(treasury, percentage_);
}
/**
* @notice Get the voting power available to a voter for a given proposal.
* @param account_ The address of the voter to check.
* @param proposalId_ The ID of the proposal being voted on.
* @return votes_ The number of votes available to be cast in voteExtraordinary.
*/
function _getVotesExtraordinary(address account_, uint256 proposalId_) internal view returns (uint256 votes_) {
if (proposalId_ == 0) revert ExtraordinaryFundingProposalInactive();
uint256 startBlock = _extraordinaryFundingProposals[proposalId_].startBlock;
votes_ = _getVotesAtSnapshotBlocks(
account_,
startBlock - VOTING_POWER_SNAPSHOT_DELAY,
startBlock
);
}
/********************************/
/*** External View Functions ****/
/********************************/
/// @inheritdoc IExtraordinaryFunding
function getMinimumThresholdPercentage() external view returns (uint256) {
return _getMinimumThresholdPercentage();
}
/// @inheritdoc IExtraordinaryFunding
function getSliceOfNonTreasury(
uint256 percentage_
) external view override returns (uint256) {
return _getSliceOfNonTreasury(percentage_);
}
/// @inheritdoc IExtraordinaryFunding
function getSliceOfTreasury(
uint256 percentage_
) external view override returns (uint256) {
return _getSliceOfTreasury(percentage_);
}
/// @inheritdoc IExtraordinaryFunding
function getExtraordinaryProposalInfo(
uint256 proposalId_
) external view override returns (uint256, uint128, uint128, uint128, uint120, bool) {
return (
_extraordinaryFundingProposals[proposalId_].proposalId,
_extraordinaryFundingProposals[proposalId_].startBlock,
_extraordinaryFundingProposals[proposalId_].endBlock,
_extraordinaryFundingProposals[proposalId_].tokensRequested,
_extraordinaryFundingProposals[proposalId_].votesReceived,
_extraordinaryFundingProposals[proposalId_].executed
);
}
/// @inheritdoc IExtraordinaryFunding
function getExtraordinaryProposalSucceeded(uint256 proposalId_) external view override returns (bool) {
// since we are casting from uint128 to uint256, we can safely assume that the value will not overflow
uint256 tokensRequested = uint256(_extraordinaryFundingProposals[proposalId_].tokensRequested);
return _extraordinaryProposalSucceeded(proposalId_, tokensRequested);
}
/// @inheritdoc IExtraordinaryFunding
function getVotesExtraordinary(address account_, uint256 proposalId_) external view override returns (uint256) {
if (hasVotedExtraordinary[proposalId_][account_]) return 0;
return _getVotesExtraordinary(account_, proposalId_);
}
}