@rev-net/core-v6 0.0.35 → 0.0.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/RISKS.md +19 -1
  2. package/package.json +9 -9
  3. package/src/REVDeployer.sol +19 -10
  4. package/src/REVLoans.sol +138 -89
  5. package/src/REVOwner.sol +6 -4
  6. package/test/REV.integrations.t.sol +14 -14
  7. package/test/REVInvincibility.t.sol +16 -16
  8. package/test/REVLifecycle.t.sol +32 -32
  9. package/test/REVLoansSourced.t.sol +15 -15
  10. package/test/TestCashOutCallerValidation.t.sol +8 -8
  11. package/test/TestConversionDocumentation.t.sol +2 -5
  12. package/test/TestCrossCurrencyReclaim.t.sol +72 -72
  13. package/test/TestLongTailEconomics.t.sol +56 -56
  14. package/test/TestSwapTerminalPermission.t.sol +21 -21
  15. package/test/audit/HiddenSupplyCashout.t.sol +61 -0
  16. package/test/audit/NemesisVerification.t.sol +97 -0
  17. package/test/audit/REVOwnerCurrencyMismatch.t.sol +188 -0
  18. package/test/audit/REVOwnerRemoteSurplusCurrencyMismatch.t.sol +140 -0
  19. package/test/audit/ReallocatePermission.t.sol +363 -0
  20. package/test/audit/RemoteLoanAccountingGap.t.sol +74 -0
  21. package/test/audit/SupportsInterfaceTest.t.sol +51 -0
  22. package/test/audit/TestFeeAllowanceLeak.t.sol +197 -0
  23. package/test/audit/TestLoansAndDeployerFixes.t.sol +576 -0
  24. package/test/fork/TestCashOutFork.t.sol +48 -48
  25. package/test/fork/TestLoanAdversarialFork.t.sol +744 -0
  26. package/test/fork/TestLoanERC20Fork.t.sol +2 -8
  27. package/test/fork/TestPermit2PaymentFork.t.sol +32 -32
  28. package/test/regression/TestBurnPermissionRequired.t.sol +5 -5
  29. package/test/regression/TestCashOutBuybackFeeLeak.t.sol +8 -8
  30. /package/test/audit/{CodexCrossChainBuybackRouteMismatch.t.sol → CrossChainBuybackRouteMismatch.t.sol} +0 -0
  31. /package/test/audit/{NemesisOperatorDelegation.t.sol → OperatorDelegation.t.sol} +0 -0
  32. /package/test/audit/{CodexPhantomSurplusTerminal.t.sol → PhantomSurplusTerminal.t.sol} +0 -0
@@ -751,11 +751,11 @@ contract REVLoansSourcedTests is TestBaseWorkflow {
751
751
 
752
752
  uint256 fullReclaimableSurplus = jbMultiTerminal().STORE()
753
753
  .currentReclaimableSurplusOf({
754
- projectId: revnetProjectId,
755
- cashOutCount: tokensToCashout,
756
- totalSupply: totalSupplyExcludingAutoMint,
757
- surplus: nativeSurplus
758
- });
754
+ projectId: revnetProjectId,
755
+ cashOutCount: tokensToCashout,
756
+ totalSupply: totalSupplyExcludingAutoMint,
757
+ surplus: nativeSurplus
758
+ });
759
759
 
760
760
  assertGe(fullReclaimableSurplus, loanable);
761
761
 
@@ -764,11 +764,11 @@ contract REVLoansSourcedTests is TestBaseWorkflow {
764
764
 
765
765
  uint256 reclaimableSurplus = jbMultiTerminal().STORE()
766
766
  .currentReclaimableSurplusOf({
767
- projectId: revnetProjectId,
768
- cashOutCount: tokensToCashout - feeTokenCount,
769
- totalSupply: totalSupplyExcludingAutoMint,
770
- surplus: nativeSurplus
771
- });
767
+ projectId: revnetProjectId,
768
+ cashOutCount: tokensToCashout - feeTokenCount,
769
+ totalSupply: totalSupplyExcludingAutoMint,
770
+ surplus: nativeSurplus
771
+ });
772
772
 
