Skip to content

Commit e121518

Browse files
committed
wip
1 parent 9feaf98 commit e121518

File tree

3 files changed

+44
-10
lines changed

3 files changed

+44
-10
lines changed

contracts/Lens_SpokePool.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ contract Lens_SpokePool is ZkSync_SpokePool {
1313
constructor(
1414
address _wrappedNativeTokenAddress,
1515
IERC20 _circleUSDC,
16+
ZkBridgeLike _zkUSDCBridge,
1617
uint256 _l1ChainId,
1718
address _l2NativeTokenVault,
1819
ITokenMessenger _cctpTokenMessenger,
@@ -22,6 +23,7 @@ contract Lens_SpokePool is ZkSync_SpokePool {
2223
ZkSync_SpokePool(
2324
_wrappedNativeTokenAddress,
2425
_circleUSDC,
26+
_zkUSDCBridge,
2527
_l1ChainId,
2628
_l2NativeTokenVault,
2729
_cctpTokenMessenger,

contracts/ZkSync_SpokePool.sol

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
1419
interface 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);

test/evm/foundry/local/eraVM_EIP7702.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ contract TestableMockSpokePool is ZkSync_SpokePool {
2323
ZkSync_SpokePool(
2424
_wrappedNativeTokenAddress,
2525
IERC20(address(0)),
26+
ZkBridgeLike(address(0)),
2627
1,
27-
address(0),
2828
ITokenMessenger(address(0)),
2929
1 hours,
3030
9 hours

0 commit comments

Comments
 (0)