@rev-net/core-v6 0.0.29 → 0.0.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/ADMINISTRATION.md +19 -9
  2. package/ARCHITECTURE.md +3 -0
  3. package/AUDIT_INSTRUCTIONS.md +11 -1
  4. package/CHANGELOG.md +26 -0
  5. package/README.md +1 -0
  6. package/RISKS.md +28 -4
  7. package/SKILLS.md +2 -1
  8. package/USER_JOURNEYS.md +28 -3
  9. package/package.json +8 -8
  10. package/references/operations.md +1 -1
  11. package/script/Deploy.s.sol +26 -4
  12. package/src/REVDeployer.sol +4 -2
  13. package/src/REVHiddenTokens.sol +149 -0
  14. package/src/REVLoans.sol +192 -199
  15. package/src/REVOwner.sol +51 -14
  16. package/src/interfaces/IREVHiddenTokens.sol +53 -0
  17. package/src/interfaces/IREVLoans.sol +8 -6
  18. package/test/REV.integrations.t.sol +12 -2
  19. package/test/REVAutoIssuanceFuzz.t.sol +12 -2
  20. package/test/REVDeployerRegressions.t.sol +14 -3
  21. package/test/REVInvincibility.t.sol +27 -8
  22. package/test/REVInvincibilityHandler.sol +1 -1
  23. package/test/REVLifecycle.t.sol +14 -3
  24. package/test/REVLoans.invariants.t.sol +15 -4
  25. package/test/REVLoansAttacks.t.sol +19 -7
  26. package/test/REVLoansFeeRecovery.t.sol +24 -13
  27. package/test/REVLoansFindings.t.sol +16 -5
  28. package/test/REVLoansRegressions.t.sol +15 -4
  29. package/test/REVLoansSourceFeeRecovery.t.sol +16 -5
  30. package/test/REVLoansSourced.t.sol +60 -25
  31. package/test/REVLoansUnSourced.t.sol +15 -4
  32. package/test/TestBurnHeldTokens.t.sol +14 -3
  33. package/test/TestCEIPattern.t.sol +19 -7
  34. package/test/TestCashOutCallerValidation.t.sol +15 -4
  35. package/test/TestConversionDocumentation.t.sol +14 -3
  36. package/test/TestCrossCurrencyReclaim.t.sol +14 -3
  37. package/test/TestCrossSourceReallocation.t.sol +15 -4
  38. package/test/TestERC2771MetaTx.t.sol +18 -5
  39. package/test/TestEmptyBuybackSpecs.t.sol +14 -3
  40. package/test/TestFlashLoanSurplus.t.sol +15 -4
  41. package/test/TestHiddenTokens.t.sol +431 -0
  42. package/test/TestHookArrayOOB.t.sol +14 -3
  43. package/test/TestLiquidationBehavior.t.sol +16 -5
  44. package/test/TestLoanSourceRotation.t.sol +20 -7
  45. package/test/TestLoansCashOutDelay.t.sol +18 -7
  46. package/test/TestLongTailEconomics.t.sol +14 -3
  47. package/test/TestLowFindings.t.sol +25 -9
  48. package/test/TestMixedFixes.t.sol +19 -8
  49. package/test/TestPermit2Signatures.t.sol +15 -4
  50. package/test/TestReallocationSandwich.t.sol +16 -4
  51. package/test/TestRevnetRegressions.t.sol +16 -5
  52. package/test/TestSplitWeightAdjustment.t.sol +16 -4
  53. package/test/TestSplitWeightE2E.t.sol +18 -4
  54. package/test/TestSplitWeightFork.t.sol +16 -3
  55. package/test/TestStageTransitionBorrowable.t.sol +14 -3
  56. package/test/TestSwapTerminalPermission.t.sol +14 -3
  57. package/test/TestUint112Overflow.t.sol +15 -4
  58. package/test/TestZeroAmountLoanGuard.t.sol +15 -4
  59. package/test/TestZeroRepayment.t.sol +15 -4
  60. package/test/audit/CodexPhantomSurplusTerminal.t.sol +367 -0
  61. package/test/audit/LoanIdOverflowGuard.t.sol +16 -5
  62. package/test/audit/NemesisOperatorDelegation.t.sol +289 -0
  63. package/test/fork/ForkTestBase.sol +18 -4
  64. package/test/fork/TestLoanBorrowFork.t.sol +2 -1
  65. package/test/fork/TestLoanERC20Fork.t.sol +4 -2
  66. package/test/fork/TestLoanTransferFork.t.sol +12 -2
  67. package/test/helpers/MaliciousContracts.sol +1 -1
  68. package/test/mock/MockBuybackCashOutRecorder.sol +2 -0
  69. package/test/mock/MockBuybackDataHook.sol +3 -1
  70. package/test/mock/MockBuybackDataHookMintPath.sol +2 -0
  71. package/test/mock/MockSuckerRegistry.sol +17 -0
  72. package/test/regression/TestBurnPermissionRequired.t.sol +16 -5
  73. package/test/regression/TestCashOutBuybackFeeLeak.t.sol +16 -3
  74. package/test/regression/TestCrossRevnetLiquidation.t.sol +14 -3
  75. package/test/regression/TestCumulativeLoanCounter.t.sol +15 -4
  76. package/test/regression/TestLiquidateGapHandling.t.sol +15 -4
  77. package/test/regression/TestZeroPriceFeed.t.sol +17 -6
package/ADMINISTRATION.md CHANGED
@@ -6,8 +6,8 @@ Admin privileges and their scope in revnet-core-v6. Revnets are designed to be a
6
6
 
7
7
  | Item | Details |
8
8
  |------|---------|
9
- | Scope | Autonomous revnet deployment, split-operator powers, loan metadata ownership, and the boundaries imposed by revnet immutability. |
10
- | Operators | The per-revnet split operator, the global `REVLoans` owner for metadata cosmetics, the protocol-owned `REVDeployer`, loan NFT holders, and permissionless callers for open lifecycle actions. |
9
+ | Scope | Autonomous revnet deployment, split-operator powers, loan metadata ownership, hidden token management, and the boundaries imposed by revnet immutability. |
10
+ | Operators | The per-revnet split operator, the global `REVLoans` owner for metadata cosmetics, the protocol-owned `REVDeployer`, loan NFT holders (or operators with `REPAY_LOAN`/`REALLOCATE_LOAN`/`OPEN_LOAN` permissions), `REVHiddenTokens` callers (or operators with `HIDE_TOKENS`/`REVEAL_TOKENS` permissions), and permissionless callers for open lifecycle actions. |
11
11
  | Highest-risk actions | Converting an existing project into a revnet, changing or burning the split operator, and configuring buyback, router, price-feed, or sucker permissions inside the narrow allowed operator surface. |