773
773
  // In the `revFee` calculation we decrease the `nativeSurplus` by the `reclaimableSurplus`
774
774
  // but due to a `stack too deep` we can't do that there, so we decrease it here.
@@ -777,11 +777,11 @@ contract REVLoansSourcedTests is TestBaseWorkflow {
777
777
 
778
778
  uint256 revFee = jbMultiTerminal().STORE()
779
779
  .currentReclaimableSurplusOf({
780
- projectId: revnetProjectId,
781
- cashOutCount: feeTokenCount,
782
- totalSupply: totalSupplyExcludingAutoMint - (tokensToCashout - feeTokenCount),
783
- surplus: nativeSurplus
784
- });
780
+ projectId: revnetProjectId,
781
+ cashOutCount: feeTokenCount,
782
+ totalSupply: totalSupplyExcludingAutoMint - (tokensToCashout - feeTokenCount),
783
+ surplus: nativeSurplus
784
+ });
785
785
 
786
786
  assertGe(fullReclaimableSurplus, mulDiv((reclaimableSurplus + revFee), 995, 1000)); // small marging for curve
787
787
  // rounding.
@@ -315,14 +315,14 @@ contract TestCashOutCallerValidation is TestBaseWorkflow {
315
315
  vm.prank(USER);
316
316
  uint256 reclaimed = jbMultiTerminal()
317
317
  .cashOutTokensOf({
318
- holder: USER,
319
- projectId: REVNET_ID,
320
- cashOutCount: tokenCount,
321
- tokenToReclaim: JBConstants.NATIVE_TOKEN,
322
- minTokensReclaimed: 0,
323
- beneficiary: payable(USER),
324
- metadata: ""
325
- });
318
+ holder: USER,
319
+ projectId: REVNET_ID,
320
+ cashOutCount: tokenCount,
321
+ tokenToReclaim: JBConstants.NATIVE_TOKEN,
322
+ minTokensReclaimed: 0,
323
+ beneficiary: payable(USER),
324
+ metadata: ""
325
+ });
326
326
 
327
327
  assertGt(reclaimed, 0, "Should have reclaimed some ETH");
328
328
 
@@ -286,11 +286,8 @@ contract TestConversionDocumentation is TestBaseWorkflow {
286
286
  vm.prank(USER);
287
287
  jbController()
288
288
  .launchRulesetsFor({
289
- projectId: projectId,
290
- rulesetConfigurations: rulesetConfigs,
291
- terminalConfigurations: termConfigs,
292
- memo: ""
293
- });
289
+ projectId: projectId, rulesetConfigurations: rulesetConfigs, terminalConfigurations: termConfigs, memo: ""
290
+ });
294
291
 
295
292
  // Now try to convert this project to a revnet — should revert.
296
293
  // Approve NFT to REV_DEPLOYER.
@@ -268,14 +268,14 @@ contract TestCrossCurrencyReclaim is TestBaseWorkflow {
268
268
  vm.prank(USER1);
269
269
  uint256 reclaimed = jbMultiTerminal()
270
270
  .cashOutTokensOf({
271
- holder: USER1,
272
- projectId: REVNET_ID,
273
- cashOutCount: tokens,
274
- tokenToReclaim: JBConstants.NATIVE_TOKEN,
275
- minTokensReclaimed: 0,
276
- beneficiary: payable(USER1),
277
- metadata: ""
278
- });
271
+ holder: USER1,
272
+ projectId: REVNET_ID,
273
+ cashOutCount: tokens,
274
+ tokenToReclaim: JBConstants.NATIVE_TOKEN,
275
+ minTokensReclaimed: 0,
276
+ beneficiary: payable(USER1),
277
+ metadata: ""
278
+ });
279
279
 
280
280
  assertGt(reclaimed, 0, "should reclaim some ETH");
281
281
  // With 50% tax and single holder cashing out everything, the bonding curve returns the full surplus.
