@rev-net/core-v6 0.0.37 → 0.0.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/CHANGELOG.md +2 -2
  2. package/README.md +6 -7
  3. package/foundry.toml +1 -1
  4. package/package.json +23 -16
  5. package/references/operations.md +1 -1
  6. package/references/runtime.md +1 -1
  7. package/script/Deploy.s.sol +12 -9
  8. package/src/REVDeployer.sol +69 -67
  9. package/src/REVHiddenTokens.sol +2 -2
  10. package/src/REVLoans.sol +26 -22
  11. package/src/REVOwner.sol +147 -29
  12. package/src/interfaces/IREVDeployer.sol +2 -1
  13. package/src/interfaces/IREVHiddenTokens.sol +4 -1
  14. package/src/interfaces/IREVOwner.sol +5 -0
  15. package/src/structs/REVAutoIssuance.sol +4 -2
  16. package/src/structs/REVConfig.sol +8 -5
  17. package/src/structs/REVDescription.sol +6 -5
  18. package/src/structs/REVLoan.sol +8 -5
  19. package/src/structs/REVStageConfig.sol +14 -16
  20. package/ADMINISTRATION.md +0 -73
  21. package/ARCHITECTURE.md +0 -116
  22. package/AUDIT_INSTRUCTIONS.md +0 -90
  23. package/RISKS.md +0 -107
  24. package/SKILLS.md +0 -46
  25. package/STYLE_GUIDE.md +0 -610
  26. package/USER_JOURNEYS.md +0 -195
  27. package/foundry.lock +0 -11
  28. package/slither-ci.config.json +0 -10
  29. package/sphinx.lock +0 -507
  30. package/test/REV.integrations.t.sol +0 -573
  31. package/test/REVAutoIssuanceFuzz.t.sol +0 -328
  32. package/test/REVDeployerRegressions.t.sol +0 -396
  33. package/test/REVInvincibility.t.sol +0 -1371
  34. package/test/REVInvincibilityHandler.sol +0 -387
  35. package/test/REVLifecycle.t.sol +0 -420
  36. package/test/REVLoans.invariants.t.sol +0 -724
  37. package/test/REVLoansAttacks.t.sol +0 -816
  38. package/test/REVLoansFeeRecovery.t.sol +0 -783
  39. package/test/REVLoansFindings.t.sol +0 -711
  40. package/test/REVLoansRegressions.t.sol +0 -364
  41. package/test/REVLoansSourceFeeRecovery.t.sol +0 -517
  42. package/test/REVLoansSourced.t.sol +0 -1839
  43. package/test/REVLoansUnSourced.t.sol +0 -409
  44. package/test/TestAuditFixVerification.t.sol +0 -675
  45. package/test/TestBurnHeldTokens.t.sol +0 -394
  46. package/test/TestCEIPattern.t.sol +0 -508
  47. package/test/TestCashOutCallerValidation.t.sol +0 -452
  48. package/test/TestConversionDocumentation.t.sol +0 -365
  49. package/test/TestCrossCurrencyReclaim.t.sol +0 -610
  50. package/test/TestCrossSourceReallocation.t.sol +0 -361
  51. package/test/TestERC2771MetaTx.t.sol +0 -585
  52. package/test/TestEmptyBuybackSpecs.t.sol +0 -300
  53. package/test/TestFlashLoanSurplus.t.sol +0 -365
  54. package/test/TestHiddenTokens.t.sol +0 -474
  55. package/test/TestHookArrayOOB.t.sol +0 -278
  56. package/test/TestLiquidationBehavior.t.sol +0 -398
  57. package/test/TestLoanSourceRotation.t.sol +0 -553
  58. package/test/TestLoansCashOutDelay.t.sol +0 -493
  59. package/test/TestLongTailEconomics.t.sol +0 -677
  60. package/test/TestLowFindings.t.sol +0 -677
  61. package/test/TestMixedFixes.t.sol +0 -593
  62. package/test/TestPermit2Signatures.t.sol +0 -683
  63. package/test/TestReallocationSandwich.t.sol +0 -412
  64. package/test/TestRevnetRegressions.t.sol +0 -350
  65. package/test/TestSplitWeightAdjustment.t.sol +0 -527
  66. package/test/TestSplitWeightE2E.t.sol +0 -605
  67. package/test/TestSplitWeightFork.t.sol +0 -855
  68. package/test/TestStageTransitionBorrowable.t.sol +0 -301
  69. package/test/TestSwapTerminalPermission.t.sol +0 -262
  70. package/test/TestTerminalEncodingInHash.t.sol +0 -326
  71. package/test/TestUint112Overflow.t.sol +0 -311
  72. package/test/TestZeroAmountLoanGuard.t.sol +0 -378
  73. package/test/TestZeroRepayment.t.sol +0 -354
  74. package/test/audit/CrossChainBuybackRouteMismatch.t.sol +0 -184
  75. package/test/audit/HiddenSupplyCashout.t.sol +0 -61
  76. package/test/audit/LoanIdOverflowGuard.t.sol +0 -523
  77. package/test/audit/NemesisVerification.t.sol +0 -97
  78. package/test/audit/OperatorDelegation.t.sol +0 -356
  79. package/test/audit/PhantomSurplusTerminal.t.sol +0 -367
  80. package/test/audit/REVOwnerCurrencyMismatch.t.sol +0 -188
  81. package/test/audit/REVOwnerRemoteSurplusCurrencyMismatch.t.sol +0 -140
  82. package/test/audit/ReallocatePermission.t.sol +0 -363
  83. package/test/audit/RemoteLoanAccountingGap.t.sol +0 -74
  84. package/test/audit/SupportsInterfaceTest.t.sol +0 -51
  85. package/test/audit/TestFeeAllowanceLeak.t.sol +0 -197
  86. package/test/audit/TestLoansAndDeployerFixes.t.sol +0 -576
  87. package/test/fork/ForkTestBase.sol +0 -727
  88. package/test/fork/TestAutoIssuanceFork.t.sol +0 -148
  89. package/test/fork/TestCashOutFork.t.sol +0 -253
  90. package/test/fork/TestIssuanceDecayFork.t.sol +0 -158
  91. package/test/fork/TestLoanAdversarialFork.t.sol +0 -744
  92. package/test/fork/TestLoanBorrowFork.t.sol +0 -163
  93. package/test/fork/TestLoanCrossRulesetFork.t.sol +0 -308
  94. package/test/fork/TestLoanERC20Fork.t.sol +0 -459
  95. package/test/fork/TestLoanLiquidationFork.t.sol +0 -135
  96. package/test/fork/TestLoanReallocateFork.t.sol +0 -113
  97. package/test/fork/TestLoanRepayFork.t.sol +0 -188
  98. package/test/fork/TestLoanTransferFork.t.sol +0 -143
  99. package/test/fork/TestPermit2PaymentFork.t.sol +0 -300
  100. package/test/fork/TestSplitWeightFork.t.sol +0 -189
  101. package/test/helpers/MaliciousContracts.sol +0 -247
  102. package/test/helpers/REVEmpty721Config.sol +0 -45
  103. package/test/mock/MockBuybackCashOutRecorder.sol +0 -84
  104. package/test/mock/MockBuybackDataHook.sol +0 -112
  105. package/test/mock/MockBuybackDataHookMintPath.sol +0 -68
  106. package/test/mock/MockSuckerRegistry.sol +0 -17
  107. package/test/regression/TestBurnPermissionRequired.t.sol +0 -294
  108. package/test/regression/TestCashOutBuybackFeeLeak.t.sol +0 -232
  109. package/test/regression/TestCrossRevnetLiquidation.t.sol +0 -255
  110. package/test/regression/TestCumulativeLoanCounter.t.sol +0 -361
  111. package/test/regression/TestLiquidateGapHandling.t.sol +0 -394
  112. package/test/regression/TestZeroPriceFeed.t.sol +0 -422
