@rev-net/core-v6 0.0.29 → 0.0.30

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 (71) hide show
  1. package/ADMINISTRATION.md +19 -9
  2. package/ARCHITECTURE.md +3 -0
  3. package/AUDIT_INSTRUCTIONS.md +11 -1
  4. package/CHANGELOG.md +26 -0
  5. package/README.md +1 -0
  6. package/RISKS.md +28 -4
  7. package/SKILLS.md +2 -1
  8. package/USER_JOURNEYS.md +17 -3
  9. package/package.json +2 -2
  10. package/references/operations.md +1 -1
  11. package/script/Deploy.s.sol +25 -6
  12. package/src/REVHiddenTokens.sol +149 -0
  13. package/src/REVLoans.sol +115 -144
  14. package/src/REVOwner.sol +11 -3
  15. package/src/interfaces/IREVHiddenTokens.sol +53 -0
  16. package/src/interfaces/IREVLoans.sol +3 -6
  17. package/test/REV.integrations.t.sol +2 -1
  18. package/test/REVAutoIssuanceFuzz.t.sol +2 -1
  19. package/test/REVDeployerRegressions.t.sol +2 -2
  20. package/test/REVInvincibility.t.sol +6 -6
  21. package/test/REVInvincibilityHandler.sol +1 -1
  22. package/test/REVLifecycle.t.sol +2 -2
  23. package/test/REVLoans.invariants.t.sol +3 -3
  24. package/test/REVLoansAttacks.t.sol +7 -6
  25. package/test/REVLoansFeeRecovery.t.sol +12 -12
  26. package/test/REVLoansFindings.t.sol +4 -4
  27. package/test/REVLoansRegressions.t.sol +3 -3
  28. package/test/REVLoansSourceFeeRecovery.t.sol +4 -4
  29. package/test/REVLoansSourced.t.sol +48 -24
  30. package/test/REVLoansUnSourced.t.sol +3 -3
  31. package/test/TestBurnHeldTokens.t.sol +2 -2
  32. package/test/TestCEIPattern.t.sol +7 -6
  33. package/test/TestCashOutCallerValidation.t.sol +2 -2
  34. package/test/TestConversionDocumentation.t.sol +2 -2
  35. package/test/TestCrossCurrencyReclaim.t.sol +2 -2
  36. package/test/TestCrossSourceReallocation.t.sol +3 -3
  37. package/test/TestERC2771MetaTx.t.sol +6 -4
  38. package/test/TestEmptyBuybackSpecs.t.sol +2 -2
  39. package/test/TestFlashLoanSurplus.t.sol +3 -3
  40. package/test/TestHiddenTokens.t.sol +420 -0
  41. package/test/TestHookArrayOOB.t.sol +2 -2
  42. package/test/TestLiquidationBehavior.t.sol +4 -4
  43. package/test/TestLoanSourceRotation.t.sol +8 -6
  44. package/test/TestLoansCashOutDelay.t.sol +6 -6
  45. package/test/TestLongTailEconomics.t.sol +2 -2
  46. package/test/TestLowFindings.t.sol +13 -8
  47. package/test/TestMixedFixes.t.sol +7 -7
  48. package/test/TestPermit2Signatures.t.sol +3 -3
  49. package/test/TestReallocationSandwich.t.sol +4 -3
  50. package/test/TestRevnetRegressions.t.sol +3 -4
  51. package/test/TestSplitWeightAdjustment.t.sol +4 -3
  52. package/test/TestSplitWeightE2E.t.sol +4 -3
  53. package/test/TestSplitWeightFork.t.sol +2 -2
  54. package/test/TestStageTransitionBorrowable.t.sol +2 -2
  55. package/test/TestSwapTerminalPermission.t.sol +2 -2
  56. package/test/TestUint112Overflow.t.sol +3 -3
  57. package/test/TestZeroAmountLoanGuard.t.sol +3 -3
  58. package/test/TestZeroRepayment.t.sol +3 -3
  59. package/test/audit/LoanIdOverflowGuard.t.sol +4 -4
  60. package/test/audit/NemesisOperatorDelegation.t.sol +278 -0
  61. package/test/fork/ForkTestBase.sol +4 -3
  62. package/test/fork/TestLoanBorrowFork.t.sol +2 -1
  63. package/test/fork/TestLoanERC20Fork.t.sol +4 -2
  64. package/test/fork/TestLoanTransferFork.t.sol +12 -2
  65. package/test/helpers/MaliciousContracts.sol +1 -1
  66. package/test/regression/TestBurnPermissionRequired.t.sol +4 -4
  67. package/test/regression/TestCashOutBuybackFeeLeak.t.sol +2 -2
  68. package/test/regression/TestCrossRevnetLiquidation.t.sol +2 -2
  69. package/test/regression/TestCumulativeLoanCounter.t.sol +3 -3
  70. package/test/regression/TestLiquidateGapHandling.t.sol +3 -3
  71. package/test/regression/TestZeroPriceFeed.t.sol +5 -5
