@@ -11,6 +11,11 @@ interface IL2AssetRouter {
1111 function withdraw (bytes32 _assetId , bytes memory _assetData ) external returns (bytes32 );
1212}
1313
14+ // https://github.com/matter-labs/era-contracts/blob/6391c0d7bf6184d7f6718060e3991ba6f0efe4a7/zksync/contracts/bridge/L2ERC20Bridge.sol#L104
15+ interface ZkBridgeLike {
16+ function withdraw (address _l1Receiver , address _l2Token , uint256 _amount ) external ;
17+ }
18+
1419interface IL2ETH {
1520 function withdraw (address _l1Receiver ) external payable ;
1621}
@@ -34,18 +39,22 @@ contract ZkSync_SpokePool is SpokePool, CircleCCTPAdapter {
3439 // Legacy bridge used to withdraw ERC20's to L1, replaced by `l2AssetRouter`.
3540 address public DEPRECATED_zkErc20Bridge;
3641
37- // @dev The offset from which the built-in, but user space contracts are located.
38- // Source: https://github.com/matter-labs/era-contracts/blob/48e189814aabb43964ed29817a7f05aa36f09fd6/l1-contracts/contracts/common/l2-helpers/L2ContractAddresses.sol#L12C1-L13C58
42+ /// @custom:oz-upgrades-unsafe-allow state-variable-immutable
43+ /// @dev Legacy bridge used to withdraw USDC to L1, for withdrawing all other ERC20's we use `l2AssetRouter`.
44+ ZkBridgeLike public immutable zkUSDCBridge;
45+
46+ /// @dev The offset from which the built-in, but user space contracts are located.
47+ /// Source: https://github.com/matter-labs/era-contracts/blob/48e189814aabb43964ed29817a7f05aa36f09fd6/l1-contracts/contracts/common/l2-helpers/L2ContractAddresses.sol#L12C1-L13C58
3948 uint160 constant USER_CONTRACTS_OFFSET = 0x10000 ; // 2^16
4049
41- // Contract used to withdraw ERC20's to L1.
42- // Source: https://github.com/matter-labs/era-contracts/blob/48e189814aabb43964ed29817a7f05aa36f09fd6/l1-contracts/contracts/common/l2-helpers/L2ContractAddresses.sol#L68
50+ /// Contract used to withdraw ERC20's to L1.
51+ /// Source: https://github.com/matter-labs/era-contracts/blob/48e189814aabb43964ed29817a7f05aa36f09fd6/l1-contracts/contracts/common/l2-helpers/L2ContractAddresses.sol#L68
4352 address constant L2_ASSET_ROUTER_ADDR = address (USER_CONTRACTS_OFFSET + 0x03 );
4453 IL2AssetRouter public constant l2AssetRouter = IL2AssetRouter (L2_ASSET_ROUTER_ADDR);
4554
46- // @dev An l2 system contract address, used in the assetId calculation for native assets.
47- // This is needed for automatic bridging, i.e. without deploying the AssetHandler contract,
48- // if the assetId can be calculated with this address then it is in fact an NTV asset
55+ /// @dev An l2 system contract address, used in the assetId calculation for native assets.
56+ /// This is needed for automatic bridging, i.e. without deploying the AssetHandler contract,
57+ /// if the assetId can be calculated with this address then it is in fact an NTV asset
4958 /// Source: https://github.com/matter-labs/era-contracts/blob/48e189814aabb43964ed29817a7f05aa36f09fd6/l1-contracts/contracts/common/l2-helpers/L2ContractAddresses.sol#L70C1-L73C85
5059 address constant L2_NATIVE_TOKEN_VAULT_ADDR = address (USER_CONTRACTS_OFFSET + 0x04 );
5160
@@ -58,8 +67,12 @@ contract ZkSync_SpokePool is SpokePool, CircleCCTPAdapter {
5867
5968 /**
6069 * @notice Constructor.
70+ * @param _zkUSDCBridge Legacy bridge used to withdraw USDC to L1, for withdrawing all other ERC20's we use `l2AssetRouter`.
6171 * @param _wrappedNativeTokenAddress wrappedNativeToken address for this network to set.
6272 * @param _circleUSDC Circle USDC address on the SpokePool. Set to 0x0 to use the standard ERC20 bridge instead.
73+ * If not set to zero, then either the zkUSDCBridge or cctpTokenMessenger must be set and will be used to
74+ * bridge this token.
75+ * @param _zkUSDCBridge Elastic chain custom bridge address for USDC (if deployed, or address(0) to disable).
6376 * @param _l1ChainId Chain ID of the L1 chain.
6477 * @param _cctpTokenMessenger TokenMessenger contract to bridge via CCTP. If the zero address is passed, CCTP bridging will be disabled.
6578 * @param _depositQuoteTimeBuffer depositQuoteTimeBuffer to set. Quote timestamps can't be set more than this amount
@@ -71,6 +84,7 @@ contract ZkSync_SpokePool is SpokePool, CircleCCTPAdapter {
7184 constructor (
7285 address _wrappedNativeTokenAddress ,
7386 IERC20 _circleUSDC ,
87+ ZkBridgeLike _zkUSDCBridge ,
7488 uint256 _l1ChainId ,
7589 ITokenMessenger _cctpTokenMessenger ,
7690 uint32 _depositQuoteTimeBuffer ,
@@ -86,6 +100,17 @@ contract ZkSync_SpokePool is SpokePool, CircleCCTPAdapter {
86100 )
87101 CircleCCTPAdapter (_circleUSDC, _cctpTokenMessenger, CircleDomainIds.Ethereum)
88102 {
103+ address zero = address (0 );
104+ if (address (_circleUSDC) != zero) {
105+ bool zkUSDCBridgeDisabled = address (_zkUSDCBridge) == zero;
106+ bool cctpUSDCBridgeDisabled = address (_cctpTokenMessenger) == zero;
107+ // Bridged and Native USDC are mutually exclusive.
108+ if (zkUSDCBridgeDisabled == cctpUSDCBridgeDisabled) {
109+ revert InvalidBridgeConfig ();
110+ }
111+ }
112+
113+ zkUSDCBridge = _zkUSDCBridge;
89114 l1ChainId = _l1ChainId;
90115 }
91116
@@ -148,8 +173,15 @@ contract ZkSync_SpokePool is SpokePool, CircleCCTPAdapter {
148173 WETH9Interface (l2TokenAddress).withdraw (amountToReturn); // Unwrap into ETH.
149174 // To withdraw tokens, we actually call 'withdraw' on the L2 eth token itself.
150175 IL2ETH (l2Eth).withdraw { value: amountToReturn }(withdrawalRecipient);
151- } else if (_isCCTPEnabled () && l2TokenAddress == address (usdcToken)) {
152- _transferUsdc (withdrawalRecipient, amountToReturn);
176+ } else if (l2TokenAddress == address (usdcToken)) {
177+ if (_isCCTPEnabled ()) {
178+ // Circle native USDC via CCTP.
179+ _transferUsdc (withdrawalRecipient, amountToReturn);
180+ } else {
181+ // Matter Labs custom USDC bridge for Circle Bridged (upgradable) USDC.
182+ IERC20 (l2TokenAddress).forceApprove (address (zkUSDCBridge), amountToReturn);
183+ zkUSDCBridge.withdraw (withdrawalRecipient, l2TokenAddress, amountToReturn);
184+ }
153185 } else {
154186 bytes32 assetId = _getAssetId (l2TokenAddress);
155187 bytes memory data = _encodeBridgeBurnData (amountToReturn, withdrawalRecipient, l2TokenAddress);
0 commit comments