@rev-net/core-v6 0.0.29 → 0.0.30
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 +19 -9
- package/ARCHITECTURE.md +3 -0
- package/AUDIT_INSTRUCTIONS.md +11 -1
- package/CHANGELOG.md +26 -0
- package/README.md +1 -0
- package/RISKS.md +28 -4
- package/SKILLS.md +2 -1
- package/USER_JOURNEYS.md +17 -3
- package/package.json +2 -2
- package/references/operations.md +1 -1
- package/script/Deploy.s.sol +25 -6
- package/src/REVHiddenTokens.sol +149 -0
- package/src/REVLoans.sol +115 -144
- package/src/REVOwner.sol +11 -3
- package/src/interfaces/IREVHiddenTokens.sol +53 -0
- package/src/interfaces/IREVLoans.sol +3 -6
- package/test/REV.integrations.t.sol +2 -1
- package/test/REVAutoIssuanceFuzz.t.sol +2 -1
- package/test/REVDeployerRegressions.t.sol +2 -2
- package/test/REVInvincibility.t.sol +6 -6
- package/test/REVInvincibilityHandler.sol +1 -1
- package/test/REVLifecycle.t.sol +2 -2
- package/test/REVLoans.invariants.t.sol +3 -3
- package/test/REVLoansAttacks.t.sol +7 -6
- package/test/REVLoansFeeRecovery.t.sol +12 -12
- package/test/REVLoansFindings.t.sol +4 -4
- package/test/REVLoansRegressions.t.sol +3 -3
- package/test/REVLoansSourceFeeRecovery.t.sol +4 -4
- package/test/REVLoansSourced.t.sol +48 -24
- package/test/REVLoansUnSourced.t.sol +3 -3
- package/test/TestBurnHeldTokens.t.sol +2 -2
- package/test/TestCEIPattern.t.sol +7 -6
- package/test/TestCashOutCallerValidation.t.sol +2 -2
- package/test/TestConversionDocumentation.t.sol +2 -2
- package/test/TestCrossCurrencyReclaim.t.sol +2 -2
- package/test/TestCrossSourceReallocation.t.sol +3 -3
- package/test/TestERC2771MetaTx.t.sol +6 -4
- package/test/TestEmptyBuybackSpecs.t.sol +2 -2
- package/test/TestFlashLoanSurplus.t.sol +3 -3
- package/test/TestHiddenTokens.t.sol +420 -0
- package/test/TestHookArrayOOB.t.sol +2 -2
- package/test/TestLiquidationBehavior.t.sol +4 -4
- package/test/TestLoanSourceRotation.t.sol +8 -6
- package/test/TestLoansCashOutDelay.t.sol +6 -6
- package/test/TestLongTailEconomics.t.sol +2 -2
- package/test/TestLowFindings.t.sol +13 -8
- package/test/TestMixedFixes.t.sol +7 -7
- package/test/TestPermit2Signatures.t.sol +3 -3
- package/test/TestReallocationSandwich.t.sol +4 -3
- package/test/TestRevnetRegressions.t.sol +3 -4
- package/test/TestSplitWeightAdjustment.t.sol +4 -3
- package/test/TestSplitWeightE2E.t.sol +4 -3
- package/test/TestSplitWeightFork.t.sol +2 -2
- package/test/TestStageTransitionBorrowable.t.sol +2 -2
- package/test/TestSwapTerminalPermission.t.sol +2 -2
- package/test/TestUint112Overflow.t.sol +3 -3
- package/test/TestZeroAmountLoanGuard.t.sol +3 -3
- package/test/TestZeroRepayment.t.sol +3 -3
- package/test/audit/LoanIdOverflowGuard.t.sol +4 -4
- package/test/audit/NemesisOperatorDelegation.t.sol +278 -0
- package/test/fork/ForkTestBase.sol +4 -3
- package/test/fork/TestLoanBorrowFork.t.sol +2 -1
- package/test/fork/TestLoanERC20Fork.t.sol +4 -2
- package/test/fork/TestLoanTransferFork.t.sol +12 -2
- package/test/helpers/MaliciousContracts.sol +1 -1
- package/test/regression/TestBurnPermissionRequired.t.sol +4 -4
- package/test/regression/TestCashOutBuybackFeeLeak.t.sol +2 -2
- package/test/regression/TestCrossRevnetLiquidation.t.sol +2 -2
- package/test/regression/TestCumulativeLoanCounter.t.sol +3 -3
- package/test/regression/TestLiquidateGapHandling.t.sol +3 -3
- package/test/regression/TestZeroPriceFeed.t.sol +5 -5
|
@@ -328,7 +328,6 @@ abstract contract ForkTestBase is TestBaseWorkflow {
|
|
|
328
328
|
|
|
329
329
|
LOANS_CONTRACT = new REVLoans({
|
|
330
330
|
controller: jbController(),
|
|
331
|
-
projects: jbProjects(),
|
|
332
331
|
revId: FEE_PROJECT_ID,
|
|
333
332
|
owner: address(this),
|
|
334
333
|
permit2: permit2(),
|
|
@@ -340,7 +339,8 @@ abstract contract ForkTestBase is TestBaseWorkflow {
|
|
|
340
339
|
jbDirectory(),
|
|
341
340
|
FEE_PROJECT_ID,
|
|
342
341
|
SUCKER_REGISTRY,
|
|
343
|
-
address(LOANS_CONTRACT)
|
|
342
|
+
address(LOANS_CONTRACT),
|
|
343
|
+
address(0)
|
|
344
344
|
);
|
|
345
345
|
|
|
346
346
|
REV_DEPLOYER = new REVDeployer{salt: "REVDeployer_Fork"}(
|
|
@@ -707,7 +707,8 @@ abstract contract ForkTestBase is TestBaseWorkflow {
|
|
|
707
707
|
minBorrowAmount: 0,
|
|
708
708
|
collateralCount: collateral,
|
|
709
709
|
beneficiary: payable(borrower),
|
|
710
|
-
prepaidFeePercent: prepaidFeePercent
|
|
710
|
+
prepaidFeePercent: prepaidFeePercent,
|
|
711
|
+
holder: borrower
|
|
711
712
|
});
|
|
712
713
|
}
|
|
713
714
|
}
|
|
@@ -97,7 +97,8 @@ contract TestLoanBorrowFork is ForkTestBase {
|
|
|
97
97
|
minBorrowAmount: 0,
|
|
98
98
|
collateralCount: borrowerTokens,
|
|
99
99
|
beneficiary: payable(BORROWER),
|
|
100
|
-
prepaidFeePercent: prepaidFeePercent
|
|
100
|
+
prepaidFeePercent: prepaidFeePercent,
|
|
101
|
+
holder: BORROWER
|
|
101
102
|
});
|
|
102
103
|
|
|
103
104
|
uint256 borrowerReceived = BORROWER.balance - borrowerEthBefore;
|
|
@@ -184,7 +184,8 @@ contract TestLoanERC20Fork is ForkTestBase {
|
|
|
184
184
|
minBorrowAmount: 0,
|
|
185
185
|
collateralCount: collateral,
|
|
186
186
|
beneficiary: payable(borrower),
|
|
187
|
-
prepaidFeePercent: prepaidFeePercent
|
|
187
|
+
prepaidFeePercent: prepaidFeePercent,
|
|
188
|
+
holder: borrower
|
|
188
189
|
});
|
|
189
190
|
}
|
|
190
191
|
|
|
@@ -263,7 +264,8 @@ contract TestLoanERC20Fork is ForkTestBase {
|
|
|
263
264
|
minBorrowAmount: 0,
|
|
264
265
|
collateralCount: borrowerTokens,
|
|
265
266
|
beneficiary: payable(BORROWER),
|
|
266
|
-
prepaidFeePercent: prepaidFeePercent
|
|
267
|
+
prepaidFeePercent: prepaidFeePercent,
|
|
268
|
+
holder: BORROWER
|
|
267
269
|
});
|
|
268
270
|
|
|
269
271
|
uint256 borrowerUsdcReceived = IERC20(USDC).balanceOf(BORROWER) - borrowerUsdcBefore;
|
|
@@ -3,6 +3,8 @@ pragma solidity 0.8.28;
|
|
|
3
3
|
|
|
4
4
|
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
5
5
|
import "./ForkTestBase.sol";
|
|
6
|
+
import {JBPermissioned} from "@bananapus/core-v6/src/abstract/JBPermissioned.sol";
|
|
7
|
+
import {JBPermissionIds} from "@bananapus/permission-ids-v6/src/JBPermissionIds.sol";
|
|
6
8
|
|
|
7
9
|
/// @notice Fork tests for transferring loan NFTs and repaying from the new owner.
|
|
8
10
|
///
|
|
@@ -79,9 +81,17 @@ contract TestLoanTransferFork is ForkTestBase {
|
|
|
79
81
|
|
|
80
82
|
JBSingleAllowance memory allowance;
|
|
81
83
|
|
|
82
|
-
// Original borrower tries to repay — should revert with
|
|
84
|
+
// Original borrower tries to repay — should revert with JBPermissioned_Unauthorized.
|
|
83
85
|
vm.prank(BORROWER);
|
|
84
|
-
vm.expectRevert(
|
|
86
|
+
vm.expectRevert(
|
|
87
|
+
abi.encodeWithSelector(
|
|
88
|
+
JBPermissioned.JBPermissioned_Unauthorized.selector,
|
|
89
|
+
newOwner,
|
|
90
|
+
BORROWER,
|
|
91
|
+
revnetId,
|
|
92
|
+
JBPermissionIds.REPAY_LOAN
|
|
93
|
+
)
|
|
94
|
+
);
|
|
85
95
|
LOANS_CONTRACT.repayLoan{value: loan.amount * 2}({
|
|
86
96
|
loanId: loanId,
|
|
87
97
|
maxRepayBorrowAmount: loan.amount * 2,
|
|
@@ -162,7 +162,7 @@ contract SurplusInflator is ERC165, IJBPayoutTerminal {
|
|
|
162
162
|
shouldInflate = false;
|
|
163
163
|
// Try to borrow at the inflated surplus
|
|
164
164
|
REVLoanSource memory source = REVLoanSource({token: JBConstants.NATIVE_TOKEN, terminal: realTerminal});
|
|
165
|
-
try loans.borrowFrom(revnetId, source, 0, 1e18, payable(address(this)), 25) {} catch {}
|
|
165
|
+
try loans.borrowFrom(revnetId, source, 0, 1e18, payable(address(this)), 25, address(this)) {} catch {}
|
|
166
166
|
}
|
|
167
167
|
return 0;
|
|
168
168
|
}
|
|
@@ -102,7 +102,6 @@ contract TestBurnPermissionRequired is TestBaseWorkflow {
|
|
|
102
102
|
.addPriceFeedFor(0, uint32(uint160(address(TOKEN))), uint32(uint160(JBConstants.NATIVE_TOKEN)), priceFeed);
|
|
103
103
|
LOANS_CONTRACT = new REVLoans({
|
|
104
104
|
controller: jbController(),
|
|
105
|
-
projects: jbProjects(),
|
|
106
105
|
revId: FEE_PROJECT_ID,
|
|
107
106
|
owner: address(this),
|
|
108
107
|
permit2: permit2(),
|
|
@@ -113,7 +112,8 @@ contract TestBurnPermissionRequired is TestBaseWorkflow {
|
|
|
113
112
|
jbDirectory(),
|
|
114
113
|
FEE_PROJECT_ID,
|
|
115
114
|
SUCKER_REGISTRY,
|
|
116
|
-
address(LOANS_CONTRACT)
|
|
115
|
+
address(LOANS_CONTRACT),
|
|
116
|
+
address(0)
|
|
117
117
|
);
|
|
118
118
|
|
|
119
119
|
REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
|
|
@@ -247,7 +247,7 @@ contract TestBurnPermissionRequired is TestBaseWorkflow {
|
|
|
247
247
|
JBPermissionIds.BURN_TOKENS // permissionId
|
|
248
248
|
)
|
|
249
249
|
);
|
|
250
|
-
LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokenCount, payable(user), 25);
|
|
250
|
+
LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokenCount, payable(user), 25, user);
|
|
251
251
|
}
|
|
252
252
|
|
|
253
253
|
/// @notice borrowFrom should succeed when the caller has granted BURN_TOKENS permission.
|
|
@@ -274,7 +274,7 @@ contract TestBurnPermissionRequired is TestBaseWorkflow {
|
|
|
274
274
|
REVLoanSource memory source = REVLoanSource({token: JBConstants.NATIVE_TOKEN, terminal: jbMultiTerminal()});
|
|
275
275
|
vm.prank(user);
|
|
276
276
|
(uint256 loanId, REVLoan memory loan) =
|
|
277
|
-
LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokenCount, payable(user), 25);
|
|
277
|
+
LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokenCount, payable(user), 25, user);
|
|
278
278
|
|
|
279
279
|
assertTrue(loanId > 0, "Loan ID should be non-zero");
|
|
280
280
|
assertTrue(loan.createdAt > 0, "Loan should be created");
|
|
@@ -77,7 +77,6 @@ contract TestCashOutBuybackFeeLeak is TestBaseWorkflow {
|
|
|
77
77
|
mockBuyback = new MockBuybackCashOutRecorder();
|
|
78
78
|
loans = new REVLoans({
|
|
79
79
|
controller: jbController(),
|
|
80
|
-
projects: jbProjects(),
|
|
81
80
|
revId: feeProjectId,
|
|
82
81
|
owner: address(this),
|
|
83
82
|
permit2: permit2(),
|
|
@@ -89,7 +88,8 @@ contract TestCashOutBuybackFeeLeak is TestBaseWorkflow {
|
|
|
89
88
|
jbDirectory(),
|
|
90
89
|
feeProjectId,
|
|
91
90
|
IJBSuckerRegistry(address(suckerRegistry)),
|
|
92
|
-
address(loans)
|
|
91
|
+
address(loans),
|
|
92
|
+
address(0)
|
|
93
93
|
);
|
|
94
94
|
|
|
95
95
|
revDeployer = new REVDeployer{salt: REV_DEPLOYER_SALT}(
|
|
@@ -100,7 +100,6 @@ contract TestCrossRevnetLiquidation is TestBaseWorkflow {
|
|
|
100
100
|
.addPriceFeedFor(0, uint32(uint160(address(TOKEN))), uint32(uint160(JBConstants.NATIVE_TOKEN)), priceFeed);
|
|
101
101
|
LOANS_CONTRACT = new REVLoans({
|
|
102
102
|
controller: jbController(),
|
|
103
|
-
projects: jbProjects(),
|
|
104
103
|
revId: FEE_PROJECT_ID,
|
|
105
104
|
owner: address(this),
|
|
106
105
|
permit2: permit2(),
|
|
@@ -111,7 +110,8 @@ contract TestCrossRevnetLiquidation is TestBaseWorkflow {
|
|
|
111
110
|
jbDirectory(),
|
|
112
111
|
FEE_PROJECT_ID,
|
|
113
112
|
SUCKER_REGISTRY,
|
|
114
|
-
address(LOANS_CONTRACT)
|
|
113
|
+
address(LOANS_CONTRACT),
|
|
114
|
+
address(0)
|
|
115
115
|
);
|
|
116
116
|
|
|
117
117
|
REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
|
|
@@ -104,7 +104,6 @@ contract TestCumulativeLoanCounter is TestBaseWorkflow {
|
|
|
104
104
|
);
|
|
105
105
|
LOANS_CONTRACT = new REVLoans({
|
|
106
106
|
controller: jbController(),
|
|
107
|
-
projects: jbProjects(),
|
|
108
107
|
revId: FEE_PROJECT_ID,
|
|
109
108
|
owner: address(this),
|
|
110
109
|
permit2: permit2(),
|
|
@@ -115,7 +114,8 @@ contract TestCumulativeLoanCounter is TestBaseWorkflow {
|
|
|
115
114
|
jbDirectory(),
|
|
116
115
|
FEE_PROJECT_ID,
|
|
117
116
|
SUCKER_REGISTRY,
|
|
118
|
-
address(LOANS_CONTRACT)
|
|
117
|
+
address(LOANS_CONTRACT),
|
|
118
|
+
address(0)
|
|
119
119
|
);
|
|
120
120
|
|
|
121
121
|
REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
|
|
@@ -242,7 +242,7 @@ contract TestCumulativeLoanCounter is TestBaseWorkflow {
|
|
|
242
242
|
);
|
|
243
243
|
REVLoanSource memory source = REVLoanSource({token: JBConstants.NATIVE_TOKEN, terminal: jbMultiTerminal()});
|
|
244
244
|
vm.prank(user);
|
|
245
|
-
(loanId,) = LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokenCount, payable(user), 25);
|
|
245
|
+
(loanId,) = LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokenCount, payable(user), 25, user);
|
|
246
246
|
}
|
|
247
247
|
|
|
248
248
|
/// @notice Verifies totalLoansBorrowedFor never decrements after loan repayment.
|
|
@@ -106,7 +106,6 @@ contract TestLiquidateGapHandling is TestBaseWorkflow {
|
|
|
106
106
|
.addPriceFeedFor(0, uint32(uint160(address(TOKEN))), uint32(uint160(JBConstants.NATIVE_TOKEN)), priceFeed);
|
|
107
107
|
LOANS_CONTRACT = new REVLoans({
|
|
108
108
|
controller: jbController(),
|
|
109
|
-
projects: jbProjects(),
|
|
110
109
|
revId: FEE_PROJECT_ID,
|
|
111
110
|
owner: address(this),
|
|
112
111
|
permit2: permit2(),
|
|
@@ -117,7 +116,8 @@ contract TestLiquidateGapHandling is TestBaseWorkflow {
|
|
|
117
116
|
jbDirectory(),
|
|
118
117
|
FEE_PROJECT_ID,
|
|
119
118
|
SUCKER_REGISTRY,
|
|
120
|
-
address(LOANS_CONTRACT)
|
|
119
|
+
address(LOANS_CONTRACT),
|
|
120
|
+
address(0)
|
|
121
121
|
);
|
|
122
122
|
|
|
123
123
|
REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
|
|
@@ -246,7 +246,7 @@ contract TestLiquidateGapHandling is TestBaseWorkflow {
|
|
|
246
246
|
);
|
|
247
247
|
REVLoanSource memory source = REVLoanSource({token: JBConstants.NATIVE_TOKEN, terminal: jbMultiTerminal()});
|
|
248
248
|
vm.prank(user);
|
|
249
|
-
(loanId,) = LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokenCount, payable(user), 25);
|
|
249
|
+
(loanId,) = LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokenCount, payable(user), 25, user);
|
|
250
250
|
}
|
|
251
251
|
|
|
252
252
|
/// @notice Liquidation should continue past deleted loan gaps.
|
|
@@ -114,7 +114,6 @@ contract TestZeroPriceFeed is TestBaseWorkflow {
|
|
|
114
114
|
|
|
115
115
|
LOANS_CONTRACT = new REVLoans({
|
|
116
116
|
controller: jbController(),
|
|
117
|
-
projects: jbProjects(),
|
|
118
117
|
revId: FEE_PROJECT_ID,
|
|
119
118
|
owner: address(this),
|
|
120
119
|
permit2: permit2(),
|
|
@@ -126,7 +125,8 @@ contract TestZeroPriceFeed is TestBaseWorkflow {
|
|
|
126
125
|
jbDirectory(),
|
|
127
126
|
FEE_PROJECT_ID,
|
|
128
127
|
SUCKER_REGISTRY,
|
|
129
|
-
address(LOANS_CONTRACT)
|
|
128
|
+
address(LOANS_CONTRACT),
|
|
129
|
+
address(0)
|
|
130
130
|
);
|
|
131
131
|
|
|
132
132
|
REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
|
|
@@ -300,7 +300,7 @@ contract TestZeroPriceFeed is TestBaseWorkflow {
|
|
|
300
300
|
_mockBurnPermission();
|
|
301
301
|
REVLoanSource memory ethSource = REVLoanSource({token: JBConstants.NATIVE_TOKEN, terminal: jbMultiTerminal()});
|
|
302
302
|
vm.prank(USER);
|
|
303
|
-
LOANS_CONTRACT.borrowFrom(REVNET_ID, ethSource, 0, ethCollateral, payable(USER), 25);
|
|
303
|
+
LOANS_CONTRACT.borrowFrom(REVNET_ID, ethSource, 0, ethCollateral, payable(USER), 25, USER);
|
|
304
304
|
|
|
305
305
|
// Step 3: Fund the terminal with TOKEN and borrow from TOKEN source.
|
|
306
306
|
uint256 tokenFunding = 1_000_000e6;
|
|
@@ -312,7 +312,7 @@ contract TestZeroPriceFeed is TestBaseWorkflow {
|
|
|
312
312
|
_mockBurnPermission();
|
|
313
313
|
REVLoanSource memory tokenSource = REVLoanSource({token: address(TOKEN), terminal: jbMultiTerminal()});
|
|
314
314
|
vm.prank(USER);
|
|
315
|
-
LOANS_CONTRACT.borrowFrom(REVNET_ID, tokenSource, 0, tokenCollateral, payable(USER), 25);
|
|
315
|
+
LOANS_CONTRACT.borrowFrom(REVNET_ID, tokenSource, 0, tokenCollateral, payable(USER), 25, USER);
|
|
316
316
|
|
|
317
317
|
// Verify both sources have nonzero totalBorrowedFrom.
|
|
318
318
|
uint256 borrowedFromEth =
|
|
@@ -384,7 +384,7 @@ contract TestZeroPriceFeed is TestBaseWorkflow {
|
|
|
384
384
|
_mockBurnPermission();
|
|
385
385
|
REVLoanSource memory ethSource = REVLoanSource({token: JBConstants.NATIVE_TOKEN, terminal: jbMultiTerminal()});
|
|
386
386
|
vm.prank(USER);
|
|
387
|
-
LOANS_CONTRACT.borrowFrom(REVNET_ID, ethSource, 0, revnetTokens / 2, payable(USER), 25);
|
|
387
|
+
LOANS_CONTRACT.borrowFrom(REVNET_ID, ethSource, 0, revnetTokens / 2, payable(USER), 25, USER);
|
|
388
388
|
|
|
389
389
|
// Step 3: Get borrowable amount.
|
|
390
390
|
vm.prank(USER);
|