@rev-net/core-v6 0.0.24 → 0.0.25

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.
package/SKILLS.md CHANGED
@@ -1,404 +1,43 @@
1
1
  # Revnet Core
2
2
 
3
- ## Purpose
4
-
5
- Deploy and manage Revnets -- autonomous, unowned Juicebox projects with staged issuance schedules, built-in Uniswap buyback pools, cross-chain suckers, and token-collateralized lending.
6
-
7
- ## Contracts
8
-
9
- | Contract | Role |
10
- |----------|------|
11
- | `REVDeployer` | Deploys revnets, permanently owns the project NFT. Manages stages, splits, auto-issuance, buyback hooks, suckers, split operators, and configuration state storage. Exposes `OWNER()` view returning the REVOwner address. Calls DEPLOYER-restricted setters on REVOwner during deployment to store `cashOutDelayOf` and `tiered721HookOf`. |
12
- | `REVOwner` | Runtime hook contract for all revnets. Implements `IJBRulesetDataHook` + `IJBCashOutHook`. Set as the `dataHook` in each revnet's ruleset metadata. Handles pay hooks, cash-out hooks, mint permissions, and sucker verification. Stores `cashOutDelayOf` and `tiered721HookOf` mappings (set by REVDeployer via DEPLOYER-restricted setters `setCashOutDelayOf()` and `setTiered721HookOf()`). (~310 lines) |
13
- | `REVLoans` | Issues token-collateralized loans from revnet treasuries. Each loan is an ERC-721 NFT. Burns collateral on borrow, re-mints on repay. Charges tiered fees (REV protocol fee + source fee + prepaid fee). (~1,359 lines) |
14
-
15
- ## Key Functions
16
-
17
- ### Deployment
18
-
19
- | Function | Permissions | What it does |
20
- |----------|------------|-------------|
21
- | `REVDeployer.deployFor(revnetId, config, terminals, suckerConfig)` | Permissionless | Deploy a new revnet (`revnetId=0`) or convert an existing Juicebox project. Encodes stage configs into rulesets, deploys ERC-20 token, initializes buyback pool at 1:1 price, sets up split operator, suckers, loans permissions, and deploys a default empty tiered ERC-721 hook. |
22
- | `REVDeployer.deployFor(revnetId, config, terminals, suckerConfig, hookConfig, allowedPosts)` | Permissionless | Same as `deployFor` but deploys a tiered ERC-721 hook with pre-configured tiers. Optionally configures Croptop posting criteria and grants publisher permission to add tiers. |
23
- | `REVDeployer.deploySuckersFor(revnetId, suckerConfig)` | Split operator | Deploy new cross-chain suckers post-launch. Validates ruleset allows sucker deployment (bit 2 of `extraMetadata`). Uses stored config hash for cross-chain matching. |
24
-
25
- ### Data Hooks (REVOwner)
26
-
27
- | Function | Permissions | What it does |
28
- |----------|------------|-------------|
29
- | `REVOwner.beforePayRecordedWith(context)` | Terminal callback | Calls the 721 hook first for split specs, then calls the buyback hook with a reduced amount context (payment minus split amount). Adjusts the returned weight proportionally for splits (`weight = mulDiv(weight, amount - splitAmount, amount)`) so the terminal only mints tokens for the amount entering the project. Assembles pay hook specs (721 hook specs first, then buyback spec). Reads `tiered721HookOf` from REVOwner storage. |
30
- | `REVOwner.beforeCashOutRecordedWith(context)` | Terminal callback | If sucker: returns full amount with 0 tax (fee exempt). Otherwise: calculates 2.5% fee, enforces 30-day cash-out delay (reads `cashOutDelayOf` from REVOwner storage), returns modified count + fee hook spec. |
31
- | `REVOwner.afterCashOutRecordedWith(context)` | Permissionless | Cash-out hook callback. Receives fee amount and pays it to the fee revnet's terminal. Falls back to returning funds if fee payment fails. |
32
- | `REVOwner.hasMintPermissionFor(revnetId, ruleset, addr)` | View | Returns `true` for: loans contract, buyback hook, buyback hook delegates, or suckers. |
33
- | `REVOwner.cashOutDelayOf(revnetId)` | View | Returns the cash-out delay timestamp from REVOwner storage. Exposed for REVLoans compatibility (REVLoans imports IREVOwner for this). |
34
-
35
- ### Split Operator
36
-
37
- | Function | Permissions | What it does |
38
- |----------|------------|-------------|
39
- | `REVDeployer.setSplitOperatorOf(revnetId, newOperator)` | Split operator | Replace the current split operator. Revokes old permissions, grants new ones. |
40
-
41
- ### Auto-Issuance
42
-
43
- | Function | Permissions | What it does |
44
- |----------|------------|-------------|
45
- | `REVDeployer.autoIssueFor(revnetId, stageId, beneficiary)` | Permissionless | Mint pre-configured auto-issuance tokens for a beneficiary once a stage has started. One-time per stage per beneficiary. |
46
- | `REVDeployer.burnHeldTokensOf(revnetId)` | Permissionless | Burn any reserved tokens held by the deployer (when splits < 100%). |
47
-
48
- ### Loans -- Borrowing
49
-
50
- | Function | Permissions | What it does |
51
- |----------|------------|-------------|
52
- | `REVLoans.borrowFrom(revnetId, source, minBorrowAmount, collateralCount, beneficiary, prepaidFeePercent)` | Permissionless (caller must grant BURN_TOKENS to REVLoans) | Open a loan: enforce cash-out delay if set (cross-chain deployment protection), burn collateral tokens, pull funds from revnet via `useAllowanceOf`, pay REV fee (1%) + terminal fee (2.5%), transfer remainder to beneficiary, mint loan NFT. |
53
- | `REVLoans.repayLoan(loanId, maxRepayBorrowAmount, collateralCountToReturn, beneficiary, allowance)` | Loan NFT owner | Repay fully or partially. Returns funds to revnet via `addToBalanceOf`, re-mints collateral tokens, burns/replaces the loan NFT. Supports permit2 signatures. |
54
- | `REVLoans.reallocateCollateralFromLoan(loanId, collateralCountToTransfer, source, minBorrowAmount, collateralCountToAdd, beneficiary, prepaidFeePercent)` | Loan NFT owner | Refinance: remove excess collateral from an existing loan and open a new loan with the freed collateral. Burns original, mints two replacements. |
55
- | `REVLoans.liquidateExpiredLoansFrom(revnetId, startingLoanId, count)` | Permissionless | Clean up loans past the 10-year liquidation duration. Burns NFTs and decrements accounting totals. Collateral is permanently lost. |
56
- | `REVLoans.setTokenUriResolver(resolver)` | Contract owner (`onlyOwner`) | Set the `IJBTokenUriResolver` used for loan NFT token URIs. |
57
-
58
- ### Loans -- Views
59
-
60
- | Function | What it does |
61
- |----------|-------------|
62
- | `REVLoans.borrowableAmountFrom(revnetId, collateralCount, decimals, currency)` | Calculate how much can be borrowed for a given collateral amount. Returns 0 during the cash-out delay period. Aggregates surplus from all terminals, applies bonding curve. |
63
- | `REVLoans.determineSourceFeeAmount(loan, amount)` | Calculate the time-proportional source fee for a loan repayment. Zero during prepaid window, linear accrual after. |
64
- | `REVLoans.loanOf(loanId)` | Returns the full `REVLoan` struct for a loan. |
65
- | `REVLoans.loanSourcesOf(revnetId)` | Returns all `(terminal, token)` pairs used for loans by a revnet. |
66
- | `REVLoans.revnetIdOfLoanWith(loanId)` | Decode the revnet ID from a loan ID (`loanId / 1_000_000_000_000`). |
67
-
68
- ## Integration Points
69
-
70
- | Dependency | Import | Used For |
71
- |------------|--------|----------|
72
- | `@bananapus/core-v6` | `IJBController`, `IJBDirectory`, `IJBPermissions`, `IJBProjects`, `IJBTerminal`, `IJBPrices`, `JBConstants`, `JBCashOuts`, `JBSurplus` | Project lifecycle, rulesets, token minting/burning, fund access, terminal payments, price feeds, bonding curve |
73
- | `@bananapus/721-hook-v6` | `IJB721TiersHook`, `IJB721TiersHookDeployer` | Deploying and registering tiered ERC-721 pay hooks |
74
- | `@bananapus/buyback-hook-v6` | `IJBBuybackHookRegistry` | Configuring Uniswap buyback pools per revnet |
75
- | `@bananapus/suckers-v6` | `IJBSuckerRegistry` | Deploying cross-chain suckers, checking sucker status for fee exemption |
76
- | `@croptop/core-v6` | `CTPublisher` | Configuring Croptop posting criteria for 721 tiers |
77
- | `@bananapus/permission-ids-v6` | `JBPermissionIds` | Permission ID constants (SET_SPLIT_GROUPS, USE_ALLOWANCE, etc.) |
78
- | `@openzeppelin/contracts` | `ERC721`, `ERC2771Context`, `Ownable`, `SafeERC20` | Loan NFTs, meta-transactions, ownership, safe token transfers |
79
- | `@uniswap/permit2` | `IPermit2`, `IAllowanceTransfer` | Gasless token approvals for loan repayments |
80
- | `@prb/math` | `mulDiv` | Precise fixed-point multiplication and division |
81
-
82
- ## Key Types
83
-
84
- | Struct | Key Fields | Used In |
85
- |--------|------------|---------|
86
- | `REVConfig` | `description` (REVDescription), `baseCurrency`, `splitOperator`, `stageConfigurations[]` | `deployFor` |
87
- | `REVStageConfig` | `startsAtOrAfter` (uint48), `initialIssuance` (uint112), `issuanceCutFrequency` (uint32), `issuanceCutPercent` (uint32), `cashOutTaxRate` (uint16), `splitPercent` (uint16), `splits[]`, `autoIssuances[]`, `extraMetadata` (uint16) | Translated into `JBRulesetConfig` |
88
- | `REVDescription` | `name`, `ticker`, `uri`, `salt` | ERC-20 token deployment and project metadata |
89
- | `REVAutoIssuance` | `chainId` (uint32), `count` (uint104), `beneficiary` | Per-stage cross-chain token auto-minting |
90
- | `REVLoan` | `amount` (uint112), `collateral` (uint112), `createdAt` (uint48), `prepaidFeePercent` (uint16), `prepaidDuration` (uint32), `source` (REVLoanSource) | Per-loan state in `REVLoans` |
91
- | `REVLoanSource` | `token`, `terminal` (IJBPayoutTerminal) | Identifies which terminal and token a loan draws from |
92
- | `REVDeploy721TiersHookConfig` | `baseline721HookConfiguration` (REVBaseline721HookConfig), `salt`, `preventSplitOperatorAdjustingTiers`, `preventSplitOperatorUpdatingMetadata`, `preventSplitOperatorMinting`, `preventSplitOperatorIncreasingDiscountPercent` | 721 hook deployment with operator permissions (preventive flags — `false` = allowed). Uses `REVBaseline721HookConfig` (not `JBDeploy721TiersHookConfig`) to omit `issueTokensForSplits` — revnets always force it to `false`. |
93
- | `REVBaseline721HookConfig` | `name`, `symbol`, `baseUri`, `tokenUriResolver`, `contractUri`, `tiersConfig`, `reserveBeneficiary`, `flags` (REV721TiersHookFlags) | Same as `JBDeploy721TiersHookConfig` but uses `REV721TiersHookFlags` which omits `issueTokensForSplits`. |
94
- | `REV721TiersHookFlags` | `noNewTiersWithReserves`, `noNewTiersWithVotes`, `noNewTiersWithOwnerMinting`, `preventOverspending` | Same as `JB721TiersHookFlags` minus `issueTokensForSplits`. Revnets do their own weight adjustment for splits. |
95
- | `REVCroptopAllowedPost` | `category` (uint24), `minimumPrice` (uint104), `minimumTotalSupply` (uint32), `maximumTotalSupply` (uint32), `allowedAddresses[]` | Croptop posting criteria |
96
- | `REVSuckerDeploymentConfig` | `deployerConfigurations[]`, `salt` | Cross-chain sucker deployment |
97
-
98
- ## Events
99
-
100
- ### REVDeployer
101
-
102
- | Event | When It Fires |
103
- |-------|---------------|
104
- | `AutoIssue(revnetId, stageId, beneficiary, count, caller)` | When tokens are auto-issued for a beneficiary during a stage via `autoIssueFor`. |
105
- | `BurnHeldTokens(revnetId, count, caller)` | When held tokens are burned from the deployer contract via `burnHeldTokensOf`. |
106
- | `DeployRevnet(revnetId, configuration, terminalConfigurations, suckerDeploymentConfiguration, rulesetConfigurations, encodedConfigurationHash, caller)` | When a new revnet is deployed via `deployFor`. |
107
- | `DeploySuckers(revnetId, encodedConfigurationHash, suckerDeploymentConfiguration, caller)` | When suckers are deployed for a revnet via `deploySuckersFor`. |
108
- | `ReplaceSplitOperator(revnetId, newSplitOperator, caller)` | When the split operator of a revnet is replaced via `setSplitOperatorOf`. |
109
- | `SetCashOutDelay(revnetId, cashOutDelay, caller)` | When the cash out delay is set for a revnet during deployment to a new chain. |
110
- | `StoreAutoIssuanceAmount(revnetId, stageId, beneficiary, count, caller)` | When an auto-issuance amount is stored for a beneficiary during deployment. |
111
-
112
- ### REVLoans
113
-
114
- | Event | When It Fires |
115
- |-------|---------------|
116
- | `Borrow(loanId, revnetId, loan, source, borrowAmount, collateralCount, sourceFeeAmount, beneficiary, caller)` | When a loan is created by borrowing from a revnet via `borrowFrom`. |
117
- | `Liquidate(loanId, revnetId, loan, caller)` | When a loan is liquidated after exceeding the 10-year liquidation duration via `liquidateExpiredLoansFrom`. |
118
- | `ReallocateCollateral(loanId, revnetId, reallocatedLoanId, reallocatedLoan, removedCollateralCount, caller)` | When collateral is reallocated from one loan to a new loan via `reallocateCollateralFromLoan`. |
119
- | `RepayLoan(loanId, revnetId, paidOffLoanId, loan, paidOffLoan, repayBorrowAmount, sourceFeeAmount, collateralCountToReturn, beneficiary, caller)` | When a loan is repaid via `repayLoan`. |
120
- | `SetTokenUriResolver(resolver, caller)` | When the token URI resolver is changed via `setTokenUriResolver`. |
121
-
122
- ## Errors
123
-
124
- ### REVDeployer
125
-
126
- | Error | When It Fires |
127
- |-------|---------------|
128
- | `REVDeployer_AutoIssuanceBeneficiaryZeroAddress()` | When an auto-issuance config has a zero-address beneficiary. |
129
- | `REVDeployer_CashOutDelayNotFinished(cashOutDelay, blockTimestamp)` | When a cash out is attempted before the 30-day delay has elapsed. |
130
- | `REVDeployer_CashOutsCantBeTurnedOffCompletely(cashOutTaxRate, maxCashOutTaxRate)` | When `cashOutTaxRate` equals `MAX_CASH_OUT_TAX_RATE` (10,000). Must be strictly less. |
131
- | `REVDeployer_MustHaveSplits()` | When a stage with `splitPercent > 0` has no splits configured. |
132
- | `REVDeployer_NothingToAutoIssue()` | When `autoIssueFor` is called but no tokens are available for auto-issuance. |
133
- | `REVDeployer_NothingToBurn()` | When `burnHeldTokensOf` is called but the deployer holds no tokens. |
134
- | `REVDeployer_RulesetDoesNotAllowDeployingSuckers()` | When `deploySuckersFor` is called but the current ruleset's `extraMetadata` bit 2 is not set. |
135
- | `REVDeployer_StageNotStarted(stageId)` | When `autoIssueFor` is called for a stage that hasn't started yet. |
136
- | `REVDeployer_StagesRequired()` | When `deployFor` is called with zero stage configurations. |
137
- | `REVDeployer_StageTimesMustIncrease()` | When stage `startsAtOrAfter` values are not strictly increasing. |
138
- | `REVDeployer_Unauthorized(revnetId, caller)` | When a non-split-operator calls a split-operator-only function. |
139
-
140
- ### REVLoans
141
-
142
- | Error | When It Fires |
143
- |-------|---------------|
144
- | `REVLoans_CashOutDelayNotFinished(cashOutDelay, blockTimestamp)` | When borrowing during the 30-day cash-out delay period (cross-chain deployment protection). |
145
- | `REVLoans_CollateralExceedsLoan(collateralToReturn, loanCollateral)` | When trying to return more collateral than the loan holds. |
146
- | `REVLoans_InvalidPrepaidFeePercent(prepaidFeePercent, min, max)` | When `prepaidFeePercent` is outside the allowed range (2.5%--50%). |
147
- | `REVLoans_InvalidTerminal(terminal, revnetId)` | When the specified terminal is not registered for the revnet. |
148
- | `REVLoans_LoanExpired(timeSinceLoanCreated, loanLiquidationDuration)` | When trying to repay or reallocate an expired loan. |
149
- | `REVLoans_LoanIdOverflow()` | When the loan ID counter exceeds the per-revnet trillion-ID namespace. |
150
- | `REVLoans_NewBorrowAmountGreaterThanLoanAmount(newBorrowAmount, loanAmount)` | When a reallocation would produce a reduced loan with a larger borrow amount than the original. |
151
- | `REVLoans_NoMsgValueAllowed()` | When `msg.value > 0` on a non-native-token repayment. |
152
- | `REVLoans_NotEnoughCollateral()` | When the caller does not have enough tokens for the requested collateral. |
153
- | `REVLoans_NothingToRepay()` | When `repayLoan` is called with zero repay amount and zero collateral to return. |
154
- | `REVLoans_OverMaxRepayBorrowAmount(maxRepayBorrowAmount, repayBorrowAmount)` | When the actual repay cost exceeds the caller's `maxRepayBorrowAmount`. |
155
- | `REVLoans_OverflowAlert(value, limit)` | When a value would overflow `uint112` storage. |
156
- | `REVLoans_PermitAllowanceNotEnough(allowanceAmount, requiredAmount)` | When the permit2 allowance is insufficient for the repayment. |
157
- | `REVLoans_ReallocatingMoreCollateralThanBorrowedAmountAllows(newBorrowAmount, loanAmount)` | When the collateral being transferred out would leave the original loan undercollateralized. |
158
- | `REVLoans_SourceMismatch()` | When `reallocateCollateralFromLoan` specifies a source that doesn't match the existing loan's source. |
159
- | `REVLoans_Unauthorized(caller, owner)` | When a non-owner tries to repay or reallocate someone else's loan. |
160
- | `REVLoans_UnderMinBorrowAmount(minBorrowAmount, borrowAmount)` | When the actual borrow amount is less than the caller's `minBorrowAmount`. |
161
- | `REVLoans_ZeroBorrowAmount()` | When a borrow or reallocation would result in zero borrowed funds. |
162
- | `REVLoans_ZeroCollateralLoanIsInvalid()` | When a loan would end up with zero collateral. |
163
-
164
- ## Constants
165
-
166
- ### REVDeployer
3
+ ## Use This File For
167
4
 
