@rev-net/core-v6 0.0.11 → 0.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/ADMINISTRATION.md +7 -7
  2. package/ARCHITECTURE.md +11 -11
  3. package/AUDIT_INSTRUCTIONS.md +295 -0
  4. package/CHANGE_LOG.md +316 -0
  5. package/README.md +9 -6
  6. package/RISKS.md +180 -35
  7. package/SKILLS.md +9 -11
  8. package/STYLE_GUIDE.md +14 -1
  9. package/USER_JOURNEYS.md +489 -0
  10. package/package.json +9 -9
  11. package/script/Deploy.s.sol +124 -40
  12. package/script/helpers/RevnetCoreDeploymentLib.sol +19 -6
  13. package/src/REVDeployer.sol +183 -175
  14. package/src/REVLoans.sol +65 -28
  15. package/src/interfaces/IREVDeployer.sol +25 -23
  16. package/src/structs/REV721TiersHookFlags.sol +1 -0
  17. package/src/structs/REVAutoIssuance.sol +1 -0
  18. package/src/structs/REVBaseline721HookConfig.sol +1 -0
  19. package/src/structs/REVConfig.sol +1 -0
  20. package/src/structs/REVCroptopAllowedPost.sol +1 -0
  21. package/src/structs/REVDeploy721TiersHookConfig.sol +13 -14
  22. package/src/structs/REVDescription.sol +1 -0
  23. package/src/structs/REVLoan.sol +1 -0
  24. package/src/structs/REVLoanSource.sol +1 -0
  25. package/src/structs/REVStageConfig.sol +1 -0
  26. package/src/structs/REVSuckerDeploymentConfig.sol +1 -0
  27. package/test/REV.integrations.t.sol +148 -19
  28. package/test/REVAutoIssuanceFuzz.t.sol +31 -6
  29. package/test/REVDeployerRegressions.t.sol +47 -9
  30. package/test/REVInvincibility.t.sol +83 -19
  31. package/test/REVInvincibilityHandler.sol +29 -0
  32. package/test/REVLifecycle.t.sol +36 -6
  33. package/test/REVLoans.invariants.t.sol +64 -10
  34. package/test/REVLoansAttacks.t.sol +54 -9
  35. package/test/REVLoansFeeRecovery.t.sol +61 -15
  36. package/test/REVLoansFindings.t.sol +42 -9
  37. package/test/REVLoansRegressions.t.sol +33 -6
  38. package/test/REVLoansSourceFeeRecovery.t.sol +491 -0
  39. package/test/REVLoansSourced.t.sol +79 -17
  40. package/test/REVLoansUnSourced.t.sol +61 -10
  41. package/test/TestBurnHeldTokens.t.sol +47 -11
  42. package/test/TestCEIPattern.t.sol +37 -6
  43. package/test/TestCashOutCallerValidation.t.sol +41 -8
  44. package/test/TestConversionDocumentation.t.sol +50 -13
  45. package/test/TestCrossCurrencyReclaim.t.sol +584 -0
  46. package/test/TestCrossSourceReallocation.t.sol +37 -6
  47. package/test/TestERC2771MetaTx.t.sol +557 -0
  48. package/test/TestEmptyBuybackSpecs.t.sol +45 -10
  49. package/test/TestFlashLoanSurplus.t.sol +39 -7
  50. package/test/TestHookArrayOOB.t.sol +42 -13
  51. package/test/TestLiquidationBehavior.t.sol +37 -7
  52. package/test/TestLoanSourceRotation.t.sol +525 -0
  53. package/test/TestLongTailEconomics.t.sol +651 -0
  54. package/test/TestLowFindings.t.sol +80 -8
  55. package/test/TestMixedFixes.t.sol +43 -9
  56. package/test/TestPermit2Signatures.t.sol +657 -0
  57. package/test/TestReallocationSandwich.t.sol +384 -0
  58. package/test/TestRevnetRegressions.t.sol +324 -0
  59. package/test/TestSplitWeightAdjustment.t.sol +52 -13
  60. package/test/TestSplitWeightE2E.t.sol +53 -18
  61. package/test/TestSplitWeightFork.t.sol +66 -21
  62. package/test/TestStageTransitionBorrowable.t.sol +38 -6
  63. package/test/TestSwapTerminalPermission.t.sol +37 -7
  64. package/test/TestUint112Overflow.t.sol +39 -6
  65. package/test/TestZeroRepayment.t.sol +37 -6
  66. package/test/fork/ForkTestBase.sol +66 -17
  67. package/test/fork/TestCashOutFork.t.sol +9 -3
  68. package/test/fork/TestLoanBorrowFork.t.sol +1 -0
  69. package/test/fork/TestLoanCrossRulesetFork.t.sol +11 -3
  70. package/test/fork/TestLoanLiquidationFork.t.sol +1 -0
  71. package/test/fork/TestLoanReallocateFork.t.sol +1 -0
  72. package/test/fork/TestLoanRepayFork.t.sol +1 -0
  73. package/test/fork/TestLoanTransferFork.t.sol +133 -0
  74. package/test/fork/TestSplitWeightFork.t.sol +3 -0
  75. package/test/helpers/REVEmpty721Config.sol +46 -0
  76. package/test/mock/MockBuybackDataHook.sol +1 -0
  77. package/test/regression/TestBurnPermissionRequired.t.sol +267 -0
  78. package/test/regression/TestCrossRevnetLiquidation.t.sol +228 -0
  79. package/test/regression/TestCumulativeLoanCounter.t.sol +38 -8
  80. package/test/regression/TestLiquidateGapHandling.t.sol +40 -8
  81. package/test/regression/TestZeroPriceFeed.t.sol +396 -0
