@croptop/core-v6 0.0.32 → 0.0.34

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.
@@ -27,7 +27,6 @@ contract CTPublisher is JBPermissioned, ERC2771Context, ICTPublisher {
27
27
  // --------------------------- custom errors ------------------------- //
28
28
  //*********************************************************************//
29
29
 
30
- // forge-lint: disable-next-line(mixed-case-variable)
31
30
  error CTPublisher_DuplicatePost(bytes32 encodedIPFSUri);
32
31
  error CTPublisher_EmptyEncodedIPFSUri();
33
32
  error CTPublisher_InsufficientEthSent(uint256 expected, uint256 sent);
@@ -66,7 +65,6 @@ contract CTPublisher is JBPermissioned, ERC2771Context, ICTPublisher {
66
65
  /// @notice The ID of the tier that an IPFS metadata has been saved to.
67
66
  /// @custom:param hook The hook for which the tier ID applies.
68
67
  /// @custom:param encodedIPFSUri The IPFS URI.
69
- // forge-lint: disable-next-line(mixed-case-variable)
70
68
  mapping(address hook => mapping(bytes32 encodedIPFSUri => uint256)) public override tierIdForEncodedIPFSUriOf;
71
69
 
72
70
  //*********************************************************************//
@@ -105,141 +103,6 @@ contract CTPublisher is JBPermissioned, ERC2771Context, ICTPublisher {
105
103
  FEE_PROJECT_ID = feeProjectId;
106
104
  }
107
105
 
108
- //*********************************************************************//
109
- // ------------------------- external views -------------------------- //
110
- //*********************************************************************//
111
-
112
- /// @notice Get the tiers for the provided encoded IPFS URIs.
113
- /// @dev The returned tier IDs may be stale if the corresponding tiers were removed externally via adjustTiers.
114
- /// In that case, the store's tierOf call will return a tier with default/empty values. Callers should check
115
- /// the returned tier's initialSupply or other fields to confirm the tier still exists.
116
- /// @param hook The hook from which to get tiers.
117
- /// @param encodedIPFSUris The URIs to get tiers of.
118
- /// @return tiers The tiers that correspond to the provided encoded IPFS URIs. If there's no tier yet, an empty tier
119
- /// is returned.
120
- function tiersFor(
121
- address hook,
122
- // forge-lint: disable-next-line(mixed-case-variable)
123
- bytes32[] memory encodedIPFSUris
124
- )
125
- external
126
- view
127
- override
128
- returns (JB721Tier[] memory tiers)
129
- {
130
- // forge-lint: disable-next-line(mixed-case-variable)
131
- uint256 numberOfEncodedIPFSUris = encodedIPFSUris.length;
132
-
133
- // Initialize the tier array being returned.
134
- tiers = new JB721Tier[](numberOfEncodedIPFSUris);
135
-
136
- // Get the tier for each provided encoded IPFS URI.
137
- for (uint256 i; i < numberOfEncodedIPFSUris;) {
138
- // Check if there's a tier ID stored for the encoded IPFS URI.
139
- uint256 tierId = tierIdForEncodedIPFSUriOf[hook][encodedIPFSUris[i]];
140
-
141
- // If there's a tier ID stored, resolve it.
142
- if (tierId != 0) {
143
- // slither-disable-next-line calls-loop
144
- tiers[i] = IJB721TiersHook(hook).STORE().tierOf({hook: hook, id: tierId, includeResolvedUri: false});
145
- }
146
-
147
- unchecked {
148
- ++i;
149
- }
150
- }
151
- }
152
-
153
- //*********************************************************************//
154
- // -------------------------- public views --------------------------- //
155
- //*********************************************************************//
156
-
157
- /// @notice Post allowances for a particular category on a particular hook.
158
- /// @param hook The hook contract for which this allowance applies.
159
- /// @param category The category for which this allowance applies.
160
- /// @return minimumPrice The minimum price that a poster must pay to record a new NFT.
161
- /// @return minimumTotalSupply The minimum total number of available tokens that a minter must set to record a new
162
- /// NFT.
163
- /// @return maximumTotalSupply The max total supply of NFTs that can be made available when minting. Must be >=
164
- /// minimumTotalSupply.
165
- /// @return maximumSplitPercent The maximum split percent that a poster can set. 0 means splits are not allowed.
166
- /// @return allowedAddresses The addresses allowed to post. Returns empty if all addresses are allowed.
167
- function allowanceFor(
168
- address hook,
169
- uint256 category
170
- )
171
- public
172
- view
173
- override
174
- returns (
175
- uint256 minimumPrice,
176
- uint256 minimumTotalSupply,
177
- uint256 maximumTotalSupply,
178
- uint256 maximumSplitPercent,
179
- address[] memory allowedAddresses
180
- )
181
- {
182
- // Get a reference to the packed values.
183
- uint256 packed = _packedAllowanceFor[hook][category];
184
-
185
- // minimum price in bits 0-103 (104 bits).
186
- // forge-lint: disable-next-line(unsafe-typecast)
187
- minimumPrice = uint256(uint104(packed));
188
- // minimum supply in bits 104-135 (32 bits).
189
- // forge-lint: disable-next-line(unsafe-typecast)
190
- minimumTotalSupply = uint256(uint32(packed >> 104));
191
- // maximum supply in bits 136-167 (32 bits).
192
- // forge-lint: disable-next-line(unsafe-typecast)
193
- maximumTotalSupply = uint256(uint32(packed >> 136));
194
- // maximum split percent in bits 168-199 (32 bits).
195
- // forge-lint: disable-next-line(unsafe-typecast)
196
- maximumSplitPercent = uint256(uint32(packed >> 168));
197
-
198
- allowedAddresses = _allowedAddresses[hook][category];
199
- }
200
-
201
- //*********************************************************************//
202
- // -------------------------- internal views ------------------------- //
203
- //*********************************************************************//
204
-
205
- /// @dev ERC-2771 specifies the context as being a single address (20 bytes).
206
- function _contextSuffixLength() internal view virtual override(ERC2771Context, Context) returns (uint256) {
207
- return super._contextSuffixLength();
208
- }
209
-
210
- /// @notice Check if an address is included in an allow list.
211
- /// @dev Uses an O(n) linear scan over the `addresses` array. This is acceptable for typical allow list sizes
212
- /// (fewer than ~100 addresses), where gas cost is negligible. For very large allow lists, a Merkle proof
213
- /// pattern would scale better, but the added complexity is not warranted for the expected use case.
214
- /// @param addrs The candidate address.
215
- /// @param addresses An array of allowed addresses.
216
- function _isAllowed(address addrs, address[] memory addresses) internal pure returns (bool) {
217
- // Keep a reference to the number of address to check against.
218
- uint256 numberOfAddresses = addresses.length;
219
-
220
- // Check if the address is included
221
- for (uint256 i; i < numberOfAddresses;) {
222
- if (addrs == addresses[i]) return true;
223
- unchecked {
224
- ++i;
225
- }
226
- }
227
-
228
- return false;
229
- }
230
-
231
- /// @notice Returns the calldata, prefered to use over `msg.data`
232
- /// @return calldata the `msg.data` of this call
233
- function _msgData() internal view override(ERC2771Context, Context) returns (bytes calldata) {
234
- return ERC2771Context._msgData();
235
- }
236
-
237
- /// @notice Returns the sender, prefered to use over `msg.sender`
238
- /// @return sender the sender address of this call.
239
- function _msgSender() internal view override(ERC2771Context, Context) returns (address sender) {
240
- return ERC2771Context._msgSender();
241
- }
242
-
243
106
  //*********************************************************************//
244
107
  // ---------------------- external transactions ---------------------- //
245
108
  //*********************************************************************//
@@ -446,7 +309,98 @@ contract CTPublisher is JBPermissioned, ERC2771Context, ICTPublisher {
446
309
  }
447
310
 
448
311
  //*********************************************************************//
449
- // ------------------------ internal functions ----------------------- //
312
+ // ------------------------- external views -------------------------- //
313
+ //*********************************************************************//
314
+
315
+ /// @notice Get the tiers for the provided encoded IPFS URIs.
316
+ /// @dev The returned tier IDs may be stale if the corresponding tiers were removed externally via adjustTiers.
317
+ /// In that case, the store's tierOf call will return a tier with default/empty values. Callers should check
318
+ /// the returned tier's initialSupply or other fields to confirm the tier still exists.
319
+ /// @param hook The hook from which to get tiers.
320
+ /// @param encodedIPFSUris The URIs to get tiers of.
321
+ /// @return tiers The tiers that correspond to the provided encoded IPFS URIs. If there's no tier yet, an empty tier
322
+ /// is returned.
323
+ function tiersFor(
324
+ address hook,
325
+ bytes32[] memory encodedIPFSUris
326
+ )
327
+ external
328
+ view
329
+ override
330
+ returns (JB721Tier[] memory tiers)
331
+ {
332
+ uint256 numberOfEncodedIPFSUris = encodedIPFSUris.length;
333
+
334
+ // Initialize the tier array being returned.
335
+ tiers = new JB721Tier[](numberOfEncodedIPFSUris);
336
+
337
+ // Get the tier for each provided encoded IPFS URI.
338
+ for (uint256 i; i < numberOfEncodedIPFSUris;) {
339
+ // Check if there's a tier ID stored for the encoded IPFS URI.
340
+ uint256 tierId = tierIdForEncodedIPFSUriOf[hook][encodedIPFSUris[i]];
341
+
342
+ // If there's a tier ID stored, resolve it.
343
+ if (tierId != 0) {
344
+ // slither-disable-next-line calls-loop
345
+ tiers[i] = IJB721TiersHook(hook).STORE().tierOf({hook: hook, id: tierId, includeResolvedUri: false});
346
+ }
347
+
348
+ unchecked {
349
+ ++i;
350
+ }
351
+ }
352
+ }
353
+
354
+ //*********************************************************************//
355
+ // -------------------------- public views --------------------------- //
356
+ //*********************************************************************//
357
+
358
+ /// @notice Post allowances for a particular category on a particular hook.
359
+ /// @param hook The hook contract for which this allowance applies.
360
+ /// @param category The category for which this allowance applies.
361
+ /// @return minimumPrice The minimum price that a poster must pay to record a new NFT.
362
+ /// @return minimumTotalSupply The minimum total number of available tokens that a minter must set to record a new
363
+ /// NFT.
364
+ /// @return maximumTotalSupply The max total supply of NFTs that can be made available when minting. Must be >=
365
+ /// minimumTotalSupply.
366
+ /// @return maximumSplitPercent The maximum split percent that a poster can set. 0 means splits are not allowed.
367
+ /// @return allowedAddresses The addresses allowed to post. Returns empty if all addresses are allowed.
368
+ function allowanceFor(
369
+ address hook,
370
+ uint256 category
371
+ )
372
+ public
373
+ view
374
+ override
375
+ returns (
376
+ uint256 minimumPrice,
377
+ uint256 minimumTotalSupply,
378
+ uint256 maximumTotalSupply,
379
+ uint256 maximumSplitPercent,
380
+ address[] memory allowedAddresses
381
+ )
382
+ {
383
+ // Get a reference to the packed values.
384
+ uint256 packed = _packedAllowanceFor[hook][category];
385
+
386
+ // minimum price in bits 0-103 (104 bits).
387
+ // forge-lint: disable-next-line(unsafe-typecast)
388
+ minimumPrice = uint256(uint104(packed));
389
+ // minimum supply in bits 104-135 (32 bits).
390
+ // forge-lint: disable-next-line(unsafe-typecast)
391
+ minimumTotalSupply = uint256(uint32(packed >> 104));
392
+ // maximum supply in bits 136-167 (32 bits).
393
+ // forge-lint: disable-next-line(unsafe-typecast)
394
+ maximumTotalSupply = uint256(uint32(packed >> 136));
395
+ // maximum split percent in bits 168-199 (32 bits).
396
+ // forge-lint: disable-next-line(unsafe-typecast)
397
+ maximumSplitPercent = uint256(uint32(packed >> 168));
398
+
399
+ allowedAddresses = _allowedAddresses[hook][category];
400
+ }
401
+
402
+ //*********************************************************************//
403
+ // ------------------------ internal helpers ------------------------- //
450
404
  //*********************************************************************//
451
405
 
452
406
  /// @notice Setup the posts.
@@ -613,4 +567,46 @@ contract CTPublisher is JBPermissioned, ERC2771Context, ICTPublisher {
613
567
  }
614
568
  }
615
569
  }
