@rev-net/core-v6 0.0.7 → 0.0.9
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/ADMINISTRATION.md +186 -0
- package/ARCHITECTURE.md +87 -0
- package/README.md +4 -2
- package/RISKS.md +49 -0
- package/SKILLS.md +22 -2
- package/STYLE_GUIDE.md +482 -0
- package/foundry.toml +6 -6
- package/package.json +13 -10
- package/script/Deploy.s.sol +3 -2
- package/src/REVDeployer.sol +129 -72
- package/src/REVLoans.sol +174 -165
- package/src/interfaces/IREVDeployer.sol +111 -72
- package/src/interfaces/IREVLoans.sol +116 -76
- package/src/structs/REV721TiersHookFlags.sol +14 -0
- package/src/structs/REVBaseline721HookConfig.sol +27 -0
- package/src/structs/REVDeploy721TiersHookConfig.sol +2 -2
- package/test/REV.integrations.t.sol +4 -3
- package/test/REVAutoIssuanceFuzz.t.sol +12 -8
- package/test/REVDeployerAuditRegressions.t.sol +4 -3
- package/test/REVInvincibility.t.sol +8 -6
- package/test/REVInvincibilityHandler.sol +1 -0
- package/test/REVLifecycle.t.sol +4 -3
- package/test/REVLoans.invariants.t.sol +5 -3
- package/test/REVLoansAttacks.t.sol +4 -3
- package/test/REVLoansAuditRegressions.t.sol +13 -24
- package/test/REVLoansFeeRecovery.t.sol +4 -3
- package/test/REVLoansSourced.t.sol +4 -3
- package/test/REVLoansUnSourced.t.sol +4 -3
- package/test/REVLoans_AuditFindings.t.sol +644 -0
- package/test/TestEmptyBuybackSpecs.t.sol +4 -3
- package/test/TestPR09_ConversionDocumentation.t.sol +4 -3
- package/test/TestPR10_LiquidationBehavior.t.sol +4 -3
- package/test/TestPR11_LowFindings.t.sol +4 -3
- package/test/TestPR12_FlashLoanSurplus.t.sol +4 -3
- package/test/TestPR13_CrossSourceReallocation.t.sol +4 -3
- package/test/TestPR15_CashOutCallerValidation.t.sol +4 -3
- package/test/TestPR16_ZeroRepayment.t.sol +4 -3
- package/test/TestPR21_Uint112Overflow.t.sol +4 -3
- package/test/TestPR22_HookArrayOOB.t.sol +4 -3
- package/test/TestPR26_BurnHeldTokens.t.sol +4 -3
- package/test/TestPR27_CEIPattern.t.sol +4 -3
- package/test/TestPR29_SwapTerminalPermission.t.sol +4 -3
- package/test/TestPR32_MixedFixes.t.sol +4 -3
- package/test/TestSplitWeightAdjustment.t.sol +445 -0
- package/test/TestSplitWeightE2E.t.sol +528 -0
- package/test/TestSplitWeightFork.t.sol +821 -0
- package/test/TestStageTransitionBorrowable.t.sol +4 -3
- package/test/fork/ForkTestBase.sol +617 -0
- package/test/fork/TestCashOutFork.t.sol +245 -0
- package/test/fork/TestLoanBorrowFork.t.sol +163 -0
- package/test/fork/TestLoanLiquidationFork.t.sol +129 -0
- package/test/fork/TestLoanReallocateFork.t.sol +103 -0
- package/test/fork/TestLoanRepayFork.t.sol +184 -0
- package/test/fork/TestSplitWeightFork.t.sol +186 -0
- package/test/mock/MockBuybackDataHook.sol +11 -4
- package/test/mock/MockBuybackDataHookMintPath.sol +11 -3
- package/test/regression/TestI20_CumulativeLoanCounter.t.sol +6 -5
- package/test/regression/TestL27_LiquidateGapHandling.t.sol +7 -6
- 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)
|
package/ARCHITECTURE.md
ADDED
|
@@ -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
|
+
→ Configure buyback hook (swap vs mint decision)
|
|
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
|
@@ -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
|
-
|
|
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/
|
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
|
@@ -25,7 +25,7 @@ Deploy and manage Revnets -- autonomous, unowned Juicebox projects with staged i
|
|
|
25
25
|
|
|
26
26
|
| Function | What it does |
|
|
27
27
|
|----------|-------------|
|
|
28
|
-
| `REVDeployer.beforePayRecordedWith(context)` |
|
|
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
|
|
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
|
|
|
@@ -242,6 +246,22 @@ loans.borrowFrom({
|
|
|
242
246
|
prepaidFeePercent: 25 // 2.5% prepaid fee (minimum)
|
|
243
247
|
});
|
|
244
248
|
|
|
249
|
+
// --- Reallocate collateral (refinance) ---
|
|
250
|
+
// Remove 500 tokens of collateral from an existing loan,
|
|
251
|
+
// use them (plus 200 new tokens) to open a fresh loan.
|
|
252
|
+
// The original loan shrinks, and a new loan NFT is minted.
|
|
253
|
+
(uint256 reallocatedLoanId, uint256 newLoanId, , ) = loans.reallocateCollateralFromLoan({
|
|
254
|
+
loanId: loanId,
|
|
255
|
+
collateralCountToTransfer: 500e18, // Move 500 tokens out of existing loan
|
|
256
|
+
source: REVLoanSource({ token: JBConstants.NATIVE_TOKEN, terminal: terminal }),
|
|
257
|
+
minBorrowAmount: 0,
|
|
258
|
+
collateralCountToAdd: 200e18, // Add 200 fresh tokens on top
|
|
259
|
+
beneficiary: payable(msg.sender), // Receive new loan proceeds
|
|
260
|
+
prepaidFeePercent: 25 // 2.5% prepaid fee on new loan
|
|
261
|
+
});
|
|
262
|
+
// Result: original loan now has 500 fewer collateral tokens (reallocatedLoanId),
|
|
263
|
+
// new loan has 700 tokens of collateral (newLoanId).
|
|
264
|
+
|
|
245
265
|
// --- Repay a loan ---
|
|
246
266
|
|
|
247
267
|
loans.repayLoan({
|