@rev-net/core-v6 0.0.16 → 0.0.18
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/ARCHITECTURE.md +1 -0
- package/AUDIT_INSTRUCTIONS.md +3 -1
- package/CHANGE_LOG.md +12 -3
- package/RISKS.md +4 -0
- package/SKILLS.md +5 -4
- package/STYLE_GUIDE.md +2 -2
- package/USER_JOURNEYS.md +3 -0
- package/foundry.toml +1 -1
- package/package.json +9 -9
- package/script/Deploy.s.sol +20 -17
- package/script/helpers/RevnetCoreDeploymentLib.sol +1 -1
- package/src/REVDeployer.sol +5 -2
- package/src/REVLoans.sol +37 -1
- package/src/structs/REVBaseline721HookConfig.sol +0 -2
- package/test/REV.integrations.t.sol +1 -1
- package/test/REVAutoIssuanceFuzz.t.sol +1 -1
- package/test/REVDeployerRegressions.t.sol +1 -1
- package/test/REVInvincibility.t.sol +1 -1
- package/test/REVInvincibilityHandler.sol +1 -1
- package/test/REVLifecycle.t.sol +1 -1
- package/test/REVLoans.invariants.t.sol +1 -1
- package/test/REVLoansAttacks.t.sol +1 -1
- package/test/REVLoansFeeRecovery.t.sol +1 -1
- package/test/REVLoansFindings.t.sol +1 -1
- package/test/REVLoansRegressions.t.sol +1 -1
- package/test/REVLoansSourceFeeRecovery.t.sol +1 -1
- package/test/REVLoansSourced.t.sol +1 -1
- package/test/REVLoansUnSourced.t.sol +1 -1
- package/test/TestBurnHeldTokens.t.sol +1 -1
- package/test/TestCEIPattern.t.sol +1 -1
- package/test/TestCashOutCallerValidation.t.sol +1 -1
- package/test/TestConversionDocumentation.t.sol +1 -1
- package/test/TestCrossCurrencyReclaim.t.sol +1 -1
- package/test/TestCrossSourceReallocation.t.sol +1 -1
- package/test/TestERC2771MetaTx.t.sol +1 -1
- package/test/TestEmptyBuybackSpecs.t.sol +1 -1
- package/test/TestFlashLoanSurplus.t.sol +1 -1
- package/test/TestHookArrayOOB.t.sol +1 -1
- package/test/TestLiquidationBehavior.t.sol +1 -1
- package/test/TestLoanSourceRotation.t.sol +1 -1
- package/test/TestLoansCashOutDelay.t.sol +467 -0
- package/test/TestLongTailEconomics.t.sol +1 -1
- package/test/TestLowFindings.t.sol +1 -1
- package/test/TestMixedFixes.t.sol +1 -1
- package/test/TestPermit2Signatures.t.sol +1 -1
- package/test/TestReallocationSandwich.t.sol +1 -1
- package/test/TestRevnetRegressions.t.sol +1 -1
- package/test/TestSplitWeightAdjustment.t.sol +1 -1
- package/test/TestSplitWeightE2E.t.sol +1 -2
- package/test/TestSplitWeightFork.t.sol +1 -2
- package/test/TestStageTransitionBorrowable.t.sol +1 -1
- package/test/TestSwapTerminalPermission.t.sol +1 -1
- package/test/TestUint112Overflow.t.sol +1 -1
- package/test/TestZeroRepayment.t.sol +1 -1
- package/test/audit/LoanIdOverflowGuard.t.sol +1 -1
- package/test/fork/ForkTestBase.sol +1 -2
- package/test/fork/TestAutoIssuanceFork.t.sol +1 -1
- package/test/fork/TestCashOutFork.t.sol +1 -1
- package/test/fork/TestIssuanceDecayFork.t.sol +1 -1
- package/test/fork/TestLoanBorrowFork.t.sol +1 -1
- package/test/fork/TestLoanCrossRulesetFork.t.sol +1 -1
- package/test/fork/TestLoanERC20Fork.t.sol +1 -1
- package/test/fork/TestLoanLiquidationFork.t.sol +1 -1
- package/test/fork/TestLoanReallocateFork.t.sol +1 -1
- package/test/fork/TestLoanRepayFork.t.sol +1 -1
- package/test/fork/TestLoanTransferFork.t.sol +1 -1
- package/test/fork/TestPermit2PaymentFork.t.sol +1 -1
- package/test/fork/TestSplitWeightFork.t.sol +1 -1
- package/test/helpers/MaliciousContracts.sol +1 -1
- package/test/helpers/REVEmpty721Config.sol +0 -1
- package/test/mock/MockBuybackCashOutRecorder.sol +1 -1
- package/test/mock/MockBuybackDataHook.sol +1 -1
- package/test/mock/MockBuybackDataHookMintPath.sol +1 -1
- package/test/regression/TestBurnPermissionRequired.t.sol +1 -1
- package/test/regression/TestCashOutBuybackFeeLeak.t.sol +1 -1
- package/test/regression/TestCrossRevnetLiquidation.t.sol +1 -1
- package/test/regression/TestCumulativeLoanCounter.t.sol +1 -1
- package/test/regression/TestLiquidateGapHandling.t.sol +1 -1
- package/test/regression/TestZeroPriceFeed.t.sol +1 -1
package/ARCHITECTURE.md
CHANGED
|
@@ -76,6 +76,7 @@ Cash Out → REVDeployer.beforeCashOutRecordedWith()
|
|
|
76
76
|
### Loan Flow
|
|
77
77
|
```
|
|
78
78
|
Borrower → REVLoans.borrowFrom()
|
|
79
|
+
→ Enforce cash-out delay if set (cross-chain deployment protection)
|
|
79
80
|
→ Burn borrower's revnet tokens as collateral
|
|
80
81
|
→ Calculate borrow amount from bonding curve value of collateral
|
|
81
82
|
→ Pull funds from treasury via USE_ALLOWANCE
|
package/AUDIT_INSTRUCTIONS.md
CHANGED
|
@@ -93,6 +93,7 @@ User cashes out via terminal
|
|
|
93
93
|
```
|
|
94
94
|
Borrower calls REVLoans.borrowFrom()
|
|
95
95
|
-> Prerequisite: caller must have granted BURN_TOKENS permission to REVLoans via JBPermissions
|
|
96
|
+
-> Enforce cash-out delay: resolve deployer from ruleset dataHook, check cashOutDelayOf(revnetId)
|
|
96
97
|
-> Validate: collateral > 0, terminal registered, prepaidFeePercent in range
|
|
97
98
|
-> Generate loan ID: revnetId * 1T + loanNumber
|
|
98
99
|
-> Create loan in storage
|
|
@@ -332,6 +333,7 @@ No prior formal audit with finding IDs has been conducted on this codebase. All
|
|
|
332
333
|
| `REVDeployer_StagesRequired` | REVDeployer | `deployFor` / `launchChainsFor` called with empty `stageConfigurations` array |
|
|
333
334
|
| `REVDeployer_StageTimesMustIncrease` | REVDeployer | Stage `startsAtOrAfter` timestamps are not strictly increasing |
|
|
334
335
|
| `REVDeployer_Unauthorized` | REVDeployer | Caller is not the split operator (for operator-gated functions) or not the project owner (for `launchChainsFor`) |
|
|
336
|
+
| `REVLoans_CashOutDelayNotFinished` | REVLoans | `borrowFrom` called during the 30-day cash-out delay period (cross-chain deployment protection) |
|
|
335
337
|
| `REVLoans_CollateralExceedsLoan` | REVLoans | `reallocateCollateralFromLoan` called with `collateralCountToReturn > loan.collateral` |
|
|
336
338
|
| `REVLoans_InvalidPrepaidFeePercent` | REVLoans | `prepaidFeePercent` outside `[MIN_PREPAID_FEE_PERCENT, MAX_PREPAID_FEE_PERCENT]` range (25-500) |
|
|
337
339
|
| `REVLoans_InvalidTerminal` | REVLoans | Loan source references a terminal not registered in `JBDirectory` for the revnet |
|
|
@@ -353,7 +355,7 @@ No prior formal audit with finding IDs has been conducted on this codebase. All
|
|
|
353
355
|
|
|
354
356
|
## Compiler and Version Info
|
|
355
357
|
|
|
356
|
-
- **Solidity**:
|
|
358
|
+
- **Solidity**: 0.8.28
|
|
357
359
|
- **EVM target**: Cancun
|
|
358
360
|
- **Optimizer**: via-IR, 100 runs
|
|
359
361
|
- **Dependencies**: OpenZeppelin 5.x, PRBMath, Permit2, nana-core-v6, nana-721-hook-v6, nana-buyback-hook-v6, nana-suckers-v6
|
package/CHANGE_LOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# revnet-core-v6 Changelog (v5 -> v6)
|
|
2
2
|
|
|
3
|
-
This document describes all changes between `revnet-core` (v5, Solidity 0.8.23) and `revnet-core-v6` (v6, Solidity
|
|
3
|
+
This document describes all changes between `revnet-core` (v5, Solidity 0.8.23) and `revnet-core-v6` (v6, Solidity 0.8.28).
|
|
4
4
|
|
|
5
5
|
## Summary
|
|
6
6
|
|
|
@@ -108,6 +108,7 @@ The boolean semantics are **inverted**: v5 used opt-in flags (`splitOperatorCan*
|
|
|
108
108
|
| `REVLoans` | `REVLoans_NothingToRepay()` |
|
|
109
109
|
| `REVLoans` | `REVLoans_ZeroBorrowAmount()` |
|
|
110
110
|
| `REVLoans` | `REVLoans_SourceMismatch()` |
|
|
111
|
+
| `REVLoans` | `REVLoans_CashOutDelayNotFinished(uint256 cashOutDelay, uint256 blockTimestamp)` |
|
|
111
112
|
| `REVLoans` | `REVLoans_LoanIdOverflow()` |
|
|
112
113
|
|
|
113
114
|
### 2.4 New Constants
|
|
@@ -129,6 +130,13 @@ The boolean semantics are **inverted**: v5 used opt-in flags (`splitOperatorCan*
|
|
|
129
130
|
|
|
130
131
|
## 3. Event Changes
|
|
131
132
|
|
|
133
|
+
### 3.0 Indexer Notes
|
|
134
|
+
|
|
135
|
+
For revnet-focused subgraphs:
|
|
136
|
+
- both deployment flows now correlate to `deployFor` rather than a split `deployFor`/`deployWith721sFor` model;
|
|
137
|
+
- revnet deployment entities should expect an associated 721 hook by default;
|
|
138
|
+
- any entity that previously depended on caller-supplied buyback-hook config should be updated for the v6 auto-configured buyback path.
|
|
139
|
+
|
|
132
140
|
### 3.1 Added Events
|
|
133
141
|
|
|
134
142
|
See section 2.2 above.
|
|
@@ -245,7 +253,7 @@ The following structs are identical between v5 and v6 (only `forge-lint` comment
|
|
|
245
253
|
|
|
246
254
|
| Change | Description |
|
|
247
255
|
|--------|-------------|
|
|
248
|
-
| **Solidity version** | Upgraded from `0.8.23` to
|
|
256
|
+
| **Solidity version** | Upgraded from `0.8.23` to `0.8.28`. |
|
|
249
257
|
| **Buyback hook architecture** | Per-revnet `buybackHookOf` mapping replaced with a single immutable `BUYBACK_HOOK` (`IJBBuybackHookRegistry`). Pools are auto-initialized for each terminal token during deployment via `_tryInitializeBuybackPoolFor`. |
|
|
250
258
|
| **Loans architecture** | Per-revnet `loansOf` mapping replaced with a single immutable `LOANS` address. The deployer grants `USE_ALLOWANCE` permission to the loans contract for all revnets in the constructor (wildcard `revnetId=0`). |
|
|
251
259
|
| **Constructor permissions** | v6 constructor grants three wildcard permissions: `MAP_SUCKER_TOKEN` to the sucker registry, `USE_ALLOWANCE` to the loans contract, and `SET_BUYBACK_POOL` to the buyback hook. v5 only granted `MAP_SUCKER_TOKEN`. |
|
|
@@ -270,7 +278,7 @@ The following structs are identical between v5 and v6 (only `forge-lint` comment
|
|
|
270
278
|
|
|
271
279
|
| Change | Description |
|
|
272
280
|
|--------|-------------|
|
|
273
|
-
| **Solidity version** | Upgraded from `0.8.23` to
|
|
281
|
+
| **Solidity version** | Upgraded from `0.8.23` to `0.8.28`. |
|
|
274
282
|
| **Deployer dependency removed** | v5 stored `REVNETS` (`IREVDeployer`) and validated that the revnet was owned by the expected deployer via `RevnetsMismatch`. v6 does not reference the deployer at all. Validation now checks the terminal directly via `DIRECTORY.isTerminalOf`. |
|
|
275
283
|
| **Constructor refactored** | v5 accepted `IREVDeployer revnets` and derived `CONTROLLER`, `DIRECTORY`, etc. from it. v6 accepts `IJBController controller` and `IJBProjects projects` directly. |
|
|
276
284
|
| **Terminal validation** | `borrowFrom` now validates that the source terminal is registered in the directory for the revnet, reverting with `REVLoans_InvalidTerminal` if not. v5 validated deployer ownership instead. |
|
|
@@ -289,6 +297,7 @@ The following structs are identical between v5 and v6 (only `forge-lint` comment
|
|
|
289
297
|
| **Source fee try-catch hardening** | The source fee payment in `_adjust` is now wrapped in a try-catch block. If the source terminal's `pay` call reverts, the ERC-20 allowance is reclaimed and the fee amount is returned to the beneficiary instead of blocking the entire loan operation. v5 called `terminal.pay` directly without error handling. |
|
|
290
298
|
| **Timestamp cast fix** | `borrowFrom` now casts `block.timestamp` to `uint48` when setting `loan.createdAt`, matching the `REVLoan.createdAt` field width. v5 used `uint40`, which would silently truncate timestamps after the year 36812. |
|
|
291
299
|
| **`ReallocateCollateral` event typo fix** | v5 used `removedcollateralCount` (lowercase 'c'). v6 fixes it to `removedCollateralCount` (uppercase 'C'). |
|
|
300
|
+
| **Cash out delay enforced in loans** | `borrowFrom` now resolves the `REVDeployer` from the ruleset's `dataHook` and checks `cashOutDelayOf(revnetId)`. If the 30-day cross-chain deployment delay hasn't passed, `borrowFrom` reverts with `REVLoans_CashOutDelayNotFinished`. `borrowableAmountFrom` returns 0 during the delay so UIs reflect the restriction. v5 did not enforce the cash out delay in the loans contract. |
|
|
292
301
|
| **NatSpec documentation** | Extensive NatSpec added to all functions, views, and internal helpers. Flash loan safety analysis documented in `_borrowableAmountFrom`. |
|
|
293
302
|
|
|
294
303
|
### 6.3 Named Arguments
|
package/RISKS.md
CHANGED
|
@@ -81,6 +81,10 @@ Read [ARCHITECTURE.md](./ARCHITECTURE.md) and [SKILLS.md](./SKILLS.md) for proto
|
|
|
81
81
|
- **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.
|
|
82
82
|
- **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.
|
|
83
83
|
|
|
84
|
+
### Cross-chain cash-out delay enforcement
|
|
85
|
+
|
|
86
|
+
- **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._setCashOutDelayIfNeeded()` sets a 30-day delay. `borrowFrom` resolves the deployer from the current ruleset's `dataHook` and checks `cashOutDelayOf(revnetId)`, 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).
|
|
87
|
+
|
|
84
88
|
### BURN_TOKENS permission prerequisite
|
|
85
89
|
|
|
86
90
|
- **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.
|
package/SKILLS.md
CHANGED
|
@@ -47,7 +47,7 @@ Deploy and manage Revnets -- autonomous, unowned Juicebox projects with staged i
|
|
|
47
47
|
|
|
48
48
|
| Function | Permissions | What it does |
|
|
49
49
|
|----------|------------|-------------|
|
|
50
|
-
| `REVLoans.borrowFrom(revnetId, source, minBorrowAmount, collateralCount, beneficiary, prepaidFeePercent)` | Permissionless (caller must grant BURN_TOKENS to REVLoans) | Open a loan: burn collateral tokens, pull funds from revnet via `useAllowanceOf`, pay REV fee (1%) + terminal fee (2.5%), transfer remainder to beneficiary, mint loan NFT. |
|
|
50
|
+
| `REVLoans.borrowFrom(revnetId, source, minBorrowAmount, collateralCount, beneficiary, prepaidFeePercent)` | Permissionless (caller must grant BURN_TOKENS to REVLoans) | Open a loan: enforce cash-out delay if set (cross-chain deployment protection), burn collateral tokens, pull funds from revnet via `useAllowanceOf`, pay REV fee (1%) + terminal fee (2.5%), transfer remainder to beneficiary, mint loan NFT. |
|
|
51
51
|
| `REVLoans.repayLoan(loanId, maxRepayBorrowAmount, collateralCountToReturn, beneficiary, allowance)` | Loan NFT owner | Repay fully or partially. Returns funds to revnet via `addToBalanceOf`, re-mints collateral tokens, burns/replaces the loan NFT. Supports permit2 signatures. |
|
|
52
52
|
| `REVLoans.reallocateCollateralFromLoan(loanId, collateralCountToTransfer, source, minBorrowAmount, collateralCountToAdd, beneficiary, prepaidFeePercent)` | Loan NFT owner | Refinance: remove excess collateral from an existing loan and open a new loan with the freed collateral. Burns original, mints two replacements. |
|
|
53
53
|
| `REVLoans.liquidateExpiredLoansFrom(revnetId, startingLoanId, count)` | Permissionless | Clean up loans past the 10-year liquidation duration. Burns NFTs and decrements accounting totals. Collateral is permanently lost. |
|
|
@@ -57,7 +57,7 @@ Deploy and manage Revnets -- autonomous, unowned Juicebox projects with staged i
|
|
|
57
57
|
|
|
58
58
|
| Function | What it does |
|
|
59
59
|
|----------|-------------|
|
|
60
|
-
| `REVLoans.borrowableAmountFrom(revnetId, collateralCount, decimals, currency)` | Calculate how much can be borrowed for a given collateral amount. Aggregates surplus from all terminals, applies bonding curve. |
|
|
60
|
+
| `REVLoans.borrowableAmountFrom(revnetId, collateralCount, decimals, currency)` | Calculate how much can be borrowed for a given collateral amount. Returns 0 during the cash-out delay period. Aggregates surplus from all terminals, applies bonding curve. |
|
|
61
61
|
| `REVLoans.determineSourceFeeAmount(loan, amount)` | Calculate the time-proportional source fee for a loan repayment. Zero during prepaid window, linear accrual after. |
|
|
62
62
|
| `REVLoans.loanOf(loanId)` | Returns the full `REVLoan` struct for a loan. |
|
|
63
63
|
| `REVLoans.loanSourcesOf(revnetId)` | Returns all `(terminal, token)` pairs used for loans by a revnet. |
|
|
@@ -139,6 +139,7 @@ Deploy and manage Revnets -- autonomous, unowned Juicebox projects with staged i
|
|
|
139
139
|
|
|
140
140
|
| Error | When It Fires |
|
|
141
141
|
|-------|---------------|
|
|
142
|
+
| `REVLoans_CashOutDelayNotFinished(cashOutDelay, blockTimestamp)` | When borrowing during the 30-day cash-out delay period (cross-chain deployment protection). |
|
|
142
143
|
| `REVLoans_CollateralExceedsLoan(collateralToReturn, loanCollateral)` | When trying to return more collateral than the loan holds. |
|
|
143
144
|
| `REVLoans_InvalidPrepaidFeePercent(prepaidFeePercent, min, max)` | When `prepaidFeePercent` is outside the allowed range (2.5%--50%). |
|
|
144
145
|
| `REVLoans_InvalidTerminal(terminal, revnetId)` | When the specified terminal is not registered for the revnet. |
|
|
@@ -212,8 +213,8 @@ Deploy and manage Revnets -- autonomous, unowned Juicebox projects with staged i
|
|
|
212
213
|
4. **Loan ID encoding.** `loanId = revnetId * 1_000_000_000_000 + loanNumber`. Each revnet supports ~1 trillion loans. Use `revnetIdOfLoanWith(loanId)` to decode.
|
|
213
214
|
5. **uint112 truncation risk.** `REVLoan.amount` and `REVLoan.collateral` are `uint112`. Values above ~5.19e33 truncate silently.
|
|
214
215
|
6. **Auto-issuance stage IDs.** Computed as `block.timestamp + i` during deployment. These match the Juicebox ruleset IDs because `JBRulesets` assigns IDs the same way (`latestId >= block.timestamp ? latestId + 1 : block.timestamp`), producing identical sequential IDs when all stages are queued in a single `deployFor()` call.
|
|
215
|
-
7. **Cash-out fee stacking.** Cash outs incur both the Juicebox terminal fee (2.5%) and the revnet cash-out fee (2.5% to fee revnet). These compound.
|
|
216
|
-
8. **30-day cash-out delay.** Applied when deploying an existing revnet to a new chain where the first stage has already started. Prevents cross-chain liquidity arbitrage.
|
|
216
|
+
7. **Cash-out fee stacking.** Cash outs incur both the Juicebox terminal fee (2.5%) and the revnet cash-out fee (2.5% to fee revnet). These compound. The 2.5% fee is deducted from the TOKEN AMOUNT being cashed out, not from the reclaim value. 2.5% of the tokens are redirected to the fee revnet, which then redeems them at the bonding curve independently. The net reclaim to the caller is based on 97.5% of the tokens, not 97.5% of the computed ETH value. This is by design.
|
|
217
|
+
8. **30-day cash-out delay.** Applied when deploying an existing revnet to a new chain where the first stage has already started. Prevents cross-chain liquidity arbitrage. Enforced in both `beforeCashOutRecordedWith` (direct cash outs) and `REVLoans.borrowFrom` / `borrowableAmountFrom` (loans). The delay is resolved via the ruleset's `dataHook` (the REVDeployer) and `cashOutDelayOf(revnetId)`.
|
|
217
218
|
9. **`cashOutTaxRate` cannot be MAX.** Must be strictly less than `MAX_CASH_OUT_TAX_RATE` (10,000). Revnets cannot fully disable cash outs.
|
|
218
219
|
10. **Split operator is singular.** Only ONE address can be split operator at a time. The operator can replace itself via `setSplitOperatorOf` but cannot delegate or multi-sig.
|
|
219
220
|
11. **NATIVE_TOKEN on non-ETH chains.** `JBConstants.NATIVE_TOKEN` on Celo means CELO, on Polygon means MATIC -- not ETH. Use ERC-20 WETH instead. The config matching hash does NOT catch terminal configuration differences.
|
package/STYLE_GUIDE.md
CHANGED
|
@@ -21,7 +21,7 @@ One contract/interface/struct/enum per file. Name the file after the type it con
|
|
|
21
21
|
|
|
22
22
|
```solidity
|
|
23
23
|
// Contracts — pin to exact version
|
|
24
|
-
pragma solidity
|
|
24
|
+
pragma solidity 0.8.28;
|
|
25
25
|
|
|
26
26
|
// Interfaces, structs, enums — caret for forward compatibility
|
|
27
27
|
pragma solidity ^0.8.0;
|
|
@@ -326,7 +326,7 @@ Standard config across all repos:
|
|
|
326
326
|
|
|
327
327
|
```toml
|
|
328
328
|
[profile.default]
|
|
329
|
-
solc = '0.8.
|
|
329
|
+
solc = '0.8.28'
|
|
330
330
|
evm_version = 'cancun'
|
|
331
331
|
optimizer_runs = 200
|
|
332
332
|
libs = ["node_modules", "lib"]
|
package/USER_JOURNEYS.md
CHANGED
|
@@ -163,6 +163,7 @@ This is a standard Juicebox payment, but REVDeployer intervenes as the data hook
|
|
|
163
163
|
**Prerequisites:**
|
|
164
164
|
- Caller must hold `collateralCount` revnet ERC-20 tokens
|
|
165
165
|
- 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`.
|
|
166
|
+
- The revnet's cash-out delay must have passed (if one was set during cross-chain deployment). `borrowableAmountFrom` returns 0 and `borrowFrom` reverts with `REVLoans_CashOutDelayNotFinished` until the 30-day delay expires.
|
|
166
167
|
|
|
167
168
|
**Key parameters:**
|
|
168
169
|
|
|
@@ -181,6 +182,7 @@ This is a standard Juicebox payment, but REVDeployer intervenes as the data hook
|
|
|
181
182
|
- `collateralCount > 0` (no zero-collateral loans)
|
|
182
183
|
- `source.terminal` is registered for the revnet in the directory
|
|
183
184
|
- `prepaidFeePercent` in range [25, 500]
|
|
185
|
+
- Cash-out delay has passed: resolves the `REVDeployer` from the current ruleset's `dataHook`, checks `cashOutDelayOf(revnetId)`. Reverts with `REVLoans_CashOutDelayNotFinished(cashOutDelay, block.timestamp)` if `cashOutDelay > block.timestamp`.
|
|
184
186
|
2. **Loan ID generation:** `revnetId * 1_000_000_000_000 + (++totalLoansBorrowedFor[revnetId])`
|
|
185
187
|
3. **Loan creation in storage:**
|
|
186
188
|
- `source`, `createdAt = block.timestamp`, `prepaidFeePercent`, `prepaidDuration = mulDiv(prepaidFeePercent, 3650 days, 500)`
|
|
@@ -210,6 +212,7 @@ This is a standard Juicebox payment, but REVDeployer intervenes as the data hook
|
|
|
210
212
|
- `prepaidDuration` at minimum (25): `25 * 3650 days / 500 = 182.5 days`. At maximum (500): `500 * 3650 days / 500 = 3650 days`.
|
|
211
213
|
- Both the REV fee payment and the source fee payment failures are non-fatal. If either `feeTerminal.pay` or `source.terminal.pay` reverts, the fee amount is transferred to the beneficiary instead.
|
|
212
214
|
- Loan NFT is minted to `_msgSender()`, not `beneficiary`. The caller owns the loan; the beneficiary receives the funds.
|
|
215
|
+
- When a revnet deploys to a new chain with `startsAtOrAfter` in the past, `REVDeployer` sets a 30-day cash-out delay. Both `borrowFrom` and `borrowableAmountFrom` enforce this delay by resolving the deployer from the current ruleset's `dataHook` and checking `cashOutDelayOf`. This prevents cross-chain arbitrage via loans during the delay window.
|
|
213
216
|
|
|
214
217
|
---
|
|
215
218
|
|
package/foundry.toml
CHANGED
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.18",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
"artifacts": "source ./.env && npx sphinx artifacts --org-id 'ea165b21-7cdc-4d7b-be59-ecdd4c26bee4' --project-name 'revnet-core-v6'"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@bananapus/721-hook-v6": "^0.0.
|
|
23
|
-
"@bananapus/buyback-hook-v6": "^0.0.
|
|
24
|
-
"@bananapus/core-v6": "^0.0.
|
|
25
|
-
"@bananapus/ownable-v6": "^0.0.
|
|
26
|
-
"@bananapus/permission-ids-v6": "^0.0.
|
|
27
|
-
"@bananapus/router-terminal-v6": "^0.0.
|
|
28
|
-
"@bananapus/suckers-v6": "^0.0.
|
|
29
|
-
"@croptop/core-v6": "^0.0.
|
|
22
|
+
"@bananapus/721-hook-v6": "^0.0.22",
|
|
23
|
+
"@bananapus/buyback-hook-v6": "^0.0.22",
|
|
24
|
+
"@bananapus/core-v6": "^0.0.28",
|
|
25
|
+
"@bananapus/ownable-v6": "^0.0.15",
|
|
26
|
+
"@bananapus/permission-ids-v6": "^0.0.14",
|
|
27
|
+
"@bananapus/router-terminal-v6": "^0.0.21",
|
|
28
|
+
"@bananapus/suckers-v6": "^0.0.18",
|
|
29
|
+
"@croptop/core-v6": "^0.0.23",
|
|
30
30
|
"@openzeppelin/contracts": "^5.6.1",
|
|
31
31
|
"@uniswap/v4-core": "^1.0.2",
|
|
32
32
|
"@uniswap/v4-periphery": "^1.0.3"
|
package/script/Deploy.s.sol
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
pragma solidity
|
|
2
|
+
pragma solidity 0.8.28;
|
|
3
3
|
|
|
4
4
|
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
5
5
|
import "@bananapus/721-hook-v6/script/helpers/Hook721DeploymentLib.sol";
|
|
@@ -323,7 +323,6 @@ contract DeployScript is Script, Sphinx {
|
|
|
323
323
|
tiersConfig: JB721InitTiersConfig({
|
|
324
324
|
tiers: new JB721TierConfig[](0), currency: ETH_CURRENCY, decimals: 18
|
|
325
325
|
}),
|
|
326
|
-
reserveBeneficiary: address(0),
|
|
327
326
|
flags: REV721TiersHookFlags({
|
|
328
327
|
noNewTiersWithReserves: false,
|
|
329
328
|
noNewTiersWithVotes: false,
|
|
@@ -459,21 +458,25 @@ contract DeployScript is Script, Sphinx {
|
|
|
459
458
|
trustedForwarder: TRUSTED_FORWARDER
|
|
460
459
|
});
|
|
461
460
|
|
|
462
|
-
//
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
461
|
+
// Only configure the fee project if singletons were freshly deployed. Re-running `deployFor` on an
|
|
462
|
+
// already-configured project would fail because the project is no longer blank.
|
|
463
|
+
if (!_singletonsExist) {
|
|
464
|
+
// Approve the basic deployer to configure the project.
|
|
465
|
+
core.projects.approve({to: address(_basicDeployer), tokenId: FEE_PROJECT_ID});
|
|
466
|
+
|
|
467
|
+
// Build the config.
|
|
468
|
+
FeeProjectConfig memory feeProjectConfig = getFeeProjectConfig();
|
|
469
|
+
|
|
470
|
+
// Configure the project.
|
|
471
|
+
_basicDeployer.deployFor({
|
|
472
|
+
revnetId: FEE_PROJECT_ID,
|
|
473
|
+
configuration: feeProjectConfig.configuration,
|
|
474
|
+
terminalConfigurations: feeProjectConfig.terminalConfigurations,
|
|
475
|
+
suckerDeploymentConfiguration: feeProjectConfig.suckerDeploymentConfiguration,
|
|
476
|
+
tiered721HookConfiguration: feeProjectConfig.tiered721HookConfiguration,
|
|
477
|
+
allowedPosts: feeProjectConfig.allowedPosts
|
|
478
|
+
});
|
|
479
|
+
}
|
|
477
480
|
}
|
|
478
481
|
|
|
479
482
|
/// @notice Check whether a contract has already been deployed at its deterministic address.
|
package/src/REVDeployer.sol
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
pragma solidity
|
|
2
|
+
pragma solidity 0.8.28;
|
|
3
3
|
|
|
4
4
|
import {IJB721TiersHook} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHook.sol";
|
|
5
5
|
import {IJB721TiersHookDeployer} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHookDeployer.sol";
|
|
@@ -282,6 +282,9 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IJBRulesetDataHook, IJBCas
|
|
|
282
282
|
}
|
|
283
283
|
|
|
284
284
|
// Split the cashed-out tokens into a fee portion and a non-fee portion.
|
|
285
|
+
// The fee is applied to TOKEN COUNT (2.5% of tokens), not to value. The fee revnet receives the bonding-curve
|
|
286
|
+
// reclaim of its 2.5% token share regardless of whether the remaining 97.5% routes through a buyback pool at
|
|
287
|
+
// a better price. This is by design.
|
|
285
288
|
// Micro cash outs (< 40 wei at 2.5% fee) round feeCashOutCount to zero, bypassing the fee.
|
|
286
289
|
// Economically insignificant: the gas cost of the transaction far exceeds the bypassed fee. No fix needed.
|
|
287
290
|
uint256 feeCashOutCount = mulDiv({x: context.cashOutCount, y: FEE, denominator: JBConstants.MAX_FEE});
|
|
@@ -608,6 +611,7 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IJBRulesetDataHook, IJBCas
|
|
|
608
611
|
sqrtPriceX96 = uint160(1 << 96);
|
|
609
612
|
} else {
|
|
610
613
|
address normalizedTerminalToken = terminalToken == JBConstants.NATIVE_TOKEN ? address(0) : terminalToken;
|
|
614
|
+
// slither-disable-next-line calls-loop
|
|
611
615
|
address projectToken = address(CONTROLLER.TOKENS().tokenOf(revnetId));
|
|
612
616
|
|
|
613
617
|
if (projectToken == address(0) || projectToken == normalizedTerminalToken) {
|
|
@@ -955,7 +959,6 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IJBRulesetDataHook, IJBCas
|
|
|
955
959
|
tokenUriResolver: tiered721HookConfiguration.baseline721HookConfiguration.tokenUriResolver,
|
|
956
960
|
contractUri: tiered721HookConfiguration.baseline721HookConfiguration.contractUri,
|
|
957
961
|
tiersConfig: tiered721HookConfiguration.baseline721HookConfiguration.tiersConfig,
|
|
958
|
-
reserveBeneficiary: tiered721HookConfiguration.baseline721HookConfiguration.reserveBeneficiary,
|
|
959
962
|
flags: JB721TiersHookFlags({
|
|
960
963
|
noNewTiersWithReserves: tiered721HookConfiguration.baseline721HookConfiguration.flags
|
|
961
964
|
.noNewTiersWithReserves,
|
package/src/REVLoans.sol
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
pragma solidity
|
|
2
|
+
pragma solidity 0.8.28;
|
|
3
3
|
|
|
4
4
|
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
|
|
5
5
|
import {ERC2771Context} from "@openzeppelin/contracts/metatx/ERC2771Context.sol";
|
|
@@ -27,6 +27,7 @@ import {JBAccountingContext} from "@bananapus/core-v6/src/structs/JBAccountingCo
|
|
|
27
27
|
import {JBRuleset} from "@bananapus/core-v6/src/structs/JBRuleset.sol";
|
|
28
28
|
import {JBSingleAllowance} from "@bananapus/core-v6/src/structs/JBSingleAllowance.sol";
|
|
29
29
|
|
|
30
|
+
import {IREVDeployer} from "./interfaces/IREVDeployer.sol";
|
|
30
31
|
import {IREVLoans} from "./interfaces/IREVLoans.sol";
|
|
31
32
|
import {REVLoan} from "./structs/REVLoan.sol";
|
|
32
33
|
import {REVLoanSource} from "./structs/REVLoanSource.sol";
|
|
@@ -55,6 +56,7 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
55
56
|
// --------------------------- custom errors ------------------------- //
|
|
56
57
|
//*********************************************************************//
|
|
57
58
|
|
|
59
|
+
error REVLoans_CashOutDelayNotFinished(uint256 cashOutDelay, uint256 blockTimestamp);
|
|
58
60
|
error REVLoans_CollateralExceedsLoan(uint256 collateralToReturn, uint256 loanCollateral);
|
|
59
61
|
error REVLoans_InvalidPrepaidFeePercent(uint256 prepaidFeePercent, uint256 min, uint256 max);
|
|
60
62
|
error REVLoans_InvalidTerminal(address terminal, uint256 revnetId);
|
|
@@ -225,6 +227,22 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
225
227
|
view
|
|
226
228
|
returns (uint256)
|
|
227
229
|
{
|
|
230
|
+
// Get the current ruleset to resolve the deployer from its data hook.
|
|
231
|
+
// slither-disable-next-line unused-return
|
|
232
|
+
(JBRuleset memory currentRuleset,) = CONTROLLER.currentRulesetOf(revnetId);
|
|
233
|
+
|
|
234
|
+
// The ruleset's data hook is the REVDeployer that configured this revnet.
|
|
235
|
+
address deployer = currentRuleset.dataHook();
|
|
236
|
+
|
|
237
|
+
// Only check the delay if a deployer is set.
|
|
238
|
+
if (deployer != address(0)) {
|
|
239
|
+
// Get the timestamp after which cash outs (and loans) are allowed.
|
|
240
|
+
uint256 cashOutDelay = IREVDeployer(deployer).cashOutDelayOf(revnetId);
|
|
241
|
+
|
|
242
|
+
// If the delay hasn't passed yet, no amount is borrowable.
|
|
243
|
+
if (cashOutDelay > block.timestamp) return 0;
|
|
244
|
+
}
|
|
245
|
+
|
|
228
246
|
return _borrowableAmountFrom({
|
|
229
247
|
revnetId: revnetId,
|
|
230
248
|
collateralCount: collateralCount,
|
|
@@ -580,6 +598,24 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
580
598
|
);
|
|
581
599
|
}
|
|
582
600
|
|
|
601
|
+
// Get the current ruleset to resolve the deployer from its data hook.
|
|
602
|
+
// slither-disable-next-line unused-return
|
|
603
|
+
(JBRuleset memory currentRuleset,) = CONTROLLER.currentRulesetOf(revnetId);
|
|
604
|
+
|
|
605
|
+
// The ruleset's data hook is the REVDeployer that configured this revnet.
|
|
606
|
+
address deployer = currentRuleset.dataHook();
|
|
607
|
+
|
|
608
|
+
// Only check the delay if a deployer is set.
|
|
609
|
+
if (deployer != address(0)) {
|
|
610
|
+
// Get the timestamp after which cash outs (and loans) are allowed.
|
|
611
|
+
uint256 cashOutDelay = IREVDeployer(deployer).cashOutDelayOf(revnetId);
|
|
612
|
+
|
|
613
|
+
// Revert if the delay hasn't passed yet.
|
|
614
|
+
if (cashOutDelay > block.timestamp) {
|
|
615
|
+
revert REVLoans_CashOutDelayNotFinished(cashOutDelay, block.timestamp);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
583
619
|
// Prevent the loan number from exceeding the ID namespace for this revnet.
|
|
584
620
|
if (totalLoansBorrowedFor[revnetId] >= _ONE_TRILLION) revert REVLoans_LoanIdOverflow();
|
|
585
621
|
|
|
@@ -12,7 +12,6 @@ import {REV721TiersHookFlags} from "./REV721TiersHookFlags.sol";
|
|
|
12
12
|
/// @custom:member tokenUriResolver The token URI resolver for the NFT collection.
|
|
13
13
|
/// @custom:member contractUri The contract URI for the NFT collection.
|
|
14
14
|
/// @custom:member tiersConfig The tier configuration for the NFT collection.
|
|
15
|
-
/// @custom:member reserveBeneficiary The default reserve beneficiary for the NFT collection.
|
|
16
15
|
/// @custom:member flags A set of flags that configure the 721 hook. Omits `issueTokensForSplits` since revnets
|
|
17
16
|
/// always force it to `false`.
|
|
18
17
|
// forge-lint: disable-next-line(pascal-case-struct)
|
|
@@ -23,6 +22,5 @@ struct REVBaseline721HookConfig {
|
|
|
23
22
|
IJB721TokenUriResolver tokenUriResolver;
|
|
24
23
|
string contractUri;
|
|
25
24
|
JB721InitTiersConfig tiersConfig;
|
|
26
|
-
address reserveBeneficiary;
|
|
27
25
|
REV721TiersHookFlags flags;
|
|
28
26
|
}
|
package/test/REVLifecycle.t.sol
CHANGED