@rev-net/core-v6 0.0.28 → 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 +8 -8
- package/references/operations.md +1 -1
- package/script/Deploy.s.sol +25 -6
- package/src/REVDeployer.sol +39 -12
- package/src/REVHiddenTokens.sol +149 -0
- package/src/REVLoans.sol +162 -97
- package/src/REVOwner.sol +11 -3
- package/src/interfaces/IREVHiddenTokens.sol +53 -0
- package/src/interfaces/IREVLoans.sol +5 -8
- 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
package/src/REVLoans.sol
CHANGED
|
@@ -4,10 +4,12 @@ pragma solidity 0.8.28;
|
|
|
4
4
|
import {IJBController} from "@bananapus/core-v6/src/interfaces/IJBController.sol";
|
|
5
5
|
import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
|
|
6
6
|
import {IJBPayoutTerminal} from "@bananapus/core-v6/src/interfaces/IJBPayoutTerminal.sol";
|
|
7
|
+
import {IJBPermissioned} from "@bananapus/core-v6/src/interfaces/IJBPermissioned.sol";
|
|
8
|
+
import {JBPermissioned} from "@bananapus/core-v6/src/abstract/JBPermissioned.sol";
|
|
7
9
|
import {IJBPrices} from "@bananapus/core-v6/src/interfaces/IJBPrices.sol";
|
|
8
|
-
import {IJBProjects} from "@bananapus/core-v6/src/interfaces/IJBProjects.sol";
|
|
9
10
|
import {IJBTerminal} from "@bananapus/core-v6/src/interfaces/IJBTerminal.sol";
|
|
10
11
|
import {IJBTokenUriResolver} from "@bananapus/core-v6/src/interfaces/IJBTokenUriResolver.sol";
|
|
12
|
+
import {JBPermissionIds} from "@bananapus/permission-ids-v6/src/JBPermissionIds.sol";
|
|
11
13
|
import {JBCashOuts} from "@bananapus/core-v6/src/libraries/JBCashOuts.sol";
|
|
12
14
|
import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
|
|
13
15
|
import {JBFees} from "@bananapus/core-v6/src/libraries/JBFees.sol";
|
|
@@ -45,7 +47,7 @@ import {REVLoanSource} from "./structs/REVLoanSource.sol";
|
|
|
45
47
|
/// cannot be
|
|
46
48
|
/// recouped.
|
|
47
49
|
/// @dev The loaned amounts include the fees taken, meaning the amount paid back is the amount borrowed plus the fees.
|
|
48
|
-
contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
50
|
+
contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans {
|
|
49
51
|
// A library that parses the packed ruleset metadata into a friendlier format.
|
|
50
52
|
using JBRulesetMetadataResolver for JBRuleset;
|
|
51
53
|
|
|
@@ -71,7 +73,6 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
71
73
|
error REVLoans_PermitAllowanceNotEnough(uint256 allowanceAmount, uint256 requiredAmount);
|
|
72
74
|
error REVLoans_ReallocatingMoreCollateralThanBorrowedAmountAllows(uint256 newBorrowAmount, uint256 loanAmount);
|
|
73
75
|
error REVLoans_SourceMismatch();
|
|
74
|
-
error REVLoans_Unauthorized(address caller, address owner);
|
|
75
76
|
error REVLoans_UnderMinBorrowAmount(uint256 minBorrowAmount, uint256 borrowAmount);
|
|
76
77
|
error REVLoans_ZeroBorrowAmount();
|
|
77
78
|
error REVLoans_ZeroCollateralLoanIsInvalid();
|
|
@@ -120,9 +121,6 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
120
121
|
/// @notice A contract that stores prices for each revnet.
|
|
121
122
|
IJBPrices public immutable override PRICES;
|
|
122
123
|
|
|
123
|
-
/// @notice Mints ERC-721s that represent revnet ownership and transfers.
|
|
124
|
-
IJBProjects public immutable override PROJECTS;
|
|
125
|
-
|
|
126
124
|
/// @notice The ID of the REV revnet that will receive the fees.
|
|
127
125
|
uint256 public immutable override REV_ID;
|
|
128
126
|
|
|
@@ -182,14 +180,12 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
182
180
|
//*********************************************************************//
|
|
183
181
|
|
|
184
182
|
/// @param controller The controller that manages revnets using this loans contract.
|
|
185
|
-
/// @param projects The contract that mints ERC-721s representing project ownership.
|
|
186
183
|
/// @param revId The ID of the REV revnet that will receive the fees.
|
|
187
184
|
/// @param owner The owner of the contract that can set the URI resolver.
|
|
188
185
|
/// @param permit2 A permit2 utility.
|
|
189
186
|
/// @param trustedForwarder A trusted forwarder of transactions to this contract.
|
|
190
187
|
constructor(
|
|
191
188
|
IJBController controller,
|
|
192
|
-
IJBProjects projects,
|
|
193
189
|
uint256 revId,
|
|
194
190
|
address owner,
|
|
195
191
|
IPermit2 permit2,
|
|
@@ -197,12 +193,12 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
197
193
|
)
|
|
198
194
|
ERC721("REV Loans", "$REVLOAN")
|
|
199
195
|
ERC2771Context(trustedForwarder)
|
|
196
|
+
JBPermissioned(IJBPermissioned(address(controller)).PERMISSIONS())
|
|
200
197
|
Ownable(owner)
|
|
201
198
|
{
|
|
202
199
|
CONTROLLER = controller;
|
|
203
200
|
DIRECTORY = controller.DIRECTORY();
|
|
204
201
|
PRICES = controller.PRICES();
|
|
205
|
-
PROJECTS = projects;
|
|
206
202
|
REV_ID = revId;
|
|
207
203
|
PERMIT2 = permit2;
|
|
208
204
|
}
|
|
@@ -227,15 +223,19 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
227
223
|
view
|
|
228
224
|
returns (uint256)
|
|
229
225
|
{
|
|
226
|
+
// Cache the current ruleset once — used by both _cashOutDelayOf and _borrowableAmountFrom.
|
|
227
|
+
JBRuleset memory currentRuleset = _currentRulesetOf(revnetId);
|
|
228
|
+
|
|
230
229
|
// If the cash out delay hasn't passed yet, no amount is borrowable.
|
|
231
|
-
if (_cashOutDelayOf(revnetId) > block.timestamp) return 0;
|
|
230
|
+
if (_cashOutDelayOf({revnetId: revnetId, currentRuleset: currentRuleset}) > block.timestamp) return 0;
|
|
232
231
|
|
|
233
232
|
return _borrowableAmountFrom({
|
|
234
233
|
revnetId: revnetId,
|
|
235
234
|
collateralCount: collateralCount,
|
|
236
235
|
decimals: decimals,
|
|
237
236
|
currency: currency,
|
|
238
|
-
terminals:
|
|
237
|
+
terminals: _terminalsOf(revnetId),
|
|
238
|
+
currentStage: currentRuleset
|
|
239
239
|
});
|
|
240
240
|
}
|
|
241
241
|
|
|
@@ -290,24 +290,6 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
290
290
|
// -------------------------- internal views ------------------------- //
|
|
291
291
|
//*********************************************************************//
|
|
292
292
|
|
|
293
|
-
/// @notice Returns the cash out delay timestamp for a revnet by resolving the data hook from the current ruleset.
|
|
294
|
-
/// @param revnetId The ID of the revnet.
|
|
295
|
-
/// @return The cash out delay timestamp. Returns 0 if no data hook is set or no delay exists.
|
|
296
|
-
function _cashOutDelayOf(uint256 revnetId) internal view returns (uint256) {
|
|
297
|
-
// Get the revnet's current ruleset to find its data hook (the REVOwner contract).
|
|
298
|
-
// slither-disable-next-line unused-return
|
|
299
|
-
(JBRuleset memory currentRuleset,) = CONTROLLER.currentRulesetOf(revnetId);
|
|
300
|
-
|
|
301
|
-
// Extract the data hook address from the ruleset's packed metadata.
|
|
302
|
-
address dataHook = currentRuleset.dataHook();
|
|
303
|
-
|
|
304
|
-
// If there's no data hook, this isn't a revnet — no cash out delay applies.
|
|
305
|
-
if (dataHook == address(0)) return 0;
|
|
306
|
-
|
|
307
|
-
// Read the cash out delay from the REVOwner contract (the data hook).
|
|
308
|
-
return IREVOwner(dataHook).cashOutDelayOf(revnetId);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
293
|
/// @notice Checks this contract's balance of a specific token.
|
|
312
294
|
/// @param token The address of the token to get this contract's balance of.
|
|
313
295
|
/// @return This contract's balance.
|
|
@@ -344,22 +326,20 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
344
326
|
/// @param decimals The decimals the resulting fixed point value will include.
|
|
345
327
|
/// @param currency The currency that the resulting amount should be in terms of.
|
|
346
328
|
/// @param terminals The terminals that the funds are being borrowed from.
|
|
329
|
+
/// @param currentStage The pre-fetched current ruleset.
|
|
347
330
|
/// @return borrowableAmount The amount that can be borrowed from the revnet.
|
|
348
331
|
function _borrowableAmountFrom(
|
|
349
332
|
uint256 revnetId,
|
|
350
333
|
uint256 collateralCount,
|
|
351
334
|
uint256 decimals,
|
|
352
335
|
uint256 currency,
|
|
353
|
-
IJBTerminal[] memory terminals
|
|
336
|
+
IJBTerminal[] memory terminals,
|
|
337
|
+
JBRuleset memory currentStage
|
|
354
338
|
)
|
|
355
339
|
internal
|
|
356
340
|
view
|
|
357
341
|
returns (uint256)
|
|
358
342
|
{
|
|
359
|
-
// Keep a reference to the current stage.
|
|
360
|
-
// slither-disable-next-line unused-return
|
|
361
|
-
(JBRuleset memory currentStage,) = CONTROLLER.currentRulesetOf(revnetId);
|
|
362
|
-
|
|
363
343
|
// Get the surplus of all the revnet's terminals in terms of the native currency.
|
|
364
344
|
uint256 totalSurplus = JBSurplus.currentSurplusOf({
|
|
365
345
|
projectId: revnetId, terminals: terminals, tokens: new address[](0), decimals: decimals, currency: currency
|
|
@@ -392,11 +372,13 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
392
372
|
/// @param loan The loan having its borrow amount determined.
|
|
393
373
|
/// @param revnetId The ID of the revnet to check for borrowable assets from.
|
|
394
374
|
/// @param collateralCount The amount of collateral that the loan will be collateralized with.
|
|
375
|
+
/// @param currentRuleset The pre-fetched current ruleset.
|
|
395
376
|
/// @return borrowAmount The amount of the loan that should be borrowed.
|
|
396
377
|
function _borrowAmountFrom(
|
|
397
378
|
REVLoan storage loan,
|
|
398
379
|
uint256 revnetId,
|
|
399
|
-
uint256 collateralCount
|
|
380
|
+
uint256 collateralCount,
|
|
381
|
+
JBRuleset memory currentRuleset
|
|
400
382
|
)
|
|
401
383
|
internal
|
|
402
384
|
view
|
|
@@ -410,22 +392,46 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
410
392
|
loan.source.terminal.accountingContextForTokenOf({projectId: revnetId, token: loan.source.token});
|
|
411
393
|
|
|
412
394
|
// Keep a reference to the revnet's terminals.
|
|
413
|
-
IJBTerminal[] memory terminals =
|
|
395
|
+
IJBTerminal[] memory terminals = _terminalsOf(revnetId);
|
|
414
396
|
|
|
415
397
|
return _borrowableAmountFrom({
|
|
416
398
|
revnetId: revnetId,
|
|
417
399
|
collateralCount: collateralCount,
|
|
418
400
|
decimals: accountingContext.decimals,
|
|
419
401
|
currency: accountingContext.currency,
|
|
420
|
-
terminals: terminals
|
|
402
|
+
terminals: terminals,
|
|
403
|
+
currentStage: currentRuleset
|
|
421
404
|
});
|
|
422
405
|
}
|
|
423
406
|
|
|
407
|
+
/// @notice Returns the cash out delay timestamp using a pre-fetched ruleset.
|
|
408
|
+
/// @param revnetId The ID of the revnet.
|
|
409
|
+
/// @param currentRuleset The pre-fetched current ruleset.
|
|
410
|
+
/// @return The cash out delay timestamp. Returns 0 if no data hook is set or no delay exists.
|
|
411
|
+
function _cashOutDelayOf(uint256 revnetId, JBRuleset memory currentRuleset) internal view returns (uint256) {
|
|
412
|
+
// Extract the data hook address from the ruleset's packed metadata.
|
|
413
|
+
address dataHook = currentRuleset.dataHook();
|
|
414
|
+
|
|
415
|
+
// If there's no data hook, this isn't a revnet — no cash out delay applies.
|
|
416
|
+
if (dataHook == address(0)) return 0;
|
|
417
|
+
|
|
418
|
+
// Read the cash out delay from the REVOwner contract (the data hook).
|
|
419
|
+
return IREVOwner(dataHook).cashOutDelayOf(revnetId);
|
|
420
|
+
}
|
|
421
|
+
|
|
424
422
|
/// @dev `ERC-2771` specifies the context as being a single address (20 bytes).
|
|
425
423
|
function _contextSuffixLength() internal view override(ERC2771Context, Context) returns (uint256) {
|
|
426
424
|
return super._contextSuffixLength();
|
|
427
425
|
}
|
|
428
426
|
|
|
427
|
+
/// @notice Returns the current ruleset for a revnet. Consolidates ABI encode/decode to a single site.
|
|
428
|
+
/// @param revnetId The ID of the revnet.
|
|
429
|
+
/// @return currentRuleset The current ruleset.
|
|
430
|
+
function _currentRulesetOf(uint256 revnetId) internal view returns (JBRuleset memory currentRuleset) {
|
|
431
|
+
// slither-disable-next-line unused-return
|
|
432
|
+
(currentRuleset,) = CONTROLLER.currentRulesetOf(revnetId);
|
|
433
|
+
}
|
|
434
|
+
|
|
429
435
|
/// @notice Determines the source fee amount for a loan being paid off a certain amount.
|
|
430
436
|
/// @param loan The loan having its source fee amount determined.
|
|
431
437
|
/// @param amount The amount being paid off.
|
|
@@ -483,6 +489,13 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
483
489
|
return ERC2771Context._msgSender();
|
|
484
490
|
}
|
|
485
491
|
|
|
492
|
+
/// @notice Returns the terminals for a revnet. Consolidates ABI encode/decode to a single site.
|
|
493
|
+
/// @param revnetId The ID of the revnet.
|
|
494
|
+
/// @return The terminals registered for the revnet.
|
|
495
|
+
function _terminalsOf(uint256 revnetId) internal view returns (IJBTerminal[] memory) {
|
|
496
|
+
return DIRECTORY.terminalsOf(revnetId);
|
|
497
|
+
}
|
|
498
|
+
|
|
486
499
|
/// @notice The total borrowed amount from a revnet, aggregated across all loan sources.
|
|
487
500
|
/// @dev Each source's `totalBorrowedFrom` is stored in the source token's native decimals (e.g. 6 for USDC,
|
|
488
501
|
/// 18 for ETH). Before aggregation, each amount is normalized to the target `decimals` to prevent mixed-decimal
|
|
@@ -503,12 +516,13 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
503
516
|
returns (uint256 borrowedAmount)
|
|
504
517
|
{
|
|
505
518
|
// Keep a reference to all sources being used to loaned out from this revnet.
|
|
506
|
-
|
|
519
|
+
// Use storage ref to avoid bulk-copying the entire array to memory.
|
|
520
|
+
REVLoanSource[] storage sources = _loanSourcesOf[revnetId];
|
|
507
521
|
|
|
508
522
|
// Iterate over all sources being used to loaned out.
|
|
509
523
|
for (uint256 i; i < sources.length; i++) {
|
|
510
524
|
// Get a reference to the token being iterated on.
|
|
511
|
-
REVLoanSource
|
|
525
|
+
REVLoanSource storage source = sources[i];
|
|
512
526
|
|
|
513
527
|
// Get a reference to the accounting context for the source.
|
|
514
528
|
// slither-disable-next-line calls-loop
|
|
@@ -564,6 +578,8 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
564
578
|
/// @dev Collateral tokens are permanently burned when the loan is created. They are re-minted to the borrower
|
|
565
579
|
/// only upon repayment. If the loan expires (after LOAN_LIQUIDATION_DURATION), the collateral is permanently
|
|
566
580
|
/// lost and cannot be recovered.
|
|
581
|
+
/// @dev A delegated operator (with OPEN_LOAN permission) can set `beneficiary` to any address, directing borrowed
|
|
582
|
+
/// funds away from the holder. Holders should only grant OPEN_LOAN to fully trusted operators.
|
|
567
583
|
/// @param revnetId The ID of the revnet being borrowed from.
|
|
568
584
|
/// @param source The source of the loan being borrowed.
|
|
569
585
|
/// @param minBorrowAmount The minimum amount being borrowed, denominated in the token of the source's accounting
|
|
@@ -580,12 +596,17 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
580
596
|
uint256 minBorrowAmount,
|
|
581
597
|
uint256 collateralCount,
|
|
582
598
|
address payable beneficiary,
|
|
583
|
-
uint256 prepaidFeePercent
|
|
599
|
+
uint256 prepaidFeePercent,
|
|
600
|
+
address holder
|
|
584
601
|
)
|
|
585
602
|
public
|
|
586
603
|
override
|
|
587
604
|
returns (uint256 loanId, REVLoan memory)
|
|
588
605
|
{
|
|
606
|
+
// Only the holder or a permissioned operator can open a loan on the holder's behalf.
|
|
607
|
+
// Note: the operator controls `beneficiary`, so they can direct borrowed funds to any address.
|
|
608
|
+
_requirePermissionFrom({account: holder, projectId: revnetId, permissionId: JBPermissionIds.OPEN_LOAN});
|
|
609
|
+
|
|
589
610
|
// A loan needs to have collateral.
|
|
590
611
|
if (collateralCount == 0) revert REVLoans_ZeroCollateralLoanIsInvalid();
|
|
591
612
|
|
|
@@ -603,9 +624,12 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
603
624
|
);
|
|
604
625
|
}
|
|
605
626
|
|
|
627
|
+
// Cache the current ruleset once — used by both _cashOutDelayOf and _borrowAmountFrom.
|
|
628
|
+
JBRuleset memory currentRuleset = _currentRulesetOf(revnetId);
|
|
629
|
+
|
|
606
630
|
// Enforce the cash out delay.
|
|
607
631
|
{
|
|
608
|
-
uint256 cashOutDelay = _cashOutDelayOf(revnetId);
|
|
632
|
+
uint256 cashOutDelay = _cashOutDelayOf({revnetId: revnetId, currentRuleset: currentRuleset});
|
|
609
633
|
if (cashOutDelay > block.timestamp) {
|
|
610
634
|
revert REVLoans_CashOutDelayNotFinished(cashOutDelay, block.timestamp);
|
|
611
635
|
}
|
|
@@ -628,8 +652,10 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
628
652
|
loan.prepaidDuration =
|
|
629
653
|
uint32(mulDiv({x: prepaidFeePercent, y: LOAN_LIQUIDATION_DURATION, denominator: MAX_PREPAID_FEE_PERCENT}));
|
|
630
654
|
|
|
631
|
-
// Get the amount of the loan.
|
|
632
|
-
uint256 borrowAmount = _borrowAmountFrom({
|
|
655
|
+
// Get the amount of the loan, using the cached ruleset.
|
|
656
|
+
uint256 borrowAmount = _borrowAmountFrom({
|
|
657
|
+
loan: loan, revnetId: revnetId, collateralCount: collateralCount, currentRuleset: currentRuleset
|
|
658
|
+
});
|
|
633
659
|
|
|
634
660
|
// Revert if the bonding curve returns zero to prevent creating zero-amount loans.
|
|
635
661
|
if (borrowAmount == 0) revert REVLoans_ZeroBorrowAmount();
|
|
@@ -648,11 +674,12 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
648
674
|
newBorrowAmount: borrowAmount,
|
|
649
675
|
newCollateralCount: collateralCount,
|
|
650
676
|
sourceFeeAmount: sourceFeeAmount,
|
|
651
|
-
beneficiary: beneficiary
|
|
677
|
+
beneficiary: beneficiary,
|
|
678
|
+
holder: holder
|
|
652
679
|
});
|
|
653
680
|
|
|
654
|
-
// Mint the loan.
|
|
655
|
-
_mint({to:
|
|
681
|
+
// Mint the loan NFT to the holder.
|
|
682
|
+
_mint({to: holder, tokenId: loanId});
|
|
656
683
|
|
|
657
684
|
emit Borrow({
|
|
658
685
|
loanId: loanId,
|
|
@@ -686,18 +713,21 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
686
713
|
// Prevent cross-revnet accounting corruption: loan numbers must stay within the revnet's ID namespace.
|
|
687
714
|
if (startingLoanId + count > _ONE_TRILLION) revert REVLoans_LoanIdOverflow();
|
|
688
715
|
|
|
716
|
+
// Cache the sender to avoid repeated ERC2771 context reads inside the loop.
|
|
717
|
+
address sender = _msgSender();
|
|
718
|
+
|
|
689
719
|
// Iterate over the desired number of loans to check for liquidation.
|
|
690
720
|
for (uint256 i; i < count; i++) {
|
|
691
721
|
// Get a reference to the next loan ID.
|
|
692
722
|
uint256 loanId = _generateLoanId({revnetId: revnetId, loanNumber: startingLoanId + i});
|
|
693
723
|
|
|
724
|
+
// Check createdAt via storage ref first to avoid loading the full struct for empty slots.
|
|
725
|
+
// slither-disable-next-line incorrect-equality
|
|
726
|
+
if (_loanOf[loanId].createdAt == 0) continue;
|
|
727
|
+
|
|
694
728
|
// Get a reference to the loan being iterated on.
|
|
695
729
|
REVLoan memory loan = _loanOf[loanId];
|
|
696
730
|
|
|
697
|
-
// If the loan doesn't exist (repaid or already liquidated), skip past this gap and continue.
|
|
698
|
-
// slither-disable-next-line incorrect-equality
|
|
699
|
-
if (loan.createdAt == 0) continue;
|
|
700
|
-
|
|
701
731
|
// Keep a reference to the loan's owner.
|
|
702
732
|
address owner = _ownerOf(loanId);
|
|
703
733
|
|
|
@@ -722,7 +752,7 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
722
752
|
totalBorrowedFrom[revnetId][loan.source.terminal][loan.source.token] -= loan.amount;
|
|
723
753
|
}
|
|
724
754
|
|
|
725
|
-
emit Liquidate({loanId: loanId, revnetId: revnetId, loan: loan, caller:
|
|
755
|
+
emit Liquidate({loanId: loanId, revnetId: revnetId, loan: loan, caller: sender});
|
|
726
756
|
}
|
|
727
757
|
}
|
|
728
758
|
|
|
@@ -731,6 +761,8 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
731
761
|
/// @dev Refinancing a loan will burn the original and create two new loans.
|
|
732
762
|
/// @dev This function is intentionally not payable — it only moves existing collateral between loans and does
|
|
733
763
|
/// not accept new funds. Any ETH sent with the call will be rejected by the EVM.
|
|
764
|
+
/// @dev A delegated operator (with REALLOCATE_LOAN permission) can set `beneficiary` to any address, directing
|
|
765
|
+
/// borrowed funds from the new loan away from the loan owner. Grant this permission only to trusted operators.
|
|
734
766
|
/// @param loanId The ID of the loan to reallocate collateral from.
|
|
735
767
|
/// @param collateralCountToTransfer The amount of collateral to transfer from the original loan.
|
|
736
768
|
/// @param source The source of the loan to create.
|
|
@@ -756,8 +788,13 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
756
788
|
override
|
|
757
789
|
returns (uint256 reallocatedLoanId, uint256 newLoanId, REVLoan memory reallocatedLoan, REVLoan memory newLoan)
|
|
758
790
|
{
|
|
759
|
-
//
|
|
760
|
-
|
|
791
|
+
// Keep a reference to the revnet ID of the loan being reallocated.
|
|
792
|
+
uint256 revnetId = revnetIdOfLoanWith(loanId);
|
|
793
|
+
|
|
794
|
+
// Only the loan owner or a permissioned operator can reallocate.
|
|
795
|
+
// Note: the operator controls `beneficiary`, so they can direct new loan proceeds to any address.
|
|
796
|
+
address loanOwner = _ownerOf(loanId);
|
|
797
|
+
_requirePermissionFrom({account: loanOwner, projectId: revnetId, permissionId: JBPermissionIds.REALLOCATE_LOAN});
|
|
761
798
|
|
|
762
799
|
// Make sure the loan hasn't expired.
|
|
763
800
|
if (block.timestamp - _loanOf[loanId].createdAt > LOAN_LIQUIDATION_DURATION) {
|
|
@@ -774,27 +811,28 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
774
811
|
|
|
775
812
|
// Note: this function is not payable, so the EVM prevents sending ETH at the call level.
|
|
776
813
|
|
|
777
|
-
// Keep a reference to the revnet ID of the loan being reallocated.
|
|
778
|
-
uint256 revnetId = revnetIdOfLoanWith(loanId);
|
|
779
|
-
|
|
780
814
|
// Refinance the loan.
|
|
781
815
|
(reallocatedLoanId, reallocatedLoan) = _reallocateCollateralFromLoan({
|
|
782
|
-
loanId: loanId, revnetId: revnetId, collateralCountToRemove: collateralCountToTransfer
|
|
816
|
+
loanId: loanId, revnetId: revnetId, collateralCountToRemove: collateralCountToTransfer, loanOwner: loanOwner
|
|
783
817
|
});
|
|
784
818
|
|
|
785
819
|
// Make a new loan with the leftover collateral from reallocating.
|
|
820
|
+
// The loan owner is the holder for the new loan (their tokens are used as collateral).
|
|
786
821
|
(newLoanId, newLoan) = borrowFrom({
|
|
787
822
|
revnetId: revnetId,
|
|
788
823
|
source: source,
|
|
789
824
|
minBorrowAmount: minBorrowAmount,
|
|
790
825
|
collateralCount: collateralCountToTransfer + collateralCountToAdd,
|
|
791
826
|
beneficiary: beneficiary,
|
|
792
|
-
prepaidFeePercent: prepaidFeePercent
|
|
827
|
+
prepaidFeePercent: prepaidFeePercent,
|
|
828
|
+
holder: loanOwner
|
|
793
829
|
});
|
|
794
830
|
}
|
|
795
831
|
|
|
796
832
|
/// @notice Allows the owner of a loan to pay it back or receive returned collateral no longer necessary to support
|
|
797
833
|
/// the loan.
|
|
834
|
+
/// @dev A delegated operator (with REPAY_LOAN permission) can set `beneficiary` to any address, directing returned
|
|
835
|
+
/// collateral tokens away from the loan owner. Grant this permission only to trusted operators.
|
|
798
836
|
/// @param loanId The ID of the loan being adjusted.
|
|
799
837
|
/// @param maxRepayBorrowAmount The maximum amount being paid off, denominated in the token of the source's
|
|
800
838
|
/// accounting context.
|
|
@@ -815,8 +853,15 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
815
853
|
override
|
|
816
854
|
returns (uint256 paidOffLoanId, REVLoan memory paidOffloan)
|
|
817
855
|
{
|
|
818
|
-
//
|
|
819
|
-
|
|
856
|
+
// Cache the sender to avoid repeated ERC2771 context reads.
|
|
857
|
+
address sender = _msgSender();
|
|
858
|
+
|
|
859
|
+
// Only the loan owner or a permissioned operator can repay.
|
|
860
|
+
// Note: the operator controls `beneficiary`, so they can direct returned collateral to any address.
|
|
861
|
+
address loanOwner = _ownerOf(loanId);
|
|
862
|
+
_requirePermissionFrom({
|
|
863
|
+
account: loanOwner, projectId: revnetIdOfLoanWith(loanId), permissionId: JBPermissionIds.REPAY_LOAN
|
|
864
|
+
});
|
|
820
865
|
|
|
821
866
|
// Keep a reference to the fee being iterated on.
|
|
822
867
|
REVLoan storage loan = _loanOf[loanId];
|
|
@@ -828,12 +873,18 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
828
873
|
// Get a reference to the revnet ID of the loan being repaid.
|
|
829
874
|
uint256 revnetId = revnetIdOfLoanWith(loanId);
|
|
830
875
|
|
|
876
|
+
// Cache the current ruleset once for borrow amount calculation.
|
|
877
|
+
JBRuleset memory currentRuleset = _currentRulesetOf(revnetId);
|
|
878
|
+
|
|
831
879
|
// Scope to limit newBorrowAmount's stack lifetime.
|
|
832
880
|
uint256 repayBorrowAmount;
|
|
833
881
|
{
|
|
834
882
|
// Get the new borrow amount.
|
|
835
883
|
uint256 newBorrowAmount = _borrowAmountFrom({
|
|
836
|
-
loan: loan,
|
|
884
|
+
loan: loan,
|
|
885
|
+
revnetId: revnetId,
|
|
886
|
+
collateralCount: loan.collateral - collateralCountToReturn,
|
|
887
|
+
currentRuleset: currentRuleset
|
|
837
888
|
});
|
|
838
889
|
|
|
839
890
|
// If the remaining collateral yields zero borrow amount, treat as full repay.
|
|
@@ -880,14 +931,15 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
880
931
|
repayBorrowAmount: repayBorrowAmount,
|
|
881
932
|
sourceFeeAmount: sourceFeeAmount,
|
|
882
933
|
collateralCountToReturn: collateralCountToReturn,
|
|
883
|
-
beneficiary: beneficiary
|
|
934
|
+
beneficiary: beneficiary,
|
|
935
|
+
loanOwner: loanOwner
|
|
884
936
|
});
|
|
885
937
|
|
|
886
938
|
// If the max repay amount is greater than the repay amount, return the difference back to the payer.
|
|
887
939
|
if (maxRepayBorrowAmount > repayBorrowAmount) {
|
|
888
940
|
_transferFrom({
|
|
889
941
|
from: address(this),
|
|
890
|
-
to: payable(
|
|
942
|
+
to: payable(sender),
|
|
891
943
|
token: sourceToken,
|
|
892
944
|
amount: maxRepayBorrowAmount - repayBorrowAmount
|
|
893
945
|
});
|
|
@@ -915,7 +967,7 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
915
967
|
function _acceptFundsFor(
|
|
916
968
|
address token,
|
|
917
969
|
uint256 amount,
|
|
918
|
-
JBSingleAllowance
|
|
970
|
+
JBSingleAllowance calldata allowance
|
|
919
971
|
)
|
|
920
972
|
internal
|
|
921
973
|
returns (uint256)
|
|
@@ -962,14 +1014,12 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
962
1014
|
/// loan is repaid. If the loan expires and is liquidated, the burned collateral is permanently lost.
|
|
963
1015
|
/// @param revnetId The ID of the revnet the loan is being added in.
|
|
964
1016
|
/// @param amount The new amount of collateral being added to the loan.
|
|
965
|
-
function _addCollateralTo(uint256 revnetId, uint256 amount) internal {
|
|
1017
|
+
function _addCollateralTo(uint256 revnetId, uint256 amount, address holder) internal {
|
|
966
1018
|
// Increment the total amount of collateral tokens.
|
|
967
1019
|
totalCollateralOf[revnetId] += amount;
|
|
968
1020
|
|
|
969
1021
|
// Permanently burn the tokens that are tracked as collateral. These are only re-minted upon repayment.
|
|
970
|
-
CONTROLLER.burnTokensOf({
|
|
971
|
-
holder: _msgSender(), projectId: revnetId, tokenCount: amount, memo: "Adding collateral to loan"
|
|
972
|
-
});
|
|
1022
|
+
CONTROLLER.burnTokensOf({holder: holder, projectId: revnetId, tokenCount: amount, memo: ""});
|
|
973
1023
|
}
|
|
974
1024
|
|
|
975
1025
|
/// @notice Add a new amount to the loan that is greater than the previous amount.
|
|
@@ -1016,7 +1066,7 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
1016
1066
|
minTokensPaidOut: 0,
|
|
1017
1067
|
beneficiary: payable(address(this)),
|
|
1018
1068
|
feeBeneficiary: beneficiary,
|
|
1019
|
-
memo: "
|
|
1069
|
+
memo: ""
|
|
1020
1070
|
});
|
|
1021
1071
|
}
|
|
1022
1072
|
|
|
@@ -1045,7 +1095,7 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
1045
1095
|
amount: revFeeAmount,
|
|
1046
1096
|
beneficiary: beneficiary,
|
|
1047
1097
|
minReturnedTokens: 0,
|
|
1048
|
-
memo: "
|
|
1098
|
+
memo: "",
|
|
1049
1099
|
metadata: bytes(abi.encodePacked(revnetId))
|
|
1050
1100
|
}) {}
|
|
1051
1101
|
catch (bytes memory) {
|
|
@@ -1085,16 +1135,22 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
1085
1135
|
/// @param newCollateralCount The new amount of collateral backing the loan.
|
|
1086
1136
|
/// @param sourceFeeAmount The amount of the fee being taken from the revnet acting as the source of the loan.
|
|
1087
1137
|
/// @param beneficiary The address receiving the returned collateral and any tokens resulting from paying fees.
|
|
1138
|
+
/// @param holder The address whose tokens are used as collateral (burned).
|
|
1088
1139
|
function _adjust(
|
|
1089
1140
|
REVLoan storage loan,
|
|
1090
1141
|
uint256 revnetId,
|
|
1091
1142
|
uint256 newBorrowAmount,
|
|
1092
1143
|
uint256 newCollateralCount,
|
|
1093
1144
|
uint256 sourceFeeAmount,
|
|
1094
|
-
address payable beneficiary
|
|
1145
|
+
address payable beneficiary,
|
|
1146
|
+
address holder
|
|
1095
1147
|
)
|
|
1096
1148
|
internal
|
|
1097
1149
|
{
|
|
1150
|
+
// Cache frequently-read storage fields to avoid repeated SLOAD.
|
|
1151
|
+
address sourceToken = loan.source.token;
|
|
1152
|
+
IJBPayoutTerminal sourceTerminal = loan.source.terminal;
|
|
1153
|
+
|
|
1098
1154
|
// Snapshot deltas from current state before writing.
|
|
1099
1155
|
uint256 addedBorrowAmount = newBorrowAmount > loan.amount ? newBorrowAmount - loan.amount : 0;
|
|
1100
1156
|
uint256 repaidBorrowAmount = loan.amount > newBorrowAmount ? loan.amount - newBorrowAmount : 0;
|
|
@@ -1131,7 +1187,7 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
1131
1187
|
|
|
1132
1188
|
// Add collateral if needed...
|
|
1133
1189
|
if (addedCollateralCount > 0) {
|
|
1134
|
-
_addCollateralTo({revnetId: revnetId, amount: addedCollateralCount});
|
|
1190
|
+
_addCollateralTo({revnetId: revnetId, amount: addedCollateralCount, holder: holder});
|
|
1135
1191
|
// ... or return collateral if needed.
|
|
1136
1192
|
} else if (returnedCollateralCount > 0) {
|
|
1137
1193
|
_returnCollateralFrom({
|
|
@@ -1143,9 +1199,8 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
1143
1199
|
// cannot block all loan operations (matching the REV fee pattern above).
|
|
1144
1200
|
if (sourceFeeAmount > 0) {
|
|
1145
1201
|
// Increase the allowance for the source terminal.
|
|
1146
|
-
uint256 payValue =
|
|
1147
|
-
to: address(
|
|
1148
|
-
});
|
|
1202
|
+
uint256 payValue =
|
|
1203
|
+
_beforeTransferTo({to: address(sourceTerminal), token: sourceToken, amount: sourceFeeAmount});
|
|
1149
1204
|
|
|
1150
1205
|
// Pay the fee. If it fails, reclaim the allowance and give the amount back to the borrower.
|
|
1151
1206
|
// NOTE: When terminal.pay() reverts (e.g. due to a misconfigured terminal or paused payments),
|
|
@@ -1153,25 +1208,23 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
1153
1208
|
// source fee portion. This is acceptable — it requires a broken/misconfigured source terminal and
|
|
1154
1209
|
// the borrower still pays the REV fee and protocol fee.
|
|
1155
1210
|
// slither-disable-next-line unused-return,arbitrary-send-eth
|
|
1156
|
-
try
|
|
1211
|
+
try sourceTerminal.pay{value: payValue}({
|
|
1157
1212
|
projectId: revnetId,
|
|
1158
|
-
token:
|
|
1213
|
+
token: sourceToken,
|
|
1159
1214
|
amount: sourceFeeAmount,
|
|
1160
1215
|
beneficiary: beneficiary,
|
|
1161
1216
|
minReturnedTokens: 0,
|
|
1162
|
-
memo: "
|
|
1217
|
+
memo: "",
|
|
1163
1218
|
metadata: bytes(abi.encodePacked(REV_ID))
|
|
1164
1219
|
}) {}
|
|
1165
1220
|
catch (bytes memory) {
|
|
1166
1221
|
// If the fee can't be processed, decrease the ERC-20 allowance and return the amount
|
|
1167
1222
|
// to the beneficiary instead.
|
|
1168
|
-
if (
|
|
1169
|
-
IERC20(
|
|
1170
|
-
.safeDecreaseAllowance({
|
|
1171
|
-
spender: address(loan.source.terminal), requestedDecrease: sourceFeeAmount
|
|
1172
|
-
});
|
|
1223
|
+
if (sourceToken != JBConstants.NATIVE_TOKEN) {
|
|
1224
|
+
IERC20(sourceToken)
|
|
1225
|
+
.safeDecreaseAllowance({spender: address(sourceTerminal), requestedDecrease: sourceFeeAmount});
|
|
1173
1226
|
}
|
|
1174
|
-
_transferFrom({from: address(this), to: beneficiary, token:
|
|
1227
|
+
_transferFrom({from: address(this), to: beneficiary, token: sourceToken, amount: sourceFeeAmount});
|
|
1175
1228
|
}
|
|
1176
1229
|
}
|
|
1177
1230
|
}
|
|
@@ -1198,7 +1251,8 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
1198
1251
|
function _reallocateCollateralFromLoan(
|
|
1199
1252
|
uint256 loanId,
|
|
1200
1253
|
uint256 revnetId,
|
|
1201
|
-
uint256 collateralCountToRemove
|
|
1254
|
+
uint256 collateralCountToRemove,
|
|
1255
|
+
address loanOwner
|
|
1202
1256
|
)
|
|
1203
1257
|
internal
|
|
1204
1258
|
returns (uint256 reallocatedLoanId, REVLoan storage reallocatedLoan)
|
|
@@ -1215,8 +1269,13 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
1215
1269
|
// Keep a reference to the new collateral amount.
|
|
1216
1270
|
uint256 newCollateralCount = loan.collateral - collateralCountToRemove;
|
|
1217
1271
|
|
|
1272
|
+
// Cache the current ruleset for borrow amount calculation.
|
|
1273
|
+
JBRuleset memory currentRuleset = _currentRulesetOf(revnetId);
|
|
1274
|
+
|
|
1218
1275
|
// Keep a reference to the new borrow amount.
|
|
1219
|
-
uint256 borrowAmount = _borrowAmountFrom({
|
|
1276
|
+
uint256 borrowAmount = _borrowAmountFrom({
|
|
1277
|
+
loan: loan, revnetId: revnetId, collateralCount: newCollateralCount, currentRuleset: currentRuleset
|
|
1278
|
+
});
|
|
1220
1279
|
|
|
1221
1280
|
// Make sure the borrow amount is not less than the original loan's amount.
|
|
1222
1281
|
if (borrowAmount < loan.amount) {
|
|
@@ -1247,12 +1306,13 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
1247
1306
|
newBorrowAmount: reallocatedLoan.amount, // Don't change the borrow amount.
|
|
1248
1307
|
newCollateralCount: newCollateralCount,
|
|
1249
1308
|
sourceFeeAmount: 0,
|
|
1250
|
-
beneficiary: payable(
|
|
1309
|
+
beneficiary: payable(loanOwner), // Return collateral to the loan owner, who will have the returned
|
|
1251
1310
|
// collateral tokens debited from their balance for the new loan.
|
|
1311
|
+
holder: loanOwner // Only used if collateral is added (not the case here — collateral is being returned).
|
|
1252
1312
|
});
|
|
1253
1313
|
|
|
1254
|
-
// Mint the replacement loan.
|
|
1255
|
-
_mint({to:
|
|
1314
|
+
// Mint the replacement loan to the loan owner.
|
|
1315
|
+
_mint({to: loanOwner, tokenId: reallocatedLoanId});
|
|
1256
1316
|
|
|
1257
1317
|
// Clear stale loan data for gas refund.
|
|
1258
1318
|
delete _loanOf[loanId];
|
|
@@ -1288,7 +1348,7 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
1288
1348
|
token: loan.source.token,
|
|
1289
1349
|
amount: repaidBorrowAmount,
|
|
1290
1350
|
shouldReturnHeldFees: false,
|
|
1291
|
-
memo: "
|
|
1351
|
+
memo: "",
|
|
1292
1352
|
metadata: bytes(abi.encodePacked(REV_ID))
|
|
1293
1353
|
});
|
|
1294
1354
|
}
|
|
@@ -1301,6 +1361,7 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
1301
1361
|
/// @param sourceFeeAmount The amount of the fee being taken from the revnet acting as the source of the loan.
|
|
1302
1362
|
/// @param collateralCountToReturn The amount of collateral being returned that the loan no longer requires.
|
|
1303
1363
|
/// @param beneficiary The address receiving the returned collateral and any tokens resulting from paying fees.
|
|
1364
|
+
/// @param loanOwner The owner of the loan NFT (receives replacement loan if partial repay).
|
|
1304
1365
|
// slither-disable-next-line reentrancy-eth,reentrancy-events
|
|
1305
1366
|
function _repayLoan(
|
|
1306
1367
|
uint256 loanId,
|
|
@@ -1309,7 +1370,8 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
1309
1370
|
uint256 repayBorrowAmount,
|
|
1310
1371
|
uint256 sourceFeeAmount,
|
|
1311
1372
|
uint256 collateralCountToReturn,
|
|
1312
|
-
address payable beneficiary
|
|
1373
|
+
address payable beneficiary,
|
|
1374
|
+
address loanOwner
|
|
1313
1375
|
)
|
|
1314
1376
|
internal
|
|
1315
1377
|
returns (uint256, REVLoan memory)
|
|
@@ -1330,7 +1392,8 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
1330
1392
|
newBorrowAmount: 0,
|
|
1331
1393
|
newCollateralCount: 0,
|
|
1332
1394
|
sourceFeeAmount: sourceFeeAmount,
|
|
1333
|
-
beneficiary: beneficiary
|
|
1395
|
+
beneficiary: beneficiary,
|
|
1396
|
+
holder: _msgSender() // Only used if collateral is added (not the case here — collateral is returned).
|
|
1334
1397
|
});
|
|
1335
1398
|
|
|
1336
1399
|
// Snapshot the zeroed loan for the return value (reflects post-repay state).
|
|
@@ -1364,7 +1427,8 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
1364
1427
|
// Get a reference to the loan being paid off.
|
|
1365
1428
|
REVLoan storage paidOffLoan = _loanOf[paidOffLoanId];
|
|
1366
1429
|
|
|
1367
|
-
//
|
|
1430
|
+
// Copy the original loan's values. amount and collateral are written here so _adjust
|
|
1431
|
+
// can compute correct deltas, then _adjust overwrites them with the final values.
|
|
1368
1432
|
paidOffLoan.amount = loan.amount;
|
|
1369
1433
|
paidOffLoan.collateral = loan.collateral;
|
|
1370
1434
|
paidOffLoan.createdAt = loan.createdAt;
|
|
@@ -1372,8 +1436,8 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
1372
1436
|
paidOffLoan.prepaidDuration = loan.prepaidDuration;
|
|
1373
1437
|
paidOffLoan.source = loan.source;
|
|
1374
1438
|
|
|
1375
|
-
// Mint the replacement loan FIRST so it exists before _adjust writes data.
|
|
1376
|
-
_mint({to:
|
|
1439
|
+
// Mint the replacement loan to the loan owner FIRST so it exists before _adjust writes data.
|
|
1440
|
+
_mint({to: loanOwner, tokenId: paidOffLoanId});
|
|
1377
1441
|
|
|
1378
1442
|
// Then adjust the loan data.
|
|
1379
1443
|
_adjust({
|
|
@@ -1382,7 +1446,8 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
1382
1446
|
newBorrowAmount: paidOffLoan.amount - (repayBorrowAmount - sourceFeeAmount),
|
|
1383
1447
|
newCollateralCount: paidOffLoan.collateral - collateralCountToReturn,
|
|
1384
1448
|
sourceFeeAmount: sourceFeeAmount,
|
|
1385
|
-
beneficiary: beneficiary
|
|
1449
|
+
beneficiary: beneficiary,
|
|
1450
|
+
holder: _msgSender() // Only used if collateral is added (not the case here — collateral is returned).
|
|
1386
1451
|
});
|
|
1387
1452
|
|
|
1388
1453
|
emit RepayLoan({
|
|
@@ -1419,7 +1484,7 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
1419
1484
|
projectId: revnetId,
|
|
1420
1485
|
tokenCount: collateralCount,
|
|
1421
1486
|
beneficiary: beneficiary,
|
|
1422
|
-
memo: "
|
|
1487
|
+
memo: "",
|
|
1423
1488
|
useReservedPercent: false
|
|
1424
1489
|
});
|
|
1425
1490
|
}
|