@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.
@@ -1,10 +1,13 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity ^0.8.0;
3
3
 
4
- import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
5
4
  import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
6
5
  import {IJBProjects} from "@bananapus/core-v6/src/interfaces/IJBProjects.sol";
7
6
  import {IJBTokens} from "@bananapus/core-v6/src/interfaces/IJBTokens.sol";
7
+ import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
8
+
9
+ import {JBSuckerState} from "../enums/JBSuckerState.sol";
10
+
8
11
  import {JBAccountingSnapshot} from "../structs/JBAccountingSnapshot.sol";
9
12
  import {JBChainAccounting} from "../structs/JBChainAccounting.sol";
10
13
  import {JBClaim} from "../structs/JBClaim.sol";
@@ -13,7 +16,6 @@ import {JBOutboxTree} from "../structs/JBOutboxTree.sol";
13
16
  import {JBPeerChainContext} from "../structs/JBPeerChainContext.sol";
14
17
  import {JBPeerChainValue} from "../structs/JBPeerChainValue.sol";
15
18
  import {JBRemoteToken} from "../structs/JBRemoteToken.sol";
16
- import {JBSuckerState} from "../enums/JBSuckerState.sol";
17
19
  import {JBTokenMapping} from "../structs/JBTokenMapping.sol";
18
20
 
19
21
  /// @notice The minimal interface for a sucker contract.
