@bananapus/suckers-v6 0.0.68 → 0.0.69
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/script/Deploy.s.sol +3 -11
- package/src/JBArbitrumSucker.sol +1 -4
- package/src/JBBaseSucker.sol +1 -4
- package/src/JBCCIPSucker.sol +1 -4
- package/src/JBOptimismSucker.sol +1 -4
- package/src/JBSucker.sol +141 -119
- package/src/JBSuckerRegistry.sol +224 -59
- package/src/archive/JBCeloSucker.sol +1 -4
- package/src/archive/JBSwapCCIPSucker.sol +1 -4
- package/src/interfaces/IJBPeerChainAdjustedAccounts.sol +10 -12
- package/src/interfaces/IJBSucker.sol +12 -32
- package/src/interfaces/IJBSuckerRegistry.sol +26 -24
- package/src/libraries/JBSuckerLib.sol +151 -172
- package/src/structs/JBMessageRoot.sol +9 -12
- package/src/structs/JBPeerChainContext.sol +19 -0
- package/src/structs/JBSourceContext.sol +25 -0
- package/src/structs/JBDenominatedAmount.sol +0 -12
package/package.json
CHANGED
package/script/Deploy.s.sol
CHANGED
|
@@ -84,7 +84,7 @@ contract DeployScript is Script, Sphinx {
|
|
|
84
84
|
bool registryAlreadyDeployed = _isDeployed({
|
|
85
85
|
salt: _REGISTRY_SALT,
|
|
86
86
|
creationCode: type(JBSuckerRegistry).creationCode,
|
|
87
|
-
arguments: abi.encode(core.directory, core.permissions, safeAddress(), trustedForwarder)
|
|
87
|
+
arguments: abi.encode(core.directory, core.permissions, core.prices, safeAddress(), trustedForwarder)
|
|
88
88
|
});
|
|
89
89
|
|
|
90
90
|
if (!registryAlreadyDeployed) {
|
|
@@ -93,6 +93,7 @@ contract DeployScript is Script, Sphinx {
|
|
|
93
93
|
new JBSuckerRegistry{salt: _REGISTRY_SALT}({
|
|
94
94
|
directory: core.directory,
|
|
95
95
|
permissions: core.permissions,
|
|
96
|
+
prices: core.prices,
|
|
96
97
|
initialOwner: safeAddress(),
|
|
97
98
|
trustedForwarder: trustedForwarder
|
|
98
99
|
})
|
|
@@ -106,7 +107,7 @@ contract DeployScript is Script, Sphinx {
|
|
|
106
107
|
initCodeHash: keccak256(
|
|
107
108
|
abi.encodePacked(
|
|
108
109
|
type(JBSuckerRegistry).creationCode,
|
|
109
|
-
abi.encode(core.directory, core.permissions, safeAddress(), trustedForwarder)
|
|
110
|
+
abi.encode(core.directory, core.permissions, core.prices, safeAddress(), trustedForwarder)
|
|
110
111
|
)
|
|
111
112
|
),
|
|
112
113
|
deployer: address(0x4e59b44847b379578588920cA78FbF26c0B4956C)
|
|
@@ -210,7 +211,6 @@ contract DeployScript is Script, Sphinx {
|
|
|
210
211
|
deployer: _opDeployer,
|
|
211
212
|
directory: core.directory,
|
|
212
213
|
permissions: core.permissions,
|
|
213
|
-
prices: core.prices,
|
|
214
214
|
tokens: core.tokens,
|
|
215
215
|
feeProjectId: 1,
|
|
216
216
|
registry: registry,
|
|
@@ -262,7 +262,6 @@ contract DeployScript is Script, Sphinx {
|
|
|
262
262
|
deployer: _opDeployer,
|
|
263
263
|
directory: core.directory,
|
|
264
264
|
permissions: core.permissions,
|
|
265
|
-
prices: core.prices,
|
|
266
265
|
tokens: core.tokens,
|
|
267
266
|
feeProjectId: 1,
|
|
268
267
|
registry: registry,
|
|
@@ -345,7 +344,6 @@ contract DeployScript is Script, Sphinx {
|
|
|
345
344
|
deployer: _baseDeployer,
|
|
346
345
|
directory: core.directory,
|
|
347
346
|
permissions: core.permissions,
|
|
348
|
-
prices: core.prices,
|
|
349
347
|
tokens: core.tokens,
|
|
350
348
|
feeProjectId: 1,
|
|
351
349
|
registry: registry,
|
|
@@ -397,7 +395,6 @@ contract DeployScript is Script, Sphinx {
|
|
|
397
395
|
deployer: _baseDeployer,
|
|
398
396
|
directory: core.directory,
|
|
399
397
|
permissions: core.permissions,
|
|
400
|
-
prices: core.prices,
|
|
401
398
|
tokens: core.tokens,
|
|
402
399
|
feeProjectId: 1,
|
|
403
400
|
registry: registry,
|
|
@@ -476,7 +473,6 @@ contract DeployScript is Script, Sphinx {
|
|
|
476
473
|
deployer: _arbDeployer,
|
|
477
474
|
directory: core.directory,
|
|
478
475
|
permissions: core.permissions,
|
|
479
|
-
prices: core.prices,
|
|
480
476
|
tokens: core.tokens,
|
|
481
477
|
feeProjectId: 1,
|
|
482
478
|
registry: registry,
|
|
@@ -531,7 +527,6 @@ contract DeployScript is Script, Sphinx {
|
|
|
531
527
|
deployer: _arbDeployer,
|
|
532
528
|
directory: core.directory,
|
|
533
529
|
permissions: core.permissions,
|
|
534
|
-
prices: core.prices,
|
|
535
530
|
tokens: core.tokens,
|
|
536
531
|
feeProjectId: 1,
|
|
537
532
|
registry: registry,
|
|
@@ -708,7 +703,6 @@ contract DeployScript is Script, Sphinx {
|
|
|
708
703
|
salt: salt,
|
|
709
704
|
directory: core.directory,
|
|
710
705
|
permissions: core.permissions,
|
|
711
|
-
prices: core.prices,
|
|
712
706
|
tokens: core.tokens,
|
|
713
707
|
configurator: safeAddress(),
|
|
714
708
|
forwarder: trustedForwarder,
|
|
@@ -725,7 +719,6 @@ contract DeployScript is Script, Sphinx {
|
|
|
725
719
|
bytes32 salt,
|
|
726
720
|
IJBDirectory directory,
|
|
727
721
|
IJBPermissions permissions,
|
|
728
|
-
IJBPrices prices,
|
|
729
722
|
IJBTokens tokens,
|
|
730
723
|
address configurator,
|
|
731
724
|
address forwarder,
|
|
@@ -781,7 +774,6 @@ contract DeployScript is Script, Sphinx {
|
|
|
781
774
|
directory: directory,
|
|
782
775
|
tokens: tokens,
|
|
783
776
|
permissions: permissions,
|
|
784
|
-
prices: prices,
|
|
785
777
|
feeProjectId: 1,
|
|
786
778
|
registry: registry,
|
|
787
779
|
trustedForwarder: forwarder
|
package/src/JBArbitrumSucker.sol
CHANGED
|
@@ -8,7 +8,6 @@ import {AddressAliasHelper} from "@arbitrum/nitro-contracts/src/libraries/Addres
|
|
|
8
8
|
import {ArbSys} from "@arbitrum/nitro-contracts/src/precompiles/ArbSys.sol";
|
|
9
9
|
import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
|
|
10
10
|
import {IJBPermissions} from "@bananapus/core-v6/src/interfaces/IJBPermissions.sol";
|
|
11
|
-
import {IJBPrices} from "@bananapus/core-v6/src/interfaces/IJBPrices.sol";
|
|
12
11
|
import {IJBTokens} from "@bananapus/core-v6/src/interfaces/IJBTokens.sol";
|
|
13
12
|
import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
|
|
14
13
|
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
@@ -56,19 +55,17 @@ contract JBArbitrumSucker is JBSucker, IJBArbitrumSucker {
|
|
|
56
55
|
|
|
57
56
|
/// @param directory A contract storing directories of terminals and controllers for each project.
|
|
58
57
|
/// @param permissions A contract storing permissions.
|
|
59
|
-
/// @param prices The price oracle used to convert peer-chain balances and surplus.
|
|
60
58
|
/// @param tokens A contract that manages token minting and burning.
|
|
61
59
|
constructor(
|
|
62
60
|
JBArbitrumSuckerDeployer deployer,
|
|
63
61
|
IJBDirectory directory,
|
|
64
62
|
IJBPermissions permissions,
|
|
65
|
-
IJBPrices prices,
|
|
66
63
|
IJBTokens tokens,
|
|
67
64
|
uint256 feeProjectId,
|
|
68
65
|
IJBSuckerRegistry registry,
|
|
69
66
|
address trustedForwarder
|
|
70
67
|
)
|
|
71
|
-
JBSucker(directory, permissions,
|
|
68
|
+
JBSucker(directory, permissions, tokens, feeProjectId, registry, trustedForwarder)
|
|
72
69
|
{
|
|
73
70
|
GATEWAYROUTER = JBArbitrumSuckerDeployer(deployer).arbGatewayRouter();
|
|
74
71
|
ARBINBOX = JBArbitrumSuckerDeployer(deployer).arbInbox();
|
package/src/JBBaseSucker.sol
CHANGED
|
@@ -3,7 +3,6 @@ pragma solidity 0.8.28;
|
|
|
3
3
|
|
|
4
4
|
import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
|
|
5
5
|
import {IJBPermissions} from "@bananapus/core-v6/src/interfaces/IJBPermissions.sol";
|
|
6
|
-
import {IJBPrices} from "@bananapus/core-v6/src/interfaces/IJBPrices.sol";
|
|
7
6
|
import {IJBTokens} from "@bananapus/core-v6/src/interfaces/IJBTokens.sol";
|
|
8
7
|
|
|
9
8
|
import {JBOptimismSucker} from "./JBOptimismSucker.sol";
|
|
@@ -19,19 +18,17 @@ contract JBBaseSucker is JBOptimismSucker {
|
|
|
19
18
|
/// @param deployer A contract that deploys clones of this contract.
|
|
20
19
|
/// @param directory A contract storing directories of terminals and controllers for each project.
|
|
21
20
|
/// @param permissions A contract storing permissions.
|
|
22
|
-
/// @param prices The price oracle used to convert peer-chain balances and surplus.
|
|
23
21
|
/// @param tokens A contract that manages token minting and burning.
|
|
24
22
|
constructor(
|
|
25
23
|
JBOptimismSuckerDeployer deployer,
|
|
26
24
|
IJBDirectory directory,
|
|
27
25
|
IJBPermissions permissions,
|
|
28
|
-
IJBPrices prices,
|
|
29
26
|
IJBTokens tokens,
|
|
30
27
|
uint256 feeProjectId,
|
|
31
28
|
IJBSuckerRegistry registry,
|
|
32
29
|
address trustedForwarder
|
|
33
30
|
)
|
|
34
|
-
JBOptimismSucker(deployer, directory, permissions,
|
|
31
|
+
JBOptimismSucker(deployer, directory, permissions, tokens, feeProjectId, registry, trustedForwarder)
|
|
35
32
|
{}
|
|
36
33
|
|
|
37
34
|
//*********************************************************************//
|
package/src/JBCCIPSucker.sol
CHANGED
|
@@ -4,7 +4,6 @@ pragma solidity 0.8.28;
|
|
|
4
4
|
// External packages (alphabetized)
|
|
5
5
|
import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
|
|
6
6
|
import {IJBPermissions} from "@bananapus/core-v6/src/interfaces/IJBPermissions.sol";
|
|
7
|
-
import {IJBPrices} from "@bananapus/core-v6/src/interfaces/IJBPrices.sol";
|
|
8
7
|
import {IJBTokens} from "@bananapus/core-v6/src/interfaces/IJBTokens.sol";
|
|
9
8
|
import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
|
|
10
9
|
import {IAny2EVMMessageReceiver} from "@chainlink/contracts-ccip/contracts/interfaces/IAny2EVMMessageReceiver.sol";
|
|
@@ -83,7 +82,6 @@ contract JBCCIPSucker is JBSucker, IAny2EVMMessageReceiver {
|
|
|
83
82
|
/// @param deployer A contract that deploys the clones for this contract.
|
|
84
83
|
/// @param directory A contract storing directories of terminals and controllers for each project.
|
|
85
84
|
/// @param permissions A contract storing permissions.
|
|
86
|
-
/// @param prices The price oracle used to convert peer-chain balances and surplus.
|
|
87
85
|
/// @param tokens A contract that manages token minting and burning.
|
|
88
86
|
/// @param feeProjectId The ID of the project that receives fees.
|
|
89
87
|
/// @param registry The sucker registry that tracks deployed suckers.
|
|
@@ -92,13 +90,12 @@ contract JBCCIPSucker is JBSucker, IAny2EVMMessageReceiver {
|
|
|
92
90
|
JBCCIPSuckerDeployer deployer,
|
|
93
91
|
IJBDirectory directory,
|
|
94
92
|
IJBPermissions permissions,
|
|
95
|
-
IJBPrices prices,
|
|
96
93
|
IJBTokens tokens,
|
|
97
94
|
uint256 feeProjectId,
|
|
98
95
|
IJBSuckerRegistry registry,
|
|
99
96
|
address trustedForwarder
|
|
100
97
|
)
|
|
101
|
-
JBSucker(directory, permissions,
|
|
98
|
+
JBSucker(directory, permissions, tokens, feeProjectId, registry, trustedForwarder)
|
|
102
99
|
{
|
|
103
100
|
// Read the remote chain ID from the deployer.
|
|
104
101
|
REMOTE_CHAIN_ID = IJBCCIPSuckerDeployer(deployer).ccipRemoteChainId();
|
package/src/JBOptimismSucker.sol
CHANGED
|
@@ -3,7 +3,6 @@ pragma solidity 0.8.28;
|
|
|
3
3
|
|
|
4
4
|
import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
|
|
5
5
|
import {IJBPermissions} from "@bananapus/core-v6/src/interfaces/IJBPermissions.sol";
|
|
6
|
-
import {IJBPrices} from "@bananapus/core-v6/src/interfaces/IJBPrices.sol";
|
|
7
6
|
import {IJBTokens} from "@bananapus/core-v6/src/interfaces/IJBTokens.sol";
|
|
8
7
|
import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
|
|
9
8
|
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
@@ -39,19 +38,17 @@ contract JBOptimismSucker is JBSucker, IJBOptimismSucker {
|
|
|
39
38
|
/// @param deployer A contract that deploys clones of this contract.
|
|
40
39
|
/// @param directory A contract storing directories of terminals and controllers for each project.
|
|
41
40
|
/// @param permissions A contract storing permissions.
|
|
42
|
-
/// @param prices The price oracle used to convert peer-chain balances and surplus.
|
|
43
41
|
/// @param tokens A contract that manages token minting and burning.
|
|
44
42
|
constructor(
|
|
45
43
|
JBOptimismSuckerDeployer deployer,
|
|
46
44
|
IJBDirectory directory,
|
|
47
45
|
IJBPermissions permissions,
|
|
48
|
-
IJBPrices prices,
|
|
49
46
|
IJBTokens tokens,
|
|
50
47
|
uint256 feeProjectId,
|
|
51
48
|
IJBSuckerRegistry registry,
|
|
52
49
|
address trustedForwarder
|
|
53
50
|
)
|
|
54
|
-
JBSucker(directory, permissions,
|
|
51
|
+
JBSucker(directory, permissions, tokens, feeProjectId, registry, trustedForwarder)
|
|
55
52
|
{
|
|
56
53
|
// Fetch the messenger and bridge by doing a callback to the deployer contract.
|
|
57
54
|
OPBRIDGE = JBOptimismSuckerDeployer(deployer).opBridge();
|
package/src/JBSucker.sol
CHANGED
|
@@ -7,11 +7,12 @@ import {IJBController} from "@bananapus/core-v6/src/interfaces/IJBController.sol
|
|
|
7
7
|
import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
|
|
8
8
|
import {IJBPermissioned} from "@bananapus/core-v6/src/interfaces/IJBPermissioned.sol";
|
|
9
9
|
import {IJBPermissions} from "@bananapus/core-v6/src/interfaces/IJBPermissions.sol";
|
|
10
|
-
import {IJBPrices} from "@bananapus/core-v6/src/interfaces/IJBPrices.sol";
|
|
11
10
|
import {IJBProjects} from "@bananapus/core-v6/src/interfaces/IJBProjects.sol";
|
|
12
11
|
import {IJBTerminal} from "@bananapus/core-v6/src/interfaces/IJBTerminal.sol";
|
|
13
12
|
import {IJBTokens} from "@bananapus/core-v6/src/interfaces/IJBTokens.sol";
|
|
13
|
+
import {JBAccountingContext} from "@bananapus/core-v6/src/structs/JBAccountingContext.sol";
|
|
14
14
|
import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
|
|
15
|
+
import {JBFixedPointNumber} from "@bananapus/core-v6/src/libraries/JBFixedPointNumber.sol";
|
|
15
16
|
import {JBPermissioned} from "@bananapus/core-v6/src/abstract/JBPermissioned.sol";
|
|
16
17
|
import {JBPermissionIds} from "@bananapus/permission-ids-v6/src/JBPermissionIds.sol";
|
|
17
18
|
import {BitMaps} from "@openzeppelin/contracts/utils/structs/BitMaps.sol";
|
|
@@ -36,9 +37,10 @@ import {MerkleLib} from "./utils/MerkleLib.sol";
|
|
|
36
37
|
|
|
37
38
|
// Local: structs (alphabetized)
|
|
38
39
|
import {JBClaim} from "./structs/JBClaim.sol";
|
|
39
|
-
import {JBDenominatedAmount} from "./structs/JBDenominatedAmount.sol";
|
|
40
40
|
import {JBInboxTreeRoot} from "./structs/JBInboxTreeRoot.sol";
|
|
41
41
|
import {JBMessageRoot} from "./structs/JBMessageRoot.sol";
|
|
42
|
+
import {JBPeerChainContext} from "./structs/JBPeerChainContext.sol";
|
|
43
|
+
import {JBSourceContext} from "./structs/JBSourceContext.sol";
|
|
42
44
|
import {JBOutboxTree} from "./structs/JBOutboxTree.sol";
|
|
43
45
|
import {JBPeerChainValue} from "./structs/JBPeerChainValue.sol";
|
|
44
46
|
import {JBRemoteToken} from "./structs/JBRemoteToken.sol";
|
|
@@ -132,9 +134,6 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
132
134
|
/// @notice The project ID that receives the `toRemoteFee` payment. Typically the protocol project (ID 1).
|
|
133
135
|
uint256 public immutable FEE_PROJECT_ID;
|
|
134
136
|
|
|
135
|
-
/// @notice The price oracle used to convert peer-chain balances and surplus.
|
|
136
|
-
IJBPrices public immutable PRICES;
|
|
137
|
-
|
|
138
137
|
/// @notice The project registry (ERC-721 ownership).
|
|
139
138
|
IJBProjects public immutable override PROJECTS;
|
|
140
139
|
|
|
@@ -177,6 +176,12 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
177
176
|
/// @notice The retained failed transport-payment refund ETH owed to each original bridge caller.
|
|
178
177
|
mapping(address account => uint256 amount) public retainedTransportPaymentRefundOf;
|
|
179
178
|
|
|
179
|
+
/// @notice The source chain freshness key for the most recent accepted peer snapshot.
|
|
180
|
+
/// @dev Only snapshots with a strictly newer source freshness key are accepted, preventing stale rollbacks.
|
|
181
|
+
/// The historical name is retained for ABI compatibility with the `JBMessageRoot.sourceTimestamp` field.
|
|
182
|
+
/// Returns 0 if no snapshot has been received yet.
|
|
183
|
+
uint256 public snapshotTimestamp;
|
|
184
|
+
|
|
180
185
|
//*********************************************************************//
|
|
181
186
|
// -------------------- internal stored properties ------------------- //
|
|
182
187
|
//*********************************************************************//
|
|
@@ -230,29 +235,28 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
230
235
|
// -------------------- private stored properties -------------------- //
|
|
231
236
|
//*********************************************************************//
|
|
232
237
|
|
|
238
|
+
/// @notice Caches a local token's authoritative accounting-context currency, derived once and reused on later
|
|
239
|
+
/// snapshots. A project's accounting-context currency is immutable once set, so the cached value never goes stale;
|
|
240
|
+
/// only an authoritative read is cached (a not-yet-configured token uses the convention without caching, so a later
|
|
241
|
+
/// snapshot re-reads it once its context exists).
|
|
242
|
+
/// @custom:param token The local token.
|
|
243
|
+
mapping(address token => uint32 currency) private _cachedCurrencyOf;
|
|
244
|
+
|
|
233
245
|
/// @notice The ID of the project (on the local chain) that this sucker is associated with.
|
|
234
246
|
uint256 private _localProjectId;
|
|
235
247
|
|
|
236
|
-
/// @notice
|
|
237
|
-
|
|
238
|
-
JBDenominatedAmount private _peerChainSurplus;
|
|
248
|
+
/// @notice Tie-breaker mixed into outbound snapshot freshness keys when multiple roots are sent at one timestamp.
|
|
249
|
+
uint256 private _outboundSnapshotSequence;
|
|
239
250
|
|
|
240
251
|
/// @notice Optional explicit peer sucker address on the remote chain.
|
|
241
252
|
/// @dev A zero value preserves the default same-address deterministic peer.
|
|
242
253
|
bytes32 private _peer;
|
|
243
254
|
|
|
244
|
-
/// @notice The
|
|
245
|
-
///
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
/// @dev Only snapshots with a strictly newer source freshness key are accepted, preventing stale rollbacks.
|
|
250
|
-
/// The historical name is retained for ABI compatibility with the `JBMessageRoot.sourceTimestamp` field.
|
|
251
|
-
/// Returns 0 if no snapshot has been received yet.
|
|
252
|
-
uint256 public snapshotTimestamp;
|
|
253
|
-
|
|
254
|
-
/// @notice Tie-breaker mixed into outbound snapshot freshness keys when multiple roots are sent at one timestamp.
|
|
255
|
-
uint256 private _outboundSnapshotSequence;
|
|
255
|
+
/// @notice The peer chain's per-currency surplus and balance from the latest snapshot. Rebuilt in full each time a
|
|
256
|
+
/// fresher snapshot is received, so a context that dropped out of the new snapshot is simply absent — no
|
|
257
|
+
/// per-entry
|
|
258
|
+
/// versioning or clearing is needed. A read sums these and values them into the requested currency.
|
|
259
|
+
JBPeerChainContext[] private _peerContexts;
|
|
256
260
|
|
|
257
261
|
//*********************************************************************//
|
|
258
262
|
// ---------------------------- constructor -------------------------- //
|
|
@@ -260,7 +264,6 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
260
264
|
|
|
261
265
|
/// @param directory A contract storing directories of terminals and controllers for each project.
|
|
262
266
|
/// @param permissions A contract storing permissions.
|
|
263
|
-
/// @param prices The price oracle used to convert peer-chain balances and surplus.
|
|
264
267
|
/// @param tokens A contract that manages token minting and burning.
|
|
265
268
|
/// @param feeProjectId The project ID that receives the `toRemoteFee` payment (typically 1).
|
|
266
269
|
/// @param registry The sucker registry that manages the global `toRemoteFee`.
|
|
@@ -268,7 +271,6 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
268
271
|
constructor(
|
|
269
272
|
IJBDirectory directory,
|
|
270
273
|
IJBPermissions permissions,
|
|
271
|
-
IJBPrices prices,
|
|
272
274
|
IJBTokens tokens,
|
|
273
275
|
uint256 feeProjectId,
|
|
274
276
|
IJBSuckerRegistry registry,
|
|
@@ -279,7 +281,6 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
279
281
|
{
|
|
280
282
|
DIRECTORY = directory;
|
|
281
283
|
FEE_PROJECT_ID = feeProjectId;
|
|
282
|
-
PRICES = prices;
|
|
283
284
|
PROJECTS = directory.PROJECTS();
|
|
284
285
|
REGISTRY = registry;
|
|
285
286
|
TOKENS = tokens;
|
|
@@ -514,18 +515,66 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
514
515
|
// This prevents a staler per-token message from rolling back shared state (surplus, balance, supply)
|
|
515
516
|
// that was already updated by a fresher message for a different token.
|
|
516
517
|
if (root.sourceTimestamp > snapshotTimestamp) {
|
|
518
|
+
// Advance the snapshot freshness key (used by the registry to dedup same-peer suckers).
|
|
517
519
|
snapshotTimestamp = root.sourceTimestamp;
|
|
518
520
|
|
|
519
521
|
// Update unconditionally — a legitimate zero supply must clear phantom cached supply.
|
|
520
522
|
peerChainTotalSupply = root.sourceTotalSupply;
|
|
521
523
|
|
|
522
|
-
//
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
524
|
+
// Rebuild the per-currency context set from scratch. A context that dropped out of this fresher snapshot is
|
|
525
|
+
// simply absent from the new set, so no per-entry clearing is needed.
|
|
526
|
+
delete _peerContexts;
|
|
527
|
+
|
|
528
|
+
// Fold each source context into the local currency it resolves to. Resolution prefers the token mapping (so
|
|
529
|
+
// a same-asset token at a different remote address binds to the right local context) and falls back to
|
|
530
|
+
// identity for same-address tokens; the local currency is then derived from that resolved local token's
|
|
531
|
+
// authoritative accounting context, NOT trusted from the wire, so a same-asset token at a different address
|
|
532
|
+
// still folds under the receiver's own currency. Multiple source contexts that resolve to the same local
|
|
533
|
+
// currency (e.g. the same token across multiple terminals) are summed.
|
|
534
|
+
uint256 numContexts = root.sourceContexts.length;
|
|
535
|
+
for (uint256 i; i < numContexts;) {
|
|
536
|
+
JBSourceContext calldata ctx = root.sourceContexts[i];
|
|
537
|
+
|
|
538
|
+
address contextToken = _localTokenForRemoteToken[ctx.token];
|
|
539
|
+
if (contextToken == address(0)) contextToken = _toAddress(ctx.token);
|
|
540
|
+
(uint32 contextCurrency, bool authoritative) = _localCurrencyOf(contextToken);
|
|
541
|
+
// Cache an authoritative currency so later snapshots reuse it instead of re-reading the terminal. The
|
|
542
|
+
// accounting-context currency is immutable, so the cache never goes stale; a not-yet-configured token
|
|
543
|
+
// is left uncached and re-read next time.
|
|
544
|
+
if (authoritative && _cachedCurrencyOf[contextToken] == 0) {
|
|
545
|
+
_cachedCurrencyOf[contextToken] = contextCurrency;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// Accumulate into an existing same-currency entry, or append a new one. The context set is small
|
|
549
|
+
// (one entry per distinct local currency), so a linear scan is cheaper than a mapping.
|
|
550
|
+
uint256 numStored = _peerContexts.length;
|
|
551
|
+
bool merged;
|
|
552
|
+
for (uint256 j; j < numStored;) {
|
|
553
|
+
if (_peerContexts[j].currency == contextCurrency) {
|
|
554
|
+
_peerContexts[j].surplus = _saturatingAddU128(_peerContexts[j].surplus, ctx.surplus);
|
|
555
|
+
_peerContexts[j].balance = _saturatingAddU128(_peerContexts[j].balance, ctx.balance);
|
|
556
|
+
merged = true;
|
|
557
|
+
break;
|
|
558
|
+
}
|
|
559
|
+
unchecked {
|
|
560
|
+
++j;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
if (!merged) {
|
|
564
|
+
_peerContexts.push(
|
|
565
|
+
JBPeerChainContext({
|
|
566
|
+
currency: contextCurrency,
|
|
567
|
+
decimals: ctx.decimals,
|
|
568
|
+
surplus: ctx.surplus,
|
|
569
|
+
balance: ctx.balance
|
|
570
|
+
})
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
unchecked {
|
|
575
|
+
++i;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
529
578
|
}
|
|
530
579
|
}
|
|
531
580
|
|
|
@@ -774,78 +823,19 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
774
823
|
return _outboxOf[token];
|
|
775
824
|
}
|
|
776
825
|
|
|
777
|
-
/// @notice The peer chain balance
|
|
778
|
-
///
|
|
779
|
-
/// @
|
|
780
|
-
///
|
|
781
|
-
/// @return
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
// forge-lint: disable-next-line(unsafe-typecast)
|
|
786
|
-
currency: uint32(currency),
|
|
787
|
-
// forge-lint: disable-next-line(unsafe-typecast)
|
|
788
|
-
decimals: uint8(decimals)
|
|
789
|
-
});
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
/// @notice The peer chain balance bundled with the peer chain ID and snapshot freshness key.
|
|
793
|
-
/// @dev Lets aggregators (e.g. `JBSuckerRegistry`) read the value, the peer chain it belongs to, and its
|
|
794
|
-
/// freshness in one call instead of three separate staticcalls. The `value` is identical to
|
|
795
|
-
/// `peerChainBalanceOf`.
|
|
796
|
-
/// @param decimals The decimal precision for the returned value.
|
|
797
|
-
/// @param currency The currency to normalize to (e.g. `uint256(uint160(JBConstants.NATIVE_TOKEN))` for ETH).
|
|
798
|
-
/// @return A `JBPeerChainValue` with the converted balance, peer chain ID, and snapshot freshness key.
|
|
799
|
-
function peerChainBalanceValueOf(
|
|
800
|
-
uint256 decimals,
|
|
801
|
-
uint256 currency
|
|
802
|
-
)
|
|
803
|
-
external
|
|
804
|
-
view
|
|
805
|
-
returns (JBPeerChainValue memory)
|
|
806
|
-
{
|
|
807
|
-
return JBPeerChainValue({
|
|
808
|
-
value: _convertPeerValue({source: _peerChainBalance, decimals: decimals, currency: currency}),
|
|
809
|
-
peerChainId: peerChainId(),
|
|
810
|
-
snapshotTimestamp: snapshotTimestamp
|
|
811
|
-
});
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
/// @notice The peer chain surplus, converted from the source denomination to the requested currency and decimal
|
|
815
|
-
/// precision using the local JBPrices oracle.
|
|
816
|
-
/// @param decimals The decimal precision for the returned value.
|
|
817
|
-
/// @param currency The currency to normalize to (e.g. `uint256(uint160(JBConstants.NATIVE_TOKEN))` for ETH).
|
|
818
|
-
/// @return A `JBDenominatedAmount` with the converted value.
|
|
819
|
-
function peerChainSurplusOf(uint256 decimals, uint256 currency) external view returns (JBDenominatedAmount memory) {
|
|
820
|
-
return JBDenominatedAmount({
|
|
821
|
-
value: _convertPeerValue({source: _peerChainSurplus, decimals: decimals, currency: currency}),
|
|
822
|
-
// forge-lint: disable-next-line(unsafe-typecast)
|
|
823
|
-
currency: uint32(currency),
|
|
824
|
-
// forge-lint: disable-next-line(unsafe-typecast)
|
|
825
|
-
decimals: uint8(decimals)
|
|
826
|
-
});
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
/// @notice The peer chain surplus bundled with the peer chain ID and snapshot freshness key.
|
|
830
|
-
/// @dev Lets aggregators (e.g. `JBSuckerRegistry`) read the value, the peer chain it belongs to, and its
|
|
831
|
-
/// freshness in one call instead of three separate staticcalls. The `value` is identical to
|
|
832
|
-
/// `peerChainSurplusOf`.
|
|
833
|
-
/// @param decimals The decimal precision for the returned value.
|
|
834
|
-
/// @param currency The currency to normalize to (e.g. `uint256(uint160(JBConstants.NATIVE_TOKEN))` for ETH).
|
|
835
|
-
/// @return A `JBPeerChainValue` with the converted surplus, peer chain ID, and snapshot freshness key.
|
|
836
|
-
function peerChainSurplusValueOf(
|
|
837
|
-
uint256 decimals,
|
|
838
|
-
uint256 currency
|
|
839
|
-
)
|
|
826
|
+
/// @notice The peer chain's raw per-context surplus and balance from the latest snapshot, bundled with the peer
|
|
827
|
+
/// chain ID and snapshot freshness key.
|
|
828
|
+
/// @dev The contexts are un-valued, in each context's own currency and decimals — the registry dedups same-peer
|
|
829
|
+
/// suckers by freshness, then values each context into a requested currency. The sucker consults no price oracle.
|
|
830
|
+
/// @return contexts The per-currency surplus and balance from the latest snapshot.
|
|
831
|
+
/// @return chainId The peer chain these contexts belong to.
|
|
832
|
+
/// @return snapshot The source freshness key of the latest snapshot.
|
|
833
|
+
function peerChainContextsOf()
|
|
840
834
|
external
|
|
841
835
|
view
|
|
842
|
-
returns (
|
|
836
|
+
returns (JBPeerChainContext[] memory contexts, uint256 chainId, uint256 snapshot)
|
|
843
837
|
{
|
|
844
|
-
return
|
|
845
|
-
value: _convertPeerValue({source: _peerChainSurplus, decimals: decimals, currency: currency}),
|
|
846
|
-
peerChainId: peerChainId(),
|
|
847
|
-
snapshotTimestamp: snapshotTimestamp
|
|
848
|
-
});
|
|
838
|
+
return (_peerContexts, peerChainId(), snapshotTimestamp);
|
|
849
839
|
}
|
|
850
840
|
|
|
851
841
|
/// @notice The peer chain total supply bundled with the peer chain ID and snapshot freshness key.
|
|
@@ -1658,25 +1648,43 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
1658
1648
|
return ERC2771Context._contextSuffixLength();
|
|
1659
1649
|
}
|
|
1660
1650
|
|
|
1661
|
-
/// @notice
|
|
1662
|
-
///
|
|
1663
|
-
///
|
|
1664
|
-
/// @
|
|
1665
|
-
///
|
|
1666
|
-
///
|
|
1667
|
-
///
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
)
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1651
|
+
/// @notice The authoritative accounting-context currency the project uses for a local token. Peer context is keyed
|
|
1652
|
+
/// by this currency so a consumer reads it under the same currency it already works in (which the project may set
|
|
1653
|
+
/// to a well-known id like USD rather than the token-keyed convention).
|
|
1654
|
+
/// @dev Both lookups use a low-level staticcall guarded by a returndata-length check, so a missing or
|
|
1655
|
+
/// non-conforming directory/terminal (including one that returns short/empty data) can't block a bridge message —
|
|
1656
|
+
/// it just yields the fallback. Returns the cached value when one exists (the accounting-context currency is
|
|
1657
|
+
/// immutable, so the cache never goes stale). Falls back to the conventional `uint32(uint160(token))` only when the
|
|
1658
|
+
/// project has no local accounting context for the token yet; that fallback is NOT cached, so a later snapshot
|
|
1659
|
+
/// re-reads it once the context exists.
|
|
1660
|
+
/// @param token The resolved local token.
|
|
1661
|
+
/// @return currency The project's accounting-context currency for the token.
|
|
1662
|
+
/// @return authoritative Whether `currency` came from a cached or configured accounting context (true) or the
|
|
1663
|
+
/// convention fallback (false). `fromRemote` caches only authoritative results, since those are immutable.
|
|
1664
|
+
function _localCurrencyOf(address token) internal view returns (uint32 currency, bool authoritative) {
|
|
1665
|
+
// Reuse the value derived on an earlier snapshot — no need to read the terminal again.
|
|
1666
|
+
uint32 cached = _cachedCurrencyOf[token];
|
|
1667
|
+
if (cached != 0) return (cached, true);
|
|
1668
|
+
|
|
1669
|
+
uint256 forProjectId = projectId();
|
|
1670
|
+
|
|
1671
|
+
// Resolve the project's primary terminal for the token. An `address` return needs a full word.
|
|
1672
|
+
(bool terminalOk, bytes memory terminalData) =
|
|
1673
|
+
address(DIRECTORY).staticcall(abi.encodeCall(IJBDirectory.primaryTerminalOf, (forProjectId, token)));
|
|
1674
|
+
if (terminalOk && terminalData.length >= 32) {
|
|
1675
|
+
address terminal = abi.decode(terminalData, (address));
|
|
1676
|
+
if (terminal != address(0)) {
|
|
1677
|
+
// Read the token's accounting context. The struct encodes to three words.
|
|
1678
|
+
(bool contextOk, bytes memory contextData) =
|
|
1679
|
+
terminal.staticcall(abi.encodeCall(IJBTerminal.accountingContextForTokenOf, (forProjectId, token)));
|
|
1680
|
+
if (contextOk && contextData.length >= 96) {
|
|
1681
|
+
JBAccountingContext memory accountingContext = abi.decode(contextData, (JBAccountingContext));
|
|
1682
|
+
if (accountingContext.currency != 0) return (accountingContext.currency, true);
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
1687
|
+
return (uint32(uint160(token)), false);
|
|
1680
1688
|
}
|
|
1681
1689
|
|
|
1682
1690
|
/// @notice The calldata. Preferred to use over `msg.data`.
|
|
@@ -1746,6 +1754,21 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
1746
1754
|
}
|
|
1747
1755
|
}
|
|
1748
1756
|
|
|
1757
|
+
/// @notice Adds two `uint128` amounts, saturating at `type(uint128).max` instead of overflowing.
|
|
1758
|
+
/// @dev Saturation keeps a pathological peer snapshot from reverting the receive path; the cap can only
|
|
1759
|
+
/// under-report a remote amount, the safe direction.
|
|
1760
|
+
/// @param a The first amount.
|
|
1761
|
+
/// @param b The second amount.
|
|
1762
|
+
/// @return The saturated sum.
|
|
1763
|
+
function _saturatingAddU128(uint128 a, uint128 b) internal pure returns (uint128) {
|
|
1764
|
+
unchecked {
|
|
1765
|
+
uint256 sum = uint256(a) + uint256(b);
|
|
1766
|
+
// The cast only runs when `sum <= type(uint128).max`, so it cannot truncate.
|
|
1767
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
1768
|
+
return sum > type(uint128).max ? type(uint128).max : uint128(sum);
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1749
1772
|
/// @notice Selects which retained inbox root a proof should be validated against, honoring a small window of
|
|
1750
1773
|
/// recently-accepted roots rather than only the latest.
|
|
1751
1774
|
/// @dev Computes the branch root implied by the proof once, then returns the first retained ring root it matches.
|
|
@@ -1857,7 +1880,6 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
1857
1880
|
|
|
1858
1881
|
JBMessageRoot memory message = JBSuckerLib.buildSnapshotMessage({
|
|
1859
1882
|
directory: DIRECTORY,
|
|
1860
|
-
prices: PRICES,
|
|
1861
1883
|
projectId: projectId(),
|
|
1862
1884
|
remoteToken: remoteToken.addr,
|
|
1863
1885
|
amount: amount,
|