@rev-net/core-v6 0.0.36 → 0.0.39

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 (101) hide show
  1. package/CHANGELOG.md +2 -2
  2. package/README.md +6 -7
  3. package/foundry.toml +1 -1
  4. package/package.json +23 -16
  5. package/references/operations.md +1 -1
  6. package/references/runtime.md +1 -1
  7. package/script/Deploy.s.sol +12 -9
  8. package/src/REVDeployer.sol +60 -65
  9. package/src/REVHiddenTokens.sol +2 -2
  10. package/src/REVLoans.sol +134 -90
  11. package/src/REVOwner.sol +124 -17
  12. package/src/interfaces/IREVDeployer.sol +2 -1
  13. package/src/interfaces/IREVHiddenTokens.sol +4 -1
  14. package/src/interfaces/IREVOwner.sol +5 -0
  15. package/ADMINISTRATION.md +0 -73
  16. package/ARCHITECTURE.md +0 -116
  17. package/AUDIT_INSTRUCTIONS.md +0 -90
  18. package/RISKS.md +0 -97
  19. package/SKILLS.md +0 -46
  20. package/STYLE_GUIDE.md +0 -610
  21. package/USER_JOURNEYS.md +0 -195
  22. package/foundry.lock +0 -11
  23. package/slither-ci.config.json +0 -10
  24. package/sphinx.lock +0 -507
  25. package/test/REV.integrations.t.sol +0 -573
  26. package/test/REVAutoIssuanceFuzz.t.sol +0 -328
  27. package/test/REVDeployerRegressions.t.sol +0 -396
  28. package/test/REVInvincibility.t.sol +0 -1371
  29. package/test/REVInvincibilityHandler.sol +0 -387
  30. package/test/REVLifecycle.t.sol +0 -420
  31. package/test/REVLoans.invariants.t.sol +0 -724
  32. package/test/REVLoansAttacks.t.sol +0 -816
  33. package/test/REVLoansFeeRecovery.t.sol +0 -783
  34. package/test/REVLoansFindings.t.sol +0 -711
  35. package/test/REVLoansRegressions.t.sol +0 -364
  36. package/test/REVLoansSourceFeeRecovery.t.sol +0 -517
  37. package/test/REVLoansSourced.t.sol +0 -1839
  38. package/test/REVLoansUnSourced.t.sol +0 -409
  39. package/test/TestAuditFixVerification.t.sol +0 -675
  40. package/test/TestBurnHeldTokens.t.sol +0 -394
  41. package/test/TestCEIPattern.t.sol +0 -508
  42. package/test/TestCashOutCallerValidation.t.sol +0 -452
  43. package/test/TestConversionDocumentation.t.sol +0 -368
  44. package/test/TestCrossCurrencyReclaim.t.sol +0 -610
  45. package/test/TestCrossSourceReallocation.t.sol +0 -361
  46. package/test/TestERC2771MetaTx.t.sol +0 -585
  47. package/test/TestEmptyBuybackSpecs.t.sol +0 -300
  48. package/test/TestFlashLoanSurplus.t.sol +0 -365
  49. package/test/TestHiddenTokens.t.sol +0 -474
  50. package/test/TestHookArrayOOB.t.sol +0 -278
  51. package/test/TestLiquidationBehavior.t.sol +0 -398
  52. package/test/TestLoanSourceRotation.t.sol +0 -553
  53. package/test/TestLoansCashOutDelay.t.sol +0 -493
  54. package/test/TestLongTailEconomics.t.sol +0 -677
  55. package/test/TestLowFindings.t.sol +0 -677
  56. package/test/TestMixedFixes.t.sol +0 -593
  57. package/test/TestPermit2Signatures.t.sol +0 -683
  58. package/test/TestReallocationSandwich.t.sol +0 -412
  59. package/test/TestRevnetRegressions.t.sol +0 -350
  60. package/test/TestSplitWeightAdjustment.t.sol +0 -527
  61. package/test/TestSplitWeightE2E.t.sol +0 -605
  62. package/test/TestSplitWeightFork.t.sol +0 -855
  63. package/test/TestStageTransitionBorrowable.t.sol +0 -301
  64. package/test/TestSwapTerminalPermission.t.sol +0 -262
  65. package/test/TestTerminalEncodingInHash.t.sol +0 -326
  66. package/test/TestUint112Overflow.t.sol +0 -311
  67. package/test/TestZeroAmountLoanGuard.t.sol +0 -378
  68. package/test/TestZeroRepayment.t.sol +0 -354
  69. package/test/audit/CodexCrossChainBuybackRouteMismatch.t.sol +0 -184
  70. package/test/audit/CodexPhantomSurplusTerminal.t.sol +0 -367
  71. package/test/audit/CodexREVOwnerRemoteSurplusCurrencyMismatch.t.sol +0 -142
  72. package/test/audit/LoanIdOverflowGuard.t.sol +0 -523
  73. package/test/audit/NemesisOperatorDelegation.t.sol +0 -356
  74. package/test/audit/SupportsInterfaceTest.t.sol +0 -51
  75. package/test/audit/TestFeeAllowanceLeak.t.sol +0 -197
  76. package/test/audit/TestLoansAndDeployerFixes.t.sol +0 -576
  77. package/test/fork/ForkTestBase.sol +0 -727
  78. package/test/fork/TestAutoIssuanceFork.t.sol +0 -148
  79. package/test/fork/TestCashOutFork.t.sol +0 -253
  80. package/test/fork/TestIssuanceDecayFork.t.sol +0 -158
  81. package/test/fork/TestLoanBorrowFork.t.sol +0 -163
  82. package/test/fork/TestLoanCrossRulesetFork.t.sol +0 -308
  83. package/test/fork/TestLoanERC20Fork.t.sol +0 -465
  84. package/test/fork/TestLoanLiquidationFork.t.sol +0 -135
  85. package/test/fork/TestLoanReallocateFork.t.sol +0 -113
  86. package/test/fork/TestLoanRepayFork.t.sol +0 -188
  87. package/test/fork/TestLoanTransferFork.t.sol +0 -143
  88. package/test/fork/TestPermit2PaymentFork.t.sol +0 -300
  89. package/test/fork/TestSplitWeightFork.t.sol +0 -189
  90. package/test/helpers/MaliciousContracts.sol +0 -247
  91. package/test/helpers/REVEmpty721Config.sol +0 -45
  92. package/test/mock/MockBuybackCashOutRecorder.sol +0 -84
  93. package/test/mock/MockBuybackDataHook.sol +0 -112
  94. package/test/mock/MockBuybackDataHookMintPath.sol +0 -68
  95. package/test/mock/MockSuckerRegistry.sol +0 -17
  96. package/test/regression/TestBurnPermissionRequired.t.sol +0 -294
  97. package/test/regression/TestCashOutBuybackFeeLeak.t.sol +0 -232
  98. package/test/regression/TestCrossRevnetLiquidation.t.sol +0 -255
  99. package/test/regression/TestCumulativeLoanCounter.t.sol +0 -361
  100. package/test/regression/TestLiquidateGapHandling.t.sol +0 -394
  101. package/test/regression/TestZeroPriceFeed.t.sol +0 -422
