@rev-net/core-v6 0.0.37 → 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 (107) 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 +17 -10
  11. package/src/REVOwner.sol +121 -14
  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 -107
  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 -365
  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/CrossChainBuybackRouteMismatch.t.sol +0 -184
  70. package/test/audit/HiddenSupplyCashout.t.sol +0 -61
  71. package/test/audit/LoanIdOverflowGuard.t.sol +0 -523
  72. package/test/audit/NemesisVerification.t.sol +0 -97
  73. package/test/audit/OperatorDelegation.t.sol +0 -356
  74. package/test/audit/PhantomSurplusTerminal.t.sol +0 -367
  75. package/test/audit/REVOwnerCurrencyMismatch.t.sol +0 -188
  76. package/test/audit/REVOwnerRemoteSurplusCurrencyMismatch.t.sol +0 -140
  77. package/test/audit/ReallocatePermission.t.sol +0 -363
  78. package/test/audit/RemoteLoanAccountingGap.t.sol +0 -74
  79. package/test/audit/SupportsInterfaceTest.t.sol +0 -51
  80. package/test/audit/TestFeeAllowanceLeak.t.sol +0 -197
  81. package/test/audit/TestLoansAndDeployerFixes.t.sol +0 -576
  82. package/test/fork/ForkTestBase.sol +0 -727
  83. package/test/fork/TestAutoIssuanceFork.t.sol +0 -148
  84. package/test/fork/TestCashOutFork.t.sol +0 -253
  85. package/test/fork/TestIssuanceDecayFork.t.sol +0 -158
  86. package/test/fork/TestLoanAdversarialFork.t.sol +0 -744
  87. package/test/fork/TestLoanBorrowFork.t.sol +0 -163
  88. package/test/fork/TestLoanCrossRulesetFork.t.sol +0 -308
  89. package/test/fork/TestLoanERC20Fork.t.sol +0 -459
  90. package/test/fork/TestLoanLiquidationFork.t.sol +0 -135
  91. package/test/fork/TestLoanReallocateFork.t.sol +0 -113
  92. package/test/fork/TestLoanRepayFork.t.sol +0 -188
  93. package/test/fork/TestLoanTransferFork.t.sol +0 -143
  94. package/test/fork/TestPermit2PaymentFork.t.sol +0 -300
  95. package/test/fork/TestSplitWeightFork.t.sol +0 -189
  96. package/test/helpers/MaliciousContracts.sol +0 -247
  97. package/test/helpers/REVEmpty721Config.sol +0 -45
  98. package/test/mock/MockBuybackCashOutRecorder.sol +0 -84
  99. package/test/mock/MockBuybackDataHook.sol +0 -112
  100. package/test/mock/MockBuybackDataHookMintPath.sol +0 -68
  101. package/test/mock/MockSuckerRegistry.sol +0 -17
  102. package/test/regression/TestBurnPermissionRequired.t.sol +0 -294
  103. package/test/regression/TestCashOutBuybackFeeLeak.t.sol +0 -232
  104. package/test/regression/TestCrossRevnetLiquidation.t.sol +0 -255
  105. package/test/regression/TestCumulativeLoanCounter.t.sol +0 -361
  106. package/test/regression/TestLiquidateGapHandling.t.sol +0 -394
  107. package/test/regression/TestZeroPriceFeed.t.sol +0 -422