@@ -0,0 +1,228 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity 0.8.26;
3
+
4
+ // forge-lint: disable-next-line(unaliased-plain-import)
5
+ import "forge-std/Test.sol";
6
+ // forge-lint: disable-next-line(unaliased-plain-import)
7
+ import /* {*} from */ "@bananapus/core-v6/test/helpers/TestBaseWorkflow.sol";
8
+ // forge-lint: disable-next-line(unaliased-plain-import)
9
+ import /* {*} from */ "./../../src/REVDeployer.sol";
10
+ // forge-lint: disable-next-line(unaliased-plain-import)
11
+ import "@croptop/core-v6/src/CTPublisher.sol";
12
+ import {MockBuybackDataHook} from "./../mock/MockBuybackDataHook.sol";
13
+ // forge-lint: disable-next-line(unaliased-plain-import)
14
+ import "@bananapus/core-v6/script/helpers/CoreDeploymentLib.sol";
15
+ // forge-lint: disable-next-line(unaliased-plain-import)
16
+ import "@bananapus/721-hook-v6/script/helpers/Hook721DeploymentLib.sol";
17
+ // forge-lint: disable-next-line(unaliased-plain-import)
18
+ import "@bananapus/suckers-v6/script/helpers/SuckerDeploymentLib.sol";
19
+ // forge-lint: disable-next-line(unaliased-plain-import)
20
+ import "@croptop/core-v6/script/helpers/CroptopDeploymentLib.sol";
21
+ // forge-lint: disable-next-line(unaliased-plain-import)
22
+ import "@bananapus/router-terminal-v6/script/helpers/RouterTerminalDeploymentLib.sol";
23
+ import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
24
+ import {JBAccountingContext} from "@bananapus/core-v6/src/structs/JBAccountingContext.sol";
25
+ import {MockPriceFeed} from "@bananapus/core-v6/test/mock/MockPriceFeed.sol";
26
+ import {MockERC20} from "@bananapus/core-v6/test/mock/MockERC20.sol";
27
+ import {REVLoans} from "../../src/REVLoans.sol";
28
+ import {REVLoan} from "../../src/structs/REVLoan.sol";
29
+ import {REVStageConfig, REVAutoIssuance} from "../../src/structs/REVStageConfig.sol";
30
+ import {REVLoanSource} from "../../src/structs/REVLoanSource.sol";
31
+ import {REVDescription} from "../../src/structs/REVDescription.sol";
32
+ import {JBSuckerDeployerConfig} from "@bananapus/suckers-v6/src/structs/JBSuckerDeployerConfig.sol";
33
+ import {JBSuckerRegistry} from "@bananapus/suckers-v6/src/JBSuckerRegistry.sol";
34
+ import {JB721TiersHookDeployer} from "@bananapus/721-hook-v6/src/JB721TiersHookDeployer.sol";
35
+ import {JB721TiersHook} from "@bananapus/721-hook-v6/src/JB721TiersHook.sol";
36
+ import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
37
+ import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
38
+ import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
39
+ import {REVEmpty721Config} from "../helpers/REVEmpty721Config.sol";
40
+
41
+ /// @notice Validates that liquidateExpiredLoansFrom rejects loan number ranges that would overflow into another
42
+ /// revnet's namespace.
43
+ /// @dev _generateLoanId computes: (revnetId * _ONE_TRILLION) + loanNumber. If startingLoanId + count > _ONE_TRILLION,
44
+ /// the generated loanId would collide with a different revnet's loans.
45
+ contract TestCrossRevnetLiquidation is TestBaseWorkflow {
46
+ // forge-lint: disable-next-line(mixed-case-variable)
47
+ bytes32 REV_DEPLOYER_SALT = "REVDeployer";
48
+
49
+ // forge-lint: disable-next-line(mixed-case-variable)
50
+ REVDeployer REV_DEPLOYER;
51
+ // forge-lint: disable-next-line(mixed-case-variable)
52
+ JB721TiersHook EXAMPLE_HOOK;
53
+ // forge-lint: disable-next-line(mixed-case-variable)
54
+ IJB721TiersHookDeployer HOOK_DEPLOYER;
55
+ // forge-lint: disable-next-line(mixed-case-variable)
56
+ IJB721TiersHookStore HOOK_STORE;
57
+ // forge-lint: disable-next-line(mixed-case-variable)
58
+ IJBAddressRegistry ADDRESS_REGISTRY;
59
+ // forge-lint: disable-next-line(mixed-case-variable)
60
+ REVLoans LOANS_CONTRACT;
61
+ // forge-lint: disable-next-line(mixed-case-variable)
62
+ MockERC20 TOKEN;
63
+ // forge-lint: disable-next-line(mixed-case-variable)
64
+ IJBSuckerRegistry SUCKER_REGISTRY;
65
+ // forge-lint: disable-next-line(mixed-case-variable)
66
+ CTPublisher PUBLISHER;
67
+ // forge-lint: disable-next-line(mixed-case-variable)
68
+ MockBuybackDataHook MOCK_BUYBACK;
69
+
70
+ // forge-lint: disable-next-line(mixed-case-variable)
71
+ uint256 FEE_PROJECT_ID;
72
+ // forge-lint: disable-next-line(mixed-case-variable)
73
+ uint256 REVNET_ID;
74
+
75
+ address private constant TRUSTED_FORWARDER = 0xB2b5841DBeF766d4b521221732F9B618fCf34A87;
76
+
77
+ /// @dev _ONE_TRILLION mirrors the private constant in REVLoans.sol.
78
+ uint256 private constant _ONE_TRILLION = 1_000_000_000_000;
79
+
80
+ function setUp() public override {
81
+ super.setUp();
82
+ FEE_PROJECT_ID = jbProjects().createFor(multisig());
83
+ SUCKER_REGISTRY = new JBSuckerRegistry(jbDirectory(), jbPermissions(), multisig(), address(0));
84
+ HOOK_STORE = new JB721TiersHookStore();
85
+ EXAMPLE_HOOK = new JB721TiersHook(
86
+ jbDirectory(), jbPermissions(), jbPrices(), jbRulesets(), HOOK_STORE, jbSplits(), multisig()
87
+ );
88
+ ADDRESS_REGISTRY = new JBAddressRegistry();
89
+ HOOK_DEPLOYER = new JB721TiersHookDeployer(EXAMPLE_HOOK, HOOK_STORE, ADDRESS_REGISTRY, multisig());
90
+ PUBLISHER = new CTPublisher(jbDirectory(), jbPermissions(), FEE_PROJECT_ID, multisig());
91
+ MOCK_BUYBACK = new MockBuybackDataHook();
92
+ TOKEN = new MockERC20("1/2 ETH", "1/2");
93
+ MockPriceFeed priceFeed = new MockPriceFeed(1e21, 6);
94
+ vm.prank(multisig());
95
+ jbPrices()
96
+ .addPriceFeedFor(0, uint32(uint160(address(TOKEN))), uint32(uint160(JBConstants.NATIVE_TOKEN)), priceFeed);
97
+ LOANS_CONTRACT = new REVLoans({
98
+ controller: jbController(),
99
+ projects: jbProjects(),
100
+ revId: FEE_PROJECT_ID,
101
+ owner: address(this),
102
+ permit2: permit2(),
103
+ trustedForwarder: TRUSTED_FORWARDER
104
+ });
105
+ REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
106
+ jbController(),
107
+ SUCKER_REGISTRY,
108
+ FEE_PROJECT_ID,
109
+ HOOK_DEPLOYER,
110
+ PUBLISHER,
111
+ IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
112
+ address(LOANS_CONTRACT),
113
+ TRUSTED_FORWARDER
114
+ );
115
+ vm.prank(multisig());
116
+ jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
117
+ _deployFeeProject();
118
+ _deployRevnet();
119
+ }
120
+
121
+ function _deployFeeProject() internal {
122
+ JBAccountingContext[] memory acc = new JBAccountingContext[](2);
123
+ acc[0] = JBAccountingContext({
124
+ token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
125
+ });
126
+ acc[1] = JBAccountingContext({token: address(TOKEN), decimals: 6, currency: uint32(uint160(address(TOKEN)))});
127
+ JBTerminalConfig[] memory tc = new JBTerminalConfig[](1);
128
+ tc[0] = JBTerminalConfig({terminal: jbMultiTerminal(), accountingContextsToAccept: acc});
129
+ REVStageConfig[] memory stages = new REVStageConfig[](1);
130
+ JBSplit[] memory splits = new JBSplit[](1);
131
+ splits[0].beneficiary = payable(multisig());
132
+ splits[0].percent = 10_000;
133
+ REVAutoIssuance[] memory ai = new REVAutoIssuance[](1);
134
+ ai[0] = REVAutoIssuance({chainId: uint32(block.chainid), count: uint104(70_000e18), beneficiary: multisig()});
135
+ stages[0] = REVStageConfig({
136
+ startsAtOrAfter: uint40(block.timestamp),
137
+ autoIssuances: ai,
138
+ splitPercent: 2000,
139
+ splits: splits,
140
+ initialIssuance: uint112(1000e18),
141
+ issuanceCutFrequency: 90 days,
142
+ issuanceCutPercent: JBConstants.MAX_WEIGHT_CUT_PERCENT / 2,
143
+ cashOutTaxRate: 6000,
144
+ extraMetadata: 0
145
+ });
146
+ REVConfig memory cfg = REVConfig({
147
+ // forge-lint: disable-next-line(named-struct-fields)
148
+ description: REVDescription("Revnet", "$REV", "ipfs://test", "REV_TOKEN"),
149
+ baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
150
+ splitOperator: multisig(),
151
+ stageConfigurations: stages
152
+ });
153
+ vm.prank(multisig());
154
+ REV_DEPLOYER.deployFor({
155
+ revnetId: FEE_PROJECT_ID,
156
+ configuration: cfg,
157
+ terminalConfigurations: tc,
158
+ suckerDeploymentConfiguration: REVSuckerDeploymentConfig({
159
+ deployerConfigurations: new JBSuckerDeployerConfig[](0), salt: keccak256("FEE")
160
+ }),
161
+ tiered721HookConfiguration: REVEmpty721Config.empty721Config(uint32(uint160(JBConstants.NATIVE_TOKEN))),
162
+ allowedPosts: REVEmpty721Config.emptyAllowedPosts()
163
+ });
164
+ }
165
+
166
+ function _deployRevnet() internal {
167
+ JBAccountingContext[] memory acc = new JBAccountingContext[](2);
168
+ acc[0] = JBAccountingContext({
169
+ token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
170
+ });
171
+ acc[1] = JBAccountingContext({token: address(TOKEN), decimals: 6, currency: uint32(uint160(address(TOKEN)))});
172
+ JBTerminalConfig[] memory tc = new JBTerminalConfig[](1);
173
+ tc[0] = JBTerminalConfig({terminal: jbMultiTerminal(), accountingContextsToAccept: acc});
174
+ REVStageConfig[] memory stages = new REVStageConfig[](1);
175
+ JBSplit[] memory splits = new JBSplit[](1);
176
+ splits[0].beneficiary = payable(multisig());
177
+ splits[0].percent = 10_000;
178
+ REVAutoIssuance[] memory ai = new REVAutoIssuance[](1);
179
+ ai[0] = REVAutoIssuance({chainId: uint32(block.chainid), count: uint104(70_000e18), beneficiary: multisig()});
180
+ stages[0] = REVStageConfig({
181
+ startsAtOrAfter: uint40(block.timestamp),
182
+ autoIssuances: ai,
183
+ splitPercent: 2000,
184
+ splits: splits,
185
+ initialIssuance: uint112(1000e18),
186
+ issuanceCutFrequency: 90 days,
187
+ issuanceCutPercent: JBConstants.MAX_WEIGHT_CUT_PERCENT / 2,
188
+ cashOutTaxRate: 6000,
189
+ extraMetadata: 0
190
+ });
191
+ REVConfig memory cfg = REVConfig({
192
+ // forge-lint: disable-next-line(named-struct-fields)
193
+ description: REVDescription("NANA", "$NANA", "ipfs://test2", "NANA_TOKEN"),
194
+ baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
195
+ splitOperator: multisig(),
196
+ stageConfigurations: stages
197
+ });
198
+ (REVNET_ID,) = REV_DEPLOYER.deployFor({
199
+ revnetId: 0,
200
+ configuration: cfg,
201
+ terminalConfigurations: tc,
202
+ suckerDeploymentConfiguration: REVSuckerDeploymentConfig({
203
+ deployerConfigurations: new JBSuckerDeployerConfig[](0), salt: keccak256("NANA")
204
+ }),
205
+ tiered721HookConfiguration: REVEmpty721Config.empty721Config(uint32(uint160(JBConstants.NATIVE_TOKEN))),
206
+ allowedPosts: REVEmpty721Config.emptyAllowedPosts()
207
+ });
208
+ }
209
+
210
+ /// @notice liquidateExpiredLoansFrom should revert when startingLoanId would overflow into another revnet's
211
+ /// namespace.
212
+ function test_liquidateExpiredLoans_revertsOnCrossRevnetOverflow() public {
213
+ vm.expectRevert(REVLoans.REVLoans_LoanIdOverflow.selector);
214
+ LOANS_CONTRACT.liquidateExpiredLoansFrom(REVNET_ID, _ONE_TRILLION, 1);
215
+ }
216
+
217
+ /// @notice liquidateExpiredLoansFrom should work for valid ranges within the revnet's namespace.
218
+ function test_liquidateExpiredLoans_normalRangeStillWorks() public {
219
+ // Calling with a valid range on a revnet with no loans should simply do nothing (no revert).
220
+ LOANS_CONTRACT.liquidateExpiredLoansFrom(REVNET_ID, 1, 5);
221
+ }
222
+
223
+ /// @notice The boundary case where startingLoanId + count == _ONE_TRILLION should succeed.
224
+ function test_liquidateExpiredLoans_boundaryExactlyAtLimit() public {
225
+ // Exactly at the boundary (not over) should not revert.
226
+ LOANS_CONTRACT.liquidateExpiredLoansFrom(REVNET_ID, _ONE_TRILLION - 1, 1);
227
+ }
228
+ }
@@ -1,28 +1,35 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity 0.8.26;
3
3
 
