@bananapus/suckers-v6 0.0.78 → 1.0.0
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/README.md +6 -5
- package/foundry.toml +2 -0
- package/package.json +3 -3
- package/references/entrypoints.md +25 -9
- package/src/JBArbitrumSucker.sol +18 -6
- package/src/JBCCIPSucker.sol +3 -12
- package/src/JBOptimismSucker.sol +5 -2
- package/src/JBSucker.sol +220 -158
- package/src/JBSuckerRegistry.sol +375 -138
- package/src/interfaces/IJBSucker.sol +44 -24
- package/src/interfaces/IJBSuckerRegistry.sol +23 -8
- package/src/libraries/JBSuckerLib.sol +215 -26
- package/src/structs/JBAccountingSnapshot.sol +9 -11
- package/src/structs/JBChainAccounting.sol +32 -0
- package/src/structs/JBMessageRoot.sol +12 -16
- package/src/structs/PeerAccountScratch.sol +18 -0
- package/src/structs/RemoteValueParams.sol +15 -0
package/README.md
CHANGED
|
@@ -22,8 +22,8 @@ The codebase includes multiple bridge variants, but the canonical deployment and
|
|
|
22
22
|
Suckers bridge a project by tracking claims in append-only Merkle trees:
|
|
23
23
|
|
|
24
24
|
- users call `prepare` to burn tokens and create a bridge claim in the local outbox tree
|
|
25
|
-
- anyone can relay the current root to the peer chain with `toRemote
|
|
26
|
-
- anyone can refresh or retry
|
|
25
|
+
- anyone can relay the current root to the peer chain with `toRemote`, which also gossips the project's per-source-chain accounting bundle
|
|
26
|
+
- anyone can refresh or retry the accounting gossip bundle with `syncAccountingData`
|
|
27
27
|
- claimants prove inclusion against the peer inbox tree to mint on the destination chain
|
|
28
28
|
|
|
29
29
|
The base implementation is extended for multiple bridge families so the same project model can work across different networks.
|
|
@@ -46,7 +46,7 @@ Each sucker pair has three jobs:
|
|
|
46
46
|
|
|
47
47
|
1. destroy or lock the local economic position into a claimable message
|
|
48
48
|
2. recreate the remote position from a bridged Merkle root plus transported value
|
|
49
|
-
3. keep
|
|
49
|
+
3. keep a per-source-chain accounting store fresh enough for cross-chain estimates, gossiping every chain's record across the sucker mesh
|
|
50
50
|
|
|
51
51
|
That means every bridge path has two trust surfaces:
|
|
52
52
|
|
|
@@ -66,8 +66,9 @@ That means every bridge path has two trust surfaces:
|
|
|
66
66
|
- do not reason about suckers as if they were generic ERC-20 bridges
|
|
67
67
|
- root ordering and message delivery semantics matter as much as proof format
|
|
68
68
|
- token mapping is part of the economic invariant
|
|
69
|
-
- peer contexts are merged only when they share both currency and decimals; same-currency contexts with different decimals are kept separate and valued independently at read time, never summed across precisions
|
|
70
|
-
-
|
|
69
|
+
- peer contexts are merged only when they share both currency and decimals; same-currency contexts with different decimals are kept separate and valued independently at read time, never summed across precisions — and this merge applies per source chain, since each chain's record is stored and folded on its own
|
|
70
|
+
- accounting propagates as a gossip bundle: a sucker sends its own chain's record plus every peer-chain record the project knows (gathered through the registry), so one sync round from a hub propagates every chain's data to every spoke without a direct sucker between each pair
|
|
71
|
+
- `syncAccountingData` pays no registry `toRemoteFee`, but bridge-specific transport costs still apply and duplicate bundles can still consume bridge/indexer resources
|
|
71
72
|
- emergency and deprecation paths are part of normal operational safety
|
|
72
73
|
|
|
73
74
|
## Where state lives
|
package/foundry.toml
CHANGED
|
@@ -3,6 +3,8 @@ solc = '0.8.28'
|
|
|
3
3
|
bytecode_hash = "none"
|
|
4
4
|
evm_version = 'cancun'
|
|
5
5
|
optimizer_runs = 200
|
|
6
|
+
# The IR pipeline keeps the per-chain accounting suckers (Arbitrum, CCIP) under the EIP-170 runtime size limit.
|
|
7
|
+
via_ir = true
|
|
6
8
|
libs = ["node_modules", "lib"]
|
|
7
9
|
fs_permissions = [{ access = "read-write", path = "./"}]
|
|
8
10
|
# Archived, unused contracts (e.g. JBSwapCCIPSucker) and their tests live under `archive/` folders and are
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bananapus/suckers-v6",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@arbitrum/nitro-contracts": "3.2.0",
|
|
30
|
-
"@bananapus/core-v6": "^0.0
|
|
31
|
-
"@bananapus/permission-ids-v6": "^0.0
|
|
30
|
+
"@bananapus/core-v6": "^1.0.0",
|
|
31
|
+
"@bananapus/permission-ids-v6": "^1.0.0",
|
|
32
32
|
"@chainlink/contracts-ccip": "1.6.4",
|
|
33
33
|
"@openzeppelin/contracts": "5.6.1",
|
|
34
34
|
"@prb/math": "4.1.2",
|
|
@@ -51,12 +51,23 @@ A sucker bridges a Juicebox project's token economy between two chains. Cashed-o
|
|
|
51
51
|
|
|
52
52
|
### `JBAccountingSnapshot` (argument to `fromRemoteAccounting`)
|
|
53
53
|
|
|
54
|
+
A cross-chain accounting gossip bundle: the sending chain's own record plus every peer-chain record it currently holds.
|
|
55
|
+
|
|
54
56
|
| Field | Type | Meaning |
|
|
55
57
|
|-------|------|---------|
|
|
56
58
|
| `version` | `uint8` | Message format version. Must match `MESSAGE_VERSION`. |
|
|
57
|
-
| `
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
| `accounts` | `JBChainAccounting[]` | One accounting record per source chain known to the sender (its own chain plus forwarded peers), each carrying its origin chain id and freshness key. The receiver stores the freshest record per source chain. |
|
|
60
|
+
|
|
61
|
+
The same `JBChainAccounting[] accounts` bundle is also carried by the root message `JBMessageRoot` (alongside `token`, `amount`, and `remoteRoot`), so a `toRemote` send propagates accounting too.
|
|
62
|
+
|
|
63
|
+
### `JBChainAccounting` (one source chain's record in a bundle)
|
|
64
|
+
|
|
65
|
+
| Field | Type | Meaning |
|
|
66
|
+
|-------|------|---------|
|
|
67
|
+
| `chainId` | `uint256` | The source chain this record describes. A receiver ignores a record for its own chain or chain 0. |
|
|
68
|
+
| `totalSupply` | `uint256` | Source-chain project-token supply, including reserved tokens. |
|
|
69
|
+
| `contexts` | `JBSourceContext[]` | Raw source-chain surplus and balance contexts, un-valued (in the source chain's own token addresses and decimals). |
|
|
70
|
+
| `timestamp` | `uint256` | Monotonic source-chain freshness key, gated independently per source chain. |
|
|
60
71
|
|
|
61
72
|
## Key functions
|
|
62
73
|
|
|
@@ -66,8 +77,8 @@ A sucker bridges a Juicebox project's token economy between two chains. Cashed-o
|
|
|
66
77
|
|----------|--------------|
|
|
67
78
|
| `prepare(uint256 projectTokenCount, bytes32 beneficiary, uint256 minTokensReclaimed, address token, bytes32 metadata)` | Cash out `projectTokenCount` project tokens into `token` and insert a leaf for `beneficiary` into the outbox tree for bridging. `minTokensReclaimed` bounds slippage; `metadata` is an opaque attribution payload carried in the leaf hash (`bytes32(0)` for a plain bridge). |
|
|
68
79
|
| `toRemote(address token) payable` | Send the current outbox tree root and bridged assets for `token` to the remote peer through the chain-specific transport. `payable` to fund the transport message and the registry's `toRemoteFee`. |
|
|
69
|
-
| `syncAccountingData() payable` | Send the
|
|
70
|
-
| `fromRemoteAccounting(JBAccountingSnapshot calldata snapshot)` | Authenticated receive path for accounting-only
|
|
80
|
+
| `syncAccountingData() payable` | Send the cross-chain accounting gossip bundle — this chain's own record plus every peer-chain record it holds (gathered across the project's suckers via the registry, minus the destination) — without sending an outbox root or paying the registry `toRemoteFee`. Can be retried with unchanged data; `payable` only funds bridge transport. |
|
|
81
|
+
| `fromRemoteAccounting(JBAccountingSnapshot calldata snapshot)` | Authenticated receive path for an accounting-only gossip bundle. Stores the freshest record per source chain, without touching any token-local inbox root. |
|
|
71
82
|
| `claim(JBClaim calldata claimData)` | Claim bridged project tokens for the leaf's beneficiary by proving inclusion against the inbox root. |
|
|
72
83
|
| `claim(JBClaim[] calldata claims)` | Claim multiple leaves in one call. Each leaf is routed through an external `this.claim` sub-call, so one failing leaf emits `ClaimFailed` and is reverted in isolation while the rest of the batch proceeds; the failed leaf stays claimable later. |
|
|
73
84
|
| `mapToken(JBTokenMapping calldata map) payable` | Map a single local token to a remote token for bridging. Mappings are immutable once the outbox tree has entries (can only be disabled, not remapped). Requires `MAP_SUCKER_TOKEN` permission (initial mappings are applied at deploy under `DEPLOY_SUCKERS`). |
|
|
@@ -97,8 +108,12 @@ A sucker bridges a Juicebox project's token economy between two chains. Cashed-o
|
|
|
97
108
|
| `outboxOf(address token)` | The outbox merkle tree (`JBOutboxTree`) for a token. |
|
|
98
109
|
| `amountToAddToBalanceOf(address token)` | Tokens received from bridging that are waiting to be added to the project's terminal balance. |
|
|
99
110
|
| `executedLeafHashOf(address token, uint256 index)` | The committed leaf hash at `(token, index)`, or `bytes32(0)` if unexecuted. Beneficiary contracts re-derive this to authenticate a settlement that a front-runner's direct `claim` already executed. |
|
|
100
|
-
| `
|
|
101
|
-
| `
|
|
111
|
+
| `peerChainIds(bool includeVirtual)` | The peer chains this sucker reports accounting for: its directly-connected peer, plus — when `includeVirtual` is true — every chain learned about through gossip. The registry aggregates the `includeVirtual: true` set. |
|
|
112
|
+
| `peerChainAccountsOf()` | The raw, un-valued `JBChainAccounting[]` record this sucker holds for every known peer chain. The registry reads this to gather a project's cross-chain knowledge and re-gossip it. |
|
|
113
|
+
| `peerChainContextsOf(uint256 chainId)` | Per-context surplus and balance for one peer chain, resolved to local currencies and folded at read time. Un-valued; returned with the chain's freshness key. |
|
|
114
|
+
| `peerChainTotalSupplyOf(uint256 chainId)` | The last-known total token supply on one peer chain (the registry sums these to compute effective cross-chain supply). |
|
|
115
|
+
| `peerChainTotalSupplyValue(uint256 chainId)` | One peer chain's total supply bundled with its chain id and freshness key (`JBPeerChainValue`). |
|
|
116
|
+
| `snapshotTimestampOf(uint256 chainId)` | The freshness key of the latest accepted record for one peer chain. |
|
|
102
117
|
| `retainedToRemoteFeeOf(address account)` | ETH owed to `account` from a failed `toRemote` fee payment. |
|
|
103
118
|
| `retainedTransportPaymentRefundOf(address account)` | ETH owed to `account` from a failed transport-payment refund. |
|
|
104
119
|
|
|
@@ -116,7 +131,8 @@ A sucker bridges a Juicebox project's token economy between two chains. Cashed-o
|
|
|
116
131
|
| `suckerPairsOf(uint256 projectId)` | The local/remote sucker pairs (`JBSuckersPair[]`) for a project. |
|
|
117
132
|
| `isSuckerOf(uint256 projectId, address addr)` | Whether `addr` is a registry-deployed sucker for the project. |
|
|
118
133
|
| `suckerDeployerIsAllowed(address deployer)` | Whether a deployer is on the allowlist. |
|
|
119
|
-
| `
|
|
120
|
-
| `
|
|
134
|
+
| `peerChainAccountsOf(uint256 projectId, uint256 exceptChainId)` | The freshest accounting record per source chain across all of a project's suckers, deduped and minus `exceptChainId` — the peer set a sucker forwards when re-gossiping. |
|
|
135
|
+
| `remoteTotalSupplyOf(uint256 projectId)` | Combined peer-chain total supply across all remote chains (aggregates every (sucker, chain) pair, deduped per source chain by freshest record). |
|
|
136
|
+
| `totalRemoteSurplusOf(uint256 projectId, uint256 currency, uint256 decimals)` | Combined peer-chain surplus valued into `currency`. Matching-currency contexts taken at par; a missing cross-currency feed skips that (sucker, chain) (conservative). |
|
|
121
137
|
| `totalRemoteBalanceOf(uint256 projectId, uint256 currency, uint256 decimals)` | Combined peer-chain balance valued into `currency`, same valuation rules as above. |
|
|
122
138
|
| `toRemoteFee()` / `MAX_TO_REMOTE_FEE()` | The current `toRemote` fee and its hardcoded ceiling. |
|
package/src/JBArbitrumSucker.sol
CHANGED
|
@@ -112,11 +112,13 @@ contract JBArbitrumSucker is JBSucker, IJBArbitrumSucker {
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
/// @notice Helper to create the retryable ticket, avoiding stack-too-deep.
|
|
115
|
+
/// @param gasLimit The destination L2 gas to provision, scaled to the message's gossip bundle.
|
|
115
116
|
function _createRetryableTicket(
|
|
116
117
|
uint256 callTransportCost,
|
|
117
118
|
uint256 nativeValue,
|
|
118
119
|
uint256 maxSubmissionCost,
|
|
119
120
|
uint256 maxFeePerGas,
|
|
121
|
+
uint256 gasLimit,
|
|
120
122
|
bytes memory data
|
|
121
123
|
)
|
|
122
124
|
internal
|
|
@@ -128,7 +130,7 @@ contract JBArbitrumSucker is JBSucker, IJBArbitrumSucker {
|
|
|
128
130
|
maxSubmissionCost: maxSubmissionCost,
|
|
129
131
|
excessFeeRefundAddress: _msgSender(),
|
|
130
132
|
callValueRefundAddress: peerAddress,
|
|
131
|
-
gasLimit:
|
|
133
|
+
gasLimit: gasLimit,
|
|
132
134
|
maxFeePerGas: maxFeePerGas,
|
|
133
135
|
data: data
|
|
134
136
|
});
|
|
@@ -158,7 +160,8 @@ contract JBArbitrumSucker is JBSucker, IJBArbitrumSucker {
|
|
|
158
160
|
transportPayment: transportPayment,
|
|
159
161
|
amount: 0,
|
|
160
162
|
data: data,
|
|
161
|
-
remoteToken: remoteToken
|
|
163
|
+
remoteToken: remoteToken,
|
|
164
|
+
gasLimit: _messagingGasLimit({accounts: snapshot.accounts})
|
|
162
165
|
});
|
|
163
166
|
} else {
|
|
164
167
|
// L2→L1 via ArbSys is free — reject any transport payment.
|
|
@@ -191,7 +194,12 @@ contract JBArbitrumSucker is JBSucker, IJBArbitrumSucker {
|
|
|
191
194
|
// L1→L2 requires transport payment for retryable tickets.
|
|
192
195
|
if (transportPayment == 0) revert JBSucker_ExpectedMsgValue({msgValue: transportPayment});
|
|
193
196
|
_toL2({
|
|
194
|
-
token: token,
|
|
197
|
+
token: token,
|
|
198
|
+
transportPayment: transportPayment,
|
|
199
|
+
amount: amount,
|
|
200
|
+
data: data,
|
|
201
|
+
remoteToken: remoteToken,
|
|
202
|
+
gasLimit: _messagingGasLimit({accounts: message.accounts})
|
|
195
203
|
});
|
|
196
204
|
} else {
|
|
197
205
|
// L2→L1 via ArbSys is free — reject any transport payment.
|
|
@@ -255,12 +263,14 @@ contract JBArbitrumSucker is JBSucker, IJBArbitrumSucker {
|
|
|
255
263
|
/// @param token The token to bridge.
|
|
256
264
|
/// @param amount The amount of tokens to bridge.
|
|
257
265
|
/// @param data The calldata to send to the remote chain. This calls `JBSucker.fromRemote` on the remote peer.
|
|
266
|
+
/// @param gasLimit The destination L2 gas to provision, scaled to the message's gossip bundle.
|
|
258
267
|
function _toL2(
|
|
259
268
|
address token,
|
|
260
269
|
uint256 transportPayment,
|
|
261
270
|
uint256 amount,
|
|
262
271
|
bytes memory data,
|
|
263
|
-
JBRemoteToken memory remoteToken
|
|
272
|
+
JBRemoteToken memory remoteToken,
|
|
273
|
+
uint256 gasLimit
|
|
264
274
|
)
|
|
265
275
|
internal
|
|
266
276
|
{
|
|
@@ -273,8 +283,9 @@ contract JBArbitrumSucker is JBSucker, IJBArbitrumSucker {
|
|
|
273
283
|
maxSubmissionCost =
|
|
274
284
|
ARBINBOX.calculateRetryableSubmissionFee({dataLength: data.length, baseFee: maxFeePerGas});
|
|
275
285
|
|
|
276
|
-
// Tracks the cost for the call to the remote peer.
|
|
277
|
-
|
|
286
|
+
// Tracks the cost for the call to the remote peer. Scaled to the bundle so a larger mesh's accounting
|
|
287
|
+
// store does not run out of gas on the destination.
|
|
288
|
+
callTransportCost = maxSubmissionCost + (gasLimit * maxFeePerGas);
|
|
278
289
|
}
|
|
279
290
|
|
|
280
291
|
// If the token is an ERC-20, bridge it to the peer.
|
|
@@ -351,6 +362,7 @@ contract JBArbitrumSucker is JBSucker, IJBArbitrumSucker {
|
|
|
351
362
|
nativeValue: nativeValue,
|
|
352
363
|
maxSubmissionCost: maxSubmissionCost,
|
|
353
364
|
maxFeePerGas: maxFeePerGas,
|
|
365
|
+
gasLimit: gasLimit,
|
|
354
366
|
data: data
|
|
355
367
|
});
|
|
356
368
|
}
|
package/src/JBCCIPSucker.sol
CHANGED
|
@@ -25,6 +25,7 @@ import {JBCCIPLib} from "./libraries/JBCCIPLib.sol";
|
|
|
25
25
|
|
|
26
26
|
// Local: structs (alphabetized)
|
|
27
27
|
import {JBAccountingSnapshot} from "./structs/JBAccountingSnapshot.sol";
|
|
28
|
+
import {JBChainAccounting} from "./structs/JBChainAccounting.sol";
|
|
28
29
|
import {JBMessageRoot} from "./structs/JBMessageRoot.sol";
|
|
29
30
|
import {JBRemoteToken} from "./structs/JBRemoteToken.sol";
|
|
30
31
|
import {JBTokenMapping} from "./structs/JBTokenMapping.sol";
|
|
@@ -77,9 +78,6 @@ contract JBCCIPSucker is JBSucker, IAny2EVMMessageReceiver {
|
|
|
77
78
|
/// @notice Message type prefix for root messages (fromRemote).
|
|
78
79
|
uint8 internal constant _CCIP_MSG_TYPE_ROOT = 0;
|
|
79
80
|
|
|
80
|
-
/// @notice Extra destination gas budgeted for each source accounting context carried in a CCIP message.
|
|
81
|
-
uint256 internal constant _CCIP_SOURCE_CONTEXT_GAS_LIMIT = 75_000;
|
|
82
|
-
|
|
83
81
|
//*********************************************************************//
|
|
84
82
|
// --------------- public immutable stored properties ---------------- //
|
|
85
83
|
//*********************************************************************//
|
|
@@ -269,7 +267,7 @@ contract JBCCIPSucker is JBSucker, IAny2EVMMessageReceiver {
|
|
|
269
267
|
{
|
|
270
268
|
_sendCcipMessage({
|
|
271
269
|
transportPayment: transportPayment,
|
|
272
|
-
gasLimit:
|
|
270
|
+
gasLimit: _messagingGasLimit({accounts: snapshot.accounts}),
|
|
273
271
|
encodedPayload: abi.encode(_CCIP_MSG_TYPE_ACCOUNTING, abi.encode(snapshot)),
|
|
274
272
|
tokenAmounts: new Client.EVMTokenAmount[](0)
|
|
275
273
|
});
|
|
@@ -343,7 +341,7 @@ contract JBCCIPSucker is JBSucker, IAny2EVMMessageReceiver {
|
|
|
343
341
|
override
|
|
344
342
|
{
|
|
345
343
|
// Budget for the root receiver plus the accounting contexts carried in the root message.
|
|
346
|
-
uint256 gasLimit =
|
|
344
|
+
uint256 gasLimit = _messagingGasLimit({accounts: suckerMessage.accounts});
|
|
347
345
|
Client.EVMTokenAmount[] memory tokenAmounts;
|
|
348
346
|
|
|
349
347
|
if (amount != 0) {
|
|
@@ -369,13 +367,6 @@ contract JBCCIPSucker is JBSucker, IAny2EVMMessageReceiver {
|
|
|
369
367
|
// ------------------------ internal views --------------------------- //
|
|
370
368
|
//*********************************************************************//
|
|
371
369
|
|
|
372
|
-
/// @notice The CCIP destination gas limit for a message carrying `sourceContextCount` accounting contexts.
|
|
373
|
-
/// @param sourceContextCount The number of source accounting contexts in the message.
|
|
374
|
-
/// @return gasLimit The destination gas limit to ask CCIP to provide.
|
|
375
|
-
function _ccipGasLimitFor(uint256 sourceContextCount) internal pure returns (uint256 gasLimit) {
|
|
376
|
-
return MESSENGER_BASE_GAS_LIMIT + (sourceContextCount * _CCIP_SOURCE_CONTEXT_GAS_LIMIT);
|
|
377
|
-
}
|
|
378
|
-
|
|
379
370
|
/// @notice Checks whether the given sender is a remote peer. Unused in this context.
|
|
380
371
|
/// @param sender The address to check.
|
|
381
372
|
/// @return _valid Whether the sender is a remote peer.
|
package/src/JBOptimismSucker.sol
CHANGED
|
@@ -7,6 +7,7 @@ import {IJBTokens} from "@bananapus/core-v6/src/interfaces/IJBTokens.sol";
|
|
|
7
7
|
import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
|
|
8
8
|
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
9
9
|
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
|
10
|
+
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
|
|
10
11
|
|
|
11
12
|
import {JBSucker} from "./JBSucker.sol";
|
|
12
13
|
import {JBOptimismSuckerDeployer} from "./deployers/JBOptimismSuckerDeployer.sol";
|
|
@@ -102,7 +103,8 @@ contract JBOptimismSucker is JBSucker, IJBOptimismSucker {
|
|
|
102
103
|
OPMESSENGER.sendMessage({
|
|
103
104
|
target: _toAddress(peer()),
|
|
104
105
|
message: abi.encodeCall(JBSucker.fromRemoteAccounting, (snapshot)),
|
|
105
|
-
|
|
106
|
+
// Scale the destination gas with the bundle so a larger mesh's accounting still stores in one relay.
|
|
107
|
+
gasLimit: SafeCast.toUint32(_messagingGasLimit({accounts: snapshot.accounts}))
|
|
106
108
|
});
|
|
107
109
|
}
|
|
108
110
|
|
|
@@ -160,7 +162,8 @@ contract JBOptimismSucker is JBSucker, IJBOptimismSucker {
|
|
|
160
162
|
OPMESSENGER.sendMessage{value: nativeValue}({
|
|
161
163
|
target: peerAddress,
|
|
162
164
|
message: abi.encodeCall(JBSucker.fromRemote, (message)),
|
|
163
|
-
|
|
165
|
+
// Scale the destination gas with the bundle so a larger mesh's accounting still stores in one relay.
|
|
166
|
+
gasLimit: SafeCast.toUint32(_messagingGasLimit({accounts: message.accounts}))
|
|
164
167
|
});
|
|
165
168
|
}
|
|
166
169
|
}
|