@@ -328,7 +328,6 @@ abstract contract ForkTestBase is TestBaseWorkflow {
328
328
 
329
329
  LOANS_CONTRACT = new REVLoans({
330
330
  controller: jbController(),
331
- projects: jbProjects(),
332
331
  revId: FEE_PROJECT_ID,
333
332
  owner: address(this),
334
333
  permit2: permit2(),
@@ -340,7 +339,8 @@ abstract contract ForkTestBase is TestBaseWorkflow {
340
339
  jbDirectory(),
341
340
  FEE_PROJECT_ID,
342
341
  SUCKER_REGISTRY,
343
- address(LOANS_CONTRACT)
342
+ address(LOANS_CONTRACT),
343
+ address(0)
344
344
  );
345
345
 
346
346
  REV_DEPLOYER = new REVDeployer{salt: "REVDeployer_Fork"}(
@@ -707,7 +707,8 @@ abstract contract ForkTestBase is TestBaseWorkflow {
707
707
  minBorrowAmount: 0,
708
708
  collateralCount: collateral,
709
709
  beneficiary: payable(borrower),
710
- prepaidFeePercent: prepaidFeePercent
710
+ prepaidFeePercent: prepaidFeePercent,
711
+ holder: borrower
711
712
  });
712
713
  }
713
714
  }
@@ -97,7 +97,8 @@ contract TestLoanBorrowFork is ForkTestBase {
97
97
  minBorrowAmount: 0,
98
98
  collateralCount: borrowerTokens,
99
99
  beneficiary: payable(BORROWER),
100
- prepaidFeePercent: prepaidFeePercent
100
+ prepaidFeePercent: prepaidFeePercent,
101
+ holder: BORROWER
101
102
  });
102
103
 
103
104
  uint256 borrowerReceived = BORROWER.balance - borrowerEthBefore;
@@ -184,7 +184,8 @@ contract TestLoanERC20Fork is ForkTestBase {
184
184
  minBorrowAmount: 0,
185
185
  collateralCount: collateral,
186
186
  beneficiary: payable(borrower),
187
- prepaidFeePercent: prepaidFeePercent
187
+ prepaidFeePercent: prepaidFeePercent,
188
+ holder: borrower
188
189
  });
189
190
  }
190
191
 
@@ -263,7 +264,8 @@ contract TestLoanERC20Fork is ForkTestBase {
263
264
  minBorrowAmount: 0,
264
265
  collateralCount: borrowerTokens,
265
266
  beneficiary: payable(BORROWER),
266
- prepaidFeePercent: prepaidFeePercent
267
+ prepaidFeePercent: prepaidFeePercent,
268
+ holder: BORROWER
267
269
  });
268
270
 
269
271
  uint256 borrowerUsdcReceived = IERC20(USDC).balanceOf(BORROWER) - borrowerUsdcBefore;
@@ -3,6 +3,8 @@ pragma solidity 0.8.28;
3
3
 
4
4
  // forge-lint: disable-next-line(unaliased-plain-import)