4
+ // forge-lint: disable-next-line(unaliased-plain-import)
4
5
  import "forge-std/Test.sol";
6
+ // forge-lint: disable-next-line(unaliased-plain-import)
5
7
  import /* {*} from */ "@bananapus/core-v6/test/helpers/TestBaseWorkflow.sol";
6
- import /* {*} from "@bananapus/721-hook-v6/src/JB721TiersHookDeployer.sol";
8
+ // import /* {*} from "@bananapus/721-hook-v6/src/JB721TiersHookDeployer.sol";
9
+ // forge-lint: disable-next-line(unaliased-plain-import)
7
10
  import /* {*} from */ "./../../src/REVDeployer.sol";
11
+ // forge-lint: disable-next-line(unaliased-plain-import)
8
12
  import "@croptop/core-v6/src/CTPublisher.sol";
9
13
  import {MockBuybackDataHook} from "./../mock/MockBuybackDataHook.sol";
14
+ // forge-lint: disable-next-line(unaliased-plain-import)
10
15
  import "@bananapus/core-v6/script/helpers/CoreDeploymentLib.sol";
16
+ // forge-lint: disable-next-line(unaliased-plain-import)
11
17
  import "@bananapus/721-hook-v6/script/helpers/Hook721DeploymentLib.sol";
