@rev-net/core-v6 0.0.13 → 0.0.15

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 (64) hide show
  1. package/CHANGE_LOG.md +8 -3
  2. package/SKILLS.md +1 -1
  3. package/foundry.toml +7 -0
  4. package/package.json +8 -9
  5. package/src/REVDeployer.sol +34 -12
  6. package/src/REVLoans.sol +1 -5
  7. package/test/REVDeployerRegressions.t.sol +6 -3
  8. package/test/REVInvincibility.t.sol +6 -18
  9. package/test/REVLoansAttacks.t.sol +19 -11
  10. package/test/REVLoansFeeRecovery.t.sol +19 -11
  11. package/test/REVLoansFindings.t.sol +19 -11
  12. package/test/REVLoansRegressions.t.sol +19 -11
  13. package/test/REVLoansSourced.t.sol +0 -8
  14. package/test/TestCashOutCallerValidation.t.sol +74 -0
  15. package/test/TestLowFindings.t.sol +3 -1
  16. package/test/TestMixedFixes.t.sol +6 -4
  17. package/test/TestSplitWeightAdjustment.t.sol +10 -5
  18. package/test/fork/TestAutoIssuanceFork.t.sol +148 -0
  19. package/test/fork/TestCashOutFork.t.sol +22 -21
  20. package/test/fork/TestIssuanceDecayFork.t.sol +158 -0
  21. package/test/fork/TestLoanERC20Fork.t.sol +463 -0
  22. package/test/fork/TestLoanRepayFork.t.sol +2 -2
  23. package/test/fork/TestPermit2PaymentFork.t.sol +299 -0
  24. package/test/helpers/MaliciousContracts.sol +36 -22
  25. package/test/mock/MockBuybackDataHook.sol +50 -6
  26. package/deployments/revnet-core-v5/arbitrum/REVDeployer.json +0 -2821
  27. package/deployments/revnet-core-v5/arbitrum/REVLoans.json +0 -2260
  28. package/deployments/revnet-core-v5/arbitrum_sepolia/REVDeployer.json +0 -2821
  29. package/deployments/revnet-core-v5/arbitrum_sepolia/REVLoans.json +0 -2260
  30. package/deployments/revnet-core-v5/base/REVDeployer.json +0 -2825
  31. package/deployments/revnet-core-v5/base/REVLoans.json +0 -2264
  32. package/deployments/revnet-core-v5/base_sepolia/REVDeployer.json +0 -2825
  33. package/deployments/revnet-core-v5/base_sepolia/REVLoans.json +0 -2264
  34. package/deployments/revnet-core-v5/ethereum/REVDeployer.json +0 -2825
  35. package/deployments/revnet-core-v5/ethereum/REVLoans.json +0 -2264
  36. package/deployments/revnet-core-v5/optimism/REVDeployer.json +0 -2821
  37. package/deployments/revnet-core-v5/optimism/REVLoans.json +0 -2260
  38. package/deployments/revnet-core-v5/optimism_sepolia/REVDeployer.json +0 -2825
  39. package/deployments/revnet-core-v5/optimism_sepolia/REVLoans.json +0 -2264
  40. package/deployments/revnet-core-v5/sepolia/REVDeployer.json +0 -2825
  41. package/deployments/revnet-core-v5/sepolia/REVLoans.json +0 -2264
  42. package/docs/book.css +0 -13
  43. package/docs/book.toml +0 -13
  44. package/docs/solidity.min.js +0 -74
  45. package/docs/src/README.md +0 -185
  46. package/docs/src/SUMMARY.md +0 -18
  47. package/docs/src/src/README.md +0 -7
  48. package/docs/src/src/REVDeployer.sol/contract.REVDeployer.md +0 -999
  49. package/docs/src/src/REVLoans.sol/contract.REVLoans.md +0 -1108
  50. package/docs/src/src/interfaces/IREVDeployer.sol/interface.IREVDeployer.md +0 -525
  51. package/docs/src/src/interfaces/IREVLoans.sol/interface.IREVLoans.md +0 -598
  52. package/docs/src/src/interfaces/README.md +0 -5
  53. package/docs/src/src/structs/README.md +0 -12
  54. package/docs/src/src/structs/REVAutoIssuance.sol/struct.REVAutoIssuance.md +0 -19
  55. package/docs/src/src/structs/REVBuybackHookConfig.sol/struct.REVBuybackHookConfig.md +0 -19
  56. package/docs/src/src/structs/REVBuybackPoolConfig.sol/struct.REVBuybackPoolConfig.md +0 -21
  57. package/docs/src/src/structs/REVConfig.sol/struct.REVConfig.md +0 -23
  58. package/docs/src/src/structs/REVCroptopAllowedPost.sol/struct.REVCroptopAllowedPost.md +0 -32
  59. package/docs/src/src/structs/REVDeploy721TiersHookConfig.sol/struct.REVDeploy721TiersHookConfig.md +0 -34
  60. package/docs/src/src/structs/REVDescription.sol/struct.REVDescription.md +0 -23
  61. package/docs/src/src/structs/REVLoan.sol/struct.REVLoan.md +0 -28
  62. package/docs/src/src/structs/REVLoanSource.sol/struct.REVLoanSource.md +0 -16
  63. package/docs/src/src/structs/REVStageConfig.sol/struct.REVStageConfig.md +0 -44
  64. package/docs/src/src/structs/REVSuckerDeploymentConfig.sol/struct.REVSuckerDeploymentConfig.md +0 -16
