@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.
- package/ARCHITECTURE.md +4 -0
- package/CHANGE_LOG.md +47 -11
- package/README.md +19 -9
- package/RISKS.md +4 -0
- package/USER_JOURNEYS.md +4 -0
- package/package.json +2 -2
- package/src/JB721TiersHook.sol +87 -85
- package/src/abstract/JB721Hook.sol +2 -2
- package/src/libraries/JB721TiersHookLib.sol +4 -8
- package/test/TestAuditGaps.sol +147 -0
- package/test/fork/ERC20CashOutFork.t.sol +612 -0
- package/test/fork/IssueTokensForSplitsFork.t.sol +504 -0
- package/docs/book.css +0 -13
- package/docs/book.toml +0 -12
- package/docs/solidity.min.js +0 -74
- package/docs/src/README.md +0 -253
- package/docs/src/SUMMARY.md +0 -38
- package/docs/src/src/JB721TiersHook.sol/contract.JB721TiersHook.md +0 -645
- package/docs/src/src/JB721TiersHookDeployer.sol/contract.JB721TiersHookDeployer.md +0 -99
- package/docs/src/src/JB721TiersHookProjectDeployer.sol/contract.JB721TiersHookProjectDeployer.md +0 -288
- package/docs/src/src/JB721TiersHookStore.sol/contract.JB721TiersHookStore.md +0 -1096
- package/docs/src/src/README.md +0 -11
- package/docs/src/src/abstract/ERC721.sol/abstract.ERC721.md +0 -430
- package/docs/src/src/abstract/JB721Hook.sol/abstract.JB721Hook.md +0 -309
- package/docs/src/src/abstract/README.md +0 -5
- package/docs/src/src/interfaces/IJB721Hook.sol/interface.IJB721Hook.md +0 -29
- package/docs/src/src/interfaces/IJB721TiersHook.sol/interface.IJB721TiersHook.md +0 -203
- package/docs/src/src/interfaces/IJB721TiersHookDeployer.sol/interface.IJB721TiersHookDeployer.md +0 -25
- package/docs/src/src/interfaces/IJB721TiersHookProjectDeployer.sol/interface.IJB721TiersHookProjectDeployer.md +0 -64
- package/docs/src/src/interfaces/IJB721TiersHookStore.sol/interface.IJB721TiersHookStore.md +0 -265
- package/docs/src/src/interfaces/IJB721TokenUriResolver.sol/interface.IJB721TokenUriResolver.md +0 -12
- package/docs/src/src/interfaces/README.md +0 -9
- package/docs/src/src/libraries/JB721Constants.sol/library.JB721Constants.md +0 -14
- package/docs/src/src/libraries/JB721TiersRulesetMetadataResolver.sol/library.JB721TiersRulesetMetadataResolver.md +0 -68
- package/docs/src/src/libraries/JBBitmap.sol/library.JBBitmap.md +0 -82
- package/docs/src/src/libraries/JBIpfsDecoder.sol/library.JBIpfsDecoder.md +0 -61
- package/docs/src/src/libraries/README.md +0 -7
- package/docs/src/src/structs/JB721InitTiersConfig.sol/struct.JB721InitTiersConfig.md +0 -27
- package/docs/src/src/structs/JB721Tier.sol/struct.JB721Tier.md +0 -59
- package/docs/src/src/structs/JB721TierConfig.sol/struct.JB721TierConfig.md +0 -60
- package/docs/src/src/structs/JB721TiersHookFlags.sol/struct.JB721TiersHookFlags.md +0 -26
- package/docs/src/src/structs/JB721TiersMintReservesConfig.sol/struct.JB721TiersMintReservesConfig.md +0 -16
- package/docs/src/src/structs/JB721TiersRulesetMetadata.sol/struct.JB721TiersRulesetMetadata.md +0 -20
- package/docs/src/src/structs/JB721TiersSetDiscountPercentConfig.sol/struct.JB721TiersSetDiscountPercentConfig.md +0 -16
- package/docs/src/src/structs/JBBitmapWord.sol/struct.JBBitmapWord.md +0 -19
- package/docs/src/src/structs/JBDeploy721TiersHookConfig.sol/struct.JBDeploy721TiersHookConfig.md +0 -34
- package/docs/src/src/structs/JBLaunchProjectConfig.sol/struct.JBLaunchProjectConfig.md +0 -23
- package/docs/src/src/structs/JBLaunchRulesetsConfig.sol/struct.JBLaunchRulesetsConfig.md +0 -22
- package/docs/src/src/structs/JBPayDataHookRulesetConfig.sol/struct.JBPayDataHookRulesetConfig.md +0 -51
- package/docs/src/src/structs/JBPayDataHookRulesetMetadata.sol/struct.JBPayDataHookRulesetMetadata.md +0 -66
- package/docs/src/src/structs/JBQueueRulesetsConfig.sol/struct.JBQueueRulesetsConfig.md +0 -21
- package/docs/src/src/structs/JBStored721Tier.sol/struct.JBStored721Tier.md +0 -42
- package/docs/src/src/structs/README.md +0 -18
package/test/TestAuditGaps.sol
CHANGED
|
@@ -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
|
}
|