@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 +2 -1
- package/package.json +1 -1
- package/references/entrypoints.md +6 -2
- package/references/operations.md +3 -1
- package/references/runtime.md +2 -2
- package/src/JBSucker.sol +42 -51
- package/src/JBSuckerRegistry.sol +319 -170
- package/src/interfaces/IJBSucker.sol +8 -5
- package/src/interfaces/IJBSuckerRegistry.sol +70 -0
- package/src/libraries/JBSuckerLib.sol +16 -15
- package/src/structs/JBChainAccounting.sol +13 -14
- package/src/structs/JBPeerChainContext.sol +4 -4
- package/src/structs/JBSourceContext.sol +7 -10
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
|
@@ -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. |
|
package/references/operations.md
CHANGED
|
@@ -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
|
|
package/references/runtime.md
CHANGED
|
@@ -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
|
|
336
|
-
/// @dev Stored
|
|
337
|
-
///
|
|
338
|
-
///
|
|
339
|
-
///
|
|
340
|
-
/// @custom:param chainId The peer chain to read the
|
|
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
|
|
886
|
-
/// @dev The registry reads this to gather a project's full cross-chain knowledge and re-gossip it.
|
|
887
|
-
///
|
|
888
|
-
///
|
|
889
|
-
/// @return accounts One
|
|
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
|
|
911
|
-
/// together. The result is un-valued — the registry values each context into
|
|
912
|
-
/// consults no price oracle. Contexts that share a currency but carry different
|
|
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(),
|
|
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 !=
|
|
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 (
|
|
1363
|
-
address mappedLocalToken = _localTokenForRemoteToken[
|
|
1349
|
+
if (remoteToken != bytes32(0)) {
|
|
1350
|
+
address mappedLocalToken = _localTokenForRemoteToken[remoteToken];
|
|
1364
1351
|
if (mappedLocalToken != address(0) && mappedLocalToken != token) {
|
|
1365
|
-
revert JBSucker_RemoteTokenAlreadyMapped({remoteToken:
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
1604
|
-
///
|
|
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
|
|
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
|
|
1638
|
-
// Each context
|
|
1639
|
-
//
|
|
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 (
|
|
2044
|
-
|
|
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
|
|
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
|
}
|