12
12
  | Recovery posture | Revnet economics are intentionally not admin-recoverable. A bad deployment generally means abandoning the revnet and deploying a new one. |
13
13
 
@@ -49,8 +49,8 @@ Admin privileges and their scope in revnet-core-v6. Revnets are designed to be a
49
49
 
50
50
  ### Loan NFT Owner
51
51
 
52
- - **How assigned:** The `_msgSender()` who calls `borrowFrom()` receives the loan ERC-721. Transferable like any ERC-721.
53
- - **Scope:** Per-loan. Only the current NFT owner can repay the loan, return collateral, or reallocate collateral to a new loan.
52
+ - **How assigned:** The `holder` specified in `borrowFrom()` receives the loan ERC-721. When no operator delegation is used, the caller passes themselves as `holder`. Transferable like any ERC-721.
53
+ - **Scope:** Per-loan. The current NFT owner — or an operator with the relevant `JBPermissionIds` (`REPAY_LOAN`, `REALLOCATE_LOAN`) — can repay the loan, return collateral, or reallocate collateral to a new loan.
54
54
 
55
55
  ### Anyone (Permissionless)
56
56
 
@@ -98,9 +98,9 @@ Optional 721 permissions (granted only if enabled at deployment via `REVDeploy72
98
98
 
99
99
  | Function | Required Role | Access Control | What It Does |
100
100
  |----------|--------------|----------------|-------------|
101
- | `borrowFrom()` | Anyone (must hold revnet tokens) | None -- but caller's tokens are burned as collateral | Opens a loan against revnet token collateral. |
102
- | `repayLoan()` | Loan NFT Owner | `_ownerOf(loanId) == _msgSender()` | Repays a loan (partially or fully) and returns collateral. |
103
- | `reallocateCollateralFromLoan()` | Loan NFT Owner | `_ownerOf(loanId) == _msgSender()` | Splits excess collateral from an existing loan into a new loan. |
101
+ | `borrowFrom()` | Holder or operator with `OPEN_LOAN` | `PERMISSIONS.hasPermission` if caller holder | Opens a loan against revnet token collateral. The `holder` parameter specifies whose tokens are burned; the loan NFT is minted to `holder`. |
102
+ | `repayLoan()` | Loan NFT Owner or operator with `REPAY_LOAN` | `PERMISSIONS.hasPermission` if caller ≠ owner | Repays a loan (partially or fully) and returns collateral. Replacement loans are minted to the original loan owner. |
103
+ | `reallocateCollateralFromLoan()` | Loan NFT Owner or operator with `REALLOCATE_LOAN` | `PERMISSIONS.hasPermission` if caller ≠ owner | Splits excess collateral from an existing loan into a new loan. Returned collateral and replacement loans go to the original loan owner. |
104
104
  | `liquidateExpiredLoansFrom()` | Anyone | None | Liquidates loans that have exceeded the 10-year liquidation duration. Permanently destroys collateral. |
105
105
  | `setTokenUriResolver()` | REVLoans Owner | `onlyOwner` (OpenZeppelin Ownable) | Sets the contract that resolves loan NFT metadata URIs. |
106
106
 
@@ -123,7 +123,7 @@ Revnets are designed to operate without a traditional project owner. The followi
123
123
  - **No approval hooks.** All rulesets are deployed with `approvalHook = address(0)`. There is no mechanism to block or delay stage transitions.
124
124
  - **Cash outs cannot be fully disabled.** The deployer enforces `cashOutTaxRate < MAX_CASH_OUT_TAX_RATE` for every stage, guaranteeing that token holders always retain some ability to cash out.
125
125
  - **Data hook is REVOwner.** `REVOwner` is set as the data hook (`metadata.dataHook = address(OWNER())`) for all rulesets, ensuring consistent fee and sucker logic without external admin control. REVOwner stores `cashOutDelayOf` and `tiered721HookOf` in its own storage (set by REVDeployer via DEPLOYER-restricted setters during deployment).
126
- - **Mint permission is restricted.** Only the loans contract, the buyback hook (and its delegates), and registered suckers can mint tokens (as determined by `REVOwner.hasMintPermissionFor`). The split operator cannot mint fungible revnet tokens.
126
+ - **Mint permission is restricted.** Only the loans contract, the hidden tokens contract, the buyback hook (and its delegates), and registered suckers can mint tokens (as determined by `REVOwner.hasMintPermissionFor`). The split operator cannot mint fungible revnet tokens.
127
127
  - **No held fee manipulation.** The deployer has no function to process or return held fees arbitrarily.
128
128
  - **Owner minting is constrained.** While `allowOwnerMinting = true` is set in ruleset metadata, the "owner" is the `REVDeployer` contract. It only uses this to mint auto-issuance tokens (amounts fixed at deployment) and to return loan collateral.
129
129
 
@@ -133,9 +133,18 @@ The `REVLoans` contract has minimal admin surface by design:
133
133
 
134
134
  - **All economic parameters are constants.** Loan liquidation duration (10 years), fee percentages (MIN 2.5%, MAX 50%), and the REV fee (1%) are hardcoded as immutable constants. No admin can change them.
135
135
  - **The only admin function is `setTokenUriResolver()`**, which controls how loan NFTs render their metadata. This is purely cosmetic and has no effect on loan economics, collateral, or fund flows.
136
- - **Loan management is permissioned to NFT holders only.** Repayment, collateral reallocation, and refinancing require ownership of the specific loan's ERC-721 NFT.
136
+ - **Loan management is permissioned to NFT holders or delegated operators.** Repayment requires loan NFT ownership or `REPAY_LOAN` permission. Collateral reallocation requires loan NFT ownership or `REALLOCATE_LOAN` permission. Borrowing on behalf of another holder requires `OPEN_LOAN` permission. In all delegated cases, collateral and loan NFTs flow to the original holder/owner, not the operator.
137
137
  - **Liquidation is permissionless.** Anyone can call `liquidateExpiredLoansFrom()` for loans past the 10-year duration.
138
138
 
139
+ ## Hidden Tokens Administration
140
+
141
+ The `REVHiddenTokens` contract inherits `JBPermissioned` for operator delegation but has no admin surface:
142
+
143
+ - **Operations are holder-initiated or operator-delegated.** Any token holder can hide or reveal their own tokens. An operator with `HIDE_TOKENS` permission can hide tokens on behalf of a holder; an operator with `REVEAL_TOKENS` permission can reveal on their behalf.
144
+ - **No owner or admin role.** The contract has no `Ownable` or privileged functions.
145
+ - **Mint permission is granted via REVOwner.** `REVHiddenTokens` is listed in `REVOwner.hasMintPermissionFor` so it can re-mint tokens on reveal. This is a global immutable set at REVOwner deployment.
146
+ - **BURN_TOKENS permission is per-user.** Each user must individually grant `BURN_TOKENS` permission to the `REVHiddenTokens` contract before hiding tokens.
147
+
139
148
  ## Immutable Configuration
140
149
 
141
150
  The following parameters are set at deployment and can never be changed:
@@ -171,6 +180,7 @@ The following parameters are set at deployment and can never be changed:
171
180
  - `BUYBACK_HOOK` -- the buyback hook (shared immutable with REVDeployer)
172
181
  - `DIRECTORY` -- the Juicebox directory (shared immutable with REVDeployer)
173
182
  - `FEE_REVNET_ID` -- the project ID that receives cash-out fees (shared immutable)
183
+ - `HIDDEN_TOKENS` -- the hidden tokens contract address (shared immutable)
174
184
  - `SUCKER_REGISTRY` -- the sucker registry (shared immutable)
175
185
  - `LOANS` -- the loans contract address (shared immutable)
176
186
  - `FEE` -- the cash-out fee constant (2.5%)
package/ARCHITECTURE.md CHANGED
@@ -9,6 +9,7 @@
9
9
  - `REVDeployer` owns launch-time configuration and runtime wrapper behavior.
10
10
  - `REVOwner` owns owner-like data-hook behavior for revnet projects.
11
11
  - `REVLoans` owns the loan lifecycle.
12
+ - `REVHiddenTokens` owns temporary token hiding and supply exclusion.
12
13
  - The repo composes several sibling repos instead of reimplementing them.
13
14
 
14
15
  ## Main Components
@@ -18,6 +19,7 @@
18
19
  | `REVDeployer` | Launches revnets, queues staged rulesets, wires hooks, grants operator permissions, and exposes runtime wrapper behavior |
19
20
  | `REVOwner` | Ownerless policy surface plugged into revnet rulesets |
20
21
  | `REVLoans` | Burn-collateral borrow/repay/liquidate flow represented as ERC-721 loans |
22
+ | `REVHiddenTokens` | Temporary token hiding: burn to exclude from totalSupply, re-mint on reveal |
21
23
  | config structs | Stage, auto-issuance, loan source, and 721-hook configuration surfaces |
22
24
 
23
25
  ## Runtime Model
@@ -50,6 +52,7 @@ borrower
50
52
  - The project is designed to be ownerless after deployment. "Easy" admin recovery paths would break the product thesis.
51
53
  - Stage configuration is effectively permanent once queued.
52
54
  - Loan collateral is burned, not escrowed. Supply-sensitive logic must treat that as real destruction until repayment.
55
+ - Hidden tokens are burned, not escrowed. They reduce totalSupply until revealed. This interacts with bonding curve valuations.
53
56
  - `REVOwner` and `REVDeployer` are tightly coupled. Their setup order is part of correctness.
54
57
 
55
58
  ## Where Complexity Lives
@@ -17,6 +17,7 @@ In scope:
17
17
  - `src/REVDeployer.sol`
18
18
  - `src/REVOwner.sol`
19
19
  - `src/REVLoans.sol`
20
+ - `src/REVHiddenTokens.sol`
20
21
  - `src/interfaces/`
21
22
  - `src/structs/`
22
23
  - deployment scripts in `script/`
@@ -45,6 +46,7 @@ The repo splits responsibilities:
45
46
  - `REVDeployer`: launches revnets, encodes stage configs, manages optional 721 and sucker composition
46
47
  - `REVOwner`: runtime data/cash-out hook used by deployed revnets
47
48
  - `REVLoans`: loan system that burns collateral on borrow and re-mints on repayment
49
+ - `REVHiddenTokens`: temporary token hiding that burns tokens to exclude from totalSupply and re-mints on reveal
48
50
 
49
51
  Important composition behavior:
50
52
  - revnet payments may be proxied through 721 and buyback hooks
@@ -75,7 +77,10 @@ Fee-free or mint-enabled paths meant for registered omnichain components must no
75
77
  6. Collateral burn/remint symmetry holds
76
78
  Loan collateral that is burned on borrow and re-minted on repay must not be duplicable, strandable, or recoverable by the wrong party.
77
79
 
78
- 7. Stage transitions do not create hidden refinancing windows
80
+ 7. Hidden token accounting is sound
81
+ Tokens hidden via REVHiddenTokens must be exactly recoverable on reveal. The hidden balance must not allow minting more tokens than were burned, and totalHiddenOf must equal the sum of all per-holder hidden balances.
82
+
83
+ 8. Stage transitions do not create hidden refinancing windows
79
84
  Changes in issuance or cash-out economics across stages must not let a borrower lock in value that the system intended to become unavailable.
80
85
 
81
86
  ## Threat Model
@@ -87,11 +92,13 @@ Prioritize:
87
92
  - array or hook-spec assumptions that depend on non-empty returns
88
93
  - split-weight accounting during 721 compositions
89
94
  - Permit2 and ERC-2771 assisted loan flows
95
+ - operator delegation abuse: `OPEN_LOAN`, `REPAY_LOAN`, `REALLOCATE_LOAN`, `HIDE_TOKENS`, `REVEAL_TOKENS` permission checks — verify collateral and loan NFTs always flow to the holder/owner, never the operator
90
96
 
91
97
  The best attacker mindsets here are:
92
98
  - a borrower who can move surplus or stage timing before and after borrowing
93
99
  - a caller exploiting the fact that revnets are composed from several optional subsystems, not one monolith
94
100
  - an operator or deployer helper that retained one capability too many
101
+ - a delegated operator who tricks a holder into granting permission, then exploits the delegation to extract value (e.g., borrowing on behalf of a holder and directing funds to a beneficiary they control)
95
102
 
96
103
  ## Hotspots
97
104
 
@@ -99,6 +106,8 @@ The best attacker mindsets here are:
99
106
  - `REVOwner.beforeCashOutRecordedWith`
100
107
  - deployer-only linkage between `REVDeployer` and `REVOwner`
101
108
  - `REVLoans` borrowable amount, fee accrual, and liquidation logic
109
+ - `REVHiddenTokens.hideTokensOf` and `revealTokensOf` burn/mint symmetry and `HIDE_TOKENS`/`REVEAL_TOKENS` permission checks
110
+ - `REVLoans` operator delegation: `OPEN_LOAN`, `REPAY_LOAN`, `REALLOCATE_LOAN` inline permission checks — verify holder/owner receives collateral and loan NFTs in all delegation paths
102
111
  - any path that assumes a valid tiered 721 hook or sucker mapping exists
103
112
 
104
113
  ## Sequences Worth Replaying
@@ -108,6 +117,7 @@ The best attacker mindsets here are:
108
117
  3. Borrow after surplus inflation, then force or observe surplus contraction before liquidation.
109
118
  4. Cash out through a legitimate sucker path versus a near-sucker spoof path.
110
119
  5. Any path where `REVOwner` expects hook arrays or external replies to be non-empty.
120
+ 6. Hide tokens, have an accomplice cash out at the inflated rate, then reveal — check whether the net outcome is profitable.
111
121
 
112
122
  ## Finding Bar
113
123
 
package/CHANGELOG.md CHANGED
@@ -9,9 +9,11 @@ This file describes the verified change from `revnet-core-v5` to the current `re
9
9
  - `REVDeployer`
10
10
  - `REVOwner`
11
11
  - `REVLoans`
12
+ - `REVHiddenTokens`
12
13
  - `IREVDeployer`
13
14
  - `IREVOwner`
14
15
  - `IREVLoans`
16
+ - `IREVHiddenTokens`
15
17
 
16
18
  ## Summary
17
19
 
@@ -21,12 +23,36 @@ This file describes the verified change from `revnet-core-v5` to the current `re
21
23
  - The v6 test tree is substantially broader than the v5 tree, with dedicated regression, fork, attack, and invariant coverage for loans, cash-outs, split weights, and lifecycle edges.
22
24
  - The repo moved from the v5 `0.8.23` baseline to `0.8.28`.
23
25
 
26
+ ## Operator delegation (permission IDs 35–39)
27
+
28
+ - Added five new `JBPermissionIds` for operator delegation in `@bananapus/permission-ids-v6`:
29
+ - `HIDE_TOKENS` (35) — hide tokens on behalf of a holder via `REVHiddenTokens.hideTokensOf`
30
+ - `OPEN_LOAN` (36) — open a loan on behalf of a token holder via `REVLoans.borrowFrom`
31
+ - `REALLOCATE_LOAN` (37) — reallocate loan collateral on behalf of a loan owner via `REVLoans.reallocateCollateralFromLoan`
32
+ - `REPAY_LOAN` (38) — repay a loan on behalf of a loan owner via `REVLoans.repayLoan`
33
+ - `REVEAL_TOKENS` (39) — reveal hidden tokens on behalf of a holder via `REVHiddenTokens.revealTokensOf`
34
+ - `REVHiddenTokens` now inherits `JBPermissioned` and accepts a `holder` parameter on `hideTokensOf` and `revealTokensOf`. An operator with the appropriate permission can act on behalf of the holder.
35
+ - `REVLoans.borrowFrom` now accepts a `holder` parameter. The loan NFT is minted to `holder`, and collateral is burned from `holder`. An operator with `OPEN_LOAN` permission can borrow on behalf of a holder.
36
+ - `REVLoans.repayLoan` now allows permissioned operators with `REPAY_LOAN` to repay on behalf of the loan NFT owner. Replacement loans are minted to the original loan owner.
37
+ - `REVLoans.reallocateCollateralFromLoan` now allows permissioned operators with `REALLOCATE_LOAN` to reallocate on behalf of the loan NFT owner. Returned collateral and replacement loans go to the original loan owner.
38
+ - `REVLoans` stores a `PERMISSIONS` immutable for inline permission checks (cannot inherit `JBPermissioned` due to existing `ERC721 + ERC2771Context + Ownable` inheritance).
39
+
40
+ ### Breaking ABI changes from delegation
41
+
42
+ - `IREVHiddenTokens.hideTokensOf` signature changed: added `address holder` parameter
43
+ - `IREVHiddenTokens.revealTokensOf` signature changed: added `address holder` parameter
44
+ - `IREVHiddenTokens.HideTokens` event: added `address holder` field
45
+ - `IREVHiddenTokens.RevealTokens` event: added `address holder` field
46
+ - `IREVLoans.borrowFrom` signature changed: added `address holder` as last parameter
47
+
24
48
  ## Verified deltas
25
49
 
26
50
  - `IREVDeployer.deployWith721sFor(...)` is gone.
27
51
  - `IREVDeployer.deployFor(...)` now has overloads that return `(uint256, IJB721TiersHook)`.
28
52
  - `IREVDeployer.BUYBACK_HOOK()`, `LOANS()`, and `OWNER()` are explicit v6 surface area.
29
53
  - `IREVOwner` is a new interface and runtime counterpart to the deployer.
54
+ - `IREVHiddenTokens` is a new interface for temporary token hiding (burn to exclude from totalSupply, re-mint on reveal).
55
+ - `REVHiddenTokens` is a new standalone contract that lets holders temporarily hide tokens to increase cash-out value for remaining holders.
30
56
  - The old caller-supplied `REVBuybackHookConfig` path is no longer part of the deployer interface.
31
57
 
32
58
  ## Breaking ABI changes
package/README.md CHANGED
@@ -28,6 +28,7 @@ The key point is that a Revnet is not just "a Juicebox project with presets." It
28
28
  | `REVDeployer` | Launches and configures Revnets, stages, split operators, and optional auxiliary features. |
29
29
  | `REVOwner` | Runtime data-hook and cash-out-hook surface used by active Revnets. |
30
30
  | `REVLoans` | Loan surface that lets users borrow against Revnet tokens with burned collateral and NFT loan positions. |
31
+ | `REVHiddenTokens` | Lets token holders temporarily hide (burn) tokens, excluding them from totalSupply and increasing cash-out value for remaining holders. Hidden tokens can be revealed (re-minted) at any time. |
31
32
 
32
33
  ## Mental Model
33
34
 
package/RISKS.md CHANGED
@@ -63,6 +63,13 @@ Read [ARCHITECTURE.md](./ARCHITECTURE.md) and [SKILLS.md](./SKILLS.md) for proto
63
63
  - **No automatic recovery mechanism.** Once a price feed returns 0, the affected source's debt remains invisible to `_totalBorrowedFrom` until the feed recovers. There is no event emitted when a source is skipped, no fallback oracle, and no circuit breaker that pauses borrowing when feed health degrades. Operators should actively monitor price feed health and treat any feed returning 0 as a risk event for the revnet's loan system.
64
64
  - **Decimal normalization truncation.** When converting from higher-decimal tokens (18) to lower-decimal targets (6), integer division truncates. For sources with large outstanding borrows in high-decimal tokens, this truncation can systematically undercount the total borrowed amount, allowing slightly more borrowing than intended. For example, converting 999,999,999,999 wei (18-decimal) to 6-decimal precision discards 12 digits of precision. Across many sources, these per-source truncation errors accumulate additively, further widening the gap between the reported and actual total debt.
65
65
 
66
+ ### Hidden token supply manipulation
67
+
68
+ - **Hiding reduces totalSupply, inflating per-token cash-out value.** When a holder hides tokens via `REVHiddenTokens`, those tokens are burned and excluded from `totalSupply`. The bonding curve sees fewer tokens, so each remaining token is worth more on cash-out. A holder with a large position can hide tokens, have an accomplice cash out at the inflated rate, then reveal. The net effect depends on the `cashOutTaxRate` and the relative positions. Operator delegation (`HIDE_TOKENS`/`REVEAL_TOKENS` permissions) extends this to permissioned operators acting on behalf of holders.
69
+ - **Hidden tokens must be revealed before use as loan collateral.** `REVHiddenTokens` and `REVLoans` are separate systems. A holder cannot borrow against hidden tokens — they must first reveal (re-mint) them, then borrow. This is by design but may confuse users.
70
+ - **Reveal mints without reserved percent.** `revealTokensOf` calls `mintTokensOf` with `useReservedPercent: false`. This is correct because the tokens were previously burned and are being restored, not newly issued. But it means revealed tokens bypass the reserved-token mechanism entirely.
71
+ - **REVHiddenTokens has mint permission via REVOwner.** The contract is added to `REVOwner.hasMintPermissionFor`. If `REVHiddenTokens` has a vulnerability, it could mint unbounded tokens for any revnet.
72
+
66
73
  ### Auto-issuance overflow potential
67
74
 
68
75
  - **`REVAutoIssuance.count` is `uint104` (~2.03e31).** Multiple auto-issuances for the same beneficiary in the same stage are summed via `+=` in `_makeRulesetConfigurations`. If cumulative auto-issuances exceed `uint256`, this wraps. In practice, `uint104` inputs limit each addition, but verify no path allows the mapping value to overflow.
@@ -121,7 +128,7 @@ REVOwner sits between the terminal and the actual hooks (buyback hook, 721 hook)
121
128
 
122
129
  ### Permission escalation through proxy
123
130
 
124
- - **`hasMintPermissionFor` grants mint to four categories.** `REVOwner.hasMintPermissionFor` grants mint to: the loans contract, buyback hook, buyback hook delegates, and suckers. If any of these contracts have a vulnerability that allows arbitrary calls, they can mint unlimited revnet tokens for any revnet.
131
+ - **`hasMintPermissionFor` grants mint to five categories.** `REVOwner.hasMintPermissionFor` grants mint to: the loans contract, the hidden tokens contract, buyback hook, buyback hook delegates, and suckers. If any of these contracts have a vulnerability that allows arbitrary calls, they can mint unlimited revnet tokens for any revnet.
125
132
  - **Wildcard permissions.** REVDeployer grants `USE_ALLOWANCE` to `LOANS` with `projectId=0` (wildcard). This means the loans contract can drain surplus from ANY revnet deployed by this deployer, constrained only by the loan logic itself. A bug in `REVLoans._addTo` that miscalculates `addedBorrowAmount` could drain treasuries.
126
133
 
127
134
  ---
@@ -192,11 +199,17 @@ These MUST hold. Breaking any of them is a finding.
192
199
  - **Cash-out fees flow to fee revnet.** If the fee terminal `pay` succeeds, the fee goes to `FEE_REVNET_ID`. If it fails, the fee returns to the originating project via `addToBalanceOf`. Funds are never lost and never kept by the caller.
193
200
  - **Loan REV fees flow to REV revnet.** If `feeTerminal.pay` succeeds, the fee goes to `REV_ID`. If it fails (try-catch), the fee amount is zeroed and added to the borrower's payout. The fee is never lost -- it either reaches the REV revnet or goes to the borrower.
194
201
 
202
+ ### Hidden token accounting
203
+
204
+ - **Hidden balance conservation.** `totalHiddenOf[revnetId]` == sum of `hiddenBalanceOf[holder][revnetId]` across all holders.
205
+ - **Reveal bounded by hide.** No holder can reveal more tokens than they have hidden. `revealTokensOf` reverts with `REVHiddenTokens_InsufficientHiddenBalance` if `tokenCount > hiddenBalanceOf[caller][revnetId]`.
206
+ - **No double-mint from reveal.** Each hidden token can only be revealed once. Decrementing `hiddenBalanceOf` before minting prevents double-reveal.
207
+
195
208
  ### Privilege isolation
196
209
 
197
210
  - **Sucker privilege.** Only addresses returning `true` from `SUCKER_REGISTRY.isSuckerOf(projectId, addr)` get 0% cashout tax. No other code path grants this exemption.
198
- - **Loan ownership.** Only `_ownerOf(loanId)` can call `repayLoan` and `reallocateCollateralFromLoan`. The loan NFT is burned before any state changes in repayment, preventing double-use.
199
- - **Mint permission.** Only `LOANS`, `BUYBACK_HOOK`, buyback hook delegates (via `BUYBACK_HOOK.hasMintPermissionFor`), and suckers (via `REVOwner._isSuckerOf`) can mint tokens. No other address passes the `REVOwner.hasMintPermissionFor` check.
211
+ - **Loan ownership.** Only `_ownerOf(loanId)` — or an operator with the relevant `JBPermissionIds` (`REPAY_LOAN` for repayment, `REALLOCATE_LOAN` for reallocation) — can call `repayLoan` and `reallocateCollateralFromLoan`. Similarly, `borrowFrom` requires the caller to be the `holder` or to have `OPEN_LOAN` permission. The loan NFT is burned before any state changes in repayment, preventing double-use. Replacement loan NFTs are minted to the original holder/owner, not the operator. However, delegated operators control the `beneficiary` parameter and can direct borrowed funds, returned collateral, or revealed tokens to any address (see §8.5).
212
+ - **Mint permission.** Only `LOANS`, `HIDDEN_TOKENS`, `BUYBACK_HOOK`, buyback hook delegates (via `BUYBACK_HOOK.hasMintPermissionFor`), and suckers (via `REVOwner._isSuckerOf`) can mint tokens. No other address passes the `REVOwner.hasMintPermissionFor` check.
200
213
 
201
214
  ---
202
215
 
@@ -218,6 +231,17 @@ These MUST hold. Breaking any of them is a finding.
218
231
 
219
232
  `_borrowableAmountFrom` reads live surplus. An attacker could inflate surplus via `addToBalanceOf`, but donations are permanent (no recovery), and the extra borrowable amount is always less than the donation. `pay` increases both surplus AND supply, neutralizing the effect. With non-zero `cashOutTaxRate`, the concave bonding curve makes this even worse for attackers. The attack is self-defeating by construction.
220
233
 
221
- ### 8.5 Borrow-repay arbitrage is unprofitable (by design)
234
+ ### 8.5 Delegated operators control beneficiary independently of holder/owner (by design)
235
+
236
+ When a holder grants `OPEN_LOAN`, `REALLOCATE_LOAN`, `REPAY_LOAN`, or `REVEAL_TOKENS` permission to an operator, the operator can set the `beneficiary` parameter to any address — including themselves. This means:
237
+
238
+ - **`borrowFrom`**: An operator with `OPEN_LOAN` permission burns the holder's tokens as collateral but can direct the borrowed funds to an arbitrary beneficiary.
239
+ - **`reallocateCollateralFromLoan`**: An operator with `REALLOCATE_LOAN` permission can direct the borrowed funds from the new loan to an arbitrary beneficiary.
240
+ - **`repayLoan`**: An operator with `REPAY_LOAN` permission can direct returned collateral tokens to an arbitrary beneficiary.
241
+ - **`revealTokensOf`**: An operator with `REVEAL_TOKENS` permission can direct revealed (re-minted) tokens to an arbitrary beneficiary instead of the original holder.
242
+
243
+ This is accepted because the JBPermissions delegation model is opt-in: a holder explicitly grants permission to a specific operator for a specific project. The holder trusts the operator to act in their interest. Restricting `beneficiary` to the holder would break legitimate delegation use cases (e.g., automated vaults, multi-sig workflows, portfolio managers). Holders should only grant these permissions to operators they fully trust.
244
+
245
+ ### 8.6 Borrow-repay arbitrage is unprofitable (by design)
222
246
 
223
247
  A borrower who pays the prepaid fee upfront (minimum 2.5% + REV fee 1% = 3.5%) can repay at any time within the prepaid duration with no additional cost. If the bonding curve value of the collateral increases during the prepaid window, the borrower can repay, recover their collateral, and cash out at the higher value. This is not profitable as a standalone strategy because the 3.5% minimum fee exceeds the expected value gained from short-term surplus fluctuations. For borrowers who need liquidity anyway, it provides free optionality — which is the intended use case.
package/SKILLS.md CHANGED
@@ -13,6 +13,7 @@
13
13
  | Deployment and stage config | [`src/REVDeployer.sol`](./src/REVDeployer.sol), [`script/Deploy.s.sol`](./script/Deploy.s.sol) |
14
14
  | Runtime owner and data-hook behavior | [`src/REVOwner.sol`](./src/REVOwner.sol) |
15
15
  | Loan accounting and liquidation behavior | [`src/REVLoans.sol`](./src/REVLoans.sol) |
16
+ | Temporary token hiding and supply exclusion | [`src/REVHiddenTokens.sol`](./src/REVHiddenTokens.sol) |
16
17
  | Types and helpers | [`src/structs/`](./src/structs/), [`src/interfaces/`](./src/interfaces/), [`test/helpers/`](./test/helpers/) |
17
18
  | Loan regressions, economics, and forks | [`test/REVLoansRegressions.t.sol`](./test/REVLoansRegressions.t.sol), [`test/TestLongTailEconomics.t.sol`](./test/TestLongTailEconomics.t.sol), [`test/fork/`](./test/fork/), [`test/regression/`](./test/regression/) |
18
19
 
@@ -38,6 +39,6 @@ Deploy and manage Revnets -- autonomous, unowned Juicebox projects with staged i
38
39
 
39
40
  ## Working Rules
40
41
 
41
- - Start in `REVDeployer` for launch-time behavior, `REVOwner` for runtime hook behavior, and `REVLoans` for collateral and debt accounting.
42
+ - Start in `REVDeployer` for launch-time behavior, `REVOwner` for runtime hook behavior, `REVLoans` for collateral and debt accounting, and `REVHiddenTokens` for temporary supply exclusion.
42
43
  - Verify any economic assumption in code or tests before relying on it. Revnet docs carry more economic interpretation than most repos.
43
44
  - For anything cross-chain or stage-related, check both the deployer path and the reading-state reference before editing.
package/USER_JOURNEYS.md CHANGED
@@ -43,6 +43,20 @@
43
43
  2. Call `autoIssueFor(...)` only once the target stage has started.
44
44
  3. The stored allocation is consumed so the same stage allocation cannot be claimed twice.
45
45
 
46
+ ## Journey 3a: Hide Tokens To Increase Cash-Out Value For Remaining Holders
47
+
48
+ **Starting state:** a holder wants to temporarily exclude some tokens from totalSupply without permanently giving them up.
49
+
50
+ **Success:** the holder burns tokens via REVHiddenTokens, reducing totalSupply and increasing the per-token cash-out value for everyone else. The holder can reveal (re-mint) their hidden tokens at any time.
51
+
52
+ **Flow**
53
+ 1. The holder grants `BURN_TOKENS` permission to the `REVHiddenTokens` contract for the revnet.
54
+ 2. Call `hideTokensOf(revnetId, tokenCount, holder)` to burn the tokens and track the hidden balance. An operator with `HIDE_TOKENS` permission can call this on behalf of the holder.
55
+ 3. The bonding curve now sees a smaller totalSupply, so each remaining token is worth more on cash out.
56
+ 4. When the holder wants their tokens back, call `revealTokensOf(revnetId, tokenCount, beneficiary, holder)` to re-mint them. An operator with `REVEAL_TOKENS` permission can call this on behalf of the holder.
57
+
58
+ **Failure cases that matter:** trying to reveal more tokens than were hidden, forgetting that revealed tokens increase totalSupply again (reducing per-token cash-out value), and not realizing that hidden tokens must be revealed before they can be used as loan collateral.
59
+
46
60
  ## Journey 4: Borrow Against Revnet Tokens Instead Of Selling Them
47
61
 
48
62
  **Starting state:** a holder wants liquidity but does not want to exit the Revnet position.
@@ -50,10 +64,10 @@
50
64
  **Success:** the holder opens a loan, receives borrowed value, and keeps an NFT loan position representing the debt.
51
65
 
52
66
  **Flow**
53
- 1. The holder interacts with `REVLoans` using the eligible Revnet token exposure as collateral.
54
- 2. The system burns or escrows the relevant token exposure and mints a loan-position NFT.
67
+ 1. The holder (or an operator with `OPEN_LOAN` permission) interacts with `REVLoans` using the eligible Revnet token exposure as collateral. The `holder` parameter specifies whose tokens are burned; the loan NFT is minted to `holder`.
68
+ 2. The system burns the relevant token exposure and mints a loan-position NFT to the holder.
55
69
  3. Loan terms depend on the live Revnet economics rather than a static side system.
56
- 4. The borrower can later repay, transfer the loan NFT, or face liquidation if conditions require it.
70
+ 4. The borrower (or an operator with `REPAY_LOAN` / `REALLOCATE_LOAN` permission) can later repay, reallocate collateral, transfer the loan NFT, or face liquidation if conditions require it.
57
71
 
58
72
  ## Journey 5: Repay, Transfer, Or Liquidate A Loan Position
59
73
 
@@ -79,6 +93,17 @@
79
93
  2. Use only those surfaces rather than treating the project like a normal owner-governed Juicebox project.
80
94
  3. Audit cross-package behavior whenever the Revnet enabled buybacks, 721 hooks, router terminals, or suckers.
81
95
 
96
+ ## Journey 7: Receive Cross-Chain Payments With Correct Hook Routing
97
+
98
+ **Starting state:** a sucker pays the Revnet on behalf of a remote user via `payRemote`, and the hooks attached via `REVOwner.beforePayRecordedWith` need to see the real user.
99
+
100
+ **Success:** the 721 hook and buyback hook see the real remote user as the beneficiary so NFTs mint to and buyback routing benefits the correct person.
101
+
102
+ **Flow**
103
+ 1. The sucker calls `terminal.pay()` with relay-beneficiary metadata.
104
+ 2. `REVOwner.beforePayRecordedWith()` resolves the relay beneficiary from the metadata when the payer is a registered sucker.
105
+ 3. The swapped beneficiary is forwarded to both the 721 hook and the buyback hook.
106
+
82
107
  ## Hand-Offs
83
108
 
84
109
  - Use [nana-core-v6](../nana-core-v6/USER_JOURNEYS.md) for the underlying project, terminal, and ruleset mechanics that Revnets package and constrain.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rev-net/core-v6",
3
- "version": "0.0.29",
3
+ "version": "0.0.31",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -19,14 +19,14 @@
19
19
  "artifacts": "source ./.env && npx sphinx artifacts --org-id 'ea165b21-7cdc-4d7b-be59-ecdd4c26bee4' --project-name 'revnet-core-v6'"
20
20
  },
21
21
  "dependencies": {
22
- "@bananapus/721-hook-v6": "^0.0.32",
23
- "@bananapus/buyback-hook-v6": "^0.0.26",
24
- "@bananapus/core-v6": "^0.0.32",
22
+ "@bananapus/721-hook-v6": "^0.0.33",
23
+ "@bananapus/buyback-hook-v6": "^0.0.27",
24
+ "@bananapus/core-v6": "^0.0.34",
25
25
  "@bananapus/ownable-v6": "^0.0.17",
26
- "@bananapus/permission-ids-v6": "^0.0.15",
26
+ "@bananapus/permission-ids-v6": "^0.0.17",
27
27
  "@bananapus/router-terminal-v6": "^0.0.26",
28
- "@bananapus/suckers-v6": "^0.0.22",
29
- "@croptop/core-v6": "^0.0.30",
28
+ "@bananapus/suckers-v6": "^0.0.25",
29
+ "@croptop/core-v6": "github:mejango/croptop-core-v6",
30
30
  "@openzeppelin/contracts": "^5.6.1",
31
31
  "@uniswap/v4-core": "^1.0.2",
32
32
  "@uniswap/v4-periphery": "^1.0.3"
@@ -35,4 +35,4 @@
35
35
  "@bananapus/address-registry-v6": "^0.0.17",
36
36
  "@sphinx-labs/plugins": "^0.33.2"
37
37
  }
38
- }
38
+ }
@@ -63,7 +63,7 @@ Use this file when you need revnet-specific risks, state reads, constants, or ex
63
63
  | `REVLoans_PermitAllowanceNotEnough(allowanceAmount, requiredAmount)` | When the permit2 allowance is insufficient for the repayment. |
64
64
  | `REVLoans_ReallocatingMoreCollateralThanBorrowedAmountAllows(newBorrowAmount, loanAmount)` | When the collateral being transferred out would leave the original loan undercollateralized. |
65
65
  | `REVLoans_SourceMismatch()` | When `reallocateCollateralFromLoan` specifies a source that doesn't match the existing loan's source. |
66
- | `REVLoans_Unauthorized(caller, owner)` | When a non-owner tries to repay or reallocate someone else's loan. |
66
+ | `JBPermissioned_Unauthorized(account, sender, projectId, permissionId)` | When a non-owner without the required permission (`OPEN_LOAN`, `REPAY_LOAN`, or `REALLOCATE_LOAN`) tries to act on someone else's loan. Inherited from `JBPermissioned`. |
67
67
  | `REVLoans_UnderMinBorrowAmount(minBorrowAmount, borrowAmount)` | When the actual borrow amount is less than the caller's `minBorrowAmount`. |
68
68
  | `REVLoans_ZeroBorrowAmount()` | When a borrow or reallocation would result in zero borrowed funds. |
69
69
  | `REVLoans_ZeroCollateralLoanIsInvalid()` | When a loan would end up with zero collateral. |
@@ -37,6 +37,7 @@ import {REVConfig} from "../src/structs/REVConfig.sol";
37
37
  import {REVDescription} from "../src/structs/REVDescription.sol";
38
38
  import {REVStageConfig} from "../src/structs/REVStageConfig.sol";
39
39
  import {REVSuckerDeploymentConfig} from "../src/structs/REVSuckerDeploymentConfig.sol";
40
+ import {REVHiddenTokens} from "./../src/REVHiddenTokens.sol";
40
41
  import {REVLoans} from "./../src/REVLoans.sol";
41
42
  import {REVDeploy721TiersHookConfig} from "../src/structs/REVDeploy721TiersHookConfig.sol";
42
43
  import {REVCroptopAllowedPost} from "../src/structs/REVCroptopAllowedPost.sol";
@@ -93,6 +94,8 @@ contract DeployScript is Script, Sphinx {
93
94
  // forge-lint: disable-next-line(mixed-case-variable)
94
95
  bytes32 REVLOANS_SALT = "_REV_LOANS_SALT_V6_";
95
96
  // forge-lint: disable-next-line(mixed-case-variable)
97
+ bytes32 REVHIDDENTOKENS_SALT = "_REV_HIDDEN_TOKENS_SALT_V6_";
98
+ // forge-lint: disable-next-line(mixed-case-variable)
96
99
  bytes32 REVOWNER_SALT = "_REV_OWNER_SALT_V6_";
97
100
  // forge-lint: disable-next-line(mixed-case-variable)
98
101
  address LOANS_OWNER;
@@ -369,10 +372,13 @@ contract DeployScript is Script, Sphinx {
369
372
 
370
373
  // Try to find an existing deployment by checking all project IDs that have already been created.
371
374
  bool _revloansExists;
375
+ bool _revHiddenTokensExists;
372
376
  bool _revOwnerExists;
373
377
  bool _revDeployerExists;
374
378
  // The address of the previously deployed REVLoans, if found.
375
379
  address _existingRevloansAddr;
380
+ // The address of the previously deployed REVHiddenTokens, if found.
381
+ address _existingHiddenTokensAddr;
376
382
  // The address of the previously deployed REVOwner, if found.
377
383
  address _existingOwnerAddr;
378
384
  // The address of the previously deployed REVDeployer, if found.
@@ -384,7 +390,7 @@ contract DeployScript is Script, Sphinx {
384
390
  salt: REVLOANS_SALT,
385
391
  creationCode: type(REVLoans).creationCode,
386
392
  arguments: abi.encode(
387
- core.controller, core.projects, _candidateId, LOANS_OWNER, PERMIT2, TRUSTED_FORWARDER
393
+ core.controller, suckers.registry, _candidateId, LOANS_OWNER, PERMIT2, TRUSTED_FORWARDER
388
394
  )
389
395
  });
390
396
 
@@ -397,6 +403,13 @@ contract DeployScript is Script, Sphinx {
397
403
  _existingRevloansAddr = _candidateRevloansAddr;
398
404
  _revloansExists = true;
399
405
 
406
+ // Also predict and verify the hidden tokens contract.
407
+ (_existingHiddenTokensAddr, _revHiddenTokensExists) = _isDeployed({
408
+ salt: REVHIDDENTOKENS_SALT,
409
+ creationCode: type(REVHiddenTokens).creationCode,
410
+ arguments: abi.encode(core.controller, TRUSTED_FORWARDER)
411
+ });
412
+
400
413
  // Also predict and verify the owner.
401
414
  (_existingOwnerAddr, _revOwnerExists) = _isDeployed({
402
415
  salt: REVOWNER_SALT,
@@ -406,7 +419,8 @@ contract DeployScript is Script, Sphinx {
406
419
  core.controller.DIRECTORY(),
407
420
  _candidateId,
408
421
  suckers.registry,
409
- _candidateRevloansAddr
422
+ _candidateRevloansAddr,
423
+ _existingHiddenTokensAddr
410
424
  )
411
425
  });
412
426
 
@@ -457,13 +471,20 @@ contract DeployScript is Script, Sphinx {
457
471
  ? REVLoans(payable(_existingRevloansAddr))
458
472
  : new REVLoans{salt: REVLOANS_SALT}({
459
473
  controller: core.controller,
460
- projects: core.projects,
474
+ suckerRegistry: suckers.registry,
461
475
  revId: FEE_PROJECT_ID,
462
476
  owner: LOANS_OWNER,
463
477
  permit2: PERMIT2,
464
478
  trustedForwarder: TRUSTED_FORWARDER
465
479
  });
466
480
 
481
+ // Deploy REVHiddenTokens — allows revnet holders to temporarily hide tokens from totalSupply.
482
+ REVHiddenTokens revHiddenTokens = _revHiddenTokensExists
483
+ ? REVHiddenTokens(_existingHiddenTokensAddr)
484
+ : new REVHiddenTokens{salt: REVHIDDENTOKENS_SALT}({
485
+ controller: core.controller, trustedForwarder: TRUSTED_FORWARDER
486
+ });
487
+
467
488
  // Deploy REVOwner — the runtime data hook that handles pay and cash out callbacks.
468
489
  REVOwner revOwner = _revOwnerExists
469
490
  ? REVOwner(_existingOwnerAddr)
@@ -472,7 +493,8 @@ contract DeployScript is Script, Sphinx {
472
493
  directory: core.controller.DIRECTORY(),
473
494
  feeRevnetId: FEE_PROJECT_ID,
474
495
  suckerRegistry: suckers.registry,
475
- loans: address(revloans)
496
+ loans: address(revloans),
497
+ hiddenTokens: address(revHiddenTokens)
476
498
  });
477
499
 
478
500
  // Deploy REVDeployer with the REVLoans, buyback hook, and REVOwner addresses.
@@ -375,7 +375,7 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
375
375
  uint256[] memory customSplitOperatorPermissionIndexes = _extraOperatorPermissions[revnetId];
376
376
 
377
377
  // Make the array that merges the default and custom operator permissions.
378
- allOperatorPermissions = new uint256[](9 + customSplitOperatorPermissionIndexes.length);
378
+ allOperatorPermissions = new uint256[](10 + customSplitOperatorPermissionIndexes.length);
379
379
  allOperatorPermissions[0] = JBPermissionIds.SET_SPLIT_GROUPS;
380
380
  allOperatorPermissions[1] = JBPermissionIds.SET_BUYBACK_POOL;
381
381
  allOperatorPermissions[2] = JBPermissionIds.SET_BUYBACK_TWAP;
@@ -385,10 +385,11 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
385
385
  allOperatorPermissions[6] = JBPermissionIds.SET_BUYBACK_HOOK;
386
386
  allOperatorPermissions[7] = JBPermissionIds.SET_ROUTER_TERMINAL;
387
387
  allOperatorPermissions[8] = JBPermissionIds.SET_TOKEN_METADATA;
388
+ allOperatorPermissions[9] = JBPermissionIds.SIGN_FOR_ERC20;
388
389
 
389
390
  // Copy the custom permissions into the array.
390
391
  for (uint256 i; i < customSplitOperatorPermissionIndexes.length;) {
391
- allOperatorPermissions[9 + i] = customSplitOperatorPermissionIndexes[i];
392
+ allOperatorPermissions[10 + i] = customSplitOperatorPermissionIndexes[i];
392
393
  unchecked {
393
394
  ++i;
394
395
  }
@@ -833,6 +834,7 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
833
834
  // Store the cash out delay of the revnet if its stages are already in progress.
834
835
  // This prevents cash out liquidity/arbitrage issues for existing revnets which
835
836
  // are deploying to a new chain.
837
+ // slither-disable-next-line reentrancy-events
836
838
  _setCashOutDelayIfNeeded({revnetId: revnetId, firstStageConfig: configuration.stageConfigurations[0]});
837
839
 
838
840
  // Deploy the revnet's ERC-20 token.