@bananapus/721-hook-v6 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.
Files changed (53) hide show
  1. package/ARCHITECTURE.md +4 -0
  2. package/CHANGE_LOG.md +47 -11
  3. package/README.md +19 -9
  4. package/RISKS.md +4 -0
  5. package/USER_JOURNEYS.md +4 -0
  6. package/package.json +2 -2
  7. package/src/JB721TiersHook.sol +87 -85
  8. package/src/abstract/JB721Hook.sol +2 -2
  9. package/src/libraries/JB721TiersHookLib.sol +4 -8
  10. package/test/TestAuditGaps.sol +147 -0
  11. package/test/fork/ERC20CashOutFork.t.sol +612 -0
  12. package/test/fork/IssueTokensForSplitsFork.t.sol +504 -0
  13. package/docs/book.css +0 -13
  14. package/docs/book.toml +0 -12
  15. package/docs/solidity.min.js +0 -74
  16. package/docs/src/README.md +0 -253
  17. package/docs/src/SUMMARY.md +0 -38
  18. package/docs/src/src/JB721TiersHook.sol/contract.JB721TiersHook.md +0 -645
  19. package/docs/src/src/JB721TiersHookDeployer.sol/contract.JB721TiersHookDeployer.md +0 -99
  20. package/docs/src/src/JB721TiersHookProjectDeployer.sol/contract.JB721TiersHookProjectDeployer.md +0 -288
  21. package/docs/src/src/JB721TiersHookStore.sol/contract.JB721TiersHookStore.md +0 -1096
  22. package/docs/src/src/README.md +0 -11
  23. package/docs/src/src/abstract/ERC721.sol/abstract.ERC721.md +0 -430
  24. package/docs/src/src/abstract/JB721Hook.sol/abstract.JB721Hook.md +0 -309
  25. package/docs/src/src/abstract/README.md +0 -5
  26. package/docs/src/src/interfaces/IJB721Hook.sol/interface.IJB721Hook.md +0 -29
  27. package/docs/src/src/interfaces/IJB721TiersHook.sol/interface.IJB721TiersHook.md +0 -203
  28. package/docs/src/src/interfaces/IJB721TiersHookDeployer.sol/interface.IJB721TiersHookDeployer.md +0 -25
  29. package/docs/src/src/interfaces/IJB721TiersHookProjectDeployer.sol/interface.IJB721TiersHookProjectDeployer.md +0 -64
  30. package/docs/src/src/interfaces/IJB721TiersHookStore.sol/interface.IJB721TiersHookStore.md +0 -265
  31. package/docs/src/src/interfaces/IJB721TokenUriResolver.sol/interface.IJB721TokenUriResolver.md +0 -12
  32. package/docs/src/src/interfaces/README.md +0 -9
  33. package/docs/src/src/libraries/JB721Constants.sol/library.JB721Constants.md +0 -14
  34. package/docs/src/src/libraries/JB721TiersRulesetMetadataResolver.sol/library.JB721TiersRulesetMetadataResolver.md +0 -68
  35. package/docs/src/src/libraries/JBBitmap.sol/library.JBBitmap.md +0 -82
  36. package/docs/src/src/libraries/JBIpfsDecoder.sol/library.JBIpfsDecoder.md +0 -61
  37. package/docs/src/src/libraries/README.md +0 -7
  38. package/docs/src/src/structs/JB721InitTiersConfig.sol/struct.JB721InitTiersConfig.md +0 -27
  39. package/docs/src/src/structs/JB721Tier.sol/struct.JB721Tier.md +0 -59
  40. package/docs/src/src/structs/JB721TierConfig.sol/struct.JB721TierConfig.md +0 -60
  41. package/docs/src/src/structs/JB721TiersHookFlags.sol/struct.JB721TiersHookFlags.md +0 -26
  42. package/docs/src/src/structs/JB721TiersMintReservesConfig.sol/struct.JB721TiersMintReservesConfig.md +0 -16
  43. package/docs/src/src/structs/JB721TiersRulesetMetadata.sol/struct.JB721TiersRulesetMetadata.md +0 -20
  44. package/docs/src/src/structs/JB721TiersSetDiscountPercentConfig.sol/struct.JB721TiersSetDiscountPercentConfig.md +0 -16
  45. package/docs/src/src/structs/JBBitmapWord.sol/struct.JBBitmapWord.md +0 -19
  46. package/docs/src/src/structs/JBDeploy721TiersHookConfig.sol/struct.JBDeploy721TiersHookConfig.md +0 -34
  47. package/docs/src/src/structs/JBLaunchProjectConfig.sol/struct.JBLaunchProjectConfig.md +0 -23
  48. package/docs/src/src/structs/JBLaunchRulesetsConfig.sol/struct.JBLaunchRulesetsConfig.md +0 -22
  49. package/docs/src/src/structs/JBPayDataHookRulesetConfig.sol/struct.JBPayDataHookRulesetConfig.md +0 -51
  50. package/docs/src/src/structs/JBPayDataHookRulesetMetadata.sol/struct.JBPayDataHookRulesetMetadata.md +0 -66
  51. package/docs/src/src/structs/JBQueueRulesetsConfig.sol/struct.JBQueueRulesetsConfig.md +0 -21
  52. package/docs/src/src/structs/JBStored721Tier.sol/struct.JBStored721Tier.md +0 -42
  53. package/docs/src/src/structs/README.md +0 -18
