@rev-net/core-v6 0.0.11 → 0.0.13
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/ADMINISTRATION.md +7 -7
- package/ARCHITECTURE.md +11 -11
- package/AUDIT_INSTRUCTIONS.md +295 -0
- package/CHANGE_LOG.md +316 -0
- package/README.md +9 -6
- package/RISKS.md +180 -35
- package/SKILLS.md +9 -11
- package/STYLE_GUIDE.md +14 -1
- package/USER_JOURNEYS.md +489 -0
- package/package.json +9 -9
- package/script/Deploy.s.sol +124 -40
- package/script/helpers/RevnetCoreDeploymentLib.sol +19 -6
- package/src/REVDeployer.sol +183 -175
- package/src/REVLoans.sol +65 -28
- package/src/interfaces/IREVDeployer.sol +25 -23
- package/src/structs/REV721TiersHookFlags.sol +1 -0
- package/src/structs/REVAutoIssuance.sol +1 -0
- package/src/structs/REVBaseline721HookConfig.sol +1 -0
- package/src/structs/REVConfig.sol +1 -0
- package/src/structs/REVCroptopAllowedPost.sol +1 -0
- package/src/structs/REVDeploy721TiersHookConfig.sol +13 -14
- package/src/structs/REVDescription.sol +1 -0
- package/src/structs/REVLoan.sol +1 -0
- package/src/structs/REVLoanSource.sol +1 -0
- package/src/structs/REVStageConfig.sol +1 -0
- package/src/structs/REVSuckerDeploymentConfig.sol +1 -0
- package/test/REV.integrations.t.sol +148 -19
- package/test/REVAutoIssuanceFuzz.t.sol +31 -6
- package/test/REVDeployerRegressions.t.sol +47 -9
- package/test/REVInvincibility.t.sol +83 -19
- package/test/REVInvincibilityHandler.sol +29 -0
- package/test/REVLifecycle.t.sol +36 -6
- package/test/REVLoans.invariants.t.sol +64 -10
- package/test/REVLoansAttacks.t.sol +54 -9
- package/test/REVLoansFeeRecovery.t.sol +61 -15
- package/test/REVLoansFindings.t.sol +42 -9
- package/test/REVLoansRegressions.t.sol +33 -6
- package/test/REVLoansSourceFeeRecovery.t.sol +491 -0
- package/test/REVLoansSourced.t.sol +79 -17
- package/test/REVLoansUnSourced.t.sol +61 -10
- package/test/TestBurnHeldTokens.t.sol +47 -11
- package/test/TestCEIPattern.t.sol +37 -6
- package/test/TestCashOutCallerValidation.t.sol +41 -8
- package/test/TestConversionDocumentation.t.sol +50 -13
- package/test/TestCrossCurrencyReclaim.t.sol +584 -0
- package/test/TestCrossSourceReallocation.t.sol +37 -6
- package/test/TestERC2771MetaTx.t.sol +557 -0
- package/test/TestEmptyBuybackSpecs.t.sol +45 -10
- package/test/TestFlashLoanSurplus.t.sol +39 -7
- package/test/TestHookArrayOOB.t.sol +42 -13
- package/test/TestLiquidationBehavior.t.sol +37 -7
- package/test/TestLoanSourceRotation.t.sol +525 -0
- package/test/TestLongTailEconomics.t.sol +651 -0
- package/test/TestLowFindings.t.sol +80 -8
- package/test/TestMixedFixes.t.sol +43 -9
- package/test/TestPermit2Signatures.t.sol +657 -0
- package/test/TestReallocationSandwich.t.sol +384 -0
- package/test/TestRevnetRegressions.t.sol +324 -0
- package/test/TestSplitWeightAdjustment.t.sol +52 -13
- package/test/TestSplitWeightE2E.t.sol +53 -18
- package/test/TestSplitWeightFork.t.sol +66 -21
- package/test/TestStageTransitionBorrowable.t.sol +38 -6
- package/test/TestSwapTerminalPermission.t.sol +37 -7
- package/test/TestUint112Overflow.t.sol +39 -6
- package/test/TestZeroRepayment.t.sol +37 -6
- package/test/fork/ForkTestBase.sol +66 -17
- package/test/fork/TestCashOutFork.t.sol +9 -3
- package/test/fork/TestLoanBorrowFork.t.sol +1 -0
- package/test/fork/TestLoanCrossRulesetFork.t.sol +11 -3
- package/test/fork/TestLoanLiquidationFork.t.sol +1 -0
- package/test/fork/TestLoanReallocateFork.t.sol +1 -0
- package/test/fork/TestLoanRepayFork.t.sol +1 -0
- package/test/fork/TestLoanTransferFork.t.sol +133 -0
- package/test/fork/TestSplitWeightFork.t.sol +3 -0
- package/test/helpers/REVEmpty721Config.sol +46 -0
- package/test/mock/MockBuybackDataHook.sol +1 -0
- package/test/regression/TestBurnPermissionRequired.t.sol +267 -0
- package/test/regression/TestCrossRevnetLiquidation.t.sol +228 -0
- package/test/regression/TestCumulativeLoanCounter.t.sol +38 -8
- package/test/regression/TestLiquidateGapHandling.t.sol +40 -8
- package/test/regression/TestZeroPriceFeed.t.sol +396 -0
package/src/REVLoans.sol
CHANGED
|
@@ -59,6 +59,7 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
59
59
|
error REVLoans_InvalidPrepaidFeePercent(uint256 prepaidFeePercent, uint256 min, uint256 max);
|
|
60
60
|
error REVLoans_InvalidTerminal(address terminal, uint256 revnetId);
|
|
61
61
|
error REVLoans_LoanExpired(uint256 timeSinceLoanCreated, uint256 loanLiquidationDuration);
|
|
62
|
+
error REVLoans_LoanIdOverflow();
|
|
62
63
|
error REVLoans_NewBorrowAmountGreaterThanLoanAmount(uint256 newBorrowAmount, uint256 loanAmount);
|
|
63
64
|
error REVLoans_NoMsgValueAllowed();
|
|
64
65
|
error REVLoans_NotEnoughCollateral();
|
|
@@ -70,6 +71,7 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
70
71
|
error REVLoans_SourceMismatch();
|
|
71
72
|
error REVLoans_Unauthorized(address caller, address owner);
|
|
72
73
|
error REVLoans_UnderMinBorrowAmount(uint256 minBorrowAmount, uint256 borrowAmount);
|
|
74
|
+
error REVLoans_ZeroBorrowAmount();
|
|
73
75
|
error REVLoans_ZeroCollateralLoanIsInvalid();
|
|
74
76
|
|
|
75
77
|
//*********************************************************************//
|
|
@@ -93,7 +95,7 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
93
95
|
uint256 public constant override MIN_PREPAID_FEE_PERCENT = 25; // 2.5%
|
|
94
96
|
|
|
95
97
|
//*********************************************************************//
|
|
96
|
-
//
|
|
98
|
+
// ------------------------ private constants ------------------------ //
|
|
97
99
|
//*********************************************************************//
|
|
98
100
|
|
|
99
101
|
/// @notice Just a kind reminder to our readers.
|
|
@@ -255,7 +257,7 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
255
257
|
/// @param amount The amount being paid off.
|
|
256
258
|
/// @return sourceFeeAmount The source fee amount for the loan.
|
|
257
259
|
function determineSourceFeeAmount(REVLoan memory loan, uint256 amount) public view returns (uint256) {
|
|
258
|
-
return _determineSourceFeeAmount(loan, amount);
|
|
260
|
+
return _determineSourceFeeAmount({loan: loan, amount: amount});
|
|
259
261
|
}
|
|
260
262
|
|
|
261
263
|
/// @notice The revnet ID for the loan with the provided loan ID.
|
|
@@ -416,8 +418,10 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
416
418
|
// If the loan period has passed the prepaid time frame, take a fee.
|
|
417
419
|
if (timeSinceLoanCreated <= loan.prepaidDuration) return 0;
|
|
418
420
|
|
|
419
|
-
// If the loan period has
|
|
420
|
-
|
|
421
|
+
// If the loan period has passed the liquidation time frame, do not allow loan management.
|
|
422
|
+
// Uses `>` (not `>=`) so the exact boundary second is still repayable — the liquidation path
|
|
423
|
+
// uses `<=`, and matching `>=` here would create a 1-second window where neither path is available.
|
|
424
|
+
if (timeSinceLoanCreated > LOAN_LIQUIDATION_DURATION) {
|
|
421
425
|
revert REVLoans_LoanExpired(timeSinceLoanCreated, LOAN_LIQUIDATION_DURATION);
|
|
422
426
|
}
|
|
423
427
|
|
|
@@ -426,15 +430,15 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
426
430
|
|
|
427
431
|
uint256 fullSourceFeeAmount = JBFees.feeAmountFrom({
|
|
428
432
|
amountBeforeFee: loan.amount - prepaid,
|
|
429
|
-
feePercent: mulDiv(
|
|
430
|
-
timeSinceLoanCreated - loan.prepaidDuration,
|
|
431
|
-
JBConstants.MAX_FEE,
|
|
432
|
-
LOAN_LIQUIDATION_DURATION - loan.prepaidDuration
|
|
433
|
-
)
|
|
433
|
+
feePercent: mulDiv({
|
|
434
|
+
x: timeSinceLoanCreated - loan.prepaidDuration,
|
|
435
|
+
y: JBConstants.MAX_FEE,
|
|
436
|
+
denominator: LOAN_LIQUIDATION_DURATION - loan.prepaidDuration
|
|
437
|
+
})
|
|
434
438
|
});
|
|
435
439
|
|
|
436
440
|
// Calculate the source fee amount for the amount being paid off.
|
|
437
|
-
return mulDiv(fullSourceFeeAmount, amount, loan.amount);
|
|
441
|
+
return mulDiv({x: fullSourceFeeAmount, y: amount, denominator: loan.amount});
|
|
438
442
|
}
|
|
439
443
|
|
|
440
444
|
/// @notice Generate a ID for a loan given a revnet ID and a loan number within that revnet.
|
|
@@ -461,9 +465,8 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
461
465
|
/// @dev Each source's `totalBorrowedFrom` is stored in the source token's native decimals (e.g. 6 for USDC,
|
|
462
466
|
/// 18 for ETH). Before aggregation, each amount is normalized to the target `decimals` to prevent mixed-decimal
|
|
463
467
|
/// arithmetic errors. For cross-currency sources, the normalized amount is then converted via the price feed.
|
|
464
|
-
/// @dev
|
|
465
|
-
///
|
|
466
|
-
/// mulDiv(1e6, 1e6, 1e21) = 0), which would cause a division-by-zero in the price conversion.
|
|
468
|
+
/// @dev Inverse price feeds may truncate to zero at low decimal counts (e.g. a feed returning 1e21 at 6 decimals
|
|
469
|
+
/// inverts to mulDiv(1e6, 1e6, 1e21) = 0). Sources with a zero price are skipped to prevent division-by-zero.
|
|
467
470
|
/// @param revnetId The ID of the revnet to check for borrowed assets from.
|
|
468
471
|
/// @param decimals The decimals the resulting fixed point value will include.
|
|
469
472
|
/// @param currency The currency the resulting value will be in terms of.
|
|
@@ -519,7 +522,12 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
519
522
|
decimals: decimals
|
|
520
523
|
});
|
|
521
524
|
|
|
522
|
-
|
|
525
|
+
// If the price feed returns zero, skip this source to avoid a division-by-zero panic
|
|
526
|
+
// that would DoS all loan operations. This intentionally understates total debt for
|
|
527
|
+
// the affected source — an acceptable tradeoff vs. blocking every borrow/repay.
|
|
528
|
+
if (pricePerUnit == 0) continue;
|
|
529
|
+
|
|
530
|
+
borrowedAmount += mulDiv({x: normalizedTokens, y: 10 ** decimals, denominator: pricePerUnit});
|
|
523
531
|
}
|
|
524
532
|
}
|
|
525
533
|
}
|
|
@@ -529,6 +537,8 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
529
537
|
//*********************************************************************//
|
|
530
538
|
|
|
531
539
|
/// @notice Open a loan by borrowing from a revnet.
|
|
540
|
+
/// @dev The caller must first grant BURN_TOKENS permission to this contract via JBPermissions.setPermissionsFor().
|
|
541
|
+
/// This is required because collateral posting burns the caller's tokens through the controller.
|
|
532
542
|
/// @dev Collateral tokens are permanently burned when the loan is created. They are re-minted to the borrower
|
|
533
543
|
/// only upon repayment. If the loan expires (after LOAN_LIQUIDATION_DURATION), the collateral is permanently
|
|
534
544
|
/// lost and cannot be recovered.
|
|
@@ -558,7 +568,7 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
558
568
|
if (collateralCount == 0) revert REVLoans_ZeroCollateralLoanIsInvalid();
|
|
559
569
|
|
|
560
570
|
// Make sure the source terminal is registered in the directory for this revnet.
|
|
561
|
-
if (!DIRECTORY.isTerminalOf(revnetId, IJBTerminal(address(source.terminal)))) {
|
|
571
|
+
if (!DIRECTORY.isTerminalOf({projectId: revnetId, terminal: IJBTerminal(address(source.terminal))})) {
|
|
562
572
|
revert REVLoans_InvalidTerminal(address(source.terminal), revnetId);
|
|
563
573
|
}
|
|
564
574
|
|
|
@@ -579,17 +589,23 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
579
589
|
|
|
580
590
|
// Set the loan's values.
|
|
581
591
|
loan.source = source;
|
|
582
|
-
loan.createdAt =
|
|
592
|
+
loan.createdAt = uint48(block.timestamp);
|
|
593
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
583
594
|
loan.prepaidFeePercent = uint16(prepaidFeePercent);
|
|
584
|
-
loan.prepaidDuration =
|
|
595
|
+
loan.prepaidDuration =
|
|
596
|
+
uint32(mulDiv({x: prepaidFeePercent, y: LOAN_LIQUIDATION_DURATION, denominator: MAX_PREPAID_FEE_PERCENT}));
|
|
585
597
|
|
|
586
598
|
// Get the amount of the loan.
|
|
587
599
|
uint256 borrowAmount = _borrowAmountFrom({loan: loan, revnetId: revnetId, collateralCount: collateralCount});
|
|
588
600
|
|
|
601
|
+
// Revert if the bonding curve returns zero to prevent creating zero-amount loans.
|
|
602
|
+
if (borrowAmount == 0) revert REVLoans_ZeroBorrowAmount();
|
|
603
|
+
|
|
589
604
|
// Make sure the minimum borrow amount is met.
|
|
590
605
|
if (borrowAmount < minBorrowAmount) revert REVLoans_UnderMinBorrowAmount(minBorrowAmount, borrowAmount);
|
|
591
606
|
|
|
592
607
|
// Get the amount of additional fee to take for the revnet issuing the loan.
|
|
608
|
+
// Fee rounding may leave a few wei of dust — economically insignificant relative to gas costs.
|
|
593
609
|
uint256 sourceFeeAmount = JBFees.feeAmountFrom({amountBeforeFee: borrowAmount, feePercent: prepaidFeePercent});
|
|
594
610
|
|
|
595
611
|
// Borrow the amount.
|
|
@@ -634,6 +650,9 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
634
650
|
/// @param startingLoanId The ID of the loan to start iterating from.
|
|
635
651
|
/// @param count The amount of loans iterate over since the last liquidated loan.
|
|
636
652
|
function liquidateExpiredLoansFrom(uint256 revnetId, uint256 startingLoanId, uint256 count) external override {
|
|
653
|
+
// Prevent cross-revnet accounting corruption: loan numbers must stay within the revnet's ID namespace.
|
|
654
|
+
if (startingLoanId + count > _ONE_TRILLION) revert REVLoans_LoanIdOverflow();
|
|
655
|
+
|
|
637
656
|
// Iterate over the desired number of loans to check for liquidation.
|
|
638
657
|
for (uint256 i; i < count; i++) {
|
|
639
658
|
// Get a reference to the next loan ID.
|
|
@@ -794,7 +813,7 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
794
813
|
if (repayBorrowAmount == 0 && collateralCountToReturn == 0) revert REVLoans_NothingToRepay();
|
|
795
814
|
|
|
796
815
|
// Keep a reference to the fee that'll be taken.
|
|
797
|
-
uint256 sourceFeeAmount = _determineSourceFeeAmount(loan, repayBorrowAmount);
|
|
816
|
+
uint256 sourceFeeAmount = _determineSourceFeeAmount({loan: loan, amount: repayBorrowAmount});
|
|
798
817
|
|
|
799
818
|
// Add the fee to the repay amount.
|
|
800
819
|
repayBorrowAmount += sourceFeeAmount;
|
|
@@ -927,8 +946,8 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
927
946
|
internal
|
|
928
947
|
{
|
|
929
948
|
// Register the source if this is the first time its being used for this revnet.
|
|
930
|
-
// Note: Sources are only appended, never removed.
|
|
931
|
-
// (terminal, token) pairs per revnet is practically
|
|
949
|
+
// Note: Sources are only appended, never removed. Gas accumulation from iteration is bounded
|
|
950
|
+
// because the number of distinct (terminal, token) pairs per revnet is practically small (~5-20).
|
|
932
951
|
if (!isLoanSourceOf[revnetId][loan.source.terminal][loan.source.token]) {
|
|
933
952
|
isLoanSourceOf[revnetId][loan.source.terminal][loan.source.token] = true;
|
|
934
953
|
_loanSourcesOf[revnetId].push(REVLoanSource({token: loan.source.token, terminal: loan.source.terminal}));
|
|
@@ -994,6 +1013,9 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
994
1013
|
}
|
|
995
1014
|
|
|
996
1015
|
// Transfer the remaining balance to the borrower.
|
|
1016
|
+
// Note: In extreme fee configurations the subtraction could theoretically underflow, but the
|
|
1017
|
+
// protocol fee (2.5%) and source fee (capped at prepaidFeePercent) are both small fractions of
|
|
1018
|
+
// the borrowed amount, so `netAmountPaidOut` will always exceed their sum in practice.
|
|
997
1019
|
_transferFrom({
|
|
998
1020
|
from: address(this),
|
|
999
1021
|
to: beneficiary,
|
|
@@ -1034,7 +1056,9 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
1034
1056
|
if (newCollateralCount > type(uint112).max) {
|
|
1035
1057
|
revert REVLoans_OverflowAlert(newCollateralCount, type(uint112).max);
|
|
1036
1058
|
}
|
|
1059
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
1037
1060
|
loan.amount = uint112(newBorrowAmount);
|
|
1061
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
1038
1062
|
loan.collateral = uint112(newCollateralCount);
|
|
1039
1063
|
|
|
1040
1064
|
// INTERACTIONS: Execute external calls with pre-computed deltas.
|
|
@@ -1063,16 +1087,17 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
1063
1087
|
});
|
|
1064
1088
|
}
|
|
1065
1089
|
|
|
1066
|
-
// If there is a source fee, pay it.
|
|
1090
|
+
// If there is a source fee, pay it. Wrapped in try-catch so a reverting source terminal
|
|
1091
|
+
// cannot block all loan operations (matching the REV fee pattern above).
|
|
1067
1092
|
if (sourceFeeAmount > 0) {
|
|
1068
|
-
// Increase the allowance for the
|
|
1093
|
+
// Increase the allowance for the source terminal.
|
|
1069
1094
|
uint256 payValue = _beforeTransferTo({
|
|
1070
1095
|
to: address(loan.source.terminal), token: loan.source.token, amount: sourceFeeAmount
|
|
1071
1096
|
});
|
|
1072
1097
|
|
|
1073
|
-
// Pay the fee.
|
|
1074
|
-
// slither-disable-next-line unused-return
|
|
1075
|
-
loan.source.terminal.pay{value: payValue}({
|
|
1098
|
+
// Pay the fee. If it fails, reclaim the allowance and give the amount back to the borrower.
|
|
1099
|
+
// slither-disable-next-line unused-return,arbitrary-send-eth
|
|
1100
|
+
try loan.source.terminal.pay{value: payValue}({
|
|
1076
1101
|
projectId: revnetId,
|
|
1077
1102
|
token: loan.source.token,
|
|
1078
1103
|
amount: sourceFeeAmount,
|
|
@@ -1080,7 +1105,18 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
1080
1105
|
minReturnedTokens: 0,
|
|
1081
1106
|
memo: "Fee from loan",
|
|
1082
1107
|
metadata: bytes(abi.encodePacked(REV_ID))
|
|
1083
|
-
})
|
|
1108
|
+
}) {}
|
|
1109
|
+
catch (bytes memory) {
|
|
1110
|
+
// If the fee can't be processed, decrease the ERC-20 allowance and return the amount
|
|
1111
|
+
// to the beneficiary instead.
|
|
1112
|
+
if (loan.source.token != JBConstants.NATIVE_TOKEN) {
|
|
1113
|
+
IERC20(loan.source.token)
|
|
1114
|
+
.safeDecreaseAllowance({
|
|
1115
|
+
spender: address(loan.source.terminal), requestedDecrease: sourceFeeAmount
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1118
|
+
_transferFrom({from: address(this), to: beneficiary, token: loan.source.token, amount: sourceFeeAmount});
|
|
1119
|
+
}
|
|
1084
1120
|
}
|
|
1085
1121
|
}
|
|
1086
1122
|
|
|
@@ -1093,7 +1129,7 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
1093
1129
|
function _beforeTransferTo(address to, address token, uint256 amount) internal returns (uint256) {
|
|
1094
1130
|
// If the token is the native token, no allowance needed.
|
|
1095
1131
|
if (token == JBConstants.NATIVE_TOKEN) return amount;
|
|
1096
|
-
IERC20(token).safeIncreaseAllowance(to, amount);
|
|
1132
|
+
IERC20(token).safeIncreaseAllowance({spender: to, value: amount});
|
|
1097
1133
|
return 0;
|
|
1098
1134
|
}
|
|
1099
1135
|
|
|
@@ -1342,7 +1378,7 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
1342
1378
|
}
|
|
1343
1379
|
|
|
1344
1380
|
// If there's sufficient approval, transfer normally.
|
|
1345
|
-
if (IERC20(token).allowance(address(from), address(this)) >= amount) {
|
|
1381
|
+
if (IERC20(token).allowance({owner: address(from), spender: address(this)}) >= amount) {
|
|
1346
1382
|
return IERC20(token).safeTransferFrom({from: from, to: to, value: amount});
|
|
1347
1383
|
}
|
|
1348
1384
|
|
|
@@ -1350,6 +1386,7 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
1350
1386
|
if (amount > type(uint160).max) revert REVLoans_OverflowAlert(amount, type(uint160).max);
|
|
1351
1387
|
|
|
1352
1388
|
// Otherwise, attempt to use the `permit2` method.
|
|
1389
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
1353
1390
|
PERMIT2.transferFrom({from: from, to: to, amount: uint160(amount), token: token});
|
|
1354
1391
|
}
|
|
1355
1392
|
|
|
@@ -8,7 +8,6 @@ import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
|
|
|
8
8
|
import {IJBPermissions} from "@bananapus/core-v6/src/interfaces/IJBPermissions.sol";
|
|
9
9
|
import {IJBProjects} from "@bananapus/core-v6/src/interfaces/IJBProjects.sol";
|
|
10
10
|
import {IJBBuybackHookRegistry} from "@bananapus/buyback-hook-v6/src/interfaces/IJBBuybackHookRegistry.sol";
|
|
11
|
-
import {IJBRulesetDataHook} from "@bananapus/core-v6/src/interfaces/IJBRulesetDataHook.sol";
|
|
12
11
|
import {JBRulesetConfig} from "@bananapus/core-v6/src/structs/JBRulesetConfig.sol";
|
|
13
12
|
import {JBTerminalConfig} from "@bananapus/core-v6/src/structs/JBTerminalConfig.sol";
|
|
14
13
|
import {IJBSuckerRegistry} from "@bananapus/suckers-v6/src/interfaces/IJBSuckerRegistry.sol";
|
|
@@ -182,52 +181,55 @@ interface IREVDeployer {
|
|
|
182
181
|
/// @param revnetId The ID of the revnet whose tokens should be burned.
|
|
183
182
|
function burnHeldTokensOf(uint256 revnetId) external;
|
|
184
183
|
|
|
185
|
-
/// @notice Deploy a revnet
|
|
184
|
+
/// @notice Deploy a revnet with a tiered ERC-721 hook and optional croptop posting support.
|
|
185
|
+
/// @dev Every revnet gets a 721 hook — pass an empty config if no tiers are needed initially.
|
|
186
186
|
/// @param revnetId The ID of the Juicebox project to initialize. Send 0 to deploy a new revnet.
|
|
187
187
|
/// @param configuration Core revnet configuration.
|
|
188
188
|
/// @param terminalConfigurations The terminals to set up for the revnet.
|
|
189
189
|
/// @param suckerDeploymentConfiguration The suckers to set up for cross-chain token transfers.
|
|
190
|
+
/// @param tiered721HookConfiguration How to set up the tiered ERC-721 hook for the revnet.
|
|
191
|
+
/// @param allowedPosts Restrictions on which croptop posts are allowed on the revnet's ERC-721 tiers.
|
|
190
192
|
/// @return The ID of the newly created or initialized revnet.
|
|
193
|
+
/// @return hook The tiered ERC-721 hook that was deployed for the revnet.
|
|
191
194
|
function deployFor(
|
|
192
195
|
uint256 revnetId,
|
|
193
196
|
REVConfig memory configuration,
|
|
194
197
|
JBTerminalConfig[] memory terminalConfigurations,
|
|
195
|
-
REVSuckerDeploymentConfig memory suckerDeploymentConfiguration
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
returns (uint256);
|
|
199
|
-
|
|
200
|
-
/// @notice Deploy new suckers for an existing revnet.
|
|
201
|
-
/// @param revnetId The ID of the revnet to deploy suckers for.
|
|
202
|
-
/// @param suckerDeploymentConfiguration The suckers to set up for the revnet.
|
|
203
|
-
/// @return suckers The addresses of the deployed suckers.
|
|
204
|
-
function deploySuckersFor(
|
|
205
|
-
uint256 revnetId,
|
|
206
|
-
REVSuckerDeploymentConfig calldata suckerDeploymentConfiguration
|
|
198
|
+
REVSuckerDeploymentConfig memory suckerDeploymentConfiguration,
|
|
199
|
+
REVDeploy721TiersHookConfig memory tiered721HookConfiguration,
|
|
200
|
+
REVCroptopAllowedPost[] memory allowedPosts
|
|
207
201
|
)
|
|
208
202
|
external
|
|
209
|
-
returns (
|
|
203
|
+
returns (uint256, IJB721TiersHook hook);
|
|
210
204
|
|
|
211
|
-
/// @notice Deploy a revnet with tiered ERC-
|
|
205
|
+
/// @notice Deploy a revnet with a default empty tiered ERC-721 hook.
|
|
206
|
+
/// @dev Convenience overload — constructs an empty 721 config internally and delegates to the 6-arg version.
|
|
212
207
|
/// @param revnetId The ID of the Juicebox project to initialize. Send 0 to deploy a new revnet.
|
|
213
208
|
/// @param configuration Core revnet configuration.
|
|
214
209
|
/// @param terminalConfigurations The terminals to set up for the revnet.
|
|
215
210
|
/// @param suckerDeploymentConfiguration The suckers to set up for cross-chain token transfers.
|
|
216
|
-
/// @param tiered721HookConfiguration How to set up the tiered ERC-721 hook.
|
|
217
|
-
/// @param allowedPosts Restrictions on which croptop posts are allowed on the revnet's ERC-721 tiers.
|
|
218
211
|
/// @return The ID of the newly created or initialized revnet.
|
|
219
212
|
/// @return hook The tiered ERC-721 hook that was deployed for the revnet.
|
|
220
|
-
function
|
|
213
|
+
function deployFor(
|
|
221
214
|
uint256 revnetId,
|
|
222
|
-
REVConfig
|
|
215
|
+
REVConfig memory configuration,
|
|
223
216
|
JBTerminalConfig[] memory terminalConfigurations,
|
|
224
|
-
REVSuckerDeploymentConfig memory suckerDeploymentConfiguration
|
|
225
|
-
REVDeploy721TiersHookConfig memory tiered721HookConfiguration,
|
|
226
|
-
REVCroptopAllowedPost[] memory allowedPosts
|
|
217
|
+
REVSuckerDeploymentConfig memory suckerDeploymentConfiguration
|
|
227
218
|
)
|
|
228
219
|
external
|
|
229
220
|
returns (uint256, IJB721TiersHook hook);
|
|
230
221
|
|
|
222
|
+
/// @notice Deploy new suckers for an existing revnet.
|
|
223
|
+
/// @param revnetId The ID of the revnet to deploy suckers for.
|
|
224
|
+
/// @param suckerDeploymentConfiguration The suckers to set up for the revnet.
|
|
225
|
+
/// @return suckers The addresses of the deployed suckers.
|
|
226
|
+
function deploySuckersFor(
|
|
227
|
+
uint256 revnetId,
|
|
228
|
+
REVSuckerDeploymentConfig calldata suckerDeploymentConfiguration
|
|
229
|
+
)
|
|
230
|
+
external
|
|
231
|
+
returns (address[] memory suckers);
|
|
232
|
+
|
|
231
233
|
/// @notice Change a revnet's split operator. Only the current split operator can call this.
|
|
232
234
|
/// @param revnetId The ID of the revnet.
|
|
233
235
|
/// @param newSplitOperator The new split operator's address.
|
|
@@ -6,6 +6,7 @@ pragma solidity ^0.8.0;
|
|
|
6
6
|
/// @custom:member noNewTiersWithOwnerMinting A flag indicating if new tiers with owner minting are forbidden.
|
|
7
7
|
/// @custom:member preventOverspending A flag indicating if payments exceeding the price of minted NFTs should be
|
|
8
8
|
/// prevented.
|
|
9
|
+
// forge-lint: disable-next-line(pascal-case-struct)
|
|
9
10
|
struct REV721TiersHookFlags {
|
|
10
11
|
bool noNewTiersWithReserves;
|
|
11
12
|
bool noNewTiersWithVotes;
|
|
@@ -4,6 +4,7 @@ pragma solidity ^0.8.0;
|
|
|
4
4
|
/// @custom:member chainId The ID of the chain on which the mint should be honored.
|
|
5
5
|
/// @custom:member count The number of tokens that should be minted.
|
|
6
6
|
/// @custom:member beneficiary The address that will receive the minted tokens.
|
|
7
|
+
// forge-lint: disable-next-line(pascal-case-struct)
|
|
7
8
|
struct REVAutoIssuance {
|
|
8
9
|
uint32 chainId;
|
|
9
10
|
uint104 count;
|
|
@@ -15,6 +15,7 @@ import {REV721TiersHookFlags} from "./REV721TiersHookFlags.sol";
|
|
|
15
15
|
/// @custom:member reserveBeneficiary The default reserve beneficiary for the NFT collection.
|
|
16
16
|
/// @custom:member flags A set of flags that configure the 721 hook. Omits `issueTokensForSplits` since revnets
|
|
17
17
|
/// always force it to `false`.
|
|
18
|
+
// forge-lint: disable-next-line(pascal-case-struct)
|
|
18
19
|
struct REVBaseline721HookConfig {
|
|
19
20
|
string name;
|
|
20
21
|
string symbol;
|
|
@@ -9,6 +9,7 @@ import {REVStageConfig} from "./REVStageConfig.sol";
|
|
|
9
9
|
/// @custom:member splitOperator The address that will receive the token premint and initial production split,
|
|
10
10
|
/// and who is allowed to change who the operator is. Only the operator can replace itself after deployment.
|
|
11
11
|
/// @custom:member stageConfigurations The periods of changing constraints.
|
|
12
|
+
// forge-lint: disable-next-line(pascal-case-struct)
|
|
12
13
|
struct REVConfig {
|
|
13
14
|
REVDescription description;
|
|
14
15
|
uint32 baseCurrency;
|
|
@@ -10,6 +10,7 @@ pragma solidity ^0.8.0;
|
|
|
10
10
|
/// @custom:member maximumSplitPercent The maximum split percent (out of JBConstants.SPLITS_TOTAL_PERCENT) that a
|
|
11
11
|
/// poster can set. 0 means splits are not allowed.
|
|
12
12
|
/// @custom:member allowedAddresses A list of addresses that are allowed to post on the category through Croptop.
|
|
13
|
+
// forge-lint: disable-next-line(pascal-case-struct)
|
|
13
14
|
struct REVCroptopAllowedPost {
|
|
14
15
|
uint24 category;
|
|
15
16
|
uint104 minimumPrice;
|
|
@@ -5,21 +5,20 @@ import {REVBaseline721HookConfig} from "./REVBaseline721HookConfig.sol";
|
|
|
5
5
|
|
|
6
6
|
/// @custom:member baseline721HookConfiguration The baseline config.
|
|
7
7
|
/// @custom:member salt The salt to base the collection's address on.
|
|
8
|
-
/// @custom:member
|
|
9
|
-
/// tiers
|
|
10
|
-
/// the
|
|
11
|
-
///
|
|
12
|
-
///
|
|
13
|
-
///
|
|
14
|
-
///
|
|
15
|
-
///
|
|
16
|
-
|
|
17
|
-
/// discount of a tier.
|
|
8
|
+
/// @custom:member preventSplitOperatorAdjustingTiers A flag indicating if the revnet's split operator should be
|
|
9
|
+
/// prevented from adding tiers and removing tiers that are allowed to be removed.
|
|
10
|
+
/// @custom:member preventSplitOperatorUpdatingMetadata A flag indicating if the revnet's split operator should be
|
|
11
|
+
/// prevented from updating the 721's metadata.
|
|
12
|
+
/// @custom:member preventSplitOperatorMinting A flag indicating if the revnet's split operator should be prevented from
|
|
13
|
+
/// minting 721's from tiers that allow it.
|
|
14
|
+
/// @custom:member preventSplitOperatorIncreasingDiscountPercent A flag indicating if the revnet's split operator should
|
|
15
|
+
/// be prevented from increasing the discount of a tier.
|
|
16
|
+
// forge-lint: disable-next-line(pascal-case-struct)
|
|
18
17
|
struct REVDeploy721TiersHookConfig {
|
|
19
18
|
REVBaseline721HookConfig baseline721HookConfiguration;
|
|
20
19
|
bytes32 salt;
|
|
21
|
-
bool
|
|
22
|
-
bool
|
|
23
|
-
bool
|
|
24
|
-
bool
|
|
20
|
+
bool preventSplitOperatorAdjustingTiers;
|
|
21
|
+
bool preventSplitOperatorUpdatingMetadata;
|
|
22
|
+
bool preventSplitOperatorMinting;
|
|
23
|
+
bool preventSplitOperatorIncreasingDiscountPercent;
|
|
25
24
|
}
|
|
@@ -6,6 +6,7 @@ pragma solidity ^0.8.0;
|
|
|
6
6
|
/// @custom:member uri The metadata URI containing revnet's info.
|
|
7
7
|
/// @custom:member salt Revnets deployed across chains by the same address with the same salt will have the same
|
|
8
8
|
/// address.
|
|
9
|
+
// forge-lint: disable-next-line(pascal-case-struct)
|
|
9
10
|
struct REVDescription {
|
|
10
11
|
string name;
|
|
11
12
|
string ticker;
|
package/src/structs/REVLoan.sol
CHANGED
|
@@ -9,6 +9,7 @@ import {REVLoanSource} from "./REVLoanSource.sol";
|
|
|
9
9
|
/// @custom:member prepaidFeePercent The percentage of the loan's fees that were prepaid.
|
|
10
10
|
/// @custom:member prepaidDuration The duration that the loan was prepaid for.
|
|
11
11
|
/// @custom:member source The source of the loan.
|
|
12
|
+
// forge-lint: disable-next-line(pascal-case-struct)
|
|
12
13
|
struct REVLoan {
|
|
13
14
|
uint112 amount;
|
|
14
15
|
uint112 collateral;
|
|
@@ -5,6 +5,7 @@ import {IJBPayoutTerminal} from "@bananapus/core-v6/src/interfaces/IJBPayoutTerm
|
|
|
5
5
|
|
|
6
6
|
/// @custom:member token The token that is being loaned.
|
|
7
7
|
/// @custom:member terminal The terminal that the loan is being made from.
|
|
8
|
+
// forge-lint: disable-next-line(pascal-case-struct)
|
|
8
9
|
struct REVLoanSource {
|
|
9
10
|
address token;
|
|
10
11
|
IJBPayoutTerminal terminal;
|
|
@@ -21,6 +21,7 @@ import {REVAutoIssuance} from "./REVAutoIssuance.sol";
|
|
|
21
21
|
/// cashed out. This rate is out of 10_000 (JBConstants.MAX_CASH_OUT_TAX_RATE). 0% corresponds to no tax when cashing
|
|
22
22
|
/// out.
|
|
23
23
|
/// @custom:member extraMetadata Extra info to attach set into this stage that may affect hooks.
|
|
24
|
+
// forge-lint: disable-next-line(pascal-case-struct)
|
|
24
25
|
struct REVStageConfig {
|
|
25
26
|
uint48 startsAtOrAfter;
|
|
26
27
|
REVAutoIssuance[] autoIssuances;
|
|
@@ -5,6 +5,7 @@ import {JBSuckerDeployerConfig} from "@bananapus/suckers-v6/src/structs/JBSucker
|
|
|
5
5
|
|
|
6
6
|
/// @custom:member deployerConfigurations The information for how to suck tokens to other chains.
|
|
7
7
|
/// @custom:member salt The salt to use for creating suckers so that they use the same address across chains.
|
|
8
|
+
// forge-lint: disable-next-line(pascal-case-struct)
|
|
8
9
|
struct REVSuckerDeploymentConfig {
|
|
9
10
|
JBSuckerDeployerConfig[] deployerConfigurations;
|
|
10
11
|
bytes32 salt;
|