570
+
571
+ /// @notice Check if an address is included in an allow list.
572
+ /// @dev Uses an O(n) linear scan over the `addresses` array. This is acceptable for typical allow list sizes
573
+ /// (fewer than ~100 addresses), where gas cost is negligible. For very large allow lists, a Merkle proof
574
+ /// pattern would scale better, but the added complexity is not warranted for the expected use case.
575
+ /// @param addrs The candidate address.
576
+ /// @param addresses An array of allowed addresses.
577
+ function _isAllowed(address addrs, address[] memory addresses) internal pure returns (bool) {
578
+ // Keep a reference to the number of address to check against.
579
+ uint256 numberOfAddresses = addresses.length;
580
+
581
+ // Check if the address is included
582
+ for (uint256 i; i < numberOfAddresses;) {
583
+ if (addrs == addresses[i]) return true;
584
+ unchecked {
585
+ ++i;
586
+ }
587
+ }
588
+
589
+ return false;
590
+ }
591
+
592
+ //*********************************************************************//
593
+ // -------------------------- internal views ------------------------- //
594
+ //*********************************************************************//
595
+
596
+ /// @dev ERC-2771 specifies the context as being a single address (20 bytes).
597
+ function _contextSuffixLength() internal view virtual override(ERC2771Context, Context) returns (uint256) {
598
+ return super._contextSuffixLength();
599
+ }
600
+
601
+ /// @notice Returns the calldata, prefered to use over `msg.data`
602
+ /// @return calldata the `msg.data` of this call
603
+ function _msgData() internal view override(ERC2771Context, Context) returns (bytes calldata) {
604
+ return ERC2771Context._msgData();
605
+ }
606
+
607
+ /// @notice Returns the sender, prefered to use over `msg.sender`
608
+ /// @return sender the sender address of this call.
609
+ function _msgSender() internal view override(ERC2771Context, Context) returns (address sender) {
610
+ return ERC2771Context._msgSender();
611
+ }
616
612
  }
