@bananapus/suckers-v6 0.0.1

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.
Files changed (149) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +422 -0
  3. package/SECURITY.md +55 -0
  4. package/SKILLS.md +163 -0
  5. package/deployments/nana-suckers-v5/arbitrum/JBArbitrumSucker.json +1425 -0
  6. package/deployments/nana-suckers-v5/arbitrum/JBArbitrumSuckerDeployer.json +391 -0
  7. package/deployments/nana-suckers-v5/arbitrum/JBCCIPSucker.json +1479 -0
  8. package/deployments/nana-suckers-v5/arbitrum/JBCCIPSuckerDeployer.json +433 -0
  9. package/deployments/nana-suckers-v5/arbitrum/JBCCIPSuckerDeployer_1.json +433 -0
  10. package/deployments/nana-suckers-v5/arbitrum/JBCCIPSuckerDeployer_2.json +433 -0
  11. package/deployments/nana-suckers-v5/arbitrum/JBCCIPSucker_1.json +1479 -0
  12. package/deployments/nana-suckers-v5/arbitrum/JBCCIPSucker_2.json +1479 -0
  13. package/deployments/nana-suckers-v5/arbitrum/JBSuckerRegistry.json +690 -0
  14. package/deployments/nana-suckers-v5/arbitrum_sepolia/JBArbitrumSucker.json +1425 -0
  15. package/deployments/nana-suckers-v5/arbitrum_sepolia/JBArbitrumSuckerDeployer.json +391 -0
  16. package/deployments/nana-suckers-v5/arbitrum_sepolia/JBCCIPSucker.json +1479 -0
  17. package/deployments/nana-suckers-v5/arbitrum_sepolia/JBCCIPSuckerDeployer.json +433 -0
  18. package/deployments/nana-suckers-v5/arbitrum_sepolia/JBCCIPSuckerDeployer_1.json +433 -0
  19. package/deployments/nana-suckers-v5/arbitrum_sepolia/JBCCIPSuckerDeployer_2.json +433 -0
  20. package/deployments/nana-suckers-v5/arbitrum_sepolia/JBCCIPSucker_1.json +1479 -0
  21. package/deployments/nana-suckers-v5/arbitrum_sepolia/JBCCIPSucker_2.json +1479 -0
  22. package/deployments/nana-suckers-v5/arbitrum_sepolia/JBSuckerRegistry.json +690 -0
  23. package/deployments/nana-suckers-v5/base/JBBaseSucker.json +1389 -0
  24. package/deployments/nana-suckers-v5/base/JBBaseSuckerDeployer.json +376 -0
  25. package/deployments/nana-suckers-v5/base/JBCCIPSucker.json +1483 -0
  26. package/deployments/nana-suckers-v5/base/JBCCIPSuckerDeployer.json +436 -0
  27. package/deployments/nana-suckers-v5/base/JBCCIPSuckerDeployer_1.json +436 -0
  28. package/deployments/nana-suckers-v5/base/JBCCIPSuckerDeployer_2.json +436 -0
  29. package/deployments/nana-suckers-v5/base/JBCCIPSucker_1.json +1483 -0
  30. package/deployments/nana-suckers-v5/base/JBCCIPSucker_2.json +1483 -0
  31. package/deployments/nana-suckers-v5/base/JBSuckerRegistry.json +694 -0
  32. package/deployments/nana-suckers-v5/base_sepolia/JBBaseSucker.json +1389 -0
  33. package/deployments/nana-suckers-v5/base_sepolia/JBBaseSuckerDeployer.json +376 -0
  34. package/deployments/nana-suckers-v5/base_sepolia/JBCCIPSucker.json +1483 -0
  35. package/deployments/nana-suckers-v5/base_sepolia/JBCCIPSuckerDeployer.json +436 -0
  36. package/deployments/nana-suckers-v5/base_sepolia/JBCCIPSuckerDeployer_1.json +436 -0
  37. package/deployments/nana-suckers-v5/base_sepolia/JBCCIPSuckerDeployer_2.json +436 -0
  38. package/deployments/nana-suckers-v5/base_sepolia/JBCCIPSucker_1.json +1483 -0
  39. package/deployments/nana-suckers-v5/base_sepolia/JBCCIPSucker_2.json +1483 -0
  40. package/deployments/nana-suckers-v5/base_sepolia/JBSuckerRegistry.json +694 -0
  41. package/deployments/nana-suckers-v5/ethereum/JBArbitrumSucker.json +1429 -0
  42. package/deployments/nana-suckers-v5/ethereum/JBArbitrumSuckerDeployer.json +394 -0
  43. package/deployments/nana-suckers-v5/ethereum/JBBaseSucker.json +1389 -0
  44. package/deployments/nana-suckers-v5/ethereum/JBBaseSuckerDeployer.json +376 -0
  45. package/deployments/nana-suckers-v5/ethereum/JBCCIPSucker.json +1483 -0
  46. package/deployments/nana-suckers-v5/ethereum/JBCCIPSuckerDeployer.json +436 -0
  47. package/deployments/nana-suckers-v5/ethereum/JBCCIPSuckerDeployer_1.json +436 -0
  48. package/deployments/nana-suckers-v5/ethereum/JBCCIPSuckerDeployer_2.json +436 -0
  49. package/deployments/nana-suckers-v5/ethereum/JBCCIPSucker_1.json +1483 -0
  50. package/deployments/nana-suckers-v5/ethereum/JBCCIPSucker_2.json +1483 -0
  51. package/deployments/nana-suckers-v5/ethereum/JBOptimismSucker.json +1389 -0
  52. package/deployments/nana-suckers-v5/ethereum/JBOptimismSuckerDeployer.json +376 -0
  53. package/deployments/nana-suckers-v5/ethereum/JBSuckerRegistry.json +694 -0
  54. package/deployments/nana-suckers-v5/optimism/JBCCIPSucker.json +1479 -0
  55. package/deployments/nana-suckers-v5/optimism/JBCCIPSuckerDeployer.json +433 -0
  56. package/deployments/nana-suckers-v5/optimism/JBCCIPSuckerDeployer_1.json +433 -0
  57. package/deployments/nana-suckers-v5/optimism/JBCCIPSuckerDeployer_2.json +433 -0
  58. package/deployments/nana-suckers-v5/optimism/JBCCIPSucker_1.json +1479 -0
  59. package/deployments/nana-suckers-v5/optimism/JBCCIPSucker_2.json +1479 -0
  60. package/deployments/nana-suckers-v5/optimism/JBOptimismSucker.json +1385 -0
  61. package/deployments/nana-suckers-v5/optimism/JBOptimismSuckerDeployer.json +373 -0
  62. package/deployments/nana-suckers-v5/optimism/JBSuckerRegistry.json +690 -0
  63. package/deployments/nana-suckers-v5/optimism_sepolia/JBCCIPSucker.json +1483 -0
  64. package/deployments/nana-suckers-v5/optimism_sepolia/JBCCIPSuckerDeployer.json +436 -0
  65. package/deployments/nana-suckers-v5/optimism_sepolia/JBCCIPSuckerDeployer_1.json +436 -0
  66. package/deployments/nana-suckers-v5/optimism_sepolia/JBCCIPSuckerDeployer_2.json +436 -0
  67. package/deployments/nana-suckers-v5/optimism_sepolia/JBCCIPSucker_1.json +1483 -0
  68. package/deployments/nana-suckers-v5/optimism_sepolia/JBCCIPSucker_2.json +1483 -0
  69. package/deployments/nana-suckers-v5/optimism_sepolia/JBOptimismSucker.json +1389 -0
  70. package/deployments/nana-suckers-v5/optimism_sepolia/JBOptimismSuckerDeployer.json +376 -0
  71. package/deployments/nana-suckers-v5/optimism_sepolia/JBSuckerRegistry.json +694 -0
  72. package/deployments/nana-suckers-v5/sepolia/JBArbitrumSucker.json +1429 -0
  73. package/deployments/nana-suckers-v5/sepolia/JBArbitrumSuckerDeployer.json +394 -0
  74. package/deployments/nana-suckers-v5/sepolia/JBBaseSucker.json +1389 -0
  75. package/deployments/nana-suckers-v5/sepolia/JBBaseSuckerDeployer.json +376 -0
  76. package/deployments/nana-suckers-v5/sepolia/JBCCIPSucker.json +1483 -0
  77. package/deployments/nana-suckers-v5/sepolia/JBCCIPSuckerDeployer.json +436 -0
  78. package/deployments/nana-suckers-v5/sepolia/JBCCIPSuckerDeployer_1.json +436 -0
  79. package/deployments/nana-suckers-v5/sepolia/JBCCIPSuckerDeployer_2.json +436 -0
  80. package/deployments/nana-suckers-v5/sepolia/JBCCIPSucker_1.json +1483 -0
  81. package/deployments/nana-suckers-v5/sepolia/JBCCIPSucker_2.json +1483 -0
  82. package/deployments/nana-suckers-v5/sepolia/JBOptimismSucker.json +1389 -0
  83. package/deployments/nana-suckers-v5/sepolia/JBOptimismSuckerDeployer.json +376 -0
  84. package/deployments/nana-suckers-v5/sepolia/JBSuckerRegistry.json +694 -0
  85. package/foundry.lock +11 -0
  86. package/foundry.toml +22 -0
  87. package/package.json +33 -0
  88. package/remappings.txt +1 -0
  89. package/script/Deploy.s.sol +506 -0
  90. package/script/helpers/SuckerDeploymentLib.sol +97 -0
  91. package/slither-ci.config.json +10 -0
  92. package/sphinx.lock +476 -0
  93. package/src/JBArbitrumSucker.sol +311 -0
  94. package/src/JBBaseSucker.sol +41 -0
  95. package/src/JBCCIPSucker.sol +303 -0
  96. package/src/JBOptimismSucker.sol +143 -0
  97. package/src/JBSucker.sol +1159 -0
  98. package/src/JBSuckerRegistry.sol +262 -0
  99. package/src/deployers/JBArbitrumSuckerDeployer.sol +86 -0
  100. package/src/deployers/JBBaseSuckerDeployer.sol +26 -0
  101. package/src/deployers/JBCCIPSuckerDeployer.sol +88 -0
  102. package/src/deployers/JBOptimismSuckerDeployer.sol +82 -0
  103. package/src/deployers/JBSuckerDeployer.sol +147 -0
  104. package/src/enums/JBAddToBalanceMode.sol +11 -0
  105. package/src/enums/JBLayer.sol +8 -0
  106. package/src/enums/JBSuckerState.sol +14 -0
  107. package/src/interfaces/IArbGatewayRouter.sol +11 -0
  108. package/src/interfaces/IArbL1GatewayRouter.sol +17 -0
  109. package/src/interfaces/IArbL2GatewayRouter.sol +14 -0
  110. package/src/interfaces/ICCIPRouter.sol +11 -0
  111. package/src/interfaces/IJBArbitrumSucker.sol +13 -0
  112. package/src/interfaces/IJBArbitrumSuckerDeployer.sol +12 -0
  113. package/src/interfaces/IJBCCIPSuckerDeployer.sol +15 -0
  114. package/src/interfaces/IJBOpSuckerDeployer.sol +11 -0
  115. package/src/interfaces/IJBOptimismSucker.sol +10 -0
  116. package/src/interfaces/IJBSucker.sol +144 -0
  117. package/src/interfaces/IJBSuckerDeployer.sol +40 -0
  118. package/src/interfaces/IJBSuckerExtended.sol +22 -0
  119. package/src/interfaces/IJBSuckerRegistry.sol +75 -0
  120. package/src/interfaces/IOPMessenger.sol +18 -0
  121. package/src/interfaces/IOPStandardBridge.sol +29 -0
  122. package/src/interfaces/IWrappedNativeToken.sol +13 -0
  123. package/src/libraries/ARBAddresses.sol +17 -0
  124. package/src/libraries/ARBChains.sol +11 -0
  125. package/src/libraries/CCIPHelper.sol +136 -0
  126. package/src/structs/JBClaim.sol +13 -0
  127. package/src/structs/JBInboxTreeRoot.sol +12 -0
  128. package/src/structs/JBLeaf.sol +14 -0
  129. package/src/structs/JBMessageRoot.sol +16 -0
  130. package/src/structs/JBOutboxTree.sol +18 -0
  131. package/src/structs/JBRemoteToken.sol +17 -0
  132. package/src/structs/JBSuckerDeployerConfig.sol +12 -0
  133. package/src/structs/JBSuckersPair.sol +11 -0
  134. package/src/structs/JBTokenMapping.sol +13 -0
  135. package/src/utils/MerkleLib.sol +1020 -0
  136. package/test/Fork.t.sol +514 -0
  137. package/test/InteropCompat.t.sol +676 -0
  138. package/test/SuckerAttacks.t.sol +509 -0
  139. package/test/SuckerDeepAttacks.t.sol +1563 -0
  140. package/test/mocks/ERC20Mock.sol +36 -0
  141. package/test/mocks/MockMessenger.sol +42 -0
  142. package/test/unit/arb.t.sol +28 -0
  143. package/test/unit/ccip_native_interop.t.sol +719 -0
  144. package/test/unit/ccip_refund.t.sol +234 -0
  145. package/test/unit/deployer.t.sol +475 -0
  146. package/test/unit/emergency.t.sol +305 -0
  147. package/test/unit/merkle.t.sol +212 -0
  148. package/test/unit/multi_chain_evolution.t.sol +622 -0
  149. package/test/unit/registry.t.sol +26 -0
