@rev-net/core-v6 0.0.8 → 0.0.10

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 (62) hide show
  1. package/ADMINISTRATION.md +186 -0
  2. package/ARCHITECTURE.md +87 -0
  3. package/README.md +8 -6
  4. package/RISKS.md +49 -0
  5. package/SKILLS.md +24 -10
  6. package/STYLE_GUIDE.md +558 -0
  7. package/docs/src/README.md +2 -2
  8. package/foundry.toml +9 -6
  9. package/package.json +12 -9
  10. package/remappings.txt +1 -1
  11. package/script/Deploy.s.sol +4 -3
  12. package/script/helpers/RevnetCoreDeploymentLib.sol +1 -1
  13. package/src/REVDeployer.sol +103 -76
  14. package/src/REVLoans.sol +14 -4
  15. package/src/interfaces/IREVDeployer.sol +2 -1
  16. package/src/structs/REV721TiersHookFlags.sol +14 -0
  17. package/src/structs/REVBaseline721HookConfig.sol +27 -0
  18. package/src/structs/REVDeploy721TiersHookConfig.sol +2 -2
  19. package/test/REV.integrations.t.sol +4 -3
  20. package/test/REVAutoIssuanceFuzz.t.sol +12 -8
  21. package/test/{REVDeployerAuditRegressions.t.sol → REVDeployerRegressions.t.sol} +4 -3
  22. package/test/REVInvincibility.t.sol +23 -25
  23. package/test/REVInvincibilityHandler.sol +1 -0
  24. package/test/REVLifecycle.t.sol +4 -4
  25. package/test/REVLoans.invariants.t.sol +5 -3
  26. package/test/REVLoansAttacks.t.sol +7 -10
  27. package/test/REVLoansFeeRecovery.t.sol +4 -5
  28. package/test/REVLoansFindings.t.sol +644 -0
  29. package/test/{REVLoansAuditRegressions.t.sol → REVLoansRegressions.t.sol} +14 -25
  30. package/test/REVLoansSourced.t.sol +7 -4
  31. package/test/REVLoansUnSourced.t.sol +4 -3
  32. package/test/{TestPR26_BurnHeldTokens.t.sol → TestBurnHeldTokens.t.sol} +4 -3
  33. package/test/{TestPR27_CEIPattern.t.sol → TestCEIPattern.t.sol} +6 -5
  34. package/test/{TestPR15_CashOutCallerValidation.t.sol → TestCashOutCallerValidation.t.sol} +4 -5
  35. package/test/{TestPR09_ConversionDocumentation.t.sol → TestConversionDocumentation.t.sol} +4 -3
  36. package/test/{TestPR13_CrossSourceReallocation.t.sol → TestCrossSourceReallocation.t.sol} +4 -3
  37. package/test/TestEmptyBuybackSpecs.t.sol +4 -3
  38. package/test/{TestPR12_FlashLoanSurplus.t.sol → TestFlashLoanSurplus.t.sol} +4 -3
  39. package/test/{TestPR22_HookArrayOOB.t.sol → TestHookArrayOOB.t.sol} +4 -3
  40. package/test/{TestPR10_LiquidationBehavior.t.sol → TestLiquidationBehavior.t.sol} +7 -6
  41. package/test/{TestPR11_LowFindings.t.sol → TestLowFindings.t.sol} +4 -3
  42. package/test/{TestPR32_MixedFixes.t.sol → TestMixedFixes.t.sol} +4 -3
  43. package/test/TestSplitWeightAdjustment.t.sol +445 -0
  44. package/test/TestSplitWeightE2E.t.sol +528 -0
  45. package/test/TestSplitWeightFork.t.sol +780 -0
  46. package/test/TestStageTransitionBorrowable.t.sol +4 -3
  47. package/test/{TestPR29_SwapTerminalPermission.t.sol → TestSwapTerminalPermission.t.sol} +4 -3
  48. package/test/{TestPR21_Uint112Overflow.t.sol → TestUint112Overflow.t.sol} +7 -6
  49. package/test/{TestPR16_ZeroRepayment.t.sol → TestZeroRepayment.t.sol} +7 -8
  50. package/test/fork/ForkTestBase.sol +649 -0
  51. package/test/fork/TestCashOutFork.t.sol +246 -0
  52. package/test/fork/TestLoanBorrowFork.t.sol +161 -0
  53. package/test/fork/TestLoanCrossRulesetFork.t.sol +300 -0
  54. package/test/fork/TestLoanLiquidationFork.t.sol +134 -0
  55. package/test/fork/TestLoanReallocateFork.t.sol +112 -0
  56. package/test/fork/TestLoanRepayFork.t.sol +187 -0
  57. package/test/fork/TestSplitWeightFork.t.sol +186 -0
  58. package/test/mock/MockBuybackDataHook.sol +9 -9
  59. package/test/mock/MockBuybackDataHookMintPath.sol +10 -9
  60. package/test/regression/{TestI20_CumulativeLoanCounter.t.sol → TestCumulativeLoanCounter.t.sol} +9 -8
  61. package/test/regression/{TestL27_LiquidateGapHandling.t.sol → TestLiquidateGapHandling.t.sol} +9 -8
  62. package/SECURITY.md +0 -68
