@ballkidz/defifa 0.0.11 → 0.0.13
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/AUDIT_INSTRUCTIONS.md +2 -2
- package/CHANGE_LOG.md +60 -5
- package/CRYPTO_ECON.md +505 -270
- package/CRYPTO_ECON.pdf +0 -0
- package/CRYPTO_ECON.tex +438 -241
- package/RISKS.md +9 -1
- package/SKILLS.md +3 -2
- package/STYLE_GUIDE.md +2 -2
- package/foundry.toml +1 -1
- package/package.json +7 -7
- package/script/Deploy.s.sol +1 -1
- package/script/helpers/DefifaDeploymentLib.sol +1 -1
- package/src/DefifaDeployer.sol +129 -131
- package/src/DefifaGovernor.sol +279 -84
- package/src/DefifaHook.sol +159 -172
- package/src/DefifaProjectOwner.sol +1 -1
- package/src/DefifaTokenUriResolver.sol +1 -1
- package/src/enums/DefifaScorecardState.sol +1 -0
- package/src/interfaces/IDefifaGovernor.sol +41 -2
- package/src/libraries/DefifaFontImporter.sol +1 -1
- package/src/libraries/DefifaHookLib.sol +70 -63
- package/src/structs/DefifaAttestations.sol +3 -3
- package/src/structs/DefifaLaunchProjectData.sol +1 -0
- package/src/structs/DefifaScorecard.sol +2 -0
- package/test/BWAFunctionComparison.t.sol +1320 -0
- package/test/DefifaAdversarialQuorum.t.sol +53 -38
- package/test/DefifaAuditLowGuards.t.sol +10 -6
- package/test/DefifaFeeAccounting.t.sol +3 -2
- package/test/DefifaGovernanceHardening.t.sol +1311 -0
- package/test/DefifaGovernor.t.sol +5 -3
- package/test/DefifaHookRegressions.t.sol +3 -2
- package/test/DefifaMintCostInvariant.t.sol +3 -2
- package/test/DefifaNoContest.t.sol +4 -3
- package/test/DefifaSecurity.t.sol +55 -42
- package/test/DefifaUSDC.t.sol +4 -3
- package/test/Fork.t.sol +12 -13
- package/test/SVG.t.sol +1 -1
- package/test/TestAuditGaps.sol +7 -5
- package/test/TestQALastMile.t.sol +5 -3
- package/test/audit/{CodexAttestationDoubleCount.t.sol → AttestationDoubleCount.t.sol} +4 -3
- package/test/audit/FixPendingReserveDilution.t.sol +366 -0
- package/test/audit/PendingReserveDilution.t.sol +298 -0
- package/test/audit/PendingReserveQuorumGrief.t.sol +355 -0
- package/test/deployScript.t.sol +1 -1
- package/test/regression/AttestationDelegateBeneficiary.t.sol +3 -2
- package/test/regression/FulfillmentBlocksRatification.t.sol +3 -2
- package/test/regression/GracePeriodBypass.t.sol +3 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
pragma solidity
|
|
2
|
+
pragma solidity 0.8.28;
|
|
3
3
|
|
|
4
4
|
import {IJBPermissions, JBPermissionsData} from "@bananapus/core-v6/src/interfaces/IJBPermissions.sol";
|
|
5
5
|
import {IJBProjects} from "@bananapus/core-v6/src/interfaces/IJBProjects.sol";
|
|
@@ -9,7 +9,11 @@ import {DefifaTierCashOutWeight} from "../structs/DefifaTierCashOutWeight.sol";
|
|
|
9
9
|
/// @notice Manages the ratification of Defifa scorecards through attestation-based governance.
|
|
10
10
|
interface IDefifaGovernor {
|
|
11
11
|
event GameInitialized(
|
|
12
|
-
uint256 indexed gameId,
|
|
12
|
+
uint256 indexed gameId,
|
|
13
|
+
uint256 attestationStartTime,
|
|
14
|
+
uint256 attestationGracePeriod,
|
|
15
|
+
uint256 timelockDuration,
|
|
16
|
+
address caller
|
|
13
17
|
);
|
|
14
18
|
|
|
15
19
|
event ScorecardAttested(uint256 indexed gameId, uint256 indexed scorecardId, uint256 weight, address caller);
|
|
@@ -24,6 +28,8 @@ interface IDefifaGovernor {
|
|
|
24
28
|
address caller
|
|
25
29
|
);
|
|
26
30
|
|
|
31
|
+
event AttestationRevoked(uint256 indexed gameId, uint256 indexed scorecardId, address account, uint256 weight);
|
|
32
|
+
|
|
27
33
|
/// @notice The number of attestations for a scorecard.
|
|
28
34
|
/// @param gameId The ID of the game.
|
|
29
35
|
/// @param scorecardId The ID of the scorecard.
|
|
@@ -63,6 +69,22 @@ interface IDefifaGovernor {
|
|
|
63
69
|
view
|
|
64
70
|
returns (uint256 attestationPower);
|
|
65
71
|
|
|
72
|
+
/// @notice Get the BWA-adjusted attestation weight for an account relative to a specific scorecard.
|
|
73
|
+
/// @param gameId The ID of the game.
|
|
74
|
+
/// @param scorecardId The ID of the scorecard (for tier weight lookup).
|
|
75
|
+
/// @param account The account to check.
|
|
76
|
+
/// @param timestamp The timestamp to check.
|
|
77
|
+
/// @return bwaAttestationPower The BWA-adjusted attestation power.
|
|
78
|
+
function getBWAAttestationWeight(
|
|
79
|
+
uint256 gameId,
|
|
80
|
+
uint256 scorecardId,
|
|
81
|
+
address account,
|
|
82
|
+
uint48 timestamp
|
|
83
|
+
)
|
|
84
|
+
external
|
|
85
|
+
view
|
|
86
|
+
returns (uint256 bwaAttestationPower);
|
|
87
|
+
|
|
66
88
|
/// @notice Whether an account has attested to a specific scorecard.
|
|
67
89
|
/// @param gameId The ID of the game.
|
|
68
90
|
/// @param scorecardId The ID of the scorecard.
|
|
@@ -96,6 +118,11 @@ interface IDefifaGovernor {
|
|
|
96
118
|
/// @return The scorecard state.
|
|
97
119
|
function stateOf(uint256 gameId, uint256 scorecardId) external view returns (DefifaScorecardState);
|
|
98
120
|
|
|
121
|
+
/// @notice The timelock duration for a game.
|
|
122
|
+
/// @param gameId The ID of the game.
|
|
123
|
+
/// @return The timelock duration in seconds.
|
|
124
|
+
function timelockDurationOf(uint256 gameId) external view returns (uint256);
|
|
125
|
+
|
|
99
126
|
/// @notice Attest to a submitted scorecard.
|
|
100
127
|
/// @param gameId The ID of the game.
|
|
101
128
|
/// @param scorecardId The ID of the scorecard to attest to.
|
|
@@ -106,7 +133,14 @@ interface IDefifaGovernor {
|
|
|
106
133
|
/// @param gameId The ID of the game.
|
|
107
134
|
/// @param attestationStartTime The timestamp when attestation begins.
|
|
108
135
|
/// @param attestationGracePeriod The grace period duration in seconds.
|
|
109
|
-
|
|
136
|
+
/// @param timelockDuration The timelock duration after quorum is met, in seconds.
|
|
137
|
+
function initializeGame(
|
|
138
|
+
uint256 gameId,
|
|
139
|
+
uint256 attestationStartTime,
|
|
140
|
+
uint256 attestationGracePeriod,
|
|
141
|
+
uint256 timelockDuration
|
|
142
|
+
)
|
|
143
|
+
external;
|
|
110
144
|
|
|
111
145
|
/// @notice Ratify a scorecard that has reached quorum.
|
|
112
146
|
/// @param gameId The ID of the game.
|
|
@@ -119,6 +153,11 @@ interface IDefifaGovernor {
|
|
|
119
153
|
external
|
|
120
154
|
returns (uint256);
|
|
121
155
|
|
|
156
|
+
/// @notice Revoke a previously submitted attestation during the ACTIVE phase.
|
|
157
|
+
/// @param gameId The ID of the game.
|
|
158
|
+
/// @param scorecardId The ID of the scorecard to revoke attestation from.
|
|
159
|
+
function revokeAttestationFrom(uint256 gameId, uint256 scorecardId) external;
|
|
160
|
+
|
|
122
161
|
/// @notice Submit a scorecard for attestation.
|
|
123
162
|
/// @param gameId The ID of the game.
|
|
124
163
|
/// @param tierWeights The tier cash out weights.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
pragma solidity
|
|
2
|
+
pragma solidity 0.8.28;
|
|
3
3
|
|
|
4
4
|
import {IJB721TiersHookStore} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHookStore.sol";
|
|
5
5
|
import {JB721Tier} from "@bananapus/721-hook-v6/src/structs/JB721Tier.sol";
|
|
@@ -42,47 +42,47 @@ library DefifaHookLib {
|
|
|
42
42
|
returns (uint256[128] memory weights)
|
|
43
43
|
{
|
|
44
44
|
// Keep a reference to the max tier ID.
|
|
45
|
-
uint256
|
|
45
|
+
uint256 maxTierId = hookStore.maxTierIdOf(hook);
|
|
46
46
|
|
|
47
47
|
// Keep a reference to the cumulative amounts.
|
|
48
|
-
uint256
|
|
48
|
+
uint256 cumulativeCashOutWeight;
|
|
49
49
|
|
|
50
50
|
// Keep a reference to the number of tier weights.
|
|
51
|
-
uint256
|
|
51
|
+
uint256 numberOfTierWeights = tierWeights.length;
|
|
52
52
|
|
|
53
53
|
// Keep a reference to the tier being iterated on.
|
|
54
|
-
JB721Tier memory
|
|
54
|
+
JB721Tier memory tier;
|
|
55
55
|
|
|
56
56
|
// Keep a reference to the last tier ID to enforce ascending order (no duplicates).
|
|
57
|
-
uint256
|
|
57
|
+
uint256 lastTierId;
|
|
58
58
|
|
|
59
|
-
for (uint256
|
|
59
|
+
for (uint256 i; i < numberOfTierWeights;) {
|
|
60
60
|
// Enforce strict ascending order to prevent duplicate tier IDs.
|
|
61
|
-
if (tierWeights[
|
|
62
|
-
|
|
61
|
+
if (tierWeights[i].id <= lastTierId && i != 0) revert DefifaHook_BadTierOrder();
|
|
62
|
+
lastTierId = tierWeights[i].id;
|
|
63
63
|
|
|
64
64
|
// Get the tier.
|
|
65
|
-
|
|
65
|
+
tier = hookStore.tierOf({hook: hook, id: tierWeights[i].id, includeResolvedUri: false});
|
|
66
66
|
|
|
67
67
|
// Can't set a cashOut weight for tiers not in category 0.
|
|
68
|
-
if (
|
|
68
|
+
if (tier.category != 0) revert DefifaHook_InvalidTierId();
|
|
69
69
|
|
|
70
70
|
// Attempting to set the cashOut weight for a tier that does not exist (yet) reverts.
|
|
71
|
-
if (
|
|
71
|
+
if (tier.id > maxTierId) revert DefifaHook_InvalidTierId();
|
|
72
72
|
|
|
73
73
|
// Save the tier weight. Tiers are 1 indexed and should be stored 0 indexed.
|
|
74
|
-
weights[
|
|
74
|
+
weights[tier.id - 1] = tierWeights[i].cashOutWeight;
|
|
75
75
|
|
|
76
76
|
// Increment the cumulative amount.
|
|
77
|
-
|
|
77
|
+
cumulativeCashOutWeight += tierWeights[i].cashOutWeight;
|
|
78
78
|
|
|
79
79
|
unchecked {
|
|
80
|
-
++
|
|
80
|
+
++i;
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
// Make sure the cumulative amount is exactly the total cashOut weight.
|
|
85
|
-
if (
|
|
85
|
+
if (cumulativeCashOutWeight != TOTAL_CASHOUT_WEIGHT) revert DefifaHook_InvalidCashoutWeights();
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
/// @notice Compute the cash out weight for a single token.
|
|
@@ -104,32 +104,39 @@ library DefifaHookLib {
|
|
|
104
104
|
returns (uint256)
|
|
105
105
|
{
|
|
106
106
|
// Keep a reference to the token's tier ID.
|
|
107
|
-
uint256
|
|
107
|
+
uint256 tierId = hookStore.tierIdOfToken(tokenId);
|
|
108
108
|
|
|
109
109
|
// Keep a reference to the tier.
|
|
110
|
-
JB721Tier memory
|
|
110
|
+
JB721Tier memory tier = hookStore.tierOf({hook: hook, id: tierId, includeResolvedUri: false});
|
|
111
111
|
|
|
112
112
|
// Get the tier's weight.
|
|
113
|
-
uint256
|
|
113
|
+
uint256 weight = tierCashOutWeights[tierId - 1];
|
|
114
114
|
|
|
115
115
|
// If there's no weight there's nothing to redeem.
|
|
116
|
-
if (
|
|
116
|
+
if (weight == 0) return 0;
|
|
117
117
|
|
|
118
118
|
// Get the amount of tokens that have already been burned.
|
|
119
|
-
uint256
|
|
119
|
+
uint256 burnedTokens = hookStore.numberOfBurnedFor({hook: hook, tierId: tierId});
|
|
120
120
|
|
|
121
121
|
// If no tiers were minted, nothing to redeem.
|
|
122
|
-
if (
|
|
122
|
+
if (tier.initialSupply - (tier.remainingSupply + burnedTokens) == 0) return 0;
|
|
123
123
|
|
|
124
124
|
// Calculate the amount of tokens that existed at the start of the last phase.
|
|
125
|
-
uint256
|
|
126
|
-
|
|
125
|
+
uint256 totalTokensForCashoutInTier =
|
|
126
|
+
tier.initialSupply - tier.remainingSupply - (burnedTokens - tokensRedeemedFrom[tierId]);
|
|
127
|
+
|
|
128
|
+
// Include pending (unminted) reserve NFTs in the denominator. Without this, paid holders
|
|
129
|
+
// could cash out before reserves are minted and extract value that should be diluted across
|
|
130
|
+
// both paid and reserved holders. By counting pending reserves, each token's share of the
|
|
131
|
+
// tier weight is computed against the full eventual supply.
|
|
132
|
+
uint256 pendingReserves = hookStore.numberOfPendingReservesFor({hook: hook, tierId: tierId});
|
|
133
|
+
totalTokensForCashoutInTier += pendingReserves;
|
|
127
134
|
|
|
128
135
|
// Calculate the percentage of the tier cashOut amount a single token counts for.
|
|
129
136
|
// Integer division rounding in cashOutWeight is unavoidable in Solidity. Rounding direction
|
|
130
137
|
// (down) is consistent and conservative — it slightly favors the project over individual cash-out recipients.
|
|
131
138
|
// The maximum error per operation is 1 wei per division.
|
|
132
|
-
return
|
|
139
|
+
return weight / totalTokensForCashoutInTier;
|
|
133
140
|
}
|
|
134
141
|
|
|
135
142
|
/// @notice Compute the cumulative cash out weight for multiple tokens.
|
|
@@ -150,17 +157,17 @@ library DefifaHookLib {
|
|
|
150
157
|
view
|
|
151
158
|
returns (uint256 cumulativeWeight)
|
|
152
159
|
{
|
|
153
|
-
uint256
|
|
154
|
-
for (uint256
|
|
160
|
+
uint256 tokenCount = tokenIds.length;
|
|
161
|
+
for (uint256 i; i < tokenCount;) {
|
|
155
162
|
cumulativeWeight += computeCashOutWeight({
|
|
156
|
-
tokenId: tokenIds[
|
|
163
|
+
tokenId: tokenIds[i],
|
|
157
164
|
hookStore: hookStore,
|
|
158
165
|
hook: hook,
|
|
159
166
|
tierCashOutWeights: tierCashOutWeights,
|
|
160
167
|
tokensRedeemedFrom: tokensRedeemedFrom
|
|
161
168
|
});
|
|
162
169
|
unchecked {
|
|
163
|
-
++
|
|
170
|
+
++i;
|
|
164
171
|
}
|
|
165
172
|
}
|
|
166
173
|
}
|
|
@@ -190,20 +197,20 @@ library DefifaHookLib {
|
|
|
190
197
|
if (totalMintCost == 0) return (0, 0);
|
|
191
198
|
|
|
192
199
|
// Keep a reference to the number of tokens being used for claims.
|
|
193
|
-
uint256
|
|
200
|
+
uint256 numberOfTokens = tokenIds.length;
|
|
194
201
|
|
|
195
202
|
// Calculate the amount paid to mint the tokens that are being burned.
|
|
196
|
-
uint256
|
|
197
|
-
for (uint256
|
|
198
|
-
|
|
199
|
-
hook: hook, tokenId: tokenIds[
|
|
203
|
+
uint256 cumulativeMintPrice;
|
|
204
|
+
for (uint256 i; i < numberOfTokens; i++) {
|
|
205
|
+
cumulativeMintPrice += hookStore.tierOfTokenId({
|
|
206
|
+
hook: hook, tokenId: tokenIds[i], includeResolvedUri: false
|
|
200
207
|
})
|
|
201
208
|
.price;
|
|
202
209
|
}
|
|
203
210
|
|
|
204
211
|
// Calculate the user's claimable amount proportional to what they paid.
|
|
205
|
-
defifaTokenAmount = mulDiv({x: defifaBalance, y:
|
|
206
|
-
baseProtocolTokenAmount = mulDiv({x: baseProtocolBalance, y:
|
|
212
|
+
defifaTokenAmount = mulDiv({x: defifaBalance, y: cumulativeMintPrice, denominator: totalMintCost});
|
|
213
|
+
baseProtocolTokenAmount = mulDiv({x: baseProtocolBalance, y: cumulativeMintPrice, denominator: totalMintCost});
|
|
207
214
|
}
|
|
208
215
|
|
|
209
216
|
/// @notice Compute the cumulative mint price for a set of token IDs.
|
|
@@ -220,10 +227,10 @@ library DefifaHookLib {
|
|
|
220
227
|
view
|
|
221
228
|
returns (uint256 cumulativeMintPrice)
|
|
222
229
|
{
|
|
223
|
-
uint256
|
|
224
|
-
for (uint256
|
|
230
|
+
uint256 numberOfTokenIds = tokenIds.length;
|
|
231
|
+
for (uint256 i; i < numberOfTokenIds; i++) {
|
|
225
232
|
cumulativeMintPrice += hookStore.tierOfTokenId({
|
|
226
|
-
hook: hook, tokenId: tokenIds[
|
|
233
|
+
hook: hook, tokenId: tokenIds[i], includeResolvedUri: false
|
|
227
234
|
})
|
|
228
235
|
.price;
|
|
229
236
|
}
|
|
@@ -275,8 +282,8 @@ library DefifaHookLib {
|
|
|
275
282
|
view
|
|
276
283
|
returns (uint256)
|
|
277
284
|
{
|
|
278
|
-
JB721Tier memory
|
|
279
|
-
return
|
|
285
|
+
JB721Tier memory tier = hookStore.tierOf({hook: hook, id: tierId, includeResolvedUri: false});
|
|
286
|
+
return tier.initialSupply - (tier.remainingSupply + hookStore.numberOfBurnedFor({hook: hook, tierId: tierId}));
|
|
280
287
|
}
|
|
281
288
|
|
|
282
289
|
/// @notice Computes the attestation units for tiers during payment processing.
|
|
@@ -296,40 +303,40 @@ library DefifaHookLib {
|
|
|
296
303
|
view
|
|
297
304
|
returns (uint256[] memory tierIds, uint256[] memory attestationAmounts, uint256 count)
|
|
298
305
|
{
|
|
299
|
-
uint256
|
|
300
|
-
tierIds = new uint256[](
|
|
301
|
-
attestationAmounts = new uint256[](
|
|
306
|
+
uint256 numberOfTiers = tierIdsToMint.length;
|
|
307
|
+
tierIds = new uint256[](numberOfTiers);
|
|
308
|
+
attestationAmounts = new uint256[](numberOfTiers);
|
|
302
309
|
|
|
303
|
-
if (
|
|
310
|
+
if (numberOfTiers == 0) return (tierIds, attestationAmounts, 0);
|
|
304
311
|
|
|
305
|
-
uint256
|
|
306
|
-
uint256
|
|
307
|
-
uint256
|
|
312
|
+
uint256 currentTierId;
|
|
313
|
+
uint256 attestationUnits;
|
|
314
|
+
uint256 accumulated;
|
|
308
315
|
|
|
309
|
-
for (uint256
|
|
310
|
-
if (
|
|
316
|
+
for (uint256 i; i < numberOfTiers;) {
|
|
317
|
+
if (currentTierId != tierIdsToMint[i]) {
|
|
311
318
|
// Flush accumulated units for previous tier.
|
|
312
|
-
if (
|
|
313
|
-
tierIds[count] =
|
|
314
|
-
attestationAmounts[count] =
|
|
319
|
+
if (currentTierId != 0) {
|
|
320
|
+
tierIds[count] = currentTierId;
|
|
321
|
+
attestationAmounts[count] = accumulated;
|
|
315
322
|
count++;
|
|
316
323
|
}
|
|
317
|
-
if (tierIdsToMint[
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
hookStore.tierOf({hook: hook, id:
|
|
321
|
-
|
|
324
|
+
if (tierIdsToMint[i] < currentTierId) revert DefifaHook_BadTierOrder();
|
|
325
|
+
currentTierId = tierIdsToMint[i];
|
|
326
|
+
attestationUnits =
|
|
327
|
+
hookStore.tierOf({hook: hook, id: currentTierId, includeResolvedUri: false}).votingUnits;
|
|
328
|
+
accumulated = attestationUnits;
|
|
322
329
|
} else {
|
|
323
|
-
|
|
330
|
+
accumulated += attestationUnits;
|
|
324
331
|
}
|
|
325
332
|
unchecked {
|
|
326
|
-
++
|
|
333
|
+
++i;
|
|
327
334
|
}
|
|
328
335
|
}
|
|
329
336
|
// Flush the last tier.
|
|
330
|
-
if (
|
|
331
|
-
tierIds[count] =
|
|
332
|
-
attestationAmounts[count] =
|
|
337
|
+
if (currentTierId != 0) {
|
|
338
|
+
tierIds[count] = currentTierId;
|
|
339
|
+
attestationAmounts[count] = accumulated;
|
|
333
340
|
count++;
|
|
334
341
|
}
|
|
335
342
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
pragma solidity ^0.8.0;
|
|
3
3
|
|
|
4
|
-
/// @custom:param A count of
|
|
5
|
-
/// @custom:param
|
|
4
|
+
/// @custom:param count A count of attestation weight.
|
|
5
|
+
/// @custom:param attestedWeightOf The BWA weight each account attested with (0 = not attested).
|
|
6
6
|
struct DefifaAttestations {
|
|
7
7
|
uint256 count;
|
|
8
|
-
mapping(address =>
|
|
8
|
+
mapping(address => uint256) attestedWeightOf;
|
|
9
9
|
}
|
|
@@ -3,7 +3,9 @@ pragma solidity ^0.8.0;
|
|
|
3
3
|
|
|
4
4
|
/// @custom:member attestationsBegin The block at which attestations to the scorecard become allowed.
|
|
5
5
|
/// @custom:member gracePeriodEnds The block at which the scorecard can become ratified.
|
|
6
|
+
/// @custom:member quorumSnapshot The HHI-adjusted quorum threshold snapshotted at submission time.
|
|
6
7
|
struct DefifaScorecard {
|
|
7
8
|
uint48 attestationsBegin;
|
|
8
9
|
uint48 gracePeriodEnds;
|
|
10
|
+
uint256 quorumSnapshot;
|
|
9
11
|
}
|