168
- | Constant | Value | Purpose |
169
- |----------|-------|---------|
170
- | `CASH_OUT_DELAY` | 2,592,000 (30 days) | Prevents cross-chain liquidity arbitrage on new chain deployments |
171
- | `FEE` | 25 (of MAX_FEE=1000) | 2.5% cash-out fee paid to fee revnet |
172
- | `DEFAULT_BUYBACK_POOL_FEE` | 10,000 | 1% Uniswap fee tier for default buyback pools |
173
- | `DEFAULT_BUYBACK_TWAP_WINDOW` | 2 days | TWAP observation window for buyback price |
174
- | `DEFAULT_BUYBACK_TICK_SPACING` | 200 | Tick spacing for default buyback V4 pools |
5
+ - Use this file when the task involves revnet deployment, staged issuance, split operator logic, auto-issuance, or the revnet loan system built on top of Juicebox core.
6
+ - Start here, then open the deployer, owner, or loans contract depending on whether the issue is launch-time config, runtime hook behavior, or debt accounting.
175
7
 
176
- ### REVLoans
8
+ ## Read This Next
177
9
 
178
- | Constant | Value | Purpose |
179
- |----------|-------|---------|
180
- | `LOAN_LIQUIDATION_DURATION` | 3,650 days (10 years) | After this, collateral is forfeit |
181
- | `MIN_PREPAID_FEE_PERCENT` | 25 (2.5%) | Minimum upfront fee borrowers must pay |
182
- | `MAX_PREPAID_FEE_PERCENT` | 500 (50%) | Maximum upfront fee |
183
- | `REV_PREPAID_FEE_PERCENT` | 10 (1%) | Protocol-level fee to $REV revnet |
184
- | `_ONE_TRILLION` | 1,000,000,000,000 | Loan ID generator base: `revnetId * 1T + loanNumber` |
10
+ | If you need... | Open this next |
11
+ |---|---|
12
+ | Repo overview and operator flow | [`README.md`](./README.md) |
13
+ | Deployment and stage config | [`src/REVDeployer.sol`](./src/REVDeployer.sol), [`script/Deploy.s.sol`](./script/Deploy.s.sol) |
14
+ | Runtime owner and data-hook behavior | [`src/REVOwner.sol`](./src/REVOwner.sol) |
15
+ | Loan accounting and liquidation behavior | [`src/REVLoans.sol`](./src/REVLoans.sol) |
16
+ | Types and helpers | [`src/structs/`](./src/structs/), [`src/interfaces/`](./src/interfaces/), [`test/helpers/`](./test/helpers/) |
17
+ | 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/) |
185
18
 
