@rev-net/core-v6 0.0.76 → 0.0.77

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/README.md CHANGED
@@ -31,6 +31,7 @@ Use this repo when the product is a treasury-backed network with encoded stage t
31
31
  | `REVDeployer` | Launches and configures Revnets, stages, operators, and optional auxiliary features. |
32
32
  | `REVOwner` | Runtime data-hook and cash-out-hook surface used by active Revnets. |
33
33
  | `REVLoans` | Loan surface that lets users borrow against Revnet tokens with burned collateral and NFT loan positions. |
34
+
34
35
  ## Mental Model
35
36
 
36
37
  Read the package in two halves:
@@ -59,6 +60,7 @@ Most mistakes come from assuming a deploy-time parameter can be changed later or
59
60
  - deployment-time configuration and operator envelope live in `REVDeployer`
60
61
  - runtime pay and cash-out behavior live in `REVOwner`
61
62
  - loan positions and loan-specific state live in `REVLoans`
63
+
62
64
  ## High-Signal Tests
63
65
 
64
66
  1. `test/REVLifecycle.t.sol`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rev-net/core-v6",
3
- "version": "0.0.76",
3
+ "version": "0.0.77",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -69,7 +69,7 @@ Use this file when you need revnet-specific risks, state reads, constants, or ex
69
69
  | `REVLoans_InvalidPrepaidFeePercent(prepaidFeePercent, min, max)` | When `prepaidFeePercent` is outside the allowed range (2.5%--50%). |
70
70
  | `REVLoans_InvalidTerminal(terminal, revnetId)` | When the specified terminal is not registered for the revnet. |
71
71
  | `REVLoans_LoanExpired(timeSinceLoanCreated, loanLiquidationDuration)` | When trying to repay or reallocate an expired loan. |
72
- | `REVLoans_LoanIdOverflow()` | When the loan ID counter exceeds the per-revnet trillion-ID namespace. |
72
+ | `REVLoans_LoanIdOverflow()` | When the loan ID counter exceeds the per-revnet 1e18-ID namespace. |
73
73
  | `REVLoans_NewBorrowAmountGreaterThanLoanAmount(newBorrowAmount, loanAmount)` | When a reallocation would produce a reduced loan with a larger borrow amount than the original. |
74
74
  | `REVLoans_NoMsgValueAllowed()` | When `msg.value > 0` on a non-native-token repayment. |
75
75
  | `REVLoans_NotEnoughCollateral()` | When the caller does not have enough tokens for the requested collateral. |
@@ -104,7 +104,8 @@ Use this file when you need revnet-specific risks, state reads, constants, or ex
104
104
  | `MIN_PREPAID_FEE_PERCENT` | 25 (2.5%) | Minimum upfront fee borrowers must pay |
105
105
  | `MAX_PREPAID_FEE_PERCENT` | 500 (50%) | Maximum upfront fee |
106
106
  | `REV_PREPAID_FEE_PERCENT` | 10 (1%) | Protocol-level fee to $REV revnet |
107
- | `_ONE_TRILLION` | 1,000,000,000,000 | Loan ID generator base: `revnetId * 1T + loanNumber` |
107
+ | `_LOAN_ID_NAMESPACE_SIZE` | 1,000,000,000,000,000,000 | Loan ID generator base: `revnetId * namespaceSize + loanNumber` |
108
+ | `_MAX_LOAN_NUMBER` | 999,999,999,999,999,999 | Largest loan number that stays in the current revnet's ID namespace |
108
109
 
109
110
  ## Storage
110
111
 
@@ -144,19 +145,19 @@ Use this file when you need revnet-specific risks, state reads, constants, or ex
144
145
  1. **Revnets are permanently ownerless.** `REVOwner` holds the project NFT for every Revnet and exposes no function to release it. Stage parameters cannot be changed after deployment.
145
146
  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.
146
147
  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.
147
- 4. **Loan ID encoding.** `loanId = revnetId * 1_000_000_000_000 + loanNumber`. Each revnet supports ~1 trillion loans. Use `revnetIdOfLoanWith(loanId)` to decode.
148
+ 4. **Loan ID encoding.** `loanId = revnetId * 1_000_000_000_000_000_000 + loanNumber`. Each revnet supports loan numbers up to `999_999_999_999_999_999`; `1_000_000_000_000_000_000` is the next revnet's namespace. Use `revnetIdOfLoanWith(loanId)` to decode.
148
149
  5. **uint112 truncation risk.** `REVLoan.amount` and `REVLoan.collateral` are `uint112`. Values above ~5.19e33 truncate silently.
149
150
  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.
150
151
  7. **Cash-out fee stacking.** Non-zero-tax ordinary 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% revnet 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. Zero-tax ordinary cash-outs route through the buyback hook without adding the revnet fee hook, matching current code behavior. This is by design.
151
152
  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 populated by REVDeployer during deployment via the bundled `initializeRevnet()` call. REVLoans imports IREVOwner (not IREVDeployer) to read it.
152
153
  9. **`cashOutTaxRate` cannot be MAX.** Must be strictly less than `MAX_CASH_OUT_TAX_RATE` (10,000). Revnets cannot fully disable cash outs.
153
154
  10. **Split operator is singular.** Only ONE address can be operator at a time. The operator can replace itself via `setOperatorOf` but cannot delegate or multi-sig.
154
- 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.
155
+ 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 or accounting-context differences.
155
156
  12. **Loan source array is unbounded.** `_loanSourceTokensOf[revnetId]` grows without limit, bounded in practice by the token accounting contexts accepted by the canonical `MULTI_TERMINAL`.
156
157
  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.
157
158
  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).
158
159
  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.
159
- 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.
160
+ 16. **Cross-chain config matching.** `hashedEncodedConfigurationOf` covers deploy-time revnet identity and economics: base currency, scope flag, name, ticker, salt, stage timing, issuance, cash-out taxes, extra metadata, and auto-issuances. It does not cover terminal configurations, accounting contexts, sucker token mappings, or mutable split recipients. Two deployments with identical hashes can still differ in those external surfaces.
160
161
  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.
161
162
  18. **Permit2 fallback.** `REVLoans` uses permit2 for ERC-20 transfers as a fallback when standard allowance is insufficient. Wrapped in try-catch.
