-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #18 from matter-labs/vb-update-system-contracts-fa…
…ir-onboarding-alpha Update L2 contracts
- Loading branch information
Showing
39 changed files
with
2,572 additions
and
357 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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:]; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.