@rev-net/core-v6 0.0.4 → 0.0.6

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.
@@ -29,7 +29,7 @@ import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/
29
29
 
30
30
  /// @notice Fuzz tests for REVDeployer multi-stage auto-issuance.
31
31
  /// Tests stage ID computation consistency and multi-stage claiming behavior.
32
- /// Related to H-5: stage IDs use block.timestamp + i which may mismatch actual ruleset IDs.
32
+ /// Stage IDs use block.timestamp + i which may mismatch actual ruleset IDs.
33
33
  contract REVAutoIssuanceFuzz_Local is TestBaseWorkflow, JBTest {
34
34
  bytes32 REV_DEPLOYER_SALT = "REVDeployer";
35
35
 
@@ -245,13 +245,13 @@ contract REVAutoIssuanceFuzz_Local is TestBaseWorkflow, JBTest {
245
245
  REV_DEPLOYER.autoIssueFor(revnetId, stageIds[1], multisig());
246
246
  }
247
247
 
248
- // ───────────────── H-5: Stage ID vs Ruleset ID comparison
248
+ // ───────────────── Stage ID vs Ruleset ID comparison
249
249
  // ─────────────────────
250
250
 
251
- /// @notice H-5 EXPLORATION: Compare stored stageIds with actual ruleset IDs.
251
+ /// @notice Compare stored stageIds with actual ruleset IDs.
252
252
  /// Stage IDs use block.timestamp + i during deployment.
253
253
  /// If actual ruleset IDs differ (e.g., on cross-chain deployment), auto-issuance breaks.
254
- function test_H5_stageId_vs_rulesetId_comparison() external {
254
+ function test_stageId_vs_rulesetId_comparison() external {
255
255
  (uint256 revnetId, uint256[] memory stageIds) = _deployMultiStageRevnet(3);
256
256
 
257
257
  // Get the actual rulesets from the controller.
@@ -263,7 +263,7 @@ contract REVAutoIssuanceFuzz_Local is TestBaseWorkflow, JBTest {
263
263
  // For stage 1, the stored key is block.timestamp + 1.
264
264
  // The actual ruleset ID depends on when JBRulesets creates it.
265
265
  // If the ruleset hasn't started yet, getRulesetOf will use the queued ruleset.
266
- // This is where H-5 manifests: the actual ID may differ from block.timestamp + 1.
266
+ // The actual ID may differ from block.timestamp + 1.
267
267
  uint256 stage1StoredKey = stageIds[1]; // block.timestamp + 1
268
268
  uint256 storedAmount = REV_DEPLOYER.amountToAutoIssue(revnetId, stage1StoredKey, multisig());
269
269
  assertGt(storedAmount, 0, "Auto-issuance stored at block.timestamp + 1");
@@ -28,8 +28,8 @@ import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStor
28
28
  import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
29
29
  import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
30
30
 
31
- /// @notice Audit regression tests for REVDeployer findings C-2, C-4, and H-5.
32
- contract REVDeployerAuditRegressions_Local is TestBaseWorkflow, JBTest {
31
+ /// @notice Regression tests for REVDeployer.
32
+ contract REVDeployerRegressions_Local is TestBaseWorkflow, JBTest {
33
33
  using JBRulesetMetadataResolver for JBRuleset;
34
34
 
35
35
  bytes32 REV_DEPLOYER_SALT = "REVDeployer";
@@ -87,13 +87,13 @@ contract REVDeployerAuditRegressions_Local is TestBaseWorkflow, JBTest {
87
87
  }
88
88
 
89
89
  //*********************************************************************//
90
- // --- [C-2] REVDeployer.beforePayRecordedWith Array OOB Regression - //
90
+ // --- REVDeployer.beforePayRecordedWith Array OOB Regression ------- //
91
91
  //*********************************************************************//
92
92
 
93
- /// @notice Tests that the C-2 array OOB pattern manifests when only buybackHook is present.
93
+ /// @notice Tests that the array OOB pattern manifests when only buybackHook is present.
94
94
  /// @dev REVDeployer line 258: hookSpecifications[1] = buybackHookSpecifications[0]
95
95
  /// always writes to index [1], even when the array has size 1 (no tiered721Hook).
96
- function test_C2_arrayOOB_onlyBuybackHook() public pure {
96
+ function test_arrayOOB_onlyBuybackHook() public pure {
97
97
  // Simulate: usesTiered721Hook=false, usesBuybackHook=true
98
98
  bool usesTiered721Hook = false;
99
99
  bool usesBuybackHook = true;
@@ -107,18 +107,18 @@ contract REVDeployerAuditRegressions_Local is TestBaseWorkflow, JBTest {
107
107
  // Index [1] WOULD be written by the bug, but that's OOB
108
108
  // Verify the pattern: writing to index 1 of a size-1 array should revert
109
109
  bool wouldRevert = (!usesTiered721Hook && usesBuybackHook);
110
- assertTrue(wouldRevert, "C-2: this combination triggers the OOB bug");
110
+ assertTrue(wouldRevert, "this combination triggers the OOB bug");
111
111
 
112
112
  // Verify the safe index: the buyback hook should go at index 0 when no tiered hook
113
113
  uint256 correctIndex = usesTiered721Hook ? 1 : 0;
114
- assertEq(correctIndex, 0, "C-2 FIX: buyback hook should use index 0 when no tiered hook");
114
+ assertEq(correctIndex, 0, "buyback hook should use index 0 when no tiered hook");
115
115
 
116
116
  // Write to the correct index (no revert)
117
117
  specs[correctIndex] = JBPayHookSpecification({hook: IJBPayHook(address(0xbeef)), amount: 1 ether, metadata: ""});
118
118
  }
119
119
 
120
120
  /// @notice Verify both hooks present works fine (no OOB).
121
- function test_C2_noOOB_bothHooksPresent() public pure {
121
+ function test_noOOB_bothHooksPresent() public pure {
122
122
  bool usesTiered721Hook = true;
123
123
  bool usesBuybackHook = true;
124
124
 
@@ -131,13 +131,13 @@ contract REVDeployerAuditRegressions_Local is TestBaseWorkflow, JBTest {
131
131
  }
132
132
 
133
133
  //*********************************************************************//
134
- // --- [C-4] hasMintPermissionFor returns false for random addresses - //
134
+ // --- hasMintPermissionFor returns false for random addresses ------- //
135
135
  //*********************************************************************//
136
136
 
137
137
  /// @notice Tests that calling hasMintPermissionFor returns false for random addresses.
138
138
  /// @dev With the buyback hook removed, hasMintPermissionFor should return false
139
139
  /// for addresses that are not the loans contract or a sucker.
140
- function test_C4_hasMintPermissionFor_noBuybackHook() public {
140
+ function test_hasMintPermissionFor_noBuybackHook() public {
141
141
  // Deploy a revnet WITHOUT a buyback hook
142
142
  JBAccountingContext[] memory accountingContextsToAccept = new JBAccountingContext[](1);
143
143
  accountingContextsToAccept[0] = JBAccountingContext({
@@ -191,17 +191,17 @@ contract REVDeployerAuditRegressions_Local is TestBaseWorkflow, JBTest {
191
191
  // With buyback hook removed, hasMintPermissionFor should return false
192
192
  // for addresses that are not the loans contract or a sucker.
193
193
  bool hasPerm = REV_DEPLOYER.hasMintPermissionFor(revnetId, currentRuleset, someRandomAddr);
194
- assertFalse(hasPerm, "C-4: random address should not have mint permission");
194
+ assertFalse(hasPerm, "random address should not have mint permission");
195
195
  }
196
196
 
197
197
  //*********************************************************************//
198
- // --- [H-5] Auto-Issuance Stage ID Mismatch ----------------------- //
198
+ // --- Auto-Issuance Stage ID Mismatch ------------------------------ //
199
199
  //*********************************************************************//
200
200
 
201
201
  /// @notice Tests that auto-issuance stage IDs are computed correctly for multi-stage revnets.
202
- /// @dev H-5: The stage ID is computed as `block.timestamp + i` which only works if stages
202
+ /// @dev The stage ID is computed as `block.timestamp + i` which only works if stages
203
203
  /// are deployed in a specific order at a specific time.
204
- function test_H5_autoIssuanceStageIdMismatch() public {
204
+ function test_autoIssuanceStageIdMismatch() public {
205
205
  JBAccountingContext[] memory accountingContextsToAccept = new JBAccountingContext[](1);
206
206
  accountingContextsToAccept[0] = JBAccountingContext({
207
207
  token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
@@ -241,7 +241,7 @@ contract REVDeployerAuditRegressions_Local is TestBaseWorkflow, JBTest {
241
241
  });
242
242
  }
243
243
 
244
- // Stage 1: also has auto-issuance — this is where H-5 manifests
244
+ // Stage 1: also has auto-issuance — this is where the mismatch manifests
245
245
  {
246
246
  REVAutoIssuance[] memory issuanceConfs = new REVAutoIssuance[](1);
247
247
  issuanceConfs[0] = REVAutoIssuance({
@@ -294,7 +294,7 @@ contract REVDeployerAuditRegressions_Local is TestBaseWorkflow, JBTest {
294
294
  // Verify the revnet was deployed
295
295
  assertGt(revnetId, 0, "revnet should be deployed");
296
296
 
297
- // The H-5 bug: auto-issuance for stage 1 is stored at key (block.timestamp + 1),
297
+ // The bug: auto-issuance for stage 1 is stored at key (block.timestamp + 1),
298
298
  // but the actual ruleset ID for stage 1 is the timestamp when that stage's ruleset
299
299
  // was queued. These may not match.
300
300
  // We verify the auto-issuance amounts are stored and can be queried.
@@ -303,12 +303,12 @@ contract REVDeployerAuditRegressions_Local is TestBaseWorkflow, JBTest {
303
303
  // Stage 0 auto-issuance should be stored at block.timestamp
304
304
  assertEq(stage0Amount, 50_000 * decimalMultiplier, "Stage 0 auto-issuance should be stored at block.timestamp");
305
305
 
306
- // Stage 1 auto-issuance is stored at (block.timestamp + 1) per H-5
306
+ // Stage 1 auto-issuance is stored at (block.timestamp + 1)
307
307
  uint256 stage1Amount = REV_DEPLOYER.amountToAutoIssue(revnetId, block.timestamp + 1, multisig());
308
308
  assertEq(
309
309
  stage1Amount,
310
310
  30_000 * decimalMultiplier,
311
- "H-5: Stage 1 auto-issuance stored at block.timestamp + 1 (may not match ruleset ID)"
311
+ "Stage 1 auto-issuance stored at block.timestamp + 1 (may not match ruleset ID)"
312
312
  );
313
313
 
314
314
  // Now check the actual ruleset IDs to demonstrate the mismatch
@@ -316,12 +316,12 @@ contract REVDeployerAuditRegressions_Local is TestBaseWorkflow, JBTest {
316
316
  if (rulesets.length >= 2) {
317
317
  uint256 actualStage1RulesetId = rulesets[1].id;
318
318
 
319
- // The H-5 mismatch: the storage key (block.timestamp + 1) likely != the actual ruleset ID
319
+ // The mismatch: the storage key (block.timestamp + 1) likely != the actual ruleset ID
320
320
  // If they don't match, auto-issuance tokens for stage 1 become unclaimable
321
321
  if (actualStage1RulesetId != block.timestamp + 1) {
322
322
  // Verify the amount at the ACTUAL ruleset ID is 0 (the mismatch)
323
323
  uint256 amountAtActualId = REV_DEPLOYER.amountToAutoIssue(revnetId, actualStage1RulesetId, multisig());
324
- assertEq(amountAtActualId, 0, "H-5 CONFIRMED: auto-issuance at actual ruleset ID is 0 (mismatch)");
324
+ assertEq(amountAtActualId, 0, "auto-issuance at actual ruleset ID is 0 (mismatch)");
325
325
  }
326
326
  }
327
327
  }
@@ -51,9 +51,9 @@ struct InvincibilityProjectConfig {
51
51
  }
52
52
 
53
53
  // =========================================================================
54
- // Section A + B: Fix Verification & Economic Attack Tests
54
+ // Section A + B: Property Verification & Economic Tests
55
55
  // =========================================================================
56
- contract REVInvincibility_FixVerify is TestBaseWorkflow, JBTest {
56
+ contract REVInvincibility_PropertyTests is TestBaseWorkflow, JBTest {
57
57
  using JBRulesetMetadataResolver for JBRuleset;
58
58
 
59
59
  bytes32 REV_DEPLOYER_SALT = "REVDeployer";
@@ -285,21 +285,21 @@ contract REVInvincibility_FixVerify is TestBaseWorkflow, JBTest {
285
285
  }
286
286
 
287
287
  // =====================================================================
288
- // SECTION A: Critical Fix Verification (8 tests)
288
+ // SECTION A: Critical Property Verification (8 tests)
289
289
  // =====================================================================
290
290
 
291
- /// @notice C-1: Borrow with collateral > uint112.max silently truncates loan.amount.
291
+ /// @notice Borrow with collateral > uint112.max silently truncates loan.amount.
292
292
  /// @dev Verifies the truncation pattern: uint112(overflowValue) wraps.
293
- function test_fixVerify_C1_uint112Truncation() public {
293
+ function test_fixVerify_uint112Truncation() public {
294
294
  // Prove the truncation math: uint112(max+1) wraps to 0
295
295
  uint256 overflowValue = uint256(type(uint112).max) + 1;
296
296
  uint112 truncated = uint112(overflowValue);
297
- assertEq(truncated, 0, "C-1: uint112 truncation wraps max+1 to 0");
297
+ assertEq(truncated, 0, "uint112 truncation wraps max+1 to 0");
298
298
 
299
299
  // Prove a more realistic overflow: max + 1000 wraps to 999
300
300
  uint256 slightlyOver = uint256(type(uint112).max) + 1000;
301
301
  truncated = uint112(slightlyOver);
302
- assertEq(truncated, 999, "C-1: uint112 truncation wraps to low bits");
302
+ assertEq(truncated, 999, "uint112 truncation wraps to low bits");
303
303
 
304
304
  // Verify normal operation stays within bounds
305
305
  uint256 payAmount = 100e18;
@@ -309,35 +309,35 @@ contract REVInvincibility_FixVerify is TestBaseWorkflow, JBTest {
309
309
 
310
310
  uint256 borrowable =
311
311
  LOANS_CONTRACT.borrowableAmountFrom(REVNET_ID, tokens, 18, uint32(uint160(JBConstants.NATIVE_TOKEN)));
312
- assertLt(borrowable, type(uint112).max, "C-1: normal borrowable within uint112");
313
- assertLt(tokens, type(uint112).max, "C-1: normal token count within uint112");
312
+ assertLt(borrowable, type(uint112).max, "normal borrowable within uint112");
313
+ assertLt(tokens, type(uint112).max, "normal token count within uint112");
314
314
  }
315
315
 
316
- /// @notice C-2: Array OOB when only buyback hook present (no tiered721Hook).
316
+ /// @notice Array OOB when only buyback hook present (no tiered721Hook).
317
317
  /// @dev hookSpecifications[1] is written but array size is 1.
318
- function test_fixVerify_C2_arrayOOB_noBuybackWithBuyback() public pure {
318
+ function test_fixVerify_arrayOOB_noBuybackWithBuyback() public pure {
319
319
  bool usesTiered721Hook = false;
320
320
  bool usesBuybackHook = true;
321
321
 
322
322
  uint256 arraySize = (usesTiered721Hook ? 1 : 0) + (usesBuybackHook ? 1 : 0);
323
- assertEq(arraySize, 1, "C-2: array size is 1");
323
+ assertEq(arraySize, 1, "array size is 1");
324
324
 
325
325
  // The bug: code writes to hookSpecifications[1] (OOB for size-1 array)
326
326
  // The fix: should write to index 0 when no tiered721Hook
327
327
  bool wouldOOB = (!usesTiered721Hook && usesBuybackHook);
328
- assertTrue(wouldOOB, "C-2: this config triggers the OOB write at index [1]");
328
+ assertTrue(wouldOOB, "this config triggers the OOB write at index [1]");
329
329
 
330
330
  uint256 correctIndex = usesTiered721Hook ? 1 : 0;
331
- assertEq(correctIndex, 0, "C-2 FIX: buyback hook should use index 0");
331
+ assertEq(correctIndex, 0, "buyback hook should use index 0");
332
332
 
333
333
  // Verify safe write
334
334
  JBPayHookSpecification[] memory specs = new JBPayHookSpecification[](arraySize);
335
335
  specs[correctIndex] = JBPayHookSpecification({hook: IJBPayHook(address(0xbeef)), amount: 1 ether, metadata: ""});
336
336
  }
337
337
 
338
- /// @notice C-3: Reentrancy — _adjust calls terminal.pay() BEFORE writing loan state.
338
+ /// @notice Reentrancy — _adjust calls terminal.pay() BEFORE writing loan state.
339
339
  /// @dev Lines 910 (external call) vs 922-923 (state writes). CEI violation.
340
- function test_fixVerify_C3_reentrancyDoubleBorrow() public {
340
+ function test_fixVerify_reentrancyDoubleBorrow() public {
341
341
  // Create a legitimate loan to confirm the system works
342
342
  uint256 payAmount = 10e18;
343
343
  vm.prank(USER);
@@ -366,25 +366,25 @@ contract REVInvincibility_FixVerify is TestBaseWorkflow, JBTest {
366
366
  // (We can't actually execute the attack through real contracts because
367
367
  // the fee terminal is the legitimate JBMultiTerminal, but the pattern
368
368
  // is confirmed by code inspection)
369
- assertTrue(true, "C-3: CEI violation confirmed at lines 910 vs 922-923");
369
+ assertTrue(true, "CEI pattern verified at lines 910 vs 922-923");
370
370
  }
371
371
 
372
- /// @notice C-4: hasMintPermissionFor returns false for random addresses.
372
+ /// @notice hasMintPermissionFor returns false for random addresses.
373
373
  /// @dev With the buyback hook removed, hasMintPermissionFor should return false
374
374
  /// for addresses that are not the loans contract or a sucker.
375
- function test_fixVerify_C4_hasMintPermission_noBuyback() public {
375
+ function test_fixVerify_hasMintPermission_noBuyback() public {
376
376
  // The fee project was deployed without buyback hook in our setup
377
377
  JBRuleset memory currentRuleset = jbRulesets().currentOf(FEE_PROJECT_ID);
378
378
 
379
379
  // hasMintPermissionFor should return false for random addresses
380
380
  address randomAddr = address(0x12345);
381
381
  bool hasPerm = REV_DEPLOYER.hasMintPermissionFor(FEE_PROJECT_ID, currentRuleset, randomAddr);
382
- assertFalse(hasPerm, "C-4: random address should not have mint permission");
382
+ assertFalse(hasPerm, "random address should not have mint permission");
383
383
  }
384
384
 
385
- /// @notice C-5: Zero-supply cash out no longer drains surplus (fixed in v6).
385
+ /// @notice Zero-supply cash out no longer drains surplus (fixed in v6).
386
386
  /// @dev JBCashOuts.cashOutFrom now returns 0 when cashOutCount == 0.
387
- function test_fixVerify_C5_zeroSupplyCashOutDrain() public pure {
387
+ function test_fixVerify_zeroSupplyCashOutDrain() public pure {
388
388
  uint256 surplus = 100e18;
389
389
  uint256 cashOutCount = 0;
390
390
  uint256 totalSupply = 0;
@@ -393,17 +393,17 @@ contract REVInvincibility_FixVerify is TestBaseWorkflow, JBTest {
393
393
  uint256 reclaimable = JBCashOuts.cashOutFrom(surplus, cashOutCount, totalSupply, cashOutTaxRate);
394
394
 
395
395
  // Fixed in v6: cashing out 0 tokens always returns 0
396
- assertEq(reclaimable, 0, "C-5 fixed: zero cash out returns nothing");
396
+ assertEq(reclaimable, 0, "zero cash out returns nothing");
397
397
 
398
398
  // Normal case: with supply, cashing out 0 still returns 0
399
399
  uint256 normalReclaimable = JBCashOuts.cashOutFrom(surplus, 0, 1000e18, cashOutTaxRate);
400
400
  assertEq(normalReclaimable, 0, "Normal: cashing out 0 of non-zero supply returns 0");
401
401
  }
402
402
 
403
- /// @notice H-2: Broken fee terminal + broken addToBalanceOf fallback bricks cash-outs.
403
+ /// @notice Broken fee terminal + broken addToBalanceOf fallback bricks cash-outs.
404
404
  /// @dev afterCashOutRecordedWith: try feeTerminal.pay() catch { addToBalanceOf() }
405
405
  /// If BOTH revert, the entire cash-out transaction reverts.
406
- function test_fixVerify_H2_brokenFeeTerminalBricksCashOuts() public {
406
+ function test_fixVerify_brokenFeeTerminalBricksCashOuts() public {
407
407
  BrokenFeeTerminal brokenTerminal = new BrokenFeeTerminal();
408
408
 
409
409
  // The vulnerability pattern:
@@ -429,10 +429,10 @@ contract REVInvincibility_FixVerify is TestBaseWorkflow, JBTest {
429
429
  brokenTerminal.addToBalanceOf(0, address(0), 0, false, "", "");
430
430
  }
431
431
 
432
- /// @notice H-5: Auto-issuance stored at block.timestamp+i, not actual ruleset IDs.
432
+ /// @notice Auto-issuance stored at block.timestamp+i, not actual ruleset IDs.
433
433
  /// @dev _makeRulesetConfigurations stores at block.timestamp+i but autoIssueFor
434
434
  /// queries by actual ruleset ID. If they mismatch, tokens are unclaimable.
435
- function test_fixVerify_H5_autoIssuanceStageIdMismatch() public {
435
+ function test_fixVerify_autoIssuanceStageIdMismatch() public {
436
436
  // Deploy a multi-stage revnet with auto-issuance on multiple stages
437
437
  JBAccountingContext[] memory ctx = new JBAccountingContext[](1);
438
438
  ctx[0] = JBAccountingContext({
@@ -495,13 +495,13 @@ contract REVInvincibility_FixVerify is TestBaseWorkflow, JBTest {
495
495
 
496
496
  // Stage 0 auto-issuance stored at block.timestamp
497
497
  uint256 stage0Amount = REV_DEPLOYER.amountToAutoIssue(h5RevnetId, block.timestamp, multisig());
498
- assertEq(stage0Amount, 50_000e18, "H-5: Stage 0 auto-issuance stored at block.timestamp");
498
+ assertEq(stage0Amount, 50_000e18, "Stage 0 auto-issuance stored at block.timestamp");
499
499
 
500
- // Stage 1 auto-issuance stored at block.timestamp + 1 (the H-5 bug)
500
+ // Stage 1 auto-issuance stored at block.timestamp + 1 (the stage ID mismatch bug)
501
501
  uint256 stage1Amount = REV_DEPLOYER.amountToAutoIssue(h5RevnetId, block.timestamp + 1, multisig());
502
- assertEq(stage1Amount, 30_000e18, "H-5: Stage 1 auto-issuance stored at block.timestamp + 1");
502
+ assertEq(stage1Amount, 30_000e18, "Stage 1 auto-issuance stored at block.timestamp + 1");
503
503
 
504
- // The H-5 bug: stages are stored at (block.timestamp + i), not at the actual ruleset IDs.
504
+ // The bug: stages are stored at (block.timestamp + i), not at the actual ruleset IDs.
505
505
  // In the test environment, stages queued in the same block happen to have sequential IDs
506
506
  // (block.timestamp, block.timestamp+1), so the storage keys coincidentally match.
507
507
  // However, if deployment happens at a different time than block.timestamp, or if stages
@@ -515,20 +515,20 @@ contract REVInvincibility_FixVerify is TestBaseWorkflow, JBTest {
515
515
  // Document the storage keys used vs what autoIssueFor expects
516
516
  // autoIssueFor calls with the CURRENT ruleset's ID (from currentOf).
517
517
  // If the ruleset ID != block.timestamp+i, the amount at that key is 0.
518
- emit log_named_uint("H-5: Storage key for stage 1", block.timestamp + 1);
519
- emit log_named_uint("H-5: Actual ruleset[0].id (most recent)", rulesets[0].id);
520
- emit log_named_uint("H-5: Actual ruleset[1].id (first)", rulesets[1].id);
518
+ emit log_named_uint("Storage key for stage 1", block.timestamp + 1);
519
+ emit log_named_uint("Actual ruleset[0].id (most recent)", rulesets[0].id);
520
+ emit log_named_uint("Actual ruleset[1].id (first)", rulesets[1].id);
521
521
 
522
522
  // The fragility: stage 1 issuance is ONLY accessible at (block.timestamp + 1).
523
523
  // Any other key returns 0.
524
524
  uint256 wrongKey = block.timestamp + 100;
525
525
  uint256 amountAtWrongKey = REV_DEPLOYER.amountToAutoIssue(h5RevnetId, wrongKey, multisig());
526
- assertEq(amountAtWrongKey, 0, "H-5: auto-issuance unreachable at wrong key");
526
+ assertEq(amountAtWrongKey, 0, "auto-issuance unreachable at wrong key");
527
527
  }
528
528
 
529
- /// @notice H-6: Unvalidated source terminal — unbounded _loanSourcesOf array growth.
529
+ /// @notice Unvalidated source terminal — unbounded _loanSourcesOf array growth.
530
530
  /// @dev borrowFrom accepts any terminal in REVLoanSource without validation.
531
- function test_fixVerify_H6_unvalidatedSourceTerminal() public {
531
+ function test_fixVerify_unvalidatedSourceTerminal() public {
532
532
  // The vulnerability: REVLoans._addTo (line 788-791) registers ANY terminal
533
533
  // as a loan source without validating it's an actual project terminal:
534
534
  // if (!isLoanSourceOf[revnetId][loan.source.terminal][loan.source.token]) {
@@ -555,12 +555,12 @@ contract REVInvincibility_FixVerify is TestBaseWorkflow, JBTest {
555
555
  assertEq(sourcesAfter.length, 1, "One loan source registered after first borrow");
556
556
  assertEq(address(sourcesAfter[0].terminal), address(jbMultiTerminal()), "Source should be multi terminal");
557
557
 
558
- // H-6: The vulnerability is that _addTo registers ANY terminal passed in REVLoanSource.
558
+ // The vulnerability is that _addTo registers ANY terminal passed in REVLoanSource.
559
559
  // There's no validation that the terminal is actually a terminal for the project.
560
560
  // This means an attacker could register fake terminals, growing the array unboundedly.
561
561
  assertTrue(
562
562
  LOANS_CONTRACT.isLoanSourceOf(REVNET_ID, jbMultiTerminal(), JBConstants.NATIVE_TOKEN),
563
- "H-6: source registered without terminal validation"
563
+ "source registered without terminal validation"
564
564
  );
565
565
  }
566
566
 
@@ -674,7 +674,7 @@ contract REVInvincibility_FixVerify is TestBaseWorkflow, JBTest {
674
674
  }
675
675
 
676
676
  /// @notice Flash loan surplus inflation: addToBalance → borrow at inflated rate.
677
- /// @dev M-11: surplus is read live, so an addToBalance before borrow inflates it.
677
+ /// @dev Surplus is read live, so an addToBalance before borrow inflates it.
678
678
  function test_econ_flashLoanSurplusInflation() public {
679
679
  // Step 1: Pay to get tokens
680
680
  uint256 payAmount = 5e18;
@@ -694,14 +694,14 @@ contract REVInvincibility_FixVerify is TestBaseWorkflow, JBTest {
694
694
  uint256 borrowableAfter =
695
695
  LOANS_CONTRACT.borrowableAmountFrom(REVNET_ID, tokens, 18, uint32(uint160(JBConstants.NATIVE_TOKEN)));
696
696
 
697
- // M-11: The borrowable amount increases because surplus grew but totalSupply didn't
698
- assertTrue(borrowableAfter > borrowableBefore, "M-11: surplus inflation increases borrowable amount");
697
+ // The borrowable amount increases because surplus grew but totalSupply didn't
698
+ assertTrue(borrowableAfter > borrowableBefore, "surplus inflation increases borrowable amount");
699
699
 
700
700
  // Quantify the inflation factor
701
701
  if (borrowableBefore > 0) {
702
702
  uint256 inflationFactor = mulDiv(borrowableAfter, 1e18, borrowableBefore);
703
- assertTrue(inflationFactor > 1e18, "M-11: inflation factor > 1x");
704
- emit log_named_uint("M-11 inflation factor (1e18=1x)", inflationFactor);
703
+ assertTrue(inflationFactor > 1e18, "inflation factor > 1x");
704
+ emit log_named_uint("inflation factor (1e18=1x)", inflationFactor);
705
705
  }
706
706
  }
707
707
 
@@ -858,11 +858,11 @@ contract REVInvincibility_FixVerify is TestBaseWorkflow, JBTest {
858
858
  }
859
859
  }
860
860
 
861
- /// @notice H-1: Double fee — REVDeployer not registered as feeless.
861
+ /// @notice Double fee — REVDeployer not registered as feeless.
862
862
  /// @dev Cash-out fee goes to REVDeployer (afterCashOutRecordedWith) which pays fee terminal.
863
863
  /// But the JBMultiTerminal's useAllowanceOf already took a protocol fee,
864
864
  /// so the fee payment to the fee terminal is a second fee on the same funds.
865
- function test_econ_doubleFeeH1() public {
865
+ function test_econ_doubleFee() public {
866
866
  // Pay into revnet
867
867
  vm.prank(USER);
868
868
  uint256 tokens =
@@ -896,7 +896,7 @@ contract REVInvincibility_FixVerify is TestBaseWorkflow, JBTest {
896
896
  }) returns (
897
897
  uint256 reclaimAmount
898
898
  ) {
899
- // The H-1 double fee means the fee project gets more than expected
899
+ // The double fee means the fee project gets more than expected
900
900
  // because both the terminal fee AND the revnet fee route to it
901
901
  uint256 feeBalanceAfter;
902
902
  {
@@ -914,7 +914,7 @@ contract REVInvincibility_FixVerify is TestBaseWorkflow, JBTest {
914
914
  emit log_named_uint("Reclaim amount", reclaimAmount);
915
915
  } catch {
916
916
  // Cash out may fail (e.g., if fee terminal isn't set up) — document the failure
917
- emit log("H-1: Cash-out reverted (may be due to fee terminal setup)");
917
+ emit log("Cash-out reverted (may be due to fee terminal setup)");
918
918
  }
919
919
  }
920
920
  }
@@ -583,7 +583,7 @@ contract InvariantREVLoansTests is StdInvariant, TestBaseWorkflow, JBTest {
583
583
  return LOANS_CONTRACT.totalBorrowedFrom(_revnetId, jbMultiTerminal(), JBConstants.NATIVE_TOKEN);
584
584
  }
585
585
 
586
- /// @notice INV-RL-3: loan.amount <= type(uint112).max for all active loans (C-1 regression).
586
+ /// @notice INV-RL-3: loan.amount <= type(uint112).max for all active loans.
587
587
  /// @dev Verifies that no loan amount exceeds the uint112 storage boundary.
588
588
  function invariant_C_LoanAmountFitsUint112() public view {
589
589
  if (PAY_HANDLER.RUNS() == 0) return;
@@ -35,7 +35,7 @@ import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
35
35
  import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
36
36
 
37
37
  /// @notice A malicious terminal that re-enters REVLoans during fee payment in _adjust().
38
- /// @dev Used to prove C-3: reentrancy during pay() callback in _adjust.
38
+ /// @dev Reentrancy during pay() callback in _adjust.
39
39
  contract ReentrantTerminal is ERC165, IJBPayoutTerminal {
40
40
  IREVLoans public loans;
41
41
  uint256 public revnetId;
@@ -174,7 +174,7 @@ struct AttackProjectConfig {
174
174
  }
175
175
 
176
176
  /// @title REVLoansAttacks
177
- /// @notice Attack tests for REVLoans covering C-1 uint112 truncation, C-3 reentrancy,
177
+ /// @notice Attack tests for REVLoans covering uint112 truncation, reentrancy,
178
178
  /// collateral race conditions, liquidation edge cases, and fuzz testing.
179
179
  contract REVLoansAttacks is TestBaseWorkflow, JBTest {
180
180
  bytes32 REV_DEPLOYER_SALT = "REVDeployer";
@@ -412,10 +412,10 @@ contract REVLoansAttacks is TestBaseWorkflow, JBTest {
412
412
  }
413
413
 
414
414
  // =========================================================================
415
- // C-1: uint112 truncation — loan amount silently wraps
415
+ // uint112 truncation — loan amount silently wraps
416
416
  // =========================================================================
417
417
  /// @notice Verify that borrowing an amount > uint112.max is properly handled.
418
- /// @dev C-1: The _adjust function casts newBorrowAmount to uint112 without overflow checks.
418
+ /// @dev The _adjust function casts newBorrowAmount to uint112 without overflow checks.
419
419
  /// If borrowAmount exceeds uint112.max, it silently truncates. This test verifies the behavior.
420
420
  function test_uint112Truncation_loanAmountSilentlyTruncates() public {
421
421
  // uint112.max = 5192296858534827628530496329220095
@@ -449,10 +449,10 @@ contract REVLoansAttacks is TestBaseWorkflow, JBTest {
449
449
  }
450
450
 
451
451
  // =========================================================================
452
- // C-1 variant: collateral > uint112.max wraps
452
+ // collateral > uint112.max wraps
453
453
  // =========================================================================
454
454
  /// @notice Verify that collateral > uint112.max would be truncated in the loan struct.
455
- /// @dev C-1 variant: loan.collateral = uint112(newCollateralCount) truncates silently.
455
+ /// @dev loan.collateral = uint112(newCollateralCount) truncates silently.
456
456
  function test_uint112Truncation_collateralTruncates() public {
457
457
  // Verify the truncation math
458
458
  uint256 maxCollateral = type(uint112).max;
@@ -476,10 +476,10 @@ contract REVLoansAttacks is TestBaseWorkflow, JBTest {
476
476
  }
477
477
 
478
478
  // =========================================================================
479
- // C-3: reentrancy — _adjust calls terminal.pay() which could re-enter
479
+ // reentrancy — _adjust calls terminal.pay() which could re-enter
480
480
  // =========================================================================
481
481
  /// @notice Verify that reentrancy during _adjust's fee payment is handled.
482
- /// @dev C-3: The _adjust function calls loan.source.terminal.pay() to pay fees.
482
+ /// @dev The _adjust function calls loan.source.terminal.pay() to pay fees.
483
483
  /// A malicious terminal could use this callback to re-enter borrowFrom().
484
484
  /// Since Solidity 0.8.23 doesn't have native reentrancy guards on REVLoans,
485
485
  /// the state (loan.amount, loan.collateral) is written AFTER the external call.
@@ -508,14 +508,14 @@ contract REVLoansAttacks is TestBaseWorkflow, JBTest {
508
508
  // This is a checks-effects-interactions violation.
509
509
  // The loan amount and collateral are read from storage during _borrowAmountFrom,
510
510
  // so a re-entrant call would see stale values.
511
- assertTrue(true, "C-3: reentrancy window confirmed between terminal.pay() and state writes");
511
+ assertTrue(true, "reentrancy window confirmed between terminal.pay() and state writes");
512
512
  }
513
513
 
514
514
  // =========================================================================
515
- // C-3 variant: re-enter repayLoan during fee payment
515
+ // re-enter repayLoan during fee payment
516
516
  // =========================================================================
517
517
  /// @notice Verify that reentering repayLoan during _adjust's fee payment is handled.
518
- /// @dev C-3 variant: malicious terminal calls repayLoan() during fee payment.
518
+ /// @dev Malicious terminal calls repayLoan() during fee payment.
519
519
  function test_reentrancy_adjustRepayReenter() public {
520
520
  // Similar to above, but the re-entrant call targets repayLoan instead of borrowFrom.
521
521
  // The concern is that during _adjust → terminal.pay(), a call to repayLoan
@@ -32,7 +32,7 @@ import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/
32
32
  import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
33
33
 
34
34
  /// @notice A fake terminal that tracks whether useAllowanceOf was called.
35
- /// @dev Used to prove H-6: REVLoans.borrowFrom does not validate source terminal registration.
35
+ /// @dev REVLoans.borrowFrom does not validate source terminal registration.
36
36
  contract FakeTerminal is ERC165, IJBPayoutTerminal {
37
37
  bool public useAllowanceCalled;
38
38
  uint256 public lastProjectId;
@@ -128,8 +128,8 @@ contract FakeTerminal is ERC165, IJBPayoutTerminal {
128
128
  }
129
129
  }
130
130
 
131
- /// @notice Audit regression tests for REVLoans finding H-6: Unvalidated Source Terminal.
132
- contract REVLoansAuditRegressions_Local is TestBaseWorkflow, JBTest {
131
+ /// @notice Regression tests for REVLoans unvalidated source terminal.
132
+ contract REVLoansRegressions_Local is TestBaseWorkflow, JBTest {
133
133
  bytes32 REV_DEPLOYER_SALT = "REVDeployer";
134
134
  bytes32 ERC20_SALT = "REV_TOKEN";
135
135
 
@@ -239,14 +239,14 @@ contract REVLoansAuditRegressions_Local is TestBaseWorkflow, JBTest {
239
239
  }
240
240
 
241
241
  //*********************************************************************//
242
- // --- [H-6] Unvalidated Source Terminal in REVLoans ---------------- //
242
+ // --- Unvalidated Source Terminal in REVLoans ---------------------- //
243
243
  //*********************************************************************//
244
244
 
245
- /// @notice Demonstrates H-6: borrowFrom accepts any terminal without validating
245
+ /// @notice Demonstrates that borrowFrom accepts any terminal without validating
246
246
  /// it is registered in the JBDirectory for the project.
247
- /// @dev The fake terminal's useAllowanceOf is called, proving no directory check occurs.
247
+ /// @dev The fake terminal's useAllowanceOf is called, showing no directory check occurs.
248
248
  /// In production, a malicious terminal could return fake amounts or misroute funds.
249
- function test_H6_unvalidatedSourceTerminal() public {
249
+ function test_unvalidatedSourceTerminal() public {
250
250
  // Step 1: User pays into the revnet to get tokens (collateral)
251
251
  vm.prank(USER);
252
252
  uint256 tokens = jbMultiTerminal().pay{value: 1e18}(REVNET_ID, JBConstants.NATIVE_TOKEN, 1e18, USER, 0, "", "");
@@ -266,7 +266,7 @@ contract REVLoansAuditRegressions_Local is TestBaseWorkflow, JBTest {
266
266
  assertFalse(found, "fake terminal should NOT be in the directory");
267
267
 
268
268
  // Step 3: Try to borrow using the fake terminal as the source
269
- // H-6 vulnerability: REVLoans.borrowFrom does NOT check if the terminal
269
+ // Vulnerability: REVLoans.borrowFrom does NOT check if the terminal
270
270
  // is registered in the directory before calling useAllowanceOf on it.
271
271
  REVLoanSource memory fakeSource =
272
272
  REVLoanSource({token: JBConstants.NATIVE_TOKEN, terminal: IJBPayoutTerminal(address(fakeTerminal))});
@@ -275,7 +275,7 @@ contract REVLoansAuditRegressions_Local is TestBaseWorkflow, JBTest {
275
275
  LOANS_CONTRACT.borrowableAmountFrom(REVNET_ID, tokens, 18, uint32(uint160(JBConstants.NATIVE_TOKEN)));
276
276
  assertGt(borrowable, 0, "should have borrowable amount");
277
277
 
278
- // H-6 PROOF: Use vm.expectCall to prove the fake terminal's useAllowanceOf
278
+ // Use vm.expectCall to verify the fake terminal's useAllowanceOf
279
279
  // is called. This works even if the outer call reverts, because expectCall
280
280
  // records the call was made regardless.
281
281
  // The code calls accountingContextForTokenOf first, then useAllowanceOf.
@@ -287,7 +287,7 @@ contract REVLoansAuditRegressions_Local is TestBaseWorkflow, JBTest {
287
287
  );
288
288
  vm.expectCall(address(fakeTerminal), abi.encodeWithSelector(IJBPayoutTerminal.useAllowanceOf.selector));
289
289
 
290
- // The borrow will reach the fake terminal (proving no validation),
290
+ // The borrow will reach the fake terminal (showing no validation),
291
291
  // but will revert downstream when trying to transfer 0 - fees (underflow).
292
292
  vm.prank(USER);
293
293
  vm.expectRevert();
@@ -296,11 +296,11 @@ contract REVLoansAuditRegressions_Local is TestBaseWorkflow, JBTest {
296
296
  // If we reach here, both vm.expectCall checks passed:
297
297
  // 1. accountingContextForTokenOf was called on the fake terminal
298
298
  // 2. useAllowanceOf was called on the fake terminal
299
- // This proves H-6: no directory validation before calling the source terminal
299
+ // This shows no directory validation before calling the source terminal
300
300
  }
301
301
 
302
302
  /// @notice Verify that the configured loan source (real terminal) is properly registered.
303
- function test_H6_configuredSourceIsRegistered() public {
303
+ function test_configuredSourceIsRegistered() public {
304
304
  // The real terminal should be in the directory
305
305
  IJBTerminal[] memory terminals = jbDirectory().terminalsOf(REVNET_ID);
306
306
  bool found = false;
@@ -831,7 +831,7 @@ contract REVLoansSourcedTests is TestBaseWorkflow, JBTest {
831
831
 
832
832
  uint256 amountDiff = borrowableFromNewCollateral > loan.amount ? 0 : loan.amount - borrowableFromNewCollateral;
833
833
 
834
- // Skip fuzz runs where both repay amount and collateral return are zero (M-27 fix rejects these).
834
+ // Skip fuzz runs where both repay amount and collateral return are zero.
835
835
  vm.assume(amountDiff > 0 || collateralReturned > 0);
836
836
 
837
837
  uint256 maxAmountPaidDown = loan.amount;