@rev-net/core-v6 0.0.29 → 0.0.31

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 (77) 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 +28 -3
  9. package/package.json +8 -8
  10. package/references/operations.md +1 -1
  11. package/script/Deploy.s.sol +26 -4
  12. package/src/REVDeployer.sol +4 -2
  13. package/src/REVHiddenTokens.sol +149 -0
  14. package/src/REVLoans.sol +192 -199
  15. package/src/REVOwner.sol +51 -14
  16. package/src/interfaces/IREVHiddenTokens.sol +53 -0
  17. package/src/interfaces/IREVLoans.sol +8 -6
  18. package/test/REV.integrations.t.sol +12 -2
  19. package/test/REVAutoIssuanceFuzz.t.sol +12 -2
  20. package/test/REVDeployerRegressions.t.sol +14 -3
  21. package/test/REVInvincibility.t.sol +27 -8
  22. package/test/REVInvincibilityHandler.sol +1 -1
  23. package/test/REVLifecycle.t.sol +14 -3
  24. package/test/REVLoans.invariants.t.sol +15 -4
  25. package/test/REVLoansAttacks.t.sol +19 -7
  26. package/test/REVLoansFeeRecovery.t.sol +24 -13
  27. package/test/REVLoansFindings.t.sol +16 -5
  28. package/test/REVLoansRegressions.t.sol +15 -4
  29. package/test/REVLoansSourceFeeRecovery.t.sol +16 -5
  30. package/test/REVLoansSourced.t.sol +60 -25
  31. package/test/REVLoansUnSourced.t.sol +15 -4
  32. package/test/TestBurnHeldTokens.t.sol +14 -3
  33. package/test/TestCEIPattern.t.sol +19 -7
  34. package/test/TestCashOutCallerValidation.t.sol +15 -4
  35. package/test/TestConversionDocumentation.t.sol +14 -3
  36. package/test/TestCrossCurrencyReclaim.t.sol +14 -3
  37. package/test/TestCrossSourceReallocation.t.sol +15 -4
  38. package/test/TestERC2771MetaTx.t.sol +18 -5
  39. package/test/TestEmptyBuybackSpecs.t.sol +14 -3
  40. package/test/TestFlashLoanSurplus.t.sol +15 -4
  41. package/test/TestHiddenTokens.t.sol +431 -0
  42. package/test/TestHookArrayOOB.t.sol +14 -3
  43. package/test/TestLiquidationBehavior.t.sol +16 -5
  44. package/test/TestLoanSourceRotation.t.sol +20 -7
  45. package/test/TestLoansCashOutDelay.t.sol +18 -7
  46. package/test/TestLongTailEconomics.t.sol +14 -3
  47. package/test/TestLowFindings.t.sol +25 -9
  48. package/test/TestMixedFixes.t.sol +19 -8
  49. package/test/TestPermit2Signatures.t.sol +15 -4
  50. package/test/TestReallocationSandwich.t.sol +16 -4
  51. package/test/TestRevnetRegressions.t.sol +16 -5
  52. package/test/TestSplitWeightAdjustment.t.sol +16 -4
  53. package/test/TestSplitWeightE2E.t.sol +18 -4
  54. package/test/TestSplitWeightFork.t.sol +16 -3
  55. package/test/TestStageTransitionBorrowable.t.sol +14 -3
  56. package/test/TestSwapTerminalPermission.t.sol +14 -3
  57. package/test/TestUint112Overflow.t.sol +15 -4
  58. package/test/TestZeroAmountLoanGuard.t.sol +15 -4
  59. package/test/TestZeroRepayment.t.sol +15 -4
  60. package/test/audit/CodexPhantomSurplusTerminal.t.sol +367 -0
  61. package/test/audit/LoanIdOverflowGuard.t.sol +16 -5
  62. package/test/audit/NemesisOperatorDelegation.t.sol +289 -0
  63. package/test/fork/ForkTestBase.sol +18 -4
  64. package/test/fork/TestLoanBorrowFork.t.sol +2 -1
  65. package/test/fork/TestLoanERC20Fork.t.sol +4 -2
  66. package/test/fork/TestLoanTransferFork.t.sol +12 -2
  67. package/test/helpers/MaliciousContracts.sol +1 -1
  68. package/test/mock/MockBuybackCashOutRecorder.sol +2 -0
  69. package/test/mock/MockBuybackDataHook.sol +3 -1
  70. package/test/mock/MockBuybackDataHookMintPath.sol +2 -0
  71. package/test/mock/MockSuckerRegistry.sol +17 -0
  72. package/test/regression/TestBurnPermissionRequired.t.sol +16 -5
  73. package/test/regression/TestCashOutBuybackFeeLeak.t.sol +16 -3
  74. package/test/regression/TestCrossRevnetLiquidation.t.sol +14 -3
  75. package/test/regression/TestCumulativeLoanCounter.t.sol +15 -4
  76. package/test/regression/TestLiquidateGapHandling.t.sol +15 -4
  77. package/test/regression/TestZeroPriceFeed.t.sol +17 -6
