@bananapus/suckers-v6 0.0.48 → 0.0.49

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.
@@ -77,13 +77,15 @@ interface IJBSuckerRegistry {
77
77
  function suckersOf(uint256 projectId) external view returns (address[] memory);
78
78
 
79
79
  /// @notice The cumulative total supply across all remote peer chains for a project.
80
- /// @dev Sums `peerChainTotalSupply` from each active sucker. Silently skips suckers that revert.
80
+ /// @dev Dedupes same-peer active suckers by freshest snapshot, then sums peer-chain values. Silently skips suckers
81
+ /// that revert.
81
82
  /// @param projectId The ID of the project.
82
83
  /// @return totalSupply The combined peer chain total supply.
83
84
  function remoteTotalSupplyOf(uint256 projectId) external view returns (uint256 totalSupply);
84
85
 
85
86
  /// @notice The cumulative balance across all remote peer chains for a project, denominated in a given currency.
86
- /// @dev Sums `peerChainBalanceOf` from each active sucker. Silently skips suckers that revert.
87
+ /// @dev Dedupes same-peer active suckers by freshest snapshot, then sums peer-chain values. Silently skips suckers
88
+ /// that revert.
87
89
  /// @param projectId The ID of the project.
88
90
  /// @param decimals The decimal precision for the returned value.
89
91
  /// @param currency The currency to normalize to.
@@ -98,7 +100,8 @@ interface IJBSuckerRegistry {
98
100
  returns (uint256 balance);
99
101
 
100
102
  /// @notice The cumulative surplus across all remote peer chains for a project, denominated in a given currency.
101
- /// @dev Sums `peerChainSurplusOf` from each active sucker. Silently skips suckers that revert.
103
+ /// @dev Dedupes same-peer active suckers by freshest snapshot, then sums peer-chain values. Silently skips suckers
104
+ /// that revert.
102
105
  /// @param projectId The ID of the project.
103
106
  /// @param decimals The decimal precision for the returned value.
104
107
  /// @param currency The currency to normalize to.
@@ -133,6 +133,70 @@ library CCIPHelper {
133
133
  /// @notice The wrapped native token address for Tempo mod chain.
134
134
  address public constant TEMPO_MOD_WETH = 0xbb2D3310E4232085d432A2e04b2Ac09c46F634E4;
135
135
 
136
+ /// @notice Returns the LINK token address for a given chain ID.
137
+ /// @param chainId The EVM chain ID to look up.
138
+ /// @return link The LINK token address.
139
+ function linkOfChain(uint256 chainId) public pure returns (address link) {
140
+ if (chainId == ETH_ID) {
141
+ return ETH_LINK;
142
+ } else if (chainId == OP_ID) {
143
+ return OP_LINK;
144
+ } else if (chainId == ARB_ID) {
145
+ return ARB_LINK;
146
+ } else if (chainId == BASE_ID) {
147
+ return BASE_LINK;
148
+ } else if (chainId == ETH_SEP_ID) {
149
+ return ETH_SEP_LINK;
150
+ } else if (chainId == OP_SEP_ID) {
151
+ return OP_SEP_LINK;
152
+ } else if (chainId == ARB_SEP_ID) {
153
+ return ARB_SEP_LINK;
154
+ } else if (chainId == BASE_SEP_ID) {
155
+ return BASE_SEP_LINK;
156
+ } else if (chainId == TEMPO_ID) {
157
+ return TEMPO_LINK;
158
+ } else if (chainId == TEMPO_MOD_ID) {
159
+ return TEMPO_MOD_LINK;
160
+ } else {
161
+ revert CCIPHelper_UnsupportedChain({chainId: chainId});
162
+ }
163
+ }
164
+
165
+ /// @notice Returns the wrapped native token address for a given chain ID.
166
+ /// @param chainId The EVM chain ID to look up.
167
+ /// @return weth The wrapped native token address.
168
+ function wethOfChain(uint256 chainId) public pure returns (address weth) {
169
+ if (chainId == ETH_ID) {
170
+ return ETH_WETH;
171
+ } else if (chainId == OP_ID) {
172
+ return OP_WETH;
173
+ } else if (chainId == ARB_ID) {
174
+ return ARB_WETH;
175
+ } else if (chainId == POLY_ID) {
176
+ return POLY_WETH;
177
+ } else if (chainId == AVA_ID) {
178
+ return AVA_WETH;
179
+ } else if (chainId == BNB_ID) {
180
+ return BNB_WETH;
181
+ } else if (chainId == BASE_ID) {
182
+ return BASE_WETH;
183
+ } else if (chainId == ETH_SEP_ID) {
184
+ return ETH_SEP_WETH;
185
+ } else if (chainId == ARB_SEP_ID) {
186
+ return ARB_SEP_WETH;
187
+ } else if (chainId == OP_SEP_ID) {
188
+ return OP_SEP_WETH;
189
+ } else if (chainId == BASE_SEP_ID) {
190
+ return BASE_SEP_WETH;
191
+ } else if (chainId == TEMPO_ID) {
192
+ return TEMPO_WETH;
193
+ } else if (chainId == TEMPO_MOD_ID) {
194
+ return TEMPO_MOD_WETH;
195
+ } else {
196
+ revert CCIPHelper_UnsupportedChain({chainId: chainId});
197
+ }
198
+ }
199
+
136
200
  /// @notice Returns the CCIP router address for a given chain ID.
137
201
  /// @param chainId The EVM chain ID to look up.
138
202
  /// @return router The CCIP router address.
@@ -202,68 +266,4 @@ library CCIPHelper {
202
266
  revert CCIPHelper_UnsupportedChain({chainId: chainId});
203
267
  }
204
268
  }
205
-
206
- /// @notice Returns the wrapped native token address for a given chain ID.
207
- /// @param chainId The EVM chain ID to look up.
208
- /// @return weth The wrapped native token address.
209
- function wethOfChain(uint256 chainId) public pure returns (address weth) {
210
- if (chainId == ETH_ID) {
211
- return ETH_WETH;
212
- } else if (chainId == OP_ID) {
213
- return OP_WETH;
214
- } else if (chainId == ARB_ID) {
215
- return ARB_WETH;
216
- } else if (chainId == POLY_ID) {
217
- return POLY_WETH;
218
- } else if (chainId == AVA_ID) {
219
- return AVA_WETH;
220
- } else if (chainId == BNB_ID) {
221
- return BNB_WETH;
222
- } else if (chainId == BASE_ID) {
223
- return BASE_WETH;
224
- } else if (chainId == ETH_SEP_ID) {
225
- return ETH_SEP_WETH;
226
- } else if (chainId == ARB_SEP_ID) {
227
- return ARB_SEP_WETH;
228
- } else if (chainId == OP_SEP_ID) {
229
- return OP_SEP_WETH;
230
- } else if (chainId == BASE_SEP_ID) {
231
- return BASE_SEP_WETH;
232
- } else if (chainId == TEMPO_ID) {
233
- return TEMPO_WETH;
234
- } else if (chainId == TEMPO_MOD_ID) {
235
- return TEMPO_MOD_WETH;
236
- } else {
237
- revert CCIPHelper_UnsupportedChain({chainId: chainId});
238
- }
239
- }
240
-
241
- /// @notice Returns the LINK token address for a given chain ID.
242
- /// @param chainId The EVM chain ID to look up.
243
- /// @return link The LINK token address.
244
- function linkOfChain(uint256 chainId) public pure returns (address link) {
245
- if (chainId == ETH_ID) {
246
- return ETH_LINK;
247
- } else if (chainId == OP_ID) {
248
- return OP_LINK;
249
- } else if (chainId == ARB_ID) {
250
- return ARB_LINK;
251
- } else if (chainId == BASE_ID) {
252
- return BASE_LINK;
253
- } else if (chainId == ETH_SEP_ID) {
254
- return ETH_SEP_LINK;
255
- } else if (chainId == OP_SEP_ID) {
256
- return OP_SEP_LINK;
257
- } else if (chainId == ARB_SEP_ID) {
258
- return ARB_SEP_LINK;
259
- } else if (chainId == BASE_SEP_ID) {
260
- return BASE_SEP_LINK;
261
- } else if (chainId == TEMPO_ID) {
262
- return TEMPO_LINK;
263
- } else if (chainId == TEMPO_MOD_ID) {
264
- return TEMPO_MOD_LINK;
265
- } else {
266
- revert CCIPHelper_UnsupportedChain({chainId: chainId});
267
- }
268
- }
269
269
  }
@@ -133,10 +133,15 @@ library JBCCIPLib {
133
133
  SafeERC20.forceApprove({token: IERC20(feeToken), spender: router, value: totalApproval});
134
134
 
135
135
  ICCIPRouter(router).ccipSend({destinationChainSelector: chainSel, message: message});
136
+
137
+ _clearTokenAmountApprovals({router: router, tokenAmounts: tokenAmounts});
138
+ SafeERC20.forceApprove({token: IERC20(feeToken), spender: router, value: 0});
136
139
  } else {
137
140
  // Native ETH fee path.
138
141
  ICCIPRouter(router).ccipSend{value: fees}({destinationChainSelector: chainSel, message: message});
139
142
 
143
+ _clearTokenAmountApprovals({router: router, tokenAmounts: tokenAmounts});
144
+
140
145
  // Calculate the excess transport payment to refund.
141
146
  refundAmount = transportPayment - fees;
142
147
 
@@ -189,4 +194,17 @@ library JBCCIPLib {
189
194
  // ABI-decode the type and payload from the raw data.
190
195
  (messageType, payload) = abi.decode(data, (uint8, bytes));
191
196
  }
197
+
198
+ //*********************************************************************//
199
+ // ----------------------- private helpers --------------------------- //
200
+ //*********************************************************************//
201
+
202
+ /// @notice Clear token approvals granted to the CCIP router for the current message.
203
+ /// @param router The CCIP router whose allowances should be revoked.
204
+ /// @param tokenAmounts The bridged token amounts approved for this message.
205
+ function _clearTokenAmountApprovals(address router, Client.EVMTokenAmount[] memory tokenAmounts) private {
206
+ for (uint256 i; i < tokenAmounts.length; i++) {
207
+ SafeERC20.forceApprove({token: IERC20(tokenAmounts[i].token), spender: router, value: 0});
208
+ }
209
+ }
192
210
  }
@@ -38,6 +38,56 @@ library JBSuckerLib {
38
38
  /// @notice The ETH decimal precision used for cross-chain peer snapshots.
39
39
  uint8 internal constant _ETH_DECIMALS = 18;
40
40
 
41
+ //*********************************************************************//
42
+ // ---------------------- external transactions ---------------------- //
43
+ //*********************************************************************//
44
+
45
+ /// @notice Build the cross-chain snapshot message (total supply, surplus, balance).
46
+ /// @dev Extracted from `JBSucker._buildSnapshotAndSend` to reduce child contract bytecode.
47
+ /// Called via DELEGATECALL. Includes ETH aggregate computation inline (cannot call own external fns).
48
+ /// @param directory The JB directory to look up controllers and terminals.
49
+ /// @param prices The price oracle to use for non-ETH terminal-token balances.
50
+ /// @param projectId The project ID.
51
+ /// @param remoteToken The remote token bytes32 address.
52
+ /// @param amount The amount of terminal tokens to bridge.
53
+ /// @param nonce The outbox nonce for this send.
54
+ /// @param root The merkle root of the outbox tree.
55
+ /// @param messageVersion The message format version.
56
+ /// @param sourceTimestamp The monotonic source freshness key for this snapshot.
57
+ /// @return message The constructed JBMessageRoot.
58
+ function buildSnapshotMessage(
59
+ IJBDirectory directory,
60
+ IJBPrices prices,
61
+ uint256 projectId,
62
+ bytes32 remoteToken,
63
+ uint256 amount,
64
+ uint64 nonce,
65
+ bytes32 root,
66
+ uint8 messageVersion,
67
+ uint256 sourceTimestamp
68
+ )
69
+ external
70
+ view
71
+ returns (JBMessageRoot memory message)
72
+ {
73
+ (uint256 localTotalSupply, uint256 ethSurplus, uint256 ethBalance) =
74
+ _snapshotAccountsOf({directory: directory, prices: prices, projectId: projectId});
75
+
76
+ // Construct the cross-chain message with the snapshot data.
77
+ message = JBMessageRoot({
78
+ version: messageVersion,
79
+ token: remoteToken,
80
+ amount: amount,
81
+ remoteRoot: JBInboxTreeRoot({nonce: nonce, root: root}),
82
+ sourceTotalSupply: localTotalSupply,
83
+ sourceCurrency: JBCurrencyIds.ETH,
84
+ sourceDecimals: _ETH_DECIMALS,
85
+ sourceSurplus: ethSurplus,
86
+ sourceBalance: ethBalance,
87
+ sourceTimestamp: sourceTimestamp
88
+ });
89
+ }
90
+
41
91
  //*********************************************************************//
42
92
  // ------------------------- external views -------------------------- //
43
93
  //*********************************************************************//
@@ -61,8 +111,114 @@ library JBSuckerLib {
61
111
  return _buildETHAggregateInternal({directory: directory, projectId: projectId, prices: prices});
62
112
  }
63
113
 
114
+ /// @notice Compute a branch root from a leaf, branch, and index. Wraps MerkleLib.branchRoot so its
115
+ /// ~170 lines of unrolled assembly live in the library's bytecode instead of each sucker's.
116
+ /// @param item The leaf hash.
117
+ /// @param branch The 32-element merkle proof branch.
118
+ /// @param index The leaf index.
119
+ /// @return The computed merkle root.
120
+ function computeBranchRoot(bytes32 item, bytes32[32] memory branch, uint256 index) external pure returns (bytes32) {
121
+ // Delegate to MerkleLib's unrolled assembly implementation.
122
+ return MerkleLib.branchRoot({item: item, branch: branch, index: index});
123
+ }
124
+
125
+ /// @notice Compute the merkle tree root from branch and count. Loop-based replacement for the unrolled
126
+ /// MerkleLib.root() — saves ~3KB per sucker when called via DELEGATECALL instead of inlining.
127
+ /// @param branch The 32-element branch array (caller copies from storage to memory).
128
+ /// @param count The number of leaves inserted into the tree.
129
+ /// @return current The merkle root.
130
+ function computeTreeRoot(bytes32[32] memory branch, uint256 count) external pure returns (bytes32 current) {
131
+ // An empty tree has a well-known root.
132
+ if (count == 0) return MerkleLib.Z_32;
133
+
134
+ // solhint-disable-next-line no-inline-assembly
135
+ assembly ("memory-safe") {
136
+ // Build zero hashes on-the-fly: Z[0] = 0, Z[i+1] = keccak256(Z[i], Z[i]).
137
+ let zPtr := mload(0x40)
138
+ mstore(0x40, add(zPtr, 0x420)) // 33 slots × 32 bytes
139
+ mstore(zPtr, 0) // Z[0] = bytes32(0)
140
+ for { let j := 0 } lt(j, 32) { j := add(j, 1) } {
141
+ let prev := mload(add(zPtr, mul(j, 0x20)))
142
+ mstore(0x00, prev)
143
+ mstore(0x20, prev)
144
+ mstore(add(zPtr, mul(add(j, 1), 0x20)), keccak256(0x00, 0x40))
145
+ }
146
+
147
+ // Walk bits of `count` from LSB to MSB.
148
+ // First set bit → initialize current = keccak256(branch[i], Z[i]).
149
+ // Each subsequent level → merge branch[i] or Z[i] with current.
150
+ let started := 0
151
+ for { let i := 0 } lt(i, 32) { i := add(i, 1) } {
152
+ switch started
153
+ case 0 {
154
+ if and(count, shl(i, 1)) {
155
+ mstore(0x00, mload(add(branch, mul(i, 0x20))))
156
+ mstore(0x20, mload(add(zPtr, mul(i, 0x20))))
157
+ current := keccak256(0x00, 0x40)
158
+ started := 1
159
+ }
160
+ }
161
+ default {
162
+ switch and(count, shl(i, 1))
163
+ case 0 {
164
+ mstore(0x00, current)
165
+ mstore(0x20, mload(add(zPtr, mul(i, 0x20))))
166
+ }
167
+ default {
168
+ mstore(0x00, mload(add(branch, mul(i, 0x20))))
169
+ mstore(0x20, current)
170
+ }
171
+ current := keccak256(0x00, 0x40)
172
+ }
173
+ }
174
+ }
175
+ }
176
+
177
+ /// @notice Convert a peer chain snapshot value to the requested currency and decimal precision.
178
+ /// @param prices The price oracle to use when currency conversion is needed.
179
+ /// @param projectId The project ID.
180
+ /// @param source The peer chain snapshot containing value, currency, and decimals.
181
+ /// @param decimals The target decimal precision.
182
+ /// @param currency The target currency.
183
+ /// @return converted The converted value.
184
+ function convertPeerValue(
185
+ IJBPrices prices,
186
+ uint256 projectId,
187
+ JBDenominatedAmount memory source,
188
+ uint256 decimals,
189
+ uint256 currency
190
+ )
191
+ external
192
+ view
193
+ returns (uint256 converted)
194
+ {
195
+ // Nothing to convert if the source value is zero.
196
+ if (source.value == 0) return 0;
197
+
198
+ // If the source currency matches the target, just adjust decimals.
199
+ // forge-lint: disable-next-line(unsafe-typecast)
200
+ if (source.currency == uint32(currency)) {
201
+ converted = JBFixedPointNumber.adjustDecimals({
202
+ value: source.value, decimals: source.decimals, targetDecimals: decimals
203
+ });
204
+ } else {
205
+ // Convert using the price oracle.
206
+ try prices.pricePerUnitOf({
207
+ projectId: projectId,
208
+ pricingCurrency: source.currency,
209
+ // forge-lint: disable-next-line(unsafe-typecast)
210
+ unitCurrency: uint32(currency),
211
+ decimals: source.decimals
212
+ }) returns (
213
+ uint256 price
214
+ ) {
215
+ converted = mulDiv({x: source.value, y: 10 ** decimals, denominator: price});
216
+ } catch {}
217
+ }
218
+ }
219
+
64
220
  //*********************************************************************//
65
- // ----------------------- internal helpers -------------------------- //
221
+ // ------------------------- internal views -------------------------- //
66
222
  //*********************************************************************//
67
223
 
68
224
  /// @dev Shared implementation for ETH aggregate. Internal so it can be called from other
@@ -151,49 +307,6 @@ library JBSuckerLib {
151
307
  }
152
308
  }
153
309
 
154
- /// @notice Convert a peer chain snapshot value to the requested currency and decimal precision.
155
- /// @param prices The price oracle to use when currency conversion is needed.
156
- /// @param projectId The project ID.
157
- /// @param source The peer chain snapshot containing value, currency, and decimals.
158
- /// @param decimals The target decimal precision.
159
- /// @param currency The target currency.
160
- /// @return converted The converted value.
161
- function convertPeerValue(
162
- IJBPrices prices,
163
- uint256 projectId,
164
- JBDenominatedAmount memory source,
165
- uint256 decimals,
166
- uint256 currency
167
- )
168
- external
169
- view
170
- returns (uint256 converted)
171
- {
172
- // Nothing to convert if the source value is zero.
173
- if (source.value == 0) return 0;
174
-
175
- // If the source currency matches the target, just adjust decimals.
176
- // forge-lint: disable-next-line(unsafe-typecast)
177
- if (source.currency == uint32(currency)) {
178
- converted = JBFixedPointNumber.adjustDecimals({
179
- value: source.value, decimals: source.decimals, targetDecimals: decimals
180
- });
181
- } else {
182
- // Convert using the price oracle.
183
- try prices.pricePerUnitOf({
184
- projectId: projectId,
185
- pricingCurrency: source.currency,
186
- // forge-lint: disable-next-line(unsafe-typecast)
187
- unitCurrency: uint32(currency),
188
- decimals: source.decimals
189
- }) returns (
190
- uint256 price
191
- ) {
192
- converted = mulDiv({x: source.value, y: 10 ** decimals, denominator: price});
193
- } catch {}
194
- }
195
- }
196
-
197
310
  /// @notice Return the project's controller if it exists and advertises the controller interface.
198
311
  /// @param directory The JB directory to look up the controller.
199
312
  /// @param projectId The project ID.
@@ -208,56 +321,6 @@ library JBSuckerLib {
208
321
  } catch {}
209
322
  }