@@ -0,0 +1,299 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity 0.8.26;
3
+
4
+ // forge-lint: disable-next-line(unaliased-plain-import)
5
+ import "./ForkTestBase.sol";
6
+ import {REVEmpty721Config} from "../helpers/REVEmpty721Config.sol";
7
+ import {MockERC20} from "@bananapus/core-v6/test/mock/MockERC20.sol";
8
+ import {MockPriceFeed} from "@bananapus/core-v6/test/mock/MockPriceFeed.sol";
9
+ import {JBSingleAllowance} from "@bananapus/core-v6/src/structs/JBSingleAllowance.sol";
10
+ import {IAllowanceTransfer} from "@uniswap/permit2/src/interfaces/IAllowanceTransfer.sol";
11
+ import {IPermit2} from "@uniswap/permit2/src/interfaces/IPermit2.sol";
12
+
13
+ /// @notice Fork tests for Permit2-based ERC-20 payments to a revnet terminal.
14
+ ///
15
+ /// Verifies that JBMultiTerminal._acceptFundsFor() correctly processes Permit2 metadata,
16
+ /// including valid signatures, expired signatures, and replayed nonces.
17
+ ///
18
+ /// Run with: FOUNDRY_PROFILE=fork forge test --match-contract TestPermit2PaymentFork -vvv
19
+ contract TestPermit2PaymentFork is ForkTestBase {
20
+ using JBMetadataResolver for bytes;
21
+
22
+ // Permit2 signing key.
23
+ uint256 constant PRIV_KEY = 0xBEEF1234;
24
+ address signer;
25
+
26
+ MockERC20 testToken;
27
+ uint256 revnetId;
28
+
29
+ // Permit2 EIP-712 typehashes (copied from nana-core-v6 test patterns).
30
+ bytes32 public constant _PERMIT_DETAILS_TYPEHASH =
31
+ keccak256("PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)");
32
+
33
+ bytes32 public constant _PERMIT_SINGLE_TYPEHASH = keccak256(
34
+ "PermitSingle(PermitDetails details,address spender,uint256 sigDeadline)PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)"
35
+ );
36
+
37
+ // Permit2 domain separator (fetched at runtime from the deployed Permit2 contract).
38
+ // forge-lint: disable-next-line(mixed-case-variable)
39
+ bytes32 DOMAIN_SEPARATOR;
40
+
41
+ function setUp() public override {
42
+ super.setUp();
43
+
44
+ signer = vm.addr(PRIV_KEY);
45
+ vm.deal(signer, 10 ether);
46
+
47
+ // Deploy fee project.
48
+ _deployFeeProject(5000);
49
+
50
+ // Deploy a mock ERC-20 for testing.
51
+ testToken = new MockERC20("Test Token", "TEST");
52
+
53
+ // Add a price feed: testToken → NATIVE_TOKEN so the buyback hook can quote.
54
+ // 1 testToken (6 dec) = 0.0005 ETH (i.e. 1 ETH = 2000 testTokens).
55
+ MockPriceFeed priceFeed = new MockPriceFeed(5e14, 18);
56
+ vm.prank(multisig());
57
+ jbPrices()
58
+ .addPriceFeedFor(
59
+ 0, uint32(uint160(address(testToken))), uint32(uint160(JBConstants.NATIVE_TOKEN)), priceFeed
60
+ );
61
+
62
+ // Deploy a revnet that accepts both native token and the test ERC-20.
63
+ revnetId = _deployRevnetWithERC20(5000);
64
+
65
+ // Fetch the Permit2 domain separator from the on-chain Permit2 contract.
66
+ DOMAIN_SEPARATOR = permit2().DOMAIN_SEPARATOR();
67
+
68
+ // Mint tokens to the signer and approve Permit2 (NOT the terminal directly).
69
+ testToken.mint(signer, 1000e6);
70
+ vm.prank(signer);
71
+ IERC20(address(testToken)).approve(address(permit2()), type(uint256).max);
72
+ }
73
+
74
+ /// @notice Deploy a revnet that accepts both native token and the test ERC-20.
75
+ function _deployRevnetWithERC20(uint16 cashOutTaxRate) internal returns (uint256 id) {
76
+ JBAccountingContext[] memory acc = new JBAccountingContext[](2);
77
+ acc[0] = JBAccountingContext({
78
+ token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
79
+ });
80
+ acc[1] = JBAccountingContext({
81
+ token: address(testToken), decimals: 6, currency: uint32(uint160(address(testToken)))
82
+ });
83
+
84
+ JBTerminalConfig[] memory tc = new JBTerminalConfig[](1);
85
+ tc[0] = JBTerminalConfig({terminal: jbMultiTerminal(), accountingContextsToAccept: acc});
86
+
87
+ REVStageConfig[] memory stages = new REVStageConfig[](1);
88
+ JBSplit[] memory splits = new JBSplit[](1);
89
+ splits[0].beneficiary = payable(multisig());
90
+ splits[0].percent = 10_000;
91
+ stages[0] = REVStageConfig({
92
+ startsAtOrAfter: uint40(block.timestamp),
93
+ autoIssuances: new REVAutoIssuance[](0),
94
+ splitPercent: 0,
95
+ splits: splits,
96
+ initialIssuance: INITIAL_ISSUANCE,
97
+ issuanceCutFrequency: 0,
98
+ issuanceCutPercent: 0,
99
+ cashOutTaxRate: cashOutTaxRate,
100
+ extraMetadata: 0
101
+ });
102
+
103
+ REVConfig memory cfg = REVConfig({
104
+ description: REVDescription("Permit2 Test", "P2T", "ipfs://p2t", "PERMIT2_SALT"),
105
+ baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
106
+ splitOperator: multisig(),
107
+ stageConfigurations: stages
108
+ });
109
+
110
+ REVSuckerDeploymentConfig memory sdc = REVSuckerDeploymentConfig({
111
+ deployerConfigurations: new JBSuckerDeployerConfig[](0), salt: keccak256(abi.encodePacked("PERMIT2_TEST"))
112
+ });
113
+
114
+ (id,) = REV_DEPLOYER.deployFor({
115
+ revnetId: 0,
116
+ configuration: cfg,
117
+ terminalConfigurations: tc,
118
+ suckerDeploymentConfiguration: sdc,
119
+ tiered721HookConfiguration: REVEmpty721Config.empty721Config(uint32(uint160(JBConstants.NATIVE_TOKEN))),
120
+ allowedPosts: REVEmpty721Config.emptyAllowedPosts()
121
+ });
122
+ }
123
+
124
+ /// @notice Build Permit2 metadata for a payment.
125
+ function _buildPermit2Metadata(
126
+ uint160 amount,
127
+ uint48 expiration,
128
+ uint48 nonce,
129
+ uint256 sigDeadline
130
+ )
131
+ internal
132
+ view
133
+ returns (bytes memory metadata)
134
+ {
135
+ // Build the permit struct for signing.
136
+ IAllowanceTransfer.PermitSingle memory permitSingle = IAllowanceTransfer.PermitSingle({
137
+ details: IAllowanceTransfer.PermitDetails({
138
+ token: address(testToken), amount: amount, expiration: expiration, nonce: nonce
139
+ }),
140
+ spender: address(jbMultiTerminal()),
141
+ sigDeadline: sigDeadline
142
+ });
143
+
144
+ // Sign it.
145
+ bytes memory sig = _getPermitSignature(permitSingle, PRIV_KEY, DOMAIN_SEPARATOR);
146
+
147
+ // Pack into JBSingleAllowance.
148
+ JBSingleAllowance memory allowance = JBSingleAllowance({
149
+ sigDeadline: sigDeadline, amount: amount, expiration: expiration, nonce: nonce, signature: sig
150
+ });
151
+
152
+ // Encode as metadata with the "permit2" key targeting the terminal, plus a zero "quote"
153
+ // so the buyback hook skips the TWAP pool lookup (no pool exists for testToken).
154
+ bytes4[] memory ids = new bytes4[](2);
155
+ bytes[] memory datas = new bytes[](2);
156
+ ids[0] = JBMetadataResolver.getId("permit2", address(jbMultiTerminal()));
157
+ datas[0] = abi.encode(allowance);
158
+ ids[1] = JBMetadataResolver.getId("quote", address(BUYBACK_HOOK));
159
+ datas[1] = abi.encode(uint256(0), uint256(0));
160
+ metadata = JBMetadataResolver.createMetadata(ids, datas);
161
+ }
162
+
163
+ /// @notice Pay a revnet with ERC-20 via Permit2 with zero direct terminal approval -- succeeds.
164
+ function testFork_Permit2PaymentSucceeds() public {
165
+ uint160 payAmount = 100e6; // 100 tokens (6 decimals)
166
+ uint48 expiration = uint48(block.timestamp + 1 days);
167
+ uint256 sigDeadline = block.timestamp + 1 days;
168
+ uint48 nonce = 0;
169
+
170
+ bytes memory metadata = _buildPermit2Metadata(payAmount, expiration, nonce, sigDeadline);
171
+
172
+ // Verify the signer has NOT approved the terminal directly.
173
+ assertEq(
174
+ IERC20(address(testToken)).allowance(signer, address(jbMultiTerminal())),
175
+ 0,
176
+ "signer should have zero direct terminal approval"
177
+ );
178
+
179
+ // Pay using Permit2.
180
+ vm.prank(signer);
181
+ uint256 tokensReceived = jbMultiTerminal()
182
+ .pay({
183
+ projectId: revnetId,
184
+ token: address(testToken),
185
+ amount: payAmount,
186
+ beneficiary: signer,
187
+ minReturnedTokens: 0,
188
+ memo: "permit2 payment",
189
+ metadata: metadata
190
+ });
191
+
192
+ // Verify payment succeeded.
193
+ assertGt(tokensReceived, 0, "should receive project tokens from Permit2 payment");
194
+
195
+ // Verify tokens were transferred from signer to terminal.
196
+ assertEq(
197
+ testToken.balanceOf(address(jbMultiTerminal())), payAmount, "terminal should hold the paid ERC-20 tokens"
198
+ );
199
+
200
+ // Verify signer's token balance decreased.
201
+ assertEq(testToken.balanceOf(signer), 1000e6 - payAmount, "signer's token balance should decrease");
202
+ }
203
+
204
+ /// @notice Payment with an expired Permit2 signature deadline should revert.
205
+ function testFork_Permit2ExpiredSignatureReverts() public {
206
+ uint160 payAmount = 100e6;
207
+ uint48 expiration = uint48(block.timestamp + 1 days);
208
+ uint48 nonce = 0;
209
+
210
+ // Set sigDeadline in the past.
211
+ uint256 expiredDeadline = block.timestamp - 1;
212
+
213
+ bytes memory metadata = _buildPermit2Metadata(payAmount, expiration, nonce, expiredDeadline);
214
+
215
+ // The Permit2 contract should reject the expired signature.
216
+ // The terminal catches permit failures in try-catch and falls back to transferFrom,
217
+ // which will also fail due to zero approval. So we expect a generic revert.
218
+ vm.prank(signer);
219
+ vm.expectRevert();
220
+ jbMultiTerminal()
221
+ .pay({
222
+ projectId: revnetId,
223
+ token: address(testToken),
224
+ amount: payAmount,
225
+ beneficiary: signer,
226
+ minReturnedTokens: 0,
227
+ memo: "expired permit2",
228
+ metadata: metadata
229
+ });
230
+ }
231
+
232
+ /// @notice Replaying the same Permit2 nonce should revert on the second payment.
233
+ function testFork_Permit2ReplayReverts() public {
234
+ uint160 payAmount = 50e6;
235
+ uint48 expiration = uint48(block.timestamp + 1 days);
236
+ uint256 sigDeadline = block.timestamp + 1 days;
237
+ uint48 nonce = 0;
238
+
239
+ bytes memory metadata = _buildPermit2Metadata(payAmount, expiration, nonce, sigDeadline);
240
+
241
+ // First payment succeeds.
242
+ vm.prank(signer);
243
+ jbMultiTerminal()
244
+ .pay({
245
+ projectId: revnetId,
246
+ token: address(testToken),
247
+ amount: payAmount,
248
+ beneficiary: signer,
249
+ minReturnedTokens: 0,
250
+ memo: "first permit2 payment",
251
+ metadata: metadata
252
+ });
253
+
254
+ // Second payment with the same nonce.
255
+ // The permit call will fail (nonce already used), terminal catches it via try-catch,
256
+ // then falls back to transferFrom which fails because there is no direct approval.
257
+ vm.prank(signer);
258
+ vm.expectRevert();
259
+ jbMultiTerminal()
260
+ .pay({
261
+ projectId: revnetId,
262
+ token: address(testToken),
263
+ amount: payAmount,
264
+ beneficiary: signer,
265
+ minReturnedTokens: 0,
266
+ memo: "replayed permit2 payment",
267
+ metadata: metadata
268
+ });
269
+ }
270
+
271
+ // ───────────────────────── Permit2 Signature Helpers
272
+ // ─────────────────────────
273
+
274
+ /// @notice Sign a PermitSingle struct and return the concatenated signature.
275
+ function _getPermitSignature(
276
+ IAllowanceTransfer.PermitSingle memory permitSingle,
277
+ uint256 privateKey,
278
+ bytes32 domainSeparator
279
+ )
280
+ internal
281
+ pure
282
+ returns (bytes memory sig)
283
+ {
284
+ bytes32 permitHash = keccak256(abi.encode(_PERMIT_DETAILS_TYPEHASH, permitSingle.details));
285
+
286
+ bytes32 msgHash = keccak256(
287
+ abi.encodePacked(
288
+ "\x19\x01",
289
+ domainSeparator,
290
+ keccak256(
291
+ abi.encode(_PERMIT_SINGLE_TYPEHASH, permitHash, permitSingle.spender, permitSingle.sigDeadline)
292
+ )
293
+ )
294
+ );
295
+
296
+ (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, msgHash);
297
+ return bytes.concat(r, s, bytes1(v));
298
+ }
299
+ }
@@ -7,6 +7,8 @@ import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
7
7
  import {JBAccountingContext} from "@bananapus/core-v6/src/structs/JBAccountingContext.sol";