@@ -73,7 +73,7 @@ interface ICTPublisher {
73
73
  /// @param hook The hook for which the tier ID applies.
74
74
  /// @param encodedIPFSUri The encoded IPFS URI to look up.
75
75
  /// @return The tier ID, or 0 if the URI has not been published.
76
- // forge-lint: disable-next-line(mixed-case-function, mixed-case-variable)
76
+ // forge-lint: disable-next-line(mixed-case-function)
77
77
  function tierIdForEncodedIPFSUriOf(address hook, bytes32 encodedIPFSUri) external view returns (uint256);
78
78
 
79
79
  /// @notice Get the tiers for the provided encoded IPFS URIs.
@@ -81,7 +81,6 @@ interface ICTPublisher {
81
81
  /// @param encodedIPFSUris The URIs to get tiers of.
82
82
  /// @return tiers The tiers that correspond to the provided encoded IPFS URIs. Empty tiers are returned for URIs
83
83
  /// without a tier.
84
- // forge-lint: disable-next-line(mixed-case-variable)
85
84
  function tiersFor(address hook, bytes32[] memory encodedIPFSUris) external view returns (JB721Tier[] memory tiers);
86
85
 
87
86
  /// @notice Configure the allowed criteria for publishing new NFTs to a hook.
@@ -11,7 +11,6 @@ pragma solidity ^0.8.0;
11
11
  /// @custom:member maximumSplitPercent The maximum split percent (out of JBConstants.SPLITS_TOTAL_PERCENT) that a
12
12
  /// poster can set. 0 means splits are not allowed.
13
13
  /// @custom:member allowedAddresses A list of addresses that are allowed to post on the category through Croptop.
14
- // forge-lint: disable-next-line(pascal-case-struct)
15
14
  struct CTAllowedPost {
16
15
  address hook;
17
16
  uint24 category;
@@ -10,7 +10,6 @@ pragma solidity ^0.8.0;
10
10
  /// @custom:member maximumSplitPercent The maximum split percent (out of JBConstants.SPLITS_TOTAL_PERCENT) that a
11
11
  /// poster can set. 0 means splits are not allowed.
12
12
  /// @custom:member allowedAddresses A list of addresses that are allowed to post on the category through Croptop.
13
- // forge-lint: disable-next-line(pascal-case-struct)
14
13
  struct CTDeployerAllowedPost {
15
14
  uint24 category;
16
15
  uint104 minimumPrice;
@@ -12,9 +12,7 @@ import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
12
12
  /// @custom:member splitPercent The percent of the tier's price to route to the splits (out of
13
13
  /// JBConstants.SPLITS_TOTAL_PERCENT).
14
14
  /// @custom:member splits The splits to route funds to when this tier is minted.
15
- // forge-lint: disable-next-line(pascal-case-struct)
16
15
  struct CTPost {
17
- // forge-lint: disable-next-line(mixed-case-variable)
18
16
  bytes32 encodedIPFSUri;
19
17
  uint32 totalSupply;
20
18
  uint104 price;
@@ -11,7 +11,6 @@ import {CTDeployerAllowedPost} from "../structs/CTDeployerAllowedPost.sol";
11
11
  /// @param name The name of the collection where posts will go.
12
12
  /// @param symbol The symbol of the collection where posts will go.
13
13
  /// @param salt A salt to use for the deterministic deployment.
14
- // forge-lint: disable-next-line(pascal-case-struct)
15
14
  struct CTProjectConfig {
16
15
  JBTerminalConfig[] terminalConfigurations;
17
16
  string projectUri;
@@ -5,7 +5,6 @@ import {JBSuckerDeployerConfig} from "@bananapus/suckers-v6/src/structs/JBSucker
5
5
 
6
6
  /// @custom:member deployerConfigurations The information for how to suck tokens to other chains.
7
7
  /// @custom:member salt The salt to use for creating suckers so that they use the same address across chains.
8
- // forge-lint: disable-next-line(pascal-case-struct)
9
8
  struct CTSuckerDeploymentConfig {
10
9
  JBSuckerDeployerConfig[] deployerConfigurations;
11
10
  bytes32 salt;
@@ -56,10 +56,11 @@ contract MockDataHook is IJBRulesetDataHook {
56
56
  uint256 cashOutTaxRate,
57
57
  uint256 cashOutCount,
58
58
  uint256 totalSupply,
59
+ uint256 surplusValue,
59
60
  JBCashOutHookSpecification[] memory hookSpecifications
60
61
  )
61
62
  {
62
- return (TAX_RATE, context.cashOutCount, context.totalSupply, hookSpecifications);
63
+ return (TAX_RATE, context.cashOutCount, context.totalSupply, context.surplus.value, hookSpecifications);
63
64
  }
64
65
 
65
66
  function beforePayRecordedWith(JBBeforePayRecordedContext calldata)
@@ -434,7 +435,7 @@ contract TestCTDeployer is Test {
434
435
  JBBeforeCashOutRecordedContext memory context =
435
436
  _buildCashOutContext(deployedProjectId, unauthorized, 100e18, 1000e18);
436
437
 
437
- (uint256 taxRate, uint256 cashOutCount, uint256 totalSupply,) = deployer.beforeCashOutRecordedWith(context);
438
+ (uint256 taxRate, uint256 cashOutCount, uint256 totalSupply,,) = deployer.beforeCashOutRecordedWith(context);
438
439
 
439
440
  assertEq(taxRate, 5000, "tax rate should come from data hook");
440
441
  assertEq(cashOutCount, 100e18, "cashOutCount should be forwarded");
@@ -455,7 +456,7 @@ contract TestCTDeployer is Test {
455
456
  JBBeforeCashOutRecordedContext memory context =
456
457
  _buildCashOutContext(deployedProjectId, suckerAddr, 100e18, 1000e18);
457
458
 
458
- (uint256 taxRate, uint256 cashOutCount, uint256 totalSupply,) = deployer.beforeCashOutRecordedWith(context);
459
+ (uint256 taxRate, uint256 cashOutCount, uint256 totalSupply,,) = deployer.beforeCashOutRecordedWith(context);
459
460
 
460
461
  assertEq(taxRate, 0, "sucker should get 0% tax rate");
461
462
  assertEq(cashOutCount, 100e18, "cashOutCount should pass through");
@@ -466,7 +467,7 @@ contract TestCTDeployer is Test {
466
467
  function test_beforeCashOutRecordedWith_returnsDefaultsWhenNoDataHook() public {
467
468
  JBBeforeCashOutRecordedContext memory context = _buildCashOutContext(999, unauthorized, 100e18, 1000e18);
468
469
 
469
- (uint256 taxRate, uint256 cashOutCount, uint256 totalSupply, JBCashOutHookSpecification[] memory specs) =
470
+ (uint256 taxRate, uint256 cashOutCount, uint256 totalSupply,, JBCashOutHookSpecification[] memory specs) =
470
471
  deployer.beforeCashOutRecordedWith(context);
471
472
 
472
473
  assertEq(taxRate, context.cashOutTaxRate, "cashOutTaxRate should be returned as-is from context");
package/test/Fork.t.sol CHANGED
@@ -21,6 +21,7 @@ import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
21
21
  import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
22
22
  import {JB721TiersHook} from "@bananapus/721-hook-v6/src/JB721TiersHook.sol";
23
23
  import {JB721TiersHookDeployer} from "@bananapus/721-hook-v6/src/JB721TiersHookDeployer.sol";
24
+ import {JB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/JB721CheckpointsDeployer.sol";
24
25
  import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
25
26
 
26
27
  // Suckers — deploy fresh within fork.
@@ -166,7 +167,7 @@ contract ForkTest is Test {
166
167
  jbPermissions = new JBPermissions(trustedForwarder);
167
168
  jbProjects = new JBProjects(multisig, address(0), trustedForwarder);
168
169
  jbDirectory = new JBDirectory(jbPermissions, jbProjects, multisig);
169
- JBERC20 jbErc20 = new JBERC20();
170
+ JBERC20 jbErc20 = new JBERC20(jbPermissions, jbProjects);
170
171
  jbTokens = new JBTokens(jbDirectory, jbErc20);
171
172
  jbRulesets = new JBRulesets(jbDirectory);
172
173
  jbPrices = new JBPrices(jbDirectory, jbPermissions, jbProjects, multisig, trustedForwarder);
@@ -193,9 +194,11 @@ contract ForkTest is Test {
193
194
  function _deploy721Hook() internal {
194
195
  JB721TiersHookStore store = new JB721TiersHookStore();
195
196
  JBAddressRegistry addressRegistry = new JBAddressRegistry();
197
+ JB721CheckpointsDeployer checkpointsDeployer = new JB721CheckpointsDeployer();
196
198
 
197
- JB721TiersHook hookImpl =
198
- new JB721TiersHook(jbDirectory, jbPermissions, jbPrices, jbRulesets, store, jbSplits, trustedForwarder);
199
+ JB721TiersHook hookImpl = new JB721TiersHook(
200
+ jbDirectory, jbPermissions, jbPrices, jbRulesets, store, jbSplits, checkpointsDeployer, trustedForwarder
201
+ );
199
202
 
200
203
  hookDeployer = new JB721TiersHookDeployer(hookImpl, store, addressRegistry, trustedForwarder);
201
204
  }
@@ -37,7 +37,7 @@ contract RevertingDataHook is IJBRulesetDataHook {
37
37
  external
38
38
  pure
39
39
  override
40
- returns (uint256, uint256, uint256, JBCashOutHookSpecification[] memory)
40
+ returns (uint256, uint256, uint256, uint256, JBCashOutHookSpecification[] memory)
41
41
  {
42
42
  revert("DATA_HOOK_REVERTED");
43
43
  }
@@ -81,10 +81,11 @@ contract SuccessDataHook is IJBRulesetDataHook {
81
81
  uint256 cashOutTaxRate,
82
82
  uint256 cashOutCount,
83
83
  uint256 totalSupply,
84
+ uint256 surplusValue,
84
85
  JBCashOutHookSpecification[] memory hookSpecifications
85
86
  )
86
87
  {
87
- return (TAX_RATE, context.cashOutCount, context.totalSupply, hookSpecifications);
88
+ return (TAX_RATE, context.cashOutCount, context.totalSupply, context.surplus.value, hookSpecifications);
88
89
  }
89
90
 
90
91
  function beforePayRecordedWith(JBBeforePayRecordedContext calldata context)
@@ -243,7 +244,7 @@ contract TestAuditGaps is Test {
243
244
  // dataHookOf[999] is address(0) by default (never set).
244
245
  JBBeforeCashOutRecordedContext memory context = _buildCashOutContext(999, unauthorized, 100e18, 1000e18);
245
246
 
246
- (uint256 taxRate, uint256 cashOutCount, uint256 totalSupply, JBCashOutHookSpecification[] memory specs) =
247
+ (uint256 taxRate, uint256 cashOutCount, uint256 totalSupply,, JBCashOutHookSpecification[] memory specs) =
247
248
  deployer.beforeCashOutRecordedWith(context);
248
249
 
249
250
  assertEq(taxRate, context.cashOutTaxRate, "cashOutTaxRate should be returned as-is from context");
@@ -269,7 +270,7 @@ contract TestAuditGaps is Test {
269
270
  JBBeforeCashOutRecordedContext memory context =
270
271
  _buildCashOutContext(hookProjectId, unauthorized, 100e18, 1000e18);
271
272
 
272
- (uint256 taxRate, uint256 cashOutCount, uint256 totalSupply,) = deployer.beforeCashOutRecordedWith(context);
273
+ (uint256 taxRate, uint256 cashOutCount, uint256 totalSupply,,) = deployer.beforeCashOutRecordedWith(context);
273
274
 
274
275
  assertEq(taxRate, 5000, "tax rate should be forwarded from data hook");
275
276
  assertEq(cashOutCount, 100e18, "cashOutCount should be forwarded");
@@ -302,7 +303,7 @@ contract TestAuditGaps is Test {
302
303
  JBBeforeCashOutRecordedContext memory context = _buildCashOutContext(hookProjectId, realSucker, 100e18, 1000e18);
303
304
 
304
305
  // Should NOT revert because suckers bypass the data hook entirely.
305
- (uint256 taxRate, uint256 cashOutCount, uint256 totalSupply,) = deployer.beforeCashOutRecordedWith(context);
306
+ (uint256 taxRate, uint256 cashOutCount, uint256 totalSupply,,) = deployer.beforeCashOutRecordedWith(context);
306
307
 
307
308
  assertEq(taxRate, 0, "sucker should get 0% tax rate");
308
309
  assertEq(cashOutCount, 100e18, "cashOutCount should pass through");
@@ -320,7 +321,7 @@ contract TestAuditGaps is Test {
320
321
  // fakeSucker is NOT registered as a sucker (default mock returns false).
321
322
  JBBeforeCashOutRecordedContext memory context = _buildCashOutContext(hookProjectId, fakeSucker, 100e18, 1000e18);
322
323
 
323
- (uint256 taxRate,,,) = deployer.beforeCashOutRecordedWith(context);
324
+ (uint256 taxRate,,,,) = deployer.beforeCashOutRecordedWith(context);
324
325
 
325
326
  // Should get the data hook's tax rate (5000), not 0.
326
327
  assertEq(taxRate, 5000, "non-sucker should not bypass tax");
@@ -340,7 +341,7 @@ contract TestAuditGaps is Test {
340
341
 
341
342
  JBBeforeCashOutRecordedContext memory context = _buildCashOutContext(hookProjectId, realSucker, 100e18, 1000e18);
342
343
 
343
- (uint256 taxRate,,,) = deployer.beforeCashOutRecordedWith(context);
344
+ (uint256 taxRate,,,,) = deployer.beforeCashOutRecordedWith(context);
344
345
 
345
346
  // Should get the data hook's tax rate, not 0.
346
347
  assertEq(taxRate, 5000, "sucker from wrong project should not bypass tax");
@@ -359,7 +360,7 @@ contract TestAuditGaps is Test {
359
360
 
360
361
  JBBeforeCashOutRecordedContext memory context = _buildCashOutContext(hookProjectId, realSucker, 100e18, 1000e18);
361
362
 
362
- (uint256 taxRate,,,) = deployer.beforeCashOutRecordedWith(context);
363
+ (uint256 taxRate,,,,) = deployer.beforeCashOutRecordedWith(context);
363
364
 
364
365
  assertEq(taxRate, 0, "valid sucker should get 0% tax");
365
366
  }