162
163
  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`.
@@ -214,7 +215,7 @@ Quick-reference for common read operations. All functions are `view`/`pure` and
214
215
 
215
216
  | What | Call | Returns |
216
217
  |------|------|---------|
217
- | Remaining auto-issuance for beneficiary | `REVDeployer.amountToAutoIssue(revnetId, stageId, beneficiary)` | `uint256` (0 if already claimed) |
218
+ | Remaining auto-issuance for beneficiary | `REVOwner.amountToAutoIssue(revnetId, stageId, beneficiary)` | `uint256` (0 if already claimed) |
218
219
 
219
220
  ### Loans
220
221
 
@@ -315,7 +316,7 @@ loans.borrowFrom({
315
316
  beneficiary: payable(msg.sender), // Receive new loan proceeds
316
317
  prepaidFeePercent: 25 // 2.5% prepaid fee on new loan
317
318
  });
318
- // Result: original loan now has 500 fewer collateral tokens (reallocatedLoanId),
319
+ // Result: original loan has 500 fewer collateral tokens (reallocatedLoanId),
319
320
  // new loan has 700 tokens of collateral (newLoanId).
320
321
 
321
322
  // --- Repay a loan ---
@@ -11,7 +11,7 @@ Deploy and manage Revnets -- autonomous, unowned Juicebox projects with staged i
11
11
  | Contract | Role |
12
12
  |----------|------|
13
13
  | `REVDeployer` | Deploys revnets. Configures stages, splits, auto-issuance amounts, buyback hooks, suckers, operators, and stores `hashedEncodedConfigurationOf`. Exposes `OWNER()` view returning the REVOwner address. Hands the JBProjects NFT to REVOwner mid-deploy and finishes by bundling every per-revnet runtime write into a single `REVOwner.initializeRevnet(revnetId, init)` call (cash-out delay, tiered 721 hook, auto-issuance allocations, extra operator permissions, the initial operator, and any integration grants). |
14
- | `REVOwner` | Project NFT owner for every revnet and runtime hook for all revnets. Implements `IJBRulesetDataHook` + `IJBCashOutHook` + `IERC721Receiver`. Set as the `dataHook` in each revnet's ruleset metadata. Holds the JBProjects NFT (project owner of record). Manages operator permissions (grants `DEPLOY_SUCKERS` + `MAP_SUCKER_TOKEN` to REVDeployer in `setDeployer` so the deployer can continue driving sucker setup). Exposes `initializeRevnet` (deployer-only single-call setup), `autoIssueFor`, `burnHeldTokensOf`, `setOperatorOf`, `isOperatorOf`. Handles pay hooks, cash-out hooks, mint permissions, and sucker verification. Stores `cashOutDelayOf`, `tiered721HookOf`, `amountToAutoIssue`, and extra-operator-permission state. |
14
+ | `REVOwner` | Project NFT owner and runtime hook for every revnet. Implements `IJBRulesetDataHook` + `IJBCashOutHook` + `IERC721Receiver`. Set as the `dataHook` in each revnet's ruleset metadata. Holds the JBProjects NFT (project owner of record). Manages operator permissions (grants `DEPLOY_SUCKERS` + `MAP_SUCKER_TOKEN` to REVDeployer in `setDeployer` so the deployer can continue driving sucker setup). Exposes `initializeRevnet` (deployer-only single-call setup), `autoIssueFor`, `burnHeldTokensOf`, `setOperatorOf`, `isOperatorOf`. Handles pay hooks, cash-out hooks, mint permissions, and sucker verification. Stores `cashOutDelayOf`, `tiered721HookOf`, `amountToAutoIssue`, and extra-operator-permission state. |
15
15
  | `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). |
16
16
 
17
17
  ## Key Functions
@@ -66,7 +66,7 @@ Deploy and manage Revnets -- autonomous, unowned Juicebox projects with staged i
66
66
  | `REVLoans.determineSourceFeeAmount(loan, amount)` | Calculate the time-proportional source fee for a loan repayment. Zero during prepaid window, linear accrual after. |
67
67
  | `REVLoans.loanOf(loanId)` | Returns the full `REVLoan` struct for a loan. |
68
68
  | `REVLoans.loanSourceTokensOf(revnetId)` | Returns all token sources used for loans by a revnet. Loans always source funds from the canonical `MULTI_TERMINAL`. |
69
- | `REVLoans.revnetIdOfLoanWith(loanId)` | Decode the revnet ID from a loan ID (`loanId / 1_000_000_000_000`). |
69
+ | `REVLoans.revnetIdOfLoanWith(loanId)` | Decode the revnet ID from a loan ID (`loanId / 1_000_000_000_000_000_000`). |
70
70
  ## Integration Points
71
71
 
72
72
  | Dependency | Import | Used For |
@@ -54,17 +54,17 @@ struct FeeProjectConfig {
54
54
  }
55
55
 