8
8
  import {IJBTerminal} from "@bananapus/core-v6/src/interfaces/IJBTerminal.sol";
9
9
  import {IJBPayoutTerminal} from "@bananapus/core-v6/src/interfaces/IJBPayoutTerminal.sol";
10
+ import {JBRuleset} from "@bananapus/core-v6/src/structs/JBRuleset.sol";
11
+ import {JBPayHookSpecification} from "@bananapus/core-v6/src/structs/JBPayHookSpecification.sol";
10
12
  import {IREVLoans} from "../../src/interfaces/IREVLoans.sol";
11
13
  import {REVLoanSource} from "../../src/structs/REVLoanSource.sol";
12
14
 
@@ -70,17 +72,7 @@ contract BrokenFeeTerminal is ERC165, IJBPayoutTerminal {
70
72
 
71
73
  function addAccountingContextsFor(uint256, JBAccountingContext[] calldata) external override {}
72
74
 
73
- function currentSurplusOf(
74
- uint256,
75
- JBAccountingContext[] memory,
76
- uint256,
77
- uint256
78
- )
79
- external
80
- pure
81
- override
82
- returns (uint256)
83
- {
75
+ function currentSurplusOf(uint256, address[] calldata, uint256, uint256) external pure override returns (uint256) {
84
76
  return 0;
85
77
  }
86
78
 
@@ -110,6 +102,22 @@ contract BrokenFeeTerminal is ERC165, IJBPayoutTerminal {
110
102
  return 0;
111
103
  }
112
104
 
105
+ function previewPayFor(
106
+ uint256,
107
+ address,
108
+ uint256,
109
+ address,
110
+ bytes calldata
111
+ )
112
+ external
113
+ pure
114
+ override
115
+ returns (JBRuleset memory, uint256, uint256, JBPayHookSpecification[] memory)
116
+ {
117
+ JBRuleset memory ruleset;
118
+ return (ruleset, 0, 0, new JBPayHookSpecification[](0));
119
+ }
120
+
113
121
  function supportsInterface(bytes4 interfaceId) public view override(ERC165, IERC165) returns (bool) {
114
122
  return interfaceId == type(IJBTerminal).interfaceId || interfaceId == type(IJBPayoutTerminal).interfaceId
115
123
  || super.supportsInterface(interfaceId);
@@ -184,17 +192,7 @@ contract SurplusInflator is ERC165, IJBPayoutTerminal {
184
192
 
185
193
  function addAccountingContextsFor(uint256, JBAccountingContext[] calldata) external override {}
186
194
 
187
- function currentSurplusOf(
188
- uint256,
189
- JBAccountingContext[] memory,
190
- uint256,
191
- uint256
192
- )
193
- external
194
- pure
195
- override
196
- returns (uint256)
197
- {
195
+ function currentSurplusOf(uint256, address[] calldata, uint256, uint256) external pure override returns (uint256) {
198
196
  return 0;
199
197
  }
200
198
 
@@ -224,6 +222,22 @@ contract SurplusInflator is ERC165, IJBPayoutTerminal {
224
222
  return 0;
225
223
  }
226
224
 
225
+ function previewPayFor(
226
+ uint256,
227
+ address,
228
+ uint256,
229
+ address,
230
+ bytes calldata
231
+ )
232
+ external
233
+ pure
234
+ override
235
+ returns (JBRuleset memory, uint256, uint256, JBPayHookSpecification[] memory)
236
+ {
237
+ JBRuleset memory ruleset;
238
+ return (ruleset, 0, 0, new JBPayHookSpecification[](0));
239
+ }
240
+
227
241
  function supportsInterface(bytes4 interfaceId) public view override(ERC165, IERC165) returns (bool) {
228
242
  return interfaceId == type(IJBTerminal).interfaceId || interfaceId == type(IJBPayoutTerminal).interfaceId
229
243
  || super.supportsInterface(interfaceId);
@@ -2,6 +2,7 @@
2
2
  pragma solidity 0.8.26;
3
3
 
4
4
  import {IJBRulesetDataHook} from "@bananapus/core-v6/src/interfaces/IJBRulesetDataHook.sol";
5
+ import {IJBCashOutHook} from "@bananapus/core-v6/src/interfaces/IJBCashOutHook.sol";
5
6
  import {IJBPayHook} from "@bananapus/core-v6/src/interfaces/IJBPayHook.sol";
6
7
  // forge-lint: disable-next-line(unused-import)
7
8
  import {IJBBuybackHook} from "@bananapus/buyback-hook-v6/src/interfaces/IJBBuybackHook.sol";
@@ -16,6 +17,13 @@ import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
16
17
 
17
18
  /// @notice A minimal mock buyback data hook for tests. Returns the default weight and a no-op pay hook specification.
18
19
  contract MockBuybackDataHook is IJBRulesetDataHook, IJBPayHook {
20
+ bool public shouldReturnCashOutHookSpec;
21
+ uint256 public cashOutCountToReturn;
22
+ bytes public cashOutHookMetadata;
23
+ uint256 public cashOutHookAmount;
24
+ uint256 public cashOutTaxRateToReturn;
25
+ uint256 public totalSupplyToReturn;
26
+
19
27
  function beforePayRecordedWith(JBBeforePayRecordedContext calldata context)
20
28
  external
21
29
  view
@@ -24,12 +32,13 @@ contract MockBuybackDataHook is IJBRulesetDataHook, IJBPayHook {
24
32
  {
25
33
  weight = context.weight;
26
34
  hookSpecifications = new JBPayHookSpecification[](1);
27
- hookSpecifications[0] = JBPayHookSpecification({hook: IJBPayHook(address(this)), amount: 0, metadata: ""});
35
+ hookSpecifications[0] =
36
+ JBPayHookSpecification({hook: IJBPayHook(address(this)), noop: false, amount: 0, metadata: ""});
28
37
  }
29
38
 
30
39
  function beforeCashOutRecordedWith(JBBeforeCashOutRecordedContext calldata context)
31
40
  external
32
- pure
41
+ view
33
42
  override
34
43
  returns (
35
44
  uint256 cashOutTaxRate,
@@ -38,10 +47,45 @@ contract MockBuybackDataHook is IJBRulesetDataHook, IJBPayHook {
38
47
  JBCashOutHookSpecification[] memory hookSpecifications
39
48
  )
40
49
  {
41
- cashOutTaxRate = context.cashOutTaxRate;
42
- cashOutCount = context.cashOutCount;
43
- totalSupply = context.totalSupply;
44
- hookSpecifications = new JBCashOutHookSpecification[](0);
50
+ cashOutTaxRate = cashOutTaxRateToReturn == 0 ? context.cashOutTaxRate : cashOutTaxRateToReturn;
51
+ cashOutCount = cashOutCountToReturn == 0 ? context.cashOutCount : cashOutCountToReturn;
52
+ totalSupply = totalSupplyToReturn == 0 ? context.totalSupply : totalSupplyToReturn;
53
+
54
+ if (!shouldReturnCashOutHookSpec) {
55
+ hookSpecifications = new JBCashOutHookSpecification[](0);
56
+ return (cashOutTaxRate, cashOutCount, totalSupply, hookSpecifications);
57
+ }
58
+
59
+ hookSpecifications = new JBCashOutHookSpecification[](1);
60
+ hookSpecifications[0] = JBCashOutHookSpecification({
61
+ hook: IJBCashOutHook(address(this)), noop: false, amount: cashOutHookAmount, metadata: cashOutHookMetadata
62
+ });
63
+ }
64
+
65
+ function configureCashOutResult(
66
+ uint256 cashOutTaxRate,
67
+ uint256 cashOutCount,
68
+ uint256 totalSupply,
69
+ uint256 hookAmount,
70
+ bytes calldata hookMetadata
71
+ )
72
+ external
73
+ {
74
+ shouldReturnCashOutHookSpec = true;
75
+ cashOutTaxRateToReturn = cashOutTaxRate;
76
+ cashOutCountToReturn = cashOutCount;
77
+ totalSupplyToReturn = totalSupply;
78
+ cashOutHookAmount = hookAmount;
79
+ cashOutHookMetadata = hookMetadata;
80
+ }
81
+
82
+ function resetCashOutResult() external {
83
+ shouldReturnCashOutHookSpec = false;
84
+ cashOutTaxRateToReturn = 0;
85
+ cashOutCountToReturn = 0;
86
+ totalSupplyToReturn = 0;
87
+ cashOutHookAmount = 0;
88
+ cashOutHookMetadata = "";
45
89
  }
46
90
 
47
91
  function hasMintPermissionFor(uint256, JBRuleset calldata, address) external pure override returns (bool) {