5
5
  import "./ForkTestBase.sol";
6
+ import {JBPermissioned} from "@bananapus/core-v6/src/abstract/JBPermissioned.sol";
7
+ import {JBPermissionIds} from "@bananapus/permission-ids-v6/src/JBPermissionIds.sol";
6
8
 
7
9
  /// @notice Fork tests for transferring loan NFTs and repaying from the new owner.
8
10
  ///
@@ -79,9 +81,17 @@ contract TestLoanTransferFork is ForkTestBase {
79
81
 
80
82
  JBSingleAllowance memory allowance;
81
83
 
82
- // Original borrower tries to repay — should revert with REVLoans_Unauthorized.
84
+ // Original borrower tries to repay — should revert with JBPermissioned_Unauthorized.
83
85
  vm.prank(BORROWER);
84
- vm.expectRevert(abi.encodeWithSelector(REVLoans.REVLoans_Unauthorized.selector, BORROWER, newOwner));
86
+ vm.expectRevert(
87
+ abi.encodeWithSelector(
88
+ JBPermissioned.JBPermissioned_Unauthorized.selector,
89
+ newOwner,
90
+ BORROWER,
91
+ revnetId,
92
+ JBPermissionIds.REPAY_LOAN
93
+ )
94
+ );
85
95
  LOANS_CONTRACT.repayLoan{value: loan.amount * 2}({
86
96
  loanId: loanId,
87
97
  maxRepayBorrowAmount: loan.amount * 2,
@@ -162,7 +162,7 @@ contract SurplusInflator is ERC165, IJBPayoutTerminal {
162
162
  shouldInflate = false;
163
163
  // Try to borrow at the inflated surplus
164
164
  REVLoanSource memory source = REVLoanSource({token: JBConstants.NATIVE_TOKEN, terminal: realTerminal});
165
- try loans.borrowFrom(revnetId, source, 0, 1e18, payable(address(this)), 25) {} catch {}
165
+ try loans.borrowFrom(revnetId, source, 0, 1e18, payable(address(this)), 25, address(this)) {} catch {}
166
166
  }
167
167
  return 0;
168
168
  }
@@ -102,7 +102,6 @@ contract TestBurnPermissionRequired is TestBaseWorkflow {
102
102
  .addPriceFeedFor(0, uint32(uint160(address(TOKEN))), uint32(uint160(JBConstants.NATIVE_TOKEN)), priceFeed);
103
103
  LOANS_CONTRACT = new REVLoans({
104
104
  controller: jbController(),
105
- projects: jbProjects(),
106
105
  revId: FEE_PROJECT_ID,
107
106
  owner: address(this),
108
107
  permit2: permit2(),
@@ -113,7 +112,8 @@ contract TestBurnPermissionRequired is TestBaseWorkflow {
113
112
  jbDirectory(),
114
113
  FEE_PROJECT_ID,
115
114
  SUCKER_REGISTRY,
116
- address(LOANS_CONTRACT)
115
+ address(LOANS_CONTRACT),
116
+ address(0)
117
117
  );
118
118
 
119
119
  REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
@@ -247,7 +247,7 @@ contract TestBurnPermissionRequired is TestBaseWorkflow {
247
247
  JBPermissionIds.BURN_TOKENS // permissionId
248
248
  )
249
249
  );
250
- LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokenCount, payable(user), 25);
250
+ LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokenCount, payable(user), 25, user);
251
251
  }
252
252
 
253
253
  /// @notice borrowFrom should succeed when the caller has granted BURN_TOKENS permission.
@@ -274,7 +274,7 @@ contract TestBurnPermissionRequired is TestBaseWorkflow {
274
274
  REVLoanSource memory source = REVLoanSource({token: JBConstants.NATIVE_TOKEN, terminal: jbMultiTerminal()});
275
275
  vm.prank(user);
276
276
  (uint256 loanId, REVLoan memory loan) =
277
- LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokenCount, payable(user), 25);
277
+ LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokenCount, payable(user), 25, user);
278
278
 