@@ -0,0 +1,719 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.13;
3
+
4
+ import "forge-std/Test.sol";
5
+
6
+ import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol";
7
+ import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol";
8
+ import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
9
+ import {IJBPermissions} from "@bananapus/core-v6/src/interfaces/IJBPermissions.sol";
10
+ import {IJBTokens} from "@bananapus/core-v6/src/interfaces/IJBTokens.sol";
11
+ import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
12
+ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
13
+ import {LibClone} from "solady/src/utils/LibClone.sol";
14
+
15
+ import "../../src/JBCCIPSucker.sol";
16
+ import "../../src/JBSucker.sol";
17
+ import {JBCCIPSuckerDeployer} from "../../src/deployers/JBCCIPSuckerDeployer.sol";
18
+ import {JBAddToBalanceMode} from "../../src/enums/JBAddToBalanceMode.sol";
19
+ import {ICCIPRouter, IWrappedNativeToken} from "../../src/interfaces/ICCIPRouter.sol";
20
+ import {JBInboxTreeRoot} from "../../src/structs/JBInboxTreeRoot.sol";
21
+ import {JBMessageRoot} from "../../src/structs/JBMessageRoot.sol";
22
+ import {JBRemoteToken} from "../../src/structs/JBRemoteToken.sol";
23
+ import {JBTokenMapping} from "../../src/structs/JBTokenMapping.sol";
24
+ import {MerkleLib} from "../../src/utils/MerkleLib.sol";
25
+
26
+ /// @notice Minimal mock WETH for testing native token wrap/unwrap flows.
27
+ contract MockWETH {
28
+ mapping(address => uint256) public balanceOf;
29
+
30
+ receive() external payable {}
31
+
32
+ function deposit() external payable {
33
+ balanceOf[msg.sender] += msg.value;
34
+ }
35
+
36
+ function withdraw(uint256 amount) external {
37
+ require(balanceOf[msg.sender] >= amount, "MockWETH: insufficient");
38
+ balanceOf[msg.sender] -= amount;
39
+ (bool ok,) = msg.sender.call{value: amount}("");
40
+ require(ok, "MockWETH: ETH transfer failed");
41
+ }
42
+
43
+ function approve(address, uint256) external pure returns (bool) {
44
+ return true;
45
+ }
46
+
47
+ function transfer(address to, uint256 amount) external returns (bool) {
48
+ balanceOf[msg.sender] -= amount;
49
+ balanceOf[to] += amount;
50
+ return true;
51
+ }
52
+
53
+ function transferFrom(address from, address to, uint256 amount) external returns (bool) {
54
+ balanceOf[from] -= amount;
55
+ balanceOf[to] += amount;
56
+ return true;
57
+ }
58
+
59
+ function allowance(address, address) external pure returns (uint256) {
60
+ return type(uint256).max;
61
+ }
62
+
63
+ function totalSupply() external pure returns (uint256) {
64
+ return type(uint256).max;
65
+ }
66
+ }
67
+
68
+ /// @notice CCIP test sucker exposing internals for testing.
69
+ contract CCIPTestSucker is JBCCIPSucker {
70
+ using MerkleLib for MerkleLib.Tree;
71
+
72
+ constructor(
73
+ JBCCIPSuckerDeployer deployer,
74
+ IJBDirectory directory,
75
+ IJBTokens tokens,
76
+ IJBPermissions permissions
77
+ )
78
+ JBCCIPSucker(deployer, directory, tokens, permissions, JBAddToBalanceMode.MANUAL, address(0))
79
+ {}
80
+
81
+ function exposed_validateTokenMapping(JBTokenMapping calldata map) external pure {
82
+ _validateTokenMapping(map);
83
+ }
84
+
85
+ function exposed_sendRootOverAMB(
86
+ uint256 transportPayment,
87
+ address token,
88
+ uint256 amount,
89
+ JBRemoteToken memory remoteToken,
90
+ JBMessageRoot memory message
91
+ )
92
+ external
93
+ payable
94
+ {
95
+ _sendRootOverAMB(transportPayment, 0, token, amount, remoteToken, message);
96
+ }
97
+
98
+ function exposed_insertIntoTree(
99
+ uint256 projectTokenCount,
100
+ address token,
101
+ uint256 terminalTokenAmount,
102
+ bytes32 beneficiary
103
+ )
104
+ external
105
+ {
106
+ _insertIntoTree(projectTokenCount, token, terminalTokenAmount, beneficiary);
107
+ }
108
+
109
+ function test_setRemoteToken(address localToken, JBRemoteToken memory remoteToken) external {
110
+ _remoteTokenFor[localToken] = remoteToken;
111
+ }
112
+
113
+ function test_getInboxRoot(address token) external view returns (bytes32) {
114
+ return _inboxOf[token].root;
115
+ }
116
+
117
+ function test_getInboxNonce(address token) external view returns (uint64) {
118
+ return _inboxOf[token].nonce;
119
+ }
120
+
121
+ function test_getOutboxRoot(address token) external view returns (bytes32) {
122
+ return _outboxOf[token].tree.root();
123
+ }
124
+ }
125
+
126
+ /// @notice Base sucker exposing _validateTokenMapping for comparison testing.
127
+ contract BaseTestSucker is JBSucker {
128
+ constructor(
129
+ IJBDirectory directory,
130
+ IJBPermissions permissions,
131
+ IJBTokens tokens
132
+ )
133
+ JBSucker(directory, permissions, tokens, JBAddToBalanceMode.MANUAL, address(0))
134
+ {}
135
+
136
+ function exposed_validateTokenMapping(JBTokenMapping calldata map) external pure {
137
+ _validateTokenMapping(map);
138
+ }
139
+
140
+ function _sendRootOverAMB(
141
+ uint256,
142
+ uint256,
143
+ address,
144
+ uint256,
145
+ JBRemoteToken memory,
146
+ JBMessageRoot memory
147
+ )
148
+ internal
149
+ override
150
+ {}
151
+
152
+ function _isRemotePeer(address sender) internal view override returns (bool) {
153
+ return sender == address(this);
154
+ }
155
+
156
+ function peerChainId() external pure override returns (uint256) {
157
+ return 1;
158
+ }
159
+ }
160
+
161
+ /// @title CCIPNativeInteropTest
162
+ /// @notice Tests for cross-chain native token interop between chains with different native tokens.
163
+ /// Validates that the CCIP sucker correctly allows NATIVE_TOKEN -> ERC20 mappings
164
+ /// (e.g., ETH mainnet <-> Celo where ETH is an ERC20 on Celo).
165
+ contract CCIPNativeInteropTest is Test {
166
+ address constant MOCK_DEPLOYER = address(0xDE);
167
+ address constant MOCK_DIRECTORY = address(0xD1);
168
+ address constant MOCK_TOKENS = address(0xD2);
169
+ address constant MOCK_PERMISSIONS = address(0xD3);
170
+ address constant MOCK_ROUTER = address(0xD4);
171
+ address constant MOCK_PROJECTS = address(0xD5);
172
+
173
+ uint256 constant PROJECT_ID = 1;
174
+ uint256 constant REMOTE_CHAIN_ID = 42_220; // Celo
175
+ uint64 constant REMOTE_CHAIN_SELECTOR = 1_311_226; // Celo CCIP selector
176
+
177
+ /// @notice Represents ETH as an ERC20 on Celo.
178
+ address celoETH = makeAddr("celoETH");
179
+
180
+ CCIPTestSucker ccipSucker;
181
+ BaseTestSucker baseSucker;
182
+ MockWETH mockWETH;
183
+
184
+ function setUp() public {
185
+ mockWETH = new MockWETH();
186
+
187
+ vm.label(MOCK_DEPLOYER, "MOCK_DEPLOYER");
188
+ vm.label(MOCK_DIRECTORY, "MOCK_DIRECTORY");
189
+ vm.label(MOCK_ROUTER, "MOCK_ROUTER");
190
+ vm.label(celoETH, "celoETH");
191
+
192
+ // Etch code at mock router so high-level Solidity calls pass extcodesize check.
193
+ vm.etch(MOCK_ROUTER, hex"01");
194
+
195
+ // Mock deployer responses for CCIP sucker constructor.
196
+ vm.mockCall(MOCK_DEPLOYER, abi.encodeWithSignature("ccipRemoteChainId()"), abi.encode(REMOTE_CHAIN_ID));
197
+ vm.mockCall(
198
+ MOCK_DEPLOYER, abi.encodeWithSignature("ccipRemoteChainSelector()"), abi.encode(REMOTE_CHAIN_SELECTOR)
199
+ );
200
+ vm.mockCall(MOCK_DEPLOYER, abi.encodeWithSignature("ccipRouter()"), abi.encode(MOCK_ROUTER));
201
+
202
+ // Mock CCIP router getWrappedNative.
203
+ vm.mockCall(MOCK_ROUTER, abi.encodeWithSignature("getWrappedNative()"), abi.encode(address(mockWETH)));
204
+
205
+ // Mock CCIP router getFee and ccipSend.
206
+ vm.mockCall(MOCK_ROUTER, abi.encodeWithSelector(IRouterClient.getFee.selector), abi.encode(uint256(0.01 ether)));
207
+ vm.mockCall(
208
+ MOCK_ROUTER, abi.encodeWithSelector(IRouterClient.ccipSend.selector), abi.encode(bytes32(uint256(1)))
209
+ );
210
+
211
+ // Mock directory.
212
+ vm.mockCall(MOCK_DIRECTORY, abi.encodeWithSignature("PROJECTS()"), abi.encode(MOCK_PROJECTS));
213
+ vm.mockCall(MOCK_PROJECTS, abi.encodeWithSignature("ownerOf(uint256)"), abi.encode(address(this)));
214
+
215
+ // Deploy CCIP singleton and clone.
216
+ CCIPTestSucker ccipSingleton = new CCIPTestSucker(
217
+ JBCCIPSuckerDeployer(MOCK_DEPLOYER),
218
+ IJBDirectory(MOCK_DIRECTORY),
219
+ IJBTokens(MOCK_TOKENS),
220
+ IJBPermissions(MOCK_PERMISSIONS)
221
+ );
222
+ ccipSucker = CCIPTestSucker(payable(LibClone.cloneDeterministic(address(ccipSingleton), bytes32("ccip"))));
223
+ ccipSucker.initialize(PROJECT_ID);
224
+
225
+ // Deploy base singleton and clone.
226
+ BaseTestSucker baseSingleton =
227
+ new BaseTestSucker(IJBDirectory(MOCK_DIRECTORY), IJBPermissions(MOCK_PERMISSIONS), IJBTokens(MOCK_TOKENS));
228
+ baseSucker = BaseTestSucker(payable(LibClone.cloneDeterministic(address(baseSingleton), bytes32("base"))));
229
+ baseSucker.initialize(PROJECT_ID);
230
+ }
231
+
232
+ // =========================================================================
233
+ // Test 1: CCIP sucker allows NATIVE_TOKEN -> ERC20 mapping
234
+ // =========================================================================
235
+
236
+ function test_mapToken_nativeToERC20_allowedOnCCIP() public view {
237
+ JBTokenMapping memory map = JBTokenMapping({
238
+ localToken: JBConstants.NATIVE_TOKEN,
239
+ minGas: 200_000,
240
+ remoteToken: bytes32(uint256(uint160(celoETH))),
241
+ minBridgeAmount: 0.01 ether
242
+ });
243
+
244
+ // Should NOT revert — CCIP sucker allows native -> ERC20 for cross-chain interop.
245
+ ccipSucker.exposed_validateTokenMapping(map);
246
+ }
247
+
248
+ // =========================================================================
249
+ // Test 2: Base sucker rejects NATIVE_TOKEN -> ERC20 mapping
250
+ // =========================================================================
251
+
252
+ function test_mapToken_nativeToERC20_rejectedOnBase() public {
253
+ JBTokenMapping memory map = JBTokenMapping({
254
+ localToken: JBConstants.NATIVE_TOKEN,
255
+ minGas: 200_000,
256
+ remoteToken: bytes32(uint256(uint160(celoETH))),
257
+ minBridgeAmount: 0.01 ether
258
+ });
259
+
260
+ vm.expectRevert(
261
+ abi.encodeWithSelector(
262
+ JBSucker.JBSucker_InvalidNativeRemoteAddress.selector, bytes32(uint256(uint160(celoETH)))
263
+ )
264
+ );
265
+ baseSucker.exposed_validateTokenMapping(map);
266
+ }
267
+
268
+ // =========================================================================
269
+ // Test 3: NATIVE_TOKEN -> NATIVE_TOKEN allowed on both
270
+ // =========================================================================
271
+
272
+ function test_mapToken_nativeToNative_allowedOnBoth() public view {
273
+ JBTokenMapping memory map = JBTokenMapping({
274
+ localToken: JBConstants.NATIVE_TOKEN,
275
+ minGas: 200_000,
276
+ remoteToken: bytes32(uint256(uint160(JBConstants.NATIVE_TOKEN))),
277
+ minBridgeAmount: 0.01 ether
278
+ });
279
+
280
+ ccipSucker.exposed_validateTokenMapping(map);
281
+ baseSucker.exposed_validateTokenMapping(map);
282
+ }
283
+
284
+ // =========================================================================
285
+ // Test 4: NATIVE_TOKEN -> address(0) disables on both
286
+ // =========================================================================
287
+
288
+ function test_mapToken_nativeToZero_disablesOnBoth() public view {
289
+ JBTokenMapping memory map = JBTokenMapping({
290
+ localToken: JBConstants.NATIVE_TOKEN, minGas: 200_000, remoteToken: bytes32(0), minBridgeAmount: 0
291
+ });
292
+
293
+ ccipSucker.exposed_validateTokenMapping(map);
294
+ baseSucker.exposed_validateTokenMapping(map);
295
+ }
296
+
297
+ // =========================================================================
298
+ // Test 5: CCIP enforces minGas for native token mappings
299
+ // =========================================================================
300
+
301
+ function test_mapToken_minGas_enforced_forNative() public {
302
+ JBTokenMapping memory map = JBTokenMapping({
303
+ localToken: JBConstants.NATIVE_TOKEN,
304
+ minGas: 100_000, // Below MESSENGER_ERC20_MIN_GAS_LIMIT (200_000)
305
+ remoteToken: bytes32(uint256(uint160(celoETH))),
306
+ minBridgeAmount: 0.01 ether
307
+ });
308
+
309
+ // CCIP sucker requires minGas for ALL tokens since native wraps to WETH.
310
+ vm.expectRevert(abi.encodeWithSelector(JBSucker.JBSucker_BelowMinGas.selector, 100_000, 200_000));
311
+ ccipSucker.exposed_validateTokenMapping(map);
312
+ }
313
+
314
+ // =========================================================================
315
+ // Test 6: ccipReceive unwraps WETH when root.token == NATIVE_TOKEN
316
+ // =========================================================================
317
+
318
+ function test_ccipReceive_nativeToken_unwraps() public {
319
+ uint256 bridgeAmount = 1 ether;
320
+
321
+ // Give MockWETH ETH backing for the unwrap and credit WETH balance to the sucker.
322
+ vm.deal(address(mockWETH), bridgeAmount);
323
+ vm.store(
324
+ address(mockWETH),
325
+ keccak256(abi.encode(address(ccipSucker), uint256(0))), // balanceOf[sucker] at slot 0
326
+ bytes32(bridgeAmount)
327
+ );
328
+
329
+ uint256 ethBefore = address(ccipSucker).balance;
330
+
331
+ // Build CCIP message with NATIVE_TOKEN root.
332
+ JBMessageRoot memory msgRoot = JBMessageRoot({
333
+ version: 1,
334
+ token: bytes32(uint256(uint160(JBConstants.NATIVE_TOKEN))),
335
+ amount: bridgeAmount,
336
+ remoteRoot: JBInboxTreeRoot({nonce: 1, root: bytes32(uint256(0xdead))})
337
+ });
338
+
339
+ Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1);
340
+ tokenAmounts[0] = Client.EVMTokenAmount({token: address(mockWETH), amount: bridgeAmount});
341
+
342
+ Client.Any2EVMMessage memory message = Client.Any2EVMMessage({
343
+ messageId: bytes32(uint256(1)),
344
+ sourceChainSelector: REMOTE_CHAIN_SELECTOR,
345
+ sender: abi.encode(address(ccipSucker)), // peer is address(this)
346
+ data: abi.encode(msgRoot),
347
+ destTokenAmounts: tokenAmounts
348
+ });
349
+
350
+ vm.prank(MOCK_ROUTER);
351
+ ccipSucker.ccipReceive(message);
352
+
353
+ // Verify ETH was unwrapped.
354
+ assertEq(address(ccipSucker).balance, ethBefore + bridgeAmount, "ETH should be unwrapped from WETH");
355
+
356
+ // Verify inbox root was stored.
357
+ assertEq(ccipSucker.test_getInboxRoot(JBConstants.NATIVE_TOKEN), bytes32(uint256(0xdead)));
358
+ assertEq(ccipSucker.test_getInboxNonce(JBConstants.NATIVE_TOKEN), 1);
359
+ }
360
+
361
+ // =========================================================================
362
+ // Test 7: ccipReceive does NOT unwrap for ERC20 tokens
363
+ // =========================================================================
364
+
365
+ function test_ccipReceive_erc20Token_noUnwrap() public {
366
+ uint256 bridgeAmount = 1 ether;
367
+
368
+ JBMessageRoot memory msgRoot = JBMessageRoot({
369
+ version: 1,
370
+ token: bytes32(uint256(uint160(celoETH))), // ERC20, not NATIVE_TOKEN
371
+ amount: bridgeAmount,
372
+ remoteRoot: JBInboxTreeRoot({nonce: 1, root: bytes32(uint256(0xbeef))})
373
+ });
374
+
375
+ Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1);
376
+ tokenAmounts[0] = Client.EVMTokenAmount({token: celoETH, amount: bridgeAmount});
377
+
378
+ Client.Any2EVMMessage memory message = Client.Any2EVMMessage({
379
+ messageId: bytes32(uint256(2)),
380
+ sourceChainSelector: REMOTE_CHAIN_SELECTOR,
381
+ sender: abi.encode(address(ccipSucker)),
382
+ data: abi.encode(msgRoot),
383
+ destTokenAmounts: tokenAmounts
384
+ });
385
+
386
+ uint256 ethBefore = address(ccipSucker).balance;
387
+
388
+ vm.prank(MOCK_ROUTER);
389
+ ccipSucker.ccipReceive(message);
390
+
391
+ // Verify NO unwrap — ETH balance unchanged.
392
+ assertEq(address(ccipSucker).balance, ethBefore, "ETH should NOT change for ERC20 token");
393
+
394
+ // Verify inbox root was stored for the ERC20 token.
395
+ assertEq(ccipSucker.test_getInboxRoot(celoETH), bytes32(uint256(0xbeef)));
396
+ }
397
+
398
+ // =========================================================================
399
+ // Test 8: _sendRootOverAMB wraps native token to WETH before CCIP send
400
+ // =========================================================================
401
+
402
+ function test_sendRoot_nativeToken_wrapsToWETH() public {
403
+ uint256 amount = 1 ether;
404
+ uint256 transport = 0.01 ether;
405
+
406
+ // Give the sucker ETH (simulating outbox balance).
407
+ vm.deal(address(ccipSucker), amount);
408
+
409
+ JBRemoteToken memory remoteToken = JBRemoteToken({
410
+ enabled: true,
411
+ emergencyHatch: false,
412
+ minGas: 200_000,
413
+ addr: bytes32(uint256(uint160(celoETH))),
414
+ minBridgeAmount: 0.01 ether
415
+ });
416
+
417
+ JBMessageRoot memory msgRoot = JBMessageRoot({
418
+ version: 1,
419
+ token: bytes32(uint256(uint160(celoETH))),
420
+ amount: amount,
421
+ remoteRoot: JBInboxTreeRoot({nonce: 1, root: bytes32(uint256(0xdead))})
422
+ });
423
+
424
+ // Verify MockWETH balance is 0 before.
425
+ assertEq(mockWETH.balanceOf(address(ccipSucker)), 0);
426
+
427
+ // Send root — this should wrap native ETH to WETH.
428
+ ccipSucker.exposed_sendRootOverAMB{value: transport}(
429
+ transport, JBConstants.NATIVE_TOKEN, amount, remoteToken, msgRoot
430
+ );
431
+
432
+ // After wrapping: MockWETH received 1 ETH via deposit, sucker got 1 WETH.
433
+ // The mocked ccipSend doesn't actually transfer WETH, so sucker still has it.
434
+ assertEq(mockWETH.balanceOf(address(ccipSucker)), amount, "WETH should be minted from native deposit");
435
+
436
+ // MockWETH should hold the ETH backing.
437
+ assertEq(address(mockWETH).balance, amount, "MockWETH should hold the deposited ETH");
438
+ }
439
+
440
+ // =========================================================================
441
+ // Test 9: Full flow ETH mainnet -> Celo (native maps to ERC20)
442
+ // =========================================================================
443
+
444
+ function test_fullFlow_ethMainnet_to_celo() public {
445
+ // Step 1: Validate that NATIVE -> celoETH mapping is accepted on CCIP.
446
+ JBTokenMapping memory map = JBTokenMapping({
447
+ localToken: JBConstants.NATIVE_TOKEN,
448
+ minGas: 200_000,
449
+ remoteToken: bytes32(uint256(uint160(celoETH))),
450
+ minBridgeAmount: 0.01 ether
451
+ });
452
+ ccipSucker.exposed_validateTokenMapping(map);
453
+
454
+ // Step 2: Set up the token mapping on the sucker.
455
+ ccipSucker.test_setRemoteToken(
456
+ JBConstants.NATIVE_TOKEN,
457
+ JBRemoteToken({
458
+ enabled: true,
459
+ emergencyHatch: false,
460
+ minGas: 200_000,
461
+ addr: bytes32(uint256(uint160(celoETH))),
462
+ minBridgeAmount: 0.01 ether
463
+ })
464
+ );
465
+
466
+ // Step 3: Insert a leaf into the outbox (simulating a user preparing to bridge).
467
+ ccipSucker.exposed_insertIntoTree({
468
+ projectTokenCount: 10 ether,
469
+ token: JBConstants.NATIVE_TOKEN,
470
+ terminalTokenAmount: 1 ether,
471
+ beneficiary: bytes32(uint256(uint160(address(0x1234))))
472
+ });
473
+
474
+ bytes32 outboxRoot = ccipSucker.test_getOutboxRoot(JBConstants.NATIVE_TOKEN);
475
+ assertTrue(outboxRoot != bytes32(0), "Outbox root should be non-zero");
476
+
477
+ // Step 4: Simulate receiving on the "Celo side" — the message arrives with
478
+ // root.token = celoETH (the ERC20 representation of ETH on Celo).
479
+ JBMessageRoot memory msgRoot = JBMessageRoot({
480
+ version: 1,
481
+ token: bytes32(uint256(uint160(celoETH))),
482
+ amount: 1 ether,
483
+ remoteRoot: JBInboxTreeRoot({nonce: 1, root: outboxRoot})
484
+ });
485
+
486
+ Client.Any2EVMMessage memory message = Client.Any2EVMMessage({
487
+ messageId: bytes32(uint256(3)),
488
+ sourceChainSelector: REMOTE_CHAIN_SELECTOR,
489
+ sender: abi.encode(address(ccipSucker)),
490
+ data: abi.encode(msgRoot),
491
+ destTokenAmounts: new Client.EVMTokenAmount[](0)
492
+ });
493
+
494
+ vm.prank(MOCK_ROUTER);
495
+ ccipSucker.ccipReceive(message);
496
+
497
+ // Verify inbox stored correctly with celoETH as the token key.
498
+ assertEq(ccipSucker.test_getInboxRoot(celoETH), outboxRoot, "Inbox root should match outbox root");
499
+ assertEq(ccipSucker.test_getInboxNonce(celoETH), 1);
500
+ }
501
+
502
+ // =========================================================================
503
+ // Test 10: Full flow Celo -> ETH mainnet (receive triggers WETH unwrap)
504
+ // =========================================================================
505
+
506
+ function test_fullFlow_celo_to_ethMainnet() public {
507
+ uint256 bridgeAmount = 1 ether;
508
+
509
+ // Give MockWETH ETH backing and credit sucker's WETH balance.
510
+ vm.deal(address(mockWETH), bridgeAmount);
511
+ vm.store(address(mockWETH), keccak256(abi.encode(address(ccipSucker), uint256(0))), bytes32(bridgeAmount));
512
+
513
+ // Simulate receiving on ETH mainnet — root.token = NATIVE_TOKEN because
514
+ // on mainnet, ETH is the native token. CCIP delivers WETH.
515
+ JBMessageRoot memory msgRoot = JBMessageRoot({
516
+ version: 1,
517
+ token: bytes32(uint256(uint160(JBConstants.NATIVE_TOKEN))),
518
+ amount: bridgeAmount,
519
+ remoteRoot: JBInboxTreeRoot({nonce: 1, root: bytes32(uint256(0xcafe))})
520
+ });
521
+
522
+ Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1);
523
+ tokenAmounts[0] = Client.EVMTokenAmount({token: address(mockWETH), amount: bridgeAmount});
524
+
525
+ Client.Any2EVMMessage memory message = Client.Any2EVMMessage({
526
+ messageId: bytes32(uint256(4)),
527
+ sourceChainSelector: REMOTE_CHAIN_SELECTOR,
528
+ sender: abi.encode(address(ccipSucker)),
529
+ data: abi.encode(msgRoot),
530
+ destTokenAmounts: tokenAmounts
531
+ });
532
+
533
+ uint256 ethBefore = address(ccipSucker).balance;
534
+
535
+ vm.prank(MOCK_ROUTER);
536
+ ccipSucker.ccipReceive(message);
537
+
538
+ // Verify unwrapping occurred.
539
+ assertEq(address(ccipSucker).balance, ethBefore + bridgeAmount, "Should unwrap WETH to ETH on receive");
540
+
541
+ // Verify inbox stored under NATIVE_TOKEN.
542
+ assertEq(ccipSucker.test_getInboxRoot(JBConstants.NATIVE_TOKEN), bytes32(uint256(0xcafe)));
543
+ }
544
+
545
+ // =========================================================================
546
+ // Test 11: ETH round-trip — NATIVE_TOKEN on mainnet <-> ERC20 on Celo
547
+ //
548
+ // This is THE critical cross-chain interop case:
549
+ // ETH mainnet: ETH is NATIVE_TOKEN (address 0xEEE...EEE)
550
+ // Celo: ETH is an ERC-20 (e.g., 0x2DEf4285787d58a2f811AF24755A8150622f4361)
551
+ //
552
+ // The mapping: NATIVE_TOKEN -> celoETH (an ERC-20 address)
553
+ // Send path: native ETH -> wrap to WETH -> CCIP bridges WETH -> Celo receives as ERC-20
554
+ // Return path: Celo sends ERC-20 ETH -> CCIP delivers as WETH -> unwrap WETH -> native ETH
555
+ // =========================================================================
556
+
557
+ function test_eth_roundTrip_nativeToERC20_and_back() public {
558
+ uint256 bridgeAmount = 2 ether;
559
+
560
+ // --- Outbound: ETH mainnet -> Celo (NATIVE_TOKEN -> celoETH ERC-20) ---
561
+
562
+ // 1. Validate the mapping is accepted.
563
+ JBTokenMapping memory outboundMap = JBTokenMapping({
564
+ localToken: JBConstants.NATIVE_TOKEN,
565
+ minGas: 200_000,
566
+ remoteToken: bytes32(uint256(uint160(celoETH))),
567
+ minBridgeAmount: 0.01 ether
568
+ });
569
+ ccipSucker.exposed_validateTokenMapping(outboundMap);
570
+
571
+ // 2. Set the mapping and simulate outbox.
572
+ ccipSucker.test_setRemoteToken(
573
+ JBConstants.NATIVE_TOKEN,
574
+ JBRemoteToken({
575
+ enabled: true,
576
+ emergencyHatch: false,
577
+ minGas: 200_000,
578
+ addr: bytes32(uint256(uint160(celoETH))),
579
+ minBridgeAmount: 0.01 ether
580
+ })
581
+ );
582
+
583
+ // 3. Wrap native ETH -> WETH for CCIP transport.
584
+ vm.deal(address(ccipSucker), bridgeAmount);
585
+ assertEq(mockWETH.balanceOf(address(ccipSucker)), 0, "No WETH before send");
586
+
587
+ JBRemoteToken memory remoteToken = JBRemoteToken({
588
+ enabled: true,
589
+ emergencyHatch: false,
590
+ minGas: 200_000,
591
+ addr: bytes32(uint256(uint160(celoETH))),
592
+ minBridgeAmount: 0.01 ether
593
+ });
594
+ JBMessageRoot memory sendMsg = JBMessageRoot({
595
+ version: 1,
596
+ token: bytes32(uint256(uint160(celoETH))),
597
+ amount: bridgeAmount,
598
+ remoteRoot: JBInboxTreeRoot({nonce: 1, root: bytes32(uint256(0xaaa))})
599
+ });
600
+ ccipSucker.exposed_sendRootOverAMB{value: 0.01 ether}(
601
+ 0.01 ether, JBConstants.NATIVE_TOKEN, bridgeAmount, remoteToken, sendMsg
602
+ );
603
+
604
+ // Verify native ETH was wrapped to WETH.
605
+ assertEq(mockWETH.balanceOf(address(ccipSucker)), bridgeAmount, "ETH should be wrapped to WETH for CCIP send");
606
+ assertEq(address(mockWETH).balance, bridgeAmount, "MockWETH holds the ETH backing");
607
+
608
+ // --- Return: Celo -> ETH mainnet (celoETH ERC-20 -> NATIVE_TOKEN) ---
609
+
610
+ // 4. Simulate CCIP delivering WETH back (router unwrap path).
611
+ // On mainnet, root.token = NATIVE_TOKEN, so ccipReceive unwraps WETH -> ETH.
612
+ vm.deal(address(mockWETH), bridgeAmount);
613
+ vm.store(address(mockWETH), keccak256(abi.encode(address(ccipSucker), uint256(0))), bytes32(bridgeAmount));
614
+
615
+ uint256 ethBefore = address(ccipSucker).balance;
616
+
617
+ JBMessageRoot memory recvMsg = JBMessageRoot({
618
+ version: 1,
619
+ token: bytes32(uint256(uint160(JBConstants.NATIVE_TOKEN))),
620
+ amount: bridgeAmount,
621
+ remoteRoot: JBInboxTreeRoot({nonce: 2, root: bytes32(uint256(0xbbb))})
622
+ });
623
+
624
+ Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1);
625
+ tokenAmounts[0] = Client.EVMTokenAmount({token: address(mockWETH), amount: bridgeAmount});
626
+
627
+ Client.Any2EVMMessage memory ccipMsg = Client.Any2EVMMessage({
628
+ messageId: bytes32(uint256(99)),
629
+ sourceChainSelector: REMOTE_CHAIN_SELECTOR,
630
+ sender: abi.encode(address(ccipSucker)),
631
+ data: abi.encode(recvMsg),
632
+ destTokenAmounts: tokenAmounts
633
+ });
634
+
635
+ vm.prank(MOCK_ROUTER);
636
+ ccipSucker.ccipReceive(ccipMsg);
637
+
638
+ // 5. Verify WETH was unwrapped back to native ETH.
639
+ assertEq(
640
+ address(ccipSucker).balance,
641
+ ethBefore + bridgeAmount,
642
+ "Return path: WETH should be unwrapped back to native ETH"
643
+ );
644
+
645
+ // Verify inbox roots for both directions.
646
+ assertEq(ccipSucker.test_getInboxRoot(JBConstants.NATIVE_TOKEN), bytes32(uint256(0xbbb)));
647
+ assertEq(ccipSucker.test_getInboxNonce(JBConstants.NATIVE_TOKEN), 2);
648
+ }
649
+
650
+ // =========================================================================
651
+ // Test 12: ERC20 -> ERC20 mapping works on both suckers
652
+ // =========================================================================
653
+
654
+ function test_mapToken_erc20ToERC20_allowedOnBoth() public view {
655
+ JBTokenMapping memory map = JBTokenMapping({
656
+ localToken: address(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48), // USDC-like
657
+ minGas: 200_000,
658
+ remoteToken: bytes32(uint256(uint160(address(0xef4229c8c3250C675F21BCefa42f58EfbfF6002a)))), // celoUSDC-like
659
+ minBridgeAmount: 1e6
660
+ });
661
+
662
+ ccipSucker.exposed_validateTokenMapping(map);
663
+ baseSucker.exposed_validateTokenMapping(map);
664
+ }
665
+
666
+ // =========================================================================
667
+ // Test 12: Both suckers enforce minGas for ERC20 tokens
668
+ // =========================================================================
669
+
670
+ function test_mapToken_minGas_enforced_forERC20() public {
671
+ JBTokenMapping memory map = JBTokenMapping({
672
+ localToken: makeAddr("USDC"),
673
+ minGas: 50_000, // Below MESSENGER_ERC20_MIN_GAS_LIMIT (200_000)
674
+ remoteToken: bytes32(uint256(uint160(makeAddr("celoUSDC")))),
675
+ minBridgeAmount: 1e6
676
+ });
677
+
678
+ vm.expectRevert(abi.encodeWithSelector(JBSucker.JBSucker_BelowMinGas.selector, 50_000, 200_000));
679
+ ccipSucker.exposed_validateTokenMapping(map);
680
+
681
+ vm.expectRevert(abi.encodeWithSelector(JBSucker.JBSucker_BelowMinGas.selector, 50_000, 200_000));
682
+ baseSucker.exposed_validateTokenMapping(map);
683
+ }
684
+
685
+ // =========================================================================
686
+ // Test 13: Base sucker skips minGas for native (no wrapping on OP/Arb)
687
+ // =========================================================================
688
+
689
+ function test_mapToken_base_skipsMinGas_forNative() public view {
690
+ JBTokenMapping memory map = JBTokenMapping({
691
+ localToken: JBConstants.NATIVE_TOKEN,
692
+ minGas: 0, // Zero minGas
693
+ remoteToken: bytes32(uint256(uint160(JBConstants.NATIVE_TOKEN))),
694
+ minBridgeAmount: 0.01 ether
695
+ });
696
+
697
+ // Base sucker skips minGas for native tokens (OP/Arb bridge natively).
698
+ baseSucker.exposed_validateTokenMapping(map);
699
+ }
700
+
701
+ // =========================================================================
702
+ // Test 14: CCIP enforces minGas even for native-to-native
703
+ // =========================================================================
704
+
705
+ function test_mapToken_ccip_enforcesMinGas_forNativeToNative() public {
706
+ JBTokenMapping memory map = JBTokenMapping({
707
+ localToken: JBConstants.NATIVE_TOKEN,
708
+ minGas: 0, // Zero minGas
709
+ remoteToken: bytes32(uint256(uint160(JBConstants.NATIVE_TOKEN))),
710
+ minBridgeAmount: 0.01 ether
711
+ });
712
+
713
+ // CCIP sucker wraps native to WETH, so needs gas for ERC20 transfer even for native-to-native.
714
+ vm.expectRevert(abi.encodeWithSelector(JBSucker.JBSucker_BelowMinGas.selector, 0, 200_000));
715
+ ccipSucker.exposed_validateTokenMapping(map);
716
+ }
717
+
718
+ receive() external payable {}
719
+ }