@bananapus/suckers-v6 1.0.0 → 1.0.2

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 CHANGED
@@ -37,7 +37,7 @@ The main idea is not "bridge the token contract." The main idea is "bridge a Jui
37
37
  | Contract | Role |
38
38
  | --- | --- |
39
39
  | `JBSucker` | Base bridge logic for prepare, relay, accounting sync, claim, token mapping, and lifecycle controls. |
40
- | `JBSuckerRegistry` | Registry for per-project sucker deployments, deployer allowlists, and shared bridge fee settings. |
40
+ | `JBSuckerRegistry` | Registry for per-project sucker deployments, deployer and owner-gated token-pair allowlists, and shared bridge fee settings. |
41
41
  | Chain-specific suckers | Transport-specific implementations for OP Stack, Arbitrum, CCIP, and related environments. |
42
42
 
43
43
  ## Mental model
@@ -66,6 +66,7 @@ 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
+ - native/native mappings and different-address local/remote token mappings must be approved by the registry owner for the specific `(localToken, remoteChainId, remoteToken)` route before a project can choose them; non-native same-address mappings and disabled mappings do not need owner approval
69
70
  - 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
71
  - 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
72
  - `syncAccountingData` pays no registry `toRemoteFee`, but bridge-specific transport costs still apply and duplicate bundles can still consume bridge/indexer resources
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bananapus/suckers-v6",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -17,7 +17,7 @@ A sucker bridges a Juicebox project's token economy between two chains. Cashed-o
17
17
  | `JBOptimismSucker` | OP Stack bridge transport. |
18
18
  | `JBCCIPSucker` | Chainlink CCIP transport. |
19
19
  | `JBBaseSucker` | Base/OP Stack transport. |
20
- | `JBSuckerRegistry` | Project-to-sucker inventory, deployer allowlist, shared `toRemote` fee, deprecation removal, cross-chain surplus/supply aggregation. |
20
+ | `JBSuckerRegistry` | Project-to-sucker inventory, deployer allowlist, owner-gated token-pair allowlist, shared `toRemote` fee, deprecation removal, cross-chain surplus/supply aggregation. |
21
21
 
22
22
  `IJBSucker` is the minimal interface; `IJBSuckerExtended` adds the deprecation, emergency-hatch, and retained-fee surface.
23
23
 
@@ -81,7 +81,7 @@ The same `JBChainAccounting[] accounts` bundle is also carried by the root messa
81
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. |
82
82
  | `claim(JBClaim calldata claimData)` | Claim bridged project tokens for the leaf's beneficiary by proving inclusion against the inbox root. |
83
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. |
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`). |
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`). Native/native mappings and different-address mappings must also be approved by the registry owner for this sucker's peer chain. |
85
85
  | `mapTokens(JBTokenMapping[] calldata maps) payable` | Map multiple local tokens to remote tokens in one call. |
86
86
 
87
87
  ### JBSucker — deprecation & emergency (`IJBSuckerExtended`)
@@ -125,6 +125,10 @@ The same `JBChainAccounting[] accounts` bundle is also carried by the root messa
125
125
  | `removeDeprecatedSucker(uint256 projectId, address sucker)` | Remove a fully deprecated sucker from a project's inventory. |
126
126
  | `allowSuckerDeployer(address deployer)` / `allowSuckerDeployers(address[] calldata deployers)` | Add deployer(s) to the allowlist. Owner-only. |
127
127
  | `removeSuckerDeployer(address deployer)` | Remove a deployer from the allowlist. Owner-only. |
128
+ | `allowTokenMapping(address localToken, uint256 remoteChainId, bytes32 remoteToken)` / `allowTokenMappings(address[] calldata localTokens, uint256[] calldata remoteChainIds, bytes32[] calldata remoteTokens)` | Add route-scoped approvals for native/native or different-address local/remote token mappings. Owner-only. |
129
+ | `removeTokenMapping(address localToken, uint256 remoteChainId, bytes32 remoteToken)` / `removeTokenMappings(address[] calldata localTokens, uint256[] calldata remoteChainIds, bytes32[] calldata remoteTokens)` | Remove route-scoped token-mapping approvals. Owner-only. |
130
+ | `requireTokenMappingAllowed(address localToken, uint256 remoteChainId, bytes32 remoteToken)` | Revert unless the mapping can be chosen. Disabled mappings and non-native same-address mappings pass directly; native/native and different-address mappings require route approval. |
131
+ | `tokenMappingIsAllowed(address localToken, uint256 remoteChainId, bytes32 remoteToken)` | Stored owner approval for an owner-gated token route. |
128
132
  | `setToRemoteFee(uint256 fee)` | Set the ETH fee (wei) paid into the fee project on each `toRemote` call. Reverts if `fee > MAX_TO_REMOTE_FEE`. Owner-only. |