18
+ // forge-lint: disable-next-line(unaliased-plain-import)
12
19
  import "@bananapus/suckers-v6/script/helpers/SuckerDeploymentLib.sol";
20
+ // forge-lint: disable-next-line(unaliased-plain-import)
13
21
  import "@croptop/core-v6/script/helpers/CroptopDeploymentLib.sol";
22
+ // forge-lint: disable-next-line(unaliased-plain-import)
14
23
  import "@bananapus/router-terminal-v6/script/helpers/RouterTerminalDeploymentLib.sol";
15
24
  import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
16
25
  import {JBAccountingContext} from "@bananapus/core-v6/src/structs/JBAccountingContext.sol";
17
26
  import {JBSingleAllowance} from "@bananapus/core-v6/src/structs/JBSingleAllowance.sol";
18
27
  import {MockPriceFeed} from "@bananapus/core-v6/test/mock/MockPriceFeed.sol";
19
- import {MockERC20} from "@bananapus/core-v6/test/mock/MockERC20.sol";
20
28
  import {REVLoans} from "../../src/REVLoans.sol";
21
29
  import {REVLoan} from "../../src/structs/REVLoan.sol";
22
30
  import {REVStageConfig, REVAutoIssuance} from "../../src/structs/REVStageConfig.sol";
