@ballkidz/defifa 0.0.17 → 0.0.19
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 +34 -6
- package/ARCHITECTURE.md +54 -102
- package/AUDIT_INSTRUCTIONS.md +96 -504
- package/CHANGELOG.md +26 -0
- package/README.md +61 -207
- package/RISKS.md +19 -2
- package/SKILLS.md +29 -281
- package/STYLE_GUIDE.md +57 -18
- package/USER_JOURNEYS.md +45 -1011
- package/foundry.lock +17 -0
- package/package.json +5 -6
- package/references/operations.md +27 -0
- package/references/runtime.md +32 -0
- package/script/Deploy.s.sol +5 -3
- package/src/DefifaDeployer.sol +33 -9
- package/src/DefifaGovernor.sol +63 -30
- package/src/DefifaHook.sol +33 -13
- package/src/interfaces/IDefifaDeployer.sol +28 -1
- package/src/interfaces/IDefifaGovernor.sol +26 -0
- package/src/interfaces/IDefifaHook.sol +30 -1
- package/src/libraries/DefifaHookLib.sol +8 -0
- package/test/DefifaGovernor.t.sol +55 -32
- package/test/DefifaSecurity.t.sol +1 -3
- package/test/Fork.t.sol +3 -9
- package/test/audit/CodexRegistryMismatch.t.sol +191 -0
- package/test/audit/PendingReserveSnapshotBypass.t.sol +40 -0
- package/test/regression/AttestationDelegateBeneficiary.t.sol +3 -5
- package/CHANGE_LOG.md +0 -164
package/foundry.lock
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"lib/base64": {
|
|
3
|
+
"branch": {
|
|
4
|
+
"name": "v1.1.0",
|
|
5
|
+
"rev": "dcbf852ba545b3d15de0ac0ef88dce934c090c8e"
|
|
6
|
+
}
|
|
7
|
+
},
|
|
8
|
+
"lib/capsules": {
|
|
9
|
+
"rev": "7432ba95ac69ba4902076660b0dc9a90aeb26706"
|
|
10
|
+
},
|
|
11
|
+
"lib/forge-std": {
|
|
12
|
+
"rev": "8bbcf6e3f8f62f419e5429a0bd89331c85c37824"
|
|
13
|
+
},
|
|
14
|
+
"lib/typeface": {
|
|
15
|
+
"rev": "edd52e2f7c8adc44e75b207bd279710f9b72f5e9"
|
|
16
|
+
}
|
|
17
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ballkidz/defifa",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.19",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=20.0.0"
|
|
@@ -13,14 +13,13 @@
|
|
|
13
13
|
"url": "https://github.com/BallKidz/defifa-collection-deployer"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@bananapus/721-hook-v6": "^0.0.
|
|
17
|
-
"@bananapus/
|
|
18
|
-
"@bananapus/core-v6": "^0.0.30",
|
|
16
|
+
"@bananapus/721-hook-v6": "^0.0.30",
|
|
17
|
+
"@bananapus/core-v6": "^0.0.31",
|
|
19
18
|
"@bananapus/permission-ids-v6": "^0.0.15",
|
|
20
|
-
"@croptop/core-v6": "^0.0.
|
|
19
|
+
"@croptop/core-v6": "^0.0.29",
|
|
21
20
|
"@openzeppelin/contracts": "^5.6.1",
|
|
22
21
|
"@prb/math": "^4.1.1",
|
|
23
|
-
"@rev-net/core-v6": "^0.0.
|
|
22
|
+
"@rev-net/core-v6": "^0.0.26",
|
|
24
23
|
"scripty.sol": "^2.1.1"
|
|
25
24
|
},
|
|
26
25
|
"devDependencies": {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Defifa Operations
|
|
2
|
+
|
|
3
|
+
## Deployment Surface
|
|
4
|
+
|
|
5
|
+
- [`src/DefifaDeployer.sol`](../src/DefifaDeployer.sol) is the first stop for launch-time config, phase queueing, and post-ratification fulfillment.
|
|
6
|
+
- [`script/Deploy.s.sol`](../script/Deploy.s.sol) is the deployment entry point when the task is about current wiring rather than game mechanics.
|
|
7
|
+
- [`src/structs/`](../src/structs/) and [`src/enums/`](../src/enums/) define launch data, phase types, and other inputs that often drift from remembered assumptions.
|
|
8
|
+
|
|
9
|
+
## Change Checklist
|
|
10
|
+
|
|
11
|
+
- If you edit lifecycle timing, verify phase transitions, no-contest triggers, and the governor's attestation windows together.
|
|
12
|
+
- If you edit hook settlement logic, re-check fee accounting and mint-cost invariants.
|
|
13
|
+
- If you touch governance thresholds or attestation behavior, inspect the governor tests before assuming the change is local.
|
|
14
|
+
- If you touch token metadata or rendering, verify whether the bug belongs in the resolver instead of settlement code.
|
|
15
|
+
|
|
16
|
+
## Common Failure Modes
|
|
17
|
+
|
|
18
|
+
- Game-state issue is blamed on the hook even though the deployer queued the wrong phase or timing.
|
|
19
|
+
- Governance behavior looks wrong, but the real issue is stale launch configuration.
|
|
20
|
+
- Settlement changes accidentally affect fee distribution or redemption accounting.
|
|
21
|
+
- Resolver issues get misdiagnosed as hook or governor problems because they surface through NFTs.
|
|
22
|
+
|
|
23
|
+
## Useful Proof Points
|
|
24
|
+
|
|
25
|
+
- [`test/Fork.t.sol`](../test/Fork.t.sol) for live-integration assumptions.
|
|
26
|
+
- [`test/TestAuditGaps.sol`](../test/TestAuditGaps.sol) and [`test/TestQALastMile.t.sol`](../test/TestQALastMile.t.sol) for pinned edge cases.
|
|
27
|
+
- [`test/BWAFunctionComparison.t.sol`](../test/BWAFunctionComparison.t.sol) and [`test/DefifaUSDC.t.sol`](../test/DefifaUSDC.t.sol) when currency or accounting context matters.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Defifa Runtime
|
|
2
|
+
|
|
3
|
+
## Contract Roles
|
|
4
|
+
|
|
5
|
+
- [`src/DefifaDeployer.sol`](../src/DefifaDeployer.sol) launches games, manages phase progression, fulfills commitments, and triggers safety exits such as no-contest.
|
|
6
|
+
- [`src/DefifaHook.sol`](../src/DefifaHook.sol) manages the NFT game pieces, delegation, and settlement-side cash-out behavior.
|
|
7
|
+
- [`src/DefifaGovernor.sol`](../src/DefifaGovernor.sol) handles scorecard submission, attestation, quorum, and ratification.
|
|
8
|
+
- [`src/DefifaTokenUriResolver.sol`](../src/DefifaTokenUriResolver.sol) renders game-card metadata.
|
|
9
|
+
- [`src/DefifaProjectOwner.sol`](../src/DefifaProjectOwner.sol) is the ownership helper for the fee project.
|
|
10
|
+
|
|
11
|
+
## Lifecycle
|
|
12
|
+
|
|
13
|
+
1. Countdown before minting opens.
|
|
14
|
+
2. Mint phase where players buy outcome NFTs and can delegate attestation power.
|
|
15
|
+
3. Optional refund phase.
|
|
16
|
+
4. Scoring phase where scorecards are submitted, attested, and ratified.
|
|
17
|
+
5. Complete or no-contest settlement depending on governance and safety conditions.
|
|
18
|
+
|
|
19
|
+
## High-Risk Areas
|
|
20
|
+
|
|
21
|
+
- Scorecard ratification and quorum assumptions: changes here directly affect who can settle the pot.
|
|
22
|
+
- No-contest and refund behavior: these paths are economic safety valves, not edge-case garnish.
|
|
23
|
+
- Fee accounting and commitment fulfillment: payout ordering and accounting drift can change final redemption value.
|
|
24
|
+
- Hook/governor/deployer coupling: many bugs come from changing one layer while assuming the others are passive.
|
|
25
|
+
|
|
26
|
+
## Tests To Trust First
|
|
27
|
+
|
|
28
|
+
- [`test/DefifaGovernor.t.sol`](../test/DefifaGovernor.t.sol) for governance flow.
|
|
29
|
+
- [`test/DefifaNoContest.t.sol`](../test/DefifaNoContest.t.sol) for safety exits.
|
|
30
|
+
- [`test/DefifaFeeAccounting.t.sol`](../test/DefifaFeeAccounting.t.sol) and [`test/DefifaMintCostInvariant.t.sol`](../test/DefifaMintCostInvariant.t.sol) for economic correctness.
|
|
31
|
+
- [`test/DefifaHookRegressions.t.sol`](../test/DefifaHookRegressions.t.sol) and [`test/regression/`](../test/regression/) for pinned regressions.
|
|
32
|
+
- [`test/DefifaSecurity.t.sol`](../test/DefifaSecurity.t.sol) and [`test/DefifaGovernanceHardening.t.sol`](../test/DefifaGovernanceHardening.t.sol) for adversarial cases.
|
package/script/Deploy.s.sol
CHANGED
|
@@ -22,9 +22,8 @@ contract DeployMainnet is Script, Sphinx {
|
|
|
22
22
|
/// @notice tracks the deployment of the address registry for the chain we are deploying to.
|
|
23
23
|
AddressRegistryDeployment registry;
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
uint256
|
|
27
|
-
uint256 _baseProtocolProjectId = 1;
|
|
25
|
+
uint256 _defifaProjectId;
|
|
26
|
+
uint256 _baseProtocolProjectId;
|
|
28
27
|
|
|
29
28
|
bytes32 _salt = bytes32(keccak256("0.0.2"));
|
|
30
29
|
|
|
@@ -53,6 +52,9 @@ contract DeployMainnet is Script, Sphinx {
|
|
|
53
52
|
)
|
|
54
53
|
);
|
|
55
54
|
|
|
55
|
+
_defifaProjectId = vm.envUint("DEFIFA_PROJECT_ID");
|
|
56
|
+
_baseProtocolProjectId = vm.envUint("BASE_PROTOCOL_PROJECT_ID");
|
|
57
|
+
|
|
56
58
|
defifaToken = IERC20(address(core.tokens.tokenOf(_defifaProjectId)));
|
|
57
59
|
baseProtocolToken = IERC20(address(core.tokens.tokenOf(_baseProtocolProjectId)));
|
|
58
60
|
|
package/src/DefifaDeployer.sol
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
JB721TiersRulesetMetadataResolver
|
|
8
8
|
} from "@bananapus/721-hook-v6/src/libraries/JB721TiersRulesetMetadataResolver.sol";
|
|
9
9
|
import {JB721TierConfig} from "@bananapus/721-hook-v6/src/structs/JB721TierConfig.sol";
|
|
10
|
+
import {JB721TierConfigFlags} from "@bananapus/721-hook-v6/src/structs/JB721TierConfigFlags.sol";
|
|
10
11
|
import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
|
|
11
12
|
import {IJBController, JBRulesetConfig, JBTerminalConfig} from "@bananapus/core-v6/src/interfaces/IJBController.sol";
|
|
12
13
|
import {IJBMultiTerminal} from "@bananapus/core-v6/src/interfaces/IJBMultiTerminal.sol";
|
|
@@ -274,6 +275,7 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
274
275
|
uint256 _defifaProjectId,
|
|
275
276
|
uint256 _baseProtocolProjectId
|
|
276
277
|
) {
|
|
278
|
+
// slither-disable-next-line missing-zero-check
|
|
277
279
|
HOOK_CODE_ORIGIN = _hookCodeOrigin;
|
|
278
280
|
TOKEN_URI_RESOLVER = _tokenUriResolver;
|
|
279
281
|
GOVERNOR = _governor;
|
|
@@ -471,13 +473,15 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
471
473
|
encodedIPFSUri: defifaTier.encodedIPFSUri,
|
|
472
474
|
category: 0,
|
|
473
475
|
discountPercent: 0,
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
476
|
+
flags: JB721TierConfigFlags({
|
|
477
|
+
allowOwnerMint: false,
|
|
478
|
+
useReserveBeneficiaryAsDefault: defifaTier.shouldUseReservedTokenBeneficiaryAsDefault,
|
|
479
|
+
transfersPausable: false,
|
|
480
|
+
useVotingUnits: false,
|
|
481
|
+
cantBeRemoved: true,
|
|
482
|
+
cantIncreaseDiscountPercent: true,
|
|
483
|
+
cantBuyWithCredits: false
|
|
484
|
+
}),
|
|
481
485
|
splitPercent: 0,
|
|
482
486
|
splits: new JBSplit[](0)
|
|
483
487
|
});
|
|
@@ -546,8 +550,13 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
546
550
|
// Transfer ownership to the specified owner.
|
|
547
551
|
hook.transferOwnership(address(GOVERNOR));
|
|
548
552
|
|
|
549
|
-
//
|
|
550
|
-
|
|
553
|
+
// Register the actual CREATE2 clone address using the same salt and minimal-proxy init code
|
|
554
|
+
// that produced the deployed hook.
|
|
555
|
+
REGISTRY.registerAddress({
|
|
556
|
+
deployer: address(this),
|
|
557
|
+
salt: keccak256(abi.encodePacked(msg.sender, currentNonce)),
|
|
558
|
+
bytecode: _cloneCreationCodeFor(address(HOOK_CODE_ORIGIN))
|
|
559
|
+
});
|
|
551
560
|
|
|
552
561
|
// slither-disable-next-line reentrancy-events
|
|
553
562
|
emit LaunchGame(gameId, hook, GOVERNOR, uriResolver, msg.sender);
|
|
@@ -936,4 +945,19 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
936
945
|
projectId: gameId, rulesetConfigurations: rulesetConfigs, memo: "Defifa game has finished."
|
|
937
946
|
});
|
|
938
947
|
}
|
|
948
|
+
|
|
949
|
+
/// @notice Returns the minimal-proxy init code used to deploy a clone for the provided implementation.
|
|
950
|
+
/// @dev Defifa deploys hooks with `Clones.cloneDeterministic`, which uses `CREATE2`.
|
|
951
|
+
/// The address registry's CREATE2 path must be given the exact init code hash that was used at deployment time,
|
|
952
|
+
/// not the runtime bytecode and not a CREATE nonce. This helper reconstructs the standard EIP-1167 creation code
|
|
953
|
+
/// by inserting the implementation address into OpenZeppelin's minimal-proxy init-code template.
|
|
954
|
+
/// @param implementation The contract address the clone will delegate all calls to.
|
|
955
|
+
/// @return bytecode The full EIP-1167 creation bytecode hashed by CREATE2 to derive the clone address.
|
|
956
|
+
function _cloneCreationCodeFor(address implementation) internal pure returns (bytes memory bytecode) {
|
|
957
|
+
// EIP-1167 minimal proxy init code, mirroring OpenZeppelin's Clones.sol layout:
|
|
958
|
+
// [prefix (20 bytes)] [implementation address (20 bytes)] [suffix (15 bytes)]
|
|
959
|
+
bytecode = abi.encodePacked(
|
|
960
|
+
hex"3d602d80600a3d3981f3363d3d373d3d3d363d73", bytes20(implementation), hex"5af43d82803e903d91602b57fd5bf3"
|
|
961
|
+
);
|
|
962
|
+
}
|
|
939
963
|
}
|
package/src/DefifaGovernor.sol
CHANGED
|
@@ -18,6 +18,7 @@ import {IDefifaHook} from "./interfaces/IDefifaHook.sol";
|
|
|
18
18
|
import {DefifaAttestations} from "./structs/DefifaAttestations.sol";
|
|
19
19
|
import {DefifaScorecard} from "./structs/DefifaScorecard.sol";
|
|
20
20
|
import {DefifaTierCashOutWeight} from "./structs/DefifaTierCashOutWeight.sol";
|
|
21
|
+
import {DefifaHookLib} from "./libraries/DefifaHookLib.sol";
|
|
21
22
|
|
|
22
23
|
/// @notice Manages the ratification of Defifa scorecards.
|
|
23
24
|
contract DefifaGovernor is Ownable, IDefifaGovernor {
|
|
@@ -83,12 +84,20 @@ contract DefifaGovernor is Ownable, IDefifaGovernor {
|
|
|
83
84
|
mapping(uint256 => mapping(uint256 => DefifaAttestations)) internal _scorecardAttestationsOf;
|
|
84
85
|
|
|
85
86
|
/// @notice Snapshot of pending reserves per tier at scorecard submission time.
|
|
86
|
-
/// @dev
|
|
87
|
+
/// @dev Used to keep unminted reserve units in the BWA denominator.
|
|
87
88
|
/// @custom:param gameId The ID of the game.
|
|
88
89
|
/// @custom:param scorecardId The ID of the scorecard.
|
|
89
90
|
/// @custom:param tierId The tier ID (1-indexed).
|
|
90
91
|
mapping(uint256 => mapping(uint256 => mapping(uint256 => uint256))) internal _pendingReservesSnapshotOf;
|
|
91
92
|
|
|
93
|
+
/// @notice Snapshot of each tier's minted attestation units at scorecard submission time.
|
|
94
|
+
/// @dev Caps later checkpoint reads so reserve mints after submission can't increase the denominator
|
|
95
|
+
/// before the pending-reserve snapshot is added back in.
|
|
96
|
+
/// @custom:param gameId The ID of the game.
|
|
97
|
+
/// @custom:param scorecardId The ID of the scorecard.
|
|
98
|
+
/// @custom:param tierId The tier ID (1-indexed).
|
|
99
|
+
mapping(uint256 => mapping(uint256 => mapping(uint256 => uint256))) internal _submittedTierAttestationUnitsOf;
|
|
100
|
+
|
|
92
101
|
/// @notice Tier weights per scorecard for BWA computation.
|
|
93
102
|
/// @custom:param gameId The ID of the game.
|
|
94
103
|
/// @custom:param scorecardId The ID of the scorecard.
|
|
@@ -260,15 +269,23 @@ contract DefifaGovernor is Ownable, IDefifaGovernor {
|
|
|
260
269
|
}
|
|
261
270
|
|
|
262
271
|
// If there's a weight assigned to the tier, make sure there is a token backed by it.
|
|
272
|
+
// slither-disable-next-line calls-loop
|
|
263
273
|
for (uint256 i; i < numberOfTierWeights; i++) {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
) {
|
|
274
|
+
// A nonzero cashout weight is only valid once that tier has live ownership.
|
|
275
|
+
// slither-disable-next-line calls-loop
|
|
276
|
+
uint256 currentTierSupply = IDefifaHook(metadata.dataHook).currentSupplyOfTier(tierWeights[i].id);
|
|
277
|
+
if (tierWeights[i].cashOutWeight > 0 && currentTierSupply == 0) {
|
|
268
278
|
revert DefifaGovernor_UnownedProposedCashoutValue();
|
|
269
279
|
}
|
|
270
280
|
}
|
|
271
281
|
|
|
282
|
+
// Run the same structural validation the hook will apply at ratification time so malformed
|
|
283
|
+
// scorecards fail on submission instead of reaching a misleading SUCCEEDED state first.
|
|
284
|
+
// slither-disable-next-line unused-return
|
|
285
|
+
DefifaHookLib.validateAndBuildWeights({
|
|
286
|
+
tierWeights: tierWeights, hookStore: IDefifaHook(metadata.dataHook).store(), hook: metadata.dataHook
|
|
287
|
+
});
|
|
288
|
+
|
|
272
289
|
// Hash the scorecard.
|
|
273
290
|
scorecardId =
|
|
274
291
|
_hashScorecardOf({gameHook: metadata.dataHook, calldataBytes: _buildScorecardCalldataFor(tierWeights)});
|
|
@@ -295,15 +312,25 @@ contract DefifaGovernor is Ownable, IDefifaGovernor {
|
|
|
295
312
|
_scorecardTierWeightsOf[gameId][scorecardId][tierWeights[i].id - 1] = tierWeights[i].cashOutWeight;
|
|
296
313
|
}
|
|
297
314
|
|
|
298
|
-
// Snapshot pending reserves
|
|
299
|
-
//
|
|
315
|
+
// Snapshot each tier's pending reserves and minted attestation units at submission time.
|
|
316
|
+
// BWA later reads an account checkpoint at `attestationsBegin - 1`. If reserve mints happen
|
|
317
|
+
// after submission but before that checkpoint, clamp the live total back down to the minted
|
|
318
|
+
// units that existed at submission and only then add the snapshotted pending reserves.
|
|
300
319
|
{
|
|
301
320
|
IJB721TiersHookStore _store = IDefifaHook(metadata.dataHook).store();
|
|
302
321
|
uint256 _numberOfTiers = _store.maxTierIdOf(metadata.dataHook);
|
|
322
|
+
// slither-disable-next-line calls-loop
|
|
303
323
|
for (uint256 i; i < _numberOfTiers; i++) {
|
|
304
324
|
uint256 tierId = i + 1;
|
|
305
|
-
|
|
306
|
-
|
|
325
|
+
// slither-disable-next-line calls-loop
|
|
326
|
+
JB721Tier memory tier = _store.tierOf({hook: metadata.dataHook, id: tierId, includeResolvedUri: false});
|
|
327
|
+
// slither-disable-next-line calls-loop
|
|
328
|
+
uint256 pendingReserves = _store.numberOfPendingReservesFor(metadata.dataHook, tierId);
|
|
329
|
+
// slither-disable-next-line calls-loop
|
|
330
|
+
uint256 submittedTierAttestationUnits =
|
|
331
|
+
IDefifaHook(metadata.dataHook).currentSupplyOfTier(tierId) * tier.votingUnits;
|
|
332
|
+
_pendingReservesSnapshotOf[gameId][scorecardId][tierId] = pendingReserves;
|
|
333
|
+
_submittedTierAttestationUnitsOf[gameId][scorecardId][tierId] = submittedTierAttestationUnits;
|
|
307
334
|
}
|
|
308
335
|
}
|
|
309
336
|
|
|
@@ -490,16 +517,17 @@ contract DefifaGovernor is Ownable, IDefifaGovernor {
|
|
|
490
517
|
// Get a reference to the number of tiers.
|
|
491
518
|
uint256 numberOfTiers = store.maxTierIdOf(metadata.dataHook);
|
|
492
519
|
|
|
493
|
-
// slither-disable-next-line calls-inside-a-loop
|
|
494
520
|
for (uint256 i; i < numberOfTiers; i++) {
|
|
495
521
|
// Tiers are 1-indexed.
|
|
496
522
|
uint256 tierId = i + 1;
|
|
497
523
|
|
|
498
524
|
// Get this account's attestation units within the tier (snapshot at timestamp).
|
|
525
|
+
// slither-disable-next-line calls-loop
|
|
499
526
|
uint256 tierAttestationUnitsForAccount =
|
|
500
527
|
hook.getPastTierAttestationUnitsOf({account: account, tier: tierId, timestamp: timestamp});
|
|
501
528
|
|
|
502
529
|
// Get the total attestation units for this tier (snapshot at timestamp).
|
|
530
|
+
// slither-disable-next-line calls-loop
|
|
503
531
|
uint256 tierTotalAttestationUnits =
|
|
504
532
|
hook.getPastTierTotalAttestationUnitsOf({tier: tierId, timestamp: timestamp});
|
|
505
533
|
|
|
@@ -508,8 +536,10 @@ contract DefifaGovernor is Ownable, IDefifaGovernor {
|
|
|
508
536
|
// When the reserve beneficiary later mints, their new NFTs add to the numerator while
|
|
509
537
|
// pending reserves decrease by the same amount — so no one's voting power shifts.
|
|
510
538
|
{
|
|
539
|
+
// slither-disable-next-line calls-loop
|
|
511
540
|
uint256 pendingReserves = store.numberOfPendingReservesFor(metadata.dataHook, tierId);
|
|
512
541
|
if (pendingReserves != 0) {
|
|
542
|
+
// slither-disable-next-line calls-loop
|
|
513
543
|
JB721Tier memory tier =
|
|
514
544
|
store.tierOf({hook: metadata.dataHook, id: tierId, includeResolvedUri: false});
|
|
515
545
|
tierTotalAttestationUnits += pendingReserves * tier.votingUnits;
|
|
@@ -567,30 +597,33 @@ contract DefifaGovernor is Ownable, IDefifaGovernor {
|
|
|
567
597
|
// Cache the total cashout weight denominator from the hook.
|
|
568
598
|
uint256 totalCashOutWeight = hook.TOTAL_CASHOUT_WEIGHT();
|
|
569
599
|
|
|
570
|
-
// slither-disable-next-line calls-inside-a-loop
|
|
571
600
|
for (uint256 i; i < numberOfTiers; i++) {
|
|
572
601
|
// Tiers are 1-indexed.
|
|
573
602
|
uint256 tierId = i + 1;
|
|
574
603
|
|
|
575
604
|
// Get this account's attestation units within the tier (snapshot at timestamp).
|
|
605
|
+
// slither-disable-next-line calls-loop
|
|
576
606
|
uint256 tierAttestationUnitsForAccount =
|
|
577
607
|
hook.getPastTierAttestationUnitsOf({account: account, tier: tierId, timestamp: timestamp});
|
|
578
608
|
|
|
579
609
|
if (tierAttestationUnitsForAccount != 0) {
|
|
580
|
-
//
|
|
610
|
+
// Start from the checkpointed tier total at the requested timestamp.
|
|
611
|
+
// If reserve mints happened after submission, clamp them out before adding the
|
|
612
|
+
// pending-reserve snapshot back in so each reserve unit is counted exactly once.
|
|
613
|
+
// slither-disable-next-line calls-loop
|
|
581
614
|
uint256 tierTotalAttestationUnits =
|
|
582
615
|
hook.getPastTierTotalAttestationUnitsOf({tier: tierId, timestamp: timestamp});
|
|
616
|
+
uint256 submittedTierAttestationUnits = _submittedTierAttestationUnitsOf[gameId][scorecardId][tierId];
|
|
617
|
+
if (tierTotalAttestationUnits > submittedTierAttestationUnits) {
|
|
618
|
+
tierTotalAttestationUnits = submittedTierAttestationUnits;
|
|
619
|
+
}
|
|
583
620
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
JB721Tier memory tier =
|
|
591
|
-
store.tierOf({hook: metadata.dataHook, id: tierId, includeResolvedUri: false});
|
|
592
|
-
tierTotalAttestationUnits += pendingReserves * tier.votingUnits;
|
|
593
|
-
}
|
|
621
|
+
uint256 pendingReserves = _pendingReservesSnapshotOf[gameId][scorecardId][tierId];
|
|
622
|
+
if (pendingReserves != 0) {
|
|
623
|
+
// slither-disable-next-line calls-loop
|
|
624
|
+
JB721Tier memory tier =
|
|
625
|
+
store.tierOf({hook: metadata.dataHook, id: tierId, includeResolvedUri: false});
|
|
626
|
+
tierTotalAttestationUnits += pendingReserves * tier.votingUnits;
|
|
594
627
|
}
|
|
595
628
|
|
|
596
629
|
// Raw power for this tier.
|
|
@@ -630,18 +663,18 @@ contract DefifaGovernor is Ownable, IDefifaGovernor {
|
|
|
630
663
|
// Keep a reference to the total eligible tier weight.
|
|
631
664
|
uint256 eligibleTierWeights;
|
|
632
665
|
|
|
633
|
-
// slither-disable-next-line calls-inside-a-loop
|
|
634
666
|
for (uint256 i; i < numberOfTiers; i++) {
|
|
635
667
|
uint256 tierId = i + 1;
|
|
636
668
|
|
|
637
669
|
// A tier contributes to quorum if it has circulating tokens OR unminted pending reserves.
|
|
638
|
-
// Pending reserves
|
|
639
|
-
//
|
|
640
|
-
//
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
)
|
|
670
|
+
// Pending reserves still belong economically to the reserve beneficiary, even after the
|
|
671
|
+
// last paid token in the tier is burned during REFUND, so excluding them would let a
|
|
672
|
+
// burner erase another participant's quorum contribution without erasing their claim.
|
|
673
|
+
// slither-disable-next-line calls-loop
|
|
674
|
+
uint256 currentTierSupply = hook.currentSupplyOfTier(tierId);
|
|
675
|
+
// slither-disable-next-line calls-loop
|
|
676
|
+
uint256 pendingReserves = store.numberOfPendingReservesFor(metadata.dataHook, tierId);
|
|
677
|
+
if (currentTierSupply != 0 || pendingReserves != 0) {
|
|
645
678
|
eligibleTierWeights += MAX_ATTESTATION_POWER_TIER;
|
|
646
679
|
}
|
|
647
680
|
}
|
package/src/DefifaHook.sol
CHANGED
|
@@ -59,6 +59,8 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
59
59
|
error DefifaHook_TransfersPaused();
|
|
60
60
|
error DefifaHook_Unauthorized(uint256 tokenId, address owner, address caller);
|
|
61
61
|
|
|
62
|
+
event PricingCurrencySet(uint256 currency, address caller);
|
|
63
|
+
|
|
62
64
|
//*********************************************************************//
|
|
63
65
|
// --------------------- public constant properties ------------------ //
|
|
64
66
|
//*********************************************************************//
|
|
@@ -497,6 +499,7 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
497
499
|
pricingCurrency = _currency;
|
|
498
500
|
gamePhaseReporter = _gamePhaseReporter;
|
|
499
501
|
gamePotReporter = _gamePotReporter;
|
|
502
|
+
// slither-disable-next-line missing-zero-check
|
|
500
503
|
defaultAttestationDelegate = _defaultAttestationDelegate;
|
|
501
504
|
|
|
502
505
|
// Store the base URI if provided.
|
|
@@ -529,19 +532,22 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
529
532
|
|
|
530
533
|
// Transfer ownership to the initializer.
|
|
531
534
|
_transferOwnership(msg.sender);
|
|
535
|
+
|
|
536
|
+
emit PricingCurrencySet(_currency, msg.sender);
|
|
532
537
|
}
|
|
533
538
|
|
|
534
539
|
/// @notice Mint reserved tokens within the tier for the provided value.
|
|
535
540
|
/// @param tierId The ID of the tier to mint within.
|
|
536
541
|
/// @param count The number of reserved tokens to mint.
|
|
537
|
-
// slither-disable-next-line reentrancy-no-eth
|
|
538
542
|
function mintReservesFor(uint256 tierId, uint256 count) public override {
|
|
539
543
|
// Minting reserves must not be paused.
|
|
544
|
+
// slither-disable-next-line calls-loop
|
|
540
545
|
if (JB721TiersRulesetMetadataResolver.mintPendingReservesPaused(
|
|
541
546
|
(JBRulesetMetadataResolver.metadata(rulesets.currentOf(PROJECT_ID)))
|
|
542
547
|
)) revert DefifaHook_ReservedTokenMintingPaused();
|
|
543
548
|
|
|
544
549
|
// Keep a reference to the reserved token beneficiary.
|
|
550
|
+
// slither-disable-next-line calls-loop
|
|
545
551
|
address reservedTokenBeneficiary = store.reserveBeneficiaryOf({hook: address(this), tierId: tierId});
|
|
546
552
|
|
|
547
553
|
// Get a reference to the old delegate.
|
|
@@ -559,12 +565,14 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
559
565
|
}
|
|
560
566
|
|
|
561
567
|
// Record the minted reserves for the tier.
|
|
568
|
+
// slither-disable-next-line calls-loop
|
|
562
569
|
uint256[] memory tokenIds = store.recordMintReservesFor({tierId: tierId, count: count});
|
|
563
570
|
|
|
564
571
|
// Keep a reference to the token ID being iterated on.
|
|
565
572
|
uint256 tokenId;
|
|
566
573
|
|
|
567
574
|
// Fetch the tier details (needed for votingUnits below).
|
|
575
|
+
// slither-disable-next-line calls-loop
|
|
568
576
|
JB721Tier memory tier = store.tierOf({hook: address(this), id: tierId, includeResolvedUri: false});
|
|
569
577
|
|
|
570
578
|
// Increment _totalMintCost so reserved recipients can claim their share of fee tokens ($DEFIFA/$NANA).
|
|
@@ -579,6 +587,7 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
579
587
|
tokenId = tokenIds[i];
|
|
580
588
|
|
|
581
589
|
// Mint the token.
|
|
590
|
+
// slither-disable-next-line reentrancy-no-eth
|
|
582
591
|
_mint({to: reservedTokenBeneficiary, tokenId: tokenId});
|
|
583
592
|
|
|
584
593
|
emit MintReservedToken(tokenId, tierId, reservedTokenBeneficiary, msg.sender);
|
|
@@ -589,6 +598,7 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
589
598
|
}
|
|
590
599
|
|
|
591
600
|
// Transfer the attestation units to the delegate.
|
|
601
|
+
// slither-disable-next-line reentrancy-no-eth
|
|
592
602
|
_transferTierAttestationUnits({
|
|
593
603
|
from: address(0), to: reservedTokenBeneficiary, tierId: tierId, amount: tier.votingUnits * tokenIds.length
|
|
594
604
|
});
|
|
@@ -602,7 +612,6 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
602
612
|
/// `context.beneficiary`. Part of `IJBCashOutHook`.
|
|
603
613
|
/// @dev Reverts if the calling contract is not one of the project's terminals.
|
|
604
614
|
/// @param context The cash out context passed in by the terminal.
|
|
605
|
-
// slither-disable-next-line locked-ether,reentrancy-no-eth
|
|
606
615
|
function afterCashOutRecordedWith(JBAfterCashOutRecordedContext calldata context)
|
|
607
616
|
external
|
|
608
617
|
payable
|
|
@@ -654,6 +663,7 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
654
663
|
|
|
655
664
|
if (isComplete) {
|
|
656
665
|
unchecked {
|
|
666
|
+
// slither-disable-next-line reentrancy-no-eth,calls-loop
|
|
657
667
|
++tokensRedeemedFrom[store.tierIdOfToken(tokenId)];
|
|
658
668
|
}
|
|
659
669
|
}
|
|
@@ -674,6 +684,7 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
674
684
|
// Claim the $DEFIFA and $NANA tokens for the user.
|
|
675
685
|
// Include pending reserve mint cost in the denominator so that unminted reserves
|
|
676
686
|
// are accounted for, preventing paid holders from claiming a disproportionate share.
|
|
687
|
+
// slither-disable-next-line reentrancy-events
|
|
677
688
|
beneficiaryReceivedTokens = _claimTokensFor({
|
|
678
689
|
beneficiary: context.holder,
|
|
679
690
|
shareToBeneficiary: cumulativeMintPrice,
|
|
@@ -795,8 +806,10 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
795
806
|
|
|
796
807
|
for (uint256 i; i < numberOfTiers;) {
|
|
797
808
|
uint256 tierId = i + 1;
|
|
809
|
+
// slither-disable-next-line calls-loop
|
|
798
810
|
uint256 pendingReserves = _store.numberOfPendingReservesFor({hook: hook, tierId: tierId});
|
|
799
811
|
if (pendingReserves != 0) {
|
|
812
|
+
// slither-disable-next-line calls-loop
|
|
800
813
|
JB721Tier memory tier = _store.tierOf({hook: hook, id: tierId, includeResolvedUri: false});
|
|
801
814
|
cost += pendingReserves * tier.price;
|
|
802
815
|
}
|
|
@@ -862,6 +875,7 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
862
875
|
/// @param tierId The ID of the tier to get attestation units for.
|
|
863
876
|
/// @return The attestation units.
|
|
864
877
|
function _getTierAttestationUnits(address account, uint256 tierId) internal view virtual returns (uint256) {
|
|
878
|
+
// slither-disable-next-line calls-loop
|
|
865
879
|
return store.tierVotingUnitsOf({hook: address(this), account: account, tierId: tierId});
|
|
866
880
|
}
|
|
867
881
|
|
|
@@ -882,7 +896,7 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
882
896
|
uint256[] memory tokenIds;
|
|
883
897
|
|
|
884
898
|
// Record the mint. The returned token IDs correspond to the tiers passed in.
|
|
885
|
-
// slither-disable-next-line reentrancy-benign
|
|
899
|
+
// slither-disable-next-line unused-return,reentrancy-benign
|
|
886
900
|
(tokenIds, leftoverAmount,) = store.recordMint({
|
|
887
901
|
amount: amount,
|
|
888
902
|
tierIds: mintTierIds,
|
|
@@ -994,8 +1008,12 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
994
1008
|
|
|
995
1009
|
// If there's either a new delegate or old delegate, set delegation and transfer units.
|
|
996
1010
|
if (attestationDelegate != address(0) || oldDelegate != address(0)) {
|
|
997
|
-
//
|
|
998
|
-
|
|
1011
|
+
// Delegation is beneficiary-owned state. A third-party payer can fund this mint, but
|
|
1012
|
+
// cannot overwrite the beneficiary's long-lived delegate preference through metadata.
|
|
1013
|
+
if (
|
|
1014
|
+
context.payer == context.beneficiary && attestationDelegate != address(0)
|
|
1015
|
+
&& attestationDelegate != oldDelegate
|
|
1016
|
+
) {
|
|
999
1017
|
_delegateTier({account: context.beneficiary, delegatee: attestationDelegate, tierId: tierId});
|
|
1000
1018
|
}
|
|
1001
1019
|
|
|
@@ -1083,8 +1101,9 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
1083
1101
|
// Transfers must not be paused (when not minting or burning).
|
|
1084
1102
|
if (from != address(0)) {
|
|
1085
1103
|
// If transfers are pausable, check if they're paused.
|
|
1086
|
-
if (tier.transfersPausable) {
|
|
1104
|
+
if (tier.flags.transfersPausable) {
|
|
1087
1105
|
// Get a reference to the project's current ruleset.
|
|
1106
|
+
// slither-disable-next-line calls-loop
|
|
1088
1107
|
JBRuleset memory ruleset = rulesets.currentOf(PROJECT_ID);
|
|
1089
1108
|
|
|
1090
1109
|
// If transfers are paused and the NFT isn't being transferred to the zero address, revert.
|
|
@@ -1101,14 +1120,15 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
1101
1120
|
if (_firstOwnerOf[tokenId] == address(0)) _firstOwnerOf[tokenId] = from;
|
|
1102
1121
|
}
|
|
1103
1122
|
|
|
1104
|
-
// Record the transfer.
|
|
1105
|
-
// slither-disable-next-line reentrancy-events,calls-loop
|
|
1106
|
-
store.recordTransferForTier({tierId: tier.id, from: from, to: to});
|
|
1107
|
-
|
|
1108
1123
|
// Dont transfer on mint since the delegation will be transferred more efficiently in _processPayment.
|
|
1109
|
-
if (from
|
|
1124
|
+
if (from != address(0)) {
|
|
1125
|
+
_transferTierAttestationUnits({from: from, to: to, tierId: tier.id, amount: tier.votingUnits});
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
// Record the transfer after local delegation state has been finalized.
|
|
1129
|
+
// slither-disable-next-line calls-loop
|
|
1130
|
+
store.recordTransferForTier({tierId: tier.id, from: from, to: to});
|
|
1110
1131
|
|
|
1111
|
-
|
|
1112
|
-
_transferTierAttestationUnits({from: from, to: to, tierId: tier.id, amount: tier.votingUnits});
|
|
1132
|
+
return from;
|
|
1113
1133
|
}
|
|
1114
1134
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
pragma solidity ^0.8.0;
|
|
3
3
|
|
|
4
|
-
import {IJB721TokenUriResolver} from "@bananapus/721-hook-v6/src/interfaces/IJB721TokenUriResolver.sol";
|
|
5
4
|
import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
|
|
5
|
+
import {IJB721TokenUriResolver} from "@bananapus/721-hook-v6/src/interfaces/IJB721TokenUriResolver.sol";
|
|
6
6
|
import {IJBController} from "@bananapus/core-v6/src/interfaces/IJBController.sol";
|
|
7
7
|
import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
|
|
8
8
|
|
|
@@ -13,12 +13,30 @@ import {IDefifaHook} from "./IDefifaHook.sol";
|
|
|
13
13
|
/// @notice Deploys and manages Defifa prediction games, including lifecycle phase transitions
|
|
14
14
|
/// and commitment fulfillment.
|
|
15
15
|
interface IDefifaDeployer {
|
|
16
|
+
/// @notice Emitted when a commitment payout fails during fulfillment.
|
|
17
|
+
/// @param gameId The ID of the game being fulfilled.
|
|
18
|
+
/// @param amount The amount that failed to pay out.
|
|
19
|
+
/// @param reason The revert reason bytes from the failed payout.
|
|
16
20
|
event CommitmentPayoutFailed(uint256 indexed gameId, uint256 amount, bytes reason);
|
|
17
21
|
|
|
22
|
+
/// @notice Emitted when a split receives a portion of the game pot.
|
|
23
|
+
/// @param split The split that received funds.
|
|
24
|
+
/// @param amount The amount sent to the split.
|
|
25
|
+
/// @param caller The address that triggered the distribution.
|
|
18
26
|
event DistributeToSplit(JBSplit split, uint256 amount, address caller);
|
|
19
27
|
|
|
28
|
+
/// @notice Emitted when a game's commitments have been fulfilled.
|
|
29
|
+
/// @param gameId The ID of the fulfilled game.
|
|
30
|
+
/// @param pot The total game pot that was fulfilled.
|
|
31
|
+
/// @param caller The address that triggered fulfillment.
|
|
20
32
|
event FulfilledCommitments(uint256 indexed gameId, uint256 pot, address caller);
|
|
21
33
|
|
|
34
|
+
/// @notice Emitted when a new Defifa game is launched.
|
|
35
|
+
/// @param gameId The ID of the launched game.
|
|
36
|
+
/// @param hook The hook deployed for the game.
|
|
37
|
+
/// @param governor The governor responsible for scorecard ratification.
|
|
38
|
+
/// @param tokenUriResolver The token URI resolver used for the game's NFTs.
|
|
39
|
+
/// @param caller The address that launched the game.
|
|
22
40
|
event LaunchGame(
|
|
23
41
|
uint256 indexed gameId,
|
|
24
42
|
IDefifaHook indexed hook,
|
|
@@ -27,10 +45,19 @@ interface IDefifaDeployer {
|
|
|
27
45
|
address caller
|
|
28
46
|
);
|
|
29
47
|
|
|
48
|
+
/// @notice Emitted when a game is queued into its no-contest phase.
|
|
49
|
+
/// @param gameId The ID of the game.
|
|
50
|
+
/// @param caller The address that queued the phase transition.
|
|
30
51
|
event QueuedNoContest(uint256 indexed gameId, address caller);
|
|
31
52
|
|
|
53
|
+
/// @notice Emitted when a game is queued into its refund phase.
|
|
54
|
+
/// @param gameId The ID of the game.
|
|
55
|
+
/// @param caller The address that queued the phase transition.
|
|
32
56
|
event QueuedRefundPhase(uint256 indexed gameId, address caller);
|
|
33
57
|
|
|
58
|
+
/// @notice Emitted when a game is queued into its scoring phase.
|
|
59
|
+
/// @param gameId The ID of the game.
|
|
60
|
+
/// @param caller The address that queued the phase transition.
|
|
34
61
|
event QueuedScoringPhase(uint256 indexed gameId, address caller);
|
|
35
62
|
|
|
36
63
|
/// @notice The fee divisor for base protocol fees (100 / fee percent).
|