@bananapus/721-hook-v6 0.0.52 → 0.0.54
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/package.json +5 -5
- package/src/JB721TiersHook.sol +6 -1
- package/src/JB721TiersHookStore.sol +19 -4
- package/src/abstract/JB721Hook.sol +13 -0
- package/src/libraries/JB721TiersHookLib.sol +25 -2
- package/src/libraries/JB721TiersRulesetMetadataResolver.sol +13 -13
- package/src/libraries/JBBitmap.sol +19 -19
- package/src/libraries/JBIpfsDecoder.sol +48 -27
- package/test/utils/ForTest_JB721TiersHook.sol +7 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bananapus/721-hook-v6",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.54",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -28,10 +28,10 @@
|
|
|
28
28
|
"artifacts": "source ./.env && npx sphinx artifacts --org-id 'ea165b21-7cdc-4d7b-be59-ecdd4c26bee4' --project-name 'nana-721-hook-v6'"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@bananapus/address-registry-v6": "^0.0.
|
|
32
|
-
"@bananapus/core-v6": "^0.0.
|
|
33
|
-
"@bananapus/ownable-v6": "^0.0.
|
|
34
|
-
"@bananapus/permission-ids-v6": "^0.0.
|
|
31
|
+
"@bananapus/address-registry-v6": "^0.0.26",
|
|
32
|
+
"@bananapus/core-v6": "^0.0.57",
|
|
33
|
+
"@bananapus/ownable-v6": "^0.0.27",
|
|
34
|
+
"@bananapus/permission-ids-v6": "^0.0.26",
|
|
35
35
|
"@openzeppelin/contracts": "5.6.1",
|
|
36
36
|
"@prb/math": "4.1.1",
|
|
37
37
|
"solady": "0.1.26"
|
package/src/JB721TiersHook.sol
CHANGED
|
@@ -44,6 +44,7 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
44
44
|
error JB721TiersHook_AlreadyInitialized(uint256 projectId);
|
|
45
45
|
error JB721TiersHook_InvalidPricingDecimals(uint256 decimals);
|
|
46
46
|
error JB721TiersHook_MintReserveNftsPaused(uint256 projectId, uint256 tierId);
|
|
47
|
+
error JB721TiersHook_MissingSplitMetadata();
|
|
47
48
|
error JB721TiersHook_NoProjectId(uint256 projectId);
|
|
48
49
|
error JB721TiersHook_TierTransfersPaused(uint256 projectId, uint256 tokenId, address from, address to);
|
|
49
50
|
|
|
@@ -715,7 +716,11 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
715
716
|
});
|
|
716
717
|
|
|
717
718
|
// Distribute any forwarded funds to tier split groups.
|
|
718
|
-
if (
|
|
719
|
+
if (context.forwardedAmount.value != 0) {
|
|
720
|
+
if (splitData.length == 0) {
|
|
721
|
+
revert JB721TiersHook_MissingSplitMetadata();
|
|
722
|
+
}
|
|
723
|
+
|
|
719
724
|
JB721TiersHookLib.distributeAll({
|
|
720
725
|
directory: DIRECTORY,
|
|
721
726
|
splits: SPLITS,
|
|
@@ -879,6 +879,20 @@ contract JB721TiersHookStore is IJB721TiersHookStore {
|
|
|
879
879
|
currentSortedTierId = _nextSortedTierIdOf({hook: hook, id: currentSortedTierId, max: lastSortedTierId});
|
|
880
880
|
}
|
|
881
881
|
|
|
882
|
+
if (previousSortedTierId != 0 && previousSortedTierId != lastSortedTierId) {
|
|
883
|
+
// All sorted IDs after `previousSortedTierId` were removed. Make the last active tier the traversal end so
|
|
884
|
+
// view functions do not keep walking a removed trailing suffix after cleanup.
|
|
885
|
+
if (_tierIdAfter[hook][previousSortedTierId] != 0) _tierIdAfter[hook][previousSortedTierId] = 0;
|
|
886
|
+
|
|
887
|
+
// Track the compacted end only when it is not the natural max tier ID. A zero value means
|
|
888
|
+
// `_lastSortedTierIdOf` can keep using `maxTierIdOf` as the implicit end.
|
|
889
|
+
uint256 maxTierId = maxTierIdOf[hook];
|
|
890
|
+
uint256 newLastTrackedSortedTierId = previousSortedTierId == maxTierId ? 0 : previousSortedTierId;
|
|
891
|
+
if (_lastTrackedSortedTierIdOf[hook] != newLastTrackedSortedTierId) {
|
|
892
|
+
_lastTrackedSortedTierIdOf[hook] = newLastTrackedSortedTierId;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
882
896
|
emit CleanTiers({hook: hook, caller: msg.sender});
|
|
883
897
|
}
|
|
884
898
|
|
|
@@ -958,9 +972,8 @@ contract JB721TiersHookStore is IJB721TiersHookStore {
|
|
|
958
972
|
revert JB721TiersHookStore_VotingUnitsNotAllowed({tierId: tierId});
|
|
959
973
|
}
|
|
960
974
|
|
|
961
|
-
// Make sure the new tier doesn't have a reserve frequency if the 721 contract's flags don't allow it to
|
|
962
|
-
|
|
963
|
-
if ((flags.noNewTiersWithReserves || tierToAdd.flags.allowOwnerMint) && tierToAdd.reserveFrequency != 0) {
|
|
975
|
+
// Make sure the new tier doesn't have a reserve frequency if the 721 contract's flags don't allow it to.
|
|
976
|
+
if (flags.noNewTiersWithReserves && tierToAdd.reserveFrequency != 0) {
|
|
964
977
|
revert JB721TiersHookStore_ReserveFrequencyNotAllowed({tierId: tierId});
|
|
965
978
|
}
|
|
966
979
|
|
|
@@ -1027,7 +1040,9 @@ contract JB721TiersHookStore is IJB721TiersHookStore {
|
|
|
1027
1040
|
_tierVotingUnitsOf[msg.sender][tierId] = uint32(tierToAdd.votingUnits);
|
|
1028
1041
|
}
|
|
1029
1042
|
|
|
1030
|
-
// If this is the first tier in a
|
|
1043
|
+
// If this is the first tier in a category within this batch, store it as that category's traversal start.
|
|
1044
|
+
// Same-category additions are inserted before older tiers in the category-sorted linked list, so a later
|
|
1045
|
+
// batch must overwrite the previous start pointer to keep category-filtered `tiersOf` queries complete.
|
|
1031
1046
|
// The `_startingTierIdOfCategory` of the category "0" will always be the same as the `_tierIdAfter` the 0th
|
|
1032
1047
|
// tier.
|
|
1033
1048
|
// Access the previous tier's category directly from calldata (0 when i == 0, matching the old
|
|
@@ -6,6 +6,7 @@ import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
|
|
|
6
6
|
import {IJBPayHook} from "@bananapus/core-v6/src/interfaces/IJBPayHook.sol";
|
|
7
7
|
import {IJBRulesetDataHook} from "@bananapus/core-v6/src/interfaces/IJBRulesetDataHook.sol";
|
|
8
8
|
import {IJBTerminal} from "@bananapus/core-v6/src/interfaces/IJBTerminal.sol";
|
|
9
|
+
import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
|
|
9
10
|
import {JBMetadataResolver} from "@bananapus/core-v6/src/libraries/JBMetadataResolver.sol";
|
|
10
11
|
import {JBAfterCashOutRecordedContext} from "@bananapus/core-v6/src/structs/JBAfterCashOutRecordedContext.sol";
|
|
11
12
|
import {JBAfterPayRecordedContext} from "@bananapus/core-v6/src/structs/JBAfterPayRecordedContext.sol";
|
|
@@ -32,6 +33,7 @@ abstract contract JB721Hook is ERC721, IJB721Hook {
|
|
|
32
33
|
|
|
33
34
|
error JB721Hook_InvalidCashOut(address caller, uint256 contextProjectId, uint256 projectId, uint256 msgValue);
|
|
34
35
|
error JB721Hook_InvalidPay(address caller, uint256 contextProjectId, uint256 projectId);
|
|
36
|
+
error JB721Hook_InvalidPayValue(address token, uint256 msgValue, uint256 forwardedValue);
|
|
35
37
|
error JB721Hook_UnauthorizedToken(uint256 tokenId, address holder);
|
|
36
38
|
error JB721Hook_UnexpectedTokenCashedOut(uint256 cashOutCount);
|
|
37
39
|
|
|
@@ -256,6 +258,17 @@ abstract contract JB721Hook is ERC721, IJB721Hook {
|
|
|
256
258
|
});
|
|
257
259
|
}
|
|
258
260
|
|
|
261
|
+
// Native-token payments must arrive as ETH on this hook call. ERC-20 payments are already held/forwarded by
|
|
262
|
+
// the terminal, so any ETH sent alongside an ERC-20 context would be accidental value trapped in the hook.
|
|
263
|
+
uint256 expectedMsgValue =
|
|
264
|
+
context.forwardedAmount.token == JBConstants.NATIVE_TOKEN ? context.forwardedAmount.value : 0;
|
|
265
|
+
// Keep the terminal-reported forwarded amount and the actual ETH attached to the callback in lockstep.
|
|
266
|
+
if (msg.value != expectedMsgValue) {
|
|
267
|
+
revert JB721Hook_InvalidPayValue({
|
|
268
|
+
token: context.forwardedAmount.token, msgValue: msg.value, forwardedValue: context.forwardedAmount.value
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
259
272
|
// Process the payment.
|
|
260
273
|
_processPayment(context);
|
|
261
274
|
}
|
|
@@ -34,7 +34,9 @@ library JB721TiersHookLib {
|
|
|
34
34
|
error JB721TiersHook_CantBuyWithCredits(uint256 restrictedCost, uint256 freshValue);
|
|
35
35
|
error JB721TiersHook_Overspending(uint256 leftoverAmount);
|
|
36
36
|
error JB721TiersHookLib_NoTerminalForLeftover(uint256 projectId, address token, uint256 leftoverAmount);
|
|
37
|
+
error JB721TiersHookLib_SplitAmountMismatch(uint256 expectedAmount, uint256 actualAmount);
|
|
37
38
|
error JB721TiersHookLib_SplitFallbackFailed(uint256 projectId, address token, uint256 amount, bytes reason);
|
|
39
|
+
error JB721TiersHookLib_SplitMetadataLengthMismatch(uint256 tierIdCount, uint256 amountCount);
|
|
38
40
|
error JB721TiersHookLib_TokenTransferAmountMismatch(uint256 expectedAmount, uint256 receivedAmount);
|
|
39
41
|
|
|
40
42
|
//*********************************************************************//
|
|
@@ -127,6 +129,29 @@ library JB721TiersHookLib {
|
|
|
127
129
|
)
|
|
128
130
|
external
|
|
129
131
|
{
|
|
132
|
+
// `encodedSplitData` is terminal-forwarded hook metadata. Each tier ID must have exactly one explicit amount so
|
|
133
|
+
// later split-group routing cannot pair a tier with the wrong value or silently ignore trailing data.
|
|
134
|
+
(uint16[] memory tierIds, uint256[] memory amounts) = abi.decode(encodedSplitData, (uint16[], uint256[]));
|
|
135
|
+
if (tierIds.length != amounts.length) {
|
|
136
|
+
revert JB721TiersHookLib_SplitMetadataLengthMismatch({
|
|
137
|
+
tierIdCount: tierIds.length, amountCount: amounts.length
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// The terminal tells the hook how much value was forwarded. Require the per-tier split metadata to account for
|
|
142
|
+
// all of it before any transfers happen, otherwise malformed metadata could over-distribute, under-distribute,
|
|
143
|
+
// or leave funds stuck in the hook.
|
|
144
|
+
uint256 totalAmount;
|
|
145
|
+
for (uint256 i; i < amounts.length;) {
|
|
146
|
+
totalAmount += amounts[i];
|
|
147
|
+
unchecked {
|
|
148
|
+
++i;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (totalAmount != amount) {
|
|
152
|
+
revert JB721TiersHookLib_SplitAmountMismatch({expectedAmount: amount, actualAmount: totalAmount});
|
|
153
|
+
}
|
|
154
|
+
|
|
130
155
|
// For ERC20 tokens, pull from terminal using the allowance it granted via _beforeTransferTo.
|
|
131
156
|
if (token != JBConstants.NATIVE_TOKEN) {
|
|
132
157
|
uint256 balanceBefore = IERC20(token).balanceOf(address(this));
|
|
@@ -139,8 +164,6 @@ library JB721TiersHookLib {
|
|
|
139
164
|
}
|
|
140
165
|
}
|
|
141
166
|
|
|
142
|
-
(uint16[] memory tierIds, uint256[] memory amounts) = abi.decode(encodedSplitData, (uint16[], uint256[]));
|
|
143
|
-
|
|
144
167
|
for (uint256 i; i < tierIds.length;) {
|
|
145
168
|
if (amounts[i] == 0) {
|
|
146
169
|
unchecked {
|
|
@@ -7,11 +7,14 @@ import {JB721TiersRulesetMetadata} from "../structs/JB721TiersRulesetMetadata.so
|
|
|
7
7
|
/// @notice Utility library to parse and store ruleset metadata associated for the tiered 721 hook.
|
|
8
8
|
/// @dev This library parses the `metadata` member of the `JBRulesetMetadata` struct.
|
|
9
9
|
library JB721TiersRulesetMetadataResolver {
|
|
10
|
-
/// @notice
|
|
11
|
-
/// @param
|
|
12
|
-
/// @return
|
|
13
|
-
function
|
|
14
|
-
return (
|
|
10
|
+
/// @notice Expand packed ruleset metadata for the 721 hook.
|
|
11
|
+
/// @param packedMetadata The packed metadata to expand.
|
|
12
|
+
/// @return metadata The metadata as a `JB721TiersRulesetMetadata` struct.
|
|
13
|
+
function expandMetadata(uint16 packedMetadata) internal pure returns (JB721TiersRulesetMetadata memory metadata) {
|
|
14
|
+
return JB721TiersRulesetMetadata({
|
|
15
|
+
pauseTransfers: transfersPaused(packedMetadata),
|
|
16
|
+
pauseMintPendingReserves: mintPendingReservesPaused(packedMetadata)
|
|
17
|
+
});
|
|
15
18
|
}
|
|
16
19
|
|
|
17
20
|
/// @notice Check whether minting pending reserves is paused based on the packed ruleset metadata.
|
|
@@ -35,13 +38,10 @@ library JB721TiersRulesetMetadataResolver {
|
|
|
35
38
|
if (metadata.pauseMintPendingReserves) packed |= 1 << 1;
|
|
36
39
|
}
|
|
37
40
|
|
|
38
|
-
/// @notice
|
|
39
|
-
/// @param
|
|
40
|
-
/// @return
|
|
41
|
-
function
|
|
42
|
-
return
|
|
43
|
-
pauseTransfers: transfersPaused(packedMetadata),
|
|
44
|
-
pauseMintPendingReserves: mintPendingReservesPaused(packedMetadata)
|
|
45
|
-
});
|
|
41
|
+
/// @notice Check whether transfers are paused based on the packed ruleset metadata.
|
|
42
|
+
/// @param data The packed metadata to check.
|
|
43
|
+
/// @return Whether transfers are paused (bit 0).
|
|
44
|
+
function transfersPaused(uint256 data) internal pure returns (bool) {
|
|
45
|
+
return (data & 1) == 1;
|
|
46
46
|
}
|
|
47
47
|
}
|
|
@@ -6,20 +6,6 @@ import {JBBitmapWord} from "../structs/JBBitmapWord.sol";
|
|
|
6
6
|
/// @title JBBitmap
|
|
7
7
|
/// @notice Utilities to manage a bool bitmap. Used for storing inactive tiers.
|
|
8
8
|
library JBBitmap {
|
|
9
|
-
/// @notice Initialize a `JBBitmapWord` struct based on a mapping storage pointer and an index.
|
|
10
|
-
function readId(
|
|
11
|
-
mapping(uint256 => uint256) storage self,
|
|
12
|
-
uint256 index
|
|
13
|
-
)
|
|
14
|
-
internal
|
|
15
|
-
view
|
|
16
|
-
returns (JBBitmapWord memory)
|
|
17
|
-
{
|
|
18
|
-
uint256 depth = _retrieveDepth(index);
|
|
19
|
-
|
|
20
|
-
return JBBitmapWord({currentWord: self[depth], currentDepth: depth});
|
|
21
|
-
}
|
|
22
|
-
|
|
23
9
|
/// @notice Get the status of the specified bit within the `JBBitmapWord` struct.
|
|
24
10
|
/// @dev The `index` is the index that the bit would have if the bitmap were reshaped to a 1*n matrix.
|
|
25
11
|
/// @return The boolean value at the specified index, which indicates whether the corresponding tier has been
|
|
@@ -35,12 +21,18 @@ library JBBitmap {
|
|
|
35
21
|
return isTierIdRemoved({self: JBBitmapWord({currentWord: self[depth], currentDepth: depth}), index: index});
|
|
36
22
|
}
|
|
37
23
|
|
|
38
|
-
/// @notice
|
|
39
|
-
|
|
40
|
-
|
|
24
|
+
/// @notice Initialize a `JBBitmapWord` struct based on a mapping storage pointer and an index.
|
|
25
|
+
function readId(
|
|
26
|
+
mapping(uint256 => uint256) storage self,
|
|
27
|
+
uint256 index
|
|
28
|
+
)
|
|
29
|
+
internal
|
|
30
|
+
view
|
|
31
|
+
returns (JBBitmapWord memory)
|
|
32
|
+
{
|
|
41
33
|
uint256 depth = _retrieveDepth(index);
|
|
42
|
-
|
|
43
|
-
self[depth]
|
|
34
|
+
|
|
35
|
+
return JBBitmapWord({currentWord: self[depth], currentDepth: depth});
|
|
44
36
|
}
|
|
45
37
|
|
|
46
38
|
/// @notice Check if the specified index is at a different depth than the current depth of the `JBBitmapWord`
|
|
@@ -51,6 +43,14 @@ library JBBitmap {
|
|
|
51
43
|
return _retrieveDepth(index) != self.currentDepth;
|
|
52
44
|
}
|
|
53
45
|
|
|
46
|
+
/// @notice Set the bit at the given index to true, indicating that the corresponding tier has been removed.
|
|
47
|
+
/// @dev This is a one-way operation.
|
|
48
|
+
function removeTier(mapping(uint256 => uint256) storage self, uint256 index) internal {
|
|
49
|
+
uint256 depth = _retrieveDepth(index);
|
|
50
|
+
// forge-lint: disable-next-line(incorrect-shift)
|
|
51
|
+
self[depth] |= uint256(1 << (index % 256));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
54
|
/// @notice Return the line number (depth) of a given index within the bitmap matrix.
|
|
55
55
|
function _retrieveDepth(uint256 index) internal pure returns (uint256) {
|
|
56
56
|
return index >> 8; // div by 256
|
|
@@ -29,8 +29,48 @@ library JBIpfsDecoder {
|
|
|
29
29
|
return string(abi.encodePacked(baseUri, ipfsHash));
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
/// @notice
|
|
33
|
-
/// @
|
|
32
|
+
/// @notice Return a new array containing the elements of `input` in reverse order.
|
|
33
|
+
/// @dev Used by `_toBase58` after the base-58 digit accumulator is finalised — the conversion algorithm
|
|
34
|
+
/// emits least-significant digits first, but base-58 strings are read most-significant first.
|
|
35
|
+
/// @param input The array to reverse.
|
|
36
|
+
/// @return output A new array of the same length with elements in reverse order.
|
|
37
|
+
function _reverse(uint8[] memory input) private pure returns (uint8[] memory) {
|
|
38
|
+
uint256 inputLength = input.length;
|
|
39
|
+
uint8[] memory output = new uint8[](inputLength);
|
|
40
|
+
for (uint256 i; i < inputLength;) {
|
|
41
|
+
unchecked {
|
|
42
|
+
// Read from the tail of `input` and write to the head of `output`.
|
|
43
|
+
output[i] = input[input.length - 1 - i];
|
|
44
|
+
++i;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return output;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/// @notice Map each base-58 digit (0–57) to its corresponding character in `ALPHABET`.
|
|
51
|
+
/// @dev Final stage of `_toBase58`: turns the numeric digit array into the canonical base-58 string bytes.
|
|
52
|
+
/// @param indices Each element must satisfy `0 <= indices[i] < 58`; out-of-range values revert via index OOB.
|
|
53
|
+
/// @return output ASCII bytes with `output[i] = ALPHABET[indices[i]]`.
|
|
54
|
+
function _toAlphabet(uint8[] memory indices) private pure returns (bytes memory) {
|
|
55
|
+
uint256 indicesLength = indices.length;
|
|
56
|
+
bytes memory output = new bytes(indicesLength);
|
|
57
|
+
for (uint256 i; i < indicesLength;) {
|
|
58
|
+
output[i] = ALPHABET[indices[i]];
|
|
59
|
+
|
|
60
|
+
unchecked {
|
|
61
|
+
++i;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return output;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/// @notice Convert a hex byte string to its base-58 string representation.
|
|
68
|
+
/// @notice Written by Martin Ludfall — Licence: MIT.
|
|
69
|
+
/// @dev Classic "long division by 58" base conversion: iterate the source bytes high-to-low, carrying remainders
|
|
70
|
+
/// through the digit accumulator. After the loop, `digits[0..digitlength)` holds the base-58 representation in
|
|
71
|
+
/// little-endian order; the final composition reverses and maps to ASCII via `_toAlphabet(_reverse(...))`.
|
|
72
|
+
/// @param source The source bytes to convert (multibase-prefixed IPFS hash, in this library's usage).
|
|
73
|
+
/// @return The base-58 encoded string.
|
|
34
74
|
function _toBase58(bytes memory source) private pure returns (string memory) {
|
|
35
75
|
if (source.length == 0) return new string(0);
|
|
36
76
|
|
|
@@ -72,6 +112,12 @@ library JBIpfsDecoder {
|
|
|
72
112
|
return string(_toAlphabet(_reverse(_truncate({array: digits, length: digitlength}))));
|
|
73
113
|
}
|
|
74
114
|
|
|
115
|
+
/// @notice Copy the first `length` elements of `array` into a new, exactly-sized array.
|
|
116
|
+
/// @dev `_toBase58` allocates a fixed 46-byte scratch buffer but only fills `digitlength` of it; this trims the
|
|
117
|
+
/// trailing zeros so downstream stages (`_reverse`, `_toAlphabet`) see only the meaningful digits.
|
|
118
|
+
/// @param array The source array. Must have `array.length >= length`.
|
|
119
|
+
/// @param length Number of leading elements to copy.
|
|
120
|
+
/// @return output A new array of exactly `length` elements containing the prefix of `array`.
|
|
75
121
|
function _truncate(uint8[] memory array, uint8 length) private pure returns (uint8[] memory) {
|
|
76
122
|
uint8[] memory output = new uint8[](length);
|
|
77
123
|
for (uint256 i; i < length;) {
|
|
@@ -83,29 +129,4 @@ library JBIpfsDecoder {
|
|
|
83
129
|
}
|
|
84
130
|
return output;
|
|
85
131
|
}
|
|
86
|
-
|
|
87
|
-
function _reverse(uint8[] memory input) private pure returns (uint8[] memory) {
|
|
88
|
-
uint256 inputLength = input.length;
|
|
89
|
-
uint8[] memory output = new uint8[](inputLength);
|
|
90
|
-
for (uint256 i; i < inputLength;) {
|
|
91
|
-
unchecked {
|
|
92
|
-
output[i] = input[input.length - 1 - i];
|
|
93
|
-
++i;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
return output;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function _toAlphabet(uint8[] memory indices) private pure returns (bytes memory) {
|
|
100
|
-
uint256 indicesLength = indices.length;
|
|
101
|
-
bytes memory output = new bytes(indicesLength);
|
|
102
|
-
for (uint256 i; i < indicesLength;) {
|
|
103
|
-
output[i] = ALPHABET[indices[i]];
|
|
104
|
-
|
|
105
|
-
unchecked {
|
|
106
|
-
++i;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
return output;
|
|
110
|
-
}
|
|
111
132
|
}
|
|
@@ -32,6 +32,8 @@ interface IJB721TiersHookStore_ForTest is IJB721TiersHookStore {
|
|
|
32
32
|
// forge-lint: disable-next-line(mixed-case-function)
|
|
33
33
|
function ForTest_dumpTiersList(address nft) external view returns (JB721Tier[] memory tiers);
|
|
34
34
|
// forge-lint: disable-next-line(mixed-case-function)
|
|
35
|
+
function ForTest_lastSortedTierIdOf(address nft) external view returns (uint256 tierId);
|
|
36
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
35
37
|
function ForTest_setTier(address hook, uint256 index, JBStored721Tier calldata newTier) external;
|
|
36
38
|
// forge-lint: disable-next-line(mixed-case-function)
|
|
37
39
|
function ForTest_setTierVotingUnits(address hook, uint256 tierId, uint32 votingUnits) external;
|
|
@@ -191,6 +193,11 @@ contract ForTest_JB721TiersHookStore is JB721TiersHookStore, IJB721TiersHookStor
|
|
|
191
193
|
}
|
|
192
194
|
}
|
|
193
195
|
|
|
196
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
197
|
+
function ForTest_lastSortedTierIdOf(address nft) public view override returns (uint256 tierId) {
|
|
198
|
+
tierId = _lastSortedTierIdOf(nft);
|
|
199
|
+
}
|
|
200
|
+
|
|
194
201
|
// forge-lint: disable-next-line(mixed-case-function)
|
|
195
202
|
function ForTest_setTier(address hook, uint256 index, JBStored721Tier calldata newTier) public override {
|
|
196
203
|
_storedTierOf[address(hook)][index] = newTier;
|