23
31
  import {REVLoanSource} from "../../src/structs/REVLoanSource.sol";
24
32
  import {REVDescription} from "../../src/structs/REVDescription.sol";
25
- import {IREVLoans} from "../../src/interfaces/IREVLoans.sol";
26
33
  import {JBSuckerDeployerConfig} from "@bananapus/suckers-v6/src/structs/JBSuckerDeployerConfig.sol";
27
34
  import {JBSuckerRegistry} from "@bananapus/suckers-v6/src/JBSuckerRegistry.sol";
28
35
  import {JB721TiersHookDeployer} from "@bananapus/721-hook-v6/src/JB721TiersHookDeployer.sol";
@@ -30,29 +37,45 @@ import {JB721TiersHook} from "@bananapus/721-hook-v6/src/JB721TiersHook.sol";
30
37
  import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
31
38
  import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
32
39
  import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
40
+ import {REVEmpty721Config} from "../helpers/REVEmpty721Config.sol";
33
41
 
34
42
  /// @notice totalLoansBorrowedFor is a cumulative counter, not an active loan count.
35
43
  /// @dev The rename from numberOfLoansFor to totalLoansBorrowedFor clarifies that the counter only increments
36
44
  /// and never decrements. Repaying or liquidating a loan does NOT reduce the counter. This test verifies that
37
45
  /// the counter remains at its high-water mark after loans are fully repaid and after loans are liquidated.