210
323
 
211
- //*********************************************************************//
212
- // -------------------- external state-changing ---------------------- //
213
- //*********************************************************************//
214
-
215
- /// @notice Build the cross-chain snapshot message (total supply, surplus, balance).
216
- /// @dev Extracted from `JBSucker._buildSnapshotAndSend` to reduce child contract bytecode.
217
- /// Called via DELEGATECALL. Includes ETH aggregate computation inline (cannot call own external fns).
218
- /// @param directory The JB directory to look up controllers and terminals.
219
- /// @param prices The price oracle to use for non-ETH terminal-token balances.
220
- /// @param projectId The project ID.
221
- /// @param remoteToken The remote token bytes32 address.
222
- /// @param amount The amount of terminal tokens to bridge.
223
- /// @param nonce The outbox nonce for this send.
224
- /// @param root The merkle root of the outbox tree.
225
- /// @param messageVersion The message format version.
226
- /// @param sourceTimestamp The monotonic source freshness key for this snapshot.
227
- /// @return message The constructed JBMessageRoot.
228
- function buildSnapshotMessage(
229
- IJBDirectory directory,
230
- IJBPrices prices,
231
- uint256 projectId,
232
- bytes32 remoteToken,
233
- uint256 amount,
234
- uint64 nonce,
235
- bytes32 root,
236
- uint8 messageVersion,
237
- uint256 sourceTimestamp
238
- )
239
- external
240
- view
241
- returns (JBMessageRoot memory message)
242
- {
243
- (uint256 localTotalSupply, uint256 ethSurplus, uint256 ethBalance) =
244
- _snapshotAccountsOf({directory: directory, prices: prices, projectId: projectId});
245
-
246
- // Construct the cross-chain message with the snapshot data.
247
- message = JBMessageRoot({
248
- version: messageVersion,
249
- token: remoteToken,
250
- amount: amount,
251
- remoteRoot: JBInboxTreeRoot({nonce: nonce, root: root}),
252
- sourceTotalSupply: localTotalSupply,
253
- sourceCurrency: JBCurrencyIds.ETH,
254
- sourceDecimals: _ETH_DECIMALS,
255
- sourceSurplus: ethSurplus,
256
- sourceBalance: ethBalance,
257
- sourceTimestamp: sourceTimestamp
258
- });
259
- }
260
-
261
324
  /// @notice Optional project-specific adjusted accounts to add to peer-chain snapshots.
