@rev-net/core-v6 0.0.36 → 0.0.39

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 (101) hide show
  1. package/CHANGELOG.md +2 -2
  2. package/README.md +6 -7
  3. package/foundry.toml +1 -1
  4. package/package.json +23 -16
  5. package/references/operations.md +1 -1
  6. package/references/runtime.md +1 -1
  7. package/script/Deploy.s.sol +12 -9
  8. package/src/REVDeployer.sol +60 -65
  9. package/src/REVHiddenTokens.sol +2 -2
  10. package/src/REVLoans.sol +134 -90
  11. package/src/REVOwner.sol +124 -17
  12. package/src/interfaces/IREVDeployer.sol +2 -1
  13. package/src/interfaces/IREVHiddenTokens.sol +4 -1
  14. package/src/interfaces/IREVOwner.sol +5 -0
  15. package/ADMINISTRATION.md +0 -73
  16. package/ARCHITECTURE.md +0 -116
  17. package/AUDIT_INSTRUCTIONS.md +0 -90
  18. package/RISKS.md +0 -97
  19. package/SKILLS.md +0 -46
  20. package/STYLE_GUIDE.md +0 -610
  21. package/USER_JOURNEYS.md +0 -195
  22. package/foundry.lock +0 -11
  23. package/slither-ci.config.json +0 -10
  24. package/sphinx.lock +0 -507
  25. package/test/REV.integrations.t.sol +0 -573
  26. package/test/REVAutoIssuanceFuzz.t.sol +0 -328
  27. package/test/REVDeployerRegressions.t.sol +0 -396
  28. package/test/REVInvincibility.t.sol +0 -1371
  29. package/test/REVInvincibilityHandler.sol +0 -387
  30. package/test/REVLifecycle.t.sol +0 -420
  31. package/test/REVLoans.invariants.t.sol +0 -724
  32. package/test/REVLoansAttacks.t.sol +0 -816
  33. package/test/REVLoansFeeRecovery.t.sol +0 -783
  34. package/test/REVLoansFindings.t.sol +0 -711
  35. package/test/REVLoansRegressions.t.sol +0 -364
  36. package/test/REVLoansSourceFeeRecovery.t.sol +0 -517
  37. package/test/REVLoansSourced.t.sol +0 -1839
  38. package/test/REVLoansUnSourced.t.sol +0 -409
  39. package/test/TestAuditFixVerification.t.sol +0 -675
  40. package/test/TestBurnHeldTokens.t.sol +0 -394
  41. package/test/TestCEIPattern.t.sol +0 -508
  42. package/test/TestCashOutCallerValidation.t.sol +0 -452
  43. package/test/TestConversionDocumentation.t.sol +0 -368
  44. package/test/TestCrossCurrencyReclaim.t.sol +0 -610
  45. package/test/TestCrossSourceReallocation.t.sol +0 -361
  46. package/test/TestERC2771MetaTx.t.sol +0 -585
  47. package/test/TestEmptyBuybackSpecs.t.sol +0 -300
  48. package/test/TestFlashLoanSurplus.t.sol +0 -365
  49. package/test/TestHiddenTokens.t.sol +0 -474
  50. package/test/TestHookArrayOOB.t.sol +0 -278
  51. package/test/TestLiquidationBehavior.t.sol +0 -398
  52. package/test/TestLoanSourceRotation.t.sol +0 -553
  53. package/test/TestLoansCashOutDelay.t.sol +0 -493
  54. package/test/TestLongTailEconomics.t.sol +0 -677
  55. package/test/TestLowFindings.t.sol +0 -677
  56. package/test/TestMixedFixes.t.sol +0 -593
  57. package/test/TestPermit2Signatures.t.sol +0 -683
  58. package/test/TestReallocationSandwich.t.sol +0 -412
  59. package/test/TestRevnetRegressions.t.sol +0 -350
  60. package/test/TestSplitWeightAdjustment.t.sol +0 -527
  61. package/test/TestSplitWeightE2E.t.sol +0 -605
  62. package/test/TestSplitWeightFork.t.sol +0 -855
  63. package/test/TestStageTransitionBorrowable.t.sol +0 -301
  64. package/test/TestSwapTerminalPermission.t.sol +0 -262
  65. package/test/TestTerminalEncodingInHash.t.sol +0 -326
  66. package/test/TestUint112Overflow.t.sol +0 -311
  67. package/test/TestZeroAmountLoanGuard.t.sol +0 -378
  68. package/test/TestZeroRepayment.t.sol +0 -354
  69. package/test/audit/CodexCrossChainBuybackRouteMismatch.t.sol +0 -184
  70. package/test/audit/CodexPhantomSurplusTerminal.t.sol +0 -367
  71. package/test/audit/CodexREVOwnerRemoteSurplusCurrencyMismatch.t.sol +0 -142
  72. package/test/audit/LoanIdOverflowGuard.t.sol +0 -523
  73. package/test/audit/NemesisOperatorDelegation.t.sol +0 -356
  74. package/test/audit/SupportsInterfaceTest.t.sol +0 -51
  75. package/test/audit/TestFeeAllowanceLeak.t.sol +0 -197
  76. package/test/audit/TestLoansAndDeployerFixes.t.sol +0 -576
  77. package/test/fork/ForkTestBase.sol +0 -727
  78. package/test/fork/TestAutoIssuanceFork.t.sol +0 -148
  79. package/test/fork/TestCashOutFork.t.sol +0 -253
  80. package/test/fork/TestIssuanceDecayFork.t.sol +0 -158
  81. package/test/fork/TestLoanBorrowFork.t.sol +0 -163
  82. package/test/fork/TestLoanCrossRulesetFork.t.sol +0 -308
  83. package/test/fork/TestLoanERC20Fork.t.sol +0 -465
  84. package/test/fork/TestLoanLiquidationFork.t.sol +0 -135
  85. package/test/fork/TestLoanReallocateFork.t.sol +0 -113
  86. package/test/fork/TestLoanRepayFork.t.sol +0 -188
  87. package/test/fork/TestLoanTransferFork.t.sol +0 -143
  88. package/test/fork/TestPermit2PaymentFork.t.sol +0 -300
  89. package/test/fork/TestSplitWeightFork.t.sol +0 -189
  90. package/test/helpers/MaliciousContracts.sol +0 -247
  91. package/test/helpers/REVEmpty721Config.sol +0 -45
  92. package/test/mock/MockBuybackCashOutRecorder.sol +0 -84
  93. package/test/mock/MockBuybackDataHook.sol +0 -112
  94. package/test/mock/MockBuybackDataHookMintPath.sol +0 -68
  95. package/test/mock/MockSuckerRegistry.sol +0 -17
  96. package/test/regression/TestBurnPermissionRequired.t.sol +0 -294
  97. package/test/regression/TestCashOutBuybackFeeLeak.t.sol +0 -232
  98. package/test/regression/TestCrossRevnetLiquidation.t.sol +0 -255
  99. package/test/regression/TestCumulativeLoanCounter.t.sol +0 -361
  100. package/test/regression/TestLiquidateGapHandling.t.sol +0 -394
  101. package/test/regression/TestZeroPriceFeed.t.sol +0 -422
