@rev-net/core-v6 0.0.32 → 0.0.34
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 +48 -212
- package/ARCHITECTURE.md +67 -41
- package/AUDIT_INSTRUCTIONS.md +54 -108
- package/CHANGELOG.md +4 -3
- package/README.md +37 -30
- package/RISKS.md +49 -207
- package/SKILLS.md +15 -13
- package/USER_JOURNEYS.md +148 -63
- package/foundry.toml +1 -0
- package/package.json +36 -36
- package/references/operations.md +7 -0
- package/references/runtime.md +9 -0
- package/src/REVDeployer.sol +3 -2
- package/src/REVHiddenTokens.sol +48 -26
- package/src/REVLoans.sol +2 -2
- package/src/REVOwner.sol +10 -2
- package/src/interfaces/IREVHiddenTokens.sol +21 -6
- package/src/structs/REV721TiersHookFlags.sol +0 -1
- package/src/structs/REVAutoIssuance.sol +0 -1
- package/src/structs/REVBaseline721HookConfig.sol +0 -1
- package/src/structs/REVConfig.sol +0 -1
- package/src/structs/REVCroptopAllowedPost.sol +0 -1
- package/src/structs/REVDeploy721TiersHookConfig.sol +0 -1
- package/src/structs/REVDescription.sol +0 -1
- package/src/structs/REVLoan.sol +0 -1
- package/src/structs/REVLoanSource.sol +0 -1
- package/src/structs/REVStageConfig.sol +0 -1
- package/src/structs/REVSuckerDeploymentConfig.sol +0 -1
- package/test/TestAuditFixVerification.t.sol +675 -0
- package/test/TestHiddenTokens.t.sol +51 -8
- package/test/audit/CodexCrossChainBuybackRouteMismatch.t.sol +184 -0
- package/test/audit/NemesisOperatorDelegation.t.sol +77 -10
package/RISKS.md
CHANGED
|
@@ -1,247 +1,89 @@
|
|
|
1
1
|
# Revnet Core Risk Register
|
|
2
2
|
|
|
3
|
-
This file focuses on the risks
|
|
4
|
-
|
|
5
|
-
Read [ARCHITECTURE.md](./ARCHITECTURE.md) and [SKILLS.md](./SKILLS.md) for protocol context first.
|
|
3
|
+
This file focuses on the staged-economics, runtime-hook, hidden-supply, and loan risks that matter in Revnets. The main question is whether the deployed economic shape still holds under real runtime behavior.
|
|
6
4
|
|
|
7
5
|
## How to use this file
|
|
8
6
|
|
|
9
|
-
- Read `Priority risks` first
|
|
10
|
-
- Use the detailed sections
|
|
11
|
-
- Treat `Accepted Behaviors` and `Invariants to Verify` as the
|
|
7
|
+
- Read `Priority risks` first.
|
|
8
|
+
- Use the detailed sections to separate stage design, hook composition, hidden supply, and loan accounting.
|
|
9
|
+
- Treat `Accepted Behaviors` and `Invariants to Verify` as the line between intended product tradeoffs and defects.
|
|
12
10
|
|
|
13
11
|
## Priority risks
|
|
14
12
|
|
|
15
13
|
| Priority | Risk | Why it matters | Primary controls |
|
|
16
14
|
|----------|------|----------------|------------------|
|
|
17
|
-
| P0 |
|
|
18
|
-
|
|
|
19
|
-
| P1 |
|
|
15
|
+
| P0 | Borrowability drift from live surplus or cross-chain state | Loans can overextend or under-credit if revnet state is read incorrectly. | Borrowability tests, omnichain-state checks, and cash-out-delay gating. |
|
|
16
|
+
| P1 | Stage configuration mistakes | Revnet economics are hard to change after launch, so bad stages are expensive. | Deployment review, stage-transition tests, and launch-time validation. |
|
|
17
|
+
| P1 | Hidden-supply and burned-collateral misunderstandings | Hidden tokens and loans both change visible supply in non-obvious ways. | Explicit supply invariants and product-level review. |
|
|
20
18
|
|
|
21
19
|
## 1. Trust Assumptions
|
|
22
20
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
- **
|
|
26
|
-
- **
|
|
27
|
-
- **Stage immutability is the trust model.** Once `deployFor()` completes, stage parameters (issuance, `cashOutTaxRate`, splits, auto-issuances) are locked forever. No owner, no governance, no upgrade path. A misconfigured deployment is permanent. This is intentional -- the absence of admin keys IS the security property.
|
|
28
|
-
- **Bonding curve is the sole collateral oracle.** `REVLoans` uses `JBCashOuts.cashOutFrom` to value collateral. There is no external price oracle, no liquidation margin, and no health factor. The borrowable amount equals the cash-out value at the moment of borrowing.
|
|
29
|
-
- **Juicebox core contracts are correct.** `JBController`, `JBMultiTerminal`, `JBTerminalStore`, `JBTokens`, `JBPrices` -- a bug in any of these is a bug in every revnet.
|
|
30
|
-
- **Buyback hook operates correctly.** `BUYBACK_HOOK` handles swap-vs-mint routing. All revnets from the same deployer share one instance. Failure falls back to direct minting (not a revert), so the failure mode is economic inefficiency, not fund loss.
|
|
31
|
-
- **Suckers are honest bridges.** Suckers get 0% cashout tax in `beforeCashOutRecordedWith`. A compromised or malicious sucker registered in `SUCKER_REGISTRY` can extract funds from any revnet at zero cost.
|
|
32
|
-
- **Auto-issuance beneficiaries are set at deployment.** Beneficiary addresses are baked into the stage configuration. If a beneficiary address is a contract that becomes compromised, or an EOA whose keys are lost, those auto-issuance tokens are either captured or permanently unclaimable.
|
|
33
|
-
- **REVLoans contract address is immutable per deployer.** `LOANS` is set once in the REVDeployer constructor (and shared as an immutable on REVOwner) with wildcard `USE_ALLOWANCE` permission (`projectId=0`). If the loans contract has a vulnerability, every revnet's surplus is exposed.
|
|
34
|
-
|
|
35
|
-
### What you do NOT need to trust
|
|
36
|
-
|
|
37
|
-
- **Project owners.** There are none. REVDeployer permanently holds the project NFT.
|
|
38
|
-
- **Split operators.** They can change splits, manage 721 tiers, deploy suckers, and set buyback pools, but cannot change stage parameters, issuance rates, cashout tax rates, or directly access treasury funds.
|
|
39
|
-
- **Token holders.** They can only cash out proportional to the bonding curve. Borrowers can only borrow up to the bonding curve value of their burned collateral.
|
|
40
|
-
|
|
41
|
-
---
|
|
21
|
+
- **`REVDeployer` and `REVOwner` are one design.** Misreading them independently is a review hazard.
|
|
22
|
+
- **Core protocol state is still upstream truth.** Revnet economics sit on top of `nana-core-v6`, not outside it.
|
|
23
|
+
- **Optional integrations matter.** Buybacks, 721 hooks, and suckers can materially change runtime behavior.
|
|
24
|
+
- **Price feeds and source accounting matter for loans.** Cross-currency debt aggregation depends on working feed assumptions.
|
|
42
25
|
|
|
43
26
|
## 2. Economic Risks
|
|
44
27
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
- **
|
|
48
|
-
- **
|
|
49
|
-
- **
|
|
50
|
-
|
|
51
|
-
### Stage transition edge cases
|
|
52
|
-
|
|
53
|
-
- **`cashOutTaxRate` increase destroys loan health.** Active loans use the CURRENT stage's `cashOutTaxRate`. When a stage transition increases this rate, existing loans become under-collateralized -- the collateral's cash-out value drops but the loan amount remains unchanged. No forced repayment or margin call mechanism exists. Over 10 years with multiple stage transitions, this compounds.
|
|
54
|
-
- **`cashOutTaxRate` decrease creates refinancing opportunity.** When a new stage lowers the tax rate, existing collateral becomes worth more. Borrowers can `reallocateCollateralFromLoan` to extract the surplus value. This creates a predictable, front-runnable event at every stage boundary.
|
|
55
|
-
- **Weight decay approaching zero over long periods.** With `issuanceCutPercent > 0` and `issuanceCutFrequency > 0`, issuance weight decays exponentially through successive rulesets. After long enough, new payments mint negligibly few tokens, meaning the token supply effectively freezes. This concentrates cash-out value among existing holders and makes the bonding curve increasingly sensitive to individual cash-outs. The current repo models decay through ruleset duration and `weightCutPercent`, so validation should focus on long-horizon ruleset progression and issuance-decay tests rather than any separate weight-cache updater.
|
|
56
|
-
- **Duration=0 stages never auto-expire.** A stage with `duration=0` (no issuance cut frequency) persists until explicitly replaced by a subsequent stage's `startsAtOrAfter`. If the next stage's `startsAtOrAfter` is far in the future, the current stage runs indefinitely at its configured parameters.
|
|
57
|
-
|
|
58
|
-
### Cross-currency reclaim calculations
|
|
59
|
-
|
|
60
|
-
- **`_totalBorrowedFrom` aggregates across currencies via `JBPrices`.** Each loan source may be in a different token/currency. Aggregation normalizes decimals and converts via price feeds. If a price feed returns zero, that source is skipped (prevents division-by-zero DoS). But a stale or manipulated price feed silently over- or under-counts total borrowed amount, affecting all subsequent borrow operations for the revnet.
|
|
61
|
-
- **Undercount from zero-price feeds is unbounded.** When `PRICES.pricePerUnitOf` returns 0 for a source, that source's entire outstanding debt is excluded from the `_totalBorrowedFrom` total. There is no cap on the magnitude of the omission -- it equals the full borrowed amount from the affected source. Concretely: if a revnet has 100 ETH borrowed across 3 sources and one source's feed returns 0, that source's debt becomes invisible, potentially allowing the remaining borrowing capacity to be over-utilized. The more debt concentrated in the affected source, the larger the undercount.
|
|
62
|
-
- **Cascading effect on borrowing capacity.** Because `_totalBorrowedFrom` is called on every `borrowFrom` to enforce `_borrowableAmountFrom`, an undercount directly inflates the perceived remaining borrowing capacity for the entire revnet -- not just the affected source. New borrowers across all sources benefit from the artificially low total, and the over-extension compounds with each new loan taken while the feed is down.
|
|
63
|
-
- **No automatic recovery mechanism.** Once a price feed returns 0, the affected source's debt remains invisible to `_totalBorrowedFrom` until the feed recovers. There is no event emitted when a source is skipped, no fallback oracle, and no circuit breaker that pauses borrowing when feed health degrades. Operators should actively monitor price feed health and treat any feed returning 0 as a risk event for the revnet's loan system.
|
|
64
|
-
- **Decimal normalization truncation.** When converting from higher-decimal tokens (18) to lower-decimal targets (6), integer division truncates. For sources with large outstanding borrows in high-decimal tokens, this truncation can systematically undercount the total borrowed amount, allowing slightly more borrowing than intended. For example, converting 999,999,999,999 wei (18-decimal) to 6-decimal precision discards 12 digits of precision. Across many sources, these per-source truncation errors accumulate additively, further widening the gap between the reported and actual total debt.
|
|
65
|
-
|
|
66
|
-
### Hidden token supply manipulation
|
|
67
|
-
|
|
68
|
-
- **Hiding reduces totalSupply, inflating per-token cash-out value.** When a holder hides tokens via `REVHiddenTokens`, those tokens are burned and excluded from `totalSupply`. The bonding curve sees fewer tokens, so each remaining token is worth more on cash-out. A holder with a large position can hide tokens, have an accomplice cash out at the inflated rate, then reveal. The net effect depends on the `cashOutTaxRate` and the relative positions. Operator delegation (`HIDE_TOKENS`/`REVEAL_TOKENS` permissions) extends this to permissioned operators acting on behalf of holders.
|
|
69
|
-
- **Hidden tokens must be revealed before use as loan collateral.** `REVHiddenTokens` and `REVLoans` are separate systems. A holder cannot borrow against hidden tokens — they must first reveal (re-mint) them, then borrow. This is by design but may confuse users.
|
|
70
|
-
- **Reveal mints without reserved percent.** `revealTokensOf` calls `mintTokensOf` with `useReservedPercent: false`. This is correct because the tokens were previously burned and are being restored, not newly issued. But it means revealed tokens bypass the reserved-token mechanism entirely.
|
|
71
|
-
- **REVHiddenTokens has mint permission via REVOwner.** The contract is added to `REVOwner.hasMintPermissionFor`. If `REVHiddenTokens` has a vulnerability, it could mint unbounded tokens for any revnet.
|
|
72
|
-
|
|
73
|
-
### Auto-issuance overflow potential
|
|
74
|
-
|
|
75
|
-
- **`REVAutoIssuance.count` is `uint104` (~2.03e31).** Multiple auto-issuances for the same beneficiary in the same stage are summed via `+=` in `_makeRulesetConfigurations`. If cumulative auto-issuances exceed `uint256`, this wraps. In practice, `uint104` inputs limit each addition, but verify no path allows the mapping value to overflow.
|
|
76
|
-
- **Auto-issuance dilutes existing holders.** Large auto-issuances at stage boundaries dilute the token supply, reducing per-token cash-out value. This is permissionless (`autoIssueFor` can be called by anyone). A griefing vector exists where someone calls `autoIssueFor` immediately before another user's cash-out to reduce their reclaim amount. However, the dilution is pre-configured and predictable.
|
|
77
|
-
|
|
78
|
-
---
|
|
79
|
-
|
|
80
|
-
## 3. Loan System Risks
|
|
81
|
-
|
|
82
|
-
### Collateral valuation during price volatility
|
|
83
|
-
|
|
84
|
-
- **Bonding curve is internal, not market-price.** Collateral is valued by the bonding curve (`JBCashOuts.cashOutFrom`) which depends on surplus, total supply, and `cashOutTaxRate`. External market price (e.g., DEX price of the revnet token) is irrelevant to collateral valuation. If the market price diverges significantly from the bonding curve value, arbitrage opportunities arise between borrowing and trading.
|
|
85
|
-
- **Surplus is cross-terminal aggregate.** `_borrowableAmountFrom` calls `JBSurplus.currentSurplusOf` across all terminals. If one terminal holds tokens in a volatile asset that has crashed, the aggregate surplus drops, reducing collateral value for ALL borrowers regardless of which terminal their loan draws from.
|
|
86
|
-
|
|
87
|
-
### Liquidation concerns
|
|
88
|
-
|
|
89
|
-
- **No cascading liquidation mechanism.** There is no health factor, no margin call, and no keeper-triggered liquidation for under-collateralized loans. The only liquidation path is `liquidateExpiredLoansFrom` after 10 years. Under-collateralized loans persist indefinitely within that window.
|
|
90
|
-
- **Liquidation iterates by loan number.** `liquidateExpiredLoansFrom` takes `startingLoanId` and `count`, iterating sequentially. Repaid and already-liquidated loans are skipped (`createdAt == 0`), but the caller pays gas for every skip. If a revnet has thousands of loans with sparse gaps (many repaid), liquidation becomes expensive. The `count` parameter bounds gas per call, but a malicious actor could create many small loans to increase cleanup costs.
|
|
91
|
-
- **Liquidation permanently destroys collateral.** Collateral was burned at borrow time. Upon liquidation, `totalCollateralOf` is decremented but no tokens are minted or returned. The collateral is permanently removed from the token supply. This deflates the total supply, increasing per-token value for remaining holders -- a mild positive externality from defaults.
|
|
92
|
-
|
|
93
|
-
### Loan source rotation after deployment
|
|
94
|
-
|
|
95
|
-
- **Loan sources grow monotonically.** `_loanSourcesOf[revnetId]` is append-only. Each new `(terminal, token)` pair used for borrowing adds an entry. Entries are never removed, even if all loans from that source are repaid. `_totalBorrowedFrom` iterates the entire array on every borrow/repay.
|
|
96
|
-
- **Removed terminals remain as loan sources.** If a terminal is de-registered from `JBDirectory` (via migration), existing loans from that terminal remain valid (the loan struct stores a direct reference to the terminal contract). New borrows against that terminal are blocked by `DIRECTORY.isTerminalOf` check in `borrowFrom`. But `_totalBorrowedFrom` still queries the de-registered terminal's `accountingContextForTokenOf` -- verify this doesn't revert.
|
|
97
|
-
|
|
98
|
-
### `reallocateCollateralFromLoan` sandwich potential
|
|
99
|
-
|
|
100
|
-
- **Reallocation is two operations in one transaction.** `reallocateCollateralFromLoan` first reduces collateral on the existing loan (via `_reallocateCollateralFromLoan`), then opens a new loan (via `borrowFrom`). Between these two operations, the surplus and total supply have changed (collateral was returned to the caller, changing supply). The new loan's borrowable amount is computed with the post-reallocation state.
|
|
101
|
-
- **Source mismatch check.** `reallocateCollateralFromLoan` enforces that the new loan's source matches the existing loan's source (`source.token == existingSource.token && source.terminal == existingSource.terminal`). This prevents cross-source value extraction.
|
|
102
|
-
- **MEV opportunity at stage boundaries.** If a borrower knows a stage transition will decrease `cashOutTaxRate`, they can wait until just after the transition and `reallocateCollateralFromLoan` to extract more borrowed funds from the same collateral. This is predictable and not preventable by design.
|
|
103
|
-
|
|
104
|
-
### Cross-chain cash-out delay enforcement
|
|
105
|
-
|
|
106
|
-
- **Loans enforce the same 30-day cash-out delay as direct cash outs.** When a revnet is deployed to a new chain where its first stage has already started, REVDeployer calls `REVOwner.setCashOutDelayOf()` to set a 30-day delay (stored on REVOwner). `borrowFrom` resolves the REVOwner from the current ruleset's `dataHook` and checks `IREVOwner.cashOutDelayOf(revnetId)` (read directly from REVOwner storage), reverting with `REVLoans_CashOutDelayNotFinished` if the delay hasn't passed. `borrowableAmountFrom` returns 0 during the delay. This prevents cross-chain arbitrage via the loan system (bridging tokens to a new chain and immediately borrowing against them before prices equilibrate).
|
|
107
|
-
|
|
108
|
-
### BURN_TOKENS permission prerequisite
|
|
109
|
-
|
|
110
|
-
- **Borrowers must grant BURN_TOKENS permission before calling `borrowFrom`.** The loans contract burns the caller's tokens as collateral via `JBController.burnTokensOf`, which requires the caller to have granted `BURN_TOKENS` permission to the loans contract for the revnet's project ID. Without this, the transaction reverts deep in `JBController` with `JBPermissioned_Unauthorized`. The prerequisite is documented in `borrowFrom`'s NatSpec.
|
|
111
|
-
|
|
112
|
-
---
|
|
113
|
-
|
|
114
|
-
## 4. Data Hook Proxy Risks
|
|
115
|
-
|
|
116
|
-
REVOwner sits between the terminal and the actual hooks (buyback hook, 721 hook). This proxy pattern creates composition risks.
|
|
28
|
+
- **Stage immutability cuts both ways.** A bad stage schedule or bad cash-out tax choice is expensive to unwind.
|
|
29
|
+
- **Borrowability depends on live economics.** If surplus, supply, or cross-chain state are wrong, loan capacity becomes wrong.
|
|
30
|
+
- **Zero or degraded price feeds can undercount debt.** If a source becomes invisible to debt aggregation, later borrowing can become too permissive.
|
|
31
|
+
- **Hidden-token mechanics change visible supply.** That affects per-token cash-out value and can change the economics seen by other holders.
|
|
32
|
+
- **Auto-issuance dilutes holders predictably but still materially.** Timing is permissionless, even if the amounts are fixed at deployment.
|
|
33
|
+
- **Omnichain expansion can corrupt surplus aggregation.** Since borrowability aggregates surplus from all registered terminals across chains, a compromised or misconfigured terminal on a remote chain affects global surplus accounting.
|
|
117
34
|
|
|
118
|
-
|
|
35
|
+
## 3. Loan Risks
|
|
119
36
|
|
|
120
|
-
- **
|
|
121
|
-
- **
|
|
122
|
-
- **
|
|
37
|
+
- **Burned collateral is not escrow.** Reviewers and integrators who model it as escrow will misread liquidation and repayment behavior.
|
|
38
|
+
- **No short-term liquidation model.** Under-collateralized loans can persist until the long expiry model allows cleanup.
|
|
39
|
+
- **Loan sources grow over time.** Debt aggregation cost and complexity increase as new source pairs are used.
|
|
40
|
+
- **Reallocation still depends on live state.** Reallocate flows can change outcomes around stage boundaries.
|
|
123
41
|
|
|
124
|
-
|
|
42
|
+
## 4. Hidden-Token Risks
|
|
125
43
|
|
|
126
|
-
- **
|
|
127
|
-
- **
|
|
44
|
+
- **Visible-supply manipulation is intentional.** Hiding tokens changes visible supply and therefore changes redemption economics.
|
|
45
|
+
- **Hidden tokens are not usable collateral while hidden.** They must be revealed before borrowing.
|
|
46
|
+
- **Reveal is restoration, not fresh issuance.** It intentionally bypasses reserved-percent behavior.
|
|
128
47
|
|
|
129
|
-
|
|
48
|
+
## 5. Hook-Composition Risks
|
|
130
49
|
|
|
131
|
-
- **`
|
|
132
|
-
- **
|
|
50
|
+
- **`REVOwner` is a real runtime authority surface.** It composes pay hooks, cash-out hooks, sucker exemptions, and fee logic.
|
|
51
|
+
- **Suckers can bypass tax and fee paths by design.** That privilege is safe only if registry and deployer assumptions are correct.
|
|
52
|
+
- **Mint-permission surfaces are broad enough to matter.** Loans, hidden tokens, buyback flows, and suckers all touch mint authority in some deployments.
|
|
133
53
|
|
|
134
|
-
|
|
54
|
+
## 6. Access-Control Risks
|
|
135
55
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
- **No function modifies rulesets after deployment.** REVDeployer holds the project NFT and is the only entity that could call `CONTROLLER.queueRulesetsOf`. But REVDeployer has no function that does this. The only ruleset interaction after deployment is reading via `currentRulesetOf` and `getRulesetOf`. This is the core immutability guarantee -- verify no code path exists that calls `queueRulesetsOf` or `launchRulesetsFor` on an already-deployed revnet.
|
|
141
|
-
- **Project NFT cannot be recovered.** Once transferred to REVDeployer via `safeTransferFrom` or minted by `launchProjectFor`, the NFT is permanently held. REVDeployer implements `onERC721Received` but only accepts from `PROJECTS`. It has no `transferFrom` or equivalent for project NFTs.
|
|
142
|
-
|
|
143
|
-
### Who can deploy and modify
|
|
144
|
-
|
|
145
|
-
- **`deployFor` is permissionless for new revnets** (`revnetId == 0`). Anyone can deploy a revnet with arbitrary configuration.
|
|
146
|
-
- **`deployFor` with existing project requires ownership.** The caller must be `PROJECTS.ownerOf(revnetId)` and the project must be blank (no controller, no rulesets).
|
|
147
|
-
- **Split operator is singular and self-replacing.** `setSplitOperatorOf` can only be called by the current split operator. If the split operator is set to address(0) or a contract with no ability to call `setSplitOperatorOf`, the role is permanently lost.
|
|
148
|
-
- **Split operator permissions are cumulative during deployment.** `_extraOperatorPermissions[revnetId]` is populated via `.push()` during deployment. If the same permission ID is pushed twice, the array has duplicates but this is harmless (the permission check uses `hasPermissions` which doesn't care about duplicates).
|
|
149
|
-
|
|
150
|
-
### Split operator trust boundaries
|
|
151
|
-
|
|
152
|
-
- **ADD_PRICE_FEED.** The split operator can add price feeds for the revnet. A malicious price feed could return manipulated values, affecting cross-currency surplus calculations and loan collateral valuations. Price feeds are immutable once added (cannot be replaced).
|
|
153
|
-
- **SET_SPLIT_GROUPS.** The split operator controls where reserved tokens go. A compromised operator can redirect all reserved tokens to themselves.
|
|
154
|
-
- **DEPLOY_SUCKERS (via `deploySuckersFor`).** The split operator can deploy new suckers if the current stage allows it (`extraMetadata` bit 2). A malicious sucker gets 0% cashout tax privilege. This is the highest-impact split operator action.
|
|
155
|
-
- **SET_ROUTER_TERMINAL.** The split operator can configure the router terminal, potentially redirecting payments.
|
|
156
|
-
|
|
157
|
-
---
|
|
158
|
-
|
|
159
|
-
## 6. DoS Vectors
|
|
160
|
-
|
|
161
|
-
### Long stage chains
|
|
162
|
-
|
|
163
|
-
- **`_makeRulesetConfigurations` iterates all stages.** Deployment cost scales linearly with stage count. There is no explicit cap on the number of stages. A deployment with hundreds of stages would be expensive but is not blocked.
|
|
164
|
-
- **Auto-issuance inner loop.** For each stage, the deployment iterates all `autoIssuances[]`. The combined iteration (stages x auto-issuances per stage) could hit the block gas limit for extreme configurations.
|
|
165
|
-
|
|
166
|
-
### Many auto-issuances
|
|
167
|
-
|
|
168
|
-
- **`autoIssueFor` is one-per-call.** Each call processes a single `(revnetId, stageId, beneficiary)` tuple. If a stage has many beneficiaries, they must each be claimed individually. Not a DoS vector against the protocol, but a usability concern.
|
|
169
|
-
|
|
170
|
-
### Loan source enumeration
|
|
171
|
-
|
|
172
|
-
- **`_totalBorrowedFrom` iterates ALL sources on every borrow and repay.** Gas cost: ~20k per source (external `accountingContextForTokenOf` call + storage read + potential price feed call). With 10 sources, this adds ~200k gas per loan operation. With 50+ sources (unlikely but possible), operations become prohibitively expensive.
|
|
173
|
-
- **Mitigation.** `borrowFrom` checks `DIRECTORY.isTerminalOf` before accepting a new source. The number of registered terminals per project is practically bounded. But nothing prevents a terminal from being registered, used for one loan, de-registered, and then a new terminal registered -- leaving stale entries in the source array.
|
|
174
|
-
|
|
175
|
-
---
|
|
56
|
+
- **The deployer-held project NFT can be misunderstood.** Revnets are owner-minimized, but the deployer path still matters for the trust model.
|
|
57
|
+
- **Split operator mistakes are high-impact.** Narrow powers like price-feed installation, split updates, sucker deployment, or router setup still matter.
|
|
58
|
+
- **There is intentionally no broad admin recovery path.** Operational teams may try to reach for powers the design never intended to leave available.
|
|
176
59
|
|
|
177
60
|
## 7. Invariants to Verify
|
|
178
61
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
-
|
|
184
|
-
-
|
|
185
|
-
- **No double-mint collateral.** Repaying a loan mints collateral back exactly once. A repaid loan cannot be repaid again (NFT is burned, storage is deleted).
|
|
186
|
-
- **No zero-collateral loans.** Every active loan has `collateral > 0` and `amount > 0`. The `borrowFrom` function reverts on `collateralCount == 0` and `borrowAmount == 0`.
|
|
187
|
-
- **Liquidation only after expiry.** `liquidateExpiredLoansFrom` skips loans where `block.timestamp <= loan.createdAt + LOAN_LIQUIDATION_DURATION`.
|
|
188
|
-
- **Total loans counter monotonicity.** `totalLoansBorrowedFor[revnetId]` only increments (on borrow, repay-with-replacement, and reallocation). It never decrements. Loan IDs are unique and never reused.
|
|
189
|
-
|
|
190
|
-
### Stage and deployment
|
|
191
|
-
|
|
192
|
-
- **Stage immutability.** After `deployFor` completes, no function in REVDeployer calls `CONTROLLER.queueRulesetsOf` or modifies ruleset parameters.
|
|
193
|
-
- **Stage progression monotonicity.** `startsAtOrAfter` values strictly increase between stages. The first stage can be 0 (mapped to `block.timestamp`).
|
|
194
|
-
- **Auto-issuance single-claim.** Each `(revnetId, stageId, beneficiary)` can only be claimed once. `amountToAutoIssue` is zeroed BEFORE the external `mintTokensOf` call (CEI pattern).
|
|
195
|
-
- **Split percentages.** Per-stage `splitPercent > 0` requires `splits.length > 0`. Split percentages are validated by `JBSplits` in core (must sum to <= `SPLITS_TOTAL_PERCENT`).
|
|
196
|
-
|
|
197
|
-
### Fee flows
|
|
198
|
-
|
|
199
|
-
- **Cash-out fees flow to fee revnet.** If the fee terminal `pay` succeeds, the fee goes to `FEE_REVNET_ID`. If it fails, the fee returns to the originating project via `addToBalanceOf`. Funds are never lost and never kept by the caller.
|
|
200
|
-
- **Loan REV fees flow to REV revnet.** If `feeTerminal.pay` succeeds, the fee goes to `REV_ID`. If it fails (try-catch), the fee amount is zeroed and added to the borrower's payout. The fee is never lost -- it either reaches the REV revnet or goes to the borrower.
|
|
201
|
-
|
|
202
|
-
### Hidden token accounting
|
|
203
|
-
|
|
204
|
-
- **Hidden balance conservation.** `totalHiddenOf[revnetId]` == sum of `hiddenBalanceOf[holder][revnetId]` across all holders.
|
|
205
|
-
- **Reveal bounded by hide.** No holder can reveal more tokens than they have hidden. `revealTokensOf` reverts with `REVHiddenTokens_InsufficientHiddenBalance` if `tokenCount > hiddenBalanceOf[caller][revnetId]`.
|
|
206
|
-
- **No double-mint from reveal.** Each hidden token can only be revealed once. Decrementing `hiddenBalanceOf` before minting prevents double-reveal.
|
|
207
|
-
|
|
208
|
-
### Privilege isolation
|
|
209
|
-
|
|
210
|
-
- **Sucker privilege.** Only addresses returning `true` from `SUCKER_REGISTRY.isSuckerOf(projectId, addr)` get 0% cashout tax. No other code path grants this exemption.
|
|
211
|
-
- **Loan ownership.** Only `_ownerOf(loanId)` — or an operator with the relevant `JBPermissionIds` (`REPAY_LOAN` for repayment, `REALLOCATE_LOAN` for reallocation) — can call `repayLoan` and `reallocateCollateralFromLoan`. Similarly, `borrowFrom` requires the caller to be the `holder` or to have `OPEN_LOAN` permission. The loan NFT is burned before any state changes in repayment, preventing double-use. Replacement loan NFTs are minted to the original holder/owner, not the operator. However, delegated operators control the `beneficiary` parameter and can direct borrowed funds, returned collateral, or revealed tokens to any address (see §8.5).
|
|
212
|
-
- **Mint permission.** Only `LOANS`, `HIDDEN_TOKENS`, `BUYBACK_HOOK`, buyback hook delegates (via `BUYBACK_HOOK.hasMintPermissionFor`), and suckers (via `REVOwner._isSuckerOf`) can mint tokens. No other address passes the `REVOwner.hasMintPermissionFor` check.
|
|
213
|
-
|
|
214
|
-
---
|
|
62
|
+
- Collateral and debt conservation across all active loans.
|
|
63
|
+
- Stage immutability after deployment.
|
|
64
|
+
- Borrowability dropping to zero when cash-out delay should still block borrowing.
|
|
65
|
+
- Hidden-balance conservation across hide and reveal flows.
|
|
66
|
+
- Sucker-only privileges staying restricted to real registered suckers.
|
|
67
|
+
- Mint permission remaining limited to the documented runtime surfaces.
|
|
215
68
|
|
|
216
69
|
## 8. Accepted Behaviors
|
|
217
70
|
|
|
218
|
-
### 8.1 Suckers receive 0%
|
|
71
|
+
### 8.1 Suckers receive 0% cash-out tax
|
|
219
72
|
|
|
220
|
-
|
|
73
|
+
Trusted suckers are intentionally exempt so bridged value preserves its economic meaning across chains.
|
|
221
74
|
|
|
222
|
-
### 8.2
|
|
75
|
+
### 8.2 There is no short-horizon liquidation model
|
|
223
76
|
|
|
224
|
-
|
|
77
|
+
Revnet loans are designed more like long-dated economic positions than instantly mark-to-market margin loans.
|
|
225
78
|
|
|
226
79
|
### 8.3 Auto-issuance dilution is permissionless but predictable
|
|
227
80
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
### 8.4 Surplus manipulation via donations is economically irrational (by design)
|
|
231
|
-
|
|
232
|
-
`_borrowableAmountFrom` reads live surplus. An attacker could inflate surplus via `addToBalanceOf`, but donations are permanent (no recovery), and the extra borrowable amount is always less than the donation. `pay` increases both surplus AND supply, neutralizing the effect. With non-zero `cashOutTaxRate`, the concave bonding curve makes this even worse for attackers. The attack is self-defeating by construction.
|
|
233
|
-
|
|
234
|
-
### 8.5 Delegated operators control beneficiary independently of holder/owner (by design)
|
|
235
|
-
|
|
236
|
-
When a holder grants `OPEN_LOAN`, `REALLOCATE_LOAN`, `REPAY_LOAN`, or `REVEAL_TOKENS` permission to an operator, the operator can set the `beneficiary` parameter to any address — including themselves. This means:
|
|
81
|
+
Anyone can trigger a valid auto-issuance once a stage is live, but the amount was fixed at deployment.
|
|
237
82
|
|
|
238
|
-
|
|
239
|
-
- **`reallocateCollateralFromLoan`**: An operator with `REALLOCATE_LOAN` permission can direct the borrowed funds from the new loan to an arbitrary beneficiary.
|
|
240
|
-
- **`repayLoan`**: An operator with `REPAY_LOAN` permission can direct returned collateral tokens to an arbitrary beneficiary.
|
|
241
|
-
- **`revealTokensOf`**: An operator with `REVEAL_TOKENS` permission can direct revealed (re-minted) tokens to an arbitrary beneficiary instead of the original holder.
|
|
83
|
+
### 8.4 Surplus manipulation by pure donation is economically self-defeating
|
|
242
84
|
|
|
243
|
-
|
|
85
|
+
The model assumes that attempts to inflate surplus through donations are not profitable once the surrounding bonding-curve math is considered.
|
|
244
86
|
|
|
245
|
-
### 8.
|
|
87
|
+
### 8.5 Omnichain terminal expansion inherits remote-chain trust
|
|
246
88
|
|
|
247
|
-
A
|
|
89
|
+
A project that expands to a new chain can register additional terminals on that chain. Because borrowability calculations aggregate surplus from all registered terminals across all chains, a compromised or misconfigured terminal on a remote chain can corrupt the project's surplus accounting globally. This is accepted because revnet terminals are set and fixed on deploy for the initial chain, but expansion to new chains inherently requires trust in the new chain's terminal infrastructure, bridge integrity, and deployment configuration. Project operators should treat each chain expansion as a trust-boundary decision.
|
package/SKILLS.md
CHANGED
|
@@ -2,20 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
## Use This File For
|
|
4
4
|
|
|
5
|
-
- Use this file when the task involves revnet deployment, staged issuance, split
|
|
6
|
-
- Start here, then
|
|
5
|
+
- Use this file when the task involves revnet deployment, staged issuance, split-operator logic, auto-issuance, hidden tokens, or the revnet loan system.
|
|
6
|
+
- Start here, then decide whether the issue is really in `REVDeployer`, `REVOwner`, `REVLoans`, or `REVHiddenTokens`.
|
|
7
7
|
|
|
8
8
|
## Read This Next
|
|
9
9
|
|
|
10
10
|
| If you need... | Open this next |
|
|
11
11
|
|---|---|
|
|
12
|
-
| Repo overview and operator flow | [`README.md`](./README.md) |
|
|
12
|
+
| Repo overview and operator flow | [`README.md`](./README.md), [`ARCHITECTURE.md`](./ARCHITECTURE.md) |
|
|
13
13
|
| Deployment and stage config | [`src/REVDeployer.sol`](./src/REVDeployer.sol), [`script/Deploy.s.sol`](./script/Deploy.s.sol) |
|
|
14
|
-
| Runtime owner and data-hook behavior | [`src/REVOwner.sol`](./src/REVOwner.sol) |
|
|
14
|
+
| Runtime owner and data-hook behavior | [`src/REVOwner.sol`](./src/REVOwner.sol), [`references/runtime.md`](./references/runtime.md) |
|
|
15
15
|
| Loan accounting and liquidation behavior | [`src/REVLoans.sol`](./src/REVLoans.sol) |
|
|
16
16
|
| Temporary token hiding and supply exclusion | [`src/REVHiddenTokens.sol`](./src/REVHiddenTokens.sol) |
|
|
17
17
|
| Types and helpers | [`src/structs/`](./src/structs/), [`src/interfaces/`](./src/interfaces/), [`test/helpers/`](./test/helpers/) |
|
|
18
|
-
|
|
|
18
|
+
| Lifecycle, loans, and economic proofs | [`test/REVLifecycle.t.sol`](./test/REVLifecycle.t.sol), [`test/REVLoansRegressions.t.sol`](./test/REVLoansRegressions.t.sol), [`test/REVLoans.invariants.t.sol`](./test/REVLoans.invariants.t.sol), [`test/TestLongTailEconomics.t.sol`](./test/TestLongTailEconomics.t.sol) |
|
|
19
19
|
|
|
20
20
|
## Repo Map
|
|
21
21
|
|
|
@@ -28,17 +28,19 @@
|
|
|
28
28
|
|
|
29
29
|
## Purpose
|
|
30
30
|
|
|
31
|
-
Deploy and manage Revnets
|
|
31
|
+
Deploy and manage Revnets: autonomous Juicebox project shapes with staged issuance schedules, optional buyback pools, cross-chain suckers, hidden-token mechanics, and token-collateralized lending.
|
|
32
32
|
|
|
33
33
|
## Reference Files
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
| Contract roles, deploy/runtime entrypoints, integration points, or key structs | [`references/runtime.md`](./references/runtime.md) |
|
|
38
|
-
| Events, errors, constants, storage, gotchas, state-reading recipes, or examples | [`references/operations.md`](./references/operations.md) |
|
|
35
|
+
- Open [`references/runtime.md`](./references/runtime.md) for contract roles, deploy/runtime entrypoints, integration points, and key structs.
|
|
36
|
+
- Open [`references/operations.md`](./references/operations.md) for events, errors, constants, storage, gotchas, and state-reading recipes.
|
|
39
37
|
|
|
40
38
|
## Working Rules
|
|
41
39
|
|
|
42
|
-
- Start in `REVDeployer` for launch-time behavior, `REVOwner` for runtime hook behavior, `REVLoans` for
|
|
43
|
-
-
|
|
44
|
-
-
|
|
40
|
+
- Start in `REVDeployer` for launch-time behavior, `REVOwner` for runtime hook behavior, `REVLoans` for debt accounting, and `REVHiddenTokens` for supply exclusion.
|
|
41
|
+
- Revnets are intentionally ownerless after deployment. Treat any admin-recovery instinct as suspect unless the code proves it.
|
|
42
|
+
- `REVOwner` is not a minor helper; it is a live runtime policy surface.
|
|
43
|
+
- Loan collateral is burned and re-minted, not escrowed. Any change that assumes escrow semantics is likely wrong.
|
|
44
|
+
- Cash-out delay is enforced in both runtime cash-outs and loan borrowing. If one path changes without the other, the protection is broken.
|
|
45
|
+
- Hidden tokens are supply exclusion, not a side balance.
|
|
46
|
+
- Loan behavior, stage transitions, hidden supply, and split-weight adjustments interact. Do not treat them as independent subsystems.
|