From 8a4349e2a1b73937e943f0b925e99dbdaec927d5 Mon Sep 17 00:00:00 2001 From: Stanislav Bezkorovainyi Date: Wed, 1 Nov 2023 12:37:17 +0100 Subject: [PATCH] Set of fixes for boojum integration (#53) * apply max system contracts address * add comment * Allow only deployments for L1->L2 * fail to publish timesstamp * remove trailing comma * correct require for L1Messenger * fix eip1559 * charge correctly for the memory overhead * check that we have enough gas for postop * fix comment in L1Messenger * remove redundant check * safeAdd for refunds * compilation fixes + EOA work correctly on delegatecall * correctly charge for gas overhead * ensure that upgrade tx always succeeds * add force deploy for keccak256 * max precompile address fix * correct refund gas for L1 gas * fix shifting * correct meta calculation * nits * prev hash * fix some nits * remove unneeded casting * fix lint * update hashes * update hashes * Update bootloader/bootloader.yul Co-authored-by: Vlad Bochok <41153528+vladbochok@users.noreply.github.com> * update max precompile address constant * Only the deployer can increment the deployment nonce * fix lint * add some tests --------- Co-authored-by: Vlad Bochok <41153528+vladbochok@users.noreply.github.com> --- SystemConfig.json | 2 +- SystemContractsHashes.json | 64 ++++++++++---------- bootloader/bootloader.yul | 62 ++++++++++++++----- contracts/Constants.sol | 10 ++- contracts/ContractDeployer.sol | 30 +++++++-- contracts/DefaultAccount.sol | 4 +- contracts/L1Messenger.sol | 14 +++-- contracts/NonceHolder.sol | 7 ++- contracts/SystemContext.sol | 5 +- contracts/libraries/SystemContractHelper.sol | 2 + contracts/libraries/Utils.sol | 8 +-- contracts/precompiles/EcAdd.yul | 2 +- contracts/precompiles/EcMul.yul | 2 +- contracts/test-contracts/DelegateCaller.sol | 20 ++++++ test/AccountCodeStorage.spec.ts | 6 ++ test/ContractDeployer.spec.ts | 9 +++ test/DefaultAccount.spec.ts | 33 +++++++++- 17 files changed, 201 insertions(+), 79 deletions(-) create mode 100644 contracts/test-contracts/DelegateCaller.sol diff --git a/SystemConfig.json b/SystemConfig.json index c88a2304..827e11b5 100644 --- a/SystemConfig.json +++ b/SystemConfig.json @@ -9,7 +9,7 @@ "L1_TX_INTRINSIC_L2_GAS": 167157, "L1_TX_INTRINSIC_PUBDATA": 88, "MAX_GAS_PER_TRANSACTION": 80000000, - "BOOTLOADER_MEMORY_FOR_TXS": 273132, + "BOOTLOADER_MEMORY_FOR_TXS": 8740224, "REFUND_GAS": 7343, "KECCAK_ROUND_COST_GAS": 40, "SHA256_ROUND_COST_GAS": 7, diff --git a/SystemContractsHashes.json b/SystemContractsHashes.json index 98db5ac0..30f6fe08 100644 --- a/SystemContractsHashes.json +++ b/SystemContractsHashes.json @@ -3,43 +3,43 @@ "contractName": "AccountCodeStorage", "bytecodePath": "artifacts-zk/cache-zk/solpp-generated-contracts/AccountCodeStorage.sol/AccountCodeStorage.json", "sourceCodePath": "cache-zk/solpp-generated-contracts/AccountCodeStorage.sol", - "bytecodeHash": "0x0100009b9ca53b692a374520c5fa42b54395e71f03b06db62922a61edad50e7d", + "bytecodeHash": "0x0100009bc0511159b5ec703d0c56f87615964017739def4ab1ee606b8ec6458c", "sourceCodeHash": "0xb7a285eceef853b5259266de51584c7120fdc0335657b457c63a331301c96d8f" }, { "contractName": "BootloaderUtilities", "bytecodePath": "artifacts-zk/cache-zk/solpp-generated-contracts/BootloaderUtilities.sol/BootloaderUtilities.json", "sourceCodePath": "cache-zk/solpp-generated-contracts/BootloaderUtilities.sol", - "bytecodeHash": "0x01000975aa1d6323aa715c4ed92458882e8ca4d2b37eab3bf6770b60a6182f6a", + "bytecodeHash": "0x010009759cab4fa9e6ca0784746e1df600ff523f0f90c1e94191755cab4b2ed0", "sourceCodeHash": "0xf40ae3c82f6eb7b88e4d926c706c3edc3c2ce07bb60f60cd21accd228f38c212" }, { "contractName": "ComplexUpgrader", "bytecodePath": "artifacts-zk/cache-zk/solpp-generated-contracts/ComplexUpgrader.sol/ComplexUpgrader.json", "sourceCodePath": "cache-zk/solpp-generated-contracts/ComplexUpgrader.sol", - "bytecodeHash": "0x0100005bad258d9c07ebd112f2951cbb4aa4be367a481d311563c9c9ca80b2d9", + "bytecodeHash": "0x0100005bfc0443349233459892b51e9f67e27ac828d44d9c7cba8c8285fd66bc", "sourceCodeHash": "0xbf583b121fde4d406912afa7af7943adb440e355fcbf476f5b454c58fd07eda0" }, { "contractName": "Compressor", "bytecodePath": "artifacts-zk/cache-zk/solpp-generated-contracts/Compressor.sol/Compressor.json", "sourceCodePath": "cache-zk/solpp-generated-contracts/Compressor.sol", - "bytecodeHash": "0x010001b7a20def59f4f9de9d6b867f8d1b9be7919b556c3b59518c3702aec838", + "bytecodeHash": "0x010001b72874590239af612f65d50a35975299f88de022493fe7f0a190e79496", "sourceCodeHash": "0xba41d1e46cd62c08f61ac78b693e5adbb5428f33640e0e55ff58cbd04093cd07" }, { "contractName": "ContractDeployer", "bytecodePath": "artifacts-zk/cache-zk/solpp-generated-contracts/ContractDeployer.sol/ContractDeployer.json", "sourceCodePath": "cache-zk/solpp-generated-contracts/ContractDeployer.sol", - "bytecodeHash": "0x010005bb18194a3c6d029f5a8787f051595cec6b1a8ad2791e922bf240053dcc", - "sourceCodeHash": "0x99e484499462d7caea209e8386bd09dad1387c60d5034f3acdccc7b271b1c764" + "bytecodeHash": "0x010006091341955c8f76409de00549fb00b275166b5a0d0d7b82cbd629bb4212", + "sourceCodeHash": "0x660e9a188006f9e6086214f8aefa7bc9dc434ce6ff220bfec98327c42953dda4" }, { "contractName": "DefaultAccount", "bytecodePath": "artifacts-zk/cache-zk/solpp-generated-contracts/DefaultAccount.sol/DefaultAccount.json", "sourceCodePath": "cache-zk/solpp-generated-contracts/DefaultAccount.sol", - "bytecodeHash": "0x0100065d36f395889bda1ffc649d545c0ffeecde42c0ad88934dd6618a990038", - "sourceCodeHash": "0xb30019238c2b8574e2a87960f4eed241548c0599c0eb5a6420d1d24d63377210" + "bytecodeHash": "0x01000651c5ae96f2aab07d720439e42491bb44c6384015e3a08e32620a4d582d", + "sourceCodeHash": "0x7356cb68b6326a6ee4871525bfb26aedf9a30c1da18461c68d10d90e1653b05c" }, { "contractName": "EmptyContract", @@ -52,50 +52,50 @@ "contractName": "ImmutableSimulator", "bytecodePath": "artifacts-zk/cache-zk/solpp-generated-contracts/ImmutableSimulator.sol/ImmutableSimulator.json", "sourceCodePath": "cache-zk/solpp-generated-contracts/ImmutableSimulator.sol", - "bytecodeHash": "0x01000047fdc45d38eb26108fafd99a8dda122e6540e4fb566fe7ce2c54090752", + "bytecodeHash": "0x01000047a3c40e3f4eb98f14967f141452ae602d8723a10975dc33960911d8c5", "sourceCodeHash": "0x8d1f252875fe4a8a1cd51bf7bd678b9bff7542bb468f75929cea69df4a16850d" }, { "contractName": "KnownCodesStorage", "bytecodePath": "artifacts-zk/cache-zk/solpp-generated-contracts/KnownCodesStorage.sol/KnownCodesStorage.json", "sourceCodePath": "cache-zk/solpp-generated-contracts/KnownCodesStorage.sol", - "bytecodeHash": "0x0100008b953a05a94540c7ad5082a5a67a023651a1dbe2d0fb832a6d7fbeb893", + "bytecodeHash": "0x0100008b0ca6c6f277035366e99407fbb4b01e743e80b7d24dea5a3d647b423e", "sourceCodeHash": "0x15cb53060dad4c62e72c62777ff6a25029c6ec0ab37adacb684d0e275cec6749" }, { "contractName": "L1Messenger", "bytecodePath": "artifacts-zk/cache-zk/solpp-generated-contracts/L1Messenger.sol/L1Messenger.json", "sourceCodePath": "cache-zk/solpp-generated-contracts/L1Messenger.sol", - "bytecodeHash": "0x010002fbdc855bdd1ac421a66db258ab77b450b2a16e295e5dd56cd6aaecc69a", - "sourceCodeHash": "0x3dce2fc308f7d911a2d80460b895322f954f43ed6bca1893f34ae3469c05b222" + "bytecodeHash": "0x01000301c943edb65f5a0b8cdd806218b8ecf25c022720fe3afe6951f202f3fa", + "sourceCodeHash": "0x11a4280dcacc9de950ee8724bc6e4f99a4268c38a0cb26ebd5f28e6ea1094463" }, { "contractName": "L2EthToken", "bytecodePath": "artifacts-zk/cache-zk/solpp-generated-contracts/L2EthToken.sol/L2EthToken.json", "sourceCodePath": "cache-zk/solpp-generated-contracts/L2EthToken.sol", - "bytecodeHash": "0x0100013900f1639f08f90edbe93e8e00166a8dc2443a7a7f77e43b282c5529c1", + "bytecodeHash": "0x01000139b506af2b02225838c5a33e30ace701b44b210a422eedab7dd31c28a3", "sourceCodeHash": "0xadc69be5b5799d0f1a6fa71d56a6706b146447c8e3c6516a5191a0b23bd134e8" }, { "contractName": "MsgValueSimulator", "bytecodePath": "artifacts-zk/cache-zk/solpp-generated-contracts/MsgValueSimulator.sol/MsgValueSimulator.json", "sourceCodePath": "cache-zk/solpp-generated-contracts/MsgValueSimulator.sol", - "bytecodeHash": "0x0100006fd1a3e535db02c2e8ff8e9cadd52aab1bde05980ab828b568e9efd8e1", + "bytecodeHash": "0x0100006fa1591d93fcc4a25e9340ad11d0e825904cd1842b8f7255701e1aacbb", "sourceCodeHash": "0xe7a85dc51512cab431d12bf062847c4dcf2f1c867e7d547ff95638f6a4e8fd4e" }, { "contractName": "NonceHolder", "bytecodePath": "artifacts-zk/cache-zk/solpp-generated-contracts/NonceHolder.sol/NonceHolder.json", "sourceCodePath": "cache-zk/solpp-generated-contracts/NonceHolder.sol", - "bytecodeHash": "0x0100012fba56e2fa8880eb52fd8db2b49ee7ee85bbfad241606097508f308f4d", - "sourceCodeHash": "0x04da0e5560c6cca2d0d5c965ee67d4cae9273367b77afef106108d4e8a2624b5" + "bytecodeHash": "0x0100012fa73fa922dd9fabb40d3275ce80396eff6ccf1b452c928c17d98bd470", + "sourceCodeHash": "0x1680f801086c654032f2331a574752e9c3b21df8a60110f4ea5fe26bb51e8095" }, { "contractName": "SystemContext", "bytecodePath": "artifacts-zk/cache-zk/solpp-generated-contracts/SystemContext.sol/SystemContext.json", "sourceCodePath": "cache-zk/solpp-generated-contracts/SystemContext.sol", - "bytecodeHash": "0x0100023fd8d36304cec41afa8b726686170552b660d0202970b0ecc4ceab8c3a", - "sourceCodeHash": "0x422768c771c4e4c077b66b9c4dd36e6dbeda4da058698ece239a0ad95b316646" + "bytecodeHash": "0x0100023ba65021e4689dd1755f82108214a1f25150d439fe58c55cdb1f376436", + "sourceCodeHash": "0x43d1d893695361edf014acd62f66dfe030868f342fe5d0aa1b6ddb520f3a5ad4" }, { "contractName": "EventWriter", @@ -108,15 +108,15 @@ "contractName": "EcAdd", "bytecodePath": "contracts/precompiles/artifacts/EcAdd.yul/EcAdd.yul.zbin", "sourceCodePath": "contracts/precompiles/EcAdd.yul", - "bytecodeHash": "0x010000c56c054a0de4a36b133d3c114ec514c3ce0334ad7759c202392386a913", - "sourceCodeHash": "0xe73c8960a8b4060113adca9f03207d379580d172df9f0b499dd5353934a557a6" + "bytecodeHash": "0x010000c5a85a372f441ac693210a18e683b530bed875fdcab2f7e101b057d433", + "sourceCodeHash": "0x32645126b8765e4f7ced63c9508c70edc4ab734843d5f0f0f01d153c27206cee" }, { "contractName": "EcMul", "bytecodePath": "contracts/precompiles/artifacts/EcMul.yul/EcMul.yul.zbin", "sourceCodePath": "contracts/precompiles/EcMul.yul", - "bytecodeHash": "0x010001378d31273c8e58caa12bcf1a5694e66a0aefdba2504adb8e3eb02b21c7", - "sourceCodeHash": "0x6c4b11542bcf85e6e02ca193fc0548353b1f21c27e972b9e73781e8f7eaf50b0" + "bytecodeHash": "0x0100013759b40792c2c3d033990e992e5508263c15252eb2d9bfbba571350675", + "sourceCodeHash": "0xdad8be6e926155a362ea05b132ba8b6c634e978a41f79bb6390b870e18049e45" }, { "contractName": "Ecrecover", @@ -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": "0x0100037b0462ed355364eaabccbea2a018afad4c8841b9856514c027400f1b10", - "sourceCodeHash": "0x467a36057882d6740a016cda812798d1be9a0ea60cb7ef90996e2c5be55e75a4" + "bytecodeHash": "0x01000385d1fa80331b4d637f064edc462feee06e1712651deee2fcef53ab2cf5", + "sourceCodeHash": "0xa265f36ee268c00e9786eec87a7383665339913c85ed645a549c51ee59bce8f4" }, { "contractName": "fee_estimate", "bytecodePath": "bootloader/build/artifacts/fee_estimate.yul/fee_estimate.yul.zbin", "sourceCodePath": "bootloader/build/fee_estimate.yul", - "bytecodeHash": "0x010009434283c0bc9f32e51a9aa84523ee7a381e3e0c5ae63f639998d915f54b", - "sourceCodeHash": "0x3fb415ac6f59c35ea17b85aabb551df1b44a6fc7e051c2e33f5fc76c17432167" + "bytecodeHash": "0x0100096b2cc4a11258bcf6566ecdc3af49e600b607750c4d792d49fe56597d56", + "sourceCodeHash": "0xe2f8836de8c5d0110081393b373ff23ddcbd014b39e4c865092236d752e43cbb" }, { "contractName": "gas_test", "bytecodePath": "bootloader/build/artifacts/gas_test.yul/gas_test.yul.zbin", "sourceCodePath": "bootloader/build/gas_test.yul", - "bytecodeHash": "0x01000927ea81a1afe5a586853a9c43fb928bcf1f1fba51a19c48ce1b940867c7", - "sourceCodeHash": "0x84648c958714d952248b8553456b5a5e3860e00871f01644297531e991a67d64" + "bytecodeHash": "0x0100094b584d299e041d0ebfed17d2bd9361aa87bcb2b3456c8849159e478d99", + "sourceCodeHash": "0xe7ecd7132cf527552113e3bdb30f8d61dcec39a4fe27ef31926a0b4c09b33ca1" }, { "contractName": "playground_batch", "bytecodePath": "bootloader/build/artifacts/playground_batch.yul/playground_batch.yul.zbin", "sourceCodePath": "bootloader/build/playground_batch.yul", - "bytecodeHash": "0x0100094d801bf4180d020692a95cf26a3c9adcaedfd5be47ec08b1637b0282da", - "sourceCodeHash": "0xe02bed16015da2f03dcf5a7ed1bf2132009e69f4bfb5335e13cc406327e84d5e" + "bytecodeHash": "0x01000975ebcb5e5fb67155058890a8286540a76ec01a57a582342832a8e56e79", + "sourceCodeHash": "0x6f154f3e3b6a15a8188d850d2d6e6e6fed140926799540c4b3352d7c242ed175" }, { "contractName": "proved_batch", "bytecodePath": "bootloader/build/artifacts/proved_batch.yul/proved_batch.yul.zbin", "sourceCodePath": "bootloader/build/proved_batch.yul", - "bytecodeHash": "0x010009411d9c2342671c57d5ce038ce3e142c750df85ac5d23f67b4e4215fede", - "sourceCodeHash": "0xd48e5abbfbb493eacfcbe6dc788eada867d58ab8596d55736b496b1c2e22c636" + "bytecodeHash": "0x01000965d96c3603e367690834b099353216bc57910f65d230036ea3d6f21942", + "sourceCodeHash": "0xee74d5fe188640d88ff798813742834bc4d2a762f6ebe88c7f3f5871d281ffd0" } ] diff --git a/bootloader/bootloader.yul b/bootloader/bootloader.yul index 98efc7a6..5f25cbfb 100644 --- a/bootloader/bootloader.yul +++ b/bootloader/bootloader.yul @@ -917,11 +917,9 @@ object "Bootloader" { // In case the operator provided smaller refund than the one calculated // by the bootloader, we return the refund calculated by the bootloader. - refundGas := max(getOperatorRefundForTx(transactionIndex), potentialRefund) + refundGas := max(getOperatorRefundForTx(transactionIndex), safeAdd(potentialRefund, reservedGas, "iop")) } - refundGas := add(refundGas, reservedGas) - if gt(refundGas, gasLimit) { assertionError("L1: refundGas > gasLimit") } @@ -939,10 +937,14 @@ object "Bootloader" { let toRefundRecipient switch success case 0 { + if iszero(isPriorityOp) { + // Upgrade transactions must always succeed + assertionError("Upgrade tx failed") + } + // If the transaction reverts, then minting the msg.value to the user has been reverted // as well, so we can simply mint everything that the user has deposited to // the refund recipient - toRefundRecipient := safeSub(getReserved0(innerTxDataOffset), payToOperator, "vji") } default { @@ -1178,7 +1180,7 @@ object "Bootloader" { /// @param txDataOffset The offset to the ABI-encoded Transaction struct. /// @param gasLimitForTx The L2 gas limit for the transaction validation & execution. /// @param gasPrice The L2 gas price that should be used by the transaction. - /// @return ergsLeft The ergs left after the validation step. + /// @return gasLeft The gas left after the validation step. function l2TxValidation( txDataOffset, gasLimitForTx, @@ -1230,9 +1232,9 @@ object "Bootloader" { /// @dev The function responsible for the execution step of the L2 transaction. /// @param txDataOffset The offset to the ABI-encoded Transaction struct. - /// @param ergsLeft The ergs left after the validation step. + /// @param gasLeft The gas left after the validation step. /// @return success Whether or not the execution step was successful. - /// @return ergsSpentOnExecute The ergs spent on the transaction execution. + /// @return gasSpentOnExecute The gas spent on the transaction execution. function l2TxExecution( txDataOffset, gasLeft, @@ -1262,7 +1264,7 @@ object "Bootloader" { default { // Note, that since gt(gasLeft, gasSpentOnFactoryDeps) = true // sub(gasLeft, gasSpentOnFactoryDeps) > 0, which is important - // because a nearCall with 0 ergs passes on all the ergs of the parent frame. + // because a nearCall with 0 gas passes on all the gas of the parent frame. gasLeft := sub(gasLeft, gasSpentOnFactoryDeps) let executeABI := getNearCallABI(gasLeft) @@ -1425,6 +1427,7 @@ object "Bootloader" { refundRecipient := paymaster if gt(gasLeft, 0) { + checkEnoughGas(gasLeft) let nearCallAbi := getNearCallABI(gasLeft) let gasBeforePostOp := gas() pop(ZKSYNC_NEAR_CALL_callPostOp( @@ -1435,7 +1438,7 @@ object "Bootloader" { success, // Since the paymaster will be refunded with reservedGas, // it should know about it - safeAdd(gasLeft, reservedGas, "jkl"), + safeAdd(gasLeft, reservedGas, "jkl") )) let gasSpentByPostOp := sub(gasBeforePostOp, gas()) @@ -1595,7 +1598,7 @@ object "Bootloader" { /// @dev Get checked for overcharged operator's overhead for the transaction. /// @param transactionIndex The index of the transaction in the batch /// @param txTotalGasLimit The total gass limit of the transaction (including the overhead). - /// @param gasPerPubdataByte The price for pubdata byte in ergs. + /// @param gasPerPubdataByte The price for pubdata byte in gas. /// @param txEncodeLen The length of the ABI-encoding of the transaction function getVerifiedOperatorOverheadForTx( transactionIndex, @@ -1755,6 +1758,37 @@ object "Bootloader" { } + /// @dev Given the callee and the data to be called with, + /// this function returns whether the mimicCall should use the `isSystem` flag. + /// This flag should only be used for contract deployments and nothing else. + /// @param to The callee of the call. + /// @param dataPtr The pointer to the calldata of the transaction. + function shouldMsgValueMimicCallBeSystem(to, dataPtr) -> ret { + let dataLen := mload(dataPtr) + // Note, that this point it is not fully known whether it is indeed the selector + // of the calldata (it might not be the case if the `dataLen` < 4), but it will be checked later on + let selector := shr(224, mload(add(dataPtr, 32))) + + let isSelectorCreate := or( + eq(selector, {{CREATE_SELECTOR}}), + eq(selector, {{CREATE_ACCOUNT_SELECTOR}}) + ) + let isSelectorCreate2 := or( + eq(selector, {{CREATE2_SELECTOR}}), + eq(selector, {{CREATE2_ACCOUNT_SELECTOR}}) + ) + + // Firstly, ensure that the selector is a valid deployment function + ret := or( + isSelectorCreate, + isSelectorCreate2 + ) + // Secondly, ensure that the callee is ContractDeployer + ret := and(ret, eq(to, CONTRACT_DEPLOYER_ADDR())) + // Thirdly, ensure that the calldata is long enough to contain the selector + ret := and(ret, gt(dataLen, 3)) + } + /// @dev Given the pointer to the calldata, the value and to /// performs the call through the msg.value simulator. /// @param to Which contract to call @@ -1764,7 +1798,7 @@ object "Bootloader" { /// the length of the calldata and the calldata itself right afterwards. function msgValueSimulatorMimicCall(to, from, value, dataPtr) -> success { // Only calls to the deployer system contract are allowed to be system - let isSystem := eq(to, CONTRACT_DEPLOYER_ADDR()) + let isSystem := shouldMsgValueMimicCallBeSystem(to, dataPtr) success := mimicCallOnlyResult( MSG_VALUE_SIMULATOR_ADDR(), @@ -2515,7 +2549,7 @@ object "Bootloader" { ) if iszero(success) { - debugLog("Failed publish timestamp data to L1", 0) + debugLog("Failed publish timestamp to L1", 0) revertWithReason(FAILED_TO_PUBLISH_TIMESTAMP_DATA_TO_L1(), 1) } } @@ -2902,7 +2936,7 @@ object "Bootloader" { - + assertEq(gt(getFrom(innerTxDataOffset), MAX_SYSTEM_CONTRACT_ADDR()), 1, "from in kernel space") @@ -3255,7 +3289,7 @@ object "Bootloader" { } } - /// @dev Returns the addition of two unsigned integers, reverting on overflow. + /// @dev Returns the subtraction of two unsigned integers, reverting on underflow. function safeSub(x, y, errMsg) -> ret { if gt(y, x) { assertionError(errMsg) diff --git a/contracts/Constants.sol b/contracts/Constants.sol index f6df0952..f0896710 100644 --- a/contracts/Constants.sol +++ b/contracts/Constants.sol @@ -27,12 +27,10 @@ address constant SHA256_SYSTEM_CONTRACT = address(0x02); address constant ECADD_SYSTEM_CONTRACT = address(0x06); address constant ECMUL_SYSTEM_CONTRACT = address(0x07); -/// @dev The current maximum deployed precompile address. -/// Note: currently only two precompiles are deployed: -/// 0x01 - ecrecover -/// 0x02 - sha256 -/// Important! So the constant should be updated if more precompiles are deployed. -uint256 constant CURRENT_MAX_PRECOMPILE_ADDRESS = uint256(uint160(SHA256_SYSTEM_CONTRACT)); +/// @dev The maximal possible address of an L1-like precompie. These precompiles maintain the following properties: +/// - Their extcodehash is EMPTY_STRING_KECCAK +/// - Their extcodesize is 0 despite having a bytecode formally deployed there. +uint256 constant CURRENT_MAX_PRECOMPILE_ADDRESS = 0xff; address payable constant BOOTLOADER_FORMAL_ADDRESS = payable(address(SYSTEM_CONTRACTS_OFFSET + 0x01)); IAccountCodeStorage constant ACCOUNT_CODE_STORAGE_SYSTEM_CONTRACT = IAccountCodeStorage( diff --git a/contracts/ContractDeployer.sol b/contracts/ContractDeployer.sol index 564ceb87..50af9742 100644 --- a/contracts/ContractDeployer.sol +++ b/contracts/ContractDeployer.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.20; 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"; @@ -44,7 +44,10 @@ contract ContractDeployer is IContractDeployer, ISystemContract { } // It is an EOA, it is still an account. - if (ACCOUNT_CODE_STORAGE_SYSTEM_CONTRACT.getRawCodeHash(_address) == 0) { + if ( + _address > address(MAX_SYSTEM_CONTRACT_ADDRESS) && + ACCOUNT_CODE_STORAGE_SYSTEM_CONTRACT.getRawCodeHash(_address) == 0 + ) { return AccountAbstractionVersion.Version1; } @@ -214,6 +217,9 @@ contract ContractDeployer is IContractDeployer, ISystemContract { function forceDeployOnAddress(ForceDeployment calldata _deployment, address _sender) external payable onlySelf { _ensureBytecodeIsKnown(_deployment.bytecodeHash); + // Since the `forceDeployOnAddress` function is called only during upgrades, the Governance is trusted to correctly select + // the addresses to deploy the new bytecodes to and to assess whether overriding the AccountInfo for the "force-deployed" + // contract is acceptable. AccountInfo memory newAccountInfo; newAccountInfo.supportedAAVersion = AccountAbstractionVersion.None; // Accounts have sequential nonces by default. @@ -228,8 +234,23 @@ contract ContractDeployer is IContractDeployer, ISystemContract { false, _deployment.callConstructor ); + } - emit ContractDeployed(_sender, _deployment.bytecodeHash, _deployment.newAddress); + /// @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. + function forceDeployKeccak256(bytes32 _keccak256BytecodeHash) external payable onlyCallFrom(FORCE_DEPLOYER) { + _ensureBytecodeIsKnown(_keccak256BytecodeHash); + _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 +316,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 +372,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/contracts/DefaultAccount.sol b/contracts/DefaultAccount.sol index f2c9e810..2257269d 100644 --- a/contracts/DefaultAccount.sol +++ b/contracts/DefaultAccount.sol @@ -101,8 +101,6 @@ contract DefaultAccount is IAccount { if (_isValidSignature(txHash, _transaction.signature)) { magic = ACCOUNT_VALIDATION_SUCCESS_MAGIC; - } else { - magic = bytes4(0); } } @@ -219,7 +217,7 @@ contract DefaultAccount is IAccount { _transaction.processPaymasterInput(); } - fallback() external payable { + fallback() external payable ignoreInDelegateCall { // fallback of default account shouldn't be called by bootloader under no circumstances assert(msg.sender != BOOTLOADER_FORMAL_ADDRESS); diff --git a/contracts/L1Messenger.sol b/contracts/L1Messenger.sol index a71240ae..47ee3265 100644 --- a/contracts/L1Messenger.sol +++ b/contracts/L1Messenger.sol @@ -81,8 +81,9 @@ contract L1Messenger is IL1Messenger, ISystemContract { // We need to charge cost of hashing, as it will be used in `publishPubdataAndClearState`: // - keccakGasCost(L2_TO_L1_LOG_SERIALIZE_SIZE) and keccakGasCost(64) when reconstructing L2ToL1Log - // - at most 2 times keccakGasCost(64) (as merkle tree can contain ~2*N leaves) - uint256 gasToPay = keccakGasCost(L2_TO_L1_LOG_SERIALIZE_SIZE) + 3 * keccakGasCost(64); + // - at most 1 time keccakGasCost(64) when building the Merkle tree (as merkle tree can contain + // ~2*N nodes, where the first N nodes are leaves the hash of which is calculated on the previous step). + uint256 gasToPay = keccakGasCost(L2_TO_L1_LOG_SERIALIZE_SIZE) + 2 * keccakGasCost(64); SystemContractHelper.burnGas(Utils.safeCastToU32(gasToPay)); } @@ -141,11 +142,12 @@ contract L1Messenger is IL1Messenger, ISystemContract { // We need to charge cost of hashing, as it will be used in `publishPubdataAndClearState`: // - keccakGasCost(L2_TO_L1_LOG_SERIALIZE_SIZE) and keccakGasCost(64) when reconstructing L2ToL1Log // - keccakGasCost(64) and gasSpentOnMessageHashing when reconstructing Messages - // - at most 2 times keccakGasCost(64) (as merkle tree can contain ~2*N leaves) + // - at most 1 time keccakGasCost(64) when building the Merkle tree (as merkle tree can contain + // ~2*N nodes, where the first N nodes are leaves the hash of which is calculated on the previous step). uint256 gasToPay = pubdataLen * gasPerPubdataBytes + keccakGasCost(L2_TO_L1_LOG_SERIALIZE_SIZE) + - 4 * + 3 * keccakGasCost(64) + gasSpentOnMessageHashing; SystemContractHelper.burnGas(Utils.safeCastToU32(gasToPay)); @@ -195,7 +197,7 @@ contract L1Messenger is IL1Messenger, ISystemContract { /// Check logs uint32 numberOfL2ToL1Logs = uint32(bytes4(_totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + 4])); - require(numberOfL2ToL1Logs <= numberOfL2ToL1Logs, "Too many L2->L1 logs"); + require(numberOfL2ToL1Logs <= L2_TO_L1_LOGS_MERKLE_TREE_LEAVES, "Too many L2->L1 logs"); calldataPtr += 4; bytes32[] memory l2ToL1LogsTreeArray = new bytes32[](L2_TO_L1_LOGS_MERKLE_TREE_LEAVES); @@ -270,7 +272,7 @@ contract L1Messenger is IL1Messenger, ISystemContract { /// Check State Diffs /// encoding is as follows: - /// header (1 byte version, 2 bytes total len of compressed, 1 byte enumeration index size, 2 bytes number of initial writes) + /// 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]) /// encoded state diffs: [20bytes address][32bytes key][32bytes derived key][8bytes enum index][32bytes initial value][32bytes final value] require( diff --git a/contracts/NonceHolder.sol b/contracts/NonceHolder.sol index 6400b49c..b2775f1c 100644 --- a/contracts/NonceHolder.sol +++ b/contracts/NonceHolder.sol @@ -132,8 +132,11 @@ contract NonceHolder is INonceHolder, ISystemContract { /// @notice Increments the deployment nonce for the account and returns the previous one. /// @param _address The address of the account which to return the deploy nonce for. /// @return prevDeploymentNonce The deployment nonce at the time this function is called. - function incrementDeploymentNonce(address _address) external onlySystemCall returns (uint256 prevDeploymentNonce) { - require(msg.sender == address(DEPLOYER_SYSTEM_CONTRACT), ""); + function incrementDeploymentNonce(address _address) external returns (uint256 prevDeploymentNonce) { + require( + msg.sender == address(DEPLOYER_SYSTEM_CONTRACT), + "Only the contract deployer can increment the deployment nonce" + ); uint256 addressAsKey = uint256(uint160(_address)); uint256 oldRawNonce = rawNonces[addressAsKey]; diff --git a/contracts/SystemContext.sol b/contracts/SystemContext.sol index 5701e94c..67f9248e 100644 --- a/contracts/SystemContext.sol +++ b/contracts/SystemContext.sol @@ -216,14 +216,14 @@ contract SystemContext is ISystemContext, ISystemContextDeprecated, ISystemContr require(_l2BlockNumber > 0, "L2 block number is never expected to be zero"); unchecked { - bytes32 correctPrevBlockHash = _calculateLegacyL2BlockHash(uint128(_l2BlockNumber - 1)); + bytes32 correctPrevBlockHash = _calculateLegacyL2BlockHash(_l2BlockNumber - 1); require(correctPrevBlockHash == _expectedPrevL2BlockHash, "The previous L2 block hash is incorrect"); // Whenever we'll be queried about the hashes of the blocks before the upgrade, // we'll use batches' hashes, so we don't need to store 256 previous hashes. // However, we do need to store the last previous hash in order to be able to correctly calculate the // hash of the new L2 block. - _setL2BlockHash(uint128(_l2BlockNumber - 1), correctPrevBlockHash); + _setL2BlockHash(_l2BlockNumber - 1, correctPrevBlockHash); } } @@ -382,7 +382,6 @@ contract SystemContext is ISystemContext, ISystemContextDeprecated, ISystemContr // The structure of the "setNewBatch" implies that currentBatchNumber > 0, but we still double check it require(currentBatchNumber > 0, "The current batch number must be greater than 0"); - bytes32 prevBatchHash = batchHash[currentBatchNumber - 1]; // In order to spend less pubdata, the packed version is published uint256 packedTimestamps = (uint256(currentBatchTimestamp) << 128) | currentL2BlockTimestamp; diff --git a/contracts/libraries/SystemContractHelper.sol b/contracts/libraries/SystemContractHelper.sol index 3912034c..a66b9670 100644 --- a/contracts/libraries/SystemContractHelper.sol +++ b/contracts/libraries/SystemContractHelper.sol @@ -270,6 +270,8 @@ library SystemContractHelper { function getZkSyncMeta() internal view returns (ZkSyncMeta memory meta) { uint256 metaPacked = getZkSyncMetaBytes(); meta.gasPerPubdataByte = getGasPerPubdataByteFromMeta(metaPacked); + meta.heapSize = getHeapSizeFromMeta(metaPacked); + meta.auxHeapSize = getAuxHeapSizeFromMeta(metaPacked); meta.shardId = getShardIdFromMeta(metaPacked); meta.callerShardId = getCallerShardIdFromMeta(metaPacked); meta.codeShardId = getCodeShardIdFromMeta(metaPacked); diff --git a/contracts/libraries/Utils.sol b/contracts/libraries/Utils.sol index 0e87161e..a2791520 100644 --- a/contracts/libraries/Utils.sol +++ b/contracts/libraries/Utils.sol @@ -83,15 +83,15 @@ library Utils { // Note that the length of the bytecode must be provided in 32-byte words. require(_bytecode.length % 32 == 0, "po"); - uint256 bytecodeLenInWords = _bytecode.length / 32; - require(bytecodeLenInWords < 2 ** 16, "pp"); // bytecode length must be less than 2^16 words - require(bytecodeLenInWords % 2 == 1, "pr"); // bytecode length in words must be odd + uint256 lengthInWords = _bytecode.length / 32; + require(lengthInWords < 2 ** 16, "pp"); // bytecode length must be less than 2^16 words + require(lengthInWords % 2 == 1, "pr"); // bytecode length in words must be odd hashedBytecode = EfficientCall.sha(_bytecode) & 0x00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; // Setting the version of the hash hashedBytecode = (hashedBytecode | bytes32(uint256(1 << 248))); // Setting the length - hashedBytecode = hashedBytecode | bytes32(bytecodeLenInWords << 224); + hashedBytecode = hashedBytecode | bytes32(lengthInWords << 224); } } diff --git a/contracts/precompiles/EcAdd.yul b/contracts/precompiles/EcAdd.yul index c5581457..bfbac645 100644 --- a/contracts/precompiles/EcAdd.yul +++ b/contracts/precompiles/EcAdd.yul @@ -247,7 +247,7 @@ object "EcAdd" { /// @dev See https://en.wikipedia.org/wiki/Montgomery_modular_multiplication#The_REDC_algorithm for further details on the Montgomery multiplication. /// @param minuend The minuend in Montgomery form. /// @param subtrahend The subtrahend in Montgomery form. - /// @return ret The result of the Montgomery addition. + /// @return ret The result of the Montgomery subtraction. function montgomerySub(minuend, subtrahend) -> ret { ret := montgomeryAdd(minuend, sub(P(), subtrahend)) } diff --git a/contracts/precompiles/EcMul.yul b/contracts/precompiles/EcMul.yul index 5de5dee0..83c45ff0 100644 --- a/contracts/precompiles/EcMul.yul +++ b/contracts/precompiles/EcMul.yul @@ -225,7 +225,7 @@ object "EcMul" { /// @dev See https://en.wikipedia.org/wiki/Montgomery_modular_multiplication#The_The_REDC_algorithm for further details on the Montgomery multiplication. /// @param minuend The minuend in Montgomery form. /// @param subtrahend The subtrahend in Montgomery form. - /// @return ret The result of the Montgomery addition. + /// @return ret The result of the Montgomery subtraction. function montgomerySub(minuend, subtrahend) -> ret { ret := montgomeryAdd(minuend, sub(P(), subtrahend)) } diff --git a/contracts/test-contracts/DelegateCaller.sol b/contracts/test-contracts/DelegateCaller.sol new file mode 100644 index 00000000..caa5aae6 --- /dev/null +++ b/contracts/test-contracts/DelegateCaller.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +contract DelegateCaller { + function delegateCall(address _to) external payable { + assembly { + calldatacopy(0, 0, calldatasize()) + let result := delegatecall(gas(), _to, 0, calldatasize(), 0, 0) + returndatacopy(0, 0, returndatasize()) + switch result + case 0 { + revert(0, returndatasize()) + } + default { + return(0, returndatasize()) + } + } + } +} diff --git a/test/AccountCodeStorage.spec.ts b/test/AccountCodeStorage.spec.ts index 9bed917b..658f2bff 100644 --- a/test/AccountCodeStorage.spec.ts +++ b/test/AccountCodeStorage.spec.ts @@ -142,9 +142,15 @@ describe('AccountCodeStorage tests', function () { describe('getCodeHash', function () { it('precompile', async () => { + // Check that the smallest precompile has EMPTY_STRING_KECCAK hash expect(await accountCodeStorage.getCodeHash('0x0000000000000000000000000000000000000001')).to.be.eq( EMPTY_STRING_KECCAK ); + + // Check that the upper end of the precompile range has EMPTY_STRING_KECCAK hash + expect(await accountCodeStorage.getCodeHash('0x00000000000000000000000000000000000000ff')).to.be.eq( + EMPTY_STRING_KECCAK + ); }); it('EOA with non-zero nonce', async () => { diff --git a/test/ContractDeployer.spec.ts b/test/ContractDeployer.spec.ts index d7ec83b5..18663a48 100644 --- a/test/ContractDeployer.spec.ts +++ b/test/ContractDeployer.spec.ts @@ -30,6 +30,7 @@ describe('ContractDeployer tests', function () { const RANDOM_ADDRESS = ethers.utils.getAddress('0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbee1'); const RANDOM_ADDRESS_2 = ethers.utils.getAddress('0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbee2'); const RANDOM_ADDRESS_3 = ethers.utils.getAddress('0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbee3'); + const EMPTY_KERNEL_ADDRESS = ethers.utils.getAddress('0x0000000000000000000000000000000000000101'); const AA_VERSION_NONE = 0; const AA_VERSION_1 = 1; const NONCE_ORDERING_SEQUENTIAL = 0; @@ -166,6 +167,14 @@ describe('ContractDeployer tests', function () { expect(await contractDeployer.extendedAccountVersion(EOA)).to.be.eq(AA_VERSION_1); }); + it('Empty address', async () => { + // Double checking that the address is indeed empty + expect(await wallet.provider.getCode(EMPTY_KERNEL_ADDRESS)).to.be.eq('0x'); + + // Now testing that the system contracts with empty bytecode are still treated as AA_VERSION_NONE + expect(await contractDeployer.extendedAccountVersion(EMPTY_KERNEL_ADDRESS)).to.be.eq(AA_VERSION_NONE); + }); + it('not AA', async () => { expect(await contractDeployer.extendedAccountVersion(contractDeployerSystemCall.address)).to.be.eq( AA_VERSION_NONE diff --git a/test/DefaultAccount.spec.ts b/test/DefaultAccount.spec.ts index a2460581..08dcd517 100644 --- a/test/DefaultAccount.spec.ts +++ b/test/DefaultAccount.spec.ts @@ -7,6 +7,7 @@ import { Callable, DefaultAccount, DefaultAccount__factory, + DelegateCaller, L2EthToken, L2EthToken__factory, MockERC20Approve, @@ -31,6 +32,7 @@ describe('DefaultAccount tests', function () { let callable: Callable; let mockERC20Approve: MockERC20Approve; let paymasterFlowInterface: ethers.utils.Interface; + let delegateCaller: DelegateCaller; const RANDOM_ADDRESS = ethers.utils.getAddress('0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef'); @@ -43,6 +45,7 @@ describe('DefaultAccount tests', function () { nonceHolder = NonceHolder__factory.connect(NONCE_HOLDER_SYSTEM_CONTRACT_ADDRESS, wallet); l2EthToken = L2EthToken__factory.connect(ETH_TOKEN_SYSTEM_CONTRACT_ADDRESS, wallet); callable = (await deployContract('Callable')) as Callable; + delegateCaller = (await deployContract('DelegateCaller')) as DelegateCaller; mockERC20Approve = (await deployContract('MockERC20Approve')) as MockERC20Approve; const paymasterFlowInterfaceArtifact = await loadArtifact('IPaymasterFlow'); @@ -352,7 +355,7 @@ describe('DefaultAccount tests', function () { }); describe('fallback/receive', function () { - it('zero value', async () => { + it('zero value by EOA wallet', async () => { const call = { from: wallet.address, to: defaultAccount.address, @@ -362,7 +365,7 @@ describe('DefaultAccount tests', function () { expect(await wallet.provider.call(call)).to.be.eq('0x'); }); - it('non-zero value', async () => { + it('non-zero value by EOA wallet', async () => { const call = { from: wallet.address, to: defaultAccount.address, @@ -371,5 +374,31 @@ describe('DefaultAccount tests', function () { }; expect(await wallet.provider.call(call)).to.be.eq('0x'); }); + + it('zero value by bootloader', async () => { + // Here we need to ensure that during delegatecalls even if `msg.sender` is the bootloader, + // the fallback is behaving correctly + const calldata = delegateCaller.interface.encodeFunctionData('delegateCall', [defaultAccount.address]); + const call = { + from: BOOTLOADER_FORMAL_ADDRESS, + to: delegateCaller.address, + value: 0, + data: calldata + }; + expect(await bootloader.call(call)).to.be.eq('0x'); + }); + + it('non-zero value by bootloader', async () => { + // Here we need to ensure that during delegatecalls even if `msg.sender` is the bootloader, + // the fallback is behaving correctly + const calldata = delegateCaller.interface.encodeFunctionData('delegateCall', [defaultAccount.address]); + const call = { + from: BOOTLOADER_FORMAL_ADDRESS, + to: delegateCaller.address, + value: 3223, + data: calldata + }; + expect(await bootloader.call(call)).to.be.eq('0x'); + }); }); });