38
46
  contract TestCumulativeLoanCounter is TestBaseWorkflow {
47
+ // forge-lint: disable-next-line(mixed-case-variable)
39
48
  bytes32 REV_DEPLOYER_SALT = "REVDeployer";
40
49
 
50
+ // forge-lint: disable-next-line(mixed-case-variable)
41
51
  REVDeployer REV_DEPLOYER;
52
+ // forge-lint: disable-next-line(mixed-case-variable)
42
53
  JB721TiersHook EXAMPLE_HOOK;
54
+ // forge-lint: disable-next-line(mixed-case-variable)
43
55
  IJB721TiersHookDeployer HOOK_DEPLOYER;
56
+ // forge-lint: disable-next-line(mixed-case-variable)
44
57
  IJB721TiersHookStore HOOK_STORE;
58
+ // forge-lint: disable-next-line(mixed-case-variable)
45
59
  IJBAddressRegistry ADDRESS_REGISTRY;
60
+ // forge-lint: disable-next-line(mixed-case-variable)
46
61
  REVLoans LOANS_CONTRACT;
62
+ // forge-lint: disable-next-line(mixed-case-variable)
47
63
  IJBSuckerRegistry SUCKER_REGISTRY;
64
+ // forge-lint: disable-next-line(mixed-case-variable)
48
65
  CTPublisher PUBLISHER;
66
+ // forge-lint: disable-next-line(mixed-case-variable)
49
67
  MockBuybackDataHook MOCK_BUYBACK;
50
68
 
69
+ // forge-lint: disable-next-line(mixed-case-variable)
51
70
  uint256 FEE_PROJECT_ID;
71
+ // forge-lint: disable-next-line(mixed-case-variable)
52
72
  uint256 REVNET_ID;
53
73
 
74
+ // forge-lint: disable-next-line(mixed-case-variable)
54
75
  address USER1 = makeAddr("user1");
76
+ // forge-lint: disable-next-line(mixed-case-variable)
55
77
  address USER2 = makeAddr("user2");
78
+ // forge-lint: disable-next-line(mixed-case-variable)
56
79
  address USER3 = makeAddr("user3");
57
80
 
58
81
  address private constant TRUSTED_FORWARDER = 0xB2b5841DBeF766d4b521221732F9B618fCf34A87;
@@ -62,8 +85,9 @@ contract TestCumulativeLoanCounter is TestBaseWorkflow {
62
85
  FEE_PROJECT_ID = jbProjects().createFor(multisig());
63
86
  SUCKER_REGISTRY = new JBSuckerRegistry(jbDirectory(), jbPermissions(), multisig(), address(0));
64
87
  HOOK_STORE = new JB721TiersHookStore();
65
- EXAMPLE_HOOK =
66
- new JB721TiersHook(jbDirectory(), jbPermissions(), jbRulesets(), HOOK_STORE, jbSplits(), multisig());
88
+ EXAMPLE_HOOK = new JB721TiersHook(
89
+ jbDirectory(), jbPermissions(), jbPrices(), jbRulesets(), HOOK_STORE, jbSplits(), multisig()
90
+ );
67
91
  ADDRESS_REGISTRY = new JBAddressRegistry();
68
92
  HOOK_DEPLOYER = new JB721TiersHookDeployer(EXAMPLE_HOOK, HOOK_STORE, ADDRESS_REGISTRY, multisig());
69
93
  PUBLISHER = new CTPublisher(jbDirectory(), jbPermissions(), FEE_PROJECT_ID, multisig());
@@ -126,6 +150,7 @@ contract TestCumulativeLoanCounter is TestBaseWorkflow {
126
150
  extraMetadata: 0
127
151
  });
128
152
  REVConfig memory cfg = REVConfig({
153
+ // forge-lint: disable-next-line(named-struct-fields)
129
154
  description: REVDescription("Revnet", "$REV", "ipfs://test", "REV_TOKEN"),
130
155
  baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
131
156
  splitOperator: multisig(),
@@ -138,7 +163,9 @@ contract TestCumulativeLoanCounter is TestBaseWorkflow {
138
163
  terminalConfigurations: tc,
139
164
  suckerDeploymentConfiguration: REVSuckerDeploymentConfig({
140
165
  deployerConfigurations: new JBSuckerDeployerConfig[](0), salt: keccak256("FEE")
141
- })
166
+ }),
167
+ tiered721HookConfiguration: REVEmpty721Config.empty721Config(uint32(uint160(JBConstants.NATIVE_TOKEN))),
168
+ allowedPosts: REVEmpty721Config.emptyAllowedPosts()
142
169
  });
143
170
  }
144
171
 
@@ -167,18 +194,21 @@ contract TestCumulativeLoanCounter is TestBaseWorkflow {
167
194
  extraMetadata: 0
168
195
  });
169
196
  REVConfig memory cfg = REVConfig({
197
+ // forge-lint: disable-next-line(named-struct-fields)
170
198
  description: REVDescription("NANA", "$NANA", "ipfs://test2", "NANA_TOKEN"),
171
199
  baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
172
200
  splitOperator: multisig(),
173
201
  stageConfigurations: stages
174
202
  });
175
- REVNET_ID = REV_DEPLOYER.deployFor({
203
+ (REVNET_ID,) = REV_DEPLOYER.deployFor({
176
204
  revnetId: 0,
177
205
  configuration: cfg,
178
206
  terminalConfigurations: tc,
179
207
  suckerDeploymentConfiguration: REVSuckerDeploymentConfig({
180
208
  deployerConfigurations: new JBSuckerDeployerConfig[](0), salt: keccak256("NANA")
181
- })
209
+ }),
210
+ tiered721HookConfiguration: REVEmpty721Config.empty721Config(uint32(uint160(JBConstants.NATIVE_TOKEN))),
211
+ allowedPosts: REVEmpty721Config.emptyAllowedPosts()
182
212
  });
183
213
  }
184
214
 
@@ -1,16 +1,25 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity 0.8.26;
3
3
 
