diff --git a/l2/contracts/bridge/L2WETH.sol b/l2/contracts/bridge/L2WETH.sol index 967ab8c..b598412 100644 --- a/l2/contracts/bridge/L2WETH.sol +++ b/l2/contracts/bridge/L2WETH.sol @@ -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. @@ -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. @@ -64,7 +53,6 @@ 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"); } @@ -72,6 +60,10 @@ contract L2WETH is ERC20PermitUpgradeable, IL2WETH, IL2StandardToken { 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); diff --git a/l2/contracts/bridge/interfaces/IL2WETH.sol b/l2/contracts/bridge/interfaces/IL2WETH.sol index 20bac27..cd6bb76 100644 --- a/l2/contracts/bridge/interfaces/IL2WETH.sol +++ b/l2/contracts/bridge/interfaces/IL2WETH.sol @@ -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; diff --git a/l2/system-contracts/AccountCodeStorage.sol b/l2/system-contracts/AccountCodeStorage.sol new file mode 100644 index 0000000..1dbdcab --- /dev/null +++ b/l2/system-contracts/AccountCodeStorage.sol @@ -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); + } + } +} diff --git a/l2/system-contracts/BootloaderUtilities.sol b/l2/system-contracts/BootloaderUtilities.sol index 6c1b435..ad5f13d 100644 --- a/l2/system-contracts/BootloaderUtilities.sol +++ b/l2/system-contracts/BootloaderUtilities.sol @@ -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 @@ -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) { @@ -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; @@ -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 diff --git a/l2/system-contracts/BytecodeCompressor.sol b/l2/system-contracts/BytecodeCompressor.sol new file mode 100644 index 0000000..7a8d1b6 --- /dev/null +++ b/l2/system-contracts/BytecodeCompressor.sol @@ -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:]; + } + } +} diff --git a/l2/system-contracts/Constants.sol b/l2/system-contracts/Constants.sol index 4e0c5bb..76a5ea9 100644 --- a/l2/system-contracts/Constants.sol +++ b/l2/system-contracts/Constants.sol @@ -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 @@ -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) @@ -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") diff --git a/l2/system-contracts/ContractDeployer.sol b/l2/system-contracts/ContractDeployer.sol new file mode 100644 index 0000000..c60bcc3 --- /dev/null +++ b/l2/system-contracts/ContractDeployer.sol @@ -0,0 +1,351 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import {ImmutableData} from "./interfaces/IImmutableSimulator.sol"; +import "./interfaces/IContractDeployer.sol"; +import {CREATE2_PREFIX, CREATE_PREFIX, NONCE_HOLDER_SYSTEM_CONTRACT, ACCOUNT_CODE_STORAGE_SYSTEM_CONTRACT, FORCE_DEPLOYER, MAX_SYSTEM_CONTRACT_ADDRESS, KNOWN_CODE_STORAGE_CONTRACT, ETH_TOKEN_SYSTEM_CONTRACT, IMMUTABLE_SIMULATOR_SYSTEM_CONTRACT} from "./Constants.sol"; + +import "./libraries/Utils.sol"; +import "./libraries/EfficientCall.sol"; +import {SystemContractHelper, ISystemContract} from "./libraries/SystemContractHelper.sol"; + +/** + * @author Matter Labs + * @notice System smart contract that is responsible for deploying other smart contracts on zkSync. + * @dev The contract is responsible for generating the address of the deployed smart contract, + * incrementing the deployment nonce and making sure that the constructor is never called twice in a contract. + * Note, contracts with bytecode that have already been published to L1 once + * do not need to be published anymore. + */ +contract ContractDeployer is IContractDeployer, ISystemContract { + /// @notice Information about an account contract. + /// @dev For EOA and simple contracts (i.e. not accounts) this value is 0. + mapping(address => AccountInfo) internal _accountInfo; + + modifier onlySelf() { + require(msg.sender == address(this), "Callable only by self"); + _; + } + + /// @notice Returns information about a certain account. + function getAccountInfo(address _address) external view returns (AccountInfo memory info) { + return _accountInfo[_address]; + } + + /// @notice Returns the account abstraction version if `_address` is a deployed contract. + /// Returns the latest supported account abstraction version if `_address` is an EOA. + function extendedAccountVersion(address _address) public view returns (AccountAbstractionVersion) { + AccountInfo memory info = _accountInfo[_address]; + if (info.supportedAAVersion != AccountAbstractionVersion.None) { + return info.supportedAAVersion; + } + + // It is an EOA, it is still an account. + if (ACCOUNT_CODE_STORAGE_SYSTEM_CONTRACT.getRawCodeHash(_address) == 0) { + return AccountAbstractionVersion.Version1; + } + + return AccountAbstractionVersion.None; + } + + /// @notice Stores the new account information + function _storeAccountInfo(address _address, AccountInfo memory _newInfo) internal { + _accountInfo[_address] = _newInfo; + } + + /// @notice Update the used version of the account. + /// @param _version The new version of the AA protocol to use. + /// @dev Note that it allows changes from account to non-account and vice versa. + function updateAccountVersion(AccountAbstractionVersion _version) external onlySystemCall { + _accountInfo[msg.sender].supportedAAVersion = _version; + + emit AccountVersionUpdated(msg.sender, _version); + } + + /// @notice Updates the nonce ordering of the account. Currently, + /// it only allows changes from sequential to arbitrary ordering. + /// @param _nonceOrdering The new nonce ordering to use. + function updateNonceOrdering(AccountNonceOrdering _nonceOrdering) external onlySystemCall { + AccountInfo memory currentInfo = _accountInfo[msg.sender]; + + require( + _nonceOrdering == AccountNonceOrdering.Arbitrary && + currentInfo.nonceOrdering == AccountNonceOrdering.Sequential, + "It is only possible to change from sequential to arbitrary ordering" + ); + + currentInfo.nonceOrdering = _nonceOrdering; + _storeAccountInfo(msg.sender, currentInfo); + + emit AccountNonceOrderingUpdated(msg.sender, _nonceOrdering); + } + + /// @notice Calculates the address of a deployed contract via create2 + /// @param _sender The account that deploys the contract. + /// @param _bytecodeHash The correctly formatted hash of the bytecode. + /// @param _salt The create2 salt. + /// @param _input The constructor data. + /// @return newAddress The derived address of the account. + function getNewAddressCreate2( + address _sender, + bytes32 _bytecodeHash, + bytes32 _salt, + bytes calldata _input + ) public view override returns (address newAddress) { + // No collision is possible with the Ethereum's CREATE2, since + // the prefix begins with 0x20.... + bytes32 constructorInputHash = EfficientCall.keccak(_input); + + bytes32 hash = keccak256( + bytes.concat(CREATE2_PREFIX, bytes32(uint256(uint160(_sender))), _salt, _bytecodeHash, constructorInputHash) + ); + + newAddress = address(uint160(uint256(hash))); + } + + /// @notice Calculates the address of a deployed contract via create + /// @param _sender The account that deploys the contract. + /// @param _senderNonce The deploy nonce of the sender's account. + function getNewAddressCreate( + address _sender, + uint256 _senderNonce + ) public pure override returns (address newAddress) { + // No collision is possible with the Ethereum's CREATE, since + // the prefix begins with 0x63.... + bytes32 hash = keccak256( + bytes.concat(CREATE_PREFIX, bytes32(uint256(uint160(_sender))), bytes32(_senderNonce)) + ); + + newAddress = address(uint160(uint256(hash))); + } + + /// @notice Deploys a contract with similar address derivation rules to the EVM's `CREATE2` opcode. + /// @param _salt The CREATE2 salt + /// @param _bytecodeHash The correctly formatted hash of the bytecode. + /// @param _input The constructor calldata + /// @dev In case of a revert, the zero address should be returned. + function create2( + bytes32 _salt, + bytes32 _bytecodeHash, + bytes calldata _input + ) external payable override returns (address) { + return create2Account(_salt, _bytecodeHash, _input, AccountAbstractionVersion.None); + } + + /// @notice Deploys a contract with similar address derivation rules to the EVM's `CREATE` opcode. + /// @param _bytecodeHash The correctly formatted hash of the bytecode. + /// @param _input The constructor calldata + /// @dev This method also accepts nonce as one of its parameters. + /// It is not used anywhere and it needed simply for the consistency for the compiler + /// @dev In case of a revert, the zero address should be returned. + /// Note: this method may be callable only in system mode, + /// that is checked in the `createAccount` by `onlySystemCall` modifier. + function create( + bytes32 _salt, + bytes32 _bytecodeHash, + bytes calldata _input + ) external payable override returns (address) { + return createAccount(_salt, _bytecodeHash, _input, AccountAbstractionVersion.None); + } + + /// @notice Deploys a contract account with similar address derivation rules to the EVM's `CREATE2` opcode. + /// @param _salt The CREATE2 salt + /// @param _bytecodeHash The correctly formatted hash of the bytecode. + /// @param _input The constructor calldata. + /// @param _aaVersion The account abstraction version to use. + /// @dev In case of a revert, the zero address should be returned. + /// Note: this method may be callable only in system mode, + /// that is checked in the `createAccount` by `onlySystemCall` modifier. + function create2Account( + bytes32 _salt, + bytes32 _bytecodeHash, + bytes calldata _input, + AccountAbstractionVersion _aaVersion + ) public payable override onlySystemCall returns (address) { + NONCE_HOLDER_SYSTEM_CONTRACT.incrementDeploymentNonce(msg.sender); + address newAddress = getNewAddressCreate2(msg.sender, _bytecodeHash, _salt, _input); + + _nonSystemDeployOnAddress(_bytecodeHash, newAddress, _aaVersion, _input); + + return newAddress; + } + + /// @notice Deploys a contract account with similar address derivation rules to the EVM's `CREATE` opcode. + /// @param _bytecodeHash The correctly formatted hash of the bytecode. + /// @param _input The constructor calldata. + /// @param _aaVersion The account abstraction version to use. + /// @dev This method also accepts salt as one of its parameters. + /// It is not used anywhere and it needed simply for the consistency for the compiler + /// @dev In case of a revert, the zero address should be returned. + function createAccount( + bytes32, // salt + bytes32 _bytecodeHash, + bytes calldata _input, + AccountAbstractionVersion _aaVersion + ) public payable override onlySystemCall returns (address) { + uint256 senderNonce = NONCE_HOLDER_SYSTEM_CONTRACT.incrementDeploymentNonce(msg.sender); + address newAddress = getNewAddressCreate(msg.sender, senderNonce); + + _nonSystemDeployOnAddress(_bytecodeHash, newAddress, _aaVersion, _input); + + return newAddress; + } + + /// @notice A struct that describes a forced deployment on an address + struct ForceDeployment { + // The bytecode hash to put on an address + bytes32 bytecodeHash; + // The address on which to deploy the bytecodehash to + address newAddress; + // Whether to run the constructor on the force deployment + bool callConstructor; + // The value with which to initialize a contract + uint256 value; + // The constructor calldata + bytes input; + } + + /// @notice The method that can be used to forcefully deploy a contract. + /// @param _deployment Information about the forced deployment. + /// @param _sender The `msg.sender` inside the constructor call. + function forceDeployOnAddress(ForceDeployment calldata _deployment, address _sender) external payable onlySelf { + _ensureBytecodeIsKnown(_deployment.bytecodeHash); + + AccountInfo memory newAccountInfo; + newAccountInfo.supportedAAVersion = AccountAbstractionVersion.None; + // Accounts have sequential nonces by default. + newAccountInfo.nonceOrdering = AccountNonceOrdering.Sequential; + _storeAccountInfo(_deployment.newAddress, newAccountInfo); + + _constructContract( + _sender, + _deployment.newAddress, + _deployment.bytecodeHash, + _deployment.input, + false, + _deployment.callConstructor + ); + + emit ContractDeployed(_sender, _deployment.bytecodeHash, _deployment.newAddress); + } + + /// @notice This method is to be used only during an upgrade to set bytecodes on specific addresses. + /// @dev We do not require `onlySystemCall` here, since the method is accessible only + /// by `FORCE_DEPLOYER`. + function forceDeployOnAddresses(ForceDeployment[] calldata _deployments) external payable { + require(msg.sender == FORCE_DEPLOYER, "Can only be called by FORCE_DEPLOYER_CONTRACT"); + + uint256 deploymentsLength = _deployments.length; + // We need to ensure that the `value` provided by the call is enough to provide `value` + // for all of the deployments + uint256 sumOfValues = 0; + for (uint256 i = 0; i < deploymentsLength; ++i) { + sumOfValues += _deployments[i].value; + } + require(msg.value == sumOfValues, "`value` provided is not equal to the combined `value`s of deployments"); + + for (uint256 i = 0; i < deploymentsLength; ++i) { + this.forceDeployOnAddress{value: _deployments[i].value}(_deployments[i], msg.sender); + } + } + + function _nonSystemDeployOnAddress( + bytes32 _bytecodeHash, + address _newAddress, + AccountAbstractionVersion _aaVersion, + bytes calldata _input + ) internal { + require(_bytecodeHash != bytes32(0x0), "BytecodeHash can not be zero"); + require(uint160(_newAddress) > MAX_SYSTEM_CONTRACT_ADDRESS, "Can not deploy contracts in kernel space"); + + // We do not allow deploying twice on the same address. + require( + ACCOUNT_CODE_STORAGE_SYSTEM_CONTRACT.getCodeHash(uint256(uint160(_newAddress))) == 0x0, + "Code hash is non-zero" + ); + // Do not allow deploying contracts to default accounts that have already executed transactions. + require(NONCE_HOLDER_SYSTEM_CONTRACT.getRawNonce(_newAddress) == 0x00, "Account is occupied"); + + _performDeployOnAddress(_bytecodeHash, _newAddress, _aaVersion, _input); + } + + /// @notice Deploy a certain bytecode on the address. + /// @param _bytecodeHash The correctly formatted hash of the bytecode. + /// @param _newAddress The address of the contract to be deployed. + /// @param _aaVersion The version of the account abstraction protocol to use. + /// @param _input The constructor calldata. + function _performDeployOnAddress( + bytes32 _bytecodeHash, + address _newAddress, + AccountAbstractionVersion _aaVersion, + bytes calldata _input + ) internal { + _ensureBytecodeIsKnown(_bytecodeHash); + + AccountInfo memory newAccountInfo; + newAccountInfo.supportedAAVersion = _aaVersion; + // Accounts have sequential nonces by default. + newAccountInfo.nonceOrdering = AccountNonceOrdering.Sequential; + _storeAccountInfo(_newAddress, newAccountInfo); + + _constructContract(msg.sender, _newAddress, _bytecodeHash, _input, false, true); + emit ContractDeployed(msg.sender, _bytecodeHash, _newAddress); + } + + /// @notice Check that bytecode hash is marked as known on the `KnownCodeStorage` system contracts + function _ensureBytecodeIsKnown(bytes32 _bytecodeHash) internal view { + uint256 knownCodeMarker = KNOWN_CODE_STORAGE_CONTRACT.getMarker(_bytecodeHash); + require(knownCodeMarker > 0, "The code hash is not known"); + } + + /// @notice Ensures that the _newAddress and assigns a new contract hash to it + /// @param _newAddress The address of the deployed contract + /// @param _bytecodeHash The correctly formatted hash of the bytecode. + function _storeConstructingByteCodeHashOnAddress(address _newAddress, bytes32 _bytecodeHash) internal { + // Set the "isConstructor" flag to the bytecode hash + bytes32 constructingBytecodeHash = Utils.constructingBytecodeHash(_bytecodeHash); + ACCOUNT_CODE_STORAGE_SYSTEM_CONTRACT.storeAccountConstructingCodeHash(_newAddress, constructingBytecodeHash); + } + + /// @notice Transfers the `msg.value` ETH to the deployed account & invokes its constructor. + /// This function must revert in case the deployment fails. + /// @param _sender The msg.sender to be used in the constructor + /// @param _newAddress The address of the deployed contract + /// @param _input The constructor calldata + /// @param _isSystem Whether the call should be a system call (could be possibly required in the future). + function _constructContract( + address _sender, + address _newAddress, + bytes32 _bytecodeHash, + bytes calldata _input, + bool _isSystem, + bool _callConstructor + ) internal { + uint256 value = msg.value; + if (_callConstructor) { + // 1. Transfer the balance to the new address on the constructor call. + if (value > 0) { + ETH_TOKEN_SYSTEM_CONTRACT.transferFromTo(address(this), _newAddress, value); + } + // 2. Set the constructed code hash on the account + _storeConstructingByteCodeHashOnAddress(_newAddress, _bytecodeHash); + + // 3. Call the constructor on behalf of the account + if (value > 0) { + // Safe to cast value, because `msg.value` <= `uint128.max` due to `MessageValueSimulator` invariant + SystemContractHelper.setValueForNextFarCall(uint128(value)); + } + bytes memory returnData = EfficientCall.mimicCall(gasleft(), _newAddress, _input, _sender, true, _isSystem); + // 4. Mark bytecode hash as constructed + ACCOUNT_CODE_STORAGE_SYSTEM_CONTRACT.markAccountCodeHashAsConstructed(_newAddress); + // 5. Set the contract immutables + ImmutableData[] memory immutables = abi.decode(returnData, (ImmutableData[])); + IMMUTABLE_SIMULATOR_SYSTEM_CONTRACT.setImmutables(_newAddress, immutables); + } else { + require(value == 0, "The value must be zero if we do not call the constructor"); + // If we do not call the constructor, we need to set the constructed code hash. + ACCOUNT_CODE_STORAGE_SYSTEM_CONTRACT.storeAccountConstructedCodeHash(_newAddress, _bytecodeHash); + } + } +} diff --git a/l2/system-contracts/DefaultAccount.sol b/l2/system-contracts/DefaultAccount.sol index 69e2a00..2658947 100644 --- a/l2/system-contracts/DefaultAccount.sol +++ b/l2/system-contracts/DefaultAccount.sol @@ -5,6 +5,7 @@ pragma solidity ^0.8.0; import "./interfaces/IAccount.sol"; import "./libraries/TransactionHelper.sol"; import "./libraries/SystemContractHelper.sol"; +import "./libraries/EfficientCall.sol"; import {BOOTLOADER_FORMAL_ADDRESS, NONCE_HOLDER_SYSTEM_CONTRACT, DEPLOYER_SYSTEM_CONTRACT, INonceHolder} from "./Constants.sol"; /** @@ -15,24 +16,24 @@ import {BOOTLOADER_FORMAL_ADDRESS, NONCE_HOLDER_SYSTEM_CONTRACT, DEPLOYER_SYSTEM * @notice If it is delegate called always returns empty data, just like EOA does. */ contract DefaultAccount is IAccount { - using TransactionHelper for *; - - /** - * @dev Simulate the behavior of the EOA if the caller is not the bootloader. - * Essentially, for all non-bootloader callers halt the execution with empty return data. - * If all functions will use this modifier AND the contract will implement an empty payable fallback() - * then the contract will be indistinguishable from the EOA when called. - */ - modifier ignoreNonBootloader() { - if (msg.sender != BOOTLOADER_FORMAL_ADDRESS) { - // If function was called outside of the bootloader, behave like an EOA. - assembly { - return(0, 0) - } - } - // Continue execution if called from the bootloader. - _; - } + using TransactionHelper for *; + + /** + * @dev Simulate the behavior of the EOA if the caller is not the bootloader. + * Essentially, for all non-bootloader callers halt the execution with empty return data. + * If all functions will use this modifier AND the contract will implement an empty payable fallback() + * then the contract will be indistinguishable from the EOA when called. + */ + modifier ignoreNonBootloader() { + if (msg.sender != BOOTLOADER_FORMAL_ADDRESS) { + // If function was called outside of the bootloader, behave like an EOA. + assembly { + return(0, 0) + } + } + // Continue execution if called from the bootloader. + _; + } /** * @dev Simulate the behavior of the EOA if it is called via `delegatecall`. @@ -70,58 +71,40 @@ contract DefaultAccount is IAccount { magic = _validateTransaction(_suggestedSignedHash, _transaction); } - /// @notice Inner method for validating transaction and increasing the nonce - /// @param _suggestedSignedHash The hash of the transaction signed by the EOA - /// @param _transaction The transaction. - function _validateTransaction(bytes32 _suggestedSignedHash, Transaction calldata _transaction) internal returns (bytes4 magic) { - // Note, that nonce holder can only be called with "isSystem" flag. - SystemContractsCaller.systemCallWithPropagatedRevert( - uint32(gasleft()), - address(NONCE_HOLDER_SYSTEM_CONTRACT), - 0, - abi.encodeCall(INonceHolder.incrementMinNonceIfEquals, (_transaction.nonce)) - ); - - bytes32 txHash; - - // Even though for the transaction types present in the system right now, - // we always provide the suggested signed hash, this should not be - // always expected. In case the bootloader has no clue what the default hash - // is, the bytes32(0) will be supplied. - if(_suggestedSignedHash == bytes32(0)) { - txHash = _transaction.encodeHash(); - } else { - txHash = _suggestedSignedHash; - } - - if (_transaction.to == uint256(uint160(address(DEPLOYER_SYSTEM_CONTRACT)))) { - require(_transaction.data.length >= 4, "Invalid call to ContractDeployer"); - } - - // The fact there is are enough balance for the account - // should be checked explicitly to prevent user paying for fee for a - // transaction that wouldn't be included on Ethereum. - uint256 totalRequiredBalance = _transaction.totalRequiredBalance(); - require(totalRequiredBalance <= address(this).balance, "Not enough balance for fee + value"); - - bytes memory signature = _transaction.signature; - bool substitutedSignature = false; - if (_transaction.signature.length == 0) { - substitutedSignature = true; - - // substituting the signature with some signature-like array to make sure that the - // validation step uses as much steps as the validation with the correct signature provided - signature = new bytes(65); - signature[65] = bytes1(uint8(27)); - } + /// @notice Inner method for validating transaction and increasing the nonce + /// @param _suggestedSignedHash The hash of the transaction signed by the EOA + /// @param _transaction The transaction. + function _validateTransaction( + bytes32 _suggestedSignedHash, + Transaction calldata _transaction + ) internal returns (bytes4 magic) { + // Note, that nonce holder can only be called with "isSystem" flag. + SystemContractsCaller.systemCallWithPropagatedRevert( + uint32(gasleft()), + address(NONCE_HOLDER_SYSTEM_CONTRACT), + 0, + abi.encodeCall(INonceHolder.incrementMinNonceIfEquals, (_transaction.nonce)) + ); + + // Even though for the transaction types present in the system right now, + // we always provide the suggested signed hash, this should not be + // always expected. In case the bootloader has no clue what the default hash + // is, the bytes32(0) will be supplied. + bytes32 txHash = _suggestedSignedHash != bytes32(0) ? _suggestedSignedHash : _transaction.encodeHash(); + + // The fact there is are enough balance for the account + // should be checked explicitly to prevent user paying for fee for a + // transaction that wouldn't be included on Ethereum. + uint256 totalRequiredBalance = _transaction.totalRequiredBalance(); + require(totalRequiredBalance <= address(this).balance, "Not enough balance for fee + value"); if (_isValidSignature(txHash, _transaction.signature)) { magic = ACCOUNT_VALIDATION_SUCCESS_MAGIC; } else { magic = bytes4(0); } - } - + } + /// @notice Method called by the bootloader to execute the transaction. /// @param _transaction The transaction to execute. /// @dev It also accepts unused _txHash and _suggestedSignedHash parameters: @@ -135,17 +118,13 @@ contract DefaultAccount is IAccount { _execute(_transaction); } - /// @notice Method that should be used to initiate a transaction from this account - /// by an external call. This is not mandatory but should be implemented so that - /// it is always possible to execute transactions from L1 for this account. - /// @dev This method is basically validate + execute. + /// @notice Method that should be used to initiate a transaction from this account by an external call. + /// @dev The custom account is supposed to implement this method to initiate a transaction on behalf + /// of the account via L1 -> L2 communication. However, the default account can initiate a transaction + /// from L1, so we formally implement the interface method, but it doesn't execute any logic. /// @param _transaction The transaction to execute. - function executeTransactionFromOutside( - Transaction calldata _transaction - ) external payable override ignoreNonBootloader ignoreInDelegateCall { - // The account recalculate the hash on its own - _validateTransaction(bytes32(0), _transaction); - _execute(_transaction); + function executeTransactionFromOutside(Transaction calldata _transaction) external payable override { + // Behave the same as for fallback/receive, just execute nothing, returns nothing } /// @notice Inner method for executing a transaction. @@ -153,44 +132,48 @@ contract DefaultAccount is IAccount { function _execute(Transaction calldata _transaction) internal { address to = address(uint160(_transaction.to)); uint128 value = Utils.safeCastToU128(_transaction.value); - bytes memory data = _transaction.data; - - if (to == address(DEPLOYER_SYSTEM_CONTRACT)) { - uint32 gas = Utils.safeCastToU32(gasleft()); + bytes calldata data = _transaction.data; + uint32 gas = Utils.safeCastToU32(gasleft()); + + // Note, that the deployment method from the deployer contract can only be called with a "systemCall" flag. + bool isSystemCall; + if (to == address(DEPLOYER_SYSTEM_CONTRACT) && data.length >= 4) { + bytes4 selector = bytes4(data[:4]); + // Check that called function is the deployment method, + // the others deployer method is not supposed to be called from the default account. + isSystemCall = + selector == DEPLOYER_SYSTEM_CONTRACT.create.selector || + selector == DEPLOYER_SYSTEM_CONTRACT.create2.selector || + selector == DEPLOYER_SYSTEM_CONTRACT.createAccount.selector || + selector == DEPLOYER_SYSTEM_CONTRACT.create2Account.selector; + } + bool success = EfficientCall.rawCall(gas, to, value, data, isSystemCall); + if (!success) { + EfficientCall.propagateRevert(); + } + } - // Note, that the deployer contract can only be called - // with a "systemCall" flag. - SystemContractsCaller.systemCallWithPropagatedRevert(gas, to, value, data); - } else { - bool success; - assembly { - success := call(gas(), to, value, add(data, 0x20), mload(data), 0, 0) - } - require(success); + /// @notice Validation that the ECDSA signature of the transaction is correct. + /// @param _hash The hash of the transaction to be signed. + /// @param _signature The signature of the transaction. + /// @return EIP1271_SUCCESS_RETURN_VALUE if the signaure is correct. It reverts otherwise. + function _isValidSignature(bytes32 _hash, bytes memory _signature) internal view returns (bool) { + require(_signature.length == 65, "Signature length is incorrect"); + uint8 v; + bytes32 r; + bytes32 s; + // Signature loading code + // we jump 32 (0x20) as the first slot of bytes contains the length + // we jump 65 (0x41) per signature + // for v we load 32 bytes ending with v (the first 31 come from s) then apply a mask + assembly { + r := mload(add(_signature, 0x20)) + s := mload(add(_signature, 0x40)) + v := and(mload(add(_signature, 0x41)), 0xff) } - } - - /// @notice Validation that the ECDSA signature of the transaction is correct. - /// @param _hash The hash of the transaction to be signed. - /// @param _signature The signature of the transaction. - /// @return EIP1271_SUCCESS_RETURN_VALUE if the signaure is correct. It reverts otherwise. - function _isValidSignature(bytes32 _hash, bytes memory _signature) internal view returns (bool) { - require(_signature.length == 65, 'Signature length is incorrect'); - uint8 v; - bytes32 r; - bytes32 s; - // Signature loading code - // we jump 32 (0x20) as the first slot of bytes contains the length - // we jump 65 (0x41) per signature - // for v we load 32 bytes ending with v (the first 31 come from s) then apply a mask - assembly { - r := mload(add(_signature, 0x20)) - s := mload(add(_signature, 0x40)) - v := and(mload(add(_signature, 0x41)), 0xff) - } - require(v == 27 || v == 28, "v is neither 27 nor 28"); - - // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature + require(v == 27 || v == 28, "v is neither 27 nor 28"); + + // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most // signatures from current libraries generate a unique signature with an s-value in the lower half order. @@ -201,10 +184,10 @@ contract DefaultAccount is IAccount { // these malleable signatures as well. require(uint256(s) <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0, "Invalid s"); - address recoveredAddress = ecrecover(_hash, v, r, s); + address recoveredAddress = ecrecover(_hash, v, r, s); return recoveredAddress == address(this) && recoveredAddress != address(0); - } + } /// @notice Method for paying the bootloader for the transaction. /// @param _transaction The transaction for which the fee is paid. @@ -235,7 +218,7 @@ contract DefaultAccount is IAccount { _transaction.processPaymasterInput(); } - fallback() external { + fallback() external payable { // fallback of default account shouldn't be called by bootloader under no circumstances assert(msg.sender != BOOTLOADER_FORMAL_ADDRESS); diff --git a/l2/system-contracts/EmptyContract.sol b/l2/system-contracts/EmptyContract.sol new file mode 100644 index 0000000..75b788d --- /dev/null +++ b/l2/system-contracts/EmptyContract.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +/** + * @author Matter Labs + * @notice The "empty" contract that is put into some system contracts by default. + * @dev The bytecode of the contract is set by default for all addresses for which no other bytecodes are deployed. + */ +contract EmptyContract { + fallback() external payable {} + + receive() external payable {} +} diff --git a/l2/system-contracts/EventWriter.yul b/l2/system-contracts/EventWriter.yul new file mode 100644 index 0000000..208a399 --- /dev/null +++ b/l2/system-contracts/EventWriter.yul @@ -0,0 +1,167 @@ +/** + * @author Matter Labs + * @notice The contract responsible for decoding and writing events using low-level instructions. + * @dev The metadata and topics are passed via registers, and the first accessible register contains their number. + * The rest of the data is passed via calldata without copying. + */ +object "EventWriter" { + code { } + object "EventWriter_deployed" { + code { + //////////////////////////////////////////////////////////////// + // HELPER FUNCTIONS + //////////////////////////////////////////////////////////////// + + // For the documentation of the helper functions, please refer to + // the corresponding functions in the SystemContractHelper.sol. + + /// @notice Returns the 0-th extraAbiParam for the current call. + /// @dev It is equal to the value of the 2-th register at the start of the call. + function getExtraAbiData_0() -> extraAbiData { + extraAbiData := verbatim_0i_1o("get_global::extra_abi_data_0") + } + + /// @notice Returns the 1-th extraAbiParam for the current call. + /// @dev It is equal to the value of the 3-th register at the start of the call. + function getExtraAbiData_1() -> extraAbiData { + extraAbiData := verbatim_0i_1o("get_global::extra_abi_data_1") + } + + /// @notice Returns the 2-th extraAbiParam for the current call. + /// @dev It is equal to the value of the 4-th register at the start of the call. + function getExtraAbiData_2() -> extraAbiData { + extraAbiData := verbatim_0i_1o("get_global::extra_abi_data_2") + } + + /// @notice Returns the 3-th extraAbiParam for the current call. + /// @dev It is equal to the value of the 5-th register at the start of the call. + function getExtraAbiData_3() -> extraAbiData { + extraAbiData := verbatim_0i_1o("get_global::extra_abi_data_3") + } + + /// @notice Returns the 4-th extraAbiParam for the current call. + /// @dev It is equal to the value of the 6-th register at the start of the call. + function getExtraAbiData_4() -> extraAbiData { + extraAbiData := verbatim_0i_1o("get_global::extra_abi_data_4") + } + + /// @notice Returns the call flags for the current call. + /// @dev Call flags is the value of the first register at the start of the call. + /// @dev The zero bit of the callFlags indicates whether the call is + /// a constructor call. The first bit of the callFlags indicates whether + /// the call is a system one. + function getCallFlags() -> ret { + ret := verbatim_0i_1o("get_global::call_flags") + } + + /// @notice Initialize a new event + /// @param initializer The event initializing value + /// @param value1 The first topic or data chunk. + function eventInitialize(initializer, value1) { + pop(verbatim_2i_0o("event_initialize", initializer, value1)) + } + + /// @notice Continue writing the previously initialized event. + /// @param value1 The first topic or data chunk. + /// @param value2 The second topic or data chunk. + function eventWrite(value1, value2) { + pop(verbatim_2i_0o("event_write", value1, value2)) + } + + // @dev Write 1-th topic and first data chunk + function writeFirstTopicWithDataChunk() { + let topic1 := getExtraAbiData_1() + let dataChunk := calldataload(0) + eventWrite(topic1, dataChunk) + } + + // @dev Write 1-th and 2-th event topics + function writeFirstTwoTopics() { + let topic1 := getExtraAbiData_1() + let topic2 := getExtraAbiData_2() + eventWrite(topic1, topic2) + } + + // @dev Write 3-th topic and first data chunk + function writeThirdTopicWithDataChunk() { + let topic3 := getExtraAbiData_3() + let dataChunk := calldataload(0) + eventWrite(topic3, dataChunk) + } + + // @dev Write 3-th and 4-th event topics + function writeSecondTwoTopics() { + let topic3 := getExtraAbiData_3() + let topic4 := getExtraAbiData_4() + eventWrite(topic3, topic4) + } + + // @dev Reverts the call if a caller hasn't set the "isSystem" flag before calling + // Note: this method is different from the `onlySystemCall` modifier that is used in system contracts. + function onlySystemCall() { + let callFlags := getCallFlags() + let isSystemCall := and(callFlags, 2) + + if iszero(isSystemCall) { + revert(0, 0) + } + } + + //////////////////////////////////////////////////////////////// + // FALLBACK + //////////////////////////////////////////////////////////////// + + // Ensure that contract is called on purpose + onlySystemCall() + + let numberOfTopics := getExtraAbiData_0() + // Only 4 indexed fields are allowed, same as on EVM + if gt(numberOfTopics, 4) { + revert(0, 0) + } + + let dataLength := calldatasize() + // Increment number of topics to include the `msg.sender` as a topic + let initializer := add(shl(32, dataLength), add(numberOfTopics, 1)) + eventInitialize(initializer, caller()) + + // Save the pointer to written data + let dataCursor + + // Handle every case separately, to save gas on loops (alternative approach) + switch numberOfTopics + case 0 { + // Nothing to publish + } + case 1 { + writeFirstTopicWithDataChunk() + dataCursor := add(dataCursor, 0x20) + } + case 2 { + writeFirstTwoTopics() + } + case 3 { + writeFirstTwoTopics() + writeThirdTopicWithDataChunk() + dataCursor := add(dataCursor, 0x20) + } + case 4 { + writeFirstTwoTopics() + writeSecondTwoTopics() + } + default { + // Unreachable + revert(0, 0) + } + + // Write all the event data, two words at a time + for {} lt(dataCursor, dataLength) { + dataCursor := add(dataCursor, 0x40) + } { + let chunk1 := calldataload(dataCursor) + let chunk2 := calldataload(add(dataCursor, 0x20)) + eventWrite(chunk1, chunk2) + } + } + } +} diff --git a/l2/system-contracts/ImmutableSimulator.sol b/l2/system-contracts/ImmutableSimulator.sol new file mode 100644 index 0000000..e56f1ce --- /dev/null +++ b/l2/system-contracts/ImmutableSimulator.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "./interfaces/IImmutableSimulator.sol"; +import {DEPLOYER_SYSTEM_CONTRACT} from "./Constants.sol"; + +/** + * @author Matter Labs + * @notice System smart contract that simulates the behavior of immutable variables in Solidity. + * @dev The contract stores the immutable variables created during deployment by other contracts on his storage. + * @dev This simulator is needed so that smart contracts with the same Solidity code but different + * constructor parameters have the same bytecode. + * @dev The users are not expected to call this contract directly, only indirectly via the compiler simulations + * for the immutable variables in Solidity. + */ +contract ImmutableSimulator is IImmutableSimulator { + /// @dev mapping (contract address) => (index of immutable variable) => value + /// @notice that address uses `uint256` type to leave the option to introduce 32-byte address space in future. + mapping(uint256 => mapping(uint256 => bytes32)) internal immutableDataStorage; + + /// @notice Method that returns the immutable with a certain index for a user. + /// @param _dest The address which the immutable belongs to. + /// @param _index The index of the immutable. + /// @return The value of the immutables. + function getImmutable(address _dest, uint256 _index) external view override returns (bytes32) { + return immutableDataStorage[uint256(uint160(_dest))][_index]; + } + + /// @notice Method used by the contract deployer to store the immutables for an account + /// @param _dest The address which to store the immutables for. + /// @param _immutables The list of the immutables. + function setImmutables(address _dest, ImmutableData[] calldata _immutables) external override { + require(msg.sender == address(DEPLOYER_SYSTEM_CONTRACT), "Callable only by the deployer system contract"); + unchecked { + uint256 immutablesLength = _immutables.length; + for (uint256 i = 0; i < immutablesLength; ++i) { + uint256 index = _immutables[i].index; + bytes32 value = _immutables[i].value; + immutableDataStorage[uint256(uint160(_dest))][index] = value; + } + } + } +} diff --git a/l2/system-contracts/KnownCodesStorage.sol b/l2/system-contracts/KnownCodesStorage.sol new file mode 100644 index 0000000..b3cb637 --- /dev/null +++ b/l2/system-contracts/KnownCodesStorage.sol @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "./interfaces/IKnownCodesStorage.sol"; +import "./libraries/Utils.sol"; +import "./libraries/SystemContractHelper.sol"; +import {BOOTLOADER_FORMAL_ADDRESS, BYTECODE_COMPRESSOR_CONTRACT} from "./Constants.sol"; + +/** + * @author Matter Labs + * @notice The storage of this contract will basically serve as a mapping for the known code hashes. + * @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. + * words. And then the next 28 bytes is the truncated hash. + */ +contract KnownCodesStorage is IKnownCodesStorage { + modifier onlyBootloader() { + require(msg.sender == BOOTLOADER_FORMAL_ADDRESS, "Callable only by the bootloader"); + _; + } + + modifier onlyBytecodeCompressor() { + require(msg.sender == address(BYTECODE_COMPRESSOR_CONTRACT), "Callable only by the bytecode compressor"); + _; + } + + /// @notice The method that is used by the bootloader to mark several bytecode hashes as known. + /// @param _shouldSendToL1 Whether the bytecode should be sent on L1. + /// @param _hashes Hashes of the bytecodes to be marked as known. + function markFactoryDeps(bool _shouldSendToL1, bytes32[] calldata _hashes) external onlyBootloader { + unchecked { + uint256 hashesLen = _hashes.length; + for (uint256 i = 0; i < hashesLen; ++i) { + uint256 codeLengthInBytes = Utils.bytecodeLenInBytes(_hashes[i]); + _markBytecodeAsPublished(_hashes[i], 0, codeLengthInBytes, _shouldSendToL1); + } + } + } + + /// @notice The method used to mark a single bytecode hash as known. + /// @dev Only trusted contacts can call this method, currently only the bytecode compressor. + /// @param _bytecodeHash The hash of the bytecode that is marked as known. + /// @param _l1PreimageHash The hash of the preimage is be shown on L1 if zero - the full bytecode will be shown. + /// @param _l1PreimageBytesLen The length of the preimage in bytes. + function markBytecodeAsPublished( + bytes32 _bytecodeHash, + bytes32 _l1PreimageHash, + uint256 _l1PreimageBytesLen + ) external onlyBytecodeCompressor { + _markBytecodeAsPublished(_bytecodeHash, _l1PreimageHash, _l1PreimageBytesLen, false); + } + + /// @notice The method used to mark a single bytecode hash as known + /// @param _bytecodeHash The hash of the bytecode that is marked as known + /// @param _l1PreimageHash The hash of the preimage to be shown on L1 if zero - the full bytecode will be shown + /// @param _l1PreimageBytesLen The length of the preimage in bytes + /// @param _shouldSendToL1 Whether the bytecode should be sent on L1 + function _markBytecodeAsPublished( + bytes32 _bytecodeHash, + bytes32 _l1PreimageHash, + uint256 _l1PreimageBytesLen, + bool _shouldSendToL1 + ) internal { + if (getMarker(_bytecodeHash) == 0) { + _validateBytecode(_bytecodeHash); + + if (_shouldSendToL1) { + _sendBytecodeToL1(_bytecodeHash, _l1PreimageHash, _l1PreimageBytesLen); + } + + // Save as known, to not resend the log to L1 + assembly { + sstore(_bytecodeHash, 1) + } + + emit MarkedAsKnown(_bytecodeHash, _shouldSendToL1); + } + } + + /// @notice Method used for sending the bytecode (preimage for the bytecode hash) on L1. + /// @dev While bytecode must be visible to L1 observers, it's not necessary to disclose the whole raw bytecode. + /// To achieve this, it's possible to utilize compressed data using a known compression algorithm. Thus, the + /// L1 preimage data may differ from the raw bytecode. + /// @param _bytecodeHash The hash of the bytecode that is marked as known. + /// @param _l1PreimageHash The hash of the preimage to be shown on L1 if zero - the full bytecode will be shown. + /// @param _l1PreimageBytesLen The length of the preimage in bytes. + /// @dev This method sends a single L2->L1 log with the bytecodeHash and l1PreimageHash. It is the responsibility of the L1 + /// smart contracts to make sure that the preimage for this bytecode hash has been shown. + function _sendBytecodeToL1(bytes32 _bytecodeHash, bytes32 _l1PreimageHash, uint256 _l1PreimageBytesLen) internal { + // Burn gas to cover the cost of publishing pubdata on L1 + + // Get the cost of 1 pubdata byte in gas + uint256 meta = SystemContractHelper.getZkSyncMetaBytes(); + uint256 pricePerPubdataByteInGas = SystemContractHelper.getGasPerPubdataByteFromMeta(meta); + + // Calculate how many bytes of calldata will need to be transferred to L1. + // We published the data as ABI-encoded `bytes`, so we pay for: + // - bytecode length in bytes, rounded up to a multiple of 32 (it always is, because of the bytecode format) + // - 32 bytes of encoded offset + // - 32 bytes of encoded length + + uint256 gasToPay = (_l1PreimageBytesLen + 64) * pricePerPubdataByteInGas; + _burnGas(Utils.safeCastToU32(gasToPay)); + + // Send a log to L1 that bytecode should be known. + // L1 smart contract will check the availability of bytecodeHash preimage. + SystemContractHelper.toL1(true, _bytecodeHash, _l1PreimageHash); + } + + /// @notice Method used for burning a certain amount of gas + /// @param _gasToPay The number of gas to burn. + function _burnGas(uint32 _gasToPay) internal view { + bool precompileCallSuccess = SystemContractHelper.precompileCall( + 0, // The precompile parameters are formal ones. We only need the precompile call to burn gas. + _gasToPay + ); + require(precompileCallSuccess, "Failed to charge gas"); + } + + /// @notice Returns the marker stored for a bytecode hash. 1 means that the bytecode hash is known + /// and can be used for deploying contracts. 0 otherwise. + function getMarker(bytes32 _hash) public view override returns (uint256 marker) { + assembly { + marker := sload(_hash) + } + } + + /// @notice Validates the format of bytecodehash + /// @dev zk-circuit accepts & handles only valid format of bytecode hash, other input has undefined behavior + /// That's why we need to validate it + function _validateBytecode(bytes32 _bytecodeHash) internal pure { + uint8 version = uint8(_bytecodeHash[0]); + require(version == 1 && _bytecodeHash[1] == bytes1(0), "Incorrectly formatted bytecodeHash"); + + require(Utils.bytecodeLenInWords(_bytecodeHash) % 2 == 1, "Code length in words must be odd"); + } +} diff --git a/l2/system-contracts/L1Messenger.sol b/l2/system-contracts/L1Messenger.sol new file mode 100644 index 0000000..cab7a10 --- /dev/null +++ b/l2/system-contracts/L1Messenger.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "./interfaces/IL1Messenger.sol"; +import "./libraries/SystemContractHelper.sol"; +import "./libraries/EfficientCall.sol"; + +/** + * @author Matter Labs + * @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 IL1Messenger { + function sendToL1(bytes calldata _message) external override returns (bytes32 hash) { + hash = EfficientCall.keccak(_message); + + // Get cost of one byte pubdata in gas from context. + uint256 meta = SystemContractHelper.getZkSyncMetaBytes(); + uint32 gasPerPubdataBytes = SystemContractHelper.getGasPerPubdataByteFromMeta(meta); + + // Calculate how many bytes of calldata will need to be transferred to L1. + // We published the data as ABI-encoded `bytes`, so we pay for: + // - message length in bytes, rounded up to a multiple of 32 + // - 32 bytes of encoded offset + // - 32 bytes of encoded length + + uint256 pubdataLen; + unchecked { + pubdataLen = ((_message.length + 31) / 32) * 32 + 64; + } + uint256 gasToPay = pubdataLen * gasPerPubdataBytes; + + // Call precompile to burn gas to cover the cost of publishing pubdata to L1. + uint256 precompileParams = SystemContractHelper.packPrecompileParams(0, 0, 0, 0, 0); + bool precompileCallSuccess = SystemContractHelper.precompileCall( + precompileParams, + Utils.safeCastToU32(gasToPay) + ); + require(precompileCallSuccess); + + SystemContractHelper.toL1(true, bytes32(uint256(uint160(msg.sender))), hash); + + emit L1MessageSent(msg.sender, hash, _message); + } +} diff --git a/l2/system-contracts/L2EthToken.sol b/l2/system-contracts/L2EthToken.sol new file mode 100644 index 0000000..7829e3d --- /dev/null +++ b/l2/system-contracts/L2EthToken.sol @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import {IEthToken} from "./interfaces/IEthToken.sol"; +import {MSG_VALUE_SYSTEM_CONTRACT, DEPLOYER_SYSTEM_CONTRACT, BOOTLOADER_FORMAL_ADDRESS, L1_MESSENGER_CONTRACT} from "./Constants.sol"; +import {SystemContractHelper} from "./libraries/SystemContractHelper.sol"; +import {IMailbox} from "./interfaces/IMailbox.sol"; + +/** + * @author Matter Labs + * @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 by the bootloader and `MsgValueSimulator`/`ContractDeployer` system contracts + * to perform the balance changes while simulating the `msg.value` Ethereum behavior. + */ +contract L2EthToken is IEthToken { + /// @notice The balances of the users. + mapping(address => uint256) balance; + + /// @notice The total amount of tokens that have been minted. + uint256 public override totalSupply; + + modifier onlyBootloader() { + require(msg.sender == BOOTLOADER_FORMAL_ADDRESS, "Callable only by the bootloader"); + _; + } + + /// @notice Transfer tokens from one address to another. + /// @param _from The address to transfer the ETH from. + /// @param _to The address to transfer the ETH to. + /// @param _amount The amount of ETH in wei being transferred. + /// @dev This function can be called only by trusted system contracts. + /// @dev This function also emits "Transfer" event, which might be removed + /// later on. + function transferFromTo(address _from, address _to, uint256 _amount) external override { + require( + msg.sender == MSG_VALUE_SYSTEM_CONTRACT || + msg.sender == address(DEPLOYER_SYSTEM_CONTRACT) || + msg.sender == BOOTLOADER_FORMAL_ADDRESS, + "Only system contracts with special access can call this method" + ); + + uint256 fromBalance = balance[_from]; + require(fromBalance >= _amount, "Transfer amount exceeds balance"); + unchecked { + balance[_from] = fromBalance - _amount; + // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by + // decrementing then incrementing. + balance[_to] += _amount; + } + + emit Transfer(_from, _to, _amount); + } + + /// @notice Returns ETH balance of an account + /// @dev It takes `uint256` as an argument to be able to properly simulate the behaviour of the + /// Ethereum's `BALANCE` opcode that accepts uint256 as an argument and truncates any upper bits + /// @param _account The address of the account to return the balance of. + function balanceOf(uint256 _account) external view override returns (uint256) { + return balance[address(uint160(_account))]; + } + + /// @notice Increase the total supply of tokens and balance of the receiver. + /// @dev This method is only callable by the bootloader. + /// @param _account The address which to mint the funds to. + /// @param _amount The amount of ETH in wei to be minted. + function mint(address _account, uint256 _amount) external override onlyBootloader { + totalSupply += _amount; + balance[_account] += _amount; + emit Mint(_account, _amount); + } + + /// @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. + /// @dev The function accepts the `msg.value`. 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 withdraw(address _l1Receiver) external payable override { + uint256 amount = msg.value; + + // Silent burning of the ether + unchecked { + // This is safe, since this contract holds the ether balances, and if user + // send a `msg.value` it will be added to the contract (`this`) balance. + balance[address(this)] -= amount; + totalSupply -= amount; + } + + // Send the L2 log, a user could use it as proof of the withdrawal + bytes memory message = _getL1WithdrawMessage(_l1Receiver, amount); + L1_MESSENGER_CONTRACT.sendToL1(message); + + emit Withdrawal(msg.sender, _l1Receiver, 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(IMailbox.finalizeEthWithdrawal.selector, _to, _amount); + } + + /// @dev This method has not been stabilized and might be + /// removed later on. + function name() external pure override returns (string memory) { + return "Ether"; + } + + /// @dev This method has not been stabilized and might be + /// removed later on. + function symbol() external pure override returns (string memory) { + return "ETH"; + } + + /// @dev This method has not been stabilized and might be + /// removed later on. + function decimals() external pure override returns (uint8) { + return 18; + } +} diff --git a/l2/system-contracts/MsgValueSimulator.sol b/l2/system-contracts/MsgValueSimulator.sol new file mode 100644 index 0000000..fcaf35e --- /dev/null +++ b/l2/system-contracts/MsgValueSimulator.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "./libraries/Utils.sol"; +import "./libraries/EfficientCall.sol"; +import {SystemContractHelper, ISystemContract} from "./libraries/SystemContractHelper.sol"; +import {MSG_VALUE_SIMULATOR_IS_SYSTEM_BIT, ETH_TOKEN_SYSTEM_CONTRACT, MAX_MSG_VALUE} from "./Constants.sol"; + +/** + * @author Matter Labs + * @notice The contract responsible for simulating transactions with `msg.value` inside zkEVM. + * @dev It accepts value and whether the call should be system in the first extraAbi param and + * the address to call in the second extraAbi param, transfers the funds and uses `mimicCall` to continue the + * call with the same msg.sender. + */ +contract MsgValueSimulator is ISystemContract { + /// @notice Extract value, isSystemCall and to from the extraAbi params. + /// @dev The contract accepts value, the callee and whether the call should a system one via its ABI params. + /// @dev The first ABI param contains the value in the [0..127] bits. The 128th contains + /// the flag whether or not the call should be a system one. + /// The second ABI params contains the callee. + function _getAbiParams() internal view returns (uint256 value, bool isSystemCall, address to) { + value = SystemContractHelper.getExtraAbiData(0); + uint256 addressAsUint = SystemContractHelper.getExtraAbiData(1); + uint256 mask = SystemContractHelper.getExtraAbiData(2); + + isSystemCall = (mask & MSG_VALUE_SIMULATOR_IS_SYSTEM_BIT) != 0; + + to = address(uint160(addressAsUint)); + } + + fallback(bytes calldata _data) external payable onlySystemCall returns (bytes memory) { + (uint256 value, bool isSystemCall, address to) = _getAbiParams(); + + // Prevent mimic call to the MsgValueSimulator to prevent an unexpected change of callee. + require(to != address(this), "MsgValueSimulator calls itself"); + + if (value != 0) { + (bool success, ) = address(ETH_TOKEN_SYSTEM_CONTRACT).call( + abi.encodeCall(ETH_TOKEN_SYSTEM_CONTRACT.transferFromTo, (msg.sender, to, value)) + ); + + // If the transfer of ETH fails, we do the most Ethereum-like behaviour in such situation: revert(0,0) + if (!success) { + assembly { + revert(0, 0) + } + } + } + + // For the next call this `msg.value` will be used. + SystemContractHelper.setValueForNextFarCall(Utils.safeCastToU128(value)); + + return EfficientCall.mimicCall(gasleft(), to, _data, msg.sender, false, isSystemCall); + } +} diff --git a/l2/system-contracts/NonceHolder.sol b/l2/system-contracts/NonceHolder.sol new file mode 100644 index 0000000..74e1dd6 --- /dev/null +++ b/l2/system-contracts/NonceHolder.sol @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "./interfaces/INonceHolder.sol"; +import "./interfaces/IContractDeployer.sol"; +import {ISystemContract} from "./libraries/SystemContractHelper.sol"; +import {DEPLOYER_SYSTEM_CONTRACT} from "./Constants.sol"; + +/** + * @author Matter Labs + * @notice A contract used for managing nonces for accounts. Together with bootloader, + * this contract ensures that the pair (sender, nonce) is always unique, ensuring + * unique transaction hashes. + * @dev The account allows for both ascending growth in nonces and mapping nonces to specific + * stored values in them. + * The users can either marked a range of nonces by increasing the `minNonce`. This way all the nonces + * less than `minNonce` will become used. The other way to mark a certain 256-bit key as nonce is to set + * some value under it in this contract. + * @dev Apart from transaction nonces, this contract also stores the deployment nonce for accounts, that + * will be used for address derivation using CREATE. For the economy of space, this nonce is stored tightly + * packed with the `minNonce`. + * @dev The behavior of some of the methods depends on the nonce ordering of the account. Nonce ordering is a mere suggestion and all the checks that are present + * here serve more as a help to users to prevent from doing mistakes, rather than any invariants. + */ +contract NonceHolder is INonceHolder, ISystemContract { + uint256 constant DEPLOY_NONCE_MULTIPLIER = 2 ** 128; + /// The minNonce can be increased by at 2^32 at a time to prevent it from + /// overflowing beyond 2**128. + uint256 constant MAXIMAL_MIN_NONCE_INCREMENT = 2 ** 32; + + /// RawNonces for accounts are stored in format + /// minNonce + 2^128 * deploymentNonce, where deploymentNonce + /// is the nonce used for deploying smart contracts. + mapping(uint256 => uint256) internal rawNonces; + + /// Mapping of values under nonces for accounts. + /// The main key of the mapping is the 256-bit address of the account, while the + /// inner mapping is a mapping from a nonce to the value stored there. + mapping(uint256 => mapping(uint256 => uint256)) internal nonceValues; + + /// @notice Returns the current minimal nonce for account. + /// @param _address The account to return the minimal nonce for + /// @return The current minimal nonce for this account. + function getMinNonce(address _address) public view returns (uint256) { + uint256 addressAsKey = uint256(uint160(_address)); + (, uint256 minNonce) = _splitRawNonce(rawNonces[addressAsKey]); + + return minNonce; + } + + /// @notice Returns the raw version of the current minimal nonce + /// @dev It is equal to minNonce + 2^128 * deployment nonce. + /// @param _address The account to return the raw nonce for + /// @return The raw nonce for this account. + function getRawNonce(address _address) public view returns (uint256) { + uint256 addressAsKey = uint256(uint160(_address)); + return rawNonces[addressAsKey]; + } + + /// @notice Increases the minimal nonce for the msg.sender and returns the previous one. + /// @param _value The number by which to increase the minimal nonce for msg.sender. + /// @return oldMinNonce The value of the minimal nonce for msg.sender before the increase. + function increaseMinNonce(uint256 _value) public onlySystemCall returns (uint256 oldMinNonce) { + require(_value <= MAXIMAL_MIN_NONCE_INCREMENT, "The value for incrementing the nonce is too high"); + + uint256 addressAsKey = uint256(uint160(msg.sender)); + uint256 oldRawNonce = rawNonces[addressAsKey]; + + unchecked { + rawNonces[addressAsKey] = (oldRawNonce + _value); + } + + (, oldMinNonce) = _splitRawNonce(oldRawNonce); + } + + /// @notice Sets the nonce value `key` for the msg.sender as used. + /// @param _key The nonce key under which the value will be set. + /// @param _value The value to store under the _key. + /// @dev The value must be non-zero. + function setValueUnderNonce(uint256 _key, uint256 _value) public onlySystemCall { + IContractDeployer.AccountInfo memory accountInfo = DEPLOYER_SYSTEM_CONTRACT.getAccountInfo(msg.sender); + + require(_value != 0, "Nonce value can not be set to 0"); + // If an account has sequential nonce ordering, we enforce that the previous + // nonce has already been used. + if (accountInfo.nonceOrdering == IContractDeployer.AccountNonceOrdering.Sequential && _key != 0) { + require(isNonceUsed(msg.sender, _key - 1), "Previous nonce has not been used"); + } + + uint256 addressAsKey = uint256(uint160(msg.sender)); + + nonceValues[addressAsKey][_key] = _value; + + emit ValueSetUnderNonce(msg.sender, _key, _value); + } + + /// @notice Gets the value stored under a custom nonce for msg.sender. + /// @param _key The key under which to get the stored value. + /// @return The value stored under the `_key` for the msg.sender. + function getValueUnderNonce(uint256 _key) public view returns (uint256) { + uint256 addressAsKey = uint256(uint160(msg.sender)); + return nonceValues[addressAsKey][_key]; + } + + /// @notice A convenience method to increment the minimal nonce if it is equal + /// to the `_expectedNonce`. + /// @param _expectedNonce The expected minimal nonce for the account. + function incrementMinNonceIfEquals(uint256 _expectedNonce) external onlySystemCall { + uint256 addressAsKey = uint256(uint160(msg.sender)); + uint256 oldRawNonce = rawNonces[addressAsKey]; + + (, uint256 oldMinNonce) = _splitRawNonce(oldRawNonce); + require(oldMinNonce == _expectedNonce, "Incorrect nonce"); + + unchecked { + rawNonces[addressAsKey] = oldRawNonce + 1; + } + } + + /// @notice Returns the deployment nonce for the accounts used for CREATE opcode. + /// @param _address The address to return the deploy nonce of. + /// @return deploymentNonce The deployment nonce of the account. + function getDeploymentNonce(address _address) external view returns (uint256 deploymentNonce) { + uint256 addressAsKey = uint256(uint160(_address)); + (deploymentNonce, ) = _splitRawNonce(rawNonces[addressAsKey]); + + return deploymentNonce; + } + + /// @notice Increments the deployment nonce for the account and returns the previous one. + /// @param _address The address of the account which to return the deploy nonce for. + /// @return prevDeploymentNonce The deployment nonce at the time this function is called. + function incrementDeploymentNonce(address _address) external onlySystemCall returns (uint256 prevDeploymentNonce) { + require(msg.sender == address(DEPLOYER_SYSTEM_CONTRACT), ""); + uint256 addressAsKey = uint256(uint160(_address)); + uint256 oldRawNonce = rawNonces[addressAsKey]; + + unchecked { + rawNonces[addressAsKey] = (oldRawNonce + DEPLOY_NONCE_MULTIPLIER); + } + + (prevDeploymentNonce, ) = _splitRawNonce(oldRawNonce); + } + + function isNonceUsed(address _address, uint256 _nonce) public view returns (bool) { + uint256 addressAsKey = uint256(uint160(_address)); + return (_nonce < getMinNonce(_address) || nonceValues[addressAsKey][_nonce] > 0); + } + + /// @notice Checks and reverts based on whether the nonce is used (not used). + /// @param _address The address the nonce of which is being checked. + /// @param _key The nonce value which is tested. + /// @param _shouldBeUsed The flag for the method. If `true`, the method checks that whether this nonce + /// is marked as used and reverts if this is not the case. If `false`, this method will check that the nonce + /// has *not* been used yet, and revert otherwise. + /// @dev This method should be used by the bootloader. + function validateNonceUsage(address _address, uint256 _key, bool _shouldBeUsed) external view { + bool isUsed = isNonceUsed(_address, _key); + + if (isUsed && !_shouldBeUsed) { + revert("Reusing the same nonce twice"); + } else if (!isUsed && _shouldBeUsed) { + revert("The nonce was not set as used"); + } + } + + /// @notice Splits the raw nonce value into the deployment nonce and the minimal nonce. + /// @param _rawMinNonce The value of the raw minimal nonce (equal to minNonce + deploymentNonce* 2**128). + /// @return deploymentNonce and minNonce. + function _splitRawNonce(uint256 _rawMinNonce) internal pure returns (uint256 deploymentNonce, uint256 minNonce) { + deploymentNonce = _rawMinNonce / DEPLOY_NONCE_MULTIPLIER; + minNonce = _rawMinNonce % DEPLOY_NONCE_MULTIPLIER; + } +} diff --git a/l2/system-contracts/SystemContext.sol b/l2/system-contracts/SystemContext.sol index 64b9942..6f80cf4 100644 --- a/l2/system-contracts/SystemContext.sol +++ b/l2/system-contracts/SystemContext.sol @@ -28,9 +28,8 @@ contract SystemContext is ISystemContext { /// @dev It is updated before each transaction by the bootloader uint256 public gasPrice; - /// @notice The current block's gasLimit (gasLimit in Ethereum terms). - /// @dev Currently set to some dummy value, it will be changed closer to mainnet. - uint256 public blockGasLimit = (1 << 30); + /// @notice The current block's gasLimit. + uint256 public blockGasLimit = type(uint32).max; /// @notice The `block.coinbase` in the current transaction. /// @dev For the support of coinbase, we will the bootloader formal address for now @@ -61,7 +60,7 @@ contract SystemContext is ISystemContext { origin = _newOrigin; } - /// @notice Set the current tx origin. + /// @notice Set the the current gas price. /// @param _gasPrice The new tx gasPrice. function setGasPrice(uint256 _gasPrice) external onlyBootloader { gasPrice = _gasPrice; @@ -113,7 +112,7 @@ contract SystemContext is ISystemContext { uint256 _baseFee ) external onlyBootloader { (uint256 currentBlockNumber, uint256 currentBlockTimestamp) = getBlockNumberAndTimestamp(); - require(_newTimestamp >= currentBlockTimestamp, "Timestamps should be incremental"); + require(_newTimestamp > currentBlockTimestamp, "Timestamps should be incremental"); require(currentBlockNumber + 1 == _expectedNewNumber, "The provided block number is not correct"); blockHash[currentBlockNumber] = _prevBlockHash; diff --git a/l2/system-contracts/interfaces/IAccount.sol b/l2/system-contracts/interfaces/IAccount.sol index c85a4ac..cb54f31 100644 --- a/l2/system-contracts/interfaces/IAccount.sol +++ b/l2/system-contracts/interfaces/IAccount.sol @@ -8,14 +8,14 @@ bytes4 constant ACCOUNT_VALIDATION_SUCCESS_MAGIC = IAccount.validateTransaction. interface IAccount { /// @notice Called by the bootloader to validate that an account agrees to process the transaction - /// (and potentially pay for it). + /// (and potentially pay for it). /// @param _txHash The hash of the transaction to be used in the explorer /// @param _suggestedSignedHash The hash of the transaction is signed by EOAs /// @param _transaction The transaction itself - /// @return magic The magic value that should be equal to the signature of this function + /// @return magic The magic value that should be equal to the signature of this function /// if the user agrees to proceed with the transaction. /// @dev The developer should strive to preserve as many steps as possible both for valid - /// and invalid transactions as this very method is also used during the gas fee estimation + /// and invalid transactions as this very method is also used during the gas fee estimation /// (without some of the necessary data, e.g. signature). function validateTransaction( bytes32 _txHash, diff --git a/l2/system-contracts/interfaces/IAccountCodeStorage.sol b/l2/system-contracts/interfaces/IAccountCodeStorage.sol index 5ed763e..977e7e1 100644 --- a/l2/system-contracts/interfaces/IAccountCodeStorage.sol +++ b/l2/system-contracts/interfaces/IAccountCodeStorage.sol @@ -5,6 +5,8 @@ pragma solidity ^0.8.0; interface IAccountCodeStorage { function storeAccountConstructingCodeHash(address _address, bytes32 _hash) external; + function storeAccountConstructedCodeHash(address _address, bytes32 _hash) external; + function markAccountCodeHashAsConstructed(address _address) external; function getRawCodeHash(address _address) external view returns (bytes32 codeHash); diff --git a/l2/system-contracts/interfaces/IBytecodeCompressor.sol b/l2/system-contracts/interfaces/IBytecodeCompressor.sol new file mode 100644 index 0000000..1958f88 --- /dev/null +++ b/l2/system-contracts/interfaces/IBytecodeCompressor.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +interface IBytecodeCompressor { + function publishCompressedBytecode( + bytes calldata _bytecode, + bytes calldata _rawCompressedData + ) external payable returns (bytes32 bytecodeHash); +} diff --git a/l2/system-contracts/interfaces/IContractDeployer.sol b/l2/system-contracts/interfaces/IContractDeployer.sol index c0335ef..d21b917 100644 --- a/l2/system-contracts/interfaces/IContractDeployer.sol +++ b/l2/system-contracts/interfaces/IContractDeployer.sol @@ -2,22 +2,21 @@ pragma solidity ^0.8.0; - interface IContractDeployer { /// @notice Defines the version of the account abstraction protocol /// that a contract claims to follow. /// - `None` means that the account is just a contract and it should never be interacted /// with as a custom account /// - `Version1` means that the account follows the first version of the account abstraction protocol - enum AccountAbstractionVersion { + enum AccountAbstractionVersion { None, Version1 } /// @notice Defines the nonce ordering used by the account - /// - `Sequential` means that it is expected that the nonces are monotonic and increment by 1 + /// - `Sequential` means that it is expected that the nonces are monotonic and increment by 1 /// at a time (the same as EOAs). - /// - `Arbitrary` means that the nonces for the accounts can be arbitrary. The operator + /// - `Arbitrary` means that the nonces for the accounts can be arbitrary. The operator /// should serve the transactions from such an account on a first-come-first-serve basis. /// @dev This ordering is more of a suggestion to the operator on how the AA expects its transactions /// to be processed and is not considered as a system invariant. @@ -30,19 +29,23 @@ interface IContractDeployer { AccountAbstractionVersion supportedAAVersion; AccountNonceOrdering nonceOrdering; } - + event ContractDeployed( address indexed deployerAddress, bytes32 indexed bytecodeHash, address indexed contractAddress ); + event AccountNonceOrderingUpdated(address indexed accountAddress, AccountNonceOrdering nonceOrdering); + + event AccountVersionUpdated(address indexed accountAddress, AccountAbstractionVersion aaVersion); + function getNewAddressCreate2( address _sender, bytes32 _bytecodeHash, bytes32 _salt, bytes calldata _input - ) external pure returns (address newAddress); + ) external view returns (address newAddress); function getNewAddressCreate(address _sender, uint256 _senderNonce) external pure returns (address newAddress); @@ -78,13 +81,11 @@ interface IContractDeployer { ) external payable returns (address newAddress); /// @notice Returns the information about a certain AA. - function getAccountInfo( - address _address - ) external view returns (AccountInfo memory info); + function getAccountInfo(address _address) external view returns (AccountInfo memory info); /// @notice Can be called by an account to update its account version function updateAccountVersion(AccountAbstractionVersion _version) external; - /// @notice Can be called by an account to update its nonce ordering + /// @notice Can be called by an account to update its nonce ordering function updateNonceOrdering(AccountNonceOrdering _nonceOrdering) external; } diff --git a/l2/system-contracts/interfaces/IKnownCodesStorage.sol b/l2/system-contracts/interfaces/IKnownCodesStorage.sol index 02cce17..c56327a 100644 --- a/l2/system-contracts/interfaces/IKnownCodesStorage.sol +++ b/l2/system-contracts/interfaces/IKnownCodesStorage.sol @@ -7,5 +7,11 @@ interface IKnownCodesStorage { function markFactoryDeps(bool _shouldSendToL1, bytes32[] calldata _hashes) external; + function markBytecodeAsPublished( + bytes32 _bytecodeHash, + bytes32 _l1PreimageHash, + uint256 _l1PreimageBytesLen + ) external; + function getMarker(bytes32 _hash) external view returns (uint256); } diff --git a/l2/system-contracts/interfaces/INonceHolder.sol b/l2/system-contracts/interfaces/INonceHolder.sol index c7c3a6d..ebddfb0 100644 --- a/l2/system-contracts/interfaces/INonceHolder.sol +++ b/l2/system-contracts/interfaces/INonceHolder.sol @@ -11,6 +11,8 @@ pragma solidity ^0.8.0; * for the transaction. */ interface INonceHolder { + event ValueSetUnderNonce(address indexed accountAddress, uint256 indexed key, uint256 value); + /// @dev Returns the current minimal nonce for account. function getMinNonce(address _address) external view returns (uint256); @@ -39,4 +41,7 @@ interface INonceHolder { /// @dev Determines whether a certain nonce has been already used for an account. function validateNonceUsage(address _address, uint256 _key, bool _shouldBeUsed) external view; + + /// @dev Returns whether a nonce has been used for an account. + function isNonceUsed(address _address, uint256 _nonce) external view returns (bool); } diff --git a/l2/system-contracts/interfaces/IPaymaster.sol b/l2/system-contracts/interfaces/IPaymaster.sol index 82b51a4..cc15193 100644 --- a/l2/system-contracts/interfaces/IPaymaster.sol +++ b/l2/system-contracts/interfaces/IPaymaster.sol @@ -19,11 +19,11 @@ interface IPaymaster { /// @param _suggestedSignedHash The hash of the transaction that is signed by an EOA /// @param _transaction The transaction itself. /// @return magic The value that should be equal to the signature of the validateAndPayForPaymasterTransaction - /// if the paymaster agrees to pay for the transaction. - /// @return context The "context" of the transaction: an array of bytes of length at most 1024 bytes, which will be + /// if the paymaster agrees to pay for the transaction. + /// @return context The "context" of the transaction: an array of bytes of length at most 1024 bytes, which will be /// passed to the `postTransaction` method of the account. /// @dev The developer should strive to preserve as many steps as possible both for valid - /// and invalid transactions as this very method is also used during the gas fee estimation + /// and invalid transactions as this very method is also used during the gas fee estimation /// (without some of the necessary data, e.g. signature). function validateAndPayForPaymasterTransaction( bytes32 _txHash, diff --git a/l2/system-contracts/libraries/EfficientCall.sol b/l2/system-contracts/libraries/EfficientCall.sol new file mode 100644 index 0000000..53dc9b3 --- /dev/null +++ b/l2/system-contracts/libraries/EfficientCall.sol @@ -0,0 +1,281 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +pragma solidity ^0.8.0; + +import "./SystemContractHelper.sol"; +import "./Utils.sol"; +import {SHA256_SYSTEM_CONTRACT, KECCAK256_SYSTEM_CONTRACT} from "../Constants.sol"; + +/** + * @author Matter Labs + * @notice This library is used to perform ultra-efficient calls using zkEVM-specific features. + * @dev EVM calls always accept a memory slice as input and return a memory slice as output. + * Therefore, even if the user has a ready-made calldata slice, they still need to copy it to memory + * before calling. This is especially inefficient for large inputs (proxies, multi-calls, etc.). + * In turn, zkEVM operates over a fat pointer, which is a set of (memory page, offset, start, length) in the memory/calldata/returndata. + * This allows forwarding the calldata slice as is, without copying it to memory. + * @dev Fat pointer is not just an integer, it is an extended data type supported on the VM level. + * zkEVM creates the wellformed fat pointers for all the calldata/returndata regions, later + * the contract may manipulate the already created fat pointers to forward a slice of the data, but not + * to create new fat pointers! + * @dev The allowed operation on fat pointers are: + * 1. `ptr.add` - Transforms `ptr.offset` into `ptr.offset + u32(_value)`. If overflow happens then it panics. + * 2. `ptr.sub` - Transforms `ptr.offset` into `ptr.offset - u32(_value)`. If underflow happens then it panics. + * 3. `ptr.pack` - Do the concatenation between the lowest 128 bits of the pointer itself and the highest 128 bits of `_value`. It is typically used to prepare the ABI for external calls. + * 4. `ptr.shrink` - Transforms `ptr.length` into `ptr.length - u32(_shrink)`. If underflow happens then it panics. + * @dev The call opcodes accept the fat pointer and change it to its canonical form before passing it to the child call + * 1. `ptr.start` is transformed into `ptr.offset + ptr.start` + * 2. `ptr.length` is transformed into `ptr.length - ptr.offset` + * 3. `ptr.offset` is transformed into `0` + */ +library EfficientCall { + /// @notice Call the `keccak256` without copying calldata to memory. + /// @param _data The preimage data. + /// @return The `keccak256` hash. + function keccak(bytes calldata _data) internal view returns (bytes32) { + bytes memory returnData = staticCall(gasleft(), KECCAK256_SYSTEM_CONTRACT, _data); + require(returnData.length == 32, "keccak256 returned invalid data"); + return bytes32(returnData); + } + + /// @notice Call the `sha256` precompile without copying calldata to memory. + /// @param _data The preimage data. + /// @return The `sha256` hash. + function sha(bytes calldata _data) internal view returns (bytes32) { + bytes memory returnData = staticCall(gasleft(), SHA256_SYSTEM_CONTRACT, _data); + require(returnData.length == 32, "sha returned invalid data"); + return bytes32(returnData); + } + + /// @notice Perform a `call` without copying calldata to memory. + /// @param _gas The gas to use for the call. + /// @param _address The address to call. + /// @param _value The `msg.value` to send. + /// @param _data The calldata to use for the call. + /// @param _isSystem Whether the call should contain the `isSystem` flag. + /// @return returnData The copied to memory return data. + function call( + uint256 _gas, + address _address, + uint256 _value, + bytes calldata _data, + bool _isSystem + ) internal returns (bytes memory returnData) { + bool success = rawCall(_gas, _address, _value, _data, _isSystem); + returnData = _verifyCallResult(success); + } + + /// @notice Perform a `staticCall` without copying calldata to memory. + /// @param _gas The gas to use for the call. + /// @param _address The address to call. + /// @param _data The calldata to use for the call. + /// @return returnData The copied to memory return data. + function staticCall( + uint256 _gas, + address _address, + bytes calldata _data + ) internal view returns (bytes memory returnData) { + bool success = rawStaticCall(_gas, _address, _data); + returnData = _verifyCallResult(success); + } + + /// @notice Perform a `delegateCall` without copying calldata to memory. + /// @param _gas The gas to use for the call. + /// @param _address The address to call. + /// @param _data The calldata to use for the call. + /// @return returnData The copied to memory return data. + function delegateCall( + uint256 _gas, + address _address, + bytes calldata _data + ) internal returns (bytes memory returnData) { + bool success = rawDelegateCall(_gas, _address, _data); + returnData = _verifyCallResult(success); + } + + /// @notice Perform a `mimicCall` (a call with custom msg.sender) without copying calldata to memory. + /// @param _gas The gas to use for the call. + /// @param _address The address to call. + /// @param _data The calldata to use for the call. + /// @param _whoToMimic The `msg.sender` for the next call. + /// @param _isConstructor Whether the call should contain the `isConstructor` flag. + /// @param _isSystem Whether the call should contain the `isSystem` flag. + /// @return returnData The copied to memory return data. + function mimicCall( + uint256 _gas, + address _address, + bytes calldata _data, + address _whoToMimic, + bool _isConstructor, + bool _isSystem + ) internal returns (bytes memory returnData) { + bool success = rawMimicCall(_gas, _address, _data, _whoToMimic, _isConstructor, _isSystem); + returnData = _verifyCallResult(success); + } + + /// @notice Perform a `call` without copying calldata to memory. + /// @param _gas The gas to use for the call. + /// @param _address The address to call. + /// @param _value The `msg.value` to send. + /// @param _data The calldata to use for the call. + /// @param _isSystem Whether the call should contain the `isSystem` flag. + /// @return success whether the call was successful. + function rawCall( + uint256 _gas, + address _address, + uint256 _value, + bytes calldata _data, + bool _isSystem + ) internal returns (bool success) { + if (_value == 0) { + _loadFarCallABIIntoActivePtr(_gas, _data, false, _isSystem); + + address callAddr = RAW_FAR_CALL_BY_REF_CALL_ADDRESS; + assembly { + success := call(_address, callAddr, 0, 0, 0xFFFF, 0, 0) + } + } else { + _loadFarCallABIIntoActivePtr(_gas, _data, false, true); + + // If there is provided `msg.value` call the `MsgValueSimulator` to forward ether. + address msgValueSimulator = MSG_VALUE_SYSTEM_CONTRACT; + address callAddr = SYSTEM_CALL_BY_REF_CALL_ADDRESS; + // We need to supply the mask to the MsgValueSimulator to denote + // that the call should be a system one. + uint256 forwardMask = _isSystem ? MSG_VALUE_SIMULATOR_IS_SYSTEM_BIT : 0; + + assembly { + success := call(msgValueSimulator, callAddr, _value, _address, 0xFFFF, forwardMask, 0) + } + } + } + + /// @notice Perform a `staticCall` without copying calldata to memory. + /// @param _gas The gas to use for the call. + /// @param _address The address to call. + /// @param _data The calldata to use for the call. + /// @return success whether the call was successful. + function rawStaticCall(uint256 _gas, address _address, bytes calldata _data) internal view returns (bool success) { + _loadFarCallABIIntoActivePtr(_gas, _data, false, false); + + address callAddr = RAW_FAR_CALL_BY_REF_CALL_ADDRESS; + assembly { + success := staticcall(_address, callAddr, 0, 0xFFFF, 0, 0) + } + } + + /// @notice Perform a `delegatecall` without copying calldata to memory. + /// @param _gas The gas to use for the call. + /// @param _address The address to call. + /// @param _data The calldata to use for the call. + /// @return success whether the call was successful. + function rawDelegateCall(uint256 _gas, address _address, bytes calldata _data) internal returns (bool success) { + _loadFarCallABIIntoActivePtr(_gas, _data, false, false); + + address callAddr = RAW_FAR_CALL_BY_REF_CALL_ADDRESS; + assembly { + success := delegatecall(_address, callAddr, 0, 0xFFFF, 0, 0) + } + } + + /// @notice Perform a `mimicCall` (call with custom msg.sender) without copying calldata to memory. + /// @param _gas The gas to use for the call. + /// @param _address The address to call. + /// @param _data The calldata to use for the call. + /// @param _whoToMimic The `msg.sender` for the next call. + /// @param _isConstructor Whether the call should contain the `isConstructor` flag. + /// @param _isSystem Whether the call should contain the `isSystem` flag. + /// @return success whether the call was successful. + /// @dev If called not in kernel mode, it will result in a revert (enforced by the VM) + function rawMimicCall( + uint256 _gas, + address _address, + bytes calldata _data, + address _whoToMimic, + bool _isConstructor, + bool _isSystem + ) internal returns (bool success) { + _loadFarCallABIIntoActivePtr(_gas, _data, _isConstructor, _isSystem); + + address callAddr = MIMIC_CALL_BY_REF_CALL_ADDRESS; + uint256 cleanupMask = ADDRESS_MASK; + assembly { + // Clearing values before usage in assembly, since Solidity + // doesn't do it by default + _whoToMimic := and(_whoToMimic, cleanupMask) + + success := call(_address, callAddr, 0, 0, _whoToMimic, 0, 0) + } + } + + /// @dev Verify that a low-level call was successful, and revert if it wasn't, by bubbling the revert reason. + /// @param _success Whether the call was successful. + /// @return returnData The copied to memory return data. + function _verifyCallResult(bool _success) private pure returns (bytes memory returnData) { + if (_success) { + uint256 size; + assembly { + size := returndatasize() + } + + returnData = new bytes(size); + assembly { + returndatacopy(add(returnData, 0x20), 0, size) + } + } else { + propagateRevert(); + } + } + + /// @dev Propagate the revert reason from the current call to the caller. + function propagateRevert() internal pure { + assembly { + let size := returndatasize() + returndatacopy(0, 0, size) + revert(0, size) + } + } + + /// @dev Load the far call ABI into active ptr, that will be used for the next call by reference. + /// @param _gas The gas to be passed to the call. + /// @param _data The calldata to be passed to the call. + /// @param _isConstructor Whether the call is a constructor call. + /// @param _isSystem Whether the call is a system call. + function _loadFarCallABIIntoActivePtr( + uint256 _gas, + bytes calldata _data, + bool _isConstructor, + bool _isSystem + ) private view { + SystemContractHelper.loadCalldataIntoActivePtr(); + + // Currently, zkEVM considers the pointer valid if(ptr.offset < ptr.length || (ptr.length == 0 && ptr.offset == 0)), otherwise panics. + // So, if the data is empty we need to make the `ptr.length = ptr.offset = 0`, otherwise follow standard logic. + if (_data.length == 0) { + // Safe to cast, offset is never bigger than `type(uint32).max` + SystemContractHelper.ptrShrinkIntoActive(uint32(msg.data.length)); + } else { + uint256 dataOffset; + assembly { + dataOffset := _data.offset + } + + // Safe to cast, offset is never bigger than `type(uint32).max` + SystemContractHelper.ptrAddIntoActive(uint32(dataOffset)); + // Safe to cast, `data.length` is never bigger than `type(uint32).max` + uint32 shrinkTo = uint32(msg.data.length - (_data.length + dataOffset)); + SystemContractHelper.ptrShrinkIntoActive(shrinkTo); + } + + uint32 gas = Utils.safeCastToU32(_gas); + uint256 farCallAbi = SystemContractsCaller.getFarCallABIWithEmptyFatPointer( + gas, + // Only rollup is supported for now + 0, + CalldataForwardingMode.ForwardFatPointer, + _isConstructor, + _isSystem + ); + SystemContractHelper.ptrPackIntoActivePtr(farCallAbi); + } +} diff --git a/l2/system-contracts/libraries/RLPEncoder.sol b/l2/system-contracts/libraries/RLPEncoder.sol index 3f76a38..ddef39a 100644 --- a/l2/system-contracts/libraries/RLPEncoder.sol +++ b/l2/system-contracts/libraries/RLPEncoder.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 +// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; @@ -38,13 +38,13 @@ library RLPEncoder { } } - /// @notice Encodes the size of bytes in RLP format. - /// @param _len The length of the bytes to encode. It has a `uint64` type since as larger values are not supported. - /// NOTE: panics if the length is 1 since the length encoding is ambiguous in this case. - function encodeNonSingleBytesLen(uint64 _len) internal pure returns (bytes memory) { - assert(_len != 1); - return _encodeLength(_len, 0x80); - } + /// @notice Encodes the size of bytes in RLP format. + /// @param _len The length of the bytes to encode. It has a `uint64` type since as larger values are not supported. + /// NOTE: panics if the length is 1 since the length encoding is ambiguous in this case. + function encodeNonSingleBytesLen(uint64 _len) internal pure returns (bytes memory) { + assert(_len != 1); + return _encodeLength(_len, 0x80); + } /// @notice Encodes the size of list items in RLP format. /// @param _len The length of the bytes to encode. It has a `uint64` type since as larger values are not supported. diff --git a/l2/system-contracts/libraries/SystemContractHelper.sol b/l2/system-contracts/libraries/SystemContractHelper.sol index c5eebcc..738ab74 100644 --- a/l2/system-contracts/libraries/SystemContractHelper.sol +++ b/l2/system-contracts/libraries/SystemContractHelper.sol @@ -67,6 +67,52 @@ library SystemContractHelper { } } + /// @notice Provide a compiler hint, by placing calldata fat pointer into virtual `ACTIVE_PTR`, + /// that can be manipulated by `ptr.add`/`ptr.sub`/`ptr.pack`/`ptr.shrink` later. + /// @dev This allows making a call by forwarding calldata pointer to the child call. + /// It is a much more efficient way to forward calldata, than standard EVM bytes copying. + function loadCalldataIntoActivePtr() internal view { + address callAddr = LOAD_CALLDATA_INTO_ACTIVE_PTR_CALL_ADDRESS; + assembly { + pop(staticcall(0, callAddr, 0, 0xFFFF, 0, 0)) + } + } + + /// @notice Compiler simulation of the `ptr.pack` opcode for the virtual `ACTIVE_PTR` pointer. + /// @dev Do the concatenation between lowest part of `ACTIVE_PTR` and highest part of `_farCallAbi` + /// forming packed fat pointer for a far call or ret ABI when necessary. + /// Note: Panics if the lowest 128 bits of `_farCallAbi` are not zeroes. + function ptrPackIntoActivePtr(uint256 _farCallAbi) internal view { + address callAddr = PTR_PACK_INTO_ACTIVE_CALL_ADDRESS; + assembly { + pop(staticcall(_farCallAbi, callAddr, 0, 0xFFFF, 0, 0)) + } + } + + /// @notice Compiler simulation of the `ptr.add` opcode for the virtual `ACTIVE_PTR` pointer. + /// @dev Transforms `ACTIVE_PTR.offset` into `ACTIVE_PTR.offset + u32(_value)`. If overflow happens then it panics. + function ptrAddIntoActive(uint32 _value) internal view { + address callAddr = PTR_ADD_INTO_ACTIVE_CALL_ADDRESS; + uint256 cleanupMask = UINT32_MASK; + assembly { + // Clearing input params as they are not cleaned by Solidity by default + _value := and(_value, cleanupMask) + pop(staticcall(_value, callAddr, 0, 0xFFFF, 0, 0)) + } + } + + /// @notice Compiler simulation of the `ptr.shrink` opcode for the virtual `ACTIVE_PTR` pointer. + /// @dev Transforms `ACTIVE_PTR.length` into `ACTIVE_PTR.length - u32(_shrink)`. If underflow happens then it panics. + function ptrShrinkIntoActive(uint32 _shrink) internal view { + address callAddr = PTR_SHRINK_INTO_ACTIVE_CALL_ADDRESS; + uint256 cleanupMask = UINT32_MASK; + assembly { + // Clearing input params as they are not cleaned by Solidity by default + _shrink := and(_shrink, cleanupMask) + pop(staticcall(_shrink, callAddr, 0, 0xFFFF, 0, 0)) + } + } + /// @notice packs precompile parameters into one word /// @param _inputMemoryOffset The memory offset in 32-byte words for the input data for calling the precompile. /// @param _inputMemoryLength The length of the input data in words. @@ -125,95 +171,6 @@ library SystemContractHelper { } } - /// @notice Perform a `mimicCall`, i.e. a call with custom msg.sender. - /// @param to The address to call - /// @param whoToMimic The `msg.sender` for the next call. - /// @param data The calldata - /// @param isConstructor Whether the call should contain the `isConstructor` flag. - /// @param isSystem Whether the call should contain the `isSystem` flag. - /// @return The returndata if the call was successful. Reverts otherwise. - /// @dev If called not in kernel mode, it will result in a revert (enforced by the VM) - function mimicCall( - address to, - address whoToMimic, - bytes memory data, - bool isConstructor, - bool isSystem - ) internal returns (bytes memory) { - bool success = rawMimicCall( - to, - whoToMimic, - data, - isConstructor, - isSystem - ); - - uint256 size; - assembly { - size := returndatasize() - } - if(!success) { - assembly { - returndatacopy(0, 0, size) - revert(0, size) - } - } - - bytes memory result = new bytes(size); - assembly { - mstore(result, size) - returndatacopy(add(result, 0x20), 0, size) - } - return result; - } - - /// @notice Perform a `mimicCall`, i.e. a call with custom msg.sender. - /// @param to The address to call - /// @param whoToMimic The `msg.sender` for the next call. - /// @param data The calldata - /// @param isConstructor Whether the call should contain the `isConstructor` flag. - /// @param isSystem Whether the call should contain the `isSystem` flag. - /// @return success whether the call was successful. - /// @dev If called not in kernel mode, it will result in a revert (enforced by the VM) - function rawMimicCall( - address to, - address whoToMimic, - bytes memory data, - bool isConstructor, - bool isSystem - ) internal returns (bool success) { - address callAddr = MIMIC_CALL_CALL_ADDRESS; - - uint32 dataStart; - assembly { - dataStart := add(data, 0x20) - } - uint32 dataLength = Utils.safeCastToU32(data.length); - uint32 gas = Utils.safeCastToU32(gasleft()); - - uint256 farCallAbi = SystemContractsCaller.getFarCallABI( - 0, - 0, - dataStart, - dataLength, - gas, - // Only rollup is supported for now - 0, - CalldataForwardingMode.UseHeap, - isConstructor, - isSystem - ); - - uint256 cleanupMask = ADDRESS_MASK; - assembly { - // Clearing values before usage in assembly, since Solidity - // doesn't do it by default - whoToMimic := and(whoToMimic, cleanupMask) - - success := call(to, callAddr, 0, farCallAbi, whoToMimic, 0, 0) - } - } - /// @notice Initialize a new event. /// @param initializer The event initializing value. /// @param value1 The first topic or data chunk. diff --git a/l2/system-contracts/libraries/SystemContractsCaller.sol b/l2/system-contracts/libraries/SystemContractsCaller.sol index df175da..594b5b3 100644 --- a/l2/system-contracts/libraries/SystemContractsCaller.sol +++ b/l2/system-contracts/libraries/SystemContractsCaller.sol @@ -72,12 +72,7 @@ library SystemContractsCaller { /// @param data The calldata. /// @return success Whether the transaction has been successful. /// @dev Note, that the `isSystem` flag can only be set when calling system contracts. - function systemCall( - uint32 gasLimit, - address to, - uint256 value, - bytes memory data - ) internal returns (bool success) { + function systemCall(uint32 gasLimit, address to, uint256 value, bytes memory data) internal returns (bool success) { address callAddr = SYSTEM_CALL_CALL_ADDRESS; uint32 dataStart; @@ -106,7 +101,7 @@ library SystemContractsCaller { } } else { address msgValueSimulator = MSG_VALUE_SYSTEM_CONTRACT; - // We need to supply the mask to the MsgValueSimulator to denote + // We need to supply the mask to the MsgValueSimulator to denote // that the call should be a system one. uint256 forwardMask = MSG_VALUE_SIMULATOR_IS_SYSTEM_BIT; @@ -139,7 +134,7 @@ library SystemContractsCaller { returnData = new bytes(size); assembly { - returndatacopy(add(returnData, 0x20), 0, size) + returndatacopy(add(returnData, 0x20), 0, size) } } @@ -148,7 +143,7 @@ library SystemContractsCaller { /// @param to The address to call. /// @param value The value to pass with the transaction. /// @param data The calldata. - /// @return returnData The returndata of the transaction. In case the transaction reverts, the error + /// @return returnData The returndata of the transaction. In case the transaction reverts, the error /// bubbles up to the parent frame. /// @dev Note, that the `isSystem` flag can only be set when calling system contracts. function systemCallWithPropagatedRevert( @@ -160,7 +155,7 @@ library SystemContractsCaller { bool success; (success, returnData) = systemCallWithReturndata(gasLimit, to, value, data); - if(!success) { + if (!success) { assembly { let size := mload(returnData) revert(add(returnData, 0x20), size) @@ -226,18 +221,46 @@ library SystemContractsCaller { bool isConstructorCall, bool isSystemCall ) internal pure returns (uint256 farCallAbi) { + // Fill in the call parameter fields + farCallAbi = getFarCallABIWithEmptyFatPointer( + gasPassed, + shardId, + forwardingMode, + isConstructorCall, + isSystemCall + ); + // Fill in the fat pointer fields farCallAbi |= dataOffset; farCallAbi |= (uint256(memoryPage) << 32); farCallAbi |= (uint256(dataStart) << 64); farCallAbi |= (uint256(dataLength) << 96); - farCallAbi |= (uint256(gasPassed) << 192); - farCallAbi |= (uint256(forwardingMode) << 224); - farCallAbi |= (uint256(shardId) << 232); + } + + /// @notice Calculates the packed representation of the FarCallABI with zero fat pointer fields. + /// @param gasPassed The gas to pass with the call. + /// @param shardId Of the account to call. Currently only 0 is supported. + /// @param forwardingMode The forwarding mode to use: + /// - provide CalldataForwardingMode.UseHeap when using your current memory + /// - provide CalldataForwardingMode.ForwardFatPointer when using custom pointer. + /// @param isConstructorCall Whether the call will be a call to the constructor + /// (ignored when the caller is not a system contract). + /// @param isSystemCall Whether the call will have the `isSystem` flag. + /// @return farCallAbiWithEmptyFatPtr The far call ABI with zero fat pointer fields. + function getFarCallABIWithEmptyFatPointer( + uint32 gasPassed, + uint8 shardId, + CalldataForwardingMode forwardingMode, + bool isConstructorCall, + bool isSystemCall + ) internal pure returns (uint256 farCallAbiWithEmptyFatPtr) { + farCallAbiWithEmptyFatPtr |= (uint256(gasPassed) << 192); + farCallAbiWithEmptyFatPtr |= (uint256(forwardingMode) << 224); + farCallAbiWithEmptyFatPtr |= (uint256(shardId) << 232); if (isConstructorCall) { - farCallAbi |= (1 << 240); + farCallAbiWithEmptyFatPtr |= (1 << 240); } if (isSystemCall) { - farCallAbi |= (1 << 248); + farCallAbiWithEmptyFatPtr |= (1 << 248); } } } diff --git a/l2/system-contracts/libraries/TransactionHelper.sol b/l2/system-contracts/libraries/TransactionHelper.sol index b12f7f2..5d5cc6c 100644 --- a/l2/system-contracts/libraries/TransactionHelper.sol +++ b/l2/system-contracts/libraries/TransactionHelper.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 +// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; @@ -9,6 +9,7 @@ import "../interfaces/IPaymasterFlow.sol"; import "../interfaces/IContractDeployer.sol"; import {ETH_TOKEN_SYSTEM_CONTRACT, BOOTLOADER_FORMAL_ADDRESS} from "../Constants.sol"; import "./RLPEncoder.sol"; +import "./EfficientCall.sol"; /// @dev The type id of zkSync's EIP-712-signed transaction. uint8 constant EIP_712_TX_TYPE = 0x71; @@ -77,8 +78,7 @@ library TransactionHelper { using SafeERC20 for IERC20; /// @notice The EIP-712 typehash for the contract's domain - bytes32 constant EIP712_DOMAIN_TYPEHASH = - keccak256("EIP712Domain(string name,string version,uint256 chainId)"); + bytes32 constant EIP712_DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,string version,uint256 chainId)"); bytes32 constant EIP712_TRANSACTION_TYPE_HASH = keccak256( @@ -91,18 +91,12 @@ library TransactionHelper { /// @dev This method assumes that address is Ether either if the address is 0 (for convenience) /// or if the address is the address of the L2EthToken system contract. function isEthToken(uint256 _addr) internal pure returns (bool) { - return - _addr == uint256(uint160(address(ETH_TOKEN_SYSTEM_CONTRACT))) || - _addr == 0; + return _addr == uint256(uint160(address(ETH_TOKEN_SYSTEM_CONTRACT))) || _addr == 0; } /// @notice Calculate the suggested signed hash of the transaction, /// i.e. the hash that is signed by EOAs and is recommended to be signed by other accounts. - function encodeHash(Transaction calldata _transaction) - internal - view - returns (bytes32 resultHash) - { + function encodeHash(Transaction calldata _transaction) internal view returns (bytes32 resultHash) { if (_transaction.txType == LEGACY_TX_TYPE) { resultHash = _encodeHashLegacyTransaction(_transaction); } else if (_transaction.txType == EIP_712_TX_TYPE) { @@ -120,11 +114,7 @@ library TransactionHelper { /// @notice Encode hash of the zkSync native transaction type. /// @return keccak256 hash of the EIP-712 encoded representation of transaction - function _encodeHashEIP712Transaction(Transaction calldata _transaction) - private - view - returns (bytes32) - { + function _encodeHashEIP712Transaction(Transaction calldata _transaction) private view returns (bytes32) { bytes32 structHash = keccak256( abi.encode( EIP712_TRANSACTION_TYPE_HASH, @@ -138,34 +128,22 @@ library TransactionHelper { _transaction.paymaster, _transaction.nonce, _transaction.value, - keccak256(_transaction.data), + EfficientCall.keccak(_transaction.data), keccak256(abi.encodePacked(_transaction.factoryDeps)), - keccak256(_transaction.paymasterInput) + EfficientCall.keccak(_transaction.paymasterInput) ) ); bytes32 domainSeparator = keccak256( - abi.encode( - EIP712_DOMAIN_TYPEHASH, - keccak256("zkSync"), - keccak256("2"), - block.chainid - ) + abi.encode(EIP712_DOMAIN_TYPEHASH, keccak256("zkSync"), keccak256("2"), block.chainid) ); - return - keccak256( - abi.encodePacked("\x19\x01", domainSeparator, structHash) - ); + return keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash)); } /// @notice Encode hash of the legacy transaction type. /// @return keccak256 of the serialized RLP encoded representation of transaction - function _encodeHashLegacyTransaction(Transaction calldata _transaction) - private - view - returns (bytes32) - { + function _encodeHashLegacyTransaction(Transaction calldata _transaction) private view returns (bytes32) { // Hash of legacy transactions are encoded as one of the: // - RLP(nonce, gasPrice, gasLimit, to, value, data, chainId, 0, 0) // - RLP(nonce, gasPrice, gasLimit, to, value, data) @@ -177,12 +155,8 @@ library TransactionHelper { // Encode `gasPrice` and `gasLimit` together to prevent "stack too deep error". bytes memory encodedGasParam; { - bytes memory encodedGasPrice = RLPEncoder.encodeUint256( - _transaction.maxFeePerGas - ); - bytes memory encodedGasLimit = RLPEncoder.encodeUint256( - _transaction.gasLimit - ); + bytes memory encodedGasPrice = RLPEncoder.encodeUint256(_transaction.maxFeePerGas); + bytes memory encodedGasLimit = RLPEncoder.encodeUint256(_transaction.gasLimit); encodedGasParam = bytes.concat(encodedGasPrice, encodedGasLimit); } @@ -196,9 +170,7 @@ library TransactionHelper { uint64 txDataLen = uint64(_transaction.data.length); if (txDataLen != 1) { // If the length is not equal to one, then only using the length can it be encoded definitely. - encodedDataLength = RLPEncoder.encodeNonSingleBytesLen( - txDataLen - ); + encodedDataLength = RLPEncoder.encodeNonSingleBytesLen(txDataLen); } else if (_transaction.data[0] >= 0x80) { // If input is a byte in [0x80, 0xff] range, RLP encoding will concatenates 0x81 with the byte. encodedDataLength = hex"81"; @@ -243,11 +215,7 @@ library TransactionHelper { /// @notice Encode hash of the EIP2930 transaction type. /// @return keccak256 of the serialized RLP encoded representation of transaction - function _encodeHashEIP2930Transaction(Transaction calldata _transaction) - private - view - returns (bytes32) - { + function _encodeHashEIP2930Transaction(Transaction calldata _transaction) private view returns (bytes32) { // Hash of EIP2930 transactions is encoded the following way: // H(0x01 || RLP(chain_id, nonce, gas_price, gas_limit, destination, amount, data, access_list)) // @@ -280,9 +248,7 @@ library TransactionHelper { uint64 txDataLen = uint64(_transaction.data.length); if (txDataLen != 1) { // If the length is not equal to one, then only using the length can it be encoded definitely. - encodedDataLength = RLPEncoder.encodeNonSingleBytesLen( - txDataLen - ); + encodedDataLength = RLPEncoder.encodeNonSingleBytesLen(txDataLen); } else if (_transaction.data[0] >= 0x80) { // If input is a byte in [0x80, 0xff] range, RLP encoding will concatenates 0x81 with the byte. encodedDataLength = hex"81"; @@ -319,11 +285,7 @@ library TransactionHelper { /// @notice Encode hash of the EIP1559 transaction type. /// @return keccak256 of the serialized RLP encoded representation of transaction - function _encodeHashEIP1559Transaction(Transaction calldata _transaction) - private - view - returns (bytes32) - { + function _encodeHashEIP1559Transaction(Transaction calldata _transaction) private view returns (bytes32) { // Hash of EIP1559 transactions is encoded the following way: // H(0x02 || RLP(chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, destination, amount, data, access_list)) // @@ -358,9 +320,7 @@ library TransactionHelper { uint64 txDataLen = uint64(_transaction.data.length); if (txDataLen != 1) { // If the length is not equal to one, then only using the length can it be encoded definitely. - encodedDataLength = RLPEncoder.encodeNonSingleBytesLen( - txDataLen - ); + encodedDataLength = RLPEncoder.encodeNonSingleBytesLen(txDataLen); } else if (_transaction.data[0] >= 0x80) { // If input is a byte in [0x80, 0xff] range, RLP encoding will concatenates 0x81 with the byte. encodedDataLength = hex"81"; @@ -399,14 +359,9 @@ library TransactionHelper { /// for tokens, etc. For more information on the expected behavior, check out /// the "Paymaster flows" section in the documentation. function processPaymasterInput(Transaction calldata _transaction) internal { - require( - _transaction.paymasterInput.length >= 4, - "The standard paymaster input must be at least 4 bytes long" - ); + require(_transaction.paymasterInput.length >= 4, "The standard paymaster input must be at least 4 bytes long"); - bytes4 paymasterInputSelector = bytes4( - _transaction.paymasterInput[0:4] - ); + bytes4 paymasterInputSelector = bytes4(_transaction.paymasterInput[0:4]); if (paymasterInputSelector == IPaymasterFlow.approvalBased.selector) { require( _transaction.paymasterInput.length >= 68, @@ -415,16 +370,10 @@ library TransactionHelper { // While the actual data consists of address, uint256 and bytes data, // the data is needed only for the paymaster, so we ignore it here for the sake of optimization - (address token, uint256 minAllowance) = abi.decode( - _transaction.paymasterInput[4:68], - (address, uint256) - ); + (address token, uint256 minAllowance) = abi.decode(_transaction.paymasterInput[4:68], (address, uint256)); address paymaster = address(uint160(_transaction.paymaster)); - uint256 currentAllowance = IERC20(token).allowance( - address(this), - paymaster - ); + uint256 currentAllowance = IERC20(token).allowance(address(this), paymaster); if (currentAllowance < minAllowance) { // Some tokens, e.g. USDT require that the allowance is firsty set to zero // and only then updated to the new value. @@ -442,10 +391,7 @@ library TransactionHelper { /// @notice Pays the required fee for the transaction to the bootloader. /// @dev Currently it pays the maximum amount "_transaction.maxFeePerGas * _transaction.gasLimit", /// it will change in the future. - function payToTheBootloader(Transaction calldata _transaction) - internal - returns (bool success) - { + function payToTheBootloader(Transaction calldata _transaction) internal returns (bool success) { address bootloaderAddr = BOOTLOADER_FORMAL_ADDRESS; uint256 amount = _transaction.maxFeePerGas * _transaction.gasLimit; @@ -455,13 +401,13 @@ library TransactionHelper { } // Returns the balance required to process the transaction. - function totalRequiredBalance(Transaction calldata _transaction) internal pure returns (uint256 requiredBalance) { - if(address(uint160(_transaction.paymaster)) != address(0)) { - // Paymaster pays for the fee - requiredBalance = _transaction.value; - } else { - // The user should have enough balance for both the fee and the value of the transaction - requiredBalance = _transaction.maxFeePerGas * _transaction.gasLimit + _transaction.value; - } + function totalRequiredBalance(Transaction calldata _transaction) internal pure returns (uint256 requiredBalance) { + if (address(uint160(_transaction.paymaster)) != address(0)) { + // Paymaster pays for the fee + requiredBalance = _transaction.value; + } else { + // The user should have enough balance for both the fee and the value of the transaction + requiredBalance = _transaction.maxFeePerGas * _transaction.gasLimit + _transaction.value; + } } } diff --git a/l2/system-contracts/libraries/UnsafeBytesCalldata.sol b/l2/system-contracts/libraries/UnsafeBytesCalldata.sol new file mode 100644 index 0000000..a5fac2c --- /dev/null +++ b/l2/system-contracts/libraries/UnsafeBytesCalldata.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +pragma solidity ^0.8.0; + +/** + * @author Matter Labs + * @dev The library provides a set of functions that help read data from calldata bytes. + * @dev Each of the functions accepts the `bytes calldata` and the offset where data should be read and returns a value of a certain type. + * + * @dev WARNING! + * 1) Functions don't check the length of the bytes array, so it can go out of bounds. + * The user of the library must check for bytes length before using any functions from the library! + * + * 2) Read variables are not cleaned up - https://docs.soliditylang.org/en/v0.8.16/internals/variable_cleanup.html. + * Using data in inline assembly can lead to unexpected behavior! + */ +library UnsafeBytesCalldata { + function readUint16(bytes calldata _bytes, uint256 _start) internal pure returns (uint16 result) { + assembly { + let offset := sub(_bytes.offset, 30) + result := calldataload(add(offset, _start)) + } + } + + function readUint64(bytes calldata _bytes, uint256 _start) internal pure returns (uint64 result) { + assembly { + let offset := sub(_bytes.offset, 24) + result := calldataload(add(offset, _start)) + } + } +} diff --git a/l2/system-contracts/libraries/Utils.sol b/l2/system-contracts/libraries/Utils.sol index 65352c9..d1f219d 100644 --- a/l2/system-contracts/libraries/Utils.sol +++ b/l2/system-contracts/libraries/Utils.sol @@ -1,6 +1,8 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 +// SPDX-License-Identifier: MIT pragma solidity >=0.8.0; +import "./EfficientCall.sol"; + /** * @author Matter Labs * @dev Common utilities used in zkSync system contracts @@ -11,7 +13,7 @@ library Utils { 0x00ff000000000000000000000000000000000000000000000000000000000000; /// @dev Bit mask to set the "isConstructor" marker in the bytecode hash - bytes32 constant SET_IS_CONSTRUCTOR_MARKER_BIT_MASK = + bytes32 constant SET_IS_CONSTRUCTOR_MARKER_BIT_MASK = 0x0001000000000000000000000000000000000000000000000000000000000000; function safeCastToU128(uint256 _x) internal pure returns (uint128) { @@ -44,8 +46,13 @@ library Utils { } } + /// @notice Denotes whether bytecode hash corresponds to a contract that already constructed + function isContractConstructed(bytes32 _bytecodeHash) internal pure returns (bool) { + return _bytecodeHash[1] == 0x00; + } + /// @notice Denotes whether bytecode hash corresponds to a contract that is on constructor or has already been constructed - function isContractConsructing(bytes32 _bytecodeHash) internal pure returns (bool) { + function isContractConstructing(bytes32 _bytecodeHash) internal pure returns (bool) { return _bytecodeHash[1] == 0x01; } @@ -63,4 +70,27 @@ library Utils { function constructedBytecodeHash(bytes32 _bytecodeHash) internal pure returns (bytes32) { return _bytecodeHash & ~IS_CONSTRUCTOR_BYTECODE_HASH_BIT_MASK; } + + /// @notice Validate the bytecode format and calculate its hash. + /// @param _bytecode The bytecode to hash. + /// @return hashedBytecode The 32-byte hash of the bytecode. + /// Note: The function reverts the execution if the bytecode has non expected format: + /// - Bytecode bytes length is not a multiple of 32 + /// - Bytecode bytes length is not less than 2^21 bytes (2^16 words) + /// - Bytecode words length is not odd + function hashL2Bytecode(bytes calldata _bytecode) internal view returns (bytes32 hashedBytecode) { + // Note that the length of the bytecode must be provided in 32-byte words. + require(_bytecode.length % 32 == 0, "po"); + + uint256 bytecodeLenInWords = _bytecode.length / 32; + require(bytecodeLenInWords < 2 ** 16, "pp"); // bytecode length must be less than 2^16 words + require(bytecodeLenInWords % 2 == 1, "pr"); // bytecode length in words must be odd + hashedBytecode = + EfficientCall.sha(_bytecode) & + 0x00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; + // Setting the version of the hash + hashedBytecode = (hashedBytecode | bytes32(uint256(1 << 248))); + // Setting the length + hashedBytecode = hashedBytecode | bytes32(bytecodeLenInWords << 224); + } } diff --git a/l2/system-contracts/precompiles/Ecrecover.yul b/l2/system-contracts/precompiles/Ecrecover.yul new file mode 100644 index 0000000..8f8889d --- /dev/null +++ b/l2/system-contracts/precompiles/Ecrecover.yul @@ -0,0 +1,97 @@ +/** + * @author Matter Labs + * @notice The contract used to emulate EVM's ecrecover precompile. + * @dev It uses `precompileCall` to call the zkEVM built-in precompiles. + */ +object "Ecrecover" { + code { } + object "Ecrecover_deployed" { + code { + //////////////////////////////////////////////////////////////// + // CONSTANTS + //////////////////////////////////////////////////////////////// + + // Group order of secp256k1, see https://en.bitcoin.it/wiki/Secp256k1 + function SECP256K1_GROUP_SIZE() -> ret { + ret := 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 + } + + /// @dev The gas cost of processing ecrecover circuit precompile. + function ECRECOVER_GAS_COST() -> ret { + ret := 1112 + } + + //////////////////////////////////////////////////////////////// + // HELPER FUNCTIONS + //////////////////////////////////////////////////////////////// + + // @dev Packs precompile parameters into one word. + // Note: functions expect to work with 32/64 bits unsigned integers. + // Caller should ensure the type matching before! + function unsafePackPrecompileParams( + uint32_inputOffsetInWords, + uint32_inputLengthInWords, + uint32_outputOffsetInWords, + uint32_outputLengthInWords, + uint64_perPrecompileInterpreted + ) -> rawParams { + rawParams := uint32_inputOffsetInWords + rawParams := or(rawParams, shl(32, uint32_inputLengthInWords)) + rawParams := or(rawParams, shl(64, uint32_outputOffsetInWords)) + rawParams := or(rawParams, shl(96, uint32_outputLengthInWords)) + rawParams := or(rawParams, shl(192, uint64_perPrecompileInterpreted)) + } + + /// @dev Executes the `precompileCall` opcode. + function precompileCall(precompileParams, gasToBurn) -> ret { + // Compiler simulation for calling `precompileCall` opcode + ret := verbatim_2i_1o("precompile", precompileParams, gasToBurn) + } + + //////////////////////////////////////////////////////////////// + // FALLBACK + //////////////////////////////////////////////////////////////// + + let digest := calldataload(0) + let v := calldataload(32) + let r := calldataload(64) + let s := calldataload(96) + + // Validate the input by the yellow paper rules (Appendix E. Precompiled contracts) + let vIsInvalid := iszero(or(eq(v, 27), eq(v, 28))) + let sIsInvalid := or(eq(s, 0), gt(s, sub(SECP256K1_GROUP_SIZE(), 1))) + let rIsInvalid := or(eq(r, 0), gt(r, sub(SECP256K1_GROUP_SIZE(), 1))) + + if or(vIsInvalid, or(sIsInvalid, rIsInvalid)) { + return(0, 0) + } + + // Store the data in memory, so the ecrecover circuit will read it + mstore(0, digest) + mstore(32, sub(v, 27)) + mstore(64, r) + mstore(96, s) + + let precompileParams := unsafePackPrecompileParams( + 0, // input offset in words + 4, // input length in words (the signed digest, v, r, s) + 0, // output offset in words + 2, // output length in words (success, signer) + 0 // No special meaning, ecrecover circuit doesn't check this value + ) + let gasToPay := ECRECOVER_GAS_COST() + + // Check whether the call is successfully handled by the ecrecover circuit + let success := precompileCall(precompileParams, gasToPay) + let internalSuccess := mload(0) + + switch and(success, internalSuccess) + case 0 { + return(0, 0) + } + default { + return(32, 32) + } + } + } +} diff --git a/l2/system-contracts/precompiles/Keccak256.yul b/l2/system-contracts/precompiles/Keccak256.yul new file mode 100644 index 0000000..15c3902 --- /dev/null +++ b/l2/system-contracts/precompiles/Keccak256.yul @@ -0,0 +1,125 @@ +/** + * @author Matter Labs + * @notice The contract used to emulate EVM's keccak256 opcode. + * @dev It accepts the data to be hashed, pad it by the specification + * and uses `precompileCall` to call the zkEVM built-in precompiles. + * @dev Thus keccak256 precompile circuit operates over padded data to perform efficient sponge round computation. + */ +object "Keccak256" { + code { } + object "Keccak256_deployed" { + code { + //////////////////////////////////////////////////////////////// + // CONSTANTS + //////////////////////////////////////////////////////////////// + + /// @dev The size of the processing keccak256 block in bytes. + function BLOCK_SIZE() -> ret { + ret := 136 + } + + /// @dev The gas cost of processing one keccak256 round. + function KECCAK_ROUND_GAS_COST() -> ret { + ret := 40 + } + + //////////////////////////////////////////////////////////////// + // HELPER FUNCTIONS + //////////////////////////////////////////////////////////////// + + // @dev Packs precompile parameters into one word. + // Note: functions expect to work with 32/64 bits unsigned integers. + // Caller should ensure the type matching before! + function unsafePackPrecompileParams( + uint32_inputOffsetInWords, + uint32_inputLengthInWords, + uint32_outputOffsetInWords, + uint32_outputLengthInWords, + uint64_perPrecompileInterpreted + ) -> rawParams { + rawParams := uint32_inputOffsetInWords + rawParams := or(rawParams, shl(32, uint32_inputLengthInWords)) + rawParams := or(rawParams, shl(64, uint32_outputOffsetInWords)) + rawParams := or(rawParams, shl(96, uint32_outputLengthInWords)) + rawParams := or(rawParams, shl(192, uint64_perPrecompileInterpreted)) + } + + /// @dev Executes the `precompileCall` opcode. + function precompileCall(precompileParams, gasToBurn) -> ret { + // Compiler simulation for calling `precompileCall` opcode + ret := verbatim_2i_1o("precompile", precompileParams, gasToBurn) + } + + //////////////////////////////////////////////////////////////// + // FALLBACK + //////////////////////////////////////////////////////////////// + + // Copy calldata to memory for pad it + let bytesSize := calldatasize() + calldatacopy(0, 0, bytesSize) + + let precompileParams + let gasToPay + + // Most often keccak256 is called with "short" input, so optimize it as a special case. + // NOTE: we consider the special case for sizes less than `BLOCK_SIZE() - 1`, so + // there is only one round and it is and padding can be done branchless + switch lt(bytesSize, sub(BLOCK_SIZE(), 1)) + case true { + // Write the 0x01 after the payload bytes and 0x80 at last byte of padded bytes + mstore(bytesSize, 0x0100000000000000000000000000000000000000000000000000000000000000) + mstore( + sub(BLOCK_SIZE(), 1), + 0x8000000000000000000000000000000000000000000000000000000000000000 + ) + + precompileParams := unsafePackPrecompileParams( + 0, // input offset in words + 5, // input length in words (Math.ceil(136/32) = 5) + 0, // output offset in words + 1, // output length in words + 1 // number of rounds + ) + gasToPay := KECCAK_ROUND_GAS_COST() + } + default { + let padLen := sub(BLOCK_SIZE(), mod(bytesSize, BLOCK_SIZE())) + let paddedByteSize := add(bytesSize, padLen) + + switch eq(padLen, 1) + case true { + // Write 0x81 after the payload bytes + mstore(bytesSize, 0x8100000000000000000000000000000000000000000000000000000000000000) + } + default { + // Write the 0x01 after the payload bytes and 0x80 at last byte of padded bytes + mstore(bytesSize, 0x0100000000000000000000000000000000000000000000000000000000000000) + mstore( + sub(paddedByteSize, 1), + 0x8000000000000000000000000000000000000000000000000000000000000000 + ) + } + + let numRounds := div(paddedByteSize, BLOCK_SIZE()) + precompileParams := unsafePackPrecompileParams( + 0, // input offset in words + div(add(paddedByteSize, 31), 32), // input length in words (safe to pass, never exceed `type(uint32).max`) + 0, // output offset in words + 1, // output length in words + numRounds // number of rounds (safe to pass, never exceed `type(uint64).max`) + ) + gasToPay := mul(KECCAK_ROUND_GAS_COST(), numRounds) + } + + let success := precompileCall(precompileParams, gasToPay) + + switch success + case 0 { + revert(0, 0) + } + default { + return(0, 32) + } + } + } +} diff --git a/l2/system-contracts/precompiles/SHA256.yul b/l2/system-contracts/precompiles/SHA256.yul new file mode 100644 index 0000000..d594f55 --- /dev/null +++ b/l2/system-contracts/precompiles/SHA256.yul @@ -0,0 +1,100 @@ +/** + * @author Matter Labs + * @notice The contract used to emulate EVM's sha256 precompile. + * @dev It accepts the data to be hashed, pad it by the specification + * and uses `precompileCall` to call the zkEVM built-in precompiles. + * @dev Thus sha256 precompile circuit operates over padded data to perform efficient sponge round computation. + */ +object "SHA256" { + code { } + object "SHA256_deployed" { + code { + //////////////////////////////////////////////////////////////// + // CONSTANTS + //////////////////////////////////////////////////////////////// + + /// @dev The size of the processing sha256 block in bytes. + function BLOCK_SIZE() -> ret { + ret := 64 + } + + /// @dev The gas cost of processing one sha256 round. + function SHA256_ROUND_GAS_COST() -> ret { + ret := 7 + } + + //////////////////////////////////////////////////////////////// + // HELPER FUNCTIONS + //////////////////////////////////////////////////////////////// + + // @dev Packs precompile parameters into one word. + // Note: functions expect to work with 32/64 bits unsigned integers. + // Caller should ensure the type matching before! + function unsafePackPrecompileParams( + uint32_inputOffsetInWords, + uint32_inputLengthInWords, + uint32_outputOffsetInWords, + uint32_outputLengthInWords, + uint64_perPrecompileInterpreted + ) -> rawParams { + rawParams := uint32_inputOffsetInWords + rawParams := or(rawParams, shl(32, uint32_inputLengthInWords)) + rawParams := or(rawParams, shl(64, uint32_outputOffsetInWords)) + rawParams := or(rawParams, shl(96, uint32_outputLengthInWords)) + rawParams := or(rawParams, shl(192, uint64_perPrecompileInterpreted)) + } + + /// @dev Executes the `precompileCall` opcode. + function precompileCall(precompileParams, gasToBurn) -> ret { + // Compiler simulation for calling `precompileCall` opcode + ret := verbatim_2i_1o("precompile", precompileParams, gasToBurn) + } + + //////////////////////////////////////////////////////////////// + // FALLBACK + //////////////////////////////////////////////////////////////// + + // Copy calldata to memory for pad it + let bytesSize := calldatasize() + calldatacopy(0, 0, bytesSize) + + // The sha256 padding includes additional 8 bytes of the total message's length in bits, + // so calculate the "full" message length with it. + let extendBytesLen := add(bytesSize, 8) + + let padLen := sub(BLOCK_SIZE(), mod(extendBytesLen, BLOCK_SIZE())) + let paddedBytesSize := add(extendBytesLen, padLen) + + // The original message size in bits + let binSize := mul(bytesSize, 8) + // Same bin size, but shifted to the left, needed for the padding + let leftShiftedBinSize := shl(sub(256, 64), binSize) + + // Write 0x80000... as padding according the sha256 specification + mstore(bytesSize, 0x8000000000000000000000000000000000000000000000000000000000000000) + // then will be some zeroes and BE encoded bit length + mstore(sub(paddedBytesSize, 8), leftShiftedBinSize) + + let numRounds := div(paddedBytesSize, BLOCK_SIZE()) + let precompileParams := unsafePackPrecompileParams( + 0, // input offset in words + // Always divisable by 32, since `BLOCK_SIZE()` is 64 bytes + div(paddedBytesSize, 32), // input length in words (safe to pass, never exceed `type(uint32).max`) + 0, // output offset in words + 1, // output length in words + numRounds // number of rounds (safe to pass, never exceed `type(uint64).max`) + ) + let gasToPay := mul(SHA256_ROUND_GAS_COST(), numRounds) + + let success := precompileCall(precompileParams, gasToPay) + + switch success + case 0 { + revert(0, 0) + } + default { + return(0, 32) + } + } + } +} diff --git a/l2/system-contracts/test-contracts/TestSystemContract.sol b/l2/system-contracts/test-contracts/TestSystemContract.sol new file mode 100644 index 0000000..b86dca9 --- /dev/null +++ b/l2/system-contracts/test-contracts/TestSystemContract.sol @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "../Constants.sol"; + +import "../DefaultAccount.sol"; + +import "../libraries/EfficientCall.sol"; +import {SystemContractHelper, ISystemContract} from "../libraries/SystemContractHelper.sol"; +import {TestSystemContractHelper} from "./TestSystemContractHelper.sol"; + +/// @notice An example of a system contract that be used for local testing. +/// @dev It is not used anywhere except for testing +contract TestSystemContract is ISystemContract { + modifier onlySelf() { + require(msg.sender == address(this)); + _; + } + + function testPrecompileCall() external view { + // Without precompile call + { + uint256 gasBefore = gasleft(); + uint256 gasAfter = gasleft(); + require(gasBefore - gasAfter < 10, "Spent too much gas"); + } + + { + uint256 gasBefore = gasleft(); + SystemContractHelper.precompileCall(0, 10000); + uint256 gasAfter = gasleft(); + require(gasBefore - gasAfter > 10000, "Did not spend enough gas"); + require(gasBefore - gasAfter < 10100, "Spent too much gas"); + } + } + + function testMimicCallAndValue(address whoToMimic, uint128 value) external { + // Note that we don't need to actually have the needed balance to set the `msg.value` for the next call + SystemContractHelper.setValueForNextFarCall(value); + this.performMimicCall( + address(this), + whoToMimic, + abi.encodeCall(TestSystemContract.saveContext, ()), + false, + false + ); + + require(latestMsgSender == whoToMimic, "mimicCall does not work"); + require(latestMsgValue == value, "setValueForNextFarCall does not work"); + } + + address public latestMsgSender; + uint128 public latestMsgValue; + uint256 public extraAbiData1; + uint256 public extraAbiData2; + + function saveContext() external payable { + latestMsgSender = msg.sender; + latestMsgValue = uint128(msg.value); + extraAbiData1 = SystemContractHelper.getExtraAbiData(0); + extraAbiData2 = SystemContractHelper.getExtraAbiData(1); + } + + function testOnlySystemModifier() external { + // Firstly, system contracts should be able to call it + (bool success, ) = address(this).call(abi.encodeCall(TestSystemContract.requireOnlySystem, ())); + require(success, "System contracts can call onlySystemCall methods"); + + // Non-system contract accounts should not be able to call it. + success = this.performRawMimicCall( + address(this), + address(MAX_SYSTEM_CONTRACT_ADDRESS + 1), + abi.encodeCall(TestSystemContract.requireOnlySystem, ()), + false, + false + ); + require(!success, "Normal acounts can not call onlySystemCall methods without proper flags"); + + success = this.performRawMimicCall( + address(this), + address(MAX_SYSTEM_CONTRACT_ADDRESS + 1), + abi.encodeCall(TestSystemContract.requireOnlySystem, ()), + false, + true + ); + require(success, "Normal acounts can not call onlySystemCall methods without proper flags"); + } + + function requireOnlySystem() external onlySystemCall {} + + function testSystemMimicCall() external { + this.performSystemMimicCall( + address(this), + address(MAX_SYSTEM_CONTRACT_ADDRESS + 1), + abi.encodeCall(TestSystemContract.saveContext, ()), + false, + 100, + 120 + ); + + require(extraAbiData1 == 100, "extraAbiData1 passed incorrectly"); + require(extraAbiData2 == 120, "extraAbiData2 passed incorrectly"); + } + + function performMimicCall( + address to, + address whoToMimic, + bytes calldata data, + bool isConstructor, + bool isSystem + ) external onlySelf returns (bytes memory) { + return EfficientCall.mimicCall(uint32(gasleft()), to, data, whoToMimic, isConstructor, isSystem); + } + + function performRawMimicCall( + address to, + address whoToMimic, + bytes calldata data, + bool isConstructor, + bool isSystem + ) external onlySelf returns (bool) { + return EfficientCall.rawMimicCall(uint32(gasleft()), to, data, whoToMimic, isConstructor, isSystem); + } + + function performSystemMimicCall( + address to, + address whoToMimic, + bytes calldata data, + bool isConstructor, + uint256 extraAbiParam1, + uint256 extraAbiParam2 + ) external onlySelf { + TestSystemContractHelper.systemMimicCall(to, whoToMimic, data, isConstructor, extraAbiParam1, extraAbiParam2); + } +} diff --git a/l2/system-contracts/test-contracts/TestSystemContractHelper.sol b/l2/system-contracts/test-contracts/TestSystemContractHelper.sol new file mode 100644 index 0000000..6a114e4 --- /dev/null +++ b/l2/system-contracts/test-contracts/TestSystemContractHelper.sol @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8; + +import {MAX_SYSTEM_CONTRACT_ADDRESS, MSG_VALUE_SYSTEM_CONTRACT} from "../Constants.sol"; + +import "../libraries/SystemContractsCaller.sol"; +import "../libraries/SystemContractHelper.sol"; +import "../libraries/Utils.sol"; + +library TestSystemContractHelper { + /// @notice Perform a `mimicCall` with `isSystem` flag, with the ability to pass extra abi data. + /// @param to The address to call + /// @param whoToMimic The `msg.sender` for the next call. + /// @param data The calldata + /// @param isConstructor Whether the call should contain the `isConstructor` flag. + /// @param extraAbiParam1 The first extraAbi param to pass with the call + /// @param extraAbiParam2 The second extraAbi param to pass with the call + /// @return The returndata if the call was successful. Reverts otherwise. + /// @dev If called not in kernel mode, it will result in a revert (enforced by the VM) + function systemMimicCall( + address to, + address whoToMimic, + bytes calldata data, + bool isConstructor, + uint256 extraAbiParam1, + uint256 extraAbiParam2 + ) internal returns (bytes memory) { + bool success = rawSystemMimicCall(to, whoToMimic, data, isConstructor, extraAbiParam1, extraAbiParam2); + + uint256 size; + assembly { + size := returndatasize() + } + if (!success) { + assembly { + returndatacopy(0, 0, size) + revert(0, size) + } + } + + bytes memory result = new bytes(size); + assembly { + mstore(result, size) + returndatacopy(add(result, 0x20), 0, size) + } + return result; + } + + /// @notice Perform a `mimicCall` with `isSystem` flag, with the ability to pass extra abi data. + /// @param to The address to call + /// @param whoToMimic The `msg.sender` for the next call. + /// @param data The calldata + /// @param isConstructor Whether the call should contain the `isConstructor` flag. + /// @param extraAbiParam1 The first extraAbi param to pass with the call + /// @param extraAbiParam2 The second extraAbi param to pass with the call + /// @return success whether the call was successful. + /// @dev If called not in kernel mode, it will result in a revert (enforced by the VM) + function rawSystemMimicCall( + address to, + address whoToMimic, + bytes calldata data, + bool isConstructor, + uint256 extraAbiParam1, + uint256 extraAbiParam2 + ) internal returns (bool success) { + SystemContractHelper.loadCalldataIntoActivePtr(); + + // Currently, zkEVM considers the pointer valid if(ptr.offset < ptr.length || (ptr.length == 0 && ptr.offset == 0)), otherwise panics. + // So, if the data is empty we need to make the `ptr.length = ptr.offset = 0`, otherwise follow standard logic. + if (data.length == 0) { + // Safe to cast, offset is never bigger than `type(uint32).max` + SystemContractHelper.ptrShrinkIntoActive(uint32(msg.data.length)); + } else { + uint256 dataOffset; + assembly { + dataOffset := data.offset + } + + // Safe to cast, offset is never bigger than `type(uint32).max` + SystemContractHelper.ptrAddIntoActive(uint32(dataOffset)); + // Safe to cast, `data.length` is never bigger than `type(uint32).max` + uint32 shrinkTo = uint32(msg.data.length - (data.length + dataOffset)); + SystemContractHelper.ptrShrinkIntoActive(shrinkTo); + } + + uint32 gas = Utils.safeCastToU32(gasleft()); + uint256 farCallAbi = SystemContractsCaller.getFarCallABIWithEmptyFatPointer( + gas, + // Only rollup is supported for now + 0, + CalldataForwardingMode.ForwardFatPointer, + isConstructor, + true + ); + SystemContractHelper.ptrPackIntoActivePtr(farCallAbi); + + address callAddr = SYSTEM_MIMIC_CALL_BY_REF_CALL_ADDRESS; + uint256 cleanupMask = ADDRESS_MASK; + assembly { + // Clearing values before usage in assembly, since Solidity + // doesn't do it by default + whoToMimic := and(whoToMimic, cleanupMask) + + success := call(to, callAddr, 0, 0, whoToMimic, extraAbiParam1, extraAbiParam2) + } + } +} diff --git a/l2/system-contracts/tests/Counter.sol b/l2/system-contracts/tests/Counter.sol new file mode 100644 index 0000000..736a8b7 --- /dev/null +++ b/l2/system-contracts/tests/Counter.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +contract Counter { + uint256 public counter; + + function increment() public { + counter += 1; + } +} diff --git a/l2/system-contracts/tests/TransactionHelperTest.sol b/l2/system-contracts/tests/TransactionHelperTest.sol new file mode 100644 index 0000000..df8e7e6 --- /dev/null +++ b/l2/system-contracts/tests/TransactionHelperTest.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "../libraries/TransactionHelper.sol"; + +contract TransactionHelperTest { + using TransactionHelper for Transaction; + + function encodeHash(Transaction calldata _transaction) public view returns (bytes32 resultHash) { + resultHash = _transaction.encodeHash(); + } +} diff --git a/package.json b/package.json index 859bc00..5973849 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@matterlabs/zksync-contracts", - "version": "0.6.0", + "version": "0.6.1", "license": "MIT", "peerDependencies": { "@openzeppelin/contracts": "4.6.0",