@bananapus/omnichain-deployers-v6 0.0.30 → 0.0.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bananapus/omnichain-deployers-v6",
3
- "version": "0.0.30",
3
+ "version": "0.0.31",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -453,6 +453,7 @@ contract JBOmnichainDeployer is
453
453
  // Build a mutable copy of the context with the latest values (possibly updated by the 721 hook).
454
454
  JBBeforeCashOutRecordedContext memory hookContext = context;
455
455
  hookContext.cashOutTaxRate = cashOutTaxRate;
456
+ hookContext.cashOutCount = cashOutCount;
456
457
  hookContext.totalSupply = totalSupply;
457
458
  hookContext.surplus.value = effectiveSurplusValue;
458
459
 
@@ -0,0 +1,232 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity 0.8.28;
3
+
4
+ import {Test} from "forge-std/Test.sol";
5
+
6
+ import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
7
+ import {IJBPermissions} from "@bananapus/core-v6/src/interfaces/IJBPermissions.sol";
8
+ import {IJBProjects} from "@bananapus/core-v6/src/interfaces/IJBProjects.sol";
9
+ import {IJBRulesetDataHook} from "@bananapus/core-v6/src/interfaces/IJBRulesetDataHook.sol";
10
+ import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
11
+ import {JBBeforeCashOutRecordedContext} from "@bananapus/core-v6/src/structs/JBBeforeCashOutRecordedContext.sol";
12
+ import {JBBeforePayRecordedContext} from "@bananapus/core-v6/src/structs/JBBeforePayRecordedContext.sol";
13
+ import {JBCashOutHookSpecification} from "@bananapus/core-v6/src/structs/JBCashOutHookSpecification.sol";
14
+ import {JBPayHookSpecification} from "@bananapus/core-v6/src/structs/JBPayHookSpecification.sol";
15
+ import {JBRuleset} from "@bananapus/core-v6/src/structs/JBRuleset.sol";
16
+ import {JBTokenAmount} from "@bananapus/core-v6/src/structs/JBTokenAmount.sol";
17
+ import {IJB721TiersHookDeployer} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHookDeployer.sol";
18
+ import {IJBSuckerRegistry} from "@bananapus/suckers-v6/src/interfaces/IJBSuckerRegistry.sol";
19
+
20
+ import {JBOmnichainDeployer} from "../../src/JBOmnichainDeployer.sol";
21
+
22
+ /// @notice Regression test: cashOutCount from the 721 hook must be propagated
23
+ /// to the extra data hook's context. Before fix, the extra hook receives the stale
24
+ /// context.cashOutCount instead of the 721-adjusted value.
25
+ contract CashOutCountPropagationTest is Test {
26
+ uint256 internal constant PROJECT_ID = 1;
27
+ uint256 internal constant RULESET_ID = 123;
28
+
29
+ // The 721 hook will change cashOutCount from ORIGINAL to ADJUSTED.
30
+ uint256 internal constant ORIGINAL_CASH_OUT_COUNT = 50 ether;
31
+ uint256 internal constant ADJUSTED_CASH_OUT_COUNT = 7 ether;
32
+ uint256 internal constant NFT_TOTAL_SUPPLY = 21 ether;
33
+ uint256 internal constant LOCAL_SURPLUS = 10 ether;
34
+
35
+ address internal holder = makeAddr("holder");
36
+ address internal permissions = makeAddr("permissions");
37
+ address internal projects = makeAddr("projects");
38
+ address internal hookDeployer = makeAddr("hookDeployer");
39
+ address internal suckerRegistry = makeAddr("suckerRegistry");
40
+ address internal directory = makeAddr("directory");
41
+
42
+ JBOmnichainDeployer internal deployer;
43
+ Mock721Hook internal nftHook;
44
+ address internal extraHookAddr;
45
+
46
+ function setUp() public {
47
+ vm.mockCall(permissions, abi.encodeWithSelector(IJBPermissions.setPermissionsFor.selector), abi.encode());
48
+
49
+ deployer = new JBOmnichainDeployer(
50
+ IJBSuckerRegistry(suckerRegistry),
51
+ IJB721TiersHookDeployer(hookDeployer),
52
+ IJBPermissions(permissions),
53
+ IJBProjects(projects),
54
+ IJBDirectory(directory),
55
+ address(0)
56
+ );
57
+
58
+ nftHook = new Mock721Hook(ADJUSTED_CASH_OUT_COUNT, NFT_TOTAL_SUPPLY);
59
+ extraHookAddr = makeAddr("extraHook");
60
+
61
+ _storeTiered721Hook(address(nftHook), true);
62
+ _storeExtraDataHook(extraHookAddr, true);
63
+ _mockSuckerRegistry();
64
+ _mockExtraHook();
65
+ }
66
+
67
+ /// @notice The extra data hook should receive the 721-adjusted cashOutCount,
68
+ /// not the original context.cashOutCount.
69
+ function test_extraHookReceivesAdjustedCashOutCount() public {
70
+ // Build the expected context that the extra hook should receive.
71
+ // The deployer copies the original context and patches cashOutTaxRate, totalSupply, surplus.value.
72
+ // With the fix, it should also patch cashOutCount.
73
+ // NOTE: We build expectedContext separately (not via pointer alias) to avoid Solidity memory aliasing.
74
+ JBBeforeCashOutRecordedContext memory expectedContext = JBBeforeCashOutRecordedContext({
75
+ terminal: address(0x1234),
76
+ holder: holder,
77
+ projectId: PROJECT_ID,
78
+ rulesetId: RULESET_ID,
79
+ cashOutCount: ADJUSTED_CASH_OUT_COUNT, // THIS IS THE FIX — must be the 721-adjusted value
80
+ totalSupply: NFT_TOTAL_SUPPLY, // from 721 hook
81
+ surplus: JBTokenAmount({
82
+ token: JBConstants.NATIVE_TOKEN,
83
+ decimals: 18,
84
+ currency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
85
+ value: LOCAL_SURPLUS // 721 hook passes through surplus value
86
+ }),
87
+ useTotalSurplus: false,
88
+ cashOutTaxRate: 5000, // 721 hook passes through
89
+ beneficiaryIsFeeless: false,
90
+ metadata: ""
91
+ });
92
+
93
+ // Build the input context with the ORIGINAL cashOutCount.
94
+ JBBeforeCashOutRecordedContext memory context = _cashOutContext();
95
+
96
+ // Sanity: the original context has a different cashOutCount than what the 721 hook returns.
97
+ assertEq(context.cashOutCount, ORIGINAL_CASH_OUT_COUNT);
98
+ assertTrue(ORIGINAL_CASH_OUT_COUNT != ADJUSTED_CASH_OUT_COUNT);
99
+
100
+ // Expect the extra hook to be called with the adjusted context (including adjusted cashOutCount).
101
+ vm.expectCall(extraHookAddr, abi.encodeCall(IJBRulesetDataHook.beforeCashOutRecordedWith, (expectedContext)));
102
+
103
+ deployer.beforeCashOutRecordedWith(context);
104
+ }
105
+
106
+ function _cashOutContext() internal view returns (JBBeforeCashOutRecordedContext memory context) {
107
+ context = JBBeforeCashOutRecordedContext({
108
+ terminal: address(0x1234),
109
+ holder: holder,
110
+ projectId: PROJECT_ID,
111
+ rulesetId: RULESET_ID,
112
+ cashOutCount: ORIGINAL_CASH_OUT_COUNT,
113
+ totalSupply: 100 ether,
114
+ surplus: JBTokenAmount({
115
+ token: JBConstants.NATIVE_TOKEN,
116
+ decimals: 18,
117
+ currency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
118
+ value: LOCAL_SURPLUS
119
+ }),
120
+ useTotalSurplus: false,
121
+ cashOutTaxRate: 5000,
122
+ beneficiaryIsFeeless: false,
123
+ metadata: ""
124
+ });
125
+ }
126
+
127
+ function _mockSuckerRegistry() internal {
128
+ vm.mockCall(
129
+ suckerRegistry,
130
+ abi.encodeWithSelector(IJBSuckerRegistry.isSuckerOf.selector, PROJECT_ID, holder),
131
+ abi.encode(false)
132
+ );
133
+ vm.mockCall(
134
+ suckerRegistry,
135
+ abi.encodeWithSelector(IJBSuckerRegistry.remoteTotalSupplyOf.selector, PROJECT_ID),
136
+ abi.encode(0)
137
+ );
138
+ vm.mockCall(
139
+ suckerRegistry,
140
+ abi.encodeWithSelector(
141
+ IJBSuckerRegistry.remoteSurplusOf.selector,
142
+ PROJECT_ID,
143
+ uint256(18),
144
+ uint256(uint32(uint160(JBConstants.NATIVE_TOKEN)))
145
+ ),
146
+ abi.encode(0)
147
+ );
148
+ }
149
+
150
+ /// @dev Mock the extra hook to return passthrough values.
151
+ function _mockExtraHook() internal {
152
+ // We use a broad mock: any call to beforeCashOutRecordedWith returns passthrough values.
153
+ // The vm.expectCall will check the exact arguments.
154
+ JBCashOutHookSpecification[] memory specs = new JBCashOutHookSpecification[](0);
155
+ vm.mockCall(
156
+ extraHookAddr,
157
+ abi.encodeWithSelector(IJBRulesetDataHook.beforeCashOutRecordedWith.selector),
158
+ abi.encode(uint256(5000), uint256(0), uint256(0), uint256(0), specs)
159
+ );
160
+ }
161
+
162
+ /// @dev Store the 721 hook at _tiered721HookOf[PROJECT_ID][RULESET_ID] (slot 1).
163
+ function _storeTiered721Hook(address hook, bool useDataHookForCashOut) internal {
164
+ bytes32 outerSlot = keccak256(abi.encode(PROJECT_ID, uint256(1)));
165
+ bytes32 valueSlot = keccak256(abi.encode(RULESET_ID, outerSlot));
166
+
167
+ uint256 packed = uint256(uint160(hook));
168
+ if (useDataHookForCashOut) packed |= uint256(1) << 160;
169
+
170
+ vm.store(address(deployer), valueSlot, bytes32(packed));
171
+ }
172
+
173
+ /// @dev Store the extra data hook at _extraDataHookOf[PROJECT_ID][RULESET_ID] (slot 0).
174
+ /// JBDeployerHookConfig has: IJBRulesetDataHook dataHook (20 bytes), bool useDataHookForPay (1 byte),
175
+ /// bool useDataHookForCashOut (1 byte). All pack into a single slot.
176
+ function _storeExtraDataHook(address hook, bool useDataHookForCashOut) internal {
177
+ bytes32 outerSlot = keccak256(abi.encode(PROJECT_ID, uint256(0)));
178
+ bytes32 valueSlot = keccak256(abi.encode(RULESET_ID, outerSlot));
179
+
180
+ uint256 packed = uint256(uint160(hook));
181
+ // useDataHookForPay is at bit 160, useDataHookForCashOut is at bit 168.
182
+ if (useDataHookForCashOut) packed |= uint256(1) << 168;
183
+
184
+ vm.store(address(deployer), valueSlot, bytes32(packed));
185
+ }
186
+ }
187
+
188
+ /// @notice Mock 721 hook that returns adjusted cashOutCount and totalSupply values.
189
+ contract Mock721Hook is IJBRulesetDataHook {
190
+ uint256 internal immutable _cashOutCount;
191
+ uint256 internal immutable _totalSupply;
192
+
193
+ constructor(uint256 cashOutCount_, uint256 totalSupply_) {
194
+ _cashOutCount = cashOutCount_;
195
+ _totalSupply = totalSupply_;
196
+ }
197
+
198
+ function beforeCashOutRecordedWith(JBBeforeCashOutRecordedContext calldata context)
199
+ external
200
+ view
201
+ returns (
202
+ uint256 cashOutTaxRate,
203
+ uint256 effectiveCashOutCount,
204
+ uint256 effectiveTotalSupply,
205
+ uint256 effectiveSurplusValue,
206
+ JBCashOutHookSpecification[] memory hookSpecifications
207
+ )
208
+ {
209
+ cashOutTaxRate = context.cashOutTaxRate;
210
+ effectiveCashOutCount = _cashOutCount;
211
+ effectiveTotalSupply = _totalSupply;
212
+ effectiveSurplusValue = context.surplus.value;
213
+ hookSpecifications = new JBCashOutHookSpecification[](0);
214
+ }
215
+
216
+ function beforePayRecordedWith(JBBeforePayRecordedContext calldata context)
217
+ external
218
+ pure
219
+ returns (uint256 weight, JBPayHookSpecification[] memory hookSpecifications)
220
+ {
221
+ weight = context.weight;
222
+ hookSpecifications = new JBPayHookSpecification[](0);
223
+ }
224
+
225
+ function hasMintPermissionFor(uint256, JBRuleset memory, address) external pure returns (bool) {
226
+ return false;
227
+ }
228
+
229
+ function supportsInterface(bytes4) external pure returns (bool) {
230
+ return true;
231
+ }
232
+ }