@@ -0,0 +1,186 @@
1
+ # Administration
2
+
3
+ Admin privileges and their scope in revnet-core-v6. Revnets are designed to be autonomous Juicebox projects with no traditional owner. This document covers what privileged operations exist, who can perform them, and -- critically -- what is intentionally made impossible.
4
+
5
+ ## Roles
6
+
7
+ ### Split Operator
8
+
9
+ - **How assigned:** Specified at deployment via `REVConfig.splitOperator`. After deployment, only the current split operator can transfer the role to a new address by calling `setSplitOperatorOf()`.
10
+ - **Scope:** Per-revnet. Each revnet has at most one split operator. The operator is the only human-controlled role in a deployed revnet.
11
+ - **Cannot be removed:** The split operator can be replaced but there is no mechanism to entirely revoke the role (it can be set to an unreachable address like `address(0)` to effectively disable it).
12
+
13
+ ### REVLoans Owner (Ownable)
14
+
15
+ - **How assigned:** Set at `REVLoans` contract deployment via the `owner` constructor parameter. Transferable via OpenZeppelin `Ownable.transferOwnership()`.
16
+ - **Scope:** Global across all revnets using this loans contract. Controls only the loan NFT metadata URI resolver -- has no power over loan parameters, collateral, or funds.
17
+
18
+ ### REVDeployer (as Juicebox project owner)
19
+
20
+ - **How assigned:** Automatic. When a revnet is deployed, the `REVDeployer` contract becomes the permanent owner of the Juicebox project NFT. If initializing an existing project, the caller's project NFT is irreversibly transferred to `REVDeployer`.
21
+ - **Scope:** The deployer holds the project NFT and uses its owner authority to enforce revnet rules. It acts as a protocol-level constraint layer, not as a discretionary admin. No human can exercise this ownership.
22
+
23
+ ### Loan NFT Owner
24
+
25
+ - **How assigned:** The `_msgSender()` who calls `borrowFrom()` receives the loan ERC-721. Transferable like any ERC-721.
26
+ - **Scope:** Per-loan. Only the current NFT owner can repay the loan, return collateral, or reallocate collateral to a new loan.
27
+
28
+ ### Anyone (Permissionless)
29
+
30
+ - **Scope:** Several functions are callable by any address with no access control, as documented in the Privileged Functions tables below.
31
+
32
+ ## Privileged Functions
33
+
34
+ ### REVDeployer
35
+
36
+ | Function | Required Role | Permission ID | What It Does |
37
+ |----------|--------------|---------------|-------------|
38
+ | `deployFor()` | Anyone (new revnet) or Juicebox project owner (existing project) | None | Deploys a new revnet or irreversibly converts an existing Juicebox project into a revnet. |
39
+ | `deployWith721sFor()` | Anyone (new revnet) or Juicebox project owner (existing project) | None | Same as `deployFor()` but also deploys a tiered ERC-721 hook and optional croptop posting rules. |
40
+ | `deploySuckersFor()` | Split Operator | Checked via `_checkIfIsSplitOperatorOf()` | Deploys new cross-chain suckers for an existing revnet. Also requires the current ruleset's `extraMetadata` bit 2 to be set (allows deploying suckers). |
41
+ | `setSplitOperatorOf()` | Split Operator | Checked via `_checkIfIsSplitOperatorOf()` | Replaces the current split operator with a new address. Revokes all operator permissions from the caller and grants them to the new address. |
42
+ | `autoIssueFor()` | Anyone | None | Mints pre-configured auto-issuance tokens for a beneficiary once the relevant stage has started. Amounts are set at deployment and can only be claimed once. |
43
+ | `burnHeldTokensOf()` | Anyone | None | Burns any of a revnet's tokens held by the `REVDeployer` contract (e.g., from reserved token splits that did not sum to 100%). |
44
+ | `afterCashOutRecordedWith()` | Anyone (called by terminal) | None | Processes cash-out fees. No caller validation needed because a non-terminal caller would only be donating their own funds. |
45
+
46
+ ### Split Operator Permissions (granted via JBPermissions)
47
+
48
+ The split operator receives the following Juicebox permission IDs, scoped to its revnet:
49
+
50
+ | Permission ID | What It Allows |
51
+ |---------------|----------------|
52
+ | `SET_SPLIT_GROUPS` | Change how reserved tokens are distributed among split recipients. |
53
+ | `SET_BUYBACK_POOL` | Configure which Uniswap V4 pool is used for the buyback hook. |
54
+ | `SET_BUYBACK_TWAP` | Adjust the TWAP window for the buyback hook. |
55
+ | `SET_PROJECT_URI` | Update the revnet's metadata URI. |
56
+ | `ADD_PRICE_FEED` | Add a new price feed for the revnet. |
57
+ | `SUCKER_SAFETY` | Manage sucker safety settings (e.g., emergency hatch). |
58
+ | `SET_BUYBACK_HOOK` | Configure the buyback hook. |
59
+ | `SET_ROUTER_TERMINAL` | Set the router terminal. |
60
+
61
+ Optional 721 permissions (granted only if enabled at deployment via `REVDeploy721TiersHookConfig`):
62
+
63
+ | Permission ID | Deployment Flag | What It Allows |
64
+ |---------------|----------------|----------------|
65
+ | `ADJUST_721_TIERS` | `splitOperatorCanAdjustTiers` | Add or remove ERC-721 tiers. |
66
+ | `SET_721_METADATA` | `splitOperatorCanUpdateMetadata` | Update ERC-721 tier metadata. |
67
+ | `MINT_721` | `splitOperatorCanMint` | Mint ERC-721s without payment from tiers with `allowOwnerMint`. |
68
+ | `SET_721_DISCOUNT_PERCENT` | `splitOperatorCanIncreaseDiscountPercent` | Increase the discount percentage of a tier. |
69
+
70
+ ### REVLoans
71
+
72
+ | Function | Required Role | Access Control | What It Does |
73
+ |----------|--------------|----------------|-------------|
74
+ | `borrowFrom()` | Anyone (must hold revnet tokens) | None -- but caller's tokens are burned as collateral | Opens a loan against revnet token collateral. |
75
+ | `repayLoan()` | Loan NFT Owner | `_ownerOf(loanId) == _msgSender()` | Repays a loan (partially or fully) and returns collateral. |
76
+ | `reallocateCollateralFromLoan()` | Loan NFT Owner | `_ownerOf(loanId) == _msgSender()` | Splits excess collateral from an existing loan into a new loan. |
77
+ | `liquidateExpiredLoansFrom()` | Anyone | None | Liquidates loans that have exceeded the 10-year liquidation duration. Permanently destroys collateral. |
78
+ | `setTokenUriResolver()` | REVLoans Owner | `onlyOwner` (OpenZeppelin Ownable) | Sets the contract that resolves loan NFT metadata URIs. |
79
+
80
+ ### Constructor-Level Permissions (set once at deployment)
81
+
82
+ These permissions are granted in the `REVDeployer` constructor and apply globally (wildcard `projectId = 0`):
83
+
84
+ | Grantee | Permission ID | Purpose |
85
+ |---------|---------------|---------|
86
+ | `SUCKER_REGISTRY` | `MAP_SUCKER_TOKEN` | Allows the sucker registry to map tokens for all revnets. |
87
+ | `LOANS` | `USE_ALLOWANCE` | Allows the loans contract to use surplus allowance from all revnets to fund loans. |
88
+
89
+ ## Autonomous Design
90
+
91
+ Revnets are designed to operate without a traditional project owner. The following mechanisms enforce autonomy:
92
+
93
+ - **Ownership transfer is permanent.** When a revnet is deployed, the Juicebox project NFT is transferred to the `REVDeployer` contract. No human holds the project NFT. There is no function to transfer it back.
94
+ - **No ruleset queuing.** The `REVDeployer` does not expose any function to queue new rulesets after deployment. The stage progression is fully determined at deploy time. Nobody -- not the split operator, not the deployer, not anyone -- can change the issuance schedule, cash-out tax rates, or stage timing after deployment.
95
+ - **No approval hooks.** All rulesets are deployed with `approvalHook = address(0)`. There is no mechanism to block or delay stage transitions.
96
+ - **Cash outs cannot be fully disabled.** The deployer enforces `cashOutTaxRate < MAX_CASH_OUT_TAX_RATE` for every stage, guaranteeing that token holders always retain some ability to cash out.
97
+ - **Data hook is the deployer itself.** The `REVDeployer` is set as the data hook (`metadata.dataHook = address(this)`) for all rulesets, ensuring consistent fee and sucker logic without external admin control.
98
+ - **Mint permission is restricted.** Only the loans contract, the buyback hook (and its delegates), and registered suckers can mint tokens. The split operator cannot mint fungible revnet tokens.
99
+ - **No held fee manipulation.** The deployer has no function to process or return held fees arbitrarily.
100
+ - **Owner minting is constrained.** While `allowOwnerMinting = true` is set in ruleset metadata, the "owner" is the `REVDeployer` contract. It only uses this to mint auto-issuance tokens (amounts fixed at deployment) and to return loan collateral.
101
+
102
+ ## Loan Administration
103
+
104
+ The `REVLoans` contract has minimal admin surface by design:
105
+
106
+ - **All economic parameters are constants.** Loan liquidation duration (10 years), fee percentages (MIN 2.5%, MAX 50%), and the REV fee (1%) are hardcoded as immutable constants. No admin can change them.
107
+ - **The only admin function is `setTokenUriResolver()`**, which controls how loan NFTs render their metadata. This is purely cosmetic and has no effect on loan economics, collateral, or fund flows.
108
+ - **Loan management is permissioned to NFT holders only.** Repayment, collateral reallocation, and refinancing require ownership of the specific loan's ERC-721 NFT.
109
+ - **Liquidation is permissionless.** Anyone can call `liquidateExpiredLoansFrom()` for loans past the 10-year duration.
110
+
111
+ ## Immutable Configuration
112
+
113
+ The following parameters are set at deployment and can never be changed:
114
+
115
+ ### REVDeployer (per-revnet, set at `deployFor` / `deployWith721sFor` time)
116
+ - Stage schedule (start times, issuance rates, cut frequencies, cut percentages)
117
+ - Cash-out tax rates per stage
118
+ - Split percentages per stage
119
+ - Auto-issuance amounts and beneficiaries
120
+ - Base currency
121
+ - ERC-20 token name and symbol
122
+ - Encoded configuration hash (used for cross-chain sucker deployment verification)
123
+
124
+ ### REVDeployer (global, set at contract deployment)
125
+ - `CONTROLLER` -- the Juicebox controller
126
+ - `DIRECTORY` -- the Juicebox directory
127
+ - `PROJECTS` -- the Juicebox projects NFT contract
128
+ - `PERMISSIONS` -- the Juicebox permissions contract
129
+ - `SUCKER_REGISTRY` -- the sucker registry
130
+ - `BUYBACK_HOOK` -- the buyback hook / data hook
131
+ - `HOOK_DEPLOYER` -- the 721 tiers hook deployer
132
+ - `PUBLISHER` -- the croptop publisher
133
+ - `LOANS` -- the loans contract address
134
+ - `FEE_REVNET_ID` -- the project ID that receives cash-out fees
135
+ - `FEE` -- the cash-out fee (2.5%)
136
+ - `CASH_OUT_DELAY` -- 30 days for cross-chain deployments
137
+
138
+ ### REVLoans (global, set at contract deployment)
139
+ - `CONTROLLER`, `DIRECTORY`, `PRICES`, `PROJECTS` -- protocol infrastructure
140
+ - `REV_ID` -- the REV revnet that receives loan fees
141
+ - `PERMIT2` -- the permit2 contract
142
+ - `LOAN_LIQUIDATION_DURATION` -- 10 years (3650 days)
143
+ - `MIN_PREPAID_FEE_PERCENT` -- 2.5% (`25` out of `MAX_FEE = 1000`)
144
+ - `MAX_PREPAID_FEE_PERCENT` -- 50% (`500` out of `MAX_FEE = 1000`)
145
+ - `REV_PREPAID_FEE_PERCENT` -- 1% (`10` out of `MAX_FEE = 1000`)
146
+
147
+ ## Admin Boundaries
148
+
149
+ What admins **cannot** do -- this is the most important section for understanding revnet security guarantees:
150
+
151
+ ### The Split Operator Cannot:
152
+ - Change issuance rates, schedules, or weight decay
153
+ - Modify cash-out tax rates
154
+ - Queue new rulesets or stages
155
+ - Pause or disable cash outs
156
+ - Mint fungible revnet tokens (only 721 minting if explicitly enabled at deploy)
157
+ - Access or redirect treasury funds (no payout limit control)
158
+ - Upgrade or migrate the revnet's controller
159
+ - Change the revnet's terminals
160
+ - Transfer the project NFT
161
+ - Modify fund access limits or surplus allowances
162
+ - Change the data hook or approval hook
163
+ - Affect loan parameters or collateral
164
+
165
+ ### The REVLoans Owner Cannot:
166
+ - Change loan interest rates, fees, or liquidation timing
167
+ - Access or redirect collateral or borrowed funds
168
+ - Prevent loan creation, repayment, or liquidation
169
+ - Mint or burn tokens
170
+ - Affect any revnet's configuration
171
+
172
+ ### The REVDeployer Contract Cannot (even though it holds the project NFT):
173
+ - Queue new rulesets (no public or internal function exists for this)
174
+ - Transfer the project NFT to any other address
175
+ - Change terminals or the controller
176
+ - Modify fund access limits after deployment
177
+ - Override the data hook logic
178
+ - Selectively block cash outs (beyond the time-limited `CASH_OUT_DELAY` for cross-chain deployments)
179
+
180
+ ### Nobody Can:
181
+ - Change a revnet's stage schedule after deployment
182
+ - Prevent token holders from eventually cashing out
183
+ - Extract funds from the treasury without going through the bonding curve
184
+ - Modify the fee structure (2.5% cash-out fee, loan fees)
185
+ - Change which contract is the data hook for a revnet
186
+ - Alter auto-issuance amounts after deployment (they can only be claimed, not changed)
@@ -0,0 +1,87 @@
1
+ # revnet-core-v6 — Architecture
2
+
3
+ ## Purpose
4
+
5
+ Autonomous revenue networks ("revnets") built on Juicebox V6. REVDeployer creates projects with pre-programmed multi-stage rulesets that cannot be changed after deployment. REVLoans enables borrowing against locked revnet tokens.
6
+
7
+ ## Contract Map
8
+
9
+ ```
10
+ src/
11
+ ├── REVDeployer.sol — Deploys revnets: stages → rulesets, data hook, buyback, suckers, 721 tiers
12
+ ├── REVLoans.sol — Borrow against locked revnet tokens (10-year max, gradual liquidation)
13
+ ├── interfaces/
14
+ │ ├── IREVDeployer.sol
15
+ │ └── IREVLoans.sol
16
+ └── structs/ — REVConfig, REVStageConfig, REVLoanSource, REVAutoIssuance, etc.
17
+ ```
18
+
19
+ ## Key Data Flows
20
+
21
+ ### Revnet Deployment
22
+ ```
23
+ Deployer → REVDeployer.deployFor()
24
+ → Create JB project via JBController
25
+ → Convert REV stages → JBRulesetConfigs
26
+ → Each stage: duration, weight, cashOutTaxRate, splits
27
+ → Auto-issuance: pre-mint tokens to specified beneficiaries per chain
28
+ → Set REVDeployer as data hook (controls pay + cashout behavior)
29
+ → Initialize buyback pools at 1:1 price, configure buyback hook
30
+ → Deploy suckers for cross-chain operation
31
+ → Deploy 721 tiers if specified
32
+ → Compute matching hash for cross-chain deployment verification
33
+ ```
34
+
35
+ ### Data Hook Behavior
36
+ ```
37
+ Payment → REVDeployer.beforePayRecordedWith()
38
+ → Delegate to buyback hook for swap-vs-mint decision
39
+ → Return pay hook specifications
40
+
41
+ Cash Out → REVDeployer.beforeCashOutRecordedWith()
42
+ → If caller is a sucker: 0% cash out tax (bridging privilege)
43
+ → Otherwise: apply configured cashOutTaxRate
44
+ → Return cash out hook specifications
45
+ ```
46
+
47
+ ### Loan Flow
48
+ ```
49
+ Borrower → REVLoans.borrowFrom()
50
+ → Lock borrower's revnet tokens as collateral
51
+ → Calculate max borrow based on bonding curve value
52
+ → Transfer borrowed funds to borrower
53
+ → Create loan with 10-year max term
54
+
55
+ Repay → REVLoans.repayLoan()
56
+ → Accept repayment (principal + prepay fee)
57
+ → Return locked collateral tokens to borrower
58
+
59
+ Liquidate → REVLoans.liquidateLoan()
60
+ → After loan term, gradually release collateral
61
+ → Liquidation schedule spreads over time
62
+ ```
63
+
64
+ ## Extension Points
65
+
66
+ | Point | Interface | Purpose |
67
+ |-------|-----------|---------|
68
+ | Data hook | `IJBRulesetDataHook` | REVDeployer acts as data hook for all revnets |
69
+ | Buyback hook | `IJBBuybackHook` | Swap-vs-mint decision on payments |
70
+ | Sucker integration | `IJBSucker` | Cross-chain token bridging |
71
+ | 721 tiers | `IJB721TiersHook` | NFT tier rewards |
72
+
73
+ ## Dependencies
74
+ - `@bananapus/core-v6` — Core protocol
75
+ - `@bananapus/721-hook-v6` — NFT tiers
76
+ - `@bananapus/buyback-hook-v6` — DEX buyback
77
+ - `@bananapus/suckers-v6` — Cross-chain bridging
78
+ - `@bananapus/router-terminal-v6` — Payment routing
79
+ - `@bananapus/permission-ids-v6` — Permission constants
80
+ - `@croptop/core-v6` — Croptop integration
81
+ - `@openzeppelin/contracts` — Standard utilities
82
+
83
+ ## Key Design Decisions
84
+ - Stages are immutable after deployment — no owner can change ruleset parameters
85
+ - Matching hash ensures cross-chain deployments have identical economic parameters
86
+ - REVDeployer is the data hook for all revnets it deploys — centralizes behavioral control
87
+ - Loans use bonding curve value, not market price — immune to external price manipulation
package/README.md CHANGED
@@ -16,9 +16,9 @@ Revnets are autonomous Juicebox projects with predetermined economic stages. Eac
16
16
 
