Skip to content

Commit

Permalink
Merge pull request #18 from matter-labs/vb-update-system-contracts-fa…
Browse files Browse the repository at this point in the history
…ir-onboarding-alpha

Update L2 contracts
  • Loading branch information
vladbochok authored Mar 24, 2023
2 parents 40cc06e + 35ee069 commit b8449bf
Show file tree
Hide file tree
Showing 39 changed files with 2,572 additions and 357 deletions.
20 changes: 6 additions & 14 deletions l2/contracts/bridge/L2WETH.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@ import "./interfaces/IL2StandardToken.sol";
/// Note: This is an upgradeable contract. In the future, we will remove upgradeability to make it trustless.
/// But for now, when the Rollup has instant upgradability, we leave the possibility of upgrading to improve the contract if needed.
contract L2WETH is ERC20PermitUpgradeable, IL2WETH, IL2StandardToken {
/// @dev Address of the L1 WETH token. It can be deposited to mint this L2 token.
address public override l1Address;

/// @dev Contract is expected to be used as proxy implementation.
constructor() {
// Disable initialization to prevent Parity hack.
Expand All @@ -28,25 +25,17 @@ contract L2WETH is ERC20PermitUpgradeable, IL2WETH, IL2StandardToken {

/// @notice Initializes a contract token for later use. Expected to be used in the proxy.
/// @dev Stores the L1 address of the bridge and set `name`/`symbol`/`decimals` getters.
/// @param _l1Address Address of the L1 token that can be deposited to mint this L2 WETH.
/// @param name_ The name of the token.
/// @param symbol_ The symbol of the token.
/// Note: The decimals are hardcoded to 18, the same as on Ether.
function bridgeInitialize(
address _l1Address,
string memory name_,
string memory symbol_
) external initializer {
require(_l1Address != address(0), "in6"); // Should be non-zero address
l1Address = _l1Address;

function initialize(string memory name_, string memory symbol_) external initializer {
// Set decoded values for name and symbol.
__ERC20_init_unchained(name_, symbol_);

// Set the name for EIP-712 signature.
__ERC20Permit_init(name_);

emit BridgeInitialize(_l1Address, name_, symbol_, 18);
emit Initialize(name_, symbol_, 18);
}

/// @notice Function for minting tokens on L2, is implemented †o be compatible with StandardToken interface.
Expand All @@ -64,14 +53,17 @@ contract L2WETH is ERC20PermitUpgradeable, IL2WETH, IL2StandardToken {
/// @param _amount The amount that will be burned.
/// @notice Should be called by the bridge before withdrawing tokens to L1.
function bridgeBurn(address _from, uint256 _amount) external override {
// TODO: unlike the `bridgeMint` method, this should be implemented.
revert("bridgeBurn is not implemented yet");
}

function l2Bridge() external view returns (address) {
revert("l2Bridge is not implemented yet");
}

function l1Address() external view returns (address) {
revert("l1Address is not implemented yet");
}

/// @notice Deposit Ether to mint WETH.
function deposit() external payable override {
depositTo(msg.sender);
Expand Down
2 changes: 2 additions & 0 deletions l2/contracts/bridge/interfaces/IL2WETH.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
pragma solidity ^0.8.0;

interface IL2WETH {
event Initialize(string name, string symbol, uint8 decimals);

function deposit() external payable;

function withdraw(uint256 _amount) external;
Expand Down
138 changes: 138 additions & 0 deletions l2/system-contracts/AccountCodeStorage.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./interfaces/IAccountCodeStorage.sol";
import "./libraries/Utils.sol";
import {DEPLOYER_SYSTEM_CONTRACT, NONCE_HOLDER_SYSTEM_CONTRACT, CURRENT_MAX_PRECOMPILE_ADDRESS} from "./Constants.sol";

/**
* @author Matter Labs
* @notice The storage of this contract serves as a mapping for the code hashes of the 32-byte account addresses.
* @dev Code hash is not strictly a hash, it's a structure where the first byte denotes the version of the hash,
* the second byte denotes whether the contract is constructed, and the next two bytes denote the length in 32-byte words.
* And then the next 28 bytes are the truncated hash.
* @dev In this version of zkSync, the first byte of the hash MUST be 1.
* @dev The length of each bytecode MUST be odd. It's internal code format requirements, due to padding of SHA256 function.
* @dev It is also assumed that all the bytecode hashes are *known*, i.e. the full bytecodes
* were published on L1 as calldata. This contract trusts the ContractDeployer and the KnownCodesStorage
* system contracts to enforce the invariants mentioned above.
*/
contract AccountCodeStorage is IAccountCodeStorage {
bytes32 constant EMPTY_STRING_KECCAK = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;

modifier onlyDeployer() {
require(msg.sender == address(DEPLOYER_SYSTEM_CONTRACT), "Callable only by the deployer system contract");
_;
}

/// @notice Stores the bytecodeHash of constructing contract.
/// @param _address The address of the account to set the codehash to.
/// @param _hash The new bytecode hash of the constructing account.
/// @dev This method trusts the ContractDeployer to make sure that the bytecode is known and well-formed,
/// but checks whether the bytecode hash corresponds to the constructing smart contract.
function storeAccountConstructingCodeHash(address _address, bytes32 _hash) external override onlyDeployer {
// Check that code hash corresponds to the deploying smart contract
require(Utils.isContractConstructing(_hash), "Code hash is not for a contract on constructor");
_storeCodeHash(_address, _hash);
}

/// @notice Stores the bytecodeHash of constructed contract.
/// @param _address The address of the account to set the codehash to.
/// @param _hash The new bytecode hash of the constructed account.
/// @dev This method trusts the ContractDeployer to make sure that the bytecode is known and well-formed,
/// but checks whether the bytecode hash corresponds to the constructed smart contract.
function storeAccountConstructedCodeHash(address _address, bytes32 _hash) external override onlyDeployer {
// Check that code hash corresponds to the deploying smart contract
require(Utils.isContractConstructed(_hash), "Code hash is not for a contract on constructor");
_storeCodeHash(_address, _hash);
}

/// @notice Marks the account bytecodeHash as constructed.
/// @param _address The address of the account to mark as constructed
function markAccountCodeHashAsConstructed(address _address) external override onlyDeployer {
bytes32 codeHash = getRawCodeHash(_address);

require(Utils.isContractConstructing(codeHash), "Code hash is not for a contract on constructor");

// Get the bytecode hash with "isConstructor" flag equal to false
bytes32 constructedBytecodeHash = Utils.constructedBytecodeHash(codeHash);

_storeCodeHash(_address, constructedBytecodeHash);
}

/// @dev Store the codehash of the account without any checks.
/// @param _address The address of the account to set the codehash to.
/// @param _hash The new account bytecode hash.
function _storeCodeHash(address _address, bytes32 _hash) internal {
uint256 addressAsKey = uint256(uint160(_address));
assembly {
sstore(addressAsKey, _hash)
}
}

/// @notice Get the codehash stored for an address.
/// @param _address The address of the account of which the codehash to return
/// @return codeHash The codehash stored for this account.
function getRawCodeHash(address _address) public view override returns (bytes32 codeHash) {
uint256 addressAsKey = uint256(uint160(_address));

assembly {
codeHash := sload(addressAsKey)
}
}

/// @notice Simulate the behavior of the `extcodehash` EVM opcode.
/// @param _input The 256-bit account address.
/// @return codeHash - hash of the bytecode according to the EIP-1052 specification.
function getCodeHash(uint256 _input) external view override returns (bytes32) {
// We consider the account bytecode hash of the last 20 bytes of the input, because
// according to the spec "If EXTCODEHASH of A is X, then EXTCODEHASH of A + 2**160 is X".
address account = address(uint160(_input));
if (uint160(account) <= CURRENT_MAX_PRECOMPILE_ADDRESS) {
return EMPTY_STRING_KECCAK;
}

bytes32 codeHash = getRawCodeHash(account);

// The code hash is equal to the `keccak256("")` if the account is an EOA with at least one transaction.
// Otherwise, the account is either deployed smart contract or an empty account,
// for both cases the code hash is equal to the raw code hash.
if (codeHash == 0x00 && NONCE_HOLDER_SYSTEM_CONTRACT.getRawNonce(account) > 0) {
codeHash = EMPTY_STRING_KECCAK;
}
// The contract is still on the constructor, which means it is not deployed yet,
// so set `keccak256("")` as a code hash. The EVM has the same behavior.
else if (Utils.isContractConstructing(codeHash)) {
codeHash = EMPTY_STRING_KECCAK;
}

return codeHash;
}

/// @notice Simulate the behavior of the `extcodesize` EVM opcode.
/// @param _input The 256-bit account address.
/// @return codeSize - the size of the deployed smart contract in bytes.
function getCodeSize(uint256 _input) external view override returns (uint256 codeSize) {
// We consider the account bytecode size of the last 20 bytes of the input, because
// according to the spec "If EXTCODESIZE of A is X, then EXTCODESIZE of A + 2**160 is X".
address account = address(uint160(_input));
bytes32 codeHash = getRawCodeHash(account);

// If the contract is a default account or is on constructor the code size is zero,
// otherwise extract the proper value for it from the bytecode hash.
// NOTE: zero address and precompiles are a special case, they are contracts, but we
// want to preserve EVM invariants (see EIP-1052 specification). That's why we automatically
// return `0` length in the following cases:
// - `codehash(0) == 0`
// - `account` is a precompile.
// - `account` is currently being constructed
if (
uint160(account) > CURRENT_MAX_PRECOMPILE_ADDRESS &&
codeHash != 0x00 &&
!Utils.isContractConstructing(codeHash)
) {
codeSize = Utils.bytecodeLenInBytes(codeHash);
}
}
}
7 changes: 4 additions & 3 deletions l2/system-contracts/BootloaderUtilities.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pragma solidity ^0.8.0;
import "./interfaces/IBootloaderUtilities.sol";
import "./libraries/TransactionHelper.sol";
import "./libraries/RLPEncoder.sol";
import "./libraries/EfficientCall.sol";

/**
* @author Matter Labs
Expand All @@ -24,7 +25,7 @@ contract BootloaderUtilities is IBootloaderUtilities {
) external view override returns (bytes32 txHash, bytes32 signedTxHash) {
signedTxHash = _transaction.encodeHash();
if (_transaction.txType == EIP_712_TX_TYPE) {
txHash = keccak256(bytes.concat(signedTxHash, keccak256(_transaction.signature)));
txHash = keccak256(bytes.concat(signedTxHash, EfficientCall.keccak(_transaction.signature)));
} else if (_transaction.txType == LEGACY_TX_TYPE) {
txHash = encodeLegacyTransactionHash(_transaction);
} else if (_transaction.txType == EIP_1559_TX_TYPE) {
Expand Down Expand Up @@ -89,7 +90,7 @@ contract BootloaderUtilities is IBootloaderUtilities {
uint256 vInt = uint256(uint8(_transaction.signature[64]));
require(vInt == 27 || vInt == 28, "Invalid v value");

// If the `chainId` is specified in the transaction, then the `v` value is encoded as
// If the `chainId` is specified in the transaction, then the `v` value is encoded as
// `35 + y + 2 * chainId == vInt + 8 + 2 * chainId`, where y - parity bit (see EIP-155).
if (_transaction.reserved[0] != 0) {
vInt += 8 + block.chainid * 2;
Expand All @@ -111,7 +112,7 @@ contract BootloaderUtilities is IBootloaderUtilities {
vEncoded.length;

// Safe cast, because the length of the list can't be so large.
encodedListLength = RLPEncoder.encodeListLen(uint64(listLength));
encodedListLength = RLPEncoder.encodeListLen(uint64(listLength));
}

return
Expand Down
92 changes: 92 additions & 0 deletions l2/system-contracts/BytecodeCompressor.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// SPDX-License-Identifier: MIT OR Apache-2.0

pragma solidity ^0.8.0;

import "./interfaces/IBytecodeCompressor.sol";
import "./Constants.sol";
import "./libraries/Utils.sol";
import "./libraries/UnsafeBytesCalldata.sol";

/**
* @author Matter Labs
* @notice Simple implementation of the compression algorithm specialized for zkEVM bytecode.
* @dev Every deployed bytecode in zkEVM should be publicly restorable from the L1 data availability.
* For this reason, the user may request the sequencer to publish the original bytecode and mark it as known.
* Or the user may compress the bytecode and publish it instead (fewer data onchain!).
*/
contract BytecodeCompressor is IBytecodeCompressor {
using UnsafeBytesCalldata for bytes;

modifier onlyBootloader() {
require(msg.sender == BOOTLOADER_FORMAL_ADDRESS, "Callable only by the bootloader");
_;
}

/// @notice Verify the compressed bytecode and publish it on the L1.
/// @param _bytecode The original bytecode to be verified against.
/// @param _rawCompressedData The compressed bytecode in a format of:
/// - 2 bytes: the length of the dictionary
/// - N bytes: the dictionary
/// - M bytes: the encoded data
/// @dev The dictionary is a sequence of 8-byte chunks, each of them has the associated index.
/// @dev The encoded data is a sequence of 2-byte chunks, each of them is an index of the dictionary.
/// @dev The compression algorithm works as follows:
/// 1. The original bytecode is split into 8-byte chunks.
/// Since the bytecode size is always a multiple of 32, this is always possible.
/// 2. For each 8-byte chunk in the original bytecode:
/// * If the chunk is not already in the dictionary, it is added to the dictionary array.
/// * If the dictionary becomes overcrowded (2^16 + 1 elements), the compression process will fail.
/// * The 2-byte index of the chunk in the dictionary is added to the encoded data.
/// @dev Currently, the method may be called only from the bootloader because the server is not ready to publish bytecodes
/// in internal transactions. However, in the future, we will allow everyone to publish compressed bytecodes.
function publishCompressedBytecode(
bytes calldata _bytecode,
bytes calldata _rawCompressedData
) external payable onlyBootloader returns (bytes32 bytecodeHash) {
unchecked {
(bytes calldata dictionary, bytes calldata encodedData) = _decodeRawBytecode(_rawCompressedData);

require(dictionary.length % 8 == 0, "Dictionary length should be a multiple of 8");
require(dictionary.length <= 2 ** 16 * 8, "Dictionary is too big");
require(
encodedData.length * 4 == _bytecode.length,
"Encoded data length should be 4 times shorter than the original bytecode"
);

for (uint256 encodedDataPointer = 0; encodedDataPointer < encodedData.length; encodedDataPointer += 2) {
uint256 indexOfEncodedChunk = uint256(encodedData.readUint16(encodedDataPointer)) * 8;
require(indexOfEncodedChunk < dictionary.length, "Encoded chunk index is out of bounds");

uint64 encodedChunk = dictionary.readUint64(indexOfEncodedChunk);
uint64 realChunk = _bytecode.readUint64(encodedDataPointer * 4);

require(encodedChunk == realChunk, "Encoded chunk does not match the original bytecode");
}
}

bytecodeHash = Utils.hashL2Bytecode(_bytecode);

bytes32 rawCompressedDataHash = L1_MESSENGER_CONTRACT.sendToL1(_rawCompressedData);
KNOWN_CODE_STORAGE_CONTRACT.markBytecodeAsPublished(
bytecodeHash,
rawCompressedDataHash,
_rawCompressedData.length
);
}

/// @notice Decode the raw compressed data into the dictionary and the encoded data.
/// @param _rawCompressedData The compressed bytecode in a format of:
/// - 2 bytes: the bytes length of the dictionary
/// - N bytes: the dictionary
/// - M bytes: the encoded data
function _decodeRawBytecode(
bytes calldata _rawCompressedData
) internal pure returns (bytes calldata dictionary, bytes calldata encodedData) {
unchecked {
// The dictionary length can't be more than 2^16, so it fits into 2 bytes.
uint256 dictionaryLen = uint256(_rawCompressedData.readUint16(0));
dictionary = _rawCompressedData[2:2 + dictionaryLen * 8];
encodedData = _rawCompressedData[2 + dictionaryLen * 8:];
}
}
}
18 changes: 13 additions & 5 deletions l2/system-contracts/Constants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import "./interfaces/IImmutableSimulator.sol";
import "./interfaces/IEthToken.sol";
import "./interfaces/IL1Messenger.sol";
import "./interfaces/ISystemContext.sol";
import "./interfaces/IBytecodeCompressor.sol";
import "./BootloaderUtilities.sol";

/// @dev All the system contracts introduced by zkSync have their addresses
Expand All @@ -23,6 +24,13 @@ uint160 constant MAX_SYSTEM_CONTRACT_ADDRESS = 0xffff; // 2^16 - 1
address constant ECRECOVER_SYSTEM_CONTRACT = address(0x01);
address constant SHA256_SYSTEM_CONTRACT = address(0x02);

/// @dev The current maximum deployed precompile address.
/// Note: currently only two precompiles are deployed:
/// 0x01 - ecrecover
/// 0x02 - sha256
/// Important! So the constant should be updated if more precompiles are deployed.
uint256 constant CURRENT_MAX_PRECOMPILE_ADDRESS = uint256(uint160(SHA256_SYSTEM_CONTRACT));

address payable constant BOOTLOADER_FORMAL_ADDRESS = payable(address(SYSTEM_CONTRACTS_OFFSET + 0x01));
IAccountCodeStorage constant ACCOUNT_CODE_STORAGE_SYSTEM_CONTRACT = IAccountCodeStorage(
address(SYSTEM_CONTRACTS_OFFSET + 0x02)
Expand Down Expand Up @@ -50,16 +58,16 @@ BootloaderUtilities constant BOOTLOADER_UTILITIES = BootloaderUtilities(address(

address constant EVENT_WRITER_CONTRACT = address(SYSTEM_CONTRACTS_OFFSET + 0x0d);

/// @dev The number of bytes that are published during the contract deployment
/// in addition to the bytecode itself.
uint256 constant BYTECODE_PUBLISHING_OVERHEAD = 100;
IBytecodeCompressor constant BYTECODE_COMPRESSOR_CONTRACT = IBytecodeCompressor(
address(SYSTEM_CONTRACTS_OFFSET + 0x0e)
);

/// @dev If the bitwise AND of the third extraAbi param when calling the MSG_VALUE_SIMULATOR
/// @dev If the bitwise AND of the extraAbi[2] param when calling the MSG_VALUE_SIMULATOR
/// is non-zero, the call will be assumed to be a system one.
uint256 constant MSG_VALUE_SIMULATOR_IS_SYSTEM_BIT = 1;

/// @dev The maximal msg.value that context can have
uint256 constant MAX_MSG_VALUE = 2**128 - 1;
uint256 constant MAX_MSG_VALUE = 2 ** 128 - 1;

/// @dev Prefix used during derivation of account addresses using CREATE2
/// @dev keccak256("zksyncCreate2")
Expand Down
Loading

0 comments on commit b8449bf

Please sign in to comment.