@rev-net/core-v6 0.0.1
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/LICENSE +21 -0
- package/README.md +65 -0
- package/REVNET_SECURITY_CHECKLIST.md +164 -0
- package/SECURITY.md +68 -0
- package/SKILLS.md +166 -0
- package/deployments/revnet-core-v5/arbitrum/REVDeployer.json +2821 -0
- package/deployments/revnet-core-v5/arbitrum/REVLoans.json +2260 -0
- package/deployments/revnet-core-v5/arbitrum_sepolia/REVDeployer.json +2821 -0
- package/deployments/revnet-core-v5/arbitrum_sepolia/REVLoans.json +2260 -0
- package/deployments/revnet-core-v5/base/REVDeployer.json +2825 -0
- package/deployments/revnet-core-v5/base/REVLoans.json +2264 -0
- package/deployments/revnet-core-v5/base_sepolia/REVDeployer.json +2825 -0
- package/deployments/revnet-core-v5/base_sepolia/REVLoans.json +2264 -0
- package/deployments/revnet-core-v5/ethereum/REVDeployer.json +2825 -0
- package/deployments/revnet-core-v5/ethereum/REVLoans.json +2264 -0
- package/deployments/revnet-core-v5/optimism/REVDeployer.json +2821 -0
- package/deployments/revnet-core-v5/optimism/REVLoans.json +2260 -0
- package/deployments/revnet-core-v5/optimism_sepolia/REVDeployer.json +2825 -0
- package/deployments/revnet-core-v5/optimism_sepolia/REVLoans.json +2264 -0
- package/deployments/revnet-core-v5/sepolia/REVDeployer.json +2825 -0
- package/deployments/revnet-core-v5/sepolia/REVLoans.json +2264 -0
- package/docs/book.css +13 -0
- package/docs/book.toml +13 -0
- package/docs/solidity.min.js +74 -0
- package/docs/src/README.md +88 -0
- package/docs/src/SUMMARY.md +20 -0
- package/docs/src/src/README.md +7 -0
- package/docs/src/src/REVDeployer.sol/contract.REVDeployer.md +968 -0
- package/docs/src/src/REVLoans.sol/contract.REVLoans.md +1047 -0
- package/docs/src/src/interfaces/IREVDeployer.sol/interface.IREVDeployer.md +243 -0
- package/docs/src/src/interfaces/IREVLoans.sol/interface.IREVLoans.md +296 -0
- package/docs/src/src/interfaces/README.md +5 -0
- package/docs/src/src/structs/README.md +14 -0
- package/docs/src/src/structs/REVAutoIssuance.sol/struct.REVAutoIssuance.md +19 -0
- package/docs/src/src/structs/REVBuybackHookConfig.sol/struct.REVBuybackHookConfig.md +19 -0
- package/docs/src/src/structs/REVBuybackPoolConfig.sol/struct.REVBuybackPoolConfig.md +21 -0
- package/docs/src/src/structs/REVConfig.sol/struct.REVConfig.md +35 -0
- package/docs/src/src/structs/REVCroptopAllowedPost.sol/struct.REVCroptopAllowedPost.md +28 -0
- package/docs/src/src/structs/REVDeploy721TiersHookConfig.sol/struct.REVDeploy721TiersHookConfig.md +34 -0
- package/docs/src/src/structs/REVDescription.sol/struct.REVDescription.md +23 -0
- package/docs/src/src/structs/REVLoan.sol/struct.REVLoan.md +28 -0
- package/docs/src/src/structs/REVLoanSource.sol/struct.REVLoanSource.md +16 -0
- package/docs/src/src/structs/REVStageConfig.sol/struct.REVStageConfig.md +44 -0
- package/docs/src/src/structs/REVSuckerDeploymentConfig.sol/struct.REVSuckerDeploymentConfig.md +16 -0
- package/foundry.lock +11 -0
- package/foundry.toml +23 -0
- package/package.json +31 -0
- package/remappings.txt +1 -0
- package/script/Deploy.s.sol +350 -0
- package/script/helpers/RevnetCoreDeploymentLib.sol +72 -0
- package/slither-ci.config.json +10 -0
- package/sphinx.lock +507 -0
- package/src/REVDeployer.sol +1257 -0
- package/src/REVLoans.sol +1333 -0
- package/src/interfaces/IREVDeployer.sol +198 -0
- package/src/interfaces/IREVLoans.sol +241 -0
- package/src/structs/REVAutoIssuance.sol +11 -0
- package/src/structs/REVConfig.sol +17 -0
- package/src/structs/REVCroptopAllowedPost.sol +20 -0
- package/src/structs/REVDeploy721TiersHookConfig.sol +25 -0
- package/src/structs/REVDescription.sol +14 -0
- package/src/structs/REVLoan.sol +19 -0
- package/src/structs/REVLoanSource.sol +11 -0
- package/src/structs/REVStageConfig.sol +34 -0
- package/src/structs/REVSuckerDeploymentConfig.sol +11 -0
- package/test/REV.integrations.t.sol +420 -0
- package/test/REVAutoIssuanceFuzz.t.sol +276 -0
- package/test/REVDeployerAuditRegressions.t.sol +328 -0
- package/test/REVInvincibility.t.sol +1275 -0
- package/test/REVInvincibilityHandler.sol +357 -0
- package/test/REVLifecycle.t.sol +364 -0
- package/test/REVLoans.invariants.t.sol +642 -0
- package/test/REVLoansAttacks.t.sol +739 -0
- package/test/REVLoansAuditRegressions.t.sol +314 -0
- package/test/REVLoansFeeRecovery.t.sol +704 -0
- package/test/REVLoansSourced.t.sol +1732 -0
- package/test/REVLoansUnSourced.t.sol +331 -0
- package/test/TestPR09_ConversionDocumentation.t.sol +304 -0
- package/test/TestPR10_LiquidationBehavior.t.sol +340 -0
- package/test/TestPR11_LowFindings.t.sol +571 -0
- package/test/TestPR12_FlashLoanSurplus.t.sol +305 -0
- package/test/TestPR13_CrossSourceReallocation.t.sol +302 -0
- package/test/TestPR15_CashOutCallerValidation.t.sol +320 -0
- package/test/TestPR16_ZeroRepayment.t.sol +297 -0
- package/test/TestPR21_Uint112Overflow.t.sol +251 -0
- package/test/TestPR22_HookArrayOOB.t.sol +221 -0
- package/test/TestPR26_BurnHeldTokens.t.sol +331 -0
- package/test/TestPR27_CEIPattern.t.sol +448 -0
- package/test/TestPR29_SwapTerminalPermission.t.sol +206 -0
- package/test/TestPR32_MixedFixes.t.sol +529 -0
- package/test/helpers/MaliciousContracts.sol +233 -0
- package/test/mock/MockBuybackDataHook.sol +61 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Revnet Research Network
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# revnet-core-v5
|
|
2
|
+
|
|
3
|
+
Deploy and operate Revnets: unowned Juicebox projects that run autonomously according to predefined stages, with built-in token-collateralized loans.
|
|
4
|
+
|
|
5
|
+
## What is a Revnet?
|
|
6
|
+
|
|
7
|
+
A Revnet is a Retailistic network — a treasury-backed token that runs itself. No owners, no governors, no multisigs. Once deployed, a revnet follows its predefined stages forever, backed by the Juicebox and Uniswap protocols.
|
|
8
|
+
|
|
9
|
+
For a Retailism TLDR, see [Retailism](https://jango.eth.sucks/9E01E72C-6028-48B7-AD04-F25393307132/).
|
|
10
|
+
|
|
11
|
+
For more Retailism information, see:
|
|
12
|
+
|
|
13
|
+
- [A Retailistic View on CAC and LTV](https://jango.eth.limo/572BD957-0331-4977-8B2D-35F84D693276/)
|
|
14
|
+
- [Modeling Retailism](https://jango.eth.limo/B762F3CC-AEFE-4DE0-B08C-7C16400AF718/)
|
|
15
|
+
- [Retailism for Devs, Investors, and Customers](https://jango.eth.limo/3EB05292-0376-4B7D-AFCF-042B70673C3D/)
|
|
16
|
+
- [Observations: Network dynamics similar between atoms, cells, organisms, groups, dance parties](https://jango.eth.limo/CF40F5D2-7BFE-43A3-9C15-1C6547FBD15C/)
|
|
17
|
+
|
|
18
|
+
Join the conversation: [Discord](https://discord.gg/nT3XqbzNEr)
|
|
19
|
+
|
|
20
|
+
## Architecture
|
|
21
|
+
|
|
22
|
+
| Contract | Description |
|
|
23
|
+
|----------|-------------|
|
|
24
|
+
| [`REVDeployer`](src/REVDeployer.sol) | 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. |
|
|
25
|
+
| [`REVLoans`](src/REVLoans.sol) | 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. |
|
|
26
|
+
|
|
27
|
+
### How they relate
|
|
28
|
+
|
|
29
|
+
`REVDeployer` owns every revnet's Juicebox project NFT and holds all administrative permissions. During deployment it grants `REVLoans` the `USE_ALLOWANCE` permission so loans can pull funds from the revnet's terminal. `REVLoans` verifies that a revnet was deployed by its expected `REVDeployer` before issuing any loan.
|
|
30
|
+
|
|
31
|
+
### Deployer Variants
|
|
32
|
+
|
|
33
|
+
This repo includes several deployer patterns for different use cases:
|
|
34
|
+
|
|
35
|
+
- **Basic revnet** — Deploy a simple revnet with `REVDeployer` using stage configurations that map to Juicebox rulesets.
|
|
36
|
+
- **Pay hook revnet** — Accept additional pay hooks that run throughout the revnet's lifetime as it receives payments.
|
|
37
|
+
- **Tiered 721 revnet** — Deploy a tiered 721 pay hook (NFT tiers) that mints NFTs as people pay into the revnet.
|
|
38
|
+
- **Croptop revnet** — A tiered 721 revnet where the public can post content through the [Croptop](https://croptop.eth.limo) publisher contract.
|
|
39
|
+
|
|
40
|
+
You can use these contracts to deploy treasuries from Etherscan, or wherever else they've been exposed from.
|
|
41
|
+
|
|
42
|
+
## Install
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm install @rev-net/core-v5
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Develop
|
|
49
|
+
|
|
50
|
+
This repo uses [npm](https://www.npmjs.com/) for package management and [Foundry](https://github.com/foundry-rs/foundry) for builds and tests.
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npm install && forge install
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
If `forge install` has issues, try `git submodule update --init --recursive`.
|
|
57
|
+
|
|
58
|
+
| Command | Description |
|
|
59
|
+
|---------|-------------|
|
|
60
|
+
| `forge build` | Compile contracts |
|
|
61
|
+
| `forge test` | Run tests |
|
|
62
|
+
| `forge test -vvvv` | Run tests with full traces |
|
|
63
|
+
| `forge fmt` | Lint |
|
|
64
|
+
| `forge coverage` | Generate test coverage report |
|
|
65
|
+
| `forge build --sizes` | Get contract sizes |
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# Revnet Pre-Deployment Security Checklist
|
|
2
|
+
|
|
3
|
+
**Status**: All findings documented, tests written. Fixes required before deployment.
|
|
4
|
+
**Test Suite**: `forge test --match-contract REVInvincibility -vvv`
|
|
5
|
+
**Date**: 2026-02-21
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Critical Findings (5)
|
|
10
|
+
|
|
11
|
+
### C-1: uint112 Truncation in REVLoans._adjust
|
|
12
|
+
- **File**: `REVLoans.sol:922-923`
|
|
13
|
+
- **Code**: `loan.amount = uint112(newBorrowAmount); loan.collateral = uint112(newCollateralCount);`
|
|
14
|
+
- **Impact**: Silently truncates borrow amounts > 5.19e33 (uint112.max), allowing loans with near-zero recorded debt but full ETH disbursement
|
|
15
|
+
- **Fix Required**: Add `require(newBorrowAmount <= type(uint112).max)` and `require(newCollateralCount <= type(uint112).max)` before the casts
|
|
16
|
+
- **Test**: `test_fixVerify_C1_uint112Truncation` — proves the truncation math
|
|
17
|
+
- **Status**: [ ] UNFIXED
|
|
18
|
+
|
|
19
|
+
### C-2: Array OOB in REVDeployer.beforePayRecordedWith
|
|
20
|
+
- **File**: `REVDeployer.sol:248-258`
|
|
21
|
+
- **Code**: `hookSpecifications[1] = buybackHookSpecifications[0]` hardcoded to index [1]
|
|
22
|
+
- **Impact**: Payment to any revnet with buyback hook but no 721 hook reverts (OOB on size-1 array)
|
|
23
|
+
- **Fix Required**: Change line 258 to use dynamic index: `hookSpecifications[usesTiered721Hook ? 1 : 0]`
|
|
24
|
+
- **Test**: `test_fixVerify_C2_arrayOOB_noBuybackWithBuyback` — proves the index logic
|
|
25
|
+
- **Status**: [ ] UNFIXED
|
|
26
|
+
|
|
27
|
+
### C-3: Reentrancy in REVLoans._adjust
|
|
28
|
+
- **File**: `REVLoans.sol:910 vs 922-923`
|
|
29
|
+
- **Code**: `terminal.pay()` at line 910 before `loan.amount = ...` at line 922
|
|
30
|
+
- **Impact**: Malicious fee terminal can reenter borrowFrom() with stale loan state, potentially extracting more than collateral supports
|
|
31
|
+
- **Fix Required**: Either add `nonReentrant` modifier or move state writes (lines 922-923) before external calls (line 910)
|
|
32
|
+
- **Test**: `test_fixVerify_C3_reentrancyDoubleBorrow` — confirms CEI violation pattern
|
|
33
|
+
- **Status**: [ ] UNFIXED
|
|
34
|
+
|
|
35
|
+
### C-4: hasMintPermissionFor Reverts on address(0)
|
|
36
|
+
- **File**: `REVDeployer.sol:353-354`
|
|
37
|
+
- **Code**: `buybackHook.hasMintPermissionFor(...)` called when `buybackHook == address(0)`
|
|
38
|
+
- **Impact**: Blocks ALL sucker claims and external mint operations for revnets without buyback hooks
|
|
39
|
+
- **Fix Required**: Add `address(buybackHook) != address(0) &&` guard before the call
|
|
40
|
+
- **Test**: `test_fixVerify_C4_hasMintPermission_noBuyback` — triggers the revert
|
|
41
|
+
- **Status**: [ ] UNFIXED
|
|
42
|
+
|
|
43
|
+
### C-5: Zero-Supply Cash Out Drains Surplus
|
|
44
|
+
- **File**: `JBCashOuts.sol:31`
|
|
45
|
+
- **Code**: `if (cashOutCount >= totalSupply) return surplus;` — 0 >= 0 is true
|
|
46
|
+
- **Impact**: When totalSupply=0, cashing out 0 tokens returns the ENTIRE surplus
|
|
47
|
+
- **Fix Required**: Add `if (cashOutCount == 0) return 0;` before the totalSupply check
|
|
48
|
+
- **Test**: `test_fixVerify_C5_zeroSupplyCashOutDrain` — proves 0/0 returns full surplus
|
|
49
|
+
- **Status**: [ ] UNFIXED (in JBCashOuts library)
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## High Findings (4 revnet-specific)
|
|
54
|
+
|
|
55
|
+
### H-1: Double Fee on Cash-Outs
|
|
56
|
+
- **File**: `REVDeployer.sol:567-624`
|
|
57
|
+
- **Impact**: Cash-out fees are charged twice — once by JBMultiTerminal (protocol fee) and once by REVDeployer's afterCashOutRecordedWith (revnet fee). REVDeployer is not registered as feeless.
|
|
58
|
+
- **Fix Required**: Register REVDeployer as feeless address, or adjust fee calculation to account for the protocol fee already taken
|
|
59
|
+
- **Test**: `test_econ_doubleFeeH1` — measures actual fee amounts
|
|
60
|
+
- **Status**: [ ] UNFIXED
|
|
61
|
+
|
|
62
|
+
### H-2: Broken Fee Terminal Bricks Cash-Outs
|
|
63
|
+
- **File**: `REVDeployer.sol:615`
|
|
64
|
+
- **Impact**: In the catch block of afterCashOutRecordedWith, `addToBalanceOf()` is NOT wrapped in try/catch. If both feeTerminal.pay() and addToBalanceOf() revert, ALL cash-outs for the revnet become permanently impossible.
|
|
65
|
+
- **Fix Required**: Wrap the fallback `addToBalanceOf()` at line 615 in its own try/catch
|
|
66
|
+
- **Test**: `test_fixVerify_H2_brokenFeeTerminalBricksCashOuts` — demonstrates both paths revert
|
|
67
|
+
- **Status**: [ ] UNFIXED
|
|
68
|
+
|
|
69
|
+
### H-5: Auto-Issuance Stage ID Mismatch
|
|
70
|
+
- **File**: `REVDeployer.sol:1223`
|
|
71
|
+
- **Code**: `amountToAutoIssue[revnetId][block.timestamp + i][...] += ...`
|
|
72
|
+
- **Impact**: Stage ID computed as `block.timestamp + i` but actual ruleset IDs from JBRulesets may differ. Auto-issuance tokens for non-first stages become permanently unclaimable.
|
|
73
|
+
- **Fix Required**: Use the actual ruleset IDs returned by `jbController().queueRulesetsOf()` instead of `block.timestamp + i`
|
|
74
|
+
- **Test**: `test_fixVerify_H5_autoIssuanceStageIdMismatch` — confirms mismatch for stage 1+
|
|
75
|
+
- **Status**: [ ] UNFIXED
|
|
76
|
+
|
|
77
|
+
### H-6: Unvalidated Source Terminal
|
|
78
|
+
- **File**: `REVLoans.sol:788-791`
|
|
79
|
+
- **Impact**: Any terminal can be registered as a loan source. Attacker can grow `_loanSourcesOf` array unboundedly, causing gas DoS on functions that iterate loan sources.
|
|
80
|
+
- **Fix Required**: Validate that `loan.source.terminal` is a registered terminal for the project via `DIRECTORY.isTerminalOf(revnetId, loan.source.terminal)`
|
|
81
|
+
- **Test**: `test_fixVerify_H6_unvalidatedSourceTerminal` — documents the unvalidated registration
|
|
82
|
+
- **Status**: [ ] UNFIXED
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Medium Findings (3 revnet-specific)
|
|
87
|
+
|
|
88
|
+
### M-7: Silent Fee Failure in REVLoans._addTo
|
|
89
|
+
- **File**: `REVLoans.sol:833-841`
|
|
90
|
+
- **Impact**: REV fee payment in `_addTo` is wrapped in try/catch. If the fee terminal reverts, the fee is silently lost — REV holders lose fee revenue without any notification.
|
|
91
|
+
- **Fix Required**: At minimum, emit an event on fee failure. Consider reverting to ensure fees are always collected.
|
|
92
|
+
- **Status**: [ ] UNFIXED
|
|
93
|
+
|
|
94
|
+
### M-10: Cross-Source Value Extraction via reallocateCollateralFromLoan
|
|
95
|
+
- **File**: `REVLoans.sol:619-654`
|
|
96
|
+
- **Impact**: Collateral from one loan source can be transferred to create a loan from a different source. If source terminals have different fee structures, this enables fee arbitrage.
|
|
97
|
+
- **Fix Required**: Consider restricting collateral reallocation to same-source loans
|
|
98
|
+
- **Status**: [ ] UNFIXED
|
|
99
|
+
|
|
100
|
+
### M-11: Flash Loan Surplus Inflation
|
|
101
|
+
- **File**: `REVLoans.sol:308-332`
|
|
102
|
+
- **Impact**: `borrowableAmountFrom` reads live surplus. An attacker can `addToBalance` (inflating surplus without minting tokens) then immediately borrow at an inflated rate within the same block.
|
|
103
|
+
- **Fix Required**: Consider using a time-weighted average surplus or adding a borrowing delay
|
|
104
|
+
- **Test**: `test_econ_flashLoanSurplusInflation` — quantifies exact inflation factor
|
|
105
|
+
- **Status**: [ ] UNFIXED
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Invariant Properties (Verified by Fuzzing)
|
|
110
|
+
|
|
111
|
+
| ID | Property | Handler Operations | Runs |
|
|
112
|
+
|----|----------|-------------------|------|
|
|
113
|
+
| INV-REV-1 | Terminal balance covers outstanding loans | payAndBorrow, repayLoan, addToBalance | 256 |
|
|
114
|
+
| INV-REV-2 | Ghost collateral sum == totalCollateralOf | payAndBorrow, repayLoan, reallocate | 256 |
|
|
115
|
+
| INV-REV-3 | Ghost borrowed sum == totalBorrowedFrom | payAndBorrow, repayLoan | 256 |
|
|
116
|
+
| INV-REV-4 | No undercollateralized loans (when no cash-outs) | payAndBorrow, advanceTime | 256 |
|
|
117
|
+
| INV-REV-5 | totalSupply + totalCollateral coherent | All 10 operations | 256 |
|
|
118
|
+
| INV-REV-6 | Fee project balance monotonically increasing | payAndBorrow (generates fees) | 256 |
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Test Execution
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# Section A+B: Fix verification + economic attacks (18 tests)
|
|
126
|
+
forge test --match-contract REVInvincibility_FixVerify -vvv
|
|
127
|
+
|
|
128
|
+
# Section C: Invariant properties (6 invariants)
|
|
129
|
+
forge test --match-contract REVInvincibility_Invariants -vvv
|
|
130
|
+
|
|
131
|
+
# Full suite
|
|
132
|
+
forge test --match-contract REVInvincibility -vvv
|
|
133
|
+
|
|
134
|
+
# Full regression (all existing tests still pass)
|
|
135
|
+
forge test -vvv
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Post-Deployment Monitoring Recommendations
|
|
141
|
+
|
|
142
|
+
1. **Loan Health Monitor**: Track `totalBorrowedFrom` vs terminal balance for each revnet. Alert if borrowed exceeds 80% of surplus.
|
|
143
|
+
2. **Fee Collection Monitor**: Verify fee project token supply increases after every borrow operation. Alert on silent fee failures.
|
|
144
|
+
3. **Collateral Consistency**: Periodically verify `sum(loan.collateral)` for all active loans matches `totalCollateralOf(revnetId)`.
|
|
145
|
+
4. **Loan Source Array**: Monitor `_loanSourcesOf` array length. Alert if it exceeds expected number of terminals.
|
|
146
|
+
5. **Auto-Issuance Claims**: After each stage transition, verify auto-issuance can be claimed at the correct ruleset ID.
|
|
147
|
+
6. **Cash-Out Availability**: Monitor that cash-outs succeed after fee terminal configuration changes.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Fix Priority Order
|
|
152
|
+
|
|
153
|
+
1. **C-3** (Reentrancy) — Highest risk, enables active exploitation
|
|
154
|
+
2. **C-5** (Zero-supply drain) — Direct fund loss
|
|
155
|
+
3. **C-1** (uint112 truncation) — Fund loss at extreme values
|
|
156
|
+
4. **C-4** (hasMintPermission revert) — Blocks sucker claims
|
|
157
|
+
5. **C-2** (Array OOB) — Breaks payments for buyback-only revnets
|
|
158
|
+
6. **H-2** (Broken fee terminal) — Permanent cash-out DoS
|
|
159
|
+
7. **H-5** (Auto-issuance mismatch) — Permanent token loss
|
|
160
|
+
8. **H-6** (Unvalidated terminal) — Gas DoS vector
|
|
161
|
+
9. **H-1** (Double fee) — Economic loss for users
|
|
162
|
+
10. **M-11** (Flash surplus inflation) — Economic exploitation
|
|
163
|
+
11. **M-10** (Cross-source extraction) — Fee arbitrage
|
|
164
|
+
12. **M-7** (Silent fee failure) — Revenue leakage
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Security Considerations
|
|
2
|
+
|
|
3
|
+
## [INTEROP-6] Cross-Chain Accounting Mismatch: NATIVE_TOKEN on Non-ETH Chains
|
|
4
|
+
|
|
5
|
+
**Severity:** Medium
|
|
6
|
+
**Status:** Acknowledged — by design, not fixable without oracle dependencies
|
|
7
|
+
|
|
8
|
+
### Description
|
|
9
|
+
|
|
10
|
+
When a revnet expands to a chain where the native token is not ETH (e.g., Celo where native = CELO), using `JBConstants.NATIVE_TOKEN` as the terminal accounting context and sucker token mapping creates a semantic mismatch. The protocol treats CELO payments as ETH-equivalent.
|
|
11
|
+
|
|
12
|
+
### What the Matching Hash Covers
|
|
13
|
+
|
|
14
|
+
The hash computed in `REVDeployer._makeRulesetConfigurations()` ensures both sides of a cross-chain deployment agree on:
|
|
15
|
+
- `baseCurrency`, `loans`, `name`, `ticker`, `salt`
|
|
16
|
+
- Per stage: timing, splits, issuance, cash-out tax
|
|
17
|
+
- Per auto-issuance: chainId, beneficiary, count
|
|
18
|
+
|
|
19
|
+
### What the Matching Hash Does NOT Cover
|
|
20
|
+
|
|
21
|
+
- Terminal configurations (which tokens are accepted)
|
|
22
|
+
- Accounting contexts (token address, decimals, currency)
|
|
23
|
+
- Sucker token mappings (localToken → remoteToken)
|
|
24
|
+
|
|
25
|
+
Two deployments can produce identical hashes while one accepts ETH-native and the other accepts CELO-native. The hash is a safety check for economic parameter alignment, not a guarantee of asset compatibility.
|
|
26
|
+
|
|
27
|
+
### Impact on Revnets
|
|
28
|
+
|
|
29
|
+
1. **Issuance mispricing** — A revnet with `baseCurrency = ETH` that accepts `NATIVE_TOKEN` on Celo prices CELO payments as ETH (1:1 without a price feed), massively overvaluing them.
|
|
30
|
+
2. **Surplus fragmentation** — Cash-out bonding curve on each chain only sees that chain's surplus. Token holders must bridge to the chain with more surplus for fair cash-out values.
|
|
31
|
+
3. **Cash-out arbitrage** — Different effective valuations across chains let arbitrageurs buy tokens cheaply on one chain and cash out on another.
|
|
32
|
+
|
|
33
|
+
### Recommended Configuration for Non-ETH Chains
|
|
34
|
+
|
|
35
|
+
When deploying a revnet to Celo or other non-ETH-native chains:
|
|
36
|
+
|
|
37
|
+
```solidity
|
|
38
|
+
// DO: Use WETH ERC20 as accounting context
|
|
39
|
+
accountingContextsToAccept[0] = JBAccountingContext({
|
|
40
|
+
token: WETH_ADDRESS, // e.g., 0xD221812... on Celo
|
|
41
|
+
decimals: 18,
|
|
42
|
+
currency: ETH_CURRENCY
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// DO: Map WETH → WETH in sucker token mappings
|
|
46
|
+
tokenMappings[0] = JBTokenMapping({
|
|
47
|
+
localToken: WETH_ADDRESS,
|
|
48
|
+
remoteToken: WETH_ADDRESS,
|
|
49
|
+
minGas: 200_000,
|
|
50
|
+
minBridgeAmount: 0.01 ether
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// DON'T: Use NATIVE_TOKEN on non-ETH chains
|
|
54
|
+
// This maps CELO → ETH which are different assets
|
|
55
|
+
tokenMappings[0] = JBTokenMapping({
|
|
56
|
+
localToken: JBConstants.NATIVE_TOKEN, // = CELO on Celo
|
|
57
|
+
remoteToken: JBConstants.NATIVE_TOKEN, // = ETH on Ethereum
|
|
58
|
+
...
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Safe Chains
|
|
63
|
+
|
|
64
|
+
OP Stack L2s where native token IS ETH: Ethereum, Optimism, Base, Arbitrum.
|
|
65
|
+
|
|
66
|
+
### Affected Chains
|
|
67
|
+
|
|
68
|
+
Any chain where native token ≠ ETH: Celo (CELO), Polygon (MATIC), Avalanche (AVAX), BNB Chain (BNB).
|
package/SKILLS.md
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# revnet-core-v5
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Deploy and manage Revnets -- autonomous, unowned Juicebox projects with staged issuance schedules and token-collateralized lending.
|
|
6
|
+
|
|
7
|
+
## Contracts
|
|
8
|
+
|
|
9
|
+
| Contract | Role |
|
|
10
|
+
|----------|------|
|
|
11
|
+
| `REVDeployer` | Deploys revnets, acts as project owner, data hook, and cash out hook. Manages stages, splits, auto-issuance, buyback hooks, 721 hooks, suckers, and split operators. |
|
|
12
|
+
| `REVLoans` | Issues token-collateralized loans from revnet treasuries. Each loan is an ERC-721. Burns collateral on borrow, re-mints on repay. Charges tiered fees (REV protocol fee + source fee + prepaid fee). |
|
|
13
|
+
|
|
14
|
+
## Key Functions
|
|
15
|
+
|
|
16
|
+
| Function | Contract | What it does |
|
|
17
|
+
|----------|----------|--------------|
|
|
18
|
+
| `deployFor` | REVDeployer | Deploy a new revnet (or convert an existing Juicebox project) with stages, terminals, buyback hook, suckers, and loans. |
|
|
19
|
+
| `deployWith721sFor` | REVDeployer | Same as `deployFor` but also deploys a tiered ERC-721 hook and configures croptop allowed posts. |
|
|
20
|
+
| `autoIssueFor` | REVDeployer | Mint pre-configured auto-issuance tokens for a beneficiary once a stage has started. |
|
|
21
|
+
| `setSplitOperatorOf` | REVDeployer | Replace the current split operator (only callable by the current split operator). |
|
|
22
|
+
| `deploySuckersFor` | REVDeployer | Deploy new cross-chain suckers for an existing revnet (split operator only, ruleset must allow it). |
|
|
23
|
+
| `beforePayRecordedWith` | REVDeployer | Data hook callback: returns the buyback hook weight and assembles pay hook specs (721 hook + buyback hook). |
|
|
24
|
+
| `beforeCashOutRecordedWith` | REVDeployer | Data hook callback: enforces cash out delay, exempts suckers from fees/taxes, calculates and routes the 2.5% cash out fee. |
|
|
25
|
+
| `afterCashOutRecordedWith` | REVDeployer | Cash out hook callback: transfers the fee amount to the fee revnet's terminal. Falls back to returning funds if the fee payment fails. |
|
|
26
|
+
| `hasMintPermissionFor` | REVDeployer | Returns true for the loans contract, buyback hook, or any sucker. |
|
|
27
|
+
| `borrowFrom` | REVLoans | Open a loan: burn collateral tokens, pull funds from the revnet via `useAllowanceOf`, pay REV fee + source fee, transfer remainder to beneficiary, mint loan NFT. |
|
|
28
|
+
| `repayLoan` | REVLoans | Repay (partially or fully): accept repayment funds, return them to the revnet via `addToBalanceOf`, re-mint collateral to beneficiary, burn/replace the loan NFT. |
|
|
29
|
+
| `reallocateCollateralFromLoan` | REVLoans | Refinance: remove excess collateral from an existing loan (when collateral has appreciated) and open a new loan with the freed collateral. Burns the original, mints two replacements. |
|
|
30
|
+
| `liquidateExpiredLoansFrom` | REVLoans | Clean up loans past the 10-year liquidation duration. Burns their NFTs and decrements accounting totals. |
|
|
31
|
+
| `borrowableAmountFrom` | REVLoans | View: calculate how much can be borrowed for a given collateral amount using the bonding curve formula. |
|
|
32
|
+
| `determineSourceFeeAmount` | REVLoans | View: calculate the time-proportional source fee for a loan repayment. |
|
|
33
|
+
|
|
34
|
+
## Integration Points
|
|
35
|
+
|
|
36
|
+
| Dependency | Import | Used For |
|
|
37
|
+
|------------|--------|----------|
|
|
38
|
+
| `@bananapus/core-v6` | `IJBController`, `IJBDirectory`, `IJBPermissions`, `IJBProjects`, `IJBTerminal`, `IJBPrices` | Project lifecycle, rulesets, token minting/burning, fund access, terminal payments, price feeds |
|
|
39
|
+
| `@bananapus/721-hook-v6` | `IJB721TiersHook`, `IJB721TiersHookDeployer` | Deploying and registering tiered ERC-721 pay hooks |
|
|
40
|
+
| `@bananapus/buyback-hook-v6` | `IJBBuybackHook` | Configuring Uniswap buyback pools per revnet |
|
|
41
|
+
| `@bananapus/suckers-v6` | `IJBSuckerRegistry` | Deploying cross-chain suckers, checking sucker status for fee exemption |
|
|
42
|
+
| `@croptop/core-v6` | `CTPublisher` | Configuring croptop posting criteria for 721 tiers |
|
|
43
|
+
| `@bananapus/permission-ids-v6` | `JBPermissionIds` | Permission ID constants (SET_SPLIT_GROUPS, USE_ALLOWANCE, etc.) |
|
|
44
|
+
| `@openzeppelin/contracts` | `ERC721`, `ERC2771Context`, `Ownable`, `SafeERC20` | Loan NFTs, meta-transactions, ownership, safe token transfers |
|
|
45
|
+
| `@uniswap/permit2` | `IPermit2`, `IAllowanceTransfer` | Gasless token approvals for loan repayments |
|
|
46
|
+
| `@prb/math` | `mulDiv` | Precise fixed-point multiplication and division |
|
|
47
|
+
|
|
48
|
+
## Key Types
|
|
49
|
+
|
|
50
|
+
| Struct/Enum | Key Fields | Used In |
|
|
51
|
+
|-------------|------------|---------|
|
|
52
|
+
| `REVConfig` | `description`, `baseCurrency`, `splitOperator`, `stageConfigurations[]`, `loanSources[]`, `loans` | `deployFor`, `deployWith721sFor` |
|
|
53
|
+
| `REVStageConfig` | `startsAtOrAfter` (uint48), `initialIssuance` (uint112), `issuanceCutFrequency` (uint32), `issuanceCutPercent` (uint32), `cashOutTaxRate` (uint16), `splitPercent` (uint16), `splits[]`, `autoIssuances[]`, `extraMetadata` (uint16) | Translated into `JBRulesetConfig` by `REVDeployer` |
|
|
54
|
+
| `REVDescription` | `name`, `ticker`, `uri`, `salt` | ERC-20 token deployment and project metadata |
|
|
55
|
+
| `REVAutoIssuance` | `chainId` (uint32), `count` (uint104), `beneficiary` | Per-stage token auto-minting, cross-chain aware |
|
|
56
|
+
| `REVLoan` | `amount` (uint112), `collateral` (uint112), `createdAt` (uint48), `prepaidFeePercent` (uint16), `prepaidDuration` (uint32), `source` | Stored per loan ID in `REVLoans` |
|
|
57
|
+
| `REVLoanSource` | `token`, `terminal` (IJBPayoutTerminal) | Identifies which terminal and token a loan draws from |
|
|
58
|
+
| `REVBuybackHookConfig` | `dataHook`, `hookToConfigure`, `poolConfigurations[]` | Buyback hook setup during deployment |
|
|
59
|
+
| `REVBuybackPoolConfig` | `token`, `fee` (uint24), `twapWindow` (uint32) | Uniswap pool configuration for buyback |
|
|
60
|
+
| `REVSuckerDeploymentConfig` | `deployerConfigurations[]`, `salt` | Cross-chain sucker deployment |
|
|
61
|
+
| `REVDeploy721TiersHookConfig` | `baseline721HookConfiguration`, `salt`, `splitOperatorCanAdjustTiers`, `splitOperatorCanUpdateMetadata`, `splitOperatorCanMint`, `splitOperatorCanIncreaseDiscountPercent` | 721 hook deployment with operator permissions |
|
|
62
|
+
| `REVCroptopAllowedPost` | `category` (uint24), `minimumPrice` (uint104), `minimumTotalSupply` (uint32), `maximumTotalSupply` (uint32), `allowedAddresses[]` | Croptop posting criteria |
|
|
63
|
+
|
|
64
|
+
## Gotchas
|
|
65
|
+
|
|
66
|
+
- `REVLoan.amount` and `REVLoan.collateral` are `uint112` -- truncation risk with very large values (Critical audit finding C-1).
|
|
67
|
+
- `REVDeployer` stage configuration arrays must have increasing `startsAtOrAfter` values or deployment reverts (array out-of-bounds risk, C-2).
|
|
68
|
+
- `REVLoans` token callbacks during borrow/repay create reentrancy surface (C-3). The contract uses burn-before-transfer and mint-after-transfer patterns.
|
|
69
|
+
- `hasMintPermissionFor` calls `buybackHookOf[revnetId]` which could be `address(0)` -- the call chain through the buyback hook can revert (C-4).
|
|
70
|
+
- The `REVDeployer` project NFT is permanently locked in the deployer contract. There is no function to release it. This is by design -- revnets are ownerless.
|
|
71
|
+
- Auto-issuance stage IDs are based on `block.timestamp + i` where `i` is the stage index. If the first stage's `startsAtOrAfter` is 0, it becomes the deployment block timestamp (H-5).
|
|
72
|
+
- `cashOutTaxRate` must be strictly less than `MAX_CASH_OUT_TAX_RATE` -- revnets cannot fully disable cash outs.
|
|
73
|
+
- Cash out delay is 30 days (`CASH_OUT_DELAY = 2_592_000`), applied only when deploying an existing revnet to a new chain (first stage already started).
|
|
74
|
+
- Loan IDs encode the revnet ID: `loanId = revnetId * 1_000_000_000_000 + loanNumber`. Use `revnetIdOfLoanWith(loanId)` to decode.
|
|
75
|
+
- Loan liquidation duration is 10 years (`LOAN_LIQUIDATION_DURATION = 3650 days`). After this, collateral is forfeit.
|
|
76
|
+
- The split operator has 6 default permissions (SET_SPLIT_GROUPS, SET_BUYBACK_POOL, SET_BUYBACK_TWAP, SET_PROJECT_URI, ADD_PRICE_FEED, SUCKER_SAFETY) plus any extras from 721 hook config.
|
|
77
|
+
- `REVLoans` uses `permit2` for ERC-20 transfers as a fallback when standard allowance is insufficient.
|
|
78
|
+
- The `FEE` constant is `25` (out of `MAX_FEE = 1000`), meaning a 2.5% cash out fee paid to the fee revnet.
|
|
79
|
+
- `REV_PREPAID_FEE_PERCENT` is `10` (1%) -- this is the protocol-level fee on loans paid to the $REV revnet.
|
|
80
|
+
- `MIN_PREPAID_FEE_PERCENT` is `25` (2.5%) and `MAX_PREPAID_FEE_PERCENT` is `500` (50%) -- bounds on the borrower-chosen prepaid fee.
|
|
81
|
+
|
|
82
|
+
### CRITICAL: NATIVE_TOKEN Accounting on Non-ETH Chains
|
|
83
|
+
|
|
84
|
+
When deploying a revnet to a chain where the native token is NOT ETH (e.g. Celo, Polygon), the terminal must NOT use `JBConstants.NATIVE_TOKEN` as its accounting context. `NATIVE_TOKEN` represents whatever is native on that chain (CELO on Celo, MATIC on Polygon), but `baseCurrency=1` (ETH) assumes ETH-denominated value.
|
|
85
|
+
|
|
86
|
+
**The matching hash does NOT catch this.** It covers economic parameters (baseCurrency, stages, auto-issuances) but NOT terminal configurations, accounting contexts, or sucker token mappings. Two deployments with identical matching hashes can have completely different terminal setups.
|
|
87
|
+
|
|
88
|
+
**On non-ETH chains:** Use ERC-20 WETH (e.g. `0xD221812de1BD094f35587EE8E174B07B6167D9Af` on Celo) or USDC as the terminal's accounting context.
|
|
89
|
+
|
|
90
|
+
**Correct (Celo):**
|
|
91
|
+
```solidity
|
|
92
|
+
JBAccountingContext({
|
|
93
|
+
token: WETH_CELO, // ERC-20 WETH, not native CELO
|
|
94
|
+
decimals: 18,
|
|
95
|
+
currency: uint32(uint160(WETH_CELO))
|
|
96
|
+
})
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Wrong (Celo):**
|
|
100
|
+
```solidity
|
|
101
|
+
JBAccountingContext({
|
|
102
|
+
token: JBConstants.NATIVE_TOKEN, // This is CELO, not ETH!
|
|
103
|
+
decimals: 18,
|
|
104
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
105
|
+
})
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
See also: `SECURITY.md` in this repo and INTEROP-6 in `AUDIT_FINDINGS.md`.
|
|
109
|
+
|
|
110
|
+
## Example Integration
|
|
111
|
+
|
|
112
|
+
```solidity
|
|
113
|
+
import {REVConfig} from "@rev-net/core-v6/src/structs/REVConfig.sol";
|
|
114
|
+
import {REVStageConfig} from "@rev-net/core-v6/src/structs/REVStageConfig.sol";
|
|
115
|
+
import {REVDescription} from "@rev-net/core-v6/src/structs/REVDescription.sol";
|
|
116
|
+
import {REVBuybackHookConfig} from "@rev-net/core-v6/src/structs/REVBuybackHookConfig.sol";
|
|
117
|
+
import {REVSuckerDeploymentConfig} from "@rev-net/core-v6/src/structs/REVSuckerDeploymentConfig.sol";
|
|
118
|
+
import {IREVDeployer} from "@rev-net/core-v6/src/interfaces/IREVDeployer.sol";
|
|
119
|
+
|
|
120
|
+
// Deploy a simple revnet with one stage.
|
|
121
|
+
function deployRevnet(IREVDeployer deployer, JBTerminalConfig[] memory terminals) external {
|
|
122
|
+
// Define the stage: 1M tokens per unit, 10% issuance cut every 30 days, 20% cash out tax.
|
|
123
|
+
REVStageConfig[] memory stages = new REVStageConfig[](1);
|
|
124
|
+
stages[0] = REVStageConfig({
|
|
125
|
+
startsAtOrAfter: 0, // Start immediately (uses block.timestamp).
|
|
126
|
+
autoIssuances: new REVAutoIssuance[](0),
|
|
127
|
+
splitPercent: 2000, // 20% of new tokens go to splits.
|
|
128
|
+
splits: splits, // Must have at least one split if splitPercent > 0.
|
|
129
|
+
initialIssuance: 1_000_000e18,
|
|
130
|
+
issuanceCutFrequency: 30 days,
|
|
131
|
+
issuanceCutPercent: 100_000_000, // 10% cut per period.
|
|
132
|
+
cashOutTaxRate: 2000, // 20% tax on cash outs.
|
|
133
|
+
extraMetadata: 0
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
REVConfig memory config = REVConfig({
|
|
137
|
+
description: REVDescription({
|
|
138
|
+
name: "My Revnet Token",
|
|
139
|
+
ticker: "MYREV",
|
|
140
|
+
uri: "ipfs://...",
|
|
141
|
+
salt: bytes32(0)
|
|
142
|
+
}),
|
|
143
|
+
baseCurrency: 1, // USD
|
|
144
|
+
splitOperator: msg.sender,
|
|
145
|
+
stageConfigurations: stages,
|
|
146
|
+
loanSources: new REVLoanSource[](0),
|
|
147
|
+
loans: address(0) // No loans contract.
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// revnetId 0 = deploy new.
|
|
151
|
+
deployer.deployFor({
|
|
152
|
+
revnetId: 0,
|
|
153
|
+
configuration: config,
|
|
154
|
+
terminalConfigurations: terminals,
|
|
155
|
+
buybackHookConfiguration: REVBuybackHookConfig({
|
|
156
|
+
dataHook: IJBRulesetDataHook(address(0)),
|
|
157
|
+
hookToConfigure: IJBBuybackHook(address(0)),
|
|
158
|
+
poolConfigurations: new REVBuybackPoolConfig[](0)
|
|
159
|
+
}),
|
|
160
|
+
suckerDeploymentConfiguration: REVSuckerDeploymentConfig({
|
|
161
|
+
deployerConfigurations: new JBSuckerDeployerConfig[](0),
|
|
162
|
+
salt: bytes32(0)
|
|
163
|
+
})
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
```
|