@rev-net/core-v6 0.0.37 → 0.0.40
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/CHANGELOG.md +2 -2
- package/README.md +6 -7
- package/foundry.toml +1 -1
- package/package.json +23 -16
- package/references/operations.md +1 -1
- package/references/runtime.md +1 -1
- package/script/Deploy.s.sol +12 -9
- package/src/REVDeployer.sol +69 -67
- package/src/REVHiddenTokens.sol +2 -2
- package/src/REVLoans.sol +26 -22
- package/src/REVOwner.sol +147 -29
- package/src/interfaces/IREVDeployer.sol +2 -1
- package/src/interfaces/IREVHiddenTokens.sol +4 -1
- package/src/interfaces/IREVOwner.sol +5 -0
- package/src/structs/REVAutoIssuance.sol +4 -2
- package/src/structs/REVConfig.sol +8 -5
- package/src/structs/REVDescription.sol +6 -5
- package/src/structs/REVLoan.sol +8 -5
- package/src/structs/REVStageConfig.sol +14 -16
- package/ADMINISTRATION.md +0 -73
- package/ARCHITECTURE.md +0 -116
- package/AUDIT_INSTRUCTIONS.md +0 -90
- package/RISKS.md +0 -107
- package/SKILLS.md +0 -46
- package/STYLE_GUIDE.md +0 -610
- package/USER_JOURNEYS.md +0 -195
- package/foundry.lock +0 -11
- package/slither-ci.config.json +0 -10
- package/sphinx.lock +0 -507
- package/test/REV.integrations.t.sol +0 -573
- package/test/REVAutoIssuanceFuzz.t.sol +0 -328
- package/test/REVDeployerRegressions.t.sol +0 -396
- package/test/REVInvincibility.t.sol +0 -1371
- package/test/REVInvincibilityHandler.sol +0 -387
- package/test/REVLifecycle.t.sol +0 -420
- package/test/REVLoans.invariants.t.sol +0 -724
- package/test/REVLoansAttacks.t.sol +0 -816
- package/test/REVLoansFeeRecovery.t.sol +0 -783
- package/test/REVLoansFindings.t.sol +0 -711
- package/test/REVLoansRegressions.t.sol +0 -364
- package/test/REVLoansSourceFeeRecovery.t.sol +0 -517
- package/test/REVLoansSourced.t.sol +0 -1839
- package/test/REVLoansUnSourced.t.sol +0 -409
- package/test/TestAuditFixVerification.t.sol +0 -675
- package/test/TestBurnHeldTokens.t.sol +0 -394
- package/test/TestCEIPattern.t.sol +0 -508
- package/test/TestCashOutCallerValidation.t.sol +0 -452
- package/test/TestConversionDocumentation.t.sol +0 -365
- package/test/TestCrossCurrencyReclaim.t.sol +0 -610
- package/test/TestCrossSourceReallocation.t.sol +0 -361
- package/test/TestERC2771MetaTx.t.sol +0 -585
- package/test/TestEmptyBuybackSpecs.t.sol +0 -300
- package/test/TestFlashLoanSurplus.t.sol +0 -365
- package/test/TestHiddenTokens.t.sol +0 -474
- package/test/TestHookArrayOOB.t.sol +0 -278
- package/test/TestLiquidationBehavior.t.sol +0 -398
- package/test/TestLoanSourceRotation.t.sol +0 -553
- package/test/TestLoansCashOutDelay.t.sol +0 -493
- package/test/TestLongTailEconomics.t.sol +0 -677
- package/test/TestLowFindings.t.sol +0 -677
- package/test/TestMixedFixes.t.sol +0 -593
- package/test/TestPermit2Signatures.t.sol +0 -683
- package/test/TestReallocationSandwich.t.sol +0 -412
- package/test/TestRevnetRegressions.t.sol +0 -350
- package/test/TestSplitWeightAdjustment.t.sol +0 -527
- package/test/TestSplitWeightE2E.t.sol +0 -605
- package/test/TestSplitWeightFork.t.sol +0 -855
- package/test/TestStageTransitionBorrowable.t.sol +0 -301
- package/test/TestSwapTerminalPermission.t.sol +0 -262
- package/test/TestTerminalEncodingInHash.t.sol +0 -326
- package/test/TestUint112Overflow.t.sol +0 -311
- package/test/TestZeroAmountLoanGuard.t.sol +0 -378
- package/test/TestZeroRepayment.t.sol +0 -354
- package/test/audit/CrossChainBuybackRouteMismatch.t.sol +0 -184
- package/test/audit/HiddenSupplyCashout.t.sol +0 -61
- package/test/audit/LoanIdOverflowGuard.t.sol +0 -523
- package/test/audit/NemesisVerification.t.sol +0 -97
- package/test/audit/OperatorDelegation.t.sol +0 -356
- package/test/audit/PhantomSurplusTerminal.t.sol +0 -367
- package/test/audit/REVOwnerCurrencyMismatch.t.sol +0 -188
- package/test/audit/REVOwnerRemoteSurplusCurrencyMismatch.t.sol +0 -140
- package/test/audit/ReallocatePermission.t.sol +0 -363
- package/test/audit/RemoteLoanAccountingGap.t.sol +0 -74
- package/test/audit/SupportsInterfaceTest.t.sol +0 -51
- package/test/audit/TestFeeAllowanceLeak.t.sol +0 -197
- package/test/audit/TestLoansAndDeployerFixes.t.sol +0 -576
- package/test/fork/ForkTestBase.sol +0 -727
- package/test/fork/TestAutoIssuanceFork.t.sol +0 -148
- package/test/fork/TestCashOutFork.t.sol +0 -253
- package/test/fork/TestIssuanceDecayFork.t.sol +0 -158
- package/test/fork/TestLoanAdversarialFork.t.sol +0 -744
- package/test/fork/TestLoanBorrowFork.t.sol +0 -163
- package/test/fork/TestLoanCrossRulesetFork.t.sol +0 -308
- package/test/fork/TestLoanERC20Fork.t.sol +0 -459
- package/test/fork/TestLoanLiquidationFork.t.sol +0 -135
- package/test/fork/TestLoanReallocateFork.t.sol +0 -113
- package/test/fork/TestLoanRepayFork.t.sol +0 -188
- package/test/fork/TestLoanTransferFork.t.sol +0 -143
- package/test/fork/TestPermit2PaymentFork.t.sol +0 -300
- package/test/fork/TestSplitWeightFork.t.sol +0 -189
- package/test/helpers/MaliciousContracts.sol +0 -247
- package/test/helpers/REVEmpty721Config.sol +0 -45
- package/test/mock/MockBuybackCashOutRecorder.sol +0 -84
- package/test/mock/MockBuybackDataHook.sol +0 -112
- package/test/mock/MockBuybackDataHookMintPath.sol +0 -68
- package/test/mock/MockSuckerRegistry.sol +0 -17
- package/test/regression/TestBurnPermissionRequired.t.sol +0 -294
- package/test/regression/TestCashOutBuybackFeeLeak.t.sol +0 -232
- package/test/regression/TestCrossRevnetLiquidation.t.sol +0 -255
- package/test/regression/TestCumulativeLoanCounter.t.sol +0 -361
- package/test/regression/TestLiquidateGapHandling.t.sol +0 -394
- package/test/regression/TestZeroPriceFeed.t.sol +0 -422
package/CHANGELOG.md
CHANGED
|
@@ -52,8 +52,8 @@ This file describes the verified change from `revnet-core-v5` to the current `re
|
|
|
52
52
|
- `IREVDeployer.deployFor(...)` now has overloads that return `(uint256, IJB721TiersHook)`.
|
|
53
53
|
- `IREVDeployer.BUYBACK_HOOK()`, `LOANS()`, and `OWNER()` are explicit v6 surface area.
|
|
54
54
|
- `IREVOwner` is a new interface and runtime counterpart to the deployer.
|
|
55
|
-
- `IREVHiddenTokens` is a new interface for temporary token hiding (burn to exclude from totalSupply, re-mint on reveal).
|
|
56
|
-
- `REVHiddenTokens` is a new standalone contract that lets holders temporarily hide tokens
|
|
55
|
+
- `IREVHiddenTokens` is a new interface for temporary token hiding (burn to exclude from live totalSupply, re-mint on reveal).
|
|
56
|
+
- `REVHiddenTokens` is a new standalone contract that lets holders temporarily hide tokens from visible/governance supply while `REVOwner` and `REVLoans` keep hidden balances in economic denominators.
|
|
57
57
|
- The old caller-supplied `REVBuybackHookConfig` path is no longer part of the deployer interface.
|
|
58
58
|
|
|
59
59
|
## Breaking ABI changes
|
package/README.md
CHANGED
|
@@ -19,7 +19,8 @@ This package provides:
|
|
|
19
19
|
- a deployer that launches Revnets and stores their long-lived configuration
|
|
20
20
|
- a runtime hook that mediates pay, cash-out, mint-permission, and delayed-cash-out behavior
|
|
21
21
|
- a loan system that burns token collateral on borrow and remints on repayment
|
|
22
|
-
- a hidden-token system that temporarily removes tokens from visible supply
|
|
22
|
+
- a hidden-token system that temporarily removes tokens from visible supply while preserving economic claim
|
|
23
|
+
denominators
|
|
23
24
|
|
|
24
25
|
It also composes with the 721 hook stack, buyback hook, router terminal, Croptop, and suckers where needed.
|
|
25
26
|
|
|
@@ -32,7 +33,7 @@ Use this repo when the product is a treasury-backed network with encoded stage t
|
|
|
32
33
|
| `REVDeployer` | Launches and configures Revnets, stages, split operators, and optional auxiliary features. |
|
|
33
34
|
| `REVOwner` | Runtime data-hook and cash-out-hook surface used by active Revnets. |
|
|
34
35
|
| `REVLoans` | Loan surface that lets users borrow against Revnet tokens with burned collateral and NFT loan positions. |
|
|
35
|
-
| `REVHiddenTokens` | Lets token holders temporarily hide tokens
|
|
36
|
+
| `REVHiddenTokens` | Lets token holders temporarily hide tokens from visible/governance supply until reveal, while cash-out and loan denominators still count hidden supply. |
|
|
36
37
|
|
|
37
38
|
## Mental Model
|
|
38
39
|
|
|
@@ -71,7 +72,7 @@ Most mistakes come from assuming a deploy-time parameter can be changed later or
|
|
|
71
72
|
2. `test/REVLoans.invariants.t.sol`
|
|
72
73
|
3. `test/TestLongTailEconomics.t.sol`
|
|
73
74
|
4. `test/fork/TestLoanBorrowFork.t.sol`
|
|
74
|
-
5. `test/audit/
|
|
75
|
+
5. `test/audit/PhantomSurplusTerminal.t.sol`
|
|
75
76
|
|
|
76
77
|
## Install
|
|
77
78
|
|
|
@@ -83,16 +84,14 @@ npm install @rev-net/core-v6
|
|
|
83
84
|
|
|
84
85
|
```bash
|
|
85
86
|
npm install
|
|
86
|
-
forge build
|
|
87
|
-
forge test
|
|
87
|
+
forge build --deny notes
|
|
88
|
+
forge test --deny notes
|
|
88
89
|
```
|
|
89
90
|
|
|
90
91
|
Useful scripts:
|
|
91
92
|
|
|
92
93
|
- `npm run deploy:mainnets`
|
|
93
94
|
- `npm run deploy:testnets`
|
|
94
|
-
- `npm run deploy:mainnets:1_1`
|
|
95
|
-
- `npm run deploy:testnets:1_1`
|
|
96
95
|
|
|
97
96
|
## Deployment Notes
|
|
98
97
|
|
package/foundry.toml
CHANGED
package/package.json
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rev-net/core-v6",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.40",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/rev-net/revnet-core-v6"
|
|
8
8
|
},
|
|
9
|
+
"files": [
|
|
10
|
+
"CHANGELOG.md",
|
|
11
|
+
"foundry.toml",
|
|
12
|
+
"references/",
|
|
13
|
+
"remappings.txt",
|
|
14
|
+
"script/Deploy.s.sol",
|
|
15
|
+
"script/helpers/",
|
|
16
|
+
"src/"
|
|
17
|
+
],
|
|
9
18
|
"engines": {
|
|
10
19
|
"node": ">=20.0.0"
|
|
11
20
|
},
|
|
@@ -13,26 +22,24 @@
|
|
|
13
22
|
"test": "forge test",
|
|
14
23
|
"coverage": "forge coverage --match-path \"./src/*.sol\" --report lcov --report summary",
|
|
15
24
|
"deploy:mainnets": "source ./.env && export START_TIME=$(date +%s) && npx sphinx propose ./script/Deploy.s.sol --networks mainnets",
|
|
16
|
-
"deploy:mainnets:1_1": "source ./.env && npx sphinx propose ./script/Deploy1_1.s.sol --networks mainnets",
|
|
17
25
|
"deploy:testnets": "source ./.env && export START_TIME=$(date +%s) && npx sphinx propose ./script/Deploy.s.sol --networks testnets",
|
|
18
|
-
"deploy:testnets:1_1": "source ./.env && npx sphinx propose ./script/Deploy1_1.s.sol --networks testnets",
|
|
19
26
|
"artifacts": "source ./.env && npx sphinx artifacts --org-id 'ea165b21-7cdc-4d7b-be59-ecdd4c26bee4' --project-name 'revnet-core-v6'"
|
|
20
27
|
},
|
|
21
28
|
"dependencies": {
|
|
22
|
-
"@bananapus/721-hook-v6": "
|
|
23
|
-
"@bananapus/buyback-hook-v6": "
|
|
24
|
-
"@bananapus/core-v6": "
|
|
25
|
-
"@bananapus/ownable-v6": "
|
|
26
|
-
"@bananapus/permission-ids-v6": "
|
|
27
|
-
"@bananapus/router-terminal-v6": "
|
|
28
|
-
"@bananapus/suckers-v6": "
|
|
29
|
-
"@croptop/core-v6": "
|
|
30
|
-
"@openzeppelin/contracts": "
|
|
31
|
-
"@uniswap/
|
|
32
|
-
"@uniswap/v4-periphery": "^1.0.3"
|
|
29
|
+
"@bananapus/721-hook-v6": "0.0.43",
|
|
30
|
+
"@bananapus/buyback-hook-v6": "0.0.37",
|
|
31
|
+
"@bananapus/core-v6": "0.0.39",
|
|
32
|
+
"@bananapus/ownable-v6": "0.0.24",
|
|
33
|
+
"@bananapus/permission-ids-v6": "0.0.22",
|
|
34
|
+
"@bananapus/router-terminal-v6": "0.0.36",
|
|
35
|
+
"@bananapus/suckers-v6": "0.0.33",
|
|
36
|
+
"@croptop/core-v6": "0.0.39",
|
|
37
|
+
"@openzeppelin/contracts": "5.6.1",
|
|
38
|
+
"@uniswap/permit2": "github:Uniswap/permit2#cc56ad0f3439c502c246fc5cfcc3db92bb8b7219"
|
|
33
39
|
},
|
|
34
40
|
"devDependencies": {
|
|
35
|
-
"@bananapus/address-registry-v6": "
|
|
36
|
-
"@sphinx-labs/plugins": "
|
|
41
|
+
"@bananapus/address-registry-v6": "0.0.25",
|
|
42
|
+
"@sphinx-labs/plugins": "0.33.3",
|
|
43
|
+
"@uniswap/v4-core": "1.0.2"
|
|
37
44
|
}
|
|
38
45
|
}
|
package/references/operations.md
CHANGED
|
@@ -143,7 +143,7 @@ Use this file when you need revnet-specific risks, state reads, constants, or ex
|
|
|
143
143
|
19. **39.16% cash-out tax crossover.** Below ~39% cash-out tax, cashing out is more capital-efficient than borrowing. Above ~39%, loans become more efficient because they preserve upside while providing liquidity. Based on CryptoEconLab academic research. Design implication: revnets intended for active token trading should consider this threshold when setting `cashOutTaxRate`.
|
|
144
144
|
20. **REVDeployer always deploys a 721 hook** via `HOOK_DEPLOYER.deployHookFor` — even if `baseline721HookConfiguration` has empty tiers. This is correct by design: it lets the split operator add and sell NFTs later without migration. Non-revnet projects should follow the same pattern by using `JB721TiersHookProjectDeployer.launchProjectFor` (or `JBOmnichainDeployer.launchProjectFor`) instead of bare `launchProjectFor`.
|
|
145
145
|
21. **REVOwner deployer binding is precomputed.** REVOwner records the account that created it as an internal one-time binder. That account must call `setDeployer(precomputedRevDeployerAddress)` exactly once before the canonical REVDeployer is deployed. This avoids an ambient public initializer while keeping the circular dependency manageable. If `setDeployer(...)` is never called, all DEPLOYER-gated runtime configuration breaks.
|
|
146
|
-
22. **Hidden tokens are economic, not cosmetic.** Hiding burns visible tokens and lowers visible supply until reveal.
|
|
146
|
+
22. **Hidden tokens are economic, not cosmetic.** Hiding burns visible tokens and lowers visible/governance supply until reveal. Cash-out and loan denominators still include hidden balances because they are recoverable claims.
|
|
147
147
|
|
|
148
148
|
### NATIVE_TOKEN Accounting on Non-ETH Chains
|
|
149
149
|
|
package/references/runtime.md
CHANGED
|
@@ -102,6 +102,6 @@ Deploy and manage Revnets -- autonomous, unowned Juicebox projects with staged i
|
|
|
102
102
|
|
|
103
103
|
| Function | Permissions | What it does |
|
|
104
104
|
|----------|------------|-------------|
|
|
105
|
-
| `REVHiddenTokens.hideTokensOf(revnetId, tokenCount, holder)` | Holder only. The holder must either be allowlisted or personally hold `HIDE_TOKENS`. | Burns visible tokens, increases hidden balance, and lowers visible supply. |
|
|
105
|
+
| `REVHiddenTokens.hideTokensOf(revnetId, tokenCount, holder)` | Holder only. The holder must either be allowlisted or personally hold `HIDE_TOKENS`. | Burns visible tokens, increases hidden balance, and lowers visible supply. Cash-out and loan denominators still include the hidden balance while it is revealable. |
|
|
106
106
|
| `REVHiddenTokens.revealTokensOf(revnetId, tokenCount, holder)` | Holder only | Re-mints previously hidden tokens back to the holder and reduces hidden balance. |
|
|
107
107
|
| `REVHiddenTokens.setTokenHidingAllowedFor(revnetId, holder, isAllowed)` | Operator with `HIDE_TOKENS` | Allows or revokes a holder's ability to hide their own tokens. |
|
package/script/Deploy.s.sol
CHANGED
|
@@ -289,14 +289,16 @@ contract DeployScript is Script, Sphinx {
|
|
|
289
289
|
if (block.chainid == 1 || block.chainid == 11_155_111) {
|
|
290
290
|
suckerDeployerConfigurations = new JBSuckerDeployerConfig[](3);
|
|
291
291
|
// OP
|
|
292
|
-
suckerDeployerConfigurations[0] =
|
|
293
|
-
|
|
292
|
+
suckerDeployerConfigurations[0] = JBSuckerDeployerConfig({
|
|
293
|
+
deployer: suckers.optimismDeployer, peer: bytes32(0), mappings: tokenMappings
|
|
294
|
+
});
|
|
294
295
|
|
|
295
296
|
suckerDeployerConfigurations[1] =
|
|
296
|
-
JBSuckerDeployerConfig({deployer: suckers.baseDeployer, mappings: tokenMappings});
|
|
297
|
+
JBSuckerDeployerConfig({deployer: suckers.baseDeployer, peer: bytes32(0), mappings: tokenMappings});
|
|
297
298
|
|
|
298
|
-
suckerDeployerConfigurations[2] =
|
|
299
|
-
|
|
299
|
+
suckerDeployerConfigurations[2] = JBSuckerDeployerConfig({
|
|
300
|
+
deployer: suckers.arbitrumDeployer, peer: bytes32(0), mappings: tokenMappings
|
|
301
|
+
});
|
|
300
302
|
} else {
|
|
301
303
|
suckerDeployerConfigurations = new JBSuckerDeployerConfig[](1);
|
|
302
304
|
// L2 -> Mainnet
|
|
@@ -304,6 +306,7 @@ contract DeployScript is Script, Sphinx {
|
|
|
304
306
|
deployer: address(suckers.optimismDeployer) != address(0)
|
|
305
307
|
? suckers.optimismDeployer
|
|
306
308
|
: address(suckers.baseDeployer) != address(0) ? suckers.baseDeployer : suckers.arbitrumDeployer,
|
|
309
|
+
peer: bytes32(0),
|
|
307
310
|
mappings: tokenMappings
|
|
308
311
|
});
|
|
309
312
|
|
|
@@ -493,8 +496,8 @@ contract DeployScript is Script, Sphinx {
|
|
|
493
496
|
directory: core.controller.DIRECTORY(),
|
|
494
497
|
feeRevnetId: FEE_PROJECT_ID,
|
|
495
498
|
suckerRegistry: suckers.registry,
|
|
496
|
-
loans:
|
|
497
|
-
hiddenTokens:
|
|
499
|
+
loans: revloans,
|
|
500
|
+
hiddenTokens: revHiddenTokens
|
|
498
501
|
});
|
|
499
502
|
|
|
500
503
|
// Deploy REVDeployer with the REVLoans, buyback hook, and REVOwner addresses.
|
|
@@ -510,7 +513,7 @@ contract DeployScript is Script, Sphinx {
|
|
|
510
513
|
hook.hook_deployer,
|
|
511
514
|
croptop.publisher,
|
|
512
515
|
IJBBuybackHookRegistry(address(buybackHook.registry)),
|
|
513
|
-
|
|
516
|
+
revloans,
|
|
514
517
|
TRUSTED_FORWARDER,
|
|
515
518
|
address(revOwner)
|
|
516
519
|
)
|
|
@@ -527,7 +530,7 @@ contract DeployScript is Script, Sphinx {
|
|
|
527
530
|
hookDeployer: hook.hook_deployer,
|
|
528
531
|
publisher: croptop.publisher,
|
|
529
532
|
buybackHook: IJBBuybackHookRegistry(address(buybackHook.registry)),
|
|
530
|
-
loans:
|
|
533
|
+
loans: revloans,
|
|
531
534
|
trustedForwarder: TRUSTED_FORWARDER,
|
|
532
535
|
owner: address(revOwner)
|
|
533
536
|
});
|
package/src/REVDeployer.sol
CHANGED
|
@@ -22,6 +22,7 @@ import {JBRuleset} from "@bananapus/core-v6/src/structs/JBRuleset.sol";
|
|
|
22
22
|
import {JBRulesetConfig} from "@bananapus/core-v6/src/structs/JBRulesetConfig.sol";
|
|
23
23
|
import {JBRulesetMetadata} from "@bananapus/core-v6/src/structs/JBRulesetMetadata.sol";
|
|
24
24
|
import {JBSplitGroup} from "@bananapus/core-v6/src/structs/JBSplitGroup.sol";
|
|
25
|
+
import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
|
|
25
26
|
import {JBTerminalConfig} from "@bananapus/core-v6/src/structs/JBTerminalConfig.sol";
|
|
26
27
|
import {JBPermissionIds} from "@bananapus/permission-ids-v6/src/JBPermissionIds.sol";
|
|
27
28
|
import {IJBSuckerRegistry} from "@bananapus/suckers-v6/src/interfaces/IJBSuckerRegistry.sol";
|
|
@@ -33,6 +34,7 @@ import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Recei
|
|
|
33
34
|
import {mulDiv, sqrt} from "@prb/math/src/Common.sol";
|
|
34
35
|
|
|
35
36
|
import {IREVDeployer} from "./interfaces/IREVDeployer.sol";
|
|
37
|
+
import {IREVLoans} from "./interfaces/IREVLoans.sol";
|
|
36
38
|
import {REVOwner} from "./REVOwner.sol";
|
|
37
39
|
import {REVAutoIssuance} from "./structs/REVAutoIssuance.sol";
|
|
38
40
|
import {REVConfig} from "./structs/REVConfig.sol";
|
|
@@ -41,8 +43,15 @@ import {REVDeploy721TiersHookConfig} from "./structs/REVDeploy721TiersHookConfig
|
|
|
41
43
|
import {REVStageConfig} from "./structs/REVStageConfig.sol";
|
|
42
44
|
import {REVSuckerDeploymentConfig} from "./structs/REVSuckerDeploymentConfig.sol";
|
|
43
45
|
|
|
44
|
-
/// @notice
|
|
45
|
-
///
|
|
46
|
+
/// @notice Deploys and configures Revnets — autonomous Juicebox projects with pre-programmed tokenomics that cannot
|
|
47
|
+
/// be
|
|
48
|
+
/// changed after launch. Each revnet progresses through stages that define issuance rate, decay schedule, cash-out tax,
|
|
49
|
+
/// split allocations, and auto-issuances. The deployer translates these stage configurations into Juicebox rulesets,
|
|
50
|
+
/// sets up a buyback hook for secondary market routing, deploys a tiered 721 hook, optionally configures Croptop
|
|
51
|
+
/// posting, and can deploy cross-chain suckers. Once deployed, the project NFT is held by this contract — no single
|
|
52
|
+
/// address can modify the revnet's rules.
|
|
53
|
+
/// @dev Revnets are unowned Juicebox projects which operate autonomously after deployment. Runtime data hook logic
|
|
54
|
+
/// (pay/cash-out callbacks) is handled by the separate `REVOwner` contract to stay within EIP-170 size limits.
|
|
46
55
|
contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
|
|
47
56
|
//*********************************************************************//
|
|
48
57
|
// --------------------------- custom errors ------------------------- //
|
|
@@ -110,7 +119,7 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
|
|
|
110
119
|
/// @notice The loan contract used by all revnets.
|
|
111
120
|
/// @dev Revnets can offer loans to their participants, collateralized by their tokens.
|
|
112
121
|
/// Participants can borrow up to the current cash out value of their tokens.
|
|
113
|
-
|
|
122
|
+
IREVLoans public immutable override LOANS;
|
|
114
123
|
|
|
115
124
|
/// @notice The runtime data hook contract that handles pay and cash out callbacks for revnets.
|
|
116
125
|
/// @dev Set as `dataHook` in each revnet's ruleset metadata. Implements `IJBRulesetDataHook` and `IJBCashOutHook`.
|
|
@@ -178,7 +187,7 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
|
|
|
178
187
|
IJB721TiersHookDeployer hookDeployer,
|
|
179
188
|
CTPublisher publisher,
|
|
180
189
|
IJBBuybackHookRegistry buybackHook,
|
|
181
|
-
|
|
190
|
+
IREVLoans loans,
|
|
182
191
|
address trustedForwarder,
|
|
183
192
|
address owner
|
|
184
193
|
)
|
|
@@ -193,20 +202,14 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
|
|
|
193
202
|
HOOK_DEPLOYER = hookDeployer;
|
|
194
203
|
PUBLISHER = publisher;
|
|
195
204
|
BUYBACK_HOOK = buybackHook;
|
|
196
|
-
// slither-disable-next-line missing-zero-check
|
|
197
205
|
LOANS = loans;
|
|
198
206
|
// slither-disable-next-line missing-zero-check
|
|
199
207
|
OWNER = owner;
|
|
200
208
|
|
|
201
|
-
// Give the sucker registry permission to map tokens for all revnets.
|
|
202
|
-
_setPermission({
|
|
203
|
-
operator: address(SUCKER_REGISTRY), revnetId: 0, permissionId: JBPermissionIds.MAP_SUCKER_TOKEN
|
|
204
|
-
});
|
|
205
|
-
|
|
206
209
|
// Give the loan contract permission to use the surplus allowance of all revnets.
|
|
207
210
|
// Uses wildcard revnetId=0 intentionally — the loan contract is a singleton shared by all revnets,
|
|
208
211
|
// and each revnet's surplus allowance limits already constrain how much can be drawn.
|
|
209
|
-
_setPermission({operator: LOANS, revnetId: 0, permissionId: JBPermissionIds.USE_ALLOWANCE});
|
|
212
|
+
_setPermission({operator: address(LOANS), revnetId: 0, permissionId: JBPermissionIds.USE_ALLOWANCE});
|
|
210
213
|
|
|
211
214
|
// Give the buyback hook (registry) permission to configure pools on all revnets.
|
|
212
215
|
_setPermission({operator: address(BUYBACK_HOOK), revnetId: 0, permissionId: JBPermissionIds.SET_BUYBACK_POOL});
|
|
@@ -356,12 +359,6 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
|
|
|
356
359
|
});
|
|
357
360
|
}
|
|
358
361
|
|
|
359
|
-
/// @notice Returns the next project ID.
|
|
360
|
-
/// @return nextProjectId The next project ID.
|
|
361
|
-
function _nextProjectId() internal view returns (uint256) {
|
|
362
|
-
return PROJECTS.count() + 1;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
362
|
/// @notice Returns the permissions that the split operator should be granted for a revnet.
|
|
366
363
|
/// @param revnetId The ID of the revnet to get split operator permissions for.
|
|
367
364
|
/// @return allOperatorPermissions The permissions that the split operator should be granted for the revnet,
|
|
@@ -375,22 +372,21 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
|
|
|
375
372
|
uint256[] memory customSplitOperatorPermissionIndexes = _extraOperatorPermissions[revnetId];
|
|
376
373
|
|
|
377
374
|
// Make the array that merges the default and custom operator permissions.
|
|
378
|
-
allOperatorPermissions = new uint256[](
|
|
375
|
+
allOperatorPermissions = new uint256[](10 + customSplitOperatorPermissionIndexes.length);
|
|
379
376
|
allOperatorPermissions[0] = JBPermissionIds.SET_SPLIT_GROUPS;
|
|
380
377
|
allOperatorPermissions[1] = JBPermissionIds.SET_BUYBACK_POOL;
|
|
381
378
|
allOperatorPermissions[2] = JBPermissionIds.SET_BUYBACK_TWAP;
|
|
382
379
|
allOperatorPermissions[3] = JBPermissionIds.SET_PROJECT_URI;
|
|
383
|
-
allOperatorPermissions[4] = JBPermissionIds.
|
|
384
|
-
allOperatorPermissions[5] = JBPermissionIds.
|
|
385
|
-
allOperatorPermissions[6] = JBPermissionIds.
|
|
386
|
-
allOperatorPermissions[7] = JBPermissionIds.
|
|
387
|
-
allOperatorPermissions[8] = JBPermissionIds.
|
|
388
|
-
allOperatorPermissions[9] = JBPermissionIds.
|
|
389
|
-
allOperatorPermissions[10] = JBPermissionIds.HIDE_TOKENS;
|
|
380
|
+
allOperatorPermissions[4] = JBPermissionIds.SUCKER_SAFETY;
|
|
381
|
+
allOperatorPermissions[5] = JBPermissionIds.SET_BUYBACK_HOOK;
|
|
382
|
+
allOperatorPermissions[6] = JBPermissionIds.SET_ROUTER_TERMINAL;
|
|
383
|
+
allOperatorPermissions[7] = JBPermissionIds.SET_TOKEN_METADATA;
|
|
384
|
+
allOperatorPermissions[8] = JBPermissionIds.SIGN_FOR_ERC20;
|
|
385
|
+
allOperatorPermissions[9] = JBPermissionIds.HIDE_TOKENS;
|
|
390
386
|
|
|
391
387
|
// Copy the custom permissions into the array.
|
|
392
388
|
for (uint256 i; i < customSplitOperatorPermissionIndexes.length;) {
|
|
393
|
-
allOperatorPermissions[
|
|
389
|
+
allOperatorPermissions[10 + i] = customSplitOperatorPermissionIndexes[i];
|
|
394
390
|
unchecked {
|
|
395
391
|
++i;
|
|
396
392
|
}
|
|
@@ -510,6 +506,8 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
|
|
|
510
506
|
/// @param allowedPosts Restrictions on which croptop posts are allowed on the revnet's ERC-721 tiers.
|
|
511
507
|
/// @return revnetId The ID of the newly created revnet.
|
|
512
508
|
/// @return hook The address of the tiered ERC-721 hook that was deployed for the revnet.
|
|
509
|
+
// The deployment flow makes external setup calls, but any observed state is revnet-scoped and reverts atomically.
|
|
510
|
+
// slither-disable-next-line reentrancy-benign
|
|
513
511
|
function deployFor(
|
|
514
512
|
uint256 revnetId,
|
|
515
513
|
REVConfig calldata configuration,
|
|
@@ -525,9 +523,8 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
|
|
|
525
523
|
// Keep a reference to the revnet ID which was passed in.
|
|
526
524
|
bool shouldDeployNewRevnet = revnetId == 0;
|
|
527
525
|
|
|
528
|
-
// If the caller is deploying a new revnet,
|
|
529
|
-
|
|
530
|
-
if (shouldDeployNewRevnet) revnetId = _nextProjectId();
|
|
526
|
+
// If the caller is deploying a new revnet, reserve its project ID before deriving hook/sucker config.
|
|
527
|
+
if (shouldDeployNewRevnet) revnetId = PROJECTS.createFor(address(this));
|
|
531
528
|
|
|
532
529
|
// Deploy the revnet with the specified tiered ERC-721 hook and croptop posting criteria.
|
|
533
530
|
hook = _deploy721RevnetFor({
|
|
@@ -544,6 +541,8 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
|
|
|
544
541
|
}
|
|
545
542
|
|
|
546
543
|
/// @inheritdoc IREVDeployer
|
|
544
|
+
// The deployment flow makes external setup calls, but any observed state is revnet-scoped and reverts atomically.
|
|
545
|
+
// slither-disable-next-line reentrancy-benign
|
|
547
546
|
function deployFor(
|
|
548
547
|
uint256 revnetId,
|
|
549
548
|
REVConfig calldata configuration,
|
|
@@ -555,7 +554,7 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
|
|
|
555
554
|
returns (uint256, IJB721TiersHook hook)
|
|
556
555
|
{
|
|
557
556
|
bool shouldDeployNewRevnet = revnetId == 0;
|
|
558
|
-
if (shouldDeployNewRevnet) revnetId =
|
|
557
|
+
if (shouldDeployNewRevnet) revnetId = PROJECTS.createFor(address(this));
|
|
559
558
|
|
|
560
559
|
// Deploy the revnet (project, rulesets, ERC-20, suckers, etc.).
|
|
561
560
|
bytes32 encodedConfigurationHash = _deployRevnetFor({
|
|
@@ -583,6 +582,8 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
|
|
|
583
582
|
REVOwner(OWNER).setTiered721HookOf({revnetId: revnetId, hook: hook});
|
|
584
583
|
|
|
585
584
|
// Grant the split operator all 721 permissions (no prevent* flags for default config).
|
|
585
|
+
// These permission IDs are only consumed by `_setSplitOperatorOf` below, after revnet setup has either
|
|
586
|
+
// completed or reverted atomically.
|
|
586
587
|
_extraOperatorPermissions[revnetId].push(JBPermissionIds.ADJUST_721_TIERS);
|
|
587
588
|
_extraOperatorPermissions[revnetId].push(JBPermissionIds.SET_721_METADATA);
|
|
588
589
|
_extraOperatorPermissions[revnetId].push(JBPermissionIds.MINT_721);
|
|
@@ -656,6 +657,8 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
|
|
|
656
657
|
//*********************************************************************//
|
|
657
658
|
|
|
658
659
|
/// @notice Deploy a revnet which sells tiered ERC-721s and (optionally) allows croptop posts to its ERC-721 tiers.
|
|
660
|
+
// The helper performs external hook/post setup after core revnet setup; any failure reverts the whole deployment.
|
|
661
|
+
// slither-disable-next-line reentrancy-benign
|
|
659
662
|
function _deploy721RevnetFor(
|
|
660
663
|
uint256 revnetId,
|
|
661
664
|
bool shouldDeployNewRevnet,
|
|
@@ -706,6 +709,9 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
|
|
|
706
709
|
// Store the tiered ERC-721 hook in the owner contract.
|
|
707
710
|
REVOwner(OWNER).setTiered721HookOf({revnetId: revnetId, hook: hook});
|
|
708
711
|
|
|
712
|
+
// These permission IDs are only consumed by `_setSplitOperatorOf` below, after revnet setup has either
|
|
713
|
+
// completed or reverted atomically.
|
|
714
|
+
|
|
709
715
|
// Give the split operator permission to add and remove tiers unless prevented.
|
|
710
716
|
if (!tiered721HookConfiguration.preventSplitOperatorAdjustingTiers) {
|
|
711
717
|
_extraOperatorPermissions[revnetId].push(JBPermissionIds.ADJUST_721_TIERS);
|
|
@@ -773,8 +779,8 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
|
|
|
773
779
|
/// uninitialized.
|
|
774
780
|
/// - The project's JBProjects NFT is permanently transferred to this contract. This is irreversible.
|
|
775
781
|
/// @param revnetId The ID of the Juicebox project to initialize as a revnet. Send 0 to deploy a new revnet.
|
|
776
|
-
/// @param shouldDeployNewRevnet Whether
|
|
777
|
-
/// revnet.
|
|
782
|
+
/// @param shouldDeployNewRevnet Whether the revnet ID was reserved by this deployment call, or the caller is
|
|
783
|
+
/// converting an existing Juicebox project into a revnet.
|
|
778
784
|
/// @param configuration Core revnet configuration. See `REVConfig`.
|
|
779
785
|
/// @param terminalConfigurations The terminals to set up for the revnet. Used for payments and cash outs.
|
|
780
786
|
/// @param suckerDeploymentConfiguration The suckers to set up for the revnet. Suckers facilitate cross-chain
|
|
@@ -795,43 +801,37 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
|
|
|
795
801
|
(rulesetConfigurations, encodedConfigurationHash) = _makeRulesetConfigurations({
|
|
796
802
|
revnetId: revnetId, configuration: configuration, terminalConfigurations: terminalConfigurations
|
|
797
803
|
});
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
// slither-disable-next-line incorrect-equality,reentrancy-benign,reentrancy-events
|
|
802
|
-
assert(
|
|
803
|
-
CONTROLLER.launchProjectFor({
|
|
804
|
-
owner: address(this),
|
|
805
|
-
projectUri: configuration.description.uri,
|
|
806
|
-
rulesetConfigurations: rulesetConfigurations,
|
|
807
|
-
terminalConfigurations: terminalConfigurations,
|
|
808
|
-
memo: ""
|
|
809
|
-
}) == revnetId
|
|
810
|
-
);
|
|
811
|
-
} else {
|
|
804
|
+
|
|
805
|
+
address owner;
|
|
806
|
+
if (!shouldDeployNewRevnet) {
|
|
812
807
|
// Keep a reference to the Juicebox project's owner.
|
|
813
|
-
|
|
808
|
+
owner = PROJECTS.ownerOf(revnetId);
|
|
814
809
|
|
|
815
810
|
// Make sure the caller is the owner of the Juicebox project.
|
|
816
811
|
if (_msgSender() != owner) revert REVDeployer_Unauthorized(revnetId, _msgSender());
|
|
812
|
+
}
|
|
817
813
|
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
// Launch the revnet rulesets for the pre-existing project.
|
|
823
|
-
// slither-disable-next-line unused-return
|
|
824
|
-
CONTROLLER.launchRulesetsFor({
|
|
825
|
-
projectId: revnetId,
|
|
826
|
-
rulesetConfigurations: rulesetConfigurations,
|
|
827
|
-
terminalConfigurations: terminalConfigurations,
|
|
828
|
-
memo: ""
|
|
829
|
-
});
|
|
814
|
+
// Store the hash before setup callbacks so reentrant readers cannot observe a zero configuration hash. Any
|
|
815
|
+
// subsequent revert rolls this write back.
|
|
816
|
+
hashedEncodedConfigurationOf[revnetId] = encodedConfigurationHash;
|
|
830
817
|
|
|
831
|
-
|
|
832
|
-
|
|
818
|
+
if (!shouldDeployNewRevnet) {
|
|
819
|
+
// Initialize the existing Juicebox project as a revnet by transferring the `JBProjects` NFT to this
|
|
820
|
+
// deployer. This is irreversible.
|
|
821
|
+
// slither-disable-next-line reentrancy-benign
|
|
822
|
+
IERC721(PROJECTS).safeTransferFrom({from: owner, to: address(this), tokenId: revnetId});
|
|
833
823
|
}
|
|
834
824
|
|
|
825
|
+
// Launch the revnet rulesets for the reserved or pre-existing blank project.
|
|
826
|
+
// slither-disable-next-line unused-return
|
|
827
|
+
CONTROLLER.launchRulesetsFor({
|
|
828
|
+
projectId: revnetId,
|
|
829
|
+
projectUri: configuration.description.uri,
|
|
830
|
+
rulesetConfigurations: rulesetConfigurations,
|
|
831
|
+
terminalConfigurations: terminalConfigurations,
|
|
832
|
+
memo: ""
|
|
833
|
+
});
|
|
834
|
+
|
|
835
835
|
// Store the cash out delay of the revnet if its stages are already in progress.
|
|
836
836
|
// This prevents cash out liquidity/arbitrage issues for existing revnets which
|
|
837
837
|
// are deploying to a new chain.
|
|
@@ -875,9 +875,6 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
|
|
|
875
875
|
});
|
|
876
876
|
}
|
|
877
877
|
|
|
878
|
-
// Store the hashed encoded configuration.
|
|
879
|
-
hashedEncodedConfigurationOf[revnetId] = encodedConfigurationHash;
|
|
880
|
-
|
|
881
878
|
emit DeployRevnet({
|
|
882
879
|
revnetId: revnetId,
|
|
883
880
|
configuration: configuration,
|
|
@@ -908,7 +905,8 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
|
|
|
908
905
|
caller: _msgSender()
|
|
909
906
|
});
|
|
910
907
|
|
|
911
|
-
//
|
|
908
|
+
// Include the caller so two revnets with identical configuration and user salt cannot collide. Same-address
|
|
909
|
+
// cross-chain deployment still works when the same operator calls this helper on each chain.
|
|
912
910
|
// slither-disable-next-line unused-return
|
|
913
911
|
suckers = SUCKER_REGISTRY.deploySuckersFor({
|
|
914
912
|
projectId: revnetId,
|
|
@@ -1014,7 +1012,10 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
|
|
|
1014
1012
|
fundAccessLimitGroups: fundAccessLimitGroups
|
|
1015
1013
|
});
|
|
1016
1014
|
|
|
1017
|
-
// Add the stage's
|
|
1015
|
+
// Add the stage's immutable economics to the byte-encoded configuration. `extraMetadata` is Juicebox
|
|
1016
|
+
// ruleset metadata forwarded to the revnet data hook, so it remains part of the revnet identity.
|
|
1017
|
+
// Reserved-token split recipients and individual weights are intentionally excluded below: the split
|
|
1018
|
+
// limit (`splitPercent`) is the economic commitment, while split routing can change over time.
|
|
1018
1019
|
encodedConfiguration = abi.encode(
|
|
1019
1020
|
encodedConfiguration,
|
|
1020
1021
|
// Use the effective start time (normalized from 0 to block.timestamp for the first stage).
|
|
@@ -1024,7 +1025,8 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
|
|
|
1024
1025
|
stageConfiguration.initialIssuance,
|
|
1025
1026
|
stageConfiguration.issuanceCutFrequency,
|
|
1026
1027
|
stageConfiguration.issuanceCutPercent,
|
|
1027
|
-
stageConfiguration.cashOutTaxRate
|
|
1028
|
+
stageConfiguration.cashOutTaxRate,
|
|
1029
|
+
stageConfiguration.extraMetadata
|
|
1028
1030
|
);
|
|
1029
1031
|
|
|
1030
1032
|
// Add each auto-mint to the byte-encoded representation.
|
package/src/REVHiddenTokens.sol
CHANGED
|
@@ -13,8 +13,8 @@ import {Context} from "@openzeppelin/contracts/utils/Context.sol";
|
|
|
13
13
|
import {IREVHiddenTokens} from "./interfaces/IREVHiddenTokens.sol";
|
|
14
14
|
|
|
15
15
|
/// @notice Allows authorized operators to hide (burn) revnet tokens on behalf of holders, excluding them from
|
|
16
|
-
/// governance weight. Hidden tokens are burned from circulating supply,
|
|
17
|
-
///
|
|
16
|
+
/// governance weight. Hidden tokens are burned from live circulating supply, but `REVOwner` and `REVLoans` add the
|
|
17
|
+
/// hidden supply back into their economic denominators while those tokens remain revealable.
|
|
18
18
|
/// Hidden tokens can be revealed (re-minted) at any time.
|
|
19
19
|
contract REVHiddenTokens is ERC2771Context, JBPermissioned, IREVHiddenTokens {
|
|
20
20
|
//*********************************************************************//
|
package/src/REVLoans.sol
CHANGED
|
@@ -35,18 +35,15 @@ import {IREVOwner} from "./interfaces/IREVOwner.sol";
|
|
|
35
35
|
import {REVLoan} from "./structs/REVLoan.sol";
|
|
36
36
|
import {REVLoanSource} from "./structs/REVLoanSource.sol";
|
|
37
37
|
|
|
38
|
-
/// @notice
|
|
39
|
-
///
|
|
40
|
-
/// structure orderly.
|
|
41
|
-
/// @dev
|
|
42
|
-
///
|
|
43
|
-
///
|
|
44
|
-
///
|
|
45
|
-
///
|
|
46
|
-
///
|
|
47
|
-
/// After the prepaid duration, the loan will increasingly cost more to pay off. After 10 years, the loan collateral
|
|
48
|
-
/// cannot be
|
|
49
|
-
/// recouped.
|
|
38
|
+
/// @notice Allows revnet token holders to borrow against their tokens instead of cashing out. The borrowable amount
|
|
39
|
+
/// equals what a cash-out would return. Collateral tokens are burned on borrow and re-minted on repayment, keeping the
|
|
40
|
+
/// revnet's token structure orderly. Each loan is represented as an ERC-721 NFT that can be transferred.
|
|
41
|
+
/// @dev Fee structure: an upfront fee is taken at borrow time. 2.5% goes to the source revnet
|
|
42
|
+
/// (MIN_PREPAID_FEE_PERCENT), 1% goes to the $REV revnet (REV_PREPAID_FEE_PERCENT), and a variable amount chosen by the
|
|
43
|
+
/// borrower determines the
|
|
44
|
+
/// prepaid duration — the more paid upfront, the longer the borrower can hold without additional cost. After the
|
|
45
|
+
/// prepaid duration expires, the repayment cost increases linearly until the loan liquidates at 10 years
|
|
46
|
+
/// (LOAN_LIQUIDATION_DURATION), at which point the collateral is permanently lost.
|
|
50
47
|
/// @dev The loaned amounts include the fees taken, meaning the amount paid back is the amount borrowed plus the fees.
|
|
51
48
|
contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans {
|
|
52
49
|
// A library that parses the packed ruleset metadata into a friendlier format.
|
|
@@ -359,10 +356,11 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
|
|
|
359
356
|
// Get the total amount of tokens in circulation.
|
|
360
357
|
uint256 totalSupply = CONTROLLER.totalTokenSupplyWithReservedTokensOf(revnetId);
|
|
361
358
|
|
|
362
|
-
// Get a
|
|
359
|
+
// Get a reference to the collateral being used to secure loans.
|
|
363
360
|
uint256 totalCollateral = totalCollateralOf[revnetId];
|
|
364
361
|
|
|
365
|
-
//
|
|
362
|
+
// Hidden tokens are intentionally excluded from borrowing math. Operators can hide tokens as a security
|
|
363
|
+
// handle without changing the fair loan market for visible token holders.
|
|
366
364
|
uint256 localSupply = totalSupply + totalCollateral;
|
|
367
365
|
|
|
368
366
|
// The local surplus includes both the treasury surplus and the outstanding borrowed amounts.
|
|
@@ -469,10 +467,13 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
|
|
|
469
467
|
revert REVLoans_LoanExpired(timeSinceLoanCreated, LOAN_LIQUIDATION_DURATION);
|
|
470
468
|
}
|
|
471
469
|
|
|
472
|
-
// Get a reference to the amount prepaid for the full loan.
|
|
473
|
-
|
|
470
|
+
// Get a reference to the amount prepaid for the full loan. This is an app-level loan fee, so keep it
|
|
471
|
+
// floor-rounded instead of applying the protocol fee helper's dust minimum.
|
|
472
|
+
uint256 prepaid = JBFees.feeAmountFromFloor({amountBeforeFee: loan.amount, feePercent: loan.prepaidFeePercent});
|
|
474
473
|
|
|
475
|
-
|
|
474
|
+
// This source fee ramps with elapsed time. Use the floor-rounded fee helper so a one-second elapsed window
|
|
475
|
+
// with zero fee percent stays free instead of inheriting the protocol fee helper's anti-dust minimum.
|
|
476
|
+
uint256 fullSourceFeeAmount = JBFees.feeAmountFromFloor({
|
|
476
477
|
amountBeforeFee: loan.amount - prepaid,
|
|
477
478
|
feePercent: mulDiv({
|
|
478
479
|
x: timeSinceLoanCreated - loan.prepaidDuration,
|
|
@@ -1017,10 +1018,11 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
|
|
|
1017
1018
|
// Keep a reference to the fee terminal.
|
|
1018
1019
|
IJBTerminal feeTerminal = DIRECTORY.primaryTerminalOf({projectId: REV_ID, token: loan.source.token});
|
|
1019
1020
|
|
|
1020
|
-
// Get the amount of additional fee to take for REV.
|
|
1021
|
+
// Get the amount of additional fee to take for REV. This is an app-level loan fee, not the terminal's
|
|
1022
|
+
// protocol fee, so keep it floor-rounded instead of applying the protocol fee helper's dust minimum.
|
|
1021
1023
|
uint256 revFeeAmount = address(feeTerminal) == address(0)
|
|
1022
1024
|
? 0
|
|
1023
|
-
: JBFees.
|
|
1025
|
+
: JBFees.feeAmountFromFloor({amountBeforeFee: addedBorrowAmount, feePercent: REV_PREPAID_FEE_PERCENT});
|
|
1024
1026
|
|
|
1025
1027
|
// Try to pay the REV fee. If it fails, revFeeAmount is zeroed so the borrower receives it instead.
|
|
1026
1028
|
if (revFeeAmount > 0) {
|
|
@@ -1238,9 +1240,11 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
|
|
|
1238
1240
|
// Make sure the minimum borrow amount is met.
|
|
1239
1241
|
if (borrowAmount < minBorrowAmount) revert REVLoans_UnderMinBorrowAmount(minBorrowAmount, borrowAmount);
|
|
1240
1242
|
|
|
1241
|
-
// Get the amount of additional fee to take for the revnet issuing the loan.
|
|
1242
|
-
//
|
|
1243
|
-
|
|
1243
|
+
// Get the amount of additional fee to take for the revnet issuing the loan. This is an app-level loan fee,
|
|
1244
|
+
// not the terminal's protocol fee, so keep it floor-rounded instead of applying the protocol fee helper's dust
|
|
1245
|
+
// minimum.
|
|
1246
|
+
uint256 sourceFeeAmount =
|
|
1247
|
+
JBFees.feeAmountFromFloor({amountBeforeFee: borrowAmount, feePercent: prepaidFeePercent});
|
|
1244
1248
|
|
|
1245
1249
|
// Borrow the amount.
|
|
1246
1250
|
_adjust({
|