@@ -424,6 +424,7 @@ contract TestAuditGaps_GasLimits is UnitTestSetup {
424
424
 
425
425
  /// @dev The block gas limit on mainnet is 30M. We use a generous limit for safety.
426
426
  uint256 constant BLOCK_GAS_LIMIT = 30_000_000;
427
+ uint256 constant OPERATING_ENVELOPE_SOFT_LIMIT = 200;
427
428
 
428
429
  // ---------------------------------------------------------------
429
430
  // Test 1: Add 100 tiers in a single adjustTiers call
@@ -446,6 +447,7 @@ contract TestAuditGaps_GasLimits is UnitTestSetup {
446
447
  reserveBeneficiary: reserveBeneficiary,
447
448
  encodedIPFSUri: tokenUris[i % 10],
448
449
  // forge-lint: disable-next-line(unsafe-typecast)
450
+ // forge-lint: disable-next-line(unsafe-typecast)
449
451
  category: uint24(i + 1), // Ascending categories
450
452
  discountPercent: 0,
451
453
  allowOwnerMint: false,
@@ -498,6 +500,7 @@ contract TestAuditGaps_GasLimits is UnitTestSetup {
498
500
  reserveBeneficiary: reserveBeneficiary,
499
501
  encodedIPFSUri: tokenUris[i % 10],
500
502
  // forge-lint: disable-next-line(unsafe-typecast)
503
+ // forge-lint: disable-next-line(unsafe-typecast)
501
504
  category: uint24(i + 1),
502
505
  discountPercent: 0,
503
506
  allowOwnerMint: false,
@@ -552,6 +555,7 @@ contract TestAuditGaps_GasLimits is UnitTestSetup {
552
555
  reserveBeneficiary: reserveBeneficiary,
553
556
  encodedIPFSUri: tokenUris[i % 10],
554
557
  // forge-lint: disable-next-line(unsafe-typecast)
558
+ // forge-lint: disable-next-line(unsafe-typecast)
555
559
  category: uint24(i + 1),
556
560
  discountPercent: 0,
557
561
  allowOwnerMint: false,
@@ -579,6 +583,7 @@ contract TestAuditGaps_GasLimits is UnitTestSetup {
579
583
  uint16[] memory tierIdsToMint = new uint16[](10);
580
584
  uint256 totalCost;
581
585
  for (uint256 i; i < 10; i++) {
586
+ // forge-lint: disable-next-line(unsafe-typecast)
582
587
  // forge-lint: disable-next-line(unsafe-typecast)
583
588
  tierIdsToMint[i] = uint16(i + 1);
584
589
  totalCost += (i + 1) * 1e15;
@@ -651,6 +656,7 @@ contract TestAuditGaps_GasLimits is UnitTestSetup {
651
656
  reserveBeneficiary: reserveBeneficiary,
652
657
  encodedIPFSUri: tokenUris[i % 10],
653
658
  // forge-lint: disable-next-line(unsafe-typecast)
659
+ // forge-lint: disable-next-line(unsafe-typecast)
654
660
  category: uint24(i + 1),
655
661
  discountPercent: 0,
656
662
  allowOwnerMint: false,
@@ -699,6 +705,7 @@ contract TestAuditGaps_GasLimits is UnitTestSetup {
699
705
  reserveBeneficiary: reserveBeneficiary,
700
706
  encodedIPFSUri: tokenUris[i % 10],
701
707
  // forge-lint: disable-next-line(unsafe-typecast)
708
+ // forge-lint: disable-next-line(unsafe-typecast)
702
709
  category: uint24(i + 1),
703
710
  discountPercent: 0,
704
711
  allowOwnerMint: false,
@@ -759,6 +766,7 @@ contract TestAuditGaps_GasLimits is UnitTestSetup {
759
766
  reserveBeneficiary: reserveBeneficiary,
760
767
  encodedIPFSUri: tokenUris[i % 10],
761
768
  // forge-lint: disable-next-line(unsafe-typecast)
769
+ // forge-lint: disable-next-line(unsafe-typecast)
762
770
  category: uint24(i + 1),
763
771
  discountPercent: 0,
764
772
  allowOwnerMint: false,
@@ -830,6 +838,7 @@ contract TestAuditGaps_GasLimits is UnitTestSetup {
830
838
  reserveBeneficiary: reserveBeneficiary,
831
839
  encodedIPFSUri: tokenUris[i % 10],
832
840
  // forge-lint: disable-next-line(unsafe-typecast)
841
+ // forge-lint: disable-next-line(unsafe-typecast)
833
842
  category: uint24(i + 1),
834
843
  discountPercent: 0,
835
844
  allowOwnerMint: false,
@@ -856,6 +865,7 @@ contract TestAuditGaps_GasLimits is UnitTestSetup {
856
865
  uint16[] memory tierIdsToMint = new uint16[](50);
857
866
  uint256 totalCost;
858
867
  for (uint256 i; i < 50; i++) {
868
+ // forge-lint: disable-next-line(unsafe-typecast)
859
869
  // forge-lint: disable-next-line(unsafe-typecast)
860
870
  tierIdsToMint[i] = uint16(i + 1);
861
871
  totalCost += (i + 1) * 1e15;
@@ -900,4 +910,141 @@ contract TestAuditGaps_GasLimits is UnitTestSetup {
900
910
 
901
911
  emit log_named_uint("Gas used to mint from 50 tiers in single payment", gasUsed);
902
912
  }
913
+
914
+ /// @notice The expensive read paths scale with tier count, not just with the beneficiary's holdings.
915
+ /// This test exists to prove that a 100-tier catalog is materially more expensive than a 10-tier catalog even
916
+ /// when the queried user owns zero NFTs.
917
+ function test_operatingEnvelope_balanceOf_100tiersIsMateriallyMoreExpensiveThan10tiers() public {
918
+ uint256 gasFor10 = _measureBalanceOfGas({tierCount: 10});
919
+ uint256 gasFor100 = _measureBalanceOfGas({tierCount: 100});
920
+
921
+ assertGt(gasFor100, gasFor10 * 4, "100-tier balanceOf should be materially more expensive than 10 tiers");
922
+ emit log_named_uint("Gas used for balanceOf (10 tiers)", gasFor10);
923
+ emit log_named_uint("Gas used for balanceOf (100 tiers)", gasFor100);
924
+ }
925
+
926
+ /// @notice Cash-out accounting also scales with the catalog size because totalCashOutWeight walks the tier set.
927
+ /// We use a ratio check instead of an absolute snapshot so the test stays stable across compiler changes while
928
+ /// still proving the production-scale cost increase.
929
+ function test_operatingEnvelope_totalCashOutWeight_100tiersIsMateriallyMoreExpensiveThan10tiers() public {
930
+ uint256 gasFor10 = _measureTotalCashOutWeightGas({tierCount: 10, mintedCount: 10});
931
+ uint256 gasFor100 = _measureTotalCashOutWeightGas({tierCount: 100, mintedCount: 10});
932
+
933
+ assertGt(
934
+ gasFor100, gasFor10 * 4, "100-tier totalCashOutWeight should be materially more expensive than 10 tiers"
935
+ );
936
+ emit log_named_uint("Gas used for totalCashOutWeight (10 tiers)", gasFor10);
937
+ emit log_named_uint("Gas used for totalCashOutWeight (100 tiers)", gasFor100);
938
+ }
939
+
940
+ function _measureBalanceOfGas(uint256 tierCount) internal returns (uint256 gasUsed) {
941
+ defaultTierConfig.initialSupply = 10;
942
+ defaultTierConfig.reserveFrequency = 0;
943
+
944
+ ForTest_JB721TiersHook targetHook = _initializeForTestHook(0);
945
+ IJB721TiersHookStore hookStore = targetHook.STORE();
946
+
947
+ vm.prank(address(targetHook));
948
+ hookStore.recordAddTiers(_sequentialTierConfigs(tierCount, 1e15, 10));
949
+
950
+ uint256 gasBefore = gasleft();
951
+ hookStore.balanceOf(address(targetHook), beneficiary);
952
+ gasUsed = gasBefore - gasleft();
953
+ }
954
+
955
+ function _measureTotalCashOutWeightGas(uint256 tierCount, uint256 mintedCount) internal returns (uint256 gasUsed) {
956
+ defaultTierConfig.initialSupply = 10;
957
+ defaultTierConfig.reserveFrequency = 0;
958
+
959
+ ForTest_JB721TiersHook targetHook = _initializeForTestHook(0);
960
+ IJB721TiersHookStore hookStore = targetHook.STORE();
961
+
962
+ vm.prank(address(targetHook));
963
+ hookStore.recordAddTiers(_sequentialTierConfigs(tierCount, 1e15, 10));
964
+
965
+ mockAndExpect(
966
+ address(mockJBDirectory),
967
+ abi.encodeWithSelector(IJBDirectory.isTerminalOf.selector, projectId, mockTerminalAddress),
968
+ abi.encode(true)
969
+ );
970
+
971
+ uint16[] memory tierIdsToMint = new uint16[](mintedCount);
972
+ uint256 totalCost;
973
+ for (uint256 i; i < mintedCount; i++) {
974
+ // forge-lint: disable-next-line(unsafe-typecast)
975
+ tierIdsToMint[i] = uint16(i + 1);
976
+ totalCost += (i + 1) * 1e15;
977
+ }
978
+
979
+ bytes[] memory data = new bytes[](1);
980
+ data[0] = abi.encode(false, tierIdsToMint);
981
+ bytes4[] memory ids = new bytes4[](1);
982
+ ids[0] = metadataHelper.getId("pay", address(targetHook));
983
+ bytes memory payerMetadata = metadataHelper.createMetadata(ids, data);
984
+
985
+ JBAfterPayRecordedContext memory payContext = JBAfterPayRecordedContext({
986
+ payer: beneficiary,
987
+ projectId: projectId,
988
+ rulesetId: 0,
989
+ amount: JBTokenAmount({
990
+ token: JBConstants.NATIVE_TOKEN,
991
+ value: totalCost,
992
+ decimals: 18,
993
+ currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
994
+ }),
995
+ forwardedAmount: JBTokenAmount({
996
+ token: JBConstants.NATIVE_TOKEN,
997
+ value: 0,
998
+ decimals: 18,
999
+ currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
1000
+ }),
1001
+ weight: 10e18,
1002
+ newlyIssuedTokenCount: 0,
1003
+ beneficiary: beneficiary,
1004
+ hookMetadata: bytes(""),
1005
+ payerMetadata: payerMetadata
1006
+ });
1007
+
1008
+ vm.prank(mockTerminalAddress);
1009
+ targetHook.afterPayRecordedWith(payContext);
1010
+
1011
+ uint256 gasBefore = gasleft();
1012
+ hookStore.totalCashOutWeight(address(targetHook));
1013
+ gasUsed = gasBefore - gasleft();
1014
+ }
1015
+
1016
+ function _sequentialTierConfigs(
1017
+ uint256 tierCount,
1018
+ uint104 priceStep,
1019
+ uint32 initialSupply
1020
+ )
1021
+ internal
1022
+ view
1023
+ returns (JB721TierConfig[] memory newTiers)
1024
+ {
1025
+ require(tierCount <= OPERATING_ENVELOPE_SOFT_LIMIT, "test helper only sized for envelope coverage");
1026
+
1027
+ newTiers = new JB721TierConfig[](tierCount);
1028
+ for (uint256 i; i < tierCount; i++) {
1029
+ newTiers[i] = JB721TierConfig({
1030
+ price: uint104((i + 1) * priceStep),
1031
+ initialSupply: initialSupply,
1032
+ votingUnits: 0,
1033
+ reserveFrequency: 0,
1034
+ reserveBeneficiary: reserveBeneficiary,
1035
+ encodedIPFSUri: tokenUris[i % 10],
1036
+ // forge-lint: disable-next-line(unsafe-typecast)
1037
+ category: uint24(i + 1),
1038
+ discountPercent: 0,
1039
+ allowOwnerMint: false,
1040
+ useReserveBeneficiaryAsDefault: false,
1041
+ transfersPausable: false,
1042
+ cannotBeRemoved: false,
1043
+ cannotIncreaseDiscountPercent: false,
1044
+ useVotingUnits: false,
1045
+ splitPercent: 0,
1046
+ splits: new JBSplit[](0)
1047
+ });
1048
+ }
1049
+ }
903
1050
  }