From 15fe5a62f03cd103afd7fa5eb03e27db25686ba9 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Mon, 14 Oct 2024 11:14:44 +0300 Subject: [PATCH] fix(api): Adapt `eth_getCode` to EVM emulator (#3073) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What ❔ Fixes returned bytecodes with EVM bytecodes by removing the length prefix and padding suffix. ## Why ❔ Callers are interested in the original EVM bytecode rather than its transformed version. ## Checklist - [x] PR title corresponds to the body of PR (we generate changelog entries from PRs). - [x] Tests for the changes have been added / updated. - [x] Documentation comments have been added / updated. - [x] Code has been formatted via `zk_supervisor fmt` and `zk_supervisor lint`. --- Cargo.lock | 10 ++ Cargo.toml | 1 + ...1717e73c5e6b063be3553d82bfecb98334980.json | 24 ---- ...35bfda52cc5bb5a4bfb11270a2a784491c967.json | 30 ++++ core/lib/dal/src/storage_web3_dal.rs | 20 ++- .../versions/vm_latest/tests/evm_emulator.rs | 11 +- .../vm_latest/tracers/evm_deploy_tracer.rs | 6 +- .../src/versions/vm_latest/utils/mod.rs | 22 +-- core/lib/utils/src/bytecode.rs | 61 ++++++++ core/node/api_server/Cargo.toml | 1 + core/node/api_server/src/testonly.rs | 25 ++++ core/node/api_server/src/utils.rs | 36 +++++ .../api_server/src/web3/namespaces/eth.rs | 21 ++- core/node/api_server/src/web3/tests/mod.rs | 134 +++++++++++++++++- 14 files changed, 338 insertions(+), 64 deletions(-) delete mode 100644 core/lib/dal/.sqlx/query-369f8f652335176ab22ee45fd6f1717e73c5e6b063be3553d82bfecb98334980.json create mode 100644 core/lib/dal/.sqlx/query-c61b15a9591e65eab7d226f5b9035bfda52cc5bb5a4bfb11270a2a784491c967.json diff --git a/Cargo.lock b/Cargo.lock index 7b1a268afef5..f9f7a88764ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1539,6 +1539,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "const-decoder" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b381abde2cdc1bc3817e394b24e05667a2dc89f37570cbd34d9c397d99e56e3f" +dependencies = [ + "compile-fmt", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -10516,6 +10525,7 @@ dependencies = [ "async-trait", "axum", "chrono", + "const-decoder", "futures 0.3.30", "governor", "hex", diff --git a/Cargo.toml b/Cargo.toml index d597f4af7542..60b5628f4191 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -114,6 +114,7 @@ blake2 = "0.10" chrono = "0.4" clap = "4.2.2" codegen = "0.2.0" +const-decoder = "0.4.0" criterion = "0.4.0" ctrlc = "3.1" dashmap = "5.5.3" diff --git a/core/lib/dal/.sqlx/query-369f8f652335176ab22ee45fd6f1717e73c5e6b063be3553d82bfecb98334980.json b/core/lib/dal/.sqlx/query-369f8f652335176ab22ee45fd6f1717e73c5e6b063be3553d82bfecb98334980.json deleted file mode 100644 index 7245fa3059ed..000000000000 --- a/core/lib/dal/.sqlx/query-369f8f652335176ab22ee45fd6f1717e73c5e6b063be3553d82bfecb98334980.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT\n bytecode\n FROM\n (\n SELECT\n *\n FROM\n storage_logs\n WHERE\n storage_logs.hashed_key = $1\n AND storage_logs.miniblock_number <= $2\n ORDER BY\n storage_logs.miniblock_number DESC,\n storage_logs.operation_number DESC\n LIMIT\n 1\n ) t\n JOIN factory_deps ON value = factory_deps.bytecode_hash\n WHERE\n value != $3\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "bytecode", - "type_info": "Bytea" - } - ], - "parameters": { - "Left": [ - "Bytea", - "Int8", - "Bytea" - ] - }, - "nullable": [ - false - ] - }, - "hash": "369f8f652335176ab22ee45fd6f1717e73c5e6b063be3553d82bfecb98334980" -} diff --git a/core/lib/dal/.sqlx/query-c61b15a9591e65eab7d226f5b9035bfda52cc5bb5a4bfb11270a2a784491c967.json b/core/lib/dal/.sqlx/query-c61b15a9591e65eab7d226f5b9035bfda52cc5bb5a4bfb11270a2a784491c967.json new file mode 100644 index 000000000000..20b791991650 --- /dev/null +++ b/core/lib/dal/.sqlx/query-c61b15a9591e65eab7d226f5b9035bfda52cc5bb5a4bfb11270a2a784491c967.json @@ -0,0 +1,30 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT\n bytecode_hash,\n bytecode\n FROM\n (\n SELECT\n value\n FROM\n storage_logs\n WHERE\n storage_logs.hashed_key = $1\n AND storage_logs.miniblock_number <= $2\n ORDER BY\n storage_logs.miniblock_number DESC,\n storage_logs.operation_number DESC\n LIMIT\n 1\n ) deploy_log\n JOIN factory_deps ON value = factory_deps.bytecode_hash\n WHERE\n value != $3\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "bytecode_hash", + "type_info": "Bytea" + }, + { + "ordinal": 1, + "name": "bytecode", + "type_info": "Bytea" + } + ], + "parameters": { + "Left": [ + "Bytea", + "Int8", + "Bytea" + ] + }, + "nullable": [ + false, + false + ] + }, + "hash": "c61b15a9591e65eab7d226f5b9035bfda52cc5bb5a4bfb11270a2a784491c967" +} diff --git a/core/lib/dal/src/storage_web3_dal.rs b/core/lib/dal/src/storage_web3_dal.rs index 78c1dc0c3d0d..10d2cfe61525 100644 --- a/core/lib/dal/src/storage_web3_dal.rs +++ b/core/lib/dal/src/storage_web3_dal.rs @@ -15,6 +15,13 @@ use zksync_utils::h256_to_u256; use crate::{models::storage_block::ResolvedL1BatchForL2Block, Core, CoreDal}; +/// Raw bytecode information returned by [`StorageWeb3Dal::get_contract_code_unchecked()`]. +#[derive(Debug)] +pub struct RawBytecode { + pub bytecode_hash: H256, + pub bytecode: Vec, +} + #[derive(Debug)] pub struct StorageWeb3Dal<'a, 'c> { pub(crate) storage: &'a mut Connection<'c, Core>, @@ -234,16 +241,17 @@ impl StorageWeb3Dal<'_, '_> { &mut self, address: Address, block_number: L2BlockNumber, - ) -> DalResult>> { + ) -> DalResult> { let hashed_key = get_code_key(&address).hashed_key(); let row = sqlx::query!( r#" SELECT + bytecode_hash, bytecode FROM ( SELECT - * + value FROM storage_logs WHERE @@ -254,7 +262,7 @@ impl StorageWeb3Dal<'_, '_> { storage_logs.operation_number DESC LIMIT 1 - ) t + ) deploy_log JOIN factory_deps ON value = factory_deps.bytecode_hash WHERE value != $3 @@ -268,7 +276,11 @@ impl StorageWeb3Dal<'_, '_> { .with_arg("block_number", &block_number) .fetch_optional(self.storage) .await?; - Ok(row.map(|row| row.bytecode)) + + Ok(row.map(|row| RawBytecode { + bytecode_hash: H256::from_slice(&row.bytecode_hash), + bytecode: row.bytecode, + })) } /// Given bytecode hash, returns bytecode and L2 block number at which it was inserted. diff --git a/core/lib/multivm/src/versions/vm_latest/tests/evm_emulator.rs b/core/lib/multivm/src/versions/vm_latest/tests/evm_emulator.rs index 4316558eda26..34780b73eb05 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/evm_emulator.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/evm_emulator.rs @@ -12,7 +12,11 @@ use zksync_types::{ utils::{key_for_eth_balance, storage_key_for_eth_balance}, AccountTreeId, Address, Execute, StorageKey, H256, U256, }; -use zksync_utils::{be_words_to_bytes, bytecode::hash_bytecode, bytes_to_be_words, h256_to_u256}; +use zksync_utils::{ + be_words_to_bytes, + bytecode::{hash_bytecode, hash_evm_bytecode}, + bytes_to_be_words, h256_to_u256, +}; use crate::{ interface::{ @@ -21,7 +25,6 @@ use crate::{ versions::testonly::default_system_env, vm_latest::{ tests::tester::{VmTester, VmTesterBuilder}, - utils::hash_evm_bytecode, HistoryEnabled, }, }; @@ -87,7 +90,7 @@ impl EvmTestBuilder { let mut storage = self.storage; let mut system_env = default_system_env(); if self.deploy_emulator { - let evm_bytecode: Vec<_> = (0..=u8::MAX).collect(); + let evm_bytecode: Vec<_> = (0..32).collect(); let evm_bytecode_hash = hash_evm_bytecode(&evm_bytecode); storage.set_value( get_known_code_key(&evm_bytecode_hash), @@ -142,7 +145,7 @@ fn tracing_evm_contract_deployment() { .build(); let account = &mut vm.rich_accounts[0]; - let args = [Token::Bytes((0..=u8::MAX).collect())]; + let args = [Token::Bytes((0..32).collect())]; let evm_bytecode = ethabi::encode(&args); let expected_bytecode_hash = hash_evm_bytecode(&evm_bytecode); let execute = Execute::for_deploy(expected_bytecode_hash, vec![0; 32], &args); diff --git a/core/lib/multivm/src/versions/vm_latest/tracers/evm_deploy_tracer.rs b/core/lib/multivm/src/versions/vm_latest/tracers/evm_deploy_tracer.rs index d91ee13a920a..becc4f225276 100644 --- a/core/lib/multivm/src/versions/vm_latest/tracers/evm_deploy_tracer.rs +++ b/core/lib/multivm/src/versions/vm_latest/tracers/evm_deploy_tracer.rs @@ -8,16 +8,14 @@ use zk_evm_1_5_0::{ }, }; use zksync_types::{CONTRACT_DEPLOYER_ADDRESS, KNOWN_CODES_STORAGE_ADDRESS}; -use zksync_utils::{bytes_to_be_words, h256_to_u256}; +use zksync_utils::{bytecode::hash_evm_bytecode, bytes_to_be_words, h256_to_u256}; use zksync_vm_interface::storage::StoragePtr; use super::{traits::VmTracer, utils::read_pointer}; use crate::{ interface::{storage::WriteStorage, tracer::TracerExecutionStatus}, tracers::dynamic::vm_1_5_0::DynTracer, - vm_latest::{ - utils::hash_evm_bytecode, BootloaderState, HistoryMode, SimpleMemory, ZkSyncVmState, - }, + vm_latest::{BootloaderState, HistoryMode, SimpleMemory, ZkSyncVmState}, }; /// Tracer responsible for collecting information about EVM deploys and providing those diff --git a/core/lib/multivm/src/versions/vm_latest/utils/mod.rs b/core/lib/multivm/src/versions/vm_latest/utils/mod.rs index e07d3eda7c4c..aeb66755f514 100644 --- a/core/lib/multivm/src/versions/vm_latest/utils/mod.rs +++ b/core/lib/multivm/src/versions/vm_latest/utils/mod.rs @@ -1,11 +1,7 @@ //! Utility functions for the VM. use once_cell::sync::Lazy; -use zk_evm_1_5_0::{ - aux_structures::MemoryPage, - sha2, - zkevm_opcode_defs::{BlobSha256Format, VersionedHashLen32}, -}; +use zk_evm_1_5_0::aux_structures::MemoryPage; use zksync_types::{H256, KNOWN_CODES_STORAGE_ADDRESS}; use zksync_vm_interface::VmEvent; @@ -15,22 +11,6 @@ pub(crate) mod logs; pub mod overhead; pub mod transaction_encoding; -pub(crate) fn hash_evm_bytecode(bytecode: &[u8]) -> H256 { - use sha2::{Digest, Sha256}; - let mut hasher = Sha256::new(); - let len = bytecode.len() as u16; - hasher.update(bytecode); - let result = hasher.finalize(); - - let mut output = [0u8; 32]; - output[..].copy_from_slice(result.as_slice()); - output[0] = BlobSha256Format::VERSION_BYTE; - output[1] = 0; - output[2..4].copy_from_slice(&len.to_be_bytes()); - - H256(output) -} - pub const fn heap_page_from_base(base: MemoryPage) -> MemoryPage { MemoryPage(base.0 + 2) } diff --git a/core/lib/utils/src/bytecode.rs b/core/lib/utils/src/bytecode.rs index 48bdb4330207..01cce5bc34d0 100644 --- a/core/lib/utils/src/bytecode.rs +++ b/core/lib/utils/src/bytecode.rs @@ -1,5 +1,6 @@ // FIXME: move to basic_types? +use zk_evm::k256::sha2::{Digest, Sha256}; use zksync_basic_types::H256; use crate::bytes_to_chunks; @@ -40,6 +41,7 @@ pub fn validate_bytecode(code: &[u8]) -> Result<(), InvalidBytecodeError> { Ok(()) } +/// Hashes the provided EraVM bytecode. pub fn hash_bytecode(code: &[u8]) -> H256 { let chunked_code = bytes_to_chunks(code); let hash = zk_evm::zkevm_opcode_defs::utils::bytecode_to_code_hash(&chunked_code) @@ -55,3 +57,62 @@ pub fn bytecode_len_in_words(bytecodehash: &H256) -> u16 { pub fn bytecode_len_in_bytes(bytecodehash: H256) -> usize { bytecode_len_in_words(&bytecodehash) as usize * 32 } + +/// Bytecode marker encoded in the first byte of the bytecode hash. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[repr(u8)] +pub enum BytecodeMarker { + /// EraVM bytecode marker (1). + EraVm = 1, + /// EVM bytecode marker (2). + Evm = 2, +} + +impl BytecodeMarker { + /// Parses a marker from the bytecode hash. + pub fn new(bytecode_hash: H256) -> Option { + Some(match bytecode_hash.as_bytes()[0] { + val if val == Self::EraVm as u8 => Self::EraVm, + val if val == Self::Evm as u8 => Self::Evm, + _ => return None, + }) + } +} + +/// Hashes the provided EVM bytecode. The bytecode must be padded to an odd number of 32-byte words; +/// bytecodes stored in the known codes storage satisfy this requirement automatically. +pub fn hash_evm_bytecode(bytecode: &[u8]) -> H256 { + validate_bytecode(bytecode).expect("invalid EVM bytecode"); + + let mut hasher = Sha256::new(); + let len = bytecode.len() as u16; + hasher.update(bytecode); + let result = hasher.finalize(); + + let mut output = [0u8; 32]; + output[..].copy_from_slice(result.as_slice()); + output[0] = BytecodeMarker::Evm as u8; + output[1] = 0; + output[2..4].copy_from_slice(&len.to_be_bytes()); + + H256(output) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn bytecode_markers_are_valid() { + let bytecode_hash = hash_bytecode(&[0; 32]); + assert_eq!( + BytecodeMarker::new(bytecode_hash), + Some(BytecodeMarker::EraVm) + ); + let bytecode_hash = hash_evm_bytecode(&[0; 32]); + assert_eq!( + BytecodeMarker::new(bytecode_hash), + Some(BytecodeMarker::Evm) + ); + } +} diff --git a/core/node/api_server/Cargo.toml b/core/node/api_server/Cargo.toml index d0723a9d23e7..067b9b3e3722 100644 --- a/core/node/api_server/Cargo.toml +++ b/core/node/api_server/Cargo.toml @@ -61,4 +61,5 @@ zksync_node_genesis.workspace = true zksync_node_test_utils.workspace = true assert_matches.workspace = true +const-decoder.workspace = true test-casing.workspace = true diff --git a/core/node/api_server/src/testonly.rs b/core/node/api_server/src/testonly.rs index 8dc7915385a1..45ed802d68f5 100644 --- a/core/node/api_server/src/testonly.rs +++ b/core/node/api_server/src/testonly.rs @@ -2,6 +2,7 @@ use std::{collections::HashMap, iter}; +use const_decoder::Decoder; use zk_evm_1_5_0::zkevm_opcode_defs::decoding::{EncodingModeProduction, VmEncodingMode}; use zksync_contracts::{ eth_contract, get_loadnext_contract, load_contract, read_bytecode, @@ -26,6 +27,30 @@ use zksync_types::{ }; use zksync_utils::{address_to_u256, u256_to_h256}; +pub(crate) const RAW_EVM_BYTECODE: &[u8] = &const_decoder::decode!( + Decoder::Hex, + b"00000000000000000000000000000000000000000000000000000000000001266080604052348015\ + 600e575f80fd5b50600436106030575f3560e01c8063816898ff146034578063fb5343f314604c57\ + 5b5f80fd5b604a60048036038101906046919060a6565b6066565b005b6052606f565b604051605d\ + 919060d9565b60405180910390f35b805f8190555050565b5f5481565b5f80fd5b5f819050919050\ + 565b6088816078565b81146091575f80fd5b50565b5f8135905060a0816081565b92915050565b5f\ + 6020828403121560b85760b76074565b5b5f60c3848285016094565b91505092915050565b60d381\ + 6078565b82525050565b5f60208201905060ea5f83018460cc565b9291505056fea2646970667358\ + 221220caca1247066da378f2ec77c310f2ae51576272367b4fa11cc4350af4e9ce4d0964736f6c63\ + 4300081a00330000000000000000000000000000000000000000000000000000" +); +pub(crate) const PROCESSED_EVM_BYTECODE: &[u8] = &const_decoder::decode!( + Decoder::Hex, + b"6080604052348015600e575f80fd5b50600436106030575f3560e01c8063816898ff146034578063\ + fb5343f314604c575b5f80fd5b604a60048036038101906046919060a6565b6066565b005b605260\ + 6f565b604051605d919060d9565b60405180910390f35b805f8190555050565b5f5481565b5f80fd\ + 5b5f819050919050565b6088816078565b81146091575f80fd5b50565b5f8135905060a081608156\ + 5b92915050565b5f6020828403121560b85760b76074565b5b5f60c3848285016094565b91505092\ + 915050565b60d3816078565b82525050565b5f60208201905060ea5f83018460cc565b9291505056\ + fea2646970667358221220caca1247066da378f2ec77c310f2ae51576272367b4fa11cc4350af4e9\ + ce4d0964736f6c634300081a0033" +); + const EXPENSIVE_CONTRACT_PATH: &str = "etc/contracts-test-data/artifacts-zk/contracts/expensive/expensive.sol/Expensive.json"; const PRECOMPILES_CONTRACT_PATH: &str = diff --git a/core/node/api_server/src/utils.rs b/core/node/api_server/src/utils.rs index 6769e773dc77..c7a1134682bf 100644 --- a/core/node/api_server/src/utils.rs +++ b/core/node/api_server/src/utils.rs @@ -6,9 +6,33 @@ use std::{ time::{Duration, Instant}, }; +use anyhow::Context; use zksync_dal::{Connection, Core, DalError}; +use zksync_multivm::circuit_sequencer_api_latest::boojum::ethereum_types::U256; use zksync_web3_decl::error::Web3Error; +pub(crate) fn prepare_evm_bytecode(raw: &[u8]) -> anyhow::Result> { + // EVM bytecodes are prefixed with a big-endian `U256` bytecode length. + let bytecode_len_bytes = raw.get(..32).context("length < 32")?; + let bytecode_len = U256::from_big_endian(bytecode_len_bytes); + let bytecode_len: usize = bytecode_len + .try_into() + .map_err(|_| anyhow::anyhow!("length ({bytecode_len}) overflow"))?; + let bytecode = raw.get(32..(32 + bytecode_len)).with_context(|| { + format!( + "prefixed length ({bytecode_len}) exceeds real length ({})", + raw.len() - 32 + ) + })?; + // Since slicing above succeeded, this one is safe. + let padding = &raw[(32 + bytecode_len)..]; + anyhow::ensure!( + padding.iter().all(|&b| b == 0), + "bytecode padding contains non-zero bytes" + ); + Ok(bytecode.to_vec()) +} + /// Opens a readonly transaction over the specified connection. pub(crate) async fn open_readonly_transaction<'r>( conn: &'r mut Connection<'_, Core>, @@ -66,3 +90,15 @@ macro_rules! report_filter { ReportFilter::new($interval, &LAST_TIMESTAMP) }}; } + +#[cfg(test)] +mod tests { + use super::*; + use crate::testonly::{PROCESSED_EVM_BYTECODE, RAW_EVM_BYTECODE}; + + #[test] + fn preparing_evm_bytecode() { + let prepared = prepare_evm_bytecode(RAW_EVM_BYTECODE).unwrap(); + assert_eq!(prepared, PROCESSED_EVM_BYTECODE); + } +} diff --git a/core/node/api_server/src/web3/namespaces/eth.rs b/core/node/api_server/src/web3/namespaces/eth.rs index 44362dd098e0..008c529ec638 100644 --- a/core/node/api_server/src/web3/namespaces/eth.rs +++ b/core/node/api_server/src/web3/namespaces/eth.rs @@ -12,7 +12,7 @@ use zksync_types::{ web3::{self, Bytes, SyncInfo, SyncState}, AccountTreeId, L2BlockNumber, StorageKey, H256, L2_BASE_TOKEN_ADDRESS, U256, }; -use zksync_utils::u256_to_h256; +use zksync_utils::{bytecode::BytecodeMarker, u256_to_h256}; use zksync_web3_decl::{ error::Web3Error, types::{Address, Block, Filter, FilterChanges, Log, U64}, @@ -21,7 +21,7 @@ use zksync_web3_decl::{ use crate::{ execution_sandbox::BlockArgs, tx_sender::BinarySearchKind, - utils::open_readonly_transaction, + utils::{open_readonly_transaction, prepare_evm_bytecode}, web3::{backend_jsonrpsee::MethodTracer, metrics::API_METRICS, state::RpcState, TypedFilter}, }; @@ -397,7 +397,22 @@ impl EthNamespace { .get_contract_code_unchecked(address, block_number) .await .map_err(DalError::generalize)?; - Ok(contract_code.unwrap_or_default().into()) + let Some(contract_code) = contract_code else { + return Ok(Bytes::default()); + }; + // Check if the bytecode is an EVM bytecode, and if so, pre-process it correspondingly. + let marker = BytecodeMarker::new(contract_code.bytecode_hash); + let prepared_bytecode = if marker == Some(BytecodeMarker::Evm) { + prepare_evm_bytecode(&contract_code.bytecode).with_context(|| { + format!( + "malformed EVM bytecode at address {address:?}, hash = {:?}", + contract_code.bytecode_hash + ) + })? + } else { + contract_code.bytecode + }; + Ok(prepared_bytecode.into()) } pub fn chain_id_impl(&self) -> U64 { diff --git a/core/node/api_server/src/web3/tests/mod.rs b/core/node/api_server/src/web3/tests/mod.rs index 632e263c6536..77b0b1824c75 100644 --- a/core/node/api_server/src/web3/tests/mod.rs +++ b/core/node/api_server/src/web3/tests/mod.rs @@ -31,17 +31,21 @@ use zksync_system_constants::{ }; use zksync_types::{ api, - block::{pack_block_info, L2BlockHeader}, + block::{pack_block_info, L2BlockHasher, L2BlockHeader}, get_nonce_key, l2::L2Tx, storage::get_code_key, + system_contracts::get_system_smart_contracts, tokens::{TokenInfo, TokenMetadata}, tx::IncludedTxLocation, utils::{storage_key_for_eth_balance, storage_key_for_standard_token_balance}, AccountTreeId, Address, L1BatchNumber, Nonce, ProtocolVersionId, StorageKey, StorageLog, H256, U256, U64, }; -use zksync_utils::u256_to_h256; +use zksync_utils::{ + bytecode::{hash_bytecode, hash_evm_bytecode}, + u256_to_h256, +}; use zksync_vm_executor::oneshot::MockOneshotExecutor; use zksync_web3_decl::{ client::{Client, DynClient, L2}, @@ -58,7 +62,10 @@ use zksync_web3_decl::{ }; use super::*; -use crate::web3::testonly::TestServerBuilder; +use crate::{ + testonly::{PROCESSED_EVM_BYTECODE, RAW_EVM_BYTECODE}, + web3::testonly::TestServerBuilder, +}; mod debug; mod filters; @@ -625,7 +632,7 @@ impl HttpTest for StorageAccessWithSnapshotRecovery { fn storage_initialization(&self) -> StorageInitialization { let address = Address::repeat_byte(1); let code_key = get_code_key(&address); - let code_hash = H256::repeat_byte(2); + let code_hash = hash_bytecode(&[0; 32]); let balance_key = storage_key_for_eth_balance(&address); let logs = vec![ StorageLog::new_write_log(code_key, code_hash), @@ -1102,3 +1109,122 @@ impl HttpTest for GenesisConfigTest { async fn tracing_genesis_config() { test_http_server(GenesisConfigTest).await; } + +#[derive(Debug)] +struct GetBytecodeTest; + +impl GetBytecodeTest { + async fn insert_evm_bytecode( + connection: &mut Connection<'_, Core>, + at_block: L2BlockNumber, + address: Address, + ) -> anyhow::Result<()> { + let evm_bytecode_hash = hash_evm_bytecode(RAW_EVM_BYTECODE); + let code_log = StorageLog::new_write_log(get_code_key(&address), evm_bytecode_hash); + connection + .storage_logs_dal() + .append_storage_logs(at_block, &[code_log]) + .await?; + + let factory_deps = HashMap::from([(evm_bytecode_hash, RAW_EVM_BYTECODE.to_vec())]); + connection + .factory_deps_dal() + .insert_factory_deps(at_block, &factory_deps) + .await?; + Ok(()) + } +} + +#[async_trait] +impl HttpTest for GetBytecodeTest { + async fn test( + &self, + client: &DynClient, + pool: &ConnectionPool, + ) -> anyhow::Result<()> { + let genesis_evm_address = Address::repeat_byte(1); + let mut connection = pool.connection().await?; + Self::insert_evm_bytecode(&mut connection, L2BlockNumber(0), genesis_evm_address).await?; + + for contract in get_system_smart_contracts(false) { + let bytecode = client + .get_code(*contract.account_id.address(), None) + .await?; + assert_eq!(bytecode.0, contract.bytecode); + } + + let bytecode = client.get_code(genesis_evm_address, None).await?; + assert_eq!(bytecode.0, PROCESSED_EVM_BYTECODE); + + let latest_block_variants = [ + api::BlockNumber::Pending, + api::BlockNumber::Latest, + api::BlockNumber::Committed, + ]; + let latest_block_variants = latest_block_variants.map(api::BlockIdVariant::BlockNumber); + + let genesis_block_variants = [ + api::BlockIdVariant::BlockNumber(api::BlockNumber::Earliest), + api::BlockIdVariant::BlockNumber(api::BlockNumber::Number(0.into())), + api::BlockIdVariant::BlockHashObject(api::BlockHashObject { + block_hash: L2BlockHasher::legacy_hash(L2BlockNumber(0)), + }), + ]; + for at_block in latest_block_variants + .into_iter() + .chain(genesis_block_variants) + { + println!("Testing {at_block:?} with genesis EVM code, latest block: 0"); + let bytecode = client.get_code(genesis_evm_address, Some(at_block)).await?; + assert_eq!(bytecode.0, PROCESSED_EVM_BYTECODE); + } + + // Create another block with an EVM bytecode. + let new_bytecode_address = Address::repeat_byte(2); + let mut connection = pool.connection().await?; + let block_header = store_l2_block(&mut connection, L2BlockNumber(1), &[]).await?; + Self::insert_evm_bytecode(&mut connection, L2BlockNumber(1), new_bytecode_address).await?; + + let bytecode = client.get_code(genesis_evm_address, None).await?; + assert_eq!(bytecode.0, PROCESSED_EVM_BYTECODE); + let bytecode = client.get_code(new_bytecode_address, None).await?; + assert_eq!(bytecode.0, PROCESSED_EVM_BYTECODE); + + let new_block_variants = [ + api::BlockIdVariant::BlockNumber(api::BlockNumber::Number(1.into())), + api::BlockIdVariant::BlockHashObject(api::BlockHashObject { + block_hash: block_header.hash, + }), + ]; + for at_block in latest_block_variants.into_iter().chain(new_block_variants) { + println!("Testing {at_block:?} with new EVM code, latest block: 1"); + let bytecode = client + .get_code(new_bytecode_address, Some(at_block)) + .await?; + assert_eq!(bytecode.0, PROCESSED_EVM_BYTECODE); + } + for at_block in genesis_block_variants { + println!("Testing {at_block:?} with new EVM code, latest block: 1"); + let bytecode = client + .get_code(new_bytecode_address, Some(at_block)) + .await?; + assert!(bytecode.0.is_empty()); + } + + for at_block in latest_block_variants + .into_iter() + .chain(new_block_variants) + .chain(genesis_block_variants) + { + println!("Testing {at_block:?} with genesis EVM code, latest block: 1"); + let bytecode = client.get_code(genesis_evm_address, Some(at_block)).await?; + assert_eq!(bytecode.0, PROCESSED_EVM_BYTECODE); + } + Ok(()) + } +} + +#[tokio::test] +async fn getting_bytecodes() { + test_http_server(GetBytecodeTest).await; +}