@ballkidz/defifa 0.0.7 → 0.0.8

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.
Files changed (46) hide show
  1. package/ADMINISTRATION.md +3 -3
  2. package/AUDIT_INSTRUCTIONS.md +422 -0
  3. package/CRYPTO_ECON.md +5 -5
  4. package/RISKS.md +38 -335
  5. package/SKILLS.md +1 -1
  6. package/USER_JOURNEYS.md +691 -0
  7. package/package.json +7 -7
  8. package/script/Deploy.s.sol +14 -3
  9. package/script/helpers/DefifaDeploymentLib.sol +13 -15
  10. package/src/DefifaDeployer.sol +221 -192
  11. package/src/DefifaGovernor.sol +286 -276
  12. package/src/DefifaHook.sol +65 -32
  13. package/src/DefifaProjectOwner.sol +27 -4
  14. package/src/DefifaTokenUriResolver.sol +136 -134
  15. package/src/enums/DefifaGamePhase.sol +1 -1
  16. package/src/enums/DefifaScorecardState.sol +1 -1
  17. package/src/interfaces/IDefifaDeployer.sol +52 -50
  18. package/src/interfaces/IDefifaGamePhaseReporter.sol +2 -2
  19. package/src/interfaces/IDefifaGamePotReporter.sol +1 -1
  20. package/src/interfaces/IDefifaGovernor.sol +53 -54
  21. package/src/interfaces/IDefifaHook.sol +104 -103
  22. package/src/interfaces/IDefifaTokenUriResolver.sol +2 -2
  23. package/src/libraries/DefifaFontImporter.sol +11 -9
  24. package/src/libraries/DefifaHookLib.sol +66 -53
  25. package/src/structs/DefifaAttestations.sol +1 -1
  26. package/src/structs/DefifaDelegation.sol +1 -1
  27. package/src/structs/DefifaLaunchProjectData.sol +4 -4
  28. package/src/structs/DefifaOpsData.sol +1 -1
  29. package/src/structs/DefifaScorecard.sol +1 -1
  30. package/src/structs/DefifaTierCashOutWeight.sol +1 -1
  31. package/src/structs/DefifaTierParams.sol +2 -1
  32. package/test/DefifaAdversarialQuorum.t.sol +602 -0
  33. package/test/DefifaAuditLowGuards.t.sol +304 -0
  34. package/test/DefifaFeeAccounting.t.sol +37 -16
  35. package/test/DefifaGovernor.t.sol +37 -11
  36. package/test/DefifaHookRegressions.t.sol +14 -12
  37. package/test/DefifaMintCostInvariant.t.sol +31 -12
  38. package/test/DefifaNoContest.t.sol +33 -13
  39. package/test/DefifaSecurity.t.sol +45 -25
  40. package/test/DefifaUSDC.t.sol +44 -34
  41. package/test/Fork.t.sol +42 -40
  42. package/test/SVG.t.sol +2 -2
  43. package/test/TestAuditGaps.sol +982 -0
  44. package/test/TestQALastMile.t.sol +511 -0
  45. package/test/regression/FulfillmentBlocksRatification.t.sol +36 -30
  46. package/test/regression/GracePeriodBypass.t.sol +15 -10
@@ -36,7 +36,6 @@ import {DefifaTierCashOutWeight} from "./structs/DefifaTierCashOutWeight.sol";
36
36
  import {DefifaGamePhase} from "./enums/DefifaGamePhase.sol";
37
37
  import {DefifaHookLib} from "./libraries/DefifaHookLib.sol";
38
38
 
39
- /// @title DefifaHook
40
39
  /// @notice A hook that transforms Juicebox treasury interactions into a Defifa game.
41
40
  contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
42
41
  using Checkpoints for Checkpoints.Trace208;
@@ -106,17 +105,17 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
106
105
  //*********************************************************************//
107
106
 
108
107
  /// @notice The $DEFIFA token that is expected to be issued from paying fees.
109
- IERC20 public immutable override defifaToken;
108
+ IERC20 public immutable override DEFIFA_TOKEN;
110
109
 