129
133
  | `suckersOf(uint256 projectId)` | All active suckers for a project. |
130
134
  | `allSuckersOf(uint256 projectId)` | Every sucker ever registered for a project, including deprecated ones. |
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Configuration surface
4
4
 
5
- - [`src/JBSuckerRegistry.sol`](../src/JBSuckerRegistry.sol) is the first stop for deployer allowlists, shared fees, project inventory, and deprecation helpers.
5
+ - [`src/JBSuckerRegistry.sol`](../src/JBSuckerRegistry.sol) is the first stop for deployer allowlists, owner-gated token-pair allowlists, shared fees, project inventory, and deprecation helpers.
6
6
  - Transport-specific deployers in `src/deployers/` are where chain-specific constants and bridge addresses live.
7
7
  - [`script/Deploy.s.sol`](../script/Deploy.s.sol) is where deployment-time environment wiring belongs.
8
8
 
@@ -10,6 +10,7 @@
10
10
 
11
11
  - If you edit base sucker accounting, verify claim flow across at least one chain-specific implementation.
12
12
  - If you edit token mapping logic, re-check the registry and deployer assumptions that feed it.
13
+ - If you edit owner-gated token mapping rules, verify native/native, different-address, non-native same-address, disabled, deploy-time, and route-scoped cases.
13
14
  - If you edit token mapping semantics, verify that remapping is still impossible once outbox activity has made economic equivalence depend on permanence.
14
15
  - If you edit deprecation or emergency paths, verify the intended operator workflow still works end to end.
15
16
  - If you edit snapshot or claim-boundary logic, verify `numberOfClaimsSent`, peer snapshots, `syncAccountingData`, and emergency exit behavior together.
@@ -18,6 +19,7 @@
18
19
  ## Common failure modes
19
20
 
20
21
  - Cross-chain issue is blamed on transport when the root or token mapping was wrong before message delivery.
22
+ - A token-pair approval is assumed to be global when it is actually scoped to one `(localToken, remoteChainId, remoteToken)` route.
21
23
  - Registry configuration drifts from what a deployer or external operator expects.
22
24
  - Emergency hatches or deprecation paths are stale because nobody exercises them until stress conditions arrive.
23
25
 
@@ -3,7 +3,7 @@
3
3
  ## Core roles
4
4
 
5
5
  - [`src/JBSucker.sol`](../src/JBSucker.sol) owns the shared prepare, relay, claim, token-mapping, and lifecycle logic.
6
- - [`src/JBSuckerRegistry.sol`](../src/JBSuckerRegistry.sol) owns project-to-sucker inventory, deployer allowlists, and shared remote-fee settings.
6
+ - [`src/JBSuckerRegistry.sol`](../src/JBSuckerRegistry.sol) owns project-to-sucker inventory, deployer allowlists, owner-gated token-pair allowlists, and shared remote-fee settings.
7
7
  - Chain-specific sucker contracts such as [`src/JBArbitrumSucker.sol`](../src/JBArbitrumSucker.sol), [`src/JBOptimismSucker.sol`](../src/JBOptimismSucker.sol), [`src/JBCCIPSucker.sol`](../src/JBCCIPSucker.sol), and [`src/JBCeloSucker.sol`](../src/JBCeloSucker.sol) own transport-specific delivery and verification.
8
8
  - Matching deployers under `src/deployers/` own clone and transport configuration.
9
9
 
@@ -17,7 +17,7 @@
17
17
 
18
18
  ## High-risk areas
19
19
 
20
- - Token mapping: mapping mistakes break economic equivalence, not just UX.
20
+ - Token mapping: mapping mistakes break economic equivalence, not just UX. Native/native and different-address mappings require route-scoped registry-owner approval before a project can choose them.
21
21
  - Root ordering and replay protection: message sequencing is part of correctness.
22
22
  - Emergency and deprecation paths: these are operational safety surfaces that must remain reliable.
23
23
  - Shared accounting vs transport logic: many incidents stem from confusing these layers.
package/src/JBSucker.sol CHANGED
@@ -332,12 +332,12 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
332
332
  /// project's chain count.
333
333
  uint256[] private _peerChainIds;
334
334
 
