@bananapus/suckers-v6 0.0.74 → 0.0.76
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
CHANGED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Sucker Entry Points
|
|
2
|
+
|
|
3
|
+
Use this file when you already know the task is in `nana-suckers-v6` and need the concrete contract/function surface to open next.
|
|
4
|
+
|
|
5
|
+
> V6 is testnet-only. Deployed sucker, registry, and deployer addresses live in `deploy-all-v6`'s address output, not here.
|
|
6
|
+
|
|
7
|
+
## Purpose
|
|
8
|
+
|
|
9
|
+
A sucker bridges a Juicebox project's token economy between two chains. Cashed-out positions are committed to a local outbox merkle tree, the root is relayed to the peer sucker on the remote chain, and beneficiaries prove inclusion against the inbox root to recreate their position on the destination chain. The `JBSucker` base owns the shared flow; chain-specific subclasses (`JBArbitrumSucker`, `JBOptimismSucker`, `JBCCIPSucker`, `JBBaseSucker`) own transport delivery and verification.
|
|
10
|
+
|
|
11
|
+
## Contracts
|
|
12
|
+
|
|
13
|
+
| Contract | Role |
|
|
14
|
+
|----------|------|
|
|
15
|
+
| `JBSucker` | Abstract base. Owns the prepare/relay/claim/token-mapping/deprecation/emergency flow and dual merkle (outbox/inbox) state. |
|
|
16
|
+
| `JBArbitrumSucker` | Arbitrum bridge transport. |
|
|
17
|
+
| `JBOptimismSucker` | OP Stack bridge transport. |
|
|
18
|
+
| `JBCCIPSucker` | Chainlink CCIP transport. |
|
|
19
|
+
| `JBBaseSucker` | Base/OP Stack transport. |
|
|
20
|
+
| `JBSuckerRegistry` | Project-to-sucker inventory, deployer allowlist, shared `toRemote` fee, deprecation removal, cross-chain surplus/supply aggregation. |
|
|
21
|
+
|
|
22
|
+
`IJBSucker` is the minimal interface; `IJBSuckerExtended` adds the deprecation, emergency-hatch, and retained-fee surface.
|
|
23
|
+
|
|
24
|
+
## Structs
|
|
25
|
+
|
|
26
|
+
### `JBTokenMapping` (argument to `mapToken` / `mapTokens`)
|
|
27
|
+
|
|
28
|
+
| Field | Type | Meaning |
|
|
29
|
+
|-------|------|---------|
|
|
30
|
+
| `localToken` | `address` | The local token address. |
|
|
31
|
+
| `minGas` | `uint32` | The minimum gas to use when bridging this token. |
|
|
32
|
+
| `remoteToken` | `bytes32` | The remote token address (bytes32 for cross-VM compatibility). |
|
|
33
|
+
|
|
34
|
+
### `JBClaim` (argument to `claim` / `exitThroughEmergencyHatch`)
|
|
35
|
+
|
|
36
|
+
| Field | Type | Meaning |
|
|
37
|
+
|-------|------|---------|
|
|
38
|
+
| `token` | `address` | The local terminal token to claim. |
|
|
39
|
+
| `leaf` | `JBLeaf` | The leaf to claim from (see below). |
|
|
40
|
+
| `proof` | `bytes32[32]` | The merkle proof. Must be of length `JBSucker._TREE_DEPTH` (32). |
|
|
41
|
+
|
|
42
|
+
### `JBLeaf` (the `leaf` field of `JBClaim`)
|
|
43
|
+
|
|
44
|
+
| Field | Type | Meaning |
|
|
45
|
+
|-------|------|---------|
|
|
46
|
+
| `index` | `uint256` | The leaf's index in the tree. |
|
|
47
|
+
| `beneficiary` | `bytes32` | The beneficiary (bytes32 for cross-VM compatibility). |
|
|
48
|
+
| `projectTokenCount` | `uint256` | The number of project tokens to claim. |
|
|
49
|
+
| `terminalTokenAmount` | `uint256` | The amount of terminal tokens to claim. |
|
|
50
|
+
| `metadata` | `bytes32` | Opaque, caller-defined payload covered by the leaf hash. `bytes32(0)` when no extra context. |
|
|
51
|
+
|
|
52
|
+
## Key functions
|
|
53
|
+
|
|
54
|
+
### JBSucker — bridge flow (`IJBSucker`)
|
|
55
|
+
|
|
56
|
+
| Function | What it does |
|
|
57
|
+
|----------|--------------|
|
|
58
|
+
| `prepare(uint256 projectTokenCount, bytes32 beneficiary, uint256 minTokensReclaimed, address token, bytes32 metadata)` | Cash out `projectTokenCount` project tokens into `token` and insert a leaf for `beneficiary` into the outbox tree for bridging. `minTokensReclaimed` bounds slippage; `metadata` is an opaque attribution payload carried in the leaf hash (`bytes32(0)` for a plain bridge). |
|
|
59
|
+
| `toRemote(address token) payable` | Send the current outbox tree root and bridged assets for `token` to the remote peer through the chain-specific transport. `payable` to fund the transport message and the registry's `toRemoteFee`. |
|
|
60
|
+
| `claim(JBClaim calldata claimData)` | Claim bridged project tokens for the leaf's beneficiary by proving inclusion against the inbox root. |
|
|
61
|
+
| `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. |
|
|
62
|
+
| `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`). |
|
|
63
|
+
| `mapTokens(JBTokenMapping[] calldata maps) payable` | Map multiple local tokens to remote tokens in one call. |
|
|
64
|
+
|
|
65
|
+
### JBSucker — deprecation & emergency (`IJBSuckerExtended`)
|
|
66
|
+
|
|
67
|
+
| Function | What it does |
|
|
68
|
+
|----------|--------------|
|
|
69
|
+
| `setDeprecation(uint40 timestamp)` | Set or update the deprecation timestamp. Drives the `JBSuckerState` lifecycle: `ENABLED` → `DEPRECATION_PENDING` → `SENDING_DISABLED` → `DEPRECATED`. Requires `SET_SUCKER_DEPRECATION` permission. |
|
|
70
|
+
| `enableEmergencyHatchFor(address[] calldata tokens)` | Open the emergency hatch for the given tokens, allowing direct claims without bridging when transport is unavailable. Requires `SUCKER_SAFETY` permission (project owner). |
|
|
71
|
+
| `exitThroughEmergencyHatch(JBClaim calldata claimData)` | Claim a leaf directly through an open emergency hatch when bridging cannot complete. |
|
|
72
|
+
| `claimRetainedToRemoteFee(address payable beneficiary)` | Withdraw ETH from a `toRemote` fee payment that previously failed and was retained for the caller. |
|
|
73
|
+
| `claimRetainedTransportPaymentRefund(address payable beneficiary)` | Withdraw ETH from a transport-payment refund that previously failed and was retained for the caller. |
|
|
74
|
+
|
|
75
|
+
### JBSucker — key views (`IJBSucker` / `IJBSuckerExtended`)
|
|
76
|
+
|
|
77
|
+
| Function | What it does |
|
|
78
|
+
|----------|--------------|
|
|
79
|
+
| `state()` | Returns the current `JBSuckerState` (deprecation lifecycle stage). |
|
|
80
|
+
| `peer()` | Returns the peer sucker address on the remote chain (bytes32). |
|
|
81
|
+
| `peerChainId()` | Returns the remote peer chain ID. |
|
|
82
|
+
| `projectId()` | Returns the local project ID this sucker serves. |
|
|
83
|
+
| `isMapped(address token)` | Whether a token has been mapped for bridging. |
|
|
84
|
+
| `remoteTokenFor(address token)` | The `JBRemoteToken` info a local token maps to. |
|
|
85
|
+
| `inboxOf(address token)` | The inbox merkle tree root (`JBInboxTreeRoot`) for a token. |
|
|
86
|
+
| `outboxOf(address token)` | The outbox merkle tree (`JBOutboxTree`) for a token. |
|
|
87
|
+
| `amountToAddToBalanceOf(address token)` | Tokens received from bridging that are waiting to be added to the project's terminal balance. |
|
|
88
|
+
| `executedLeafHashOf(address token, uint256 index)` | The committed leaf hash at `(token, index)`, or `bytes32(0)` if unexecuted. Beneficiary contracts re-derive this to authenticate a settlement that a front-runner's direct `claim` already executed. |
|
|
89
|
+
| `peerChainTotalSupply()` | The last-known peer-chain total token supply (used by data hooks to compute effective cross-chain supply). |
|
|
90
|
+
| `peerChainContextsOf()` | Per-context raw surplus and balance from the latest peer snapshot, with chain ID and freshness key. Un-valued (each context in its own currency/decimals). |
|
|
91
|
+
| `retainedToRemoteFeeOf(address account)` | ETH owed to `account` from a failed `toRemote` fee payment. |
|
|
92
|
+
| `retainedTransportPaymentRefundOf(address account)` | ETH owed to `account` from a failed transport-payment refund. |
|
|
93
|
+
|
|
94
|
+
### JBSuckerRegistry (`IJBSuckerRegistry`)
|
|
95
|
+
|
|
96
|
+
| Function | What it does |
|
|
97
|
+
|----------|--------------|
|
|
98
|
+
| `deploySuckersFor(uint256 projectId, bytes32 salt, JBSuckerDeployerConfig[] calldata configurations)` | Deploy one or more suckers for a project and apply each config's initial token mappings. Requires `DEPLOY_SUCKERS`. Returns the deployed sucker addresses. |
|
|
99
|
+
| `removeDeprecatedSucker(uint256 projectId, address sucker)` | Remove a fully deprecated sucker from a project's inventory. |
|
|
100
|
+
| `allowSuckerDeployer(address deployer)` / `allowSuckerDeployers(address[] calldata deployers)` | Add deployer(s) to the allowlist. Owner-only. |
|
|
101
|
+
| `removeSuckerDeployer(address deployer)` | Remove a deployer from the allowlist. Owner-only. |
|
|
102
|
+
| `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. |
|
|
103
|
+
| `suckersOf(uint256 projectId)` | All active suckers for a project. |
|
|
104
|
+
| `allSuckersOf(uint256 projectId)` | Every sucker ever registered for a project, including deprecated ones. |
|
|
105
|
+
| `suckerPairsOf(uint256 projectId)` | The local/remote sucker pairs (`JBSuckersPair[]`) for a project. |
|
|
106
|
+
| `isSuckerOf(uint256 projectId, address addr)` | Whether `addr` is a registry-deployed sucker for the project. |
|
|
107
|
+
| `suckerDeployerIsAllowed(address deployer)` | Whether a deployer is on the allowlist. |
|
|
108
|
+
| `remoteTotalSupplyOf(uint256 projectId)` | Combined peer-chain total supply across all remote chains (dedups same-peer suckers by freshest snapshot). |
|
|
109
|
+
| `totalRemoteSurplusOf(uint256 projectId, uint256 currency, uint256 decimals)` | Combined peer-chain surplus valued into `currency`. Matching-currency contexts taken at par; missing cross-currency feed skips that sucker (conservative). |
|
|
110
|
+
| `totalRemoteBalanceOf(uint256 projectId, uint256 currency, uint256 decimals)` | Combined peer-chain balance valued into `currency`, same valuation rules as above. |
|
|
111
|
+
| `toRemoteFee()` / `MAX_TO_REMOTE_FEE()` | The current `toRemote` fee and its hardcoded ceiling. |
|
package/src/JBSucker.sol
CHANGED
|
@@ -143,6 +143,9 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
143
143
|
/// @notice Thrown when `msg.value` is sent for an action that expects none.
|
|
144
144
|
error JBSucker_UnexpectedMsgValue(uint256 value);
|
|
145
145
|
|
|
146
|
+
/// @notice Thrown when an ERC-20 terminal balance does not decrease by the amount added to the project balance.
|
|
147
|
+
error JBSucker_UnexpectedTokenBalance(address token, uint256 expectedBalance, uint256 actualBalance);
|
|
148
|
+
|
|
146
149
|
/// @notice Thrown when a required beneficiary address is the zero address.
|
|
147
150
|
error JBSucker_ZeroBeneficiary(bytes32 beneficiary);
|
|
148
151
|
|
|
@@ -648,42 +651,34 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
648
651
|
/// @param maps A list of local and remote terminal token addresses to map, and minimum amount/gas limits for
|
|
649
652
|
/// bridging them.
|
|
650
653
|
function mapTokens(JBTokenMapping[] calldata maps) external payable override {
|
|
651
|
-
uint256
|
|
654
|
+
uint256 disableCandidates;
|
|
652
655
|
|
|
653
|
-
//
|
|
654
|
-
//
|
|
655
|
-
// is set to 0 for each call. Any ETH sent with the transaction is refunded after the second loop.
|
|
656
|
+
// Count mappings that currently need a final outbox flush. This is an upper bound because duplicated disables
|
|
657
|
+
// in the same batch can become no-ops after the first one updates `numberOfClaimsSent`.
|
|
656
658
|
for (uint256 h; h < maps.length;) {
|
|
657
659
|
JBOutboxTree storage _outbox = _outboxOf[maps[h].localToken];
|
|
658
660
|
if (maps[h].remoteToken == bytes32(0) && _outbox.numberOfClaimsSent != _outbox.tree.count) {
|
|
659
|
-
|
|
661
|
+
++disableCandidates;
|
|
660
662
|
}
|
|
661
663
|
unchecked {
|
|
662
664
|
++h;
|
|
663
665
|
}
|
|
664
666
|
}
|
|
665
667
|
|
|
666
|
-
//
|
|
668
|
+
// Split the attached value across disable candidates, then refund any value not actually used by a final
|
|
669
|
+
// outbox flush. Enable-only and duplicate/no-op disable entries do not consume transport payment.
|
|
670
|
+
uint256 transportPaymentValue = disableCandidates == 0 ? 0 : msg.value / disableCandidates;
|
|
671
|
+
uint256 transportPaymentSpent;
|
|
667
672
|
for (uint256 i; i < maps.length;) {
|
|
668
|
-
_mapToken({map: maps[i], transportPaymentValue:
|
|
673
|
+
transportPaymentSpent += _mapToken({map: maps[i], transportPaymentValue: transportPaymentValue});
|
|
669
674
|
unchecked {
|
|
670
675
|
++i;
|
|
671
676
|
}
|
|
672
677
|
}
|
|
673
678
|
|
|
674
|
-
//
|
|
675
|
-
if (
|
|
676
|
-
|
|
677
|
-
_sendNativeTo({beneficiary: payable(_msgSender()), amount: msg.value});
|
|
678
|
-
}
|
|
679
|
-
} else {
|
|
680
|
-
// Refund any remainder from integer division so dust wei isn't stuck in the contract.
|
|
681
|
-
uint256 remainder = msg.value % numberToDisable;
|
|
682
|
-
if (remainder > 0) {
|
|
683
|
-
// Best-effort refund — don't revert if caller can't accept ETH.
|
|
684
|
-
(bool _ok,) = _msgSender().call{value: remainder}("");
|
|
685
|
-
_ok; // Silence unused-variable warning; failure is intentionally ignored.
|
|
686
|
-
}
|
|
679
|
+
// Return enable-only value, duplicate/no-op disable value, and integer-division dust to the caller.
|
|
680
|
+
if (msg.value > transportPaymentSpent) {
|
|
681
|
+
_sendNativeTo({beneficiary: payable(_msgSender()), amount: msg.value - transportPaymentSpent});
|
|
687
682
|
}
|
|
688
683
|
}
|
|
689
684
|
|
|
@@ -1123,8 +1118,14 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
1123
1118
|
});
|
|
1124
1119
|
|
|
1125
1120
|
if (isErc20) {
|
|
1126
|
-
//
|
|
1127
|
-
|
|
1121
|
+
// The terminal must pull exactly `amount`; fee-on-transfer or non-conforming tokens are unsupported.
|
|
1122
|
+
uint256 expectedBalance = balanceBefore - amount;
|
|
1123
|
+
uint256 actualBalance = IERC20(token).balanceOf(address(this));
|
|
1124
|
+
if (actualBalance != expectedBalance) {
|
|
1125
|
+
revert JBSucker_UnexpectedTokenBalance({
|
|
1126
|
+
token: token, expectedBalance: expectedBalance, actualBalance: actualBalance
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1128
1129
|
}
|
|
1129
1130
|
}
|
|
1130
1131
|
|
|
@@ -1229,7 +1230,14 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
1229
1230
|
/// @param map The local and remote terminal token addresses to map, and minimum amount/gas limits for bridging
|
|
1230
1231
|
/// them.
|
|
1231
1232
|
/// @param transportPaymentValue The amount of `msg.value` to send for the token mapping.
|
|
1232
|
-
|
|
1233
|
+
/// @return transportPaymentSpent The amount of transport payment used by a final outbox flush.
|
|
1234
|
+
function _mapToken(
|
|
1235
|
+
JBTokenMapping calldata map,
|
|
1236
|
+
uint256 transportPaymentValue
|
|
1237
|
+
)
|
|
1238
|
+
internal
|
|
1239
|
+
returns (uint256 transportPaymentSpent)
|
|
1240
|
+
{
|
|
1233
1241
|
address token = map.localToken;
|
|
1234
1242
|
JBRemoteToken memory currentMapping = _remoteTokenFor[token];
|
|
1235
1243
|
|
|
@@ -1288,6 +1296,7 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
1288
1296
|
// _sendRoot uses the `currentMapping` parameter, not storage, so this is safe.
|
|
1289
1297
|
_remoteTokenFor[token].enabled = false;
|
|
1290
1298
|
_sendRoot({transportPayment: transportPaymentValue, token: token, remoteToken: currentMapping});
|
|
1299
|
+
transportPaymentSpent = transportPaymentValue;
|
|
1291
1300
|
}
|
|
1292
1301
|
|
|
1293
1302
|
// Update the reverse reservation if an unused local token is being remapped to a new remote token.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity 0.8.28;
|
|
3
|
+
|
|
4
|
+
import {JBSourceContext} from "../structs/JBSourceContext.sol";
|
|
5
|
+
|
|
6
|
+
/// @notice Helpers for reading optional `IJBPeerChainAdjustedAccounts` return data.
|
|
7
|
+
library JBPeerChainAdjustedAccountsLib {
|
|
8
|
+
/// @notice Decodes peer-chain adjusted accounting return data, falling back to no contribution if malformed.
|
|
9
|
+
/// @param data The raw return data from a `peerChainAdjustedAccountsOf` call.
|
|
10
|
+
/// @return supply The extra supply to include in `sourceTotalSupply`.
|
|
11
|
+
/// @return contexts The extra per-context surplus and balance to include in the snapshot, un-valued.
|
|
12
|
+
function decode(bytes memory data) internal pure returns (uint256 supply, JBSourceContext[] memory contexts) {
|
|
13
|
+
// `data` is a Solidity `bytes` value. In memory, its first word is the byte length, and the hook's ABI return
|
|
14
|
+
// payload begins one word later at `data + 32`.
|
|
15
|
+
//
|
|
16
|
+
// The payload for `(uint256, JBSourceContext[])` is:
|
|
17
|
+
// word 0: supply
|
|
18
|
+
// word 1: offset to the dynamic `contexts` array tail, relative to the payload start
|
|
19
|
+
// tail word 0: contexts.length
|
|
20
|
+
// tail words: each `JBSourceContext`, encoded as 4 ABI words.
|
|
21
|
+
//
|
|
22
|
+
// A valid return needs at least the two-word tuple head plus the array-length word. Anything shorter would
|
|
23
|
+
// make the reads below point outside the returned buffer, so the optional contribution is ignored.
|
|
24
|
+
if (data.length < 96) return (0, new JBSourceContext[](0));
|
|
25
|
+
|
|
26
|
+
// The tuple head is fixed-width, so read it directly instead of `abi.decode`:
|
|
27
|
+
// - `supply` is word 0 of the ABI payload.
|
|
28
|
+
// - `contextsOffset` is word 1 and points to the dynamic-array tail.
|
|
29
|
+
// Manual reads let malformed optional hooks fail soft instead of reverting the whole snapshot.
|
|
30
|
+
uint256 contextsOffset;
|
|
31
|
+
assembly ("memory-safe") {
|
|
32
|
+
// Skip the `bytes` length word and read payload word 0.
|
|
33
|
+
supply := mload(add(data, 32))
|
|
34
|
+
// Read payload word 1. The value is payload-relative, not memory-object-relative.
|
|
35
|
+
contextsOffset := mload(add(data, 64))
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// The dynamic array tail must start after the two-word tuple head (`>= 64`), be ABI-word aligned, and leave
|
|
39
|
+
// room for its own length word. If any of these fail, `abi.decode` would revert; this helper treats the
|
|
40
|
+
// optional hook as absent.
|
|
41
|
+
if (contextsOffset < 64 || contextsOffset % 32 != 0 || contextsOffset > data.length - 32) {
|
|
42
|
+
return (0, new JBSourceContext[](0));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// `contextsOffset` was proven to leave room for the array length word, so this read is in-bounds. Add `32` to
|
|
46
|
+
// `data` first because offsets are relative to the payload start, not the `bytes` length word.
|
|
47
|
+
uint256 contextCount;
|
|
48
|
+
assembly ("memory-safe") {
|
|
49
|
+
contextCount := mload(add(add(data, 32), contextsOffset))
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Skip the array-length word to reach the first encoded `JBSourceContext`.
|
|
53
|
+
uint256 contextsStart = contextsOffset + 32;
|
|
54
|
+
// Each `JBSourceContext` is four ABI words: token, decimals, surplus, balance. This bounds check prevents both
|
|
55
|
+
// oversized allocation from a hostile length word and out-of-bounds reads in the loop.
|
|
56
|
+
if (contextCount > (data.length - contextsStart) / 128) return (0, new JBSourceContext[](0));
|
|
57
|
+
|
|
58
|
+
// Only allocate after proving the claimed array length fits inside the returned bytes.
|
|
59
|
+
contexts = new JBSourceContext[](contextCount);
|
|
60
|
+
|
|
61
|
+
for (uint256 i; i < contextCount; i++) {
|
|
62
|
+
// Move to the encoded struct for this index. The multiplication is safe because `contextCount` already
|
|
63
|
+
// proved every 128-byte struct fits in the buffer.
|
|
64
|
+
uint256 contextOffset = contextsStart + i * 128;
|
|
65
|
+
// Read narrowed fields as full words first. The ABI decoder would reject out-of-range values for
|
|
66
|
+
// `uint8`/`uint128`, so the manual decoder must check those ranges before casting.
|
|
67
|
+
bytes32 token;
|
|
68
|
+
uint256 decimals;
|
|
69
|
+
uint256 surplus;
|
|
70
|
+
uint256 contextBalance;
|
|
71
|
+
|
|
72
|
+
assembly ("memory-safe") {
|
|
73
|
+
// Point at the first word of the encoded `JBSourceContext`.
|
|
74
|
+
let contextPointer := add(add(data, 32), contextOffset)
|
|
75
|
+
// Struct word 0: source-local token, padded to bytes32.
|
|
76
|
+
token := mload(contextPointer)
|
|
77
|
+
// Struct word 1: decimal precision, encoded as a full ABI word.
|
|
78
|
+
decimals := mload(add(contextPointer, 32))
|
|
79
|
+
// Struct word 2: raw surplus in the context's own decimals.
|
|
80
|
+
surplus := mload(add(contextPointer, 64))
|
|
81
|
+
// Struct word 3: raw recorded balance in the context's own decimals.
|
|
82
|
+
contextBalance := mload(add(contextPointer, 96))
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Mirror ABI decoder type checks before narrowing. Returning `(0, [])` avoids silently truncating a
|
|
86
|
+
// malformed hook's values into smaller wire types.
|
|
87
|
+
if (decimals > type(uint8).max || surplus > type(uint128).max || contextBalance > type(uint128).max) {
|
|
88
|
+
return (0, new JBSourceContext[](0));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Casting is safe because the guard above rejected larger values, but forge lint cannot infer that.
|
|
92
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
93
|
+
uint8 checkedDecimals = uint8(decimals);
|
|
94
|
+
// Casting is safe for the same reason as `checkedDecimals`.
|
|
95
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
96
|
+
uint128 checkedSurplus = uint128(surplus);
|
|
97
|
+
// Casting is safe for the same reason as `checkedDecimals`.
|
|
98
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
99
|
+
uint128 checkedBalance = uint128(contextBalance);
|
|
100
|
+
|
|
101
|
+
// Store the checked values using the struct's actual wire types. At this point every memory read was
|
|
102
|
+
// inside the buffer and every narrowed cast has been proven safe.
|
|
103
|
+
contexts[i] = JBSourceContext({
|
|
104
|
+
token: token, decimals: checkedDecimals, surplus: checkedSurplus, balance: checkedBalance
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -45,8 +45,19 @@ library JBRelayBeneficiary {
|
|
|
45
45
|
return beneficiary;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
//
|
|
49
|
-
|
|
48
|
+
// Load the first metadata word directly so malformed trailing bytes still fall back instead of reverting the
|
|
49
|
+
// surrounding payment.
|
|
50
|
+
uint256 relayBeneficiaryWord;
|
|
51
|
+
assembly ("memory-safe") {
|
|
52
|
+
relayBeneficiaryWord := mload(add(data, 32))
|
|
53
|
+
}
|
|
54
|
+
if (relayBeneficiaryWord > type(uint160).max) {
|
|
55
|
+
return beneficiary;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// The range guard above ensures the address cast cannot truncate.
|
|
59
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
60
|
+
address relayBeneficiary = address(uint160(relayBeneficiaryWord));
|
|
50
61
|
if (relayBeneficiary == address(0)) {
|
|
51
62
|
return beneficiary;
|
|
52
63
|
}
|
|
@@ -12,6 +12,7 @@ import {JBRulesetMetadata} from "@bananapus/core-v6/src/structs/JBRulesetMetadat
|
|
|
12
12
|
import {IERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
|
|
13
13
|
|
|
14
14
|
import {IJBPeerChainAdjustedAccounts} from "../interfaces/IJBPeerChainAdjustedAccounts.sol";
|
|
15
|
+
import {JBPeerChainAdjustedAccountsLib} from "./JBPeerChainAdjustedAccountsLib.sol";
|
|
15
16
|
import {JBInboxTreeRoot} from "../structs/JBInboxTreeRoot.sol";
|
|
16
17
|
import {JBMessageRoot} from "../structs/JBMessageRoot.sol";
|
|
17
18
|
import {JBSourceContext} from "../structs/JBSourceContext.sol";
|
|
@@ -249,15 +250,13 @@ library JBSuckerLib {
|
|
|
249
250
|
address dataHook = ruleset.dataHook();
|
|
250
251
|
if (dataHook == address(0) || dataHook.code.length == 0) return (0, new JBSourceContext[](0));
|
|
251
252
|
|
|
252
|
-
// Ask the hook for any off-terminal supply and per-context surplus/balance.
|
|
253
|
-
//
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
)
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
return (0, new JBSourceContext[](0));
|
|
260
|
-
}
|
|
253
|
+
// Ask the hook for any off-terminal supply and per-context surplus/balance. Non-supporting, broken, or
|
|
254
|
+
// malformed hooks are ignored so the baseline snapshot still goes out.
|
|
255
|
+
(bool hookCallSucceeded, bytes memory hookData) =
|
|
256
|
+
dataHook.staticcall(abi.encodeCall(IJBPeerChainAdjustedAccounts.peerChainAdjustedAccountsOf, (projectId)));
|
|
257
|
+
if (!hookCallSucceeded) return (0, new JBSourceContext[](0));
|
|
258
|
+
|
|
259
|
+
return JBPeerChainAdjustedAccountsLib.decode(hookData);
|
|
261
260
|
}
|
|
262
261
|
|
|
263
262
|
/// @notice Reads one accounting context's raw surplus and balance into a `JBSourceContext`, performing no price
|