package/src/REVOwner.sol CHANGED
@@ -59,6 +59,9 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook {
59
59
  /// @notice The Juicebox project ID of the revnet that receives cash out fees.
60
60
  uint256 public immutable FEE_REVNET_ID;
61
61
 
62
+ /// @notice The hidden tokens contract used by all revnets.
63
+ address public immutable HIDDEN_TOKENS;
64
+
62
65
  /// @notice The loan contract used by all revnets.
63
66
  address public immutable LOANS;
64
67
 
@@ -99,12 +102,14 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook {
99
102
  /// @param feeRevnetId The Juicebox project ID of the fee revnet.
100
103
  /// @param suckerRegistry The sucker registry.
101
104
  /// @param loans The loan contract address.
105
+ /// @param hiddenTokens The hidden tokens contract address.
102
106
  constructor(
103
107
  IJBBuybackHookRegistry buybackHook,
104
108
  IJBDirectory directory,
105
109
  uint256 feeRevnetId,
106
110
  IJBSuckerRegistry suckerRegistry,
107
- address loans
111
+ address loans,
112
+ address hiddenTokens
108
113
  ) {
109
114
  BUYBACK_HOOK = buybackHook;
110
115
  DIRECTORY = directory;
@@ -112,6 +117,8 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook {
112
117
  SUCKER_REGISTRY = suckerRegistry;
113
118
  // slither-disable-next-line missing-zero-check
114
119
  LOANS = loans;
120
+ // slither-disable-next-line missing-zero-check
121
+ HIDDEN_TOKENS = hiddenTokens;
115
122
  _DEPLOYER_BINDER = msg.sender;
116
123
  }
117
124
 
@@ -130,7 +137,8 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook {
130
137
  /// @return cashOutTaxRate The cash out tax rate, which influences the amount of terminal tokens which get cashed
131
138
  /// out.
132
139
  /// @return cashOutCount The number of revnet tokens that are cashed out.
133
- /// @return totalSupply The total revnet token supply.
140
+ /// @return totalSupply The total token supply across all chains (for both proportional reclaim and tax).
141
+ /// @return effectiveSurplusValue The global surplus across all chains for proportional reclaim.
134
142
  /// @return hookSpecifications The amount of funds and the data to send to cash out hooks (this contract).
135
143
  function beforeCashOutRecordedWith(JBBeforeCashOutRecordedContext calldata context)
136
144
  external
@@ -140,6 +148,7 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook {
140
148
  uint256 cashOutTaxRate,
141
149
  uint256 cashOutCount,
142
150
  uint256 totalSupply,
151
+ uint256 effectiveSurplusValue,
143
152
  JBCashOutHookSpecification[] memory hookSpecifications
144
153
  )
145
154
  {
@@ -147,7 +156,7 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook {
147
156
  // This relies on the sucker registry to only contain trusted sucker contracts deployed via
148
157
  // the registry's own deploySuckersFor flow — external addresses cannot register as suckers.
149
158
  if (_isSuckerOf({revnetId: context.projectId, addr: context.holder})) {
150
- return (0, context.cashOutCount, context.totalSupply, hookSpecifications);
159
+ return (0, context.cashOutCount, context.totalSupply, context.surplus.value, hookSpecifications);
151
160
  }
152
161
 
153
162
  // Keep a reference to the cash out delay of the revnet.
@@ -161,11 +170,23 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook {
161
170
  // Get the terminal that will receive the cash out fee.
162
171
  IJBTerminal feeTerminal = DIRECTORY.primaryTerminalOf({projectId: FEE_REVNET_ID, token: context.surplus.token});
163
172
 
173
+ // Compute the cross-chain total supply (local + remote peer chain supplies) for cross-chain-aware bonding
174
+ // curve.
175
+ totalSupply = context.totalSupply + SUCKER_REGISTRY.remoteTotalSupplyOf(context.projectId);
176
+ effectiveSurplusValue = context.surplus.value
177
+ + SUCKER_REGISTRY.remoteSurplusOf({
178
+ projectId: context.projectId,
179
+ decimals: context.surplus.decimals,
180
+ currency: uint256(uint160(context.surplus.token))
181
+ });
182
+
164
183
  // If there's no cash out tax (100% cash out tax rate), if there's no fee terminal, or if the beneficiary is
165
- // feeless (e.g. the router terminal routing value between projects), proxy directly to the buyback hook.
184
+ // feeless (e.g. the router terminal routing value between projects), proxy to the buyback hook with our
185
+ // totalSupply and effectiveSurplusValue.
166
186
  if (context.cashOutTaxRate == 0 || address(feeTerminal) == address(0) || context.beneficiaryIsFeeless) {
167
187
  // slither-disable-next-line unused-return
168
- return BUYBACK_HOOK.beforeCashOutRecordedWith(context);
188
+ (cashOutTaxRate, cashOutCount,,, hookSpecifications) = BUYBACK_HOOK.beforeCashOutRecordedWith(context);
189
+ return (cashOutTaxRate, cashOutCount, totalSupply, effectiveSurplusValue, hookSpecifications);
169
190
  }
170
191
 
171
192
  // Split the cashed-out tokens into a fee portion and a non-fee portion.
@@ -178,20 +199,33 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook {
178
199
  uint256 nonFeeCashOutCount = context.cashOutCount - feeCashOutCount;
179
200
 
180
201
  // Calculate how much surplus the non-fee tokens can reclaim via the bonding curve.
202
+ // Use effective (cross-chain) surplus; cap at local surplus.
181
203
  uint256 postFeeReclaimedAmount = JBCashOuts.cashOutFrom({
182
- surplus: context.surplus.value,
204
+ surplus: effectiveSurplusValue,
183
205
  cashOutCount: nonFeeCashOutCount,
184
- totalSupply: context.totalSupply,
206
+ totalSupply: totalSupply,
185
207
  cashOutTaxRate: context.cashOutTaxRate
186
208
  });
209
+ // Cap at local surplus — the bonding curve uses cross-chain effective surplus which can exceed what this
210
+ // chain's terminal actually holds.
211
+ if (postFeeReclaimedAmount > context.surplus.value) postFeeReclaimedAmount = context.surplus.value;
187
212
 
188
213
  // Calculate how much the fee tokens reclaim from the remaining surplus after the non-fee reclaim.
214
+ // Use remaining effective surplus; cap at remaining local surplus.
189
215
  uint256 feeAmount = JBCashOuts.cashOutFrom({
190
- surplus: context.surplus.value - postFeeReclaimedAmount,
216
+ surplus: effectiveSurplusValue > postFeeReclaimedAmount
217
+ ? effectiveSurplusValue - postFeeReclaimedAmount
218
+ : 0,
191
219
  cashOutCount: feeCashOutCount,
192
- totalSupply: context.totalSupply - nonFeeCashOutCount,
220
+ totalSupply: totalSupply - nonFeeCashOutCount,
193
221
  cashOutTaxRate: context.cashOutTaxRate
194
222
  });
223
+ // Cap the fee reclaim at remaining local surplus. The bonding curve uses the cross-chain effective surplus,
224
+ // which can exceed what's actually held locally. Without this cap, the terminal would try to send more than
225
+ // it has.
226
+ if (feeAmount > context.surplus.value - postFeeReclaimedAmount) {
227
+ feeAmount = context.surplus.value - postFeeReclaimedAmount;
228
+ }
195
229
 
196
230
  // Build a context for the buyback hook using only the non-fee token count.
197
231
  JBBeforeCashOutRecordedContext memory buybackHookContext = context;
@@ -199,11 +233,13 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook {
199
233
 
200
234
  // Let the buyback hook adjust the cash out parameters and optionally return a hook specification.
201
235
  JBCashOutHookSpecification[] memory buybackHookSpecifications;
202
- (cashOutTaxRate, cashOutCount, totalSupply, buybackHookSpecifications) =
236
+ (cashOutTaxRate, cashOutCount,,, buybackHookSpecifications) =
203
237
  BUYBACK_HOOK.beforeCashOutRecordedWith(buybackHookContext);
204
238
 
205
239
  // If the fee rounds down to zero, return the buyback hook's response directly — no fee to process.
206
- if (feeAmount == 0) return (cashOutTaxRate, cashOutCount, totalSupply, buybackHookSpecifications);
240
+ if (feeAmount == 0) {
241
+ return (cashOutTaxRate, cashOutCount, totalSupply, effectiveSurplusValue, buybackHookSpecifications);
242
+ }
207
243
 
208
244
  // Build a hook spec that routes the fee amount to this contract's `afterCashOutRecordedWith` for processing.
209
245
  JBCashOutHookSpecification memory feeSpec = JBCashOutHookSpecification({
@@ -225,7 +261,7 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook {
225
261
  hookSpecifications[0] = feeSpec;
226
262
  }
227
263
 
228
- return (cashOutTaxRate, cashOutCount, totalSupply, hookSpecifications);
264
+ return (cashOutTaxRate, cashOutCount, totalSupply, effectiveSurplusValue, hookSpecifications);
229
265
  }
230
266
 
231
267
  /// @notice Before a revnet processes an incoming payment, determine the weight and pay hooks to use.
@@ -302,8 +338,9 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook {
302
338
  override
303
339
  returns (bool)
304
340
  {
305
- // The loans contract, buyback hook (and its delegates), and suckers are allowed to mint the revnet's tokens.
306
- return addr == LOANS || addr == address(BUYBACK_HOOK)
341
+ // The loans contract, hidden tokens contract, buyback hook (and its delegates), and suckers are allowed to mint
342
+ // the revnet's tokens.
343
+ return addr == LOANS || addr == HIDDEN_TOKENS || addr == address(BUYBACK_HOOK)
307
344
  || BUYBACK_HOOK.hasMintPermissionFor({projectId: revnetId, ruleset: ruleset, addr: addr})
308
345
  || _isSuckerOf({revnetId: revnetId, addr: addr});
309
346
  }
@@ -0,0 +1,53 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.0;
3
+
4
+ import {IJBController} from "@bananapus/core-v6/src/interfaces/IJBController.sol";
5
+
6
+ /// @notice Manages hiding (burning) and revealing (re-minting) revnet tokens to exclude them from totalSupply.
7
+ interface IREVHiddenTokens {
8
+ /// @notice Emitted when tokens are hidden (burned and tracked for later reveal).
9
+ /// @param revnetId The ID of the revnet whose tokens are hidden.
10
+ /// @param tokenCount The number of tokens hidden.
11
+ /// @param holder The address whose tokens are hidden.
12
+ /// @param caller The address that hid the tokens.
13
+ event HideTokens(uint256 indexed revnetId, uint256 tokenCount, address holder, address caller);
14
+
15
+ /// @notice Emitted when previously hidden tokens are revealed (re-minted).
16
+ /// @param revnetId The ID of the revnet whose tokens are revealed.
17
+ /// @param tokenCount The number of tokens revealed.
18
+ /// @param beneficiary The address receiving the revealed tokens.
19
+ /// @param holder The address whose hidden balance is decremented.
20
+ /// @param caller The address that revealed the tokens.
21
+ event RevealTokens(
22
+ uint256 indexed revnetId, uint256 tokenCount, address beneficiary, address holder, address caller
23
+ );
24
+
25
+ /// @notice The controller that manages revnets using this contract.
26
+ /// @return The controller contract.
27
+ function CONTROLLER() external view returns (IJBController);
28
+
29
+ /// @notice The number of tokens a holder has hidden for a given revnet.
30
+ /// @param holder The address of the token holder.
31
+ /// @param revnetId The ID of the revnet.
32
+ /// @return The number of hidden tokens.
33
+ function hiddenBalanceOf(address holder, uint256 revnetId) external view returns (uint256);
34
+
35
+ /// @notice The total number of hidden tokens for a revnet.
36
+ /// @param revnetId The ID of the revnet.
37
+ /// @return The total hidden token count.
38
+ function totalHiddenOf(uint256 revnetId) external view returns (uint256);
39
+
40
+ /// @notice Hide tokens by burning them and tracking them for later reveal.
41
+ /// @dev The holder must have granted BURN_TOKENS permission to this contract.
42
+ /// @param revnetId The ID of the revnet whose tokens to hide.
43
+ /// @param tokenCount The number of tokens to hide.
44
+ /// @param holder The address whose tokens to hide.
45
+ function hideTokensOf(uint256 revnetId, uint256 tokenCount, address holder) external;
46
+
47
+ /// @notice Reveal previously hidden tokens by re-minting them.
48
+ /// @param revnetId The ID of the revnet whose tokens to reveal.
49
+ /// @param tokenCount The number of tokens to reveal.
50
+ /// @param beneficiary The address that will receive the revealed tokens.
51
+ /// @param holder The address whose hidden balance to decrement.
52
+ function revealTokensOf(uint256 revnetId, uint256 tokenCount, address beneficiary, address holder) external;
53
+ }
@@ -5,9 +5,9 @@ import {IJBController} from "@bananapus/core-v6/src/interfaces/IJBController.sol
5
5
  import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
6
6
  import {IJBPayoutTerminal} from "@bananapus/core-v6/src/interfaces/IJBPayoutTerminal.sol";
7
7
  import {IJBPrices} from "@bananapus/core-v6/src/interfaces/IJBPrices.sol";
8
- import {IJBProjects} from "@bananapus/core-v6/src/interfaces/IJBProjects.sol";
9
8
  import {IJBTokenUriResolver} from "@bananapus/core-v6/src/interfaces/IJBTokenUriResolver.sol";
10
9
  import {JBSingleAllowance} from "@bananapus/core-v6/src/structs/JBSingleAllowance.sol";
10
+ import {IJBSuckerRegistry} from "@bananapus/suckers-v6/src/interfaces/IJBSuckerRegistry.sol";
11
11
  import {IPermit2} from "@uniswap/permit2/src/interfaces/IPermit2.sol";
12
12
  import {REVLoan} from "../structs/REVLoan.sol";
13
13
  import {REVLoanSource} from "../structs/REVLoanSource.sol";
@@ -164,14 +164,14 @@ interface IREVLoans {
164
164
  /// @return The prices contract.
165
165
  function PRICES() external view returns (IJBPrices);
166
166
 
167
- /// @notice The contract that mints ERC-721s representing project ownership.
168
- /// @return The projects contract.
169
- function PROJECTS() external view returns (IJBProjects);
170
-
171
167
  /// @notice The ID of the REV revnet that receives protocol fees from loans.
172
168
  /// @return The REV revnet ID.
173
169
  function REV_ID() external view returns (uint256);
174
170
 
171
+ /// @notice The sucker registry used to discover peer chain suckers for cross-chain supply/surplus awareness.
172
+ /// @return The sucker registry.
173
+ function SUCKER_REGISTRY() external view returns (IJBSuckerRegistry);
174
+
175
175
  /// @notice The fee percent charged by the REV revnet on each loan, in terms of `JBConstants.MAX_FEE`.
176
176
  /// @return The REV prepaid fee percent.
177
177
  function REV_PREPAID_FEE_PERCENT() external view returns (uint256);
@@ -219,6 +219,7 @@ interface IREVLoans {
219
219
  /// @param collateralCount The amount of tokens to use as collateral for the loan.
220
220
  /// @param beneficiary The address that will receive the borrowed funds and fee payment tokens.
221
221
  /// @param prepaidFeePercent The fee percent to charge upfront, in terms of `JBConstants.MAX_FEE`.
222
+ /// @param holder The address whose tokens are used as collateral and who receives the loan NFT.
222
223
  /// @return loanId The ID of the loan created from borrowing.
223
224
  /// @return The loan created from borrowing.
224
225
  function borrowFrom(
@@ -227,7 +228,8 @@ interface IREVLoans {
227
228
  uint256 minBorrowAmount,
228
229
  uint256 collateralCount,
229
230
  address payable beneficiary,
230
- uint256 prepaidFeePercent
231
+ uint256 prepaidFeePercent,
232
+ address holder
231
233
  )
232
234
  external
233
235
  returns (uint256 loanId, REVLoan memory);
@@ -40,6 +40,8 @@ import {JBArbitrumSucker, JBLayer, IArbGatewayRouter, IInbox} from "@bananapus/s
40
40
 
41
41
  import {JB721TiersHook} from "@bananapus/721-hook-v6/src/JB721TiersHook.sol";
42
42
  import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
43
+ import {JB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/JB721CheckpointsDeployer.sol";
44
+ import {IJB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/interfaces/IJB721CheckpointsDeployer.sol";
43
45
  import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
44
46
  import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
45
47
 
@@ -211,7 +213,14 @@ contract REVnet_Integrations is TestBaseWorkflow {
211
213
  HOOK_STORE = new JB721TiersHookStore();
212
214
 
213
215
  EXAMPLE_HOOK = new JB721TiersHook(
214
- jbDirectory(), jbPermissions(), jbPrices(), jbRulesets(), HOOK_STORE, jbSplits(), multisig()
216
+ jbDirectory(),
217
+ jbPermissions(),
218
+ jbPrices(),
219
+ jbRulesets(),
220
+ HOOK_STORE,
221
+ jbSplits(),
222
+ IJB721CheckpointsDeployer(address(new JB721CheckpointsDeployer())),
223
+ multisig()
215
224
  );
216
225
 
217
226
  ADDRESS_REGISTRY = new JBAddressRegistry();
@@ -226,7 +235,8 @@ contract REVnet_Integrations is TestBaseWorkflow {
226
235
  jbDirectory(),
227
236
  FEE_PROJECT_ID,
228
237
  SUCKER_REGISTRY,
229
- makeAddr("loans")
238
+ makeAddr("loans"),
239
+ address(0)
230
240
  );
231
241
 
232
242
  REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
@@ -32,6 +32,8 @@ import {JBSuckerRegistry} from "@bananapus/suckers-v6/src/JBSuckerRegistry.sol";
32
32
  import {JB721TiersHookDeployer} from "@bananapus/721-hook-v6/src/JB721TiersHookDeployer.sol";
33
33
  import {JB721TiersHook} from "@bananapus/721-hook-v6/src/JB721TiersHook.sol";
34
34
  import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
35
+ import {JB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/JB721CheckpointsDeployer.sol";
36
+ import {IJB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/interfaces/IJB721CheckpointsDeployer.sol";
35
37
  import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
36
38
  import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
37
39
  import {REVEmpty721Config} from "./helpers/REVEmpty721Config.sol";
@@ -77,7 +79,14 @@ contract REVAutoIssuanceFuzz_Local is TestBaseWorkflow {
77
79
  SUCKER_REGISTRY = new JBSuckerRegistry(jbDirectory(), jbPermissions(), multisig(), address(0));
78
80
  HOOK_STORE = new JB721TiersHookStore();
79
81
  EXAMPLE_HOOK = new JB721TiersHook(
80
- jbDirectory(), jbPermissions(), jbPrices(), jbRulesets(), HOOK_STORE, jbSplits(), multisig()
82
+ jbDirectory(),
83
+ jbPermissions(),
84
+ jbPrices(),
85
+ jbRulesets(),
86
+ HOOK_STORE,
87
+ jbSplits(),
88
+ IJB721CheckpointsDeployer(address(new JB721CheckpointsDeployer())),
89
+ multisig()
81
90
  );
82
91
  ADDRESS_REGISTRY = new JBAddressRegistry();
83
92
  HOOK_DEPLOYER = new JB721TiersHookDeployer(EXAMPLE_HOOK, HOOK_STORE, ADDRESS_REGISTRY, multisig());
@@ -89,7 +98,8 @@ contract REVAutoIssuanceFuzz_Local is TestBaseWorkflow {
89
98
  jbDirectory(),
90
99
  FEE_PROJECT_ID,
91
100
  SUCKER_REGISTRY,
92
- makeAddr("loans")
101
+ makeAddr("loans"),
102
+ address(0)
93
103
  );
94
104
 
95
105
  REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
@@ -35,10 +35,13 @@ import {JBSuckerRegistry} from "@bananapus/suckers-v6/src/JBSuckerRegistry.sol";
35
35
  import {JB721TiersHookDeployer} from "@bananapus/721-hook-v6/src/JB721TiersHookDeployer.sol";
36
36
  import {JB721TiersHook} from "@bananapus/721-hook-v6/src/JB721TiersHook.sol";
37
37
  import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
38
+ import {JB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/JB721CheckpointsDeployer.sol";
39
+ import {IJB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/interfaces/IJB721CheckpointsDeployer.sol";
38
40
  import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
39
41
  import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
40
42
  import {REVOwner} from "../src/REVOwner.sol";
41
43
  import {IREVDeployer} from "../src/interfaces/IREVDeployer.sol";
44
+ import {MockSuckerRegistry} from "./mock/MockSuckerRegistry.sol";
42
45
 
43
46
  /// @notice Regression tests for REVDeployer.
44
47
  contract REVDeployerRegressions is TestBaseWorkflow {
@@ -83,7 +86,14 @@ contract REVDeployerRegressions is TestBaseWorkflow {
83
86
  SUCKER_REGISTRY = new JBSuckerRegistry(jbDirectory(), jbPermissions(), multisig(), address(0));
84
87
  HOOK_STORE = new JB721TiersHookStore();
85
88
  EXAMPLE_HOOK = new JB721TiersHook(
86
- jbDirectory(), jbPermissions(), jbPrices(), jbRulesets(), HOOK_STORE, jbSplits(), multisig()
89
+ jbDirectory(),
90
+ jbPermissions(),
91
+ jbPrices(),
92
+ jbRulesets(),
93
+ HOOK_STORE,
94
+ jbSplits(),
95
+ IJB721CheckpointsDeployer(address(new JB721CheckpointsDeployer())),
96
+ multisig()
87
97
  );
88
98
  ADDRESS_REGISTRY = new JBAddressRegistry();
89
99
  HOOK_DEPLOYER = new JB721TiersHookDeployer(EXAMPLE_HOOK, HOOK_STORE, ADDRESS_REGISTRY, multisig());
@@ -92,7 +102,7 @@ contract REVDeployerRegressions is TestBaseWorkflow {
92
102
 
93
103
  LOANS_CONTRACT = new REVLoans({
94
104
  controller: jbController(),
95
- projects: jbProjects(),
105
+ suckerRegistry: IJBSuckerRegistry(address(new MockSuckerRegistry())),
96
106
  revId: FEE_PROJECT_ID,
97
107
  owner: address(this),
98
108
  permit2: permit2(),
@@ -104,7 +114,8 @@ contract REVDeployerRegressions is TestBaseWorkflow {
104
114
  jbDirectory(),
105
115
  FEE_PROJECT_ID,
106
116
  SUCKER_REGISTRY,
107
- address(LOANS_CONTRACT)
117
+ address(LOANS_CONTRACT),
118
+ address(0)
108
119
  );
109
120
 
110
121
  REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
@@ -41,6 +41,8 @@ import {JBSuckerRegistry} from "@bananapus/suckers-v6/src/JBSuckerRegistry.sol";
41
41
  import {JB721TiersHookDeployer} from "@bananapus/721-hook-v6/src/JB721TiersHookDeployer.sol";
42
42
  import {JB721TiersHook} from "@bananapus/721-hook-v6/src/JB721TiersHook.sol";
43
43
  import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
44
+ import {JB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/JB721CheckpointsDeployer.sol";
45
+ import {IJB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/interfaces/IJB721CheckpointsDeployer.sol";
44
46
  import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
45
47
  import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
46
48
  import {mulDiv} from "@prb/math/src/Common.sol";
@@ -50,6 +52,7 @@ import {BrokenFeeTerminal} from "./helpers/MaliciousContracts.sol";
50
52
  import {REVEmpty721Config} from "./helpers/REVEmpty721Config.sol";
51
53
  import {REVOwner} from "../src/REVOwner.sol";
52
54
  import {IREVDeployer} from "../src/interfaces/IREVDeployer.sol";
55
+ import {MockSuckerRegistry} from "./mock/MockSuckerRegistry.sol";
53
56
 
54
57
  // =========================================================================
55
58
  // Shared config struct
@@ -234,7 +237,14 @@ contract REVInvincibility_PropertyTests is TestBaseWorkflow {
234
237
  SUCKER_REGISTRY = new JBSuckerRegistry(jbDirectory(), jbPermissions(), multisig(), address(0));
235
238
  HOOK_STORE = new JB721TiersHookStore();
236
239
  EXAMPLE_HOOK = new JB721TiersHook(
237
- jbDirectory(), jbPermissions(), jbPrices(), jbRulesets(), HOOK_STORE, jbSplits(), multisig()
240
+ jbDirectory(),
241
+ jbPermissions(),
242
+ jbPrices(),
243
+ jbRulesets(),
244
+ HOOK_STORE,
245
+ jbSplits(),
246
+ IJB721CheckpointsDeployer(address(new JB721CheckpointsDeployer())),
247
+ multisig()
238
248
  );
239
249
  ADDRESS_REGISTRY = new JBAddressRegistry();
240
250
  HOOK_DEPLOYER = new JB721TiersHookDeployer(EXAMPLE_HOOK, HOOK_STORE, ADDRESS_REGISTRY, multisig());
@@ -244,7 +254,7 @@ contract REVInvincibility_PropertyTests is TestBaseWorkflow {
244
254
 
245
255
  LOANS_CONTRACT = new REVLoans({
246
256
  controller: jbController(),
247
- projects: jbProjects(),
257
+ suckerRegistry: IJBSuckerRegistry(address(new MockSuckerRegistry())),
248
258
  revId: FEE_PROJECT_ID,
249
259
  owner: address(this),
250
260
  permit2: permit2(),
@@ -256,7 +266,8 @@ contract REVInvincibility_PropertyTests is TestBaseWorkflow {
256
266
  jbDirectory(),
257
267
  FEE_PROJECT_ID,
258
268
  SUCKER_REGISTRY,
259
- address(LOANS_CONTRACT)
269
+ address(LOANS_CONTRACT),
270
+ address(0)
260
271
  );
261
272
 
262
273
  REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
@@ -329,7 +340,7 @@ contract REVInvincibility_PropertyTests is TestBaseWorkflow {
329
340
  REVLoanSource memory source = REVLoanSource({token: JBConstants.NATIVE_TOKEN, terminal: jbMultiTerminal()});
330
341
 
331
342
  vm.prank(user);
332
- (loanId,) = LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokenCount, payable(user), prepaidFee);
343
+ (loanId,) = LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokenCount, payable(user), prepaidFee, user);
333
344
  }
334
345
 
335
346
  // =====================================================================
@@ -788,7 +799,7 @@ contract REVInvincibility_PropertyTests is TestBaseWorkflow {
788
799
 
789
800
  if (borrowableA > 0) {
790
801
  vm.prank(userA);
791
- LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokensA, payable(userA), 25);
802
+ LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokensA, payable(userA), 25, userA);
792
803
  }
793
804
 
794
805
  // UserB's tokens should still have proportional cash-out value
@@ -1019,7 +1030,14 @@ contract REVInvincibility_Invariants is StdInvariant, TestBaseWorkflow {
1019
1030
  SUCKER_REGISTRY = new JBSuckerRegistry(jbDirectory(), jbPermissions(), multisig(), address(0));
1020
1031
  HOOK_STORE = new JB721TiersHookStore();
1021
1032
  EXAMPLE_HOOK = new JB721TiersHook(
1022
- jbDirectory(), jbPermissions(), jbPrices(), jbRulesets(), HOOK_STORE, jbSplits(), multisig()
1033
+ jbDirectory(),
1034
+ jbPermissions(),
1035
+ jbPrices(),
1036
+ jbRulesets(),
1037
+ HOOK_STORE,
1038
+ jbSplits(),
1039
+ IJB721CheckpointsDeployer(address(new JB721CheckpointsDeployer())),
1040
+ multisig()
1023
1041
  );
1024
1042
  ADDRESS_REGISTRY = new JBAddressRegistry();
1025
1043
  HOOK_DEPLOYER = new JB721TiersHookDeployer(EXAMPLE_HOOK, HOOK_STORE, ADDRESS_REGISTRY, multisig());
@@ -1028,7 +1046,7 @@ contract REVInvincibility_Invariants is StdInvariant, TestBaseWorkflow {
1028
1046
 
1029
1047
  LOANS_CONTRACT = new REVLoans({
1030
1048
  controller: jbController(),
1031
- projects: jbProjects(),
1049
+ suckerRegistry: IJBSuckerRegistry(address(new MockSuckerRegistry())),
1032
1050
  revId: FEE_PROJECT_ID,
1033
1051
  owner: address(this),
1034
1052
  permit2: permit2(),
@@ -1040,7 +1058,8 @@ contract REVInvincibility_Invariants is StdInvariant, TestBaseWorkflow {
1040
1058
  jbDirectory(),
1041
1059
  FEE_PROJECT_ID,
1042
1060
  SUCKER_REGISTRY,
1043
- address(LOANS_CONTRACT)
1061
+ address(LOANS_CONTRACT),
1062
+ address(0)
1044
1063
  );
1045
1064
 
1046
1065
  REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
@@ -145,7 +145,7 @@ contract REVInvincibilityHandler is JBTest {
145
145
 
146
146
  REVLoanSource memory source = REVLoanSource({token: JBConstants.NATIVE_TOKEN, terminal: TERMINAL});
147
147
  (, REVLoan memory loan) =
148
- LOANS.borrowFrom(REVNET_ID, source, borrowable, receivedTokens, payable(USER), prepaidFee);
148
+ LOANS.borrowFrom(REVNET_ID, source, borrowable, receivedTokens, payable(USER), prepaidFee, USER);
149
149
 
150
150
  COLLATERAL_SUM += receivedTokens;
151
151
  BORROWED_SUM += loan.amount;
@@ -34,11 +34,14 @@ import {JBSuckerRegistry} from "@bananapus/suckers-v6/src/JBSuckerRegistry.sol";
34
34
  import {JB721TiersHookDeployer} from "@bananapus/721-hook-v6/src/JB721TiersHookDeployer.sol";
35
35
  import {JB721TiersHook} from "@bananapus/721-hook-v6/src/JB721TiersHook.sol";
36
36
  import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
37
+ import {JB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/JB721CheckpointsDeployer.sol";
38
+ import {IJB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/interfaces/IJB721CheckpointsDeployer.sol";
37
39
  import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
38
40
  import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
39
41
  import {REVEmpty721Config} from "./helpers/REVEmpty721Config.sol";
40
42
  import {REVOwner} from "../src/REVOwner.sol";
41
43
  import {IREVDeployer} from "../src/interfaces/IREVDeployer.sol";
44
+ import {MockSuckerRegistry} from "./mock/MockSuckerRegistry.sol";
42
45
 
43
46
  /// @notice Full revnet lifecycle E2E: deploy 3-stage -> pay -> advance stages -> cash out.
44
47
  contract REVLifecycle_Local is TestBaseWorkflow {
@@ -91,7 +94,14 @@ contract REVLifecycle_Local is TestBaseWorkflow {
91
94
  SUCKER_REGISTRY = new JBSuckerRegistry(jbDirectory(), jbPermissions(), multisig(), address(0));
92
95
  HOOK_STORE = new JB721TiersHookStore();
93
96
  EXAMPLE_HOOK = new JB721TiersHook(
94
- jbDirectory(), jbPermissions(), jbPrices(), jbRulesets(), HOOK_STORE, jbSplits(), multisig()
97
+ jbDirectory(),
98
+ jbPermissions(),
99
+ jbPrices(),
100
+ jbRulesets(),
101
+ HOOK_STORE,
102
+ jbSplits(),
103
+ IJB721CheckpointsDeployer(address(new JB721CheckpointsDeployer())),
104
+ multisig()
95
105
  );
96
106
  ADDRESS_REGISTRY = new JBAddressRegistry();
97
107
  HOOK_DEPLOYER = new JB721TiersHookDeployer(EXAMPLE_HOOK, HOOK_STORE, ADDRESS_REGISTRY, multisig());
@@ -100,7 +110,7 @@ contract REVLifecycle_Local is TestBaseWorkflow {
100
110
 
101
111
  LOANS_CONTRACT = new REVLoans({
102
112
  controller: jbController(),
103
- projects: jbProjects(),
113
+ suckerRegistry: IJBSuckerRegistry(address(new MockSuckerRegistry())),
104
114
  revId: FEE_PROJECT_ID,
105
115
  owner: address(this),
106
116
  permit2: permit2(),
@@ -112,7 +122,8 @@ contract REVLifecycle_Local is TestBaseWorkflow {
112
122
  jbDirectory(),
113
123
  FEE_PROJECT_ID,
114
124
  SUCKER_REGISTRY,
115
- address(LOANS_CONTRACT)
125
+ address(LOANS_CONTRACT),
126
+ address(0)
116
127
  );
117
128
 
118
129
  REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
@@ -39,11 +39,14 @@ import {JBSuckerRegistry} from "@bananapus/suckers-v6/src/JBSuckerRegistry.sol";
39
39
  import {JB721TiersHookDeployer} from "@bananapus/721-hook-v6/src/JB721TiersHookDeployer.sol";
40
40
  import {JB721TiersHook} from "@bananapus/721-hook-v6/src/JB721TiersHook.sol";
41
41
  import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
42
+ import {JB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/JB721CheckpointsDeployer.sol";
43
+ import {IJB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/interfaces/IJB721CheckpointsDeployer.sol";
42
44
  import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
43
45
  import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
44
46
  import {JBTest} from "@bananapus/core-v6/test/helpers/JBTest.sol";
45
47
  import {REVOwner} from "../src/REVOwner.sol";
46
48
  import {IREVDeployer} from "../src/interfaces/IREVDeployer.sol";
49
+ import {MockSuckerRegistry} from "./mock/MockSuckerRegistry.sol";
47
50
 
48
51
  struct FeeProjectConfig {
49
52
  REVConfig configuration;
@@ -117,7 +120,7 @@ contract REVLoansCallHandler is JBTest {
117
120
 
118
121
  REVLoanSource memory sauce = REVLoanSource({token: JBConstants.NATIVE_TOKEN, terminal: TERMINAL});
119
122
  (, REVLoan memory lastLoan) =
120
- LOANS.borrowFrom(REVNET_ID, sauce, borrowable, receivedTokens, payable(USER), prepaidFee);
123
+ LOANS.borrowFrom(REVNET_ID, sauce, borrowable, receivedTokens, payable(USER), prepaidFee, USER);
121
124
 
122
125
  COLLATERAL_SUM += receivedTokens;
123
126
  BORROWED_SUM += lastLoan.amount;
@@ -530,7 +533,14 @@ contract InvariantREVLoansTests is StdInvariant, TestBaseWorkflow {
530
533
  HOOK_STORE = new JB721TiersHookStore();
531
534
 
532
535
  EXAMPLE_HOOK = new JB721TiersHook(
533
- jbDirectory(), jbPermissions(), jbPrices(), jbRulesets(), HOOK_STORE, jbSplits(), multisig()
536
+ jbDirectory(),
537
+ jbPermissions(),
538
+ jbPrices(),
539
+ jbRulesets(),
540
+ HOOK_STORE,
541
+ jbSplits(),
542
+ IJB721CheckpointsDeployer(address(new JB721CheckpointsDeployer())),
543
+ multisig()
534
544
  );
535
545
 
536
546
  ADDRESS_REGISTRY = new JBAddressRegistry();
@@ -542,7 +552,7 @@ contract InvariantREVLoansTests is StdInvariant, TestBaseWorkflow {
542
552
 
543
553
  LOANS_CONTRACT = new REVLoans({
544
554
  controller: jbController(),
545
- projects: jbProjects(),
555
+ suckerRegistry: IJBSuckerRegistry(address(new MockSuckerRegistry())),
546
556
  revId: FEE_PROJECT_ID,
547
557
  owner: address(this),
548
558
  permit2: permit2(),
@@ -554,7 +564,8 @@ contract InvariantREVLoansTests is StdInvariant, TestBaseWorkflow {
554
564
  jbDirectory(),
555
565
  FEE_PROJECT_ID,
556
566
  SUCKER_REGISTRY,
557
- address(LOANS_CONTRACT)
567
+ address(LOANS_CONTRACT),
568
+ address(0)
558
569
  );
559
570
 
560
571
  REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(