@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
@@ -0,0 +1,420 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity 0.8.28;
3
+
4
+ // forge-lint: disable-next-line(unaliased-plain-import)
5
+ import "forge-std/Test.sol";
6
+ // forge-lint: disable-next-line(unaliased-plain-import)
7
+ import /* {*} from */ "@bananapus/core-v6/test/helpers/TestBaseWorkflow.sol";
8
+ // forge-lint: disable-next-line(unaliased-plain-import)
9
+ import /* {*} from */ "./../src/REVDeployer.sol";
10
+ // forge-lint: disable-next-line(unaliased-plain-import)
11
+ import "@croptop/core-v6/src/CTPublisher.sol";
12
+ import {MockBuybackDataHook} from "./mock/MockBuybackDataHook.sol";
13
+ import {REVEmpty721Config} from "./helpers/REVEmpty721Config.sol";
14
+ // forge-lint: disable-next-line(unaliased-plain-import)
15
+ import "@bananapus/core-v6/script/helpers/CoreDeploymentLib.sol";
16
+ // forge-lint: disable-next-line(unaliased-plain-import)
17
+ import "@bananapus/721-hook-v6/script/helpers/Hook721DeploymentLib.sol";
18
+ // forge-lint: disable-next-line(unaliased-plain-import)
19
+ import "@bananapus/suckers-v6/script/helpers/SuckerDeploymentLib.sol";
20
+ // forge-lint: disable-next-line(unaliased-plain-import)
21
+ import "@croptop/core-v6/script/helpers/CroptopDeploymentLib.sol";
22
+ // forge-lint: disable-next-line(unaliased-plain-import)
23
+ import "@bananapus/router-terminal-v6/script/helpers/RouterTerminalDeploymentLib.sol";
24
+ import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
25
+ import {JBPermissionIds} from "@bananapus/permission-ids-v6/src/JBPermissionIds.sol";
26
+ import {JBAccountingContext} from "@bananapus/core-v6/src/structs/JBAccountingContext.sol";
27
+ import {JBPermissionsData} from "@bananapus/core-v6/src/structs/JBPermissionsData.sol";
28
+ import {MockERC20} from "@bananapus/core-v6/test/mock/MockERC20.sol";
29
+ import {REVLoans} from "../src/REVLoans.sol";
30
+ import {REVHiddenTokens} from "../src/REVHiddenTokens.sol";
31
+ import {IREVHiddenTokens} from "../src/interfaces/IREVHiddenTokens.sol";
32
+ import {REVStageConfig, REVAutoIssuance} from "../src/structs/REVStageConfig.sol";
33
+ import {REVDescription} from "../src/structs/REVDescription.sol";
34
+ import {IREVLoans} from "./../src/interfaces/IREVLoans.sol";
35
+ import {JBSuckerDeployerConfig} from "@bananapus/suckers-v6/src/structs/JBSuckerDeployerConfig.sol";
36
+ import {JBSuckerRegistry} from "@bananapus/suckers-v6/src/JBSuckerRegistry.sol";
37
+ import {JB721TiersHookDeployer} from "@bananapus/721-hook-v6/src/JB721TiersHookDeployer.sol";
38
+ import {JB721TiersHook} from "@bananapus/721-hook-v6/src/JB721TiersHook.sol";
39
+ import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
40
+ import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
41
+ import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
42
+ import {REVOwner} from "../src/REVOwner.sol";
43
+ import {IREVDeployer} from "../src/interfaces/IREVDeployer.sol";
44
+
45
+ /// @notice Tests for the standalone REVHiddenTokens contract.
46
+ contract TestHiddenTokens is TestBaseWorkflow {
47
+ // forge-lint: disable-next-line(mixed-case-variable)
48
+ bytes32 REV_DEPLOYER_SALT = "REVDeployer";
49
+ // forge-lint: disable-next-line(mixed-case-variable)
50
+ bytes32 ERC20_SALT = "REV_TOKEN";
51
+
52
+ // forge-lint: disable-next-line(mixed-case-variable)
53
+ REVDeployer REV_DEPLOYER;
54
+ // forge-lint: disable-next-line(mixed-case-variable)
55
+ REVOwner REV_OWNER;
56
+ // forge-lint: disable-next-line(mixed-case-variable)
57
+ JB721TiersHook EXAMPLE_HOOK;
58
+ // forge-lint: disable-next-line(mixed-case-variable)
59
+ IJB721TiersHookDeployer HOOK_DEPLOYER;
60
+ // forge-lint: disable-next-line(mixed-case-variable)
61
+ IJB721TiersHookStore HOOK_STORE;
62
+ // forge-lint: disable-next-line(mixed-case-variable)
63
+ IJBAddressRegistry ADDRESS_REGISTRY;
64
+ // forge-lint: disable-next-line(mixed-case-variable)
65
+ IREVLoans LOANS_CONTRACT;
66
+ // forge-lint: disable-next-line(mixed-case-variable)
67
+ REVHiddenTokens HIDDEN_TOKENS;
68
+ // forge-lint: disable-next-line(mixed-case-variable)
69
+ IJBSuckerRegistry SUCKER_REGISTRY;
70
+ // forge-lint: disable-next-line(mixed-case-variable)
71
+ CTPublisher PUBLISHER;
72
+ // forge-lint: disable-next-line(mixed-case-variable)
73
+ MockBuybackDataHook MOCK_BUYBACK;
74
+
75
+ // forge-lint: disable-next-line(mixed-case-variable)
76
+ uint256 FEE_PROJECT_ID;
77
+ // forge-lint: disable-next-line(mixed-case-variable)
78
+ uint256 REVNET_ID;
79
+
80
+ // forge-lint: disable-next-line(mixed-case-variable)
81
+ address USER = makeAddr("user");
82
+ // forge-lint: disable-next-line(mixed-case-variable)
83
+ address BENEFICIARY = makeAddr("beneficiary");
84
+
85
+ address private constant TRUSTED_FORWARDER = 0xB2b5841DBeF766d4b521221732F9B618fCf34A87;
86
+
87
+ function setUp() public override {
88
+ super.setUp();
89
+ FEE_PROJECT_ID = jbProjects().createFor(multisig());
90
+ SUCKER_REGISTRY = new JBSuckerRegistry(jbDirectory(), jbPermissions(), multisig(), address(0));
91
+ HOOK_STORE = new JB721TiersHookStore();
92
+ EXAMPLE_HOOK = new JB721TiersHook(
93
+ jbDirectory(), jbPermissions(), jbPrices(), jbRulesets(), HOOK_STORE, jbSplits(), multisig()
94
+ );
95
+ ADDRESS_REGISTRY = new JBAddressRegistry();
96
+ HOOK_DEPLOYER = new JB721TiersHookDeployer(EXAMPLE_HOOK, HOOK_STORE, ADDRESS_REGISTRY, multisig());
97
+ PUBLISHER = new CTPublisher(jbDirectory(), jbPermissions(), FEE_PROJECT_ID, multisig());
98
+ MOCK_BUYBACK = new MockBuybackDataHook();
99
+
100
+ LOANS_CONTRACT = new REVLoans({
101
+ controller: jbController(),
102
+ revId: FEE_PROJECT_ID,
103
+ owner: address(this),
104
+ permit2: permit2(),
105
+ trustedForwarder: TRUSTED_FORWARDER
106
+ });
107
+
108
+ HIDDEN_TOKENS = new REVHiddenTokens(jbController(), TRUSTED_FORWARDER);
109
+
110
+ REV_OWNER = new REVOwner(
111
+ IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
112
+ jbDirectory(),
113
+ FEE_PROJECT_ID,
114
+ SUCKER_REGISTRY,
115
+ address(LOANS_CONTRACT),
116
+ address(HIDDEN_TOKENS)
117
+ );
118
+
119
+ REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
120
+ jbController(),
121
+ SUCKER_REGISTRY,
122
+ FEE_PROJECT_ID,
123
+ HOOK_DEPLOYER,
124
+ PUBLISHER,
125
+ IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
126
+ address(LOANS_CONTRACT),
127
+ TRUSTED_FORWARDER,
128
+ address(REV_OWNER)
129
+ );
130
+
131
+ REV_OWNER.setDeployer(REV_DEPLOYER);
132
+
133
+ vm.prank(multisig());
134
+ jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
135
+ _deployFeeProject();
136
+ REVNET_ID = _deployRevnet();
137
+ vm.deal(USER, 100e18);
138
+ _grantBurnPermission(USER, REVNET_ID);
139
+ }
140
+
141
+ // ──────────────────── Test: Hiding reduces totalSupply
142
+ // ────────────────────
143
+
144
+ function test_hideTokens_reducesTotalSupply() public {
145
+ // Pay to get tokens.
146
+ uint256 payAmount = 10e18;
147
+ vm.prank(USER);
148
+ jbMultiTerminal().pay{value: payAmount}({
149
+ projectId: REVNET_ID,
150
+ token: JBConstants.NATIVE_TOKEN,
151
+ amount: payAmount,
152
+ beneficiary: USER,
153
+ minReturnedTokens: 0,
154
+ memo: "",
155
+ metadata: ""
156
+ });
157
+
158
+ uint256 userTokens = jbController().TOKENS().totalBalanceOf(USER, REVNET_ID);
159
+ assertGt(userTokens, 0, "User should have tokens after paying");
160
+
161
+ uint256 totalSupplyBefore = jbController().TOKENS().totalSupplyOf(REVNET_ID);
162
+
163
+ // Hide half the tokens.
164
+ uint256 hideCount = userTokens / 2;
165
+ vm.prank(USER);
166
+ HIDDEN_TOKENS.hideTokensOf(REVNET_ID, hideCount, USER);
167
+
168
+ uint256 totalSupplyAfter = jbController().TOKENS().totalSupplyOf(REVNET_ID);
169
+ assertEq(totalSupplyAfter, totalSupplyBefore - hideCount, "Total supply should decrease by hidden amount");
170
+ assertEq(HIDDEN_TOKENS.hiddenBalanceOf(USER, REVNET_ID), hideCount, "Hidden balance should match");
171
+ assertEq(HIDDEN_TOKENS.totalHiddenOf(REVNET_ID), hideCount, "Total hidden should match");
172
+ }
173
+
174
+ // ──────────────────── Test: Revealing restores tokens
175
+ // ────────────────────
176
+
177
+ function test_revealTokens_restoresTokens() public {
178
+ // Pay to get tokens.
179
+ uint256 payAmount = 10e18;
180
+ vm.prank(USER);
181
+ jbMultiTerminal().pay{value: payAmount}({
182
+ projectId: REVNET_ID,
183
+ token: JBConstants.NATIVE_TOKEN,
184
+ amount: payAmount,
185
+ beneficiary: USER,
186
+ minReturnedTokens: 0,
187
+ memo: "",
188
+ metadata: ""
189
+ });
190
+
191
+ uint256 userTokensBefore = jbController().TOKENS().totalBalanceOf(USER, REVNET_ID);
192
+ uint256 totalSupplyBefore = jbController().TOKENS().totalSupplyOf(REVNET_ID);
193
+
194
+ // Hide tokens.
195
+ uint256 hideCount = userTokensBefore / 2;
196
+ vm.prank(USER);
197
+ HIDDEN_TOKENS.hideTokensOf(REVNET_ID, hideCount, USER);
198
+
199
+ // Reveal tokens to beneficiary.
200
+ vm.prank(USER);
201
+ HIDDEN_TOKENS.revealTokensOf(REVNET_ID, hideCount, BENEFICIARY, USER);
202
+
203
+ uint256 totalSupplyAfter = jbController().TOKENS().totalSupplyOf(REVNET_ID);
204
+ assertEq(totalSupplyAfter, totalSupplyBefore, "Total supply should be restored");
205
+ assertEq(HIDDEN_TOKENS.hiddenBalanceOf(USER, REVNET_ID), 0, "Hidden balance should be zero");
206
+ assertEq(HIDDEN_TOKENS.totalHiddenOf(REVNET_ID), 0, "Total hidden should be zero");
207
+ assertEq(
208
+ jbController().TOKENS().totalBalanceOf(BENEFICIARY, REVNET_ID),
209
+ hideCount,
210
+ "Beneficiary should receive tokens"
211
+ );
212
+ }
213
+
214
+ // ──────────────────── Test: Insufficient hidden balance reverts
215
+ // ────────────────────
216
+
217
+ function test_revealTokens_revertsOnInsufficientBalance() public {
218
+ // Pay to get tokens.
219
+ uint256 payAmount = 10e18;
220
+ vm.prank(USER);
221
+ jbMultiTerminal().pay{value: payAmount}({
222
+ projectId: REVNET_ID,
223
+ token: JBConstants.NATIVE_TOKEN,
224
+ amount: payAmount,
225
+ beneficiary: USER,
226
+ minReturnedTokens: 0,
227
+ memo: "",
228
+ metadata: ""
229
+ });
230
+
231
+ uint256 userTokens = jbController().TOKENS().totalBalanceOf(USER, REVNET_ID);
232
+ uint256 hideCount = userTokens / 4;
233
+
234
+ // Hide some tokens.
235
+ vm.prank(USER);
236
+ HIDDEN_TOKENS.hideTokensOf(REVNET_ID, hideCount, USER);
237
+
238
+ // Try to reveal more than hidden — should revert.
239
+ vm.prank(USER);
240
+ vm.expectRevert(
241
+ abi.encodeWithSelector(
242
+ REVHiddenTokens.REVHiddenTokens_InsufficientHiddenBalance.selector, hideCount, hideCount + 1
243
+ )
244
+ );
245
+ HIDDEN_TOKENS.revealTokensOf(REVNET_ID, hideCount + 1, USER, USER);
246
+ }
247
+
248
+ // ──────────────────── Test: Hidden tokens inflate cash out rate
249
+ // ────────────────────
250
+
251
+ function test_hiddenTokens_inflateCashOutRate() public {
252
+ // Pay to get tokens for 2 users.
253
+ uint256 payAmount = 10e18;
254
+ vm.prank(USER);
255
+ jbMultiTerminal().pay{value: payAmount}({
256
+ projectId: REVNET_ID,
257
+ token: JBConstants.NATIVE_TOKEN,
258
+ amount: payAmount,
259
+ beneficiary: USER,
260
+ minReturnedTokens: 0,
261
+ memo: "",
262
+ metadata: ""
263
+ });
264
+
265
+ uint256 userTokens = jbController().TOKENS().totalBalanceOf(USER, REVNET_ID);
266
+
267
+ // Hide half the user's tokens.
268
+ uint256 hideCount = userTokens / 2;
269
+ vm.prank(USER);
270
+ HIDDEN_TOKENS.hideTokensOf(REVNET_ID, hideCount, USER);
271
+
272
+ // The remaining tokens now represent a larger share of totalSupply.
273
+ uint256 totalSupply = jbController().TOKENS().totalSupplyOf(REVNET_ID);
274
+ uint256 remainingBalance = jbController().TOKENS().totalBalanceOf(USER, REVNET_ID);
275
+ assertEq(remainingBalance, userTokens - hideCount, "Remaining balance should be half");
276
+ assertEq(totalSupply, userTokens - hideCount, "Total supply should equal remaining balance");
277
+ }
278
+
279
+ // ──────────────────── Test: Events emitted correctly
280
+ // ────────────────────
281
+
282
+ function test_hideTokens_emitsEvent() public {
283
+ uint256 payAmount = 10e18;
284
+ vm.prank(USER);
285
+ jbMultiTerminal().pay{value: payAmount}({
286
+ projectId: REVNET_ID,
287
+ token: JBConstants.NATIVE_TOKEN,
288
+ amount: payAmount,
289
+ beneficiary: USER,
290
+ minReturnedTokens: 0,
291
+ memo: "",
292
+ metadata: ""
293
+ });
294
+
295
+ uint256 userTokens = jbController().TOKENS().totalBalanceOf(USER, REVNET_ID);
296
+
297
+ vm.prank(USER);
298
+ vm.expectEmit(true, false, false, true);
299
+ emit IREVHiddenTokens.HideTokens(REVNET_ID, userTokens, USER, USER);
300
+ HIDDEN_TOKENS.hideTokensOf(REVNET_ID, userTokens, USER);
301
+ }
302
+
303
+ function test_revealTokens_emitsEvent() public {
304
+ uint256 payAmount = 10e18;
305
+ vm.prank(USER);
306
+ jbMultiTerminal().pay{value: payAmount}({
307
+ projectId: REVNET_ID,
308
+ token: JBConstants.NATIVE_TOKEN,
309
+ amount: payAmount,
310
+ beneficiary: USER,
311
+ minReturnedTokens: 0,
312
+ memo: "",
313
+ metadata: ""
314
+ });
315
+
316
+ uint256 userTokens = jbController().TOKENS().totalBalanceOf(USER, REVNET_ID);
317
+
318
+ vm.prank(USER);
319
+ HIDDEN_TOKENS.hideTokensOf(REVNET_ID, userTokens, USER);
320
+
321
+ vm.prank(USER);
322
+ vm.expectEmit(true, false, false, true);
323
+ emit IREVHiddenTokens.RevealTokens(REVNET_ID, userTokens, BENEFICIARY, USER, USER);
324
+ HIDDEN_TOKENS.revealTokensOf(REVNET_ID, userTokens, BENEFICIARY, USER);
325
+ }
326
+
327
+ // ──────────────────── Internal helpers
328
+ // ────────────────────
329
+
330
+ function _grantBurnPermission(address account, uint256 revnetId) internal {
331
+ uint8[] memory permissionIds = new uint8[](1);
332
+ permissionIds[0] = JBPermissionIds.BURN_TOKENS;
333
+ JBPermissionsData memory permissionsData = JBPermissionsData({
334
+ operator: address(HIDDEN_TOKENS),
335
+ // forge-lint: disable-next-line(unsafe-typecast)
336
+ projectId: uint56(revnetId),
337
+ permissionIds: permissionIds
338
+ });
339
+ vm.prank(account);
340
+ jbPermissions().setPermissionsFor(account, permissionsData);
341
+ }
342
+
343
+ function _deployFeeProject() internal {
344
+ JBAccountingContext[] memory acc = new JBAccountingContext[](1);
345
+ acc[0] = JBAccountingContext({
346
+ token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
347
+ });
348
+ JBTerminalConfig[] memory tc = new JBTerminalConfig[](1);
349
+ tc[0] = JBTerminalConfig({terminal: jbMultiTerminal(), accountingContextsToAccept: acc});
350
+ REVStageConfig[] memory stages = new REVStageConfig[](1);
351
+ stages[0] = REVStageConfig({
352
+ startsAtOrAfter: uint48(block.timestamp),
353
+ autoIssuances: new REVAutoIssuance[](0),
354
+ splitPercent: 0,
355
+ splits: new JBSplit[](0),
356
+ initialIssuance: uint112(1000e18),
357
+ issuanceCutFrequency: 0,
358
+ issuanceCutPercent: 0,
359
+ cashOutTaxRate: 0,
360
+ extraMetadata: 0
361
+ });
362
+ // forge-lint: disable-next-line(named-struct-fields)
363
+ REVConfig memory feeConfig = REVConfig({
364
+ description: REVDescription("Fee Revnet", "FEE", "", ERC20_SALT),
365
+ baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
366
+ splitOperator: multisig(),
367
+ stageConfigurations: stages
368
+ });
369
+ vm.prank(multisig());
370
+ REV_DEPLOYER.deployFor({
371
+ revnetId: FEE_PROJECT_ID,
372
+ configuration: feeConfig,
373
+ terminalConfigurations: tc,
374
+ suckerDeploymentConfiguration: REVSuckerDeploymentConfig({
375
+ deployerConfigurations: new JBSuckerDeployerConfig[](0), salt: keccak256("FEE")
376
+ }),
377
+ tiered721HookConfiguration: REVEmpty721Config.empty721Config(uint32(uint160(JBConstants.NATIVE_TOKEN))),
378
+ allowedPosts: REVEmpty721Config.emptyAllowedPosts()
379
+ });
380
+ }
381
+
382
+ function _deployRevnet() internal returns (uint256) {
383
+ JBAccountingContext[] memory acc = new JBAccountingContext[](1);
384
+ acc[0] = JBAccountingContext({
385
+ token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
386
+ });
387
+ JBTerminalConfig[] memory tc = new JBTerminalConfig[](1);
388
+ tc[0] = JBTerminalConfig({terminal: jbMultiTerminal(), accountingContextsToAccept: acc});
389
+ REVStageConfig[] memory stages = new REVStageConfig[](1);
390
+ stages[0] = REVStageConfig({
391
+ startsAtOrAfter: uint48(block.timestamp),
392
+ autoIssuances: new REVAutoIssuance[](0),
393
+ splitPercent: 0,
394
+ splits: new JBSplit[](0),
395
+ initialIssuance: uint112(1000e18),
396
+ issuanceCutFrequency: 0,
397
+ issuanceCutPercent: 0,
398
+ cashOutTaxRate: 5000, // 50% cash out tax rate
399
+ extraMetadata: 0
400
+ });
401
+ // forge-lint: disable-next-line(named-struct-fields)
402
+ REVConfig memory revConfig = REVConfig({
403
+ description: REVDescription("Test Revnet", "TEST", "", bytes32("TEST_TOKEN")),
404
+ baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
405
+ splitOperator: multisig(),
406
+ stageConfigurations: stages
407
+ });
408
+ (uint256 revnetId,) = REV_DEPLOYER.deployFor({
409
+ revnetId: 0,
410
+ configuration: revConfig,
411
+ terminalConfigurations: tc,
412
+ suckerDeploymentConfiguration: REVSuckerDeploymentConfig({
413
+ deployerConfigurations: new JBSuckerDeployerConfig[](0), salt: keccak256("NANA")
414
+ }),
415
+ tiered721HookConfiguration: REVEmpty721Config.empty721Config(uint32(uint160(JBConstants.NATIVE_TOKEN))),
416
+ allowedPosts: REVEmpty721Config.emptyAllowedPosts()
417
+ });
418
+ return revnetId;
419
+ }
420
+ }
@@ -92,7 +92,6 @@ contract TestHookArrayOOB is TestBaseWorkflow {
92
92
  MOCK_BUYBACK = new MockBuybackDataHook();
93
93
  LOANS_CONTRACT = new REVLoans({
94
94
  controller: jbController(),
95
- projects: jbProjects(),
96
95
  revId: FEE_PROJECT_ID,
97
96
  owner: address(this),
98
97
  permit2: permit2(),
@@ -103,7 +102,8 @@ contract TestHookArrayOOB is TestBaseWorkflow {
103
102
  jbDirectory(),
104
103
  FEE_PROJECT_ID,
105
104
  SUCKER_REGISTRY,
106
- address(LOANS_CONTRACT)
105
+ address(LOANS_CONTRACT),
106
+ address(0)
107
107
  );
108
108
 
109
109
  REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
@@ -100,7 +100,6 @@ contract TestLiquidationBehavior 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 TestLiquidationBehavior 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}(
@@ -247,7 +247,7 @@ contract TestLiquidationBehavior is TestBaseWorkflow {
247
247
  );
248
248
  REVLoanSource memory source = REVLoanSource({token: JBConstants.NATIVE_TOKEN, terminal: jbMultiTerminal()});
249
249
  vm.prank(user);
250
- (loanId,) = LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokenCount, payable(user), prepaidFee);
250
+ (loanId,) = LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokenCount, payable(user), prepaidFee, user);
251
251
  }
