diff --git a/contracts/Storage.sol b/contracts/Storage.sol index 8aad72b..e6376d4 100644 --- a/contracts/Storage.sol +++ b/contracts/Storage.sol @@ -82,21 +82,21 @@ contract Storage is ZkLinkAcceptor, Config { /// @dev Priority Requests mapping (request id - operation) /// Contains op type, pubdata and expiration block of unsatisfied requests. /// Numbers are in order of requests receiving - mapping(uint64 => Operations.PriorityOperation) internal priorityRequests; + mapping(uint64 => Operations.PriorityOperation) public priorityRequests; /// @notice User authenticated fact hashes for some nonce. mapping(address => mapping(uint32 => bytes32)) public authFacts; /// @dev Timer for authFacts entry reset (address, nonce -> timer). /// Used when user wants to reset `authFacts` for some nonce. - mapping(address => mapping(uint32 => uint256)) internal authFactsResetTimer; + mapping(address => mapping(uint32 => uint256)) public authFactsResetTimer; /// @dev Stored hashed StoredBlockInfo for some block number - mapping(uint32 => bytes32) internal storedBlockHashes; + mapping(uint32 => bytes32) public storedBlockHashes; /// @dev Store sync hash for slaver chains /// chainId => syncHash - mapping(uint8 => bytes32) internal synchronizedChains; + mapping(uint8 => bytes32) public synchronizedChains; /// @notice A set of permitted validators mapping(address => bool) public validators; diff --git a/contracts/ZkLinkPeriphery.sol b/contracts/ZkLinkPeriphery.sol index 249f2ef..de425bb 100644 --- a/contracts/ZkLinkPeriphery.sol +++ b/contracts/ZkLinkPeriphery.sol @@ -364,6 +364,9 @@ contract ZkLinkPeriphery is ReentrancyGuard, Storage, Events { for (uint8 i = 0; i < _block.syncHashs.length; ++i) { SyncHash memory sync = _block.syncHashs[i]; bytes32 remoteSyncHash = synchronizedChains[sync.chainId]; + if (remoteSyncHash == bytes32(0)) { + remoteSyncHash = EMPTY_STRING_KECCAK; + } if (remoteSyncHash != sync.syncHash) { return false; } @@ -523,6 +526,15 @@ contract ZkLinkPeriphery is ReentrancyGuard, Storage, Events { } // #endif // =======================Withdraw to L1====================== + /// @notice Estimate the fee to withdraw token to L1 for user by gateway + function estimateWithdrawToL1Fee(address owner, address token, uint128 amount, uint16 fastWithdrawFeeRate, uint32 accountIdOfNonce, uint8 subAccountIdOfNonce, uint32 nonce) public view returns (uint256 nativeFee) { + if (token == ETH_ADDRESS) { + nativeFee = gateway.estimateWithdrawETHFee(owner, amount, accountIdOfNonce, subAccountIdOfNonce, nonce, fastWithdrawFeeRate); + } else { + nativeFee = gateway.estimateWithdrawERC20Fee(owner, token, amount, accountIdOfNonce, subAccountIdOfNonce, nonce, fastWithdrawFeeRate); + } + } + /// @notice Withdraw token to L1 for user by gateway /// @param owner User receive token on L1 /// @param token Token address @@ -537,6 +549,10 @@ contract ZkLinkPeriphery is ReentrancyGuard, Storage, Events { bytes32 withdrawHash = getWithdrawHash(accountIdOfNonce, subAccountIdOfNonce, nonce, owner, token, amount, fastWithdrawFeeRate); require(pendingL1Withdraws[withdrawHash] == true, "M0"); + // ensure supply fee + uint256 fee = estimateWithdrawToL1Fee(owner, token, amount, fastWithdrawFeeRate, accountIdOfNonce, subAccountIdOfNonce, nonce); + require(msg.value >= fee, "M1"); + // ===Effects=== pendingL1Withdraws[withdrawHash] = false; @@ -544,11 +560,19 @@ contract ZkLinkPeriphery is ReentrancyGuard, Storage, Events { // transfer token to gateway // send msg.value as bridge fee to gateway if (token == ETH_ADDRESS) { - gateway.withdrawETH{value: msg.value + amount}(owner, amount, accountIdOfNonce, subAccountIdOfNonce, nonce, fastWithdrawFeeRate); + gateway.withdrawETH{value: fee + amount}(owner, amount, accountIdOfNonce, subAccountIdOfNonce, nonce, fastWithdrawFeeRate); } else { IERC20(token).safeApprove(address(gateway), amount); - gateway.withdrawERC20{value: msg.value}(owner, token, amount, accountIdOfNonce, subAccountIdOfNonce, nonce, fastWithdrawFeeRate); + gateway.withdrawERC20{value: fee}(owner, token, amount, accountIdOfNonce, subAccountIdOfNonce, nonce, fastWithdrawFeeRate); } + + uint256 leftMsgValue = msg.value - fee; + if (leftMsgValue > 0) { + // solhint-disable-next-line avoid-low-level-calls + (bool success, ) = msg.sender.call{value: leftMsgValue}(""); + require(success, "M2"); + } + emit WithdrawalL1(withdrawHash); } } diff --git a/contracts/dev-contracts/L2GatewayMock.sol b/contracts/dev-contracts/L2GatewayMock.sol index 1119cfa..558da58 100644 --- a/contracts/dev-contracts/L2GatewayMock.sol +++ b/contracts/dev-contracts/L2GatewayMock.sol @@ -6,10 +6,18 @@ import "../interfaces/IL2Gateway.sol"; contract L2GatewayMock is IL2Gateway { + function estimateWithdrawETHFee(address /**_owner**/, uint128 /**_amount**/, uint32 /**_accountIdOfNonce**/, uint8 /**_subAccountIdOfNonce**/, uint32 /**_nonce**/, uint16 /**_fastWithdrawFeeRate**/) external view returns (uint256 nativeFee) { + nativeFee = 0; + } + function withdrawETH(address /**_owner*/, uint128 /**_amount*/, uint32 /**_accountIdOfNonce*/, uint8 /**_subAccountIdOfNonce*/, uint32 /**_nonce*/, uint16 /**_fastWithdrawFeeRate*/) external payable { // do nothing } + function estimateWithdrawERC20Fee(address /**_owner**/, address /**_token**/, uint128 /**_amount**/, uint32 /**_accountIdOfNonce**/, uint8 /**_subAccountIdOfNonce**/, uint32 /**_nonce**/, uint16 /**_fastWithdrawFeeRate**/) external view returns (uint256 nativeFee) { + nativeFee = 0; + } + function withdrawERC20(address /**_owner*/, address _token, uint128 _amount, uint32 /**_accountIdOfNonce*/, uint8 /**_subAccountIdOfNonce*/, uint32 /**_nonce*/, uint16 /**_fastWithdrawFeeRate*/) external payable { // transfer token to self IERC20(_token).transferFrom(msg.sender, address(this), _amount); @@ -30,4 +38,8 @@ contract L2GatewayMock is IL2Gateway { function sendMasterSyncHash(uint32, bytes32) external payable { // do nothing } + + function claimBlockConfirmation(uint32 _blockNumber) external { + // do nothing + } } \ No newline at end of file diff --git a/contracts/gateway/AddressAliasHelper.sol b/contracts/gateway/AddressAliasHelper.sol new file mode 100644 index 0000000..e09b249 --- /dev/null +++ b/contracts/gateway/AddressAliasHelper.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2019-2021, Offchain Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +pragma solidity ^0.8.0; + +library AddressAliasHelper { + uint160 internal constant offset = uint160(0x1111000000000000000000000000000000001111); + + /// @notice Utility function converts the address that submitted a tx + /// to the inbox on L1 to the msg.sender viewed on L2 + /// @param l1Address the address in the L1 that triggered the tx to L2 + /// @return l2Address L2 address as viewed in msg.sender + function applyL1ToL2Alias(address l1Address) internal pure returns (address l2Address) { + unchecked { + l2Address = address(uint160(l1Address) + offset); + } + } + + /// @notice Utility function that converts the msg.sender viewed on L2 to the + /// address that submitted a tx to the inbox on L1 + /// @param l2Address L2 address as viewed in msg.sender + /// @return l1Address the address in the L1 that triggered the tx to L2 + function undoL1ToL2Alias(address l2Address) internal pure returns (address l1Address) { + unchecked { + l1Address = address(uint160(l2Address) - offset); + } + } +} diff --git a/contracts/gateway/BaseGateway.sol b/contracts/gateway/BaseGateway.sol new file mode 100644 index 0000000..895dca8 --- /dev/null +++ b/contracts/gateway/BaseGateway.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.0; + +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; +import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; + +abstract contract BaseGateway is OwnableUpgradeable, UUPSUpgradeable, ReentrancyGuardUpgradeable, PausableUpgradeable { + /// @notice Gateway address on remote chain + address public remoteGateway; + + /** + * @dev This empty reserved space is put in place to allow future versions to add new + * variables without shifting down storage in the inheritance chain. + * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + */ + uint256[49] private __gap; + + event SetRemoteGateway(address remoteGateWay); + + function __BaseGateway_init() internal onlyInitializing { + __BaseGateway_init_unchained(); + } + + function __BaseGateway_init_unchained() internal onlyInitializing { + __Ownable_init(); + __UUPSUpgradeable_init(); + __ReentrancyGuard_init(); + __Pausable_init(); + } + + function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} + + /// @notice Set remote Gateway address + /// @param _remoteGateway remote gateway address + function setRemoteGateway(address _remoteGateway) external onlyOwner { + remoteGateway = _remoteGateway; + emit SetRemoteGateway(_remoteGateway); + } + + /// @dev Pause the contract, can only be called by the owner + function pause() external onlyOwner { + _pause(); + } + + /// @dev Unpause the contract, can only be called by the owner + function unpause() external onlyOwner { + _unpause(); + } +} diff --git a/contracts/gateway/L1BaseGateway.sol b/contracts/gateway/L1BaseGateway.sol new file mode 100644 index 0000000..9f0fae0 --- /dev/null +++ b/contracts/gateway/L1BaseGateway.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.0; + +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import {ZkLinkAcceptor} from "../ZkLinkAcceptor.sol"; +import {IArbitrator} from "../interfaces/IArbitrator.sol"; + +abstract contract L1BaseGateway is ZkLinkAcceptor, OwnableUpgradeable { + /// @notice The arbitrator to confirm block + IArbitrator public arbitrator; + + /** + * @dev This empty reserved space is put in place to allow future versions to add new + * variables without shifting down storage in the inheritance chain. + * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + */ + uint256[49] private __gap; + + /// @dev Modifier to make sure the caller is the known arbitrator. + modifier onlyArbitrator() { + require(msg.sender == address(arbitrator), "Not arbitrator"); + _; + } + + event SetArbitrator(address arbitrator); + + /// @notice Set arbitrator + function setArbitrator(IArbitrator _arbitrator) external onlyOwner { + arbitrator = _arbitrator; + emit SetArbitrator(address(_arbitrator)); + } +} diff --git a/contracts/gateway/L2BaseGateway.sol b/contracts/gateway/L2BaseGateway.sol new file mode 100644 index 0000000..683d875 --- /dev/null +++ b/contracts/gateway/L2BaseGateway.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.0; + +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {IZkLink} from "../interfaces/IZkLink.sol"; + +abstract contract L2BaseGateway is UUPSUpgradeable { + /// @notice The zkLink contract + IZkLink public zkLink; + + /** + * @dev This empty reserved space is put in place to allow future versions to add new + * variables without shifting down storage in the inheritance chain. + * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + */ + uint256[49] private __gap; + + /// @dev Ensure withdraw come from zkLink + modifier onlyZkLink() { + require(msg.sender == address(zkLink), "Not zkLink contract"); + _; + } + + function __L2BaseGateway_init(IZkLink _zkLink) internal onlyInitializing { + __UUPSUpgradeable_init(); + __L2BaseGateway_init_unchained(_zkLink); + } + + function __L2BaseGateway_init_unchained(IZkLink _zkLink) internal onlyInitializing { + zkLink = _zkLink; + } +} diff --git a/contracts/gateway/LineaGateway.sol b/contracts/gateway/linea/LineaGateway.sol similarity index 74% rename from contracts/gateway/LineaGateway.sol rename to contracts/gateway/linea/LineaGateway.sol index d0e827e..aba119e 100644 --- a/contracts/gateway/LineaGateway.sol +++ b/contracts/gateway/linea/LineaGateway.sol @@ -1,20 +1,17 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.0; -import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; -import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; -import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import {IMessageService} from "../interfaces/linea/IMessageService.sol"; -import {IUSDCBridge} from "../interfaces/linea/IUSDCBridge.sol"; -import {ITokenBridge} from "../interfaces/linea/ITokenBridge.sol"; +import {IMessageService} from "../../interfaces/linea/IMessageService.sol"; +import {IUSDCBridge} from "../../interfaces/linea/IUSDCBridge.sol"; +import {ITokenBridge} from "../../interfaces/linea/ITokenBridge.sol"; -import {ILineaGateway} from "../interfaces/ILineaGateway.sol"; +import {ILineaGateway} from "../../interfaces/linea/ILineaGateway.sol"; +import {BaseGateway} from "../BaseGateway.sol"; -abstract contract LineaGateway is OwnableUpgradeable, UUPSUpgradeable, ReentrancyGuardUpgradeable, PausableUpgradeable, ILineaGateway { +abstract contract LineaGateway is BaseGateway, ILineaGateway { using SafeERC20 for IERC20; /// @notice Linea message service on local chain @@ -26,15 +23,12 @@ abstract contract LineaGateway is OwnableUpgradeable, UUPSUpgradeable, Reentranc /// @notice Linea USDC bridge on local chain IUSDCBridge public usdcBridge; - /// @notice Gateway address on remote chain - address public remoteGateway; - /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - uint256[46] private __gap; + uint256[47] private __gap; /// @dev Modifier to make sure the caller is the known message service. modifier onlyMessageService() { @@ -49,22 +43,16 @@ abstract contract LineaGateway is OwnableUpgradeable, UUPSUpgradeable, Reentranc } function __LineaGateway_init(IMessageService _messageService, ITokenBridge _tokenBridge, IUSDCBridge _usdcBridge) internal onlyInitializing { + __BaseGateway_init(); __LineaGateway_init_unchained(_messageService, _tokenBridge, _usdcBridge); } function __LineaGateway_init_unchained(IMessageService _messageService, ITokenBridge _tokenBridge, IUSDCBridge _usdcBridge) internal onlyInitializing { - __Ownable_init(); - __UUPSUpgradeable_init(); - __ReentrancyGuard_init(); - __Pausable_init(); - messageService = _messageService; tokenBridge = _tokenBridge; usdcBridge = _usdcBridge; } - function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} - function claimETH(uint256 _value, bytes calldata _callData, uint256 _nonce) external override nonReentrant whenNotPaused { // no fee on remote chain messageService.claimMessage(remoteGateway, address(this), 0, _value, payable(msg.sender), _callData, _nonce); @@ -83,23 +71,6 @@ abstract contract LineaGateway is OwnableUpgradeable, UUPSUpgradeable, Reentranc messageService.claimMessage(remoteGateway, address(this), 0, 0, payable(msg.sender), _cbCallData, _cbNonce); } - /// @notice Set remote Gateway address - /// @param _remoteGateway remote gateway address - function setRemoteGateway(address _remoteGateway) external onlyOwner { - remoteGateway = _remoteGateway; - emit SetRemoteGateway(_remoteGateway); - } - - /// @dev Pause the contract, can only be called by the owner - function pause() external onlyOwner { - _pause(); - } - - /// @dev Unpause the contract, can only be called by the owner - function unpause() external onlyOwner { - _unpause(); - } - /// @dev Bridge token to remote gateway /// @param _token The token on local chain /// @param _amount The token amount diff --git a/contracts/gateway/LineaL1Gateway.sol b/contracts/gateway/linea/LineaL1Gateway.sol similarity index 73% rename from contracts/gateway/LineaL1Gateway.sol rename to contracts/gateway/linea/LineaL1Gateway.sol index 012de37..59eb4d0 100644 --- a/contracts/gateway/LineaL1Gateway.sol +++ b/contracts/gateway/linea/LineaL1Gateway.sol @@ -4,16 +4,16 @@ pragma solidity ^0.8.0; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import {ILineaL2Gateway} from "../interfaces/ILineaL2Gateway.sol"; -import {ILineaL1Gateway} from "../interfaces/ILineaL1Gateway.sol"; -import {IMessageService} from "../interfaces/linea/IMessageService.sol"; -import {IUSDCBridge} from "../interfaces/linea/IUSDCBridge.sol"; -import {ITokenBridge} from "../interfaces/linea/ITokenBridge.sol"; -import {IArbitrator} from "../interfaces/IArbitrator.sol"; +import {ILineaL2Gateway} from "../../interfaces/linea/ILineaL2Gateway.sol"; +import {ILineaL1Gateway} from "../../interfaces/linea/ILineaL1Gateway.sol"; +import {IMessageService} from "../../interfaces/linea/IMessageService.sol"; +import {IUSDCBridge} from "../../interfaces/linea/IUSDCBridge.sol"; +import {ITokenBridge} from "../../interfaces/linea/ITokenBridge.sol"; +import {IL2Gateway} from "../../interfaces/IL2Gateway.sol"; import {LineaGateway} from "./LineaGateway.sol"; -import "../ZkLinkAcceptor.sol"; +import {L1BaseGateway} from "../L1BaseGateway.sol"; -contract LineaL1Gateway is ZkLinkAcceptor, LineaGateway, ILineaL1Gateway { +contract LineaL1Gateway is L1BaseGateway, LineaGateway, ILineaL1Gateway { using SafeERC20 for IERC20; /// @notice L2 claim message gas fee users should pay for @@ -22,36 +22,34 @@ contract LineaL1Gateway is ZkLinkAcceptor, LineaGateway, ILineaL1Gateway { /// @notice Used to prevent off-chain monitoring events from being lost uint32 public txNonce; - /// @notice The arbitrator to confirm block - IArbitrator public arbitrator; - - /// @dev Modifier to make sure the caller is the known arbitrator. - modifier onlyArbitrator() { - require(msg.sender == address(arbitrator), "Not arbitrator"); - _; - } + event Deposit(uint32 indexed txNonce, address token, uint256 amount, bytes32 zklinkAddress, uint8 subAccountId, bool _mapping); + event ClaimedWithdrawETH(address _receiver, uint256 _amount); + event ClaimedWithdrawERC20(address _receiver, address _token, uint256 _amount); + event SetFee(uint64 fee); + event WithdrawFee(address receiver, uint256 amount); function initialize(IMessageService _messageService, ITokenBridge _tokenBridge, IUSDCBridge _usdcBridge) external initializer { __LineaGateway_init(_messageService, _tokenBridge, _usdcBridge); } - function depositETH(bytes32 _zkLinkAddress, uint8 _subAccountId) external payable override nonReentrant whenNotPaused { + function depositETH(uint256 _amount, bytes32 _zkLinkAddress, uint8 _subAccountId) external payable override nonReentrant whenNotPaused { // ensure amount bridged is not zero - require(msg.value > fee, "Value too low"); - uint256 amount = msg.value - fee; + require(_amount > 0, "Invalid eth amount"); + require(msg.value == _amount + fee, "Invalid msg value"); uint32 _txNonce = txNonce; - bytes memory callData = abi.encodeCall(ILineaL2Gateway.claimETHCallback, (_txNonce, _zkLinkAddress, _subAccountId, amount)); + bytes memory callData = abi.encodeCall(ILineaL2Gateway.claimETHCallback, (_txNonce, _zkLinkAddress, _subAccountId, _amount)); // transfer no fee to Linea - messageService.sendMessage{value: amount}(remoteGateway, 0, callData); + messageService.sendMessage{value: _amount}(remoteGateway, 0, callData); - emit Deposit(_txNonce, ETH_ADDRESS, amount, _zkLinkAddress, _subAccountId, false); + emit Deposit(_txNonce, ETH_ADDRESS, _amount, _zkLinkAddress, _subAccountId, false); txNonce = _txNonce + 1; } function depositERC20(address _token, uint256 _amount, bytes32 _zkLinkAddress, uint8 _subAccountId, bool _mapping) external payable override nonReentrant whenNotPaused { - require(msg.value == fee, "Invalid msg value"); + // ensure amount bridged is not zero require(_amount > 0, "Invalid token amount"); + require(msg.value == fee, "Invalid msg value"); // transfer token from sender to LineaL1Gateway uint256 balanceBefore = IERC20(_token).balanceOf(address(this)); @@ -102,24 +100,18 @@ contract LineaL1Gateway is ZkLinkAcceptor, LineaGateway, ILineaL1Gateway { arbitrator.receiveMasterSyncHash(_blockNumber, _syncHash); } - function estimateConfirmBlockFee(uint32 /**blockNumber**/) external view returns (uint nativeFee) { + function estimateConfirmBlockFee(uint32 /**blockNumber**/) public view returns (uint nativeFee) { nativeFee = messageService.minimumFeeInWei(); } function confirmBlock(uint32 blockNumber) external payable override onlyArbitrator { - uint256 coinbaseFee = messageService.minimumFeeInWei(); + uint256 coinbaseFee = estimateConfirmBlockFee(blockNumber); require(msg.value == coinbaseFee, "Invalid fee"); - bytes memory callData = abi.encodeCall(ILineaL2Gateway.claimBlockConfirmation, (blockNumber)); + bytes memory callData = abi.encodeCall(IL2Gateway.claimBlockConfirmation, (blockNumber)); messageService.sendMessage{value: msg.value}(address(remoteGateway), coinbaseFee, callData); } - /// @notice Set arbitrator - function setArbitrator(IArbitrator _arbitrator) external onlyOwner { - arbitrator = _arbitrator; - emit SetArbitrator(address(_arbitrator)); - } - /// @notice Set deposit fee function setFee(uint64 _fee) external onlyOwner { fee = _fee; diff --git a/contracts/gateway/LineaL2Gateway.sol b/contracts/gateway/linea/LineaL2Gateway.sol similarity index 78% rename from contracts/gateway/LineaL2Gateway.sol rename to contracts/gateway/linea/LineaL2Gateway.sol index ce3a021..c6d9f6b 100644 --- a/contracts/gateway/LineaL2Gateway.sol +++ b/contracts/gateway/linea/LineaL2Gateway.sol @@ -4,30 +4,23 @@ pragma solidity ^0.8.0; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import {ILineaL2Gateway} from "../interfaces/ILineaL2Gateway.sol"; -import {ILineaL1Gateway} from "../interfaces/ILineaL1Gateway.sol"; -import {IMessageService} from "../interfaces/linea/IMessageService.sol"; -import {IUSDCBridge} from "../interfaces/linea/IUSDCBridge.sol"; -import {ITokenBridge} from "../interfaces/linea/ITokenBridge.sol"; -import {IZkLink} from "../interfaces/IZkLink.sol"; +import {ILineaL2Gateway} from "../../interfaces/linea/ILineaL2Gateway.sol"; +import {ILineaL1Gateway} from "../../interfaces/linea/ILineaL1Gateway.sol"; +import {IMessageService} from "../../interfaces/linea/IMessageService.sol"; +import {IUSDCBridge} from "../../interfaces/linea/IUSDCBridge.sol"; +import {ITokenBridge} from "../../interfaces/linea/ITokenBridge.sol"; +import {IZkLink} from "../../interfaces/IZkLink.sol"; import {LineaGateway} from "./LineaGateway.sol"; +import {L2BaseGateway} from "../L2BaseGateway.sol"; -contract LineaL2Gateway is LineaGateway, ILineaL2Gateway { +contract LineaL2Gateway is L2BaseGateway, LineaGateway, ILineaL2Gateway { using SafeERC20 for IERC20; - /// @notice The zkLink contract - IZkLink public zkLink; - - /// @dev Ensure withdraw come from zkLink - modifier onlyZkLink() { - require(msg.sender == address(zkLink), "Not zkLink contract"); - _; - } + event ClaimedDeposit(uint32 indexed _txNonce); function initialize(IZkLink _zkLink, IMessageService _messageService, ITokenBridge _tokenBridge, IUSDCBridge _usdcBridge) external initializer { + __L2BaseGateway_init(_zkLink); __LineaGateway_init(_messageService, _tokenBridge, _usdcBridge); - - zkLink = _zkLink; } function claimETHCallback(uint32 _txNonce, bytes32 _zkLinkAddress, uint8 _subAccountId, uint256 _amount) external payable onlyMessageService onlyRemoteGateway { @@ -52,6 +45,10 @@ contract LineaL2Gateway is LineaGateway, ILineaL2Gateway { zkLink.receiveBlockConfirmation(_blockNumber); } + function estimateWithdrawETHFee(address /**_owner**/, uint128 /**_amount**/, uint32 /**_accountIdOfNonce**/, uint8 /**_subAccountIdOfNonce**/, uint32 /**_nonce**/, uint16 /**_fastWithdrawFeeRate**/) external view returns (uint256 nativeFee) { + nativeFee = messageService.minimumFeeInWei(); + } + function withdrawETH(address _owner, uint128 _amount, uint32 _accountIdOfNonce, uint8 _subAccountIdOfNonce, uint32 _nonce, uint16 _fastWithdrawFeeRate) external payable override onlyZkLink whenNotPaused { uint256 coinbaseFee = messageService.minimumFeeInWei(); require(msg.value == _amount + coinbaseFee, "Invalid fee"); @@ -60,6 +57,10 @@ contract LineaL2Gateway is LineaGateway, ILineaL2Gateway { messageService.sendMessage{value: msg.value}(address(remoteGateway), coinbaseFee, callData); } + function estimateWithdrawERC20Fee(address /**_owner**/, address /**_token**/, uint128 /**_amount**/, uint32 /**_accountIdOfNonce**/, uint8 /**_subAccountIdOfNonce**/, uint32 /**_nonce**/, uint16 /**_fastWithdrawFeeRate**/) external view returns (uint256 nativeFee) { + nativeFee = messageService.minimumFeeInWei() * 2; + } + function withdrawERC20(address _owner, address _token, uint128 _amount, uint32 _accountIdOfNonce, uint8 _subAccountIdOfNonce, uint32 _nonce, uint16 _fastWithdrawFeeRate) external payable override onlyZkLink whenNotPaused { // ensure msg value can satisfy the need to send two bridge messages uint256 coinbaseFee = messageService.minimumFeeInWei(); diff --git a/contracts/gateway/zksync/ZkSyncL1Gateway.sol b/contracts/gateway/zksync/ZkSyncL1Gateway.sol new file mode 100644 index 0000000..9a3a420 --- /dev/null +++ b/contracts/gateway/zksync/ZkSyncL1Gateway.sol @@ -0,0 +1,218 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.0; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + +import {IL2Gateway} from "../../interfaces/IL2Gateway.sol"; +import {IZkSyncL2Gateway} from "../../interfaces/zksync/IZkSyncL2Gateway.sol"; +import {IZkSyncL1Gateway} from "../../interfaces/zksync/IZkSyncL1Gateway.sol"; +import {IZkSync} from "../../interfaces/zksync/IZkSync.sol"; +import {IL1Bridge} from "../../interfaces/zksync/IL1Bridge.sol"; +import {L1BaseGateway} from "../L1BaseGateway.sol"; +import {BaseGateway} from "../BaseGateway.sol"; +import {ZkSyncMessageConfig} from "./ZkSyncMessageConfig.sol"; +import {Bytes} from "../../zksync/Bytes.sol"; + +contract ZkSyncL1Gateway is ZkSyncMessageConfig, L1BaseGateway, BaseGateway, IZkSyncL1Gateway { + using SafeERC20 for IERC20; + + /// @dev The L2 gasPricePerPubdata required to be used in bridges. + uint256 public constant REQUIRED_L2_GAS_PRICE_PER_PUBDATA = 800; + + /// @dev The gas limit of finalizeDeposit on L2ERC20Bridge + uint256 public finalizeDepositL2GasLimit = 1000000; + + /// @dev The gas limit of claimETH on ZkSyncL2Gateway + uint256 public claimETHL2GasLimit = 2000000; + + /// @dev The gas limit of claimERC20 on ZkSyncL2Gateway + uint256 public claimERC20L2GasLimit = 2000000; + + /// @dev The gas limit of claimBlockConfirmation on ZkSyncL2Gateway + uint256 public claimBlockConfirmationL2GasLimit = 500000; + + /// @notice ZkSync message service on local chain + IZkSync public messageService; + + /// @notice ZkSync token bridge on local chain + IL1Bridge public tokenBridge; + + /// @notice Used to prevent off-chain monitoring events from being lost + uint32 public txNonce; + + /// @dev A mapping L2 batch number => message number => flag + /// @dev Used to indicate that zkSync L2 -> L1 message was already processed + mapping(uint256 => mapping(uint256 => bool)) public isMessageFinalized; + + event Deposit(uint32 indexed txNonce, address token, uint256 amount, bytes32 zklinkAddress, uint8 subAccountId, bool _mapping); + event ClaimedWithdrawETH(address _receiver, uint256 _amount); + event ClaimedWithdrawERC20(address _receiver, address _token, uint256 _amount); + + function initialize(IZkSync _messageService, IL1Bridge _tokenBridge) external initializer { + __BaseGateway_init(); + + messageService = _messageService; + tokenBridge = _tokenBridge; + } + + function depositETH(uint256 _amount, bytes32 _zkLinkAddress, uint8 _subAccountId) external payable override nonReentrant whenNotPaused { + // ensure amount bridged is not zero + require(_amount > 0, "Invalid eth amount"); + + uint256 claimDepositFee = messageService.l2TransactionBaseCost(tx.gasprice, claimETHL2GasLimit, REQUIRED_L2_GAS_PRICE_PER_PUBDATA); + uint256 requiredValue = claimDepositFee + _amount; + require(msg.value >= requiredValue, "Value too low"); + uint256 leftMsgValue = msg.value - requiredValue; + + uint32 _txNonce = txNonce; + bytes memory executeData = abi.encodeCall(IZkSyncL2Gateway.claimETH, (_txNonce, _zkLinkAddress, _subAccountId, _amount)); + messageService.requestL2Transaction{value: requiredValue}(remoteGateway, _amount, executeData, claimETHL2GasLimit, REQUIRED_L2_GAS_PRICE_PER_PUBDATA, new bytes[](0), msg.sender); + + if (leftMsgValue > 0) { + // solhint-disable-next-line avoid-low-level-calls + (bool success, ) = msg.sender.call{value: leftMsgValue}(""); + require(success, "Refund failed"); + } + + emit Deposit(_txNonce, ETH_ADDRESS, _amount, _zkLinkAddress, _subAccountId, false); + txNonce = _txNonce + 1; + } + + function depositERC20(address _token, uint256 _amount, bytes32 _zkLinkAddress, uint8 _subAccountId, bool _mapping) external payable override nonReentrant whenNotPaused { + // ensure amount bridged is not zero + require(_amount > 0, "Invalid token amount"); + + // transfer token from sender to ZkSyncL1Gateway + IERC20(_token).safeTransferFrom(msg.sender, address(this), _amount); + + // approve bridge + IERC20(_token).safeApprove(address(tokenBridge), _amount); + // bridge token to remoteGateway + uint256 leftMsgValue = msg.value; + uint256 bridgeTokenFee = messageService.l2TransactionBaseCost(tx.gasprice, finalizeDepositL2GasLimit, REQUIRED_L2_GAS_PRICE_PER_PUBDATA); + require(leftMsgValue >= bridgeTokenFee, "Insufficient fee for bridge token"); + tokenBridge.deposit{value: bridgeTokenFee}(remoteGateway, _token, _amount, finalizeDepositL2GasLimit, REQUIRED_L2_GAS_PRICE_PER_PUBDATA, msg.sender); + leftMsgValue -= bridgeTokenFee; + + // send depositERC20 command to ZkSyncL2Gateway(the second message send to ZkSync) + uint256 claimDepositFee = messageService.l2TransactionBaseCost(tx.gasprice, claimERC20L2GasLimit, REQUIRED_L2_GAS_PRICE_PER_PUBDATA); + require(leftMsgValue >= claimDepositFee, "Insufficient fee for claim token"); + uint32 _txNonce = txNonce; + bytes memory executeData = abi.encodeCall(IZkSyncL2Gateway.claimERC20, (_txNonce, _token, _amount, _zkLinkAddress, _subAccountId, _mapping)); + messageService.requestL2Transaction{value: claimDepositFee}(remoteGateway, 0, executeData, claimERC20L2GasLimit, REQUIRED_L2_GAS_PRICE_PER_PUBDATA, new bytes[](0), msg.sender); + leftMsgValue -= claimDepositFee; + + if (leftMsgValue > 0) { + // solhint-disable-next-line avoid-low-level-calls + (bool success, ) = msg.sender.call{value: leftMsgValue}(""); + require(success, "Refund failed"); + } + + emit Deposit(_txNonce, _token, _amount, _zkLinkAddress, _subAccountId, _mapping); + txNonce = _txNonce + 1; + } + + function estimateConfirmBlockFee(uint32 /**blockNumber**/) public view returns (uint nativeFee) { + nativeFee = messageService.l2TransactionBaseCost(tx.gasprice, claimBlockConfirmationL2GasLimit, REQUIRED_L2_GAS_PRICE_PER_PUBDATA); + } + + function confirmBlock(uint32 blockNumber) external payable override onlyArbitrator { + require(msg.value == estimateConfirmBlockFee(blockNumber), "Invalid fee"); + + bytes memory executeData = abi.encodeCall(IL2Gateway.claimBlockConfirmation, (blockNumber)); + messageService.requestL2Transaction{value: msg.value}(remoteGateway, 0, executeData, claimBlockConfirmationL2GasLimit, REQUIRED_L2_GAS_PRICE_PER_PUBDATA, new bytes[](0), tx.origin); + } + + function finalizeMessage(uint256 _l2BatchNumber, uint256 _l2MessageIndex, uint16 _l2TxNumberInBatch, bytes memory _message, bytes32[] calldata _merkleProof) external override { + require(!isMessageFinalized[_l2BatchNumber][_l2MessageIndex], "Message was finalized"); + + IZkSync.L2Message memory l2ToL1Message = IZkSync.L2Message({ + txNumberInBatch: _l2TxNumberInBatch, + sender: remoteGateway, + data: _message + }); + + bool success = messageService.proveL2MessageInclusion(_l2BatchNumber, _l2MessageIndex, l2ToL1Message, _merkleProof); + require(success, "Invalid message"); + + (uint256 offset, uint32 functionSignature) = Bytes.readUInt32(_message, 0); + require(bytes4(functionSignature) == this.finalizeMessage.selector, "Invalid function selector"); + + uint8 messageType; + (offset, messageType) = Bytes.readUint8(_message, offset); + if (messageType == MESSAGE_WITHDRAW_ETH) { + claimETH(_message, offset); + } else if (messageType == MESSAGE_WITHDRAW_ERC20) { + claimERC20(_message, offset); + } else if (messageType == MESSAGE_SEND_SLAVER_SYNC_HASH) { + claimSlaverSyncHash(_message, offset); + } else if (messageType == MESSAGE_SEND_MASTER_SYNC_HASH) { + claimMasterSyncHash(_message, offset); + } else { + revert("Invalid message type"); + } + + isMessageFinalized[_l2BatchNumber][_l2MessageIndex] = true; + } + + function claimETH(bytes memory _message, uint256 offset) internal { + address _owner; + uint128 _amount; + uint32 _accountIdOfNonce; + uint8 _subAccountIdOfNonce; + uint32 _nonce; + uint16 _fastWithdrawFeeRate; + (offset, _owner) = Bytes.readAddress(_message, offset); + (offset, _amount) = Bytes.readUInt128(_message, offset); + (offset, _accountIdOfNonce) = Bytes.readUInt32(_message, offset); + (offset, _subAccountIdOfNonce) = Bytes.readUint8(_message, offset); + (offset, _nonce) = Bytes.readUInt32(_message, offset); + (offset, _fastWithdrawFeeRate) = Bytes.readUInt16(_message, offset); + + // send eth to receiver + address receiver = updateAcceptReceiver(_owner, ETH_ADDRESS, _amount, _accountIdOfNonce, _subAccountIdOfNonce, _nonce, _fastWithdrawFeeRate); + // solhint-disable-next-line avoid-low-level-calls + (bool success, ) = receiver.call{value: _amount}(""); + require(success, "Claim eth failed"); + emit ClaimedWithdrawETH(receiver, _amount); + } + + function claimERC20(bytes memory _message, uint256 offset) internal { + address _owner; + address _l1Token; + uint128 _amount; + uint32 _accountIdOfNonce; + uint8 _subAccountIdOfNonce; + uint32 _nonce; + uint16 _fastWithdrawFeeRate; + (offset, _owner) = Bytes.readAddress(_message, offset); + (offset, _l1Token) = Bytes.readAddress(_message, offset); + (offset, _amount) = Bytes.readUInt128(_message, offset); + (offset, _accountIdOfNonce) = Bytes.readUInt32(_message, offset); + (offset, _subAccountIdOfNonce) = Bytes.readUint8(_message, offset); + (offset, _nonce) = Bytes.readUInt32(_message, offset); + (offset, _fastWithdrawFeeRate) = Bytes.readUInt16(_message, offset); + + // send token to receiver + address receiver = updateAcceptReceiver(_owner, _l1Token, _amount, _accountIdOfNonce, _subAccountIdOfNonce, _nonce, _fastWithdrawFeeRate); + IERC20(_l1Token).safeTransfer(receiver, _amount); + emit ClaimedWithdrawERC20(receiver, _l1Token, _amount); + } + + function claimSlaverSyncHash(bytes memory _message, uint256 offset) internal { + bytes32 _syncHash; + (offset, _syncHash) = Bytes.readBytes32(_message, offset); + + arbitrator.receiveSlaverSyncHash(_syncHash); + } + + function claimMasterSyncHash(bytes memory _message, uint256 offset) internal{ + uint32 _blockNumber; + bytes32 _syncHash; + (offset, _blockNumber) = Bytes.readUInt32(_message, offset); + (offset, _syncHash) = Bytes.readBytes32(_message, offset); + + arbitrator.receiveMasterSyncHash(_blockNumber, _syncHash); + } +} diff --git a/contracts/gateway/zksync/ZkSyncL2Gateway.sol b/contracts/gateway/zksync/ZkSyncL2Gateway.sol new file mode 100644 index 0000000..c192ad1 --- /dev/null +++ b/contracts/gateway/zksync/ZkSyncL2Gateway.sol @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.0; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + +import {IZkLink} from "../../interfaces/IZkLink.sol"; +import {IL2Bridge} from "../../interfaces/zksync/IL2Bridge.sol"; +import {IL2Messenger} from "../../interfaces/zksync/IL2Messenger.sol"; +import {IL2ETHToken} from "../../interfaces/zksync/IL2ETHToken.sol"; +import {IZkSyncL2Gateway} from "../../interfaces/zksync/IZkSyncL2Gateway.sol"; +import {IZkSyncL1Gateway} from "../../interfaces/zksync/IZkSyncL1Gateway.sol"; +import {BaseGateway} from "../BaseGateway.sol"; +import {L2BaseGateway} from "../L2BaseGateway.sol"; +import {AddressAliasHelper} from "../AddressAliasHelper.sol"; +import {ZkSyncMessageConfig} from "./ZkSyncMessageConfig.sol"; + +contract ZkSyncL2Gateway is ZkSyncMessageConfig, L2BaseGateway, BaseGateway, IZkSyncL2Gateway { + using SafeERC20 for IERC20; + + uint160 internal constant SYSTEM_CONTRACTS_OFFSET = 0x8000; // 2^15 + + /// @notice ZkSync system message service on local chain + IL2Messenger public constant L2_MESSENGER = IL2Messenger(address(SYSTEM_CONTRACTS_OFFSET + 0x08)); + + /// @notice ZkSync eth bridge service on local chain + IL2ETHToken public constant L2_ETH_ADDRESS = IL2ETHToken(address(SYSTEM_CONTRACTS_OFFSET + 0x0a)); + + /// @notice ZkSync token bridge service on local chain + IL2Bridge public tokenBridge; + + event ClaimedDeposit(uint32 indexed _txNonce); + + /// @dev Modifier to make sure the original sender is gateway on remote chain. + modifier onlyRemoteGateway() { + require(AddressAliasHelper.undoL1ToL2Alias(msg.sender) == remoteGateway, "Not remote gateway"); + _; + } + + function initialize(IZkLink _zkLink, IL2Bridge _tokenBridge) external initializer { + __L2BaseGateway_init(_zkLink); + __BaseGateway_init(); + + tokenBridge = _tokenBridge; + } + + function claimETH(uint32 _txNonce, bytes32 _zkLinkAddress, uint8 _subAccountId, uint256 _amount) external payable override onlyRemoteGateway { + require(msg.value == _amount, "Claim eth value not match"); + + zkLink.depositETH{value: _amount}(_zkLinkAddress, _subAccountId); + emit ClaimedDeposit(_txNonce); + } + + function claimERC20(uint32 _txNonce, address _l1Token, uint256 _amount, bytes32 _zkLinkAddress, uint8 _subAccountId, bool _mapping) external override onlyRemoteGateway { + // find target token on zkSync + address targetToken = tokenBridge.l2TokenAddress(_l1Token); + // approve token to zkLink + IERC20(targetToken).safeApprove(address(zkLink), _amount); + // deposit erc20 to zkLink + uint104 amount = uint104(_amount); + zkLink.depositERC20(IERC20(targetToken), amount, _zkLinkAddress, _subAccountId, _mapping); + emit ClaimedDeposit(_txNonce); + } + + function claimBlockConfirmation(uint32 _blockNumber) external override onlyRemoteGateway{ + zkLink.receiveBlockConfirmation(_blockNumber); + } + + function estimateWithdrawETHFee(address /**_owner**/, uint128 /**_amount**/, uint32 /**_accountIdOfNonce**/, uint8 /**_subAccountIdOfNonce**/, uint32 /**_nonce**/, uint16 /**_fastWithdrawFeeRate**/) external pure returns (uint256 nativeFee) { + nativeFee = 0; + } + + function withdrawETH(address _owner, uint128 _amount, uint32 _accountIdOfNonce, uint8 _subAccountIdOfNonce, uint32 _nonce, uint16 _fastWithdrawFeeRate) external payable override onlyZkLink whenNotPaused { + require(msg.value == _amount, "Invalid value"); + + // send eth to ZkSyncL1Gateway(the first message send to L1) + L2_ETH_ADDRESS.withdraw{value: _amount}(remoteGateway); + + // send withdraw eth command to ZkSyncL1Gateway(the second message send to L1) + bytes memory callData = abi.encodePacked(IZkSyncL1Gateway.finalizeMessage.selector, MESSAGE_WITHDRAW_ETH, _owner, _amount, _accountIdOfNonce, _subAccountIdOfNonce, _nonce, _fastWithdrawFeeRate); + L2_MESSENGER.sendToL1(callData); + } + + function estimateWithdrawERC20Fee(address /**_owner**/, address /**_token**/, uint128 /**_amount**/, uint32 /**_accountIdOfNonce**/, uint8 /**_subAccountIdOfNonce**/, uint32 /**_nonce**/, uint16 /**_fastWithdrawFeeRate**/) external pure returns (uint256 nativeFee) { + nativeFee = 0; + } + + function withdrawERC20(address _owner, address _token, uint128 _amount, uint32 _accountIdOfNonce, uint8 _subAccountIdOfNonce, uint32 _nonce, uint16 _fastWithdrawFeeRate) external payable override onlyZkLink whenNotPaused { + // transfer token from sender to ZkSyncL2Gateway + IERC20(_token).safeTransferFrom(msg.sender, address(this), _amount); + + // bridge token to remoteGateway(the first message send to L1) + tokenBridge.withdraw(remoteGateway, _token, _amount); + + // send withdrawERC20 command to ZkSyncL1Gateway(the second message send to L1) + address l1Token = tokenBridge.l1TokenAddress(_token); + bytes memory callData = abi.encodePacked(IZkSyncL1Gateway.finalizeMessage.selector, MESSAGE_WITHDRAW_ERC20, _owner, l1Token, _amount, _accountIdOfNonce, _subAccountIdOfNonce, _nonce, _fastWithdrawFeeRate); + L2_MESSENGER.sendToL1(callData); + } + + function estimateSendSlaverSyncHashFee(bytes32 /**syncHash**/) external pure returns (uint nativeFee) { + nativeFee = 0; + } + + function sendSlaverSyncHash(bytes32 syncHash) external payable override onlyZkLink whenNotPaused { + bytes memory callData = abi.encodePacked(IZkSyncL1Gateway.finalizeMessage.selector, MESSAGE_SEND_SLAVER_SYNC_HASH, syncHash); + L2_MESSENGER.sendToL1(callData); + } + + function estimateSendMasterSyncHashFee(uint32 /**blockNumber**/, bytes32 /**syncHash**/) external pure returns (uint nativeFee) { + nativeFee = 0; + } + + function sendMasterSyncHash(uint32 blockNumber, bytes32 syncHash) external payable override onlyZkLink whenNotPaused { + bytes memory callData = abi.encodePacked(IZkSyncL1Gateway.finalizeMessage.selector, MESSAGE_SEND_MASTER_SYNC_HASH, blockNumber, syncHash); + L2_MESSENGER.sendToL1(callData); + } +} diff --git a/contracts/gateway/zksync/ZkSyncMessageConfig.sol b/contracts/gateway/zksync/ZkSyncMessageConfig.sol new file mode 100644 index 0000000..b7bcce5 --- /dev/null +++ b/contracts/gateway/zksync/ZkSyncMessageConfig.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.0; + +abstract contract ZkSyncMessageConfig { + uint8 internal constant MESSAGE_WITHDRAW_ETH = 1; + uint8 internal constant MESSAGE_WITHDRAW_ERC20 = 2; + uint8 internal constant MESSAGE_SEND_SLAVER_SYNC_HASH = 3; + uint8 internal constant MESSAGE_SEND_MASTER_SYNC_HASH = 4; +} diff --git a/contracts/interfaces/IL2Gateway.sol b/contracts/interfaces/IL2Gateway.sol index d40fd1c..32a8284 100644 --- a/contracts/interfaces/IL2Gateway.sol +++ b/contracts/interfaces/IL2Gateway.sol @@ -2,6 +2,9 @@ pragma solidity ^0.8.0; interface IL2Gateway { + /// @notice Estimate the fee to call send withdraw message + function estimateWithdrawETHFee(address _owner, uint128 _amount, uint32 _accountIdOfNonce, uint8 _subAccountIdOfNonce, uint32 _nonce, uint16 _fastWithdrawFeeRate) external view returns (uint256 nativeFee); + /// @notice Withdraw ETH to L1 for owner /// @param _owner The address received eth on L1 /// @param _amount The eth amount received @@ -11,6 +14,9 @@ interface IL2Gateway { /// @param _fastWithdrawFeeRate Fast withdraw fee rate taken by acceptor function withdrawETH(address _owner, uint128 _amount, uint32 _accountIdOfNonce, uint8 _subAccountIdOfNonce, uint32 _nonce, uint16 _fastWithdrawFeeRate) external payable; + /// @notice Estimate the fee to call send withdraw message + function estimateWithdrawERC20Fee(address _owner, address _token, uint128 _amount, uint32 _accountIdOfNonce, uint8 _subAccountIdOfNonce, uint32 _nonce, uint16 _fastWithdrawFeeRate) external view returns (uint256 nativeFee); + /// @notice Withdraw ERC20 token to L1 for owner /// @dev gateway need to pay fee to message service /// @param _owner The address received token on L1 @@ -39,4 +45,8 @@ interface IL2Gateway { /// @param blockNumber the block number /// @param syncHash the sync hash function sendMasterSyncHash(uint32 blockNumber, bytes32 syncHash) external payable; + + /// @notice Claim block confirmation from message service + /// @param _blockNumber The confirmed block number + function claimBlockConfirmation(uint32 _blockNumber) external; } diff --git a/contracts/interfaces/ILineaGateway.sol b/contracts/interfaces/linea/ILineaGateway.sol similarity index 95% rename from contracts/interfaces/ILineaGateway.sol rename to contracts/interfaces/linea/ILineaGateway.sol index 6ba36b0..8ac38bf 100644 --- a/contracts/interfaces/ILineaGateway.sol +++ b/contracts/interfaces/linea/ILineaGateway.sol @@ -2,8 +2,6 @@ pragma solidity ^0.8.0; interface ILineaGateway { - event SetRemoteGateway(address remoteGateWay); - /// @notice Claim ETH message /// @param _value The value to be transferred to the gateway on local chain from remote chain /// @param _callData The `claimETHCallback` encoded call data diff --git a/contracts/interfaces/ILineaL1Gateway.sol b/contracts/interfaces/linea/ILineaL1Gateway.sol similarity index 82% rename from contracts/interfaces/ILineaL1Gateway.sol rename to contracts/interfaces/linea/ILineaL1Gateway.sol index 4188c04..770cd3a 100644 --- a/contracts/interfaces/ILineaL1Gateway.sol +++ b/contracts/interfaces/linea/ILineaL1Gateway.sol @@ -2,20 +2,14 @@ pragma solidity ^0.8.0; import {ILineaGateway} from "./ILineaGateway.sol"; -import {IL1Gateway} from "./IL1Gateway.sol"; +import {IL1Gateway} from "../IL1Gateway.sol"; interface ILineaL1Gateway is IL1Gateway, ILineaGateway { - event Deposit(uint32 indexed txNonce, address token, uint256 amount, bytes32 zklinkAddress, uint8 subAccountId, bool _mapping); - event ClaimedWithdrawETH(address _receiver, uint256 _amount); - event ClaimedWithdrawERC20(address _receiver, address _token, uint256 _amount); - event SetArbitrator(address arbitrator); - event SetFee(uint64 fee); - event WithdrawFee(address receiver, uint256 amount); - /// @notice Deposit ETH to zkLink on Linea + /// @param _amount The amount to deposit /// @param _zkLinkAddress The zkLink address deposited to /// @param _subAccountId The sub account id - function depositETH(bytes32 _zkLinkAddress, uint8 _subAccountId) external payable; + function depositETH(uint256 _amount, bytes32 _zkLinkAddress, uint8 _subAccountId) external payable; /// @notice Deposit ERC20 to zkLink on Linea /// @param _token The token on L1 diff --git a/contracts/interfaces/ILineaL2Gateway.sol b/contracts/interfaces/linea/ILineaL2Gateway.sol similarity index 80% rename from contracts/interfaces/ILineaL2Gateway.sol rename to contracts/interfaces/linea/ILineaL2Gateway.sol index eab998f..05ff9a8 100644 --- a/contracts/interfaces/ILineaL2Gateway.sol +++ b/contracts/interfaces/linea/ILineaL2Gateway.sol @@ -1,12 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.0; -import {IL2Gateway} from "./IL2Gateway.sol"; +import {IL2Gateway} from "../IL2Gateway.sol"; import {ILineaGateway} from "./ILineaGateway.sol"; interface ILineaL2Gateway is ILineaGateway, IL2Gateway { - event ClaimedDeposit(uint32 indexed _txNonce); - /// @notice Claim ETH callback from message service /// @param _txNonce The deposit sequence of L1 gateway /// @param _zkLinkAddress The zkLink address deposited to @@ -23,8 +21,4 @@ interface ILineaL2Gateway is ILineaGateway, IL2Gateway { /// @param _subAccountId The sub account id /// @param _mapping If receive a mapping token on zkLink function claimERC20Callback(uint32 _txNonce, bool _isUSDC, address _nativeToken, uint256 _amount, bytes32 _zkLinkAddress, uint8 _subAccountId, bool _mapping) external; - - /// @notice Claim block confirmation from message service - /// @param _blockNumber The confirmed block number - function claimBlockConfirmation(uint32 _blockNumber) external; } diff --git a/contracts/interfaces/zksync/IL1Bridge.sol b/contracts/interfaces/zksync/IL1Bridge.sol new file mode 100644 index 0000000..3a44eed --- /dev/null +++ b/contracts/interfaces/zksync/IL1Bridge.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +pragma solidity ^0.8.0; + +/// @author Matter Labs +interface IL1Bridge { + function deposit( + address _l2Receiver, + address _l1Token, + uint256 _amount, + uint256 _l2TxGasLimit, + uint256 _l2TxGasPerPubdataByte, + address _refundRecipient + ) external payable returns (bytes32 txHash); +} diff --git a/contracts/interfaces/zksync/IL2Bridge.sol b/contracts/interfaces/zksync/IL2Bridge.sol new file mode 100644 index 0000000..f947795 --- /dev/null +++ b/contracts/interfaces/zksync/IL2Bridge.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +pragma solidity ^0.8.0; + +/// @author Matter Labs +interface IL2Bridge { + function finalizeDeposit( + address _l1Sender, + address _l2Receiver, + address _l1Token, + uint256 _amount, + bytes calldata _data + ) external payable; + + function withdraw(address _l1Receiver, address _l2Token, uint256 _amount) external; + + function l1TokenAddress(address _l2Token) external view returns (address); + + function l2TokenAddress(address _l1Token) external view returns (address); + + function l1Bridge() external view returns (address); +} diff --git a/contracts/interfaces/zksync/IL2ETHToken.sol b/contracts/interfaces/zksync/IL2ETHToken.sol new file mode 100644 index 0000000..88b082a --- /dev/null +++ b/contracts/interfaces/zksync/IL2ETHToken.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +pragma solidity ^0.8.0; + +/// @author Matter Labs +interface IL2ETHToken { + /// @notice Initiate the ETH withdrawal, funds will be available to claim on L1 `finalizeEthWithdrawal` method. + /// @param _l1Receiver The address on L1 to receive the funds. + function withdraw(address _l1Receiver) external payable; +} diff --git a/contracts/interfaces/zksync/IL2Messenger.sol b/contracts/interfaces/zksync/IL2Messenger.sol new file mode 100644 index 0000000..e4f773e --- /dev/null +++ b/contracts/interfaces/zksync/IL2Messenger.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +pragma solidity ^0.8.0; + +/// @author Matter Labs +interface IL2Messenger { + function sendToL1(bytes memory _message) external returns (bytes32); +} diff --git a/contracts/interfaces/zksync/IZkSync.sol b/contracts/interfaces/zksync/IZkSync.sol new file mode 100644 index 0000000..59184a3 --- /dev/null +++ b/contracts/interfaces/zksync/IZkSync.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +pragma solidity ^0.8.0; + +/// @author Matter Labs +interface IZkSync { + /// @dev An arbitrary length message passed from L2 + /// @notice Under the hood it is `L2Log` sent from the special system L2 contract + /// @param txNumberInBatch The L2 transaction number in the batch, in which the message was sent + /// @param sender The address of the L2 account from which the message was passed + /// @param data An arbitrary length message + struct L2Message { + uint16 txNumberInBatch; + address sender; + bytes data; + } + + function requestL2Transaction( + address _contractL2, + uint256 _l2Value, + bytes calldata _calldata, + uint256 _l2GasLimit, + uint256 _l2GasPerPubdataByteLimit, + bytes[] calldata _factoryDeps, + address _refundRecipient + ) external payable returns (bytes32 canonicalTxHash); + + function l2TransactionBaseCost( + uint256 _gasPrice, + uint256 _l2GasLimit, + uint256 _l2GasPerPubdataByteLimit + ) external view returns (uint256); + + function proveL2MessageInclusion( + uint256 _l2BatchNumber, + uint256 _index, + L2Message calldata _message, + bytes32[] calldata _proof + ) external view returns (bool); +} diff --git a/contracts/interfaces/zksync/IZkSyncL1Gateway.sol b/contracts/interfaces/zksync/IZkSyncL1Gateway.sol new file mode 100644 index 0000000..b070854 --- /dev/null +++ b/contracts/interfaces/zksync/IZkSyncL1Gateway.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.0; + +import {IL1Gateway} from "../IL1Gateway.sol"; + +interface IZkSyncL1Gateway is IL1Gateway { + /// @notice Deposit ETH to zkLink on zkSync + /// @param _amount The amount to deposit + /// @param _zkLinkAddress The zkLink address deposited to + /// @param _subAccountId The sub account id + function depositETH(uint256 _amount, bytes32 _zkLinkAddress, uint8 _subAccountId) external payable; + + /// @notice Deposit ERC20 to zkLink on zkSync + /// @param _token The token on L1 + /// @param _amount The amount to deposit + /// @param _zkLinkAddress The zkLink address deposited to + /// @param _subAccountId The sub account id + /// @param _mapping If receive a mapping token on zkLink + function depositERC20(address _token, uint256 _amount, bytes32 _zkLinkAddress, uint8 _subAccountId, bool _mapping) external payable; + + /// @notice Finalize the message sent from ZkSyncL2Gateway + /// @param _l2BatchNumber The L2 batch number where the message was processed + /// @param _l2MessageIndex The position in the L2 logs Merkle tree of the l2Log that was sent with the message + /// @param _l2TxNumberInBatch The L2 transaction number in the batch, in which the log was sent + /// @param _message The L2 withdraw data, stored in an L2 -> L1 message + /// @param _merkleProof The Merkle proof of the inclusion L2 -> L1 message about withdrawal initialization + function finalizeMessage(uint256 _l2BatchNumber, uint256 _l2MessageIndex, uint16 _l2TxNumberInBatch, bytes calldata _message, bytes32[] calldata _merkleProof) external; +} diff --git a/contracts/interfaces/zksync/IZkSyncL2Gateway.sol b/contracts/interfaces/zksync/IZkSyncL2Gateway.sol new file mode 100644 index 0000000..cbd841b --- /dev/null +++ b/contracts/interfaces/zksync/IZkSyncL2Gateway.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.0; + +import {IL2Gateway} from "../IL2Gateway.sol"; + +interface IZkSyncL2Gateway is IL2Gateway { + /// @notice Claim ETH + /// @param _txNonce The deposit sequence of L1 gateway + /// @param _zkLinkAddress The zkLink address deposited to + /// @param _subAccountId The sub account id + /// @param _amount The eth amount to deposit + function claimETH(uint32 _txNonce, bytes32 _zkLinkAddress, uint8 _subAccountId, uint256 _amount) external payable; + + /// @notice Claim ERC20 + /// @param _txNonce The deposit sequence of L1 gateway + /// @param _l1Token The token on ethereum + /// @param _amount The amount to deposit + /// @param _zkLinkAddress The zkLink address deposited to + /// @param _subAccountId The sub account id + /// @param _mapping If receive a mapping token on zkLink + function claimERC20(uint32 _txNonce, address _l1Token, uint256 _amount, bytes32 _zkLinkAddress, uint8 _subAccountId, bool _mapping) external; +}