111
110
  /// @notice The $BASE_PROTOCOL token that is expected to be issued from paying fees.
112
- IERC20 public immutable override baseProtocolToken;
111
+ IERC20 public immutable override BASE_PROTOCOL_TOKEN;
113
112
 
114
113
  //*********************************************************************//
115
114
  // --------------------- public stored properties -------------------- //
116
115
  //*********************************************************************//
117
116
 
118
117
  /// @notice The address of the origin 'DefifaHook', used to check in the init if the contract is the original or not
119
- address public immutable override codeOrigin;
118
+ address public immutable override CODE_ORIGIN;
120
119
 
121
120
  /// @notice The contract that stores and manages the NFT's data.
122
121
  IJB721TiersHookStore public override store;
@@ -267,7 +266,7 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
267
266
 
268
267
  // Fetch the cash out hook metadata using the corresponding metadata ID.
269
268
  (bool metadataExists, bytes memory metadata) = JBMetadataResolver.getDataFor({
270
- id: JBMetadataResolver.getId({purpose: "cashOut", target: codeOrigin}), metadata: context.metadata
269
+ id: JBMetadataResolver.getId({purpose: "cashOut", target: CODE_ORIGIN}), metadata: context.metadata
271
270
  });
272
271
 
273
272
  uint256[] memory decodedTokenIds;
@@ -279,8 +278,9 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
279
278
  DefifaGamePhase _gamePhase = gamePhaseReporter.currentGamePhaseOf(context.projectId);
280
279
 
281
280
  // Calculate the amount paid to mint the tokens that are being burned.
282
- uint256 _cumulativeMintPrice =
283
- DefifaHookLib.computeCumulativeMintPrice({tokenIds: decodedTokenIds, _store: store, hook: address(this)});
281
+ uint256 _cumulativeMintPrice = DefifaHookLib.computeCumulativeMintPrice({
282
+ tokenIds: decodedTokenIds, hookStore: store, hook: address(this)
283
+ });
284
284
 
285
285
  // Use this contract as the only cash out hook.
286
286
  hookSpecifications = new JBCashOutHookSpecification[](1);
@@ -292,7 +292,7 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
292
292
  gamePhase: _gamePhase,
293
293
  cumulativeMintPrice: _cumulativeMintPrice,
294
294
  surplusValue: context.surplus.value,
295
- _amountRedeemed: amountRedeemed,
295
+ totalAmountRedeemed: amountRedeemed,
296
296
  cumulativeCashOutWeight: cashOutWeightOf(decodedTokenIds)
297
297
  });
298
298
 