@@ -310,14 +310,14 @@ contract TestCrossCurrencyReclaim is TestBaseWorkflow {
310
310
  vm.prank(USER1);
311
311
  uint256 reclaimedToken = jbMultiTerminal()
312
312
  .cashOutTokensOf({
313
- holder: USER1,
314
- projectId: REVNET_ID,
315
- cashOutCount: revTokens,
316
- tokenToReclaim: address(TOKEN),
317
- minTokensReclaimed: 0,
318
- beneficiary: payable(USER1),
319
- metadata: ""
320
- });
313
+ holder: USER1,
314
+ projectId: REVNET_ID,
315
+ cashOutCount: revTokens,
316
+ tokenToReclaim: address(TOKEN),
317
+ minTokensReclaimed: 0,
318
+ beneficiary: payable(USER1),
319
+ metadata: ""
320
+ });
321
321
 
322
322
  assertGt(reclaimedToken, 0, "should reclaim some TOKEN");
323
323
  assertLe(reclaimedToken, tokenAmount, "should not exceed total TOKEN paid in");
@@ -358,14 +358,14 @@ contract TestCrossCurrencyReclaim is TestBaseWorkflow {
358
358
  vm.expectRevert();
359
359
  jbMultiTerminal()
360
360
  .cashOutTokensOf({
361
- holder: USER1,
362
- projectId: REVNET_ID,
363
- cashOutCount: revTokens,
364
- tokenToReclaim: address(TOKEN),
365
- minTokensReclaimed: 0,
366
- beneficiary: payable(USER1),
367
- metadata: ""
368
- });
361
+ holder: USER1,
362
+ projectId: REVNET_ID,
363
+ cashOutCount: revTokens,
364
+ tokenToReclaim: address(TOKEN),
365
+ minTokensReclaimed: 0,
366
+ beneficiary: payable(USER1),
367
+ metadata: ""
368
+ });
369
369
  }
370
370
 
371
371
  /// @notice Pay with TOKEN (so the surplus is in TOKEN), then pay with ETH too.
@@ -395,14 +395,14 @@ contract TestCrossCurrencyReclaim is TestBaseWorkflow {
395
395
  vm.prank(USER1);
396
396
  uint256 reclaimedToken = jbMultiTerminal()
397
397
  .cashOutTokensOf({
398
- holder: USER1,
399
- projectId: REVNET_ID,
400
- cashOutCount: cashOutCount,
401
- tokenToReclaim: address(TOKEN),
402
- minTokensReclaimed: 0,
403
- beneficiary: payable(USER1),
404
- metadata: ""
405
- });
398
+ holder: USER1,
399
+ projectId: REVNET_ID,
400
+ cashOutCount: cashOutCount,
401
+ tokenToReclaim: address(TOKEN),
402
+ minTokensReclaimed: 0,
403
+ beneficiary: payable(USER1),
404
+ metadata: ""
405
+ });
406
406
 
407
407
  assertGt(reclaimedToken, 0, "should reclaim some TOKEN");
408
408
  assertLe(reclaimedToken, tokenPayment, "should not exceed total TOKEN paid in");
@@ -442,14 +442,14 @@ contract TestCrossCurrencyReclaim is TestBaseWorkflow {
442
442
  vm.prank(USER1);
443
443
  uint256 reclaimed = jbMultiTerminal()
444
444
  .cashOutTokensOf({
445
- holder: USER1,
446
- projectId: REVNET_ID,
447
- cashOutCount: tokens1,
448
- tokenToReclaim: JBConstants.NATIVE_TOKEN,
449
- minTokensReclaimed: 0,
450
- beneficiary: payable(USER1),
451
- metadata: ""
452
- });
445
+ holder: USER1,
446
+ projectId: REVNET_ID,
447
+ cashOutCount: tokens1,
448
+ tokenToReclaim: JBConstants.NATIVE_TOKEN,
449
+ minTokensReclaimed: 0,
450
+ beneficiary: payable(USER1),
451
+ metadata: ""
452
+ });
453
453
  assertGt(reclaimed, 0, "should reclaim ETH from mixed-currency surplus");
454
454
 
455
455
  // The reclaimed amount should be less than the original payment (due to bonding curve tax).
