Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions contracts/Lens_SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ contract Lens_SpokePool is ZkSync_SpokePool {
address _wrappedNativeTokenAddress,
IERC20 _circleUSDC,
ZkBridgeLike _zkUSDCBridge,
uint256 _l1ChainId,
ITokenMessenger _cctpTokenMessenger,
uint32 _depositQuoteTimeBuffer,
uint32 _fillDeadlineBuffer
Expand All @@ -22,6 +23,7 @@ contract Lens_SpokePool is ZkSync_SpokePool {
_wrappedNativeTokenAddress,
_circleUSDC,
_zkUSDCBridge,
_l1ChainId,
_cctpTokenMessenger,
_depositQuoteTimeBuffer,
_fillDeadlineBuffer
Expand Down
70 changes: 47 additions & 23 deletions contracts/ZkSync_SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ import { CircleCCTPAdapter, CircleDomainIds, ITokenMessenger } from "./libraries
import { CrossDomainAddressUtils } from "./libraries/CrossDomainAddressUtils.sol";
import "./SpokePool.sol";

// https://github.com/matter-labs/era-contracts/blob/48e189814aabb43964ed29817a7f05aa36f09fd6/l1-contracts/contracts/bridge/asset-router/L2AssetRouter.sol#L321
interface IL2AssetRouter {
function withdraw(bytes32 _assetId, bytes memory _assetData) external returns (bytes32);
}

// https://github.com/matter-labs/era-contracts/blob/6391c0d7bf6184d7f6718060e3991ba6f0efe4a7/zksync/contracts/bridge/L2ERC20Bridge.sol#L104
interface ZkBridgeLike {
function withdraw(address _l1Receiver, address _l2Token, uint256 _amount) external;
Expand All @@ -31,23 +36,45 @@ contract ZkSync_SpokePool is SpokePool, CircleCCTPAdapter {
// ETH on ZkSync implements a subset of the ERC-20 interface, with additional built-in support to bridge to L1.
address public l2Eth;

// Bridge used to withdraw ERC20's to L1
ZkBridgeLike public zkErc20Bridge;
// Legacy bridge used to withdraw ERC20's to L1, replaced by `l2AssetRouter`.
address public DEPRECATED_zkErc20Bridge;

/// @dev Legacy bridge used to withdraw USDC to L1, for withdrawing all other ERC20's we use `l2AssetRouter`.
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
ZkBridgeLike public immutable zkUSDCBridge;

/// @dev The offset from which the built-in, but user space contracts are located.
/// Source: https://github.com/matter-labs/era-contracts/blob/48e189814aabb43964ed29817a7f05aa36f09fd6/l1-contracts/contracts/common/l2-helpers/L2ContractAddresses.sol#L12C1-L13C58
uint160 constant USER_CONTRACTS_OFFSET = 0x10000; // 2^16

/// Contract used to withdraw ERC20's to L1.
/// Source: https://github.com/matter-labs/era-contracts/blob/48e189814aabb43964ed29817a7f05aa36f09fd6/l1-contracts/contracts/common/l2-helpers/L2ContractAddresses.sol#L68
address constant L2_ASSET_ROUTER_ADDR = address(USER_CONTRACTS_OFFSET + 0x03);
IL2AssetRouter public constant l2AssetRouter = IL2AssetRouter(L2_ASSET_ROUTER_ADDR);

/// @dev An l2 system contract address, used in the assetId calculation for native assets.
/// This is needed for automatic bridging, i.e. without deploying the AssetHandler contract,
/// if the assetId can be calculated with this address then it is in fact an NTV asset
/// Source: https://github.com/matter-labs/era-contracts/blob/48e189814aabb43964ed29817a7f05aa36f09fd6/l1-contracts/contracts/common/l2-helpers/L2ContractAddresses.sol#L70C1-L73C85
address constant L2_NATIVE_TOKEN_VAULT_ADDR = address(USER_CONTRACTS_OFFSET + 0x04);

/// Used to compute asset ID needed to withdraw tokens.
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
uint256 public immutable l1ChainId;

event SetZkBridge(address indexed erc20Bridge, address indexed oldErc20Bridge);

error InvalidBridgeConfig();

/**
* @notice Constructor.
* @notice Circle bridged & native USDC are optionally supported via configuration, but are mutually exclusive.
* @param _zkUSDCBridge Legacy bridge used to withdraw USDC to L1, for withdrawing all other ERC20's we use `l2AssetRouter`.
* @param _wrappedNativeTokenAddress wrappedNativeToken address for this network to set.
* @param _circleUSDC Circle USDC address on the SpokePool. Set to 0x0 to use the standard ERC20 bridge instead.
* If not set to zero, then either the zkUSDCBridge or cctpTokenMessenger must be set and will be used to
* bridge this token.
* @param _zkUSDCBridge Elastic chain custom bridge address for USDC (if deployed, or address(0) to disable).
* @param _l1ChainId Chain ID of the L1 chain.
* @param _cctpTokenMessenger TokenMessenger contract to bridge via CCTP. If the zero address is passed, CCTP bridging will be disabled.
* @param _depositQuoteTimeBuffer depositQuoteTimeBuffer to set. Quote timestamps can't be set more than this amount
* into the past from the block time of the deposit.
Expand All @@ -59,6 +86,7 @@ contract ZkSync_SpokePool is SpokePool, CircleCCTPAdapter {
address _wrappedNativeTokenAddress,
IERC20 _circleUSDC,
ZkBridgeLike _zkUSDCBridge,
uint256 _l1ChainId,
ITokenMessenger _cctpTokenMessenger,
uint32 _depositQuoteTimeBuffer,
uint32 _fillDeadlineBuffer
Expand All @@ -84,45 +112,31 @@ contract ZkSync_SpokePool is SpokePool, CircleCCTPAdapter {
}

zkUSDCBridge = _zkUSDCBridge;
l1ChainId = _l1ChainId;
}

/**
* @notice Initialize the ZkSync SpokePool.
* @param _initialDepositId Starting deposit ID. Set to 0 unless this is a re-deployment in order to mitigate
* relay hash collisions.
* @param _zkErc20Bridge Address of L2 ERC20 gateway. Can be reset by admin.
* @param _crossDomainAdmin Cross domain admin to set. Can be changed by admin.
* @param _withdrawalRecipient Address which receives token withdrawals. Can be changed by admin. For Spoke Pools on L2, this will
* likely be the hub pool.
*/
function initialize(
uint32 _initialDepositId,
ZkBridgeLike _zkErc20Bridge,
address _crossDomainAdmin,
address _withdrawalRecipient
) public initializer {
l2Eth = 0x000000000000000000000000000000000000800A;
__SpokePool_init(_initialDepositId, _crossDomainAdmin, _withdrawalRecipient);
_setZkBridge(_zkErc20Bridge);
}

modifier onlyFromCrossDomainAdmin() {
require(msg.sender == CrossDomainAddressUtils.applyL1ToL2Alias(crossDomainAdmin), "ONLY_COUNTERPART_GATEWAY");
_;
}

/********************************************************
* ZKSYNC-SPECIFIC CROSS-CHAIN ADMIN FUNCTIONS *
********************************************************/

/**
* @notice Change L2 token bridge addresses. Callable only by admin.
* @param _zkErc20Bridge New address of L2 ERC20 gateway.
*/
function setZkBridge(ZkBridgeLike _zkErc20Bridge) public onlyAdmin nonReentrant {
_setZkBridge(_zkErc20Bridge);
}

/**************************************
* INTERNAL FUNCTIONS *
**************************************/
Expand Down Expand Up @@ -170,14 +184,24 @@ contract ZkSync_SpokePool is SpokePool, CircleCCTPAdapter {
zkUSDCBridge.withdraw(withdrawalRecipient, l2TokenAddress, amountToReturn);
}
} else {
zkErc20Bridge.withdraw(withdrawalRecipient, l2TokenAddress, amountToReturn);
bytes32 assetId = _getAssetId(l2TokenAddress);
bytes memory data = _encodeBridgeBurnData(amountToReturn, withdrawalRecipient, l2TokenAddress);
l2AssetRouter.withdraw(assetId, data);
}
}

function _setZkBridge(ZkBridgeLike _zkErc20Bridge) internal {
address oldErc20Bridge = address(zkErc20Bridge);
zkErc20Bridge = _zkErc20Bridge;
emit SetZkBridge(address(_zkErc20Bridge), oldErc20Bridge);
// Implementation from https://github.com/matter-labs/era-contracts/blob/48e189814aabb43964ed29817a7f05aa36f09fd6/l1-contracts/contracts/common/libraries/DataEncoding.sol#L117C14-L117C62
function _getAssetId(address _tokenAddress) internal view returns (bytes32) {
return keccak256(abi.encode(l1ChainId, L2_NATIVE_TOKEN_VAULT_ADDR, _tokenAddress));
}

// Implementation from https://github.com/matter-labs/era-contracts/blob/48e189814aabb43964ed29817a7f05aa36f09fd6/l1-contracts/contracts/common/libraries/DataEncoding.sol#L24C1-L30C6
function _encodeBridgeBurnData(
uint256 _amount,
address _remoteReceiver,
address _l2TokenAddress
) internal pure returns (bytes memory) {
return abi.encode(_amount, _remoteReceiver, _l2TokenAddress);
}

function _requireAdminSender() internal override onlyFromCrossDomainAdmin {}
Expand Down
4 changes: 2 additions & 2 deletions deploy/016_deploy_zksync_spokepool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,9 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {

const artifact = await deployer.loadArtifact(contractName);

const { zkErc20Bridge, zkUSDCBridge = ZERO_ADDRESS, cctpTokenMessenger } = L2_ADDRESS_MAP[spokeChainId];
const { zkUSDCBridge = ZERO_ADDRESS, cctpTokenMessenger } = L2_ADDRESS_MAP[spokeChainId];
const initArgs = [
0, // Start at 0 since this first time we're deploying this spoke pool. On future upgrades increase this.
zkErc20Bridge,
hubPool.address,
hubPool.address,
];
Expand All @@ -44,6 +43,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
WETH[spokeChainId],
usdcAddress,
zkUSDCBridge,
hubChainId,
cctpTokenMessenger,
QUOTE_TIME_BUFFER,
FILL_DEADLINE_BUFFER,
Expand Down
4 changes: 2 additions & 2 deletions deploy/059_deploy_lens_spokepool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,10 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {

const artifact = await deployer.loadArtifact(contractName);

const { zkErc20Bridge, zkUSDCBridge, cctpTokenMessenger } = L2_ADDRESS_MAP[spokeChainId];
const { zkUSDCBridge, cctpTokenMessenger } = L2_ADDRESS_MAP[spokeChainId];

const initArgs = [
100_000, // Redeployment of the Spoke Pool proxy @ 09-01-2025. Offset the initial deposit ID by 100k
zkErc20Bridge,
hubPool.address,
hubPool.address,
];
Expand All @@ -44,6 +43,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
WGHO[spokeChainId],
usdcAddress,
zkUSDCBridge,
hubChainId,
cctpTokenMessenger,
QUOTE_TIME_BUFFER,
FILL_DEADLINE_BUFFER,
Expand Down
2 changes: 0 additions & 2 deletions deploy/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,6 @@ export const L2_ADDRESS_MAP: { [key: number]: { [contractName: string]: string }
cctpMessageTransmitter: "0x7865fAfC2db2093669d92c0F33AeEF291086BEFD",
},
[CHAIN_IDs.ZK_SYNC]: {
zkErc20Bridge: "0x11f943b2c77b743AB90f4A0Ae7d5A4e7FCA3E102",
cctpTokenMessenger: ZERO_ADDRESS, // CCTP not available on zkSync.
"1inchV6Router": "0x6fd4383cB451173D5f9304F041C7BCBf27d561fF",
permit2: "0x0000000000225e31d15943971f47ad3022f714fa",
Expand Down Expand Up @@ -259,7 +258,6 @@ export const L2_ADDRESS_MAP: { [key: number]: { [contractName: string]: string }
cctpV2TokenMessenger: "0x0000000000000000000000000000000000000000",
},
[CHAIN_IDs.LENS]: {
zkErc20Bridge: "0xfBEC23c5BB0E076F2ef4d0AaD7fe331aE5A01143",
zkUSDCBridge: "0x7188B6975EeC82ae914b6eC7AC32b3c9a18b2c81",
cctpTokenMessenger: ZERO_ADDRESS, // Not available on Lens.
permit2: "0x0000000000225e31D15943971F47aD3022F714Fa",
Expand Down
Loading
Loading