From 6bfc135b7ebf75f18415780a64326c9c36c1476a Mon Sep 17 00:00:00 2001 From: vladbochok Date: Wed, 4 Oct 2023 17:01:52 +0200 Subject: [PATCH 01/36] feat: Remove padding from keccak256 precompile contract --- contracts/precompiles/Keccak256.yul | 125 ++++++++++++---------------- 1 file changed, 51 insertions(+), 74 deletions(-) diff --git a/contracts/precompiles/Keccak256.yul b/contracts/precompiles/Keccak256.yul index b078d580..e3774224 100644 --- a/contracts/precompiles/Keccak256.yul +++ b/contracts/precompiles/Keccak256.yul @@ -1,15 +1,10 @@ /** * @author Matter Labs - * @custom:security-contact security@matterlabs.dev * @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. + * @dev It accepts the data to be hashed in the calldata, propagate it to the zkEVM built-in circuit precompile via `precompileCall` and burn . */ object "Keccak256" { - code { - return(0, 0) - } + code { } object "Keccak256_deployed" { code { //////////////////////////////////////////////////////////////// @@ -26,24 +21,38 @@ object "Keccak256" { ret := 40 } + /// @dev Returns a 32-bit mask value + function UINT32_BIT_MASK() -> ret { + ret := 0xffffffff + } + //////////////////////////////////////////////////////////////// // HELPER FUNCTIONS //////////////////////////////////////////////////////////////// + + /// @dev Load raw calldata fat pointer + function getCalldataPtr() -> calldataPtr { + calldataPtr := verbatim_0i_1o("get_global::ptr_calldata") + } - // @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! + /// @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_inputOffsetInBytes, + uint32_inputLengthInBytes, uint32_outputOffsetInWords, uint32_outputLengthInWords, + uint32_memoryPageToRead, + uint32_memoryPageToWrite, uint64_perPrecompileInterpreted ) -> rawParams { - rawParams := uint32_inputOffsetInWords - rawParams := or(rawParams, shl(32, uint32_inputLengthInWords)) + rawParams := uint32_inputOffsetInBytes + rawParams := or(rawParams, shl(32, uint32_inputLengthInBytes)) rawParams := or(rawParams, shl(64, uint32_outputOffsetInWords)) rawParams := or(rawParams, shl(96, uint32_outputLengthInWords)) + rawParams := or(rawParams, shl(128, uint32_memoryPageToRead)) + rawParams := or(rawParams, shl(160, uint32_memoryPageToWrite)) rawParams := or(rawParams, shl(192, uint64_perPrecompileInterpreted)) } @@ -56,73 +65,41 @@ object "Keccak256" { //////////////////////////////////////////////////////////////// // FALLBACK //////////////////////////////////////////////////////////////// + + // 1. Load raw calldata fat pointer + let calldataFatPtr := getCalldataPtr() - // 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() + // 2. Parse calldata fat pointer + let ptrOffset := and(calldataFatPtr, UINT32_BIT_MASK()) + // TODO: Remove this check before merging. + // Assert that calldata ptr offset if zero. + if ptrOffset { + revert(0, 0) } - default { - let padLen := sub(BLOCK_SIZE(), mod(bytesSize, BLOCK_SIZE())) - let paddedByteSize := add(bytesSize, padLen) + let ptrMemoryPage := and(shr(32, calldataFatPtr), UINT32_BIT_MASK()) + let ptrStart := and(shr(64, calldataFatPtr), UINT32_BIT_MASK()) + let ptrLength := and(shr(96, calldataFatPtr), UINT32_BIT_MASK()) - 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) - } + // 3. Pack precompile parameters + let precompileParams := unsafePackPrecompileParams( + ptrStart, // input offset in bytes + ptrLength, // input length in bytes (safe to pass, never exceed `type(uint32).max`) + 0, // output offset in words + 1, // output length in words (NOTE: VM doesn't check this value for now, but this could change in future) + ptrMemoryPage, // memory page to read from + 0, // memory page to write to (0 means write to heap) + 0 // per precompile interpreted value (0 since circuit doesn't react on this value anyway) + ) + // 4. Calculate number of required hash rounds per calldata + let numRounds := div(add(ptrLength, sub(BLOCK_SIZE(), 1)), BLOCK_SIZE()) + let gasToPay := mul(KECCAK_ROUND_GAS_COST(), numRounds) + // 5. Call precompile let success := precompileCall(precompileParams, gasToPay) - - switch success - case 0 { + if iszero(success) { revert(0, 0) } - default { - return(0, 32) - } + return(0, 32) } } } From 3c7f0598a840e1e9a17e7031ee6f736ce3583162 Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Thu, 12 Oct 2023 19:07:21 +0200 Subject: [PATCH 02/36] migration for keccak --- bootloader/bootloader.yul | 91 ++++++++++++++++++++++++++-------- contracts/ContractDeployer.sol | 23 +++++++-- scripts/process.ts | 52 +++---------------- 3 files changed, 97 insertions(+), 69 deletions(-) diff --git a/bootloader/bootloader.yul b/bootloader/bootloader.yul index 98efc7a6..c66f7d9d 100644 --- a/bootloader/bootloader.yul +++ b/bootloader/bootloader.yul @@ -488,6 +488,10 @@ object "Bootloader" { ret := 0x000000000000000000000000000000000000800e } + function KECCAK256_ADDR() -> ret { + ret := 0x0000000000000000000000000000000000008010 + } + function L1_MESSENGER_ADDR() -> ret { ret := 0x0000000000000000000000000000000000008008 } @@ -634,6 +638,70 @@ object "Bootloader" { } } + /// @dev Checks whether the code hash of the Keccak256 precompile contract is correct and updates it if needed. + /// @dev When we upgrade to the new version of the Keccak256 precompile contract, the keccak precompile will not work correctly + /// and so the upgrade it should be done before any `keccak` calls. + /// @dev Since this upgrade has + function upgradeKeccakIfNeeded() { + let expectedCodeHash := {{KECCAK256_EXPECTED_CODE_HASH}} + + let actualCodeHash := getRawCodeHash(KECCAK256_ADDR(), true) + if iszero(eq(expectedCodeHash, actualCodeHash)) { + // The `mimicCallOnlyResult` requires that the first word of the data + // contains its length. Here is 36 bytes, i.e. 4 byte selector + 32 byte hash. + mstore(0, 36) + mstore(32, {{PADDED_FORCE_DEPLOY_KECCAK256_SELECTOR}}) + mstore(36, expectedCodeHash) + + // We'll use a mimicCall to simulate the correct sender. + let success := mimicCallOnlyResult( + CONTRACT_DEPLOYER_ADDR(), + FORCE_DEPLOYER(), + 0, + 0, + 0, + 0, + 0, + 0 + ) + + if iszero(success) { + assertionError("keccak256 upgrade fail") + } + } + } + + function getRawCodeHash(addr, assertSuccess) -> ret { + mstore(0, {{RIGHT_PADDED_GET_RAW_CODE_HASH_SELECTOR}}) + mstore(4, addr) + let success := call( + gas(), + ACCOUNT_CODE_STORAGE_ADDR(), + 0, + 0, + 36, + 0, + 32 + ) + + // In case the call to the account code storage fails, + // it most likely means that the caller did not provide enough gas for + // the call. + // In case the caller is certain that the amount of gas provided is enough, i.e. + // (`assertSuccess` = true), then we should panic. + if iszero(success) { + if assertSuccess { + // The call must've succeeded, but revert the bootloader. + assertionError("getRawCodeHash failed") + } + + // Most likely not enough gas provided, revert the current frame. + nearCallPanic() + } + + ret := mload(0) + } + /// @dev Calculates the canonical hash of the L1->L2 transaction that will be /// sent to L1 as a message to the L1 contract that a certain operation has been processed. function getCanonicalL1TxHash(txDataOffset) -> ret { @@ -1956,26 +2024,7 @@ object "Bootloader" { /// @dev Checks whether an address is an EOA (i.e. has not code deployed on it) /// @param addr The address to check function isEOA(addr) -> ret { - mstore(0, {{RIGHT_PADDED_GET_RAW_CODE_HASH_SELECTOR}}) - mstore(4, addr) - let success := call( - gas(), - ACCOUNT_CODE_STORAGE_ADDR(), - 0, - 0, - 36, - 0, - 32 - ) - - if iszero(success) { - // The call to the account code storage should always succeed - nearCallPanic() - } - - let rawCodeHash := mload(0) - - ret := iszero(rawCodeHash) + ret := iszero(getRawCodeHash(addr, false)) } /// @dev Calls the `payForTransaction` method of an account @@ -3657,6 +3706,8 @@ object "Bootloader" { /// the operator still provides it to make sure that its data is in sync. let EXPECTED_BASE_FEE := mload(192) + upgradeKeccakIfNeeded() + validateOperatorProvidedPrices(L1_GAS_PRICE, FAIR_L2_GAS_PRICE) let baseFee := 0 diff --git a/contracts/ContractDeployer.sol b/contracts/ContractDeployer.sol index ed6d3fc2..e82b88ad 100644 --- a/contracts/ContractDeployer.sol +++ b/contracts/ContractDeployer.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.0; import {ImmutableData} from "./interfaces/IImmutableSimulator.sol"; import {IContractDeployer} from "./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, COMPLEX_UPGRADER_CONTRACT} from "./Constants.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, COMPLEX_UPGRADER_CONTRACT, KECCAK256_SYSTEM_CONTRACT} from "./Constants.sol"; import {Utils} from "./libraries/Utils.sol"; import {EfficientCall} from "./libraries/EfficientCall.sol"; @@ -228,8 +228,24 @@ contract ContractDeployer is IContractDeployer, ISystemContract { false, _deployment.callConstructor ); + } + + /// @notice The method that is temporarily needed to upgrade the Keccka256 precompile. It is to be removed in the + /// future. Unlike a normal forced deployment, it does not update account information as it requires updating a + /// mapping, and so requires Keccak256 precompile to work already. + /// @dev This method expects the sender (FORCE_DEPLOYER) to provide the correct bytecode hash for the Keccak256 + /// precompile. + function forceDeployKeccak256(bytes32 _keccak256BytecodeHash) external payable onlyCallFrom(FORCE_DEPLOYER) { + _ensureBytecodeIsKnown(_keccak256BytecodeHash); - emit ContractDeployed(_sender, _deployment.bytecodeHash, _deployment.newAddress); + _constructContract( + msg.sender, + address(KECCAK256_SYSTEM_CONTRACT), + _keccak256BytecodeHash, + msg.data[0:0], + false, + false + ); } /// @notice This method is to be used only during an upgrade to set bytecodes on specific addresses. @@ -295,7 +311,6 @@ contract ContractDeployer is IContractDeployer, ISystemContract { _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 @@ -352,5 +367,7 @@ contract ContractDeployer is IContractDeployer, ISystemContract { // If we do not call the constructor, we need to set the constructed code hash. ACCOUNT_CODE_STORAGE_SYSTEM_CONTRACT.storeAccountConstructedCodeHash(_newAddress, _bytecodeHash); } + + emit ContractDeployed(_sender, _bytecodeHash, _newAddress); } } diff --git a/scripts/process.ts b/scripts/process.ts index 82ae76a5..d47a6c40 100644 --- a/scripts/process.ts +++ b/scripts/process.ts @@ -3,6 +3,7 @@ const preprocess = require('preprocess'); import { existsSync, mkdirSync, write, writeFileSync } from 'fs'; import { SYSTEM_CONTRACTS, getRevertSelector, getTransactionUtils } from './constants'; import * as hre from 'hardhat'; +import * as fs from 'fs'; import { ethers } from 'ethers'; import { renderFile } from 'template-file'; import { utils } from 'zksync-web3'; @@ -35,50 +36,9 @@ function getPaddedSelector(contractName: string, method: string): string { const SYSTEM_PARAMS = require('../SystemConfig.json'); -function getSystemContextExpectedHash() { - const artifact = hre.artifacts.readArtifactSync('SystemContext'); - return ethers.utils.hexlify(utils.hashBytecode(artifact.bytecode)); -} - -function upgradeSystemContextCalldata() { - // Here we need to encode the force deployment for the system context contract as well as transform - // it into writing of the calldata into the bootloader memory. - - const newHash = getSystemContextExpectedHash(); - const artifact = new ethers.utils.Interface(hre.artifacts.readArtifactSync('ContractDeployer').abi); - - const forceDeplyment: ForceDeployment = { - bytecodeHash: newHash, - newAddress: SYSTEM_CONTRACTS.systemContext.address, - callConstructor: false, - value: 0, - input: '0x' - }; - - let calldata = artifact.encodeFunctionData('forceDeployOnAddresses', [[forceDeplyment]]); - const originalLength = (calldata.length - 2) / 2; - - // Padding calldata from the right. We really need to do it, since Yul would "implicitly" pad it from the left and it - // it is not what we want. - while ((calldata.length - 2) % 64 != 0) { - calldata += '0'; - } - - // We will apply tabulation to make the compiled bootloader code more readable - const TABULATION = '\t\t\t\t\t'; - // In the first slot we need to store the calldata's length - let data = `mstore(0x00, ${originalLength})\n`; - - const slices = (calldata.length - 2) / 64; - - for (let slice = 0; slice < slices; slice++) { - const offset = slice * 32; - const sliceHex = calldata.slice(2 + offset * 2, 2 + offset * 2 + 64); - - data += `${TABULATION}mstore(${offset + 32}, 0x${sliceHex})\n`; - } - - return data; +function getKeccak256ExpectedHash() { + const bytecode = fs.readFileSync('contracts/precompiles/artifacts/Keccak256.yul/Keccak256.yul.zbin'); + return ethers.utils.hexlify(utils.hashBytecode(bytecode)); } // Maybe in the future some of these params will be passed @@ -123,8 +83,8 @@ let params = { COMPRESSED_BYTECODES_SLOTS: 32768, ENSURE_RETURNED_MAGIC: 1, FORBID_ZERO_GAS_PER_PUBDATA: 1, - SYSTEM_CONTEXT_EXPECTED_CODE_HASH: getSystemContextExpectedHash(), - UPGRADE_SYSTEM_CONTEXT_CALLDATA: upgradeSystemContextCalldata(), + KECCAK256_EXPECTED_CODE_HASH: getKeccak256ExpectedHash(), + PADDED_FORCE_DEPLOY_KECCAK256_SELECTOR: getPaddedSelector('ContractDeployer', 'forceDeployKeccak256'), // One of "worst case" scenarios for the number of state diffs in a batch is when 120kb of pubdata is spent // on repeated writes, that are all zeroed out. In this case, the number of diffs is 120k / 5 = 24k. This means that they will have // accoomdate 6528000 bytes of calldata for the uncompressed state diffs. Adding 120k on top leaves us with From 2c85388f0c82e83d426534619656f533a4eee8e0 Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Fri, 13 Oct 2023 17:25:27 +0200 Subject: [PATCH 03/36] remove todo --- contracts/precompiles/Keccak256.yul | 5 ----- 1 file changed, 5 deletions(-) diff --git a/contracts/precompiles/Keccak256.yul b/contracts/precompiles/Keccak256.yul index e3774224..eb119f8d 100644 --- a/contracts/precompiles/Keccak256.yul +++ b/contracts/precompiles/Keccak256.yul @@ -71,11 +71,6 @@ object "Keccak256" { // 2. Parse calldata fat pointer let ptrOffset := and(calldataFatPtr, UINT32_BIT_MASK()) - // TODO: Remove this check before merging. - // Assert that calldata ptr offset if zero. - if ptrOffset { - revert(0, 0) - } let ptrMemoryPage := and(shr(32, calldataFatPtr), UINT32_BIT_MASK()) let ptrStart := and(shr(64, calldataFatPtr), UINT32_BIT_MASK()) let ptrLength := and(shr(96, calldataFatPtr), UINT32_BIT_MASK()) From 1af9f215d482e280ff0ab24116b200a331bce12c Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Fri, 13 Oct 2023 17:28:02 +0200 Subject: [PATCH 04/36] use staticcall where appropriate --- bootloader/bootloader.yul | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootloader/bootloader.yul b/bootloader/bootloader.yul index c66f7d9d..385ec2ae 100644 --- a/bootloader/bootloader.yul +++ b/bootloader/bootloader.yul @@ -674,7 +674,7 @@ object "Bootloader" { function getRawCodeHash(addr, assertSuccess) -> ret { mstore(0, {{RIGHT_PADDED_GET_RAW_CODE_HASH_SELECTOR}}) mstore(4, addr) - let success := call( + let success := staticcall( gas(), ACCOUNT_CODE_STORAGE_ADDR(), 0, From cbb1de835f53de46815ffc075200519a54a7905d Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Fri, 13 Oct 2023 17:30:47 +0200 Subject: [PATCH 05/36] upd comments --- bootloader/bootloader.yul | 3 +-- contracts/ContractDeployer.sol | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/bootloader/bootloader.yul b/bootloader/bootloader.yul index 385ec2ae..a6246e11 100644 --- a/bootloader/bootloader.yul +++ b/bootloader/bootloader.yul @@ -641,7 +641,6 @@ object "Bootloader" { /// @dev Checks whether the code hash of the Keccak256 precompile contract is correct and updates it if needed. /// @dev When we upgrade to the new version of the Keccak256 precompile contract, the keccak precompile will not work correctly /// and so the upgrade it should be done before any `keccak` calls. - /// @dev Since this upgrade has function upgradeKeccakIfNeeded() { let expectedCodeHash := {{KECCAK256_EXPECTED_CODE_HASH}} @@ -691,7 +690,7 @@ object "Bootloader" { // (`assertSuccess` = true), then we should panic. if iszero(success) { if assertSuccess { - // The call must've succeeded, but revert the bootloader. + // The call must've succeeded, but it didn't. So we revert the bootloader. assertionError("getRawCodeHash failed") } diff --git a/contracts/ContractDeployer.sol b/contracts/ContractDeployer.sol index e82b88ad..962aade3 100644 --- a/contracts/ContractDeployer.sol +++ b/contracts/ContractDeployer.sol @@ -230,7 +230,7 @@ contract ContractDeployer is IContractDeployer, ISystemContract { ); } - /// @notice The method that is temporarily needed to upgrade the Keccka256 precompile. It is to be removed in the + /// @notice The method that is temporarily needed to upgrade the Keccak256 precompile. It is to be removed in the /// future. Unlike a normal forced deployment, it does not update account information as it requires updating a /// mapping, and so requires Keccak256 precompile to work already. /// @dev This method expects the sender (FORCE_DEPLOYER) to provide the correct bytecode hash for the Keccak256 From 74116edbd25756a15ba849a1deb1d300b91cee93 Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Mon, 16 Oct 2023 10:28:48 +0200 Subject: [PATCH 06/36] remove unused var --- contracts/precompiles/Keccak256.yul | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/precompiles/Keccak256.yul b/contracts/precompiles/Keccak256.yul index eb119f8d..ba69e8dd 100644 --- a/contracts/precompiles/Keccak256.yul +++ b/contracts/precompiles/Keccak256.yul @@ -70,7 +70,6 @@ object "Keccak256" { let calldataFatPtr := getCalldataPtr() // 2. Parse calldata fat pointer - let ptrOffset := and(calldataFatPtr, UINT32_BIT_MASK()) let ptrMemoryPage := and(shr(32, calldataFatPtr), UINT32_BIT_MASK()) let ptrStart := and(shr(64, calldataFatPtr), UINT32_BIT_MASK()) let ptrLength := and(shr(96, calldataFatPtr), UINT32_BIT_MASK()) From e416085bc367237183958421e3f21da6210c8955 Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Mon, 16 Oct 2023 18:27:18 +0200 Subject: [PATCH 07/36] upd scripts --- scripts/quick-setup.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/quick-setup.sh b/scripts/quick-setup.sh index 341d77d2..697bc3cb 100755 --- a/scripts/quick-setup.sh +++ b/scripts/quick-setup.sh @@ -10,6 +10,7 @@ cargo +nightly install --git https://github.com/matter-labs/era-test-node.git -- yarn yarn build +yarn preprocess && yarn compile-yul era_test_node run > /dev/null 2>&1 & export TEST_NODE_PID=$! yarn test -kill $TEST_NODE_PID +kill $TEST_NODE_PID \ No newline at end of file From 26642154e1edbd69dee615b2e81bab0ed6098d23 Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Wed, 18 Oct 2023 11:52:46 +0200 Subject: [PATCH 08/36] fix staticall params --- bootloader/bootloader.yul | 1 - 1 file changed, 1 deletion(-) diff --git a/bootloader/bootloader.yul b/bootloader/bootloader.yul index a6246e11..e3272c4e 100644 --- a/bootloader/bootloader.yul +++ b/bootloader/bootloader.yul @@ -677,7 +677,6 @@ object "Bootloader" { gas(), ACCOUNT_CODE_STORAGE_ADDR(), 0, - 0, 36, 0, 32 From d89a9189ec3b4808de835012717124614346274c Mon Sep 17 00:00:00 2001 From: AntonD3 <74021421+AntonD3@users.noreply.github.com> Date: Fri, 20 Oct 2023 18:00:06 +0400 Subject: [PATCH 09/36] Zero ptr keccak test (#44) * Zero ptr keccak test * Update contracts/test-contracts/KeccakTest.sol Co-authored-by: Vlad Bochok <41153528+vladbochok@users.noreply.github.com> --------- Co-authored-by: Stanislav Bezkorovainyi Co-authored-by: Vlad Bochok <41153528+vladbochok@users.noreply.github.com> --- contracts/test-contracts/KeccakTest.sol | 76 +++++++++++++++++++++++++ test/Keccak256.spec.ts | 14 +++++ 2 files changed, 90 insertions(+) create mode 100644 contracts/test-contracts/KeccakTest.sol create mode 100644 test/Keccak256.spec.ts diff --git a/contracts/test-contracts/KeccakTest.sol b/contracts/test-contracts/KeccakTest.sol new file mode 100644 index 00000000..5e089e26 --- /dev/null +++ b/contracts/test-contracts/KeccakTest.sol @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; +pragma abicoder v2; + +import "../libraries/SystemContractsCaller.sol"; +import "../Constants.sol"; + +contract KeccakTest { + // Just some computation-heavy function, it will be used to test out of gas + function infiniteFuction(uint256 n) pure public returns (uint256 sumOfSquares) { + for(uint i = 0; i < n; i++) { + sumOfSquares += i * i; + } + } + + function _loadFarCallABIIntoActivePtr( + uint256 _gas + ) private view { + uint256 farCallAbi = SystemContractsCaller.getFarCallABIWithEmptyFatPointer( + uint32(_gas), + // Only rollup is supported for now + 0, + CalldataForwardingMode.ForwardFatPointer, + false, + false + ); + _ptrPackIntoActivePtr(farCallAbi); + } + + function _loadReturnDataIntoActivePtr() internal { + address callAddr = LOAD_LATEST_RETURNDATA_INTO_ACTIVE_PTR_CALL_ADDRESS; + assembly { + pop(staticcall(0, callAddr, 0, 0xFFFF, 0, 0)) + } + } + + function _ptrPackIntoActivePtr(uint256 _farCallAbi) internal view { + address callAddr = PTR_PACK_INTO_ACTIVE_CALL_ADDRESS; + assembly { + pop(staticcall(_farCallAbi, callAddr, 0, 0xFFFF, 0, 0)) + } + } + + function rawCallByRef(address _address) internal returns (bool success) { + address callAddr = RAW_FAR_CALL_BY_REF_CALL_ADDRESS; + assembly { + success := call(_address, callAddr, 0, 0, 0xFFFF, 0, 0) + } + } + + function zeroPointerTest() external { + try this.infiniteFuction{gas: 1000000}(1000000) returns (uint256 sumOfSquares) { + revert("The transaction should have failed"); + } catch {} + + _loadReturnDataIntoActivePtr(); + _loadFarCallABIIntoActivePtr(1000000); + bool success = rawCallByRef(KECCAK256_SYSTEM_CONTRACT); + require(success, "The call to keccak should have succeeded"); + + uint256 returndataSize = 0; + assembly { + returndataSize := returndatasize() + } + require(returndataSize == 32, "The return data size should be 32 bytes"); + + bytes32 result; + assembly { + returndatacopy(0, 0, 32) + result := mload(0) + } + + require(result == bytes32(0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470), "The result is not correct"); + } +} diff --git a/test/Keccak256.spec.ts b/test/Keccak256.spec.ts new file mode 100644 index 00000000..f7a24894 --- /dev/null +++ b/test/Keccak256.spec.ts @@ -0,0 +1,14 @@ +import { KeccakTest } from '../typechain-types'; +import { deployContract } from './shared/utils'; + +describe('Keccak256 tests', function () { + let keccakTest: KeccakTest; + + before(async () => { + keccakTest = (await deployContract('KeccakTest')) as KeccakTest + }); + + it('zero pointer test', async () => { + await keccakTest.zeroPointerTest() + }); +}); From db2c0d8c8363b565b1cee6d2f30864f3b26cd78d Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Fri, 20 Oct 2023 18:52:02 +0200 Subject: [PATCH 10/36] add unit test for upgrade keccak --- contracts/test-contracts/AlwaysRevert.sol | 9 ++++ contracts/test-contracts/AlwaysZero.sol | 12 ++++++ contracts/test-contracts/KeccakTest.sol | 47 ++++++++++++++++++++- test/Keccak256.spec.ts | 50 +++++++++++++++++++++-- test/shared/constants.ts | 1 + 5 files changed, 114 insertions(+), 5 deletions(-) create mode 100644 contracts/test-contracts/AlwaysRevert.sol create mode 100644 contracts/test-contracts/AlwaysZero.sol diff --git a/contracts/test-contracts/AlwaysRevert.sol b/contracts/test-contracts/AlwaysRevert.sol new file mode 100644 index 00000000..90211748 --- /dev/null +++ b/contracts/test-contracts/AlwaysRevert.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +contract AlwaysRevert { + fallback() external { + revert(""); + } +} diff --git a/contracts/test-contracts/AlwaysZero.sol b/contracts/test-contracts/AlwaysZero.sol new file mode 100644 index 00000000..d6874d40 --- /dev/null +++ b/contracts/test-contracts/AlwaysZero.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +contract AlwaysZero { + fallback() external { + assembly { + mstore(0,0) + return(0,32) + } + } +} diff --git a/contracts/test-contracts/KeccakTest.sol b/contracts/test-contracts/KeccakTest.sol index 5e089e26..33d25c1f 100644 --- a/contracts/test-contracts/KeccakTest.sol +++ b/contracts/test-contracts/KeccakTest.sol @@ -5,8 +5,11 @@ pragma abicoder v2; import "../libraries/SystemContractsCaller.sol"; import "../Constants.sol"; +import "../libraries/EfficientCall.sol"; contract KeccakTest { + bytes32 constant EMPTY_STRING_KECCAK = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; + // Just some computation-heavy function, it will be used to test out of gas function infiniteFuction(uint256 n) pure public returns (uint256 sumOfSquares) { for(uint i = 0; i < n; i++) { @@ -50,7 +53,7 @@ contract KeccakTest { } function zeroPointerTest() external { - try this.infiniteFuction{gas: 1000000}(1000000) returns (uint256 sumOfSquares) { + try this.infiniteFuction{gas: 1000000}(1000000) returns (uint256) { revert("The transaction should have failed"); } catch {} @@ -71,6 +74,46 @@ contract KeccakTest { result := mload(0) } - require(result == bytes32(0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470), "The result is not correct"); + require(result == EMPTY_STRING_KECCAK, "The result is not correct"); + } + + function keccakUpgradeTest( + bytes calldata eraseCallData, + bytes calldata upgradeCalldata + ) external returns (bytes32 hash) { + // Firstly, we reset keccak256 bytecode to be some random bytecode + EfficientCall.mimicCall( + gasleft(), + address(DEPLOYER_SYSTEM_CONTRACT), + eraseCallData, + FORCE_DEPLOYER, + false, + false + ); + + // Since the keccak contract has been erased, it should not work anymore + try this.callKeccak(msg.data[0:0]) returns (bytes32) { + revert("The keccak should not work anymore"); + } catch {} + // bytes32 incorrectHash = this.callKeccak(msg.data[0:0]); + // require(incorrectHash == bytes32(0), "The keccak should now return always incorrect values"); + + // Upgrading it back to the correct version: + EfficientCall.mimicCall( + gasleft(), + address(DEPLOYER_SYSTEM_CONTRACT), + upgradeCalldata, + FORCE_DEPLOYER, + false, + false + ); + + // Now it should work again + hash = this.callKeccak(msg.data[0:0]); + require(hash == EMPTY_STRING_KECCAK, "Keccak should start working again"); + } + + function callKeccak(bytes calldata _data) external pure returns (bytes32 hash) { + hash = keccak256(_data); } } diff --git a/test/Keccak256.spec.ts b/test/Keccak256.spec.ts index f7a24894..ba1caa9c 100644 --- a/test/Keccak256.spec.ts +++ b/test/Keccak256.spec.ts @@ -1,14 +1,58 @@ -import { KeccakTest } from '../typechain-types'; -import { deployContract } from './shared/utils'; +import { CONTRACT_DEPLOYER_ADDRESS, hashBytecode } from 'zksync-web3/build/src/utils'; +import { KeccakTest, KeccakTest__factory } from '../typechain-types'; +import { KECCAK256_CONTRACT_ADDRESS } from './shared/constants'; +import { getCode, getWallets, loadArtifact, publishBytecode, setCode } from './shared/utils'; +import { ethers } from 'hardhat'; describe('Keccak256 tests', function () { let keccakTest: KeccakTest; + let correctKeccakCodeHash: string; + let alwaysRevertCodeHash: string; + + // Kernel space address, needed to enable mimicCall + const KECCAK_TEST_ADDRESS = '0x0000000000000000000000000000000000009000'; + before(async () => { - keccakTest = (await deployContract('KeccakTest')) as KeccakTest + await setCode( + KECCAK_TEST_ADDRESS, + (await loadArtifact('KeccakTest')).bytecode + ); + + keccakTest = KeccakTest__factory.connect(KECCAK_TEST_ADDRESS, getWallets()[0]); + const correctKeccakCode = await getCode(KECCAK256_CONTRACT_ADDRESS); + + // The test node might use outdated contracts + const correctContractDeployerCode = (await loadArtifact('ContractDeployer')).bytecode; + await setCode(CONTRACT_DEPLOYER_ADDRESS, correctContractDeployerCode); + + const emptyContractCode = (await loadArtifact('AlwaysRevert')).bytecode; + + await publishBytecode(correctKeccakCode); + await publishBytecode(emptyContractCode); + + correctKeccakCodeHash = ethers.utils.hexlify(hashBytecode(correctKeccakCode)); + alwaysRevertCodeHash = ethers.utils.hexlify(hashBytecode(emptyContractCode)); }); it('zero pointer test', async () => { await keccakTest.zeroPointerTest() }); + + it('keccak upgrade test', async() => { + const deployerInterfact = new ethers.utils.Interface((await loadArtifact('ContractDeployer')).abi); + + const eraseInput = deployerInterfact.encodeFunctionData('forceDeployKeccak256', [ + alwaysRevertCodeHash + ]); + + const upgradeInput = deployerInterfact.encodeFunctionData('forceDeployKeccak256', [ + correctKeccakCodeHash + ]); + + await keccakTest.keccakUpgradeTest( + eraseInput, + upgradeInput + ); + }) }); diff --git a/test/shared/constants.ts b/test/shared/constants.ts index 489259cb..19fb4a45 100644 --- a/test/shared/constants.ts +++ b/test/shared/constants.ts @@ -9,6 +9,7 @@ export const L1_MESSENGER_SYSTEM_CONTRACT_ADDRESS = '0x0000000000000000000000000 export const ETH_TOKEN_SYSTEM_CONTRACT_ADDRESS = '0x000000000000000000000000000000000000800a'; export const EVENT_WRITER_CONTRACT_ADDRESS = '0x000000000000000000000000000000000000800d'; export const COMPRESSOR_CONTRACT_ADDRESS = '0x000000000000000000000000000000000000800e'; +export const KECCAK256_CONTRACT_ADDRESS = '0x0000000000000000000000000000000000008010'; export const EMPTY_STRING_KECCAK = '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470'; export const TWO_IN_256 = BigNumber.from(2).pow(256); From 9bec033b8121efb30dea5f2abfb95b58c0898a0b Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Fri, 20 Oct 2023 18:56:26 +0200 Subject: [PATCH 11/36] remove redundant test contract --- contracts/test-contracts/AlwaysZero.sol | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 contracts/test-contracts/AlwaysZero.sol diff --git a/contracts/test-contracts/AlwaysZero.sol b/contracts/test-contracts/AlwaysZero.sol deleted file mode 100644 index d6874d40..00000000 --- a/contracts/test-contracts/AlwaysZero.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -contract AlwaysZero { - fallback() external { - assembly { - mstore(0,0) - return(0,32) - } - } -} From 13664fc51aa8b60f3bb7c1a6056a58cf71e86a7f Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Mon, 23 Oct 2023 11:06:51 +0200 Subject: [PATCH 12/36] use latest vm version --- scripts/quick-setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/quick-setup.sh b/scripts/quick-setup.sh index 697bc3cb..da6bec9c 100755 --- a/scripts/quick-setup.sh +++ b/scripts/quick-setup.sh @@ -6,7 +6,7 @@ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh rustup toolchain install nightly # install era-test-node -cargo +nightly install --git https://github.com/matter-labs/era-test-node.git --locked --branch boojum-integration +cargo +nightly install --git https://github.com/matter-labs/era-test-node.git --locked --branch boojum-integration-vm-1.4.1 yarn yarn build From 217e115a91348c6a4dafde19a9777613e782e747 Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Mon, 23 Oct 2023 11:38:25 +0200 Subject: [PATCH 13/36] readme for testing --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index ba52b0c9..3842e054 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,16 @@ Once the audit for the release branch is complete and all the fixes from the aud Since scripts, READMEs, etc., are code that is not subject to audits, these are to be merged directly into the `main` branch. The rest of the release branches as well as the `dev` branch should merge `main` to synchronize with these changes. +## Testing + +The tests of the system contracts utilize the zkSync test node. In order to run the tests, execute the following command in the root of hte repository: + +``` +bash ./scripts/quick-setup.sh +``` + +The `quick-setup.sh` script will proceeed to install all the necessary dependencies and will run the tests. + ## License The zkSync Era system-contracts are distributed under the terms of the MIT license. From bd52d324843646678ea349365613dcc219826555 Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Thu, 2 Nov 2023 11:43:45 +0100 Subject: [PATCH 14/36] add constructor code --- contracts/precompiles/Keccak256.yul | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/contracts/precompiles/Keccak256.yul b/contracts/precompiles/Keccak256.yul index ba69e8dd..31cb289a 100644 --- a/contracts/precompiles/Keccak256.yul +++ b/contracts/precompiles/Keccak256.yul @@ -4,7 +4,9 @@ * @dev It accepts the data to be hashed in the calldata, propagate it to the zkEVM built-in circuit precompile via `precompileCall` and burn . */ object "Keccak256" { - code { } + code { + return(0, 0) + } object "Keccak256_deployed" { code { //////////////////////////////////////////////////////////////// From cc1d9c67778d962e9ca8cdd9a9fb16a06e76342c Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Thu, 2 Nov 2023 12:22:49 +0100 Subject: [PATCH 15/36] better tests for keccak precompile --- test/Keccak256.spec.ts | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/test/Keccak256.spec.ts b/test/Keccak256.spec.ts index ba1caa9c..03d729aa 100644 --- a/test/Keccak256.spec.ts +++ b/test/Keccak256.spec.ts @@ -1,10 +1,15 @@ import { CONTRACT_DEPLOYER_ADDRESS, hashBytecode } from 'zksync-web3/build/src/utils'; import { KeccakTest, KeccakTest__factory } from '../typechain-types'; import { KECCAK256_CONTRACT_ADDRESS } from './shared/constants'; -import { getCode, getWallets, loadArtifact, publishBytecode, setCode } from './shared/utils'; +import { getWallets, loadArtifact, publishBytecode, setCode } from './shared/utils'; import { ethers } from 'hardhat'; +import { readYulBytecode } from '../scripts/utils'; +import { Language } from '../scripts/constants'; +import { Wallet } from 'ethers'; +import { expect } from 'chai'; describe('Keccak256 tests', function () { + let testWallet: Wallet; let keccakTest: KeccakTest; let correctKeccakCodeHash: string; @@ -14,15 +19,21 @@ describe('Keccak256 tests', function () { const KECCAK_TEST_ADDRESS = '0x0000000000000000000000000000000000009000'; before(async () => { + testWallet = getWallets()[0]; + await setCode( KECCAK_TEST_ADDRESS, (await loadArtifact('KeccakTest')).bytecode ); keccakTest = KeccakTest__factory.connect(KECCAK_TEST_ADDRESS, getWallets()[0]); - const correctKeccakCode = await getCode(KECCAK256_CONTRACT_ADDRESS); + const correctKeccakCode = readYulBytecode({ + codeName: 'Keccak256', + path: 'precompiles', + lang: Language.Yul, + address: ethers.constants.AddressZero + }); - // The test node might use outdated contracts const correctContractDeployerCode = (await loadArtifact('ContractDeployer')).bytecode; await setCode(CONTRACT_DEPLOYER_ADDRESS, correctContractDeployerCode); @@ -39,6 +50,27 @@ describe('Keccak256 tests', function () { await keccakTest.zeroPointerTest() }); + it('general functionality test', async () => { + // We currently do not have fussing support, so we generate random data using + // hash function. + + const seed = ethers.utils.randomBytes(32); + // Displaying seed for reproducible tests + console.log('Keccak256 fussing seed', ethers.utils.hexlify(seed)); + + for(let i = 0; i < 5; i++) { + const data = ethers.utils.keccak256(ethers.utils.hexConcat([seed, i])); + + const correctHash = ethers.utils.keccak256(data); + const hashFromPrecompile = await testWallet.provider.call({ + to: KECCAK256_CONTRACT_ADDRESS, + data: data + }); + + expect(hashFromPrecompile).to.equal(correctHash, 'Hash is incorrect'); + } + }); + it('keccak upgrade test', async() => { const deployerInterfact = new ethers.utils.Interface((await loadArtifact('ContractDeployer')).abi); From 40d31d52481577526a75ddcda54149c0f1cb13a9 Mon Sep 17 00:00:00 2001 From: vladbochok Date: Fri, 3 Nov 2023 16:13:39 +0400 Subject: [PATCH 16/36] Fix N-01 --- bootloader/bootloader.yul | 2 ++ contracts/ContractDeployer.sol | 10 +++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/bootloader/bootloader.yul b/bootloader/bootloader.yul index e3272c4e..811a8fa3 100644 --- a/bootloader/bootloader.yul +++ b/bootloader/bootloader.yul @@ -638,6 +638,8 @@ object "Bootloader" { } } + /// @dev The function that is temporarily needed to upgrade the Keccak256 precompile. This function and `ContractDeployer:forceDeployKeccak256` + /// are to be removed once the upgrade is complete. /// @dev Checks whether the code hash of the Keccak256 precompile contract is correct and updates it if needed. /// @dev When we upgrade to the new version of the Keccak256 precompile contract, the keccak precompile will not work correctly /// and so the upgrade it should be done before any `keccak` calls. diff --git a/contracts/ContractDeployer.sol b/contracts/ContractDeployer.sol index 962aade3..9f2da959 100644 --- a/contracts/ContractDeployer.sol +++ b/contracts/ContractDeployer.sol @@ -230,11 +230,11 @@ contract ContractDeployer is IContractDeployer, ISystemContract { ); } - /// @notice The method that is temporarily needed to upgrade the Keccak256 precompile. It is to be removed in the - /// future. Unlike a normal forced deployment, it does not update account information as it requires updating a - /// mapping, and so requires Keccak256 precompile to work already. - /// @dev This method expects the sender (FORCE_DEPLOYER) to provide the correct bytecode hash for the Keccak256 - /// precompile. + /// @notice The method that is temporarily needed to upgrade the Keccak256 precompile. This function and `Bootloader:upgradeKeccakIfNeeded` + /// are to be removed once the upgrade is complete. Unlike a normal forced deployment, it does not update account information as it requires + /// updating a mapping, and so requires Keccak256 precompile to work already. + /// @dev This method expects the sender (FORCE_DEPLOYER) to provide the correct bytecode hash for the Keccak256 + /// precompile. function forceDeployKeccak256(bytes32 _keccak256BytecodeHash) external payable onlyCallFrom(FORCE_DEPLOYER) { _ensureBytecodeIsKnown(_keccak256BytecodeHash); From a90e2c0e8b4a588fab6b3904b2c23a496c86415a Mon Sep 17 00:00:00 2001 From: vladbochok Date: Fri, 3 Nov 2023 16:49:22 +0400 Subject: [PATCH 17/36] Add missing comments --- contracts/precompiles/Keccak256.yul | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/contracts/precompiles/Keccak256.yul b/contracts/precompiles/Keccak256.yul index ba69e8dd..8a119478 100644 --- a/contracts/precompiles/Keccak256.yul +++ b/contracts/precompiles/Keccak256.yul @@ -11,12 +11,21 @@ object "Keccak256" { // CONSTANTS //////////////////////////////////////////////////////////////// - /// @dev The size of the processing keccak256 block in bytes. + /// @dev Returns the block size used by the keccak256 hashing function. + /// The value 136 bytes corresponds to the size of the input data block that the keccak256 + /// algorithm processes in each round, as defined in the keccak256 specification. This is derived + /// from the formula (1600 - 2 * bit length of the digest) / 8, where the bit length for keccak256 + /// is 256 bits. For more details, refer to the Keccak specification at + /// https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf#page=30 function BLOCK_SIZE() -> ret { ret := 136 } /// @dev The gas cost of processing one keccak256 round. + /// @dev This constant is made equal to the corresponding constant in + /// https://github.com/matter-labs/era-zkevm_opcode_defs/blob/v1.4.1/src/circuit_prices.rs, + /// which was automatically generated depending on the capacity of rounds for a + /// single Keccak256 circuit. function KECCAK_ROUND_GAS_COST() -> ret { ret := 40 } From d6c93edd1291af6efd1099c250f2e660a84e040d Mon Sep 17 00:00:00 2001 From: vladbochok Date: Fri, 3 Nov 2023 16:54:07 +0400 Subject: [PATCH 18/36] Reorder getter functions in the Bootloader --- bootloader/bootloader.yul | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bootloader/bootloader.yul b/bootloader/bootloader.yul index e3272c4e..5a579a54 100644 --- a/bootloader/bootloader.yul +++ b/bootloader/bootloader.yul @@ -444,10 +444,6 @@ object "Bootloader" { ret := 0x0000000000000000000000000000000000008001 } - function MAX_SYSTEM_CONTRACT_ADDR() -> ret { - ret := 0x000000000000000000000000000000000000ffff - } - function ACCOUNT_CODE_STORAGE_ADDR() -> ret { ret := 0x0000000000000000000000000000000000008002 } @@ -468,6 +464,10 @@ object "Bootloader" { ret := 0x0000000000000000000000000000000000008007 } + function L1_MESSENGER_ADDR() -> ret { + ret := 0x0000000000000000000000000000000000008008 + } + function MSG_VALUE_SIMULATOR_ADDR() -> ret { ret := 0x0000000000000000000000000000000000008009 } @@ -492,8 +492,8 @@ object "Bootloader" { ret := 0x0000000000000000000000000000000000008010 } - function L1_MESSENGER_ADDR() -> ret { - ret := 0x0000000000000000000000000000000000008008 + function MAX_SYSTEM_CONTRACT_ADDR() -> ret { + ret := 0x000000000000000000000000000000000000ffff } /// @dev The minimal allowed distance in bytes between the pointer to the compressed data From 4f1b6c6b27edfb850d14b3a9cba703d583dbec78 Mon Sep 17 00:00:00 2001 From: vladbochok Date: Fri, 3 Nov 2023 17:13:45 +0400 Subject: [PATCH 19/36] Fix the formula --- contracts/precompiles/Keccak256.yul | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/precompiles/Keccak256.yul b/contracts/precompiles/Keccak256.yul index ba69e8dd..82e7c212 100644 --- a/contracts/precompiles/Keccak256.yul +++ b/contracts/precompiles/Keccak256.yul @@ -85,7 +85,7 @@ object "Keccak256" { 0 // per precompile interpreted value (0 since circuit doesn't react on this value anyway) ) // 4. Calculate number of required hash rounds per calldata - let numRounds := div(add(ptrLength, sub(BLOCK_SIZE(), 1)), BLOCK_SIZE()) + let numRounds := add(div(ptrLength, BLOCK_SIZE()), 1) let gasToPay := mul(KECCAK_ROUND_GAS_COST(), numRounds) // 5. Call precompile From 7afd89d69d120bfae2f2bf13b36c2bd517078d9e Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Fri, 3 Nov 2023 14:15:22 +0100 Subject: [PATCH 20/36] better random tests --- test/Keccak256.spec.ts | 59 +++++++++++++++++++++++++++++++++--------- 1 file changed, 47 insertions(+), 12 deletions(-) diff --git a/test/Keccak256.spec.ts b/test/Keccak256.spec.ts index 03d729aa..0602bf24 100644 --- a/test/Keccak256.spec.ts +++ b/test/Keccak256.spec.ts @@ -5,8 +5,9 @@ import { getWallets, loadArtifact, publishBytecode, setCode } from './shared/uti import { ethers } from 'hardhat'; import { readYulBytecode } from '../scripts/utils'; import { Language } from '../scripts/constants'; -import { Wallet } from 'ethers'; +import { BytesLike, Wallet, providers } from 'ethers'; import { expect } from 'chai'; +import { ECDH } from 'crypto'; describe('Keccak256 tests', function () { let testWallet: Wallet; @@ -58,17 +59,26 @@ describe('Keccak256 tests', function () { // Displaying seed for reproducible tests console.log('Keccak256 fussing seed', ethers.utils.hexlify(seed)); - for(let i = 0; i < 5; i++) { - const data = ethers.utils.keccak256(ethers.utils.hexConcat([seed, i])); - - const correctHash = ethers.utils.keccak256(data); - const hashFromPrecompile = await testWallet.provider.call({ - to: KECCAK256_CONTRACT_ADDRESS, - data: data - }); - - expect(hashFromPrecompile).to.equal(correctHash, 'Hash is incorrect'); - } + // Testing empty array + await compareCorrectHash('0x', testWallet.provider!); + const BLOCK_SIZE = 136; + + await compareCorrectHash(randomHexFromSeed(seed, BLOCK_SIZE), testWallet.provider!); + await compareCorrectHash(randomHexFromSeed(seed, BLOCK_SIZE - 1), testWallet.provider!); + await compareCorrectHash(randomHexFromSeed(seed, BLOCK_SIZE - 2), testWallet.provider!); + await compareCorrectHash(randomHexFromSeed(seed, BLOCK_SIZE + 1), testWallet.provider!); + await compareCorrectHash(randomHexFromSeed(seed, BLOCK_SIZE + 2), testWallet.provider!); + + await compareCorrectHash(randomHexFromSeed(seed, 101 * BLOCK_SIZE), testWallet.provider!); + await compareCorrectHash(randomHexFromSeed(seed, 101 * BLOCK_SIZE - 1), testWallet.provider!); + await compareCorrectHash(randomHexFromSeed(seed, 101 * BLOCK_SIZE - 2), testWallet.provider!); + await compareCorrectHash(randomHexFromSeed(seed, 101 * BLOCK_SIZE + 1), testWallet.provider!); + await compareCorrectHash(randomHexFromSeed(seed, 101 * BLOCK_SIZE + 2), testWallet.provider!); + + // In order to get random length, we use modulo operation + await compareCorrectHash(randomHexFromSeed(seed, ethers.BigNumber.from(seed).mod(113).toNumber()), testWallet.provider!); + await compareCorrectHash(randomHexFromSeed(seed, ethers.BigNumber.from(seed).mod(1101).toNumber()), testWallet.provider!); + await compareCorrectHash(randomHexFromSeed(seed, ethers.BigNumber.from(seed).mod(17).toNumber()), testWallet.provider!); }); it('keccak upgrade test', async() => { @@ -88,3 +98,28 @@ describe('Keccak256 tests', function () { ); }) }); + +async function compareCorrectHash ( + data: BytesLike, + provider: providers.Provider +) { + const correctHash = ethers.utils.keccak256(data); + const hashFromPrecompile = await provider.call({ + to: KECCAK256_CONTRACT_ADDRESS, + data + }); + expect(hashFromPrecompile).to.equal(correctHash, 'Hash is incorrect'); +} + +function randomHexFromSeed( + seed: BytesLike, + len: number, +) { + const hexLen = len * 2 + 2; + let data = '0x'; + while (data.length < hexLen) { + const next = ethers.utils.keccak256(ethers.utils.hexConcat([seed, data])); + data = ethers.utils.hexConcat([data, next]); + } + return data.substring(0, hexLen); +} From e84ea6e3b5248d8260cb7cfeeb8fcaed0452b34a Mon Sep 17 00:00:00 2001 From: koloz Date: Wed, 15 Nov 2023 13:13:49 -0500 Subject: [PATCH 21/36] added test to validate keccak output --- contracts/test-contracts/KeccakTest.sol | 54 +++++++++++++++++++++---- test/Keccak256.spec.ts | 44 +++++++++++++++++++- 2 files changed, 89 insertions(+), 9 deletions(-) diff --git a/contracts/test-contracts/KeccakTest.sol b/contracts/test-contracts/KeccakTest.sol index 33d25c1f..6c5f5c73 100644 --- a/contracts/test-contracts/KeccakTest.sol +++ b/contracts/test-contracts/KeccakTest.sol @@ -11,15 +11,13 @@ contract KeccakTest { bytes32 constant EMPTY_STRING_KECCAK = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; // Just some computation-heavy function, it will be used to test out of gas - function infiniteFuction(uint256 n) pure public returns (uint256 sumOfSquares) { - for(uint i = 0; i < n; i++) { + function infiniteFuction(uint256 n) public pure returns (uint256 sumOfSquares) { + for (uint i = 0; i < n; i++) { sumOfSquares += i * i; } } - function _loadFarCallABIIntoActivePtr( - uint256 _gas - ) private view { + function _loadFarCallABIIntoActivePtr(uint256 _gas) private view { uint256 farCallAbi = SystemContractsCaller.getFarCallABIWithEmptyFatPointer( uint32(_gas), // Only rollup is supported for now @@ -78,7 +76,7 @@ contract KeccakTest { } function keccakUpgradeTest( - bytes calldata eraseCallData, + bytes calldata eraseCallData, bytes calldata upgradeCalldata ) external returns (bytes32 hash) { // Firstly, we reset keccak256 bytecode to be some random bytecode @@ -111,9 +109,49 @@ contract KeccakTest { // Now it should work again hash = this.callKeccak(msg.data[0:0]); require(hash == EMPTY_STRING_KECCAK, "Keccak should start working again"); - } - + } + function callKeccak(bytes calldata _data) external pure returns (bytes32 hash) { hash = keccak256(_data); } + + function keccakValidationTest( + bytes calldata upgradeCalldata, + bytes calldata resetCalldata, + bytes[] calldata testInputs, + bytes32[] calldata expectedOutputs + ) external { + require(testInputs.length == expectedOutputs.length, "mismatch between number of inputs and outputs"); + + // Firstly, we upgrade keccak256 bytecode to the correct version. + EfficientCall.mimicCall( + gasleft(), + address(DEPLOYER_SYSTEM_CONTRACT), + upgradeCalldata, + FORCE_DEPLOYER, + false, + false + ); + + bytes32[] memory result = new bytes32[](testInputs.length); + + for (uint256 i = 0; i < testInputs.length; i++) { + bytes32 res = this.callKeccak(testInputs[i]); + result[i] = res; + } + + for (uint256 i = 0; i < result.length; i++) { + require(result[i] == expectedOutputs[i], "hash was not calculated correctly"); + } + + // Upgrading it back to the correct version: + EfficientCall.mimicCall( + gasleft(), + address(DEPLOYER_SYSTEM_CONTRACT), + resetCalldata, + FORCE_DEPLOYER, + false, + false + ); + } } diff --git a/test/Keccak256.spec.ts b/test/Keccak256.spec.ts index 0602bf24..391ad0ca 100644 --- a/test/Keccak256.spec.ts +++ b/test/Keccak256.spec.ts @@ -1,7 +1,7 @@ import { CONTRACT_DEPLOYER_ADDRESS, hashBytecode } from 'zksync-web3/build/src/utils'; import { KeccakTest, KeccakTest__factory } from '../typechain-types'; import { KECCAK256_CONTRACT_ADDRESS } from './shared/constants'; -import { getWallets, loadArtifact, publishBytecode, setCode } from './shared/utils'; +import { getWallets, loadArtifact, publishBytecode, setCode, getCode } from './shared/utils'; import { ethers } from 'hardhat'; import { readYulBytecode } from '../scripts/utils'; import { Language } from '../scripts/constants'; @@ -13,6 +13,7 @@ describe('Keccak256 tests', function () { let testWallet: Wallet; let keccakTest: KeccakTest; + let oldKeccakCodeHash: string; let correctKeccakCodeHash: string; let alwaysRevertCodeHash: string; @@ -27,6 +28,9 @@ describe('Keccak256 tests', function () { (await loadArtifact('KeccakTest')).bytecode ); + const keccakCode = await getCode(KECCAK256_CONTRACT_ADDRESS); + oldKeccakCodeHash = ethers.utils.hexlify(hashBytecode(keccakCode)); + keccakTest = KeccakTest__factory.connect(KECCAK_TEST_ADDRESS, getWallets()[0]); const correctKeccakCode = readYulBytecode({ codeName: 'Keccak256', @@ -40,6 +44,7 @@ describe('Keccak256 tests', function () { const emptyContractCode = (await loadArtifact('AlwaysRevert')).bytecode; + await publishBytecode(keccakCode); await publishBytecode(correctKeccakCode); await publishBytecode(emptyContractCode); @@ -97,6 +102,43 @@ describe('Keccak256 tests', function () { upgradeInput ); }) + + it('keccak validation test', async() => { + const deployerInterfact = new ethers.utils.Interface((await loadArtifact('ContractDeployer')).abi); + + const upgradeInput = deployerInterfact.encodeFunctionData('forceDeployKeccak256', [ + correctKeccakCodeHash + ]); + + const resetInput = deployerInterfact.encodeFunctionData('forceDeployKeccak256', [ + oldKeccakCodeHash + ]); + + const BLOCK_SIZE = 136; + const seed = ethers.utils.randomBytes(32); + const input1 = randomHexFromSeed(seed, BLOCK_SIZE + 0); + const input2 = randomHexFromSeed(seed, BLOCK_SIZE - 1); + const input3 = randomHexFromSeed(seed, BLOCK_SIZE - 2); + const input4 = randomHexFromSeed(seed, BLOCK_SIZE + 1); + const input5 = randomHexFromSeed(seed, BLOCK_SIZE + 2); + + const inputsToTest = [ + input1, + input2, + input3, + input4, + input5 + ] + + const expectedOutput = inputsToTest.map((e) => ethers.utils.keccak256(e)); + + await keccakTest.keccakValidationTest( + upgradeInput, + resetInput, + inputsToTest, + expectedOutput + ); + }) }); async function compareCorrectHash ( From 62d0ee768fbf6ea14b519ae2f45538581cd5442a Mon Sep 17 00:00:00 2001 From: koloz Date: Wed, 15 Nov 2023 16:59:49 -0500 Subject: [PATCH 22/36] added test to make sure upgradeIfNexessary worked as intended --- .../test-contracts/Keccak256Mock.yul | 99 +++++++++++++++++++ contracts/test-contracts/KeccakTest.sol | 15 ++- scripts/compile-yul.ts | 1 + test/Keccak256.spec.ts | 30 +++++- 4 files changed, 143 insertions(+), 2 deletions(-) create mode 100644 contracts/precompiles/test-contracts/Keccak256Mock.yul diff --git a/contracts/precompiles/test-contracts/Keccak256Mock.yul b/contracts/precompiles/test-contracts/Keccak256Mock.yul new file mode 100644 index 00000000..b37eb69c --- /dev/null +++ b/contracts/precompiles/test-contracts/Keccak256Mock.yul @@ -0,0 +1,99 @@ +/** + * @author Matter Labs + * @notice The contract used to emulate EVM's keccak256 opcode. + * @dev It accepts the data to be hashed in the calldata, propagate it to the zkEVM built-in circuit precompile via `precompileCall` and burn . + */ + 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 + } + + /// @dev Returns a 32-bit mask value + function UINT32_BIT_MASK() -> ret { + ret := 0xffffffff + } + + //////////////////////////////////////////////////////////////// + // HELPER FUNCTIONS + //////////////////////////////////////////////////////////////// + + /// @dev Load raw calldata fat pointer + function getCalldataPtr() -> calldataPtr { + calldataPtr := verbatim_0i_1o("get_global::ptr_calldata") + } + + /// @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_inputOffsetInBytes, + uint32_inputLengthInBytes, + uint32_outputOffsetInWords, + uint32_outputLengthInWords, + uint32_memoryPageToRead, + uint32_memoryPageToWrite, + uint64_perPrecompileInterpreted + ) -> rawParams { + rawParams := uint32_inputOffsetInBytes + rawParams := or(rawParams, shl(32, uint32_inputLengthInBytes)) + rawParams := or(rawParams, shl(64, uint32_outputOffsetInWords)) + rawParams := or(rawParams, shl(96, uint32_outputLengthInWords)) + rawParams := or(rawParams, shl(128, uint32_memoryPageToRead)) + rawParams := or(rawParams, shl(160, uint32_memoryPageToWrite)) + 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 + //////////////////////////////////////////////////////////////// + + // 1. Load raw calldata fat pointer + let calldataFatPtr := getCalldataPtr() + + // 2. Parse calldata fat pointer + let ptrMemoryPage := and(shr(32, calldataFatPtr), UINT32_BIT_MASK()) + let ptrStart := and(shr(64, calldataFatPtr), UINT32_BIT_MASK()) + let ptrLength := and(shr(96, calldataFatPtr), UINT32_BIT_MASK()) + + // 3. Pack precompile parameters + let precompileParams := unsafePackPrecompileParams( + ptrStart, // input offset in bytes + ptrLength, // input length in bytes (safe to pass, never exceed `type(uint32).max`) + 0, // output offset in words + 1, // output length in words (NOTE: VM doesn't check this value for now, but this could change in future) + ptrMemoryPage, // memory page to read from + 0, // memory page to write to (0 means write to heap) + 0 // per precompile interpreted value (0 since circuit doesn't react on this value anyway) + ) + // 4. Calculate number of required hash rounds per calldata + let numRounds := div(add(ptrLength, sub(BLOCK_SIZE(), 1)), BLOCK_SIZE()) + let gasToPay := 0 + + // 5. Call precompile + let success := precompileCall(precompileParams, gasToPay) + if iszero(success) { + revert(0, 0) + } + return(0, 32) + } + } +} diff --git a/contracts/test-contracts/KeccakTest.sol b/contracts/test-contracts/KeccakTest.sol index 6c5f5c73..7a9aa033 100644 --- a/contracts/test-contracts/KeccakTest.sol +++ b/contracts/test-contracts/KeccakTest.sol @@ -111,6 +111,19 @@ contract KeccakTest { require(hash == EMPTY_STRING_KECCAK, "Keccak should start working again"); } + function keccakPerformUpgrade( + bytes calldata upgradeCalldata + ) external { + EfficientCall.mimicCall( + gasleft(), + address(DEPLOYER_SYSTEM_CONTRACT), + upgradeCalldata, + FORCE_DEPLOYER, + false, + false + ); + } + function callKeccak(bytes calldata _data) external pure returns (bytes32 hash) { hash = keccak256(_data); } @@ -144,7 +157,7 @@ contract KeccakTest { require(result[i] == expectedOutputs[i], "hash was not calculated correctly"); } - // Upgrading it back to the correct version: + // Upgrading it back to the original version: EfficientCall.mimicCall( gasleft(), address(DEPLOYER_SYSTEM_CONTRACT), diff --git a/scripts/compile-yul.ts b/scripts/compile-yul.ts index fe42b4e9..aeb17f2d 100644 --- a/scripts/compile-yul.ts +++ b/scripts/compile-yul.ts @@ -90,6 +90,7 @@ class CompilerPaths { async function main() { await compileYulFolder('contracts'); await compileYulFolder('contracts/precompiles'); + await compileYulFolder('contracts/precompiles/test-contracts'); await compileYulFolder('bootloader/build'); await compileYulFolder('bootloader/tests'); } diff --git a/test/Keccak256.spec.ts b/test/Keccak256.spec.ts index 391ad0ca..f28d5231 100644 --- a/test/Keccak256.spec.ts +++ b/test/Keccak256.spec.ts @@ -7,7 +7,7 @@ import { readYulBytecode } from '../scripts/utils'; import { Language } from '../scripts/constants'; import { BytesLike, Wallet, providers } from 'ethers'; import { expect } from 'chai'; -import { ECDH } from 'crypto'; +import * as hre from 'hardhat'; describe('Keccak256 tests', function () { let testWallet: Wallet; @@ -16,6 +16,7 @@ describe('Keccak256 tests', function () { let oldKeccakCodeHash: string; let correctKeccakCodeHash: string; let alwaysRevertCodeHash: string; + let keccakMockCodeHash: string; // Kernel space address, needed to enable mimicCall const KECCAK_TEST_ADDRESS = '0x0000000000000000000000000000000000009000'; @@ -31,6 +32,15 @@ describe('Keccak256 tests', function () { const keccakCode = await getCode(KECCAK256_CONTRACT_ADDRESS); oldKeccakCodeHash = ethers.utils.hexlify(hashBytecode(keccakCode)); + const keccakMockCode = readYulBytecode({ + codeName: 'Keccak256Mock', + path: 'precompiles/test-contracts', + lang: Language.Yul, + address: ethers.constants.AddressZero + }); + + keccakMockCodeHash = ethers.utils.hexlify(hashBytecode(keccakMockCode)); + keccakTest = KeccakTest__factory.connect(KECCAK_TEST_ADDRESS, getWallets()[0]); const correctKeccakCode = readYulBytecode({ codeName: 'Keccak256', @@ -47,6 +57,7 @@ describe('Keccak256 tests', function () { await publishBytecode(keccakCode); await publishBytecode(correctKeccakCode); await publishBytecode(emptyContractCode); + await publishBytecode(keccakMockCode); correctKeccakCodeHash = ethers.utils.hexlify(hashBytecode(correctKeccakCode)); alwaysRevertCodeHash = ethers.utils.hexlify(hashBytecode(emptyContractCode)); @@ -139,6 +150,23 @@ describe('Keccak256 tests', function () { expectedOutput ); }) + + it('keccak upgrade if needed test', async() => { + const deployerInterfact = new ethers.utils.Interface((await loadArtifact('ContractDeployer')).abi); + + const mockKeccakInput = deployerInterfact.encodeFunctionData('forceDeployKeccak256', [ + keccakMockCodeHash + ]); + + await keccakTest.keccakPerformUpgrade( + mockKeccakInput + ); + + const keccakCode = await getCode(KECCAK256_CONTRACT_ADDRESS); + const keccakCodeHash = ethers.utils.hexlify(hashBytecode(keccakCode)); + + expect(keccakCodeHash).to.eq(oldKeccakCodeHash); + }) }); async function compareCorrectHash ( From 523485892a7c6a954aca3d86ea9a4d6340c5da85 Mon Sep 17 00:00:00 2001 From: koloz Date: Wed, 15 Nov 2023 17:26:35 -0500 Subject: [PATCH 23/36] mined a few blocks to trigger keccak upgrade --- test/Keccak256.spec.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/Keccak256.spec.ts b/test/Keccak256.spec.ts index f28d5231..6fe7e5ab 100644 --- a/test/Keccak256.spec.ts +++ b/test/Keccak256.spec.ts @@ -162,6 +162,8 @@ describe('Keccak256 tests', function () { mockKeccakInput ); + await hre.network.provider.send("hardhat_mine", ["0x100"]); + const keccakCode = await getCode(KECCAK256_CONTRACT_ADDRESS); const keccakCodeHash = ethers.utils.hexlify(hashBytecode(keccakCode)); From d695779ca7daad69f2c9f413a763465a455e381b Mon Sep 17 00:00:00 2001 From: koloz Date: Wed, 15 Nov 2023 18:03:28 -0500 Subject: [PATCH 24/36] added in additional check to make sure the mock keccak was in use --- test/Keccak256.spec.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/test/Keccak256.spec.ts b/test/Keccak256.spec.ts index 6fe7e5ab..a79a8142 100644 --- a/test/Keccak256.spec.ts +++ b/test/Keccak256.spec.ts @@ -162,10 +162,15 @@ describe('Keccak256 tests', function () { mockKeccakInput ); + var keccakCode = await getCode(KECCAK256_CONTRACT_ADDRESS); + var keccakCodeHash = ethers.utils.hexlify(hashBytecode(keccakCode)); + + expect(keccakCodeHash).to.eq(keccakMockCodeHash); + await hre.network.provider.send("hardhat_mine", ["0x100"]); - const keccakCode = await getCode(KECCAK256_CONTRACT_ADDRESS); - const keccakCodeHash = ethers.utils.hexlify(hashBytecode(keccakCode)); + keccakCode = await getCode(KECCAK256_CONTRACT_ADDRESS); + keccakCodeHash = ethers.utils.hexlify(hashBytecode(keccakCode)); expect(keccakCodeHash).to.eq(oldKeccakCodeHash); }) From a4dc0d95225764056e0cbb2f3edaf636c98fbad2 Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Thu, 16 Nov 2023 10:59:16 +0100 Subject: [PATCH 25/36] remove unneeded tests + make compilation work out of the box --- package.json | 2 +- scripts/compile-yul.ts | 27 ++++++++++++++---- test/Keccak256.spec.ts | 63 +++++++++++++++--------------------------- 3 files changed, 45 insertions(+), 47 deletions(-) diff --git a/package.json b/package.json index f4876dca..e51c8128 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,6 @@ "fmt": "prettier --config prettier.js --write contracts/*.sol contracts/**/*.sol", "preprocess": "rm -rf ./bootloader/build && yarn ts-node scripts/process.ts", "deploy-preimages": "ts-node scripts/deploy-preimages.ts", - "compile-yul": "ts-node scripts/compile-yul.ts" + "compile-yul": "ts-node scripts/compile-yul.ts compile-precompiles && yarn preprocess && ts-node scripts/compile-yul.ts compile-bootloader" } } \ No newline at end of file diff --git a/scripts/compile-yul.ts b/scripts/compile-yul.ts index aeb17f2d..5b214cbe 100644 --- a/scripts/compile-yul.ts +++ b/scripts/compile-yul.ts @@ -1,5 +1,6 @@ import * as hre from 'hardhat'; import * as fs from 'fs'; +import { Command } from 'commander'; import { exec as _exec, spawn as _spawn } from 'child_process'; import { getZksolcUrl, saltFromUrl } from '@matterlabs/hardhat-zksync-solc'; @@ -88,11 +89,27 @@ class CompilerPaths { async function main() { - await compileYulFolder('contracts'); - await compileYulFolder('contracts/precompiles'); - await compileYulFolder('contracts/precompiles/test-contracts'); - await compileYulFolder('bootloader/build'); - await compileYulFolder('bootloader/tests'); + + const program = new Command(); + + program.version('0.1.0').name('compile yul').description('publish preimages for the L2 contracts'); + + program + .command('compile-bootloader') + .action(async () => { + await compileYulFolder('bootloader/build'); + await compileYulFolder('bootloader/tests'); + }); + + program + .command('compile-precompiles') + .action(async () => { + await compileYulFolder('contracts'); + await compileYulFolder('contracts/precompiles'); + await compileYulFolder('contracts/precompiles/test-contracts'); + }); + + await program.parseAsync(process.argv); } main() diff --git a/test/Keccak256.spec.ts b/test/Keccak256.spec.ts index a79a8142..7d037944 100644 --- a/test/Keccak256.spec.ts +++ b/test/Keccak256.spec.ts @@ -67,36 +67,6 @@ describe('Keccak256 tests', function () { await keccakTest.zeroPointerTest() }); - it('general functionality test', async () => { - // We currently do not have fussing support, so we generate random data using - // hash function. - - const seed = ethers.utils.randomBytes(32); - // Displaying seed for reproducible tests - console.log('Keccak256 fussing seed', ethers.utils.hexlify(seed)); - - // Testing empty array - await compareCorrectHash('0x', testWallet.provider!); - const BLOCK_SIZE = 136; - - await compareCorrectHash(randomHexFromSeed(seed, BLOCK_SIZE), testWallet.provider!); - await compareCorrectHash(randomHexFromSeed(seed, BLOCK_SIZE - 1), testWallet.provider!); - await compareCorrectHash(randomHexFromSeed(seed, BLOCK_SIZE - 2), testWallet.provider!); - await compareCorrectHash(randomHexFromSeed(seed, BLOCK_SIZE + 1), testWallet.provider!); - await compareCorrectHash(randomHexFromSeed(seed, BLOCK_SIZE + 2), testWallet.provider!); - - await compareCorrectHash(randomHexFromSeed(seed, 101 * BLOCK_SIZE), testWallet.provider!); - await compareCorrectHash(randomHexFromSeed(seed, 101 * BLOCK_SIZE - 1), testWallet.provider!); - await compareCorrectHash(randomHexFromSeed(seed, 101 * BLOCK_SIZE - 2), testWallet.provider!); - await compareCorrectHash(randomHexFromSeed(seed, 101 * BLOCK_SIZE + 1), testWallet.provider!); - await compareCorrectHash(randomHexFromSeed(seed, 101 * BLOCK_SIZE + 2), testWallet.provider!); - - // In order to get random length, we use modulo operation - await compareCorrectHash(randomHexFromSeed(seed, ethers.BigNumber.from(seed).mod(113).toNumber()), testWallet.provider!); - await compareCorrectHash(randomHexFromSeed(seed, ethers.BigNumber.from(seed).mod(1101).toNumber()), testWallet.provider!); - await compareCorrectHash(randomHexFromSeed(seed, ethers.BigNumber.from(seed).mod(17).toNumber()), testWallet.provider!); - }); - it('keccak upgrade test', async() => { const deployerInterfact = new ethers.utils.Interface((await loadArtifact('ContractDeployer')).abi); @@ -125,20 +95,28 @@ describe('Keccak256 tests', function () { oldKeccakCodeHash ]); - const BLOCK_SIZE = 136; const seed = ethers.utils.randomBytes(32); - const input1 = randomHexFromSeed(seed, BLOCK_SIZE + 0); - const input2 = randomHexFromSeed(seed, BLOCK_SIZE - 1); - const input3 = randomHexFromSeed(seed, BLOCK_SIZE - 2); - const input4 = randomHexFromSeed(seed, BLOCK_SIZE + 1); - const input5 = randomHexFromSeed(seed, BLOCK_SIZE + 2); + // Displaying seed for reproducible tests + console.log('Keccak256 fussing seed', ethers.utils.hexlify(seed)); + + const BLOCK_SIZE = 136; const inputsToTest = [ - input1, - input2, - input3, - input4, - input5 + '0x', + randomHexFromSeed(seed, BLOCK_SIZE), + randomHexFromSeed(seed, BLOCK_SIZE - 1), + randomHexFromSeed(seed, BLOCK_SIZE - 2), + randomHexFromSeed(seed, BLOCK_SIZE + 1), + randomHexFromSeed(seed, BLOCK_SIZE + 2), + randomHexFromSeed(seed, 101 * BLOCK_SIZE), + randomHexFromSeed(seed, 101 * BLOCK_SIZE - 1), + randomHexFromSeed(seed, 101 * BLOCK_SIZE - 2), + randomHexFromSeed(seed, 101 * BLOCK_SIZE + 1), + randomHexFromSeed(seed, 101 * BLOCK_SIZE + 2), + // In order to get random length, we use modulo operation + randomHexFromSeed(seed, ethers.BigNumber.from(seed).mod(113).toNumber()), + randomHexFromSeed(seed, ethers.BigNumber.from(seed).mod(1101).toNumber()), + randomHexFromSeed(seed, ethers.BigNumber.from(seed).mod(17).toNumber()), ] const expectedOutput = inputsToTest.map((e) => ethers.utils.keccak256(e)); @@ -167,6 +145,9 @@ describe('Keccak256 tests', function () { expect(keccakCodeHash).to.eq(keccakMockCodeHash); + // Needed to create a new batch & thus start the bootloader once more. + // After this, the bootloader should automatically return the code hash to the + // previous one. await hre.network.provider.send("hardhat_mine", ["0x100"]); keccakCode = await getCode(KECCAK256_CONTRACT_ADDRESS); From 54c4a77991fa630c924e80c2b607c27cd2572495 Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Tue, 21 Nov 2023 17:03:22 +0100 Subject: [PATCH 26/36] fix build scripts --- .github/workflows/ci.yaml | 2 +- README.md | 4 +--- bootloader/test_infra/README.md | 2 +- bootloader/tests/README.md | 2 +- package.json | 4 +++- scripts/quick-setup.sh | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 1b370e0b..6d15a9cc 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -22,7 +22,7 @@ jobs: run: yarn build - name: Build yul artifacts - run: yarn preprocess && yarn compile-yul + run: yarn build-yul - name: Create cache uses: actions/cache/save@v3 diff --git a/README.md b/README.md index 3842e054..9a46941c 100644 --- a/README.md +++ b/README.md @@ -34,9 +34,7 @@ This repository is used as a submodule of the [zksync-2-dev](https://github.com/ Compile the solidity contracts: `yarn build` -Run the bootloader preprocessor: `yarn preprocess` - -Compile the yul contracts: `yarn hardhat run ./scripts/compile-yul.ts` +Compile the yul contracts: `yarn build-yul` ## Update Process diff --git a/bootloader/test_infra/README.md b/bootloader/test_infra/README.md index cf846f13..1373142e 100644 --- a/bootloader/test_infra/README.md +++ b/bootloader/test_infra/README.md @@ -5,7 +5,7 @@ This crate allows you to run the unittests against the bootloader code. You should put your tests in ../tests/bootloader/bootloader_test.yul, then compile the yul with: ```shell -yarn build && yarn preprocess && yarn compile-yul +yarn build && yarn build-yul ``` And afterwards run the testing infrastructure: diff --git a/bootloader/tests/README.md b/bootloader/tests/README.md index fbebf447..3753c11a 100644 --- a/bootloader/tests/README.md +++ b/bootloader/tests/README.md @@ -11,7 +11,7 @@ Please put bootloader unittests in `bootloader/bootloader_test.yul` file, and an To execute tests, you should first run yarn to prepare the source code: ```shell -yarn preprocess && yarn compile-yul +yarn build-yul ``` And then run the test framework: diff --git a/package.json b/package.json index e51c8128..ee2b9ae2 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,8 @@ "fmt": "prettier --config prettier.js --write contracts/*.sol contracts/**/*.sol", "preprocess": "rm -rf ./bootloader/build && yarn ts-node scripts/process.ts", "deploy-preimages": "ts-node scripts/deploy-preimages.ts", - "compile-yul": "ts-node scripts/compile-yul.ts compile-precompiles && yarn preprocess && ts-node scripts/compile-yul.ts compile-bootloader" + "compile-precompiles": "ts-node scripts/compile-yul.ts compile-precompiles", + "compile-bootloader": "ts-node scripts/compile-yul.ts compile-bootloader", + "build-yul": "yarn compile-precompiles && yarn preprocess && yarn compile-bootloader" } } \ No newline at end of file diff --git a/scripts/quick-setup.sh b/scripts/quick-setup.sh index da6bec9c..9a8d4e99 100755 --- a/scripts/quick-setup.sh +++ b/scripts/quick-setup.sh @@ -10,7 +10,7 @@ cargo +nightly install --git https://github.com/matter-labs/era-test-node.git -- yarn yarn build -yarn preprocess && yarn compile-yul +yarn build-yul era_test_node run > /dev/null 2>&1 & export TEST_NODE_PID=$! yarn test kill $TEST_NODE_PID \ No newline at end of file From 4c5a6cdb3664ac142ce9c6793e793721d606db48 Mon Sep 17 00:00:00 2001 From: Stanislav Bezkorovainyi Date: Tue, 21 Nov 2023 17:12:39 +0100 Subject: [PATCH 27/36] Update README.md Co-authored-by: Vlad Bochok <41153528+vladbochok@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9a46941c..dc0639a2 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ Since scripts, READMEs, etc., are code that is not subject to audits, these are ## Testing -The tests of the system contracts utilize the zkSync test node. In order to run the tests, execute the following command in the root of hte repository: +The tests of the system contracts utilize the zkSync test node. In order to run the tests, execute the following command in the root of the repository: ``` bash ./scripts/quick-setup.sh From 9703ff5a3f8147697e8be34cedf10148ece6e575 Mon Sep 17 00:00:00 2001 From: Stanislav Bezkorovainyi Date: Tue, 21 Nov 2023 17:13:17 +0100 Subject: [PATCH 28/36] Update contracts/precompiles/Keccak256.yul Co-authored-by: Vlad Bochok <41153528+vladbochok@users.noreply.github.com> --- contracts/precompiles/Keccak256.yul | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/precompiles/Keccak256.yul b/contracts/precompiles/Keccak256.yul index 17235a36..1ae0d900 100644 --- a/contracts/precompiles/Keccak256.yul +++ b/contracts/precompiles/Keccak256.yul @@ -1,7 +1,7 @@ /** * @author Matter Labs * @notice The contract used to emulate EVM's keccak256 opcode. - * @dev It accepts the data to be hashed in the calldata, propagate it to the zkEVM built-in circuit precompile via `precompileCall` and burn . + * @dev It accepts the data to be hashed in the calldata, propagates it to the zkEVM built-in circuit precompile via `precompileCall`, and burns the gas. */ object "Keccak256" { code { From 66cfc622ddaa55065105388c8ad3fd178224be59 Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Tue, 21 Nov 2023 17:16:41 +0100 Subject: [PATCH 29/36] resolve nits --- bootloader/bootloader.yul | 6 +++++- contracts/precompiles/Keccak256.yul | 1 + scripts/quick-setup.sh | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/bootloader/bootloader.yul b/bootloader/bootloader.yul index 432f2955..af3ce1dc 100644 --- a/bootloader/bootloader.yul +++ b/bootloader/bootloader.yul @@ -2024,7 +2024,11 @@ object "Bootloader" { /// @dev Checks whether an address is an EOA (i.e. has not code deployed on it) /// @param addr The address to check function isEOA(addr) -> ret { - ret := iszero(getRawCodeHash(addr, false)) + ret := 0 + + if gt(addr, MAX_SYSTEM_CONTRACT_ADDR()) { + ret := iszero(getRawCodeHash(addr, false)) + } } /// @dev Calls the `payForTransaction` method of an account diff --git a/contracts/precompiles/Keccak256.yul b/contracts/precompiles/Keccak256.yul index 17235a36..40b1afd0 100644 --- a/contracts/precompiles/Keccak256.yul +++ b/contracts/precompiles/Keccak256.yul @@ -1,5 +1,6 @@ /** * @author Matter Labs + * @custom:security-contact security@matterlabs.dev * @notice The contract used to emulate EVM's keccak256 opcode. * @dev It accepts the data to be hashed in the calldata, propagate it to the zkEVM built-in circuit precompile via `precompileCall` and burn . */ diff --git a/scripts/quick-setup.sh b/scripts/quick-setup.sh index 9a8d4e99..efbd8e66 100755 --- a/scripts/quick-setup.sh +++ b/scripts/quick-setup.sh @@ -13,4 +13,4 @@ yarn build yarn build-yul era_test_node run > /dev/null 2>&1 & export TEST_NODE_PID=$! yarn test -kill $TEST_NODE_PID \ No newline at end of file +kill $TEST_NODE_PID From 8d0915d9a2223e5c6ff019b963e2dcb9f17d5bce Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Sun, 26 Nov 2023 17:26:36 +0100 Subject: [PATCH 30/36] fmt --- contracts/test-contracts/KeccakTest.sol | 4 +- scripts/compile-yul.ts | 36 ++- scripts/process.ts | 109 ++++---- test/Keccak256.spec.ts | 319 +++++++++++------------- test/shared/constants.ts | 22 +- 5 files changed, 229 insertions(+), 261 deletions(-) diff --git a/contracts/test-contracts/KeccakTest.sol b/contracts/test-contracts/KeccakTest.sol index 7a9aa033..75a58ac7 100644 --- a/contracts/test-contracts/KeccakTest.sol +++ b/contracts/test-contracts/KeccakTest.sol @@ -111,9 +111,7 @@ contract KeccakTest { require(hash == EMPTY_STRING_KECCAK, "Keccak should start working again"); } - function keccakPerformUpgrade( - bytes calldata upgradeCalldata - ) external { + function keccakPerformUpgrade(bytes calldata upgradeCalldata) external { EfficientCall.mimicCall( gasleft(), address(DEPLOYER_SYSTEM_CONTRACT), diff --git a/scripts/compile-yul.ts b/scripts/compile-yul.ts index 6a8b168e..dc651674 100644 --- a/scripts/compile-yul.ts +++ b/scripts/compile-yul.ts @@ -88,26 +88,22 @@ class CompilerPaths { } async function main() { - const program = new Command(); - - program.version('0.1.0').name('compile yul').description('publish preimages for the L2 contracts'); - - program - .command('compile-bootloader') - .action(async () => { - await compileYulFolder('bootloader/build'); - await compileYulFolder('bootloader/tests'); - }); - - program - .command('compile-precompiles') - .action(async () => { - await compileYulFolder('contracts'); - await compileYulFolder('contracts/precompiles'); - await compileYulFolder('contracts/precompiles/test-contracts'); - }); - - await program.parseAsync(process.argv); + const program = new Command(); + + program.version("0.1.0").name("compile yul").description("publish preimages for the L2 contracts"); + + program.command("compile-bootloader").action(async () => { + await compileYulFolder("bootloader/build"); + await compileYulFolder("bootloader/tests"); + }); + + program.command("compile-precompiles").action(async () => { + await compileYulFolder("contracts"); + await compileYulFolder("contracts/precompiles"); + await compileYulFolder("contracts/precompiles/test-contracts"); + }); + + await program.parseAsync(process.argv); } main() diff --git a/scripts/process.ts b/scripts/process.ts index 2cbfa322..efde8973 100644 --- a/scripts/process.ts +++ b/scripts/process.ts @@ -42,64 +42,67 @@ function getPaddedSelector(contractName: string, method: string): string { } function getKeccak256ExpectedHash() { - const bytecode = readFileSync('contracts/precompiles/artifacts/Keccak256.yul/Keccak256.yul.zbin'); - return ethers.utils.hexlify(utils.hashBytecode(bytecode)); + const bytecode = readFileSync("contracts/precompiles/artifacts/Keccak256.yul/Keccak256.yul.zbin"); + return ethers.utils.hexlify(utils.hashBytecode(bytecode)); } // Maybe in the future some of these params will be passed // in a JSON file. For now, a simple object is ok here. const params = { - MARK_BATCH_AS_REPUBLISHED_SELECTOR: getSelector('KnownCodesStorage', 'markFactoryDeps'), - VALIDATE_TX_SELECTOR: getSelector('IAccount', 'validateTransaction'), - EXECUTE_TX_SELECTOR: getSelector('DefaultAccount', 'executeTransaction'), - RIGHT_PADDED_GET_ACCOUNT_VERSION_SELECTOR: getPaddedSelector('ContractDeployer', 'extendedAccountVersion'), - RIGHT_PADDED_GET_RAW_CODE_HASH_SELECTOR: getPaddedSelector('AccountCodeStorage', 'getRawCodeHash'), - PAY_FOR_TX_SELECTOR: getSelector('DefaultAccount', 'payForTransaction'), - PRE_PAYMASTER_SELECTOR: getSelector('DefaultAccount', 'prepareForPaymaster'), - VALIDATE_AND_PAY_PAYMASTER: getSelector('IPaymaster', 'validateAndPayForPaymasterTransaction'), - // It doesn't used directly now but is important to keep the way to regenerate it when needed - TX_UTILITIES: getTransactionUtils(), - RIGHT_PADDED_POST_TRANSACTION_SELECTOR: getPaddedSelector('IPaymaster', 'postTransaction'), - RIGHT_PADDED_SET_TX_ORIGIN: getPaddedSelector('SystemContext', 'setTxOrigin'), - RIGHT_PADDED_SET_GAS_PRICE: getPaddedSelector('SystemContext', 'setGasPrice'), - RIGHT_PADDED_INCREMENT_TX_NUMBER_IN_BLOCK_SELECTOR: getPaddedSelector('SystemContext', 'incrementTxNumberInBatch'), - RIGHT_PADDED_RESET_TX_NUMBER_IN_BLOCK_SELECTOR: getPaddedSelector('SystemContext', 'resetTxNumberInBatch'), - RIGHT_PADDED_SEND_L2_TO_L1_LOG_SELECTOR: getPaddedSelector('L1Messenger', 'sendL2ToL1Log'), - PUBLISH_PUBDATA_SELECTOR: getSelector('L1Messenger', 'publishPubdataAndClearState'), - RIGHT_PADDED_SET_NEW_BATCH_SELECTOR: getPaddedSelector('SystemContext', 'setNewBatch'), - RIGHT_PADDED_OVERRIDE_BATCH_SELECTOR: getPaddedSelector('SystemContext', 'unsafeOverrideBatch'), - // Error - REVERT_ERROR_SELECTOR: padZeroRight(getRevertSelector(), PADDED_SELECTOR_LENGTH), - RIGHT_PADDED_VALIDATE_NONCE_USAGE_SELECTOR: getPaddedSelector('INonceHolder', 'validateNonceUsage'), - RIGHT_PADDED_MINT_ETHER_SELECTOR: getPaddedSelector('L2EthToken', 'mint'), - GET_TX_HASHES_SELECTOR: getSelector('BootloaderUtilities', 'getTransactionHashes'), - CREATE_SELECTOR: getSelector('ContractDeployer', 'create'), - CREATE2_SELECTOR: getSelector('ContractDeployer', 'create2'), - CREATE_ACCOUNT_SELECTOR: getSelector('ContractDeployer', 'createAccount'), - CREATE2_ACCOUNT_SELECTOR: getSelector('ContractDeployer', 'create2Account'), - PADDED_TRANSFER_FROM_TO_SELECTOR: getPaddedSelector('L2EthToken', 'transferFromTo'), - SUCCESSFUL_ACCOUNT_VALIDATION_MAGIC_VALUE: getPaddedSelector('IAccount', 'validateTransaction'), - SUCCESSFUL_PAYMASTER_VALIDATION_MAGIC_VALUE: getPaddedSelector('IPaymaster', 'validateAndPayForPaymasterTransaction'), - PUBLISH_COMPRESSED_BYTECODE_SELECTOR: getSelector('Compressor', 'publishCompressedBytecode'), - GET_MARKER_PADDED_SELECTOR: getPaddedSelector('KnownCodesStorage', 'getMarker'), - RIGHT_PADDED_SET_L2_BLOCK_SELECTOR: getPaddedSelector('SystemContext', 'setL2Block'), - RIGHT_PADDED_APPEND_TRANSACTION_TO_L2_BLOCK_SELECTOR: getPaddedSelector('SystemContext', 'appendTransactionToCurrentL2Block'), - RIGHT_PADDED_PUBLISH_TIMESTAMP_DATA_TO_L1_SELECTOR: getPaddedSelector('SystemContext', 'publishTimestampDataToL1'), - COMPRESSED_BYTECODES_SLOTS: 32768, - ENSURE_RETURNED_MAGIC: 1, - FORBID_ZERO_GAS_PER_PUBDATA: 1, - KECCAK256_EXPECTED_CODE_HASH: getKeccak256ExpectedHash(), - PADDED_FORCE_DEPLOY_KECCAK256_SELECTOR: getPaddedSelector('ContractDeployer', 'forceDeployKeccak256'), - // One of "worst case" scenarios for the number of state diffs in a batch is when 120kb of pubdata is spent - // on repeated writes, that are all zeroed out. In this case, the number of diffs is 120k / 5 = 24k. This means that they will have - // accoomdate 6528000 bytes of calldata for the uncompressed state diffs. Adding 120k on top leaves us with - // roughly 6650000 bytes needed for calldata. 207813 slots are needed to accomodate this amount of data. - // We round up to 208000 slots just in case. - // - // In theory though much more calldata could be used (if for instance 1 byte is used for enum index). It is the responsibility of the - // operator to ensure that it can form the correct calldata for the L1Messenger. - OPERATOR_PROVIDED_L1_MESSENGER_PUBDATA_SLOTS: 208000, - ...SYSTEM_PARAMS + MARK_BATCH_AS_REPUBLISHED_SELECTOR: getSelector("KnownCodesStorage", "markFactoryDeps"), + VALIDATE_TX_SELECTOR: getSelector("IAccount", "validateTransaction"), + EXECUTE_TX_SELECTOR: getSelector("DefaultAccount", "executeTransaction"), + RIGHT_PADDED_GET_ACCOUNT_VERSION_SELECTOR: getPaddedSelector("ContractDeployer", "extendedAccountVersion"), + RIGHT_PADDED_GET_RAW_CODE_HASH_SELECTOR: getPaddedSelector("AccountCodeStorage", "getRawCodeHash"), + PAY_FOR_TX_SELECTOR: getSelector("DefaultAccount", "payForTransaction"), + PRE_PAYMASTER_SELECTOR: getSelector("DefaultAccount", "prepareForPaymaster"), + VALIDATE_AND_PAY_PAYMASTER: getSelector("IPaymaster", "validateAndPayForPaymasterTransaction"), + // It doesn't used directly now but is important to keep the way to regenerate it when needed + TX_UTILITIES: getTransactionUtils(), + RIGHT_PADDED_POST_TRANSACTION_SELECTOR: getPaddedSelector("IPaymaster", "postTransaction"), + RIGHT_PADDED_SET_TX_ORIGIN: getPaddedSelector("SystemContext", "setTxOrigin"), + RIGHT_PADDED_SET_GAS_PRICE: getPaddedSelector("SystemContext", "setGasPrice"), + RIGHT_PADDED_INCREMENT_TX_NUMBER_IN_BLOCK_SELECTOR: getPaddedSelector("SystemContext", "incrementTxNumberInBatch"), + RIGHT_PADDED_RESET_TX_NUMBER_IN_BLOCK_SELECTOR: getPaddedSelector("SystemContext", "resetTxNumberInBatch"), + RIGHT_PADDED_SEND_L2_TO_L1_LOG_SELECTOR: getPaddedSelector("L1Messenger", "sendL2ToL1Log"), + PUBLISH_PUBDATA_SELECTOR: getSelector("L1Messenger", "publishPubdataAndClearState"), + RIGHT_PADDED_SET_NEW_BATCH_SELECTOR: getPaddedSelector("SystemContext", "setNewBatch"), + RIGHT_PADDED_OVERRIDE_BATCH_SELECTOR: getPaddedSelector("SystemContext", "unsafeOverrideBatch"), + // Error + REVERT_ERROR_SELECTOR: padZeroRight(getRevertSelector(), PADDED_SELECTOR_LENGTH), + RIGHT_PADDED_VALIDATE_NONCE_USAGE_SELECTOR: getPaddedSelector("INonceHolder", "validateNonceUsage"), + RIGHT_PADDED_MINT_ETHER_SELECTOR: getPaddedSelector("L2EthToken", "mint"), + GET_TX_HASHES_SELECTOR: getSelector("BootloaderUtilities", "getTransactionHashes"), + CREATE_SELECTOR: getSelector("ContractDeployer", "create"), + CREATE2_SELECTOR: getSelector("ContractDeployer", "create2"), + CREATE_ACCOUNT_SELECTOR: getSelector("ContractDeployer", "createAccount"), + CREATE2_ACCOUNT_SELECTOR: getSelector("ContractDeployer", "create2Account"), + PADDED_TRANSFER_FROM_TO_SELECTOR: getPaddedSelector("L2EthToken", "transferFromTo"), + SUCCESSFUL_ACCOUNT_VALIDATION_MAGIC_VALUE: getPaddedSelector("IAccount", "validateTransaction"), + SUCCESSFUL_PAYMASTER_VALIDATION_MAGIC_VALUE: getPaddedSelector("IPaymaster", "validateAndPayForPaymasterTransaction"), + PUBLISH_COMPRESSED_BYTECODE_SELECTOR: getSelector("Compressor", "publishCompressedBytecode"), + GET_MARKER_PADDED_SELECTOR: getPaddedSelector("KnownCodesStorage", "getMarker"), + RIGHT_PADDED_SET_L2_BLOCK_SELECTOR: getPaddedSelector("SystemContext", "setL2Block"), + RIGHT_PADDED_APPEND_TRANSACTION_TO_L2_BLOCK_SELECTOR: getPaddedSelector( + "SystemContext", + "appendTransactionToCurrentL2Block" + ), + RIGHT_PADDED_PUBLISH_TIMESTAMP_DATA_TO_L1_SELECTOR: getPaddedSelector("SystemContext", "publishTimestampDataToL1"), + COMPRESSED_BYTECODES_SLOTS: 32768, + ENSURE_RETURNED_MAGIC: 1, + FORBID_ZERO_GAS_PER_PUBDATA: 1, + KECCAK256_EXPECTED_CODE_HASH: getKeccak256ExpectedHash(), + PADDED_FORCE_DEPLOY_KECCAK256_SELECTOR: getPaddedSelector("ContractDeployer", "forceDeployKeccak256"), + // One of "worst case" scenarios for the number of state diffs in a batch is when 120kb of pubdata is spent + // on repeated writes, that are all zeroed out. In this case, the number of diffs is 120k / 5 = 24k. This means that they will have + // accoomdate 6528000 bytes of calldata for the uncompressed state diffs. Adding 120k on top leaves us with + // roughly 6650000 bytes needed for calldata. 207813 slots are needed to accomodate this amount of data. + // We round up to 208000 slots just in case. + // + // In theory though much more calldata could be used (if for instance 1 byte is used for enum index). It is the responsibility of the + // operator to ensure that it can form the correct calldata for the L1Messenger. + OPERATOR_PROVIDED_L1_MESSENGER_PUBDATA_SLOTS: 208000, + ...SYSTEM_PARAMS, }; function extractTestFunctionNames(sourceCode: string): string[] { diff --git a/test/Keccak256.spec.ts b/test/Keccak256.spec.ts index 7d037944..ec296bf9 100644 --- a/test/Keccak256.spec.ts +++ b/test/Keccak256.spec.ts @@ -1,183 +1,154 @@ -import { CONTRACT_DEPLOYER_ADDRESS, hashBytecode } from 'zksync-web3/build/src/utils'; -import { KeccakTest, KeccakTest__factory } from '../typechain-types'; -import { KECCAK256_CONTRACT_ADDRESS } from './shared/constants'; -import { getWallets, loadArtifact, publishBytecode, setCode, getCode } from './shared/utils'; -import { ethers } from 'hardhat'; -import { readYulBytecode } from '../scripts/utils'; -import { Language } from '../scripts/constants'; -import { BytesLike, Wallet, providers } from 'ethers'; -import { expect } from 'chai'; -import * as hre from 'hardhat'; - -describe('Keccak256 tests', function () { - let testWallet: Wallet; - let keccakTest: KeccakTest; - - let oldKeccakCodeHash: string; - let correctKeccakCodeHash: string; - let alwaysRevertCodeHash: string; - let keccakMockCodeHash: string; - - // Kernel space address, needed to enable mimicCall - const KECCAK_TEST_ADDRESS = '0x0000000000000000000000000000000000009000'; - - before(async () => { - testWallet = getWallets()[0]; - - await setCode( - KECCAK_TEST_ADDRESS, - (await loadArtifact('KeccakTest')).bytecode - ); - - const keccakCode = await getCode(KECCAK256_CONTRACT_ADDRESS); - oldKeccakCodeHash = ethers.utils.hexlify(hashBytecode(keccakCode)); - - const keccakMockCode = readYulBytecode({ - codeName: 'Keccak256Mock', - path: 'precompiles/test-contracts', - lang: Language.Yul, - address: ethers.constants.AddressZero - }); - - keccakMockCodeHash = ethers.utils.hexlify(hashBytecode(keccakMockCode)); - - keccakTest = KeccakTest__factory.connect(KECCAK_TEST_ADDRESS, getWallets()[0]); - const correctKeccakCode = readYulBytecode({ - codeName: 'Keccak256', - path: 'precompiles', - lang: Language.Yul, - address: ethers.constants.AddressZero - }); - - const correctContractDeployerCode = (await loadArtifact('ContractDeployer')).bytecode; - await setCode(CONTRACT_DEPLOYER_ADDRESS, correctContractDeployerCode); - - const emptyContractCode = (await loadArtifact('AlwaysRevert')).bytecode; - - await publishBytecode(keccakCode); - await publishBytecode(correctKeccakCode); - await publishBytecode(emptyContractCode); - await publishBytecode(keccakMockCode); - - correctKeccakCodeHash = ethers.utils.hexlify(hashBytecode(correctKeccakCode)); - alwaysRevertCodeHash = ethers.utils.hexlify(hashBytecode(emptyContractCode)); +import { CONTRACT_DEPLOYER_ADDRESS, hashBytecode } from "zksync-web3/build/src/utils"; +import { KeccakTest, KeccakTest__factory } from "../typechain-types"; +import { KECCAK256_CONTRACT_ADDRESS } from "./shared/constants"; +import { getWallets, loadArtifact, publishBytecode, setCode, getCode } from "./shared/utils"; +import { ethers } from "hardhat"; +import { readYulBytecode } from "../scripts/utils"; +import { Language } from "../scripts/constants"; +import { BytesLike, Wallet, providers } from "ethers"; +import { expect } from "chai"; +import * as hre from "hardhat"; + +describe("Keccak256 tests", function () { + let testWallet: Wallet; + let keccakTest: KeccakTest; + + let oldKeccakCodeHash: string; + let correctKeccakCodeHash: string; + let alwaysRevertCodeHash: string; + let keccakMockCodeHash: string; + + // Kernel space address, needed to enable mimicCall + const KECCAK_TEST_ADDRESS = "0x0000000000000000000000000000000000009000"; + + before(async () => { + testWallet = getWallets()[0]; + + await setCode(KECCAK_TEST_ADDRESS, (await loadArtifact("KeccakTest")).bytecode); + + const keccakCode = await getCode(KECCAK256_CONTRACT_ADDRESS); + oldKeccakCodeHash = ethers.utils.hexlify(hashBytecode(keccakCode)); + + const keccakMockCode = readYulBytecode({ + codeName: "Keccak256Mock", + path: "precompiles/test-contracts", + lang: Language.Yul, + address: ethers.constants.AddressZero, }); - it('zero pointer test', async () => { - await keccakTest.zeroPointerTest() + keccakMockCodeHash = ethers.utils.hexlify(hashBytecode(keccakMockCode)); + + keccakTest = KeccakTest__factory.connect(KECCAK_TEST_ADDRESS, getWallets()[0]); + const correctKeccakCode = readYulBytecode({ + codeName: "Keccak256", + path: "precompiles", + lang: Language.Yul, + address: ethers.constants.AddressZero, }); - it('keccak upgrade test', async() => { - const deployerInterfact = new ethers.utils.Interface((await loadArtifact('ContractDeployer')).abi); - - const eraseInput = deployerInterfact.encodeFunctionData('forceDeployKeccak256', [ - alwaysRevertCodeHash - ]); - - const upgradeInput = deployerInterfact.encodeFunctionData('forceDeployKeccak256', [ - correctKeccakCodeHash - ]); - - await keccakTest.keccakUpgradeTest( - eraseInput, - upgradeInput - ); - }) - - it('keccak validation test', async() => { - const deployerInterfact = new ethers.utils.Interface((await loadArtifact('ContractDeployer')).abi); - - const upgradeInput = deployerInterfact.encodeFunctionData('forceDeployKeccak256', [ - correctKeccakCodeHash - ]); - - const resetInput = deployerInterfact.encodeFunctionData('forceDeployKeccak256', [ - oldKeccakCodeHash - ]); - - const seed = ethers.utils.randomBytes(32); - // Displaying seed for reproducible tests - console.log('Keccak256 fussing seed', ethers.utils.hexlify(seed)); - - const BLOCK_SIZE = 136; - - const inputsToTest = [ - '0x', - randomHexFromSeed(seed, BLOCK_SIZE), - randomHexFromSeed(seed, BLOCK_SIZE - 1), - randomHexFromSeed(seed, BLOCK_SIZE - 2), - randomHexFromSeed(seed, BLOCK_SIZE + 1), - randomHexFromSeed(seed, BLOCK_SIZE + 2), - randomHexFromSeed(seed, 101 * BLOCK_SIZE), - randomHexFromSeed(seed, 101 * BLOCK_SIZE - 1), - randomHexFromSeed(seed, 101 * BLOCK_SIZE - 2), - randomHexFromSeed(seed, 101 * BLOCK_SIZE + 1), - randomHexFromSeed(seed, 101 * BLOCK_SIZE + 2), - // In order to get random length, we use modulo operation - randomHexFromSeed(seed, ethers.BigNumber.from(seed).mod(113).toNumber()), - randomHexFromSeed(seed, ethers.BigNumber.from(seed).mod(1101).toNumber()), - randomHexFromSeed(seed, ethers.BigNumber.from(seed).mod(17).toNumber()), - ] - - const expectedOutput = inputsToTest.map((e) => ethers.utils.keccak256(e)); - - await keccakTest.keccakValidationTest( - upgradeInput, - resetInput, - inputsToTest, - expectedOutput - ); - }) - - it('keccak upgrade if needed test', async() => { - const deployerInterfact = new ethers.utils.Interface((await loadArtifact('ContractDeployer')).abi); - - const mockKeccakInput = deployerInterfact.encodeFunctionData('forceDeployKeccak256', [ - keccakMockCodeHash - ]); - - await keccakTest.keccakPerformUpgrade( - mockKeccakInput - ); - - var keccakCode = await getCode(KECCAK256_CONTRACT_ADDRESS); - var keccakCodeHash = ethers.utils.hexlify(hashBytecode(keccakCode)); - - expect(keccakCodeHash).to.eq(keccakMockCodeHash); - - // Needed to create a new batch & thus start the bootloader once more. - // After this, the bootloader should automatically return the code hash to the - // previous one. - await hre.network.provider.send("hardhat_mine", ["0x100"]); - - keccakCode = await getCode(KECCAK256_CONTRACT_ADDRESS); - keccakCodeHash = ethers.utils.hexlify(hashBytecode(keccakCode)); - - expect(keccakCodeHash).to.eq(oldKeccakCodeHash); - }) + const correctContractDeployerCode = (await loadArtifact("ContractDeployer")).bytecode; + await setCode(CONTRACT_DEPLOYER_ADDRESS, correctContractDeployerCode); + + const emptyContractCode = (await loadArtifact("AlwaysRevert")).bytecode; + + await publishBytecode(keccakCode); + await publishBytecode(correctKeccakCode); + await publishBytecode(emptyContractCode); + await publishBytecode(keccakMockCode); + + correctKeccakCodeHash = ethers.utils.hexlify(hashBytecode(correctKeccakCode)); + alwaysRevertCodeHash = ethers.utils.hexlify(hashBytecode(emptyContractCode)); + }); + + it("zero pointer test", async () => { + await keccakTest.zeroPointerTest(); + }); + + it("keccak upgrade test", async () => { + const deployerInterfact = new ethers.utils.Interface((await loadArtifact("ContractDeployer")).abi); + + const eraseInput = deployerInterfact.encodeFunctionData("forceDeployKeccak256", [alwaysRevertCodeHash]); + + const upgradeInput = deployerInterfact.encodeFunctionData("forceDeployKeccak256", [correctKeccakCodeHash]); + + await keccakTest.keccakUpgradeTest(eraseInput, upgradeInput); + }); + + it("keccak validation test", async () => { + const deployerInterfact = new ethers.utils.Interface((await loadArtifact("ContractDeployer")).abi); + + const upgradeInput = deployerInterfact.encodeFunctionData("forceDeployKeccak256", [correctKeccakCodeHash]); + + const resetInput = deployerInterfact.encodeFunctionData("forceDeployKeccak256", [oldKeccakCodeHash]); + + const seed = ethers.utils.randomBytes(32); + // Displaying seed for reproducible tests + console.log("Keccak256 fussing seed", ethers.utils.hexlify(seed)); + + const BLOCK_SIZE = 136; + + const inputsToTest = [ + "0x", + randomHexFromSeed(seed, BLOCK_SIZE), + randomHexFromSeed(seed, BLOCK_SIZE - 1), + randomHexFromSeed(seed, BLOCK_SIZE - 2), + randomHexFromSeed(seed, BLOCK_SIZE + 1), + randomHexFromSeed(seed, BLOCK_SIZE + 2), + randomHexFromSeed(seed, 101 * BLOCK_SIZE), + randomHexFromSeed(seed, 101 * BLOCK_SIZE - 1), + randomHexFromSeed(seed, 101 * BLOCK_SIZE - 2), + randomHexFromSeed(seed, 101 * BLOCK_SIZE + 1), + randomHexFromSeed(seed, 101 * BLOCK_SIZE + 2), + // In order to get random length, we use modulo operation + randomHexFromSeed(seed, ethers.BigNumber.from(seed).mod(113).toNumber()), + randomHexFromSeed(seed, ethers.BigNumber.from(seed).mod(1101).toNumber()), + randomHexFromSeed(seed, ethers.BigNumber.from(seed).mod(17).toNumber()), + ]; + + const expectedOutput = inputsToTest.map((e) => ethers.utils.keccak256(e)); + + await keccakTest.keccakValidationTest(upgradeInput, resetInput, inputsToTest, expectedOutput); + }); + + it("keccak upgrade if needed test", async () => { + const deployerInterfact = new ethers.utils.Interface((await loadArtifact("ContractDeployer")).abi); + + const mockKeccakInput = deployerInterfact.encodeFunctionData("forceDeployKeccak256", [keccakMockCodeHash]); + + await keccakTest.keccakPerformUpgrade(mockKeccakInput); + + var keccakCode = await getCode(KECCAK256_CONTRACT_ADDRESS); + var keccakCodeHash = ethers.utils.hexlify(hashBytecode(keccakCode)); + + expect(keccakCodeHash).to.eq(keccakMockCodeHash); + + // Needed to create a new batch & thus start the bootloader once more. + // After this, the bootloader should automatically return the code hash to the + // previous one. + await hre.network.provider.send("hardhat_mine", ["0x100"]); + + keccakCode = await getCode(KECCAK256_CONTRACT_ADDRESS); + keccakCodeHash = ethers.utils.hexlify(hashBytecode(keccakCode)); + + expect(keccakCodeHash).to.eq(oldKeccakCodeHash); + }); }); -async function compareCorrectHash ( - data: BytesLike, - provider: providers.Provider -) { - const correctHash = ethers.utils.keccak256(data); - const hashFromPrecompile = await provider.call({ - to: KECCAK256_CONTRACT_ADDRESS, - data - }); - expect(hashFromPrecompile).to.equal(correctHash, 'Hash is incorrect'); +async function compareCorrectHash(data: BytesLike, provider: providers.Provider) { + const correctHash = ethers.utils.keccak256(data); + const hashFromPrecompile = await provider.call({ + to: KECCAK256_CONTRACT_ADDRESS, + data, + }); + expect(hashFromPrecompile).to.equal(correctHash, "Hash is incorrect"); } -function randomHexFromSeed( - seed: BytesLike, - len: number, -) { - const hexLen = len * 2 + 2; - let data = '0x'; - while (data.length < hexLen) { - const next = ethers.utils.keccak256(ethers.utils.hexConcat([seed, data])); - data = ethers.utils.hexConcat([data, next]); - } - return data.substring(0, hexLen); +function randomHexFromSeed(seed: BytesLike, len: number) { + const hexLen = len * 2 + 2; + let data = "0x"; + while (data.length < hexLen) { + const next = ethers.utils.keccak256(ethers.utils.hexConcat([seed, data])); + data = ethers.utils.hexConcat([data, next]); + } + return data.substring(0, hexLen); } diff --git a/test/shared/constants.ts b/test/shared/constants.ts index 3bd88ea4..135e4b13 100644 --- a/test/shared/constants.ts +++ b/test/shared/constants.ts @@ -1,15 +1,15 @@ import { BigNumber } from "ethers"; -export const BOOTLOADER_FORMAL_ADDRESS = '0x0000000000000000000000000000000000008001'; -export const NONCE_HOLDER_SYSTEM_CONTRACT_ADDRESS = '0x0000000000000000000000000000000000008003'; -export const KNOWN_CODE_STORAGE_CONTRACT_ADDRESS = '0x0000000000000000000000000000000000008004'; -export const DEPLOYER_SYSTEM_CONTRACT_ADDRESS = '0x0000000000000000000000000000000000008006'; -export const FORCE_DEPLOYER_ADDRESS = '0x0000000000000000000000000000000000008007'; -export const L1_MESSENGER_SYSTEM_CONTRACT_ADDRESS = '0x0000000000000000000000000000000000008008'; -export const ETH_TOKEN_SYSTEM_CONTRACT_ADDRESS = '0x000000000000000000000000000000000000800a'; -export const EVENT_WRITER_CONTRACT_ADDRESS = '0x000000000000000000000000000000000000800d'; -export const COMPRESSOR_CONTRACT_ADDRESS = '0x000000000000000000000000000000000000800e'; -export const KECCAK256_CONTRACT_ADDRESS = '0x0000000000000000000000000000000000008010'; -export const EMPTY_STRING_KECCAK = '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470'; +export const BOOTLOADER_FORMAL_ADDRESS = "0x0000000000000000000000000000000000008001"; +export const NONCE_HOLDER_SYSTEM_CONTRACT_ADDRESS = "0x0000000000000000000000000000000000008003"; +export const KNOWN_CODE_STORAGE_CONTRACT_ADDRESS = "0x0000000000000000000000000000000000008004"; +export const DEPLOYER_SYSTEM_CONTRACT_ADDRESS = "0x0000000000000000000000000000000000008006"; +export const FORCE_DEPLOYER_ADDRESS = "0x0000000000000000000000000000000000008007"; +export const L1_MESSENGER_SYSTEM_CONTRACT_ADDRESS = "0x0000000000000000000000000000000000008008"; +export const ETH_TOKEN_SYSTEM_CONTRACT_ADDRESS = "0x000000000000000000000000000000000000800a"; +export const EVENT_WRITER_CONTRACT_ADDRESS = "0x000000000000000000000000000000000000800d"; +export const COMPRESSOR_CONTRACT_ADDRESS = "0x000000000000000000000000000000000000800e"; +export const KECCAK256_CONTRACT_ADDRESS = "0x0000000000000000000000000000000000008010"; +export const EMPTY_STRING_KECCAK = "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"; export const TWO_IN_256 = BigNumber.from(2).pow(256); From 2e036be41b30f11d1d23a89914d63bd41a51d0ff Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Tue, 28 Nov 2023 14:05:16 +0100 Subject: [PATCH 31/36] remove outdated comment --- bootloader/bootloader.yul | 3 --- 1 file changed, 3 deletions(-) diff --git a/bootloader/bootloader.yul b/bootloader/bootloader.yul index f650f771..18c47e14 100644 --- a/bootloader/bootloader.yul +++ b/bootloader/bootloader.yul @@ -994,9 +994,6 @@ object "Bootloader" { let payToOperator := safeMul(gasPrice, safeSub(gasLimit, refundGas, "lpah"), "mnk") - // Note, that for now, the L1->L2 transactions are free, i.e. the gasPrice - // for such transactions is always zero, so the `refundGas` is not used anywhere - // except for notifications for the operator for API purposes. notifyAboutRefund(refundGas) // Paying the fee to the operator From b1728f3122091375b6e88f22a0881333869228f5 Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Tue, 28 Nov 2023 14:05:47 +0100 Subject: [PATCH 32/36] recalculate hashes --- SystemContractsHashes.json | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/SystemContractsHashes.json b/SystemContractsHashes.json index 78e00ae2..e488f212 100644 --- a/SystemContractsHashes.json +++ b/SystemContractsHashes.json @@ -31,8 +31,8 @@ "contractName": "ContractDeployer", "bytecodePath": "artifacts-zk/cache-zk/solpp-generated-contracts/ContractDeployer.sol/ContractDeployer.json", "sourceCodePath": "cache-zk/solpp-generated-contracts/ContractDeployer.sol", - "bytecodeHash": "0x010006091341955c8f76409de00549fb00b275166b5a0d0d7b82cbd629bb4212", - "sourceCodeHash": "0x660e9a188006f9e6086214f8aefa7bc9dc434ce6ff220bfec98327c42953dda4" + "bytecodeHash": "0x0100060979b132ab8099217a507b897f5fb6d553a59c904c8804155c7f5df0d9", + "sourceCodeHash": "0xb014c683908ffe22c519249915622e551229429c441e845ebf3f30ac4dfd69a2" }, { "contractName": "DefaultAccount", @@ -129,8 +129,8 @@ "contractName": "Keccak256", "bytecodePath": "contracts/precompiles/artifacts/Keccak256.yul/Keccak256.yul.zbin", "sourceCodePath": "contracts/precompiles/Keccak256.yul", - "bytecodeHash": "0x0100001fb52ca33668d01c230a1c3b13ede90fe2e37d77222410e9f183cb7a89", - "sourceCodeHash": "0x6415e127a4e07907fb87d0cbdf480fff8c70326c4f2f670af0cf3248862e4df4" + "bytecodeHash": "0x0100000ff07e0447e5fc9b78f38f23439c2c6fcabe123a88d9822b60cb469fb2", + "sourceCodeHash": "0x6984ae0b234765ce272a30beee73323bde70df5f0d0584bb87ef723b2c6bb0cc" }, { "contractName": "SHA256", @@ -143,35 +143,35 @@ "contractName": "bootloader_test", "bytecodePath": "bootloader/build/artifacts/bootloader_test.yul/bootloader_test.yul.zbin", "sourceCodePath": "bootloader/build/bootloader_test.yul", - "bytecodeHash": "0x0100038548508a2a29b0c6e8a86fc0ec5c512baf563155e8171afd1a060c81fa", - "sourceCodeHash": "0x8a2f1171cb02b1500e75e607a7a16ea8782b54800fb3396d0aea241117539265" + "bytecodeHash": "0x01000387a79b481d237d79d556fc67a39ebc3a568519722795bfbd9abb2ffdab", + "sourceCodeHash": "0x54e88df0f5fc2270bddfad0ded0b3d8176330cc9f26611c877fadf923c4a92f2" }, { "contractName": "fee_estimate", "bytecodePath": "bootloader/build/artifacts/fee_estimate.yul/fee_estimate.yul.zbin", "sourceCodePath": "bootloader/build/fee_estimate.yul", - "bytecodeHash": "0x01000989a967ab5b446c084adf05e13399a24e5cf37e9cf7db05a5dd6d7c5e0b", - "sourceCodeHash": "0xf8d6ef018c46d562d4473628c4b13af322320a4c24015c884083bf5654ce7408" + "bytecodeHash": "0x0100098d0cc21dc89ddcdcb4099848bdc5c9a5062211c23c52297a64c6e0a835", + "sourceCodeHash": "0xb788c7d357a1a49d943ae73ec07646d40d0a89794fa455466cbf57c226212fad" }, { "contractName": "gas_test", "bytecodePath": "bootloader/build/artifacts/gas_test.yul/gas_test.yul.zbin", "sourceCodePath": "bootloader/build/gas_test.yul", - "bytecodeHash": "0x0100096912f983649836f812c6db81c814cc0a5ff24b80ecffbf79ca01e7946c", - "sourceCodeHash": "0xc130da5db5af59763a518394dddf96358664eef53b11260d4c4f86ae967c454d" + "bytecodeHash": "0x0100096f71bd6415bba3ff20d90f72ea413146ad15696d9ee4a893eae071fc7f", + "sourceCodeHash": "0x18036e240b01c6323e24d877c5e3881e8aa018a8d6849537a508fab396cf05cd" }, { "contractName": "playground_batch", "bytecodePath": "bootloader/build/artifacts/playground_batch.yul/playground_batch.yul.zbin", "sourceCodePath": "bootloader/build/playground_batch.yul", - "bytecodeHash": "0x0100099308cc5367de190e240aa355df7d0cfacb6a752726bad8f3100044629f", - "sourceCodeHash": "0x7fd1d118ecb97b79a2bb9d66e132638344d6ad333486f8ee520455679ae5aaaa" + "bytecodeHash": "0x01000999b9b17ebf060b49c79a2d54db2806ae66841333b851ac32fcfd34acdc", + "sourceCodeHash": "0xe97177eab3cb932eed5279fa19f1e1d1d9e003208cb21a73b82ccff2fcb40a11" }, { "contractName": "proved_batch", "bytecodePath": "bootloader/build/artifacts/proved_batch.yul/proved_batch.yul.zbin", "sourceCodePath": "bootloader/build/proved_batch.yul", - "bytecodeHash": "0x01000983d4ac4f797cf5c077e022f72284969b13248c2a8e9846f574bdeb5b88", - "sourceCodeHash": "0x444b9dad3a29511c5a80d6f50e1ccf4500031452d2ef3edb4f63593b7070a24d" + "bytecodeHash": "0x01000989939be72b6adff6864ccbc9ad7f8b2738552ab823fc945a5587c04197", + "sourceCodeHash": "0x28826c1f6af835ff6fcd8c1a66757dfd997ab37436c081c470cf5674a7580b58" } ] From fdaf99879bb3826900307bd78daed6b66d1d41ee Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Tue, 28 Nov 2023 14:14:44 +0100 Subject: [PATCH 33/36] fix lint --- test/Keccak256.spec.ts | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/test/Keccak256.spec.ts b/test/Keccak256.spec.ts index ec296bf9..8866957c 100644 --- a/test/Keccak256.spec.ts +++ b/test/Keccak256.spec.ts @@ -1,16 +1,16 @@ import { CONTRACT_DEPLOYER_ADDRESS, hashBytecode } from "zksync-web3/build/src/utils"; -import { KeccakTest, KeccakTest__factory } from "../typechain-types"; +import type { KeccakTest } from "../typechain-types"; +import { KeccakTest__factory } from "../typechain-types"; import { KECCAK256_CONTRACT_ADDRESS } from "./shared/constants"; import { getWallets, loadArtifact, publishBytecode, setCode, getCode } from "./shared/utils"; import { ethers } from "hardhat"; import { readYulBytecode } from "../scripts/utils"; import { Language } from "../scripts/constants"; -import { BytesLike, Wallet, providers } from "ethers"; +import type { BytesLike } from "ethers"; import { expect } from "chai"; import * as hre from "hardhat"; describe("Keccak256 tests", function () { - let testWallet: Wallet; let keccakTest: KeccakTest; let oldKeccakCodeHash: string; @@ -22,8 +22,6 @@ describe("Keccak256 tests", function () { const KECCAK_TEST_ADDRESS = "0x0000000000000000000000000000000000009000"; before(async () => { - testWallet = getWallets()[0]; - await setCode(KECCAK_TEST_ADDRESS, (await loadArtifact("KeccakTest")).bytecode); const keccakCode = await getCode(KECCAK256_CONTRACT_ADDRESS); @@ -117,8 +115,8 @@ describe("Keccak256 tests", function () { await keccakTest.keccakPerformUpgrade(mockKeccakInput); - var keccakCode = await getCode(KECCAK256_CONTRACT_ADDRESS); - var keccakCodeHash = ethers.utils.hexlify(hashBytecode(keccakCode)); + let keccakCode = await getCode(KECCAK256_CONTRACT_ADDRESS); + let keccakCodeHash = ethers.utils.hexlify(hashBytecode(keccakCode)); expect(keccakCodeHash).to.eq(keccakMockCodeHash); @@ -134,15 +132,6 @@ describe("Keccak256 tests", function () { }); }); -async function compareCorrectHash(data: BytesLike, provider: providers.Provider) { - const correctHash = ethers.utils.keccak256(data); - const hashFromPrecompile = await provider.call({ - to: KECCAK256_CONTRACT_ADDRESS, - data, - }); - expect(hashFromPrecompile).to.equal(correctHash, "Hash is incorrect"); -} - function randomHexFromSeed(seed: BytesLike, len: number) { const hexLen = len * 2 + 2; let data = "0x"; From 820cc14620bba30fa694f9701a292ec937a7c60b Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Tue, 28 Nov 2023 14:36:42 +0100 Subject: [PATCH 34/36] upd hashes --- SystemContractsHashes.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/SystemContractsHashes.json b/SystemContractsHashes.json index e488f212..5e4fab5d 100644 --- a/SystemContractsHashes.json +++ b/SystemContractsHashes.json @@ -143,35 +143,35 @@ "contractName": "bootloader_test", "bytecodePath": "bootloader/build/artifacts/bootloader_test.yul/bootloader_test.yul.zbin", "sourceCodePath": "bootloader/build/bootloader_test.yul", - "bytecodeHash": "0x01000387a79b481d237d79d556fc67a39ebc3a568519722795bfbd9abb2ffdab", - "sourceCodeHash": "0x54e88df0f5fc2270bddfad0ded0b3d8176330cc9f26611c877fadf923c4a92f2" + "bytecodeHash": "0x010003871415f5cca4a7de47f2874ea3c466b0a198e2bd7864c9ee0bfe79386a", + "sourceCodeHash": "0x1d10966f6610a0fbf91e02cfb88d4d9fa4f535b330225a9c7bba35031c8c430c" }, { "contractName": "fee_estimate", "bytecodePath": "bootloader/build/artifacts/fee_estimate.yul/fee_estimate.yul.zbin", "sourceCodePath": "bootloader/build/fee_estimate.yul", - "bytecodeHash": "0x0100098d0cc21dc89ddcdcb4099848bdc5c9a5062211c23c52297a64c6e0a835", - "sourceCodeHash": "0xb788c7d357a1a49d943ae73ec07646d40d0a89794fa455466cbf57c226212fad" + "bytecodeHash": "0x0100098dca0bf9bf28480a4c9e89b4e37fd5f2c4e55fa27a3cfba9f591782864", + "sourceCodeHash": "0x4c4712fd3f6c93f2156f7ea926b3a8f5d38c4ea3d083ff11161ec65ce1480b9b" }, { "contractName": "gas_test", "bytecodePath": "bootloader/build/artifacts/gas_test.yul/gas_test.yul.zbin", "sourceCodePath": "bootloader/build/gas_test.yul", - "bytecodeHash": "0x0100096f71bd6415bba3ff20d90f72ea413146ad15696d9ee4a893eae071fc7f", - "sourceCodeHash": "0x18036e240b01c6323e24d877c5e3881e8aa018a8d6849537a508fab396cf05cd" + "bytecodeHash": "0x0100096f5f48ce42cc95f405f7bf478b751b34500b72c4c2b81e6b350a5c4b3e", + "sourceCodeHash": "0x45102bca1f8fae984e610a7ba43dcac55f4e1dd7e9085e70871eb20d08d91d4e" }, { "contractName": "playground_batch", "bytecodePath": "bootloader/build/artifacts/playground_batch.yul/playground_batch.yul.zbin", "sourceCodePath": "bootloader/build/playground_batch.yul", - "bytecodeHash": "0x01000999b9b17ebf060b49c79a2d54db2806ae66841333b851ac32fcfd34acdc", - "sourceCodeHash": "0xe97177eab3cb932eed5279fa19f1e1d1d9e003208cb21a73b82ccff2fcb40a11" + "bytecodeHash": "0x01000999afa4146af1bb8a54114800cd3d2f84e342a1aaddbf4d5cdde1366c75", + "sourceCodeHash": "0xdd1d7d13af59cd281f63f8224d7a8b93bbc31721ce25ee2e364e202d009204f5" }, { "contractName": "proved_batch", "bytecodePath": "bootloader/build/artifacts/proved_batch.yul/proved_batch.yul.zbin", "sourceCodePath": "bootloader/build/proved_batch.yul", - "bytecodeHash": "0x01000989939be72b6adff6864ccbc9ad7f8b2738552ab823fc945a5587c04197", - "sourceCodeHash": "0x28826c1f6af835ff6fcd8c1a66757dfd997ab37436c081c470cf5674a7580b58" + "bytecodeHash": "0x0100098971446445ba27bad8243e336c273fec04d95cbaaecc5350499799d052", + "sourceCodeHash": "0x61440d1300634184f8e1b5619c791c7380d8da7a8b9cfe76f9a51a7aaba125cc" } ] From 8979a4f8f5596ae4fc3741aba1118091f4d35145 Mon Sep 17 00:00:00 2001 From: Stanislav Bezkorovainyi Date: Tue, 9 Jan 2024 09:50:54 +0100 Subject: [PATCH 35/36] Fix typographical errors (#91) fix typos --- bootloader/bootloader.yul | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bootloader/bootloader.yul b/bootloader/bootloader.yul index 18c47e14..358c11fa 100644 --- a/bootloader/bootloader.yul +++ b/bootloader/bootloader.yul @@ -65,7 +65,7 @@ object "Bootloader" { /// @dev The computational overhead for a batch. /// It includes the combined price for 1 instance of all the circuits /// (since they might be partially filled), the price for running - /// the common parts of the bootloader as well as general maintainance of the system. + /// the common parts of the bootloader as well as general maintenance of the system. function BATCH_OVERHEAD_L2_GAS() -> ret { ret := {{BATCH_OVERHEAD_L2_GAS}} } @@ -108,7 +108,7 @@ object "Bootloader" { } /// @dev The slot from which the scratch space starts. - /// Scatch space is used for various temporary values + /// Scratch space is used for various temporary values function SCRATCH_SPACE_BEGIN_SLOT() -> ret { ret := 8 } @@ -343,7 +343,7 @@ object "Bootloader" { /// @dev Slots needed to store L1 Messenger pubdata. /// @dev Note that are many more these than the maximal pubdata in batch, since - /// it needs to also accomodate uncompressed state diffs that are required for the state diff + /// it needs to also accommodate uncompressed state diffs that are required for the state diff /// compression verification. function OPERATOR_PROVIDED_L1_MESSENGER_PUBDATA_SLOTS() -> ret { ret := {{OPERATOR_PROVIDED_L1_MESSENGER_PUBDATA_SLOTS}} @@ -2764,7 +2764,7 @@ object "Bootloader" { ) } default { - // For L2 transactions, we use near call panic, it will triger the validation + // For L2 transactions, we use near call panic, it will trigger the validation // step of the transaction to fail, returning a consistent error message. nearCallPanic() } @@ -3881,7 +3881,7 @@ object "Bootloader" { setTxOrigin(0) setGasPrice(0) - // Transfering all the ETH received in the block to the operator + // Transferring all the ETH received in the block to the operator directETHTransfer( selfbalance(), OPERATOR_ADDRESS @@ -3894,7 +3894,7 @@ object "Bootloader" { // So we need to have this method to reflect it in the system contracts too. // // The reason is that as of now our node requires that each storage write (event, etc) belongs to a particular - // L2 block. In case a batch is sealed by timeout (i.e. the resources of the batch have not been exhaused, but we need + // L2 block. In case a batch is sealed by timeout (i.e. the resources of the batch have not been exhausted, but we need // to seal it to assure timely finality), we need to process sending funds to the operator *after* the last // non-empty L2 block has been already sealed. We can not override old L2 blocks, so we need to create a new empty "fictive" block for it. // From 51f5f0a4901216b5d7080ff4461f626956b9b397 Mon Sep 17 00:00:00 2001 From: Stanislav Bezkorovainyi Date: Tue, 9 Jan 2024 09:53:01 +0100 Subject: [PATCH 36/36] Fix misleading comment (#92) * fix misleading comment * fix lint --- contracts/L1Messenger.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/L1Messenger.sol b/contracts/L1Messenger.sol index 47ee3265..30160164 100644 --- a/contracts/L1Messenger.sol +++ b/contracts/L1Messenger.sol @@ -272,8 +272,8 @@ contract L1Messenger is IL1Messenger, ISystemContract { /// Check State Diffs /// encoding is as follows: - /// header (1 byte version, 3 bytes total len of compressed, 1 byte enumeration index size, 2 bytes number of initial writes) - /// body (N bytes of initial writes [32 byte derived key || compressed value], M bytes repeated writes [enumeration index || compressed value]) + /// header (1 byte version, 3 bytes total len of compressed, 1 byte enumeration index size) + /// body (`compressedStateDiffSize` bytes, 4 bytes number of state diffs, `numberOfStateDiffs` * `STATE_DIFF_ENTRY_SIZE` bytes for the uncompressed state diffs) /// encoded state diffs: [20bytes address][32bytes key][32bytes derived key][8bytes enum index][32bytes initial value][32bytes final value] require( uint256(uint8(bytes1(_totalL2ToL1PubdataAndStateDiffs[calldataPtr]))) ==