@@ -480,14 +480,14 @@ contract TestCrossCurrencyReclaim is TestBaseWorkflow {
480
480
  vm.prank(USER1);
481
481
  uint256 reclaimed = jbMultiTerminal()
482
482
  .cashOutTokensOf({
483
- holder: USER1,
484
- projectId: REVNET_ID,
485
- cashOutCount: tokens,
486
- tokenToReclaim: JBConstants.NATIVE_TOKEN,
487
- minTokensReclaimed: 0,
488
- beneficiary: payable(USER1),
489
- metadata: ""
490
- });
483
+ holder: USER1,
484
+ projectId: REVNET_ID,
485
+ cashOutCount: tokens,
486
+ tokenToReclaim: JBConstants.NATIVE_TOKEN,
487
+ minTokensReclaimed: 0,
488
+ beneficiary: payable(USER1),
489
+ metadata: ""
490
+ });
491
491
 
492
492
  assertLe(reclaimed, 1, "tiny payment should not yield more than original amount");
493
493
  }
@@ -517,14 +517,14 @@ contract TestCrossCurrencyReclaim is TestBaseWorkflow {
517
517
  vm.prank(USER1);
518
518
  uint256 reclaimed = jbMultiTerminal()
519
519
  .cashOutTokensOf({
520
- holder: USER1,
521
- projectId: REVNET_ID,
522
- cashOutCount: revTokens,
523
- tokenToReclaim: address(TOKEN),
524
- minTokensReclaimed: 0,
525
- beneficiary: payable(USER1),
526
- metadata: ""
527
- });
520
+ holder: USER1,
521
+ projectId: REVNET_ID,
522
+ cashOutCount: revTokens,
523
+ tokenToReclaim: address(TOKEN),
524
+ minTokensReclaimed: 0,
525
+ beneficiary: payable(USER1),
526
+ metadata: ""
527
+ });
528
528
 
529
529
  // Should reclaim some TOKEN (bounded by the original payment amount).
530
530
  assertLe(reclaimed, tokenAmount, "should not exceed original TOKEN payment");
@@ -559,14 +559,14 @@ contract TestCrossCurrencyReclaim is TestBaseWorkflow {
559
559
  vm.prank(USER1);
560
560
  uint256 reclaimed = jbMultiTerminal()
561
561
  .cashOutTokensOf({
562
- holder: USER1,
563
- projectId: REVNET_ID,
564
- cashOutCount: revTokens,
565
- tokenToReclaim: address(TOKEN),
566
- minTokensReclaimed: 0,
567
- beneficiary: payable(USER1),
568
- metadata: ""
569
- });
562
+ holder: USER1,
563
+ projectId: REVNET_ID,
564
+ cashOutCount: revTokens,
565
+ tokenToReclaim: address(TOKEN),
566
+ minTokensReclaimed: 0,
567
+ beneficiary: payable(USER1),
568
+ metadata: ""
569
+ });
570
570
 
571
571
  assertLe(reclaimed, tokenAmount, "should not exceed total TOKEN paid");
572
572
  }
@@ -595,14 +595,14 @@ contract TestCrossCurrencyReclaim is TestBaseWorkflow {
595
595
  vm.prank(USER1);
596
596
  uint256 reclaimed = jbMultiTerminal()
597
597
  .cashOutTokensOf({
598
- holder: USER1,
599
- projectId: REVNET_ID,
600
- cashOutCount: tokens,
601
- tokenToReclaim: JBConstants.NATIVE_TOKEN,
602
- minTokensReclaimed: 0,
603
- beneficiary: payable(USER1),
604
- metadata: ""
605
- });
598
+ holder: USER1,
599
+ projectId: REVNET_ID,
600
+ cashOutCount: tokens,
601
+ tokenToReclaim: JBConstants.NATIVE_TOKEN,
602
+ minTokensReclaimed: 0,
603
+ beneficiary: payable(USER1),
604
+ metadata: ""
605
+ });
606
606
 
607
607
  // With 0% cash out tax, no fee is charged on cash outs (per REVDeployer.beforeCashOutRecordedWith).
608
608
  assertEq(reclaimed, balanceBefore, "0% tax, single holder should reclaim full balance");