@@ -1,727 +0,0 @@
1
- // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.28;
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
- // forge-lint: disable-next-line(unaliased-plain-import)
13
- import "@bananapus/core-v6/script/helpers/CoreDeploymentLib.sol";
14
- // forge-lint: disable-next-line(unaliased-plain-import)
15
- import "@bananapus/721-hook-v6/script/helpers/Hook721DeploymentLib.sol";
16
- // forge-lint: disable-next-line(unaliased-plain-import)
17
- import "@bananapus/suckers-v6/script/helpers/SuckerDeploymentLib.sol";
18
- // forge-lint: disable-next-line(unaliased-plain-import)
19
- import "@croptop/core-v6/script/helpers/CroptopDeploymentLib.sol";
20
- // forge-lint: disable-next-line(unaliased-plain-import)
21
- import "@bananapus/router-terminal-v6/script/helpers/RouterTerminalDeploymentLib.sol";
22
-
23
- import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
24
- import {JBMetadataResolver} from "@bananapus/core-v6/src/libraries/JBMetadataResolver.sol";
25
- import {JBAccountingContext} from "@bananapus/core-v6/src/structs/JBAccountingContext.sol";
26
- import {REVLoans} from "../../src/REVLoans.sol";
27
- import {REVLoan} from "../../src/structs/REVLoan.sol";
28
- import {REVStageConfig, REVAutoIssuance} from "../../src/structs/REVStageConfig.sol";
29
- import {REVLoanSource} from "../../src/structs/REVLoanSource.sol";
30
- import {REVDescription} from "../../src/structs/REVDescription.sol";
31
- import {IREVLoans} from "../../src/interfaces/IREVLoans.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 {JB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/JB721CheckpointsDeployer.sol";
38
- import {IJB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/interfaces/IJB721CheckpointsDeployer.sol";
39
- import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
40
- import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
41
- import {IJBRulesetDataHook} from "@bananapus/core-v6/src/interfaces/IJBRulesetDataHook.sol";
42
- import {IJBBuybackHookRegistry} from "@bananapus/buyback-hook-v6/src/interfaces/IJBBuybackHookRegistry.sol";
43
- // forge-lint: disable-next-line(unused-import)
44
- import {IJBPayHook} from "@bananapus/core-v6/src/interfaces/IJBPayHook.sol";
45
- import {JB721TierConfig} from "@bananapus/721-hook-v6/src/structs/JB721TierConfig.sol";
46
- import {JB721TierConfigFlags} from "@bananapus/721-hook-v6/src/structs/JB721TierConfigFlags.sol";
47
- import {JB721InitTiersConfig} from "@bananapus/721-hook-v6/src/structs/JB721InitTiersConfig.sol";
48
- import {IJB721TokenUriResolver} from "@bananapus/721-hook-v6/src/interfaces/IJB721TokenUriResolver.sol";
49
- import "@bananapus/721-hook-v6/src/JB721CheckpointsDeployer.sol";
50
- import {IJB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/interfaces/IJB721CheckpointsDeployer.sol";
51
- import {REVDeploy721TiersHookConfig} from "../../src/structs/REVDeploy721TiersHookConfig.sol";
52
- import {REVBaseline721HookConfig} from "../../src/structs/REVBaseline721HookConfig.sol";
53
- import {REV721TiersHookFlags} from "../../src/structs/REV721TiersHookFlags.sol";
54
- import {REVCroptopAllowedPost} from "../../src/structs/REVCroptopAllowedPost.sol";
55
- import {REVEmpty721Config} from "../helpers/REVEmpty721Config.sol";
56
-
57
- // Buyback hook
58
- import {JBBuybackHook} from "@bananapus/buyback-hook-v6/src/JBBuybackHook.sol";
59
- import {JBBuybackHookRegistry} from "@bananapus/buyback-hook-v6/src/JBBuybackHookRegistry.sol";
60
- // forge-lint: disable-next-line(unused-import)
61
- import {IJBBuybackHook} from "@bananapus/buyback-hook-v6/src/interfaces/IJBBuybackHook.sol";
62
- import {IGeomeanOracle} from "@bananapus/buyback-hook-v6/src/interfaces/IGeomeanOracle.sol";
63
-
64
- // Uniswap V4
65
- import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
66
- import {IUnlockCallback} from "@uniswap/v4-core/src/interfaces/callback/IUnlockCallback.sol";
67
- import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
68
- import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
69
- // forge-lint: disable-next-line(unused-import)
70
- import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol";
71
- import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol";
72
- import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
73
- import {ModifyLiquidityParams, SwapParams} from "@uniswap/v4-core/src/types/PoolOperation.sol";
74
- // forge-lint: disable-next-line(unused-import)
75
- import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
76
- import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol";
77
-
78
- import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol";
79
- import {REVOwner} from "../../src/REVOwner.sol";
80
- import {IREVDeployer} from "../../src/interfaces/IREVDeployer.sol";
81
- import {MockSuckerRegistry} from "../mock/MockSuckerRegistry.sol";
82
-
83
- /// @notice Helper that adds liquidity to a V4 pool via the unlock/callback pattern.
84
- contract LiquidityHelper is IUnlockCallback {
85
- // forge-lint: disable-next-line(screaming-snake-case-immutable)
86
- IPoolManager public immutable poolManager;
87
-
88
- enum Action {
89
- ADD_LIQUIDITY,
90
- SWAP
91
- }
92
-
93
- struct AddLiqParams {
94
- PoolKey key;
95
- int24 tickLower;
96
- int24 tickUpper;
97
- int256 liquidityDelta;
98
- }
99
-
100
- struct DoSwapParams {
101
- PoolKey key;
102
- bool zeroForOne;
103
- int256 amountSpecified;
104
- uint160 sqrtPriceLimitX96;
105
- }
106
-
107
- constructor(IPoolManager _poolManager) {
108
- poolManager = _poolManager;
109
- }
110
-
111
- function addLiquidity(
112
- PoolKey calldata key,
113
- int24 tickLower,
114
- int24 tickUpper,
115
- int256 liquidityDelta
116
- )
117
- external
118
- payable
119
- {
120
- bytes memory data =
121
- // forge-lint: disable-next-line(named-struct-fields)
122
- abi.encode(Action.ADD_LIQUIDITY, abi.encode(AddLiqParams(key, tickLower, tickUpper, liquidityDelta)));
123
- poolManager.unlock(data);
124
- }
125
-
126
- function swap(
127
- PoolKey calldata key,
128
- bool zeroForOne,
129
- int256 amountSpecified,
130
- uint160 sqrtPriceLimitX96
131
- )
132
- external
133
- payable
134
- {
135
- bytes memory data =
136
- // forge-lint: disable-next-line(named-struct-fields)
137
- abi.encode(Action.SWAP, abi.encode(DoSwapParams(key, zeroForOne, amountSpecified, sqrtPriceLimitX96)));
138
- poolManager.unlock(data);
139
- }
140
-
141
- function unlockCallback(bytes calldata data) external override returns (bytes memory) {
142
- require(msg.sender == address(poolManager), "only PM");
143
-
144
- (Action action, bytes memory inner) = abi.decode(data, (Action, bytes));
145
-
146
- if (action == Action.ADD_LIQUIDITY) {
147
- return _handleAddLiquidity(inner);
148
- } else {
149
- return _handleSwap(inner);
150
- }
151
- }
152
-
153
- function _handleAddLiquidity(bytes memory data) internal returns (bytes memory) {
154
- AddLiqParams memory params = abi.decode(data, (AddLiqParams));
155
-
156
- (BalanceDelta callerDelta,) = poolManager.modifyLiquidity(
157
- params.key,
158
- ModifyLiquidityParams({
159
- tickLower: params.tickLower,
160
- tickUpper: params.tickUpper,
161
- liquidityDelta: params.liquidityDelta,
162
- salt: bytes32(0)
163
- }),
164
- ""
165
- );
166
-
167
- _settleIfNegative(params.key.currency0, callerDelta.amount0());
168
- _settleIfNegative(params.key.currency1, callerDelta.amount1());
169
- _takeIfPositive(params.key.currency0, callerDelta.amount0());
170
- _takeIfPositive(params.key.currency1, callerDelta.amount1());
171
-
172
- return abi.encode(callerDelta);
173
- }
174
-
175
- function _handleSwap(bytes memory data) internal returns (bytes memory) {
176
- DoSwapParams memory params = abi.decode(data, (DoSwapParams));
177
-
178
- BalanceDelta delta = poolManager.swap(
179
- params.key,
180
- // forge-lint: disable-next-line(named-struct-fields)
181
- SwapParams(params.zeroForOne, params.amountSpecified, params.sqrtPriceLimitX96),
182
- ""
183
- );
184
-
185
- if (delta.amount0() < 0) {
186
- _settleIfNegative(params.key.currency0, delta.amount0());
187
- } else {
188
- _takeIfPositive(params.key.currency0, delta.amount0());
189
- }
190
- if (delta.amount1() < 0) {
191
- _settleIfNegative(params.key.currency1, delta.amount1());
192
- } else {
193
- _takeIfPositive(params.key.currency1, delta.amount1());
194
- }
195
-
196
- return abi.encode(delta);
197
- }
198
-
199
- function _settleIfNegative(Currency currency, int128 delta) internal {
200
- if (delta >= 0) return;
201
- // forge-lint: disable-next-line(unsafe-typecast)
202
- uint256 amount = uint256(uint128(-delta));
203
-
204
- if (currency.isAddressZero()) {
205
- poolManager.settle{value: amount}();
206
- } else {
207
- poolManager.sync(currency);
208
- // forge-lint: disable-next-line(erc20-unchecked-transfer)
209
- IERC20(Currency.unwrap(currency)).transfer(address(poolManager), amount);
210
- poolManager.settle();
211
- }
212
- }
213
-
214
- function _takeIfPositive(Currency currency, int128 delta) internal {
215
- if (delta <= 0) return;
216
- // forge-lint: disable-next-line(unsafe-typecast)
217
- uint256 amount = uint256(uint128(delta));
218
- poolManager.take(currency, address(this), amount);
219
- }
220
-
221
- receive() external payable {}
222
- }
223
-
224
- /// @notice Shared base for fork tests. Deploys full JB core + REVDeployer infrastructure on a mainnet fork.
225
- ///
226
- /// Requires: RPC_ETHEREUM_MAINNET env var for mainnet fork (real PoolManager).
227
- abstract contract ForkTestBase is TestBaseWorkflow {
228
- using JBMetadataResolver for bytes;
229
- using PoolIdLibrary for PoolKey;
230
- using CurrencyLibrary for Currency;
231
- using StateLibrary for IPoolManager;
232
-
233
- // ───────────────────────── Mainnet constants
234
- // ─────────────────────────
235
-
236
- address constant POOL_MANAGER_ADDR = 0x000000000004444c5dc75cB358380D2e3dE08A90;
237
-
238
- /// @notice Full-range tick bounds for tickSpacing = 60.
239
- int24 constant TICK_LOWER = -887_200;
240
- int24 constant TICK_UPPER = 887_200;
241
-
242
- // ───────────────────────── State
243
- // ─────────────────────────
244
-
245
- // forge-lint: disable-next-line(mixed-case-variable)
246
- REVDeployer REV_DEPLOYER;
247
- // forge-lint: disable-next-line(mixed-case-variable)
248
- REVOwner REV_OWNER;
249
- // forge-lint: disable-next-line(mixed-case-variable)
250
- JBBuybackHook BUYBACK_HOOK;
251
- // forge-lint: disable-next-line(mixed-case-variable)
252
- JBBuybackHookRegistry BUYBACK_REGISTRY;
253
- // forge-lint: disable-next-line(mixed-case-variable)
254
- JB721TiersHook EXAMPLE_HOOK;
255
- // forge-lint: disable-next-line(mixed-case-variable)
256
- IJB721TiersHookDeployer HOOK_DEPLOYER;
257
- // forge-lint: disable-next-line(mixed-case-variable)
258
- IJB721TiersHookStore HOOK_STORE;
259
- // forge-lint: disable-next-line(mixed-case-variable)
260
- IJBAddressRegistry ADDRESS_REGISTRY;
261
- // forge-lint: disable-next-line(mixed-case-variable)
262
- IREVLoans LOANS_CONTRACT;
263
- // forge-lint: disable-next-line(mixed-case-variable)
264
- IJBSuckerRegistry SUCKER_REGISTRY;
265
- // forge-lint: disable-next-line(mixed-case-variable)
266
- CTPublisher PUBLISHER;
267
- IPoolManager poolManager;
268
- LiquidityHelper liqHelper;
269
-
270
- // forge-lint: disable-next-line(mixed-case-variable)
271
- uint256 FEE_PROJECT_ID;
272
-
273
- address private constant TRUSTED_FORWARDER = 0xB2b5841DBeF766d4b521221732F9B618fCf34A87;
274
- // forge-lint: disable-next-line(mixed-case-variable)
275
- address PAYER = makeAddr("payer");
276
- // forge-lint: disable-next-line(mixed-case-variable)
277
- address BORROWER = makeAddr("borrower");
278
- // forge-lint: disable-next-line(mixed-case-variable)
279
- address SPLIT_BENEFICIARY = makeAddr("splitBeneficiary");
280
-
281
- // Tier configuration: 1 ETH tier with 30% split.
282
- uint104 constant TIER_PRICE = 1 ether;
283
- uint32 constant SPLIT_PERCENT = 300_000_000; // 30% of SPLITS_TOTAL_PERCENT (1_000_000_000)
284
- uint112 constant INITIAL_ISSUANCE = 1000e18; // 1000 tokens per ETH
285
-
286
- // ───────────────────────── Setup
287
- // ─────────────────────────
288
-
289
- function setUp() public virtual override {
290
- // Fork mainnet at a stable block — deterministic and post-V4 deployment.
291
- vm.createSelectFork("ethereum", 21_700_000);
292
-
293
- // Verify V4 PoolManager is deployed.
294
- require(POOL_MANAGER_ADDR.code.length > 0, "PoolManager not deployed at expected address");
295
-
296
- // Deploy fresh JB core on the forked mainnet.
297
- super.setUp();
298
-
299
- poolManager = IPoolManager(POOL_MANAGER_ADDR);
300
- liqHelper = new LiquidityHelper(poolManager);
301
-
302
- FEE_PROJECT_ID = jbProjects().createFor(multisig());
303
-
304
- SUCKER_REGISTRY = new JBSuckerRegistry(jbDirectory(), jbPermissions(), multisig(), address(0));
305
- HOOK_STORE = new JB721TiersHookStore();
306
- EXAMPLE_HOOK = new JB721TiersHook(
307
- jbDirectory(),
308
- jbPermissions(),
309
- jbPrices(),
310
- jbRulesets(),
311
- HOOK_STORE,
312
- jbSplits(),
313
- IJB721CheckpointsDeployer(address(new JB721CheckpointsDeployer())),
314
- multisig()
315
- );
316
- ADDRESS_REGISTRY = new JBAddressRegistry();
317
- HOOK_DEPLOYER = new JB721TiersHookDeployer(EXAMPLE_HOOK, HOOK_STORE, ADDRESS_REGISTRY, multisig());
318
- PUBLISHER = new CTPublisher(jbDirectory(), jbPermissions(), FEE_PROJECT_ID, multisig());
319
-
320
- // Deploy REAL buyback hook with real PoolManager.
321
- BUYBACK_HOOK = new JBBuybackHook(
322
- jbDirectory(),
323
- jbPermissions(),
324
- jbPrices(),
325
- jbProjects(),
326
- jbTokens(),
327
- poolManager,
328
- IHooks(address(0)), // oracleHook
329
- address(0) // trustedForwarder
330
- );
331
-
332
- // Deploy the registry and set the buyback hook as the default.
333
- BUYBACK_REGISTRY = new JBBuybackHookRegistry(
334
- jbPermissions(),
335
- jbProjects(),
336
- address(this), // owner
337
- address(0) // trustedForwarder
338
- );
339
- BUYBACK_REGISTRY.setDefaultHook(IJBRulesetDataHook(address(BUYBACK_HOOK)));
340
-
341
- LOANS_CONTRACT = new REVLoans({
342
- controller: jbController(),
343
- suckerRegistry: IJBSuckerRegistry(address(new MockSuckerRegistry())),
344
- revId: FEE_PROJECT_ID,
345
- owner: address(this),
346
- permit2: permit2(),
347
- trustedForwarder: TRUSTED_FORWARDER
348
- });
349
-
350
- REV_OWNER = new REVOwner(
351
- IJBBuybackHookRegistry(address(BUYBACK_REGISTRY)),
352
- jbDirectory(),
353
- FEE_PROJECT_ID,
354
- SUCKER_REGISTRY,
355
- address(LOANS_CONTRACT),
356
- address(0)
357
- );
358
-
359
- REV_DEPLOYER = new REVDeployer{salt: "REVDeployer_Fork"}(
360
- jbController(),
361
- SUCKER_REGISTRY,
362
- FEE_PROJECT_ID,
363
- HOOK_DEPLOYER,
364
- PUBLISHER,
365
- IJBBuybackHookRegistry(address(BUYBACK_REGISTRY)),
366
- address(LOANS_CONTRACT),
367
- TRUSTED_FORWARDER,
368
- address(REV_OWNER)
369
- );
370
-
371
- REV_OWNER.setDeployer(REV_DEPLOYER);
372
-
373
- vm.prank(multisig());
374
- jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
375
-
376
- // Fund payer and borrower.
377
- vm.deal(PAYER, 100 ether);
378
- vm.deal(BORROWER, 100 ether);
379
- }
380
-
381
- // ───────────────────────── Config Helpers
382
- // ─────────────────────────
383
-
384
- function _buildMinimalConfig(uint16 cashOutTaxRate)
385
- internal
386
- view
387
- returns (REVConfig memory cfg, JBTerminalConfig[] memory tc, REVSuckerDeploymentConfig memory sdc)
388
- {
389
- JBAccountingContext[] memory acc = new JBAccountingContext[](1);
390
- acc[0] = JBAccountingContext({
391
- token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
392
- });
393
- tc = new JBTerminalConfig[](1);
394
- tc[0] = JBTerminalConfig({terminal: jbMultiTerminal(), accountingContextsToAccept: acc});
395
-
396
- REVStageConfig[] memory stages = new REVStageConfig[](1);
397
- JBSplit[] memory splits = new JBSplit[](1);
398
- splits[0].beneficiary = payable(multisig());
399
- splits[0].percent = 10_000;
400
- stages[0] = REVStageConfig({
401
- startsAtOrAfter: uint40(block.timestamp),
402
- autoIssuances: new REVAutoIssuance[](0),
403
- splitPercent: 0,
404
- splits: splits,
405
- initialIssuance: INITIAL_ISSUANCE,
406
- issuanceCutFrequency: 0,
407
- issuanceCutPercent: 0,
408
- cashOutTaxRate: cashOutTaxRate,
409
- extraMetadata: 0
410
- });
411
-
412
- cfg = REVConfig({
413
- // forge-lint: disable-next-line(named-struct-fields)
414
- description: REVDescription("Fork Test", "FORK", "ipfs://fork", "FORK_SALT"),
415
- baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
416
- splitOperator: multisig(),
417
- stageConfigurations: stages
418
- });
419
-
420
- sdc = REVSuckerDeploymentConfig({
421
- deployerConfigurations: new JBSuckerDeployerConfig[](0), salt: keccak256(abi.encodePacked("FORK_TEST"))
422
- });
423
- }
424
-
425
- function _build721Config() internal view returns (REVDeploy721TiersHookConfig memory) {
426
- JB721TierConfig[] memory tiers = new JB721TierConfig[](1);
427
- JBSplit[] memory tierSplits = new JBSplit[](1);
428
- tierSplits[0] = JBSplit({
429
- preferAddToBalance: false,
430
- percent: uint32(JBConstants.SPLITS_TOTAL_PERCENT),
431
- projectId: 0,
432
- beneficiary: payable(SPLIT_BENEFICIARY),
433
- lockedUntil: 0,
434
- hook: IJBSplitHook(address(0))
435
- });
436
-
437
- tiers[0] = JB721TierConfig({
438
- price: TIER_PRICE,
439
- initialSupply: 100,
440
- votingUnits: 0,
441
- reserveFrequency: 0,
442
- reserveBeneficiary: address(0),
443
- // forge-lint: disable-next-line(unsafe-typecast)
444
- encodedIPFSUri: bytes32("tier1"),
445
- category: 1,
446
- discountPercent: 0,
447
- flags: JB721TierConfigFlags({
448
- allowOwnerMint: false,
449
- useReserveBeneficiaryAsDefault: false,
450
- transfersPausable: false,
451
- useVotingUnits: false,
452
- cantBeRemoved: false,
453
- cantIncreaseDiscountPercent: false,
454
- cantBuyWithCredits: false
455
- }),
456
- splitPercent: SPLIT_PERCENT,
457
- splits: tierSplits
458
- });
459
-
460
- return REVDeploy721TiersHookConfig({
461
- baseline721HookConfiguration: REVBaseline721HookConfig({
462
- name: "Fork NFT",
463
- symbol: "FNFT",
464
- baseUri: "ipfs://",
465
- tokenUriResolver: IJB721TokenUriResolver(address(0)),
466
- contractUri: "ipfs://contract",
467
- tiersConfig: JB721InitTiersConfig({
468
- tiers: tiers, currency: uint32(uint160(JBConstants.NATIVE_TOKEN)), decimals: 18
469
- }),
470
- flags: REV721TiersHookFlags({
471
- noNewTiersWithReserves: false,
472
- noNewTiersWithVotes: false,
473
- noNewTiersWithOwnerMinting: false,
474
- preventOverspending: false
475
- })
476
- }),
477
- // forge-lint: disable-next-line(unsafe-typecast)
478
- salt: bytes32("FORK_721"),
479
- preventSplitOperatorAdjustingTiers: false,
480
- preventSplitOperatorUpdatingMetadata: false,
481
- preventSplitOperatorMinting: false,
482
- preventSplitOperatorIncreasingDiscountPercent: false
483
- });
484
- }
485
-
486
- // ───────────────────────── Deployment Helpers
487
- // ─────────────────────────
488
-
489
- /// @notice Deploy the fee project using the given tax rate.
490
- function _deployFeeProject(uint16 cashOutTaxRate) internal {
491
- (REVConfig memory feeCfg, JBTerminalConfig[] memory feeTc, REVSuckerDeploymentConfig memory feeSdc) =
492
- _buildMinimalConfig(cashOutTaxRate);
493
- // forge-lint: disable-next-line(named-struct-fields)
494
- feeCfg.description = REVDescription("Fee", "FEE", "ipfs://fee", "FEE_SALT");
495
-
496
- vm.prank(multisig());
497
- REV_DEPLOYER.deployFor({
498
- revnetId: FEE_PROJECT_ID,
499
- configuration: feeCfg,
500
- terminalConfigurations: feeTc,
501
- suckerDeploymentConfiguration: feeSdc,
502
- tiered721HookConfiguration: REVEmpty721Config.empty721Config(uint32(uint160(JBConstants.NATIVE_TOKEN))),
503
- allowedPosts: REVEmpty721Config.emptyAllowedPosts()
504
- });
505
- }
506
-
507
- /// @notice Deploy a revnet (no 721 hook) with the given cash out tax rate.
508
- function _deployRevnet(uint16 cashOutTaxRate) internal returns (uint256 revnetId) {
509
- (REVConfig memory cfg, JBTerminalConfig[] memory tc, REVSuckerDeploymentConfig memory sdc) =
510
- _buildMinimalConfig(cashOutTaxRate);
511
-
512
- (revnetId,) = REV_DEPLOYER.deployFor({
513
- revnetId: 0,
514
- configuration: cfg,
515
- terminalConfigurations: tc,
516
- suckerDeploymentConfiguration: sdc,
517
- tiered721HookConfiguration: REVEmpty721Config.empty721Config(uint32(uint160(JBConstants.NATIVE_TOKEN))),
518
- allowedPosts: REVEmpty721Config.emptyAllowedPosts()
519
- });
520
- }
521
-
522
- /// @notice Deploy a revnet with 721 tiers and the given cash out tax rate.
523
- function _deployRevnetWith721(uint16 cashOutTaxRate) internal returns (uint256 revnetId, IJB721TiersHook hook) {
524
- (REVConfig memory cfg, JBTerminalConfig[] memory tc, REVSuckerDeploymentConfig memory sdc) =
525
- _buildMinimalConfig(cashOutTaxRate);
526
- // Use a different salt to avoid CREATE2 collision with _deployRevnet's ERC-20.
527
- cfg.description.salt = "FORK_721_SALT";
528
- REVDeploy721TiersHookConfig memory hookConfig = _build721Config();
529
-
530
- (revnetId, hook) = REV_DEPLOYER.deployFor({
531
- revnetId: 0,
532
- configuration: cfg,
533
- terminalConfigurations: tc,
534
- suckerDeploymentConfiguration: sdc,
535
- tiered721HookConfiguration: hookConfig,
536
- allowedPosts: new REVCroptopAllowedPost[](0)
537
- });
538
- }
539
-
540
- // ───────────────────────── Pool Helpers
541
- // ─────────────────────────
542
-
543
- /// @notice Set up a V4 pool for the revnet's project token / native ETH pair at 1:1 price.
544
- function _setupPool(uint256 revnetId, uint256 liquidityTokenAmount) internal returns (PoolKey memory key) {
545
- address projectToken = address(jbTokens().tokenOf(revnetId));
546
- require(projectToken != address(0), "project token not deployed");
547
-
548
- // Native ETH is represented as address(0) in V4 pool keys.
549
- // address(0) is always less than any deployed token address.
550
- key = PoolKey({
551
- currency0: Currency.wrap(address(0)),
552
- currency1: Currency.wrap(projectToken),
553
- fee: REV_DEPLOYER.DEFAULT_BUYBACK_POOL_FEE(),
554
- tickSpacing: REV_DEPLOYER.DEFAULT_BUYBACK_TICK_SPACING(),
555
- hooks: IHooks(address(0))
556
- });
557
-
558
- // Pool is already initialized at fair issuance price by REVDeployer during deployment.
559
- // At high tick (~69078 for 1000 tokens/ETH), full-range liquidity needs ~32x more project tokens than ETH.
560
- // Mint 50x project tokens and use a smaller liquidity delta to stay within budget.
561
- vm.prank(address(jbController()));
562
- jbTokens().mintFor(address(liqHelper), revnetId, liquidityTokenAmount * 50);
563
- // Fund with ETH for the native currency side.
564
- vm.deal(address(liqHelper), liquidityTokenAmount);
565
-
566
- vm.startPrank(address(liqHelper));
567
- IERC20(projectToken).approve(address(poolManager), type(uint256).max);
568
- vm.stopPrank();
569
-
570
- // forge-lint: disable-next-line(unsafe-typecast)
571
- int256 liquidityDelta = int256(liquidityTokenAmount / 50);
572
- vm.prank(address(liqHelper));
573
- liqHelper.addLiquidity{value: liquidityTokenAmount}(key, TICK_LOWER, TICK_UPPER, liquidityDelta);
574
-
575
- // Mock geomean oracle at tick 69078 (~1000 tokens/ETH, matching INITIAL_ISSUANCE).
576
- _mockOracle(liquidityDelta, 69_078, uint32(REV_DEPLOYER.DEFAULT_BUYBACK_TWAP_WINDOW()));
577
- }
578
-
579
- /// @notice Mock the IGeomeanOracle at address(0) for hookless pools.
580
- function _mockOracle(int256 liquidity, int24 tick, uint32 twapWindow) internal {
581
- vm.etch(address(0), hex"00");
582
-
583
- int56[] memory tickCumulatives = new int56[](2);
584
- tickCumulatives[0] = 0;
585
- // forge-lint: disable-next-line(unsafe-typecast)
586
- tickCumulatives[1] = int56(tick) * int56(int32(twapWindow));
587
-
588
- uint136[] memory secondsPerLiquidityCumulativeX128s = new uint136[](2);
589
- secondsPerLiquidityCumulativeX128s[0] = 0;
590
- uint256 liq = uint256(liquidity > 0 ? liquidity : -liquidity);
591
- if (liq == 0) liq = 1;
592
- // forge-lint: disable-next-line(unsafe-typecast)
593
- secondsPerLiquidityCumulativeX128s[1] = uint136((uint256(twapWindow) << 128) / liq);
594
-
595
- vm.mockCall(
596
- address(0),
597
- abi.encodeWithSelector(IGeomeanOracle.observe.selector),
598
- abi.encode(tickCumulatives, secondsPerLiquidityCumulativeX128s)
599
- );
600
- }
601
-
602
- // ───────────────────────── Balance Helpers
603
- // ─────────────────────────
604
-
605
- /// @notice Get a project's balance in the terminal store.
606
- function _terminalBalance(uint256 projectId, address token) internal view returns (uint256) {
607
- return jbTerminalStore().balanceOf(address(jbMultiTerminal()), projectId, token);
608
- }
609
-
610
- // ───────────────────────── Payment Helpers
611
- // ─────────────────────────
612
-
613
- /// @notice Pay the revnet with ETH.
614
- function _payRevnet(uint256 revnetId, address payer, uint256 amount) internal returns (uint256 tokensReceived) {
615
- vm.prank(payer);
616
- tokensReceived = jbMultiTerminal().pay{value: amount}({
617
- projectId: revnetId,
618
- token: JBConstants.NATIVE_TOKEN,
619
- amount: amount,
620
- beneficiary: payer,
621
- minReturnedTokens: 0,
622
- memo: "",
623
- metadata: ""
624
- });
625
- }
626
-
627
- // ───────────────────────── Metadata Helpers
628
- // ─────────────────────────
629
-
630
- /// @notice Build payment metadata with both 721 tier selection AND buyback quote.
631
- function _buildPayMetadataWithQuote(
632
- address hookMetadataTarget,
633
- uint256 amountToSwapWith,
634
- uint256 minimumSwapAmountOut
635
- )
636
- internal
637
- view
638
- returns (bytes memory)
639
- {
640
- uint16[] memory tierIds = new uint16[](1);
641
- tierIds[0] = 1;
642
- bytes memory tierData = abi.encode(true, tierIds);
643
- bytes4 tierMetadataId = JBMetadataResolver.getId("pay", hookMetadataTarget);
644
-
645
- bytes memory quoteData = abi.encode(amountToSwapWith, minimumSwapAmountOut);
646
- bytes4 quoteMetadataId = JBMetadataResolver.getId("quote");
647
-
648
- bytes4[] memory ids = new bytes4[](2);
649
- ids[0] = tierMetadataId;
650
- ids[1] = quoteMetadataId;
651
- bytes[] memory datas = new bytes[](2);
652
- datas[0] = tierData;
653
- datas[1] = quoteData;
654
-
655
- return JBMetadataResolver.createMetadata(ids, datas);
656
- }
657
-
658
- /// @notice Build payment metadata with only 721 tier selection (no quote -> TWAP/spot fallback).
659
- function _buildPayMetadataNoQuote(address hookMetadataTarget) internal pure returns (bytes memory) {
660
- uint16[] memory tierIds = new uint16[](1);
661
- tierIds[0] = 1;
662
- bytes memory tierData = abi.encode(true, tierIds);
663
- bytes4 tierMetadataId = JBMetadataResolver.getId("pay", hookMetadataTarget);
664
-
665
- bytes4[] memory ids = new bytes4[](1);
666
- ids[0] = tierMetadataId;
667
- bytes[] memory datas = new bytes[](1);
668
- datas[0] = tierData;
669
-
670
- return JBMetadataResolver.createMetadata(ids, datas);
671
- }
672
-
673
- // ───────────────────────── ERC721 Helpers
674
- // ─────────────────────────
675
-
676
- /// @notice Get the owner of a loan NFT. REVLoans is ERC721 but typed as IREVLoans.
677
- function _loanOwnerOf(uint256 loanId) internal view returns (address) {
678
- return REVLoans(payable(address(LOANS_CONTRACT))).ownerOf(loanId);
679
- }
680
-
681
- // ───────────────────────── Loan Helpers
682
- // ─────────────────────────
683
-
684
- /// @notice Build a native token loan source.
685
- function _nativeLoanSource() internal view returns (REVLoanSource memory) {
686
- return REVLoanSource({token: JBConstants.NATIVE_TOKEN, terminal: jbMultiTerminal()});
687
- }
688
-
689
- /// @notice Grant BURN_TOKENS permission to the loans contract for a given account.
690
- function _grantBurnPermission(address account, uint256 revnetId) internal {
691
- // Permission ID 11 = BURN_TOKENS in JBPermissionIds
692
- mockExpect(
693
- address(jbPermissions()),
694
- abi.encodeCall(IJBPermissions.hasPermission, (address(LOANS_CONTRACT), account, revnetId, 11, true, true)),
695
- abi.encode(true)
696
- );
697
- }
698
-
699
- /// @notice Create a loan for the given borrower. Returns the loan ID and loan struct.
700
- function _createLoan(
701
- uint256 revnetId,
702
- address borrower,
703
- uint256 collateral,
704
- uint256 prepaidFeePercent
705
- )
706
- internal
707
- returns (uint256 loanId, REVLoan memory loan)
708
- {
709
- REVLoanSource memory source = _nativeLoanSource();
710
- uint256 borrowable =
711
- LOANS_CONTRACT.borrowableAmountFrom(revnetId, collateral, 18, uint32(uint160(JBConstants.NATIVE_TOKEN)));
712
- require(borrowable > 0, "no borrowable amount");
713
-
714
- _grantBurnPermission(borrower, revnetId);
715
-
716
- vm.prank(borrower);
717
- (loanId, loan) = LOANS_CONTRACT.borrowFrom({
718
- revnetId: revnetId,
719
- source: source,
720
- minBorrowAmount: 0,
721
- collateralCount: collateral,
722
- beneficiary: payable(borrower),
723
- prepaidFeePercent: prepaidFeePercent,
724
- holder: borrower
725
- });
726
- }
727
- }