279
279
  assertTrue(loanId > 0, "Loan ID should be non-zero");
280
280
  assertTrue(loan.createdAt > 0, "Loan should be created");
@@ -77,7 +77,6 @@ contract TestCashOutBuybackFeeLeak is TestBaseWorkflow {
77
77
  mockBuyback = new MockBuybackCashOutRecorder();
78
78
  loans = new REVLoans({
79
79
  controller: jbController(),
80
- projects: jbProjects(),
81
80
  revId: feeProjectId,
82
81
  owner: address(this),
83
82
  permit2: permit2(),
@@ -89,7 +88,8 @@ contract TestCashOutBuybackFeeLeak is TestBaseWorkflow {
89
88
  jbDirectory(),
90
89
  feeProjectId,
91
90
  IJBSuckerRegistry(address(suckerRegistry)),
92
- address(loans)
91
+ address(loans),
92
+ address(0)
93
93
  );
94
94
 
95
95
  revDeployer = new REVDeployer{salt: REV_DEPLOYER_SALT}(
@@ -100,7 +100,6 @@ contract TestCrossRevnetLiquidation is TestBaseWorkflow {
100
100
  .addPriceFeedFor(0, uint32(uint160(address(TOKEN))), uint32(uint160(JBConstants.NATIVE_TOKEN)), priceFeed);
101
101
  LOANS_CONTRACT = new REVLoans({
102
102
  controller: jbController(),
103
- projects: jbProjects(),
104
103
  revId: FEE_PROJECT_ID,
105
104
  owner: address(this),
106
105
  permit2: permit2(),
@@ -111,7 +110,8 @@ contract TestCrossRevnetLiquidation is TestBaseWorkflow {
111
110
  jbDirectory(),
112
111
  FEE_PROJECT_ID,
113
112
  SUCKER_REGISTRY,
114
- address(LOANS_CONTRACT)
113
+ address(LOANS_CONTRACT),
114
+ address(0)
115
115
  );
116
116
 
117
117
  REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
@@ -104,7 +104,6 @@ contract TestCumulativeLoanCounter is TestBaseWorkflow {
104
104
  );
105
105
  LOANS_CONTRACT = new REVLoans({
106
106
  controller: jbController(),
107
- projects: jbProjects(),
108
107
  revId: FEE_PROJECT_ID,
109
108
  owner: address(this),
110
109
  permit2: permit2(),
@@ -115,7 +114,8 @@ contract TestCumulativeLoanCounter is TestBaseWorkflow {
115
114
  jbDirectory(),
116
115
  FEE_PROJECT_ID,
117
116
  SUCKER_REGISTRY,
118
- address(LOANS_CONTRACT)
117
+ address(LOANS_CONTRACT),
118
+ address(0)
119
119
  );
120
120
 
121
121
  REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
@@ -242,7 +242,7 @@ contract TestCumulativeLoanCounter is TestBaseWorkflow {
242
242
  );
243
243
  REVLoanSource memory source = REVLoanSource({token: JBConstants.NATIVE_TOKEN, terminal: jbMultiTerminal()});
244
244
  vm.prank(user);
245
- (loanId,) = LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokenCount, payable(user), 25);
245
+ (loanId,) = LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokenCount, payable(user), 25, user);
246
246
  }
247
247
 
248
248
  /// @notice Verifies totalLoansBorrowedFor never decrements after loan repayment.
@@ -106,7 +106,6 @@ contract TestLiquidateGapHandling is TestBaseWorkflow {
106
106
  .addPriceFeedFor(0, uint32(uint160(address(TOKEN))), uint32(uint160(JBConstants.NATIVE_TOKEN)), priceFeed);
107
107
  LOANS_CONTRACT = new REVLoans({
108
108
  controller: jbController(),
109
- projects: jbProjects(),
110
109
  revId: FEE_PROJECT_ID,
111
110
  owner: address(this),
112
111
  permit2: permit2(),
@@ -117,7 +116,8 @@ contract TestLiquidateGapHandling is TestBaseWorkflow {
117
116
  jbDirectory(),
118
117
  FEE_PROJECT_ID,
119
118
  SUCKER_REGISTRY,
120
- address(LOANS_CONTRACT)
119
+ address(LOANS_CONTRACT),
120
+ address(0)
121
121
  );
122
122
 
123
123
  REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
@@ -246,7 +246,7 @@ contract TestLiquidateGapHandling is TestBaseWorkflow {
246
246
  );
247
247
  REVLoanSource memory source = REVLoanSource({token: JBConstants.NATIVE_TOKEN, terminal: jbMultiTerminal()});
248
248
  vm.prank(user);
249
- (loanId,) = LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokenCount, payable(user), 25);
249
+ (loanId,) = LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokenCount, payable(user), 25, user);
250
250
  }
251
251
 
252
252
  /// @notice Liquidation should continue past deleted loan gaps.
@@ -114,7 +114,6 @@ contract TestZeroPriceFeed is TestBaseWorkflow {
114
114
 
115
115
  LOANS_CONTRACT = new REVLoans({
116
116
  controller: jbController(),
117
- projects: jbProjects(),
118
117
  revId: FEE_PROJECT_ID,
119
118
  owner: address(this),
120
119
  permit2: permit2(),
@@ -126,7 +125,8 @@ contract TestZeroPriceFeed is TestBaseWorkflow {
126
125
  jbDirectory(),
127
126
  FEE_PROJECT_ID,
128
127
  SUCKER_REGISTRY,
129
- address(LOANS_CONTRACT)
128
+ address(LOANS_CONTRACT),
129
+ address(0)
130
130
  );
131
131
 
132
132
  REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
@@ -300,7 +300,7 @@ contract TestZeroPriceFeed is TestBaseWorkflow {
300
300
  _mockBurnPermission();
301
301
  REVLoanSource memory ethSource = REVLoanSource({token: JBConstants.NATIVE_TOKEN, terminal: jbMultiTerminal()});
302
302
  vm.prank(USER);
303
- LOANS_CONTRACT.borrowFrom(REVNET_ID, ethSource, 0, ethCollateral, payable(USER), 25);
303
+ LOANS_CONTRACT.borrowFrom(REVNET_ID, ethSource, 0, ethCollateral, payable(USER), 25, USER);
304
304
 
305
305
  // Step 3: Fund the terminal with TOKEN and borrow from TOKEN source.
306
306
  uint256 tokenFunding = 1_000_000e6;
@@ -312,7 +312,7 @@ contract TestZeroPriceFeed is TestBaseWorkflow {
312
312
  _mockBurnPermission();
313
313
  REVLoanSource memory tokenSource = REVLoanSource({token: address(TOKEN), terminal: jbMultiTerminal()});
314
314
  vm.prank(USER);
315
- LOANS_CONTRACT.borrowFrom(REVNET_ID, tokenSource, 0, tokenCollateral, payable(USER), 25);
315
+ LOANS_CONTRACT.borrowFrom(REVNET_ID, tokenSource, 0, tokenCollateral, payable(USER), 25, USER);
316
316
 
317
317
  // Verify both sources have nonzero totalBorrowedFrom.
318
318
  uint256 borrowedFromEth =
@@ -384,7 +384,7 @@ contract TestZeroPriceFeed is TestBaseWorkflow {
384
384
  _mockBurnPermission();
385
385
  REVLoanSource memory ethSource = REVLoanSource({token: JBConstants.NATIVE_TOKEN, terminal: jbMultiTerminal()});
386
386
  vm.prank(USER);
387
- LOANS_CONTRACT.borrowFrom(REVNET_ID, ethSource, 0, revnetTokens / 2, payable(USER), 25);
387
+ LOANS_CONTRACT.borrowFrom(REVNET_ID, ethSource, 0, revnetTokens / 2, payable(USER), 25, USER);
388
388
 
389
389
  // Step 3: Get borrowable amount.
390
390
  vm.prank(USER);