@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.
- package/package.json +3 -3
- package/src/JBArbitrumSucker.sol +4 -1
- package/src/JBCeloSucker.sol +2 -0
- package/src/JBOptimismSucker.sol +2 -0
- package/src/JBSucker.sol +35 -1
- package/src/JBSuckerRegistry.sol +76 -57
- package/src/JBSwapCCIPSucker.sol +33 -96
- package/src/interfaces/IJBSucker.sol +11 -6
- package/src/interfaces/IJBSuckerRegistry.sol +6 -3
- package/src/libraries/CCIPHelper.sol +64 -64
- package/src/libraries/JBCCIPLib.sol +18 -0
- package/src/libraries/JBSuckerLib.sol +157 -161
- package/src/libraries/JBSwapPoolLib.sol +268 -268
- package/src/structs/PeerValueScratch.sol +18 -0
|
@@ -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
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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
|
}
|