252
252
 
253
253
  /// @notice Verify that collateral is burned (not escrowed) — totalCollateralOf increases and loans contract holds
@@ -273,7 +273,7 @@ contract TestLiquidationBehavior is TestBaseWorkflow {
273
273
  );
274
274
  REVLoanSource memory source = REVLoanSource({token: JBConstants.NATIVE_TOKEN, terminal: jbMultiTerminal()});
275
275
  vm.prank(USER);
276
- LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokenCount, payable(USER), 25);
276
+ LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokenCount, payable(USER), 25, USER);
277
277
 
278
278
  uint256 totalCollateralAfterBorrow = LOANS_CONTRACT.totalCollateralOf(REVNET_ID);
279
279
 
@@ -108,7 +108,6 @@ contract TestLoanSourceRotation is TestBaseWorkflow {
108
108
 
109
109
  LOANS_CONTRACT = new REVLoans({
110
110
  controller: jbController(),
111
- projects: jbProjects(),
112
111
  revId: FEE_PROJECT_ID,
113
112
  owner: address(this),
114
113
  permit2: permit2(),
@@ -120,7 +119,8 @@ contract TestLoanSourceRotation is TestBaseWorkflow {
120
119
  jbDirectory(),
121
120
  FEE_PROJECT_ID,
122
121
  SUCKER_REGISTRY,
123
- address(LOANS_CONTRACT)
122
+ address(LOANS_CONTRACT),
123
+ address(0)
124
124
  );
125
125
 
126
126
  REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
@@ -271,7 +271,7 @@ contract TestLoanSourceRotation is TestBaseWorkflow {
271
271
  );
272
272
 
273
273
  vm.prank(user);
274
- (loanId,) = LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokenCount, payable(user), 25);
274
+ (loanId,) = LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokenCount, payable(user), 25, user);
275
275
  }