4
+ // forge-lint: disable-next-line(unaliased-plain-import)
4
5
  import "forge-std/Test.sol";
6
+ // forge-lint: disable-next-line(unaliased-plain-import)
5
7
  import /* {*} from */ "@bananapus/core-v6/test/helpers/TestBaseWorkflow.sol";
6
- import /* {*} from "@bananapus/721-hook-v6/src/JB721TiersHookDeployer.sol";
8
+ // import /* {*} from "@bananapus/721-hook-v6/src/JB721TiersHookDeployer.sol";
9
+ // forge-lint: disable-next-line(unaliased-plain-import)
7
10
  import /* {*} from */ "./../../src/REVDeployer.sol";
11
+ // forge-lint: disable-next-line(unaliased-plain-import)
8
12
  import "@croptop/core-v6/src/CTPublisher.sol";
9
13
  import {MockBuybackDataHook} from "./../mock/MockBuybackDataHook.sol";
14
+ // forge-lint: disable-next-line(unaliased-plain-import)
10
15
  import "@bananapus/core-v6/script/helpers/CoreDeploymentLib.sol";
16
+ // forge-lint: disable-next-line(unaliased-plain-import)
11
17
  import "@bananapus/721-hook-v6/script/helpers/Hook721DeploymentLib.sol";
18
+ // forge-lint: disable-next-line(unaliased-plain-import)
12
19
  import "@bananapus/suckers-v6/script/helpers/SuckerDeploymentLib.sol";
20
+ // forge-lint: disable-next-line(unaliased-plain-import)
13
21
  import "@croptop/core-v6/script/helpers/CroptopDeploymentLib.sol";
22
+ // forge-lint: disable-next-line(unaliased-plain-import)
14
23
  import "@bananapus/router-terminal-v6/script/helpers/RouterTerminalDeploymentLib.sol";
15
24
  import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
16
25
  import {JBAccountingContext} from "@bananapus/core-v6/src/structs/JBAccountingContext.sol";
@@ -22,7 +31,6 @@ import {REVLoan} from "../../src/structs/REVLoan.sol";
22
31
  import {REVStageConfig, REVAutoIssuance} from "../../src/structs/REVStageConfig.sol";
23
32
  import {REVLoanSource} from "../../src/structs/REVLoanSource.sol";
24
33
  import {REVDescription} from "../../src/structs/REVDescription.sol";
25
- import {IREVLoans} from "../../src/interfaces/IREVLoans.sol";
26
34
  import {JBSuckerDeployerConfig} from "@bananapus/suckers-v6/src/structs/JBSuckerDeployerConfig.sol";
27
35
  import {JBSuckerRegistry} from "@bananapus/suckers-v6/src/JBSuckerRegistry.sol";
28
36
  import {JB721TiersHookDeployer} from "@bananapus/721-hook-v6/src/JB721TiersHookDeployer.sol";
@@ -30,31 +38,47 @@ import {JB721TiersHook} from "@bananapus/721-hook-v6/src/JB721TiersHook.sol";
30
38
  import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
31
39
  import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
32
40
  import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
33
- import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
41
+ import {REVEmpty721Config} from "../helpers/REVEmpty721Config.sol";
34
42
 
35
43
  /// @notice liquidateExpiredLoansFrom halts on deleted loan gaps.
36
44
  /// @dev Before the fix, the function used `break` when encountering a deleted loan (createdAt == 0),
37
45
  /// which stopped the entire iteration. Expired loans after the gap were never liquidated.
38
46
  /// After the fix, `continue` is used instead, so the loop skips gaps and keeps processing.
