@ballkidz/defifa 0.0.28 → 0.0.29
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/CRYPTO_ECON.md +3 -3
- package/package.json +1 -1
- package/src/DefifaDeployer.sol +17 -12
- package/src/DefifaTokenUriResolver.sol +13 -3
package/CRYPTO_ECON.md
CHANGED
|
@@ -777,11 +777,11 @@ Defifa includes a comprehensive safety system — the **NO_CONTEST** mechanism
|
|
|
777
777
|
|
|
778
778
|
#### 9.1.1 Trigger 1: Minimum Participation Threshold
|
|
779
779
|
|
|
780
|
-
**Mechanism.** At game creation, the organizer sets `minParticipation` — a minimum
|
|
780
|
+
**Mechanism.** At game creation, the organizer sets `minParticipation` — a minimum token supply required for the game to proceed to scoring. The `currentGamePhaseOf()` function checks the total token supply (via `CONTROLLER.TOKENS().totalSupplyOf(gameId)`) against this threshold before returning SCORING. If the supply is below the threshold, it returns NO_CONTEST.
|
|
781
781
|
|
|
782
782
|
**What it solves.** Ghost games with negligible participation skip directly to refundability without requiring any governance action. A 32-team World Cup game with `minParticipation = 1 ETH` won't enter scoring if only 50 people mint (0.5 ETH pot).
|
|
783
783
|
|
|
784
|
-
**Attack surface.** An adversary who wants to force no-contest can
|
|
784
|
+
**Attack surface.** An adversary who wants to force no-contest can cash out enough tokens during the refund phase to push the supply below the threshold. Note that direct balance top-ups (via `addToBalanceOf`) cannot inflate participation since the check uses token supply, not treasury balance. Mitigation: set the threshold conservatively low relative to expected participation.
|
|
785
785
|
|
|
786
786
|
**Configuration.** Set to 0 to disable. The threshold is set at launch before any minting occurs, so calibration depends on organizer judgment.
|
|
787
787
|
|
|
@@ -820,7 +820,7 @@ The phase resolution follows strict priority:
|
|
|
820
820
|
|
|
821
821
|
2. **Explicit trigger is sticky.** Once `noContestTriggeredFor[gameId]` is set, the game stays in NO_CONTEST permanently (cannot transition to SCORING even if conditions change).
|
|
822
822
|
|
|
823
|
-
3. **Both thresholds are checked independently.** A game can enter NO_CONTEST from either `minParticipation` (
|
|
823
|
+
3. **Both thresholds are checked independently.** A game can enter NO_CONTEST from either `minParticipation` (token supply too low) or `scorecardTimeout` (time elapsed) — whichever condition is met first.
|
|
824
824
|
|
|
825
825
|
#### 9.1.5 The Default Attestation Delegate
|
|
826
826
|
|
package/package.json
CHANGED
package/src/DefifaDeployer.sol
CHANGED
|
@@ -254,13 +254,12 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
254
254
|
// Get the game's ops data for the safety mechanism checks. Cache to avoid repeated SLOAD.
|
|
255
255
|
DefifaOpsData memory ops = _opsOf[gameId];
|
|
256
256
|
|
|
257
|
-
// Check minimum participation threshold
|
|
258
|
-
//
|
|
257
|
+
// Check minimum participation threshold using token supply (not terminal balance).
|
|
258
|
+
// Token supply reflects actual minted participation — direct `addToBalanceOf` top-ups
|
|
259
|
+
// don't mint tokens and therefore can't bypass this check.
|
|
259
260
|
if (ops.minParticipation > 0) {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
.balanceOf({terminal: address(terminal), projectId: gameId, token: ops.token});
|
|
263
|
-
if (balance < ops.minParticipation) return DefifaGamePhase.NO_CONTEST;
|
|
261
|
+
uint256 totalTokenSupply = CONTROLLER.TOKENS().totalSupplyOf(gameId);
|
|
262
|
+
if (totalTokenSupply < ops.minParticipation) return DefifaGamePhase.NO_CONTEST;
|
|
264
263
|
}
|
|
265
264
|
|
|
266
265
|
// Check scorecard ratification timeout: if enough time has passed without a ratified scorecard, the game is
|
|
@@ -427,13 +426,19 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
427
426
|
revert DefifaDeployer_InvalidCurrency();
|
|
428
427
|
}
|
|
429
428
|
|
|
430
|
-
// If a scorecard timeout is set, it must exceed the
|
|
429
|
+
// If a scorecard timeout is set, it must exceed the full ratification window:
|
|
430
|
+
// attestation delay (time from scoring start to attestation start) + grace period + timelock.
|
|
431
431
|
// Otherwise the game would enter NO_CONTEST before a scorecard could ever reach SUCCEEDED.
|
|
432
|
-
if (
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
432
|
+
if (launchProjectData.scorecardTimeout > 0) {
|
|
433
|
+
// Attestation delay: how long after scoring starts before attestations can begin.
|
|
434
|
+
uint256 attestationDelay = launchProjectData.attestationStartTime > launchProjectData.start
|
|
435
|
+
? launchProjectData.attestationStartTime - launchProjectData.start
|
|
436
|
+
: 0;
|
|
437
|
+
if (
|
|
438
|
+
launchProjectData.scorecardTimeout
|
|
439
|
+
<= attestationDelay + launchProjectData.attestationGracePeriod + launchProjectData.timelockDuration
|
|
440
|
+
) revert DefifaDeployer_InvalidGameConfiguration();
|
|
441
|
+
}
|
|
437
442
|
|
|
438
443
|
// Reserve the game ID up front so permissionless project creations cannot invalidate hook deployment.
|
|
439
444
|
gameId = CONTROLLER.PROJECTS().createFor(address(this));
|
|
@@ -282,9 +282,19 @@ contract DefifaTokenUriResolver is IDefifaTokenUriResolver, IJB721TokenUriResolv
|
|
|
282
282
|
}
|
|
283
283
|
|
|
284
284
|
// Concatenate the strings
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
285
|
+
if (isEth) {
|
|
286
|
+
return string(abi.encodePacked("\u039E", integerPart, ".", decimalPartStr));
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Try to get the token symbol; fall back to a truncated hex address if the call reverts.
|
|
290
|
+
string memory tokenSymbol;
|
|
291
|
+
try IERC20Metadata(token).symbol() returns (string memory s) {
|
|
292
|
+
tokenSymbol = _escapeSvg(s);
|
|
293
|
+
} catch {
|
|
294
|
+
tokenSymbol = Strings.toHexString(uint160(token), 20);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return string(abi.encodePacked(integerPart, ".", decimalPartStr, " ", tokenSymbol));
|
|
288
298
|
}
|
|
289
299
|
|
|
290
300
|
/// @notice Gets a substring.
|