@rev-net/core-v6 0.0.36 → 0.0.39
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 +60 -65
- package/src/REVHiddenTokens.sol +2 -2
- package/src/REVLoans.sol +134 -90
- package/src/REVOwner.sol +124 -17
- package/src/interfaces/IREVDeployer.sol +2 -1
- package/src/interfaces/IREVHiddenTokens.sol +4 -1
- package/src/interfaces/IREVOwner.sol +5 -0
- package/ADMINISTRATION.md +0 -73
- package/ARCHITECTURE.md +0 -116
- package/AUDIT_INSTRUCTIONS.md +0 -90
- package/RISKS.md +0 -97
- 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 -368
- 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/CodexCrossChainBuybackRouteMismatch.t.sol +0 -184
- package/test/audit/CodexPhantomSurplusTerminal.t.sol +0 -367
- package/test/audit/CodexREVOwnerRemoteSurplusCurrencyMismatch.t.sol +0 -142
- package/test/audit/LoanIdOverflowGuard.t.sol +0 -523
- package/test/audit/NemesisOperatorDelegation.t.sol +0 -356
- 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/TestLoanBorrowFork.t.sol +0 -163
- package/test/fork/TestLoanCrossRulesetFork.t.sol +0 -308
- package/test/fork/TestLoanERC20Fork.t.sol +0 -465
- 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/src/REVLoans.sol
CHANGED
|
@@ -359,10 +359,11 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
|
|
|
359
359
|
// Get the total amount of tokens in circulation.
|
|
360
360
|
uint256 totalSupply = CONTROLLER.totalTokenSupplyWithReservedTokensOf(revnetId);
|
|
361
361
|
|
|
362
|
-
// Get a
|
|
362
|
+
// Get a reference to the collateral being used to secure loans.
|
|
363
363
|
uint256 totalCollateral = totalCollateralOf[revnetId];
|
|
364
364
|
|
|
365
|
-
//
|
|
365
|
+
// Hidden tokens are intentionally excluded from borrowing math. Operators can hide tokens as a security
|
|
366
|
+
// handle without changing the fair loan market for visible token holders.
|
|
366
367
|
uint256 localSupply = totalSupply + totalCollateral;
|
|
367
368
|
|
|
368
369
|
// The local surplus includes both the treasury surplus and the outstanding borrowed amounts.
|
|
@@ -469,10 +470,13 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
|
|
|
469
470
|
revert REVLoans_LoanExpired(timeSinceLoanCreated, LOAN_LIQUIDATION_DURATION);
|
|
470
471
|
}
|
|
471
472
|
|
|
472
|
-
// Get a reference to the amount prepaid for the full loan.
|
|
473
|
-
|
|
473
|
+
// Get a reference to the amount prepaid for the full loan. This is an app-level loan fee, so keep it
|
|
474
|
+
// floor-rounded instead of applying the protocol fee helper's dust minimum.
|
|
475
|
+
uint256 prepaid = JBFees.feeAmountFromFloor({amountBeforeFee: loan.amount, feePercent: loan.prepaidFeePercent});
|
|
474
476
|
|
|
475
|
-
|
|
477
|
+
// This source fee ramps with elapsed time. Use the floor-rounded fee helper so a one-second elapsed window
|
|
478
|
+
// with zero fee percent stays free instead of inheriting the protocol fee helper's anti-dust minimum.
|
|
479
|
+
uint256 fullSourceFeeAmount = JBFees.feeAmountFromFloor({
|
|
476
480
|
amountBeforeFee: loan.amount - prepaid,
|
|
477
481
|
feePercent: mulDiv({
|
|
478
482
|
x: timeSinceLoanCreated - loan.prepaidDuration,
|
|
@@ -627,93 +631,15 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
|
|
|
627
631
|
// Note: the operator controls `beneficiary`, so they can direct borrowed funds to any address.
|
|
628
632
|
_requirePermissionFrom({account: holder, projectId: revnetId, permissionId: JBPermissionIds.OPEN_LOAN});
|
|
629
633
|
|
|
630
|
-
|
|
631
|
-
if (collateralCount == 0) revert REVLoans_ZeroCollateralLoanIsInvalid();
|
|
632
|
-
|
|
633
|
-
// Make sure the source terminal is registered in the directory for this revnet.
|
|
634
|
-
if (!DIRECTORY.isTerminalOf({projectId: revnetId, terminal: IJBTerminal(address(source.terminal))})) {
|
|
635
|
-
revert REVLoans_InvalidTerminal(address(source.terminal), revnetId);
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
// Make sure the prepaid fee percent is between `MIN_PREPAID_FEE_PERCENT` and `MAX_PREPAID_FEE_PERCENT`. Meaning
|
|
639
|
-
// an 16 year loan can be paid upfront with a
|
|
640
|
-
// payment of 50% of the borrowed assets, the cheapest possible rate.
|
|
641
|
-
if (prepaidFeePercent < MIN_PREPAID_FEE_PERCENT || prepaidFeePercent > MAX_PREPAID_FEE_PERCENT) {
|
|
642
|
-
revert REVLoans_InvalidPrepaidFeePercent(
|
|
643
|
-
prepaidFeePercent, MIN_PREPAID_FEE_PERCENT, MAX_PREPAID_FEE_PERCENT
|
|
644
|
-
);
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
// Cache the current ruleset once — used by both _cashOutDelayOf and _borrowAmountFrom.
|
|
648
|
-
JBRuleset memory currentRuleset = _currentRulesetOf(revnetId);
|
|
649
|
-
|
|
650
|
-
// Enforce the cash out delay.
|
|
651
|
-
{
|
|
652
|
-
uint256 cashOutDelay = _cashOutDelayOf({revnetId: revnetId, currentRuleset: currentRuleset});
|
|
653
|
-
if (cashOutDelay > block.timestamp) {
|
|
654
|
-
revert REVLoans_CashOutDelayNotFinished(cashOutDelay, block.timestamp);
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
// Prevent the loan number from exceeding the ID namespace for this revnet.
|
|
659
|
-
if (totalLoansBorrowedFor[revnetId] >= _ONE_TRILLION) revert REVLoans_LoanIdOverflow();
|
|
660
|
-
|
|
661
|
-
// Get a reference to the loan ID.
|
|
662
|
-
loanId = _generateLoanId({revnetId: revnetId, loanNumber: ++totalLoansBorrowedFor[revnetId]});
|
|
663
|
-
|
|
664
|
-
// Get a reference to the loan being created.
|
|
665
|
-
REVLoan storage loan = _loanOf[loanId];
|
|
666
|
-
|
|
667
|
-
// Set the loan's values.
|
|
668
|
-
loan.source = source;
|
|
669
|
-
loan.createdAt = uint48(block.timestamp);
|
|
670
|
-
// forge-lint: disable-next-line(unsafe-typecast)
|
|
671
|
-
loan.prepaidFeePercent = uint16(prepaidFeePercent);
|
|
672
|
-
loan.prepaidDuration =
|
|
673
|
-
uint32(mulDiv({x: prepaidFeePercent, y: LOAN_LIQUIDATION_DURATION, denominator: MAX_PREPAID_FEE_PERCENT}));
|
|
674
|
-
|
|
675
|
-
// Get the amount of the loan, using the cached ruleset.
|
|
676
|
-
uint256 borrowAmount = _borrowAmountFrom({
|
|
677
|
-
loan: loan, revnetId: revnetId, collateralCount: collateralCount, currentRuleset: currentRuleset
|
|
678
|
-
});
|
|
679
|
-
|
|
680
|
-
// Revert if the bonding curve returns zero to prevent creating zero-amount loans.
|
|
681
|
-
if (borrowAmount == 0) revert REVLoans_ZeroBorrowAmount();
|
|
682
|
-
|
|
683
|
-
// Make sure the minimum borrow amount is met.
|
|
684
|
-
if (borrowAmount < minBorrowAmount) revert REVLoans_UnderMinBorrowAmount(minBorrowAmount, borrowAmount);
|
|
685
|
-
|
|
686
|
-
// Get the amount of additional fee to take for the revnet issuing the loan.
|
|
687
|
-
// Fee rounding may leave a few wei of dust — economically insignificant relative to gas costs.
|
|
688
|
-
uint256 sourceFeeAmount = JBFees.feeAmountFrom({amountBeforeFee: borrowAmount, feePercent: prepaidFeePercent});
|
|
689
|
-
|
|
690
|
-
// Borrow the amount.
|
|
691
|
-
_adjust({
|
|
692
|
-
loan: loan,
|
|
693
|
-
revnetId: revnetId,
|
|
694
|
-
newBorrowAmount: borrowAmount,
|
|
695
|
-
newCollateralCount: collateralCount,
|
|
696
|
-
sourceFeeAmount: sourceFeeAmount,
|
|
697
|
-
beneficiary: beneficiary,
|
|
698
|
-
holder: holder
|
|
699
|
-
});
|
|
700
|
-
|
|
701
|
-
// Mint the loan NFT to the holder.
|
|
702
|
-
_mint({to: holder, tokenId: loanId});
|
|
703
|
-
|
|
704
|
-
emit Borrow({
|
|
705
|
-
loanId: loanId,
|
|
634
|
+
return _borrowFrom({
|
|
706
635
|
revnetId: revnetId,
|
|
707
|
-
loan: loan,
|
|
708
636
|
source: source,
|
|
709
|
-
|
|
637
|
+
minBorrowAmount: minBorrowAmount,
|
|
710
638
|
collateralCount: collateralCount,
|
|
711
|
-
sourceFeeAmount: sourceFeeAmount,
|
|
712
639
|
beneficiary: beneficiary,
|
|
713
|
-
|
|
640
|
+
prepaidFeePercent: prepaidFeePercent,
|
|
641
|
+
holder: holder
|
|
714
642
|
});
|
|
715
|
-
|
|
716
|
-
return (loanId, loan);
|
|
717
643
|
}
|
|
718
644
|
|
|
719
645
|
/// @notice Liquidates loans that have exceeded the 10-year liquidation duration.
|
|
@@ -838,7 +764,9 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
|
|
|
838
764
|
|
|
839
765
|
// Make a new loan with the leftover collateral from reallocating.
|
|
840
766
|
// The loan owner is the holder for the new loan (their tokens are used as collateral).
|
|
841
|
-
|
|
767
|
+
// Uses _borrowFrom to skip the OPEN_LOAN permission check — the caller already proved REALLOCATE_LOAN
|
|
768
|
+
// permission above, and requiring OPEN_LOAN here would block operators with only REALLOCATE_LOAN.
|
|
769
|
+
(newLoanId, newLoan) = _borrowFrom({
|
|
842
770
|
revnetId: revnetId,
|
|
843
771
|
source: source,
|
|
844
772
|
minBorrowAmount: minBorrowAmount,
|
|
@@ -1093,10 +1021,11 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
|
|
|
1093
1021
|
// Keep a reference to the fee terminal.
|
|
1094
1022
|
IJBTerminal feeTerminal = DIRECTORY.primaryTerminalOf({projectId: REV_ID, token: loan.source.token});
|
|
1095
1023
|
|
|
1096
|
-
// Get the amount of additional fee to take for REV.
|
|
1024
|
+
// Get the amount of additional fee to take for REV. This is an app-level loan fee, not the terminal's
|
|
1025
|
+
// protocol fee, so keep it floor-rounded instead of applying the protocol fee helper's dust minimum.
|
|
1097
1026
|
uint256 revFeeAmount = address(feeTerminal) == address(0)
|
|
1098
1027
|
? 0
|
|
1099
|
-
: JBFees.
|
|
1028
|
+
: JBFees.feeAmountFromFloor({amountBeforeFee: addedBorrowAmount, feePercent: REV_PREPAID_FEE_PERCENT});
|
|
1100
1029
|
|
|
1101
1030
|
// Try to pay the REV fee. If it fails, revFeeAmount is zeroed so the borrower receives it instead.
|
|
1102
1031
|
if (revFeeAmount > 0) {
|
|
@@ -1234,6 +1163,121 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
|
|
|
1234
1163
|
IERC20(token).forceApprove({spender: to, value: 0});
|
|
1235
1164
|
}
|
|
1236
1165
|
|
|
1166
|
+
/// @notice Internal implementation of loan creation, without the OPEN_LOAN permission check.
|
|
1167
|
+
/// @dev Called by `borrowFrom` (after its own permission check) and by `reallocateCollateralFromLoan`
|
|
1168
|
+
/// (which only requires REALLOCATE_LOAN permission).
|
|
1169
|
+
/// @param revnetId The ID of the revnet being borrowed from.
|
|
1170
|
+
/// @param source The source of the loan being borrowed.
|
|
1171
|
+
/// @param minBorrowAmount The minimum amount being borrowed.
|
|
1172
|
+
/// @param collateralCount The amount of tokens to use as collateral for the loan.
|
|
1173
|
+
/// @param beneficiary The address that'll receive the borrowed funds and the tokens resulting from fee payments.
|
|
1174
|
+
/// @param prepaidFeePercent The fee percent that will be charged upfront.
|
|
1175
|
+
/// @param holder The address whose tokens are used as collateral and who receives the loan NFT.
|
|
1176
|
+
/// @return loanId The ID of the loan created from borrowing.
|
|
1177
|
+
/// @return loan The loan created from borrowing.
|
|
1178
|
+
function _borrowFrom(
|
|
1179
|
+
uint256 revnetId,
|
|
1180
|
+
REVLoanSource calldata source,
|
|
1181
|
+
uint256 minBorrowAmount,
|
|
1182
|
+
uint256 collateralCount,
|
|
1183
|
+
address payable beneficiary,
|
|
1184
|
+
uint256 prepaidFeePercent,
|
|
1185
|
+
address holder
|
|
1186
|
+
)
|
|
1187
|
+
internal
|
|
1188
|
+
returns (uint256 loanId, REVLoan memory)
|
|
1189
|
+
{
|
|
1190
|
+
// A loan needs to have collateral.
|
|
1191
|
+
if (collateralCount == 0) revert REVLoans_ZeroCollateralLoanIsInvalid();
|
|
1192
|
+
|
|
1193
|
+
// Make sure the source terminal is registered in the directory for this revnet.
|
|
1194
|
+
if (!DIRECTORY.isTerminalOf({projectId: revnetId, terminal: IJBTerminal(address(source.terminal))})) {
|
|
1195
|
+
revert REVLoans_InvalidTerminal(address(source.terminal), revnetId);
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
// Make sure the prepaid fee percent is between `MIN_PREPAID_FEE_PERCENT` and `MAX_PREPAID_FEE_PERCENT`. Meaning
|
|
1199
|
+
// an 16 year loan can be paid upfront with a
|
|
1200
|
+
// payment of 50% of the borrowed assets, the cheapest possible rate.
|
|
1201
|
+
if (prepaidFeePercent < MIN_PREPAID_FEE_PERCENT || prepaidFeePercent > MAX_PREPAID_FEE_PERCENT) {
|
|
1202
|
+
revert REVLoans_InvalidPrepaidFeePercent(
|
|
1203
|
+
prepaidFeePercent, MIN_PREPAID_FEE_PERCENT, MAX_PREPAID_FEE_PERCENT
|
|
1204
|
+
);
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
// Cache the current ruleset once — used by both _cashOutDelayOf and _borrowAmountFrom.
|
|
1208
|
+
JBRuleset memory currentRuleset = _currentRulesetOf(revnetId);
|
|
1209
|
+
|
|
1210
|
+
// Enforce the cash out delay.
|
|
1211
|
+
{
|
|
1212
|
+
uint256 cashOutDelay = _cashOutDelayOf({revnetId: revnetId, currentRuleset: currentRuleset});
|
|
1213
|
+
if (cashOutDelay > block.timestamp) {
|
|
1214
|
+
revert REVLoans_CashOutDelayNotFinished(cashOutDelay, block.timestamp);
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
// Prevent the loan number from exceeding the ID namespace for this revnet.
|
|
1219
|
+
if (totalLoansBorrowedFor[revnetId] >= _ONE_TRILLION) revert REVLoans_LoanIdOverflow();
|
|
1220
|
+
|
|
1221
|
+
// Get a reference to the loan ID.
|
|
1222
|
+
loanId = _generateLoanId({revnetId: revnetId, loanNumber: ++totalLoansBorrowedFor[revnetId]});
|
|
1223
|
+
|
|
1224
|
+
// Get a reference to the loan being created.
|
|
1225
|
+
REVLoan storage loan = _loanOf[loanId];
|
|
1226
|
+
|
|
1227
|
+
// Set the loan's values.
|
|
1228
|
+
loan.source = source;
|
|
1229
|
+
loan.createdAt = uint48(block.timestamp);
|
|
1230
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
1231
|
+
loan.prepaidFeePercent = uint16(prepaidFeePercent);
|
|
1232
|
+
loan.prepaidDuration =
|
|
1233
|
+
uint32(mulDiv({x: prepaidFeePercent, y: LOAN_LIQUIDATION_DURATION, denominator: MAX_PREPAID_FEE_PERCENT}));
|
|
1234
|
+
|
|
1235
|
+
// Get the amount of the loan, using the cached ruleset.
|
|
1236
|
+
uint256 borrowAmount = _borrowAmountFrom({
|
|
1237
|
+
loan: loan, revnetId: revnetId, collateralCount: collateralCount, currentRuleset: currentRuleset
|
|
1238
|
+
});
|
|
1239
|
+
|
|
1240
|
+
// Revert if the bonding curve returns zero to prevent creating zero-amount loans.
|
|
1241
|
+
if (borrowAmount == 0) revert REVLoans_ZeroBorrowAmount();
|
|
1242
|
+
|
|
1243
|
+
// Make sure the minimum borrow amount is met.
|
|
1244
|
+
if (borrowAmount < minBorrowAmount) revert REVLoans_UnderMinBorrowAmount(minBorrowAmount, borrowAmount);
|
|
1245
|
+
|
|
1246
|
+
// Get the amount of additional fee to take for the revnet issuing the loan. This is an app-level loan fee,
|
|
1247
|
+
// not the terminal's protocol fee, so keep it floor-rounded instead of applying the protocol fee helper's dust
|
|
1248
|
+
// minimum.
|
|
1249
|
+
uint256 sourceFeeAmount =
|
|
1250
|
+
JBFees.feeAmountFromFloor({amountBeforeFee: borrowAmount, feePercent: prepaidFeePercent});
|
|
1251
|
+
|
|
1252
|
+
// Borrow the amount.
|
|
1253
|
+
_adjust({
|
|
1254
|
+
loan: loan,
|
|
1255
|
+
revnetId: revnetId,
|
|
1256
|
+
newBorrowAmount: borrowAmount,
|
|
1257
|
+
newCollateralCount: collateralCount,
|
|
1258
|
+
sourceFeeAmount: sourceFeeAmount,
|
|
1259
|
+
beneficiary: beneficiary,
|
|
1260
|
+
holder: holder
|
|
1261
|
+
});
|
|
1262
|
+
|
|
1263
|
+
// Mint the loan NFT to the holder.
|
|
1264
|
+
_mint({to: holder, tokenId: loanId});
|
|
1265
|
+
|
|
1266
|
+
emit Borrow({
|
|
1267
|
+
loanId: loanId,
|
|
1268
|
+
revnetId: revnetId,
|
|
1269
|
+
loan: loan,
|
|
1270
|
+
source: source,
|
|
1271
|
+
borrowAmount: borrowAmount,
|
|
1272
|
+
collateralCount: collateralCount,
|
|
1273
|
+
sourceFeeAmount: sourceFeeAmount,
|
|
1274
|
+
beneficiary: beneficiary,
|
|
1275
|
+
caller: _msgSender()
|
|
1276
|
+
});
|
|
1277
|
+
|
|
1278
|
+
return (loanId, loan);
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1237
1281
|
/// @notice Reallocates collateral from a loan by making a new loan based on the original, with reduced collateral.
|
|
1238
1282
|
/// @param loanId The ID of the loan to reallocate collateral from.
|
|
1239
1283
|
/// @param revnetId The ID of the revnet the loan is from.
|
package/src/REVOwner.sol
CHANGED
|
@@ -9,12 +9,14 @@ import {IJBRulesetDataHook} from "@bananapus/core-v6/src/interfaces/IJBRulesetDa
|
|
|
9
9
|
import {IJBTerminal} from "@bananapus/core-v6/src/interfaces/IJBTerminal.sol";
|
|
10
10
|
import {JBCashOuts} from "@bananapus/core-v6/src/libraries/JBCashOuts.sol";
|
|
11
11
|
import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
|
|
12
|
+
import {JBAccountingContext} from "@bananapus/core-v6/src/structs/JBAccountingContext.sol";
|
|
12
13
|
import {JBAfterCashOutRecordedContext} from "@bananapus/core-v6/src/structs/JBAfterCashOutRecordedContext.sol";
|
|
13
14
|
import {JBBeforeCashOutRecordedContext} from "@bananapus/core-v6/src/structs/JBBeforeCashOutRecordedContext.sol";
|
|
14
15
|
import {JBBeforePayRecordedContext} from "@bananapus/core-v6/src/structs/JBBeforePayRecordedContext.sol";
|
|
15
16
|
import {JBCashOutHookSpecification} from "@bananapus/core-v6/src/structs/JBCashOutHookSpecification.sol";
|
|
16
17
|
import {JBPayHookSpecification} from "@bananapus/core-v6/src/structs/JBPayHookSpecification.sol";
|
|
17
18
|
import {JBRuleset} from "@bananapus/core-v6/src/structs/JBRuleset.sol";
|
|
19
|
+
import {IJBPeerChainAdjustedAccounts} from "@bananapus/suckers-v6/src/interfaces/IJBPeerChainAdjustedAccounts.sol";
|
|
18
20
|
import {IJBSuckerRegistry} from "@bananapus/suckers-v6/src/interfaces/IJBSuckerRegistry.sol";
|
|
19
21
|
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
|
|
20
22
|
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
@@ -22,11 +24,14 @@ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol
|
|
|
22
24
|
import {mulDiv} from "@prb/math/src/Common.sol";
|
|
23
25
|
|
|
24
26
|
import {IREVDeployer} from "./interfaces/IREVDeployer.sol";
|
|
27
|
+
import {IREVHiddenTokens} from "./interfaces/IREVHiddenTokens.sol";
|
|
28
|
+
import {IREVLoans} from "./interfaces/IREVLoans.sol";
|
|
29
|
+
import {REVLoanSource} from "./structs/REVLoanSource.sol";
|
|
25
30
|
|
|
26
31
|
/// @notice Handles the runtime data hook and cash out hook behavior for revnets.
|
|
27
32
|
/// @dev Separated from `REVDeployer` to stay within the EIP-170 contract size limit.
|
|
28
33
|
/// This contract is set as the `dataHook` in each revnet's ruleset metadata.
|
|
29
|
-
contract REVOwner is IJBRulesetDataHook, IJBCashOutHook {
|
|
34
|
+
contract REVOwner is IJBRulesetDataHook, IJBCashOutHook, IJBPeerChainAdjustedAccounts {
|
|
30
35
|
// A library that adds default safety checks to ERC20 functionality.
|
|
31
36
|
using SafeERC20 for IERC20;
|
|
32
37
|
|
|
@@ -61,10 +66,10 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook {
|
|
|
61
66
|
uint256 public immutable FEE_REVNET_ID;
|
|
62
67
|
|
|
63
68
|
/// @notice The hidden tokens contract used by all revnets.
|
|
64
|
-
|
|
69
|
+
IREVHiddenTokens public immutable HIDDEN_TOKENS;
|
|
65
70
|
|
|
66
71
|
/// @notice The loan contract used by all revnets.
|
|
67
|
-
|
|
72
|
+
IREVLoans public immutable LOANS;
|
|
68
73
|
|
|
69
74
|
/// @notice Deploys and tracks suckers for revnets.
|
|
70
75
|
IJBSuckerRegistry public immutable SUCKER_REGISTRY;
|
|
@@ -102,23 +107,21 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook {
|
|
|
102
107
|
/// @param directory The directory of terminals and controllers.
|
|
103
108
|
/// @param feeRevnetId The Juicebox project ID of the fee revnet.
|
|
104
109
|
/// @param suckerRegistry The sucker registry.
|
|
105
|
-
/// @param loans The loan contract
|
|
106
|
-
/// @param hiddenTokens The hidden tokens contract
|
|
110
|
+
/// @param loans The loan contract.
|
|
111
|
+
/// @param hiddenTokens The hidden tokens contract.
|
|
107
112
|
constructor(
|
|
108
113
|
IJBBuybackHookRegistry buybackHook,
|
|
109
114
|
IJBDirectory directory,
|
|
110
115
|
uint256 feeRevnetId,
|
|
111
116
|
IJBSuckerRegistry suckerRegistry,
|
|
112
|
-
|
|
113
|
-
|
|
117
|
+
IREVLoans loans,
|
|
118
|
+
IREVHiddenTokens hiddenTokens
|
|
114
119
|
) {
|
|
115
120
|
BUYBACK_HOOK = buybackHook;
|
|
116
121
|
DIRECTORY = directory;
|
|
117
122
|
FEE_REVNET_ID = feeRevnetId;
|
|
118
123
|
SUCKER_REGISTRY = suckerRegistry;
|
|
119
|
-
// slither-disable-next-line missing-zero-check
|
|
120
124
|
LOANS = loans;
|
|
121
|
-
// slither-disable-next-line missing-zero-check
|
|
122
125
|
HIDDEN_TOKENS = hiddenTokens;
|
|
123
126
|
_DEPLOYER_BINDER = msg.sender;
|
|
124
127
|
}
|
|
@@ -153,11 +156,23 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook {
|
|
|
153
156
|
JBCashOutHookSpecification[] memory hookSpecifications
|
|
154
157
|
)
|
|
155
158
|
{
|
|
159
|
+
// Treat outstanding local loans as temporarily off-terminal revnet assets. Borrowed funds are owed back to
|
|
160
|
+
// the revnet, while burned loan collateral can be re-minted on repayment, so both affect fair cash-out math.
|
|
161
|
+
(uint256 totalBorrowed, uint256 totalCollateral) = _localLoanStateOf({
|
|
162
|
+
revnetId: context.projectId, decimals: context.surplus.decimals, currency: context.surplus.currency
|
|
163
|
+
});
|
|
164
|
+
|
|
156
165
|
// If the cash out is from a sucker, return the full cash out amount without taxes or fees.
|
|
157
166
|
// This relies on the sucker registry to only contain trusted sucker contracts deployed via
|
|
158
167
|
// the registry's own deploySuckersFor flow — external addresses cannot register as suckers.
|
|
159
168
|
if (_isSuckerOf({revnetId: context.projectId, addr: context.holder})) {
|
|
160
|
-
return (
|
|
169
|
+
return (
|
|
170
|
+
0,
|
|
171
|
+
context.cashOutCount,
|
|
172
|
+
context.totalSupply + totalCollateral,
|
|
173
|
+
context.surplus.value + totalBorrowed,
|
|
174
|
+
hookSpecifications
|
|
175
|
+
);
|
|
161
176
|
}
|
|
162
177
|
|
|
163
178
|
// Keep a reference to the cash out delay of the revnet.
|
|
@@ -173,12 +188,12 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook {
|
|
|
173
188
|
|
|
174
189
|
// Compute the cross-chain total supply (local + remote peer chain supplies) for cross-chain-aware bonding
|
|
175
190
|
// curve.
|
|
176
|
-
totalSupply = context.totalSupply + SUCKER_REGISTRY.remoteTotalSupplyOf(context.projectId);
|
|
177
|
-
effectiveSurplusValue = context.surplus.value
|
|
191
|
+
totalSupply = context.totalSupply + totalCollateral + SUCKER_REGISTRY.remoteTotalSupplyOf(context.projectId);
|
|
192
|
+
effectiveSurplusValue = context.surplus.value + totalBorrowed
|
|
178
193
|
+ SUCKER_REGISTRY.remoteSurplusOf({
|
|
179
194
|
projectId: context.projectId,
|
|
180
195
|
decimals: context.surplus.decimals,
|
|
181
|
-
currency: uint256(
|
|
196
|
+
currency: uint256(context.surplus.currency)
|
|
182
197
|
});
|
|
183
198
|
|
|
184
199
|
// If there's no cash out tax (100% cash out tax rate), if there's no fee terminal, or if the beneficiary is
|
|
@@ -350,11 +365,35 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook {
|
|
|
350
365
|
{
|
|
351
366
|
// The loans contract, hidden tokens contract, buyback hook (and its delegates), and suckers are allowed to mint
|
|
352
367
|
// the revnet's tokens.
|
|
353
|
-
return addr == LOANS || addr == HIDDEN_TOKENS || addr == address(BUYBACK_HOOK)
|
|
368
|
+
return addr == address(LOANS) || addr == address(HIDDEN_TOKENS) || addr == address(BUYBACK_HOOK)
|
|
354
369
|
|| BUYBACK_HOOK.hasMintPermissionFor({projectId: revnetId, ruleset: ruleset, addr: addr})
|
|
355
370
|
|| _isSuckerOf({revnetId: revnetId, addr: addr});
|
|
356
371
|
}
|
|
357
372
|
|
|
373
|
+
/// @notice Additional revnet accounts that peer-chain snapshots should include.
|
|
374
|
+
/// @dev Hidden tokens are intentionally excluded. Revnet operators can hide tokens as a security handle without
|
|
375
|
+
/// changing loan or cash-out math for other holders. Outstanding loan debt is counted as both surplus and balance:
|
|
376
|
+
/// it is value owed back to this chain's revnet and should travel to peer snapshots with the collateral supply.
|
|
377
|
+
/// @param revnetId The ID of the revnet being snapshotted.
|
|
378
|
+
/// @param decimals The decimals the returned surplus should use.
|
|
379
|
+
/// @param currency The currency the returned surplus should be in terms of.
|
|
380
|
+
/// @return supply The loan-collateral supply to include in the peer snapshot.
|
|
381
|
+
/// @return surplus The outstanding loan debt to include in `sourceSurplus`.
|
|
382
|
+
/// @return balance The outstanding loan debt to include in `sourceBalance`.
|
|
383
|
+
function peerChainAdjustedAccountsOf(
|
|
384
|
+
uint256 revnetId,
|
|
385
|
+
uint256 decimals,
|
|
386
|
+
uint256 currency
|
|
387
|
+
)
|
|
388
|
+
external
|
|
389
|
+
view
|
|
390
|
+
override
|
|
391
|
+
returns (uint256 supply, uint256 surplus, uint256 balance)
|
|
392
|
+
{
|
|
393
|
+
(surplus, supply) = _localLoanStateOf({revnetId: revnetId, decimals: decimals, currency: currency});
|
|
394
|
+
balance = surplus;
|
|
395
|
+
}
|
|
396
|
+
|
|
358
397
|
//*********************************************************************//
|
|
359
398
|
// --------------------- external transactions ----------------------- //
|
|
360
399
|
//*********************************************************************//
|
|
@@ -397,8 +436,8 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook {
|
|
|
397
436
|
if (context.forwardedAmount.token != JBConstants.NATIVE_TOKEN) {
|
|
398
437
|
IERC20(context.forwardedAmount.token)
|
|
399
438
|
.safeDecreaseAllowance({
|
|
400
|
-
|
|
401
|
-
|
|
439
|
+
spender: address(feeTerminal), requestedDecrease: context.forwardedAmount.value
|
|
440
|
+
});
|
|
402
441
|
}
|
|
403
442
|
|
|
404
443
|
// If the fee can't be processed, return the funds to the project.
|
|
@@ -460,7 +499,8 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook {
|
|
|
460
499
|
/// @return A flag indicating if the provided interface ID is supported.
|
|
461
500
|
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
|
|
462
501
|
return interfaceId == type(IERC165).interfaceId || interfaceId == type(IJBRulesetDataHook).interfaceId
|
|
463
|
-
|| interfaceId == type(IJBCashOutHook).interfaceId
|
|
502
|
+
|| interfaceId == type(IJBCashOutHook).interfaceId
|
|
503
|
+
|| interfaceId == type(IJBPeerChainAdjustedAccounts).interfaceId;
|
|
464
504
|
}
|
|
465
505
|
|
|
466
506
|
//*********************************************************************//
|
|
@@ -475,6 +515,73 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook {
|
|
|
475
515
|
return SUCKER_REGISTRY.isSuckerOf({projectId: revnetId, addr: addr});
|
|
476
516
|
}
|
|
477
517
|
|
|
518
|
+
/// @notice Total outstanding local loan debt and collateral for a revnet.
|
|
519
|
+
/// @dev This is included in cash-out and peer-snapshot math because borrowed funds are still owed to the revnet
|
|
520
|
+
/// and collateral can re-enter supply when the loan is repaid.
|
|
521
|
+
/// @param revnetId The ID of the revnet to check.
|
|
522
|
+
/// @param decimals The decimals the resulting fixed point debt value should use.
|
|
523
|
+
/// @param currency The currency the resulting debt value should be in terms of.
|
|
524
|
+
/// @return borrowedAmount The local outstanding loan debt converted into `currency`.
|
|
525
|
+
/// @return collateralCount The local burned loan collateral count.
|
|
526
|
+
function _localLoanStateOf(
|
|
527
|
+
uint256 revnetId,
|
|
528
|
+
uint256 decimals,
|
|
529
|
+
uint256 currency
|
|
530
|
+
)
|
|
531
|
+
internal
|
|
532
|
+
view
|
|
533
|
+
returns (uint256 borrowedAmount, uint256 collateralCount)
|
|
534
|
+
{
|
|
535
|
+
IREVLoans loans = LOANS;
|
|
536
|
+
if (address(loans) == address(0) || address(loans).code.length == 0) return (0, 0);
|
|
537
|
+
|
|
538
|
+
collateralCount = loans.totalCollateralOf(revnetId);
|
|
539
|
+
|
|
540
|
+
REVLoanSource[] memory sources = loans.loanSourcesOf(revnetId);
|
|
541
|
+
// Loan sources are project configuration, and this read-only aggregation needs the latest terminal/pricing
|
|
542
|
+
// state for each configured source.
|
|
543
|
+
for (uint256 i; i < sources.length; i++) {
|
|
544
|
+
REVLoanSource memory source = sources[i];
|
|
545
|
+
// Each configured source must be queried live so cash-out math includes current outstanding debt.
|
|
546
|
+
// slither-disable-next-line calls-loop
|
|
547
|
+
uint256 tokensLoaned =
|
|
548
|
+
loans.totalBorrowedFrom({revnetId: revnetId, terminal: source.terminal, token: source.token});
|
|
549
|
+
if (tokensLoaned == 0) continue;
|
|
550
|
+
|
|
551
|
+
// Read the source token's accounting context so debt can be normalized before cross-currency conversion.
|
|
552
|
+
// slither-disable-next-line calls-loop
|
|
553
|
+
JBAccountingContext memory accountingContext =
|
|
554
|
+
source.terminal.accountingContextForTokenOf({projectId: revnetId, token: source.token});
|
|
555
|
+
|
|
556
|
+
// Normalize each source from its native token decimals into the caller's requested decimals.
|
|
557
|
+
uint256 normalizedTokens;
|
|
558
|
+
if (accountingContext.decimals > decimals) {
|
|
559
|
+
normalizedTokens = tokensLoaned / (10 ** (accountingContext.decimals - decimals));
|
|
560
|
+
} else if (accountingContext.decimals < decimals) {
|
|
561
|
+
normalizedTokens = tokensLoaned * (10 ** (decimals - accountingContext.decimals));
|
|
562
|
+
} else {
|
|
563
|
+
normalizedTokens = tokensLoaned;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
if (accountingContext.currency == currency) {
|
|
567
|
+
borrowedAmount += normalizedTokens;
|
|
568
|
+
} else {
|
|
569
|
+
// Convert source-token debt into the requested currency using the loans contract's shared prices.
|
|
570
|
+
// slither-disable-next-line calls-loop
|
|
571
|
+
uint256 pricePerUnit = loans.PRICES()
|
|
572
|
+
.pricePerUnitOf({
|
|
573
|
+
projectId: revnetId,
|
|
574
|
+
pricingCurrency: accountingContext.currency,
|
|
575
|
+
unitCurrency: currency,
|
|
576
|
+
decimals: decimals
|
|
577
|
+
});
|
|
578
|
+
if (pricePerUnit == 0) continue;
|
|
579
|
+
|
|
580
|
+
borrowedAmount += mulDiv({x: normalizedTokens, y: 10 ** decimals, denominator: pricePerUnit});
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
478
585
|
//*********************************************************************//
|
|
479
586
|
// --------------------- internal transactions ----------------------- //
|
|
480
587
|
//*********************************************************************//
|
|
@@ -16,6 +16,7 @@ import {REVConfig} from "../structs/REVConfig.sol";
|
|
|
16
16
|
import {REVCroptopAllowedPost} from "../structs/REVCroptopAllowedPost.sol";
|
|
17
17
|
import {REVDeploy721TiersHookConfig} from "../structs/REVDeploy721TiersHookConfig.sol";
|
|
18
18
|
import {REVSuckerDeploymentConfig} from "../structs/REVSuckerDeploymentConfig.sol";
|
|
19
|
+
import {IREVLoans} from "./IREVLoans.sol";
|
|
19
20
|
|
|
20
21
|
/// @notice Deploys and manages revnets -- Juicebox projects with pre-configured tokenomics.
|
|
21
22
|
interface IREVDeployer {
|
|
@@ -143,7 +144,7 @@ interface IREVDeployer {
|
|
|
143
144
|
|
|
144
145
|
/// @notice The loan contract used by all revnets.
|
|
145
146
|
/// @return The loans contract address.
|
|
146
|
-
function LOANS() external view returns (
|
|
147
|
+
function LOANS() external view returns (IREVLoans);
|
|
147
148
|
|
|
148
149
|
/// @notice The runtime data hook contract that handles pay and cash out callbacks for revnets.
|
|
149
150
|
/// @return The owner contract address.
|
|
@@ -3,7 +3,10 @@ pragma solidity ^0.8.0;
|
|
|
3
3
|
|
|
4
4
|
import {IJBController} from "@bananapus/core-v6/src/interfaces/IJBController.sol";
|
|
5
5
|
|
|
6
|
-
/// @notice Manages hiding (burning) and revealing (re-minting) revnet tokens to exclude them from totalSupply.
|
|
6
|
+
/// @notice Manages hiding (burning) and revealing (re-minting) revnet tokens to exclude them from live totalSupply.
|
|
7
|
+
/// @dev Hidden balances are an operator-controlled security handle. They remain revealable, but cash-out and loan
|
|
8
|
+
/// accounting intentionally excludes `totalHiddenOf` so hidden inventory cannot dilute other holders' access to
|
|
9
|
+
/// revnet capital.
|
|
7
10
|
interface IREVHiddenTokens {
|
|
8
11
|
/// @notice Emitted when a holder is allowed or disallowed to hide their own tokens.
|
|
9
12
|
/// @param revnetId The ID of the revnet.
|
|
@@ -2,9 +2,14 @@
|
|
|
2
2
|
pragma solidity ^0.8.0;
|
|
3
3
|
|
|
4
4
|
import {IREVDeployer} from "./IREVDeployer.sol";
|
|
5
|
+
import {IREVHiddenTokens} from "./IREVHiddenTokens.sol";
|
|
5
6
|
|
|
6
7
|
/// @notice Interface for the REVOwner contract that handles runtime data hook and cash out hook behavior for revnets.
|
|
7
8
|
interface IREVOwner {
|
|
9
|
+
/// @notice The hidden tokens contract used by the revnet owner hook.
|
|
10
|
+
/// @return The hidden tokens contract.
|
|
11
|
+
function HIDDEN_TOKENS() external view returns (IREVHiddenTokens);
|
|
12
|
+
|
|
8
13
|
/// @notice The timestamp of when cashouts will become available to a specific revnet's participants.
|
|
9
14
|
/// @param revnetId The ID of the revnet.
|
|
10
15
|
/// @return The cash out delay timestamp.
|
package/ADMINISTRATION.md
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
# Administration
|
|
2
|
-
|
|
3
|
-
## At A Glance
|
|
4
|
-
|
|
5
|
-
| Item | Details |
|
|
6
|
-
| --- | --- |
|
|
7
|
-
| Scope | Revnet deployment shape, bounded runtime operators, loan-owner cosmetics, and optional integration control surfaces |
|
|
8
|
-
| Control posture | Intentionally narrow and mostly deployment-defined |
|
|
9
|
-
| Highest-risk actions | Bad stage design, wrong split-operator assignment, and misunderstanding which runtime surfaces stay live after launch |
|
|
10
|
-
| Recovery posture | Usually replacement, not patching; the design intentionally avoids easy admin escape hatches |
|
|
11
|
-
|
|
12
|
-
## Purpose
|
|
13
|
-
|
|
14
|
-
`revnet-core-v6` is designed to collapse ordinary post-launch governance into deployment-time decisions plus a small set of bounded runtime roles. The main administration task is understanding which power still exists and which power was intentionally removed.
|
|
15
|
-
|
|
16
|
-
## Control Model
|
|
17
|
-
|
|
18
|
-
- `REVDeployer` holds the project NFT and therefore remains part of the ownership model.
|
|
19
|
-
- Revnet economics are mainly fixed at deployment through staged rulesets.
|
|
20
|
-
- `REVOwner` provides live runtime policy, but not broad human governance.
|
|
21
|
-
- Split operators can hold narrow powers depending on stage and deployment config.
|
|
22
|
-
- `REVLoans` has a cosmetic global owner surface, but loan economics are still bounded by revnet logic.
|
|
23
|
-
|
|
24
|
-
## Roles
|
|
25
|
-
|
|
26
|
-
| Role | How Assigned | Scope | Notes |
|
|
27
|
-
| --- | --- | --- | --- |
|
|
28
|
-
| `REVDeployer` | Deployed singleton | Global launcher and project-NFT holder | Part of the ownership model |
|
|
29
|
-
| Split operator | Deployment config | Per revnet | Holds only the allowed operator envelope |
|
|
30
|
-
| Auto-issuance beneficiary | Deployment config | Per stage | Can receive preconfigured stage issuance |
|
|
31
|
-
| Borrower or delegated loan operator | Token holder plus permission | Per holder or loan | Can open or manage loans within loan rules |
|
|
32
|
-
| `REVLoans` owner | Constructor owner | Global cosmetic/admin surface | Does not turn Revnets back into ordinary governed projects |
|
|
33
|
-
|
|
34
|
-
## Privileged Surfaces
|
|
35
|
-
|
|
36
|
-
- `deployFor(...)` defines the revnet's long-lived shape
|
|
37
|
-
- split-operator paths can manage only the permissions left open by deployment
|
|
38
|
-
- `autoIssueFor(...)` consumes preconfigured stage issuance
|
|
39
|
-
- loan operators can redirect borrowed value if a holder delegates loan permissions
|
|
40
|
-
- hidden-token flows require the holder's permission grant and mint permission wiring through `REVOwner`
|
|
41
|
-
|
|
42
|
-
## Immutable And One-Way
|
|
43
|
-
|
|
44
|
-
- Stage configuration is effectively permanent after deployment.
|
|
45
|
-
- The deployer-held project NFT is not a normal owner-recovery tool.
|
|
46
|
-
- Loan collateral is burned at borrow time and only reminted through repayment or documented flows.
|
|
47
|
-
- Hidden-token balances change visible supply until reveal.
|
|
48
|
-
|
|
49
|
-
## Operational Notes
|
|
50
|
-
|
|
51
|
-
- Treat revnet launch as the real governance decision.
|
|
52
|
-
- Validate stage timing, split-operator scope, and optional integrations before deployment.
|
|
53
|
-
- Review cash-out delay, hidden-token semantics, and loan permissions together.
|
|
54
|
-
- Do not assume there is a broad admin override for bad economics after launch.
|
|
55
|
-
|
|
56
|
-
## Machine Notes
|
|
57
|
-
|
|
58
|
-
- Do not describe Revnets as fully adminless if the deployer-held NFT still matters for the trust model.
|
|
59
|
-
- Also do not describe them as ordinary owner-controlled projects. The point is that the available control surface is intentionally narrow.
|
|
60
|
-
- If a question is about runtime cash-outs, buybacks, or mint permissions, inspect `REVOwner` before inferring behavior from deployment prose.
|
|
61
|
-
|
|
62
|
-
## Recovery
|
|
63
|
-
|
|
64
|
-
- If launch-time economics are wrong, recovery usually means replacement, not in-place repair.
|
|
65
|
-
- If optional integrations are misconfigured, fix only where the code still exposes a valid path.
|
|
66
|
-
- If the design intentionally omitted a recovery path, do not invent one in documentation or ops guidance.
|
|
67
|
-
|
|
68
|
-
## Admin Boundaries
|
|
69
|
-
|
|
70
|
-
- No ordinary owner can casually rewrite staged economics after launch.
|
|
71
|
-
- Split operators are not general-purpose governors.
|
|
72
|
-
- Loan mechanics, hidden-token mechanics, and cash-out policy remain bounded by the deployed revnet logic.
|
|
73
|
-
- This repo should not be documented as if it had a normal mutable project-owner model.
|