@@ -158,10 +160,11 @@ interface IJBSucker is IERC165 {
158
160
  /// @return The peer address.
159
161
  function peer() external view returns (bytes32);
160
162
 
161
- /// @notice The raw, un-valued accounting record this sucker holds for every peer chain it has heard about.
163
+ /// @notice The un-valued accounting record this sucker holds for every peer chain it has heard about.
162
164
  /// @dev The registry reads this to gather a project's full cross-chain knowledge and re-gossip it. Records are
163
- /// returned exactly as received so the next receiver resolves them to its own local currencies independently.
164
- /// @return accounts One raw accounting record per known peer chain.
165
+ /// keyed to this sucker's local token when a source token matches a remote-token mapping. The next receiver only
166
+ /// needs its mapping to this peer's token.
167
+ /// @return accounts One accounting record per known peer chain.
165
168
  function peerChainAccountsOf() external view returns (JBChainAccounting[] memory accounts);
166
169
 
167
170
  /// @notice One peer chain's per-currency surplus and balance from its latest accepted record, plus its freshness
@@ -35,6 +35,20 @@ interface IJBSuckerRegistry {
35
35
  /// @param caller The address that removed the sucker.
36
36
  event SuckerDeprecated(uint256 projectId, address sucker, address caller);
37
37
 
38
+ /// @notice Emitted when a local-to-remote token mapping is added to the allowlist.
39
+ /// @param localToken The local token address.
40
+ /// @param remoteChainId The ID of the remote chain.
41
+ /// @param remoteToken The remote token address encoded as bytes32.
42
+ /// @param caller The address that allowed the token mapping.
43
+ event TokenMappingAllowed(address localToken, uint256 remoteChainId, bytes32 remoteToken, address caller);
44
+
45
+ /// @notice Emitted when a local-to-remote token mapping is removed from the allowlist.
46
+ /// @param localToken The local token address.
47
+ /// @param remoteChainId The ID of the remote chain.
48
+ /// @param remoteToken The remote token address encoded as bytes32.
49
+ /// @param caller The address that removed the token mapping.
50
+ event TokenMappingRemoved(address localToken, uint256 remoteChainId, bytes32 remoteToken, address caller);
51
+
38
52
  /// @notice Emitted when the toRemoteFee is changed.
39
53
  /// @param oldFee The previous fee.
40
54
  /// @param newFee The new fee.
@@ -90,6 +104,14 @@ interface IJBSuckerRegistry {
90
104
  /// @return totalSupply The combined peer chain total supply.
91
105
  function remoteTotalSupplyOf(uint256 projectId) external view returns (uint256 totalSupply);
92
106
 
107
+ /// @notice Reverts unless a token mapping can be chosen by a project.
108
+ /// @dev Disable mappings never require owner approval. Non-native same-address mappings pass through directly.
109
+ /// Native-to-native and differing-address mappings must be explicitly allowed.
110
+ /// @param localToken The local token address.
111
+ /// @param remoteChainId The ID of the remote chain.
112
+ /// @param remoteToken The remote token address encoded as bytes32.
113
+ function requireTokenMappingAllowed(address localToken, uint256 remoteChainId, bytes32 remoteToken) external view;
114
+
93
115
  /// @notice Whether the specified sucker deployer is approved by this registry.
94
116
  /// @param deployer The address of the deployer to check.
95
117
  /// @return Whether the deployer is allowed.
@@ -105,6 +127,20 @@ interface IJBSuckerRegistry {
105
127
  /// @return The addresses of the suckers.
106
128
  function suckersOf(uint256 projectId) external view returns (address[] memory);
107
129
 
130
+ /// @notice Whether a local-to-remote token mapping is approved by this registry.
131
+ /// @param localToken The local token address.
132
+ /// @param remoteChainId The ID of the remote chain.
133
+ /// @param remoteToken The remote token address encoded as bytes32.
134
+ /// @return Whether the token mapping is allowed.
135
+ function tokenMappingIsAllowed(
136
+ address localToken,
137
+ uint256 remoteChainId,
138
+ bytes32 remoteToken
139
+ )
140
+ external
141
+ view
142
+ returns (bool);
143
+
108
144
  /// @notice The ETH fee (in wei) paid into the fee project on each toRemote() call.
109
145
  /// @return The current fee.
110
146
  function toRemoteFee() external view returns (uint256);
@@ -153,6 +189,23 @@ interface IJBSuckerRegistry {
153
189
  /// @param deployers The addresses of the deployers to allow.
154
190
  function allowSuckerDeployers(address[] calldata deployers) external;
155
191
 
192
+ /// @notice Add a local-to-remote token mapping to the allowlist.
193
+ /// @param localToken The local token address.
194
+ /// @param remoteChainId The ID of the remote chain.
195
+ /// @param remoteToken The remote token address encoded as bytes32.
196
+ function allowTokenMapping(address localToken, uint256 remoteChainId, bytes32 remoteToken) external;
197
+
198
+ /// @notice Add multiple local-to-remote token mappings to the allowlist.
199
+ /// @param localTokens The local token addresses.
200
+ /// @param remoteChainIds The remote chain IDs.
201
+ /// @param remoteTokens The remote token addresses encoded as bytes32.
202
+ function allowTokenMappings(
203
+ address[] calldata localTokens,
204
+ uint256[] calldata remoteChainIds,
205
+ bytes32[] calldata remoteTokens
206
+ )
207
+ external;
208
+
156
209
  /// @notice Deploy one or more suckers for the specified project.
157
210
  /// @dev This call also applies each configuration's token mappings on the deployed suckers. `DEPLOY_SUCKERS`
158
211
  /// authorizes those initial mappings; use `MAP_SUCKER_TOKEN` for post-deployment mapping changes.
@@ -177,6 +230,23 @@ interface IJBSuckerRegistry {
177
230
  /// @param deployer The address of the deployer to remove.
178
231
  function removeSuckerDeployer(address deployer) external;
179
232
 
233
+ /// @notice Remove a local-to-remote token mapping from the allowlist.
234
+ /// @param localToken The local token address.
235
+ /// @param remoteChainId The ID of the remote chain.
236
+ /// @param remoteToken The remote token address encoded as bytes32.
237
+ function removeTokenMapping(address localToken, uint256 remoteChainId, bytes32 remoteToken) external;
238
+
239
+ /// @notice Remove multiple local-to-remote token mappings from the allowlist.
240
+ /// @param localTokens The local token addresses.
241
+ /// @param remoteChainIds The remote chain IDs.
242
+ /// @param remoteTokens The remote token addresses encoded as bytes32.
243
+ function removeTokenMappings(
244
+ address[] calldata localTokens,
245
+ uint256[] calldata remoteChainIds,
246
+ bytes32[] calldata remoteTokens
247
+ )
248
+ external;
249
+
180
250
  /// @notice Set the ETH fee (in wei) paid on each toRemote() call. Owner only.
181
251
  /// @param fee The new fee amount in wei.
182
252
  function setToRemoteFee(uint256 fee) external;
@@ -5,22 +5,24 @@ import {IJBController} from "@bananapus/core-v6/src/interfaces/IJBController.sol
5
5
  import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
6
6
  import {IJBMultiTerminal} from "@bananapus/core-v6/src/interfaces/IJBMultiTerminal.sol";
7
7
  import {IJBTerminal} from "@bananapus/core-v6/src/interfaces/IJBTerminal.sol";
8
- import {JBAccountingContext} from "@bananapus/core-v6/src/structs/JBAccountingContext.sol";
9
8
  import {JBRulesetMetadataResolver} from "@bananapus/core-v6/src/libraries/JBRulesetMetadataResolver.sol";
9
+ import {JBAccountingContext} from "@bananapus/core-v6/src/structs/JBAccountingContext.sol";
10
10
  import {JBRuleset} from "@bananapus/core-v6/src/structs/JBRuleset.sol";
11
11
  import {JBRulesetMetadata} from "@bananapus/core-v6/src/structs/JBRulesetMetadata.sol";
12
12
  import {IERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
13
13
 
14
14
  import {IJBPeerChainAdjustedAccounts} from "../interfaces/IJBPeerChainAdjustedAccounts.sol";
15
15
  import {IJBSuckerRegistry} from "../interfaces/IJBSuckerRegistry.sol";
16
+
16
17
  import {JBPeerChainAdjustedAccountsLib} from "./JBPeerChainAdjustedAccountsLib.sol";
18
+ import {MerkleLib} from "../utils/MerkleLib.sol";
19
+
17
20
  import {JBAccountingSnapshot} from "../structs/JBAccountingSnapshot.sol";
18
21
  import {JBChainAccounting} from "../structs/JBChainAccounting.sol";
19
22
  import {JBInboxTreeRoot} from "../structs/JBInboxTreeRoot.sol";
20
23
  import {JBMessageRoot} from "../structs/JBMessageRoot.sol";
21
24
  import {JBPeerChainContext} from "../structs/JBPeerChainContext.sol";
22
25
  import {JBSourceContext} from "../structs/JBSourceContext.sol";
23
- import {MerkleLib} from "../utils/MerkleLib.sol";
24
26
 
25
27
  /// @notice Library with bytecode-heavy functions extracted from JBSucker to reduce child contract sizes.
26
28
  /// @dev These are `external` library functions, so they are deployed as a separate contract and called via
@@ -186,23 +188,20 @@ library JBSuckerLib {
186
188
  }
187
189
  }
188
190
 
189
- /// @notice Folds a peer chain's raw source contexts into per-currency surplus and balance, resolving each to a
190
- /// local currency.
191
+ /// @notice Folds a peer chain's source contexts into per-currency surplus and balance.
191
192
  /// @dev Extracted from `JBSucker.peerChainContextsOf` to reduce child contract bytecode. Called via DELEGATECALL.
192
- /// Each `localTokens[i]` is the local token the sucker resolved `rawContexts[i].token` to (via its token mapping or
193
- /// identity); this derives that token's authoritative accounting-context currency and merges entries that share
194
- /// BOTH currency AND decimals. The accounting-context currency is immutable, so re-resolving on each read is safe.
195
- /// Entries that share a currency but carry different decimals stay separate, since the raw amounts are on different
196
- /// scales. No price oracle is consulted.
193
+ /// The caller stores mapped remote-token keys in its local token namespace before this runs. This derives each
194
+ /// token's authoritative accounting-context currency and merges entries that share BOTH currency AND decimals. The
195
+ /// accounting-context currency is immutable, so re-resolving on each read is safe. Entries that share a currency
196
+ /// but carry different decimals stay separate, since the raw amounts are on different scales. No price oracle is
197
+ /// consulted.
197
198
  /// @param directory The JB directory to look up the project's terminals.
198
199
  /// @param projectId The project whose accounting contexts to read.
199
- /// @param localTokens The local token each raw context resolves to, parallel to `rawContexts`.
200
- /// @param rawContexts The peer chain's raw per-context surplus and balance.
200
+ /// @param rawContexts The peer chain's per-context surplus and balance.
201
201
  /// @return contexts The per-currency surplus and balance for the chain.
202
202
  function foldPeerContexts(
203
203
  IJBDirectory directory,
204
204
  uint256 projectId,
205
- address[] memory localTokens,
206
205
  JBSourceContext[] memory rawContexts
207
206
  )
208
207
  external
@@ -219,7 +218,8 @@ library JBSuckerLib {
219
218
  uint8 ctxDecimals = rawContexts[i].decimals;
220
219
  uint128 ctxSurplus = rawContexts[i].surplus;
221
220
  uint128 ctxBalance = rawContexts[i].balance;
222
- uint32 ctxCurrency = _currencyOf({directory: directory, projectId: projectId, token: localTokens[i]});
221
+ address token = address(uint160(uint256(rawContexts[i].token)));
222
+ uint32 ctxCurrency = _currencyOf({directory: directory, projectId: projectId, token: token});
223
223
 
224
224
  // Fold into an existing entry that matches on BOTH currency AND decimals, or append a new one.
225
225
  bool merged;
@@ -265,7 +265,8 @@ library JBSuckerLib {
265
265
  /// peer records are gathered from the registry, which is the only contract that sees a hub chain's per-peer
266
266
  /// suckers together; it dedups them to the freshest per chain. A reverting or unset registry yields a local-only
267
267
  /// bundle, so a standalone sucker still propagates its own record. Forwarded peer records keep their own origin
268
- /// chain and freshness key so the receiver gates each chain independently.
268
+ /// chain and freshness key, while their context token keys are already localized by the sucker they were gathered
269
+ /// from so the receiver only needs its mapping to the direct peer's token.
269
270
  /// @param directory The JB directory to look up controllers and terminals.
270
271
  /// @param registry The sucker registry that aggregates the project's per-chain records.
271
272
  /// @param projectId The project to snapshot.
@@ -297,7 +298,7 @@ library JBSuckerLib {
297
298
  peers = gathered;
298
299
  } catch {}
299
300
 
300
- // The local record leads; forwarded peer records follow verbatim, keeping their own origin chain and freshness.
301
+ // The local record leads; forwarded peer records follow, keeping their own origin chain and freshness.
301
302
  accounts = new JBChainAccounting[](peers.length + 1);
302
303
  accounts[0] = JBChainAccounting({
303
304
  chainId: block.chainid, totalSupply: localTotalSupply, contexts: localContexts, timestamp: sourceTimestamp
@@ -4,24 +4,23 @@ pragma solidity ^0.8.0;
4
4
  import {JBSourceContext} from "./JBSourceContext.sol";
5
5
 
6
6
  /// @notice One source chain's project-wide accounting, carried as a record in a cross-chain gossip bundle.
7
- /// @dev A sucker sends its own chain's record alongside every peer-chain record it already holds, each stamped with
8
- /// the originating chain's own freshness key. The receiving chain stores the freshest record per source chain, so a
9
- /// project's accounting propagates across a hub-and-spoke sucker mesh (L2s bridged only through mainnet) without a
10
- /// direct sucker between every pair of chains. `contexts` carry the source chain's own token addresses, so each
11
- /// receiver resolves them to its own local currencies independently. Trust is transitive across the mesh: a receiver
12
- /// authenticates only the directly-bridged peer that delivers a bundle, not the origin of each forwarded record, so
13
- /// any authenticated peer can forward a record for any other chain. A record is therefore only as trustworthy as the
14
- /// project's same-address sucker invariant — the same CREATE2 same-bytecode assumption every paired sucker already
15
- /// relies on — and a peer running adversarial bytecode could forge another chain's record. The freshest-per-chain
16
- /// gate bounds rollback, not authorship; the supply view it feeds is clamped downstream by each chain's own local
17
- /// surplus, so a forged record cannot by itself over-credit a cash out.
7
+ /// @dev A sucker sends its own chain's record alongside every peer-chain record it already holds. Each record is
8
+ /// stamped with its originating chain's freshness key. The receiving chain stores the freshest record per source chain,
9
+ /// so a project's accounting propagates across a hub-and-spoke sucker mesh without a direct sucker between every pair
10
+ /// of chains. `contexts` are un-valued token-denominated amounts. A direct sender's own record carries source-chain
11
+ /// token addresses. Forwarded peer records use the forwarding hub's local same-asset token when a mapping exists, so
12
+ /// the next receiver only needs a mapping to that hub token. Trust is transitive across the mesh: a receiver
13
+ /// authenticates only the directly-bridged peer that delivers a bundle, not the origin of each forwarded record. A
14
+ /// record is therefore only as trustworthy as the project's same-address sucker invariant — the same CREATE2
15
+ /// same-bytecode assumption every paired sucker already relies on — and a peer running adversarial bytecode could
16
+ /// forge another chain's record. The freshest-per-chain gate bounds rollback, not authorship; the supply view it feeds
17
+ /// is clamped downstream by each chain's own local surplus, so a forged record cannot by itself over-credit a cash out.
18
18
  /// @custom:member chainId The source chain this record describes. A receiver ignores a record for its own chain, since
19
19
  /// it reads its own local accounting directly.
20
20
  /// @custom:member totalSupply The total token supply (including reserved tokens) on the source chain when the record
21
21
  /// was taken. Used by the receiving chain to track cross-chain supply for cash out tax calculations.
22
- /// @custom:member contexts The source chain's surplus and balance per accounting context, each in the context's own
23
- /// currency and decimals, un-valued. The receiver resolves each entry to its same-asset local context and folds it in
24
- /// at par.
22
+ /// @custom:member contexts The source chain's surplus and balance per accounting context, each in its own decimals and
23
+ /// un-valued. The token key may be the source token for direct records or a forwarding hub's local same-asset token.
25
24
  /// @custom:member timestamp A monotonic source-chain freshness key for the record. The receiver keeps the freshest
26
25
  /// record per source chain, so stale relays cannot roll back surplus, balance, or supply.
27
26
  struct JBChainAccounting {
@@ -3,10 +3,10 @@ pragma solidity ^0.8.0;
3
3
 
4
4
  /// @notice A remote chain's surplus and balance for one local currency, as last received.
5
5
  /// @dev One entry per local currency in the latest snapshot. The local currency is the project's authoritative
6
- /// accounting-context currency for the token a remote context resolves to (identity for same-address tokens, or via
7
- /// the sucker's token mapping for same-asset tokens at different addresses). `surplus`/`balance` are raw, un-valued
8
- /// amounts in that currency's own units; a read values them into a requested currency via the prices contract, exactly
9
- /// as the terminal store values local surplus (an identity short-circuit means same-currency reads are taken at par).
6
+ /// accounting-context currency for the stored token key. A receiver stores mapped remote-token keys under its local
7
+ /// same-asset token before this view is built. `surplus`/`balance` are raw, un-valued amounts in that currency's own
8
+ /// units; a read values them into a requested currency via the prices contract, exactly as the terminal store values
9
+ /// local surplus (an identity short-circuit means same-currency reads are taken at par).
10
10
  /// @custom:member currency The local currency this context resolves to.
11
11
  /// @custom:member decimals The context's native decimal precision, used to rescale to a requested precision.
12
12
  /// @custom:member surplus The raw surplus held in this currency on the peer chain.
@@ -1,17 +1,14 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity ^0.8.0;
3
3
 
4
- /// @notice One source-chain accounting context, carried raw (un-valued) in a cross-chain snapshot.
4
+ /// @notice One source-chain accounting context, carried un-valued in a cross-chain snapshot.
5
5
  /// @dev The source chain reports its surplus and balance per accounting context in that context's own decimals — it
6
- /// performs no price-feed valuation. The destination chain resolves `token` to its own local token (identity for
7
- /// same-address tokens, or via the sucker's remote-to-local token mapping for same-asset tokens at different
8
- /// addresses), derives that local token's currency as `uint32(uint160(localToken))`, and folds the context into its
9
- /// same-currency local context at par. The destination derives the currency from the resolved local token rather than
10
- /// trusting a wire-carried currency, so a same-asset token at a different address (e.g. USDC) still folds under the
11
- /// receiver's own currency. The only conversions ever performed are the ones a project already needs for its own local
12
- /// surplus.
13
- /// @custom:member token The source-local token this context was read from, resolved to a local token (and thence a
14
- /// local currency) on receipt. Padded to bytes32 for cross-VM compatibility.
6
+ /// performs no price-feed valuation. A receiver stores `token` as its own local token when a remote-token mapping
7
+ /// exists, or leaves the token key unchanged otherwise. Read paths derive the context's currency from that stored token
8
+ /// key instead of trusting a wire-carried currency, so same-asset tokens at different addresses (e.g. USDC) still fold
9
+ /// under the receiver's own currency. Any later valuation uses the project's normal local surplus conversions.
10
+ /// @custom:member token The source token key, or a receiver-local token key after a remote-token mapping has been
11
+ /// applied. Padded to bytes32 for cross-VM compatibility.
15
12
  /// @custom:member decimals The context's native decimal precision (e.g. 18 for ETH, 6 for USDC).
16
13
  /// @custom:member surplus The raw, un-valued surplus held in this context, in the context's own units. Capped to
17
14
  /// `uint128` for cross-VM (SVM) compatibility, matching the leaf-amount cap.