@@ -1,527 +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
- import {MockBuybackDataHookMintPath} from "./mock/MockBuybackDataHookMintPath.sol";
13
- import {MockBuybackDataHook} from "./mock/MockBuybackDataHook.sol";
14
- // forge-lint: disable-next-line(unaliased-plain-import)
15
- import "@bananapus/core-v6/script/helpers/CoreDeploymentLib.sol";
16
- // forge-lint: disable-next-line(unaliased-plain-import)
17
- import "@bananapus/721-hook-v6/script/helpers/Hook721DeploymentLib.sol";
18
- // forge-lint: disable-next-line(unaliased-plain-import)
19
- import "@bananapus/suckers-v6/script/helpers/SuckerDeploymentLib.sol";
20
- // forge-lint: disable-next-line(unaliased-plain-import)
21
- import "@croptop/core-v6/script/helpers/CroptopDeploymentLib.sol";
22
- // forge-lint: disable-next-line(unaliased-plain-import)
23
- import "@bananapus/router-terminal-v6/script/helpers/RouterTerminalDeploymentLib.sol";
24
- import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
25
- import {JBAccountingContext} from "@bananapus/core-v6/src/structs/JBAccountingContext.sol";
26
- import {REVLoans} from "../src/REVLoans.sol";
27
- import {REVStageConfig, REVAutoIssuance} from "../src/structs/REVStageConfig.sol";
28
- import {REVDescription} from "../src/structs/REVDescription.sol";
29
- import {IREVLoans} from "./../src/interfaces/IREVLoans.sol";
30
- import {JBSuckerDeployerConfig} from "@bananapus/suckers-v6/src/structs/JBSuckerDeployerConfig.sol";
31
- import {JBSuckerRegistry} from "@bananapus/suckers-v6/src/JBSuckerRegistry.sol";
32
- import {JB721TiersHookDeployer} from "@bananapus/721-hook-v6/src/JB721TiersHookDeployer.sol";
33
- import {JB721TiersHook} from "@bananapus/721-hook-v6/src/JB721TiersHook.sol";
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";
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 {IJBRulesetDataHook} from "@bananapus/core-v6/src/interfaces/IJBRulesetDataHook.sol";
40
- import {IJBBuybackHookRegistry} from "@bananapus/buyback-hook-v6/src/interfaces/IJBBuybackHookRegistry.sol";
41
- import {IJBPayHook} from "@bananapus/core-v6/src/interfaces/IJBPayHook.sol";
42
- import {JBBeforePayRecordedContext} from "@bananapus/core-v6/src/structs/JBBeforePayRecordedContext.sol";
43
- import {JBPayHookSpecification} from "@bananapus/core-v6/src/structs/JBPayHookSpecification.sol";
44
- import {JBTokenAmount} from "@bananapus/core-v6/src/structs/JBTokenAmount.sol";
45
- import {REVEmpty721Config} from "./helpers/REVEmpty721Config.sol";
46
- import {REVOwner} from "../src/REVOwner.sol";
47
- import {IREVDeployer} from "../src/interfaces/IREVDeployer.sol";
48
- import {MockSuckerRegistry} from "./mock/MockSuckerRegistry.sol";
49
-
50
- /// @notice Tests for the split weight adjustment in REVDeployer.beforePayRecordedWith.
51
- contract TestSplitWeightAdjustment is TestBaseWorkflow {
52
- // forge-lint: disable-next-line(mixed-case-variable)
53
- bytes32 REV_DEPLOYER_SALT = "REVDeployer_SWA";
54
-
55
- // forge-lint: disable-next-line(mixed-case-variable)
56
- REVDeployer REV_DEPLOYER;
57
- // forge-lint: disable-next-line(mixed-case-variable)
58
- REVOwner REV_OWNER;
59
- // forge-lint: disable-next-line(mixed-case-variable)
60
- JB721TiersHook EXAMPLE_HOOK;
61
- // forge-lint: disable-next-line(mixed-case-variable)
62
- IJB721TiersHookDeployer HOOK_DEPLOYER;
63
- // forge-lint: disable-next-line(mixed-case-variable)
64
- IJB721TiersHookStore HOOK_STORE;
65
- // forge-lint: disable-next-line(mixed-case-variable)
66
- IJBAddressRegistry ADDRESS_REGISTRY;
67
- // forge-lint: disable-next-line(mixed-case-variable)
68
- IREVLoans LOANS_CONTRACT;
69
- // forge-lint: disable-next-line(mixed-case-variable)
70
- IJBSuckerRegistry SUCKER_REGISTRY;
71
- // forge-lint: disable-next-line(mixed-case-variable)
72
- CTPublisher PUBLISHER;
73
- // forge-lint: disable-next-line(mixed-case-variable)
74
- MockBuybackDataHookMintPath MOCK_BUYBACK;
75
-
76
- // forge-lint: disable-next-line(mixed-case-variable)
77
- uint256 FEE_PROJECT_ID;
78
-
79
- address private constant TRUSTED_FORWARDER = 0xB2b5841DBeF766d4b521221732F9B618fCf34A87;
80
- // forge-lint: disable-next-line(mixed-case-variable)
81
- address USER = makeAddr("user");
82
-
83
- function setUp() public override {
84
- super.setUp();
85
- FEE_PROJECT_ID = jbProjects().createFor(multisig());
86
- SUCKER_REGISTRY = new JBSuckerRegistry(jbDirectory(), jbPermissions(), multisig(), address(0));
87
- HOOK_STORE = new JB721TiersHookStore();
88
- EXAMPLE_HOOK = new JB721TiersHook(
89
- jbDirectory(),
90
- jbPermissions(),
91
- jbPrices(),
92
- jbRulesets(),
93
- HOOK_STORE,
94
- jbSplits(),
95
- IJB721CheckpointsDeployer(address(new JB721CheckpointsDeployer())),
96
- multisig()
97
- );
98
- ADDRESS_REGISTRY = new JBAddressRegistry();
99
- HOOK_DEPLOYER = new JB721TiersHookDeployer(EXAMPLE_HOOK, HOOK_STORE, ADDRESS_REGISTRY, multisig());
100
- PUBLISHER = new CTPublisher(jbDirectory(), jbPermissions(), FEE_PROJECT_ID, multisig());
101
- MOCK_BUYBACK = new MockBuybackDataHookMintPath();
102
- LOANS_CONTRACT = new REVLoans({
103
- controller: jbController(),
104
- suckerRegistry: IJBSuckerRegistry(address(new MockSuckerRegistry())),
105
- revId: FEE_PROJECT_ID,
106
- owner: address(this),
107
- permit2: permit2(),
108
- trustedForwarder: TRUSTED_FORWARDER
109
- });
110
- REV_OWNER = new REVOwner(
111
- IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
112
- jbDirectory(),
113
- FEE_PROJECT_ID,
114
- SUCKER_REGISTRY,
115
- address(LOANS_CONTRACT),
116
- address(0)
117
- );
118
-
119
- REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
120
- jbController(),
121
- SUCKER_REGISTRY,
122
- FEE_PROJECT_ID,
123
- HOOK_DEPLOYER,
124
- PUBLISHER,
125
- IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
126
- address(LOANS_CONTRACT),
127
- TRUSTED_FORWARDER,
128
- address(REV_OWNER)
129
- );
130
-
131
- REV_OWNER.setDeployer(REV_DEPLOYER);
132
-
133
- vm.prank(multisig());
134
- jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
135
- }
136
-
137
- function _buildMinimalConfig()
138
- internal
139
- view
140
- returns (REVConfig memory cfg, JBTerminalConfig[] memory tc, REVSuckerDeploymentConfig memory sdc)
141
- {
142
- JBAccountingContext[] memory acc = new JBAccountingContext[](1);
143
- acc[0] = JBAccountingContext({
144
- token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
145
- });
146
- tc = new JBTerminalConfig[](1);
147
- tc[0] = JBTerminalConfig({terminal: jbMultiTerminal(), accountingContextsToAccept: acc});
148
-
149
- REVStageConfig[] memory stages = new REVStageConfig[](1);
150
- JBSplit[] memory splits = new JBSplit[](1);
151
- splits[0].beneficiary = payable(multisig());
152
- splits[0].percent = 10_000;
153
- stages[0] = REVStageConfig({
154
- startsAtOrAfter: uint40(block.timestamp),
155
- autoIssuances: new REVAutoIssuance[](0),
156
- splitPercent: 0,
157
- splits: splits,
158
- initialIssuance: uint112(1000e18),
159
- issuanceCutFrequency: 0,
160
- issuanceCutPercent: 0,
161
- cashOutTaxRate: 5000,
162
- extraMetadata: 0
163
- });
164
-
165
- cfg = REVConfig({
166
- // forge-lint: disable-next-line(named-struct-fields)
167
- description: REVDescription("Test", "TST", "ipfs://test", "TEST_SALT"),
168
- baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
169
- splitOperator: multisig(),
170
- stageConfigurations: stages
171
- });
172
-
173
- sdc = REVSuckerDeploymentConfig({
174
- deployerConfigurations: new JBSuckerDeployerConfig[](0), salt: keccak256(abi.encodePacked("TEST"))
175
- });
176
- }
177
-
178
- function _deployRevnet() internal returns (uint256 revnetId) {
179
- // Deploy fee project first.
180
- (REVConfig memory feeCfg, JBTerminalConfig[] memory feeTc, REVSuckerDeploymentConfig memory feeSdc) =
181
- _buildMinimalConfig();
182
- vm.prank(multisig());
183
- REV_DEPLOYER.deployFor({
184
- revnetId: FEE_PROJECT_ID,
185
- configuration: feeCfg,
186
- terminalConfigurations: feeTc,
187
- suckerDeploymentConfiguration: feeSdc,
188
- tiered721HookConfiguration: REVEmpty721Config.empty721Config(uint32(uint160(JBConstants.NATIVE_TOKEN))),
189
- allowedPosts: REVEmpty721Config.emptyAllowedPosts()
190
- });
191
-
192
- // Deploy the revnet.
193
- (REVConfig memory cfg, JBTerminalConfig[] memory tc, REVSuckerDeploymentConfig memory sdc) =
194
- _buildMinimalConfig();
195
- // forge-lint: disable-next-line(named-struct-fields)
196
- cfg.description = REVDescription("Test2", "TS2", "ipfs://test2", "TEST_SALT_2");
197
- (revnetId,) = REV_DEPLOYER.deployFor({
198
- revnetId: 0,
199
- configuration: cfg,
200
- terminalConfigurations: tc,
201
- suckerDeploymentConfiguration: sdc,
202
- tiered721HookConfiguration: REVEmpty721Config.empty721Config(uint32(uint160(JBConstants.NATIVE_TOKEN))),
203
- allowedPosts: REVEmpty721Config.emptyAllowedPosts()
204
- });
205
- }
206
-
207
- /// @notice Empty 721 hook (no tiers) = no split adjustment, weight passes through.
208
- function test_beforePay_empty721_noAdjustment() public {
209
- uint256 revnetId = _deployRevnet();
210
-
211
- JBBeforePayRecordedContext memory context = JBBeforePayRecordedContext({
212
- terminal: address(jbMultiTerminal()),
213
- payer: USER,
214
- amount: JBTokenAmount({
215
- token: JBConstants.NATIVE_TOKEN,
216
- value: 1 ether,
217
- decimals: 18,
218
- currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
219
- }),
220
- projectId: revnetId,
221
- rulesetId: 0,
222
- beneficiary: USER,
223
- weight: 1000e18,
224
- reservedPercent: 0,
225
- metadata: ""
226
- });
227
-
228
- // 721 hook is deployed but has no tiers, buyback returns context.weight.
229
- (uint256 weight, JBPayHookSpecification[] memory specs) = REV_OWNER.beforePayRecordedWith(context);
230
-
231
- assertEq(weight, context.weight, "weight should pass through unchanged");
232
- assertEq(specs.length, 1, "should have 721 hook spec even with no tiers");
233
- }
234
-
235
- /// @notice When 721 hook returns splits, weight is adjusted proportionally.
236
- function test_beforePay_splitAdjustsWeight() public {
237
- uint256 revnetId = _deployRevnet();
238
-
239
- // Mock a 721 hook for this project.
240
- address mock721 = makeAddr("mock721");
241
- vm.etch(mock721, bytes("0x01"));
242
-
243
- // Store the mock 721 hook.
244
- // tiered721HookOf is internal, so we use vm.store.
245
- // Slot for tiered721HookOf[revnetId]: keccak256(abi.encode(revnetId, slot))
246
- // Need to find the storage slot for tiered721HookOf mapping.
247
- bytes32 slot = keccak256(abi.encode(revnetId, uint256(1))); // slot 1 for tiered721HookOf in REVOwner
248
- vm.store(address(REV_OWNER), slot, bytes32(uint256(uint160(mock721))));
249
-
250
- // Verify the store worked.
251
- assertEq(address(REV_OWNER.tiered721HookOf(revnetId)), mock721, "721 hook stored");
252
-
253
- // Mock 721 hook returning 0.3 ETH split on 1 ETH payment.
254
- JBPayHookSpecification[] memory hookSpecs = new JBPayHookSpecification[](1);
255
- hookSpecs[0] =
256
- JBPayHookSpecification({hook: IJBPayHook(mock721), noop: false, amount: 0.3 ether, metadata: bytes("")});
257
- vm.mockCall(
258
- mock721,
259
- abi.encodeWithSelector(IJBRulesetDataHook.beforePayRecordedWith.selector),
260
- abi.encode(uint256(700e18), hookSpecs)
261
- );
262
-
263
- JBBeforePayRecordedContext memory context = JBBeforePayRecordedContext({
264
- terminal: address(jbMultiTerminal()),
265
- payer: USER,
266
- amount: JBTokenAmount({
267
- token: JBConstants.NATIVE_TOKEN,
268
- value: 1 ether,
269
- decimals: 18,
270
- currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
271
- }),
272
- projectId: revnetId,
273
- rulesetId: 0,
274
- beneficiary: USER,
275
- weight: 1000e18,
276
- reservedPercent: 0,
277
- metadata: ""
278
- });
279
-
280
- (uint256 weight,) = REV_OWNER.beforePayRecordedWith(context);
281
-
282
- // Buyback returns context.weight (1000e18) since mock buyback passes through.
283
- // Weight adjusted for 0.3 ETH split on 1 ETH: 1000e18 * 0.7 = 700e18.
284
- assertEq(weight, 700e18, "weight = buybackWeight * (amount - split) / amount");
285
- }
286
-
287
- /// @notice When 721 splits take the full amount, weight is zero.
288
- function test_beforePay_fullSplit_weightZero() public {
289
- uint256 revnetId = _deployRevnet();
290
-
291
- address mock721 = makeAddr("mock721_full");
292
- vm.etch(mock721, bytes("0x01"));
293
- bytes32 slot = keccak256(abi.encode(revnetId, uint256(1))); // slot 1 for tiered721HookOf in REVOwner
294
- vm.store(address(REV_OWNER), slot, bytes32(uint256(uint160(mock721))));
295
-
296
- // Mock 721 hook returning full 1 ETH split.
297
- JBPayHookSpecification[] memory hookSpecs = new JBPayHookSpecification[](1);
298
- hookSpecs[0] =
299
- JBPayHookSpecification({hook: IJBPayHook(mock721), noop: false, amount: 1 ether, metadata: bytes("")});
300
- vm.mockCall(
301
- mock721,
302
- abi.encodeWithSelector(IJBRulesetDataHook.beforePayRecordedWith.selector),
303
- abi.encode(uint256(0), hookSpecs)
304
- );
305
-
306
- JBBeforePayRecordedContext memory context = JBBeforePayRecordedContext({
307
- terminal: address(jbMultiTerminal()),
308
- payer: USER,
309
- amount: JBTokenAmount({
310
- token: JBConstants.NATIVE_TOKEN,
311
- value: 1 ether,
312
- decimals: 18,
313
- currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
314
- }),
315
- projectId: revnetId,
316
- rulesetId: 0,
317
- beneficiary: USER,
318
- weight: 1000e18,
319
- reservedPercent: 0,
320
- metadata: ""
321
- });
322
-
323
- (uint256 weight,) = REV_OWNER.beforePayRecordedWith(context);
324
-
325
- assertEq(weight, 0, "full split = zero weight");
326
- }
327
-
328
- /// @notice 721 with splits + buyback (AMM swap path) — weight adjusted, both specs present.
329
- function test_beforePay_splitPlusBuybackAMM_correctWeight() public {
330
- // Deploy with the AMM-path buyback hook.
331
- (REVConfig memory feeCfg, JBTerminalConfig[] memory feeTc, REVSuckerDeploymentConfig memory feeSdc) =
332
- _buildMinimalConfig();
333
- vm.prank(multisig());
334
- jbProjects().approve(address(0), FEE_PROJECT_ID); // clear old approval
335
-
336
- // Deploy a new REVDeployer with the AMM buyback mock.
337
- MockBuybackDataHook ammBuyback = new MockBuybackDataHook();
338
- REVOwner ammOwner = new REVOwner(
339
- IJBBuybackHookRegistry(address(ammBuyback)),
340
- jbDirectory(),
341
- FEE_PROJECT_ID,
342
- SUCKER_REGISTRY,
343
- address(LOANS_CONTRACT),
344
- address(0)
345
- );
346
- REVDeployer ammDeployer = new REVDeployer{salt: "REVDeployer_AMM"}(
347
- jbController(),
348
- SUCKER_REGISTRY,
349
- FEE_PROJECT_ID,
350
- HOOK_DEPLOYER,
351
- PUBLISHER,
352
- IJBBuybackHookRegistry(address(ammBuyback)),
353
- address(LOANS_CONTRACT),
354
- TRUSTED_FORWARDER,
355
- address(ammOwner)
356
- );
357
-
358
- ammOwner.setDeployer(ammDeployer);
359
-
360
- vm.prank(multisig());
361
- jbProjects().approve(address(ammDeployer), FEE_PROJECT_ID);
362
-
363
- vm.prank(multisig());
364
- ammDeployer.deployFor({
365
- revnetId: FEE_PROJECT_ID,
366
- configuration: feeCfg,
367
- terminalConfigurations: feeTc,
368
- suckerDeploymentConfiguration: feeSdc,
369
- tiered721HookConfiguration: REVEmpty721Config.empty721Config(uint32(uint160(JBConstants.NATIVE_TOKEN))),
370
- allowedPosts: REVEmpty721Config.emptyAllowedPosts()
371
- });
372
-
373
- (REVConfig memory cfg, JBTerminalConfig[] memory tc, REVSuckerDeploymentConfig memory sdc) =
374
- _buildMinimalConfig();
375
- // forge-lint: disable-next-line(named-struct-fields)
376
- cfg.description = REVDescription("AMM", "AMM", "ipfs://amm", "AMM_SALT");
377
- (uint256 revnetId,) = ammDeployer.deployFor({
378
- revnetId: 0,
379
- configuration: cfg,
380
- terminalConfigurations: tc,
381
- suckerDeploymentConfiguration: sdc,
382
- tiered721HookConfiguration: REVEmpty721Config.empty721Config(uint32(uint160(JBConstants.NATIVE_TOKEN))),
383
- allowedPosts: REVEmpty721Config.emptyAllowedPosts()
384
- });
385
-
386
- // Mock a 721 hook for this project.
387
- address mock721 = makeAddr("mock721_amm");
388
- vm.etch(mock721, bytes("0x01"));
389
- bytes32 slot = keccak256(abi.encode(revnetId, uint256(1))); // slot 1 for tiered721HookOf in REVOwner
390
- vm.store(address(ammOwner), slot, bytes32(uint256(uint160(mock721))));
391
-
392
- // Mock 721 hook returning 0.4 ETH split on 1 ETH payment.
393
- JBPayHookSpecification[] memory hookSpecs = new JBPayHookSpecification[](1);
394
- hookSpecs[0] =
395
- JBPayHookSpecification({hook: IJBPayHook(mock721), noop: false, amount: 0.4 ether, metadata: bytes("")});
396
- vm.mockCall(
397
- mock721,
398
- abi.encodeWithSelector(IJBRulesetDataHook.beforePayRecordedWith.selector),
399
- abi.encode(uint256(600e18), hookSpecs)
400
- );
401
-
402
- JBBeforePayRecordedContext memory context = JBBeforePayRecordedContext({
403
- terminal: address(jbMultiTerminal()),
404
- payer: USER,
405
- amount: JBTokenAmount({
406
- token: JBConstants.NATIVE_TOKEN,
407
- value: 1 ether,
408
- decimals: 18,
409
- currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
410
- }),
411
- projectId: revnetId,
412
- rulesetId: 0,
413
- beneficiary: USER,
414
- weight: 1000e18,
415
- reservedPercent: 0,
416
- metadata: ""
417
- });
418
-
419
- (uint256 weight, JBPayHookSpecification[] memory specs) = ammOwner.beforePayRecordedWith(context);
420
-
421
- // AMM buyback returns context.weight (passes through the reduced weight from context).
422
- // The buyback mock receives a reduced context with 0.6 ETH and returns that weight.
423
- // Then weight is adjusted: weight * 0.6 = 600e18.
424
- assertEq(weight, 600e18, "weight = buybackWeight * (amount - split) / amount");
425
-
426
- // Specs: [721 hook with split, buyback spec].
427
- assertEq(specs.length, 2, "721 spec + buyback spec");
428
- assertEq(address(specs[0].hook), mock721, "first = 721 hook");
429
- assertEq(specs[0].amount, 0.4 ether, "721 split amount preserved");
430
- assertEq(address(specs[1].hook), address(ammBuyback), "second = buyback hook");
431
- }
432
-
433
- /// @notice 721 with splits + buyback (mint path, no AMM trigger) — weight adjusted, only 721 spec.
434
- function test_beforePay_splitPlusBuybackMintPath_correctWeight() public {
435
- uint256 revnetId = _deployRevnet();
436
-
437
- address mock721 = makeAddr("mock721_mint");
438
- vm.etch(mock721, bytes("0x01"));
439
- bytes32 slot = keccak256(abi.encode(revnetId, uint256(1))); // slot 1 for tiered721HookOf in REVOwner
440
- vm.store(address(REV_OWNER), slot, bytes32(uint256(uint160(mock721))));
441
-
442
- // Mock 721 hook returning 0.2 ETH split.
443
- JBPayHookSpecification[] memory hookSpecs = new JBPayHookSpecification[](1);
444
- hookSpecs[0] =
445
- JBPayHookSpecification({hook: IJBPayHook(mock721), noop: false, amount: 0.2 ether, metadata: bytes("")});
446
- vm.mockCall(
447
- mock721,
448
- abi.encodeWithSelector(IJBRulesetDataHook.beforePayRecordedWith.selector),
449
- abi.encode(uint256(800e18), hookSpecs)
450
- );
451
-
452
- JBBeforePayRecordedContext memory context = JBBeforePayRecordedContext({
453
- terminal: address(jbMultiTerminal()),
454
- payer: USER,
455
- amount: JBTokenAmount({
456
- token: JBConstants.NATIVE_TOKEN,
457
- value: 1 ether,
458
- decimals: 18,
459
- currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
460
- }),
461
- projectId: revnetId,
462
- rulesetId: 0,
463
- beneficiary: USER,
464
- weight: 1000e18,
465
- reservedPercent: 0,
466
- metadata: ""
467
- });
468
-
469
- (uint256 weight, JBPayHookSpecification[] memory specs) = REV_OWNER.beforePayRecordedWith(context);
470
-
471
- // Buyback mint path: returns context.weight (1000e18 from reduced context = 0.8 ETH context).
472
- // Actually MockBuybackDataHookMintPath returns context.weight with empty specs.
473
- // Weight adjusted: 1000e18 * 0.8 = 800e18.
474
- assertEq(weight, 800e18, "weight adjusted for split with mint-path buyback");
475
-
476
- // Only 721 spec (buyback mint path returns empty).
477
- assertEq(specs.length, 1, "only 721 spec (buyback empty)");
478
- assertEq(address(specs[0].hook), mock721, "spec = 721 hook");
479
- assertEq(specs[0].amount, 0.2 ether, "split amount");
480
- }
481
-
482
- /// @notice Splits forward actual 721 hook specs (not hardcoded amount: 0).
483
- function test_beforePay_splitForwardsActualSpecs() public {
484
- uint256 revnetId = _deployRevnet();
485
-
486
- address mock721 = makeAddr("mock721_specs");
487
- vm.etch(mock721, bytes("0x01"));
488
- bytes32 slot = keccak256(abi.encode(revnetId, uint256(1))); // slot 1 for tiered721HookOf in REVOwner
489
- vm.store(address(REV_OWNER), slot, bytes32(uint256(uint160(mock721))));
490
-
491
- // Mock 721 hook returning split amount with metadata.
492
- bytes memory splitMeta = abi.encode(uint256(42));
493
- JBPayHookSpecification[] memory hookSpecs = new JBPayHookSpecification[](1);
494
- hookSpecs[0] =
495
- JBPayHookSpecification({hook: IJBPayHook(mock721), noop: false, amount: 0.5 ether, metadata: splitMeta});
496
- vm.mockCall(
497
- mock721,
498
- abi.encodeWithSelector(IJBRulesetDataHook.beforePayRecordedWith.selector),
499
- abi.encode(uint256(500e18), hookSpecs)
500
- );
501
-
502
- JBBeforePayRecordedContext memory context = JBBeforePayRecordedContext({
503
- terminal: address(jbMultiTerminal()),
504
- payer: USER,
505
- amount: JBTokenAmount({
506
- token: JBConstants.NATIVE_TOKEN,
507
- value: 1 ether,
508
- decimals: 18,
509
- currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
510
- }),
511
- projectId: revnetId,
512
- rulesetId: 0,
513
- beneficiary: USER,
514
- weight: 1000e18,
515
- reservedPercent: 0,
516
- metadata: ""
517
- });
518
-
519
- (, JBPayHookSpecification[] memory specs) = REV_OWNER.beforePayRecordedWith(context);
520
-
521
- // Should have 721 hook spec (buyback empty).
522
- assertEq(specs.length, 1, "should have 1 spec (721 hook, buyback empty)");
523
- assertEq(address(specs[0].hook), mock721, "spec points to 721 hook");
524
- assertEq(specs[0].amount, 0.5 ether, "split amount forwarded");
525
- assertEq(specs[0].metadata, splitMeta, "split metadata forwarded");
526
- }
527
- }