@bananapus/suckers-v6 0.0.76 → 0.0.78
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 +6 -2
- package/package.json +3 -3
- package/references/entrypoints.md +11 -0
- package/references/operations.md +1 -1
- package/references/runtime.md +5 -3
- package/src/JBArbitrumSucker.sol +75 -31
- package/src/JBBaseSucker.sol +1 -1
- package/src/JBCCIPSucker.sol +123 -58
- package/src/JBOptimismSucker.sol +26 -1
- package/src/JBSucker.sol +289 -205
- package/src/interfaces/IJBSucker.sol +14 -0
- package/src/libraries/JBSuckerLib.sol +32 -0
- package/src/structs/JBAccountingSnapshot.sol +19 -0
package/src/JBSucker.sol
CHANGED
|
@@ -36,14 +36,15 @@ import {JBSuckerLib} from "./libraries/JBSuckerLib.sol";
|
|
|
36
36
|
import {MerkleLib} from "./utils/MerkleLib.sol";
|
|
37
37
|
|
|
38
38
|
// Local: structs (alphabetized)
|
|
39
|
+
import {JBAccountingSnapshot} from "./structs/JBAccountingSnapshot.sol";
|
|
39
40
|
import {JBClaim} from "./structs/JBClaim.sol";
|
|
40
41
|
import {JBInboxTreeRoot} from "./structs/JBInboxTreeRoot.sol";
|
|
41
42
|
import {JBMessageRoot} from "./structs/JBMessageRoot.sol";
|
|
42
|
-
import {JBPeerChainContext} from "./structs/JBPeerChainContext.sol";
|
|
43
|
-
import {JBSourceContext} from "./structs/JBSourceContext.sol";
|
|
44
43
|
import {JBOutboxTree} from "./structs/JBOutboxTree.sol";
|
|
44
|
+
import {JBPeerChainContext} from "./structs/JBPeerChainContext.sol";
|
|
45
45
|
import {JBPeerChainValue} from "./structs/JBPeerChainValue.sol";
|
|
46
46
|
import {JBRemoteToken} from "./structs/JBRemoteToken.sol";
|
|
47
|
+
import {JBSourceContext} from "./structs/JBSourceContext.sol";
|
|
47
48
|
import {JBTokenMapping} from "./structs/JBTokenMapping.sol";
|
|
48
49
|
|
|
49
50
|
/// @notice Bridges a Juicebox project's tokens and their backing terminal-token funds between two chains. Token
|
|
@@ -68,6 +69,9 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
68
69
|
// --------------------------- custom errors ------------------------- //
|
|
69
70
|
//*********************************************************************//
|
|
70
71
|
|
|
72
|
+
/// @notice Thrown when a bridge-specific implementation does not support accounting-only messages.
|
|
73
|
+
error JBSucker_AccountingSyncUnsupported();
|
|
74
|
+
|
|
71
75
|
/// @notice Thrown when a terminal-token or project-token amount being bridged exceeds the `uint128` cap enforced
|
|
72
76
|
/// for cross-VM compatibility.
|
|
73
77
|
error JBSucker_AmountExceedsUint128(uint256 amount);
|
|
@@ -401,39 +405,43 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
401
405
|
}
|
|
402
406
|
}
|
|
403
407
|
|
|
404
|
-
/// @notice Claim
|
|
405
|
-
///
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
// Attempt to validate the proof against the inbox tree for the terminal token. The leaf hash includes
|
|
409
|
-
// `claimData.leaf.metadata` so the proof is only valid for the exact (amount, beneficiary, metadata) tuple the
|
|
410
|
-
// origin committed to.
|
|
411
|
-
_validate({
|
|
412
|
-
projectTokenCount: claimData.leaf.projectTokenCount,
|
|
413
|
-
terminalToken: claimData.token,
|
|
414
|
-
terminalTokenAmount: claimData.leaf.terminalTokenAmount,
|
|
415
|
-
beneficiary: claimData.leaf.beneficiary,
|
|
416
|
-
metadata: claimData.leaf.metadata,
|
|
417
|
-
index: claimData.leaf.index,
|
|
418
|
-
leaves: claimData.proof
|
|
419
|
-
});
|
|
408
|
+
/// @notice Claim retained failed-fee ETH.
|
|
409
|
+
/// @param beneficiary The address that should receive the retained ETH.
|
|
410
|
+
function claimRetainedToRemoteFee(address payable beneficiary) external override {
|
|
411
|
+
if (beneficiary == address(0)) revert JBSucker_ZeroBeneficiary({beneficiary: bytes32(0)});
|
|
420
412
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
413
|
+
address account = _msgSender();
|
|
414
|
+
uint256 amount = retainedToRemoteFeeOf[account];
|
|
415
|
+
if (amount == 0) revert JBSucker_NoRetainedToRemoteFee(account);
|
|
416
|
+
|
|
417
|
+
retainedToRemoteFeeOf[account] = 0;
|
|
418
|
+
retainedToRemoteFeeBalance -= amount;
|
|
419
|
+
|
|
420
|
+
_sendNativeTo({beneficiary: beneficiary, amount: amount});
|
|
421
|
+
|
|
422
|
+
// State was cleared before sending ETH; the event is emitted after the transfer so failed sends do not log.
|
|
423
|
+
emit RetainedToRemoteFeeClaimed({
|
|
424
|
+
account: account, beneficiary: beneficiary, amount: amount, caller: _msgSender()
|
|
429
425
|
});
|
|
426
|
+
}
|
|
430
427
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
428
|
+
/// @notice Claim retained failed transport-payment refund ETH.
|
|
429
|
+
/// @param beneficiary The address that should receive the retained ETH.
|
|
430
|
+
function claimRetainedTransportPaymentRefund(address payable beneficiary) external override {
|
|
431
|
+
if (beneficiary == address(0)) revert JBSucker_ZeroBeneficiary({beneficiary: bytes32(0)});
|
|
432
|
+
|
|
433
|
+
address account = _msgSender();
|
|
434
|
+
uint256 amount = retainedTransportPaymentRefundOf[account];
|
|
435
|
+
if (amount == 0) revert JBSucker_NoRetainedTransportPaymentRefund(account);
|
|
436
|
+
|
|
437
|
+
retainedTransportPaymentRefundOf[account] = 0;
|
|
438
|
+
retainedTransportPaymentRefundBalance -= amount;
|
|
439
|
+
|
|
440
|
+
_sendNativeTo({beneficiary: beneficiary, amount: amount});
|
|
441
|
+
|
|
442
|
+
// State was cleared before sending ETH; the event is emitted after the transfer so failed sends do not log.
|
|
443
|
+
emit RetainedTransportPaymentRefundClaimed({
|
|
444
|
+
account: account, beneficiary: beneficiary, amount: amount, caller: _msgSender()
|
|
437
445
|
});
|
|
438
446
|
}
|
|
439
447
|
|
|
@@ -571,78 +579,38 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
571
579
|
});
|
|
572
580
|
}
|
|
573
581
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
snapshotTimestamp = root.sourceTimestamp;
|
|
581
|
-
|
|
582
|
-
// Update unconditionally — a legitimate zero supply must clear phantom cached supply.
|
|
583
|
-
peerChainTotalSupply = root.sourceTotalSupply;
|
|
584
|
-
|
|
585
|
-
// Rebuild the per-currency context set from scratch. A context that dropped out of this fresher snapshot is
|
|
586
|
-
// simply absent from the new set, so no per-entry clearing is needed.
|
|
587
|
-
delete _peerContexts;
|
|
588
|
-
|
|
589
|
-
// Fold each source context into the local currency it resolves to. Resolution prefers the token mapping (so
|
|
590
|
-
// a same-asset token at a different remote address binds to the right local context) and falls back to
|
|
591
|
-
// identity for same-address tokens; the local currency is then derived from that resolved local token's
|
|
592
|
-
// authoritative accounting context, NOT trusted from the wire, so a same-asset token at a different address
|
|
593
|
-
// still folds under the receiver's own currency. Multiple source contexts that resolve to the same local
|
|
594
|
-
// currency (e.g. the same token across multiple terminals) are summed.
|
|
595
|
-
uint256 numContexts = root.sourceContexts.length;
|
|
596
|
-
for (uint256 i; i < numContexts;) {
|
|
597
|
-
JBSourceContext calldata ctx = root.sourceContexts[i];
|
|
598
|
-
|
|
599
|
-
address contextToken = _localTokenForRemoteToken[ctx.token];
|
|
600
|
-
if (contextToken == address(0)) contextToken = _toAddress(ctx.token);
|
|
601
|
-
(uint32 contextCurrency, bool authoritative) = _localCurrencyOf(contextToken);
|
|
602
|
-
// Cache an authoritative currency so later snapshots reuse it instead of re-reading the terminal. The
|
|
603
|
-
// accounting-context currency is immutable, so the cache never goes stale; a not-yet-configured token
|
|
604
|
-
// is left uncached and re-read next time.
|
|
605
|
-
if (authoritative && _cachedCurrencyOf[contextToken] == 0) {
|
|
606
|
-
_cachedCurrencyOf[contextToken] = contextCurrency;
|
|
607
|
-
}
|
|
582
|
+
_storePeerChainAccounting({
|
|
583
|
+
sourceTimestamp: root.sourceTimestamp,
|
|
584
|
+
sourceTotalSupply: root.sourceTotalSupply,
|
|
585
|
+
sourceContexts: root.sourceContexts
|
|
586
|
+
});
|
|
587
|
+
}
|
|
608
588
|
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
merged = true;
|
|
624
|
-
break;
|
|
625
|
-
}
|
|
626
|
-
unchecked {
|
|
627
|
-
++j;
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
if (!merged) {
|
|
631
|
-
_peerContexts.push(
|
|
632
|
-
JBPeerChainContext({
|
|
633
|
-
currency: contextCurrency,
|
|
634
|
-
decimals: ctx.decimals,
|
|
635
|
-
surplus: ctx.surplus,
|
|
636
|
-
balance: ctx.balance
|
|
637
|
-
})
|
|
638
|
-
);
|
|
639
|
-
}
|
|
589
|
+
/// @notice Receive a peer-chain accounting snapshot from the remote sucker without updating any token-local inbox
|
|
590
|
+
/// root.
|
|
591
|
+
/// @dev This can only be called by the messenger contract on the local chain, with a message from the remote peer.
|
|
592
|
+
/// It shares the same freshness gate as `fromRemote`, so stale accounting-only messages cannot roll back fresher
|
|
593
|
+
/// state delivered by a root message.
|
|
594
|
+
/// @param snapshot The peer-chain accounting snapshot to receive.
|
|
595
|
+
function fromRemoteAccounting(JBAccountingSnapshot calldata snapshot) external override {
|
|
596
|
+
// Make sure that the message came from our peer.
|
|
597
|
+
// Use msg.sender (not _msgSender()) because bridge messengers never use ERC2771 meta-transactions.
|
|
598
|
+
// Using _msgSender() would allow a trusted forwarder to spoof the bridge messenger address via the
|
|
599
|
+
// ERC-2771 calldata suffix.
|
|
600
|
+
if (!_isRemotePeer(msg.sender)) {
|
|
601
|
+
revert JBSucker_NotPeer({caller: _toBytes32(msg.sender)});
|
|
602
|
+
}
|
|
640
603
|
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
}
|
|
604
|
+
// Validate the message version to reject incompatible messages.
|
|
605
|
+
if (snapshot.version != MESSAGE_VERSION) {
|
|
606
|
+
revert JBSucker_InvalidMessageVersion({received: snapshot.version, expected: MESSAGE_VERSION});
|
|
645
607
|
}
|
|
608
|
+
|
|
609
|
+
_storePeerChainAccounting({
|
|
610
|
+
sourceTimestamp: snapshot.sourceTimestamp,
|
|
611
|
+
sourceTotalSupply: snapshot.sourceTotalSupply,
|
|
612
|
+
sourceContexts: snapshot.sourceContexts
|
|
613
|
+
});
|
|
646
614
|
}
|
|
647
615
|
|
|
648
616
|
/// @notice Configure which remote-chain tokens each local terminal token maps to, enabling (or disabling) those
|
|
@@ -789,6 +757,26 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
789
757
|
emit DeprecationTimeUpdated({timestamp: timestamp, caller: _msgSender()});
|
|
790
758
|
}
|
|
791
759
|
|
|
760
|
+
/// @notice Send the latest peer-chain accounting data without sending an outbox root or paying the registry
|
|
761
|
+
/// `toRemoteFee`.
|
|
762
|
+
/// @dev The caller still provides any bridge transport payment through `msg.value`.
|
|
763
|
+
function syncAccountingData() external payable override {
|
|
764
|
+
// Accounting-only messages are outbound bridge messages and follow the same deprecation boundary as roots.
|
|
765
|
+
_requireSendingEnabled();
|
|
766
|
+
|
|
767
|
+
uint256 sourceTimestamp = _nextSourceTimestamp();
|
|
768
|
+
JBAccountingSnapshot memory snapshot = JBSuckerLib.buildAccountingSnapshot({
|
|
769
|
+
directory: DIRECTORY,
|
|
770
|
+
projectId: projectId(),
|
|
771
|
+
messageVersion: MESSAGE_VERSION,
|
|
772
|
+
sourceTimestamp: sourceTimestamp
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
emit AccountingDataSynced({sourceTimestamp: sourceTimestamp, caller: _msgSender()});
|
|
776
|
+
|
|
777
|
+
_sendAccountingSnapshotOverAMB({transportPayment: msg.value, snapshot: snapshot});
|
|
778
|
+
}
|
|
779
|
+
|
|
792
780
|
/// @notice Send the accumulated outbox merkle root and locked terminal-token funds for a given `token` across the
|
|
793
781
|
/// bridge to the remote peer sucker. Anyone can call this once entries exist in the outbox. Requires `msg.value`
|
|
794
782
|
/// to cover the registry's `toRemoteFee` plus any bridge transport payment.
|
|
@@ -939,12 +927,6 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
939
927
|
return amount;
|
|
940
928
|
}
|
|
941
929
|
|
|
942
|
-
/// @notice Returns the chain on which the peer is located.
|
|
943
|
-
/// @dev `public` (not `external`) so the combined peer-chain views in this contract can read it internally
|
|
944
|
-
/// without a self-call; subclasses implement the bridge-specific chain ID.
|
|
945
|
-
/// @return chain ID of the peer.
|
|
946
|
-
function peerChainId() public view virtual returns (uint256);
|
|
947
|
-
|
|
948
930
|
/// @notice The peer sucker on the remote chain, as a bytes32 for cross-VM compatibility.
|
|
949
931
|
/// @dev Defaults to `_toBytes32(address(this))`, assuming deterministic cross-chain deployment via CREATE2. The
|
|
950
932
|
/// deployer (`JBSuckerDeployer`) uses `salt = keccak256(abi.encodePacked(_msgSender(), salt))` to ensure
|
|
@@ -957,6 +939,12 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
957
939
|
return _toBytes32(address(this));
|
|
958
940
|
}
|
|
959
941
|
|
|
942
|
+
/// @notice Returns the chain on which the peer is located.
|
|
943
|
+
/// @dev `public` (not `external`) so the combined peer-chain views in this contract can read it internally
|
|
944
|
+
/// without a self-call; subclasses implement the bridge-specific chain ID.
|
|
945
|
+
/// @return chain ID of the peer.
|
|
946
|
+
function peerChainId() public view virtual returns (uint256);
|
|
947
|
+
|
|
960
948
|
/// @notice The ID of the project (on the local chain) that this sucker is associated with.
|
|
961
949
|
/// @return The local project ID.
|
|
962
950
|
function projectId() public view returns (uint256) {
|
|
@@ -1003,43 +991,39 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
1003
991
|
// ----------------------- public transactions ----------------------- //
|
|
1004
992
|
//*********************************************************************//
|
|
1005
993
|
|
|
1006
|
-
/// @notice Claim
|
|
1007
|
-
///
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
emit RetainedToRemoteFeeClaimed({
|
|
1022
|
-
account: account, beneficiary: beneficiary, amount: amount, caller: _msgSender()
|
|
994
|
+
/// @notice Claim a single bridged entry: verifies the merkle proof against the inbox root, mints the specified
|
|
995
|
+
/// project tokens for the beneficiary, and deposits the terminal tokens into the project's local balance.
|
|
996
|
+
/// @param claimData The terminal token, merkle tree leaf, and proof for the claim.
|
|
997
|
+
function claim(JBClaim calldata claimData) public virtual override {
|
|
998
|
+
// Attempt to validate the proof against the inbox tree for the terminal token. The leaf hash includes
|
|
999
|
+
// `claimData.leaf.metadata` so the proof is only valid for the exact (amount, beneficiary, metadata) tuple the
|
|
1000
|
+
// origin committed to.
|
|
1001
|
+
_validate({
|
|
1002
|
+
projectTokenCount: claimData.leaf.projectTokenCount,
|
|
1003
|
+
terminalToken: claimData.token,
|
|
1004
|
+
terminalTokenAmount: claimData.leaf.terminalTokenAmount,
|
|
1005
|
+
beneficiary: claimData.leaf.beneficiary,
|
|
1006
|
+
metadata: claimData.leaf.metadata,
|
|
1007
|
+
index: claimData.leaf.index,
|
|
1008
|
+
leaves: claimData.proof
|
|
1023
1009
|
});
|
|
1024
|
-
}
|
|
1025
|
-
|
|
1026
|
-
/// @notice Claim retained failed transport-payment refund ETH.
|
|
1027
|
-
/// @param beneficiary The address that should receive the retained ETH.
|
|
1028
|
-
function claimRetainedTransportPaymentRefund(address payable beneficiary) external override {
|
|
1029
|
-
if (beneficiary == address(0)) revert JBSucker_ZeroBeneficiary({beneficiary: bytes32(0)});
|
|
1030
|
-
|
|
1031
|
-
address account = _msgSender();
|
|
1032
|
-
uint256 amount = retainedTransportPaymentRefundOf[account];
|
|
1033
|
-
if (amount == 0) revert JBSucker_NoRetainedTransportPaymentRefund(account);
|
|
1034
1010
|
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1011
|
+
emit Claimed({
|
|
1012
|
+
beneficiary: claimData.leaf.beneficiary,
|
|
1013
|
+
token: claimData.token,
|
|
1014
|
+
projectTokenCount: claimData.leaf.projectTokenCount,
|
|
1015
|
+
terminalTokenAmount: claimData.leaf.terminalTokenAmount,
|
|
1016
|
+
index: claimData.leaf.index,
|
|
1017
|
+
metadata: claimData.leaf.metadata,
|
|
1018
|
+
caller: _msgSender()
|
|
1019
|
+
});
|
|
1039
1020
|
|
|
1040
|
-
//
|
|
1041
|
-
|
|
1042
|
-
|
|
1021
|
+
// Give the user their project tokens, send the project its funds.
|
|
1022
|
+
_handleClaim({
|
|
1023
|
+
terminalToken: claimData.token,
|
|
1024
|
+
terminalTokenAmount: claimData.leaf.terminalTokenAmount,
|
|
1025
|
+
projectTokenAmount: claimData.leaf.projectTokenCount,
|
|
1026
|
+
beneficiary: claimData.leaf.beneficiary
|
|
1043
1027
|
});
|
|
1044
1028
|
}
|
|
1045
1029
|
|
|
@@ -1056,20 +1040,14 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
1056
1040
|
_initialize({initialProjectId: localProjectId, remotePeer: remotePeer});
|
|
1057
1041
|
}
|
|
1058
1042
|
|
|
1059
|
-
/// @notice Initializes the sucker's project and optional peer address.
|
|
1060
|
-
/// @param initialProjectId The ID of the project (on the local chain) that this sucker is associated with.
|
|
1061
|
-
/// @param remotePeer The remote peer address. Leave zero to use the default deterministic same-address peer.
|
|
1062
|
-
function _initialize(uint256 initialProjectId, bytes32 remotePeer) internal {
|
|
1063
|
-
_localProjectId = initialProjectId;
|
|
1064
|
-
_peer = remotePeer;
|
|
1065
|
-
deployer = _msgSender();
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
1043
|
/// @notice Map an ERC-20 token on the local chain to a remote-chain ERC-20 token for bridging.
|
|
1069
1044
|
/// @param map The local and remote terminal token addresses to map, and minimum amount/gas limits for bridging
|
|
1070
1045
|
/// them.
|
|
1071
1046
|
function mapToken(JBTokenMapping calldata map) public payable override {
|
|
1072
|
-
_mapToken({map: map, transportPaymentValue: msg.value});
|
|
1047
|
+
uint256 transportPaymentSpent = _mapToken({map: map, transportPaymentValue: msg.value});
|
|
1048
|
+
if (msg.value > transportPaymentSpent) {
|
|
1049
|
+
_sendNativeTo({beneficiary: payable(_msgSender()), amount: msg.value - transportPaymentSpent});
|
|
1050
|
+
}
|
|
1073
1051
|
}
|
|
1074
1052
|
|
|
1075
1053
|
//*********************************************************************//
|
|
@@ -1165,6 +1143,15 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
1165
1143
|
});
|
|
1166
1144
|
}
|
|
1167
1145
|
|
|
1146
|
+
/// @notice Initializes the sucker's project and optional peer address.
|
|
1147
|
+
/// @param initialProjectId The ID of the project (on the local chain) that this sucker is associated with.
|
|
1148
|
+
/// @param remotePeer The remote peer address. Leave zero to use the default deterministic same-address peer.
|
|
1149
|
+
function _initialize(uint256 initialProjectId, bytes32 remotePeer) internal {
|
|
1150
|
+
_localProjectId = initialProjectId;
|
|
1151
|
+
_peer = remotePeer;
|
|
1152
|
+
deployer = _msgSender();
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1168
1155
|
/// @notice Inserts a new leaf into the outbox merkle tree for the specified `token`.
|
|
1169
1156
|
/// @param projectTokenCount The amount of project tokens to cash out.
|
|
1170
1157
|
/// @param token The terminal token to cash out for.
|
|
@@ -1377,6 +1364,49 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
1377
1364
|
assert(reclaimedAmount == _balanceOf({token: token, addr: address(this)}) - balanceBefore);
|
|
1378
1365
|
}
|
|
1379
1366
|
|
|
1367
|
+
/// @notice Retain a failed `toRemoteFee` payment for later caller refund.
|
|
1368
|
+
/// @param account The account that can reclaim the retained fee.
|
|
1369
|
+
/// @param amount The retained fee amount.
|
|
1370
|
+
function _retainToRemoteFee(address account, uint256 amount) internal {
|
|
1371
|
+
retainedToRemoteFeeOf[account] += amount;
|
|
1372
|
+
retainedToRemoteFeeBalance += amount;
|
|
1373
|
+
emit RetainedToRemoteFee({account: account, amount: amount, caller: _msgSender()});
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
/// @notice Retains a failed transport-payment refund as account-scoped native credit.
|
|
1377
|
+
/// @param account The account that can reclaim the retained refund.
|
|
1378
|
+
/// @param amount The retained refund amount.
|
|
1379
|
+
function _retainTransportPaymentRefund(address account, uint256 amount) internal {
|
|
1380
|
+
retainedTransportPaymentRefundOf[account] += amount;
|
|
1381
|
+
retainedTransportPaymentRefundBalance += amount;
|
|
1382
|
+
emit RetainedTransportPaymentRefund({account: account, amount: amount, caller: _msgSender()});
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
/// @notice Performs the logic to send an accounting-only message to the peer over the AMB.
|
|
1386
|
+
/// @dev Bridge-specific implementations override this for supported transports.
|
|
1387
|
+
/// @param transportPayment The amount of `msg.value` paid to the transport for this message.
|
|
1388
|
+
/// @param snapshot The accounting snapshot to send to the remote chain.
|
|
1389
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
1390
|
+
function _sendAccountingSnapshotOverAMB(
|
|
1391
|
+
uint256 transportPayment,
|
|
1392
|
+
JBAccountingSnapshot memory snapshot
|
|
1393
|
+
)
|
|
1394
|
+
internal
|
|
1395
|
+
virtual
|
|
1396
|
+
{
|
|
1397
|
+
transportPayment;
|
|
1398
|
+
snapshot;
|
|
1399
|
+
revert JBSucker_AccountingSyncUnsupported();
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
/// @notice Send native tokens, reverting if the recipient rejects them.
|
|
1403
|
+
/// @param beneficiary The recipient.
|
|
1404
|
+
/// @param amount The amount to send.
|
|
1405
|
+
function _sendNativeTo(address payable beneficiary, uint256 amount) internal {
|
|
1406
|
+
(bool success,) = beneficiary.call{value: amount}("");
|
|
1407
|
+
if (!success) revert JBSucker_RefundFailed({beneficiary: beneficiary, amount: amount});
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1380
1410
|
/// @notice Send the outbox root for the specified token to the remote peer.
|
|
1381
1411
|
/// @dev Some bridges require a nonzero `transportPayment`; zero-cost bridges must reject nonzero values.
|
|
1382
1412
|
/// @param transportPayment The amount of `msg.value` paid to the transport for this message.
|
|
@@ -1436,14 +1466,6 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
1436
1466
|
});
|
|
1437
1467
|
}
|
|
1438
1468
|
|
|
1439
|
-
/// @notice Send native tokens, reverting if the recipient rejects them.
|
|
1440
|
-
/// @param beneficiary The recipient.
|
|
1441
|
-
/// @param amount The amount to send.
|
|
1442
|
-
function _sendNativeTo(address payable beneficiary, uint256 amount) internal {
|
|
1443
|
-
(bool success,) = beneficiary.call{value: amount}("");
|
|
1444
|
-
if (!success) revert JBSucker_RefundFailed({beneficiary: beneficiary, amount: amount});
|
|
1445
|
-
}
|
|
1446
|
-
|
|
1447
1469
|
/// @notice Performs the logic to send a message to the peer over the AMB.
|
|
1448
1470
|
/// @dev This is chain/sucker/bridge specific logic.
|
|
1449
1471
|
/// @param transportPayment The amount of `msg.value` that is going to get paid for sending this message.
|
|
@@ -1464,6 +1486,81 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
1464
1486
|
internal
|
|
1465
1487
|
virtual;
|
|
1466
1488
|
|
|
1489
|
+
/// @notice Store the latest peer-chain accounting snapshot if it is fresher than the cached one.
|
|
1490
|
+
/// @dev The context set is rebuilt from scratch for each fresher snapshot. A stale snapshot is ignored instead of
|
|
1491
|
+
/// reverting so bridge delivery order cannot roll back supply, surplus, or balance.
|
|
1492
|
+
/// @param sourceTimestamp The source-chain freshness key.
|
|
1493
|
+
/// @param sourceTotalSupply The source-chain total project-token supply.
|
|
1494
|
+
/// @param sourceContexts The source-chain per-context surplus and balance.
|
|
1495
|
+
function _storePeerChainAccounting(
|
|
1496
|
+
uint256 sourceTimestamp,
|
|
1497
|
+
uint256 sourceTotalSupply,
|
|
1498
|
+
JBSourceContext[] calldata sourceContexts
|
|
1499
|
+
)
|
|
1500
|
+
internal
|
|
1501
|
+
{
|
|
1502
|
+
// Only accept snapshots whose source freshness key is strictly newer than the last accepted one.
|
|
1503
|
+
if (sourceTimestamp <= snapshotTimestamp) return;
|
|
1504
|
+
|
|
1505
|
+
// Advance the snapshot freshness key used by the registry to dedup same-peer suckers.
|
|
1506
|
+
snapshotTimestamp = sourceTimestamp;
|
|
1507
|
+
|
|
1508
|
+
// Update unconditionally — a legitimate zero supply must clear phantom cached supply.
|
|
1509
|
+
peerChainTotalSupply = sourceTotalSupply;
|
|
1510
|
+
|
|
1511
|
+
// Rebuild the per-currency context set from scratch. A context that dropped out of this fresher snapshot is
|
|
1512
|
+
// simply absent from the new set, so no per-entry clearing is needed.
|
|
1513
|
+
delete _peerContexts;
|
|
1514
|
+
|
|
1515
|
+
// Fold each source context into the local currency it resolves to. Resolution prefers the token mapping (so a
|
|
1516
|
+
// same-asset token at a different remote address binds to the right local context) and falls back to identity
|
|
1517
|
+
// for same-address tokens; the local currency is then derived from that resolved local token's authoritative
|
|
1518
|
+
// accounting context, NOT trusted from the wire.
|
|
1519
|
+
uint256 numContexts = sourceContexts.length;
|
|
1520
|
+
for (uint256 i; i < numContexts;) {
|
|
1521
|
+
JBSourceContext calldata ctx = sourceContexts[i];
|
|
1522
|
+
|
|
1523
|
+
address contextToken = _localTokenForRemoteToken[ctx.token];
|
|
1524
|
+
if (contextToken == address(0)) contextToken = _toAddress(ctx.token);
|
|
1525
|
+
(uint32 contextCurrency, bool authoritative) = _localCurrencyOf(contextToken);
|
|
1526
|
+
|
|
1527
|
+
// Cache an authoritative currency so later snapshots reuse it instead of re-reading the terminal. The
|
|
1528
|
+
// accounting-context currency is immutable, so the cache never goes stale; a not-yet-configured token is
|
|
1529
|
+
// left uncached and re-read next time.
|
|
1530
|
+
if (authoritative && _cachedCurrencyOf[contextToken] == 0) {
|
|
1531
|
+
_cachedCurrencyOf[contextToken] = contextCurrency;
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
// Accumulate into an existing entry that matches on BOTH currency AND decimals, or append a new one. The
|
|
1535
|
+
// decimals must match: `surplus`/`balance` are raw, un-valued token amounts, so two contexts that share a
|
|
1536
|
+
// currency but carry different decimals are on different scales and cannot be summed directly.
|
|
1537
|
+
uint256 numStored = _peerContexts.length;
|
|
1538
|
+
bool merged;
|
|
1539
|
+
for (uint256 j; j < numStored;) {
|
|
1540
|
+
if (_peerContexts[j].currency == contextCurrency && _peerContexts[j].decimals == ctx.decimals) {
|
|
1541
|
+
_peerContexts[j].surplus = _saturatingAddU128(_peerContexts[j].surplus, ctx.surplus);
|
|
1542
|
+
_peerContexts[j].balance = _saturatingAddU128(_peerContexts[j].balance, ctx.balance);
|
|
1543
|
+
merged = true;
|
|
1544
|
+
break;
|
|
1545
|
+
}
|
|
1546
|
+
unchecked {
|
|
1547
|
+
++j;
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
if (!merged) {
|
|
1551
|
+
_peerContexts.push(
|
|
1552
|
+
JBPeerChainContext({
|
|
1553
|
+
currency: contextCurrency, decimals: ctx.decimals, surplus: ctx.surplus, balance: ctx.balance
|
|
1554
|
+
})
|
|
1555
|
+
);
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
unchecked {
|
|
1559
|
+
++i;
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1467
1564
|
/// @notice Validates a leaf as being in the inbox merkle tree and registers the leaf as executed (to prevent
|
|
1468
1565
|
/// double-spending).
|
|
1469
1566
|
/// @dev Reverts if the leaf is invalid.
|
|
@@ -1657,28 +1754,6 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
1657
1754
|
return IERC20(token).balanceOf(addr);
|
|
1658
1755
|
}
|
|
1659
1756
|
|
|
1660
|
-
/// @notice Compute the merkle root of an outbox tree by reading its branch into memory and delegating
|
|
1661
|
-
/// to JBSuckerLib.computeTreeRoot (via DELEGATECALL). Replaces inlined MerkleLib.root() to save ~3KB.
|
|
1662
|
-
/// @param tree The storage-backed merkle tree.
|
|
1663
|
-
/// @return The merkle root.
|
|
1664
|
-
function _computeOutboxRoot(MerkleLib.Tree storage tree) internal view returns (bytes32) {
|
|
1665
|
-
uint256 count = tree.count;
|
|
1666
|
-
// An empty tree has a known zero root.
|
|
1667
|
-
if (count == 0) return MerkleLib.Z_32;
|
|
1668
|
-
|
|
1669
|
-
// Copy only the non-zero branch slots from storage into memory for the root computation.
|
|
1670
|
-
bytes32[_TREE_DEPTH] memory branch;
|
|
1671
|
-
for (uint256 i; i < _TREE_DEPTH;) {
|
|
1672
|
-
if (count & (uint256(1) << i) != 0) {
|
|
1673
|
-
branch[i] = tree.branch[i];
|
|
1674
|
-
}
|
|
1675
|
-
unchecked {
|
|
1676
|
-
++i;
|
|
1677
|
-
}
|
|
1678
|
-
}
|
|
1679
|
-
return JBSuckerLib.computeTreeRoot({branch: branch, count: count});
|
|
1680
|
-
}
|
|
1681
|
-
|
|
1682
1757
|
/// @notice Builds a hash as they are stored in the merkle tree.
|
|
1683
1758
|
/// @param projectTokenCount The number of project tokens to cash out.
|
|
1684
1759
|
/// @param terminalTokenAmount The amount of terminal tokens to reclaim from the cash out.
|
|
@@ -1707,6 +1782,28 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
1707
1782
|
}
|
|
1708
1783
|
}
|
|
1709
1784
|
|
|
1785
|
+
/// @notice Compute the merkle root of an outbox tree by reading its branch into memory and delegating
|
|
1786
|
+
/// to JBSuckerLib.computeTreeRoot (via DELEGATECALL). Replaces inlined MerkleLib.root() to save ~3KB.
|
|
1787
|
+
/// @param tree The storage-backed merkle tree.
|
|
1788
|
+
/// @return The merkle root.
|
|
1789
|
+
function _computeOutboxRoot(MerkleLib.Tree storage tree) internal view returns (bytes32) {
|
|
1790
|
+
uint256 count = tree.count;
|
|
1791
|
+
// An empty tree has a known zero root.
|
|
1792
|
+
if (count == 0) return MerkleLib.Z_32;
|
|
1793
|
+
|
|
1794
|
+
// Copy only the non-zero branch slots from storage into memory for the root computation.
|
|
1795
|
+
bytes32[_TREE_DEPTH] memory branch;
|
|
1796
|
+
for (uint256 i; i < _TREE_DEPTH;) {
|
|
1797
|
+
if (count & (uint256(1) << i) != 0) {
|
|
1798
|
+
branch[i] = tree.branch[i];
|
|
1799
|
+
}
|
|
1800
|
+
unchecked {
|
|
1801
|
+
++i;
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
return JBSuckerLib.computeTreeRoot({branch: branch, count: count});
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1710
1807
|
/// @notice The length of the context suffix for ERC-2771 meta-transactions.
|
|
1711
1808
|
/// @dev ERC-2771 specifies the context as being a single address (20 bytes).
|
|
1712
1809
|
/// @return The suffix length in bytes.
|
|
@@ -1779,24 +1876,6 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
1779
1876
|
return PROJECTS.ownerOf(forProjectId);
|
|
1780
1877
|
}
|
|
1781
1878
|
|
|
1782
|
-
/// @notice Retain a failed `toRemoteFee` payment for later caller refund.
|
|
1783
|
-
/// @param account The account that can reclaim the retained fee.
|
|
1784
|
-
/// @param amount The retained fee amount.
|
|
1785
|
-
function _retainToRemoteFee(address account, uint256 amount) internal {
|
|
1786
|
-
retainedToRemoteFeeOf[account] += amount;
|
|
1787
|
-
retainedToRemoteFeeBalance += amount;
|
|
1788
|
-
emit RetainedToRemoteFee({account: account, amount: amount, caller: _msgSender()});
|
|
1789
|
-
}
|
|
1790
|
-
|
|
1791
|
-
/// @notice Retains a failed transport-payment refund as account-scoped native credit.
|
|
1792
|
-
/// @param account The account that can reclaim the retained refund.
|
|
1793
|
-
/// @param amount The retained refund amount.
|
|
1794
|
-
function _retainTransportPaymentRefund(address account, uint256 amount) internal {
|
|
1795
|
-
retainedTransportPaymentRefundOf[account] += amount;
|
|
1796
|
-
retainedTransportPaymentRefundBalance += amount;
|
|
1797
|
-
emit RetainedTransportPaymentRefund({account: account, amount: amount, caller: _msgSender()});
|
|
1798
|
-
}
|
|
1799
|
-
|
|
1800
1879
|
/// @notice Returns the peer address as an EVM address.
|
|
1801
1880
|
/// @return The peer address.
|
|
1802
1881
|
function _peerAddress() internal view returns (address) {
|
|
@@ -1937,12 +2016,7 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
1937
2016
|
)
|
|
1938
2017
|
private
|
|
1939
2018
|
{
|
|
1940
|
-
uint256 sourceTimestamp;
|
|
1941
|
-
unchecked {
|
|
1942
|
-
// High bits preserve the source-chain timestamp for operators/indexers. Low bits make same-timestamp
|
|
1943
|
-
// roots distinct so the receiver can still reject stale project-wide snapshots with a strict `>`.
|
|
1944
|
-
sourceTimestamp = (block.timestamp << 128) | ++_outboundSnapshotSequence;
|
|
1945
|
-
}
|
|
2019
|
+
uint256 sourceTimestamp = _nextSourceTimestamp();
|
|
1946
2020
|
|
|
1947
2021
|
JBMessageRoot memory message = JBSuckerLib.buildSnapshotMessage({
|
|
1948
2022
|
directory: DIRECTORY,
|
|
@@ -1965,4 +2039,14 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
1965
2039
|
message: message
|
|
1966
2040
|
});
|
|
1967
2041
|
}
|
|
2042
|
+
|
|
2043
|
+
/// @notice Build the next source-chain freshness key.
|
|
2044
|
+
/// @dev High bits preserve the source-chain timestamp for operators/indexers. Low bits make same-timestamp
|
|
2045
|
+
/// snapshots distinct so the receiver can reject stale project-wide snapshots with a strict `>`.
|
|
2046
|
+
/// @return sourceTimestamp The next freshness key.
|
|
2047
|
+
function _nextSourceTimestamp() private returns (uint256 sourceTimestamp) {
|
|
2048
|
+
unchecked {
|
|
2049
|
+
sourceTimestamp = (block.timestamp << 128) | ++_outboundSnapshotSequence;
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
1968
2052
|
}
|