@rev-net/core-v6 0.0.6 → 0.0.7

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 (27) hide show
  1. package/SKILLS.md +1 -1
  2. package/docs/book.toml +1 -1
  3. package/docs/src/README.md +151 -54
  4. package/docs/src/SUMMARY.md +0 -2
  5. package/docs/src/src/REVDeployer.sol/contract.REVDeployer.md +148 -117
  6. package/docs/src/src/REVLoans.sol/contract.REVLoans.md +120 -59
  7. package/docs/src/src/interfaces/IREVDeployer.sol/interface.IREVDeployer.md +296 -14
  8. package/docs/src/src/interfaces/IREVLoans.sol/interface.IREVLoans.md +318 -16
  9. package/docs/src/src/structs/README.md +0 -2
  10. package/docs/src/src/structs/REVAutoIssuance.sol/struct.REVAutoIssuance.md +4 -4
  11. package/docs/src/src/structs/REVConfig.sol/struct.REVConfig.md +5 -17
  12. package/docs/src/src/structs/REVCroptopAllowedPost.sol/struct.REVCroptopAllowedPost.md +10 -6
  13. package/docs/src/src/structs/REVDeploy721TiersHookConfig.sol/struct.REVDeploy721TiersHookConfig.md +7 -7
  14. package/docs/src/src/structs/REVDescription.sol/struct.REVDescription.md +5 -5
  15. package/docs/src/src/structs/REVLoan.sol/struct.REVLoan.md +7 -7
  16. package/docs/src/src/structs/REVLoanSource.sol/struct.REVLoanSource.md +3 -3
  17. package/docs/src/src/structs/REVStageConfig.sol/struct.REVStageConfig.md +10 -10
  18. package/docs/src/src/structs/REVSuckerDeploymentConfig.sol/struct.REVSuckerDeploymentConfig.md +3 -3
  19. package/package.json +6 -6
  20. package/slither-ci.config.json +1 -1
  21. package/src/REVLoans.sol +20 -9
  22. package/src/interfaces/IREVDeployer.sol +0 -2
  23. package/src/interfaces/IREVLoans.sol +10 -4
  24. package/test/TestPR27_CEIPattern.t.sol +2 -2
  25. package/test/TestPR32_MixedFixes.t.sol +1 -1
  26. package/test/regression/TestI20_CumulativeLoanCounter.t.sol +303 -0
  27. package/test/regression/TestL27_LiquidateGapHandling.t.sol +334 -0
@@ -1,77 +1,77 @@
1
1
  # REVLoans