276
276
 
277
277
  //*********************************************************************//
@@ -323,7 +323,8 @@ contract TestLoanSourceRotation is TestBaseWorkflow {
323
323
  );
324
324
 
325
325
  vm.prank(user2);
326
- (uint256 loanId2,) = LOANS_CONTRACT.borrowFrom(REVNET_ID, tokenSource, 0, user2Tokens, payable(user2), 25);
326
+ (uint256 loanId2,) =
327
+ LOANS_CONTRACT.borrowFrom(REVNET_ID, tokenSource, 0, user2Tokens, payable(user2), 25, user2);
327
328
  assertGt(loanId2, 0, "TOKEN loan should be created");
328
329
 
329
330
  // Both sources should now be registered.
@@ -415,7 +416,8 @@ contract TestLoanSourceRotation is TestBaseWorkflow {
415
416
  );
416
417
 
417
418
  vm.prank(user2);
418
- (uint256 loanId2,) = LOANS_CONTRACT.borrowFrom(REVNET_ID, ethSource2, 0, user2Tokens, payable(user2), 25);
419
+ (uint256 loanId2,) =
420
+ LOANS_CONTRACT.borrowFrom(REVNET_ID, ethSource2, 0, user2Tokens, payable(user2), 25, user2);
419
421
  assertGt(loanId2, 0, "second loan should be created");
