Skip to content
Draft
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
396 changes: 223 additions & 173 deletions AllContractsHashes.json

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions l1-contracts/contracts/common/Config.sol
Original file line number Diff line number Diff line change
Expand Up @@ -227,3 +227,9 @@ enum L2DACommitmentScheme {

/// @dev The L2 data availability commitment scheme that permanent rollups are expected to use.
L2DACommitmentScheme constant ROLLUP_L2_DA_COMMITMENT_SCHEME = L2DACommitmentScheme.BLOBS_AND_PUBDATA_KECCAK256;

/// @dev Minimal allowed code size limit, equals to EVM EIP-170 value.
uint32 constant MIN_CODE_SIZE_LIMIT = 0x6000;

/// @dev Maximal allowed code size limit, 1 MB.
uint32 constant MAX_CODE_SIZE_LIMIT = 0x100000;
6 changes: 6 additions & 0 deletions l1-contracts/contracts/common/L1ContractErrors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ error IncorrectBatchBounds(
);
// 0xc1b4bc7b
error IncorrectBatchChainId(uint256, uint256);
// 0x55424854
error IncorrectBatchCodeSizeLimit(uint32, uint32);
// 0xdd381a4c
error IncorrectBridgeHubAddress(address bridgehub);
// 0x1929b7de
Expand Down Expand Up @@ -394,6 +396,10 @@ error ZeroGasPriceL1TxZKsyncOS();
error ZKChainLimitReached();
// 0x646ac57e
error ZKsyncOSNotForceDeployForExistingContract(address);
// 0xbcfe4b69
error CodeSizeLimitTooLow(uint32 codeSizeLimit, uint32 minCodeSizeLimit);
// 0x47386ac7
error CodeSizeLimitTooBig(uint32 codeSizeLimit, uint32 maxCodeSizeLimit);

enum SharedBridgeKey {
PostUpgradeFirstBatch,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ uint256 constant STATE_DIFF_COMPRESSION_VERSION_NUMBER = 1;
* - The contract on L1 accepts all sent messages and if the message came from this system contract
* it requires the preimage of `value` to be provided.
*/
interface IL2ToL1Messenger {
interface IL2ToL1MessengerEra {
// Possibly in the future we will be able to track the messages sent to L1 with
// some hooks in the VM. For now, it is much easier to track them with L2 events.
event L1MessageSent(address indexed _sender, bytes32 indexed _hash, bytes _message);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// We use a floating point pragma here so it can be used within other projects that interact with the ZKsync ecosystem without using our exact pragma version.
pragma solidity ^0.8.20;

/**
* @author Matter Labs
* @custom:security-contact security@matterlabs.dev
* @notice Smart contract for sending arbitrary length messages to L1
* @dev by default ZkSync can send fixed-length messages on L1.
* A fixed length message has 4 parameters `senderAddress`, `isService`, `key`, `value`,
* the first one is taken from the context, the other three are chosen by the sender.
* @dev To send a variable-length message we use this trick:
* - This system contract accepts an arbitrary length message and sends a fixed length message with
* parameters `senderAddress == this`, `isService == true`, `key == msg.sender`, `value == keccak256(message)`.
* - The contract on L1 accepts all sent messages and if the message came from this system contract
* it requires that the preimage of `value` be provided.
*/
interface IL2ToL1MessengerZKSyncOS {
/// @notice L2 event emitted to track L1 messages.
event L1MessageSent(address indexed _sender, bytes32 indexed _hash, bytes _message);

/// @notice Sends an arbitrary length message to L1.
/// @param _message The variable length message to be sent to L1.
/// @return Returns the keccak256 hashed value of the message.
function sendToL1(bytes calldata _message) external returns (bytes32);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// We use a floating point pragma here so it can be used within other projects that interact with the ZKsync ecosystem without using our exact pragma version.
pragma solidity ^0.8.21;

import {IL2ToL1Messenger} from "./IL2ToL1Messenger.sol";
import {IL2ToL1MessengerEra} from "./IL2ToL1MessengerEra.sol";
import {IL2InteropRootStorage} from "../interfaces/IL2InteropRootStorage.sol";
import {IMessageVerification} from "../../state-transition/chain-interfaces/IMessageVerification.sol";

Expand Down Expand Up @@ -33,7 +33,7 @@ address constant L2_FORCE_DEPLOYER_ADDR = address(SYSTEM_CONTRACTS_OFFSET + 0x07
/// @dev The address of the L2ToL1Messenger system contract
address constant L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR = address(SYSTEM_CONTRACTS_OFFSET + 0x08);
/// @dev The address of the special smart contract that can send arbitrary length message as an L2 log
IL2ToL1Messenger constant L2_TO_L1_MESSENGER_SYSTEM_CONTRACT = IL2ToL1Messenger(
IL2ToL1MessengerEra constant L2_TO_L1_MESSENGER_SYSTEM_CONTRACT = IL2ToL1MessengerEra(
L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR
);

Expand Down Expand Up @@ -99,3 +99,12 @@ address constant L2_CHAIN_ASSET_HANDLER_ADDR = address(USER_CONTRACTS_OFFSET + 0
address constant L2_NTV_BEACON_DEPLOYER_ADDR = address(USER_CONTRACTS_OFFSET + 0x0b);

address constant L2_SYSTEM_CONTRACT_PROXY_ADMIN_ADDR = address(USER_CONTRACTS_OFFSET + 0x0c);

/// @dev the offset for the system hooks for ZKsync OS
uint160 constant SYSTEM_HOOKS_OFFSET = 0x7000;

/// @dev The address of the L2ToL1Messenger system hook
address constant L1_MESSENGER_HOOK = address(SYSTEM_HOOKS_OFFSET + 0x01);

/// @dev The address of the system hook responsible for setting bytecode on address. Can only be called from L2_COMPLEX_UPGRADER address
address constant SET_BYTECODE_ON_ADDRESS_HOOK = address(SYSTEM_HOOKS_OFFSET + 0x02);
14 changes: 14 additions & 0 deletions l1-contracts/contracts/l2-system/zksync-os/Burner.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.28;

/**
* @author Matter Labs
* @custom:security-contact security@matterlabs.dev
* @notice Helper burner contract used for base token withdrawals.
*/
contract Burner {
constructor() payable {
selfdestruct(payable(address(this)));
}
}
27 changes: 27 additions & 0 deletions l1-contracts/contracts/l2-system/zksync-os/L1MessageGasLib.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

library L1MessageGasLib {
uint256 internal constant SHA3 = 30;
uint256 internal constant SHA3WORD = 6;
uint256 internal constant LOG = 375;
uint256 internal constant LOGDATA = 8;
uint256 internal constant L2_TO_L1_LOG_SERIALIZE_SIZE = 88;

function ceilDiv(uint256 x, uint256 y) internal pure returns (uint256) {
return (x + y - 1) / y;
}

/// @dev Exact Solidity equivalent of `keccak256_ergs_cost(len) / ERGS_PER_GAS`
function gasKeccak(uint256 len) internal pure returns (uint256) {
uint256 words = ceilDiv(len, 32);
return SHA3 + SHA3WORD * words;
}

/// @dev Exact Solidity equivalent of `l1_message_ergs_cost / ERGS_PER_GAS`
function estimateL1MessageGas(uint256 messageLen) internal pure returns (uint256) {
uint256 hashing = gasKeccak(L2_TO_L1_LOG_SERIALIZE_SIZE) + gasKeccak(64) * 3 + gasKeccak(messageLen);
uint256 logCost = LOG + LOGDATA * messageLen;
return hashing + logCost;
}
}
59 changes: 59 additions & 0 deletions l1-contracts/contracts/l2-system/zksync-os/L1Messenger.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.28;

import {L1_MESSENGER_HOOK} from "contracts/common/l2-helpers/L2ContractAddresses.sol";
import {IL2ToL1MessengerZKSyncOS} from "contracts/common/l2-helpers/IL2ToL1MessengerZKSyncOS.sol";
import {L1MessengerHookFailed, NotEnoughGasSupplied, NotSelfCall} from "./errors/ZKOSContractErrors.sol";
import {L1MessageGasLib} from "./L1MessageGasLib.sol";

/**
* @author Matter Labs
* @custom:security-contact security@matterlabs.dev
* @notice Smart contract for sending arbitrary length messages to L1
* @dev by default ZKsync can send fixed length messages on L1.
* A fixed length message has 4 parameters `senderAddress` `isService`, `key`, `value`,
* the first one is taken from the context, the other three are chosen by the sender.
* @dev To send a variable length message we use this trick:
* - This system contract accepts a arbitrary length message and sends a fixed length message with
* parameters `senderAddress == this`, `marker == true`, `key == msg.sender`, `value == keccak256(message)`.
* - The contract on L1 accepts all sent messages and if the message came from this system contract
* it requires that the preimage of `value` be provided.
*/
contract L1Messenger is IL2ToL1MessengerZKSyncOS {
function burnGas(bytes calldata _message) internal {
uint256 gasToBurn = L1MessageGasLib.estimateL1MessageGas(_message.length);

// If not enough gas to burn the desired amount, revert
if ((gasleft() * 63) / 64 < gasToBurn) {
revert NotEnoughGasSupplied();
}

(bool success, ) = address(this).call{gas: gasToBurn}("");
success; // ignored
}

/// @notice Public functionality to send messages to L1.
/// @param _message The message intended to be sent to L1.
function sendToL1(bytes calldata _message) external returns (bytes32 hash) {
// As a first step we burn the respective amount of gas, which is the explicit cost of sending L2->L1 message.
burnGas(_message);

// Call system hook at the known system address.
// Calldata to the hook is exactly `message`.
(bool ok, ) = L1_MESSENGER_HOOK.call(abi.encodePacked(msg.sender, _message));
require(ok, L1MessengerHookFailed());
hash = keccak256(_message);

emit L1MessageSent(msg.sender, hash, _message);
}

// --- Burner entrypoint: only callable by self ---
fallback() external {
// This fallback is used *only* for self-call burning
require(msg.sender == address(this), NotSelfCall());
assembly {
invalid()
}
}
}
72 changes: 72 additions & 0 deletions l1-contracts/contracts/l2-system/zksync-os/L2BaseToken.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.28;

import {L2_TO_L1_MESSENGER_SYSTEM_CONTRACT} from "contracts/common/l2-helpers/L2ContractAddresses.sol";
import {IMailboxImpl} from "contracts/state-transition/chain-interfaces/IMailbox.sol";
import {Burner} from "./Burner.sol";
import {IBaseToken} from "./ZKOSContractHelper.sol";
import {L1MessengerSendFailed} from "./errors/ZKOSContractErrors.sol";

/**
* @author Matter Labs
* @custom:security-contact security@matterlabs.dev
* @notice Native ETH contract.
* @dev It does NOT provide interfaces for personal interaction with tokens like `transfer`, `approve`, and `transferFrom`.
* Instead, this contract is used only as an entrypoint for native token withdrawals.
*/
contract L2BaseToken is IBaseToken {
/// @notice Initiate the withdrawal of the base token, 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 {
uint256 amount = _burnMsgValue();

// Send the L2 log, a user could use it as proof of the withdrawal
bytes memory message = _getL1WithdrawMessage(_l1Receiver, amount);
bytes32 msgHash = L2_TO_L1_MESSENGER_SYSTEM_CONTRACT.sendToL1(message);
if (msgHash == bytes32(0)) revert L1MessengerSendFailed();

emit Withdrawal(msg.sender, _l1Receiver, amount);
}

/// @notice Initiate the withdrawal of the base token, with the sent message. The funds will be available to claim on L1 `finalizeEthWithdrawal` method.
/// @param _l1Receiver The address on L1 to receive the funds.
/// @param _additionalData Additional data to be sent to L1 with the withdrawal.
function withdrawWithMessage(address _l1Receiver, bytes calldata _additionalData) external payable {
uint256 amount = _burnMsgValue();

// Send the L2 log, a user could use it as proof of the withdrawal
bytes memory message = _getExtendedWithdrawMessage(_l1Receiver, amount, msg.sender, _additionalData);
bytes32 msgHash = L2_TO_L1_MESSENGER_SYSTEM_CONTRACT.sendToL1(message);
if (msgHash == bytes32(0)) revert L1MessengerSendFailed();

emit WithdrawalWithMessage(msg.sender, _l1Receiver, amount, _additionalData);
}

/// @dev The function burn the sent `msg.value`.
/// NOTE: Since this contract holds the mapping of all ether balances of the system,
/// the sent `msg.value` is added to the `this` balance before the call.
/// So the balance of `address(this)` is always bigger or equal to the `msg.value`!
function _burnMsgValue() internal returns (uint256 amount) {
amount = msg.value;

if (amount == 0) return 0;
new Burner{value: amount}();
}

/// @dev Get the message to be sent to L1 to initiate a withdrawal.
function _getL1WithdrawMessage(address _to, uint256 _amount) internal pure returns (bytes memory) {
return abi.encodePacked(IMailboxImpl.finalizeEthWithdrawal.selector, _to, _amount);
}

/// @dev Get the message to be sent to L1 to initiate a withdrawal.
function _getExtendedWithdrawMessage(
address _to,
uint256 _amount,
address _sender,
bytes memory _additionalData
) internal pure returns (bytes memory) {
// solhint-disable-next-line func-named-parameters
return abi.encodePacked(IMailboxImpl.finalizeEthWithdrawal.selector, _to, _amount, _sender, _additionalData);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import {L2_COMPLEX_UPGRADER_ADDR, SET_BYTECODE_ON_ADDRESS_HOOK} from "contracts/common/l2-helpers/L2ContractAddresses.sol";
import {IZKOSContractDeployer} from "./interfaces/IZKOSContractDeployer.sol";
import {SetBytecodeOnAddressHookFailed, Unauthorized} from "./errors/ZKOSContractErrors.sol";

/// @title ZKOSContractDeployer
/// @notice Minimal wrapper that forwards to the set bytecode on address system hook at a hardcoded address.
contract ZKOSContractDeployer is IZKOSContractDeployer {
/// @notice Checks that the message sender is the native token vault.
modifier onlyComplexUpgrader() {
if (msg.sender != L2_COMPLEX_UPGRADER_ADDR) {
revert Unauthorized(msg.sender);
}
_;
}

/// @inheritdoc IZKOSContractDeployer
function setBytecodeDetailsEVM(
address _addr,
bytes32 _bytecodeHash,
uint32 _bytecodeLength,
bytes32 _observableBytecodeHash,
uint32 _observableBytecodeLength
) external override onlyComplexUpgrader {
(bool ok, ) = SET_BYTECODE_ON_ADDRESS_HOOK.call(
abi.encode(_addr, _bytecodeHash, _bytecodeLength, _observableBytecodeHash, _observableBytecodeLength)
);

if (!ok) {
revert SetBytecodeOnAddressHookFailed();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// SPDX-License-Identifier: MIT
// We use a floating point pragma here so it can be used within other projects that interact with the ZKsync ecosystem without using our exact pragma version.
pragma solidity ^0.8.20;

/**
* @author Matter Labs
* @custom:security-contact security@matterlabs.dev
* @notice Interface for the contract that is used to simulate Base Token on L2.
*/
interface IBaseToken {
/// @notice Allows the withdrawal of Base Token to a given L1 receiver.
/// @param _l1Receiver The address on L1 to receive the withdrawn Base Token.
function withdraw(address _l1Receiver) external payable;

/// @notice Allows the withdrawal of Base Token to a given L1 receiver along with an additional message.
/// @param _l1Receiver The address on L1 to receive the withdrawn Base Token.
/// @param _additionalData Additional message or data to be sent alongside the withdrawal.
function withdrawWithMessage(address _l1Receiver, bytes memory _additionalData) external payable;

/// @notice Emitted when a base-token withdrawal is initiated.
/// @param _l2Sender The L2 address that initiated the withdrawal.
/// @param _l1Receiver The L1 address that will receive the withdrawn Base Token.
/// @param _amount The amount of Base Token (in wei) withdrawn.
event Withdrawal(address indexed _l2Sender, address indexed _l1Receiver, uint256 _amount);

/// @notice Emitted when a base-token withdrawal with an additional message is initiated.
/// @param _l2Sender The L2 address that initiated the withdrawal.
/// @param _l1Receiver The L1 address that will receive the withdrawn Base Token.
/// @param _amount The amount of Base Token (in wei) withdrawn.
/// @param _additionalData Arbitrary data/message forwarded alongside the withdrawal.
event WithdrawalWithMessage(
address indexed _l2Sender,
address indexed _l1Receiver,
uint256 _amount,
bytes _additionalData
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: MIT
// We use a floating point pragma here so it can be used within other projects that interact with the ZKsync ecosystem without using our exact pragma version.
pragma solidity ^0.8.20;

// 0xf801b069
error L1MessengerHookFailed();
// 0xa3628b43
error L1MessengerSendFailed();
// 0x497087ab
error NotEnoughGasSupplied();
// 0xec7cdc0a
error NotSelfCall();
// 0x058f5efe
error SetBytecodeOnAddressHookFailed();
// 0x8e4a23d6
error Unauthorized(address);
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ interface IZKOSContractDeployer {
address _addr,
bytes32 _bytecodeHash,
uint32 _bytecodeLength,
bytes32 _observableBytecodeHash
bytes32 _observableBytecodeHash,
uint32 _observableBytecodeLength
) external;
}
Loading