@ballkidz/defifa 0.0.7 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ADMINISTRATION.md +3 -3
- package/ARCHITECTURE.md +2 -0
- package/AUDIT_INSTRUCTIONS.md +422 -0
- package/CRYPTO_ECON.md +5 -5
- package/README.md +1 -1
- package/RISKS.md +38 -335
- package/SKILLS.md +1 -1
- package/USER_JOURNEYS.md +691 -0
- package/package.json +7 -7
- package/script/Deploy.s.sol +14 -3
- package/script/helpers/DefifaDeploymentLib.sol +13 -15
- package/src/DefifaDeployer.sol +221 -192
- package/src/DefifaGovernor.sol +286 -276
- package/src/DefifaHook.sol +68 -34
- package/src/DefifaProjectOwner.sol +27 -4
- package/src/DefifaTokenUriResolver.sol +136 -134
- package/src/enums/DefifaGamePhase.sol +1 -1
- package/src/enums/DefifaScorecardState.sol +1 -1
- package/src/interfaces/IDefifaDeployer.sol +52 -50
- package/src/interfaces/IDefifaGamePhaseReporter.sol +2 -2
- package/src/interfaces/IDefifaGamePotReporter.sol +1 -1
- package/src/interfaces/IDefifaGovernor.sol +53 -54
- package/src/interfaces/IDefifaHook.sol +104 -103
- package/src/interfaces/IDefifaTokenUriResolver.sol +2 -2
- package/src/libraries/DefifaFontImporter.sol +11 -9
- package/src/libraries/DefifaHookLib.sol +66 -53
- package/src/structs/DefifaAttestations.sol +1 -1
- package/src/structs/DefifaDelegation.sol +1 -1
- package/src/structs/DefifaLaunchProjectData.sol +4 -4
- package/src/structs/DefifaOpsData.sol +1 -1
- package/src/structs/DefifaScorecard.sol +1 -1
- package/src/structs/DefifaTierCashOutWeight.sol +1 -1
- package/src/structs/DefifaTierParams.sol +2 -1
- package/test/DefifaAdversarialQuorum.t.sol +602 -0
- package/test/DefifaAuditLowGuards.t.sol +304 -0
- package/test/DefifaFeeAccounting.t.sol +37 -16
- package/test/DefifaGovernor.t.sol +43 -19
- package/test/DefifaHookRegressions.t.sol +14 -12
- package/test/DefifaMintCostInvariant.t.sol +31 -12
- package/test/DefifaNoContest.t.sol +34 -16
- package/test/DefifaSecurity.t.sol +46 -28
- package/test/DefifaUSDC.t.sol +45 -36
- package/test/Fork.t.sol +43 -43
- package/test/SVG.t.sol +2 -2
- package/test/TestAuditGaps.sol +982 -0
- package/test/TestQALastMile.t.sol +511 -0
- package/test/regression/FulfillmentBlocksRatification.t.sol +36 -30
- package/test/regression/GracePeriodBypass.t.sol +15 -10
package/src/DefifaGovernor.sol
CHANGED
|
@@ -1,89 +1,257 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
pragma solidity 0.8.26;
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import {IJBController} from "@bananapus/core-v6/src/interfaces/IJBController.sol";
|
|
5
|
+
import {JBRulesetMetadata} from "@bananapus/core-v6/src/structs/JBRulesetMetadata.sol";
|
|
6
6
|
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
|
|
8
|
+
import {mulDiv} from "@prb/math/src/Common.sol";
|
|
9
|
+
|
|
10
|
+
import {DefifaHook} from "./DefifaHook.sol";
|
|
11
|
+
import {DefifaGamePhase} from "./enums/DefifaGamePhase.sol";
|
|
12
|
+
import {DefifaScorecardState} from "./enums/DefifaScorecardState.sol";
|
|
9
13
|
import {IDefifaDeployer} from "./interfaces/IDefifaDeployer.sol";
|
|
10
|
-
import {
|
|
14
|
+
import {IDefifaGovernor} from "./interfaces/IDefifaGovernor.sol";
|
|
15
|
+
import {IDefifaHook} from "./interfaces/IDefifaHook.sol";
|
|
11
16
|
import {DefifaAttestations} from "./structs/DefifaAttestations.sol";
|
|
17
|
+
import {DefifaScorecard} from "./structs/DefifaScorecard.sol";
|
|
12
18
|
import {DefifaTierCashOutWeight} from "./structs/DefifaTierCashOutWeight.sol";
|
|
13
|
-
import {DefifaGamePhase} from "./enums/DefifaGamePhase.sol";
|
|
14
|
-
import {DefifaScorecardState} from "./enums/DefifaScorecardState.sol";
|
|
15
|
-
import {DefifaHook} from "./DefifaHook.sol";
|
|
16
19
|
|
|
17
|
-
import {IJBController} from "@bananapus/core-v6/src/interfaces/IJBController.sol";
|
|
18
|
-
import {JBRulesetMetadata} from "@bananapus/core-v6/src/structs/JBRulesetMetadata.sol";
|
|
19
|
-
import {IJB721TiersHookStore} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHookStore.sol";
|
|
20
|
-
import {JB721Tier} from "@bananapus/721-hook-v6/src/structs/JB721Tier.sol";
|
|
21
|
-
|
|
22
|
-
/// @title DefifaGovernor
|
|
23
20
|
/// @notice Manages the ratification of Defifa scorecards.
|
|
24
21
|
contract DefifaGovernor is Ownable, IDefifaGovernor {
|
|
25
22
|
//*********************************************************************//
|
|
26
23
|
// --------------------------- custom errors ------------------------- //
|
|
27
24
|
//*********************************************************************//
|
|
25
|
+
|
|
28
26
|
error DefifaGovernor_AlreadyAttested();
|
|
27
|
+
error DefifaGovernor_AlreadyInitialized();
|
|
29
28
|
error DefifaGovernor_AlreadyRatified();
|
|
30
|
-
error DefifaGovernor_GameNotFound();
|
|
31
|
-
error DefifaGovernor_NotAllowed();
|
|
32
29
|
error DefifaGovernor_DuplicateScorecard();
|
|
30
|
+
error DefifaGovernor_GameNotFound();
|
|
33
31
|
error DefifaGovernor_IncorrectTierOrder();
|
|
32
|
+
error DefifaGovernor_NotAllowed();
|
|
33
|
+
error DefifaGovernor_Uint48Overflow();
|
|
34
34
|
error DefifaGovernor_UnknownProposal();
|
|
35
35
|
error DefifaGovernor_UnownedProposedCashoutValue();
|
|
36
36
|
|
|
37
37
|
//*********************************************************************//
|
|
38
|
-
//
|
|
38
|
+
// ------------------------- public constants ------------------------ //
|
|
39
39
|
//*********************************************************************//
|
|
40
40
|
|
|
41
|
-
/// @notice The
|
|
42
|
-
|
|
43
|
-
/// _scorecardId The ID of the scorecard to retrieve.
|
|
44
|
-
mapping(uint256 => mapping(uint256 => DefifaScorecard)) internal _scorecardOf;
|
|
41
|
+
/// @notice The max attestation power each tier has if every token within the tier attestations.
|
|
42
|
+
uint256 public constant override MAX_ATTESTATION_POWER_TIER = 1_000_000_000;
|
|
45
43
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
//*********************************************************************//
|
|
45
|
+
// --------------- public immutable stored properties ---------------- //
|
|
46
|
+
//*********************************************************************//
|
|
47
|
+
|
|
48
|
+
/// @notice The controller with which new projects should be deployed.
|
|
49
|
+
IJBController public immutable override CONTROLLER;
|
|
50
50
|
|
|
51
51
|
//*********************************************************************//
|
|
52
|
-
// ---------------------
|
|
52
|
+
// --------------------- public stored properties -------------------- //
|
|
53
53
|
//*********************************************************************//
|
|
54
54
|
|
|
55
|
-
/// @notice The
|
|
56
|
-
///
|
|
57
|
-
mapping(uint256 => uint256)
|
|
55
|
+
/// @notice The latest proposal submitted by the default attestation delegate.
|
|
56
|
+
/// @custom:param gameId The ID of the game of the default attestation delegate proposal.
|
|
57
|
+
mapping(uint256 => uint256) public override defaultAttestationDelegateProposalOf;
|
|
58
|
+
|
|
59
|
+
/// @notice The scorecard that has been ratified.
|
|
60
|
+
/// @custom:param gameId The ID of the game of the ratified scorecard.
|
|
61
|
+
mapping(uint256 => uint256) public override ratifiedScorecardIdOf;
|
|
58
62
|
|
|
59
63
|
//*********************************************************************//
|
|
60
|
-
//
|
|
64
|
+
// -------------------- internal stored properties ------------------- //
|
|
61
65
|
//*********************************************************************//
|
|
62
66
|
|
|
63
|
-
/// @notice The
|
|
64
|
-
|
|
67
|
+
/// @notice The scorecard information, packed into a uint256.
|
|
68
|
+
/// @custom:param gameId The ID of the game for which the scorecard info applies.
|
|
69
|
+
mapping(uint256 => uint256) internal _packedScorecardInfoOf;
|
|
70
|
+
|
|
71
|
+
/// @notice The scorecards.
|
|
72
|
+
/// @custom:param gameId The ID of the game for which the scorecard affects.
|
|
73
|
+
/// @custom:param scorecardId The ID of the scorecard to retrieve.
|
|
74
|
+
mapping(uint256 => mapping(uint256 => DefifaScorecard)) internal _scorecardOf;
|
|
75
|
+
|
|
76
|
+
/// @notice The attestations to a scorecard.
|
|
77
|
+
/// @custom:param gameId The ID of the game for which the scorecard affects.
|
|
78
|
+
/// @custom:param scorecardId The ID of the scorecard that has been attested to.
|
|
79
|
+
mapping(uint256 => mapping(uint256 => DefifaAttestations)) internal _scorecardAttestationsOf;
|
|
65
80
|
|
|
66
81
|
//*********************************************************************//
|
|
67
|
-
//
|
|
82
|
+
// -------------------------- constructor ---------------------------- //
|
|
68
83
|
//*********************************************************************//
|
|
69
84
|
|
|
70
|
-
|
|
71
|
-
|
|
85
|
+
constructor(IJBController controller, address owner) Ownable(owner) {
|
|
86
|
+
CONTROLLER = controller;
|
|
87
|
+
}
|
|
72
88
|
|
|
73
89
|
//*********************************************************************//
|
|
74
|
-
//
|
|
90
|
+
// ---------------------- external transactions ---------------------- //
|
|
75
91
|
//*********************************************************************//
|
|
76
92
|
|
|
77
|
-
/// @notice
|
|
78
|
-
///
|
|
79
|
-
|
|
93
|
+
/// @notice Attests to a scorecard.
|
|
94
|
+
/// @param gameId The ID of the game to which the scorecard belongs.
|
|
95
|
+
/// @param scorecardId The scorecard ID.
|
|
96
|
+
/// @return weight The attestation weight that was applied.
|
|
97
|
+
function attestToScorecardFrom(uint256 gameId, uint256 scorecardId) external override returns (uint256 weight) {
|
|
98
|
+
// Get the game's current funding cycle along with its metadata.
|
|
99
|
+
// slither-disable-next-line unused-return
|
|
100
|
+
(, JBRulesetMetadata memory _metadata) = CONTROLLER.currentRulesetOf(gameId);
|
|
80
101
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
102
|
+
// Make sure the game is in its scoring phase.
|
|
103
|
+
if (IDefifaHook(_metadata.dataHook).gamePhaseReporter().currentGamePhaseOf(gameId) != DefifaGamePhase.SCORING) {
|
|
104
|
+
revert DefifaGovernor_NotAllowed();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Keep a reference to the scorecard being attested to.
|
|
108
|
+
DefifaScorecard storage _scorecard = _scorecardOf[gameId][scorecardId];
|
|
109
|
+
|
|
110
|
+
// Keep a reference to the scorecard state.
|
|
111
|
+
DefifaScorecardState _state = stateOf({gameId: gameId, scorecardId: scorecardId});
|
|
112
|
+
|
|
113
|
+
if (_state != DefifaScorecardState.ACTIVE && _state != DefifaScorecardState.SUCCEEDED) {
|
|
114
|
+
revert DefifaGovernor_NotAllowed();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Keep a reference to the attestations for the scorecard.
|
|
118
|
+
DefifaAttestations storage _attestations = _scorecardAttestationsOf[gameId][scorecardId];
|
|
119
|
+
|
|
120
|
+
// Make sure the account isn't attesting to the same scorecard again.
|
|
121
|
+
if (_attestations.hasAttested[msg.sender]) revert DefifaGovernor_AlreadyAttested();
|
|
122
|
+
|
|
123
|
+
// Get a reference to the attestation weight.
|
|
124
|
+
weight = getAttestationWeight({gameId: gameId, account: msg.sender, timestamp: _scorecard.attestationsBegin});
|
|
125
|
+
|
|
126
|
+
// Increase the attestation count.
|
|
127
|
+
_attestations.count += weight;
|
|
128
|
+
|
|
129
|
+
// Store the fact that the account has attested to the scorecard.
|
|
130
|
+
_attestations.hasAttested[msg.sender] = true;
|
|
131
|
+
|
|
132
|
+
emit ScorecardAttested(gameId, scorecardId, weight, msg.sender);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/// @notice Ratifies a scorecard that has been approved.
|
|
136
|
+
/// @param gameId The ID of the game.
|
|
137
|
+
/// @param tierWeights The weights of each tier in the approved scorecard.
|
|
138
|
+
/// @return scorecardId The scorecard ID that was ratified.
|
|
139
|
+
function ratifyScorecardFrom(
|
|
140
|
+
uint256 gameId,
|
|
141
|
+
DefifaTierCashOutWeight[] calldata tierWeights
|
|
142
|
+
)
|
|
143
|
+
external
|
|
144
|
+
override
|
|
145
|
+
returns (uint256 scorecardId)
|
|
146
|
+
{
|
|
147
|
+
// Make sure a scorecard hasn't been ratified yet.
|
|
148
|
+
if (ratifiedScorecardIdOf[gameId] != 0) revert DefifaGovernor_AlreadyRatified();
|
|
149
|
+
|
|
150
|
+
// Get the game's current funding cycle along with its metadata.
|
|
151
|
+
// slither-disable-next-line unused-return
|
|
152
|
+
(, JBRulesetMetadata memory _metadata) = CONTROLLER.currentRulesetOf(gameId);
|
|
153
|
+
|
|
154
|
+
// Build the calldata to the target
|
|
155
|
+
bytes memory _calldata = _buildScorecardCalldataFor(tierWeights);
|
|
156
|
+
|
|
157
|
+
// Attempt to execute the proposal.
|
|
158
|
+
scorecardId = _hashScorecardOf({gameHook: _metadata.dataHook, calldataBytes: _calldata});
|
|
159
|
+
|
|
160
|
+
// Make sure the proposal being ratified has succeeded.
|
|
161
|
+
if (stateOf({gameId: gameId, scorecardId: scorecardId}) != DefifaScorecardState.SUCCEEDED) {
|
|
162
|
+
revert DefifaGovernor_NotAllowed();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Set the ratified scorecard.
|
|
166
|
+
ratifiedScorecardIdOf[gameId] = scorecardId;
|
|
167
|
+
|
|
168
|
+
// Execute the scorecard via low-level call since the governor is the delegate's owner.
|
|
169
|
+
(bool success, bytes memory returndata) = _metadata.dataHook.call(_calldata);
|
|
170
|
+
// slither-disable-next-line unused-return
|
|
171
|
+
Address.verifyCallResult({success: success, returndata: returndata});
|
|
172
|
+
|
|
173
|
+
// Fulfill any commitments for the game. The internal try-catch in fulfillCommitmentsOf
|
|
174
|
+
// handles sendPayoutsOf failures, ensuring the final ruleset is always queued.
|
|
175
|
+
IDefifaDeployer(CONTROLLER.PROJECTS().ownerOf(gameId)).fulfillCommitmentsOf(gameId);
|
|
176
|
+
|
|
177
|
+
emit ScorecardRatified(gameId, scorecardId, msg.sender);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/// @notice Submits a scorecard to be attested to.
|
|
181
|
+
/// @param gameId The ID of the game.
|
|
182
|
+
/// @param tierWeights The weights of each tier in the scorecard.
|
|
183
|
+
/// @return scorecardId The scorecard's ID.
|
|
184
|
+
function submitScorecardFor(
|
|
185
|
+
uint256 gameId,
|
|
186
|
+
DefifaTierCashOutWeight[] calldata tierWeights
|
|
187
|
+
)
|
|
188
|
+
external
|
|
189
|
+
override
|
|
190
|
+
returns (uint256 scorecardId)
|
|
191
|
+
{
|
|
192
|
+
// Make sure a proposal hasn't yet been ratified.
|
|
193
|
+
if (ratifiedScorecardIdOf[gameId] != 0) revert DefifaGovernor_AlreadyRatified();
|
|
194
|
+
|
|
195
|
+
// Make sure the game has been initialized.
|
|
196
|
+
// slither-disable-next-line incorrect-equality
|
|
197
|
+
if (_packedScorecardInfoOf[gameId] == 0) revert DefifaGovernor_GameNotFound();
|
|
198
|
+
|
|
199
|
+
// Make sure no weight is assigned to an unowned tier.
|
|
200
|
+
uint256 _numberOfTierWeights = tierWeights.length;
|
|
201
|
+
|
|
202
|
+
// Get the game's current funding cycle along with its metadata.
|
|
203
|
+
// slither-disable-next-line unused-return
|
|
204
|
+
(, JBRulesetMetadata memory _metadata) = CONTROLLER.currentRulesetOf(gameId);
|
|
205
|
+
|
|
206
|
+
// Make sure the game is in its scoring phase.
|
|
207
|
+
if (IDefifaHook(_metadata.dataHook).gamePhaseReporter().currentGamePhaseOf(gameId) != DefifaGamePhase.SCORING) {
|
|
208
|
+
revert DefifaGovernor_NotAllowed();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// If there's a weight assigned to the tier, make sure there is a token backed by it.
|
|
212
|
+
for (uint256 _i; _i < _numberOfTierWeights; _i++) {
|
|
213
|
+
if (
|
|
214
|
+
tierWeights[_i].cashOutWeight > 0
|
|
215
|
+
&& IDefifaHook(_metadata.dataHook).currentSupplyOfTier(tierWeights[_i].id) == 0
|
|
216
|
+
) {
|
|
217
|
+
revert DefifaGovernor_UnownedProposedCashoutValue();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Hash the scorecard.
|
|
222
|
+
scorecardId =
|
|
223
|
+
_hashScorecardOf({gameHook: _metadata.dataHook, calldataBytes: _buildScorecardCalldataFor(tierWeights)});
|
|
224
|
+
|
|
225
|
+
// Store the scorecard
|
|
226
|
+
DefifaScorecard storage _scorecard = _scorecardOf[gameId][scorecardId];
|
|
227
|
+
if (_scorecard.attestationsBegin != 0) revert DefifaGovernor_DuplicateScorecard();
|
|
228
|
+
|
|
229
|
+
uint256 _attestationStartTime = attestationStartTimeOf(gameId);
|
|
230
|
+
uint256 _timeUntilAttestationsBegin =
|
|
231
|
+
block.timestamp > _attestationStartTime ? 0 : _attestationStartTime - block.timestamp;
|
|
232
|
+
|
|
233
|
+
// Casting to uint48 is safe because block.timestamp fits in uint48 until year 8921556.
|
|
234
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
235
|
+
uint48 _attestationsBegin = uint48(block.timestamp + _timeUntilAttestationsBegin);
|
|
236
|
+
_scorecard.attestationsBegin = _attestationsBegin;
|
|
237
|
+
// Grace period extends from when attestations begin, not from submission time.
|
|
238
|
+
// This prevents the grace period from expiring before attestations even start
|
|
239
|
+
// when a scorecard is submitted early.
|
|
240
|
+
_scorecard.gracePeriodEnds = uint48(_attestationsBegin + attestationGracePeriodOf(gameId));
|
|
241
|
+
|
|
242
|
+
// Keep a reference to the default attestation delegate.
|
|
243
|
+
address _defaultAttestationDelegate = IDefifaHook(_metadata.dataHook).defaultAttestationDelegate();
|
|
244
|
+
|
|
245
|
+
// If the scorecard is being sent from the default attestation delegate, store it.
|
|
246
|
+
if (msg.sender == _defaultAttestationDelegate) {
|
|
247
|
+
defaultAttestationDelegateProposalOf[gameId] = scorecardId;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
emit ScorecardSubmitted(gameId, scorecardId, tierWeights, msg.sender == _defaultAttestationDelegate, msg.sender);
|
|
251
|
+
}
|
|
84
252
|
|
|
85
253
|
//*********************************************************************//
|
|
86
|
-
//
|
|
254
|
+
// ----------------------- external views ---------------------------- //
|
|
87
255
|
//*********************************************************************//
|
|
88
256
|
|
|
89
257
|
/// @notice The number of attestations the given scorecard has.
|
|
@@ -116,7 +284,52 @@ contract DefifaGovernor is Ownable, IDefifaGovernor {
|
|
|
116
284
|
override
|
|
117
285
|
returns (uint256)
|
|
118
286
|
{
|
|
119
|
-
return _hashScorecardOf({
|
|
287
|
+
return _hashScorecardOf({gameHook: gameHook, calldataBytes: _buildScorecardCalldataFor(tierWeights)});
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
//*********************************************************************//
|
|
291
|
+
// ----------------------- public transactions ----------------------- //
|
|
292
|
+
//*********************************************************************//
|
|
293
|
+
|
|
294
|
+
/// @notice Initializes a game.
|
|
295
|
+
/// @param gameId The ID of the game.
|
|
296
|
+
/// @param attestationStartTime The amount of time between a scorecard being submitted and attestations to it being
|
|
297
|
+
/// enabled, measured in seconds.
|
|
298
|
+
/// @param attestationGracePeriod The amount of time that must go by before a scorecard can be ratified.
|
|
299
|
+
function initializeGame(
|
|
300
|
+
uint256 gameId,
|
|
301
|
+
uint256 attestationStartTime,
|
|
302
|
+
uint256 attestationGracePeriod
|
|
303
|
+
)
|
|
304
|
+
public
|
|
305
|
+
virtual
|
|
306
|
+
override
|
|
307
|
+
onlyOwner
|
|
308
|
+
{
|
|
309
|
+
// Make sure the game hasn't already been initialized.
|
|
310
|
+
if (_packedScorecardInfoOf[gameId] != 0) revert DefifaGovernor_AlreadyInitialized();
|
|
311
|
+
|
|
312
|
+
// Set a default attestation start time if needed.
|
|
313
|
+
if (attestationStartTime == 0) attestationStartTime = block.timestamp;
|
|
314
|
+
|
|
315
|
+
// Enforce a minimum grace period of 1 day to prevent instant ratification.
|
|
316
|
+
if (attestationGracePeriod < 1 days) attestationGracePeriod = 1 days;
|
|
317
|
+
|
|
318
|
+
// Ensure values fit within their allocated 48-bit widths before packing.
|
|
319
|
+
if (attestationStartTime > type(uint48).max) revert DefifaGovernor_Uint48Overflow();
|
|
320
|
+
if (attestationGracePeriod > type(uint48).max) revert DefifaGovernor_Uint48Overflow();
|
|
321
|
+
|
|
322
|
+
// Pack the values.
|
|
323
|
+
uint256 _packed;
|
|
324
|
+
// attestation start time in bits 0-47 (48 bits).
|
|
325
|
+
_packed |= attestationStartTime;
|
|
326
|
+
// attestation grace period in bits 48-95 (48 bits).
|
|
327
|
+
_packed |= attestationGracePeriod << 48;
|
|
328
|
+
|
|
329
|
+
// Store the packed value.
|
|
330
|
+
_packedScorecardInfoOf[gameId] = _packed;
|
|
331
|
+
|
|
332
|
+
emit GameInitialized(gameId, attestationStartTime, attestationGracePeriod, msg.sender);
|
|
120
333
|
}
|
|
121
334
|
|
|
122
335
|
//*********************************************************************//
|
|
@@ -149,14 +362,14 @@ contract DefifaGovernor is Ownable, IDefifaGovernor {
|
|
|
149
362
|
/// MAX_ATTESTATION_POWER_TIER; a holder of 1-of-100 gets MAX_ATTESTATION_POWER_TIER / 100.
|
|
150
363
|
/// This ensures each game outcome (tier) has equal governance weight — the scorecard reflects
|
|
151
364
|
/// consensus across outcomes, not dominance by whichever outcome sold the most tokens.
|
|
152
|
-
/// @param
|
|
153
|
-
/// @param
|
|
154
|
-
/// @param
|
|
365
|
+
/// @param gameId The ID of the game for which attestations are being counted.
|
|
366
|
+
/// @param account The account to get attestations for.
|
|
367
|
+
/// @param timestamp The timestamp to measure attestations from.
|
|
155
368
|
/// @return attestationPower The amount of attestation power of an account.
|
|
156
369
|
function getAttestationWeight(
|
|
157
|
-
uint256
|
|
158
|
-
address
|
|
159
|
-
uint48
|
|
370
|
+
uint256 gameId,
|
|
371
|
+
address account,
|
|
372
|
+
uint48 timestamp
|
|
160
373
|
)
|
|
161
374
|
public
|
|
162
375
|
view
|
|
@@ -165,18 +378,19 @@ contract DefifaGovernor is Ownable, IDefifaGovernor {
|
|
|
165
378
|
{
|
|
166
379
|
// Get the game's current funding cycle along with its metadata.
|
|
167
380
|
// slither-disable-next-line unused-return
|
|
168
|
-
(, JBRulesetMetadata memory _metadata) =
|
|
381
|
+
(, JBRulesetMetadata memory _metadata) = CONTROLLER.currentRulesetOf(gameId);
|
|
169
382
|
|
|
170
383
|
// Get a reference to the number of tiers.
|
|
171
384
|
uint256 _numberOfTiers = IDefifaHook(_metadata.dataHook).store().maxTierIdOf(_metadata.dataHook);
|
|
172
385
|
|
|
386
|
+
// slither-disable-next-line calls-inside-a-loop
|
|
173
387
|
for (uint256 _i; _i < _numberOfTiers; _i++) {
|
|
174
388
|
// Tiers are 1-indexed.
|
|
175
389
|
uint256 _tierId = _i + 1;
|
|
176
390
|
|
|
177
|
-
// Get this account's attestation units within the tier (snapshot at
|
|
391
|
+
// Get this account's attestation units within the tier (snapshot at timestamp).
|
|
178
392
|
uint256 _tierAttestationUnitsForAccount = IDefifaHook(_metadata.dataHook)
|
|
179
|
-
.getPastTierAttestationUnitsOf({account:
|
|
393
|
+
.getPastTierAttestationUnitsOf({account: account, tier: _tierId, timestamp: timestamp});
|
|
180
394
|
|
|
181
395
|
// Scale the account's share of the tier to MAX_ATTESTATION_POWER_TIER.
|
|
182
396
|
// e.g. holding 3 of 10 tokens → 3/10 * MAX_ATTESTATION_POWER_TIER attestation power from this tier.
|
|
@@ -186,7 +400,7 @@ contract DefifaGovernor is Ownable, IDefifaGovernor {
|
|
|
186
400
|
x: MAX_ATTESTATION_POWER_TIER,
|
|
187
401
|
y: _tierAttestationUnitsForAccount,
|
|
188
402
|
denominator: IDefifaHook(_metadata.dataHook)
|
|
189
|
-
.getPastTierTotalAttestationUnitsOf({tier: _tierId, timestamp:
|
|
403
|
+
.getPastTierTotalAttestationUnitsOf({tier: _tierId, timestamp: timestamp})
|
|
190
404
|
});
|
|
191
405
|
}
|
|
192
406
|
}
|
|
@@ -199,11 +413,16 @@ contract DefifaGovernor is Ownable, IDefifaGovernor {
|
|
|
199
413
|
/// regardless of supply, each tier's community has equal influence — a tier with 1 token and a tier
|
|
200
414
|
/// with 100 tokens both cap at MAX_ATTESTATION_POWER_TIER when fully attested. This prevents
|
|
201
415
|
/// high-supply tiers from dominating governance, keeping the game fair across all outcomes.
|
|
416
|
+
/// @dev Note: quorum is computed from the live supply (currentSupplyOfTier) rather than a snapshot. This means
|
|
417
|
+
/// the quorum threshold can shift between when a scorecard is submitted and when it is evaluated, e.g. if
|
|
418
|
+
/// tokens are burned after attestation. In practice this is acceptable because attestation weights are
|
|
419
|
+
/// snapshotted and quorum only increases (new mints) or decreases (burns) — the latter makes ratification
|
|
420
|
+
/// easier, not harder.
|
|
202
421
|
/// @return The quorum number of attestations.
|
|
203
422
|
function quorum(uint256 gameId) public view override returns (uint256) {
|
|
204
423
|
// Get the game's current funding cycle along with its metadata.
|
|
205
424
|
// slither-disable-next-line unused-return
|
|
206
|
-
(, JBRulesetMetadata memory _metadata) =
|
|
425
|
+
(, JBRulesetMetadata memory _metadata) = CONTROLLER.currentRulesetOf(gameId);
|
|
207
426
|
|
|
208
427
|
// Get a reference to the number of tiers.
|
|
209
428
|
uint256 _numberOfTiers = IDefifaHook(_metadata.dataHook).store().maxTierIdOf(_metadata.dataHook);
|
|
@@ -211,6 +430,7 @@ contract DefifaGovernor is Ownable, IDefifaGovernor {
|
|
|
211
430
|
// Keep a reference to the total eligible tier weight.
|
|
212
431
|
uint256 _eligibleTierWeights;
|
|
213
432
|
|
|
433
|
+
// slither-disable-next-line calls-inside-a-loop
|
|
214
434
|
for (uint256 _i; _i < _numberOfTiers; _i++) {
|
|
215
435
|
// Each minted tier contributes MAX_ATTESTATION_POWER_TIER to the quorum denominator.
|
|
216
436
|
if (IDefifaHook(_metadata.dataHook).currentSupplyOfTier(_i + 1) != 0) {
|
|
@@ -262,244 +482,34 @@ contract DefifaGovernor is Ownable, IDefifaGovernor {
|
|
|
262
482
|
}
|
|
263
483
|
|
|
264
484
|
// If quorum has been reached, the state is SUCCEEDED, otherwise it is ACTIVE.
|
|
485
|
+
// Note: scorecards that fail to reach quorum remain ACTIVE indefinitely — there is no DEFEATED
|
|
486
|
+
// state transition for unratified scorecards. This is by design: new scorecards can always be
|
|
487
|
+
// submitted and the game's no-contest timeout (scorecardTimeout) provides the ultimate backstop.
|
|
265
488
|
return quorum(gameId) <= _scorecardAttestationsOf[gameId][scorecardId].count
|
|
266
489
|
? DefifaScorecardState.SUCCEEDED
|
|
267
490
|
: DefifaScorecardState.ACTIVE;
|
|
268
491
|
}
|
|
269
492
|
|
|
270
493
|
//*********************************************************************//
|
|
271
|
-
//
|
|
272
|
-
//*********************************************************************//
|
|
273
|
-
|
|
274
|
-
constructor(IJBController _controller, address _owner) Ownable(_owner) {
|
|
275
|
-
controller = _controller;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
//*********************************************************************//
|
|
279
|
-
// ----------------------- public transactions ----------------------- //
|
|
280
|
-
//*********************************************************************//
|
|
281
|
-
|
|
282
|
-
/// @notice Initializes a game.
|
|
283
|
-
/// @param _attestationStartTime The amount of time between a scorecard being submitted and attestations to it being
|
|
284
|
-
/// enabled, measured in seconds. @param _attestationGracePeriod The amount of time that must go by before a
|
|
285
|
-
/// scorecard can be ratified.
|
|
286
|
-
function initializeGame(
|
|
287
|
-
uint256 _gameId,
|
|
288
|
-
uint256 _attestationStartTime,
|
|
289
|
-
uint256 _attestationGracePeriod
|
|
290
|
-
)
|
|
291
|
-
public
|
|
292
|
-
virtual
|
|
293
|
-
override
|
|
294
|
-
onlyOwner
|
|
295
|
-
{
|
|
296
|
-
// Set a default attestation start time if needed.
|
|
297
|
-
if (_attestationStartTime == 0) _attestationStartTime = block.timestamp;
|
|
298
|
-
|
|
299
|
-
// Enforce a minimum grace period of 1 day to prevent instant ratification.
|
|
300
|
-
if (_attestationGracePeriod < 1 days) _attestationGracePeriod = 1 days;
|
|
301
|
-
|
|
302
|
-
// Pack the values.
|
|
303
|
-
uint256 _packed;
|
|
304
|
-
// attestation start time in bits 0-47 (48 bits).
|
|
305
|
-
_packed |= _attestationStartTime;
|
|
306
|
-
// attestation grace period in bits 48-95 (48 bits).
|
|
307
|
-
_packed |= _attestationGracePeriod << 48;
|
|
308
|
-
|
|
309
|
-
// Store the packed value.
|
|
310
|
-
_packedScorecardInfoOf[_gameId] = _packed;
|
|
311
|
-
|
|
312
|
-
emit GameInitialized(_gameId, _attestationStartTime, _attestationGracePeriod, msg.sender);
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
//*********************************************************************//
|
|
316
|
-
// ---------------------- external transactions ---------------------- //
|
|
317
|
-
//*********************************************************************//
|
|
318
|
-
|
|
319
|
-
/// @notice Attests to a scorecard.
|
|
320
|
-
/// @param gameId The ID of the game to which the scorecard belongs.
|
|
321
|
-
/// @param scorecardId The scorecard ID.
|
|
322
|
-
/// @return weight The attestation weight that was applied.
|
|
323
|
-
function attestToScorecardFrom(uint256 gameId, uint256 scorecardId) external override returns (uint256 weight) {
|
|
324
|
-
// Get the game's current funding cycle along with its metadata.
|
|
325
|
-
// slither-disable-next-line unused-return
|
|
326
|
-
(, JBRulesetMetadata memory _metadata) = controller.currentRulesetOf(gameId);
|
|
327
|
-
|
|
328
|
-
// Make sure the game is in its scoring phase.
|
|
329
|
-
if (IDefifaHook(_metadata.dataHook).gamePhaseReporter().currentGamePhaseOf(gameId) != DefifaGamePhase.SCORING) {
|
|
330
|
-
revert DefifaGovernor_NotAllowed();
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
// Keep a reference to the scorecard being attested to.
|
|
334
|
-
DefifaScorecard storage _scorecard = _scorecardOf[gameId][scorecardId];
|
|
335
|
-
|
|
336
|
-
// Keep a reference to the scorecard state.
|
|
337
|
-
DefifaScorecardState _state = stateOf({gameId: gameId, scorecardId: scorecardId});
|
|
338
|
-
|
|
339
|
-
if (_state != DefifaScorecardState.ACTIVE && _state != DefifaScorecardState.SUCCEEDED) {
|
|
340
|
-
revert DefifaGovernor_NotAllowed();
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// Keep a reference to the attestations for the scorecard.
|
|
344
|
-
DefifaAttestations storage _attestations = _scorecardAttestationsOf[gameId][scorecardId];
|
|
345
|
-
|
|
346
|
-
// Make sure the account isn't attesting to the same scorecard again.
|
|
347
|
-
if (_attestations.hasAttested[msg.sender]) revert DefifaGovernor_AlreadyAttested();
|
|
348
|
-
|
|
349
|
-
// Get a reference to the attestation weight.
|
|
350
|
-
weight = getAttestationWeight({_gameId: gameId, _account: msg.sender, _timestamp: _scorecard.attestationsBegin});
|
|
351
|
-
|
|
352
|
-
// Increase the attestation count.
|
|
353
|
-
_attestations.count += weight;
|
|
354
|
-
|
|
355
|
-
// Store the fact that the account has attested to the scorecard.
|
|
356
|
-
_attestations.hasAttested[msg.sender] = true;
|
|
357
|
-
|
|
358
|
-
emit ScorecardAttested(gameId, scorecardId, weight, msg.sender);
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
/// @notice Ratifies a scorecard that has been approved.
|
|
362
|
-
/// @param gameId The ID of the game.
|
|
363
|
-
/// @param tierWeights The weights of each tier in the approved scorecard.
|
|
364
|
-
/// @return scorecardId The scorecard ID that was ratified.
|
|
365
|
-
function ratifyScorecardFrom(
|
|
366
|
-
uint256 gameId,
|
|
367
|
-
DefifaTierCashOutWeight[] calldata tierWeights
|
|
368
|
-
)
|
|
369
|
-
external
|
|
370
|
-
override
|
|
371
|
-
returns (uint256 scorecardId)
|
|
372
|
-
{
|
|
373
|
-
// Make sure a scorecard hasn't been ratified yet.
|
|
374
|
-
if (ratifiedScorecardIdOf[gameId] != 0) revert DefifaGovernor_AlreadyRatified();
|
|
375
|
-
|
|
376
|
-
// Get the game's current funding cycle along with its metadata.
|
|
377
|
-
// slither-disable-next-line unused-return
|
|
378
|
-
(, JBRulesetMetadata memory _metadata) = controller.currentRulesetOf(gameId);
|
|
379
|
-
|
|
380
|
-
// Build the calldata to the target
|
|
381
|
-
bytes memory _calldata = _buildScorecardCalldataFor(tierWeights);
|
|
382
|
-
|
|
383
|
-
// Attempt to execute the proposal.
|
|
384
|
-
scorecardId = _hashScorecardOf({_gameHook: _metadata.dataHook, _calldata: _calldata});
|
|
385
|
-
|
|
386
|
-
// Make sure the proposal being ratified has succeeded.
|
|
387
|
-
if (stateOf({gameId: gameId, scorecardId: scorecardId}) != DefifaScorecardState.SUCCEEDED) {
|
|
388
|
-
revert DefifaGovernor_NotAllowed();
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
// Set the ratified scorecard.
|
|
392
|
-
ratifiedScorecardIdOf[gameId] = scorecardId;
|
|
393
|
-
|
|
394
|
-
// Execute the scorecard via low-level call since the governor is the delegate's owner.
|
|
395
|
-
(bool success, bytes memory returndata) = _metadata.dataHook.call(_calldata);
|
|
396
|
-
// slither-disable-next-line unused-return
|
|
397
|
-
Address.verifyCallResult({success: success, returndata: returndata});
|
|
398
|
-
|
|
399
|
-
// Fulfill any commitments for the game. Wrapped in try-catch so that a fulfillment
|
|
400
|
-
// failure (e.g. from sendPayoutsOf reverting) does not permanently block ratification.
|
|
401
|
-
// Fulfillment can be retried separately by calling fulfillCommitmentsOf directly.
|
|
402
|
-
try IDefifaDeployer(controller.PROJECTS().ownerOf(gameId)).fulfillCommitmentsOf(gameId) {}
|
|
403
|
-
catch (bytes memory reason) {
|
|
404
|
-
emit FulfillmentFailed(gameId, reason);
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
emit ScorecardRatified(gameId, scorecardId, msg.sender);
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
/// @notice Submits a scorecard to be attested to.
|
|
411
|
-
/// @param _tierWeights The weights of each tier in the scorecard.
|
|
412
|
-
/// @return scorecardId The scorecard's ID.
|
|
413
|
-
function submitScorecardFor(
|
|
414
|
-
uint256 _gameId,
|
|
415
|
-
DefifaTierCashOutWeight[] calldata _tierWeights
|
|
416
|
-
)
|
|
417
|
-
external
|
|
418
|
-
override
|
|
419
|
-
returns (uint256 scorecardId)
|
|
420
|
-
{
|
|
421
|
-
// Make sure a proposal hasn't yet been ratified.
|
|
422
|
-
if (ratifiedScorecardIdOf[_gameId] != 0) revert DefifaGovernor_AlreadyRatified();
|
|
423
|
-
|
|
424
|
-
// Make sure the game has been initialized.
|
|
425
|
-
// slither-disable-next-line incorrect-equality
|
|
426
|
-
if (_packedScorecardInfoOf[_gameId] == 0) revert DefifaGovernor_GameNotFound();
|
|
427
|
-
|
|
428
|
-
// Make sure no weight is assigned to an unowned tier.
|
|
429
|
-
uint256 _numberOfTierWeights = _tierWeights.length;
|
|
430
|
-
|
|
431
|
-
// Get the game's current funding cycle along with its metadata.
|
|
432
|
-
// slither-disable-next-line unused-return
|
|
433
|
-
(, JBRulesetMetadata memory _metadata) = controller.currentRulesetOf(_gameId);
|
|
434
|
-
|
|
435
|
-
// Make sure the game is in its scoring phase.
|
|
436
|
-
if (IDefifaHook(_metadata.dataHook).gamePhaseReporter().currentGamePhaseOf(_gameId) != DefifaGamePhase.SCORING)
|
|
437
|
-
{
|
|
438
|
-
revert DefifaGovernor_NotAllowed();
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
// If there's a weight assigned to the tier, make sure there is a token backed by it.
|
|
442
|
-
for (uint256 _i; _i < _numberOfTierWeights; _i++) {
|
|
443
|
-
if (
|
|
444
|
-
_tierWeights[_i].cashOutWeight > 0
|
|
445
|
-
&& IDefifaHook(_metadata.dataHook).currentSupplyOfTier(_tierWeights[_i].id) == 0
|
|
446
|
-
) {
|
|
447
|
-
revert DefifaGovernor_UnownedProposedCashoutValue();
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
// Hash the scorecard.
|
|
452
|
-
scorecardId =
|
|
453
|
-
_hashScorecardOf({_gameHook: _metadata.dataHook, _calldata: _buildScorecardCalldataFor(_tierWeights)});
|
|
454
|
-
|
|
455
|
-
// Store the scorecard
|
|
456
|
-
DefifaScorecard storage _scorecard = _scorecardOf[_gameId][scorecardId];
|
|
457
|
-
if (_scorecard.attestationsBegin != 0) revert DefifaGovernor_DuplicateScorecard();
|
|
458
|
-
|
|
459
|
-
uint256 _attestationStartTime = attestationStartTimeOf(_gameId);
|
|
460
|
-
uint256 _timeUntilAttestationsBegin =
|
|
461
|
-
block.timestamp > _attestationStartTime ? 0 : _attestationStartTime - block.timestamp;
|
|
462
|
-
|
|
463
|
-
uint48 _attestationsBegin = uint48(block.timestamp + _timeUntilAttestationsBegin);
|
|
464
|
-
_scorecard.attestationsBegin = _attestationsBegin;
|
|
465
|
-
// Grace period extends from when attestations begin, not from submission time.
|
|
466
|
-
// This prevents the grace period from expiring before attestations even start
|
|
467
|
-
// when a scorecard is submitted early.
|
|
468
|
-
_scorecard.gracePeriodEnds = uint48(_attestationsBegin + attestationGracePeriodOf(_gameId));
|
|
469
|
-
|
|
470
|
-
// Keep a reference to the default attestation delegate.
|
|
471
|
-
address _defaultAttestationDelegate = IDefifaHook(_metadata.dataHook).defaultAttestationDelegate();
|
|
472
|
-
|
|
473
|
-
// If the scorecard is being sent from the default attestation delegate, store it.
|
|
474
|
-
if (msg.sender == _defaultAttestationDelegate) {
|
|
475
|
-
defaultAttestationDelegateProposalOf[_gameId] = scorecardId;
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
emit ScorecardSubmitted(
|
|
479
|
-
_gameId, scorecardId, _tierWeights, msg.sender == _defaultAttestationDelegate, msg.sender
|
|
480
|
-
);
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
//*********************************************************************//
|
|
484
|
-
// ------------------------ internal functions ----------------------- //
|
|
494
|
+
// ----------------------- internal helpers -------------------------- //
|
|
485
495
|
//*********************************************************************//
|
|
486
496
|
|
|
487
497
|
/// @notice Build the normalized calldata.
|
|
488
|
-
/// @param
|
|
498
|
+
/// @param tierWeights The weights of each tier in the scorecard data.
|
|
489
499
|
/// @return The calldata to send alongside the transactions.
|
|
490
|
-
function _buildScorecardCalldataFor(DefifaTierCashOutWeight[] calldata
|
|
500
|
+
function _buildScorecardCalldataFor(DefifaTierCashOutWeight[] calldata tierWeights)
|
|
491
501
|
internal
|
|
492
502
|
pure
|
|
493
503
|
returns (bytes memory)
|
|
494
504
|
{
|
|
495
505
|
// Build the calldata from the tier weights.
|
|
496
|
-
return abi.encodeWithSelector(DefifaHook.setTierCashOutWeightsTo.selector, (
|
|
506
|
+
return abi.encodeWithSelector(DefifaHook.setTierCashOutWeightsTo.selector, (tierWeights));
|
|
497
507
|
}
|
|
498
508
|
|
|
499
509
|
/// @notice A value representing the contents of a scorecard.
|
|
500
|
-
/// @param
|
|
501
|
-
/// @param
|
|
502
|
-
function _hashScorecardOf(address
|
|
503
|
-
return uint256(keccak256(abi.encode(
|
|
510
|
+
/// @param gameHook The address where the game is being played.
|
|
511
|
+
/// @param calldataBytes The calldata that will be sent if the scorecard is ratified.
|
|
512
|
+
function _hashScorecardOf(address gameHook, bytes memory calldataBytes) internal pure virtual returns (uint256) {
|
|
513
|
+
return uint256(keccak256(abi.encode(gameHook, calldataBytes)));
|
|
504
514
|
}
|
|
505
515
|
}
|