17
17
  ```
18
18
  1. Deploy revnet with stage configurations
19
- → REVDeployer.deployFor(revnetId=0, config, terminals, ...)
19
+ → REVDeployer.deployFor(revnetId=0, config, terminals, suckerConfig)
20
20
  → Creates Juicebox project owned by REVDeployer (permanently)
21
- → Deploys ERC-20 token, configures buyback pools, deploys suckers
21
+ → Deploys ERC-20 token, initializes buyback pools at 1:1 price, deploys suckers
22
22
  |
23
23
  2. Stage 1 begins (startsAtOrAfter or block.timestamp)
24
24
  → Tokens issued at initialIssuance rate per unit of base currency
@@ -62,7 +62,7 @@ Revnets are autonomous Juicebox projects with predetermined economic stages. Eac
62
62
 
63
63
  | Contract | Description |
64
64
  |----------|-------------|
65
- | `REVDeployer` | Deploys revnets as Juicebox projects owned by the deployer contract itself (no human owner). Translates stage configurations into Juicebox rulesets, manages buyback hooks, tiered 721 hooks, suckers, split operators, auto-issuance, and cash-out fees. Acts as the ruleset data hook and cash-out hook for every revnet it deploys. |
65
+ | `REVDeployer` | Deploys revnets as Juicebox projects owned by the deployer contract itself (no human owner). Translates stage configurations into Juicebox rulesets, manages buyback hooks, tiered 721 hooks, suckers, split operators, auto-issuance, and cash-out fees. Acts as the ruleset data hook and cash-out hook for every revnet it deploys. When 721 tier splits are active, adjusts the payment weight so the terminal only mints tokens proportional to the amount entering the project treasury (the split portion is forwarded separately). |
66
66
  | `REVLoans` | Lets participants borrow against their revnet tokens. Collateral tokens are burned on borrow and re-minted on repayment. Each loan is an ERC-721 NFT. Charges a prepaid fee (2.5% min, 50% max) that determines the interest-free duration; after that window, a time-proportional source fee accrues. Loans liquidate after 10 years. |
67
67
 
68
68
  ### How They Relate
@@ -114,7 +114,9 @@ src/
114
114
  REVAutoIssuance.sol # Per-stage cross-chain premint
115
115
  REVLoan.sol # Loan state
116
116
  REVLoanSource.sol # Terminal + token pair for loans
117
- REVDeploy721TiersHookConfig.sol # 721 hook deployment config
117
+ REVBaseline721HookConfig.sol # 721 hook config (omits issueTokensForSplits)
118
+ REV721TiersHookFlags.sol # 721 hook flags for revnets (no issueTokensForSplits)
119
+ REVDeploy721TiersHookConfig.sol # 721 hook deployment config wrapper
118
120
  REVCroptopAllowedPost.sol # Croptop posting criteria
119
121
  REVSuckerDeploymentConfig.sol # Cross-chain sucker deployment
120
122
  test/
@@ -123,13 +125,13 @@ test/
123
125
  REVAutoIssuanceFuzz.t.sol # Auto-issuance fuzz tests
124
126
  REVInvincibility.t.sol # Economic property fuzzing
125
127
  REVInvincibilityHandler.sol # Fuzz handler
126
- REVDeployerAuditRegressions.t.sol # Deployer audit regressions
128
+ REVDeployerRegressions.t.sol # Deployer regressions
127
129
  REVLoansSourced.t.sol # Multi-source loan tests
128
130
  REVLoansUnSourced.t.sol # Loan error cases
129
131
  REVLoansFeeRecovery.t.sol # Fee calculation tests
130
132
  REVLoansAttacks.t.sol # Flash loan, reentrancy scenarios
131
133
  REVLoans.invariants.t.sol # Loan fuzzing invariants
132
- REVLoansAuditRegressions.t.sol # Loan audit regressions
134
+ REVLoansRegressions.t.sol # Loan regressions
133
135
  TestPR09-32_*.t.sol # Per-PR regression tests
134
136
  helpers/
135
137
  MaliciousContracts.sol # Attack contract mocks
package/RISKS.md ADDED
@@ -0,0 +1,49 @@
1
+ # revnet-core-v6 — Risks
2
+
3
+ ## Trust Assumptions
4
+
5
+ 1. **REVDeployer Contract** — Acts as data hook for all deployed revnets. A bug in REVDeployer affects every revnet's pay and cashout behavior.
6
+ 2. **Immutable Stages** — Once deployed, stage parameters cannot be changed. If configured incorrectly, there is no fix (by design — this IS the trust model).
7
+ 3. **Buyback Hook** — REVDeployer delegates to the buyback hook for swap-vs-mint decisions. Buyback hook failure falls back to direct minting.
8
+ 4. **Suckers** — Cross-chain bridge implementations trusted for token transport. Bridge compromise = fund loss.
9
+ 5. **Core Protocol** — Relies on JBController, JBMultiTerminal, JBTerminalStore for correct operation.
10
+
11
+ ## Known Risks
12
+
13
+ | Risk | Description | Mitigation |
14
+ |------|-------------|------------|
15
+ | Irreversible deployment | Stage parameters cannot be changed after deployment | Thorough testing before deploy; matching hash verification |
16
+ | Loan collateral manipulation | Attacker inflates surplus to borrow more, then deflates | Borrow based on bonding curve value at time of borrow; existing loans unaffected by surplus changes |
17
+ | 10-year liquidation drift | Collateral real value may diverge from loan over 10 years | Gradual liquidation schedule; early repayment available |
18
+ | Loans beat cash-outs | Above ~39% cashOutTaxRate, borrowing is more capital-efficient than cashing out | By design (CryptoEconLab finding); creates natural demand for loans |
19
+ | Matching hash gap | Hash covers economic parameters but NOT terminal configs, accounting contexts, or token mappings | Verify full configuration manually before cross-chain deploy |
20
+
21
+ ## INTEROP-6: NATIVE_TOKEN on Non-ETH Chains
22
+
23
+ **Severity:** Medium
24
+ **Status:** Acknowledged — by design
25
+
26
+ When a revnet expands to a chain where the native token is not ETH (e.g., Celo), using `NATIVE_TOKEN` as the accounting context creates a semantic mismatch — CELO payments priced as ETH without a price feed.
27
+
28
+ **Impact:** Issuance mispricing, surplus fragmentation, cross-chain arbitrage.
29
+
30
+ **Safe chains:** Ethereum, Optimism, Base, Arbitrum
31
+ **Affected chains:** Celo, Polygon, Avalanche, BNB Chain
32
+
33
+ **Mitigation:** Use WETH ERC20 (not NATIVE_TOKEN) on non-ETH chains. Map `WETH → WETH` in sucker token mappings.
34
+
35
+ ## Privileged Roles
36
+
37
+ | Role | Capabilities | Notes |
38
+ |------|-------------|-------|
39
+ | Deployer (one-time) | Configures all stage parameters | Parameters immutable after deploy |
40
+ | Auto-issuance beneficiaries | Receive pre-minted tokens per stage | Configured at deploy time |
41
+ | Suckers | 0% cashout tax privilege | Enables cross-chain bridging without fee |
42
+
43
+ ## Reentrancy Considerations
44
+
45
+ | Function | Protection | Risk |
46
+ |----------|-----------|------|
47
+ | `REVLoans.borrowFrom` | Collateral locked BEFORE funds transferred | LOW |
48
+ | `REVLoans.repayLoan` | Loan state cleared BEFORE collateral returned | LOW |
49
+ | `REVDeployer.beforePayRecordedWith` | View function, no state changes | NONE |
package/SKILLS.md CHANGED
@@ -17,15 +17,15 @@ Deploy and manage Revnets -- autonomous, unowned Juicebox projects with staged i
17
17
 
18
18
  | Function | What it does |
19
19
  |----------|-------------|
20
- | `REVDeployer.deployFor(revnetId, config, terminals, buybackHookConfig, suckerConfig)` | Deploy a new revnet (`revnetId=0`) or convert an existing Juicebox project. Encodes stage configs into rulesets, deploys ERC-20 token, sets up split operator, buyback pools, suckers, and loans permissions. |
21
- | `REVDeployer.deployWith721sFor(revnetId, config, terminals, buybackHookConfig, suckerConfig, hookConfig, allowedPosts)` | Same as `deployFor` but also deploys a tiered ERC-721 hook. Optionally configures Croptop posting criteria and grants publisher permission to add tiers. |
20
+ | `REVDeployer.deployFor(revnetId, config, terminals, suckerConfig)` | Deploy a new revnet (`revnetId=0`) or convert an existing Juicebox project. Encodes stage configs into rulesets, deploys ERC-20 token, initializes buyback pool at 1:1 price, sets up split operator, suckers, and loans permissions. |
21
+ | `REVDeployer.deployWith721sFor(revnetId, config, terminals, suckerConfig, hookConfig, allowedPosts)` | Same as `deployFor` but also deploys a tiered ERC-721 hook. Optionally configures Croptop posting criteria and grants publisher permission to add tiers. |
22
22
  | `REVDeployer.deploySuckersFor(revnetId, suckerConfig)` | Deploy new cross-chain suckers post-launch. Split operator only. Validates ruleset allows sucker deployment (bit 2 of `extraMetadata`). Uses stored config hash for cross-chain matching. |
23
23
 
24
24
  ### Data Hooks
25
25
 
26
26
  | Function | What it does |
27
27
  |----------|-------------|
28
- | `REVDeployer.beforePayRecordedWith(context)` | Returns adjusted weight from buyback hook and assembles pay hook specs (721 hook if present + buyback hook). |
28
+ | `REVDeployer.beforePayRecordedWith(context)` | Calls the 721 hook first for split specs, then calls the buyback hook with a reduced amount context (payment minus split amount). Adjusts the returned weight proportionally for splits (`weight = mulDiv(weight, amount - splitAmount, amount)`) so the terminal only mints tokens for the amount entering the project. Assembles pay hook specs (721 hook specs first, then buyback spec). |
29
29
  | `REVDeployer.beforeCashOutRecordedWith(context)` | If sucker: returns full amount with 0 tax (fee exempt). Otherwise: calculates 2.5% fee, enforces 30-day cash-out delay, returns modified count + fee hook spec. |
30
30
  | `REVDeployer.afterCashOutRecordedWith(context)` | Cash-out hook callback. Receives fee amount and pays it to the fee revnet's terminal. Falls back to returning funds if fee payment fails. |
31
31
  | `REVDeployer.hasMintPermissionFor(revnetId, ruleset, addr)` | Returns `true` for: loans contract, buyback hook, buyback hook delegates, or suckers. |
@@ -86,7 +86,9 @@ Deploy and manage Revnets -- autonomous, unowned Juicebox projects with staged i
86
86
  | `REVAutoIssuance` | `chainId` (uint32), `count` (uint104), `beneficiary` | Per-stage cross-chain token auto-minting |
87
87
  | `REVLoan` | `amount` (uint112), `collateral` (uint112), `createdAt` (uint48), `prepaidFeePercent` (uint16), `prepaidDuration` (uint32), `source` (REVLoanSource) | Per-loan state in `REVLoans` |
88
88
  | `REVLoanSource` | `token`, `terminal` (IJBPayoutTerminal) | Identifies which terminal and token a loan draws from |
89
- | `REVDeploy721TiersHookConfig` | `baseline721HookConfiguration`, `salt`, `splitOperatorCanAdjustTiers`, `CanUpdateMetadata`, `CanMint`, `CanIncreaseDiscountPercent` | 721 hook deployment with operator permissions |
89
+ | `REVDeploy721TiersHookConfig` | `baseline721HookConfiguration` (REVBaseline721HookConfig), `salt`, `splitOperatorCanAdjustTiers`, `CanUpdateMetadata`, `CanMint`, `CanIncreaseDiscountPercent` | 721 hook deployment with operator permissions. Uses `REVBaseline721HookConfig` (not `JBDeploy721TiersHookConfig`) to omit `issueTokensForSplits` — revnets always force it to `false`. |
90
+ | `REVBaseline721HookConfig` | `name`, `symbol`, `baseUri`, `tokenUriResolver`, `contractUri`, `tiersConfig`, `reserveBeneficiary`, `flags` (REV721TiersHookFlags) | Same as `JBDeploy721TiersHookConfig` but uses `REV721TiersHookFlags` which omits `issueTokensForSplits`. |
91
+ | `REV721TiersHookFlags` | `noNewTiersWithReserves`, `noNewTiersWithVotes`, `noNewTiersWithOwnerMinting`, `preventOverspending` | Same as `JB721TiersHookFlags` minus `issueTokensForSplits`. Revnets do their own weight adjustment for splits. |
90
92
  | `REVCroptopAllowedPost` | `category` (uint24), `minimumPrice` (uint104), `minimumTotalSupply` (uint32), `maximumTotalSupply` (uint32), `allowedAddresses[]` | Croptop posting criteria |
91
93
  | `REVSuckerDeploymentConfig` | `deployerConfigurations[]`, `salt` | Cross-chain sucker deployment |
92
94
 
@@ -154,6 +156,8 @@ Deploy and manage Revnets -- autonomous, unowned Juicebox projects with staged i
154
156
  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.
155
157
  17. **Loan fee model.** Three layers: (1) REV protocol fee (1%) taken when funds pulled, (2) terminal fee (2.5%) charged by `useAllowanceOf`, (3) prepaid source fee (2.5%-50%, borrower-chosen) that buys an interest-free window. After the prepaid window, time-proportional source fee accrues linearly over the remaining 10-year loan duration.
156
158
  18. **Permit2 fallback.** `REVLoans` uses permit2 for ERC-20 transfers as a fallback when standard allowance is insufficient. Wrapped in try-catch.
159
+ 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`.
160
+ 20. **REVDeployer always deploys a 721 hook** via `HOOK_DEPLOYER.deployHookFor` — even if `baseline721HookConfiguration` has empty tiers. This is correct by design: it lets the split operator add and sell NFTs later without migration. Non-revnet projects should follow the same pattern by using `JB721TiersHookProjectDeployer.launchProjectFor` (or `JBOmnichainDeployer.launch721ProjectFor`) instead of bare `launchProjectFor`.
157
161
 
