@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.
- package/LICENSE +21 -0
- package/README.md +422 -0
- package/SECURITY.md +55 -0
- package/SKILLS.md +163 -0
- package/deployments/nana-suckers-v5/arbitrum/JBArbitrumSucker.json +1425 -0
- package/deployments/nana-suckers-v5/arbitrum/JBArbitrumSuckerDeployer.json +391 -0
- package/deployments/nana-suckers-v5/arbitrum/JBCCIPSucker.json +1479 -0
- package/deployments/nana-suckers-v5/arbitrum/JBCCIPSuckerDeployer.json +433 -0
- package/deployments/nana-suckers-v5/arbitrum/JBCCIPSuckerDeployer_1.json +433 -0
- package/deployments/nana-suckers-v5/arbitrum/JBCCIPSuckerDeployer_2.json +433 -0
- package/deployments/nana-suckers-v5/arbitrum/JBCCIPSucker_1.json +1479 -0
- package/deployments/nana-suckers-v5/arbitrum/JBCCIPSucker_2.json +1479 -0
- package/deployments/nana-suckers-v5/arbitrum/JBSuckerRegistry.json +690 -0
- package/deployments/nana-suckers-v5/arbitrum_sepolia/JBArbitrumSucker.json +1425 -0
- package/deployments/nana-suckers-v5/arbitrum_sepolia/JBArbitrumSuckerDeployer.json +391 -0
- package/deployments/nana-suckers-v5/arbitrum_sepolia/JBCCIPSucker.json +1479 -0
- package/deployments/nana-suckers-v5/arbitrum_sepolia/JBCCIPSuckerDeployer.json +433 -0
- package/deployments/nana-suckers-v5/arbitrum_sepolia/JBCCIPSuckerDeployer_1.json +433 -0
- package/deployments/nana-suckers-v5/arbitrum_sepolia/JBCCIPSuckerDeployer_2.json +433 -0
- package/deployments/nana-suckers-v5/arbitrum_sepolia/JBCCIPSucker_1.json +1479 -0
- package/deployments/nana-suckers-v5/arbitrum_sepolia/JBCCIPSucker_2.json +1479 -0
- package/deployments/nana-suckers-v5/arbitrum_sepolia/JBSuckerRegistry.json +690 -0
- package/deployments/nana-suckers-v5/base/JBBaseSucker.json +1389 -0
- package/deployments/nana-suckers-v5/base/JBBaseSuckerDeployer.json +376 -0
- package/deployments/nana-suckers-v5/base/JBCCIPSucker.json +1483 -0
- package/deployments/nana-suckers-v5/base/JBCCIPSuckerDeployer.json +436 -0
- package/deployments/nana-suckers-v5/base/JBCCIPSuckerDeployer_1.json +436 -0
- package/deployments/nana-suckers-v5/base/JBCCIPSuckerDeployer_2.json +436 -0
- package/deployments/nana-suckers-v5/base/JBCCIPSucker_1.json +1483 -0
- package/deployments/nana-suckers-v5/base/JBCCIPSucker_2.json +1483 -0
- package/deployments/nana-suckers-v5/base/JBSuckerRegistry.json +694 -0
- package/deployments/nana-suckers-v5/base_sepolia/JBBaseSucker.json +1389 -0
- package/deployments/nana-suckers-v5/base_sepolia/JBBaseSuckerDeployer.json +376 -0
- package/deployments/nana-suckers-v5/base_sepolia/JBCCIPSucker.json +1483 -0
- package/deployments/nana-suckers-v5/base_sepolia/JBCCIPSuckerDeployer.json +436 -0
- package/deployments/nana-suckers-v5/base_sepolia/JBCCIPSuckerDeployer_1.json +436 -0
- package/deployments/nana-suckers-v5/base_sepolia/JBCCIPSuckerDeployer_2.json +436 -0
- package/deployments/nana-suckers-v5/base_sepolia/JBCCIPSucker_1.json +1483 -0
- package/deployments/nana-suckers-v5/base_sepolia/JBCCIPSucker_2.json +1483 -0
- package/deployments/nana-suckers-v5/base_sepolia/JBSuckerRegistry.json +694 -0
- package/deployments/nana-suckers-v5/ethereum/JBArbitrumSucker.json +1429 -0
- package/deployments/nana-suckers-v5/ethereum/JBArbitrumSuckerDeployer.json +394 -0
- package/deployments/nana-suckers-v5/ethereum/JBBaseSucker.json +1389 -0
- package/deployments/nana-suckers-v5/ethereum/JBBaseSuckerDeployer.json +376 -0
- package/deployments/nana-suckers-v5/ethereum/JBCCIPSucker.json +1483 -0
- package/deployments/nana-suckers-v5/ethereum/JBCCIPSuckerDeployer.json +436 -0
- package/deployments/nana-suckers-v5/ethereum/JBCCIPSuckerDeployer_1.json +436 -0
- package/deployments/nana-suckers-v5/ethereum/JBCCIPSuckerDeployer_2.json +436 -0
- package/deployments/nana-suckers-v5/ethereum/JBCCIPSucker_1.json +1483 -0
- package/deployments/nana-suckers-v5/ethereum/JBCCIPSucker_2.json +1483 -0
- package/deployments/nana-suckers-v5/ethereum/JBOptimismSucker.json +1389 -0
- package/deployments/nana-suckers-v5/ethereum/JBOptimismSuckerDeployer.json +376 -0
- package/deployments/nana-suckers-v5/ethereum/JBSuckerRegistry.json +694 -0
- package/deployments/nana-suckers-v5/optimism/JBCCIPSucker.json +1479 -0
- package/deployments/nana-suckers-v5/optimism/JBCCIPSuckerDeployer.json +433 -0
- package/deployments/nana-suckers-v5/optimism/JBCCIPSuckerDeployer_1.json +433 -0
- package/deployments/nana-suckers-v5/optimism/JBCCIPSuckerDeployer_2.json +433 -0
- package/deployments/nana-suckers-v5/optimism/JBCCIPSucker_1.json +1479 -0
- package/deployments/nana-suckers-v5/optimism/JBCCIPSucker_2.json +1479 -0
- package/deployments/nana-suckers-v5/optimism/JBOptimismSucker.json +1385 -0
- package/deployments/nana-suckers-v5/optimism/JBOptimismSuckerDeployer.json +373 -0
- package/deployments/nana-suckers-v5/optimism/JBSuckerRegistry.json +690 -0
- package/deployments/nana-suckers-v5/optimism_sepolia/JBCCIPSucker.json +1483 -0
- package/deployments/nana-suckers-v5/optimism_sepolia/JBCCIPSuckerDeployer.json +436 -0
- package/deployments/nana-suckers-v5/optimism_sepolia/JBCCIPSuckerDeployer_1.json +436 -0
- package/deployments/nana-suckers-v5/optimism_sepolia/JBCCIPSuckerDeployer_2.json +436 -0
- package/deployments/nana-suckers-v5/optimism_sepolia/JBCCIPSucker_1.json +1483 -0
- package/deployments/nana-suckers-v5/optimism_sepolia/JBCCIPSucker_2.json +1483 -0
- package/deployments/nana-suckers-v5/optimism_sepolia/JBOptimismSucker.json +1389 -0
- package/deployments/nana-suckers-v5/optimism_sepolia/JBOptimismSuckerDeployer.json +376 -0
- package/deployments/nana-suckers-v5/optimism_sepolia/JBSuckerRegistry.json +694 -0
- package/deployments/nana-suckers-v5/sepolia/JBArbitrumSucker.json +1429 -0
- package/deployments/nana-suckers-v5/sepolia/JBArbitrumSuckerDeployer.json +394 -0
- package/deployments/nana-suckers-v5/sepolia/JBBaseSucker.json +1389 -0
- package/deployments/nana-suckers-v5/sepolia/JBBaseSuckerDeployer.json +376 -0
- package/deployments/nana-suckers-v5/sepolia/JBCCIPSucker.json +1483 -0
- package/deployments/nana-suckers-v5/sepolia/JBCCIPSuckerDeployer.json +436 -0
- package/deployments/nana-suckers-v5/sepolia/JBCCIPSuckerDeployer_1.json +436 -0
- package/deployments/nana-suckers-v5/sepolia/JBCCIPSuckerDeployer_2.json +436 -0
- package/deployments/nana-suckers-v5/sepolia/JBCCIPSucker_1.json +1483 -0
- package/deployments/nana-suckers-v5/sepolia/JBCCIPSucker_2.json +1483 -0
- package/deployments/nana-suckers-v5/sepolia/JBOptimismSucker.json +1389 -0
- package/deployments/nana-suckers-v5/sepolia/JBOptimismSuckerDeployer.json +376 -0
- package/deployments/nana-suckers-v5/sepolia/JBSuckerRegistry.json +694 -0
- package/foundry.lock +11 -0
- package/foundry.toml +22 -0
- package/package.json +33 -0
- package/remappings.txt +1 -0
- package/script/Deploy.s.sol +506 -0
- package/script/helpers/SuckerDeploymentLib.sol +97 -0
- package/slither-ci.config.json +10 -0
- package/sphinx.lock +476 -0
- package/src/JBArbitrumSucker.sol +311 -0
- package/src/JBBaseSucker.sol +41 -0
- package/src/JBCCIPSucker.sol +303 -0
- package/src/JBOptimismSucker.sol +143 -0
- package/src/JBSucker.sol +1159 -0
- package/src/JBSuckerRegistry.sol +262 -0
- package/src/deployers/JBArbitrumSuckerDeployer.sol +86 -0
- package/src/deployers/JBBaseSuckerDeployer.sol +26 -0
- package/src/deployers/JBCCIPSuckerDeployer.sol +88 -0
- package/src/deployers/JBOptimismSuckerDeployer.sol +82 -0
- package/src/deployers/JBSuckerDeployer.sol +147 -0
- package/src/enums/JBAddToBalanceMode.sol +11 -0
- package/src/enums/JBLayer.sol +8 -0
- package/src/enums/JBSuckerState.sol +14 -0
- package/src/interfaces/IArbGatewayRouter.sol +11 -0
- package/src/interfaces/IArbL1GatewayRouter.sol +17 -0
- package/src/interfaces/IArbL2GatewayRouter.sol +14 -0
- package/src/interfaces/ICCIPRouter.sol +11 -0
- package/src/interfaces/IJBArbitrumSucker.sol +13 -0
- package/src/interfaces/IJBArbitrumSuckerDeployer.sol +12 -0
- package/src/interfaces/IJBCCIPSuckerDeployer.sol +15 -0
- package/src/interfaces/IJBOpSuckerDeployer.sol +11 -0
- package/src/interfaces/IJBOptimismSucker.sol +10 -0
- package/src/interfaces/IJBSucker.sol +144 -0
- package/src/interfaces/IJBSuckerDeployer.sol +40 -0
- package/src/interfaces/IJBSuckerExtended.sol +22 -0
- package/src/interfaces/IJBSuckerRegistry.sol +75 -0
- package/src/interfaces/IOPMessenger.sol +18 -0
- package/src/interfaces/IOPStandardBridge.sol +29 -0
- package/src/interfaces/IWrappedNativeToken.sol +13 -0
- package/src/libraries/ARBAddresses.sol +17 -0
- package/src/libraries/ARBChains.sol +11 -0
- package/src/libraries/CCIPHelper.sol +136 -0
- package/src/structs/JBClaim.sol +13 -0
- package/src/structs/JBInboxTreeRoot.sol +12 -0
- package/src/structs/JBLeaf.sol +14 -0
- package/src/structs/JBMessageRoot.sol +16 -0
- package/src/structs/JBOutboxTree.sol +18 -0
- package/src/structs/JBRemoteToken.sol +17 -0
- package/src/structs/JBSuckerDeployerConfig.sol +12 -0
- package/src/structs/JBSuckersPair.sol +11 -0
- package/src/structs/JBTokenMapping.sol +13 -0
- package/src/utils/MerkleLib.sol +1020 -0
- package/test/Fork.t.sol +514 -0
- package/test/InteropCompat.t.sol +676 -0
- package/test/SuckerAttacks.t.sol +509 -0
- package/test/SuckerDeepAttacks.t.sol +1563 -0
- package/test/mocks/ERC20Mock.sol +36 -0
- package/test/mocks/MockMessenger.sol +42 -0
- package/test/unit/arb.t.sol +28 -0
- package/test/unit/ccip_native_interop.t.sol +719 -0
- package/test/unit/ccip_refund.t.sol +234 -0
- package/test/unit/deployer.t.sol +475 -0
- package/test/unit/emergency.t.sol +305 -0
- package/test/unit/merkle.t.sol +212 -0
- package/test/unit/multi_chain_evolution.t.sol +622 -0
- package/test/unit/registry.t.sol +26 -0
|
@@ -0,0 +1,622 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.13;
|
|
3
|
+
|
|
4
|
+
import "forge-std/Test.sol";
|
|
5
|
+
import /* {*} from */ "@bananapus/core-v6/test/helpers/TestBaseWorkflow.sol";
|
|
6
|
+
import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
|
|
7
|
+
|
|
8
|
+
import {IJBController} from "@bananapus/core-v6/src/interfaces/IJBController.sol";
|
|
9
|
+
import {IJBSucker} from "../../src/interfaces/IJBSucker.sol";
|
|
10
|
+
import {IJBSuckerDeployer} from "../../src/interfaces/IJBSuckerDeployer.sol";
|
|
11
|
+
|
|
12
|
+
import "../../src/JBSucker.sol";
|
|
13
|
+
import "../../src/JBOptimismSucker.sol";
|
|
14
|
+
import "../../src/JBCCIPSucker.sol";
|
|
15
|
+
import "../../src/deployers/JBOptimismSuckerDeployer.sol";
|
|
16
|
+
import "../../src/deployers/JBCCIPSuckerDeployer.sol";
|
|
17
|
+
import {JBSuckerRegistry} from "../../src/JBSuckerRegistry.sol";
|
|
18
|
+
import {JBSuckerDeployerConfig} from "../../src/structs/JBSuckerDeployerConfig.sol";
|
|
19
|
+
import {JBSuckersPair} from "../../src/structs/JBSuckersPair.sol";
|
|
20
|
+
import {JBSuckerState} from "../../src/enums/JBSuckerState.sol";
|
|
21
|
+
import {JBTokenMapping} from "../../src/structs/JBTokenMapping.sol";
|
|
22
|
+
import {JBRemoteToken} from "../../src/structs/JBRemoteToken.sol";
|
|
23
|
+
|
|
24
|
+
/// @title MultiChainEvolutionTest
|
|
25
|
+
/// @notice Tests that a project can start on one chain and incrementally expand to new chains over time.
|
|
26
|
+
///
|
|
27
|
+
/// Lifecycle story:
|
|
28
|
+
/// Phase 1: Project launches on Ethereum mainnet (no cross-chain).
|
|
29
|
+
/// Phase 2: Expand to Optimism — deploy OP sucker, map ETH (NATIVE_TOKEN -> NATIVE_TOKEN).
|
|
30
|
+
/// Phase 3: Add USDC bridging to Optimism — map USDC on the existing OP sucker.
|
|
31
|
+
/// Phase 4: Expand to Celo via CCIP — deploy CCIP sucker, map ETH as NATIVE_TOKEN -> celoETH (ERC-20).
|
|
32
|
+
/// Phase 5: Add USDC bridging to Celo — map USDC -> celoUSDC on the existing CCIP sucker.
|
|
33
|
+
/// Phase 6: Deprecate the OP sucker — Celo sucker continues working.
|
|
34
|
+
contract MultiChainEvolutionTest is Test, TestBaseWorkflow, IERC721Receiver {
|
|
35
|
+
JBSuckerRegistry registry;
|
|
36
|
+
uint256 projectId;
|
|
37
|
+
|
|
38
|
+
// Mock bridge/messenger addresses for OP.
|
|
39
|
+
IOPMessenger constant MOCK_OP_MESSENGER = IOPMessenger(address(0xA001));
|
|
40
|
+
IOPStandardBridge constant MOCK_OP_BRIDGE = IOPStandardBridge(address(0xA002));
|
|
41
|
+
|
|
42
|
+
// Mock CCIP router.
|
|
43
|
+
address constant MOCK_CCIP_ROUTER_ADDR = address(0xA003);
|
|
44
|
+
|
|
45
|
+
// Celo chain constants.
|
|
46
|
+
uint256 constant CELO_CHAIN_ID = 42_220;
|
|
47
|
+
uint64 constant CELO_CHAIN_SELECTOR = 1_311_226;
|
|
48
|
+
|
|
49
|
+
// Token addresses representing tokens on remote chains.
|
|
50
|
+
// On Celo, ETH is an ERC-20 (not native).
|
|
51
|
+
address celoETH = address(0xCE10E001);
|
|
52
|
+
// USDC addresses (local and remote).
|
|
53
|
+
address localUSDC = address(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);
|
|
54
|
+
address opUSDC = address(0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85);
|
|
55
|
+
address celoUSDC = address(0xef4229c8c3250C675F21BCefa42f58EfbfF6002a);
|
|
56
|
+
|
|
57
|
+
// Deployers (set in setUp).
|
|
58
|
+
JBOptimismSuckerDeployer opDeployer;
|
|
59
|
+
JBCCIPSuckerDeployer ccipDeployer;
|
|
60
|
+
|
|
61
|
+
function setUp() public override {
|
|
62
|
+
super.setUp();
|
|
63
|
+
|
|
64
|
+
vm.label(address(MOCK_OP_MESSENGER), "OP_MESSENGER");
|
|
65
|
+
vm.label(address(MOCK_OP_BRIDGE), "OP_BRIDGE");
|
|
66
|
+
vm.label(MOCK_CCIP_ROUTER_ADDR, "CCIP_ROUTER");
|
|
67
|
+
|
|
68
|
+
// Etch code at mock addresses so Solidity's extcodesize checks pass.
|
|
69
|
+
vm.etch(address(MOCK_OP_MESSENGER), hex"01");
|
|
70
|
+
vm.etch(address(MOCK_OP_BRIDGE), hex"01");
|
|
71
|
+
vm.etch(MOCK_CCIP_ROUTER_ADDR, hex"01");
|
|
72
|
+
|
|
73
|
+
// Deploy the registry.
|
|
74
|
+
registry = new JBSuckerRegistry(jbDirectory(), jbPermissions(), address(this), address(0));
|
|
75
|
+
|
|
76
|
+
// --- Set up OP deployer ---
|
|
77
|
+
opDeployer = new JBOptimismSuckerDeployer({
|
|
78
|
+
directory: jbDirectory(),
|
|
79
|
+
permissions: jbPermissions(),
|
|
80
|
+
tokens: jbTokens(),
|
|
81
|
+
configurator: address(this),
|
|
82
|
+
trustedForwarder: address(0)
|
|
83
|
+
});
|
|
84
|
+
opDeployer.setChainSpecificConstants(MOCK_OP_MESSENGER, MOCK_OP_BRIDGE);
|
|
85
|
+
|
|
86
|
+
JBOptimismSucker opSingleton = new JBOptimismSucker({
|
|
87
|
+
deployer: opDeployer,
|
|
88
|
+
directory: jbDirectory(),
|
|
89
|
+
permissions: jbPermissions(),
|
|
90
|
+
tokens: jbTokens(),
|
|
91
|
+
addToBalanceMode: JBAddToBalanceMode.MANUAL,
|
|
92
|
+
trustedForwarder: address(0)
|
|
93
|
+
});
|
|
94
|
+
opDeployer.configureSingleton(opSingleton);
|
|
95
|
+
|
|
96
|
+
// --- Set up CCIP deployer ---
|
|
97
|
+
ccipDeployer = new JBCCIPSuckerDeployer({
|
|
98
|
+
directory: jbDirectory(),
|
|
99
|
+
permissions: jbPermissions(),
|
|
100
|
+
tokens: jbTokens(),
|
|
101
|
+
configurator: address(this),
|
|
102
|
+
trustedForwarder: address(0)
|
|
103
|
+
});
|
|
104
|
+
ccipDeployer.setChainSpecificConstants({
|
|
105
|
+
remoteChainId: CELO_CHAIN_ID,
|
|
106
|
+
remoteChainSelector: CELO_CHAIN_SELECTOR,
|
|
107
|
+
router: ICCIPRouter(MOCK_CCIP_ROUTER_ADDR)
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
JBCCIPSucker ccipSingleton = new JBCCIPSucker({
|
|
111
|
+
deployer: ccipDeployer,
|
|
112
|
+
directory: jbDirectory(),
|
|
113
|
+
permissions: jbPermissions(),
|
|
114
|
+
tokens: jbTokens(),
|
|
115
|
+
addToBalanceMode: JBAddToBalanceMode.MANUAL,
|
|
116
|
+
trustedForwarder: address(0)
|
|
117
|
+
});
|
|
118
|
+
ccipDeployer.configureSingleton(ccipSingleton);
|
|
119
|
+
|
|
120
|
+
// Allow both deployers in the registry.
|
|
121
|
+
registry.allowSuckerDeployer(address(opDeployer));
|
|
122
|
+
registry.allowSuckerDeployer(address(ccipDeployer));
|
|
123
|
+
|
|
124
|
+
// --- Launch project ---
|
|
125
|
+
JBRulesetMetadata memory metadata = JBRulesetMetadata({
|
|
126
|
+
reservedPercent: 0,
|
|
127
|
+
cashOutTaxRate: 0,
|
|
128
|
+
baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
|
|
129
|
+
pausePay: false,
|
|
130
|
+
pauseCreditTransfers: false,
|
|
131
|
+
allowOwnerMinting: true,
|
|
132
|
+
allowSetCustomToken: false,
|
|
133
|
+
allowTerminalMigration: false,
|
|
134
|
+
allowSetTerminals: false,
|
|
135
|
+
allowSetController: false,
|
|
136
|
+
allowAddAccountingContext: true,
|
|
137
|
+
allowAddPriceFeed: true,
|
|
138
|
+
ownerMustSendPayouts: false,
|
|
139
|
+
holdFees: false,
|
|
140
|
+
useTotalSurplusForCashOuts: true,
|
|
141
|
+
useDataHookForPay: false,
|
|
142
|
+
useDataHookForCashOut: false,
|
|
143
|
+
dataHook: address(0),
|
|
144
|
+
metadata: 0
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
JBRulesetConfig[] memory rulesetConfigs = new JBRulesetConfig[](1);
|
|
148
|
+
rulesetConfigs[0].mustStartAtOrAfter = 0;
|
|
149
|
+
rulesetConfigs[0].duration = 0;
|
|
150
|
+
rulesetConfigs[0].weight = 1000 * 10 ** 18;
|
|
151
|
+
rulesetConfigs[0].weightCutPercent = 0;
|
|
152
|
+
rulesetConfigs[0].approvalHook = IJBRulesetApprovalHook(address(0));
|
|
153
|
+
rulesetConfigs[0].metadata = metadata;
|
|
154
|
+
rulesetConfigs[0].splitGroups = new JBSplitGroup[](0);
|
|
155
|
+
rulesetConfigs[0].fundAccessLimitGroups = new JBFundAccessLimitGroup[](0);
|
|
156
|
+
|
|
157
|
+
JBAccountingContext[] memory tokensToAccept = new JBAccountingContext[](1);
|
|
158
|
+
tokensToAccept[0] = JBAccountingContext({
|
|
159
|
+
token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
JBTerminalConfig[] memory terminalConfigs = new JBTerminalConfig[](1);
|
|
163
|
+
terminalConfigs[0] = JBTerminalConfig({terminal: jbMultiTerminal(), accountingContextsToAccept: tokensToAccept});
|
|
164
|
+
|
|
165
|
+
projectId = jbController()
|
|
166
|
+
.launchProjectFor({
|
|
167
|
+
owner: address(this),
|
|
168
|
+
projectUri: "myproject",
|
|
169
|
+
rulesetConfigurations: rulesetConfigs,
|
|
170
|
+
terminalConfigurations: terminalConfigs,
|
|
171
|
+
memo: ""
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// =========================================================================
|
|
176
|
+
// Full lifecycle: project starts on one chain, evolves to many
|
|
177
|
+
// =========================================================================
|
|
178
|
+
|
|
179
|
+
// Storage vars for lifecycle test (avoids stack-too-deep).
|
|
180
|
+
IJBSucker _opSucker;
|
|
181
|
+
IJBSucker _celoSucker;
|
|
182
|
+
|
|
183
|
+
function test_lifecycle_projectExpandsAcrossChainsOverTime() public {
|
|
184
|
+
// ---------------------------------------------------------------
|
|
185
|
+
// Phase 1: Project exists only on Ethereum mainnet.
|
|
186
|
+
// No suckers deployed yet.
|
|
187
|
+
// ---------------------------------------------------------------
|
|
188
|
+
|
|
189
|
+
assertEq(registry.suckersOf(projectId).length, 0, "Phase 1: no suckers yet");
|
|
190
|
+
|
|
191
|
+
// Grant the registry MAP_SUCKER_TOKEN permission so deploySuckersFor can call mapTokens.
|
|
192
|
+
_grantMapPermission(address(registry));
|
|
193
|
+
|
|
194
|
+
// ---------------------------------------------------------------
|
|
195
|
+
// Phase 2: Expand to Optimism.
|
|
196
|
+
// Deploy OP sucker. Map NATIVE_TOKEN -> NATIVE_TOKEN
|
|
197
|
+
// (both chains have ETH as native).
|
|
198
|
+
// ---------------------------------------------------------------
|
|
199
|
+
{
|
|
200
|
+
JBTokenMapping[] memory opMappings = new JBTokenMapping[](1);
|
|
201
|
+
opMappings[0] = JBTokenMapping({
|
|
202
|
+
localToken: JBConstants.NATIVE_TOKEN,
|
|
203
|
+
minGas: 200_000,
|
|
204
|
+
remoteToken: bytes32(uint256(uint160(JBConstants.NATIVE_TOKEN))),
|
|
205
|
+
minBridgeAmount: 0.01 ether
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
JBSuckerDeployerConfig[] memory opConfig = new JBSuckerDeployerConfig[](1);
|
|
209
|
+
opConfig[0] =
|
|
210
|
+
JBSuckerDeployerConfig({deployer: IJBSuckerDeployer(address(opDeployer)), mappings: opMappings});
|
|
211
|
+
|
|
212
|
+
_opSucker = IJBSucker(registry.deploySuckersFor(projectId, bytes32("op_salt"), opConfig)[0]);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
assertEq(registry.suckersOf(projectId).length, 1, "Phase 2: one sucker (OP)");
|
|
216
|
+
assertTrue(registry.isSuckerOf(projectId, address(_opSucker)), "Phase 2: OP sucker registered");
|
|
217
|
+
|
|
218
|
+
JBRemoteToken memory opNative = _opSucker.remoteTokenFor(JBConstants.NATIVE_TOKEN);
|
|
219
|
+
assertTrue(opNative.enabled, "Phase 2: native mapping enabled on OP");
|
|
220
|
+
assertEq(
|
|
221
|
+
opNative.addr, bytes32(uint256(uint160(JBConstants.NATIVE_TOKEN))), "Phase 2: native maps to native on OP"
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
// ---------------------------------------------------------------
|
|
225
|
+
// Phase 3: Add USDC bridging to Optimism.
|
|
226
|
+
// Call mapToken on the existing OP sucker.
|
|
227
|
+
// ---------------------------------------------------------------
|
|
228
|
+
|
|
229
|
+
_opSucker.mapToken(
|
|
230
|
+
JBTokenMapping({
|
|
231
|
+
localToken: localUSDC,
|
|
232
|
+
minGas: 200_000,
|
|
233
|
+
remoteToken: bytes32(uint256(uint160(opUSDC))),
|
|
234
|
+
minBridgeAmount: 1e6
|
|
235
|
+
})
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
assertTrue(_opSucker.remoteTokenFor(localUSDC).enabled, "Phase 3: USDC enabled on OP");
|
|
239
|
+
assertEq(
|
|
240
|
+
_opSucker.remoteTokenFor(localUSDC).addr, bytes32(uint256(uint160(opUSDC))), "Phase 3: USDC maps to opUSDC"
|
|
241
|
+
);
|
|
242
|
+
assertTrue(_opSucker.remoteTokenFor(JBConstants.NATIVE_TOKEN).enabled, "Phase 3: native still works");
|
|
243
|
+
|
|
244
|
+
// ---------------------------------------------------------------
|
|
245
|
+
// Phase 4: Expand to Celo via CCIP.
|
|
246
|
+
// Deploy CCIP sucker. Map NATIVE_TOKEN -> celoETH (ERC-20).
|
|
247
|
+
//
|
|
248
|
+
// THIS IS THE KEY CROSS-CHAIN NATIVE TOKEN INTEROP CASE:
|
|
249
|
+
// On Ethereum, ETH is the native token (NATIVE_TOKEN).
|
|
250
|
+
// On Celo, ETH is an ERC-20 (celoETH address).
|
|
251
|
+
// The CCIP sucker allows this mapping because it wraps
|
|
252
|
+
// native ETH -> WETH for CCIP transport, and the receiving
|
|
253
|
+
// side processes celoETH as an ERC-20 (no unwrap).
|
|
254
|
+
// ---------------------------------------------------------------
|
|
255
|
+
{
|
|
256
|
+
JBTokenMapping[] memory celoMappings = new JBTokenMapping[](1);
|
|
257
|
+
celoMappings[0] = JBTokenMapping({
|
|
258
|
+
localToken: JBConstants.NATIVE_TOKEN,
|
|
259
|
+
minGas: 200_000,
|
|
260
|
+
remoteToken: bytes32(uint256(uint160(celoETH))), // ERC-20 on Celo, NOT NATIVE_TOKEN
|
|
261
|
+
minBridgeAmount: 0.01 ether
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
JBSuckerDeployerConfig[] memory celoConfig = new JBSuckerDeployerConfig[](1);
|
|
265
|
+
celoConfig[0] =
|
|
266
|
+
JBSuckerDeployerConfig({deployer: IJBSuckerDeployer(address(ccipDeployer)), mappings: celoMappings});
|
|
267
|
+
|
|
268
|
+
_celoSucker = IJBSucker(registry.deploySuckersFor(projectId, bytes32("celo_salt"), celoConfig)[0]);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
assertEq(registry.suckersOf(projectId).length, 2, "Phase 4: two suckers (OP + Celo)");
|
|
272
|
+
assertTrue(registry.isSuckerOf(projectId, address(_celoSucker)), "Phase 4: Celo sucker registered");
|
|
273
|
+
assertTrue(registry.isSuckerOf(projectId, address(_opSucker)), "Phase 4: OP sucker still registered");
|
|
274
|
+
|
|
275
|
+
JBRemoteToken memory celoNative = _celoSucker.remoteTokenFor(JBConstants.NATIVE_TOKEN);
|
|
276
|
+
assertTrue(celoNative.enabled, "Phase 4: native mapping enabled on Celo");
|
|
277
|
+
assertEq(
|
|
278
|
+
celoNative.addr, bytes32(uint256(uint160(celoETH))), "Phase 4: native maps to celoETH (ERC-20) on Celo"
|
|
279
|
+
);
|
|
280
|
+
assertEq(registry.suckerPairsOf(projectId).length, 2, "Phase 4: two sucker pairs");
|
|
281
|
+
|
|
282
|
+
// ---------------------------------------------------------------
|
|
283
|
+
// Phase 5: Add USDC bridging to Celo.
|
|
284
|
+
// Call mapToken on the existing CCIP sucker.
|
|
285
|
+
// ---------------------------------------------------------------
|
|
286
|
+
|
|
287
|
+
_celoSucker.mapToken(
|
|
288
|
+
JBTokenMapping({
|
|
289
|
+
localToken: localUSDC,
|
|
290
|
+
minGas: 200_000,
|
|
291
|
+
remoteToken: bytes32(uint256(uint160(celoUSDC))),
|
|
292
|
+
minBridgeAmount: 1e6
|
|
293
|
+
})
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
assertTrue(_celoSucker.remoteTokenFor(localUSDC).enabled, "Phase 5: USDC enabled on Celo");
|
|
297
|
+
assertEq(
|
|
298
|
+
_celoSucker.remoteTokenFor(localUSDC).addr,
|
|
299
|
+
bytes32(uint256(uint160(celoUSDC))),
|
|
300
|
+
"Phase 5: USDC maps to celoUSDC"
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
// OP sucker is completely independent — its mappings unchanged.
|
|
304
|
+
assertEq(
|
|
305
|
+
_opSucker.remoteTokenFor(JBConstants.NATIVE_TOKEN).addr,
|
|
306
|
+
bytes32(uint256(uint160(JBConstants.NATIVE_TOKEN))),
|
|
307
|
+
"Phase 5: OP native mapping unchanged"
|
|
308
|
+
);
|
|
309
|
+
assertEq(
|
|
310
|
+
_opSucker.remoteTokenFor(localUSDC).addr,
|
|
311
|
+
bytes32(uint256(uint160(opUSDC))),
|
|
312
|
+
"Phase 5: OP USDC mapping unchanged"
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
// ---------------------------------------------------------------
|
|
316
|
+
// Phase 6: Deprecate the OP sucker.
|
|
317
|
+
// The Celo sucker continues working.
|
|
318
|
+
// ---------------------------------------------------------------
|
|
319
|
+
|
|
320
|
+
uint40 deprecationTime = uint40(block.timestamp + 14 days);
|
|
321
|
+
JBOptimismSucker(payable(address(_opSucker))).setDeprecation(deprecationTime);
|
|
322
|
+
vm.warp(deprecationTime);
|
|
323
|
+
|
|
324
|
+
assertEq(uint8(_opSucker.state()), uint8(JBSuckerState.DEPRECATED), "Phase 6: OP sucker deprecated");
|
|
325
|
+
|
|
326
|
+
registry.removeDeprecatedSucker(projectId, address(_opSucker));
|
|
327
|
+
assertEq(registry.suckersOf(projectId).length, 1, "Phase 6: only Celo sucker remains");
|
|
328
|
+
|
|
329
|
+
assertEq(uint8(_celoSucker.state()), uint8(JBSuckerState.ENABLED), "Phase 6: Celo still enabled");
|
|
330
|
+
assertTrue(
|
|
331
|
+
_celoSucker.remoteTokenFor(JBConstants.NATIVE_TOKEN).enabled, "Phase 6: Celo native mapping still works"
|
|
332
|
+
);
|
|
333
|
+
assertEq(
|
|
334
|
+
_celoSucker.remoteTokenFor(JBConstants.NATIVE_TOKEN).addr,
|
|
335
|
+
bytes32(uint256(uint160(celoETH))),
|
|
336
|
+
"Phase 6: Celo native still maps to celoETH"
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// =========================================================================
|
|
341
|
+
// Focused: deploy suckers to multiple chains in one transaction
|
|
342
|
+
// =========================================================================
|
|
343
|
+
|
|
344
|
+
function test_canDeployToMultipleChainsAtOnce() public {
|
|
345
|
+
_grantMapPermission(address(registry));
|
|
346
|
+
|
|
347
|
+
// Deploy to both OP and Celo in a single deploySuckersFor call.
|
|
348
|
+
JBSuckerDeployerConfig[] memory configs = new JBSuckerDeployerConfig[](2);
|
|
349
|
+
|
|
350
|
+
JBTokenMapping[] memory opMappings = new JBTokenMapping[](1);
|
|
351
|
+
opMappings[0] = JBTokenMapping({
|
|
352
|
+
localToken: JBConstants.NATIVE_TOKEN,
|
|
353
|
+
minGas: 200_000,
|
|
354
|
+
remoteToken: bytes32(uint256(uint160(JBConstants.NATIVE_TOKEN))),
|
|
355
|
+
minBridgeAmount: 0.01 ether
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
JBTokenMapping[] memory celoMappings = new JBTokenMapping[](1);
|
|
359
|
+
celoMappings[0] = JBTokenMapping({
|
|
360
|
+
localToken: JBConstants.NATIVE_TOKEN,
|
|
361
|
+
minGas: 200_000,
|
|
362
|
+
remoteToken: bytes32(uint256(uint160(celoETH))),
|
|
363
|
+
minBridgeAmount: 0.01 ether
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
configs[0] = JBSuckerDeployerConfig({deployer: IJBSuckerDeployer(address(opDeployer)), mappings: opMappings});
|
|
367
|
+
configs[1] =
|
|
368
|
+
JBSuckerDeployerConfig({deployer: IJBSuckerDeployer(address(ccipDeployer)), mappings: celoMappings});
|
|
369
|
+
|
|
370
|
+
address[] memory suckers = registry.deploySuckersFor(projectId, bytes32("both"), configs);
|
|
371
|
+
|
|
372
|
+
assertEq(suckers.length, 2, "Should deploy 2 suckers");
|
|
373
|
+
assertEq(registry.suckersOf(projectId).length, 2, "Registry should track 2 suckers");
|
|
374
|
+
|
|
375
|
+
// Verify each sucker has its own independent mapping.
|
|
376
|
+
IJBSucker opSucker = IJBSucker(suckers[0]);
|
|
377
|
+
IJBSucker celoSucker = IJBSucker(suckers[1]);
|
|
378
|
+
|
|
379
|
+
assertEq(
|
|
380
|
+
opSucker.remoteTokenFor(JBConstants.NATIVE_TOKEN).addr,
|
|
381
|
+
bytes32(uint256(uint160(JBConstants.NATIVE_TOKEN))),
|
|
382
|
+
"OP: native -> native"
|
|
383
|
+
);
|
|
384
|
+
assertEq(
|
|
385
|
+
celoSucker.remoteTokenFor(JBConstants.NATIVE_TOKEN).addr,
|
|
386
|
+
bytes32(uint256(uint160(celoETH))),
|
|
387
|
+
"Celo: native -> celoETH (ERC-20)"
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// =========================================================================
|
|
392
|
+
// Focused: add tokens incrementally to an existing sucker
|
|
393
|
+
// =========================================================================
|
|
394
|
+
|
|
395
|
+
function test_canMapTokensIncrementallyToExistingSucker() public {
|
|
396
|
+
_grantMapPermission(address(registry));
|
|
397
|
+
|
|
398
|
+
// Deploy CCIP sucker with just native mapping.
|
|
399
|
+
JBTokenMapping[] memory initialMappings = new JBTokenMapping[](1);
|
|
400
|
+
initialMappings[0] = JBTokenMapping({
|
|
401
|
+
localToken: JBConstants.NATIVE_TOKEN,
|
|
402
|
+
minGas: 200_000,
|
|
403
|
+
remoteToken: bytes32(uint256(uint160(celoETH))),
|
|
404
|
+
minBridgeAmount: 0.01 ether
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
JBSuckerDeployerConfig[] memory config = new JBSuckerDeployerConfig[](1);
|
|
408
|
+
config[0] =
|
|
409
|
+
JBSuckerDeployerConfig({deployer: IJBSuckerDeployer(address(ccipDeployer)), mappings: initialMappings});
|
|
410
|
+
|
|
411
|
+
address[] memory suckers = registry.deploySuckersFor(projectId, bytes32("incr"), config);
|
|
412
|
+
IJBSucker sucker = IJBSucker(suckers[0]);
|
|
413
|
+
|
|
414
|
+
// Initially: only native mapped.
|
|
415
|
+
assertTrue(sucker.remoteTokenFor(JBConstants.NATIVE_TOKEN).enabled, "Native should be mapped");
|
|
416
|
+
assertFalse(sucker.remoteTokenFor(localUSDC).enabled, "USDC should NOT be mapped yet");
|
|
417
|
+
|
|
418
|
+
// Later: project owner adds USDC.
|
|
419
|
+
sucker.mapToken(
|
|
420
|
+
JBTokenMapping({
|
|
421
|
+
localToken: localUSDC,
|
|
422
|
+
minGas: 200_000,
|
|
423
|
+
remoteToken: bytes32(uint256(uint160(celoUSDC))),
|
|
424
|
+
minBridgeAmount: 1e6
|
|
425
|
+
})
|
|
426
|
+
);
|
|
427
|
+
|
|
428
|
+
// Now both are mapped.
|
|
429
|
+
assertTrue(sucker.remoteTokenFor(JBConstants.NATIVE_TOKEN).enabled, "Native still mapped");
|
|
430
|
+
assertTrue(sucker.remoteTokenFor(localUSDC).enabled, "USDC now mapped");
|
|
431
|
+
assertEq(sucker.remoteTokenFor(localUSDC).addr, bytes32(uint256(uint160(celoUSDC))), "USDC maps to celoUSDC");
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// =========================================================================
|
|
435
|
+
// Focused: modifications to one sucker don't affect another
|
|
436
|
+
// =========================================================================
|
|
437
|
+
|
|
438
|
+
function test_suckerMappingsAreIndependent() public {
|
|
439
|
+
_grantMapPermission(address(registry));
|
|
440
|
+
|
|
441
|
+
// Deploy two suckers.
|
|
442
|
+
JBTokenMapping[] memory nativeMappings = new JBTokenMapping[](1);
|
|
443
|
+
nativeMappings[0] = JBTokenMapping({
|
|
444
|
+
localToken: JBConstants.NATIVE_TOKEN,
|
|
445
|
+
minGas: 200_000,
|
|
446
|
+
remoteToken: bytes32(uint256(uint160(JBConstants.NATIVE_TOKEN))),
|
|
447
|
+
minBridgeAmount: 0.01 ether
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
JBSuckerDeployerConfig[] memory opConfig = new JBSuckerDeployerConfig[](1);
|
|
451
|
+
opConfig[0] =
|
|
452
|
+
JBSuckerDeployerConfig({deployer: IJBSuckerDeployer(address(opDeployer)), mappings: nativeMappings});
|
|
453
|
+
|
|
454
|
+
JBTokenMapping[] memory celoNativeMappings = new JBTokenMapping[](1);
|
|
455
|
+
celoNativeMappings[0] = JBTokenMapping({
|
|
456
|
+
localToken: JBConstants.NATIVE_TOKEN,
|
|
457
|
+
minGas: 200_000,
|
|
458
|
+
remoteToken: bytes32(uint256(uint160(celoETH))),
|
|
459
|
+
minBridgeAmount: 0.01 ether
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
JBSuckerDeployerConfig[] memory celoConfig = new JBSuckerDeployerConfig[](1);
|
|
463
|
+
celoConfig[0] =
|
|
464
|
+
JBSuckerDeployerConfig({deployer: IJBSuckerDeployer(address(ccipDeployer)), mappings: celoNativeMappings});
|
|
465
|
+
|
|
466
|
+
address[] memory opSuckers = registry.deploySuckersFor(projectId, bytes32("ind_op"), opConfig);
|
|
467
|
+
address[] memory celoSuckers = registry.deploySuckersFor(projectId, bytes32("ind_celo"), celoConfig);
|
|
468
|
+
|
|
469
|
+
IJBSucker opSucker = IJBSucker(opSuckers[0]);
|
|
470
|
+
IJBSucker celoSucker = IJBSucker(celoSuckers[0]);
|
|
471
|
+
|
|
472
|
+
// Map USDC only on the Celo sucker.
|
|
473
|
+
celoSucker.mapToken(
|
|
474
|
+
JBTokenMapping({
|
|
475
|
+
localToken: localUSDC,
|
|
476
|
+
minGas: 200_000,
|
|
477
|
+
remoteToken: bytes32(uint256(uint160(celoUSDC))),
|
|
478
|
+
minBridgeAmount: 1e6
|
|
479
|
+
})
|
|
480
|
+
);
|
|
481
|
+
|
|
482
|
+
// OP sucker should NOT have USDC mapped.
|
|
483
|
+
assertFalse(opSucker.remoteTokenFor(localUSDC).enabled, "OP sucker should NOT have USDC");
|
|
484
|
+
|
|
485
|
+
// Celo sucker should have USDC mapped.
|
|
486
|
+
assertTrue(celoSucker.remoteTokenFor(localUSDC).enabled, "Celo sucker should have USDC");
|
|
487
|
+
|
|
488
|
+
// Disable native on OP sucker.
|
|
489
|
+
opSucker.mapToken(
|
|
490
|
+
JBTokenMapping({
|
|
491
|
+
localToken: JBConstants.NATIVE_TOKEN, minGas: 200_000, remoteToken: bytes32(0), minBridgeAmount: 0
|
|
492
|
+
})
|
|
493
|
+
);
|
|
494
|
+
|
|
495
|
+
// OP native disabled, Celo native unaffected.
|
|
496
|
+
assertFalse(opSucker.remoteTokenFor(JBConstants.NATIVE_TOKEN).enabled, "OP native disabled");
|
|
497
|
+
assertTrue(celoSucker.remoteTokenFor(JBConstants.NATIVE_TOKEN).enabled, "Celo native still enabled");
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// =========================================================================
|
|
501
|
+
// Focused: CCIP sucker accepts NATIVE -> ERC20, OP sucker rejects it
|
|
502
|
+
// =========================================================================
|
|
503
|
+
|
|
504
|
+
function test_nativeToERC20_acceptedOnCCIP_rejectedOnOP() public {
|
|
505
|
+
_grantMapPermission(address(registry));
|
|
506
|
+
|
|
507
|
+
// Deploy both suckers with just a placeholder native->native mapping.
|
|
508
|
+
JBTokenMapping[] memory nativeMappings = new JBTokenMapping[](1);
|
|
509
|
+
nativeMappings[0] = JBTokenMapping({
|
|
510
|
+
localToken: JBConstants.NATIVE_TOKEN,
|
|
511
|
+
minGas: 200_000,
|
|
512
|
+
remoteToken: bytes32(uint256(uint160(JBConstants.NATIVE_TOKEN))),
|
|
513
|
+
minBridgeAmount: 0.01 ether
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
JBSuckerDeployerConfig[] memory opConfig = new JBSuckerDeployerConfig[](1);
|
|
517
|
+
opConfig[0] =
|
|
518
|
+
JBSuckerDeployerConfig({deployer: IJBSuckerDeployer(address(opDeployer)), mappings: nativeMappings});
|
|
519
|
+
address[] memory opSuckers = registry.deploySuckersFor(projectId, bytes32("natv_op"), opConfig);
|
|
520
|
+
|
|
521
|
+
// CCIP sucker: deploy with NATIVE -> celoETH (ERC-20). Should succeed.
|
|
522
|
+
JBTokenMapping[] memory celoEthMappings = new JBTokenMapping[](1);
|
|
523
|
+
celoEthMappings[0] = JBTokenMapping({
|
|
524
|
+
localToken: JBConstants.NATIVE_TOKEN,
|
|
525
|
+
minGas: 200_000,
|
|
526
|
+
remoteToken: bytes32(uint256(uint160(celoETH))),
|
|
527
|
+
minBridgeAmount: 0.01 ether
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
JBSuckerDeployerConfig[] memory celoConfig = new JBSuckerDeployerConfig[](1);
|
|
531
|
+
celoConfig[0] =
|
|
532
|
+
JBSuckerDeployerConfig({deployer: IJBSuckerDeployer(address(ccipDeployer)), mappings: celoEthMappings});
|
|
533
|
+
|
|
534
|
+
// This should succeed — CCIP allows NATIVE -> ERC-20.
|
|
535
|
+
address[] memory celoSuckers = registry.deploySuckersFor(projectId, bytes32("natv_celo"), celoConfig);
|
|
536
|
+
assertEq(
|
|
537
|
+
IJBSucker(celoSuckers[0]).remoteTokenFor(JBConstants.NATIVE_TOKEN).addr,
|
|
538
|
+
bytes32(uint256(uint160(celoETH))),
|
|
539
|
+
"CCIP: NATIVE -> celoETH accepted"
|
|
540
|
+
);
|
|
541
|
+
|
|
542
|
+
// OP sucker: try to map NATIVE -> celoETH (ERC-20). Should REVERT.
|
|
543
|
+
vm.expectRevert(
|
|
544
|
+
abi.encodeWithSelector(
|
|
545
|
+
JBSucker.JBSucker_InvalidNativeRemoteAddress.selector, bytes32(uint256(uint160(celoETH)))
|
|
546
|
+
)
|
|
547
|
+
);
|
|
548
|
+
IJBSucker(opSuckers[0])
|
|
549
|
+
.mapToken(
|
|
550
|
+
JBTokenMapping({
|
|
551
|
+
localToken: JBConstants.NATIVE_TOKEN,
|
|
552
|
+
minGas: 200_000,
|
|
553
|
+
remoteToken: bytes32(uint256(uint160(celoETH))),
|
|
554
|
+
minBridgeAmount: 0.01 ether
|
|
555
|
+
})
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// =========================================================================
|
|
560
|
+
// Focused: can replace a deprecated sucker with a new one
|
|
561
|
+
// =========================================================================
|
|
562
|
+
|
|
563
|
+
function test_canReplaceSuckerAfterDeprecation() public {
|
|
564
|
+
_grantMapPermission(address(registry));
|
|
565
|
+
|
|
566
|
+
// Deploy initial OP sucker.
|
|
567
|
+
JBTokenMapping[] memory mappings = new JBTokenMapping[](1);
|
|
568
|
+
mappings[0] = JBTokenMapping({
|
|
569
|
+
localToken: JBConstants.NATIVE_TOKEN,
|
|
570
|
+
minGas: 200_000,
|
|
571
|
+
remoteToken: bytes32(uint256(uint160(JBConstants.NATIVE_TOKEN))),
|
|
572
|
+
minBridgeAmount: 0.01 ether
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
JBSuckerDeployerConfig[] memory config = new JBSuckerDeployerConfig[](1);
|
|
576
|
+
config[0] = JBSuckerDeployerConfig({deployer: IJBSuckerDeployer(address(opDeployer)), mappings: mappings});
|
|
577
|
+
|
|
578
|
+
address[] memory firstDeploy = registry.deploySuckersFor(projectId, bytes32("v1"), config);
|
|
579
|
+
address oldSucker = firstDeploy[0];
|
|
580
|
+
assertEq(registry.suckersOf(projectId).length, 1);
|
|
581
|
+
|
|
582
|
+
// Deprecate it.
|
|
583
|
+
uint40 deprecationTime = uint40(block.timestamp + 14 days);
|
|
584
|
+
JBOptimismSucker(payable(oldSucker)).setDeprecation(deprecationTime);
|
|
585
|
+
vm.warp(deprecationTime);
|
|
586
|
+
assertEq(uint8(IJBSucker(oldSucker).state()), uint8(JBSuckerState.DEPRECATED));
|
|
587
|
+
|
|
588
|
+
// Remove from registry.
|
|
589
|
+
registry.removeDeprecatedSucker(projectId, oldSucker);
|
|
590
|
+
assertEq(registry.suckersOf(projectId).length, 0, "Old sucker removed");
|
|
591
|
+
|
|
592
|
+
// Deploy a replacement sucker with a new salt.
|
|
593
|
+
address[] memory secondDeploy = registry.deploySuckersFor(projectId, bytes32("v2"), config);
|
|
594
|
+
address newSucker = secondDeploy[0];
|
|
595
|
+
|
|
596
|
+
assertEq(registry.suckersOf(projectId).length, 1, "New sucker deployed");
|
|
597
|
+
assertTrue(registry.isSuckerOf(projectId, newSucker), "New sucker registered");
|
|
598
|
+
assertFalse(registry.isSuckerOf(projectId, oldSucker), "Old sucker no longer registered");
|
|
599
|
+
assertTrue(newSucker != oldSucker, "New sucker has different address");
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// =========================================================================
|
|
603
|
+
// Helpers
|
|
604
|
+
// =========================================================================
|
|
605
|
+
|
|
606
|
+
function _grantMapPermission(address operator) internal {
|
|
607
|
+
uint8[] memory permissions = new uint8[](1);
|
|
608
|
+
permissions[0] = JBPermissionIds.MAP_SUCKER_TOKEN;
|
|
609
|
+
|
|
610
|
+
jbPermissions()
|
|
611
|
+
.setPermissionsFor(
|
|
612
|
+
address(this),
|
|
613
|
+
JBPermissionsData({operator: operator, projectId: uint56(projectId), permissionIds: permissions})
|
|
614
|
+
);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function onERC721Received(address, address, uint256, bytes calldata) external pure returns (bytes4) {
|
|
618
|
+
return IERC721Receiver.onERC721Received.selector;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
receive() external payable {}
|
|
622
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.13;
|
|
3
|
+
|
|
4
|
+
import "forge-std/Test.sol";
|
|
5
|
+
|
|
6
|
+
import {IJBController} from "@bananapus/core-v6/src/interfaces/IJBController.sol";
|
|
7
|
+
import "../../src/JBOptimismSucker.sol";
|
|
8
|
+
import "../../src/deployers/JBOptimismSuckerDeployer.sol";
|
|
9
|
+
|
|
10
|
+
import {JBLeaf} from "../../src/structs/JBLeaf.sol";
|
|
11
|
+
import {JBClaim} from "../../src/structs/JBClaim.sol";
|
|
12
|
+
|
|
13
|
+
import {JBProjects} from "@bananapus/core-v6/src/JBProjects.sol";
|
|
14
|
+
import {JBDirectory} from "@bananapus/core-v6/src/JBDirectory.sol";
|
|
15
|
+
import {JBPermissions} from "@bananapus/core-v6/src/JBPermissions.sol";
|
|
16
|
+
|
|
17
|
+
import {JBSuckerRegistry} from "./../../src/JBSuckerRegistry.sol";
|
|
18
|
+
|
|
19
|
+
contract RegistryUnitTest is Test {
|
|
20
|
+
function testDeployNoProjectCheck() public {
|
|
21
|
+
JBProjects _projects = new JBProjects(msg.sender, address(0), address(0));
|
|
22
|
+
JBPermissions _permissions = new JBPermissions(address(0));
|
|
23
|
+
JBDirectory _directory = new JBDirectory(_permissions, _projects, address(100));
|
|
24
|
+
new JBSuckerRegistry(_directory, _permissions, address(100), address(0));
|
|
25
|
+
}
|
|
26
|
+
}
|