@rev-net/core-v6 0.0.12 → 0.0.13

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 (78) hide show
  1. package/AUDIT_INSTRUCTIONS.md +295 -0
  2. package/CHANGE_LOG.md +316 -0
  3. package/README.md +2 -2
  4. package/RISKS.md +180 -35
  5. package/SKILLS.md +1 -1
  6. package/USER_JOURNEYS.md +489 -0
  7. package/package.json +9 -9
  8. package/script/Deploy.s.sol +40 -6
  9. package/script/helpers/RevnetCoreDeploymentLib.sol +7 -1
  10. package/src/REVDeployer.sol +63 -47
  11. package/src/REVLoans.sol +51 -15
  12. package/src/interfaces/IREVDeployer.sol +0 -1
  13. package/src/structs/REV721TiersHookFlags.sol +1 -0
  14. package/src/structs/REVAutoIssuance.sol +1 -0
  15. package/src/structs/REVBaseline721HookConfig.sol +1 -0
  16. package/src/structs/REVConfig.sol +1 -0
  17. package/src/structs/REVCroptopAllowedPost.sol +1 -0
  18. package/src/structs/REVDeploy721TiersHookConfig.sol +1 -0
  19. package/src/structs/REVDescription.sol +1 -0
  20. package/src/structs/REVLoan.sol +1 -0
  21. package/src/structs/REVLoanSource.sol +1 -0
  22. package/src/structs/REVStageConfig.sol +1 -0
  23. package/src/structs/REVSuckerDeploymentConfig.sol +1 -0
  24. package/test/REV.integrations.t.sol +132 -12
  25. package/test/REVAutoIssuanceFuzz.t.sol +23 -3
  26. package/test/REVDeployerRegressions.t.sol +35 -4
  27. package/test/REVInvincibility.t.sol +58 -8
  28. package/test/REVInvincibilityHandler.sol +29 -0
  29. package/test/REVLifecycle.t.sol +28 -3
  30. package/test/REVLoans.invariants.t.sol +52 -5
  31. package/test/REVLoansAttacks.t.sol +43 -5
  32. package/test/REVLoansFeeRecovery.t.sol +50 -11
  33. package/test/REVLoansFindings.t.sol +27 -3
  34. package/test/REVLoansRegressions.t.sol +25 -3
  35. package/test/REVLoansSourceFeeRecovery.t.sol +491 -0
  36. package/test/REVLoansSourced.t.sol +56 -7
  37. package/test/REVLoansUnSourced.t.sol +49 -5
  38. package/test/TestBurnHeldTokens.t.sol +32 -5
  39. package/test/TestCEIPattern.t.sol +26 -2
  40. package/test/TestCashOutCallerValidation.t.sol +30 -4
  41. package/test/TestConversionDocumentation.t.sol +26 -5
  42. package/test/TestCrossCurrencyReclaim.t.sol +584 -0
  43. package/test/TestCrossSourceReallocation.t.sol +26 -2
  44. package/test/TestERC2771MetaTx.t.sol +557 -0
  45. package/test/TestEmptyBuybackSpecs.t.sol +23 -3
  46. package/test/TestFlashLoanSurplus.t.sol +28 -3
  47. package/test/TestHookArrayOOB.t.sol +24 -4
  48. package/test/TestLiquidationBehavior.t.sol +26 -3
  49. package/test/TestLoanSourceRotation.t.sol +525 -0
  50. package/test/TestLongTailEconomics.t.sol +651 -0
  51. package/test/TestLowFindings.t.sol +65 -2
  52. package/test/TestMixedFixes.t.sol +28 -3
  53. package/test/TestPermit2Signatures.t.sol +657 -0
  54. package/test/TestReallocationSandwich.t.sol +384 -0
  55. package/test/TestRevnetRegressions.t.sol +324 -0
  56. package/test/TestSplitWeightAdjustment.t.sol +24 -2
  57. package/test/TestSplitWeightE2E.t.sol +29 -2
  58. package/test/TestSplitWeightFork.t.sol +46 -7
  59. package/test/TestStageTransitionBorrowable.t.sol +24 -2
  60. package/test/TestSwapTerminalPermission.t.sol +23 -3
  61. package/test/TestUint112Overflow.t.sol +28 -2
  62. package/test/TestZeroRepayment.t.sol +26 -2
  63. package/test/fork/ForkTestBase.sol +46 -3
  64. package/test/fork/TestCashOutFork.t.sol +1 -1
  65. package/test/fork/TestLoanBorrowFork.t.sol +1 -0
  66. package/test/fork/TestLoanCrossRulesetFork.t.sol +3 -1
  67. package/test/fork/TestLoanLiquidationFork.t.sol +1 -0
  68. package/test/fork/TestLoanReallocateFork.t.sol +1 -0
  69. package/test/fork/TestLoanRepayFork.t.sol +1 -0
  70. package/test/fork/TestLoanTransferFork.t.sol +133 -0
  71. package/test/fork/TestSplitWeightFork.t.sol +3 -0
  72. package/test/helpers/REVEmpty721Config.sol +1 -0
  73. package/test/mock/MockBuybackDataHook.sol +1 -0
  74. package/test/regression/TestBurnPermissionRequired.t.sol +267 -0
  75. package/test/regression/TestCrossRevnetLiquidation.t.sol +228 -0
  76. package/test/regression/TestCumulativeLoanCounter.t.sol +27 -4
  77. package/test/regression/TestLiquidateGapHandling.t.sol +29 -4
  78. package/test/regression/TestZeroPriceFeed.t.sol +396 -0
