@rev-net/core-v6 0.0.37 → 0.0.40

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 (112) 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 +69 -67
  9. package/src/REVHiddenTokens.sol +2 -2
  10. package/src/REVLoans.sol +26 -22
  11. package/src/REVOwner.sol +147 -29
  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/src/structs/REVAutoIssuance.sol +4 -2
  16. package/src/structs/REVConfig.sol +8 -5
  17. package/src/structs/REVDescription.sol +6 -5
  18. package/src/structs/REVLoan.sol +8 -5
  19. package/src/structs/REVStageConfig.sol +14 -16
  20. package/ADMINISTRATION.md +0 -73
  21. package/ARCHITECTURE.md +0 -116
  22. package/AUDIT_INSTRUCTIONS.md +0 -90
  23. package/RISKS.md +0 -107
  24. package/SKILLS.md +0 -46
  25. package/STYLE_GUIDE.md +0 -610
  26. package/USER_JOURNEYS.md +0 -195
  27. package/foundry.lock +0 -11
  28. package/slither-ci.config.json +0 -10
  29. package/sphinx.lock +0 -507
  30. package/test/REV.integrations.t.sol +0 -573
  31. package/test/REVAutoIssuanceFuzz.t.sol +0 -328
  32. package/test/REVDeployerRegressions.t.sol +0 -396
  33. package/test/REVInvincibility.t.sol +0 -1371
  34. package/test/REVInvincibilityHandler.sol +0 -387
  35. package/test/REVLifecycle.t.sol +0 -420
  36. package/test/REVLoans.invariants.t.sol +0 -724
  37. package/test/REVLoansAttacks.t.sol +0 -816
  38. package/test/REVLoansFeeRecovery.t.sol +0 -783
  39. package/test/REVLoansFindings.t.sol +0 -711
  40. package/test/REVLoansRegressions.t.sol +0 -364
  41. package/test/REVLoansSourceFeeRecovery.t.sol +0 -517
  42. package/test/REVLoansSourced.t.sol +0 -1839
  43. package/test/REVLoansUnSourced.t.sol +0 -409
  44. package/test/TestAuditFixVerification.t.sol +0 -675
  45. package/test/TestBurnHeldTokens.t.sol +0 -394
  46. package/test/TestCEIPattern.t.sol +0 -508
  47. package/test/TestCashOutCallerValidation.t.sol +0 -452
  48. package/test/TestConversionDocumentation.t.sol +0 -365
  49. package/test/TestCrossCurrencyReclaim.t.sol +0 -610
  50. package/test/TestCrossSourceReallocation.t.sol +0 -361
  51. package/test/TestERC2771MetaTx.t.sol +0 -585
  52. package/test/TestEmptyBuybackSpecs.t.sol +0 -300
  53. package/test/TestFlashLoanSurplus.t.sol +0 -365
  54. package/test/TestHiddenTokens.t.sol +0 -474
  55. package/test/TestHookArrayOOB.t.sol +0 -278
  56. package/test/TestLiquidationBehavior.t.sol +0 -398
  57. package/test/TestLoanSourceRotation.t.sol +0 -553
  58. package/test/TestLoansCashOutDelay.t.sol +0 -493
  59. package/test/TestLongTailEconomics.t.sol +0 -677
  60. package/test/TestLowFindings.t.sol +0 -677
  61. package/test/TestMixedFixes.t.sol +0 -593
  62. package/test/TestPermit2Signatures.t.sol +0 -683
  63. package/test/TestReallocationSandwich.t.sol +0 -412
  64. package/test/TestRevnetRegressions.t.sol +0 -350
  65. package/test/TestSplitWeightAdjustment.t.sol +0 -527
  66. package/test/TestSplitWeightE2E.t.sol +0 -605
  67. package/test/TestSplitWeightFork.t.sol +0 -855
  68. package/test/TestStageTransitionBorrowable.t.sol +0 -301
  69. package/test/TestSwapTerminalPermission.t.sol +0 -262
  70. package/test/TestTerminalEncodingInHash.t.sol +0 -326
  71. package/test/TestUint112Overflow.t.sol +0 -311
  72. package/test/TestZeroAmountLoanGuard.t.sol +0 -378
  73. package/test/TestZeroRepayment.t.sol +0 -354
  74. package/test/audit/CrossChainBuybackRouteMismatch.t.sol +0 -184
  75. package/test/audit/HiddenSupplyCashout.t.sol +0 -61
  76. package/test/audit/LoanIdOverflowGuard.t.sol +0 -523
  77. package/test/audit/NemesisVerification.t.sol +0 -97
  78. package/test/audit/OperatorDelegation.t.sol +0 -356
  79. package/test/audit/PhantomSurplusTerminal.t.sol +0 -367
  80. package/test/audit/REVOwnerCurrencyMismatch.t.sol +0 -188
  81. package/test/audit/REVOwnerRemoteSurplusCurrencyMismatch.t.sol +0 -140
  82. package/test/audit/ReallocatePermission.t.sol +0 -363
  83. package/test/audit/RemoteLoanAccountingGap.t.sol +0 -74
  84. package/test/audit/SupportsInterfaceTest.t.sol +0 -51
  85. package/test/audit/TestFeeAllowanceLeak.t.sol +0 -197
  86. package/test/audit/TestLoansAndDeployerFixes.t.sol +0 -576
  87. package/test/fork/ForkTestBase.sol +0 -727
  88. package/test/fork/TestAutoIssuanceFork.t.sol +0 -148
  89. package/test/fork/TestCashOutFork.t.sol +0 -253
  90. package/test/fork/TestIssuanceDecayFork.t.sol +0 -158
  91. package/test/fork/TestLoanAdversarialFork.t.sol +0 -744
  92. package/test/fork/TestLoanBorrowFork.t.sol +0 -163
  93. package/test/fork/TestLoanCrossRulesetFork.t.sol +0 -308
  94. package/test/fork/TestLoanERC20Fork.t.sol +0 -459
  95. package/test/fork/TestLoanLiquidationFork.t.sol +0 -135
  96. package/test/fork/TestLoanReallocateFork.t.sol +0 -113
  97. package/test/fork/TestLoanRepayFork.t.sol +0 -188
  98. package/test/fork/TestLoanTransferFork.t.sol +0 -143
  99. package/test/fork/TestPermit2PaymentFork.t.sol +0 -300
  100. package/test/fork/TestSplitWeightFork.t.sol +0 -189
  101. package/test/helpers/MaliciousContracts.sol +0 -247
  102. package/test/helpers/REVEmpty721Config.sol +0 -45
  103. package/test/mock/MockBuybackCashOutRecorder.sol +0 -84
  104. package/test/mock/MockBuybackDataHook.sol +0 -112
  105. package/test/mock/MockBuybackDataHookMintPath.sol +0 -68
  106. package/test/mock/MockSuckerRegistry.sol +0 -17
  107. package/test/regression/TestBurnPermissionRequired.t.sol +0 -294
  108. package/test/regression/TestCashOutBuybackFeeLeak.t.sol +0 -232
  109. package/test/regression/TestCrossRevnetLiquidation.t.sol +0 -255
  110. package/test/regression/TestCumulativeLoanCounter.t.sol +0 -361
  111. package/test/regression/TestLiquidateGapHandling.t.sol +0 -394
  112. 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
- }