158
162
  ### NATIVE_TOKEN Accounting on Non-ETH Chains
159
163
 
@@ -183,7 +187,6 @@ JBAccountingContext({
183
187
  import {REVConfig} from "@rev-net/core-v6/src/structs/REVConfig.sol";
184
188
  import {REVStageConfig} from "@rev-net/core-v6/src/structs/REVStageConfig.sol";
185
189
  import {REVDescription} from "@rev-net/core-v6/src/structs/REVDescription.sol";
186
- import {REVBuybackHookConfig} from "@rev-net/core-v6/src/structs/REVBuybackHookConfig.sol";
187
190
  import {REVSuckerDeploymentConfig} from "@rev-net/core-v6/src/structs/REVSuckerDeploymentConfig.sol";
188
191
  import {IREVDeployer} from "@rev-net/core-v6/src/interfaces/IREVDeployer.sol";
189
192
 
@@ -220,11 +223,6 @@ deployer.deployFor({
220
223
  revnetId: 0, // 0 = deploy new
221
224
  configuration: config,
222
225
  terminalConfigurations: terminals,
223
- buybackHookConfiguration: REVBuybackHookConfig({
224
- dataHook: IJBRulesetDataHook(address(0)),
225
- hookToConfigure: IJBBuybackHook(address(0)),
226
- poolConfigurations: new REVBuybackPoolConfig[](0)
227
- }),
228
226
  suckerDeploymentConfiguration: REVSuckerDeploymentConfig({
229
227
  deployerConfigurations: new JBSuckerDeployerConfig[](0),
230
228
  salt: bytes32(0)
@@ -242,6 +240,22 @@ loans.borrowFrom({
242
240
  prepaidFeePercent: 25 // 2.5% prepaid fee (minimum)
243
241
  });
244
242
 
243
+ // --- Reallocate collateral (refinance) ---
244
+ // Remove 500 tokens of collateral from an existing loan,
245
+ // use them (plus 200 new tokens) to open a fresh loan.
246
+ // The original loan shrinks, and a new loan NFT is minted.
247
+ (uint256 reallocatedLoanId, uint256 newLoanId, , ) = loans.reallocateCollateralFromLoan({
248
+ loanId: loanId,
249
+ collateralCountToTransfer: 500e18, // Move 500 tokens out of existing loan
250
+ source: REVLoanSource({ token: JBConstants.NATIVE_TOKEN, terminal: terminal }),
251
+ minBorrowAmount: 0,
252
+ collateralCountToAdd: 200e18, // Add 200 fresh tokens on top
253
+ beneficiary: payable(msg.sender), // Receive new loan proceeds
254
+ prepaidFeePercent: 25 // 2.5% prepaid fee on new loan
255
+ });
256
+ // Result: original loan now has 500 fewer collateral tokens (reallocatedLoanId),
257
+ // new loan has 700 tokens of collateral (newLoanId).
258
+
245
259
  // --- Repay a loan ---
246
260
 
247
261
  loans.repayLoan({