@@ -363,14 +363,14 @@ contract TestLongTailEconomics is TestBaseWorkflow {
363
363
  vm.prank(users[i]);
364
364
  uint256 reclaimed = jbMultiTerminal()
365
365
  .cashOutTokensOf({
366
- holder: users[i],
367
- projectId: REVNET_ID,
368
- cashOutCount: userTokens[i],
369
- tokenToReclaim: JBConstants.NATIVE_TOKEN,
370
- minTokensReclaimed: 0,
371
- beneficiary: payable(users[i]),
372
- metadata: ""
373
- });
366
+ holder: users[i],
367
+ projectId: REVNET_ID,
368
+ cashOutCount: userTokens[i],
369
+ tokenToReclaim: JBConstants.NATIVE_TOKEN,
370
+ minTokensReclaimed: 0,
371
+ beneficiary: payable(users[i]),
372
+ metadata: ""
373
+ });
374
374
  totalReclaimed += reclaimed;
375
375
  }
376
376
 
@@ -412,14 +412,14 @@ contract TestLongTailEconomics is TestBaseWorkflow {
412
412
  vm.prank(payer);
413
413
  uint256 reclaimedHalf = jbMultiTerminal()
414
414
  .cashOutTokensOf({
415
- holder: payer,
416
- projectId: REVNET_ID,
417
- cashOutCount: halfTokens,
418
- tokenToReclaim: JBConstants.NATIVE_TOKEN,
419
- minTokensReclaimed: 0,
420
- beneficiary: payable(payer),
421
- metadata: ""
422
- });
415
+ holder: payer,
416
+ projectId: REVNET_ID,
417
+ cashOutCount: halfTokens,
418
+ tokenToReclaim: JBConstants.NATIVE_TOKEN,
419
+ minTokensReclaimed: 0,
420
+ beneficiary: payable(payer),
421
+ metadata: ""
422
+ });
423
423
 
424
424
  // With a 50% tax rate and being the only holder, cashing out half the tokens
425
425
  // should return less than half the surplus (bonding curve subproportional behavior).
@@ -452,14 +452,14 @@ contract TestLongTailEconomics is TestBaseWorkflow {
452
452
  vm.prank(users[i]);
453
453
  jbMultiTerminal()
454
454
  .cashOutTokensOf({
455
- holder: users[i],
456
- projectId: REVNET_ID,
457
- cashOutCount: userBalance / 2,
458
- tokenToReclaim: JBConstants.NATIVE_TOKEN,
459
- minTokensReclaimed: 0,
460
- beneficiary: payable(users[i]),
461
- metadata: ""
462
- });
455
+ holder: users[i],
456
+ projectId: REVNET_ID,
457
+ cashOutCount: userBalance / 2,
458
+ tokenToReclaim: JBConstants.NATIVE_TOKEN,
459
+ minTokensReclaimed: 0,
460
+ beneficiary: payable(users[i]),
461
+ metadata: ""
462
+ });
463
463
  }
464
464
  }
465
465
 
@@ -479,14 +479,14 @@ contract TestLongTailEconomics is TestBaseWorkflow {
479
479
  vm.prank(users[3]);
480
480
  jbMultiTerminal()
481
481
  .cashOutTokensOf({
482
- holder: users[3],
483
- projectId: REVNET_ID,
484
- cashOutCount: user3Balance,
485
- tokenToReclaim: JBConstants.NATIVE_TOKEN,
486
- minTokensReclaimed: 0,
487
- beneficiary: payable(users[3]),
488
- metadata: ""
489
- });
482
+ holder: users[3],
483
+ projectId: REVNET_ID,
484
+ cashOutCount: user3Balance,
485
+ tokenToReclaim: JBConstants.NATIVE_TOKEN,
486
+ minTokensReclaimed: 0,
487
+ beneficiary: payable(users[3]),
488
+ metadata: ""
489
+ });
490
490
  }
491
491
  }
492
492
 