335
- /// @notice Each peer chain's raw, un-valued per-context surplus and balance from its latest accepted record.
336
- /// @dev Stored exactly as received — in the source chain's own token addresses and decimals so the record can
337
- /// be
338
- /// re-gossiped to other chains faithfully and resolved to local currencies at read time. A fresher record for a
339
- /// chain replaces that chain's set from scratch; a context dropped by the fresher record simply vanishes.
340
- /// @custom:param chainId The peer chain to read the raw contexts of.
335
+ /// @notice Each peer chain's un-valued per-context surplus and balance from its latest accepted record.
336
+ /// @dev Stored in this sucker's token namespace where a remote-token mapping exists; unmapped tokens stay as
337
+ /// received. `peerChainContextsOf` resolves these token keys for local valuation.
338
+ /// A fresher record for a chain replaces that chain's set from scratch; a context dropped by the fresher record
339
+ /// simply vanishes.
340
+ /// @custom:param chainId The peer chain to read the contexts of.
341
341
  mapping(uint256 chainId => JBSourceContext[]) private _peerContextsOf;
342
342
 
343
343
  //*********************************************************************//
@@ -882,11 +882,11 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
882
882
  return _outboxOf[token];
883
883
  }
884
884
 
885
- /// @notice The raw, un-valued accounting record this sucker holds for every peer chain it has heard about.
886
- /// @dev The registry reads this to gather a project's full cross-chain knowledge and re-gossip it. Records are
887
- /// returned exactly as received (in each source chain's own token addresses and decimals) so the next receiver
888
- /// resolves them to its own local currencies independently.
889
- /// @return accounts One raw accounting record per known peer chain.
885
+ /// @notice The un-valued accounting record this sucker holds for every peer chain it has heard about.
886
+ /// @dev The registry reads this to gather a project's full cross-chain knowledge and re-gossip it. Incoming
887
+ /// contexts are stored in this sucker's token namespace where a mapping exists, so a hub forwards sibling-chain
888
+ /// accounting in hub-local token terms and the next spoke only needs its mapping to the hub token.
889
+ /// @return accounts One accounting record per known peer chain.
890
890
  function peerChainAccountsOf() external view returns (JBChainAccounting[] memory accounts) {
891
891
  uint256[] storage chainIds = _peerChainIds;
892
892
  uint256 numChains = chainIds.length;
@@ -907,10 +907,10 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
907
907
 
908
908
  /// @notice One peer chain's per-currency surplus and balance from its latest accepted record, plus that record's
909
909
  /// freshness key.
910
- /// @dev Resolves each raw stored context to its local currency and folds same-currency, same-decimals entries
911
- /// together. The result is un-valued — the registry values each context into a requested currency; the sucker
912
- /// consults no price oracle. Contexts that share a currency but carry different decimals are kept separate because
913
- /// the raw amounts are on different scales and cannot be summed directly.
910
+ /// @dev Stored contexts are already keyed in this sucker's token namespace where a mapping exists. This folds
911
+ /// same-currency, same-decimals entries together. The result is un-valued — the registry values each context into
912
+ /// a requested currency; the sucker consults no price oracle. Contexts that share a currency but carry different
913
+ /// decimals are kept separate because the raw amounts are on different scales and cannot be summed directly.
914
914
  /// @param chainId The peer chain to read the contexts of.
915
915
  /// @return contexts The per-currency surplus and balance for the chain.
916
916
  /// @return snapshot The source freshness key of the chain's latest accepted record.
@@ -919,25 +919,8 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
919
919
  view
920
920
  returns (JBPeerChainContext[] memory contexts, uint256 snapshot)
921
921
  {
922
- JBSourceContext[] storage rawContexts = _peerContextsOf[chainId];
923
- uint256 numRaw = rawContexts.length;
924
-
925
- // Copy the raw contexts to memory and resolve each source-local token to a local token (mapping first, identity
926
- // fallback). The bytecode-heavy currency resolution and fold then run in the library.
927
- JBSourceContext[] memory raw = new JBSourceContext[](numRaw);
928
- address[] memory localTokens = new address[](numRaw);
929
- for (uint256 i; i < numRaw;) {
930
- JBSourceContext storage ctx = rawContexts[i];
931
- raw[i] = ctx;
932
- address contextToken = _localTokenForRemoteToken[ctx.token];
933
- localTokens[i] = contextToken == address(0) ? _toAddress(ctx.token) : contextToken;
934
- unchecked {
935
- ++i;
936
- }
937
- }
938
-
939
922
  contexts = JBSuckerLib.foldPeerContexts({
940
- directory: DIRECTORY, projectId: projectId(), localTokens: localTokens, rawContexts: raw
923
+ directory: DIRECTORY, projectId: projectId(), rawContexts: _peerContextsOf[chainId]
941
924
  });
942
925
  snapshot = snapshotTimestampOf[chainId];
943
926
  }
@@ -1324,6 +1307,7 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
1324
1307
  returns (uint256 transportPaymentSpent)
1325
1308
  {
1326
1309
  address token = map.localToken;
1310
+ bytes32 remoteToken = map.remoteToken;
1327
1311
  JBRemoteToken memory currentMapping = _remoteTokenFor[token];
1328
1312
 
1329
1313
  // Once the emergency hatch for a token is enabled it can't be disabled.
@@ -1345,12 +1329,15 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
1345
1329
  alsoGrantAccessIf: _msgSender() == address(REGISTRY)
1346
1330
  });