2
- [Git Source](https://github.com/rev-net/revnet-core-v5/blob/364afaae78a8f60af2b98252dc96af1c2e4760d3/src/REVLoans.sol)
2
+ [Git Source](https://github.com/rev-net/revnet-core-v6/blob/94c003a3a16de2bd012d63cccedd6bd38d21f6e7/src/REVLoans.sol)
3
3
 
4
4
  **Inherits:**
5
5
  ERC721, ERC2771Context, Ownable, [IREVLoans](/src/interfaces/IREVLoans.sol/interface.IREVLoans.md)
6
6
 
7
7
  A contract for borrowing from revnets.
8
8
 
9
- *Tokens used as collateral are burned, and reminted when the loan is paid off. This keeps the revnet's token
10
- structure orderly.*
9
+ Tokens used as collateral are burned, and reminted when the loan is paid off. This keeps the revnet's token
10
+ structure orderly.
11
11
 
12
- *The borrowable amount is the same as the cash out amount.*
12
+ The borrowable amount is the same as the cash out amount.
13
13
 
14
- *An upfront fee is taken when a loan is created. 2.5% is charged by the underlying protocol, 2.5% is charged
14
+ An upfront fee is taken when a loan is created. 2.5% is charged by the underlying protocol, 2.5% is charged
15
15
  by the
16
16
  revnet issuing the loan, and a variable amount charged by the revnet that receives the fees. This variable amount is
17
17
  chosen by the borrower, the more paid upfront, the longer the prepaid duration. The loan can be repaid anytime
18
18
  within the prepaid duration without additional fees.
19
19
  After the prepaid duration, the loan will increasingly cost more to pay off. After 10 years, the loan collateral
20
20
  cannot be
21
- recouped.*
21
+ recouped.
22
22
 
23
- *The loaned amounts include the fees taken, meaning the amount paid back is the amount borrowed plus the fees.*
23
+ The loaned amounts include the fees taken, meaning the amount paid back is the amount borrowed plus the fees.
24
24
 
25
25
 
26
26
  ## State Variables
27
27
  ### LOAN_LIQUIDATION_DURATION
28
- *After the prepaid duration, the loan will cost more to pay off. After 10 years, the loan
28
+ After the prepaid duration, the loan will cost more to pay off. After 10 years, the loan
29
29
  collateral cannot be recouped. This means paying 50% of the loan amount upfront will pay for having access to
30
30
  the remaining 50% for 10 years,
31
31
  whereas paying 0% of the loan upfront will cost 100% of the loan amount to be paid off after 10 years. After 10
32
- years with repayment, both loans cost 100% and are liquidated.*
32
+ years with repayment, both loans cost 100% and are liquidated.
33
33
 
34
34
 
35
35
  ```solidity
36
- uint256 public constant override LOAN_LIQUIDATION_DURATION = 3650 days;
36
+ uint256 public constant override LOAN_LIQUIDATION_DURATION = 3650 days
37
37
  ```
38
38
 
39
39
 
40
40
  ### MAX_PREPAID_FEE_PERCENT
41
- *The maximum amount of a loan that can be prepaid at the time of borrowing, in terms of JBConstants.MAX_FEE.*
41
+ The maximum amount of a loan that can be prepaid at the time of borrowing, in terms of JBConstants.MAX_FEE.
42
42
 
43
43
 
44
44
  ```solidity
45
- uint256 public constant override MAX_PREPAID_FEE_PERCENT = 500;
45
+ uint256 public constant override MAX_PREPAID_FEE_PERCENT = 500
46
46
  ```
47
47
 
48
48
 
49
49
  ### REV_PREPAID_FEE_PERCENT
50
- *A fee of 1% is charged by the $REV revnet.*
50
+ A fee of 1% is charged by the $REV revnet.
51
51
 
52
52
 
53
53
  ```solidity
54
- uint256 public constant override REV_PREPAID_FEE_PERCENT = 10;
54
+ uint256 public constant override REV_PREPAID_FEE_PERCENT = 10
55
55
  ```
56
56
 
57
57
 
58
58
  ### MIN_PREPAID_FEE_PERCENT
59
- *A fee of 2.5% is charged by the loan's source upfront.*
59
+ A fee of 2.5% is charged by the loan's source upfront.
60
60
 
61
61
 
62
62
  ```solidity
63
- uint256 public constant override MIN_PREPAID_FEE_PERCENT = 25;
63
+ uint256 public constant override MIN_PREPAID_FEE_PERCENT = 25
64
64
  ```
65
65
 
66
66
 
67
67
  ### _ONE_TRILLION
68
68
  Just a kind reminder to our readers.
69
69
 
70
- *Used in loan token ID generation.*
70
+ Used in loan token ID generation.
71
71
 
72
72
 
73
73
  ```solidity
74
- uint256 private constant _ONE_TRILLION = 1_000_000_000_000;
74
+ uint256 private constant _ONE_TRILLION = 1_000_000_000_000
75
75
  ```
76
76
 
77
77
 
@@ -80,7 +80,7 @@ The permit2 utility.
80
80
 
81
81
 
82
82
  ```solidity
83
- IPermit2 public immutable override PERMIT2;
83
+ IPermit2 public immutable override PERMIT2
84
84
  ```
85
85
 
86
86
 
@@ -89,16 +89,7 @@ The controller of revnets that use this loans contract.
89
89
 
90
90
 
91
91
  ```solidity
92
- IJBController public immutable override CONTROLLER;
93
- ```
94
-
95
-
96
- ### REVNETS
97
- Mints ERC-721s that represent project ownership and transfers.
98
-
99
-
100
- ```solidity
101
- IREVDeployer public immutable override REVNETS;
92
+ IJBController public immutable override CONTROLLER
102
93
  ```
103
94
 
104
95
 
@@ -107,7 +98,7 @@ The directory of terminals and controllers for revnets.
107
98
 
108
99
 
109
100
  ```solidity
110
- IJBDirectory public immutable override DIRECTORY;
101
+ IJBDirectory public immutable override DIRECTORY
111
102
  ```
112
103
 
113
104
 
@@ -116,7 +107,7 @@ A contract that stores prices for each revnet.
116
107
 
117
108
 
118
109
  ```solidity
119
- IJBPrices public immutable override PRICES;
110
+ IJBPrices public immutable override PRICES
120
111
  ```
121
112
 
122
113
 
@@ -125,7 +116,7 @@ Mints ERC-721s that represent revnet ownership and transfers.
125
116
 
126
117
 
127
118
  ```solidity
128
- IJBProjects public immutable override PROJECTS;
119
+ IJBProjects public immutable override PROJECTS
129
120
  ```
130
121
 
131
122
 
@@ -134,7 +125,7 @@ The ID of the REV revnet that will receive the fees.
134
125
 
135
126
 
136
127
  ```solidity
137
- uint256 public immutable override REV_ID;
128
+ uint256 public immutable override REV_ID
138
129
  ```
139
130
 
140
131
 
@@ -144,17 +135,22 @@ token.
144
135
 
145
136
 
146
137
  ```solidity
147
- mapping(uint256 revnetId => mapping(IJBPayoutTerminal terminal => mapping(address token => bool))) public override
148
- isLoanSourceOf;
138
+ mapping(uint256 revnetId => mapping(IJBPayoutTerminal terminal => mapping(address token => bool)))
139
+ public
140
+ override isLoanSourceOf
149
141
  ```
150
142
 
151
143
 
152
- ### numberOfLoansFor
153
- The amount of loans that have been created.
144
+ ### totalLoansBorrowedFor
145
+ The cumulative number of loans ever created for a revnet, used as a loan ID sequence counter.
146
+
147
+ This counter only increments (on borrow, repay-with-new-loan, and reallocation) and never decrements.
148
+ It does NOT represent the number of currently active loans. Repaid and liquidated loans leave permanent gaps
149
+ in the ID sequence. Integrators should not use this to count active loans.
154
150
 
155
151
 
156
152
  ```solidity
157
- mapping(uint256 revnetId => uint256) public override numberOfLoansFor;
153
+ mapping(uint256 revnetId => uint256) public override totalLoansBorrowedFor
158
154
  ```
159
155
 
160
156
 
@@ -163,7 +159,7 @@ The contract resolving each project ID to its ERC721 URI.
163
159
 
164
160
 
165
161
  ```solidity
166
- IJBTokenUriResolver public override tokenUriResolver;
162
+ IJBTokenUriResolver public override tokenUriResolver
167
163
  ```
168
164
 
169
165
 
@@ -172,8 +168,9 @@ The total amount loaned out by a revnet from a specified terminal in a specified
172
168
 
173
169
 
174
170
  ```solidity
175
- mapping(uint256 revnetId => mapping(IJBPayoutTerminal terminal => mapping(address token => uint256))) public override
176
- totalBorrowedFrom;
171
+ mapping(uint256 revnetId => mapping(IJBPayoutTerminal terminal => mapping(address token => uint256)))
172
+ public
173
+ override totalBorrowedFrom
177
174
  ```
178
175
 
179
176
 
@@ -182,19 +179,24 @@ The total amount of collateral supporting a revnet's loans.
182
179
 
183
180
 
184
181
  ```solidity
185
- mapping(uint256 revnetId => uint256) public override totalCollateralOf;
182
+ mapping(uint256 revnetId => uint256) public override totalCollateralOf
186
183
  ```
187
184
 
188
185
 
189
186
  ### _loanSourcesOf
190
187
  The sources of each revnet's loan.
191
188
 
189
+ This array grows monotonically -- entries are appended when a new (terminal, token) pair is first used for
190
+ borrowing, but are never removed. The `isLoanSourceOf` mapping tracks whether a source has been registered.
191
+ Since the number of distinct (terminal, token) pairs per revnet is practically bounded (typically < 10),
192
+ the gas cost of iterating this array in `loanSourcesOf` remains manageable.
193
+
192
194
  **Note:**
193
195
  member: revnetId The ID of the revnet issuing the loan.
194
196
 
195
197
 
196
198
  ```solidity
197
- mapping(uint256 revnetId => REVLoanSource[]) internal _loanSourcesOf;
199
+ mapping(uint256 revnetId => REVLoanSource[]) internal _loanSourcesOf
198
200
  ```
199
201
 
200
202
 
@@ -206,7 +208,7 @@ member: The ID of the loan.
206
208
 
207
209
 
208
210
  ```solidity
209
- mapping(uint256 loanId => REVLoan) internal _loanOf;
211
+ mapping(uint256 loanId => REVLoan) internal _loanOf
210
212
  ```
211
213
 
212
214
 
@@ -216,7 +218,8 @@ mapping(uint256 loanId => REVLoan) internal _loanOf;
216
218
 
217
219
  ```solidity
218
220
  constructor(
219
- IREVDeployer revnets,
221
+ IJBController controller,
222
+ IJBProjects projects,
220
223
  uint256 revId,
221
224
  address owner,
222
225
  IPermit2 permit2,
@@ -230,7 +233,8 @@ constructor(
230
233
 
231
234
  |Name|Type|Description|
232
235
  |----|----|-----------|
233
- |`revnets`|`IREVDeployer`|A contract from which revnets using this loans contract are deployed.|
236
+ |`controller`|`IJBController`|The controller that manages revnets using this loans contract.|
237
+ |`projects`|`IJBProjects`|The contract that mints ERC-721s representing project ownership.|
234
238
  |`revId`|`uint256`|The ID of the REV revnet that will receive the fees.|
235
239
  |`owner`|`address`|The owner of the contract that can set the URI resolver.|
236
240
  |`permit2`|`IPermit2`|A permit2 utility.|
@@ -285,6 +289,9 @@ function loanOf(uint256 loanId) external view override returns (REVLoan memory);
285
289
 
286
290
  The sources of each revnet's loan.
287
291
 
292
+ This array only grows -- sources are never removed. The number of distinct sources is practically bounded
293
+ by the number of unique (terminal, token) pairs used for borrowing, which is typically small.
294
+
288
295
  **Note:**
289
296
  member: revnetId The ID of the revnet issuing the loan.
290
297
 
@@ -348,7 +355,7 @@ function revnetIdOfLoanWith(uint256 loanId) public pure override returns (uint25
348
355
 
349
356
  |Name|Type|Description|
350
357
  |----|----|-----------|
351
- |`loanId`|`uint256`|The loan ID of the loan to get the revent ID of.|
358
+ |`loanId`|`uint256`|The loan ID of the loan to get the revnet ID of.|
352
359
 
353
360
  **Returns**
354
361
 
@@ -380,7 +387,29 @@ function _balanceOf(address token) internal view returns (uint256);
380
387
 
381
388
  ### _borrowableAmountFrom
382
389
 
383
- *The amount that can be borrowed from a revnet given a certain amount of collateral.*
390
+ This function reads live surplus from the revnet's terminals. A potential concern is flash loan
391
+ manipulation: an attacker could temporarily inflate surplus via `addToBalanceOf` or `pay`, borrow at the
392
+ inflated rate, then repay the flash loan. However, this attack is economically irrational:
393
+ - `addToBalanceOf` permanently donates funds to the project (no recovery mechanism). The attacker's extra
394
+ borrowable amount equals `donation * (collateralCount / totalSupply)`, which is always less than the
395
+ donation since `collateralCount < totalSupply`. The attacker loses more than they gain.
396
+ - `pay` increases both surplus AND totalSupply (via newly minted tokens), so the net effect on the
397
+ borrowable-amount-per-token ratio is neutral — the increased surplus is offset by supply dilution.
398
+ - With non-zero `cashOutTaxRate`, the bonding curve is concave, making the attack even less profitable.
399
+ - Refinancing during inflated surplus (`reallocateCollateralFromLoan`) does not help either: the freed
400
+ collateral can only borrow a fraction of the donated amount, keeping the attack net-negative.
401
+ In summary, any attempt to inflate surplus to increase borrowing power costs the attacker more than it yields,
402
+ because the bonding curve ensures no individual can extract more than their proportional share of surplus.
403
+
404
+ The amount that can be borrowed from a revnet given a certain amount of collateral.
405
+
406
+ The system intentionally allows up to 100% LTV (loan-to-value) by design. The borrowable amount equals
407
+ what the collateral tokens would receive if cashed out, computed via the bonding curve formula in
408
+ `JBCashOuts.cashOutFrom`. The `cashOutTaxRate` configured for the current stage serves as an implicit margin
409
+ buffer: a non-zero tax rate reduces the cash-out value below the pro-rata share of surplus, creating an
410
+ effective collateralization margin. For example, a 20% `cashOutTaxRate` means borrowers can only extract ~80%
411
+ of their pro-rata surplus, providing a ~20% buffer against collateral depreciation before liquidation.
412
+ A `cashOutTaxRate` of 0 means the full pro-rata amount is borrowable (true 100% LTV with no margin).
384
413
 
385
414
 
386
415
  ```solidity
@@ -444,7 +473,7 @@ function _borrowAmountFrom(
444
473
 
445
474
  ### _contextSuffixLength
446
475
 
447
- *`ERC-2771` specifies the context as being a single address (20 bytes).*
476
+ `ERC-2771` specifies the context as being a single address (20 bytes).
448
477
 
449
478
 
450
479
  ```solidity
@@ -527,7 +556,15 @@ function _msgSender() internal view override(ERC2771Context, Context) returns (a
527
556
 
528
557
  ### _totalBorrowedFrom
529
558
 
530
- The total borrowed amount from a revnet.
559
+ The total borrowed amount from a revnet, aggregated across all loan sources.
560
+
561
+ Each source's `totalBorrowedFrom` is stored in the source token's native decimals (e.g. 6 for USDC,
562
+ 18 for ETH). Before aggregation, each amount is normalized to the target `decimals` to prevent mixed-decimal
563
+ arithmetic errors. For cross-currency sources, the normalized amount is then converted via the price feed.
564
+
565
+ Callers should ensure the price feed has sufficient precision for the target `decimals`. Inverse price
566
+ feeds may truncate to zero at low decimal counts (e.g. a feed returning 1e21 at 6 decimals inverts to
567
+ mulDiv(1e6, 1e6, 1e21) = 0), which would cause a division-by-zero in the price conversion.
531
568
 
532
569
 
533
570
  ```solidity
@@ -559,6 +596,10 @@ function _totalBorrowedFrom(
559
596
 
560
597
  Open a loan by borrowing from a revnet.
561
598
 
599
+ Collateral tokens are permanently burned when the loan is created. They are re-minted to the borrower
600
+ only upon repayment. If the loan expires (after LOAN_LIQUIDATION_DURATION), the collateral is permanently
601
+ lost and cannot be recovered.
602
+
562
603
 
563
604
  ```solidity
564
605
  function borrowFrom(
@@ -594,10 +635,19 @@ function borrowFrom(
594
635
 
595
636
  ### liquidateExpiredLoansFrom
596
637
 
597
- Cleans up any liquiditated loans.
638
+ Liquidates loans that have exceeded the 10-year liquidation duration.
598
639
 
599
- *Since some loans may be reallocated or paid off, loans within startingLoanId and startingLoanId + count may
600
- be skipped, so choose these parameters carefully to avoid extra gas usage.*
640
+ Liquidation permanently destroys the collateral backing expired loans. Since collateral tokens were burned
641
+ at deposit time (not held in escrow), there is nothing to return upon liquidation -- the collateral count is
642
+ simply removed from tracking. The borrower retains whatever funds they received from the loan, but the
643
+ collateral tokens that were burned to secure the loan are permanently lost.
644
+
645
+ This is an intentional design choice to keep the protocol simple and to incentivize timely repayment or
646
+ refinancing. Borrowers have the full LOAN_LIQUIDATION_DURATION (10 years) to repay their loan and recover
647
+ their collateral via re-minting.
648
+
649
+ Since some loans may be reallocated or paid off, loans within startingLoanId and startingLoanId + count
650
+ may be skipped, so choose these parameters carefully to avoid extra gas usage.
601
651
 
602
652
 
603
653
  ```solidity
@@ -616,9 +666,12 @@ function liquidateExpiredLoansFrom(uint256 revnetId, uint256 startingLoanId, uin
616
666
 
617
667
  Refinances a loan by transferring extra collateral from an existing loan to a new loan.
618
668
 
619
- *Useful if a loan's collateral has gone up in value since the loan was created.*
669
+ Useful if a loan's collateral has gone up in value since the loan was created.
670
+
671
+ Refinancing a loan will burn the original and create two new loans.
620
672
 
621
- *Refinancing a loan will burn the original and create two new loans.*
673
+ This function is intentionally not payable it only moves existing collateral between loans and does
674
+ not accept new funds. Any ETH sent with the call will be rejected by the EVM.
622
675
 
623
676
 
624
677
  ```solidity
@@ -632,7 +685,6 @@ function reallocateCollateralFromLoan(
632
685
  uint256 prepaidFeePercent
633
686
  )
634
687
  external
635
- payable
636
688
  override
637
689
  returns (uint256 reallocatedLoanId, uint256 newLoanId, REVLoan memory reallocatedLoan, REVLoan memory newLoan);
638
690
  ```
@@ -712,7 +764,10 @@ function setTokenUriResolver(IJBTokenUriResolver resolver) external override onl
712
764
 
713
765
  ### _addCollateralTo
714
766
 
715
- Adds collateral to a loan.
767
+ Adds collateral to a loan by burning the collateral tokens permanently.
768
+
769
+ The collateral tokens are burned via the controller, not held in escrow. They are only re-minted if the
770
+ loan is repaid. If the loan expires and is liquidated, the burned collateral is permanently lost.
716
771
 
717
772
 
718
773
  ```solidity
@@ -1009,6 +1064,12 @@ error REVLoans_NewBorrowAmountGreaterThanLoanAmount(uint256 newBorrowAmount, uin
1009
1064
  error REVLoans_NoMsgValueAllowed();
1010
1065
  ```
1011
1066
 
1067
+ ### REVLoans_NothingToRepay
1068
+
1069
+ ```solidity
1070
+ error REVLoans_NothingToRepay();
1071
+ ```
1072
+
1012
1073
  ### REVLoans_LoanExpired
1013
1074
 
1014
1075
  ```solidity
@@ -1021,10 +1082,10 @@ error REVLoans_LoanExpired(uint256 timeSinceLoanCreated, uint256 loanLiquidation
1021
1082
  error REVLoans_ReallocatingMoreCollateralThanBorrowedAmountAllows(uint256 newBorrowAmount, uint256 loanAmount);
1022
1083
  ```
1023
1084
 
1024
- ### REVLoans_RevnetsMismatch
1085
+ ### REVLoans_SourceMismatch
1025
1086
 
1026
1087
  ```solidity
1027
- error REVLoans_RevnetsMismatch(address revnetOwner, address revnets);
1088
+ error REVLoans_SourceMismatch();
1028
1089
  ```
1029
1090
 
1030
1091
  ### REVLoans_Unauthorized