186
- ## Storage
19
+ ## Repo Map
187
20
 
188
- ### REVDeployer
21
+ | Area | Where to look |
22
+ |---|---|
23
+ | Main contracts | [`src/`](./src/) |
24
+ | Scripts | [`script/`](./script/) |
25
+ | Types | [`src/structs/`](./src/structs/), [`src/interfaces/`](./src/interfaces/) |
26
+ | Tests | [`test/`](./test/) |
189
27
 
190
- | Mapping | Visibility | Type | Purpose |
191
- |---------|-----------|------|---------|
192
- | `amountToAutoIssue` | `public` | `revnetId => stageId => beneficiary => uint256` | Premint tokens per stage per beneficiary |
193
- | `hashedEncodedConfigurationOf` | `public` | `revnetId => bytes32` | Config hash for cross-chain sucker validation |
194
- | `_extraOperatorPermissions` | `internal` | `revnetId => uint256[]` | Custom permissions for split operator (no auto-getter) |
195
-
196
- ### REVOwner
197
-
198
- | Mapping | Visibility | Type | Purpose |
199
- |---------|-----------|------|---------|
200
- | `DEPLOYER` | `public` | `address` | REVDeployer address (storage variable, set once via `setDeployer()` called from REVDeployer's constructor) |
201
- | `cashOutDelayOf` | `public` | `revnetId => uint256` | Timestamp when cash outs unlock (0 = no delay). Set by REVDeployer via `setCashOutDelayOf()`. |
202
- | `tiered721HookOf` | `public` | `revnetId => address` | Deployed 721 hook address (if any). Set by REVDeployer via `setTiered721HookOf()`. |
203
-
204
- ### REVLoans
205
-
206
- | Mapping | Visibility | Type | Purpose |
207
- |---------|-----------|------|---------|
208
- | `isLoanSourceOf` | `public` | `revnetId => terminal => token => bool` | Is this (terminal, token) pair used for loans? |
209
- | `totalLoansBorrowedFor` | `public` | `revnetId => uint256` | Counter for loan numbering |
210
- | `totalBorrowedFrom` | `public` | `revnetId => terminal => token => uint256` | Tracks debt per loan source |
211
- | `totalCollateralOf` | `public` | `revnetId => uint256` | Sum of all burned collateral |
212
- | `_loanOf` | `internal` | `loanId => REVLoan` | Per-loan state (use `loanOf(loanId)` view) |
213
- | `_loanSourcesOf` | `internal` | `revnetId => REVLoanSource[]` | Array of all loan sources used (use `loanSourcesOf(revnetId)` view) |
214
- | `tokenUriResolver` | `public` | `IJBTokenUriResolver` | Resolver for loan NFT token URIs |
215
-
216
- ## Gotchas
217
-
218
- 1. **Revnets are permanently ownerless.** `REVDeployer` holds the project NFT forever. There is no function to release it. Stage parameters cannot be changed after deployment.
219
- 2. **Collateral is burned, not held.** Unlike traditional lending, collateral tokens are destroyed at borrow time and re-minted on repay. If a loan liquidates after 10 years, the collateral is permanently lost.
220
- 3. **100% LTV by design.** Borrowable amount equals the pro-rata cash-out value. No safety margin unless the stage has `cashOutTaxRate > 0`. A tax of 20% creates ~20% effective collateral buffer.
221
- 4. **Loan ID encoding.** `loanId = revnetId * 1_000_000_000_000 + loanNumber`. Each revnet supports ~1 trillion loans. Use `revnetIdOfLoanWith(loanId)` to decode.
222
- 5. **uint112 truncation risk.** `REVLoan.amount` and `REVLoan.collateral` are `uint112`. Values above ~5.19e33 truncate silently.
223
- 6. **Auto-issuance stage IDs.** Computed as `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.
224
- 7. **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. The 2.5% fee is deducted from the TOKEN AMOUNT being cashed out, not from the reclaim value. 2.5% of the tokens are redirected to the fee revnet, which then redeems them at the bonding curve independently. The net reclaim to the caller is based on 97.5% of the tokens, not 97.5% of the computed ETH value. This is by design.
225
- 8. **30-day cash-out delay.** Applied when deploying an existing revnet to a new chain where the first stage has already started. Prevents cross-chain liquidity arbitrage. Enforced in both `beforeCashOutRecordedWith` (direct cash outs) and `REVLoans.borrowFrom` / `borrowableAmountFrom` (loans). The delay is stored on REVOwner (`cashOutDelayOf(revnetId)`) and set by REVDeployer during deployment via `setCashOutDelayOf()`. REVLoans imports IREVOwner (not IREVDeployer) to read it.
226
- 9. **`cashOutTaxRate` cannot be MAX.** Must be strictly less than `MAX_CASH_OUT_TAX_RATE` (10,000). Revnets cannot fully disable cash outs.
227
- 10. **Split operator is singular.** Only ONE address can be split operator at a time. The operator can replace itself via `setSplitOperatorOf` but cannot delegate or multi-sig.
228
- 11. **NATIVE_TOKEN on non-ETH chains.** `JBConstants.NATIVE_TOKEN` on Celo means CELO, on Polygon means MATIC -- not ETH. Use ERC-20 WETH instead. The config matching hash does NOT catch terminal configuration differences.
229
- 12. **Loan source array is unbounded.** `_loanSourcesOf[revnetId]` grows without limit. No validation that a terminal is actually registered for the project.
230
- 13. **Flash-loan surplus exposure.** `borrowableAmountFrom` reads live surplus. A flash loan can temporarily inflate the treasury to borrow more than the sustained value supports.
231
- 14. **Fee revnet must have terminals.** Cash-out fees and loan protocol fees are paid to `FEE_REVNET_ID`. If that project has no terminal for the token, the fee silently fails (try-catch).
232
- 15. **Buyback hook is immutable per deployer.** `BUYBACK_HOOK` is set at construction time on both REVDeployer and REVOwner. All revnets deployed by the same deployer share the same buyback hook.
233
- 16. **Cross-chain config matching.** `hashedEncodedConfigurationOf` covers economic parameters (baseCurrency, stages, auto-issuances) but NOT terminal configurations, accounting contexts, or sucker token mappings. Two deployments with identical hashes can have different terminal setups.
234
- 17. **Loan fee model has three layers.** See Constants table for exact values: REV protocol fee, terminal fee, and prepaid source fee (borrower-chosen, buys interest-free window). After the prepaid window, source fee accrues linearly over the remaining loan duration.
235
- 18. **Permit2 fallback.** `REVLoans` uses permit2 for ERC-20 transfers as a fallback when standard allowance is insufficient. Wrapped in try-catch.
236
- 19. **39.16% cash-out tax crossover.** Below ~39% cash-out tax, cashing out is more capital-efficient than borrowing. Above ~39%, loans become more efficient because they preserve upside while providing liquidity. Based on CryptoEconLab academic research. Design implication: revnets intended for active token trading should consider this threshold when setting `cashOutTaxRate`.
237
- 20. **REVDeployer always deploys a 721 hook** via `HOOK_DEPLOYER.deployHookFor` — even if `baseline721HookConfiguration` has empty tiers. This is correct by design: it lets the split operator add and sell NFTs later without migration. Non-revnet projects should follow the same pattern by using `JB721TiersHookProjectDeployer.launchProjectFor` (or `JBOmnichainDeployer.launchProjectFor`) instead of bare `launchProjectFor`.
238
- 21. **REVOwner circular dependency.** REVOwner and REVDeployer have a circular dependency broken by `setDeployer()`, called atomically from REVDeployer's constructor. `REVOwner.DEPLOYER` is a storage variable (not immutable). `setDeployer()` sets `msg.sender` as `DEPLOYER` if not already set -- reverts if already set. If `setDeployer()` is never called, all runtime hook behavior breaks. Deploy order: REVOwner first, then REVDeployer(owner=REVOwner) -- the constructor calls `REVOwner.setDeployer()` atomically. REVOwner stores `cashOutDelayOf` and `tiered721HookOf` mappings, which are set by REVDeployer via DEPLOYER-restricted setters (`setCashOutDelayOf()`, `setTiered721HookOf()`).
239
-
240
- ### NATIVE_TOKEN Accounting on Non-ETH Chains
241
-
242
- When deploying to a chain where the native token is NOT ETH (Celo, Polygon), the terminal must NOT use `JBConstants.NATIVE_TOKEN` as its accounting context. `NATIVE_TOKEN` represents whatever is native on that chain, but `baseCurrency=1` (ETH) assumes ETH-denominated value.
243
-
244
- **Correct (Celo):**
245
- ```solidity
246
- JBAccountingContext({
247
- token: WETH_CELO, // ERC-20 WETH, not native CELO
248
- decimals: 18,
249
- currency: uint32(uint160(WETH_CELO))
250
- })
251
- ```
252
-
253
- **Wrong (Celo):**
254
- ```solidity
255
- JBAccountingContext({
256
- token: JBConstants.NATIVE_TOKEN, // This is CELO, not ETH!
257
- decimals: 18,
258
- currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
259
- })
260
- ```
261
-
262
- ## Reading Revnet State
263
-
264
- Quick-reference for common read operations. All functions are `view`/`pure` and permissionless.
265
-
266
- ### Current Stage & Ruleset
267
-
268
- | What | Call | Returns |
269
- |------|------|---------|
270
- | Current ruleset (stage) | `IJBController(CONTROLLER).currentRulesetOf(revnetId)` | `(JBRuleset, JBRulesetMetadata)` -- the active stage's parameters |
271
- | All queued rulesets | `IJBController(CONTROLLER).allRulesetsOf(revnetId, startingId, size)` | `JBRulesetWithMetadata[]` -- paginated list of stages |
272
- | Specific stage by ID | `IJBController(CONTROLLER).getRulesetOf(revnetId, stageId)` | `(JBRuleset, JBRulesetMetadata)` for that stage |
273
-
274
- ### Split Operator
275
-
276
- | What | Call | Returns |
277
- |------|------|---------|
278
- | Check if address is split operator | `REVDeployer.isSplitOperatorOf(revnetId, addr)` | `bool` |
279
-
280
- ### Token Supply & Surplus
281
-
282
- | What | Call | Returns |
283
- |------|------|---------|
284
- | Total supply (incl. pending reserved) | `IJBController(CONTROLLER).totalTokenSupplyWithReservedTokensOf(revnetId)` | `uint256` |
285
- | Pending reserved tokens | `IJBController(CONTROLLER).pendingReservedTokenBalanceOf(revnetId)` | `uint256` |
286
- | Current surplus (single terminal) | `IJBTerminalStore(STORE).currentSurplusOf(terminal, revnetId, configs, decimals, currency)` | `uint256` |
287
-
288
- ### Auto-Issuance
289
-
290
- | What | Call | Returns |
291
- |------|------|---------|
292
- | Remaining auto-issuance for beneficiary | `REVDeployer.amountToAutoIssue(revnetId, stageId, beneficiary)` | `uint256` (0 if already claimed) |
293
-
294
- ### Loans
295
-
296
- | What | Call | Returns |
297
- |------|------|---------|
298
- | Borrowable amount for collateral | `REVLoans.borrowableAmountFrom(revnetId, collateralCount, decimals, currency)` | `uint256` |
299
- | Total borrowed (per source) | `REVLoans.totalBorrowedFrom(revnetId, terminal, token)` | `uint256` |
300
- | Total collateral locked | `REVLoans.totalCollateralOf(revnetId)` | `uint256` |
301
- | Loan details | `REVLoans.loanOf(loanId)` | `REVLoan` struct |
302
- | All loan sources | `REVLoans.loanSourcesOf(revnetId)` | `REVLoanSource[]` |
303
- | Loan count | `REVLoans.totalLoansBorrowedFor(revnetId)` | `uint256` |
304
- | Source fee for repayment | `REVLoans.determineSourceFeeAmount(loan, amount)` | `uint256` |
305
- | Revnet ID from loan ID | `REVLoans.revnetIdOfLoanWith(loanId)` | `uint256` (pure) |
306
- | Loan NFT owner | `REVLoans.ownerOf(loanId)` | `address` (ERC-721) |
307
-
308
- ### Deployer Config
309
-
310
- | What | Call | Returns |
311
- |------|------|---------|
312
- | Config hash (cross-chain matching) | `REVDeployer.hashedEncodedConfigurationOf(revnetId)` | `bytes32` |
313
- | REVOwner address | `REVDeployer.OWNER()` | `address` |
314
-
315
- ### REVOwner State
316
-
317
- | What | Call | Returns |
318
- |------|------|---------|
319
- | 721 hook address | `REVOwner.tiered721HookOf(revnetId)` | `IJB721TiersHook` |
320
- | Cash-out delay timestamp | `REVOwner.cashOutDelayOf(revnetId)` | `uint256` (0 = no delay) |
321
-
322
- ## Example Integration
323
-
324
- ```solidity
325
- import {REVConfig} from "@rev-net/core-v6/src/structs/REVConfig.sol";
326
- import {REVStageConfig} from "@rev-net/core-v6/src/structs/REVStageConfig.sol";
327
- import {REVDescription} from "@rev-net/core-v6/src/structs/REVDescription.sol";
328
- import {REVSuckerDeploymentConfig} from "@rev-net/core-v6/src/structs/REVSuckerDeploymentConfig.sol";
329
- import {IREVDeployer} from "@rev-net/core-v6/src/interfaces/IREVDeployer.sol";
330
-
331
- // --- Deploy a simple revnet with one stage ---
332
-
333
- REVStageConfig[] memory stages = new REVStageConfig[](1);
334
- stages[0] = REVStageConfig({
335
- startsAtOrAfter: 0, // Start immediately (uses block.timestamp)
336
- autoIssuances: new REVAutoIssuance[](0),
337
- splitPercent: 2000, // 20% of new tokens go to splits
338
- splits: splits, // Reserved token split destinations
339
- initialIssuance: 1_000_000e18, // 1M tokens per unit of base currency
340
- issuanceCutFrequency: 30 days, // Decay period
341
- issuanceCutPercent: 100_000_000, // 10% cut per period (out of 1e9)
342
- cashOutTaxRate: 2000, // 20% tax on cash outs
343
- extraMetadata: 0
344
- });
345
-
346
- REVConfig memory config = REVConfig({
347
- description: REVDescription({
348
- name: "My Revnet Token",
349
- ticker: "MYREV",
350
- uri: "ipfs://...",
351
- salt: bytes32(0)
352
- }),
353
- baseCurrency: 1, // ETH
354
- splitOperator: msg.sender,
355
- stageConfigurations: stages
356
- });
357
-
358
- deployer.deployFor({
359
- revnetId: 0, // 0 = deploy new
360
- configuration: config,
361
- terminalConfigurations: terminals,
362
- suckerDeploymentConfiguration: REVSuckerDeploymentConfig({
363
- deployerConfigurations: new JBSuckerDeployerConfig[](0),
364
- salt: bytes32(0)
365
- })
366
- });
28
+ ## Purpose
367
29
 
368
- // --- Borrow against revnet tokens ---
30
+ Deploy and manage Revnets -- autonomous, unowned Juicebox projects with staged issuance schedules, built-in Uniswap buyback pools, cross-chain suckers, and token-collateralized lending.
369
31
 
370
- loans.borrowFrom({
371
- revnetId: revnetId,
372
- source: REVLoanSource({ token: JBConstants.NATIVE_TOKEN, terminal: terminal }),
373
- minBorrowAmount: 0,
374
- collateralCount: 1000e18, // Burn 1000 tokens as collateral
375
- beneficiary: msg.sender, // Receive borrowed funds
376
- prepaidFeePercent: 25 // 2.5% prepaid fee (minimum)
377
- });
32
+ ## Reference Files
378
33
 
379
- // --- Reallocate collateral (refinance) ---
380
- // Remove 500 tokens of collateral from an existing loan,
381
- // use them (plus 200 new tokens) to open a fresh loan.
382
- // The original loan shrinks, and a new loan NFT is minted.
383
- (uint256 reallocatedLoanId, uint256 newLoanId, , ) = loans.reallocateCollateralFromLoan({
384
- loanId: loanId,
385
- collateralCountToTransfer: 500e18, // Move 500 tokens out of existing loan
386
- source: REVLoanSource({ token: JBConstants.NATIVE_TOKEN, terminal: terminal }),
387
- minBorrowAmount: 0,
388
- collateralCountToAdd: 200e18, // Add 200 fresh tokens on top
389
- beneficiary: payable(msg.sender), // Receive new loan proceeds
390
- prepaidFeePercent: 25 // 2.5% prepaid fee on new loan
391
- });
392
- // Result: original loan now has 500 fewer collateral tokens (reallocatedLoanId),
393
- // new loan has 700 tokens of collateral (newLoanId).
34
+ | If you need... | Open this next |
35
+ |---|---|
36
+ | Contract roles, deploy/runtime entrypoints, integration points, or key structs | [`references/runtime.md`](./references/runtime.md) |
37
+ | Events, errors, constants, storage, gotchas, state-reading recipes, or examples | [`references/operations.md`](./references/operations.md) |
394
38
 
395
- // --- Repay a loan ---
39
+ ## Working Rules
396
40
 
397
- loans.repayLoan({
398
- loanId: loanId,
399
- maxRepayBorrowAmount: type(uint256).max, // Repay in full
400
- collateralCountToReturn: loan.collateral, // Return all collateral
401
- beneficiary: msg.sender, // Receive re-minted tokens
402
- allowance: JBSingleAllowance({ ... }) // Optional permit2
403
- });
404
- ```
41
+ - Start in `REVDeployer` for launch-time behavior, `REVOwner` for runtime hook behavior, and `REVLoans` for collateral and debt accounting.
42
+ - Verify any economic assumption in code or tests before relying on it. Revnet docs carry more economic interpretation than most repos.
43
+ - For anything cross-chain or stage-related, check both the deployer path and the reading-state reference before editing.
package/STYLE_GUIDE.md CHANGED
@@ -26,8 +26,8 @@ pragma solidity 0.8.28;
26
26
  // Interfaces, structs, enums — caret for forward compatibility
27
27
  pragma solidity ^0.8.0;
28
28
 
29
- // Libraries — caret, may use newer features
30
- pragma solidity ^0.8.17;
29
+ // Libraries — pin to exact version like contracts
30
+ pragma solidity 0.8.28;
31
31
  ```
32
32
 
33
33
  ## Imports
@@ -86,12 +86,20 @@ contract JBExample is JBPermissioned, IJBExample {
86
86
 
87
87
  uint256 internal constant _FEE_BENEFICIARY_PROJECT_ID = 1;
88
88
 
89
+ //*********************************************************************//
90
+ // ------------------------ private constants ------------------------ //
91
+ //*********************************************************************//
92
+
89
93
  //*********************************************************************//
90
94
  // --------------- public immutable stored properties ---------------- //
91
95
  //*********************************************************************//
92
96
 
93
97
  IJBDirectory public immutable override DIRECTORY;
94
98
 
99
+ //*********************************************************************//
100
+ // -------------- internal immutable stored properties -------------- //
101
+ //*********************************************************************//
102
+
95
103
  //*********************************************************************//
96
104
  // --------------------- public stored properties -------------------- //
97
105
  //*********************************************************************//
@@ -100,10 +108,26 @@ contract JBExample is JBPermissioned, IJBExample {
100
108
  // -------------------- internal stored properties ------------------- //
101
109
  //*********************************************************************//
102
110
 
111
+ //*********************************************************************//
112
+ // -------------------- private stored properties -------------------- //
113
+ //*********************************************************************//
114
+
115
+ //*********************************************************************//
116
+ // ------------------- transient stored properties ------------------- //
117
+ //*********************************************************************//
118
+
103
119
  //*********************************************************************//
104
120
  // -------------------------- constructor ---------------------------- //
105
121
  //*********************************************************************//
106
122
 
123
+ //*********************************************************************//
124
+ // ---------------------------- modifiers ---------------------------- //
125
+ //*********************************************************************//
126
+
127
+ //*********************************************************************//
128
+ // ------------------------- receive / fallback ---------------------- //
129
+ //*********************************************************************//
130
+
107
131
  //*********************************************************************//
108
132
  // ---------------------- external transactions ---------------------- //
109
133
  //*********************************************************************//
@@ -112,10 +136,18 @@ contract JBExample is JBPermissioned, IJBExample {
112
136
  // ----------------------- external views ---------------------------- //
113
137
  //*********************************************************************//
114
138
 
139
+ //*********************************************************************//
140
+ // -------------------------- public views --------------------------- //
141
+ //*********************************************************************//
142
+
115
143
  //*********************************************************************//
116
144
  // ----------------------- public transactions ----------------------- //
117
145
  //*********************************************************************//
118
146
 
147
+ //*********************************************************************//
148
+ // ---------------------- internal transactions ---------------------- //
149
+ //*********************************************************************//
150
+
119
151
  //*********************************************************************//
120
152
  // ----------------------- internal helpers -------------------------- //
121
153
  //*********************************************************************//
@@ -134,17 +166,28 @@ contract JBExample is JBPermissioned, IJBExample {
134
166
  1. Custom errors
135
167
  2. Public constants
136
168
  3. Internal constants
137
- 4. Public immutable stored properties
138
- 5. Internal immutable stored properties
139
- 6. Public stored properties
140
- 7. Internal stored properties
141
- 8. Constructor
142
- 9. External transactions
143
- 10. External views
144
- 11. Public transactions
145
- 12. Internal helpers
146
- 13. Internal views
147
- 14. Private helpers
169
+ 4. Private constants
170
+ 5. Public immutable stored properties
171
+ 6. Internal immutable stored properties
172
+ 7. Public stored properties
173
+ 8. Internal stored properties
174
+ 9. Private stored properties
175
+ 10. Transient stored properties
176
+ 11. Constructor
177
+ 12. Modifiers
178
+ 13. Receive / fallback
179
+ 14. External transactions
180
+ 15. External views
181
+ 16. Public views
182
+ 17. Public transactions
183
+ 18. Internal transactions
184
+ 19. Internal helpers
185
+ 20. Internal views
186
+ 21. Private helpers
187
+
188
+ Use these additional section labels where they better match the contents of the block:
189
+ - `internal functions` is accepted as equivalent to `internal helpers`
190
+ - `events` and `structs` are acceptable in specialized contracts that define them explicitly
148
191
 
149
192
  Functions are alphabetized within each section.
150
193
 
@@ -197,7 +240,7 @@ interface IJBExample is IJBBase {
197
240
  | Public/external function | `camelCase` | `cashOutTokensOf` |
198
241
  | Internal/private function | `_camelCase` | `_processFee` |
199
242
  | Internal storage | `_camelCase` | `_accountingContextForTokenOf` |
200
- | Function parameter | `camelCase` | `projectId`, `cashOutCount` |
243
+ | Function parameter | `camelCase` (no underscores) | `projectId`, `cashOutCount` |
201
244
 
202
245
  ## NatSpec
203
246
 
@@ -348,6 +391,7 @@ wrap_comments = true
348
391
 
349
392
  **Optional sections (add only when needed):**
350
393
  - `[rpc_endpoints]` — repos with fork tests. Maps named endpoints to env vars (e.g. `ethereum = "${RPC_ETHEREUM_MAINNET}"`).
394
+ - `[profile.ci_sizes]` — only when CI needs different optimizer settings than defaults for the size check step (e.g. `optimizer_runs = 200` when the default profile uses a lower value).
351
395
 
352
396
  **Common variations:**
353
397
  - `via_ir = true` when hitting stack-too-deep
@@ -563,9 +607,4 @@ CI checks formatting via `forge fmt --check`.
563
607
 
564
608
  ### Contract Size Checks
565
609
 
566
- CI runs `forge build --sizes` to catch contracts approaching the 24KB limit.
567
-
568
- ## Repo-Specific Deviations
569
-
570
- - **`optimizer_runs = 100`** — reduced for contract size compliance
571
- - **`npm install --omit=dev && npm install @sphinx-labs/contracts` in CI** — test files import deployment helpers from dependencies (`SuckerDeploymentLib`, `CroptopDeploymentLib`) that use `@sphinx-labs` contracts. Installing just the contracts package avoids the full `@sphinx-labs/plugins` tree (445+ Solidity files) that would bloat compilation
610
+ CI runs `forge build --sizes` to catch contracts approaching the 24KB limit. When the repo's default `optimizer_runs` differs from what you want for size checking, use `FOUNDRY_PROFILE=ci_sizes forge build --sizes` with a `[profile.ci_sizes]` section in `foundry.toml`.