262
325
  /// @dev Reads the current ruleset's data hook and asks it for extra supply/surplus. Non-supporting hooks,
263
326
  /// broken hooks, and short return data are ignored so a project's baseline snapshot remains usable.
@@ -337,71 +400,4 @@ library JBSuckerLib {
337
400
  ethBalance += additionalBalance;
338
401
  }
339
402
  }
340
-
341
- //*********************************************************************//
342
- // -------------------- merkle tree helpers -------------------------- //
343
- //*********************************************************************//
344
-
345
- /// @notice Compute the merkle tree root from branch and count. Loop-based replacement for the unrolled
346
- /// MerkleLib.root() — saves ~3KB per sucker when called via DELEGATECALL instead of inlining.
347
- /// @param branch The 32-element branch array (caller copies from storage to memory).
348
- /// @param count The number of leaves inserted into the tree.
349
- /// @return current The merkle root.
350
- function computeTreeRoot(bytes32[32] memory branch, uint256 count) external pure returns (bytes32 current) {
351
- // An empty tree has a well-known root.
352
- if (count == 0) return MerkleLib.Z_32;
353
-
354
- // solhint-disable-next-line no-inline-assembly
355
- assembly ("memory-safe") {
356
- // Build zero hashes on-the-fly: Z[0] = 0, Z[i+1] = keccak256(Z[i], Z[i]).
357
- let zPtr := mload(0x40)
358
- mstore(0x40, add(zPtr, 0x420)) // 33 slots × 32 bytes
359
- mstore(zPtr, 0) // Z[0] = bytes32(0)
360
- for { let j := 0 } lt(j, 32) { j := add(j, 1) } {
361
- let prev := mload(add(zPtr, mul(j, 0x20)))
362
- mstore(0x00, prev)
363
- mstore(0x20, prev)
364
- mstore(add(zPtr, mul(add(j, 1), 0x20)), keccak256(0x00, 0x40))
365
- }
366
-
367
- // Walk bits of `count` from LSB to MSB.
368
- // First set bit → initialize current = keccak256(branch[i], Z[i]).
369
- // Each subsequent level → merge branch[i] or Z[i] with current.
370
- let started := 0
371
- for { let i := 0 } lt(i, 32) { i := add(i, 1) } {
372
- switch started
373
- case 0 {
374
- if and(count, shl(i, 1)) {
375
- mstore(0x00, mload(add(branch, mul(i, 0x20))))
376
- mstore(0x20, mload(add(zPtr, mul(i, 0x20))))
377
- current := keccak256(0x00, 0x40)
378
- started := 1
379
- }
380
- }
381
- default {
382
- switch and(count, shl(i, 1))
383
- case 0 {
384
- mstore(0x00, current)
385
- mstore(0x20, mload(add(zPtr, mul(i, 0x20))))
386
- }
387
- default {
388
- mstore(0x00, mload(add(branch, mul(i, 0x20))))
389
- mstore(0x20, current)
390
- }
391
- current := keccak256(0x00, 0x40)
392
- }
393
- }
394
- }
395
- }
396
-
397
- /// @notice Compute a branch root from a leaf, branch, and index. Wraps MerkleLib.branchRoot so its
398
- /// ~170 lines of unrolled assembly live in the library's bytecode instead of each sucker's.
399
- /// @param item The leaf hash.
400
- /// @param branch The 32-element merkle proof branch.
401
- /// @param index The leaf index.
402
- /// @return The computed merkle root.
403
- function computeBranchRoot(bytes32 item, bytes32[32] memory branch, uint256 index) external pure returns (bytes32) {
404
- // Delegate to MerkleLib's unrolled assembly implementation.
405
- return MerkleLib.branchRoot({item: item, branch: branch, index: index});
406
- }
407
403
  }