@@ -315,7 +315,7 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
315
315
  {
316
316
  cumulativeWeight = DefifaHookLib.computeCashOutWeightBatch({
317
317
  tokenIds: tokenIds,
318
- _store: store,
318
+ hookStore: store,
319
319
  hook: address(this),
320
320
  tierCashOutWeights: _tierCashOutWeights,
321
321
  tokensRedeemedFrom: tokensRedeemedFrom
@@ -328,7 +328,7 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
328
328
  function cashOutWeightOf(uint256 tokenId) public view override returns (uint256) {
329
329
  return DefifaHookLib.computeCashOutWeight({
330
330
  tokenId: tokenId,
331
- _store: store,
331
+ hookStore: store,
332
332
  hook: address(this),
333
333
  tierCashOutWeights: _tierCashOutWeights,
334
334
  tokensRedeemedFrom: tokensRedeemedFrom
@@ -338,7 +338,7 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
338
338
  /// @notice The amount of tokens of a tier that are currently in circulation.
339
339
  /// @param tierId The ID of the tier to get the current supply of.
340
340
  function currentSupplyOfTier(uint256 tierId) public view returns (uint256) {
341
- return DefifaHookLib.computeCurrentSupply({_store: store, hook: address(this), tierId: tierId});
341
+ return DefifaHookLib.computeCurrentSupply({hookStore: store, hook: address(this), tierId: tierId});
342
342
  }
343
343
 
344
344
  /// @notice Indicates if this contract adheres to the specified interface.
@@ -356,8 +356,8 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
356
356
  view
357
357
  returns (uint256 defifaTokenAllocation, uint256 baseProtocolTokenAllocation)
358
358
  {
359
- defifaTokenAllocation = defifaToken.balanceOf(address(this));
360
- baseProtocolTokenAllocation = baseProtocolToken.balanceOf(address(this));
359
+ defifaTokenAllocation = DEFIFA_TOKEN.balanceOf(address(this));
360
+ baseProtocolTokenAllocation = BASE_PROTOCOL_TOKEN.balanceOf(address(this));
361
361
  }
362
362
 
363
363
  /// @notice The metadata URI of the provided token ID.
@@ -384,11 +384,11 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
384
384
  // slither-disable-next-line unused-return
385
385
  return DefifaHookLib.computeTokensClaim({
386
386
  tokenIds: tokenIds,
387
- _store: store,
387
+ hookStore: store,
388
388
  hook: address(this),
389
389
  totalMintCost: _totalMintCost,
390
- defifaBalance: defifaToken.balanceOf(address(this)),
391
- baseProtocolBalance: baseProtocolToken.balanceOf(address(this))
390
+ defifaBalance: DEFIFA_TOKEN.balanceOf(address(this)),
391
+ baseProtocolBalance: BASE_PROTOCOL_TOKEN.balanceOf(address(this))
392
392
  });
393
393
  }
394
394
 
@@ -412,9 +412,9 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
412
412
  JB721Hook(_directory)
413
413
  Ownable(msg.sender)
414
414
  {
415
- codeOrigin = address(this);
416
- defifaToken = _defifaToken;
417
- baseProtocolToken = _baseProtocolToken;
415
+ CODE_ORIGIN = address(this);
416
+ DEFIFA_TOKEN = _defifaToken;
417
+ BASE_PROTOCOL_TOKEN = _baseProtocolToken;
418
418
  }
419
419
 
420
420
  //*********************************************************************//
@@ -483,7 +483,7 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
483
483
  override
484
484
  {
485
485
  // Make the original un-initializable.
486
- if (address(this) == codeOrigin) revert();
486
+ if (address(this) == CODE_ORIGIN) revert();
487
487
 
488
488
  // Stop re-initialization.
489
489
  if (address(store) != address(0)) revert();
@@ -568,6 +568,9 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
568
568
  JB721Tier memory _tier = store.tierOf({hook: address(this), id: tierId, includeResolvedUri: false});
569
569
 
570
570
  // Increment _totalMintCost so reserved recipients can claim their share of fee tokens ($DEFIFA/$NANA).
571
+ // Note: reserved mints dilute existing fee token claimants because they increase the total mint cost
572
+ // denominator without contributing new funds to the fee token balances. This is the intended design —
573
+ // reserved recipients receive a proportional claim on fee tokens as if they had paid to mint.
571
574
  _totalMintCost += _tier.price * count;
572
575
 
573
576
  for (uint256 _i; _i < count;) {
@@ -676,6 +679,9 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
676
679
  }
677
680
 
678
681
  // If there's nothing being claimed and we did not distribute fee tokens, revert to prevent burning for nothing.
682
+ // Tokens in 0-weight tiers (losing teams) cannot burn to reclaim fees if no fee tokens were
683
+ // distributed. This is correct behavior — 0-weight means the tier has no claim on the pot. Burning would
684
+ // return 0 value regardless.
679
685
  if (context.reclaimedAmount.value == 0 && !_beneficiaryReceivedTokens) revert DefifaHook_NothingToClaim();
680
686
 
681
687
  // Decrement the paid mint cost by the cumulative mint price of the tokens being burned.
@@ -718,7 +724,7 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
718
724
 
719
725
  // Validate weights and build the array. Reverts on invalid input.
720
726
  _tierCashOutWeights =
721
- DefifaHookLib.validateAndBuildWeights({tierWeights: tierWeights, _store: store, hook: address(this)});
727
+ DefifaHookLib.validateAndBuildWeights({tierWeights: tierWeights, hookStore: store, hook: address(this)});
722
728
 
723
729
  // Mark the cashOut weight as set.
724
730
  cashOutWeightIsSet = true;
@@ -730,6 +736,9 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
730
736
  /// @param delegatee The account to delegate tier attestation units to.
731
737
  /// @param tierId The ID of the tier to delegate attestation units for.
732
738
  function setTierDelegateTo(address delegatee, uint256 tierId) public virtual override {
739
+ // Make sure a delegate is specified.
740
+ if (delegatee == address(0)) revert DefifaHook_DelegateAddressZero();
741
+
733
742
  // Make sure the current game phase is the minting phase.
734
743
  if (gamePhaseReporter.currentGamePhaseOf(PROJECT_ID) != DefifaGamePhase.MINT) {
735
744
  revert DefifaHook_DelegateChangesUnavailableInThisPhase();
@@ -785,11 +794,11 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
785
794
  returns (bool beneficiaryReceivedTokens)
786
795
  {
787
796
  return DefifaHookLib.claimTokensFor({
788
- _beneficiary: _beneficiary,
797
+ beneficiary: _beneficiary,
789
798
  shareToBeneficiary: shareToBeneficiary,
790
799
  outOfTotal: outOfTotal,
791
- _defifaToken: defifaToken,
792
- _baseProtocolToken: baseProtocolToken
800
+ defifaToken: DEFIFA_TOKEN,
801
+ baseProtocolToken: BASE_PROTOCOL_TOKEN
793
802
  });
794
803
  }
795
804
 
@@ -847,6 +856,7 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
847
856
  uint256[] memory _tokenIds;
848
857
 
849
858
  // Record the mint. The returned token IDs correspond to the tiers passed in.
859
+ // slither-disable-next-line reentrancy-benign
850
860
  (_tokenIds, leftoverAmount) = store.recordMint({
851
861
  amount: _amount,
852
862
  tierIds: _mintTierIds,
@@ -892,8 +902,14 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
892
902
  // Get the current amount for the sending delegate.
893
903
  uint208 _current = _delegateTierCheckpoints[_from][_tierId].latest();
894
904
  // Set the new amount for the sending delegate.
905
+ // uint208 is sufficient for attestation values: each tier's attestation units are bounded by the NFT
906
+ // supply (max ~999_999_999 per tier * 128 tiers), well within uint208's ~4.1e62 range.
907
+ // forge-lint: disable-next-line(unsafe-typecast)
895
908
  (uint256 _oldValue, uint256 _newValue) = _delegateTierCheckpoints[_from][_tierId].push({
896
- key: uint48(block.timestamp), value: _current - uint208(_amount)
909
+ // forge-lint: disable-next-line(unsafe-typecast)
910
+ key: uint48(block.timestamp),
911
+ // forge-lint: disable-next-line(unsafe-typecast)
912
+ value: _current - uint208(_amount)
897
913
  });
898
914
  emit TierDelegateAttestationsChanged(_from, _tierId, _oldValue, _newValue, msg.sender);
899
915
  }
@@ -903,8 +919,12 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
903
919
  // Get the current amount for the receiving delegate.
904
920
  uint208 _current = _delegateTierCheckpoints[_to][_tierId].latest();
905
921
  // Set the new amount for the receiving delegate.
922
+ // forge-lint: disable-next-line(unsafe-typecast)
906
923
  (uint256 _oldValue, uint256 _newValue) = _delegateTierCheckpoints[_to][_tierId].push({
907
- key: uint48(block.timestamp), value: _current + uint208(_amount)
924
+ // forge-lint: disable-next-line(unsafe-typecast)
925
+ key: uint48(block.timestamp),
926
+ // forge-lint: disable-next-line(unsafe-typecast)
927
+ value: _current + uint208(_amount)
908
928
  });
909
929
  emit TierDelegateAttestationsChanged(_to, _tierId, _oldValue, _newValue, msg.sender);
910
930
  }
@@ -918,7 +938,7 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
918
938
 
919
939
  // Resolve the metadata.
920
940
  (bool found, bytes memory metadata) = JBMetadataResolver.getDataFor({
921
- id: JBMetadataResolver.getId({purpose: "pay", target: codeOrigin}), metadata: context.payerMetadata
941
+ id: JBMetadataResolver.getId({purpose: "pay", target: CODE_ORIGIN}), metadata: context.payerMetadata
922
942
  });
923
943
 
924
944
  if (!found) revert DefifaHook_NothingToMint();
@@ -935,8 +955,9 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
935
955
  if (_tierIdsToMint.length == 0) revert DefifaHook_NothingToMint();
936
956
 
937
957
  // Compute attestation units per unique tier (validates ascending order, reverts on bad order).
938
- (uint256[] memory _tierIds, uint256[] memory _attestationAmounts, uint256 _uniqueTierCount) =
939
- DefifaHookLib.computeAttestationUnits({_tierIdsToMint: _tierIdsToMint, _store: store, hook: address(this)});
958
+ (uint256[] memory _tierIds, uint256[] memory _attestationAmounts, uint256 _uniqueTierCount) = DefifaHookLib.computeAttestationUnits({
959
+ tierIdsToMint: _tierIdsToMint, hookStore: store, hook: address(this)
960
+ });
940
961
 
941
962
  // Apply attestation units for each unique tier.
942
963
  for (uint256 _i; _i < _uniqueTierCount;) {
@@ -992,19 +1013,31 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
992
1013
 
993
1014
  // If minting, add to the total tier checkpoints.
994
1015
  if (_from == address(0)) {
1016
+ // Casting to uint208/uint48 is safe because attestation unit amounts are bounded by NFT supply counts.
1017
+ // forge-lint: disable-next-line(unsafe-typecast)
1018
+ uint208 newValue = _current + uint208(_amount);
1019
+ // forge-lint: disable-next-line(unsafe-typecast)
995
1020
  // slither-disable-next-line unused-return
996
- _totalTierCheckpoints[_tierId].push({key: uint48(block.timestamp), value: _current + uint208(_amount)});
1021
+ _totalTierCheckpoints[_tierId].push({key: uint48(block.timestamp), value: newValue});
997
1022
  }
998
1023
 
999
1024
  // If burning, subtract from the total tier checkpoints.
1000
1025
  if (_to == address(0)) {
1026
+ // Casting to uint208/uint48 is safe because attestation unit amounts are bounded by NFT supply counts.
1027
+ // forge-lint: disable-next-line(unsafe-typecast)
1028
+ uint208 newValue = _current - uint208(_amount);
1029
+ // forge-lint: disable-next-line(unsafe-typecast)
1001
1030
  // slither-disable-next-line unused-return
1002
- _totalTierCheckpoints[_tierId].push({key: uint48(block.timestamp), value: _current - uint208(_amount)});
1031
+ _totalTierCheckpoints[_tierId].push({key: uint48(block.timestamp), value: newValue});
1003
1032
  }
1004
1033
  }
1005
1034
 
1006
1035
  // Resolve the recipient's delegate. If the recipient has no delegate set, auto-delegate to themselves to
1007
1036
  // prevent attestation units from being permanently lost.
1037
+ // Note: delegation persists after token transfers. If Alice delegates to Bob, then transfers her token
1038
+ // to Carol, Carol's attestation units auto-delegate to Carol (not Bob). However, Alice's delegation
1039
+ // to Bob persists — if Alice later receives another token, her units still go to Bob. This matches
1040
+ // ERC5805Votes behavior where delegation is an account-level setting, not a token-level one.
1008
1041
  address _toDelegate = _tierDelegation[_to][_tierId];
1009
1042
  if (_toDelegate == address(0) && _to != address(0)) {
1010
1043
  _toDelegate = _to;
@@ -1051,7 +1084,7 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
1051
1084
  }
1052
1085
 
1053
1086
  // Record the transfer.
1054
- // slither-disable-next-line reentrency-events,calls-loop
1087
+ // slither-disable-next-line reentrancy-events,calls-loop
1055
1088
  store.recordTransferForTier({tierId: tier.id, from: from, to: to});
1056
1089
 
1057
1090
  // Dont transfer on mint since the delegation will be transferred more efficiently in _processPayment.
@@ -1,16 +1,27 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity 0.8.26;
3
3
 
4
- import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
5
- import {DefifaDeployer} from "./DefifaDeployer.sol";
6
4
  import {IJBPermissions, JBPermissionsData} from "@bananapus/core-v6/src/interfaces/IJBPermissions.sol";
7
5
  import {IJBProjects} from "@bananapus/core-v6/src/interfaces/IJBProjects.sol";
8
6
  import {JBPermissionIds} from "@bananapus/permission-ids-v6/src/JBPermissionIds.sol";
7
+ import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
8
+
9
+ import {DefifaDeployer} from "./DefifaDeployer.sol";
9
10
 
10
11
  /// @notice A contract that can be sent a project to be burned, while still allowing defifa permissions.
11
12
  /// @dev Once the project NFT is transferred here, it cannot be recovered. This contract permanently
12
13
  /// holds the project NFT and grants SET_SPLIT_GROUPS permission to the Defifa deployer.
13
14
  contract DefifaProjectOwner is IERC721Receiver {
15
+ //*********************************************************************//
16
+ // --------------------------- custom errors ------------------------- //
17
+ //*********************************************************************//
18
+
19
+ error DefifaProjectOwner_InvalidSender();
20
+
21
+ //*********************************************************************//
22
+ // --------------- public immutable stored properties ---------------- //
23
+ //*********************************************************************//
24
+
14
25
  /// @notice The contract where operator permissions are stored.
15
26
  IJBPermissions public immutable PERMISSIONS;
16
27
 
@@ -20,6 +31,10 @@ contract DefifaProjectOwner is IERC721Receiver {
20
31
  /// @notice The Defifa deployer.
21
32
  DefifaDeployer public immutable DEPLOYER;
22
33
 
34
+ //*********************************************************************//
35
+ // -------------------------- constructor ---------------------------- //
36
+ //*********************************************************************//
37
+
23
38
  /// @param permissions The contract where operator permissions are stored.
24
39
  /// @param projects The contract from which projects are minted.
25
40
  /// @param deployer The Defifa deployer which will receive permissions to set splits.
@@ -29,6 +44,10 @@ contract DefifaProjectOwner is IERC721Receiver {
29
44
  DEPLOYER = deployer;
30
45
  }
31
46
 
47
+ //*********************************************************************//
48
+ // ---------------------- external transactions ---------------------- //
49
+ //*********************************************************************//
50
+
32
51
  /// @notice Give the defifa deployer permission to set splits on this contract's behalf.
33
52
  function onERC721Received(
34
53
  address operator,
@@ -44,7 +63,7 @@ contract DefifaProjectOwner is IERC721Receiver {
44
63
  operator;
45
64
 
46
65
  // Make sure the 721 received is the JBProjects contract.
47
- if (msg.sender != address(PROJECTS)) revert();
66
+ if (msg.sender != address(PROJECTS)) revert DefifaProjectOwner_InvalidSender();
48
67
 
49
68
  // Set the correct permission.
50
69
  uint8[] memory permissionIds = new uint8[](1);
@@ -54,7 +73,11 @@ contract DefifaProjectOwner is IERC721Receiver {
54
73
  PERMISSIONS.setPermissionsFor({
55
74
  account: address(this),
56
75
  permissionsData: JBPermissionsData({
57
- operator: address(DEPLOYER), projectId: uint64(tokenId), permissionIds: permissionIds
76
+ // Casting to uint64 is safe because Juicebox project IDs are sequential and will not exceed uint64.
77
+ operator: address(DEPLOYER),
78
+ // forge-lint: disable-next-line(unsafe-typecast)
79
+ projectId: uint64(tokenId),
80
+ permissionIds: permissionIds
58
81
  })
59
82
  });
60
83