package/src/REVOwner.sol CHANGED
@@ -9,12 +9,14 @@ import {IJBRulesetDataHook} from "@bananapus/core-v6/src/interfaces/IJBRulesetDa
9
9
  import {IJBTerminal} from "@bananapus/core-v6/src/interfaces/IJBTerminal.sol";
10
10
  import {JBCashOuts} from "@bananapus/core-v6/src/libraries/JBCashOuts.sol";
11
11
  import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
12
+ import {JBAccountingContext} from "@bananapus/core-v6/src/structs/JBAccountingContext.sol";
12
13
  import {JBAfterCashOutRecordedContext} from "@bananapus/core-v6/src/structs/JBAfterCashOutRecordedContext.sol";
13
14
  import {JBBeforeCashOutRecordedContext} from "@bananapus/core-v6/src/structs/JBBeforeCashOutRecordedContext.sol";
14
15
  import {JBBeforePayRecordedContext} from "@bananapus/core-v6/src/structs/JBBeforePayRecordedContext.sol";
15
16
  import {JBCashOutHookSpecification} from "@bananapus/core-v6/src/structs/JBCashOutHookSpecification.sol";
16
17
  import {JBPayHookSpecification} from "@bananapus/core-v6/src/structs/JBPayHookSpecification.sol";
17
18
  import {JBRuleset} from "@bananapus/core-v6/src/structs/JBRuleset.sol";
19
+ import {IJBPeerChainAdjustedAccounts} from "@bananapus/suckers-v6/src/interfaces/IJBPeerChainAdjustedAccounts.sol";
18
20
  import {IJBSuckerRegistry} from "@bananapus/suckers-v6/src/interfaces/IJBSuckerRegistry.sol";
19
21
  import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
20
22
  import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
@@ -22,11 +24,18 @@ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol
22
24
  import {mulDiv} from "@prb/math/src/Common.sol";
23
25
 
24
26
  import {IREVDeployer} from "./interfaces/IREVDeployer.sol";