@@ -0,0 +1,295 @@
1
+ # Audit Instructions -- revnet-core-v6
2
+
3
+ You are auditing the Revnet + Loans system for Juicebox V6. Revnets are autonomous, ownerless Juicebox projects with pre-programmed multi-stage tokenomics. REVLoans enables borrowing against locked revnet tokens using the bonding curve as the sole collateral valuation mechanism.
4
+
5
+ Read [RISKS.md](./RISKS.md) for the trust model and known risks. Read [ARCHITECTURE.md](./ARCHITECTURE.md) for the system overview. Read [SKILLS.md](./SKILLS.md) for the complete function reference. Then come back here.
6
+
7
+ ## Scope
8
+
9
+ **In scope:**
10
+
11
+ | Contract | Lines | Role |
12
+ |----------|-------|------|
13
+ | `src/REVDeployer.sol` | ~1,287 | Deploys revnets. Acts as data hook and cash-out hook for all revnets. Manages stages, splits, auto-issuance, buyback hook delegation, 721 hook deployment, suckers, and split operator permissions. |
14
+ | `src/REVLoans.sol` | ~1,359 | Token-collateralized lending. Burns collateral on borrow, re-mints on repay. ERC-721 loan NFTs. Three-layer fee model. Permit2 integration. |
15
+ | `src/interfaces/` | ~525 | Interface definitions for both contracts |
16
+ | `src/structs/` | ~150 | All struct definitions |
17
+
18
+ **Dependencies (assumed correct, but verify integration points):**
19
+ - `@bananapus/core-v6` -- JBController, JBMultiTerminal, JBTerminalStore, JBTokens, JBPrices, JBRulesets
20
+ - `@bananapus/721-hook-v6` -- IJB721TiersHook, IJB721TiersHookDeployer
21
+ - `@bananapus/buyback-hook-v6` -- IJBBuybackHookRegistry
22
+ - `@bananapus/suckers-v6` -- IJBSuckerRegistry
23
+ - `@croptop/core-v6` -- CTPublisher
24
+ - `@openzeppelin/contracts` -- ERC721, ERC2771Context, Ownable, SafeERC20
25
+ - `@uniswap/permit2` -- IPermit2, IAllowanceTransfer
26
+ - `@prb/math` -- mulDiv
27
+
28
+ ## The System in 90 Seconds
29
+
30
+ A **revnet** is a Juicebox project that nobody owns. REVDeployer deploys it, permanently holds its project NFT, and acts as the data hook for all payments and cash-outs. The revnet's economics are encoded as a sequence of **stages** that map 1:1 to Juicebox rulesets. Stages are immutable after deployment.
31
+
32
+ Each stage defines:
33
+ - **Initial issuance** (`initialIssuance`): tokens minted per unit of base currency
34
+ - **Issuance decay** (`issuanceCutFrequency` + `issuanceCutPercent`): how issuance decreases over time
35
+ - **Cash-out tax** (`cashOutTaxRate`): bonding curve parameter (0 = no tax, 9999 = max allowed)
36
+ - **Split percent** (`splitPercent`): percentage of minted tokens sent to reserved splits
37
+ - **Auto-issuances**: pre-configured token mints that can be claimed once per stage per beneficiary
38
+
39
+ **REVLoans** lets users borrow against their revnet tokens:
40
+ 1. Burn tokens as collateral
41
+ 2. Borrow up to the bonding curve cash-out value of those tokens
42
+ 3. Pay a three-layer fee (2.5% protocol + 1% REV + 2.5%-50% source prepaid)
43
+ 4. Receive an ERC-721 representing the loan
44
+ 5. Repay anytime to re-mint collateral tokens
45
+ 6. After 10 years, anyone can liquidate (collateral permanently lost)
46
+
47
+ ## How Revnets Interact with Juicebox Core
48
+
49
+ Understanding this interaction is essential. REVDeployer wraps core Juicebox functions with revnet-specific logic.
50
+
51
+ ### Payment Flow
52
+
53
+ ```
54
+ User pays terminal
55
+ -> Terminal calls JBTerminalStore.recordPaymentFrom()
56
+ -> Store calls REVDeployer.beforePayRecordedWith() [data hook]
57
+ -> REVDeployer calls 721 hook's beforePayRecordedWith() for split specs
58
+ -> REVDeployer calls buyback hook's beforePayRecordedWith() for swap decision
59
+ -> REVDeployer scales weight: mulDiv(weight, projectAmount, totalAmount)
60
+ -> Returns merged specs: [721 hook spec, buyback hook spec]
61
+ -> Store records payment with modified weight
62
+ -> Terminal mints tokens via Controller
63
+ -> Terminal executes pay hook specs (721 hook first, then buyback hook)
64
+ ```
65
+
66
+ **Key insight:** The weight scaling in `beforePayRecordedWith` ensures the terminal only mints tokens proportional to the amount entering the project (excluding 721 tier split amounts). Without this scaling, payers would get token credit for the split portion too.
67
+
68
+ ### Cash-Out Flow
69
+
70
+ ```
71
+ User cashes out via terminal
72
+ -> Terminal calls JBTerminalStore.recordCashOutFor()
73
+ -> Store calls REVDeployer.beforeCashOutRecordedWith() [data hook]
74
+ -> If sucker: return 0% tax, full amount (fee exempt)
75
+ -> If cashOutDelay not passed: revert
76
+ -> If cashOutTaxRate == 0 or no fee terminal: return as-is
77
+ -> Otherwise: split cashOutCount into fee portion + non-fee portion
78
+ -> Compute reclaim for non-fee portion via bonding curve
79
+ -> Compute fee amount via bonding curve on remaining surplus
80
+ -> Return modified cashOutCount + hook spec for fee payment
81
+ -> Store records cash-out with modified parameters
82
+ -> Terminal burns tokens
83
+ -> Terminal transfers reclaimed amount to user
84
+ -> Terminal calls REVDeployer.afterCashOutRecordedWith() [cash-out hook]
85
+ -> REVDeployer pays fee to fee revnet terminal
86
+ -> On failure: returns funds to originating project
87
+ ```
88
+
89
+ **Key insight:** The cash-out fee is computed as a two-step bonding curve calculation, not a simple percentage of the reclaimed amount. This is because burning fewer tokens (non-fee portion) changes the surplus-to-supply ratio for the fee portion.
90
+
91
+ ### Loan Flow
92
+
93
+ ```
94
+ Borrower calls REVLoans.borrowFrom()
95
+ -> Prerequisite: caller must have granted BURN_TOKENS permission to REVLoans via JBPermissions
96
+ -> Validate: collateral > 0, terminal registered, prepaidFeePercent in range
97
+ -> Generate loan ID: revnetId * 1T + loanNumber
98
+ -> Create loan in storage
99
+ -> Calculate borrowAmount via bonding curve:
100
+ -> totalSurplus = aggregate from all terminals
101
+ -> totalBorrowed = aggregate from all loan sources
102
+ -> borrowable = JBCashOuts.cashOutFrom(surplus + borrowed, collateral, supply + totalCollateral, taxRate)
103
+ -> Calculate source fee: JBFees.feeAmountFrom(borrowAmount, prepaidFeePercent)
104
+ -> _adjust():
105
+ -> Write loan.amount and loan.collateral to storage (CEI)
106
+ -> _addTo(): pull funds via useAllowanceOf, pay REV fee, transfer to beneficiary
107
+ -> _addCollateralTo(): burn collateral tokens via Controller
108
+ -> Pay source fee to terminal
109
+ -> Mint loan ERC-721 to borrower
110
+ ```
111
+
112
+ **Key insight:** `_borrowableAmountFrom` includes `totalBorrowed` in the surplus calculation (`surplus + totalBorrowed`) and `totalCollateral` in the supply calculation (`totalSupply + totalCollateral`). This means outstanding loans don't reduce the borrowable amount for new loans -- the virtual surplus and virtual supply are used.
113
+
114
+ ## Key State Variables
115
+
116
+ ### REVDeployer Storage
117
+
118
+ | Variable | Purpose | Audit Focus |
119
+ |----------|---------|-------------|
120
+ | `amountToAutoIssue[revnetId][stageId][beneficiary]` | Premint tokens per stage per beneficiary | Single-claim enforcement (zeroed before mint) |
121
+ | `cashOutDelayOf[revnetId]` | Timestamp when cash-outs unlock | Applied only for existing revnets deployed to new chains |
122
+ | `hashedEncodedConfigurationOf[revnetId]` | Config hash for cross-chain sucker validation | Gap: does NOT cover terminal configs |
123
+ | `tiered721HookOf[revnetId]` | 721 hook address | Set once during deploy, never changed |
124
+ | `_extraOperatorPermissions[revnetId]` | Custom permissions for split operator | Set during deploy based on 721 hook prevention flags |
125
+
126
+ ### REVLoans Storage
127
+
128
+ | Variable | Purpose | Audit Focus |
129
+ |----------|---------|-------------|
130
+ | `_loanOf[loanId]` | Per-loan state (REVLoan struct) | Deleted on repay/liquidate; verify no stale reads |
131
+ | `totalCollateralOf[revnetId]` | Sum of all burned collateral for a revnet | Must match sum of active loan collaterals |
132
+ | `totalBorrowedFrom[revnetId][terminal][token]` | Total debt per loan source | Must match sum of active loan amounts per source |
133
+ | `totalLoansBorrowedFor[revnetId]` | Monotonically increasing loan counter | Used for loan ID generation; never decrements |
134
+ | `isLoanSourceOf[revnetId][terminal][token]` | Whether a source has been used | Only set to true, never back to false |
135
+ | `_loanSourcesOf[revnetId]` | Array of all loan sources | Only grows; iterated in `_totalBorrowedFrom` |
136
+
137
+ ### REVLoan Struct (packed storage)
138
+
139
+ ```solidity
140
+ struct REVLoan {
141
+ uint112 amount; // Borrowed amount in source token's decimals
142
+ uint112 collateral; // Number of revnet tokens burned as collateral
143
+ uint48 createdAt; // Block timestamp when loan was created
144
+ uint16 prepaidFeePercent; // Fee percent prepaid (25-500, out of MAX_FEE=1000)
145
+ uint32 prepaidDuration; // Seconds of interest-free window
146
+ REVLoanSource source; // (token, terminal) pair
147
+ }
148
+ ```
149
+
150
+ **Note:** `uint112` max is ~5.19e33. Amounts above this are checked in `_adjust` and revert with `REVLoans_OverflowAlert`.
151
+
152
+ ## Priority Audit Areas
153
+
154
+ Audit in this order. Earlier items have higher blast radius:
155
+
156
+ ### 1. Loan collateral valuation and manipulation
157
+
158
+ The bonding curve is the sole collateral oracle. Verify:
159
+
160
+ - `_borrowableAmountFrom` correctly aggregates surplus across all terminals
161
+ - `totalBorrowed` and `totalCollateral` adjustments in the virtual surplus/supply calculation are correct
162
+ - Stage transitions don't allow arbitrage (borrow under old tax rate, benefit from new rate)
163
+ - Rounding in `JBCashOuts.cashOutFrom` doesn't favor the borrower
164
+ - Cross-currency aggregation in `_totalBorrowedFrom` handles decimal normalization correctly
165
+ - Price feed failures (zero price) are handled gracefully (sources skipped, not reverted)
166
+
167
+ ### 2. CEI pattern in loan operations
168
+
169
+ No reentrancy guard. Verify the CEI ordering in:
170
+
171
+ - `_adjust`: writes `loan.amount` and `loan.collateral` before `_addTo` / `_removeFrom` / `_addCollateralTo` / `_returnCollateralFrom`
172
+ - `borrowFrom`: `_adjust` before `_mint` (ERC-721 onReceived callback)
173
+ - `repayLoan`: `_burn` before `_adjust` before `_mint` (for partial repay)
174
+ - `reallocateCollateralFromLoan`: `_reallocateCollateralFromLoan` before `borrowFrom` -- two full loan operations in sequence
175
+ - `liquidateExpiredLoansFrom`: `_burn` and `delete` before storage updates
176
+
177
+ **Specific concern:** In `reallocateCollateralFromLoan`, the reallocation creates a new loan NFT and then `borrowFrom` creates another. Between these two operations, tokens are minted back to the caller (returned collateral) which are then immediately burned (new loan collateral). If `borrowFrom` triggers an external callback (via pay hooks or the ERC-721 mint), can the caller manipulate state between the two operations?
178
+
179
+ ### 3. Data hook composition
180
+
181
+ REVDeployer proxies between the terminal and two hooks. Verify:
182
+
183
+ - The 721 hook's `beforePayRecordedWith` is called with the full context, but the buyback hook's is called with a reduced amount. Is this always correct?
184
+ - When the 721 hook returns specs with `amount >= context.amount.value`, `projectAmount` is 0 and weight is 0. This means no tokens are minted by the terminal (all funds go to 721 splits). Verify this is safe -- does the buyback hook handle a zero-amount context gracefully?
185
+ - The `hookSpecifications` array sizing assumes at most one spec from each hook. Verify neither hook can return multiple specs.
186
+ - The weight scaling `mulDiv(weight, projectAmount, context.amount.value)` -- can this produce a weight of 0 when it shouldn't, or a weight > 0 when it should be 0?
187
+
188
+ ### 4. Cash-out fee calculation
189
+
190
+ The two-step bonding curve fee calculation in `beforeCashOutRecordedWith`:
191
+
192
+ ```solidity
193
+ feeCashOutCount = mulDiv(cashOutCount, FEE, MAX_FEE) // 2.5% of tokens
194
+ nonFeeCashOutCount = cashOutCount - feeCashOutCount
195
+
196
+ postFeeReclaimedAmount = JBCashOuts.cashOutFrom(surplus, nonFeeCashOutCount, totalSupply, taxRate)
197
+ feeAmount = JBCashOuts.cashOutFrom(surplus - postFeeReclaimedAmount, feeCashOutCount, totalSupply - nonFeeCashOutCount, taxRate)
198
+ ```
199
+
200
+ Verify:
201
+ - `postFeeReclaimedAmount + feeAmount <= directReclaim` (total <= what you'd get without fee splitting)
202
+ - Micro cash-outs (< 40 wei at 2.5%) round `feeCashOutCount` to zero, bypassing the fee. This is documented as economically insignificant. Verify.
203
+ - The `cashOutCount` returned to the terminal is `nonFeeCashOutCount`, but the terminal still burns the full `cashOutCount` tokens. **Wait, is this correct?** Trace through the terminal to verify how many tokens are actually burned.
204
+
205
+ ### 5. Permission model
206
+
207
+ REVDeployer grants wildcard permissions during construction:
208
+
209
+ ```solidity
210
+ constructor() {
211
+ _setPermission(SUCKER_REGISTRY, 0, MAP_SUCKER_TOKEN); // All revnets
212
+ _setPermission(LOANS, 0, USE_ALLOWANCE); // All revnets
213
+ _setPermission(BUYBACK_HOOK, 0, SET_BUYBACK_POOL); // All revnets
214
+ }
215
+ ```
216
+
217
+ These are projectId=0 (wildcard) permissions. Verify:
218
+ - `JBPermissions` resolves wildcard correctly -- these grant the permission for ALL revnets owned by REVDeployer, not just project 0
219
+ - The LOANS contract can call `useAllowanceOf` on any revnet's terminal -- verify this is constrained by the bonding curve calculation in `borrowFrom`
220
+ - No other permission is granted at wildcard level
221
+
222
+ ### 6. Auto-issuance timing
223
+
224
+ Stage IDs computed during deployment must match JBRulesets-assigned IDs:
225
+
226
+ ```solidity
227
+ amountToAutoIssue[revnetId][block.timestamp + i][beneficiary] += count;
228
+ ```
229
+
230
+ Later claimed via:
231
+ ```solidity
232
+ (JBRuleset memory ruleset,) = CONTROLLER.getRulesetOf(revnetId, stageId);
233
+ if (ruleset.start > block.timestamp) revert REVDeployer_StageNotStarted(stageId);
234
+ ```
235
+
236
+ Verify:
237
+ - JBRulesets assigns IDs as `latestId >= block.timestamp ? latestId + 1 : block.timestamp`. Does this produce `block.timestamp, block.timestamp+1, block.timestamp+2, ...` when all stages are queued in one transaction?
238
+ - What if another contract queued a ruleset for the same project in the same block? (Shouldn't be possible since REVDeployer owns the project, but verify.)
239
+ - `getRulesetOf` returns the ruleset by ID. If the stage hasn't started yet, `ruleset.start` is the derived start time, not the queue time. The timing guard uses `ruleset.start`, which is correct. But what if `startsAtOrAfter` is 0 for the first stage and `block.timestamp` is used? The stage starts immediately -- can auto-issuance be claimed in the same transaction as deployment?
240
+
241
+ ### 7. Loan fee model
242
+
243
+ Three layers of fees on borrow:
244
+
245
+ 1. **Protocol fee (2.5%)** -- charged by `useAllowanceOf` (JBMultiTerminal takes it automatically)
246
+ 2. **REV fee (1%)** -- `JBFees.feeAmountFrom(borrowAmount, REV_PREPAID_FEE_PERCENT=10)` paid to REV revnet. Try-catch; zeroed on failure.
247
+ 3. **Source prepaid fee (2.5%-50%)** -- `JBFees.feeAmountFrom(borrowAmount, prepaidFeePercent)` paid back to the revnet via `terminal.pay`. NOT try-catch; reverts on failure.
248
+
249
+ On repay, the source fee is time-proportional:
250
+
251
+ ```solidity
252
+ if (timeSinceLoanCreated <= prepaidDuration) return 0; // Free window
253
+ // After prepaid window: linear accrual
254
+ fullSourceFeeAmount = JBFees.feeAmountFrom(
255
+ loan.amount - prepaid,
256
+ mulDiv(timeSinceLoanCreated - prepaidDuration, MAX_FEE, LOAN_LIQUIDATION_DURATION - prepaidDuration)
257
+ );
258
+ sourceFeeAmount = mulDiv(fullSourceFeeAmount, amount, loan.amount);
259
+ ```
260
+
261
+ Verify:
262
+ - The `prepaidDuration` calculation: `mulDiv(prepaidFeePercent, LOAN_LIQUIDATION_DURATION, MAX_PREPAID_FEE_PERCENT)`. At 2.5% (25), this is `25 * 3650 days / 500 = 182.5 days`. At 50% (500), it's `500 * 3650 days / 500 = 3650 days` (full duration). Is this the intended mapping?
263
+ - The linear accrual formula: at `timeSinceLoanCreated = LOAN_LIQUIDATION_DURATION`, the fee percent approaches MAX_FEE (100%). Is this correct? The borrower would owe the full remaining loan amount as a fee, making repayment impossible.
264
+ - Actually, at liquidation time, `_determineSourceFeeAmount` reverts with `REVLoans_LoanExpired`. So the fee approaches but never reaches 100%. Verify the revert boundary is correct: `>=` vs `>`.
265
+
266
+ ## How to Run Tests
267
+
268
+ ```bash
269
+ cd revnet-core-v6
270
+ npm install
271
+ forge build
272
+ forge test
273
+
274
+ # Run with verbosity for debugging
275
+ forge test -vvvv --match-test testName
276
+
277
+ # Write a PoC
278
+ forge test --match-path test/audit/ExploitPoC.t.sol -vvv
279
+
280
+ # Gas analysis
281
+ forge test --gas-report
282
+ ```
283
+
284
+ ## Anti-Patterns to Hunt
285
+
286
+ | Pattern | Where | Why |
287
+ |---------|-------|-----|
288
+ | `mulDiv` rounding direction | `beforePayRecordedWith` weight scaling, `_determineSourceFeeAmount`, `_borrowableAmountFrom` | Rounding in borrower's favor compounds over many loans |
289
+ | Source fee `pay` without try-catch | `_adjust` line 1086 | If source fee terminal reverts, entire borrow/repay reverts (DoS) |
290
+ | `delete _loanOf[loanId]` after external calls | `_repayLoan`, `_reallocateCollateralFromLoan` | Verify delete happens after all references to the loan are resolved |
291
+ | Loan storage read after `_adjust` mutates it | `_repayLoan` partial repay path | `_adjust` modifies `loan` via storage pointer; subsequent reads see mutated values |
292
+ | Unbounded loop in `_totalBorrowedFrom` | Called during every borrow operation | Gas griefing if many distinct loan sources accumulate |
293
+ | `uint112` truncation | `_adjust` explicit check | Verify all paths that set `loan.amount` or `loan.collateral` go through `_adjust` |
294
+ | Permit2 try-catch swallowing | `_acceptFundsFor` | If permit fails, fall through to regular transfer. Is the state consistent? |
295
+ | ERC-721 `_mint` callback | `borrowFrom`, `_repayLoan`, `_reallocateCollateralFromLoan` | `onERC721Received` can re-enter. Verify all state is settled before mint. |
package/CHANGE_LOG.md ADDED
@@ -0,0 +1,316 @@
1
+ # revnet-core-v6 Changelog (v5 -> v6)
2
+
3
+ This document describes all changes between `revnet-core` (v5, Solidity 0.8.23) and `revnet-core-v6` (v6, Solidity 0.8.26).
4
+
5
+ ---
6
+
7
+ ## 1. Breaking Changes
8
+
9
+ ### 1.1 Removed Structs
10
+
11
+ | Struct | Notes |
12
+ |--------|-------|
13
+ | `REVBuybackHookConfig` | Removed entirely. Buyback hook configuration is no longer passed by the caller. The deployer auto-configures buyback pools via an immutable `BUYBACK_HOOK` registry. |
14
+ | `REVBuybackPoolConfig` | Removed entirely. Was used within `REVBuybackHookConfig`. Buyback pools are now auto-initialized with default parameters. |
15
+
16
+ ### 1.2 Struct Field Changes
17
+
18
+ #### REVConfig
19
+
20
+ | Change | v5 | v6 |
21
+ |--------|----|----|
22
+ | `loanSources` field | `REVLoanSource[] loanSources` | Removed |
23
+ | `loans` field | `address loans` | Removed |
24
+
25
+ Loan sources and the loans contract address are no longer part of the per-revnet configuration. In v6, loans are managed via a single immutable `LOANS` address on the deployer, and fund access limits for loans are derived from terminal configurations rather than explicit loan sources.
26
+
27
+ #### REVDeploy721TiersHookConfig
28
+
29
+ | Change | v5 | v6 |
30
+ |--------|----|----|
31
+ | `baseline721HookConfiguration` type | `JBDeploy721TiersHookConfig` | `REVBaseline721HookConfig` |
32
+ | `splitOperatorCanAdjustTiers` | `bool splitOperatorCanAdjustTiers` | Renamed to `bool preventSplitOperatorAdjustingTiers` |
33
+ | `splitOperatorCanUpdateMetadata` | `bool splitOperatorCanUpdateMetadata` | Renamed to `bool preventSplitOperatorUpdatingMetadata` |
34
+ | `splitOperatorCanMint` | `bool splitOperatorCanMint` | Renamed to `bool preventSplitOperatorMinting` |
35
+ | `splitOperatorCanIncreaseDiscountPercent` | `bool splitOperatorCanIncreaseDiscountPercent` | Renamed to `bool preventSplitOperatorIncreasingDiscountPercent` |
36
+
37
+ The boolean semantics are **inverted**: v5 used opt-in flags (`splitOperatorCan*`), v6 uses opt-out flags (`preventSplitOperator*`). In v6, the permissions are granted by default unless explicitly prevented.
38
+
39
+ #### REVCroptopAllowedPost
40
+
41
+ | Change | v5 | v6 |
42
+ |--------|----|----|
43
+ | `maximumSplitPercent` field | Not present | Added: `uint32 maximumSplitPercent` |
44
+
45
+ ### 1.3 IREVDeployer Interface Changes
46
+
47
+ | Change | v5 | v6 |
48
+ |--------|----|----|
49
+ | `deployFor` (no 721s) return type | `returns (uint256)` | `returns (uint256, IJB721TiersHook)` |
50
+ | `deployFor` (no 721s) parameters | `(uint256, REVConfig, JBTerminalConfig[], REVBuybackHookConfig, REVSuckerDeploymentConfig)` | `(uint256, REVConfig, JBTerminalConfig[], REVSuckerDeploymentConfig)` |
51
+ | `deployWith721sFor` | `deployWith721sFor(uint256, REVConfig, JBTerminalConfig[], REVBuybackHookConfig, REVSuckerDeploymentConfig, REVDeploy721TiersHookConfig, REVCroptopAllowedPost[])` | Removed. Replaced by `deployFor` overload with 6 parameters. |
52
+ | `buybackHookOf` view | `buybackHookOf(uint256) returns (IJBRulesetDataHook)` | Removed. Replaced by immutable `BUYBACK_HOOK()`. |
53
+ | `loansOf` view | `loansOf(uint256) returns (address)` | Removed. Replaced by immutable `LOANS()`. |
54
+
55
+ ### 1.4 IREVLoans Interface Changes
56
+
57
+ | Change | v5 | v6 |
58
+ |--------|----|----|
59
+ | `REVNETS` view | `REVNETS() returns (IREVDeployer)` | Removed. The loans contract no longer stores a reference to the deployer. |
60
+ | `numberOfLoansFor` view | `numberOfLoansFor(uint256) returns (uint256)` | Renamed to `totalLoansBorrowedFor(uint256) returns (uint256)` |
61
+ | `reallocateCollateralFromLoan` mutability | `external payable` | `external` (not payable) |
62
+ | Constructor | `constructor(IREVDeployer, uint256, address, IPermit2, address)` | `constructor(IJBController, IJBProjects, uint256, address, IPermit2, address)` |
63
+
64
+ ### 1.5 Removed Errors
65
+
66
+ | Contract | v5 Error | v6 Replacement |
67
+ |----------|----------|----------------|
68
+ | `REVDeployer` | `REVDeployer_LoanSourceDoesntMatchTerminalConfigurations(address, address)` | Removed. Loan sources are no longer validated against terminal configurations. |
69
+ | `REVLoans` | `REVLoans_RevnetsMismatch(address, address)` | Replaced by `REVLoans_InvalidTerminal(address, uint256)`. Terminal validation replaces deployer ownership check. |
70
+
71
+ ---
72
+
73
+ ## 2. New Features
74
+
75
+ ### 2.1 New Functions
76
+
77
+ #### IREVDeployer / REVDeployer
78
+
79
+ | Function | Description |
80
+ |----------|-------------|
81
+ | `burnHeldTokensOf(uint256 revnetId)` | Burns any of the revnet's tokens held by the deployer contract. Project tokens can accumulate here from reserved token distribution when splits do not sum to 100%. |
82
+ | `deployFor` (4-arg overload) | Convenience overload that deploys a revnet with a default empty 721 hook. Constructs an empty 721 config internally. Returns `(uint256, IJB721TiersHook)`. |
83
+ | `BUYBACK_HOOK()` | Returns the immutable `IJBBuybackHookRegistry` used as a data hook to route payments through buyback pools. |
84
+ | `LOANS()` | Returns the immutable address of the single loan contract shared by all revnets. |
85
+ | `DEFAULT_BUYBACK_POOL_FEE()` | Returns the default Uniswap pool fee tier (`10_000` = 1%) for auto-configured buyback pools. |
86
+ | `DEFAULT_BUYBACK_TWAP_WINDOW()` | Returns the default TWAP window (`2 days`) for auto-configured buyback pools. |
87
+
88
+ ### 2.2 New Events
89
+
90
+ | Contract | Event |
91
+ |----------|-------|
92
+ | `IREVDeployer` | `BurnHeldTokens(uint256 indexed revnetId, uint256 count, address caller)` |
93
+
94
+ ### 2.3 New Errors
95
+
96
+ | Contract | Error |
97
+ |----------|-------|
98
+ | `REVDeployer` | `REVDeployer_NothingToBurn()` |
99
+ | `REVLoans` | `REVLoans_InvalidTerminal(address terminal, uint256 revnetId)` |
100
+ | `REVLoans` | `REVLoans_NothingToRepay()` |
101
+ | `REVLoans` | `REVLoans_ZeroBorrowAmount()` |
102
+ | `REVLoans` | `REVLoans_SourceMismatch()` |
103
+ | `REVLoans` | `REVLoans_LoanIdOverflow()` |
104
+
105
+ ### 2.4 New Constants
106
+
107
+ | Contract | Constant | Description |
108
+ |----------|----------|-------------|
109
+ | `REVDeployer` | `DEFAULT_BUYBACK_POOL_FEE = 10_000` | Default Uniswap pool fee tier (1%) for auto-configured buyback pools. |
110
+ | `REVDeployer` | `DEFAULT_BUYBACK_TICK_SPACING = 200` | Default tick spacing for buyback pools, aligned with `UniV4DeploymentSplitHook.TICK_SPACING`. |
111
+ | `REVDeployer` | `DEFAULT_BUYBACK_TWAP_WINDOW = 2 days` | Default TWAP window for buyback pools. |
112
+
113
+ ### 2.5 New Structs
114
+
115
+ | Struct | Description |
116
+ |--------|-------------|
117
+ | `REVBaseline721HookConfig` | Replaces `JBDeploy721TiersHookConfig` as the type for `REVDeploy721TiersHookConfig.baseline721HookConfiguration`. Contains `name`, `symbol`, `baseUri`, `tokenUriResolver`, `contractUri`, `tiersConfig`, `reserveBeneficiary`, and `flags` (`REV721TiersHookFlags`). |
118
+ | `REV721TiersHookFlags` | A subset of `JB721TiersHookFlags` that omits `issueTokensForSplits` (revnets always force it to `false`). Contains `noNewTiersWithReserves`, `noNewTiersWithVotes`, `noNewTiersWithOwnerMinting`, `preventOverspending`. |
119
+
120
+ ---
121
+
122
+ ## 3. Event Changes
123
+
124
+ ### 3.1 Added Events
125
+
126
+ See section 2.2 above.
127
+
128
+ ### 3.2 Removed Events
129
+
130
+ | Contract | Event | Notes |
131
+ |----------|-------|-------|
132
+ | `IREVDeployer` | `SetAdditionalOperator(uint256 revnetId, address additionalOperator, uint256[] permissionIds, address caller)` | Removed entirely. |
133
+
134
+ ### 3.3 Modified Events
135
+
136
+ | Contract | Event | Change |
137
+ |----------|-------|--------|
138
+ | `IREVDeployer` | `DeployRevnet` | Removed `REVBuybackHookConfig buybackHookConfiguration` parameter. |
139
+ | `IREVLoans` | `ReallocateCollateral` | Typo fix: `removedcollateralCount` (lowercase 'c') renamed to `removedCollateralCount` (uppercase 'C'). |
140
+
141
+ ### 3.4 NatSpec Documentation
142
+
143
+ All events in v6 interfaces gained comprehensive NatSpec documentation (`@notice`, `@param`). This is a documentation-only change that does not affect the ABI.
144
+
145
+ ---
146
+
147
+ ## 4. Error Changes
148
+
149
+ ### 4.1 Removed Errors
150
+
151
+ | Contract | Error | Notes |
152
+ |----------|-------|-------|
153
+ | `REVDeployer` | `REVDeployer_LoanSourceDoesntMatchTerminalConfigurations(address, address)` | Loan sources removed from `REVConfig`. |
154
+ | `REVLoans` | `REVLoans_RevnetsMismatch(address, address)` | Replaced by terminal validation via `DIRECTORY.isTerminalOf`. |
155
+
156
+ ### 4.2 New Errors
157
+
158
+ See section 2.3 above.
159
+
160
+ ### 4.3 Unchanged Errors
161
+
162
+ The following errors are identical between v5 and v6:
163
+
164
+ **REVDeployer:**
165
+ - `REVDeployer_AutoIssuanceBeneficiaryZeroAddress()`
166
+ - `REVDeployer_CashOutDelayNotFinished(uint256, uint256)`
167
+ - `REVDeployer_CashOutsCantBeTurnedOffCompletely(uint256, uint256)`
168
+ - `REVDeployer_MustHaveSplits()`
169
+ - `REVDeployer_NothingToAutoIssue()`
170
+ - `REVDeployer_RulesetDoesNotAllowDeployingSuckers()`
171
+ - `REVDeployer_StageNotStarted(uint256)`
172
+ - `REVDeployer_StagesRequired()`
173
+ - `REVDeployer_StageTimesMustIncrease()`
174
+ - `REVDeployer_Unauthorized(uint256, address)`
175
+
176
+ **REVLoans:**
177
+ - `REVLoans_CollateralExceedsLoan(uint256, uint256)`
178
+ - `REVLoans_InvalidPrepaidFeePercent(uint256, uint256, uint256)`
179
+ - `REVLoans_LoanExpired(uint256, uint256)`
180
+ - `REVLoans_NewBorrowAmountGreaterThanLoanAmount(uint256, uint256)`
181
+ - `REVLoans_NoMsgValueAllowed()`
182
+ - `REVLoans_NotEnoughCollateral()`
183
+ - `REVLoans_OverflowAlert(uint256, uint256)`
184
+ - `REVLoans_OverMaxRepayBorrowAmount(uint256, uint256)`
185
+ - `REVLoans_PermitAllowanceNotEnough(uint256, uint256)`
186
+ - `REVLoans_ReallocatingMoreCollateralThanBorrowedAmountAllows(uint256, uint256)`
187
+ - `REVLoans_Unauthorized(address, address)`
188
+ - `REVLoans_UnderMinBorrowAmount(uint256, uint256)`
189
+ - `REVLoans_ZeroCollateralLoanIsInvalid()`
190
+
191
+ ---
192
+
193
+ ## 5. Struct Changes
194
+
195
+ ### 5.1 Removed Structs
196
+
197
+ | Struct | Notes |
198
+ |--------|-------|
199
+ | `REVBuybackHookConfig` | Buyback hook is now an immutable on the deployer; configuration is automatic. |
200
+ | `REVBuybackPoolConfig` | Was used within `REVBuybackHookConfig`. |
201
+
202
+ ### 5.2 New Structs
203
+
204
+ | Struct | Notes |
205
+ |--------|-------|
206
+ | `REVBaseline721HookConfig` | Replaces `JBDeploy721TiersHookConfig` in `REVDeploy721TiersHookConfig`. Provides a revnet-specific 721 config that uses `REV721TiersHookFlags` instead of `JB721TiersHookFlags`, omitting `issueTokensForSplits`. |
207
+ | `REV721TiersHookFlags` | A subset of `JB721TiersHookFlags` without `issueTokensForSplits` (always forced to `false` for revnets). |
208
+
209
+ ### 5.3 Modified Structs
210
+
211
+ | Struct | Field | v5 | v6 |
212
+ |--------|-------|----|----|
213
+ | `REVConfig` | `loanSources` | `REVLoanSource[] loanSources` | Removed |
214
+ | `REVConfig` | `loans` | `address loans` | Removed |
215
+ | `REVCroptopAllowedPost` | `maximumSplitPercent` | Not present | `uint32 maximumSplitPercent` |
216
+ | `REVDeploy721TiersHookConfig` | `baseline721HookConfiguration` | `JBDeploy721TiersHookConfig` | `REVBaseline721HookConfig` |
217
+ | `REVDeploy721TiersHookConfig` | `splitOperatorCanAdjustTiers` | `bool splitOperatorCanAdjustTiers` | Renamed/inverted: `bool preventSplitOperatorAdjustingTiers` |
218
+ | `REVDeploy721TiersHookConfig` | `splitOperatorCanUpdateMetadata` | `bool splitOperatorCanUpdateMetadata` | Renamed/inverted: `bool preventSplitOperatorUpdatingMetadata` |
219
+ | `REVDeploy721TiersHookConfig` | `splitOperatorCanMint` | `bool splitOperatorCanMint` | Renamed/inverted: `bool preventSplitOperatorMinting` |
220
+ | `REVDeploy721TiersHookConfig` | `splitOperatorCanIncreaseDiscountPercent` | `bool splitOperatorCanIncreaseDiscountPercent` | Renamed/inverted: `bool preventSplitOperatorIncreasingDiscountPercent` |
221
+
222
+ ### 5.4 Unchanged Structs
223
+
224
+ The following structs are identical between v5 and v6 (only `forge-lint` comments added):
225
+ - `REVAutoIssuance`
226
+ - `REVDescription`
227
+ - `REVLoan`
228
+ - `REVLoanSource`
229
+ - `REVStageConfig`
230
+ - `REVSuckerDeploymentConfig`
231
+
232
+ ---
233
+
234
+ ## 6. Implementation Changes
235
+
236
+ ### 6.1 REVDeployer
237
+
238
+ | Change | Description |
239
+ |--------|-------------|
240
+ | **Solidity version** | Upgraded from `0.8.23` to `0.8.26`. |
241
+ | **Buyback hook architecture** | Per-revnet `buybackHookOf` mapping replaced with a single immutable `BUYBACK_HOOK` (`IJBBuybackHookRegistry`). Pools are auto-initialized for each terminal token during deployment via `_tryInitializeBuybackPoolFor`. |
242
+ | **Loans architecture** | Per-revnet `loansOf` mapping replaced with a single immutable `LOANS` address. The deployer grants `USE_ALLOWANCE` permission to the loans contract for all revnets in the constructor (wildcard `revnetId=0`). |
243
+ | **Constructor permissions** | v6 constructor grants three wildcard permissions: `MAP_SUCKER_TOKEN` to the sucker registry, `USE_ALLOWANCE` to the loans contract, and `SET_BUYBACK_POOL` to the buyback hook. v5 only granted `MAP_SUCKER_TOKEN`. |
244
+ | **Deploy function consolidation** | `deployFor` and `deployWith721sFor` merged into two `deployFor` overloads: a 6-arg version (with 721 config and allowed posts) and a 4-arg convenience version (auto-creates empty 721 hook). Both return `(uint256, IJB721TiersHook)`. |
245
+ | **Every revnet gets a 721 hook** | The 4-arg `deployFor` overload auto-deploys a default empty 721 hook with all split operator permissions granted. In v5, the simple `deployFor` did not deploy any 721 hook. |
246
+ | **721 permission semantics inverted** | v5 used opt-in flags (`splitOperatorCanAdjustTiers` etc.) that conditionally pushed permissions. v6 uses opt-out flags (`preventSplitOperatorAdjustingTiers` etc.) that grant permissions by default unless prevented. |
247
+ | **`beforePayRecordedWith` rewrite** | v5 fetched the buyback hook from `buybackHookOf[revnetId]` and the 721 hook separately, passing the 721 hook as a zero-amount `JBPayHookSpecification`. v6 queries the 721 hook first as a data hook to determine its tier split amount, reduces the payment context amount for the buyback hook query, and scales the buyback weight proportionally (`weight * projectAmount / totalAmount`) to prevent minting tokens for the split portion of payments. |
248
+ | **`hasMintPermissionFor` updated** | v5 checked `loansOf[revnetId]`, `buybackHookOf[revnetId]`, and suckers. v6 checks the immutable `LOANS`, the immutable `BUYBACK_HOOK`, and delegates to `BUYBACK_HOOK.hasMintPermissionFor` for buyback delegates. |
249
+ | **Loan fund access limits simplified** | v5 derived fund access limits from `configuration.loanSources` and validated them against terminal configurations via `_matchingCurrencyOf`. v6 derives them from all terminal configurations directly (one unlimited surplus allowance per terminal+token pair). The `_matchingCurrencyOf` helper is removed. |
250
+ | **`burnHeldTokensOf` added** | New function to burn any project tokens held by the deployer. Reverts with `REVDeployer_NothingToBurn` if the balance is zero. |
251
+ | **Split operator permissions expanded** | Default permissions increased from 6 (v5) to 9 (v6). Added `SET_BUYBACK_HOOK`, `SET_ROUTER_TERMINAL`, and `SET_TOKEN_METADATA`. |
252
+ | **Encoded configuration hash** | v5 included `configuration.loans` in the encoded configuration. v6 does not, since loans are no longer per-revnet. |
253
+ | **Deploy ordering** | v6 `_deploy721RevnetFor` deploys the revnet first via `_deployRevnetFor`, then deploys the 721 hook and sets split operator permissions. v5 deployed the 721 hook then called `_deployRevnetFor`. |
254
+ | **Croptop `maximumSplitPercent`** | v6 passes the new `maximumSplitPercent` field from `REVCroptopAllowedPost` to `CTAllowedPost`. |
255
+ | **Auto-initialized buyback pools** | During deployment, `_tryInitializeBuybackPoolFor` is called for every terminal token to set up Uniswap V4 buyback pools at a generic 1:1 `sqrtPriceX96`. Failures (e.g., pool already initialized) are silently caught via try-catch. |
256
+
257
+ ### 6.2 REVLoans
258
+
259
+ | Change | Description |
260
+ |--------|-------------|
261
+ | **Solidity version** | Upgraded from `0.8.23` to `0.8.26`. |
262
+ | **Deployer dependency removed** | v5 stored `REVNETS` (`IREVDeployer`) and validated that the revnet was owned by the expected deployer via `RevnetsMismatch`. v6 does not reference the deployer at all. Validation now checks the terminal directly via `DIRECTORY.isTerminalOf`. |
263
+ | **Constructor refactored** | v5 accepted `IREVDeployer revnets` and derived `CONTROLLER`, `DIRECTORY`, etc. from it. v6 accepts `IJBController controller` and `IJBProjects projects` directly. |
264
+ | **Terminal validation** | `borrowFrom` now validates that the source terminal is registered in the directory for the revnet, reverting with `REVLoans_InvalidTerminal` if not. v5 validated deployer ownership instead. |
265
+ | **`numberOfLoansFor` renamed** | Renamed to `totalLoansBorrowedFor` to clarify that it is a monotonically increasing counter, not a count of active loans. |
266
+ | **`reallocateCollateralFromLoan` not payable** | v5 marked this function as `external payable`. v6 removes `payable` since the function only moves existing collateral between loans and does not accept new funds. |
267
+ | **Source mismatch check** | `reallocateCollateralFromLoan` now validates that the provided source matches the existing loan's source, reverting with `REVLoans_SourceMismatch()` if they differ. |
268
+ | **Zero borrow amount check** | `borrowFrom` now reverts with `REVLoans_ZeroBorrowAmount()` if the bonding curve returns zero. v5 did not have this check and would create a zero-amount loan. |
269
+ | **Nothing to repay check** | `repayLoan` now reverts with `REVLoans_NothingToRepay()` if both `repayBorrowAmount` and `collateralCountToReturn` are zero, preventing unbounded `totalLoansBorrowedFor` inflation. |
270
+ | **Liquidation loop behavior** | v5 broke out of the loop when encountering a loan with `createdAt == 0` (`break`). v6 continues iterating (`continue`), skipping gaps from repaid or previously liquidated loans. |
271
+ | **Liquidation cleanup** | v6 adds `delete _loanOf[loanId]` after burning a liquidated loan, clearing stale loan data for a gas refund. v5 did not clean up the loan data. |
272
+ | **`_totalBorrowedFrom` decimal normalization** | v6 normalizes token amounts from the source's native decimals to the target decimals before currency conversion. v5 did not perform decimal normalization, which could cause mixed-decimal arithmetic errors for tokens with non-18 decimals (e.g., USDC with 6 decimals). |
273
+ | **`_totalBorrowedFrom` zero-price safety** | v6 skips sources with a zero price to prevent division-by-zero panics that would DoS all loan operations. v5 did not handle this case. |
274
+ | **`_determineSourceFeeAmount` boundary** | v6 uses `>=` for the liquidation check (`timeSinceLoanCreated >= LOAN_LIQUIDATION_DURATION`). v5 used `>`. This means v6 considers a loan expired exactly at the boundary, while v5 allowed one more second. |
275
+ | **`ReallocateCollateral` event typo fix** | v5 used `removedcollateralCount` (lowercase 'c'). v6 fixes it to `removedCollateralCount` (uppercase 'C'). |
276
+ | **NatSpec documentation** | Extensive NatSpec added to all functions, views, and internal helpers. Flash loan safety analysis documented in `_borrowableAmountFrom`. |
277
+
278
+ ### 6.3 Named Arguments
279
+
280
+ Throughout the codebase, function calls were updated to use named argument syntax (e.g., `foo({bar: 1, baz: 2})`) for improved readability.
281
+
282
+ ---
283
+
284
+ ## 7. Migration Table
285
+
286
+ ### Interfaces
287
+
288
+ | v5 | v6 | Notes |
289
+ |----|----|-------|
290
+ | `IREVDeployer` | `IREVDeployer` | `deployWith721sFor` removed; two `deployFor` overloads (both return `IJB721TiersHook`). `buybackHookOf` and `loansOf` removed. `BUYBACK_HOOK`, `LOANS`, `DEFAULT_BUYBACK_POOL_FEE`, `DEFAULT_BUYBACK_TWAP_WINDOW`, `burnHeldTokensOf` added. `BurnHeldTokens` event added, `SetAdditionalOperator` event removed. `DeployRevnet` event lost `buybackHookConfiguration` param. NatSpec added. |
291
+ | `IREVLoans` | `IREVLoans` | `REVNETS` removed. `numberOfLoansFor` renamed to `totalLoansBorrowedFor`. `reallocateCollateralFromLoan` no longer payable. Constructor takes `IJBController` + `IJBProjects` instead of `IREVDeployer`. `ReallocateCollateral` event typo fixed. NatSpec added. |
292
+
293
+ ### Contracts
294
+
295
+ | v5 | v6 | Notes |
296
+ |----|----|-------|
297
+ | `REVDeployer` | `REVDeployer` | Buyback hook architecture changed from per-revnet mapping to immutable registry. Loans changed from per-revnet to single immutable. Deploy functions consolidated. Every revnet gets a 721 hook. 721 permission flags inverted. `beforePayRecordedWith` rewritten for split-aware weight scaling. `burnHeldTokensOf` added. Split operator gains 3 new default permissions. |
298
+ | `REVLoans` | `REVLoans` | Deployer dependency removed. Terminal validation replaces deployer ownership check. `numberOfLoansFor` renamed. `reallocateCollateralFromLoan` not payable. Source mismatch, zero borrow, and nothing-to-repay checks added. Liquidation loop uses `continue` instead of `break`. Stale loan data cleaned up on liquidation. Decimal normalization and zero-price safety in `_totalBorrowedFrom`. |
299
+
300
+ ### Structs
301
+
302
+ | v5 | v6 | Notes |
303
+ |----|----|-------|
304
+ | `REVAutoIssuance` | `REVAutoIssuance` | Identical (lint comment added) |
305
+ | `REVBuybackHookConfig` | (removed) | Buyback hook is now an immutable on the deployer |
306
+ | `REVBuybackPoolConfig` | (removed) | Was used within `REVBuybackHookConfig` |
307
+ | (not present) | `REVBaseline721HookConfig` | New struct for revnet-specific 721 hook configuration |
308
+ | (not present) | `REV721TiersHookFlags` | New subset of `JB721TiersHookFlags` without `issueTokensForSplits` |
309
+ | `REVConfig` | `REVConfig` | Removed `loanSources` and `loans` fields |
310
+ | `REVCroptopAllowedPost` | `REVCroptopAllowedPost` | Added `maximumSplitPercent` field |
311
+ | `REVDeploy721TiersHookConfig` | `REVDeploy721TiersHookConfig` | `baseline721HookConfiguration` type changed. Boolean flags inverted from opt-in to opt-out. |
312
+ | `REVDescription` | `REVDescription` | Identical (lint comment added) |
313
+ | `REVLoan` | `REVLoan` | Identical (lint comment added) |
314
+ | `REVLoanSource` | `REVLoanSource` | Identical (lint comment added) |
315
+ | `REVStageConfig` | `REVStageConfig` | Identical (lint comment added) |
316
+ | `REVSuckerDeploymentConfig` | `REVSuckerDeploymentConfig` | Identical (lint comment added) |
package/README.md CHANGED
@@ -47,7 +47,7 @@ Revnets are autonomous Juicebox projects with predetermined economic stages. Eac
47
47
 
48
48
  `REVLoans` lets participants borrow against their revnet tokens. Unlike traditional lending:
49
49
 
50
- - **Collateral is burned, not held.** Tokens are destroyed on borrow and re-minted on repay. This keeps the token supply accurate -- collateral tokens don't exist during the loan.
50
+ - **Collateral is burned, not held.** Tokens are destroyed on borrow and re-minted on repay. This keeps the token supply accurate -- collateral tokens don't exist during the loan. Callers must first grant `BURN_TOKENS` permission to the loans contract via `JBPermissions.setPermissionsFor()`.
51
51
  - **Borrowable amount = cash-out value.** The bonding curve determines how much you can borrow for a given amount of collateral.
52
52
  - **Prepaid fee model.** Borrowers choose a prepaid fee (2.5%-50%) that buys an interest-free window. After that window, a time-proportional source fee accrues.
53
53
  - **Each loan is an ERC-721 NFT.** Loans can be transferred, and expired loans (10 years) can be liquidated by anyone.
@@ -183,7 +183,7 @@ Plus optional from 721 hook config: `ADJUST_721_TIERS`, `SET_721_METADATA`, `MIN
183
183
  - **Loan flash-loan exposure.** `borrowableAmountFrom` reads live surplus, which can be inflated via flash loans. A borrower could temporarily inflate the treasury to borrow more than the sustained value would support.
184
184
  - **uint112 truncation.** `REVLoan.amount` and `REVLoan.collateral` are `uint112` -- values above ~5.19e33 truncate silently.
185
185
  - **Cash-out fee stacking.** Cash outs incur both the Juicebox terminal fee (2.5%) and the revnet cash-out fee (2.5% to fee revnet). These compound.
186
- - **Auto-issuance stage ID mismatch.** Stage IDs are computed as `block.timestamp + i` during deployment, but actual Juicebox ruleset IDs depend on queuing logic. If timestamps don't align, auto-issuance for later stages may be unclaimed.
186
+ - **Auto-issuance stage IDs.** Stage IDs are `block.timestamp + i` during deployment. These match the Juicebox ruleset IDs because `JBRulesets` assigns IDs the same way (`latestId >= block.timestamp ? latestId + 1 : block.timestamp`), producing identical sequential IDs when all stages are queued in a single `deployFor()` call.
187
187
  - **NATIVE_TOKEN on non-ETH chains.** Using `JBConstants.NATIVE_TOKEN` on Celo or Polygon means CELO/MATIC, not ETH. Use ERC-20 WETH instead. The matching hash does NOT catch this -- it covers economic parameters but NOT terminal configurations.
188
188
  - **30-day cash-out delay.** When deploying an existing revnet to a new chain where the first stage has already started, a 30-day delay is imposed before cash outs are allowed, preventing cross-chain liquidity arbitrage.
189
189
  - **Loan source array growth.** `_loanSourcesOf[revnetId]` is unbounded. If an attacker creates loans from many different terminals/tokens, the array grows without limit.