@rev-net/core-v6 0.0.12 → 0.0.14
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/AUDIT_INSTRUCTIONS.md +295 -0
- package/CHANGE_LOG.md +321 -0
- package/README.md +2 -2
- package/RISKS.md +180 -35
- package/SKILLS.md +1 -1
- package/USER_JOURNEYS.md +489 -0
- package/package.json +9 -9
- package/script/Deploy.s.sol +40 -6
- package/script/helpers/RevnetCoreDeploymentLib.sol +7 -1
- package/src/REVDeployer.sol +63 -47
- package/src/REVLoans.sol +51 -15
- package/src/interfaces/IREVDeployer.sol +0 -1
- package/src/structs/REV721TiersHookFlags.sol +1 -0
- package/src/structs/REVAutoIssuance.sol +1 -0
- package/src/structs/REVBaseline721HookConfig.sol +1 -0
- package/src/structs/REVConfig.sol +1 -0
- package/src/structs/REVCroptopAllowedPost.sol +1 -0
- package/src/structs/REVDeploy721TiersHookConfig.sol +1 -0
- package/src/structs/REVDescription.sol +1 -0
- package/src/structs/REVLoan.sol +1 -0
- package/src/structs/REVLoanSource.sol +1 -0
- package/src/structs/REVStageConfig.sol +1 -0
- package/src/structs/REVSuckerDeploymentConfig.sol +1 -0
- package/test/REV.integrations.t.sol +132 -12
- package/test/REVAutoIssuanceFuzz.t.sol +23 -3
- package/test/REVDeployerRegressions.t.sol +35 -4
- package/test/REVInvincibility.t.sol +58 -8
- package/test/REVInvincibilityHandler.sol +29 -0
- package/test/REVLifecycle.t.sol +28 -3
- package/test/REVLoans.invariants.t.sol +52 -5
- package/test/REVLoansAttacks.t.sol +43 -5
- package/test/REVLoansFeeRecovery.t.sol +50 -11
- package/test/REVLoansFindings.t.sol +27 -3
- package/test/REVLoansRegressions.t.sol +25 -3
- package/test/REVLoansSourceFeeRecovery.t.sol +491 -0
- package/test/REVLoansSourced.t.sol +56 -7
- package/test/REVLoansUnSourced.t.sol +49 -5
- package/test/TestBurnHeldTokens.t.sol +32 -5
- package/test/TestCEIPattern.t.sol +26 -2
- package/test/TestCashOutCallerValidation.t.sol +30 -4
- package/test/TestConversionDocumentation.t.sol +26 -5
- package/test/TestCrossCurrencyReclaim.t.sol +584 -0
- package/test/TestCrossSourceReallocation.t.sol +26 -2
- package/test/TestERC2771MetaTx.t.sol +557 -0
- package/test/TestEmptyBuybackSpecs.t.sol +23 -3
- package/test/TestFlashLoanSurplus.t.sol +28 -3
- package/test/TestHookArrayOOB.t.sol +24 -4
- package/test/TestLiquidationBehavior.t.sol +26 -3
- package/test/TestLoanSourceRotation.t.sol +525 -0
- package/test/TestLongTailEconomics.t.sol +651 -0
- package/test/TestLowFindings.t.sol +65 -2
- package/test/TestMixedFixes.t.sol +28 -3
- package/test/TestPermit2Signatures.t.sol +657 -0
- package/test/TestReallocationSandwich.t.sol +384 -0
- package/test/TestRevnetRegressions.t.sol +324 -0
- package/test/TestSplitWeightAdjustment.t.sol +24 -2
- package/test/TestSplitWeightE2E.t.sol +29 -2
- package/test/TestSplitWeightFork.t.sol +46 -7
- package/test/TestStageTransitionBorrowable.t.sol +24 -2
- package/test/TestSwapTerminalPermission.t.sol +23 -3
- package/test/TestUint112Overflow.t.sol +28 -2
- package/test/TestZeroRepayment.t.sol +26 -2
- package/test/fork/ForkTestBase.sol +46 -3
- package/test/fork/TestCashOutFork.t.sol +1 -1
- package/test/fork/TestLoanBorrowFork.t.sol +1 -0
- package/test/fork/TestLoanCrossRulesetFork.t.sol +3 -1
- package/test/fork/TestLoanLiquidationFork.t.sol +1 -0
- package/test/fork/TestLoanReallocateFork.t.sol +1 -0
- package/test/fork/TestLoanRepayFork.t.sol +1 -0
- package/test/fork/TestLoanTransferFork.t.sol +133 -0
- package/test/fork/TestSplitWeightFork.t.sol +3 -0
- package/test/helpers/REVEmpty721Config.sol +1 -0
- package/test/mock/MockBuybackDataHook.sol +1 -0
- package/test/regression/TestBurnPermissionRequired.t.sol +267 -0
- package/test/regression/TestCrossRevnetLiquidation.t.sol +228 -0
- package/test/regression/TestCumulativeLoanCounter.t.sol +27 -4
- package/test/regression/TestLiquidateGapHandling.t.sol +29 -4
- package/test/regression/TestZeroPriceFeed.t.sol +396 -0
- package/deployments/revnet-core-v5/arbitrum/REVDeployer.json +0 -2821
- package/deployments/revnet-core-v5/arbitrum/REVLoans.json +0 -2260
- package/deployments/revnet-core-v5/arbitrum_sepolia/REVDeployer.json +0 -2821
- package/deployments/revnet-core-v5/arbitrum_sepolia/REVLoans.json +0 -2260
- package/deployments/revnet-core-v5/base/REVDeployer.json +0 -2825
- package/deployments/revnet-core-v5/base/REVLoans.json +0 -2264
- package/deployments/revnet-core-v5/base_sepolia/REVDeployer.json +0 -2825
- package/deployments/revnet-core-v5/base_sepolia/REVLoans.json +0 -2264
- package/deployments/revnet-core-v5/ethereum/REVDeployer.json +0 -2825
- package/deployments/revnet-core-v5/ethereum/REVLoans.json +0 -2264
- package/deployments/revnet-core-v5/optimism/REVDeployer.json +0 -2821
- package/deployments/revnet-core-v5/optimism/REVLoans.json +0 -2260
- package/deployments/revnet-core-v5/optimism_sepolia/REVDeployer.json +0 -2825
- package/deployments/revnet-core-v5/optimism_sepolia/REVLoans.json +0 -2264
- package/deployments/revnet-core-v5/sepolia/REVDeployer.json +0 -2825
- package/deployments/revnet-core-v5/sepolia/REVLoans.json +0 -2264
- package/docs/book.css +0 -13
- package/docs/book.toml +0 -13
- package/docs/solidity.min.js +0 -74
- package/docs/src/README.md +0 -185
- package/docs/src/SUMMARY.md +0 -18
- package/docs/src/src/README.md +0 -7
- package/docs/src/src/REVDeployer.sol/contract.REVDeployer.md +0 -999
- package/docs/src/src/REVLoans.sol/contract.REVLoans.md +0 -1108
- package/docs/src/src/interfaces/IREVDeployer.sol/interface.IREVDeployer.md +0 -525
- package/docs/src/src/interfaces/IREVLoans.sol/interface.IREVLoans.md +0 -598
- package/docs/src/src/interfaces/README.md +0 -5
- package/docs/src/src/structs/README.md +0 -12
- package/docs/src/src/structs/REVAutoIssuance.sol/struct.REVAutoIssuance.md +0 -19
- package/docs/src/src/structs/REVBuybackHookConfig.sol/struct.REVBuybackHookConfig.md +0 -19
- package/docs/src/src/structs/REVBuybackPoolConfig.sol/struct.REVBuybackPoolConfig.md +0 -21
- package/docs/src/src/structs/REVConfig.sol/struct.REVConfig.md +0 -23
- package/docs/src/src/structs/REVCroptopAllowedPost.sol/struct.REVCroptopAllowedPost.md +0 -32
- package/docs/src/src/structs/REVDeploy721TiersHookConfig.sol/struct.REVDeploy721TiersHookConfig.md +0 -34
- package/docs/src/src/structs/REVDescription.sol/struct.REVDescription.md +0 -23
- package/docs/src/src/structs/REVLoan.sol/struct.REVLoan.md +0 -28
- package/docs/src/src/structs/REVLoanSource.sol/struct.REVLoanSource.md +0 -16
- package/docs/src/src/structs/REVStageConfig.sol/struct.REVStageConfig.md +0 -44
- package/docs/src/src/structs/REVSuckerDeploymentConfig.sol/struct.REVSuckerDeploymentConfig.md +0 -16
package/USER_JOURNEYS.md
ADDED
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
# User Journeys -- revnet-core-v6
|
|
2
|
+
|
|
3
|
+
Every user path through the Revnet + Loans system. For each journey: entry point, key parameters, state changes, events, and edge cases.
|
|
4
|
+
|
|
5
|
+
Read [SKILLS.md](./SKILLS.md) for the complete function reference. Read [ARCHITECTURE.md](./ARCHITECTURE.md) for the system overview.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. Deploy a New Revnet
|
|
10
|
+
|
|
11
|
+
**Entry point:** `REVDeployer.deployFor(revnetId=0, configuration, terminalConfigurations, suckerDeploymentConfiguration)` (4-arg version, deploys with default empty 721 hook)
|
|
12
|
+
|
|
13
|
+
Or: `REVDeployer.deployFor(revnetId=0, configuration, terminalConfigurations, suckerDeploymentConfiguration, tiered721HookConfiguration, allowedPosts)` (6-arg version, deploys with configured 721 tiers and optional croptop posts)
|
|
14
|
+
|
|
15
|
+
**Key parameters:**
|
|
16
|
+
|
|
17
|
+
| Parameter | Type | Description |
|
|
18
|
+
|-----------|------|-------------|
|
|
19
|
+
| `revnetId` | `uint256` | Set to 0 to deploy a new revnet. |
|
|
20
|
+
| `configuration.description` | `REVDescription` | `name`, `ticker`, `uri`, `salt` for the ERC-20 token. |
|
|
21
|
+
| `configuration.baseCurrency` | `uint32` | 1 = ETH, 2 = USD. Determines the denomination for issuance weights. |
|
|
22
|
+
| `configuration.splitOperator` | `address` | The address that will manage splits, 721 tiers, and suckers. |
|
|
23
|
+
| `configuration.stageConfigurations` | `REVStageConfig[]` | One or more stages defining the revnet's economics. |
|
|
24
|
+
| `terminalConfigurations` | `JBTerminalConfig[]` | Which terminals and tokens the revnet accepts. |
|
|
25
|
+
| `suckerDeploymentConfiguration` | `REVSuckerDeploymentConfig` | Cross-chain sucker config. Set `salt = bytes32(0)` to skip. |
|
|
26
|
+
|
|
27
|
+
**What happens (in order):**
|
|
28
|
+
|
|
29
|
+
1. `revnetId = PROJECTS.count() + 1` (next available ID)
|
|
30
|
+
2. `_makeRulesetConfigurations` converts stages to JBRulesetConfigs:
|
|
31
|
+
- Validates: at least one stage, `startsAtOrAfter` strictly increasing, `cashOutTaxRate < MAX`, splits required if `splitPercent > 0`
|
|
32
|
+
- Each stage becomes a ruleset with: duration = `issuanceCutFrequency`, weight = `initialIssuance`, weightCutPercent = `issuanceCutPercent`, data hook = REVDeployer address
|
|
33
|
+
- Fund access limits: unlimited surplus allowance per terminal/token (for loans)
|
|
34
|
+
- Encoded configuration hash computed from economic parameters
|
|
35
|
+
- Auto-issuance amounts stored: `amountToAutoIssue[revnetId][block.timestamp + i][beneficiary] += count`
|
|
36
|
+
3. `CONTROLLER.launchProjectFor` creates the Juicebox project, minting the ERC-721 to REVDeployer
|
|
37
|
+
4. Cash-out delay set if first stage's `startsAtOrAfter` has already passed (existing revnet deploying to new chain)
|
|
38
|
+
5. `CONTROLLER.deployERC20For` deploys the project's ERC-20 token
|
|
39
|
+
6. Buyback pools initialized for each terminal token via `_tryInitializeBuybackPoolFor` (try-catch, silent failure OK)
|
|
40
|
+
7. Suckers deployed if `suckerDeploymentConfiguration.salt != bytes32(0)`
|
|
41
|
+
8. Config hash stored: `hashedEncodedConfigurationOf[revnetId] = encodedConfigurationHash`
|
|
42
|
+
9. **4-arg version only:** Default empty 721 hook deployed, split operator gets all 721 permissions
|
|
43
|
+
10. **6-arg version only:** Configured 721 hook deployed with prevention flags applied, croptop posts configured if any
|
|
44
|
+
|
|
45
|
+
**Events:**
|
|
46
|
+
- `DeployRevnet(revnetId, configuration, terminalConfigurations, suckerDeploymentConfiguration, rulesetConfigurations, encodedConfigurationHash, caller)`
|
|
47
|
+
- `StoreAutoIssuanceAmount(revnetId, stageId, beneficiary, count, caller)` for each auto-issuance on this chain
|
|
48
|
+
- `DeploySuckers(...)` if suckers are deployed
|
|
49
|
+
- `SetCashOutDelay(revnetId, cashOutDelay, caller)` if applicable
|
|
50
|
+
|
|
51
|
+
**Edge cases:**
|
|
52
|
+
- If `startsAtOrAfter = 0` for the first stage, `block.timestamp` is used. The stage starts immediately.
|
|
53
|
+
- Auto-issuances with `chainId != block.chainid` are included in the config hash but not stored on this chain.
|
|
54
|
+
- Auto-issuances with `count = 0` are skipped (not stored, not included in config hash).
|
|
55
|
+
- Buyback pool initialization silently fails if the pool already exists.
|
|
56
|
+
- The `assert` on `launchProjectFor` return value catches project ID mismatches (should never happen).
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 2. Convert an Existing Juicebox Project to a Revnet
|
|
61
|
+
|
|
62
|
+
**Entry point:** `REVDeployer.deployFor(revnetId=<existingProjectId>, configuration, terminalConfigurations, suckerDeploymentConfiguration)`
|
|
63
|
+
|
|
64
|
+
**Prerequisites:**
|
|
65
|
+
- Caller must own the project's ERC-721 NFT
|
|
66
|
+
- Project must have no controller and no rulesets (blank project)
|
|
67
|
+
|
|
68
|
+
**What happens:**
|
|
69
|
+
|
|
70
|
+
1. `_msgSender()` must equal `PROJECTS.ownerOf(revnetId)` (owner check)
|
|
71
|
+
2. Project NFT transferred from owner to REVDeployer via `safeTransferFrom` -- **irreversible**
|
|
72
|
+
3. `CONTROLLER.launchRulesetsFor` initializes rulesets for the existing project
|
|
73
|
+
4. `CONTROLLER.setUriOf` sets the project's metadata URI
|
|
74
|
+
5. Cash-out delay applied if first stage has already started
|
|
75
|
+
6. Same remaining steps as new deployment (ERC-20, buyback pools, suckers, 721 hook)
|
|
76
|
+
|
|
77
|
+
**Edge cases:**
|
|
78
|
+
- This is a **one-way operation**. The project NFT is permanently locked in REVDeployer.
|
|
79
|
+
- `launchRulesetsFor` reverts if rulesets already exist. `setControllerOf` reverts if a controller is already set.
|
|
80
|
+
- Useful in deploy scripts where the project ID is needed before configuration (e.g., for cross-chain sucker peer mappings).
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## 3. Pay a Revnet
|
|
85
|
+
|
|
86
|
+
**Entry point:** `JBMultiTerminal.pay(projectId, token, amount, beneficiary, minReturnedTokens, memo, metadata)`
|
|
87
|
+
|
|
88
|
+
This is a standard Juicebox payment, but REVDeployer intervenes as the data hook.
|
|
89
|
+
|
|
90
|
+
**What happens:**
|
|
91
|
+
|
|
92
|
+
1. Terminal records payment in store
|
|
93
|
+
2. Store calls `REVDeployer.beforePayRecordedWith(context)`:
|
|
94
|
+
- Calls 721 hook's `beforePayRecordedWith` for split specs (tier purchases)
|
|
95
|
+
- Computes `projectAmount = context.amount.value - totalSplitAmount`
|
|
96
|
+
- Calls buyback hook's `beforePayRecordedWith` with reduced amount context
|
|
97
|
+
- Scales weight: `weight = mulDiv(weight, projectAmount, context.amount.value)` (or 0 if `projectAmount == 0`)
|
|
98
|
+
- Returns merged hook specs: [721 hook spec, buyback hook spec]
|
|
99
|
+
3. Store calculates token count using the modified weight
|
|
100
|
+
4. Terminal mints tokens via controller
|
|
101
|
+
5. Terminal executes hook specs:
|
|
102
|
+
- 721 hook processes tier purchases
|
|
103
|
+
- Buyback hook processes swap (if applicable)
|
|
104
|
+
|
|
105
|
+
**Edge cases:**
|
|
106
|
+
- If the buyback hook determines a DEX swap is better, weight = 0 and the buyback hook spec receives the full project amount. The buyback hook buys tokens on the DEX and mints them to the payer.
|
|
107
|
+
- If `totalSplitAmount >= context.amount.value`, `projectAmount = 0`, weight = 0, and no tokens are minted by the terminal. All funds go to 721 tier splits.
|
|
108
|
+
- If no 721 hook is set (`tiered721HookOf[revnetId] == address(0)`), only the buyback hook is consulted.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## 4. Cash Out from a Revnet
|
|
113
|
+
|
|
114
|
+
**Entry point:** `JBMultiTerminal.cashOutTokensOf(holder, projectId, tokenCount, token, minTokensReclaimed, beneficiary, metadata)`
|
|
115
|
+
|
|
116
|
+
**What happens:**
|
|
117
|
+
|
|
118
|
+
1. Terminal records cash-out in store
|
|
119
|
+
2. Store calls `REVDeployer.beforeCashOutRecordedWith(context)`:
|
|
120
|
+
- **If sucker:** Returns 0% tax, full cash-out count, no hooks (fee exempt)
|
|
121
|
+
- **If cash-out delay active:** Reverts with `REVDeployer_CashOutDelayNotFinished`
|
|
122
|
+
- **If no tax or no fee terminal:** Returns parameters unchanged
|
|
123
|
+
- **Otherwise:** Splits cash-out into fee portion (2.5%) and non-fee portion:
|
|
124
|
+
- `feeCashOutCount = mulDiv(cashOutCount, 25, 1000)`
|
|
125
|
+
- `nonFeeCashOutCount = cashOutCount - feeCashOutCount`
|
|
126
|
+
- Computes `postFeeReclaimedAmount` via bonding curve for non-fee tokens
|
|
127
|
+
- Computes `feeAmount` via bonding curve for fee tokens (on remaining surplus)
|
|
128
|
+
- Returns `nonFeeCashOutCount` as the adjusted cash-out count + hook spec for fee
|
|
129
|
+
3. Terminal burns ALL of the user's specified token count
|
|
130
|
+
4. Terminal transfers the reclaimed amount to the beneficiary
|
|
131
|
+
5. Terminal calls `REVDeployer.afterCashOutRecordedWith(context)`:
|
|
132
|
+
- Transfers fee amount from terminal to this contract
|
|
133
|
+
- Pays fee to fee revnet's terminal via `feeTerminal.pay`
|
|
134
|
+
- On failure: returns funds to the originating project via `addToBalanceOf`
|
|
135
|
+
|
|
136
|
+
**Edge cases:**
|
|
137
|
+
- Suckers bypass both the cash-out fee AND the cash-out delay. The `_isSuckerOf` check is the only gate.
|
|
138
|
+
- `cashOutTaxRate == 0` means no tax and no revnet fee (but the terminal's 2.5% protocol fee still applies).
|
|
139
|
+
- Micro cash-outs (< 40 wei at 2.5%) round `feeCashOutCount` to 0, bypassing the fee. Gas cost far exceeds the bypassed fee.
|
|
140
|
+
- The fee is paid to `FEE_REVNET_ID`, not `REV_ID`. These may be different projects.
|
|
141
|
+
- Cash-out fees stack: the terminal takes its 2.5% protocol fee, THEN the revnet takes its 2.5% fee from the remaining amount.
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## 5. Borrow Against Revnet Tokens (REVLoans)
|
|
146
|
+
|
|
147
|
+
**Entry point:** `REVLoans.borrowFrom(revnetId, source, minBorrowAmount, collateralCount, beneficiary, prepaidFeePercent)`
|
|
148
|
+
|
|
149
|
+
**Prerequisites:**
|
|
150
|
+
- Caller must hold `collateralCount` revnet ERC-20 tokens
|
|
151
|
+
- Caller must grant `BURN_TOKENS` permission to the REVLoans contract for the revnet's project ID via `JBPermissions.setPermissionsFor()`. Without this, the transaction reverts in `JBController.burnTokensOf` with `JBPermissioned_Unauthorized`.
|
|
152
|
+
|
|
153
|
+
**Key parameters:**
|
|
154
|
+
|
|
155
|
+
| Parameter | Type | Description |
|
|
156
|
+
|-----------|------|-------------|
|
|
157
|
+
| `revnetId` | `uint256` | The revnet to borrow from. |
|
|
158
|
+
| `source` | `REVLoanSource` | `{token, terminal}` -- which terminal and token to borrow. |
|
|
159
|
+
| `minBorrowAmount` | `uint256` | Slippage protection -- revert if you'd get less. |
|
|
160
|
+
| `collateralCount` | `uint256` | Number of revnet tokens to burn as collateral. |
|
|
161
|
+
| `beneficiary` | `address payable` | Receives the borrowed funds and fee payment tokens. |
|
|
162
|
+
| `prepaidFeePercent` | `uint256` | 25-500 (2.5%-50% of MAX_FEE=1000). Higher = longer interest-free window. |
|
|
163
|
+
|
|
164
|
+
**What happens:**
|
|
165
|
+
|
|
166
|
+
1. **Validation:**
|
|
167
|
+
- `collateralCount > 0` (no zero-collateral loans)
|
|
168
|
+
- `source.terminal` is registered for the revnet in the directory
|
|
169
|
+
- `prepaidFeePercent` in range [25, 500]
|
|
170
|
+
2. **Loan ID generation:** `revnetId * 1_000_000_000_000 + (++totalLoansBorrowedFor[revnetId])`
|
|
171
|
+
3. **Loan creation in storage:**
|
|
172
|
+
- `source`, `createdAt = block.timestamp`, `prepaidFeePercent`, `prepaidDuration = mulDiv(prepaidFeePercent, 3650 days, 500)`
|
|
173
|
+
4. **Borrow amount calculation:**
|
|
174
|
+
- `totalSurplus` from all terminals (aggregated via `JBSurplus.currentSurplusOf`)
|
|
175
|
+
- `totalBorrowed` from all loan sources (aggregated via `_totalBorrowedFrom`)
|
|
176
|
+
- `borrowAmount = JBCashOuts.cashOutFrom(surplus + borrowed, collateral, supply + totalCollateral, cashOutTaxRate)`
|
|
177
|
+
5. **Validation:** `borrowAmount > 0`, `borrowAmount >= minBorrowAmount`
|
|
178
|
+
6. **Source fee:** `JBFees.feeAmountFrom(borrowAmount, prepaidFeePercent)`
|
|
179
|
+
7. **`_adjust` executes:**
|
|
180
|
+
- Writes `loan.amount = borrowAmount` and `loan.collateral = collateralCount` to storage (CEI)
|
|
181
|
+
- `_addTo`:
|
|
182
|
+
- Registers the source if first time
|
|
183
|
+
- Increments `totalBorrowedFrom`
|
|
184
|
+
- Calls `terminal.useAllowanceOf` to pull funds (incurs 2.5% protocol fee automatically)
|
|
185
|
+
- Pays REV fee (1%) to `REV_ID` via `feeTerminal.pay` (try-catch; zeroed on failure)
|
|
186
|
+
- Transfers remaining: `netAmountPaidOut - revFeeAmount - sourceFeeAmount` to beneficiary
|
|
187
|
+
- `_addCollateralTo`: increments `totalCollateralOf`, burns collateral via `CONTROLLER.burnTokensOf`
|
|
188
|
+
- Pays source fee to revnet via `terminal.pay` (NOT try-catch -- reverts on failure)
|
|
189
|
+
8. **Mint loan ERC-721** to `_msgSender()`
|
|
190
|
+
|
|
191
|
+
**Events:** `Borrow(loanId, revnetId, loan, source, borrowAmount, collateralCount, sourceFeeAmount, beneficiary, caller)`
|
|
192
|
+
|
|
193
|
+
**Edge cases:**
|
|
194
|
+
- Revnets always deploy an ERC-20 at creation, so collateral is always ERC-20 tokens (never credits).
|
|
195
|
+
- The `minBorrowAmount` check is against the raw bonding curve output, BEFORE fees are deducted. The actual amount received is less.
|
|
196
|
+
- `prepaidDuration` at minimum (25): `25 * 3650 days / 500 = 182.5 days`. At maximum (500): `500 * 3650 days / 500 = 3650 days`.
|
|
197
|
+
- The REV fee payment failure is non-fatal (borrower gets the fee amount instead). The source fee payment failure IS fatal (entire transaction reverts).
|
|
198
|
+
- Loan NFT is minted to `_msgSender()`, not `beneficiary`. The caller owns the loan; the beneficiary receives the funds.
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## 6. Repay a Loan
|
|
203
|
+
|
|
204
|
+
**Entry point:** `REVLoans.repayLoan(loanId, maxRepayBorrowAmount, collateralCountToReturn, beneficiary, allowance)`
|
|
205
|
+
|
|
206
|
+
**Key parameters:**
|
|
207
|
+
|
|
208
|
+
| Parameter | Type | Description |
|
|
209
|
+
|-----------|------|-------------|
|
|
210
|
+
| `loanId` | `uint256` | The loan to repay (from the ERC-721). |
|
|
211
|
+
| `maxRepayBorrowAmount` | `uint256` | Maximum amount willing to pay. Use `type(uint256).max` for "whatever it costs." |
|
|
212
|
+
| `collateralCountToReturn` | `uint256` | How many collateral tokens to get back (up to `loan.collateral`). |
|
|
213
|
+
| `beneficiary` | `address payable` | Receives the re-minted collateral tokens and fee payment tokens. |
|
|
214
|
+
| `allowance` | `JBSingleAllowance` | Optional permit2 data. Set `amount = 0` to skip. |
|
|
215
|
+
|
|
216
|
+
**What happens:**
|
|
217
|
+
|
|
218
|
+
1. **Authorization:** `_ownerOf(loanId) == _msgSender()` (only loan NFT owner can repay)
|
|
219
|
+
2. **Collateral check:** `collateralCountToReturn <= loan.collateral`
|
|
220
|
+
3. **Calculate new borrow amount** for remaining collateral via bonding curve:
|
|
221
|
+
- `newBorrowAmount = _borrowAmountFrom(loan, revnetId, loan.collateral - collateralCountToReturn)`
|
|
222
|
+
- Verify `newBorrowAmount <= loan.amount` (collateral value hasn't increased enough to over-collateralize)
|
|
223
|
+
- `repayBorrowAmount = loan.amount - newBorrowAmount`
|
|
224
|
+
4. **Nothing-to-do check:** Reverts if `repayBorrowAmount == 0 && collateralCountToReturn == 0`
|
|
225
|
+
5. **Source fee calculation:**
|
|
226
|
+
- If within prepaid window (`timeSinceCreated <= prepaidDuration`): fee = 0
|
|
227
|
+
- If expired (`timeSinceCreated >= LOAN_LIQUIDATION_DURATION`): revert `REVLoans_LoanExpired`
|
|
228
|
+
- Otherwise: linear fee based on time elapsed beyond prepaid window
|
|
229
|
+
- `repayBorrowAmount += sourceFeeAmount` (fee added to repayment)
|
|
230
|
+
6. **Accept funds:** `_acceptFundsFor` handles native token (uses `msg.value`) or ERC-20 (with optional permit2)
|
|
231
|
+
7. **Max repay check:** `repayBorrowAmount <= maxRepayBorrowAmount`
|
|
232
|
+
8. **Execute repay** via `_repayLoan`:
|
|
233
|
+
- **Full repay** (`collateralCountToReturn == loan.collateral`): burns original NFT, calls `_adjust` with amounts zeroed, deletes loan, returns same loan ID
|
|
234
|
+
- **Partial repay:** burns original NFT, creates new loan with reduced amount/collateral, copies `createdAt`/`prepaidFeePercent`/`prepaidDuration` from original, mints new NFT
|
|
235
|
+
9. **Refund excess:** If `maxRepayBorrowAmount > repayBorrowAmount`, transfers the difference back to `_msgSender()`
|
|
236
|
+
|
|
237
|
+
**Events:** `RepayLoan(loanId, revnetId, paidOffLoanId, loan, paidOffLoan, repayBorrowAmount, sourceFeeAmount, collateralCountToReturn, beneficiary, caller)`
|
|
238
|
+
|
|
239
|
+
**Edge cases:**
|
|
240
|
+
- The source fee on repay is time-proportional. At the boundary of the prepaid window, it jumps from 0 to a small amount.
|
|
241
|
+
- Partial repay creates a new loan NFT with a new loan ID but preserves `createdAt` and `prepaidFeePercent`. The prepaid window clock doesn't reset.
|
|
242
|
+
- If the collateral has increased in value since borrowing (surplus grew), `newBorrowAmount > loan.amount` triggers `REVLoans_NewBorrowAmountGreaterThanLoanAmount`. The borrower should use `reallocateCollateralFromLoan` instead.
|
|
243
|
+
- For ERC-20 repayments, the contract tries standard `transferFrom` first. If allowance is insufficient, it falls through to permit2.
|
|
244
|
+
- `msg.value` is used directly for native token repayments (overrides `maxRepayBorrowAmount`).
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## 7. Get Liquidated
|
|
249
|
+
|
|
250
|
+
**Entry point:** `REVLoans.liquidateExpiredLoansFrom(revnetId, startingLoanId, count)`
|
|
251
|
+
|
|
252
|
+
**Permissionless.** Anyone can call this to clean up expired loans.
|
|
253
|
+
|
|
254
|
+
**Key parameters:**
|
|
255
|
+
|
|
256
|
+
| Parameter | Type | Description |
|
|
257
|
+
|-----------|------|-------------|
|
|
258
|
+
| `revnetId` | `uint256` | The revnet to liquidate loans from. |
|
|
259
|
+
| `startingLoanId` | `uint256` | The LOAN NUMBER (not full loan ID) to start iterating from. |
|
|
260
|
+
| `count` | `uint256` | How many loan numbers to iterate over. |
|
|
261
|
+
|
|
262
|
+
**What happens (per loan in range):**
|
|
263
|
+
|
|
264
|
+
1. Construct full loan ID: `revnetId * 1_000_000_000_000 + (startingLoanId + i)`
|
|
265
|
+
2. Read loan from storage. If `createdAt == 0`, skip (already repaid or liquidated).
|
|
266
|
+
3. Check ownership. If `_ownerOf(loanId) == address(0)`, skip (already burned).
|
|
267
|
+
4. Check expiry: `block.timestamp > loan.createdAt + LOAN_LIQUIDATION_DURATION` (strictly greater than)
|
|
268
|
+
5. Burn the loan NFT via `_burn(loanId)`
|
|
269
|
+
6. Delete loan data: `delete _loanOf[loanId]`
|
|
270
|
+
7. Decrement `totalCollateralOf[revnetId]` by `loan.collateral`
|
|
271
|
+
8. Decrement `totalBorrowedFrom[revnetId][terminal][token]` by `loan.amount`
|
|
272
|
+
|
|
273
|
+
**Events:** `Liquidate(loanId, revnetId, loan, caller)` for each liquidated loan
|
|
274
|
+
|
|
275
|
+
**Edge cases:**
|
|
276
|
+
- The collateral was burned when the loan was created. There is nothing to "seize" -- liquidation is purely bookkeeping cleanup.
|
|
277
|
+
- The borrower retains whatever funds they borrowed. The burned collateral tokens are permanently lost.
|
|
278
|
+
- `startingLoanId` is the loan NUMBER within the revnet, not the full loan ID. The function constructs full IDs internally.
|
|
279
|
+
- Gaps in the loan ID sequence (from repaid or already-liquidated loans) are skipped via the `createdAt == 0` check.
|
|
280
|
+
- Gas cost scales linearly with `count`. Choose parameters carefully to avoid iterating over many empty slots.
|
|
281
|
+
- The `>` comparison means a loan is liquidatable starting at `createdAt + LOAN_LIQUIDATION_DURATION + 1` second.
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## 8. Reallocate Collateral Between Loans
|
|
286
|
+
|
|
287
|
+
**Entry point:** `REVLoans.reallocateCollateralFromLoan(loanId, collateralCountToTransfer, source, minBorrowAmount, collateralCountToAdd, beneficiary, prepaidFeePercent)`
|
|
288
|
+
|
|
289
|
+
**Purpose:** If a loan's collateral has appreciated (surplus grew, or tax rate decreased), extract excess collateral and use it to open a new loan.
|
|
290
|
+
|
|
291
|
+
**Key parameters:**
|
|
292
|
+
|
|
293
|
+
| Parameter | Type | Description |
|
|
294
|
+
|-----------|------|-------------|
|
|
295
|
+
| `loanId` | `uint256` | The existing loan to take collateral from. |
|
|
296
|
+
| `collateralCountToTransfer` | `uint256` | Tokens to move from existing loan to new loan. |
|
|
297
|
+
| `source` | `REVLoanSource` | Must match the existing loan's source (same terminal + token). |
|
|
298
|
+
| `minBorrowAmount` | `uint256` | Slippage protection for the new loan. |
|
|
299
|
+
| `collateralCountToAdd` | `uint256` | Additional fresh tokens to add to the new loan (from caller's balance). |
|
|
300
|
+
| `beneficiary` | `address payable` | Receives proceeds from the new loan. |
|
|
301
|
+
| `prepaidFeePercent` | `uint256` | For the new loan (25-500). |
|
|
302
|
+
|
|
303
|
+
**What happens:**
|
|
304
|
+
|
|
305
|
+
1. **Authorization:** `_ownerOf(loanId) == _msgSender()`
|
|
306
|
+
2. **Source match:** New loan source must match existing loan source (prevents cross-source value extraction)
|
|
307
|
+
3. **`_reallocateCollateralFromLoan`:**
|
|
308
|
+
- Burns original loan NFT
|
|
309
|
+
- Validates `collateralCountToTransfer <= loan.collateral`
|
|
310
|
+
- Computes `newCollateralCount = loan.collateral - collateralCountToTransfer`
|
|
311
|
+
- Computes `borrowAmount = _borrowAmountFrom(loan, revnetId, newCollateralCount)`
|
|
312
|
+
- Validates `borrowAmount >= loan.amount` (remaining collateral must still cover the original loan amount)
|
|
313
|
+
- Creates replacement loan with original values
|
|
314
|
+
- Calls `_adjust` to reduce collateral (returns excess tokens to caller via `_returnCollateralFrom`)
|
|
315
|
+
- Mints replacement loan NFT to caller
|
|
316
|
+
- Deletes original loan data
|
|
317
|
+
4. **`borrowFrom`:** Opens a new loan with `collateralCountToTransfer + collateralCountToAdd` as collateral
|
|
318
|
+
- The `collateralCountToTransfer` tokens were just re-minted to the caller in step 3
|
|
319
|
+
- The `collateralCountToAdd` tokens come from the caller's existing balance
|
|
320
|
+
- Both are burned as collateral for the new loan
|
|
321
|
+
|
|
322
|
+
**Events:**
|
|
323
|
+
- `ReallocateCollateral(loanId, revnetId, reallocatedLoanId, reallocatedLoan, removedCollateralCount, caller)`
|
|
324
|
+
- `Borrow(newLoanId, revnetId, ...)` from the `borrowFrom` call
|
|
325
|
+
|
|
326
|
+
**Edge cases:**
|
|
327
|
+
- This function is NOT payable. Any ETH sent with the call is rejected at the EVM level.
|
|
328
|
+
- The original loan's `createdAt` and `prepaidFeePercent` are preserved on the replacement loan. The new loan gets fresh values.
|
|
329
|
+
- If `collateralCountToTransfer == loan.collateral`, the replacement loan has 0 collateral and must have `borrowAmount >= loan.amount` (which requires the bonding curve to return 0 for 0 collateral, which it does). But `borrowAmount = 0 < loan.amount` would revert. So you can't transfer ALL collateral -- some must remain.
|
|
330
|
+
- Between `_reallocateCollateralFromLoan` and `borrowFrom`, collateral tokens are minted to the caller and then immediately burned. If the caller is a contract with a `receive` function that re-enters, verify this is safe.
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
## 9. Claim Auto-Issuance
|
|
335
|
+
|
|
336
|
+
**Entry point:** `REVDeployer.autoIssueFor(revnetId, stageId, beneficiary)`
|
|
337
|
+
|
|
338
|
+
**Permissionless.** Anyone can call this on behalf of any beneficiary.
|
|
339
|
+
|
|
340
|
+
**Key parameters:**
|
|
341
|
+
|
|
342
|
+
| Parameter | Type | Description |
|
|
343
|
+
|-----------|------|-------------|
|
|
344
|
+
| `revnetId` | `uint256` | The revnet to claim from. |
|
|
345
|
+
| `stageId` | `uint256` | The stage ID (= `block.timestamp + stageIndex` from deployment). |
|
|
346
|
+
| `beneficiary` | `address` | The address to receive the tokens. |
|
|
347
|
+
|
|
348
|
+
**What happens:**
|
|
349
|
+
|
|
350
|
+
1. `CONTROLLER.getRulesetOf(revnetId, stageId)` retrieves the ruleset
|
|
351
|
+
2. Validates `ruleset.start <= block.timestamp` (stage has started)
|
|
352
|
+
3. Reads `count = amountToAutoIssue[revnetId][stageId][beneficiary]`
|
|
353
|
+
4. Validates `count > 0`
|
|
354
|
+
5. **Zeroes the amount BEFORE minting** (CEI pattern): `amountToAutoIssue[...] = 0`
|
|
355
|
+
6. `CONTROLLER.mintTokensOf(revnetId, count, beneficiary, "", useReservedPercent=false)`
|
|
356
|
+
|
|
357
|
+
**Events:** `AutoIssue(revnetId, stageId, beneficiary, count, caller)`
|
|
358
|
+
|
|
359
|
+
**Edge cases:**
|
|
360
|
+
- `useReservedPercent = false` means the FULL `count` goes to the beneficiary. No split percent is applied.
|
|
361
|
+
- Auto-issuance is one-time per (revnetId, stageId, beneficiary). Once claimed, `amountToAutoIssue` is zero and calling again reverts with `REVDeployer_NothingToAutoIssue`.
|
|
362
|
+
- The `stageId` must exactly match the value stored during deployment. If the deployment timestamp assumption doesn't hold (see RISKS.md S-3), the stage ID may be wrong and the claim will fail.
|
|
363
|
+
- Auto-issuances for other chains (where `chainId != block.chainid`) are not stored on this chain and cannot be claimed here.
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
## 10. Burn Held Tokens
|
|
368
|
+
|
|
369
|
+
**Entry point:** `REVDeployer.burnHeldTokensOf(revnetId)`
|
|
370
|
+
|
|
371
|
+
**Permissionless.** Anyone can call this.
|
|
372
|
+
|
|
373
|
+
**Purpose:** When reserved token splits don't sum to 100%, the remainder goes to the project owner (REVDeployer). This function burns those tokens to prevent them from sitting idle.
|
|
374
|
+
|
|
375
|
+
**What happens:**
|
|
376
|
+
|
|
377
|
+
1. Reads REVDeployer's token balance for the revnet
|
|
378
|
+
2. Reverts with `REVDeployer_NothingToBurn` if balance is 0
|
|
379
|
+
3. Burns all held tokens via `CONTROLLER.burnTokensOf`
|
|
380
|
+
|
|
381
|
+
**Events:** `BurnHeldTokens(revnetId, balance, caller)`
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
## 11. Change Split Operator
|
|
386
|
+
|
|
387
|
+
**Entry point:** `REVDeployer.setSplitOperatorOf(revnetId, newSplitOperator)`
|
|
388
|
+
|
|
389
|
+
**Authorization:** Only the current split operator can call this.
|
|
390
|
+
|
|
391
|
+
**What happens:**
|
|
392
|
+
|
|
393
|
+
1. `_checkIfIsSplitOperatorOf(revnetId, _msgSender())` verifies caller is current operator
|
|
394
|
+
2. Revokes all permissions from old operator: `_setPermissionsFor(this, _msgSender(), revnetId, empty)`
|
|
395
|
+
3. Grants all split operator permissions to new operator: `_setSplitOperatorOf(revnetId, newSplitOperator)`
|
|
396
|
+
|
|
397
|
+
**Default permissions (9):** SET_SPLIT_GROUPS, SET_BUYBACK_POOL, SET_BUYBACK_TWAP, SET_PROJECT_URI, ADD_PRICE_FEED, SUCKER_SAFETY, SET_BUYBACK_HOOK, SET_ROUTER_TERMINAL, SET_TOKEN_METADATA
|
|
398
|
+
|
|
399
|
+
**Additional permissions (if not prevented by 721 hook deployment flags):** ADJUST_721_TIERS, SET_721_METADATA, MINT_721, SET_721_DISCOUNT_PERCENT
|
|
400
|
+
|
|
401
|
+
**Events:** `ReplaceSplitOperator(revnetId, newSplitOperator, caller)`
|
|
402
|
+
|
|
403
|
+
**Edge cases:**
|
|
404
|
+
- The split operator is singular. There can only be one at a time.
|
|
405
|
+
- Setting `newSplitOperator = address(0)` effectively abandons the operator role. Nobody can change splits after that.
|
|
406
|
+
- The old operator's permissions are fully revoked (set to empty array), not just the split-specific ones.
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
## 12. Deploy Suckers for an Existing Revnet
|
|
411
|
+
|
|
412
|
+
**Entry point:** `REVDeployer.deploySuckersFor(revnetId, suckerDeploymentConfiguration)`
|
|
413
|
+
|
|
414
|
+
**Authorization:** Only the split operator can call this. The current stage must allow sucker deployment (bit 2 of `extraMetadata`).
|
|
415
|
+
|
|
416
|
+
**What happens:**
|
|
417
|
+
|
|
418
|
+
1. `_checkIfIsSplitOperatorOf(revnetId, _msgSender())`
|
|
419
|
+
2. Reads current ruleset metadata
|
|
420
|
+
3. Checks `(metadata.metadata >> 2) & 1 == 1` (third bit = allow suckers)
|
|
421
|
+
4. Deploys suckers via `SUCKER_REGISTRY.deploySuckersFor` using stored config hash
|
|
422
|
+
|
|
423
|
+
**Events:** `DeploySuckers(revnetId, encodedConfigurationHash, suckerDeploymentConfiguration, caller)`
|
|
424
|
+
|
|
425
|
+
**Edge cases:**
|
|
426
|
+
- The `extraMetadata` bit check means sucker deployment can be disabled for specific stages. If the current stage doesn't allow it, the transaction reverts.
|
|
427
|
+
- The `encodedConfigurationHash` used for the salt comes from the stored hash, not a newly computed one. This ensures cross-chain consistency.
|
|
428
|
+
|
|
429
|
+
---
|
|
430
|
+
|
|
431
|
+
## 13. Stage Transitions (Automatic)
|
|
432
|
+
|
|
433
|
+
**No entry point.** Stage transitions happen automatically via the Juicebox rulesets system.
|
|
434
|
+
|
|
435
|
+
**How it works:**
|
|
436
|
+
|
|
437
|
+
Each stage is a JBRuleset with:
|
|
438
|
+
- `duration = issuanceCutFrequency` (e.g., 30 days)
|
|
439
|
+
- `weight = initialIssuance`
|
|
440
|
+
- `weightCutPercent = issuanceCutPercent`
|
|
441
|
+
- `mustStartAtOrAfter = startsAtOrAfter`
|
|
442
|
+
|
|
443
|
+
When a stage's duration expires, it either:
|
|
444
|
+
1. **Cycles:** If no later stage is ready to start, the current stage repeats with decayed weight (`weight *= (1 - weightCutPercent/1e9)`)
|
|
445
|
+
2. **Transitions:** If the next stage's `startsAtOrAfter` has been reached, the next stage activates with its own `initialIssuance`
|
|
446
|
+
|
|
447
|
+
**Impact on active loans:**
|
|
448
|
+
|
|
449
|
+
When a stage transition changes `cashOutTaxRate`:
|
|
450
|
+
- `_borrowableAmountFrom` uses the CURRENT stage's `cashOutTaxRate`
|
|
451
|
+
- A higher tax rate reduces borrowable amount per unit of collateral
|
|
452
|
+
- A lower tax rate increases borrowable amount per unit of collateral
|
|
453
|
+
- Existing loans are NOT automatically adjusted -- they retain their original `amount`
|
|
454
|
+
- A loan can become under-collateralized if the new tax rate is higher (collateral's cash-out value drops below `loan.amount`)
|
|
455
|
+
- The protocol has no mechanism to force repayment -- only the 10-year expiry applies
|
|
456
|
+
|
|
457
|
+
**Impact on payments:**
|
|
458
|
+
- New payments use the new stage's issuance rate
|
|
459
|
+
- The buyback hook compares DEX price vs. new issuance rate for swap-vs-mint decisions
|
|
460
|
+
|
|
461
|
+
**Impact on cash-outs:**
|
|
462
|
+
- The bonding curve uses the new stage's `cashOutTaxRate`
|
|
463
|
+
- If the new stage has a higher tax rate, cash-outs return less
|
|
464
|
+
- If the new stage has a lower tax rate, cash-outs return more
|
|
465
|
+
|
|
466
|
+
**Edge cases:**
|
|
467
|
+
- If `issuanceCutFrequency = 0` (no duration), the stage never expires. It must be replaced by a later stage's `startsAtOrAfter`.
|
|
468
|
+
- Weight decay across many cycles (20,000+) requires progressive cache updates via `updateRulesetWeightCache()`. Without caching, operations revert with `WeightCacheRequired`.
|
|
469
|
+
- The issuance cut is applied per cycle. After N cycles: `weight = initialIssuance * (1 - issuanceCutPercent/1e9)^N`.
|
|
470
|
+
|
|
471
|
+
---
|
|
472
|
+
|
|
473
|
+
## Summary: Entry Points by Actor
|
|
474
|
+
|
|
475
|
+
| Actor | Function | Authorization |
|
|
476
|
+
|-------|----------|---------------|
|
|
477
|
+
| Anyone | `REVDeployer.deployFor(0, ...)` | None (deploys new revnet) |
|
|
478
|
+
| Project owner | `REVDeployer.deployFor(existingId, ...)` | Must own project NFT |
|
|
479
|
+
| Anyone | `JBMultiTerminal.pay(...)` | None |
|
|
480
|
+
| Token holder | `JBMultiTerminal.cashOutTokensOf(...)` | Must hold tokens |
|
|
481
|
+
| Token holder | `REVLoans.borrowFrom(...)` | Must hold tokens + grant `BURN_TOKENS` permission to REVLoans |
|
|
482
|
+
| Loan owner | `REVLoans.repayLoan(...)` | Must own loan NFT |
|
|
483
|
+
| Loan owner | `REVLoans.reallocateCollateralFromLoan(...)` | Must own loan NFT |
|
|
484
|
+
| Anyone | `REVLoans.liquidateExpiredLoansFrom(...)` | None (permissionless after 10 years) |
|
|
485
|
+
| Anyone | `REVDeployer.autoIssueFor(...)` | None (permissionless after stage starts) |
|
|
486
|
+
| Anyone | `REVDeployer.burnHeldTokensOf(...)` | None |
|
|
487
|
+
| Split operator | `REVDeployer.setSplitOperatorOf(...)` | Must be current split operator |
|
|
488
|
+
| Split operator | `REVDeployer.deploySuckersFor(...)` | Must be current split operator + stage allows it |
|
|
489
|
+
| Contract owner | `REVLoans.setTokenUriResolver(...)` | Must be Ownable owner of REVLoans |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rev-net/core-v6",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.14",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -20,14 +20,14 @@
|
|
|
20
20
|
"artifacts": "source ./.env && npx sphinx artifacts --org-id 'ea165b21-7cdc-4d7b-be59-ecdd4c26bee4' --project-name 'revnet-core-v6'"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@bananapus/721-hook-v6": "^0.0.
|
|
24
|
-
"@bananapus/buyback-hook-v6": "^0.0.
|
|
25
|
-
"@bananapus/core-v6": "^0.0.
|
|
26
|
-
"@bananapus/ownable-v6": "^0.0.
|
|
27
|
-
"@bananapus/permission-ids-v6": "^0.0.
|
|
28
|
-
"@bananapus/router-terminal-v6": "^0.0.
|
|
29
|
-
"@bananapus/suckers-v6": "^0.0.
|
|
30
|
-
"@croptop/core-v6": "^0.0.
|
|
23
|
+
"@bananapus/721-hook-v6": "^0.0.17",
|
|
24
|
+
"@bananapus/buyback-hook-v6": "^0.0.13",
|
|
25
|
+
"@bananapus/core-v6": "^0.0.17",
|
|
26
|
+
"@bananapus/ownable-v6": "^0.0.10",
|
|
27
|
+
"@bananapus/permission-ids-v6": "^0.0.10",
|
|
28
|
+
"@bananapus/router-terminal-v6": "^0.0.13",
|
|
29
|
+
"@bananapus/suckers-v6": "^0.0.11",
|
|
30
|
+
"@croptop/core-v6": "^0.0.18",
|
|
31
31
|
"@openzeppelin/contracts": "^5.6.1",
|
|
32
32
|
"@uniswap/v4-core": "^1.0.2",
|
|
33
33
|
"@uniswap/v4-periphery": "^1.0.3"
|
package/script/Deploy.s.sol
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
pragma solidity 0.8.26;
|
|
3
3
|
|
|
4
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
4
5
|
import "@bananapus/721-hook-v6/script/helpers/Hook721DeploymentLib.sol";
|
|
6
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
5
7
|
import "@bananapus/buyback-hook-v6/script/helpers/BuybackDeploymentLib.sol";
|
|
8
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
6
9
|
import "@bananapus/core-v6/script/helpers/CoreDeploymentLib.sol";
|
|
10
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
7
11
|
import "@bananapus/suckers-v6/script/helpers/SuckerDeploymentLib.sol";
|
|
12
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
8
13
|
import "@bananapus/router-terminal-v6/script/helpers/RouterTerminalDeploymentLib.sol";
|
|
14
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
9
15
|
import "@croptop/core-v6/script/helpers/CroptopDeploymentLib.sol";
|
|
10
16
|
|
|
11
17
|
import {Sphinx} from "@sphinx-labs/contracts/contracts/foundry/SphinxPlugin.sol";
|
|
@@ -20,7 +26,6 @@ import {JBTokenMapping} from "@bananapus/suckers-v6/src/structs/JBTokenMapping.s
|
|
|
20
26
|
import {IPermit2} from "@uniswap/permit2/src/interfaces/IPermit2.sol";
|
|
21
27
|
import {IJBSplitHook} from "@bananapus/core-v6/src/interfaces/IJBSplitHook.sol";
|
|
22
28
|
import {IJBTerminal} from "@bananapus/core-v6/src/interfaces/IJBTerminal.sol";
|
|
23
|
-
import {IJBRulesetDataHook} from "@bananapus/core-v6/src/interfaces/IJBRulesetDataHook.sol";
|
|
24
29
|
import {IJBBuybackHookRegistry} from "@bananapus/buyback-hook-v6/src/interfaces/IJBBuybackHookRegistry.sol";
|
|
25
30
|
import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
|
|
26
31
|
|
|
@@ -30,11 +35,10 @@ import {REVConfig} from "../src/structs/REVConfig.sol";
|
|
|
30
35
|
import {REVDescription} from "../src/structs/REVDescription.sol";
|
|
31
36
|
import {REVStageConfig} from "../src/structs/REVStageConfig.sol";
|
|
32
37
|
import {REVSuckerDeploymentConfig} from "../src/structs/REVSuckerDeploymentConfig.sol";
|
|
33
|
-
import {REVLoans
|
|
38
|
+
import {REVLoans} from "./../src/REVLoans.sol";
|
|
34
39
|
import {REVDeploy721TiersHookConfig} from "../src/structs/REVDeploy721TiersHookConfig.sol";
|
|
35
40
|
import {REVCroptopAllowedPost} from "../src/structs/REVCroptopAllowedPost.sol";
|
|
36
41
|
import {IJB721TokenUriResolver} from "@bananapus/721-hook-v6/src/interfaces/IJB721TokenUriResolver.sol";
|
|
37
|
-
import {IJBPrices} from "@bananapus/core-v6/src/interfaces/IJBPrices.sol";
|
|
38
42
|
import {JB721InitTiersConfig} from "@bananapus/721-hook-v6/src/structs/JB721InitTiersConfig.sol";
|
|
39
43
|
import {JB721TierConfig} from "@bananapus/721-hook-v6/src/structs/JB721TierConfig.sol";
|
|
40
44
|
import {REVBaseline721HookConfig} from "../src/structs/REVBaseline721HookConfig.sol";
|
|
@@ -62,26 +66,47 @@ contract DeployScript is Script, Sphinx {
|
|
|
62
66
|
/// @notice tracks the deployment of the router terminal.
|
|
63
67
|
RouterTerminalDeployment routerTerminal;
|
|
64
68
|
|
|
69
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
65
70
|
uint32 PREMINT_CHAIN_ID = 1;
|
|
71
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
66
72
|
string NAME = "Revnet";
|
|
73
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
67
74
|
string SYMBOL = "REV";
|
|
75
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
68
76
|
string PROJECT_URI = "ipfs://QmcCBD5fM927LjkLDSJWtNEU9FohcbiPSfqtGRHXFHzJ4W";
|
|
77
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
69
78
|
uint32 NATIVE_CURRENCY = uint32(uint160(JBConstants.NATIVE_TOKEN));
|
|
79
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
70
80
|
uint32 ETH_CURRENCY = JBCurrencyIds.ETH;
|
|
81
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
71
82
|
uint8 DECIMALS = 18;
|
|
83
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
72
84
|
uint256 DECIMAL_MULTIPLIER = 10 ** DECIMALS;
|
|
85
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
73
86
|
bytes32 ERC20_SALT = "_REV_ERC20_SALT_V6_";
|
|
87
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
74
88
|
bytes32 SUCKER_SALT = "_REV_SUCKER_SALT_V6_";
|
|
89
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
75
90
|
bytes32 DEPLOYER_SALT = "_REV_DEPLOYER_SALT_V6_";
|
|
91
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
76
92
|
bytes32 REVLOANS_SALT = "_REV_LOANS_SALT_V6_";
|
|
93
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
77
94
|
address LOANS_OWNER;
|
|
95
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
78
96
|
address OPERATOR;
|
|
97
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
79
98
|
address TRUSTED_FORWARDER;
|
|
99
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
80
100
|
IPermit2 PERMIT2;
|
|
101
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
81
102
|
uint48 REV_START_TIME = 1_740_089_444;
|
|
103
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
82
104
|
uint104 REV_MAINNET_AUTO_ISSUANCE_ = 1_050_482_341_387_116_262_330_122;
|
|
105
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
83
106
|
uint104 REV_BASE_AUTO_ISSUANCE_ = 38_544_322_230_437_559_731_228;
|
|
107
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
84
108
|
uint104 REV_OP_AUTO_ISSUANCE_ = 32_069_388_242_375_817_844;
|
|
109
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
85
110
|
uint104 REV_ARB_AUTO_ISSUANCE_ = 3_479_431_776_906_850_000_000;
|
|
86
111
|
|
|
87
112
|
function configureSphinx() public override {
|
|
@@ -190,6 +215,7 @@ contract DeployScript is Script, Sphinx {
|
|
|
190
215
|
autoIssuances: issuanceConfs,
|
|
191
216
|
splitPercent: 3800, // 38%
|
|
192
217
|
splits: splits,
|
|
218
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
193
219
|
initialIssuance: uint112(10_000 * DECIMAL_MULTIPLIER),
|
|
194
220
|
issuanceCutFrequency: 90 days,
|
|
195
221
|
issuanceCutPercent: 380_000_000, // 38%
|
|
@@ -201,7 +227,11 @@ contract DeployScript is Script, Sphinx {
|
|
|
201
227
|
{
|
|
202
228
|
REVAutoIssuance[] memory issuanceConfs = new REVAutoIssuance[](1);
|
|
203
229
|
issuanceConfs[0] = REVAutoIssuance({
|
|
204
|
-
|
|
230
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
231
|
+
chainId: PREMINT_CHAIN_ID,
|
|
232
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
233
|
+
count: uint104(1_550_000 * DECIMAL_MULTIPLIER),
|
|
234
|
+
beneficiary: OPERATOR
|
|
205
235
|
});
|
|
206
236
|
|
|
207
237
|
stageConfigurations[1] = REVStageConfig({
|
|
@@ -241,9 +271,8 @@ contract DeployScript is Script, Sphinx {
|
|
|
241
271
|
JBTokenMapping[] memory tokenMappings = new JBTokenMapping[](1);
|
|
242
272
|
tokenMappings[0] = JBTokenMapping({
|
|
243
273
|
localToken: JBConstants.NATIVE_TOKEN,
|
|
244
|
-
remoteToken: bytes32(uint256(uint160(JBConstants.NATIVE_TOKEN))),
|
|
245
274
|
minGas: 200_000,
|
|
246
|
-
|
|
275
|
+
remoteToken: bytes32(uint256(uint160(JBConstants.NATIVE_TOKEN)))
|
|
247
276
|
});
|
|
248
277
|
|
|
249
278
|
REVSuckerDeploymentConfig memory suckerDeploymentConfiguration;
|
|
@@ -314,6 +343,7 @@ contract DeployScript is Script, Sphinx {
|
|
|
314
343
|
|
|
315
344
|
function deploy() public sphinx {
|
|
316
345
|
// TODO figure out how to reference project ID if the contracts are already deployed.
|
|
346
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
317
347
|
uint256 FEE_PROJECT_ID = core.projects.createFor(safeAddress());
|
|
318
348
|
|
|
319
349
|
// Deploy REVLoans first — it only depends on the controller.
|
|
@@ -389,6 +419,10 @@ contract DeployScript is Script, Sphinx {
|
|
|
389
419
|
view
|
|
390
420
|
returns (address deployedTo, bool isDeployed)
|
|
391
421
|
{
|
|
422
|
+
// Note: This uses the Arachnid deterministic-deployment-proxy address, which differs from
|
|
423
|
+
// the Sphinx deployer used at runtime. As a result, the predicted address won't match and
|
|
424
|
+
// _isDeployed will always return false when deploying via Sphinx. This is benign — it just
|
|
425
|
+
// means contracts are always freshly deployed rather than skipped.
|
|
392
426
|
address _deployedTo = vm.computeCreate2Address({
|
|
393
427
|
salt: salt,
|
|
394
428
|
initCodeHash: keccak256(abi.encodePacked(creationCode, arguments)),
|