39
47
  contract TestLiquidateGapHandling is TestBaseWorkflow {
48
+ // forge-lint: disable-next-line(mixed-case-variable)
40
49
  bytes32 REV_DEPLOYER_SALT = "REVDeployer";
41
50
 
51
+ // forge-lint: disable-next-line(mixed-case-variable)
42
52
  REVDeployer REV_DEPLOYER;
53
+ // forge-lint: disable-next-line(mixed-case-variable)
43
54
  JB721TiersHook EXAMPLE_HOOK;
55
+ // forge-lint: disable-next-line(mixed-case-variable)
44
56
  IJB721TiersHookDeployer HOOK_DEPLOYER;
57
+ // forge-lint: disable-next-line(mixed-case-variable)
45
58
  IJB721TiersHookStore HOOK_STORE;
59
+ // forge-lint: disable-next-line(mixed-case-variable)
46
60
  IJBAddressRegistry ADDRESS_REGISTRY;
61
+ // forge-lint: disable-next-line(mixed-case-variable)
47
62
  REVLoans LOANS_CONTRACT;
63
+ // forge-lint: disable-next-line(mixed-case-variable)
48
64
  MockERC20 TOKEN;
65
+ // forge-lint: disable-next-line(mixed-case-variable)
49
66
  IJBSuckerRegistry SUCKER_REGISTRY;
67
+ // forge-lint: disable-next-line(mixed-case-variable)
50
68
  CTPublisher PUBLISHER;
69
+ // forge-lint: disable-next-line(mixed-case-variable)
51
70
  MockBuybackDataHook MOCK_BUYBACK;
52
71
 
72
+ // forge-lint: disable-next-line(mixed-case-variable)
53
73
  uint256 FEE_PROJECT_ID;
74
+ // forge-lint: disable-next-line(mixed-case-variable)
54
75
  uint256 REVNET_ID;
55
76
 
77
+ // forge-lint: disable-next-line(mixed-case-variable)
56
78
  address USER1 = makeAddr("user1");
79
+ // forge-lint: disable-next-line(mixed-case-variable)
57
80
  address USER2 = makeAddr("user2");
81
+ // forge-lint: disable-next-line(mixed-case-variable)
58
82
  address USER3 = makeAddr("user3");
59
83
 
60
84
  address private constant TRUSTED_FORWARDER = 0xB2b5841DBeF766d4b521221732F9B618fCf34A87;
@@ -64,8 +88,9 @@ contract TestLiquidateGapHandling is TestBaseWorkflow {
64
88
  FEE_PROJECT_ID = jbProjects().createFor(multisig());
65
89
  SUCKER_REGISTRY = new JBSuckerRegistry(jbDirectory(), jbPermissions(), multisig(), address(0));
66
90
  HOOK_STORE = new JB721TiersHookStore();
67
- EXAMPLE_HOOK =
68
- new JB721TiersHook(jbDirectory(), jbPermissions(), jbRulesets(), HOOK_STORE, jbSplits(), multisig());
91
+ EXAMPLE_HOOK = new JB721TiersHook(
92
+ jbDirectory(), jbPermissions(), jbPrices(), jbRulesets(), HOOK_STORE, jbSplits(), multisig()
93
+ );
69
94
  ADDRESS_REGISTRY = new JBAddressRegistry();
70
95
  HOOK_DEPLOYER = new JB721TiersHookDeployer(EXAMPLE_HOOK, HOOK_STORE, ADDRESS_REGISTRY, multisig());
71
96
  PUBLISHER = new CTPublisher(jbDirectory(), jbPermissions(), FEE_PROJECT_ID, multisig());
@@ -128,6 +153,7 @@ contract TestLiquidateGapHandling is TestBaseWorkflow {
128
153
  extraMetadata: 0
129
154
  });
130
155
  REVConfig memory cfg = REVConfig({
156
+ // forge-lint: disable-next-line(named-struct-fields)
131
157
  description: REVDescription("Revnet", "$REV", "ipfs://test", "REV_TOKEN"),
132
158
  baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
133
159
  splitOperator: multisig(),
@@ -140,7 +166,9 @@ contract TestLiquidateGapHandling is TestBaseWorkflow {
140
166
  terminalConfigurations: tc,
141
167
  suckerDeploymentConfiguration: REVSuckerDeploymentConfig({
142
168
  deployerConfigurations: new JBSuckerDeployerConfig[](0), salt: keccak256("FEE")
143
- })
169
+ }),
170
+ tiered721HookConfiguration: REVEmpty721Config.empty721Config(uint32(uint160(JBConstants.NATIVE_TOKEN))),
171
+ allowedPosts: REVEmpty721Config.emptyAllowedPosts()
144
172
  });
145
173
  }
146
174
 
@@ -170,18 +198,21 @@ contract TestLiquidateGapHandling is TestBaseWorkflow {
170
198
  extraMetadata: 0
171
199
  });
172
200
  REVConfig memory cfg = REVConfig({
201
+ // forge-lint: disable-next-line(named-struct-fields)
173
202
  description: REVDescription("NANA", "$NANA", "ipfs://test2", "NANA_TOKEN"),
174
203
  baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
175
204
  splitOperator: multisig(),
176
205
  stageConfigurations: stages
177
206
  });
178
- REVNET_ID = REV_DEPLOYER.deployFor({
207
+ (REVNET_ID,) = REV_DEPLOYER.deployFor({
179
208
  revnetId: 0,
180
209
  configuration: cfg,
181
210
  terminalConfigurations: tc,
182
211
  suckerDeploymentConfiguration: REVSuckerDeploymentConfig({
183
212
  deployerConfigurations: new JBSuckerDeployerConfig[](0), salt: keccak256("NANA")
184
- })
213
+ }),
214
+ tiered721HookConfiguration: REVEmpty721Config.empty721Config(uint32(uint160(JBConstants.NATIVE_TOKEN))),
215
+ allowedPosts: REVEmpty721Config.emptyAllowedPosts()
185
216
  });
186
217
  }
187
218
 
@@ -288,6 +319,7 @@ contract TestLiquidateGapHandling is TestBaseWorkflow {
288
319
  /// Loan 1 and 4 should both be liquidated despite the double gap.
289
320
  function test_liquidationHandlesMultipleConsecutiveGaps() public {
290
321
  // Create 4 loans from the same user (simpler)
322
+ // forge-lint: disable-next-line(mixed-case-variable)
291
323
  address USER4 = makeAddr("user4");
292
324
  vm.deal(USER4, 100e18);
293
325