56
56
  contract DeployScript is Script, Sphinx {
57
- /// @notice tracks the deployment of the buyback hook.
57
+ /// @notice Tracks the deployment of the buyback hook.
58
58
  BuybackDeployment buybackHook;
59
- /// @notice tracks the deployment of the core contracts for the chain we are deploying to.
59
+ /// @notice Tracks the deployment of the core contracts for the chain we are deploying to.
60
60
  CoreDeployment core;
61
- /// @notice tracks the deployment of the croptop contracts for the chain we are deploying to.
61
+ /// @notice Tracks the deployment of the Croptop contracts for the chain we are deploying to.
62
62
  CroptopDeployment croptop;
63
- /// @notice tracks the deployment of the 721 hook contracts for the chain we are deploying to.
63
+ /// @notice Tracks the deployment of the 721 hook contracts for the chain we are deploying to.
64
64
  Hook721Deployment hook;
65
- /// @notice tracks the deployment of the sucker contracts for the chain we are deploying to.
65
+ /// @notice Tracks the deployment of the sucker contracts for the chain we are deploying to.
66
66
  SuckerDeployment suckers;
67
- /// @notice tracks the deployment of the router terminal registry package.
67
+ /// @notice Tracks the deployment of the router terminal registry package.
68
68
  RouterTerminalDeployment routerTerminal;
69
69
  uint32 private constant _PREMINT_CHAIN_ID = 1;
70
70
  string private constant _NAME = "Revnet";
@@ -102,7 +102,7 @@ contract DeployScript is Script, Sphinx {
102
102
  // Get the loans owner address.
103
103
  loansOwner = safeAddress();
104
104
 
105
- // Get the deployment addresses for the nana CORE for this chain.
105
+ // Get the core deployment addresses for this chain.
106
106
  // We want to do this outside of the `sphinx` modifier.
107
107
  core = CoreDeploymentLib.getDeployment(
108
108
  vm.envOr({
@@ -116,13 +116,13 @@ contract DeployScript is Script, Sphinx {
116
116
  defaultValue: string("node_modules/@bananapus/suckers-v6/deployments/")
117
117
  })
118
118
  );
119
- // Get the deployment addresses for the 721 hook contracts for this chain.
119
+ // Get the Croptop deployment addresses for this chain.
120
120
  croptop = CroptopDeploymentLib.getDeployment(
121
121
  vm.envOr({
122
122
  name: "CROPTOP_CORE_DEPLOYMENT_PATH", defaultValue: string("node_modules/@croptop/core-v6/deployments/")
123
123
  })
124
124
  );
125
- // Get the deployment addresses for the 721 hook contracts for this chain.
125
+ // Get the buyback hook deployment addresses for this chain.
126
126
  hook = Hook721DeploymentLib.getDeployment(
127
127
  vm.envOr({
128
128
  name: "NANA_721_DEPLOYMENT_PATH",
@@ -223,7 +223,7 @@ contract DeployScript is Script, Sphinx {
223
223
  autoIssuances: new REVAutoIssuance[](0),
224
224
  splitPercent: 3800, // 38%
225
225
  splits: splits,
226
- initialIssuance: 0, // no more issaunce.
226
+ initialIssuance: 0, // no more issuance.
227
227
  issuanceCutFrequency: 0,
228
228
  issuanceCutPercent: 0,
229
229
  cashOutTaxRate: 1000, // 0.1
@@ -316,24 +316,11 @@ contract DeployScript is Script, Sphinx {
316
316
  }
317
317
 
318
318
  function deploy() public sphinx {
319
- // Check if singletons are already deployed before creating a new fee project.
320
- // This prevents creating orphan projects on script restarts.
319
+ // Check whether the deterministic singletons already exist before creating a new fee project.
320
+ // This prevents orphan projects on script restarts.
321
321
  uint256 feeProjectId;
322
322
 
323
- // Predict the REVLoans address for an arbitrary fee project ID to check if it exists.
324
- // We can't predict without a fee project ID, so we first check the REVDeployer which also stores it.
325
- // Try a two-step approach: compute addresses assuming singletons exist, then check.
326
-
327
- // First, check if REVLoans is already deployed by trying with project ID = 0 (placeholder).
328
- // We need to iterate: if either singleton exists, extract the fee project ID from it.
329
- // Since both encode feeProjectId, we check if any code exists at the predicted address
330
- // for sequential project IDs starting from 1.
331
-
332
- // A simpler approach: predict the REVDeployer address for each possible fee project ID
333
- // until we find one that's deployed, or give up and create a new one.
334
- // In practice, the fee project is always one of the first few projects created.
335
-
336
- // Check the next project ID that would be created — if singletons were deployed with a previous ID,
323
+ // Check the next project ID that would be created. If singletons were deployed with a previous ID,
337
324
  // that ID must be less than the current count.
338
325
  uint256 _nextProjectId = core.projects.count() + 1;
339
326
 
@@ -47,13 +47,12 @@ import {REVOwnerRevnetInit} from "./structs/REVOwnerRevnetInit.sol";
47
47
  import {REVStageConfig} from "./structs/REVStageConfig.sol";
48
48
  import {REVSuckerDeploymentConfig} from "./structs/REVSuckerDeploymentConfig.sol";
49
49
 
50
- /// @notice Deploys and configures Revnets autonomous Juicebox projects with pre-programmed tokenomics that cannot
51
- /// be
52
- /// changed after launch. Each revnet progresses through stages that define issuance rate, decay schedule, cash-out tax,
53
- /// split allocations, and auto-issuances. The deployer translates these stage configurations into Juicebox rulesets,
54
- /// sets up a buyback hook for secondary market routing, deploys a tiered 721 hook, optionally configures Croptop
55
- /// posting, and can deploy cross-chain suckers. Once deployed, the project NFT is held by this contract — no single
56
- /// address can modify the revnet's rules.
50
+ /// @notice Deploys and configures Revnets: autonomous Juicebox projects with immutable staged tokenomics.
51
+ /// @dev Each revnet progresses through stages that define issuance rate, decay schedule, cash-out tax, split
52
+ /// allocations, and auto-issuances. The deployer translates these stage configurations into Juicebox rulesets, sets up
53
+ /// a buyback hook for secondary market routing, deploys a tiered 721 hook, optionally configures Croptop posting, and
54
+ /// can deploy cross-chain suckers. Once deployed, the project NFT is held by this contract no single address can
55
+ /// modify the revnet's rules.
57
56
  /// @dev Revnets are unowned Juicebox projects which operate autonomously after deployment. Runtime data hook logic
58
57
  /// (pay/cash-out callbacks) is handled by the separate `REVOwner` contract to stay within EIP-170 size limits.
59
58
  contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
@@ -106,21 +105,21 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
106
105
  /// @notice The directory of terminals and controllers for Juicebox projects (and revnets).
107
106
  IJBDirectory public immutable override DIRECTORY;
108
107
 
109
- /// @notice The Juicebox project ID of the revnet that receives cash out fees.
108
+ /// @notice The Juicebox project ID of the revnet that receives cash-out fees.
110
109
  uint256 public immutable override FEE_REVNET_ID;
111
110
 
112
111
  /// @notice Deploys tiered ERC-721 hooks for revnets.
113
112
  IJB721TiersHookDeployer public immutable override HOOK_DEPLOYER;
114
113
 
115
- /// @notice The loan contract used by all revnets.
114
+ /// @notice The loan contract used by every revnet.
116
115
  /// @dev Revnets can offer loans to their participants, collateralized by their tokens.
117
- /// Participants can borrow up to the current cash out value of their tokens.
116
+ /// Participants can borrow up to the current cash-out value of their tokens.
118
117
  IREVLoans public immutable override LOANS;
119
118
 
120
119
  /// @notice The canonical terminal that holds revnet treasury balances.
121
120
  IJBTerminal public immutable override MULTI_TERMINAL;
122
121
 
123
- /// @notice The runtime data hook contract that handles pay and cash out callbacks for revnets.
122
+ /// @notice The runtime data hook contract that handles pay and cash-out callbacks for revnets.
124
123
  /// @dev Set as `dataHook` in each revnet's ruleset metadata. Implements `IJBRulesetDataHook` and `IJBCashOutHook`.
125
124
  address public immutable override OWNER;
126
125
 
@@ -162,11 +161,11 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
162
161
  /// @param suckerRegistry The registry to use for deploying and tracking each revnet's suckers.
163
162
  /// @param feeRevnetId The Juicebox project ID of the revnet that will receive fees.
164
163
  /// @param hookDeployer The deployer to use for revnet's tiered ERC-721 hooks.
165
- /// @param publisher The croptop publisher revnets can use to publish ERC-721 posts to their tiered ERC-721 hooks.
164
+ /// @param publisher The Croptop publisher revnets can use to publish ERC-721 posts to their tiered ERC-721 hooks.
166
165
  /// @param buybackHook The buyback hook used as a data hook to route payments through buyback pools.
167
- /// @param loans The loan contract used by all revnets.
166
+ /// @param loans The loan contract used by every revnet.
168
167
  /// @param trustedForwarder The trusted forwarder for the ERC2771Context.
169
- /// @param owner The runtime data hook contract (REVOwner) that handles pay and cash out callbacks.
168
+ /// @param owner The runtime data hook contract (REVOwner) that handles pay and cash-out callbacks.
170
169
  constructor(
171
170
  IJBController controller,
172
171
  IJBTerminal multiTerminal,
@@ -301,11 +300,11 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
301
300
  metadata.cashOutTaxRate = stageConfiguration.cashOutTaxRate;
302
301
  metadata.baseCurrency = baseCurrency;
303
302
  metadata.scopeCashOutsToLocalBalances = scopeCashOutsToLocalBalances;
304
- metadata.allowOwnerMinting = true; // Allow this contract to auto-mint tokens as the revnet's owner.
305
- metadata.useDataHookForPay = true; // Call this contract's `beforePayRecordedWith(…)` callback on payments.
306
- metadata.useDataHookForCashOut = true; // Call this contract's `beforeCashOutRecordedWith(…)` callback on cash
307
- // outs.
308
- metadata.dataHook = OWNER; // The REVOwner contract is the data hook.
303
+ // Allow `REVOwner` to auto-mint configured allocations and handle pay/cash-out callbacks.
304
+ metadata.allowOwnerMinting = true;
305
+ metadata.useDataHookForPay = true;
306
+ metadata.useDataHookForCashOut = true;
307
+ metadata.dataHook = OWNER;
309
308
  metadata.metadata = stageConfiguration.extraMetadata;
310
309
 
311
310
  // Package the reserved token splits.
@@ -449,7 +448,7 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
449
448
  sqrtPriceX96: sqrtPriceX96
450
449
  }) {}
451
450
  catch {
452
- // Two failure modes both end up here and both must NOT block the revnet deploy:
451
+ // Two failure modes both end up here and both must not block the revnet deploy:
453
452
  // 1. The V4 pool is already initialized at the expected price (idempotent re-deploy). The buyback
454
453
  // hook's strict price check inside `initializePoolFor` would still call `_setPoolFor`, so the
455
454
  // try-branch already covered this. We reach this catch only when the check rejected the existing
@@ -470,7 +469,7 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
470
469
  /// @dev When initializing an existing project (revnetId != 0):
471
470
  /// - The project must not yet have a controller or rulesets. `JBController.launchRulesetsFor` enforces this —
472
471
  /// it reverts if rulesets have already been launched, and `JBDirectory.setControllerOf` only allows setting the
473
- /// first controller. This means conversion only works for blank projects (just an ID with no on-chain state).
472
+ /// first controller. This means conversion only works for blank projects: an ID with no on-chain state.
474
473
  /// - This is useful in deploy scripts where the project ID is needed before configuration (e.g. for cross-chain
475
474
  /// sucker peer mappings): create the project first, then initialize it as a revnet here.
476
475
  /// - Initialization is a one-way operation: the project's ownership NFT is permanently transferred to this
@@ -480,7 +479,7 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
480
479
  /// @param accountingContextsToAccept The accounting contexts the canonical multi terminal should accept.
481
480
  /// @param suckerDeploymentConfiguration The suckers to set up for cross-chain token transfers.
482
481
  /// @param tiered721HookConfiguration How to configure the tiered ERC-721 hook for the revnet.
483
- /// @param allowedPosts Restrictions on which croptop posts to allow on the revnet's ERC-721 tiers.
482
+ /// @param allowedPosts Restrictions on which Croptop posts to allow on the revnet's ERC-721 tiers.
484
483
  /// @return revnetId The ID of the newly created revnet.
485
484
  /// @return hook The address of the tiered ERC-721 hook deployed for the revnet.
486
485
  // The deployment flow makes external setup calls, but any observed state is revnet-scoped and reverts atomically.
@@ -507,7 +506,7 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
507
506
  revert REVDeployer_ProjectCreationFeeNotNeeded({revnetId: revnetId, value: msg.value});
508
507
  }
509
508
 
510
- // Deploy the revnet with the specified tiered ERC-721 hook and croptop posting criteria.
509
+ // Deploy the revnet with the specified tiered ERC-721 hook and Croptop posting criteria.
511
510
  hook = _deploy721RevnetFor({
512
511
  revnetId: revnetId,
513
512
  shouldDeployNewRevnet: shouldDeployNewRevnet,
@@ -619,7 +618,7 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
619
618
  // --------------------- internal transactions ----------------------- //
620
619
  //*********************************************************************//
621
620
 
622
- /// @notice Deploy a revnet which sells tiered ERC-721s and (optionally) allows croptop posts to its ERC-721 tiers.
621
+ /// @notice Deploy a revnet which sells tiered ERC-721s and optionally allows Croptop posts to its ERC-721 tiers.
623
622
  // The helper performs external hook/post setup after core revnet setup; any failure reverts the whole deployment.
624
623
  function _deploy721RevnetFor(
625
624
  uint256 revnetId,
@@ -726,7 +725,7 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
726
725
  // deployer passes that check directly only before ownership is scoped to REVOwner below.
727
726
  PUBLISHER.configurePostingCriteriaFor({allowedPosts: formattedAllowedPosts});
728
727
 
729
- // Give the croptop publisher permission to post new ERC-721 tiers on the revnet's behalf.
728
+ // Give the Croptop publisher permission to post new ERC-721 tiers on the revnet's behalf.
730
729
  ownerInit.extraGrants = new REVOwnerExtraGrant[](1);
731
730
  ownerInit.extraGrants[0] =
732
731
  REVOwnerExtraGrant({operator: address(PUBLISHER), permissionId: JBPermissionIds.ADJUST_721_TIERS});
@@ -798,7 +797,7 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
798
797
  memo: ""
799
798
  });
800
799
 
801
- // Compute the cash out delay if the revnet's stages are already in progress. This prevents cash out
800
+ // Compute the cash-out delay if the revnet's stages are already in progress. This prevents cash-out
802
801
  // liquidity/arbitrage issues for existing revnets which are deploying to a new chain.
803
802
  ownerInit.cashOutDelay =
804
803
  _computeCashOutDelayIfNeeded({revnetId: revnetId, firstStageConfig: configuration.stageConfigurations[0]});
@@ -951,11 +950,11 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
951
950
 
952
951
  // Iterate through each stage to set up its ruleset.
953
952
  for (uint256 i; i < configuration.stageConfigurations.length;) {
954
- // Set the stage being iterated on.
953
+ // Read the stage being converted into a ruleset.
955
954
  REVStageConfig calldata stageConfiguration = configuration.stageConfigurations[i];
956
955
 
957
956
  // Make sure the revnet has at least one split if it has a split percent.
958
- // Otherwise, the split would go to this contract since its the revnet's owner.
957
+ // Otherwise, the reserved-token residue would stay with this contract as the revnet's owner.
959
958
  if (stageConfiguration.splitPercent > 0 && stageConfiguration.splits.length == 0) {
960
959
  revert REVDeployer_MustHaveSplits({stageIndex: i, splitPercent: stageConfiguration.splitPercent});
961
960
  }
@@ -975,7 +974,7 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
975
974
  // Store for the next iteration's ordering check.
976
975
  previousStageStart = effectiveStart;
977
976
 
978
- // Make sure the revnet doesn't prevent cashouts all together.
977
+ // Make sure the revnet does not disable cash-outs completely.
979
978
  if (stageConfiguration.cashOutTaxRate >= JBConstants.MAX_CASH_OUT_TAX_RATE) {
980
979
  revert REVDeployer_CashOutsCantBeTurnedOffCompletely({
981
980
  cashOutTaxRate: stageConfiguration.cashOutTaxRate,
@@ -1055,12 +1054,12 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
1055
1054
  encodedConfigurationHash = keccak256(encodedConfiguration);
1056
1055
  }
1057
1056
 
1058
- /// @notice Compute the cash out delay for the revnet's first stage if its stages are already in progress.
1057
+ /// @notice Compute the cash-out delay for the revnet's first stage if its stages are already in progress.
1059
1058
  /// @dev Returns 0 when no delay is needed. Emits `SetCashOutDelay` so deployer-side telemetry stays unchanged
1060
1059
  /// even though the actual storage write happens later inside `REVOwner.initializeRevnet`.
1061
- /// @param revnetId The ID of the revnet to compute the cash out delay for.
1060
+ /// @param revnetId The ID of the revnet to compute the cash-out delay for.
1062
1061
  /// @param firstStageConfig The revnet's first stage.
1063
- /// @return cashOutDelay The timestamp after which cash outs are allowed, or 0 if no delay applies.
1062
+ /// @return cashOutDelay The timestamp after which cash-outs are allowed, or 0 if no delay applies.
1064
1063
  function _computeCashOutDelayIfNeeded(
1065
1064
  uint256 revnetId,
1066
1065
  REVStageConfig calldata firstStageConfig
@@ -1069,11 +1068,11 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
1069
1068
  returns (uint256 cashOutDelay)
1070
1069
  {
1071
1070
  // If this is the first revnet being deployed (with a `startsAtOrAfter` of 0),
1072
- // or if the first stage hasn't started yet, we don't need to set a cash out delay.
1071
+ // or if the first stage hasn't started yet, we don't need to set a cash-out delay.
1073
1072
  // forge-lint: disable-next-line(block-timestamp)
1074
1073
  if (firstStageConfig.startsAtOrAfter == 0 || firstStageConfig.startsAtOrAfter >= block.timestamp) return 0;
1075
1074
 
1076
- // Calculate the timestamp at which the cash out delay ends.
1075
+ // Calculate the timestamp at which the cash-out delay ends.
1077
1076
  cashOutDelay = block.timestamp + CASH_OUT_DELAY;
1078
1077
 
1079
1078
  emit SetCashOutDelay({revnetId: revnetId, cashOutDelay: cashOutDelay, caller: _msgSender()});
package/src/REVLoans.sol CHANGED
@@ -41,8 +41,8 @@ import {REVLoan} from "./structs/REVLoan.sol";
41
41
  /// revnet's token structure orderly. Each loan is represented as an ERC-721 NFT that can be transferred.
42
42
  /// @dev Fee structure: an upfront fee is taken at borrow time. 2.5% goes to the source revnet
43
43
  /// (MIN_PREPAID_FEE_PERCENT), 1% goes to the $REV revnet (REV_PREPAID_FEE_PERCENT), and a variable amount chosen by the
44
- /// borrower determines the
45
- /// prepaid duration — the more paid upfront, the longer the borrower can hold without additional cost. After the
44
+ /// borrower determines the prepaid duration — the more paid upfront, the longer the borrower can hold without
45
+ /// additional cost. After the
46
46
  /// prepaid duration expires, the repayment cost increases linearly until the loan liquidates at 10 years
47
47
  /// (LOAN_LIQUIDATION_DURATION), at which point the collateral is permanently lost.
48
48
  /// @dev The loaned amounts include the fees taken, meaning the amount paid back is the amount borrowed plus the fees.
@@ -104,9 +104,12 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
104
104
  // ------------------------ private constants ------------------------ //
105
105
  //*********************************************************************//
106
106
 
107
- /// @notice Just a kind reminder to our readers.
108
- /// @dev Used in loan token ID generation.
109
- uint256 private constant _ONE_TRILLION = 1_000_000_000_000;
107
+ /// @notice The number of loan IDs reserved for each revnet.
108
+ /// @dev Loan IDs are encoded as `revnetId * _LOAN_ID_NAMESPACE_SIZE + loanNumber`.
109
+ uint256 private constant _LOAN_ID_NAMESPACE_SIZE = 1_000_000_000_000_000_000;
110
+
111
+ /// @dev This is the largest loan number that stays in the current revnet's ID namespace.
112
+ uint256 private constant _MAX_LOAN_NUMBER = _LOAN_ID_NAMESPACE_SIZE - 1;
110
113
 
111
114
  //*********************************************************************//
112
115
  // --------------- public immutable stored properties ---------------- //
@@ -171,8 +174,8 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
171
174
  // --------------------- internal stored properties ------------------ //
172
175
  //*********************************************************************//
173
176
 
174
- /// @notice The loans.
175
- /// @custom:member The ID of the loan.
177
+ /// @notice The loan state for each loan ID.
178
+ /// @custom:param loanId The ID of the loan.
176
179
  mapping(uint256 loanId => REVLoan) internal _loanOf;
177
180
 
178
181
  /// @notice The sources of each revnet's loan.
@@ -308,7 +311,7 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
308
311
  /// @param loanId The loan ID to look up.
309
312
  /// @return The ID of the revnet.
310
313
  function revnetIdOfLoanWith(uint256 loanId) public pure override returns (uint256) {
311
- return loanId / _ONE_TRILLION;
314
+ return loanId / _LOAN_ID_NAMESPACE_SIZE;
312
315
  }
313
316
 
314
317
  /// @notice Returns the URI where the ERC-721 standard JSON of a loan is hosted.
@@ -518,7 +521,7 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
518
521
  /// @param loanNumber The loan number within the revnet.
519
522
  /// @return The token ID of the 721.
520
523
  function _generateLoanId(uint256 revnetId, uint256 loanNumber) internal pure returns (uint256) {
521
- return (revnetId * _ONE_TRILLION) + loanNumber;
524
+ return (revnetId * _LOAN_ID_NAMESPACE_SIZE) + loanNumber;
522
525
  }
523
526
 
524
527
  /// @notice The calldata. Preferred to use over `msg.data`.
@@ -689,15 +692,15 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
689
692
 
690
693
  // Prevent cross-revnet accounting corruption: every iterated loan number must stay within the revnet's ID
691
694
  // namespace.
692
- if (startingLoanId > _ONE_TRILLION) {
695
+ if (startingLoanId > _MAX_LOAN_NUMBER) {
693
696
  revert REVLoans_LoanIdOverflow({
694
- revnetId: revnetId, loanNumber: startingLoanId, maxLoanNumber: _ONE_TRILLION
697
+ revnetId: revnetId, loanNumber: startingLoanId, maxLoanNumber: _MAX_LOAN_NUMBER
695
698
  });
696
699
  }
697
- uint256 maxCount = _ONE_TRILLION - startingLoanId + 1;
700
+ uint256 maxCount = _MAX_LOAN_NUMBER - startingLoanId + 1;
698
701
  if (count > maxCount) {
699
702
  revert REVLoans_LoanIdOverflow({
700
- revnetId: revnetId, loanNumber: _ONE_TRILLION + 1, maxLoanNumber: _ONE_TRILLION
703
+ revnetId: revnetId, loanNumber: _LOAN_ID_NAMESPACE_SIZE, maxLoanNumber: _MAX_LOAN_NUMBER
701
704
  });
702
705
  }
703
706
 
@@ -1388,8 +1391,10 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
1388
1391
  /// @return loanId The allocated loan ID.
1389
1392
  function _nextLoanIdFor(uint256 revnetId) internal returns (uint256 loanId) {
1390
1393
  uint256 loanNumber = totalLoansBorrowedFor[revnetId] + 1;
1391
- if (loanNumber > _ONE_TRILLION) {
1392
- revert REVLoans_LoanIdOverflow({revnetId: revnetId, loanNumber: loanNumber, maxLoanNumber: _ONE_TRILLION});
1394
+ if (loanNumber > _MAX_LOAN_NUMBER) {
1395
+ revert REVLoans_LoanIdOverflow({
1396
+ revnetId: revnetId, loanNumber: loanNumber, maxLoanNumber: _MAX_LOAN_NUMBER
1397
+ });
1393
1398
  }
1394
1399
  totalLoansBorrowedFor[revnetId] = loanNumber;
1395
1400
  return _generateLoanId({revnetId: revnetId, loanNumber: loanNumber});
package/src/REVOwner.sol CHANGED
@@ -38,10 +38,10 @@ import {REVOwnerAutoIssuance} from "./structs/REVOwnerAutoIssuance.sol";
38
38
  import {REVOwnerExtraGrant} from "./structs/REVOwnerExtraGrant.sol";
39
39
  import {REVOwnerRevnetInit} from "./structs/REVOwnerRevnetInit.sol";
40
40
 
41
- /// @notice The runtime hook for all revnets — set as every revnet's `dataHook` in ruleset metadata. At pay time, it
41
+ /// @notice The runtime hook for every revnet — set as each revnet's `dataHook` in ruleset metadata. At pay time, it
42
42
  /// coordinates the 721 hook (NFT tier minting) with the buyback hook (secondary market swap routing) and scales weight
43
43
  /// for split deductions. At cash-out time, it aggregates cross-chain total supply and surplus (including outstanding
44
- /// loan debt and collateral), grants suckers 0% tax, splits a 2.5% fee from non-sucker cash outs with a non-zero
44
+ /// loan debt and collateral), grants suckers 0% tax, splits a 2.5% fee from non-sucker cash-outs with a non-zero
45
45
  /// cash-out tax, and routes fee proceeds to the fee revnet via `afterCashOutRecordedWith`.
46
46
  /// @dev Separated from `REVDeployer` to stay within the EIP-170 contract size limit. Also implements
47
47
  /// `IJBPeerChainAdjustedAccounts` to expose loan state to peer-chain supply/surplus snapshots.
@@ -73,10 +73,10 @@ contract REVOwner is IREVOwner, IJBRulesetDataHook, IJBCashOutHook, IJBPeerChain
73
73
  /// @notice The directory of terminals and controllers for Juicebox projects.
74
74
  IJBDirectory public immutable override DIRECTORY;
75
75
 
76
- /// @notice The Juicebox project ID of the revnet that receives cash out fees.
76
+ /// @notice The Juicebox project ID of the revnet that receives cash-out fees.
77
77
  uint256 public immutable override FEE_REVNET_ID;
78
78
 
79
- /// @notice The loan contract used by all revnets.
79
+ /// @notice The loan contract used by every revnet.
80
80
  IREVLoans public immutable override LOANS;
81
81
 
82
82
  /// @notice Deploys and tracks suckers for revnets.
@@ -86,9 +86,9 @@ contract REVOwner is IREVOwner, IJBRulesetDataHook, IJBCashOutHook, IJBPeerChain
86
86
  // --------------------- public stored properties -------------------- //
87
87
  //*********************************************************************//
88
88
 
89
- /// @notice The timestamp of when cashouts will become available to a specific revnet's participants.
89
+ /// @notice The timestamp when cash-outs become available to a specific revnet's participants.
90
90
  /// @dev Only applies to existing revnets deploying onto a new network.
91
- /// @custom:param revnetId The ID of the revnet to check the cash out delay for.
91
+ /// @custom:param revnetId The ID of the revnet to check the cash-out delay for.
92
92
  mapping(uint256 revnetId => uint256 cashOutDelay) public override cashOutDelayOf;
93
93
 
94
94
  /// @notice Each revnet's tiered ERC-721 hook.
@@ -107,7 +107,7 @@ contract REVOwner is IREVOwner, IJBRulesetDataHook, IJBCashOutHook, IJBPeerChain
107
107
  /// @dev Set once via `setDeployer()` using the precomputed canonical REVDeployer address.
108
108
  IREVDeployer public override deployer;
109
109
 
110
- /// @notice The controller used by all revnets to manage their projects.
110
+ /// @notice The controller used by every revnet to manage its project.
111
111
  /// @dev Cached from the deployer at `setDeployer()` time.
112
112
  IJBController public override CONTROLLER;
113
113
 
@@ -165,7 +165,7 @@ contract REVOwner is IREVOwner, IJBRulesetDataHook, IJBCashOutHook, IJBPeerChain
165
165
  // ------------------------- external views -------------------------- //
166
166
  //*********************************************************************//
167
167
 
168
- /// @notice Called before a cash out is recorded. Suckers get 0% tax (bridged tokens redeem at face value). For
168
+ /// @notice Called before a cash-out is recorded. Suckers get 0% tax (bridged tokens redeem at face value). For
169
169
  /// regular holders, aggregates cross-chain total supply and surplus (including outstanding loan debt/collateral),
170
170
  /// splits a 2.5% fee from the cashed-out token count when cash-out tax is non-zero, computes bonding curve
171
171
  /// reclaims for both the holder's portion and the fee portion, then delegates to the buyback hook for potential
@@ -173,12 +173,12 @@ contract REVOwner is IREVOwner, IJBRulesetDataHook, IJBCashOutHook, IJBPeerChain
173
173
  /// @dev Part of `IJBRulesetDataHook`. In the non-zero-tax fee path, REVOwner is intentionally not registered as a
174
174
  /// feeless address — the protocol fee (2.5%) applies on top of the rev fee. The fee hook spec amount sent to
175
175
  /// `afterCashOutRecordedWith` will have the protocol fee deducted by the terminal before reaching this contract.
176
- /// @param context Standard Juicebox cash out context. See `JBBeforeCashOutRecordedContext`.
177
- /// @return cashOutTaxRate The cash out tax rate, which influences the amount of terminal tokens reclaimed.
176
+ /// @param context Standard Juicebox cash-out context. See `JBBeforeCashOutRecordedContext`.
177
+ /// @return cashOutTaxRate The cash-out tax rate, which influences the amount of terminal tokens reclaimed.
178
178
  /// @return cashOutCount The number of revnet tokens to cash out.
179
179
  /// @return totalSupply The total token supply across all chains (for both proportional reclaim and tax).
180
180
  /// @return effectiveSurplusValue The global surplus across all chains for proportional reclaim.
181
- /// @return hookSpecifications The amount of funds and data to send to cash out hooks (this contract).
181
+ /// @return hookSpecifications The amount of funds and data to send to cash-out hooks (this contract).
182
182
  function beforeCashOutRecordedWith(JBBeforeCashOutRecordedContext calldata context)
183
183
  external
184
184
  view
@@ -201,7 +201,7 @@ contract REVOwner is IREVOwner, IJBRulesetDataHook, IJBCashOutHook, IJBPeerChain
201
201
  totalSupply = context.totalSupply + totalCollateral;
202
202
  effectiveSurplusValue = context.surplus.value + totalBorrowed;
203
203
 
204
- // If the cash out is from a sucker, return the full cash out amount without taxes or fees.
204
+ // If the cash-out is from a sucker, return the full cash-out amount without taxes or fees.
205
205
  // Sucker cash-outs are the bridge accounting path: the value moving out of this chain must stay proportional
206
206
  // to this chain's local backing. Do not add remote supply/surplus here, even for unscoped revnets.
207
207
  // This relies on the sucker registry to only contain trusted sucker contracts deployed via
@@ -210,16 +210,16 @@ contract REVOwner is IREVOwner, IJBRulesetDataHook, IJBCashOutHook, IJBPeerChain
210
210
  return (0, context.cashOutCount, totalSupply, effectiveSurplusValue, hookSpecifications);
211
211
  }
212
212
 
213
- // Keep a reference to the cash out delay of the revnet.
213
+ // Keep a reference to the cash-out delay of the revnet.
214
214
  uint256 cashOutDelay = cashOutDelayOf[context.projectId];
215
215
 
216
- // Enforce the cash out delay.
216
+ // Enforce the cash-out delay.
217
217
  // forge-lint: disable-next-line(block-timestamp)
218
218
  if (cashOutDelay > block.timestamp) {
219
219
  revert REVOwner_CashOutDelayNotFinished({cashOutDelay: cashOutDelay, blockTimestamp: block.timestamp});
220
220
  }
221
221
 
222
- // Get the terminal that will receive the cash out fee.
222
+ // Get the terminal that will receive the cash-out fee.
223
223
  IJBTerminal feeTerminal = DIRECTORY.primaryTerminalOf({projectId: FEE_REVNET_ID, token: context.surplus.token});
224
224
 
225
225
  // If the ruleset aggregates cross-chain state, add remote supply and surplus.
@@ -232,8 +232,8 @@ contract REVOwner is IREVOwner, IJBRulesetDataHook, IJBCashOutHook, IJBPeerChain
232
232
  });
233
233
  }
234
234
 
235
- // If there's no cash out tax, if there's no fee terminal, or if the beneficiary is feeless (e.g. the router
236
- // terminal routing value between projects), proxy to the buyback hook with our totalSupply and
235
+ // If there is no cash-out tax, no fee terminal, or a feeless beneficiary (e.g. the router terminal routing
236
+ // value between projects), proxy to the buyback hook with our totalSupply and
237
237
  // effectiveSurplusValue. Zero-tax ordinary cash-outs do not add the revnet fee hook.
238
238
  if (context.cashOutTaxRate == 0 || address(feeTerminal) == address(0) || context.beneficiaryIsFeeless) {
239
239
  // Build a modified context with cross-chain-adjusted values so the buyback hook sees the global state
@@ -249,8 +249,8 @@ contract REVOwner is IREVOwner, IJBRulesetDataHook, IJBCashOutHook, IJBPeerChain
249
249
  // The fee is applied to TOKEN COUNT (2.5% of tokens), not to value. The fee revnet receives the bonding-curve
250
250
  // reclaim of its 2.5% token share regardless of whether the remaining 97.5% routes through a buyback pool at
251
251
  // a better price. This is by design.
252
- // Micro cash outs (< 40 wei at 2.5% fee) round feeCashOutCount to zero, bypassing the fee.
253
- // Economically insignificant: the gas cost of the transaction far exceeds the bypassed fee. No fix needed.
252
+ // Micro cash-outs (< 40 wei at 2.5% fee) round feeCashOutCount to zero. This accepted floor is economically
253
+ // negligible because gas dominates the avoided fee.
254
254
  uint256 feeCashOutCount = JBFees.standardFeeAmountFrom(context.cashOutCount);
255
255
  uint256 nonFeeCashOutCount = context.cashOutCount - feeCashOutCount;
256
256
 
@@ -311,7 +311,7 @@ contract REVOwner is IREVOwner, IJBRulesetDataHook, IJBCashOutHook, IJBPeerChain
311
311
  buybackHookContext.totalSupply = totalSupply;
312
312
  buybackHookContext.surplus.value = buybackSurplus;
313
313
 
314
- // Let the buyback hook adjust the cash out parameters and optionally return a hook specification.
314
+ // Let the buyback hook adjust the cash-out parameters and optionally return a hook specification.
315
315
  JBCashOutHookSpecification[] memory buybackHookSpecifications;
316
316
  (cashOutTaxRate, cashOutCount,,, buybackHookSpecifications) =
317
317
  BUYBACK_HOOK.beforeCashOutRecordedWith(buybackHookContext);
@@ -325,11 +325,11 @@ contract REVOwner is IREVOwner, IJBRulesetDataHook, IJBCashOutHook, IJBPeerChain
325
325
  // totalSupply, cashOutTaxRate)` and add the fee spec on top. When local liquidity is the binding cap, that
326
326
  // sum can exceed local surplus and revert. `cashOutFrom` is linear in `surplus`, so scale the surplus we
327
327
  // report so the store-side reclaim is at most `localSurplus - feeAmount`, preserving room for the fee.
328
- // The fee is NOT scaled here it was already scaled by the PR #149 block above. Only the store-facing
329
- // surplus is touched; the buyback hook already received the full pre-cap value for its routing decision.
328
+ // The fee was already scaled, if needed, by the local-liquidity block above. Only the store-facing surplus is
329
+ // touched; the buyback hook already received the full pre-cap value for its routing decision.
330
330
  //
331
331
  // Worked example (local=10, global=100, 500 of 1000 tokens at 50% tax):
332
- // above this block (PR #149):
332
+ // local-liquidity scaling above:
333
333
  // unscaledReclaim = cashOutFrom(100, 487.5, 1000, 5000) ≈ 36 ETH (global)
334
334
  // feeAmount = cashOutFrom(64, 12.5, 512.5, 5000) ≈ 0.78 ETH (global)
335
335
  // grossOutflow ≈ 36.78 > 10 → scale both proportionally to local liquidity:
@@ -343,10 +343,9 @@ contract REVOwner is IREVOwner, IJBRulesetDataHook, IJBCashOutHook, IJBPeerChain
343
343
  // storeReclaim = 36 × (27.18 / 100) ≈ 9.786 ETH
344
344
  // balanceDiff = 9.786 + 0.214 = 10 ETH = localSurplus ✓ no revert
345
345
  //
346
- // Underflow safety on `localSurplus feeAmount`: after PR #149 the relation
347
- // `feeAmount localSurplus` holds in both branches in the scaling branch because
348
- // `feeAmount grossOutflow` and the multiplier is `localSurplus / grossOutflow 1`;
349
- // in the else branch because `feeAmount ≤ grossOutflow ≤ localSurplus` already.
346
+ // Underflow safety: `feeAmount <= localSurplus` holds in both branches.
347
+ // In the scaling branch, `feeAmount <= grossOutflow` and the multiplier is
348
+ // `localSurplus / grossOutflow <= 1`. In the else branch, `feeAmount <= grossOutflow <= localSurplus`.
350
349
  uint256 reclaimCap = context.surplus.value - feeAmount;
351
350
  if (unscaledReclaim > reclaimCap) {
352
351
  effectiveSurplusValue = mulDiv({x: effectiveSurplusValue, y: reclaimCap, denominator: unscaledReclaim});
@@ -486,11 +485,11 @@ contract REVOwner is IREVOwner, IJBRulesetDataHook, IJBCashOutHook, IJBPeerChain
486
485
  // --------------------- external transactions ----------------------- //
487
486
  //*********************************************************************//
488
487
 
489
- /// @notice Process the fee from a cash out.
488
+ /// @notice Process the fee from a cash-out.
490
489
  /// @param context Cash out context passed in by the terminal.
491
490
  function afterCashOutRecordedWith(JBAfterCashOutRecordedContext calldata context) external payable override {
492
491
  // No caller validation needed — this hook only pays fees to the fee project using funds forwarded by the
493
- // caller. A non-terminal caller would just be donating their own funds as fees. There's nothing to exploit.
492
+ // caller. A non-terminal caller would donate their own funds as fees. There's nothing to exploit.
494
493
 
495
494
  if (context.forwardedAmount.token == JBConstants.NATIVE_TOKEN) {
496
495
  // Native fee processing must be value-balanced by the current call. Otherwise a non-terminal caller could
@@ -568,14 +567,14 @@ contract REVOwner is IREVOwner, IJBRulesetDataHook, IJBCashOutHook, IJBPeerChain
568
567
  }
569
568
 
570
569
  // Store the cash-out delay if the deployer computed one (existing revnet landing on a new chain). A zero
571
- // delay means cash outs are unlocked immediately, so skip the storage write to save gas in the common case.
570
+ // delay means cash-outs are unlocked immediately, so skip the storage write to save gas in the common case.
572
571
  if (init.cashOutDelay != 0) {
573
572
  cashOutDelayOf[revnetId] = init.cashOutDelay;
574
573
  }
575
574
 
576
- // Bind the tiered ERC-721 hook the deployer just created for this revnet so `beforePayRecordedWith` can
577
- // route NFT-tier mints through it. The deployer always deploys a hook (empty or pre-tiered), so the zero
578
- // guard is defensive.
575
+ // Bind the tiered ERC-721 hook the deployer created for this revnet so `beforePayRecordedWith` can route
576
+ // NFT-tier mints through it. The deployer always deploys a hook (empty or pre-tiered), so the zero guard is
577
+ // defensive.
579
578
  if (address(init.tiered721Hook) != address(0)) {
580
579
  tiered721HookOf[revnetId] = init.tiered721Hook;
581
580
  }
@@ -647,7 +646,7 @@ contract REVOwner is IREVOwner, IJBRulesetDataHook, IJBCashOutHook, IJBPeerChain
647
646
  PROJECTS = newDeployer.PROJECTS();
648
647
 
649
648
  // Grant the wildcard operator permissions used by every revnet, scoped on this contract's account.
650
- // The loan contract is the singleton shared by all revnets and uses the surplus allowance of each
649
+ // The loan contract is the singleton shared by every revnet and uses the surplus allowance of each
651
650
  // revnet's terminal. The buyback hook registry configures pools on every revnet.
652
651
  uint8[] memory loanPermissionIds = new uint8[](1);
653
652
  loanPermissionIds[0] = JBPermissionIds.USE_ALLOWANCE;
@@ -774,8 +773,9 @@ contract REVOwner is IREVOwner, IJBRulesetDataHook, IJBCashOutHook, IJBPeerChain
774
773
 
775
774
  /// @notice Indicates if this contract adheres to the specified interface.
776
775
  /// @dev See `IERC165.supportsInterface`.
777
- /// @return A flag indicating if the provided interface ID is supported.
778
- function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
776
+ /// @param interfaceId The interface ID to check.
777
+ /// @return flag A flag indicating whether the interface is supported.
778
+ function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool flag) {
779
779
  return interfaceId == type(IERC165).interfaceId || interfaceId == type(IJBRulesetDataHook).interfaceId
780
780
  || interfaceId == type(IJBCashOutHook).interfaceId
781
781
  || interfaceId == type(IJBPeerChainAdjustedAccounts).interfaceId;
@@ -52,9 +52,9 @@ interface IREVDeployer {
52
52
  address caller
53
53
  );
54
54
 
55
- /// @notice Emitted when the cash out delay is set for a revnet.
55
+ /// @notice Emitted when the cash-out delay is set for a revnet.
56
56
  /// @param revnetId The ID of the revnet.
57
- /// @param cashOutDelay The cash out delay in seconds.
57
+ /// @param cashOutDelay The cash-out delay in seconds.
58
58
  /// @param caller The address that set the delay.
59
59
  event SetCashOutDelay(uint256 indexed revnetId, uint256 cashOutDelay, address caller);
60
60
 
@@ -73,7 +73,7 @@ interface IREVDeployer {
73
73
  function BUYBACK_HOOK() external view returns (IJBBuybackHookRegistry);
74
74
 
75
75
  /// @notice The number of seconds until a revnet's participants can cash out after deploying to a new network.
76
- /// @return The cash out delay in seconds.
76
+ /// @return The cash-out delay in seconds.
77
77
  function CASH_OUT_DELAY() external view returns (uint256);
78
78
 
79
79
  /// @notice The controller used to create and manage Juicebox projects for revnets.
@@ -92,7 +92,7 @@ interface IREVDeployer {
92
92
  /// @return The directory contract.
93
93
  function DIRECTORY() external view returns (IJBDirectory);
94
94
 
95
- /// @notice The Juicebox project ID of the revnet that receives cash out fees.
95
+ /// @notice The Juicebox project ID of the revnet that receives cash-out fees.
96
96
  /// @return The fee revnet ID.
97
97
  function FEE_REVNET_ID() external view returns (uint256);
98
98
 
@@ -105,7 +105,7 @@ interface IREVDeployer {
105
105
  /// @return The hook deployer contract.
106
106
  function HOOK_DEPLOYER() external view returns (IJB721TiersHookDeployer);
107
107
 
108
- /// @notice The loan contract used by all revnets.
108
+ /// @notice The loan contract used by every revnet.
109
109
  /// @return The loans contract address.
110
110
  function LOANS() external view returns (IREVLoans);
111
111
 
@@ -113,7 +113,7 @@ interface IREVDeployer {
113
113
  /// @return The multi terminal contract.
114
114
  function MULTI_TERMINAL() external view returns (IJBTerminal);
115
115
 
116
- /// @notice The runtime data hook contract that handles pay and cash out callbacks for revnets.
116
+ /// @notice The runtime data hook contract that handles pay and cash-out callbacks for revnets.
117
117
  /// @return The owner contract address.
118
118
  function OWNER() external view returns (address);
119
119
 
@@ -125,7 +125,7 @@ interface IREVDeployer {
125
125
  /// @return The projects contract.
126
126
  function PROJECTS() external view returns (IJBProjects);
127
127
 
128
- /// @notice The croptop publisher revnets can use to publish ERC-721 posts to their tiered ERC-721 hooks.
128
+ /// @notice The Croptop publisher revnets can use to publish ERC-721 posts to their tiered ERC-721 hooks.
129
129
  /// @return The publisher contract.
130
130
  function PUBLISHER() external view returns (CTPublisher);
131
131
 
@@ -137,14 +137,14 @@ interface IREVDeployer {
137
137
  /// @return The sucker registry contract.
138
138
  function SUCKER_REGISTRY() external view returns (IJBSuckerRegistry);
139
139
 
140
- /// @notice Deploy a revnet with a tiered ERC-721 hook and optional croptop posting support.
140
+ /// @notice Deploy a revnet with a tiered ERC-721 hook and optional Croptop posting support.
141
141
  /// @dev Every revnet gets a 721 hook — pass an empty config if no tiers are needed initially.
142
142
  /// @param revnetId The ID of the Juicebox project to initialize. Send 0 to deploy a new revnet.
143
143
  /// @param configuration Core revnet configuration.
144
144
  /// @param accountingContextsToAccept The accounting contexts the canonical multi terminal should accept.
145
145
  /// @param suckerDeploymentConfiguration The suckers to set up for cross-chain token transfers.
146
146
  /// @param tiered721HookConfiguration How to configure the tiered ERC-721 hook for the revnet.
147
- /// @param allowedPosts Restrictions on which croptop posts to allow on the revnet's ERC-721 tiers.
147
+ /// @param allowedPosts Restrictions on which Croptop posts to allow on the revnet's ERC-721 tiers.
148
148
  /// @return The ID of the newly created or initialized revnet.
149
149
  /// @return hook The tiered ERC-721 hook deployed for the revnet.
150
150
  function deployFor(
@@ -54,7 +54,7 @@ interface IREVOwner {
54
54
  /// @return The directory instance.
55
55
  function DIRECTORY() external view returns (IJBDirectory);
56
56
 
57
- /// @notice The Juicebox project ID of the revnet that receives cash out fees.
57
+ /// @notice The Juicebox project ID of the revnet that receives cash-out fees.
58
58
  /// @return The fee revnet's Juicebox project ID.
59
59
  function FEE_REVNET_ID() external view returns (uint256);
60
60
 
@@ -81,9 +81,9 @@ interface IREVOwner {
81
81
  /// @return The number of tokens available to auto-issue.
82
82
  function amountToAutoIssue(uint256 revnetId, uint256 stageId, address beneficiary) external view returns (uint256);
83
83
 
84
- /// @notice The timestamp at which cash outs become available for a specific revnet's participants.
84
+ /// @notice The timestamp at which cash-outs become available for a specific revnet's participants.
85
85
  /// @param revnetId The ID of the revnet.
86
- /// @return The cash out delay timestamp (0 if no delay applies).
86
+ /// @return The cash-out delay timestamp (0 if no delay applies).
87
87
  function cashOutDelayOf(uint256 revnetId) external view returns (uint256);
88
88
 
89
89
  /// @notice The canonical deployer that manages runtime hook state.
@@ -5,7 +5,8 @@ pragma solidity ^0.8.0;
5
5
  /// @custom:member category The category to allow posts for.
6
6
  /// @custom:member minimumPrice The minimum price a post to the specified category must cost.
7
7
  /// @custom:member minimumTotalSupply The minimum total supply of NFTs to make available when minting.
8
- /// @custom:member maxTotalSupply The max total supply of NFTs to make available when minting. Leave as 0 for unlimited.
8
+ /// @custom:member maximumTotalSupply The max total supply of NFTs to make available when minting. Leave as 0 for
9
+ /// unlimited.
9
10
  /// @custom:member maximumSplitPercent The maximum split percent (out of JBConstants.SPLITS_TOTAL_PERCENT) a poster can
10
11
  /// set. 0 means splits are not allowed.
11
12
  /// @custom:member allowedAddresses The addresses allowed to post on the category through Croptop.
@@ -3,16 +3,13 @@ pragma solidity ^0.8.0;
3
3
 
4
4
  import {REVBaseline721HookConfig} from "./REVBaseline721HookConfig.sol";
5
5
 
6
- /// @custom:member baseline721HookConfiguration The baseline 721 hook config.
6
+ /// @custom:member baseline721HookConfiguration The base tiered ERC-721 hook config.
7
7
  /// @custom:member salt The salt to derive the collection's address from.
8
- /// @custom:member preventOperatorAdjustingTiers Whether to prevent the operator from adding and removing
9
- /// tiers.
10
- /// @custom:member preventOperatorUpdatingMetadata Whether to prevent the operator from updating the 721's
11
- /// metadata.
12
- /// @custom:member preventOperatorMinting Whether to prevent the operator from minting 721s from tiers that
13
- /// allow it.
14
- /// @custom:member preventOperatorIncreasingDiscountPercent Whether to prevent the operator from increasing
15
- /// the discount of a tier.
8
+ /// @custom:member preventOperatorAdjustingTiers Whether to prevent the operator from adding and removing tiers.
9
+ /// @custom:member preventOperatorUpdatingMetadata Whether to prevent the operator from updating the 721's metadata.
10
+ /// @custom:member preventOperatorMinting Whether to prevent the operator from minting 721s from tiers that allow it.
11
+ /// @custom:member preventOperatorIncreasingDiscountPercent Whether to prevent the operator from increasing a tier's
12
+ /// discount.
16
13
  struct REVDeploy721TiersHookConfig {
17
14
  REVBaseline721HookConfig baseline721HookConfiguration;
18
15
  bytes32 salt;
@@ -7,10 +7,10 @@ import {REVAutoIssuance} from "./REVAutoIssuance.sol";
7
7
 
8
8
  /// @notice A stage in a revnet's lifecycle. Each stage defines the token issuance rate, how quickly it decays, what
9
9
  /// percentage goes to splits, and the cash-out tax rate. Stages are processed in order — each one activates at or
10
- /// after
11
- /// its `startsAtOrAfter` timestamp.
10
+ /// after its `startsAtOrAfter` timestamp.
12
11
  /// @custom:member startsAtOrAfter The earliest timestamp this stage can begin. Must be strictly increasing across
13
- /// stages. @custom:member autoIssuances Tokens to mint without payment during this stage (per-chain, per-beneficiary).
12
+ /// stages.
13
+ /// @custom:member autoIssuances Tokens to mint without payment during this stage (per-chain, per-beneficiary).
14
14
  /// @custom:member splitPercent The percentage of newly issued tokens routed to splits, out of 10,000.
15
15
  /// @custom:member splits The split recipients for this stage's production allocation.
16
16
  /// @custom:member initialIssuance Tokens per unit of base currency at stage start (18-decimal fixed point).
@@ -3,8 +3,8 @@ pragma solidity ^0.8.0;
3
3
 
4
4
  import {JBSuckerDeployerConfig} from "@bananapus/suckers-v6/src/structs/JBSuckerDeployerConfig.sol";
5
5
 
6
- /// @custom:member deployerConfigurations The configuration for bridging tokens to other chains.
7
- /// @custom:member salt The salt to use for deterministic sucker addresses across chains.
6
+ /// @custom:member deployerConfigurations The sucker deployers and token bridge mappings to configure.
7
+ /// @custom:member salt Extra caller-provided entropy mixed with the revnet config hash and deployer caller.
8
8
  struct REVSuckerDeploymentConfig {
9
9
  JBSuckerDeployerConfig[] deployerConfigurations;
10
10
  bytes32 salt;