@@ -556,14 +556,14 @@ contract TestLongTailEconomics is TestBaseWorkflow {
556
556
  vm.prank(lateUser);
557
557
  uint256 reclaimed = jbMultiTerminal()
558
558
  .cashOutTokensOf({
559
- holder: lateUser,
560
- projectId: REVNET_ID,
561
- cashOutCount: lateTokens,
562
- tokenToReclaim: JBConstants.NATIVE_TOKEN,
563
- minTokensReclaimed: 0,
564
- beneficiary: payable(lateUser),
565
- metadata: ""
566
- });
559
+ holder: lateUser,
560
+ projectId: REVNET_ID,
561
+ cashOutCount: lateTokens,
562
+ tokenToReclaim: JBConstants.NATIVE_TOKEN,
563
+ minTokensReclaimed: 0,
564
+ beneficiary: payable(lateUser),
565
+ metadata: ""
566
+ });
567
567
 
568
568
  // The late entrant should not extract more than they put in.
569
569
  assertLe(reclaimed, 10e18, "late entrant should not extract more than they paid");
@@ -599,14 +599,14 @@ contract TestLongTailEconomics is TestBaseWorkflow {
599
599
  vm.prank(users[0]);
600
600
  jbMultiTerminal()
601
601
  .cashOutTokensOf({
602
- holder: users[0],
603
- projectId: REVNET_ID,
604
- cashOutCount: balance0 / 3,
605
- tokenToReclaim: JBConstants.NATIVE_TOKEN,
606
- minTokensReclaimed: 0,
607
- beneficiary: payable(users[0]),
608
- metadata: ""
609
- });
602
+ holder: users[0],
603
+ projectId: REVNET_ID,
604
+ cashOutCount: balance0 / 3,
605
+ tokenToReclaim: JBConstants.NATIVE_TOKEN,
606
+ minTokensReclaimed: 0,
607
+ beneficiary: payable(users[0]),
608
+ metadata: ""
609
+ });
610
610
  }
611
611
  }
612
612
 
@@ -654,14 +654,14 @@ contract TestLongTailEconomics is TestBaseWorkflow {
654
654
  vm.prank(user);
655
655
  jbMultiTerminal()
656
656
  .cashOutTokensOf({
657
- holder: user,
658
- projectId: REVNET_ID,
659
- cashOutCount: portion,
660
- tokenToReclaim: JBConstants.NATIVE_TOKEN,
661
- minTokensReclaimed: 0,
662
- beneficiary: payable(user),
663
- metadata: ""
664
- });
657
+ holder: user,
658
+ projectId: REVNET_ID,
659
+ cashOutCount: portion,
660
+ tokenToReclaim: JBConstants.NATIVE_TOKEN,
661
+ minTokensReclaimed: 0,
662
+ beneficiary: payable(user),
663
+ metadata: ""
664
+ });
665
665
 
666
666
  uint256 feeBalanceAfterRound =
667
667
  jbTerminalStore().balanceOf(address(jbMultiTerminal()), FEE_PROJECT_ID, JBConstants.NATIVE_TOKEN);
@@ -206,24 +206,24 @@ contract TestSwapTerminalPermission is TestBaseWorkflow {
206
206
  function test_splitOperator_hasRegistryPermissions() public view {
207
207
  bool hasBuybackHook = jbPermissions()
208
208
  .hasPermission({
209
- operator: multisig(),
210
- account: address(REV_DEPLOYER),
211
- projectId: TEST_REVNET_ID,
212
- permissionId: JBPermissionIds.SET_BUYBACK_HOOK,
213
- includeRoot: false,
214
- includeWildcardProjectId: false
215
- });
209
+ operator: multisig(),
210
+ account: address(REV_DEPLOYER),
211
+ projectId: TEST_REVNET_ID,
212
+ permissionId: JBPermissionIds.SET_BUYBACK_HOOK,
213
+ includeRoot: false,
214
+ includeWildcardProjectId: false
215
+ });
216
216
  assertTrue(hasBuybackHook, "Split operator should have SET_BUYBACK_HOOK permission");
217
217
 
218
218
  bool hasRouterTerminal = jbPermissions()
219
219
  .hasPermission({
220
- operator: multisig(),
221
- account: address(REV_DEPLOYER),
222
- projectId: TEST_REVNET_ID,
223
- permissionId: JBPermissionIds.SET_ROUTER_TERMINAL,
224
- includeRoot: false,
225
- includeWildcardProjectId: false
226
- });
220
+ operator: multisig(),
221
+ account: address(REV_DEPLOYER),
222
+ projectId: TEST_REVNET_ID,
223
+ permissionId: JBPermissionIds.SET_ROUTER_TERMINAL,
224
+ includeRoot: false,
225
+ includeWildcardProjectId: false
226
+ });
227
227
  assertTrue(hasRouterTerminal, "Split operator should have SET_ROUTER_TERMINAL permission");