25
-
26
- /// @notice Handles the runtime data hook and cash out hook behavior for revnets.
27
- /// @dev Separated from `REVDeployer` to stay within the EIP-170 contract size limit.
28
- /// This contract is set as the `dataHook` in each revnet's ruleset metadata.
29
- contract REVOwner is IJBRulesetDataHook, IJBCashOutHook {
27
+ import {IREVHiddenTokens} from "./interfaces/IREVHiddenTokens.sol";
28
+ import {IREVLoans} from "./interfaces/IREVLoans.sol";
29
+ import {REVLoanSource} from "./structs/REVLoanSource.sol";
30
+
31
+ /// @notice The runtime hook for all revnets — set as every revnet's `dataHook` in ruleset metadata. At pay time, it
32
+ /// coordinates the 721 hook (NFT tier minting) with the buyback hook (secondary market swap routing) and scales weight
33
+ /// for split deductions. At cash-out time, it aggregates cross-chain total supply and surplus (including outstanding
34
+ /// loan debt and collateral), grants suckers 0% tax, splits a 2.5% fee from non-sucker cash outs, and routes fee
35
+ /// proceeds to the fee revnet via `afterCashOutRecordedWith`.
36
+ /// @dev Separated from `REVDeployer` to stay within the EIP-170 contract size limit. Also implements
37
+ /// `IJBPeerChainAdjustedAccounts` to expose loan state to peer-chain supply/surplus snapshots.
38
+ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook, IJBPeerChainAdjustedAccounts {
30
39
  // A library that adds default safety checks to ERC20 functionality.
31
40
  using SafeERC20 for IERC20;
32
41
 
@@ -61,10 +70,10 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook {
61
70
  uint256 public immutable FEE_REVNET_ID;
62
71
 
63
72
  /// @notice The hidden tokens contract used by all revnets.
64
- address public immutable HIDDEN_TOKENS;
73
+ IREVHiddenTokens public immutable HIDDEN_TOKENS;
65
74
 
66
75
  /// @notice The loan contract used by all revnets.
67
- address public immutable LOANS;
76
+ IREVLoans public immutable LOANS;
68
77
 
69
78
  /// @notice Deploys and tracks suckers for revnets.
70
79
  IJBSuckerRegistry public immutable SUCKER_REGISTRY;
@@ -102,23 +111,21 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook {
102
111
  /// @param directory The directory of terminals and controllers.
103
112
  /// @param feeRevnetId The Juicebox project ID of the fee revnet.
104
113
  /// @param suckerRegistry The sucker registry.
105
- /// @param loans The loan contract address.
106
- /// @param hiddenTokens The hidden tokens contract address.
114
+ /// @param loans The loan contract.
115
+ /// @param hiddenTokens The hidden tokens contract.
107
116
  constructor(
108
117
  IJBBuybackHookRegistry buybackHook,
109
118
  IJBDirectory directory,
110
119
  uint256 feeRevnetId,
111
120
  IJBSuckerRegistry suckerRegistry,
112
- address loans,
113
- address hiddenTokens
121
+ IREVLoans loans,
122
+ IREVHiddenTokens hiddenTokens
114
123
  ) {
115
124
  BUYBACK_HOOK = buybackHook;
116
125
  DIRECTORY = directory;
117
126
  FEE_REVNET_ID = feeRevnetId;
118
127
  SUCKER_REGISTRY = suckerRegistry;
119
- // slither-disable-next-line missing-zero-check
120
128
  LOANS = loans;
121
- // slither-disable-next-line missing-zero-check
122
129
  HIDDEN_TOKENS = hiddenTokens;
123
130
  _DEPLOYER_BINDER = msg.sender;
124
131
  }
@@ -127,13 +134,14 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook {
127
134
  // ------------------------- external views -------------------------- //
128
135
  //*********************************************************************//
129
136
 
130
- /// @notice Determine how a cash out from a revnet should be processed.
131
- /// @dev This function is part of `IJBRulesetDataHook`, and gets called before the revnet processes a cash out.
132
- /// @dev If a sucker is cashing out, no taxes or fees are imposed.
133
- /// @dev REVOwner is intentionally not registered as a feeless address. The protocol fee (2.5%) applies on top
134
- /// of the rev fee this is by design. The fee hook spec amount sent to `afterCashOutRecordedWith` will have the
135
- /// protocol fee deducted by the terminal before reaching this contract, so the rev fee is computed on the
136
- /// post-protocol-fee amount.
137
+ /// @notice Called before a cash out is recorded. Suckers get 0% tax (bridged tokens redeem at face value). For
138
+ /// regular holders, aggregates cross-chain total supply and surplus (including outstanding loan debt/collateral),
139
+ /// splits a 2.5% fee from the cashed-out token count, computes bonding curve reclaims for both the holder's portion
140
+ /// and the fee portion, then delegates to the buyback hook for potential swap routing.
141
+ /// @dev Part of `IJBRulesetDataHook`. REVOwner is intentionally not registered as a feeless address the
142
+ /// protocol
143
+ /// fee (2.5%) applies on top of the rev fee. The fee hook spec amount sent to `afterCashOutRecordedWith` will have
144
+ /// the protocol fee deducted by the terminal before reaching this contract.
137
145
  /// @param context Standard Juicebox cash out context. See `JBBeforeCashOutRecordedContext`.
138
146
  /// @return cashOutTaxRate The cash out tax rate, which influences the amount of terminal tokens which get cashed
139
147
  /// out.
@@ -153,11 +161,23 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook {
153
161
  JBCashOutHookSpecification[] memory hookSpecifications
154
162
  )
155
163
  {
164
+ // Treat outstanding local loans as temporarily off-terminal revnet assets. Borrowed funds are owed back to
165
+ // the revnet, while burned loan collateral can be re-minted on repayment, so both affect fair cash-out math.
166
+ (uint256 totalBorrowed, uint256 totalCollateral) = _localLoanStateOf({
167
+ revnetId: context.projectId, decimals: context.surplus.decimals, currency: context.surplus.currency
168
+ });
169
+
156
170
  // If the cash out is from a sucker, return the full cash out amount without taxes or fees.
157
171
  // This relies on the sucker registry to only contain trusted sucker contracts deployed via
158
172
  // the registry's own deploySuckersFor flow — external addresses cannot register as suckers.
159
173
  if (_isSuckerOf({revnetId: context.projectId, addr: context.holder})) {
160
- return (0, context.cashOutCount, context.totalSupply, context.surplus.value, hookSpecifications);
174
+ return (
175
+ 0,
176
+ context.cashOutCount,
177
+ context.totalSupply + totalCollateral,
178
+ context.surplus.value + totalBorrowed,
179
+ hookSpecifications
180
+ );
161
181
  }
162
182
 
163
183
  // Keep a reference to the cash out delay of the revnet.
@@ -173,8 +193,8 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook {
173
193
 
174
194
  // Compute the cross-chain total supply (local + remote peer chain supplies) for cross-chain-aware bonding
175
195
  // curve.
176
- totalSupply = context.totalSupply + SUCKER_REGISTRY.remoteTotalSupplyOf(context.projectId);
177
- effectiveSurplusValue = context.surplus.value
196
+ totalSupply = context.totalSupply + totalCollateral + SUCKER_REGISTRY.remoteTotalSupplyOf(context.projectId);
197
+ effectiveSurplusValue = context.surplus.value + totalBorrowed
178
198
  + SUCKER_REGISTRY.remoteSurplusOf({
179
199
  projectId: context.projectId,
180
200
  decimals: context.surplus.decimals,
@@ -274,8 +294,11 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook {
274
294
  return (cashOutTaxRate, cashOutCount, totalSupply, effectiveSurplusValue, hookSpecifications);
275
295
  }
276
296
 
277
- /// @notice Before a revnet processes an incoming payment, determine the weight and pay hooks to use.
278
- /// @dev This function is part of `IJBRulesetDataHook`, and gets called before the revnet processes a payment.
297
+ /// @notice Called before a payment is recorded. Coordinates the 721 hook (NFT tier minting with split deductions)
298
+ /// with the buyback hook (which may route funds through a Uniswap pool for a better token price). Merges their hook
299
+ /// specifications and scales the minting weight so payers only receive tokens proportional to funds entering the
300
+ /// project (not the split portion).
301
+ /// @dev Part of `IJBRulesetDataHook`. The 721 hook spec comes first in the returned array.
279
302
  /// @param context Standard Juicebox payment context. See `JBBeforePayRecordedContext`.
280
303
  /// @return weight The weight which revnet tokens are minted relative to. This can be used to customize how many
281
304
  /// tokens get minted by a payment.
@@ -332,8 +355,11 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook {
332
355
  if (usesBuybackHook) hookSpecifications[usesTiered721Hook ? 1 : 0] = buybackHookSpecs[0];
333
356
  }
334
357
 
335
- /// @notice A flag indicating whether an address has permission to mint a revnet's tokens on-demand.
336
- /// @dev Required by the `IJBRulesetDataHook` interface.
358
+ /// @notice Returns whether an address may mint a revnet's tokens on-demand. Grants permission to: the loans
359
+ /// contract (re-mints collateral on repayment), hidden tokens contract (re-mints on reveal), buyback hook and its
360
+ /// delegates
361
+ /// (mints tokens from pool swaps), and suckers (mints bridged tokens on the destination chain).
362
+ /// @dev Part of `IJBRulesetDataHook`.
337
363
  /// @param revnetId The ID of the revnet to check permissions for.
338
364
  /// @param ruleset The ruleset to check the mint permission for.
339
365
  /// @param addr The address to check the mint permission of.
@@ -350,11 +376,35 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook {
350
376
  {
351
377
  // The loans contract, hidden tokens contract, buyback hook (and its delegates), and suckers are allowed to mint
352
378
  // the revnet's tokens.
353
- return addr == LOANS || addr == HIDDEN_TOKENS || addr == address(BUYBACK_HOOK)
379
+ return addr == address(LOANS) || addr == address(HIDDEN_TOKENS) || addr == address(BUYBACK_HOOK)
354
380
  || BUYBACK_HOOK.hasMintPermissionFor({projectId: revnetId, ruleset: ruleset, addr: addr})
355
381
  || _isSuckerOf({revnetId: revnetId, addr: addr});
356
382
  }
357
383
 
384
+ /// @notice Additional revnet accounts that peer-chain snapshots should include.
385
+ /// @dev Hidden tokens are intentionally excluded. Revnet operators can hide tokens as a security handle without
386
+ /// changing loan or cash-out math for other holders. Outstanding loan debt is counted as both surplus and balance:
387
+ /// it is value owed back to this chain's revnet and should travel to peer snapshots with the collateral supply.
388
+ /// @param revnetId The ID of the revnet being snapshotted.
389
+ /// @param decimals The decimals the returned surplus should use.
390
+ /// @param currency The currency the returned surplus should be in terms of.
391
+ /// @return supply The loan-collateral supply to include in the peer snapshot.
392
+ /// @return surplus The outstanding loan debt to include in `sourceSurplus`.
393
+ /// @return balance The outstanding loan debt to include in `sourceBalance`.
394
+ function peerChainAdjustedAccountsOf(
395
+ uint256 revnetId,
396
+ uint256 decimals,
397
+ uint256 currency
398
+ )
399
+ external
400
+ view
401
+ override
402
+ returns (uint256 supply, uint256 surplus, uint256 balance)
403
+ {
404
+ (surplus, supply) = _localLoanStateOf({revnetId: revnetId, decimals: decimals, currency: currency});
405
+ balance = surplus;
406
+ }
407
+
358
408
  //*********************************************************************//
359
409
  // --------------------- external transactions ----------------------- //
360
410
  //*********************************************************************//
@@ -460,7 +510,8 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook {
460
510
  /// @return A flag indicating if the provided interface ID is supported.
461
511
  function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
462
512
  return interfaceId == type(IERC165).interfaceId || interfaceId == type(IJBRulesetDataHook).interfaceId
463
- || interfaceId == type(IJBCashOutHook).interfaceId;
513
+ || interfaceId == type(IJBCashOutHook).interfaceId
514
+ || interfaceId == type(IJBPeerChainAdjustedAccounts).interfaceId;
464
515
  }
465
516
 
466
517
  //*********************************************************************//
@@ -475,6 +526,73 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook {
475
526
  return SUCKER_REGISTRY.isSuckerOf({projectId: revnetId, addr: addr});
476
527
  }
477
528
 
529
+ /// @notice Total outstanding local loan debt and collateral for a revnet.
530
+ /// @dev This is included in cash-out and peer-snapshot math because borrowed funds are still owed to the revnet
531
+ /// and collateral can re-enter supply when the loan is repaid.
532
+ /// @param revnetId The ID of the revnet to check.
533
+ /// @param decimals The decimals the resulting fixed point debt value should use.
534
+ /// @param currency The currency the resulting debt value should be in terms of.
535
+ /// @return borrowedAmount The local outstanding loan debt converted into `currency`.
536
+ /// @return collateralCount The local burned loan collateral count.
537
+ function _localLoanStateOf(
538
+ uint256 revnetId,
539
+ uint256 decimals,
540
+ uint256 currency
541
+ )
542
+ internal
543
+ view
544
+ returns (uint256 borrowedAmount, uint256 collateralCount)
545
+ {
546
+ IREVLoans loans = LOANS;
547
+ if (address(loans) == address(0) || address(loans).code.length == 0) return (0, 0);
548
+
549
+ collateralCount = loans.totalCollateralOf(revnetId);
550
+
551
+ REVLoanSource[] memory sources = loans.loanSourcesOf(revnetId);
552
+ // Loan sources are project configuration, and this read-only aggregation needs the latest terminal/pricing
553
+ // state for each configured source.
554
+ for (uint256 i; i < sources.length; i++) {
555
+ REVLoanSource memory source = sources[i];
556
+ // Each configured source must be queried live so cash-out math includes current outstanding debt.
557
+ // slither-disable-next-line calls-loop
558
+ uint256 tokensLoaned =
559
+ loans.totalBorrowedFrom({revnetId: revnetId, terminal: source.terminal, token: source.token});
560
+ if (tokensLoaned == 0) continue;
561
+
562
+ // Read the source token's accounting context so debt can be normalized before cross-currency conversion.
563
+ // slither-disable-next-line calls-loop
564
+ JBAccountingContext memory accountingContext =
565
+ source.terminal.accountingContextForTokenOf({projectId: revnetId, token: source.token});
566
+
567
+ // Normalize each source from its native token decimals into the caller's requested decimals.
568
+ uint256 normalizedTokens;
569
+ if (accountingContext.decimals > decimals) {
570
+ normalizedTokens = tokensLoaned / (10 ** (accountingContext.decimals - decimals));
571
+ } else if (accountingContext.decimals < decimals) {
572
+ normalizedTokens = tokensLoaned * (10 ** (decimals - accountingContext.decimals));
573
+ } else {
574
+ normalizedTokens = tokensLoaned;
575
+ }
576
+
577
+ if (accountingContext.currency == currency) {
578
+ borrowedAmount += normalizedTokens;
579
+ } else {
580
+ // Convert source-token debt into the requested currency using the loans contract's shared prices.
581
+ // slither-disable-next-line calls-loop
582
+ uint256 pricePerUnit = loans.PRICES()
583
+ .pricePerUnitOf({
584
+ projectId: revnetId,
585
+ pricingCurrency: accountingContext.currency,
586
+ unitCurrency: currency,
587
+ decimals: decimals
588
+ });
589
+ if (pricePerUnit == 0) continue;
590
+
591
+ borrowedAmount += mulDiv({x: normalizedTokens, y: 10 ** decimals, denominator: pricePerUnit});
592
+ }
593
+ }
594
+ }
595
+
478
596
  //*********************************************************************//
479
597
  // --------------------- internal transactions ----------------------- //
480
598
  //*********************************************************************//
@@ -16,6 +16,7 @@ import {REVConfig} from "../structs/REVConfig.sol";
16
16
  import {REVCroptopAllowedPost} from "../structs/REVCroptopAllowedPost.sol";
17
17
  import {REVDeploy721TiersHookConfig} from "../structs/REVDeploy721TiersHookConfig.sol";
18
18
  import {REVSuckerDeploymentConfig} from "../structs/REVSuckerDeploymentConfig.sol";
19
+ import {IREVLoans} from "./IREVLoans.sol";
19
20
 
20
21
  /// @notice Deploys and manages revnets -- Juicebox projects with pre-configured tokenomics.
21
22
  interface IREVDeployer {
@@ -143,7 +144,7 @@ interface IREVDeployer {
143
144
 
144
145
  /// @notice The loan contract used by all revnets.
145
146
  /// @return The loans contract address.
146
- function LOANS() external view returns (address);
147
+ function LOANS() external view returns (IREVLoans);
147
148
 
148
149
  /// @notice The runtime data hook contract that handles pay and cash out callbacks for revnets.
149
150
  /// @return The owner contract address.
@@ -3,7 +3,10 @@ pragma solidity ^0.8.0;
3
3
 
4
4
  import {IJBController} from "@bananapus/core-v6/src/interfaces/IJBController.sol";
5
5
 
6
- /// @notice Manages hiding (burning) and revealing (re-minting) revnet tokens to exclude them from totalSupply.
6
+ /// @notice Manages hiding (burning) and revealing (re-minting) revnet tokens to exclude them from live totalSupply.
7
+ /// @dev Hidden balances are an operator-controlled security handle. They remain revealable, but cash-out and loan
8
+ /// accounting intentionally excludes `totalHiddenOf` so hidden inventory cannot dilute other holders' access to
9
+ /// revnet capital.
7
10
  interface IREVHiddenTokens {
8
11
  /// @notice Emitted when a holder is allowed or disallowed to hide their own tokens.
9
12
  /// @param revnetId The ID of the revnet.
@@ -2,9 +2,14 @@
2
2
  pragma solidity ^0.8.0;
3
3
 
4
4
  import {IREVDeployer} from "./IREVDeployer.sol";
5
+ import {IREVHiddenTokens} from "./IREVHiddenTokens.sol";
5
6
 
6
7
  /// @notice Interface for the REVOwner contract that handles runtime data hook and cash out hook behavior for revnets.
7
8
  interface IREVOwner {
9
+ /// @notice The hidden tokens contract used by the revnet owner hook.
10
+ /// @return The hidden tokens contract.
11
+ function HIDDEN_TOKENS() external view returns (IREVHiddenTokens);
12
+
8
13
  /// @notice The timestamp of when cashouts will become available to a specific revnet's participants.
9
14
  /// @param revnetId The ID of the revnet.
10
15
  /// @return The cash out delay timestamp.
@@ -1,8 +1,10 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity ^0.8.0;
3
3
 
4
- /// @custom:member chainId The ID of the chain on which the mint should be honored.
5
- /// @custom:member count The number of tokens that should be minted.
4
+ /// @notice A per-stage token mint that happens without any payment think of it as a scheduled premint. Each auto
5
+ /// issuance specifies a chain, beneficiary, and token count. Can be claimed once per stage via `autoIssueFor`.
6
+ /// @custom:member chainId The chain ID where this auto-issuance should be honored (only mints on the matching chain).
7
+ /// @custom:member count The number of tokens to mint for the beneficiary.
6
8
  /// @custom:member beneficiary The address that will receive the minted tokens.
7
9
  struct REVAutoIssuance {
8
10
  uint32 chainId;
@@ -4,11 +4,14 @@ pragma solidity ^0.8.0;
4
4
  import {REVDescription} from "./REVDescription.sol";
5
5
  import {REVStageConfig} from "./REVStageConfig.sol";
6
6
 
7
- /// @custom:member description The description of the revnet.
8
- /// @custom:member baseCurrency The currency that the issuance is based on.
9
- /// @custom:member splitOperator The address that will receive the token premint and initial production split,
10
- /// and who is allowed to change who the operator is. Only the operator can replace itself after deployment.
11
- /// @custom:member stageConfigurations The periods of changing constraints.
7
+ /// @notice Top-level configuration for deploying a revnet. Defines the revnet's identity, base currency for issuance
8
+ /// pricing, the split operator (who receives production splits and can reassign that role), and the ordered list of
9
+ /// stages that govern the revnet's lifecycle.
10
+ /// @custom:member description The revnet's name, ticker, metadata URI, and deployment salt.
11
+ /// @custom:member baseCurrency The currency that issuance pricing is denominated in (e.g. ETH or USD).
12
+ /// @custom:member splitOperator The address that receives production splits and can reassign the operator role.
13
+ /// Only the current operator can replace itself after deployment.
14
+ /// @custom:member stageConfigurations The ordered stages that define how the revnet's tokenomics evolve over time.
12
15
  struct REVConfig {
13
16
  REVDescription description;
14
17
  uint32 baseCurrency;
@@ -1,11 +1,12 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity ^0.8.0;
3
3
 
4
- /// @custom:member name The name of the ERC-20 token being create for the revnet.
5
- /// @custom:member ticker The ticker of the ERC-20 token being created for the revnet.
6
- /// @custom:member uri The metadata URI containing revnet's info.
7
- /// @custom:member salt Revnets deployed across chains by the same address with the same salt will have the same
8
- /// address.
4
+ /// @notice Identity and metadata for a revnet deployment.
5
+ /// @custom:member name The name of the ERC-20 token created for the revnet.
6
+ /// @custom:member ticker The ticker symbol of the ERC-20 token created for the revnet.
7
+ /// @custom:member uri The metadata URI containing the revnet's off-chain info (logo, description, links).
8
+ /// @custom:member salt A deployment salt — revnets deployed across chains by the same address with the same salt get
9
+ /// deterministic matching addresses.
9
10
  struct REVDescription {
10
11
  string name;
11
12
  string ticker;
@@ -3,12 +3,15 @@ pragma solidity ^0.8.0;
3
3
 
4
4
  import {REVLoanSource} from "./REVLoanSource.sol";
5
5
 
6
- /// @custom:member borrowedAmount The amount that is being borrowed.
7
- /// @custom:member collateralTokenCount The number of collateral tokens currently accounted for.
6
+ /// @notice An active loan against a revnet. The borrower locked collateral tokens (which were burned) and received
7
+ /// funds from the revnet's terminal. The loan can be repaid within the prepaid duration at no extra cost; after that,
8
+ /// repayment cost increases linearly until liquidation at 10 years.
9
+ /// @custom:member amount The amount borrowed (includes fees taken at creation).
10
+ /// @custom:member collateral The number of revnet tokens burned as collateral.
8
11
  /// @custom:member createdAt The timestamp when the loan was created.
9
- /// @custom:member prepaidFeePercent The percentage of the loan's fees that were prepaid.
10
- /// @custom:member prepaidDuration The duration that the loan was prepaid for.
11
- /// @custom:member source The source of the loan.
12
+ /// @custom:member prepaidFeePercent The percentage of fees prepaid at creation (determines prepaid duration).
13
+ /// @custom:member prepaidDuration The duration (seconds) during which repayment costs nothing beyond the original
14
+ /// amount. @custom:member source The terminal and token from which funds were drawn.
12
15
  struct REVLoan {
13
16
  uint112 amount;
14
17
  uint112 collateral;
@@ -5,22 +5,20 @@ import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
5
5
 
6
6
  import {REVAutoIssuance} from "./REVAutoIssuance.sol";
7
7
 
8
- /// @custom:member startsAtOrAfter The timestamp to start a stage at the given rate at or after.
9
- /// @custom:member autoIssuances The configurations of mints during this stage.
10
- /// @custom:member splitPercent The percentage of newly issued tokens that should be split with the operator, out
11
- /// of 10_000 (JBConstants.MAX_RESERVED_PERCENT).
12
- /// @custom:member splits The splits for the revnet.
13
- /// @custom:member initialIssuance The number of revnet tokens that one unit of the revnet's base currency will buy, as
14
- /// a fixed point number
15
- /// with 18 decimals.
16
- /// @custom:member issuanceCutFrequency The number of seconds between applied issuance decreases. This
17
- /// should be at least 24 hours.
18
- /// @custom:member issuanceCutPercent The percent that issuance should decrease over time. This percentage is out
19
- /// of 1_000_000_000 (JBConstants.MAX_CUT_PERCENT). 0% corresponds to no issuance increase.
20
- /// @custom:member cashOutTaxRate The factor determining how much each token can cash out from the revnet once
21
- /// cashed out. This rate is out of 10_000 (JBConstants.MAX_CASH_OUT_TAX_RATE). 0% corresponds to no tax when cashing
22
- /// out.
23
- /// @custom:member extraMetadata Extra info to attach set into this stage that may affect hooks.
8
+ /// @notice A stage in a revnet's lifecycle. Each stage defines the token issuance rate, how quickly it decays, what
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.
12
+ /// @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).
14
+ /// @custom:member splitPercent The percentage of newly issued tokens routed to splits, out of 10,000.
15
+ /// @custom:member splits The split recipients for this stage's production allocation.
16
+ /// @custom:member initialIssuance Tokens per unit of base currency at stage start (18-decimal fixed point).
17
+ /// @custom:member issuanceCutFrequency Seconds between each issuance reduction. Should be >= 24 hours.
18
+ /// @custom:member issuanceCutPercent How much issuance decreases each period, out of 1,000,000,000. 0 = no decay.
19
+ /// @custom:member cashOutTaxRate The tax on cash outs, out of 10,000. 0 = no tax (full reclaim). Higher = more tax
20
+ /// retained by the treasury.
21
+ /// @custom:member extraMetadata Additional metadata bits passed to hooks for stage-specific behavior.
24
22
  struct REVStageConfig {
25
23
  uint48 startsAtOrAfter;
26
24
  REVAutoIssuance[] autoIssuances;
package/ADMINISTRATION.md DELETED
@@ -1,73 +0,0 @@
1
- # Administration
2
-
3
- ## At A Glance
4
-
5
- | Item | Details |
6
- | --- | --- |
7
- | Scope | Revnet deployment shape, bounded runtime operators, loan-owner cosmetics, and optional integration control surfaces |
8
- | Control posture | Intentionally narrow and mostly deployment-defined |
9
- | Highest-risk actions | Bad stage design, wrong split-operator assignment, and misunderstanding which runtime surfaces stay live after launch |
10
- | Recovery posture | Usually replacement, not patching; the design intentionally avoids easy admin escape hatches |
11
-
12
- ## Purpose
13
-
14
- `revnet-core-v6` is designed to collapse ordinary post-launch governance into deployment-time decisions plus a small set of bounded runtime roles. The main administration task is understanding which power still exists and which power was intentionally removed.
15
-
16
- ## Control Model
17
-
18
- - `REVDeployer` holds the project NFT and therefore remains part of the ownership model.
19
- - Revnet economics are mainly fixed at deployment through staged rulesets.
20
- - `REVOwner` provides live runtime policy, but not broad human governance.
21
- - Split operators can hold narrow powers depending on stage and deployment config.
22
- - `REVLoans` has a cosmetic global owner surface, but loan economics are still bounded by revnet logic.
23
-
24
- ## Roles
25
-
26
- | Role | How Assigned | Scope | Notes |
27
- | --- | --- | --- | --- |
28
- | `REVDeployer` | Deployed singleton | Global launcher and project-NFT holder | Part of the ownership model |
29
- | Split operator | Deployment config | Per revnet | Holds only the allowed operator envelope |
30
- | Auto-issuance beneficiary | Deployment config | Per stage | Can receive preconfigured stage issuance |
31
- | Borrower or delegated loan operator | Token holder plus permission | Per holder or loan | Can open or manage loans within loan rules |
32
- | `REVLoans` owner | Constructor owner | Global cosmetic/admin surface | Does not turn Revnets back into ordinary governed projects |
33
-
34
- ## Privileged Surfaces
35
-
36
- - `deployFor(...)` defines the revnet's long-lived shape
37
- - split-operator paths can manage only the permissions left open by deployment
38
- - `autoIssueFor(...)` consumes preconfigured stage issuance
39
- - loan operators can redirect borrowed value if a holder delegates loan permissions
40
- - hidden-token flows require the holder's permission grant and mint permission wiring through `REVOwner`
41
-
42
- ## Immutable And One-Way
43
-
44
- - Stage configuration is effectively permanent after deployment.
45
- - The deployer-held project NFT is not a normal owner-recovery tool.
46
- - Loan collateral is burned at borrow time and only reminted through repayment or documented flows.
47
- - Hidden-token balances change visible supply until reveal.
48
-
49
- ## Operational Notes
50
-
51
- - Treat revnet launch as the real governance decision.
52
- - Validate stage timing, split-operator scope, and optional integrations before deployment.
53
- - Review cash-out delay, hidden-token semantics, and loan permissions together.
54
- - Do not assume there is a broad admin override for bad economics after launch.
55
-
56
- ## Machine Notes
57
-
58
- - Do not describe Revnets as fully adminless if the deployer-held NFT still matters for the trust model.
59
- - Also do not describe them as ordinary owner-controlled projects. The point is that the available control surface is intentionally narrow.
60
- - If a question is about runtime cash-outs, buybacks, or mint permissions, inspect `REVOwner` before inferring behavior from deployment prose.
61
-
62
- ## Recovery
63
-
64
- - If launch-time economics are wrong, recovery usually means replacement, not in-place repair.
65
- - If optional integrations are misconfigured, fix only where the code still exposes a valid path.
66
- - If the design intentionally omitted a recovery path, do not invent one in documentation or ops guidance.
67
-
68
- ## Admin Boundaries
69
-
70
- - No ordinary owner can casually rewrite staged economics after launch.
71
- - Split operators are not general-purpose governors.
72
- - Loan mechanics, hidden-token mechanics, and cash-out policy remain bounded by the deployed revnet logic.
73
- - This repo should not be documented as if it had a normal mutable project-owner model.