@@ -1,523 +0,0 @@
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
- // forge-lint: disable-next-line(unaliased-plain-import)
14
- import "@bananapus/core-v6/script/helpers/CoreDeploymentLib.sol";
15
- // forge-lint: disable-next-line(unaliased-plain-import)
16
- import "@bananapus/721-hook-v6/script/helpers/Hook721DeploymentLib.sol";
17
- // forge-lint: disable-next-line(unaliased-plain-import)
18
- import "@bananapus/suckers-v6/script/helpers/SuckerDeploymentLib.sol";
19
- // forge-lint: disable-next-line(unaliased-plain-import)
20
- import "@croptop/core-v6/script/helpers/CroptopDeploymentLib.sol";
21
- // forge-lint: disable-next-line(unaliased-plain-import)
22
- import "@bananapus/router-terminal-v6/script/helpers/RouterTerminalDeploymentLib.sol";
23
- // Core constants and structs used throughout the test.
24
- import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
25
- import {JBAccountingContext} from "@bananapus/core-v6/src/structs/JBAccountingContext.sol";
26
- import {JBSingleAllowance} from "@bananapus/core-v6/src/structs/JBSingleAllowance.sol";
27
- // Price feed mock for native-token-to-native-token identity pricing.
28
- import {MockPriceFeed} from "@bananapus/core-v6/test/mock/MockPriceFeed.sol";
29
- // REVLoans contract and its supporting types.
30
- import {REVLoans} from "../../src/REVLoans.sol";
31
- import {REVLoan} from "../../src/structs/REVLoan.sol";
32
- import {REVStageConfig, REVAutoIssuance} from "../../src/structs/REVStageConfig.sol";
33
- import {REVLoanSource} from "../../src/structs/REVLoanSource.sol";
34
- import {REVDescription} from "../../src/structs/REVDescription.sol";
35
- // Deployment dependencies for suckers, 721 hooks, and address registry.
36
- import {JBSuckerDeployerConfig} from "@bananapus/suckers-v6/src/structs/JBSuckerDeployerConfig.sol";
37
- import {JBSuckerRegistry} from "@bananapus/suckers-v6/src/JBSuckerRegistry.sol";
38
- import {JB721TiersHookDeployer} from "@bananapus/721-hook-v6/src/JB721TiersHookDeployer.sol";
39
- import {JB721TiersHook} from "@bananapus/721-hook-v6/src/JB721TiersHook.sol";
40
- import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
41
- import {JB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/JB721CheckpointsDeployer.sol";
42
- import {IJB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/interfaces/IJB721CheckpointsDeployer.sol";
43
- import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
44
- import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
45
- // Helper that provides empty 721 tier configs for revnet deployment.
46
- import {REVEmpty721Config} from "../helpers/REVEmpty721Config.sol";
47
- import {REVOwner} from "../../src/REVOwner.sol";
48
- import {IREVDeployer} from "../../src/interfaces/IREVDeployer.sol";
49
- import {MockSuckerRegistry} from "../mock/MockSuckerRegistry.sol";
50
-
51
- /// @notice Regression tests for the loan ID overflow guard in REVLoans.
52
- /// @dev The totalLoansBorrowedFor counter must never exceed _ONE_TRILLION (1e12).
53
- /// When it reaches that limit, borrowFrom, _reallocateCollateralFromLoan, and
54
- /// the partial-repay branch of repayLoan must all revert with REVLoans_LoanIdOverflow().
55
- /// These tests use vm.store to set the counter to the limit, then verify the revert.
56
- contract LoanIdOverflowGuard is TestBaseWorkflow {
57
- // ---------------------------------------------------------------
58
- // Constants
59
- // ---------------------------------------------------------------
60
-
61
- /// @dev Salt for deterministic REVDeployer deployment.
62
- // forge-lint: disable-next-line(mixed-case-variable)
63
- bytes32 REV_DEPLOYER_SALT = "REVDeployer";
64
-
65
- /// @dev The overflow boundary -- must match _ONE_TRILLION in REVLoans.sol.
66
- uint256 private constant _ONE_TRILLION = 1_000_000_000_000;
67
-
68
- /// @dev Storage slot of the totalLoansBorrowedFor mapping in REVLoans (slot 8).
69
- /// Determined via `forge inspect REVLoans storage-layout`.
70
- uint256 private constant TOTAL_LOANS_BORROWED_FOR_SLOT = 8;
71
-
72
- /// @dev The address that is allowed to forward meta-transactions.
73
- address private constant TRUSTED_FORWARDER = 0xB2b5841DBeF766d4b521221732F9B618fCf34A87;
74
-
75
- // ---------------------------------------------------------------
76
- // State variables
77
- // ---------------------------------------------------------------
78
-
79
- // forge-lint: disable-next-line(mixed-case-variable)
80
- REVDeployer REV_DEPLOYER;
81
- // forge-lint: disable-next-line(mixed-case-variable)
82
- REVOwner REV_OWNER;
83
- // forge-lint: disable-next-line(mixed-case-variable)
84
- JB721TiersHook EXAMPLE_HOOK;
85
- // forge-lint: disable-next-line(mixed-case-variable)
86
- IJB721TiersHookDeployer HOOK_DEPLOYER;
87
- // forge-lint: disable-next-line(mixed-case-variable)
88
- IJB721TiersHookStore HOOK_STORE;
89
- // forge-lint: disable-next-line(mixed-case-variable)
90
- IJBAddressRegistry ADDRESS_REGISTRY;
91
- // forge-lint: disable-next-line(mixed-case-variable)
92
- REVLoans LOANS_CONTRACT;
93
- // forge-lint: disable-next-line(mixed-case-variable)
94
- IJBSuckerRegistry SUCKER_REGISTRY;
95
- // forge-lint: disable-next-line(mixed-case-variable)
96
- CTPublisher PUBLISHER;
97
- // forge-lint: disable-next-line(mixed-case-variable)
98
- MockBuybackDataHook MOCK_BUYBACK;
99
-
100
- /// @dev The fee project ID (project 1).
101
- // forge-lint: disable-next-line(mixed-case-variable)
102
- uint256 FEE_PROJECT_ID;
103
-
104
- /// @dev The revnet project ID used by all tests.
105
- // forge-lint: disable-next-line(mixed-case-variable)
106
- uint256 REVNET_ID;
107
-
108
- /// @dev Test user address with ETH for paying into the revnet.
109
- // forge-lint: disable-next-line(mixed-case-variable)
110
- address USER = makeAddr("user");
111
-
112
- /// @dev Second test user used to increase revnet surplus between loan creation and reallocation.
113
- // forge-lint: disable-next-line(mixed-case-variable)
114
- address USER2 = makeAddr("user2");
115
-
116
- // ---------------------------------------------------------------
117
- // Setup
118
- // ---------------------------------------------------------------
119
-
120
- function setUp() public override {
121
- // Initialize the base test workflow (deploys core contracts).
122
- super.setUp();
123
-
124
- // Create the fee project owned by multisig.
125
- FEE_PROJECT_ID = jbProjects().createFor(multisig());
126
-
127
- // Deploy the sucker registry (no deployers, no initial suckers).
128
- SUCKER_REGISTRY = new JBSuckerRegistry(jbDirectory(), jbPermissions(), multisig(), address(0));
129
-
130
- // Deploy the 721 hook store.
131
- HOOK_STORE = new JB721TiersHookStore();
132
-
133
- // Deploy the example 721 hook (needed as the implementation for the deployer).
134
- EXAMPLE_HOOK = new JB721TiersHook(
135
- jbDirectory(),
136
- jbPermissions(),
137
- jbPrices(),
138
- jbRulesets(),
139
- HOOK_STORE,
140
- jbSplits(),
141
- IJB721CheckpointsDeployer(address(new JB721CheckpointsDeployer())),
142
- multisig()
143
- );
144
-
145
- // Deploy the address registry (used by the hook deployer).
146
- ADDRESS_REGISTRY = new JBAddressRegistry();
147
-
148
- // Deploy the 721 hook deployer.
149
- HOOK_DEPLOYER = new JB721TiersHookDeployer(EXAMPLE_HOOK, HOOK_STORE, ADDRESS_REGISTRY, multisig());
150
-
151
- // Deploy the croptop publisher.
152
- PUBLISHER = new CTPublisher(jbDirectory(), jbPermissions(), FEE_PROJECT_ID, multisig());
153
-
154
- // Deploy the mock buyback data hook (satisfies the IJBBuybackHookRegistry interface).
155
- MOCK_BUYBACK = new MockBuybackDataHook();
156
-
157
- // Add a 1:1 native token price feed so bonding curve math works.
158
- MockPriceFeed priceFeed = new MockPriceFeed(1e18, 18);
159
- vm.prank(multisig());
160
- jbPrices()
161
- .addPriceFeedFor(
162
- 0, uint32(uint160(JBConstants.NATIVE_TOKEN)), uint32(uint160(JBConstants.NATIVE_TOKEN)), priceFeed
163
- );
164
-
165
- // Deploy the REVLoans contract.
166
- LOANS_CONTRACT = new REVLoans({
167
- controller: jbController(),
168
- suckerRegistry: IJBSuckerRegistry(address(new MockSuckerRegistry())),
169
- revId: FEE_PROJECT_ID,
170
- owner: address(this),
171
- permit2: permit2(),
172
- trustedForwarder: TRUSTED_FORWARDER
173
- });
174
-
175
- // Deploy the REVDeployer with a deterministic salt.
176
- REV_OWNER = new REVOwner(
177
- IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
178
- jbDirectory(),
179
- FEE_PROJECT_ID,
180
- SUCKER_REGISTRY,
181
- address(LOANS_CONTRACT),
182
- address(0)
183
- );
184
-
185
- REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
186
- jbController(),
187
- SUCKER_REGISTRY,
188
- FEE_PROJECT_ID,
189
- HOOK_DEPLOYER,
190
- PUBLISHER,
191
- IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
192
- address(LOANS_CONTRACT),
193
- TRUSTED_FORWARDER,
194
- address(REV_OWNER)
195
- );
196
-
197
- REV_OWNER.setDeployer(REV_DEPLOYER);
198
-
199
- // Approve the deployer to configure the fee project.
200
- vm.prank(multisig());
201
- jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
202
-
203
- // Deploy the fee project's revnet configuration.
204
- _deployFeeProject();
205
-
206
- // Deploy the test revnet that loans will be issued against.
207
- _deployRevnet();
208
-
209
- // Give the test users 100 ETH each.
210
- vm.deal(USER, 100e18);
211
- vm.deal(USER2, 100e18);
212
- }
213
-
214
- // ---------------------------------------------------------------
215
- // Internal helpers
216
- // ---------------------------------------------------------------
217
-
218
- /// @dev Deploys the fee project (project 1) with a single stage.
219
- function _deployFeeProject() internal {
220
- // Accept native token through the multi terminal.
221
- JBAccountingContext[] memory acc = new JBAccountingContext[](1);
222
- acc[0] = JBAccountingContext({
223
- token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
224
- });
225
-
226
- // Configure a single terminal.
227
- JBTerminalConfig[] memory tc = new JBTerminalConfig[](1);
228
- tc[0] = JBTerminalConfig({terminal: jbMultiTerminal(), accountingContextsToAccept: acc});
229
-
230
- // A single stage with auto-issuance for the multisig.
231
- REVStageConfig[] memory stages = new REVStageConfig[](1);
232
- JBSplit[] memory splits = new JBSplit[](1);
233
- splits[0].beneficiary = payable(multisig());
234
- splits[0].percent = 10_000;
235
-
236
- // Auto-issue 70k tokens to multisig on this chain.
237
- REVAutoIssuance[] memory ai = new REVAutoIssuance[](1);
238
- ai[0] = REVAutoIssuance({chainId: uint32(block.chainid), count: uint104(70_000e18), beneficiary: multisig()});
239
-
240
- // Build the stage configuration.
241
- stages[0] = REVStageConfig({
242
- startsAtOrAfter: uint40(block.timestamp),
243
- autoIssuances: ai,
244
- splitPercent: 2000,
245
- splits: splits,
246
- initialIssuance: uint112(1000e18),
247
- issuanceCutFrequency: 90 days,
248
- issuanceCutPercent: JBConstants.MAX_WEIGHT_CUT_PERCENT / 2,
249
- cashOutTaxRate: 6000,
250
- extraMetadata: 0
251
- });
252
-
253
- // Build the revnet configuration for the fee project.
254
- REVConfig memory cfg = REVConfig({
255
- // forge-lint: disable-next-line(named-struct-fields)
256
- description: REVDescription("Revnet", "$REV", "ipfs://test", "REV_TOKEN"),
257
- baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
258
- splitOperator: multisig(),
259
- stageConfigurations: stages
260
- });
261
-
262
- // Deploy the fee project revnet.
263
- vm.prank(multisig());
264
- REV_DEPLOYER.deployFor({
265
- revnetId: FEE_PROJECT_ID,
266
- configuration: cfg,
267
- terminalConfigurations: tc,
268
- suckerDeploymentConfiguration: REVSuckerDeploymentConfig({
269
- deployerConfigurations: new JBSuckerDeployerConfig[](0), salt: keccak256("FEE")
270
- }),
271
- tiered721HookConfiguration: REVEmpty721Config.empty721Config(uint32(uint160(JBConstants.NATIVE_TOKEN))),
272
- allowedPosts: REVEmpty721Config.emptyAllowedPosts()
273
- });
274
- }
275
-
276
- /// @dev Deploys the test revnet (project 2) with a single stage and 60% cash-out tax.
277
- function _deployRevnet() internal {
278
- // Accept native token through the multi terminal.
279
- JBAccountingContext[] memory acc = new JBAccountingContext[](1);
280
- acc[0] = JBAccountingContext({
281
- token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
282
- });
283
-
284
- // Configure a single terminal.
285
- JBTerminalConfig[] memory tc = new JBTerminalConfig[](1);
286
- tc[0] = JBTerminalConfig({terminal: jbMultiTerminal(), accountingContextsToAccept: acc});
287
-
288
- // A single stage with auto-issuance for the multisig.
289
- REVStageConfig[] memory stages = new REVStageConfig[](1);
290
- JBSplit[] memory splits = new JBSplit[](1);
291
- splits[0].beneficiary = payable(multisig());
292
- splits[0].percent = 10_000;
293
-
294
- // Auto-issue 70k tokens to multisig on this chain.
295
- REVAutoIssuance[] memory ai = new REVAutoIssuance[](1);
296
- ai[0] = REVAutoIssuance({chainId: uint32(block.chainid), count: uint104(70_000e18), beneficiary: multisig()});
297
-
298
- // Build the stage configuration.
299
- stages[0] = REVStageConfig({
300
- startsAtOrAfter: uint40(block.timestamp),
301
- autoIssuances: ai,
302
- splitPercent: 2000,
303
- splits: splits,
304
- initialIssuance: uint112(1000e18),
305
- issuanceCutFrequency: 90 days,
306
- issuanceCutPercent: JBConstants.MAX_WEIGHT_CUT_PERCENT / 2,
307
- cashOutTaxRate: 6000,
308
- extraMetadata: 0
309
- });
310
-
311
- // Build the revnet configuration for the test project.
312
- REVConfig memory cfg = REVConfig({
313
- // forge-lint: disable-next-line(named-struct-fields)
314
- description: REVDescription("NANA", "$NANA", "ipfs://test2", "NANA_TOKEN"),
315
- baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
316
- splitOperator: multisig(),
317
- stageConfigurations: stages
318
- });
319
-
320
- // Deploy the test revnet (revnetId 0 means "create new").
321
- (REVNET_ID,) = REV_DEPLOYER.deployFor({
322
- revnetId: 0,
323
- configuration: cfg,
324
- terminalConfigurations: tc,
325
- suckerDeploymentConfiguration: REVSuckerDeploymentConfig({
326
- deployerConfigurations: new JBSuckerDeployerConfig[](0), salt: keccak256("NANA")
327
- }),
328
- tiered721HookConfiguration: REVEmpty721Config.empty721Config(uint32(uint160(JBConstants.NATIVE_TOKEN))),
329
- allowedPosts: REVEmpty721Config.emptyAllowedPosts()
330
- });
331
- }
332
-
333
- /// @dev Creates a loan for the given user by paying ETH into the revnet then borrowing.
334
- /// @param user The address that will own the loan.
335
- /// @param ethAmount The amount of ETH to pay into the revnet as collateral.
336
- /// @return loanId The ID of the created loan.
337
- /// @return tokenCount The number of revnet tokens received from paying.
338
- function _setupLoan(address user, uint256 ethAmount) internal returns (uint256 loanId, uint256 tokenCount) {
339
- // Pay ETH into the revnet and receive tokens.
340
- vm.prank(user);
341
- tokenCount =
342
- jbMultiTerminal().pay{value: ethAmount}(REVNET_ID, JBConstants.NATIVE_TOKEN, ethAmount, user, 0, "", "");
343
-
344
- // Compute the borrowable amount from the tokens received.
345
- uint256 borrowAmount =
346
- LOANS_CONTRACT.borrowableAmountFrom(REVNET_ID, tokenCount, 18, uint32(uint160(JBConstants.NATIVE_TOKEN)));
347
-
348
- // Sanity check: the user should be able to borrow something.
349
- require(borrowAmount > 0, "Borrow amount should be > 0");
350
-
351
- // Mock the permissions check so LOANS_CONTRACT can burn the user's tokens.
352
- mockExpect(
353
- address(jbPermissions()),
354
- abi.encodeCall(IJBPermissions.hasPermission, (address(LOANS_CONTRACT), user, REVNET_ID, 11, true, true)),
355
- abi.encode(true)
356
- );
357
-
358
- // Build the loan source pointing at the real multi terminal and native token.
359
- REVLoanSource memory source = REVLoanSource({token: JBConstants.NATIVE_TOKEN, terminal: jbMultiTerminal()});
360
-
361
- // Borrow with minimum fee percent (25 = 2.5%).
362
- vm.prank(user);
363
- (loanId,) = LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokenCount, payable(user), 25, user);
364
- }
365
-
366
- /// @dev Computes the storage slot for totalLoansBorrowedFor[revnetId].
367
- /// @param revnetId The revnet ID to compute the mapping slot for.
368
- /// @return The keccak256 slot for the mapping entry.
369
- function _totalLoansBorrowedSlot(uint256 revnetId) internal pure returns (bytes32) {
370
- // Solidity mapping slot: keccak256(abi.encode(key, baseSlot)).
371
- return keccak256(abi.encode(revnetId, TOTAL_LOANS_BORROWED_FOR_SLOT));
372
- }
373
-
374
- // ---------------------------------------------------------------
375
- // Test 1: borrowFrom overflow guard
376
- // ---------------------------------------------------------------
377
-
378
- /// @notice Verifies that borrowFrom reverts with REVLoans_LoanIdOverflow when
379
- /// the totalLoansBorrowedFor counter has reached _ONE_TRILLION.
380
- function test_borrowFrom_revertsAtOverflowBoundary() public {
381
- // Pay ETH into the revnet so the user has tokens for collateral.
382
- vm.prank(USER);
383
- uint256 tokens = jbMultiTerminal().pay{value: 5e18}(REVNET_ID, JBConstants.NATIVE_TOKEN, 5e18, USER, 0, "", "");
384
-
385
- // Verify the user received tokens.
386
- assertGt(tokens, 0, "user should receive tokens from paying");
387
-
388
- // Compute the borrowable amount from the user's tokens.
389
- uint256 borrowAmount =
390
- LOANS_CONTRACT.borrowableAmountFrom(REVNET_ID, tokens, 18, uint32(uint160(JBConstants.NATIVE_TOKEN)));
391
-
392
- // Sanity check: there should be a borrowable amount.
393
- assertGt(borrowAmount, 0, "borrowable amount should be > 0");
394
-
395
- // No permission mock needed: the overflow guard fires before any permission/burn check.
396
-
397
- // Use vm.store to set totalLoansBorrowedFor[REVNET_ID] to _ONE_TRILLION.
398
- vm.store(address(LOANS_CONTRACT), _totalLoansBorrowedSlot(REVNET_ID), bytes32(_ONE_TRILLION));
399
-
400
- // Confirm the counter is now at the overflow boundary.
401
- assertEq(
402
- LOANS_CONTRACT.totalLoansBorrowedFor(REVNET_ID),
403
- _ONE_TRILLION,
404
- "counter should be at _ONE_TRILLION after vm.store"
405
- );
406
-
407
- // Build the loan source pointing at the real terminal and native token.
408
- REVLoanSource memory source = REVLoanSource({token: JBConstants.NATIVE_TOKEN, terminal: jbMultiTerminal()});
409
-
410
- // Expect the overflow revert.
411
- vm.expectRevert(REVLoans.REVLoans_LoanIdOverflow.selector);
412
-
413
- // Attempt to borrow -- should revert because the counter is at the limit.
414
- vm.prank(USER);
415
- LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokens, payable(USER), 25, USER);
416
- }
417
-
418
- // ---------------------------------------------------------------
419
- // Test 2: reallocateCollateralFromLoan overflow guard
420
- // ---------------------------------------------------------------
421
-
422
- /// @notice Verifies that reallocateCollateralFromLoan reverts with REVLoans_LoanIdOverflow
423
- /// when the totalLoansBorrowedFor counter has reached _ONE_TRILLION.
424
- /// @dev A second user injects surplus after the loan is created so that removing a small
425
- /// amount of collateral still leaves borrowable value >= the original loan amount (otherwise
426
- /// the ReallocatingMoreCollateralThanBorrowedAmountAllows check fires first).
427
- function test_reallocateCollateral_revertsAtOverflowBoundary() public {
428
- // Create a loan with enough collateral that we can split off some for reallocation.
429
- (uint256 loanId,) = _setupLoan(USER, 10e18);
430
-
431
- // Verify the loan was created successfully.
432
- REVLoan memory loan = LOANS_CONTRACT.loanOf(loanId);
433
- assertGt(loan.collateral, 0, "loan should have collateral");
434
- assertGt(loan.amount, 0, "loan should have a borrow amount");
435
-
436
- // Add surplus to the revnet WITHOUT minting tokens (addToBalanceOf, not pay).
437
- // This increases the per-token borrowable value so that after removing a small
438
- // amount of collateral, the borrowable amount still exceeds the original loan
439
- // amount (avoiding the ReallocatingMoreCollateralThanBorrowedAmountAllows check
440
- // at line 1181 and reaching the overflow guard at line 1186).
441
- vm.prank(USER2);
442
- jbMultiTerminal().addToBalanceOf{value: 50e18}(REVNET_ID, JBConstants.NATIVE_TOKEN, 50e18, false, "", "");
443
-
444
- // No permission mock needed: the overflow guard in _reallocateCollateralFromLoan fires
445
- // before any permission/burn check is reached.
446
-
447
- // Use vm.store to set totalLoansBorrowedFor[REVNET_ID] to _ONE_TRILLION.
448
- vm.store(address(LOANS_CONTRACT), _totalLoansBorrowedSlot(REVNET_ID), bytes32(_ONE_TRILLION));
449
-
450
- // Confirm the counter is at the overflow boundary.
451
- assertEq(
452
- LOANS_CONTRACT.totalLoansBorrowedFor(REVNET_ID),
453
- _ONE_TRILLION,
454
- "counter should be at _ONE_TRILLION after vm.store"
455
- );
456
-
457
- // Build the loan source matching the existing loan's source.
458
- REVLoanSource memory source = REVLoanSource({token: JBConstants.NATIVE_TOKEN, terminal: jbMultiTerminal()});
459
-
460
- // Transfer only 1 token of collateral to trigger the reallocation path.
461
- uint256 collateralToTransfer = 1;
462
-
463
- // Expect the overflow revert from _reallocateCollateralFromLoan.
464
- vm.expectRevert(REVLoans.REVLoans_LoanIdOverflow.selector);
465
-
466
- // Attempt to reallocate -- should revert because the counter is at the limit.
467
- vm.prank(USER);
468
- LOANS_CONTRACT.reallocateCollateralFromLoan(
469
- loanId,
470
- collateralToTransfer,
471
- source,
472
- 0, // minBorrowAmount for the new loan
473
- 0, // no additional collateral to add
474
- payable(USER),
475
- 25 // prepaidFeePercent (2.5%)
476
- );
477
- }
478
-
479
- // ---------------------------------------------------------------
480
- // Test 3: repayLoan (partial) overflow guard
481
- // ---------------------------------------------------------------
482
-
483
- /// @notice Verifies that a partial repayLoan reverts with REVLoans_LoanIdOverflow
484
- /// when the totalLoansBorrowedFor counter has reached _ONE_TRILLION.
485
- /// @dev A partial repayment creates a replacement loan with a new ID, which requires
486
- /// incrementing the counter. If the counter is at the limit, this must revert.
487
- function test_partialRepay_revertsAtOverflowBoundary() public {
488
- // Create a loan for partial repayment testing.
489
- (uint256 loanId,) = _setupLoan(USER, 5e18);
490
-
491
- // Verify the loan exists and has a borrow amount.
492
- REVLoan memory loan = LOANS_CONTRACT.loanOf(loanId);
493
- assertGt(loan.amount, 0, "loan should have a borrow amount");
494
- assertGt(loan.collateral, 0, "loan should have collateral");
495
-
496
- // Calculate a partial repayment (half the borrow amount, return no collateral).
497
- uint256 halfAmount = loan.amount / 2;
498
-
499
- // Sanity check: half amount must be non-zero for a meaningful partial repay.
500
- assertGt(halfAmount, 0, "half amount should be > 0");
501
-
502
- // Use vm.store to set totalLoansBorrowedFor[REVNET_ID] to _ONE_TRILLION.
503
- vm.store(address(LOANS_CONTRACT), _totalLoansBorrowedSlot(REVNET_ID), bytes32(_ONE_TRILLION));
504
-
505
- // Confirm the counter is at the overflow boundary.
506
- assertEq(
507
- LOANS_CONTRACT.totalLoansBorrowedFor(REVNET_ID),
508
- _ONE_TRILLION,
509
- "counter should be at _ONE_TRILLION after vm.store"
510
- );
511
-
512
- // Build an empty allowance (no permit2 needed for native token repayment).
513
- JBSingleAllowance memory allowance;
514
-
515
- // Expect the overflow revert from the partial-repay branch.
516
- vm.expectRevert(REVLoans.REVLoans_LoanIdOverflow.selector);
517
-
518
- // Attempt a partial repayment -- should revert because creating the replacement loan
519
- // would exceed the _ONE_TRILLION loan ID namespace.
520
- vm.prank(USER);
521
- LOANS_CONTRACT.repayLoan{value: halfAmount}(loanId, halfAmount, 0, payable(USER), allowance);
522
- }
523
- }