1347
1331
 
1332
+ // The registry owner gates mappings that assert economic equivalence across distinct chain assets.
1333
+ REGISTRY.requireTokenMappingAllowed({localToken: token, remoteChainId: peerChainId(), remoteToken: remoteToken});
1334
+
1348
1335
  // Make sure that the token does not get remapped to another remote token.
1349
1336
  // As this would cause the funds for this token to be double spendable on the other side.
1350
1337
  // It should not be possible to cause any issues even without this check
1351
1338
  // a bridge *should* never accept such a request. This is mostly a sanity check.
1352
1339
  if (
1353
- currentMapping.addr != bytes32(0) && currentMapping.addr != map.remoteToken && map.remoteToken != bytes32(0)
1340
+ currentMapping.addr != bytes32(0) && currentMapping.addr != remoteToken && remoteToken != bytes32(0)
1354
1341
  && _outboxOf[token].tree.count != 0
1355
1342
  ) {
1356
1343
  revert JBSucker_TokenAlreadyMapped({localToken: token, mappedTo: currentMapping.addr});
@@ -1359,10 +1346,10 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
1359
1346
  // A remote token can back only one local token's outbox in this sucker. Otherwise two independent source
1360
1347
  // nonces would race into the same destination inbox key (`root.token`), making one token's root stale or
1361
1348
  // overwriting the other. Other suckers have separate inbox/outbox storage and are unaffected.
1362
- if (map.remoteToken != bytes32(0)) {
1363
- address mappedLocalToken = _localTokenForRemoteToken[map.remoteToken];
1349
+ if (remoteToken != bytes32(0)) {
1350
+ address mappedLocalToken = _localTokenForRemoteToken[remoteToken];
1364
1351
  if (mappedLocalToken != address(0) && mappedLocalToken != token) {
1365
- revert JBSucker_RemoteTokenAlreadyMapped({remoteToken: map.remoteToken, localToken: mappedLocalToken});
1352
+ revert JBSucker_RemoteTokenAlreadyMapped({remoteToken: remoteToken, localToken: mappedLocalToken});
1366
1353
  }
1367
1354
  }
1368
1355
 
@@ -1376,7 +1363,7 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
1376
1363
  // created and the mapping cannot be changed to a different remote token. If tokens are stuck in this state
1377
1364
  // (e.g., the bridge is non-functional), the project owner can call `enableEmergencyHatchFor` to allow
1378
1365
  // local withdrawals via `exitThroughEmergencyHatch`.
1379
- if (map.remoteToken == bytes32(0) && _outboxOf[token].numberOfClaimsSent != _outboxOf[token].tree.count) {
1366
+ if (remoteToken == bytes32(0) && _outboxOf[token].numberOfClaimsSent != _outboxOf[token].tree.count) {
1380
1367
  // Disable before external call to prevent reentrancy via prepare().
1381
1368
  // _sendRoot uses the `currentMapping` parameter, not storage, so this is safe.
1382
1369
  _remoteTokenFor[token].enabled = false;
@@ -1386,13 +1373,13 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
1386
1373
 
1387
1374
  // Update the reverse reservation if an unused local token is being remapped to a new remote token.
1388
1375
  if (
1389
- map.remoteToken != bytes32(0) && currentMapping.addr != bytes32(0) && currentMapping.addr != map.remoteToken
1376
+ remoteToken != bytes32(0) && currentMapping.addr != bytes32(0) && currentMapping.addr != remoteToken
1390
1377
  && _localTokenForRemoteToken[currentMapping.addr] == token
1391
1378
  ) {
1392
1379
  delete _localTokenForRemoteToken[currentMapping.addr];
1393
1380
  }
1394
1381
 
1395
- bytes32 remoteToken = map.remoteToken == bytes32(0) ? currentMapping.addr : map.remoteToken;
1382
+ remoteToken = remoteToken == bytes32(0) ? currentMapping.addr : remoteToken;
1396
1383
  if (remoteToken != bytes32(0)) _localTokenForRemoteToken[remoteToken] = token;
1397
1384
 
1398
1385
  // Update the token mapping.
@@ -1600,12 +1587,13 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
1600
1587
  /// @notice Store one source chain's accounting record if it is fresher than the one already held for that chain.
1601
1588
  /// @dev A record for the local chain is ignored — a chain reads its own accounting directly. Each peer chain is
1602
1589
  /// gated independently on a strictly-newer freshness key, so a stale relay cannot roll back any chain and records
1603
- /// delivered out of order converge. Contexts are stored raw (in the source chain's own token addresses) so the
1604
- /// record can be re-gossiped faithfully; each receiver resolves them to its own local currencies at read time.
1590
+ /// delivered out of order converge. Any source token that matches this sucker's remote-token mapping is re-keyed
1591
+ /// to this sucker's local token before storage, so forwarded records are already in the current hop's token
1592
+ /// namespace.
1605
1593
  /// @param chainId The source chain the record describes.
1606
1594
  /// @param sourceTimestamp The record's source-chain freshness key.
1607
1595
  /// @param sourceTotalSupply The source chain's total project-token supply.
1608
- /// @param sourceContexts The source chain's raw per-context surplus and balance.
1596
+ /// @param sourceContexts The source chain's per-context surplus and balance.
1609
1597
  function _storeChainAccounting(
1610
1598
  uint256 chainId,
1611
1599
  uint256 sourceTimestamp,
@@ -1634,15 +1622,17 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
1634
1622
  _peerChainIds.push(chainId);
1635
1623
  }
1636
1624
 
1637
- // Rebuild this chain's raw context set from scratch. A context dropped by the fresher record is simply absent.
1638
- // Each context is stored verbatim so it can be re-gossiped faithfully; folding to a local currency happens at
1639
- // read time in `peerChainContextsOf`.
1625
+ // Rebuild this chain's context set from scratch. A context dropped by the fresher record is simply absent.
1626
+ // Each context keeps its source amount and decimals, while its token key is localized for this hop when a
1627
+ // mapping exists.
1640
1628
  delete _peerContextsOf[chainId];
1641
1629
  JBSourceContext[] storage storedContexts = _peerContextsOf[chainId];
1642
1630
 
1643
1631
  uint256 numContexts = sourceContexts.length;
1644
1632
  for (uint256 i; i < numContexts;) {
1645
1633
  storedContexts.push(sourceContexts[i]);
1634
+ address localToken = _localTokenForRemoteToken[sourceContexts[i].token];
1635
+ if (localToken != address(0)) storedContexts[i].token = bytes32(uint256(uint160(localToken)));
1646
1636
  unchecked {
1647
1637
  ++i;
1648
1638
  }
@@ -2036,17 +2026,18 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
2036
2026
  /// @notice Allow sucker implementations to add/override mapping rules to suit their specific needs.
2037
2027
  /// @param map The token mapping to validate.
2038
2028
  function _validateTokenMapping(JBTokenMapping calldata map) internal pure virtual {
2039
- bool isNative = map.localToken == JBConstants.NATIVE_TOKEN;
2040
-
2041
2029
  // If the token being mapped is the native token, the `remoteToken` must also be the native token.
2042
2030
  // The native token can also be mapped to the 0 address, which is used to disable native token bridging.
2043
- if (isNative && map.remoteToken != _toBytes32(JBConstants.NATIVE_TOKEN) && map.remoteToken != bytes32(0)) {
2044
- revert JBSucker_InvalidNativeRemoteAddress({remoteToken: map.remoteToken});
2031
+ if (map.localToken == JBConstants.NATIVE_TOKEN) {
2032
+ if (map.remoteToken != _toBytes32(JBConstants.NATIVE_TOKEN) && map.remoteToken != bytes32(0)) {
2033
+ revert JBSucker_InvalidNativeRemoteAddress({remoteToken: map.remoteToken});
2034
+ }
2035
+ return;
2045
2036
  }
2046
2037
 
2047
2038
  // Enforce a reasonable minimum gas limit for bridging. A minimum which is too low could lead to the loss of
2048
2039
  // funds.
2049
- if (map.minGas < MESSENGER_ERC20_MIN_GAS_LIMIT && !isNative) {
2040
+ if (map.minGas < MESSENGER_ERC20_MIN_GAS_LIMIT) {
2050
2041
  revert JBSucker_BelowMinGas({minGas: map.minGas, minGasLimit: MESSENGER_ERC20_MIN_GAS_LIMIT});
2051
2042
  }
2052
2043
  }