420
422
 
421
423
  // First loan should be unaffected.
@@ -530,7 +532,7 @@ contract TestLoanSourceRotation is TestBaseWorkflow {
530
532
  );
531
533
 
532
534
  vm.prank(user2);
533
- (uint256 loanId2,) = LOANS_CONTRACT.borrowFrom(REVNET_ID, ethSource, 0, tokens, payable(user2), 25);
535
+ (uint256 loanId2,) = LOANS_CONTRACT.borrowFrom(REVNET_ID, ethSource, 0, tokens, payable(user2), 25, user2);
534
536
  assertGt(loanId2, 0, "second loan should succeed");
535
537
 
536
538
  uint256 countAfterSecond = LOANS_CONTRACT.totalLoansBorrowedFor(REVNET_ID);
@@ -211,7 +211,6 @@ contract TestLoansCashOutDelay is TestBaseWorkflow {
211
211
 
212
212
  LOANS_CONTRACT = new REVLoans({
213
213
  controller: jbController(),
214
- projects: jbProjects(),
215
214
  revId: FEE_PROJECT_ID,
216
215
  owner: address(this),
217
216
  permit2: permit2(),
@@ -223,7 +222,8 @@ contract TestLoansCashOutDelay is TestBaseWorkflow {
223
222
  jbDirectory(),
224
223
  FEE_PROJECT_ID,
225
224
  SUCKER_REGISTRY,
226
- address(LOANS_CONTRACT)
225
+ address(LOANS_CONTRACT),
226
+ address(0)
227
227
  );
228
228
 
229
229
  REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
@@ -347,7 +347,7 @@ contract TestLoansCashOutDelay is TestBaseWorkflow {
347
347
  abi.encodeWithSelector(REVLoans.REVLoans_CashOutDelayNotFinished.selector, cashOutDelay, block.timestamp)
348
348
  );
349
349
  vm.prank(USER);
350
- LOANS_CONTRACT.borrowFrom(DELAYED_REVNET_ID, source, 1, tokenCount, payable(USER), 25);
350
+ LOANS_CONTRACT.borrowFrom(DELAYED_REVNET_ID, source, 1, tokenCount, payable(USER), 25, USER);
351
351
  }
352
352
 
353
353
  /// @notice After warping past the delay, borrowableAmountFrom should return a non-zero value.
@@ -393,7 +393,7 @@ contract TestLoansCashOutDelay is TestBaseWorkflow {
393
393
  // Borrow — should succeed.
394
394
  vm.prank(USER);
395
395
  (uint256 loanId,) =
396
- LOANS_CONTRACT.borrowFrom(DELAYED_REVNET_ID, source, borrowable, tokenCount, payable(USER), 25);
396
+ LOANS_CONTRACT.borrowFrom(DELAYED_REVNET_ID, source, borrowable, tokenCount, payable(USER), 25, USER);
397
397
  assertGt(loanId, 0, "Should have created a loan");
398
398
  }
399
399
 
@@ -431,7 +431,7 @@ contract TestLoansCashOutDelay is TestBaseWorkflow {
431
431
  // Borrow — should succeed without any delay.
432
432
  vm.prank(USER);
433
433
  (uint256 loanId,) =
434
- LOANS_CONTRACT.borrowFrom(NORMAL_REVNET_ID, source, borrowable, tokenCount, payable(USER), 25);
434
+ LOANS_CONTRACT.borrowFrom(NORMAL_REVNET_ID, source, borrowable, tokenCount, payable(USER), 25, USER);
435
435
  assertGt(loanId, 0, "Should have created a loan");
436
436
  }
437
437
 
@@ -477,6 +477,6 @@ contract TestLoansCashOutDelay is TestBaseWorkflow {
477
477
  abi.encodeWithSelector(REVLoans.REVLoans_CashOutDelayNotFinished.selector, cashOutDelay, block.timestamp)
478
478
  );
479
479
  vm.prank(USER);
480
- LOANS_CONTRACT.borrowFrom(DELAYED_REVNET_ID, source, 1, tokenCount, payable(USER), 25);
480
+ LOANS_CONTRACT.borrowFrom(DELAYED_REVNET_ID, source, 1, tokenCount, payable(USER), 25, USER);
481
481
  }
482
482
  }