228
228
  }
229
229
 
@@ -244,13 +244,13 @@ contract TestSwapTerminalPermission is TestBaseWorkflow {
244
244
  for (uint256 i = 0; i < expectedPermissions.length; i++) {
245
245
  bool hasPermission = jbPermissions()
246
246
  .hasPermission({
247
- operator: multisig(),
248
- account: address(REV_DEPLOYER),
249
- projectId: TEST_REVNET_ID,
250
- permissionId: expectedPermissions[i],
251
- includeRoot: false,
252
- includeWildcardProjectId: false
253
- });
247
+ operator: multisig(),
248
+ account: address(REV_DEPLOYER),
249
+ projectId: TEST_REVNET_ID,
250
+ permissionId: expectedPermissions[i],
251
+ includeRoot: false,
252
+ includeWildcardProjectId: false
253
+ });
254
254
  assertTrue(
255
255
  hasPermission,
256
256
  string.concat(
@@ -0,0 +1,61 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity 0.8.28;
3
+
4
+ import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
5
+
6
+ import {TestHiddenTokens} from "../TestHiddenTokens.t.sol";
7
+
8
+ contract CodexNemesisHiddenSupplyCashoutTest is TestHiddenTokens {
9
+ function test_hiddenSupplyCanDrainCashoutAndThenBeRevealed() public {
10
+ uint256 payAmount = 10 ether;
11
+
12
+ vm.prank(USER);
13
+ uint256 minted = jbMultiTerminal().pay{value: payAmount}({
14
+ projectId: REVNET_ID,
15
+ token: JBConstants.NATIVE_TOKEN,
16
+ amount: payAmount,
17
+ beneficiary: USER,
18
+ minReturnedTokens: 0,
19
+ memo: "",
20
+ metadata: ""
21
+ });
22
+
23
+ uint256 terminalBalanceBefore =
24
+ jbTerminalStore().balanceOf(address(jbMultiTerminal()), REVNET_ID, JBConstants.NATIVE_TOKEN);
25
+ assertEq(terminalBalanceBefore, payAmount, "setup: revnet terminal balance");
26
+
27
+ _allowHolderToHide(USER, REVNET_ID);
28
+
29
+ uint256 hiddenCount = minted / 2;
30
+ vm.prank(USER);
31
+ HIDDEN_TOKENS.hideTokensOf(REVNET_ID, hiddenCount, USER);
32
+
33
+ uint256 visibleSupply = jbController().TOKENS().totalSupplyOf(REVNET_ID);
34
+ assertEq(visibleSupply, minted - hiddenCount, "hidden tokens left the cash-out denominator");
35
+
36
+ vm.prank(USER);
37
+ jbMultiTerminal()
38
+ .cashOutTokensOf({
39
+ holder: USER,
40
+ projectId: REVNET_ID,
41
+ cashOutCount: visibleSupply,
42
+ tokenToReclaim: JBConstants.NATIVE_TOKEN,
43
+ minTokensReclaimed: 0,
44
+ beneficiary: payable(USER),
45
+ metadata: ""
46
+ });
47
+
48
+ uint256 terminalBalanceAfter =
49
+ jbTerminalStore().balanceOf(address(jbMultiTerminal()), REVNET_ID, JBConstants.NATIVE_TOKEN);
50
+ assertEq(terminalBalanceAfter, 0, "visible tranche drained the revnet balance");
51
+
52
+ vm.prank(USER);
53
+ HIDDEN_TOKENS.revealTokensOf(REVNET_ID, hiddenCount, USER);
54
+
55
+ assertEq(
56
+ jbController().TOKENS().totalBalanceOf(USER, REVNET_ID),
57
+ hiddenCount,
58
+ "hidden tranche was restored after the cash out"
59
+ );
60
+ }
61
+ }