@rev-net/core-v6 0.0.12 → 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/AUDIT_INSTRUCTIONS.md +295 -0
- package/CHANGE_LOG.md +316 -0
- package/README.md +2 -2
- package/RISKS.md +180 -35
- package/SKILLS.md +1 -1
- package/USER_JOURNEYS.md +489 -0
- package/package.json +9 -9
- package/script/Deploy.s.sol +40 -6
- package/script/helpers/RevnetCoreDeploymentLib.sol +7 -1
- package/src/REVDeployer.sol +63 -47
- package/src/REVLoans.sol +51 -15
- package/src/interfaces/IREVDeployer.sol +0 -1
- 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 +1 -0
- 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 +132 -12
- package/test/REVAutoIssuanceFuzz.t.sol +23 -3
- package/test/REVDeployerRegressions.t.sol +35 -4
- package/test/REVInvincibility.t.sol +58 -8
- package/test/REVInvincibilityHandler.sol +29 -0
- package/test/REVLifecycle.t.sol +28 -3
- package/test/REVLoans.invariants.t.sol +52 -5
- package/test/REVLoansAttacks.t.sol +43 -5
- package/test/REVLoansFeeRecovery.t.sol +50 -11
- package/test/REVLoansFindings.t.sol +27 -3
- package/test/REVLoansRegressions.t.sol +25 -3
- package/test/REVLoansSourceFeeRecovery.t.sol +491 -0
- package/test/REVLoansSourced.t.sol +56 -7
- package/test/REVLoansUnSourced.t.sol +49 -5
- package/test/TestBurnHeldTokens.t.sol +32 -5
- package/test/TestCEIPattern.t.sol +26 -2
- package/test/TestCashOutCallerValidation.t.sol +30 -4
- package/test/TestConversionDocumentation.t.sol +26 -5
- package/test/TestCrossCurrencyReclaim.t.sol +584 -0
- package/test/TestCrossSourceReallocation.t.sol +26 -2
- package/test/TestERC2771MetaTx.t.sol +557 -0
- package/test/TestEmptyBuybackSpecs.t.sol +23 -3
- package/test/TestFlashLoanSurplus.t.sol +28 -3
- package/test/TestHookArrayOOB.t.sol +24 -4
- package/test/TestLiquidationBehavior.t.sol +26 -3
- package/test/TestLoanSourceRotation.t.sol +525 -0
- package/test/TestLongTailEconomics.t.sol +651 -0
- package/test/TestLowFindings.t.sol +65 -2
- package/test/TestMixedFixes.t.sol +28 -3
- 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 +24 -2
- package/test/TestSplitWeightE2E.t.sol +29 -2
- package/test/TestSplitWeightFork.t.sol +46 -7
- package/test/TestStageTransitionBorrowable.t.sol +24 -2
- package/test/TestSwapTerminalPermission.t.sol +23 -3
- package/test/TestUint112Overflow.t.sol +28 -2
- package/test/TestZeroRepayment.t.sol +26 -2
- package/test/fork/ForkTestBase.sol +46 -3
- package/test/fork/TestCashOutFork.t.sol +1 -1
- package/test/fork/TestLoanBorrowFork.t.sol +1 -0
- package/test/fork/TestLoanCrossRulesetFork.t.sol +3 -1
- 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 +1 -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 +27 -4
- package/test/regression/TestLiquidateGapHandling.t.sol +29 -4
- package/test/regression/TestZeroPriceFeed.t.sol +396 -0
|
@@ -9,6 +9,7 @@ import {IREVDeployer} from "./../../src/interfaces/IREVDeployer.sol";
|
|
|
9
9
|
import {IREVLoans} from "./../../src/interfaces/IREVLoans.sol";
|
|
10
10
|
|
|
11
11
|
struct RevnetCoreDeployment {
|
|
12
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
12
13
|
IREVDeployer basic_deployer;
|
|
13
14
|
IREVLoans loans;
|
|
14
15
|
}
|
|
@@ -16,6 +17,7 @@ struct RevnetCoreDeployment {
|
|
|
16
17
|
library RevnetCoreDeploymentLib {
|
|
17
18
|
// Cheat code address, 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D.
|
|
18
19
|
address internal constant VM_ADDRESS = address(uint160(uint256(keccak256("hevm cheat code"))));
|
|
20
|
+
// forge-lint: disable-next-line(screaming-snake-case-const)
|
|
19
21
|
Vm internal constant vm = Vm(VM_ADDRESS);
|
|
20
22
|
|
|
21
23
|
function getDeployment(string memory path) internal returns (RevnetCoreDeployment memory deployment) {
|
|
@@ -38,6 +40,7 @@ library RevnetCoreDeploymentLib {
|
|
|
38
40
|
|
|
39
41
|
function getDeployment(
|
|
40
42
|
string memory path,
|
|
43
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
41
44
|
string memory network_name
|
|
42
45
|
)
|
|
43
46
|
internal
|
|
@@ -64,7 +67,9 @@ library RevnetCoreDeploymentLib {
|
|
|
64
67
|
/// @return The address of the contract.
|
|
65
68
|
function _getDeploymentAddress(
|
|
66
69
|
string memory path,
|
|
70
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
67
71
|
string memory project_name,
|
|
72
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
68
73
|
string memory network_name,
|
|
69
74
|
string memory contractName
|
|
70
75
|
)
|
|
@@ -73,7 +78,8 @@ library RevnetCoreDeploymentLib {
|
|
|
73
78
|
returns (address)
|
|
74
79
|
{
|
|
75
80
|
string memory deploymentJson =
|
|
76
|
-
|
|
81
|
+
// forge-lint: disable-next-line(unsafe-cheatcode)
|
|
82
|
+
vm.readFile(string.concat(path, project_name, "/", network_name, "/", contractName, ".json"));
|
|
77
83
|
return stdJson.readAddress({json: deploymentJson, key: ".address"});
|
|
78
84
|
}
|
|
79
85
|
}
|
package/src/REVDeployer.sol
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
pragma solidity 0.8.26;
|
|
3
3
|
|
|
4
|
-
import {ERC2771Context} from "@openzeppelin/contracts/metatx/ERC2771Context.sol";
|
|
5
|
-
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
6
|
-
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
|
7
|
-
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
|
|
8
|
-
import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
|
|
9
|
-
import {mulDiv} from "@prb/math/src/Common.sol";
|
|
10
4
|
import {IJB721TiersHook} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHook.sol";
|
|
11
5
|
import {IJB721TiersHookDeployer} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHookDeployer.sol";
|
|
12
6
|
import {JB721TiersHookFlags} from "@bananapus/721-hook-v6/src/structs/JB721TiersHookFlags.sol";
|
|
@@ -15,13 +9,11 @@ import {IJBBuybackHookRegistry} from "@bananapus/buyback-hook-v6/src/interfaces/
|
|
|
15
9
|
import {IJBCashOutHook} from "@bananapus/core-v6/src/interfaces/IJBCashOutHook.sol";
|
|
16
10
|
import {IJBController} from "@bananapus/core-v6/src/interfaces/IJBController.sol";
|
|
17
11
|
import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
|
|
18
|
-
import {IJBPayHook} from "@bananapus/core-v6/src/interfaces/IJBPayHook.sol";
|
|
19
12
|
import {IJBPermissioned} from "@bananapus/core-v6/src/interfaces/IJBPermissioned.sol";
|
|
20
13
|
import {IJBPermissions} from "@bananapus/core-v6/src/interfaces/IJBPermissions.sol";
|
|
21
14
|
import {IJBProjects} from "@bananapus/core-v6/src/interfaces/IJBProjects.sol";
|
|
22
15
|
import {IJBRulesetApprovalHook} from "@bananapus/core-v6/src/interfaces/IJBRulesetApprovalHook.sol";
|
|
23
16
|
import {IJBRulesetDataHook} from "@bananapus/core-v6/src/interfaces/IJBRulesetDataHook.sol";
|
|
24
|
-
import {IJBSplitHook} from "@bananapus/core-v6/src/interfaces/IJBSplitHook.sol";
|
|
25
17
|
import {IJBTerminal} from "@bananapus/core-v6/src/interfaces/IJBTerminal.sol";
|
|
26
18
|
import {JBCashOuts} from "@bananapus/core-v6/src/libraries/JBCashOuts.sol";
|
|
27
19
|
import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
|
|
@@ -38,14 +30,18 @@ import {JBPermissionsData} from "@bananapus/core-v6/src/structs/JBPermissionsDat
|
|
|
38
30
|
import {JBRuleset} from "@bananapus/core-v6/src/structs/JBRuleset.sol";
|
|
39
31
|
import {JBRulesetConfig} from "@bananapus/core-v6/src/structs/JBRulesetConfig.sol";
|
|
40
32
|
import {JBRulesetMetadata} from "@bananapus/core-v6/src/structs/JBRulesetMetadata.sol";
|
|
41
|
-
import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
|
|
42
|
-
import {JBTokenAmount} from "@bananapus/core-v6/src/structs/JBTokenAmount.sol";
|
|
43
33
|
import {JBSplitGroup} from "@bananapus/core-v6/src/structs/JBSplitGroup.sol";
|
|
44
34
|
import {JBTerminalConfig} from "@bananapus/core-v6/src/structs/JBTerminalConfig.sol";
|
|
45
35
|
import {JBPermissionIds} from "@bananapus/permission-ids-v6/src/JBPermissionIds.sol";
|
|
46
36
|
import {IJBSuckerRegistry} from "@bananapus/suckers-v6/src/interfaces/IJBSuckerRegistry.sol";
|
|
47
37
|
import {CTPublisher} from "@croptop/core-v6/src/CTPublisher.sol";
|
|
48
38
|
import {CTAllowedPost} from "@croptop/core-v6/src/structs/CTAllowedPost.sol";
|
|
39
|
+
import {ERC2771Context} from "@openzeppelin/contracts/metatx/ERC2771Context.sol";
|
|
40
|
+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
41
|
+
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
|
42
|
+
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
|
|
43
|
+
import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
|
|
44
|
+
import {mulDiv} from "@prb/math/src/Common.sol";
|
|
49
45
|
|
|
50
46
|
import {IREVDeployer} from "./interfaces/IREVDeployer.sol";
|
|
51
47
|
import {REVAutoIssuance} from "./structs/REVAutoIssuance.sol";
|
|
@@ -224,6 +220,8 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IJBRulesetDataHook, IJBCas
|
|
|
224
220
|
});
|
|
225
221
|
|
|
226
222
|
// Give the loan contract permission to use the surplus allowance of all revnets.
|
|
223
|
+
// Uses wildcard revnetId=0 intentionally — the loan contract is a singleton shared by all revnets,
|
|
224
|
+
// and each revnet's surplus allowance limits already constrain how much can be drawn.
|
|
227
225
|
_setPermission({operator: LOANS, revnetId: 0, permissionId: JBPermissionIds.USE_ALLOWANCE});
|
|
228
226
|
|
|
229
227
|
// Give the buyback hook (registry) permission to configure pools on all revnets.
|
|
@@ -255,6 +253,8 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IJBRulesetDataHook, IJBCas
|
|
|
255
253
|
)
|
|
256
254
|
{
|
|
257
255
|
// If the cash out is from a sucker, return the full cash out amount without taxes or fees.
|
|
256
|
+
// This relies on the sucker registry to only contain trusted sucker contracts deployed via
|
|
257
|
+
// the registry's own deploySuckersFor flow — external addresses cannot register as suckers.
|
|
258
258
|
if (_isSuckerOf({revnetId: context.projectId, addr: context.holder})) {
|
|
259
259
|
return (0, context.cashOutCount, context.totalSupply, hookSpecifications);
|
|
260
260
|
}
|
|
@@ -270,12 +270,15 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IJBRulesetDataHook, IJBCas
|
|
|
270
270
|
// Get the terminal that will receive the cash out fee.
|
|
271
271
|
IJBTerminal feeTerminal = DIRECTORY.primaryTerminalOf({projectId: FEE_REVNET_ID, token: context.surplus.token});
|
|
272
272
|
|
|
273
|
-
// If there's no cash out tax (100% cash out tax rate),
|
|
274
|
-
|
|
273
|
+
// If there's no cash out tax (100% cash out tax rate), if there's no fee terminal, or if the beneficiary is
|
|
274
|
+
// feeless (e.g. the router terminal routing value between projects), do not charge a fee.
|
|
275
|
+
if (context.cashOutTaxRate == 0 || address(feeTerminal) == address(0) || context.beneficiaryIsFeeless) {
|
|
275
276
|
return (context.cashOutTaxRate, context.cashOutCount, context.totalSupply, hookSpecifications);
|
|
276
277
|
}
|
|
277
278
|
|
|
278
279
|
// Get a reference to the number of tokens being used to pay the fee (out of the total being cashed out).
|
|
280
|
+
// Micro cash outs (< 40 wei at 2.5% fee) round feeCashOutCount to zero, bypassing the fee.
|
|
281
|
+
// Economically insignificant: the gas cost of the transaction far exceeds the bypassed fee. No fix needed.
|
|
279
282
|
uint256 feeCashOutCount = mulDiv({x: context.cashOutCount, y: FEE, denominator: JBConstants.MAX_FEE});
|
|
280
283
|
uint256 nonFeeCashOutCount = context.cashOutCount - feeCashOutCount;
|
|
281
284
|
|
|
@@ -483,26 +486,6 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IJBRulesetDataHook, IJBCas
|
|
|
483
486
|
}
|
|
484
487
|
}
|
|
485
488
|
|
|
486
|
-
/// @notice Try to initialize a Uniswap V4 buyback pool for a terminal token at a generic 1:1 price.
|
|
487
|
-
/// @dev Called after the ERC-20 token is deployed so the pool can be initialized in the PoolManager.
|
|
488
|
-
/// Silently catches failures (e.g., if the pool is already initialized).
|
|
489
|
-
/// @param revnetId The ID of the revnet.
|
|
490
|
-
/// @param terminalToken The terminal token to initialize a buyback pool for.
|
|
491
|
-
function _tryInitializeBuybackPoolFor(uint256 revnetId, address terminalToken) internal {
|
|
492
|
-
// Try to initialize the pool at a generic 1:1 sqrtPriceX96 and configure the buyback hook.
|
|
493
|
-
// The buyback hook constructs the PoolKey internally from the project token, terminal token, and pool params.
|
|
494
|
-
// slither-disable-next-line calls-loop
|
|
495
|
-
try BUYBACK_HOOK.initializePoolFor({
|
|
496
|
-
projectId: revnetId,
|
|
497
|
-
fee: DEFAULT_BUYBACK_POOL_FEE,
|
|
498
|
-
tickSpacing: DEFAULT_BUYBACK_TICK_SPACING,
|
|
499
|
-
twapWindow: DEFAULT_BUYBACK_TWAP_WINDOW,
|
|
500
|
-
terminalToken: terminalToken,
|
|
501
|
-
sqrtPriceX96: uint160(1 << 96)
|
|
502
|
-
}) {}
|
|
503
|
-
catch {} // Pool may already be initialized — that's OK.
|
|
504
|
-
}
|
|
505
|
-
|
|
506
489
|
/// @notice Make a ruleset configuration for a revnet's stage.
|
|
507
490
|
/// @param baseCurrency The base currency of the revnet.
|
|
508
491
|
/// @param stageConfiguration The stage configuration to make a ruleset for.
|
|
@@ -582,6 +565,26 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IJBRulesetDataHook, IJBCas
|
|
|
582
565
|
}
|
|
583
566
|
}
|
|
584
567
|
|
|
568
|
+
/// @notice Try to initialize a Uniswap V4 buyback pool for a terminal token at a generic 1:1 price.
|
|
569
|
+
/// @dev Called after the ERC-20 token is deployed so the pool can be initialized in the PoolManager.
|
|
570
|
+
/// Silently catches failures (e.g., if the pool is already initialized).
|
|
571
|
+
/// @param revnetId The ID of the revnet.
|
|
572
|
+
/// @param terminalToken The terminal token to initialize a buyback pool for.
|
|
573
|
+
function _tryInitializeBuybackPoolFor(uint256 revnetId, address terminalToken) internal {
|
|
574
|
+
// Try to initialize the pool at a generic 1:1 sqrtPriceX96 and configure the buyback hook.
|
|
575
|
+
// The buyback hook constructs the PoolKey internally from the project token, terminal token, and pool params.
|
|
576
|
+
// slither-disable-next-line calls-loop
|
|
577
|
+
try BUYBACK_HOOK.initializePoolFor({
|
|
578
|
+
projectId: revnetId,
|
|
579
|
+
fee: DEFAULT_BUYBACK_POOL_FEE,
|
|
580
|
+
tickSpacing: DEFAULT_BUYBACK_TICK_SPACING,
|
|
581
|
+
twapWindow: DEFAULT_BUYBACK_TWAP_WINDOW,
|
|
582
|
+
terminalToken: terminalToken,
|
|
583
|
+
sqrtPriceX96: uint160(1 << 96)
|
|
584
|
+
}) {}
|
|
585
|
+
catch {} // Pool may already be initialized — that's OK.
|
|
586
|
+
}
|
|
587
|
+
|
|
585
588
|
//*********************************************************************//
|
|
586
589
|
// --------------------- external transactions ----------------------- //
|
|
587
590
|
//*********************************************************************//
|
|
@@ -649,7 +652,13 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IJBRulesetDataHook, IJBCas
|
|
|
649
652
|
/// @param stageId The ID of the stage auto-mint tokens are available from.
|
|
650
653
|
/// @param beneficiary The address to auto-mint tokens to.
|
|
651
654
|
function autoIssueFor(uint256 revnetId, uint256 stageId, address beneficiary) external override {
|
|
652
|
-
// Get
|
|
655
|
+
// Get the ruleset for the stage to check if it has started.
|
|
656
|
+
// Stage IDs are `block.timestamp + i` where `i` is the stage index. These match real JB ruleset IDs
|
|
657
|
+
// because JBRulesets assigns IDs the same way: `latestId >= block.timestamp ? latestId + 1 : block.timestamp`
|
|
658
|
+
// (see JBRulesets.sol L172). When all stages are queued in a single deployFor() call, the sequential
|
|
659
|
+
// IDs `block.timestamp`, `block.timestamp + 1`, ... exactly correspond to the JB-assigned ruleset IDs.
|
|
660
|
+
// The returned `ruleset.start` contains the derived start time (from `deriveStartFrom` using the stage's
|
|
661
|
+
// `mustStartAtOrAfter`), NOT the queue timestamp — so the timing guard correctly blocks early claims.
|
|
653
662
|
// slither-disable-next-line unused-return
|
|
654
663
|
(JBRuleset memory ruleset,) = CONTROLLER.getRulesetOf({projectId: revnetId, rulesetId: stageId});
|
|
655
664
|
|
|
@@ -678,6 +687,17 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IJBRulesetDataHook, IJBCas
|
|
|
678
687
|
});
|
|
679
688
|
}
|
|
680
689
|
|
|
690
|
+
/// @notice Burn any of a revnet's tokens held by this contract.
|
|
691
|
+
/// @dev Project tokens can end up here from reserved token distribution when splits don't sum to 100%.
|
|
692
|
+
/// @param revnetId The ID of the revnet whose tokens should be burned.
|
|
693
|
+
function burnHeldTokensOf(uint256 revnetId) external override {
|
|
694
|
+
uint256 balance = CONTROLLER.TOKENS().totalBalanceOf({holder: address(this), projectId: revnetId});
|
|
695
|
+
if (balance == 0) revert REVDeployer_NothingToBurn();
|
|
696
|
+
CONTROLLER.burnTokensOf({holder: address(this), projectId: revnetId, tokenCount: balance, memo: ""});
|
|
697
|
+
// slither-disable-next-line reentrancy-events
|
|
698
|
+
emit BurnHeldTokens(revnetId, balance, _msgSender());
|
|
699
|
+
}
|
|
700
|
+
|
|
681
701
|
/// @notice Launch a revnet, or initialize an existing Juicebox project as a revnet.
|
|
682
702
|
/// @dev When initializing an existing project (revnetId != 0):
|
|
683
703
|
/// - The project must not yet have a controller or rulesets. `JBController.launchRulesetsFor` enforces this —
|
|
@@ -780,17 +800,6 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IJBRulesetDataHook, IJBCas
|
|
|
780
800
|
return (revnetId, hook);
|
|
781
801
|
}
|
|
782
802
|
|
|
783
|
-
/// @notice Burn any of a revnet's tokens held by this contract.
|
|
784
|
-
/// @dev Project tokens can end up here from reserved token distribution when splits don't sum to 100%.
|
|
785
|
-
/// @param revnetId The ID of the revnet whose tokens should be burned.
|
|
786
|
-
function burnHeldTokensOf(uint256 revnetId) external override {
|
|
787
|
-
uint256 balance = CONTROLLER.TOKENS().totalBalanceOf({holder: address(this), projectId: revnetId});
|
|
788
|
-
if (balance == 0) revert REVDeployer_NothingToBurn();
|
|
789
|
-
CONTROLLER.burnTokensOf({holder: address(this), projectId: revnetId, tokenCount: balance, memo: ""});
|
|
790
|
-
// slither-disable-next-line reentrancy-events
|
|
791
|
-
emit BurnHeldTokens(revnetId, balance, _msgSender());
|
|
792
|
-
}
|
|
793
|
-
|
|
794
803
|
/// @notice Deploy new suckers for an existing revnet.
|
|
795
804
|
/// @dev Only the revnet's split operator can deploy new suckers.
|
|
796
805
|
/// @param revnetId The ID of the revnet to deploy suckers for.
|
|
@@ -1122,6 +1131,9 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IJBRulesetDataHook, IJBCas
|
|
|
1122
1131
|
/// of collateral) or lower issuance weight (reducing the surplus-per-token ratio). Borrowers should monitor
|
|
1123
1132
|
/// upcoming stage transitions and adjust their positions accordingly, as loans that fall below their required
|
|
1124
1133
|
/// collateralization may become eligible for liquidation.
|
|
1134
|
+
/// @dev `cashOutTaxRate` changes at stage boundaries may allow users to cash out just before a rate increase.
|
|
1135
|
+
/// This is accepted behavior — the arbitrage window is bounded by the ruleset design, and all stages are
|
|
1136
|
+
/// configured immutably at deployment time.
|
|
1125
1137
|
/// @param revnetId The ID of the revnet to make rulesets for.
|
|
1126
1138
|
/// @param configuration The configuration containing the revnet's stages.
|
|
1127
1139
|
/// @param terminalConfigurations The terminals to set up for the revnet. Used for payments and cash outs.
|
|
@@ -1229,8 +1241,11 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IJBRulesetDataHook, IJBCas
|
|
|
1229
1241
|
});
|
|
1230
1242
|
|
|
1231
1243
|
// Store the amount of tokens that can be auto-minted on this chain during this stage.
|
|
1232
|
-
// The
|
|
1233
|
-
//
|
|
1244
|
+
// The stage ID is `block.timestamp + i`. This matches the ruleset ID that JBRulesets assigns
|
|
1245
|
+
// because JBRulesets uses `latestId >= block.timestamp ? latestId + 1 : block.timestamp`
|
|
1246
|
+
// (JBRulesets.sol L172), producing the same sequential IDs when all stages are queued in one tx.
|
|
1247
|
+
// `autoIssueFor` later calls `getRulesetOf(revnetId, stageId)` — the returned `ruleset.start`
|
|
1248
|
+
// is the derived start time (not the queue time), so the timing guard works correctly.
|
|
1234
1249
|
// slither-disable-next-line reentrancy-benign
|
|
1235
1250
|
amountToAutoIssue[revnetId][block.timestamp + i][autoIssuance.beneficiary] += autoIssuance.count;
|
|
1236
1251
|
}
|
|
@@ -1288,7 +1303,8 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IJBRulesetDataHook, IJBCas
|
|
|
1288
1303
|
{
|
|
1289
1304
|
// Set up the permission data.
|
|
1290
1305
|
JBPermissionsData memory permissionData =
|
|
1291
|
-
|
|
1306
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
1307
|
+
JBPermissionsData({operator: operator, projectId: uint64(revnetId), permissionIds: permissionIds});
|
|
1292
1308
|
|
|
1293
1309
|
// Set the permissions.
|
|
1294
1310
|
PERMISSIONS.setPermissionsFor({account: account, permissionsData: permissionData});
|
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.
|
|
@@ -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
|
|
|
@@ -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,6 +522,11 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
519
522
|
decimals: decimals
|
|
520
523
|
});
|
|
521
524
|
|
|
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
|
+
|
|
522
530
|
borrowedAmount += mulDiv({x: normalizedTokens, y: 10 ** decimals, denominator: pricePerUnit});
|
|
523
531
|
}
|
|
524
532
|
}
|
|
@@ -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.
|
|
@@ -579,7 +589,8 @@ 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
595
|
loan.prepaidDuration =
|
|
585
596
|
uint32(mulDiv({x: prepaidFeePercent, y: LOAN_LIQUIDATION_DURATION, denominator: MAX_PREPAID_FEE_PERCENT}));
|
|
@@ -587,10 +598,14 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
587
598
|
// Get the amount of the loan.
|
|
588
599
|
uint256 borrowAmount = _borrowAmountFrom({loan: loan, revnetId: revnetId, collateralCount: collateralCount});
|
|
589
600
|
|
|
601
|
+
// Revert if the bonding curve returns zero to prevent creating zero-amount loans.
|
|
602
|
+
if (borrowAmount == 0) revert REVLoans_ZeroBorrowAmount();
|
|
603
|
+
|
|
590
604
|
// Make sure the minimum borrow amount is met.
|
|
591
605
|
if (borrowAmount < minBorrowAmount) revert REVLoans_UnderMinBorrowAmount(minBorrowAmount, borrowAmount);
|
|
592
606
|
|
|
593
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.
|
|
594
609
|
uint256 sourceFeeAmount = JBFees.feeAmountFrom({amountBeforeFee: borrowAmount, feePercent: prepaidFeePercent});
|
|
595
610
|
|
|
596
611
|
// Borrow the amount.
|
|
@@ -635,6 +650,9 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
635
650
|
/// @param startingLoanId The ID of the loan to start iterating from.
|
|
636
651
|
/// @param count The amount of loans iterate over since the last liquidated loan.
|
|
637
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
|
+
|
|
638
656
|
// Iterate over the desired number of loans to check for liquidation.
|
|
639
657
|
for (uint256 i; i < count; i++) {
|
|
640
658
|
// Get a reference to the next loan ID.
|
|
@@ -928,8 +946,8 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
928
946
|
internal
|
|
929
947
|
{
|
|
930
948
|
// Register the source if this is the first time its being used for this revnet.
|
|
931
|
-
// Note: Sources are only appended, never removed.
|
|
932
|
-
// (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).
|
|
933
951
|
if (!isLoanSourceOf[revnetId][loan.source.terminal][loan.source.token]) {
|
|
934
952
|
isLoanSourceOf[revnetId][loan.source.terminal][loan.source.token] = true;
|
|
935
953
|
_loanSourcesOf[revnetId].push(REVLoanSource({token: loan.source.token, terminal: loan.source.terminal}));
|
|
@@ -995,6 +1013,9 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
995
1013
|
}
|
|
996
1014
|
|
|
997
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.
|
|
998
1019
|
_transferFrom({
|
|
999
1020
|
from: address(this),
|
|
1000
1021
|
to: beneficiary,
|
|
@@ -1035,7 +1056,9 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
1035
1056
|
if (newCollateralCount > type(uint112).max) {
|
|
1036
1057
|
revert REVLoans_OverflowAlert(newCollateralCount, type(uint112).max);
|
|
1037
1058
|
}
|
|
1059
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
1038
1060
|
loan.amount = uint112(newBorrowAmount);
|
|
1061
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
1039
1062
|
loan.collateral = uint112(newCollateralCount);
|
|
1040
1063
|
|
|
1041
1064
|
// INTERACTIONS: Execute external calls with pre-computed deltas.
|
|
@@ -1064,16 +1087,17 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
1064
1087
|
});
|
|
1065
1088
|
}
|
|
1066
1089
|
|
|
1067
|
-
// 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).
|
|
1068
1092
|
if (sourceFeeAmount > 0) {
|
|
1069
|
-
// Increase the allowance for the
|
|
1093
|
+
// Increase the allowance for the source terminal.
|
|
1070
1094
|
uint256 payValue = _beforeTransferTo({
|
|
1071
1095
|
to: address(loan.source.terminal), token: loan.source.token, amount: sourceFeeAmount
|
|
1072
1096
|
});
|
|
1073
1097
|
|
|
1074
|
-
// Pay the fee.
|
|
1075
|
-
// slither-disable-next-line unused-return
|
|
1076
|
-
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}({
|
|
1077
1101
|
projectId: revnetId,
|
|
1078
1102
|
token: loan.source.token,
|
|
1079
1103
|
amount: sourceFeeAmount,
|
|
@@ -1081,7 +1105,18 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
1081
1105
|
minReturnedTokens: 0,
|
|
1082
1106
|
memo: "Fee from loan",
|
|
1083
1107
|
metadata: bytes(abi.encodePacked(REV_ID))
|
|
1084
|
-
})
|
|
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
|
+
}
|
|
1085
1120
|
}
|
|
1086
1121
|
}
|
|
1087
1122
|
|
|
@@ -1351,6 +1386,7 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
|
|
|
1351
1386
|
if (amount > type(uint160).max) revert REVLoans_OverflowAlert(amount, type(uint160).max);
|
|
1352
1387
|
|
|
1353
1388
|
// Otherwise, attempt to use the `permit2` method.
|
|
1389
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
1354
1390
|
PERMIT2.transferFrom({from: from, to: to, amount: uint160(amount), token: token});
|
|
1355
1391
|
}
|
|
1356
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";
|
|
@@ -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;
|
|
@@ -13,6 +13,7 @@ import {REVBaseline721HookConfig} from "./REVBaseline721HookConfig.sol";
|
|
|
13
13
|
/// minting 721's from tiers that allow it.
|
|
14
14
|
/// @custom:member preventSplitOperatorIncreasingDiscountPercent A flag indicating if the revnet's split operator should
|
|
15
15
|
/// be prevented from increasing the discount of a tier.
|
|
16
|
+
// forge-lint: disable-next-line(pascal-case-struct)
|
|
16
17
|
struct REVDeploy721TiersHookConfig {
|
|
17
18
|
REVBaseline721HookConfig baseline721HookConfiguration;
|
|
18
19
|
bytes32 salt;
|
|
@@ -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;
|