@@ -101,7 +101,6 @@ contract TestLongTailEconomics is TestBaseWorkflow {
101
101
 
102
102
  LOANS_CONTRACT = new REVLoans({
103
103
  controller: jbController(),
104
- projects: jbProjects(),
105
104
  revId: FEE_PROJECT_ID,
106
105
  owner: address(this),
107
106
  permit2: permit2(),
@@ -113,7 +112,8 @@ contract TestLongTailEconomics 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}(
@@ -290,7 +290,6 @@ contract TestLowFindings is TestBaseWorkflow {
290
290
 
291
291
  LOANS_CONTRACT = new REVLoans({
292
292
  controller: jbController(),
293
- projects: jbProjects(),
294
293
  revId: FEE_PROJECT_ID,
295
294
  owner: address(this),
296
295
  permit2: permit2(),
@@ -302,7 +301,8 @@ contract TestLowFindings is TestBaseWorkflow {
302
301
  jbDirectory(),
303
302
  FEE_PROJECT_ID,
304
303
  SUCKER_REGISTRY,
305
- address(LOANS_CONTRACT)
304
+ address(LOANS_CONTRACT),
305
+ address(0)
306
306
  );
307
307
 
308
308
  REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
@@ -388,7 +388,8 @@ contract TestLowFindings is TestBaseWorkflow {
388
388
  uint256 minPrepaid = LOANS_CONTRACT.MIN_PREPAID_FEE_PERCENT();
389
389
 
390
390
  vm.prank(USER);
391
- (uint256 loanId,) = LOANS_CONTRACT.borrowFrom(revnetId, source, loanable, tokens, payable(USER), minPrepaid);
391
+ (uint256 loanId,) =
392
+ LOANS_CONTRACT.borrowFrom(revnetId, source, loanable, tokens, payable(USER), minPrepaid, USER);
392
393
 
393
394
  REVLoan memory loanBefore = LOANS_CONTRACT.loanOf(loanId);
394
395
  assertGt(loanBefore.amount, 0, "Loan should have an amount");
@@ -435,7 +436,8 @@ contract TestLowFindings is TestBaseWorkflow {
435
436
  uint256 minPrepaid = LOANS_CONTRACT.MIN_PREPAID_FEE_PERCENT();
436
437
 
437
438
  vm.prank(USER);
438
- (uint256 loanId,) = LOANS_CONTRACT.borrowFrom(revnetId, source, loanable, tokens, payable(USER), minPrepaid);
439
+ (uint256 loanId,) =
440
+ LOANS_CONTRACT.borrowFrom(revnetId, source, loanable, tokens, payable(USER), minPrepaid, USER);
439
441
 
440
442
  REVLoan memory loanBefore = LOANS_CONTRACT.loanOf(loanId);
441
443
  assertGt(loanBefore.amount, 0, "Loan should exist before liquidation");
@@ -479,7 +481,8 @@ contract TestLowFindings is TestBaseWorkflow {
479
481
  uint256 minPrepaid = LOANS_CONTRACT.MIN_PREPAID_FEE_PERCENT();
480
482
 
481
483
  vm.prank(USER);
482
- (uint256 loanId,) = LOANS_CONTRACT.borrowFrom(revnetId, source, loanable, tokens, payable(USER), minPrepaid);
484
+ (uint256 loanId,) =
485
+ LOANS_CONTRACT.borrowFrom(revnetId, source, loanable, tokens, payable(USER), minPrepaid, USER);
483
486
 
484
487
  REVLoan memory loanBefore = LOANS_CONTRACT.loanOf(loanId);
485
488
  assertGt(loanBefore.collateral, 1, "Need >1 collateral for partial return");
@@ -536,7 +539,8 @@ contract TestLowFindings is TestBaseWorkflow {
536
539
  uint256 minPrepaid = LOANS_CONTRACT.MIN_PREPAID_FEE_PERCENT();
537
540
 
538
541
  vm.prank(USER);
539
- (uint256 loanId,) = LOANS_CONTRACT.borrowFrom(revnetId, source, loanable, tokens, payable(USER), minPrepaid);
542
+ (uint256 loanId,) =
543
+ LOANS_CONTRACT.borrowFrom(revnetId, source, loanable, tokens, payable(USER), minPrepaid, USER);
540
544
 
541
545
  REVLoan memory loan = LOANS_CONTRACT.loanOf(loanId);
542
546
 
@@ -587,7 +591,8 @@ contract TestLowFindings is TestBaseWorkflow {
587
591
 
588
592
  // Borrow the full max against all tokens.
589
593
  vm.prank(USER);
590
- (uint256 loanId,) = LOANS_CONTRACT.borrowFrom(revnetId, source, loanable, tokens, payable(USER), minPrepaid);
594
+ (uint256 loanId,) =
595
+ LOANS_CONTRACT.borrowFrom(revnetId, source, loanable, tokens, payable(USER), minPrepaid, USER);
591
596
 
592
597
  REVLoan memory loanBefore = LOANS_CONTRACT.loanOf(loanId);
593
598
  assertGt(loanBefore.collateral, 0, "Loan should have collateral");
@@ -656,6 +661,6 @@ contract TestLowFindings is TestBaseWorkflow {
656
661
  // Attempt to borrow with 1 wei of collateral -- bonding curve returns 0, should revert.
657
662
  vm.prank(USER);
658
663
  vm.expectRevert(REVLoans.REVLoans_ZeroBorrowAmount.selector);
659
- LOANS_CONTRACT.borrowFrom(revnetId, source, 0, 1, payable(USER), minPrepaid);
664
+ LOANS_CONTRACT.borrowFrom(revnetId, source, 0, 1, payable(USER), minPrepaid, USER);
660
665
  }
661
666
  }