diff --git a/core/lib/basic_types/src/bytecode.rs b/core/lib/basic_types/src/bytecode.rs index 585ba0ef8c88..570519bbbd25 100644 --- a/core/lib/basic_types/src/bytecode.rs +++ b/core/lib/basic_types/src/bytecode.rs @@ -10,6 +10,8 @@ //! Both bytecode kinds are right-padded to consist of an integer, odd number of 32-byte words. All methods //! in this module operate on padded bytecodes unless explicitly specified otherwise. +use std::iter; + use anyhow::Context as _; use sha2::{Digest, Sha256}; @@ -68,21 +70,31 @@ pub struct BytecodeHash(H256); impl BytecodeHash { /// Hashes the provided EraVM bytecode. pub fn for_bytecode(bytecode: &[u8]) -> Self { - Self::for_generic_bytecode(BytecodeMarker::EraVm, bytecode) + Self::for_generic_bytecode(BytecodeMarker::EraVm, bytecode, bytecode.len()) } /// Hashes the provided padded EVM bytecode. - pub fn for_evm_bytecode(bytecode: &[u8]) -> Self { - Self::for_generic_bytecode(BytecodeMarker::Evm, bytecode) + pub fn for_evm_bytecode(raw_bytecode_len: usize, bytecode: &[u8]) -> Self { + Self::for_generic_bytecode(BytecodeMarker::Evm, bytecode, raw_bytecode_len) + } + + /// Hashes the provided raw EVM bytecode. + pub fn for_raw_evm_bytecode(bytecode: &[u8]) -> Self { + let padded_evm_bytecode = pad_evm_bytecode(bytecode); + Self::for_evm_bytecode(bytecode.len(), &padded_evm_bytecode) } - fn for_generic_bytecode(kind: BytecodeMarker, bytecode: &[u8]) -> Self { + fn for_generic_bytecode( + kind: BytecodeMarker, + bytecode: &[u8], + bytecode_len_in_bytes: usize, + ) -> Self { validate_bytecode(bytecode).expect("invalid bytecode"); let mut hasher = Sha256::new(); let len = match kind { - BytecodeMarker::EraVm => (bytecode.len() / 32) as u16, - BytecodeMarker::Evm => bytecode.len() as u16, + BytecodeMarker::EraVm => (bytecode_len_in_bytes / 32) as u16, + BytecodeMarker::Evm => bytecode_len_in_bytes as u16, }; hasher.update(bytecode); let result = hasher.finalize(); @@ -157,23 +169,22 @@ impl BytecodeMarker { } /// Removes padding from an EVM bytecode, returning the original EVM bytecode. -pub fn trim_padded_evm_bytecode(raw: &[u8]) -> anyhow::Result<&[u8]> { +pub fn trim_padded_evm_bytecode(bytecode_hash: BytecodeHash, raw: &[u8]) -> anyhow::Result<&[u8]> { + if bytecode_hash.marker() != BytecodeMarker::Evm { + anyhow::bail!("only EVM bytecode hashes allowed") + } validate_bytecode(raw).context("bytecode fails basic validity checks")?; - // 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(|| { + // Actual raw unpadded EVM bytecode length is encoded in bytecode hash + let bytecode_len: usize = bytecode_hash.len_in_bytes(); + let bytecode = raw.get(0..bytecode_len).with_context(|| { format!( - "prefixed length ({bytecode_len}) exceeds real length ({})", - raw.len() - 32 + "encoded length ({bytecode_len}) exceeds real length ({})", + raw.len() ) })?; // Since slicing above succeeded, this one is safe. - let padding = &raw[(32 + bytecode_len)..]; + let padding = &raw[bytecode_len..]; anyhow::ensure!( padding.iter().all(|&b| b == 0), "bytecode padding contains non-zero bytes" @@ -181,6 +192,25 @@ pub fn trim_padded_evm_bytecode(raw: &[u8]) -> anyhow::Result<&[u8]> { Ok(bytecode) } +/// Pads an EVM bytecode in the same ways it's done by system contracts. +pub fn pad_evm_bytecode(deployed_bytecode: &[u8]) -> Vec { + let mut padded = Vec::with_capacity(deployed_bytecode.len()); + padded.extend_from_slice(deployed_bytecode); + + // Pad to the 32-byte word boundary. + if padded.len() % 32 != 0 { + padded.extend(iter::repeat(0).take(32 - padded.len() % 32)); + } + assert_eq!(padded.len() % 32, 0); + + // Pad to contain the odd number of words. + if (padded.len() / 32) % 2 != 1 { + padded.extend_from_slice(&[0; 32]); + } + assert_eq!((padded.len() / 32) % 2, 1); + padded +} + #[doc(hidden)] // only useful for tests pub mod testonly { use const_decoder::Decoder; @@ -223,14 +253,19 @@ mod tests { assert_eq!(bytecode_hash.marker(), BytecodeMarker::EraVm); assert_eq!(bytecode_hash.len_in_bytes(), 32); - let bytecode_hash = BytecodeHash::for_evm_bytecode(&[0; 32]); + let bytecode_hash = BytecodeHash::for_raw_evm_bytecode(&[0; 32]); + assert_eq!(bytecode_hash.marker(), BytecodeMarker::Evm); + assert_eq!(bytecode_hash.len_in_bytes(), 32); + + let bytecode_hash = BytecodeHash::for_evm_bytecode(32, &[0; 64]); assert_eq!(bytecode_hash.marker(), BytecodeMarker::Evm); assert_eq!(bytecode_hash.len_in_bytes(), 32); } #[test] fn preparing_evm_bytecode() { - let prepared = trim_padded_evm_bytecode(RAW_EVM_BYTECODE).unwrap(); + let bytecode_hash = BytecodeHash::for_raw_evm_bytecode(&RAW_EVM_BYTECODE); + let prepared = trim_padded_evm_bytecode(bytecode_hash, RAW_EVM_BYTECODE).unwrap(); assert_eq!(prepared, PROCESSED_EVM_BYTECODE); } } diff --git a/core/lib/contract_verifier/src/lib.rs b/core/lib/contract_verifier/src/lib.rs index 284d9921a674..43da4127b809 100644 --- a/core/lib/contract_verifier/src/lib.rs +++ b/core/lib/contract_verifier/src/lib.rs @@ -14,7 +14,7 @@ use tokio::time; use zksync_dal::{contract_verification_dal::DeployedContractData, ConnectionPool, Core, CoreDal}; use zksync_queued_job_processor::{async_trait, JobProcessor}; use zksync_types::{ - bytecode::{trim_padded_evm_bytecode, BytecodeMarker}, + bytecode::{trim_padded_evm_bytecode, BytecodeHash, BytecodeMarker}, contract_verification_api::{ self as api, CompilationArtifacts, VerificationIncomingRequest, VerificationInfo, VerificationRequest, @@ -257,8 +257,12 @@ impl ContractVerifier { let deployed_bytecode = match bytecode_marker { BytecodeMarker::EraVm => deployed_contract.bytecode.as_slice(), - BytecodeMarker::Evm => trim_padded_evm_bytecode(&deployed_contract.bytecode) - .context("invalid stored EVM bytecode")?, + BytecodeMarker::Evm => trim_padded_evm_bytecode( + BytecodeHash::try_from(deployed_contract.bytecode_hash) + .context("Invalid bytecode hash")?, + &deployed_contract.bytecode, + ) + .context("invalid stored EVM bytecode")?, }; if artifacts.deployed_bytecode() != deployed_bytecode { diff --git a/core/lib/contract_verifier/src/tests/mod.rs b/core/lib/contract_verifier/src/tests/mod.rs index f66732675ce6..f51cd4868e75 100644 --- a/core/lib/contract_verifier/src/tests/mod.rs +++ b/core/lib/contract_verifier/src/tests/mod.rs @@ -163,7 +163,7 @@ async fn mock_evm_deployment( factory_deps: vec![], }; let bytecode = pad_evm_bytecode(deployed_bytecode); - let bytecode_hash = BytecodeHash::for_evm_bytecode(&bytecode).value(); + let bytecode_hash = BytecodeHash::for_evm_bytecode(deployed_bytecode.len(), &bytecode).value(); mock_deployment_inner(storage, address, bytecode_hash, bytecode, deployment).await; } diff --git a/core/lib/multivm/src/versions/testonly/evm_emulator.rs b/core/lib/multivm/src/versions/testonly/evm_emulator.rs index b979efe360db..bcf6eccfdf5d 100644 --- a/core/lib/multivm/src/versions/testonly/evm_emulator.rs +++ b/core/lib/multivm/src/versions/testonly/evm_emulator.rs @@ -76,7 +76,8 @@ impl EvmTestBuilder { let mut system_env = default_system_env(); if self.deploy_emulator { let evm_bytecode: Vec<_> = (0..32).collect(); - let evm_bytecode_hash = BytecodeHash::for_evm_bytecode(&evm_bytecode).value(); + let evm_bytecode_hash = + BytecodeHash::for_evm_bytecode(evm_bytecode.len(), &evm_bytecode).value(); storage.set_value( get_known_code_key(&evm_bytecode_hash), H256::from_low_u64_be(1), @@ -131,7 +132,8 @@ pub(crate) fn test_tracing_evm_contract_deployment() { let args = [Token::Bytes((0..32).collect())]; let evm_bytecode = ethabi::encode(&args); - let expected_bytecode_hash = BytecodeHash::for_evm_bytecode(&evm_bytecode).value(); + let expected_bytecode_hash = + BytecodeHash::for_evm_bytecode(evm_bytecode.len(), &evm_bytecode).value(); let execute = Execute::for_deploy(expected_bytecode_hash, vec![0; 32], &args); let deploy_tx = account.get_l2_tx_for_execute(execute, None); let (_, vm_result) = vm @@ -148,7 +150,8 @@ pub(crate) fn test_tracing_evm_contract_deployment() { // "Deploy" a bytecode in another transaction and check that the first tx doesn't interfere with the returned `dynamic_factory_deps`. let args = [Token::Bytes((0..32).rev().collect())]; let evm_bytecode = ethabi::encode(&args); - let expected_bytecode_hash = BytecodeHash::for_evm_bytecode(&evm_bytecode).value(); + let expected_bytecode_hash = + BytecodeHash::for_evm_bytecode(evm_bytecode.len(), &evm_bytecode).value(); let execute = Execute::for_deploy(expected_bytecode_hash, vec![0; 32], &args); let deploy_tx = account.get_l2_tx_for_execute(execute, None); let (_, vm_result) = vm @@ -324,7 +327,8 @@ pub(crate) fn test_mock_emulator_with_deployment(revert: bool) { let mock_emulator_abi = &TestContract::mock_evm_emulator().abi; let new_evm_bytecode = vec![0xfe; 96]; - let new_evm_bytecode_hash = BytecodeHash::for_evm_bytecode(&new_evm_bytecode).value(); + let new_evm_bytecode_hash = + BytecodeHash::for_evm_bytecode(new_evm_bytecode.len(), &new_evm_bytecode).value(); let test_fn = mock_emulator_abi.function("testDeploymentAndCall").unwrap(); let test_tx = account.get_l2_tx_for_execute( @@ -402,7 +406,10 @@ pub(crate) fn test_mock_emulator_with_recursive_deployment() { let bytecodes: HashMap<_, _> = (0_u8..10) .map(|byte| { let bytecode = vec![byte; 32]; - (BytecodeHash::for_evm_bytecode(&bytecode).value(), bytecode) + ( + BytecodeHash::for_evm_bytecode(bytecode.len(), &bytecode).value(), + bytecode, + ) }) .collect(); let test_fn = mock_emulator_abi @@ -448,7 +455,10 @@ fn test_mock_emulator_with_partial_reverts_and_rng(rng: &mut impl let all_bytecodes: HashMap<_, _> = (0_u8..10) .map(|_| { let bytecode = vec![rng.gen(); 32]; - (BytecodeHash::for_evm_bytecode(&bytecode).value(), bytecode) + ( + BytecodeHash::for_evm_bytecode(bytecode.len(), &bytecode).value(), + bytecode, + ) }) .collect(); let should_revert: Vec<_> = (0..10).map(|_| rng.gen::()).collect(); diff --git a/core/lib/multivm/src/versions/vm_fast/evm_deploy_tracer.rs b/core/lib/multivm/src/versions/vm_fast/evm_deploy_tracer.rs index c443c99ccf9a..322680b80b64 100644 --- a/core/lib/multivm/src/versions/vm_fast/evm_deploy_tracer.rs +++ b/core/lib/multivm/src/versions/vm_fast/evm_deploy_tracer.rs @@ -37,8 +37,10 @@ pub(super) struct EvmDeployTracer { impl EvmDeployTracer { pub(super) fn new(bytecodes: DynamicBytecodes) -> Self { - let tracked_signature = - ethabi::short_signature("publishEVMBytecode", &[ethabi::ParamType::Bytes]); + let tracked_signature = ethabi::short_signature( + "publishEVMBytecode", + &[ethabi::ParamType::Uint(256), ethabi::ParamType::Bytes], + ); Self { tracked_signature, bytecodes, @@ -64,10 +66,20 @@ impl EvmDeployTracer { match ethabi::decode(&[ethabi::ParamType::Bytes], data) { Ok(decoded) => { // `unwrap`s should be safe since the function signature is checked above. - let published_bytecode = decoded.into_iter().next().unwrap().into_bytes().unwrap(); - let bytecode_hash = - BytecodeHash::for_evm_bytecode(&published_bytecode).value_u256(); - self.bytecodes.insert(bytecode_hash, published_bytecode); + let mut decoded_iter = decoded.into_iter(); + let raw_bytecode_len = decoded_iter.next().unwrap().into_uint().unwrap().try_into(); + match raw_bytecode_len { + Ok(raw_bytecode_len) => { + let published_bytecode = decoded_iter.next().unwrap().into_bytes().unwrap(); + let bytecode_hash = + BytecodeHash::for_evm_bytecode(raw_bytecode_len, &published_bytecode) + .value_u256(); + self.bytecodes.insert(bytecode_hash, published_bytecode); + } + Err(err) => { + tracing::error!("Invalid bytecode len in `publishEVMBytecode` call: {err}") + } + } } Err(err) => tracing::error!("Unable to decode `publishEVMBytecode` call: {err}"), } 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 2e6ab8089eb0..dd03a9427efa 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 @@ -27,14 +27,16 @@ use crate::{ #[derive(Debug)] pub(crate) struct EvmDeployTracer { tracked_signature: [u8; 4], - pending_bytecodes: Vec>, + pending_bytecodes: Vec<(usize, Vec)>, _phantom: PhantomData, } impl EvmDeployTracer { pub(crate) fn new() -> Self { - let tracked_signature = - ethabi::short_signature("publishEVMBytecode", &[ethabi::ParamType::Bytes]); + let tracked_signature = ethabi::short_signature( + "publishEVMBytecode", + &[ethabi::ParamType::Uint(256), ethabi::ParamType::Bytes], + ); Self { tracked_signature, @@ -77,10 +79,23 @@ impl DynTracer> for EvmDeployTracer { return; } - match ethabi::decode(&[ethabi::ParamType::Bytes], data) { + match ethabi::decode( + &[ethabi::ParamType::Uint(256), ethabi::ParamType::Bytes], + data, + ) { Ok(decoded) => { - let published_bytecode = decoded.into_iter().next().unwrap().into_bytes().unwrap(); - self.pending_bytecodes.push(published_bytecode); + let mut decoded_iter = decoded.into_iter(); + let raw_bytecode_len = decoded_iter.next().unwrap().into_uint().unwrap().try_into(); + match raw_bytecode_len { + Ok(raw_bytecode_len) => { + let published_bytecode = decoded_iter.next().unwrap().into_bytes().unwrap(); + self.pending_bytecodes + .push((raw_bytecode_len, published_bytecode)); + } + Err(err) => { + tracing::error!("Invalid bytecode len in `publishEVMBytecode` call: {err}") + } + } } Err(err) => tracing::error!("Unable to decode `publishEVMBytecode` call: {err}"), } @@ -94,8 +109,9 @@ impl VmTracer for EvmDeployTracer { _bootloader_state: &mut BootloaderState, ) -> TracerExecutionStatus { let timestamp = Timestamp(state.local_state.timestamp); - for published_bytecode in mem::take(&mut self.pending_bytecodes) { - let hash = BytecodeHash::for_evm_bytecode(&published_bytecode).value_u256(); + for (raw_bytecode_len, published_bytecode) in mem::take(&mut self.pending_bytecodes) { + let hash = + BytecodeHash::for_evm_bytecode(raw_bytecode_len, &published_bytecode).value_u256(); let as_words = bytes_to_be_words(&published_bytecode); state .decommittment_processor diff --git a/core/node/api_server/src/web3/namespaces/eth.rs b/core/node/api_server/src/web3/namespaces/eth.rs index 2765de2c2892..d2728fca8afb 100644 --- a/core/node/api_server/src/web3/namespaces/eth.rs +++ b/core/node/api_server/src/web3/namespaces/eth.rs @@ -6,7 +6,7 @@ use zksync_types::{ state_override::StateOverride, BlockId, BlockNumber, FeeHistory, GetLogsFilter, Transaction, TransactionId, TransactionReceipt, TransactionVariant, }, - bytecode::{trim_padded_evm_bytecode, BytecodeMarker}, + bytecode::{trim_padded_evm_bytecode, BytecodeHash, BytecodeMarker}, l2::{L2Tx, TransactionType}, transaction_request::CallRequest, u256_to_h256, @@ -404,14 +404,20 @@ impl EthNamespace { // 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) { - trim_padded_evm_bytecode(&contract_code.bytecode) - .with_context(|| { - format!( - "malformed EVM bytecode at address {address:?}, hash = {:?}", - contract_code.bytecode_hash - ) - })? - .to_vec() + trim_padded_evm_bytecode( + BytecodeHash::try_from(contract_code.bytecode_hash).expect(&format!( + "Invalid bytecode hash at address {address:?}: {:?}", + contract_code.bytecode_hash + )), + &contract_code.bytecode, + ) + .with_context(|| { + format!( + "malformed EVM bytecode at address {address:?}, hash = {:?}", + contract_code.bytecode_hash + ) + })? + .to_vec() } else { contract_code.bytecode }; diff --git a/core/node/api_server/src/web3/tests/mod.rs b/core/node/api_server/src/web3/tests/mod.rs index f447f135917a..dc94727f07aa 100644 --- a/core/node/api_server/src/web3/tests/mod.rs +++ b/core/node/api_server/src/web3/tests/mod.rs @@ -1171,7 +1171,7 @@ impl GetBytecodeTest { at_block: L2BlockNumber, address: Address, ) -> anyhow::Result<()> { - let evm_bytecode_hash = BytecodeHash::for_evm_bytecode(RAW_EVM_BYTECODE).value(); + let evm_bytecode_hash = BytecodeHash::for_raw_evm_bytecode(RAW_EVM_BYTECODE).value(); let code_log = StorageLog::new_write_log(get_code_key(&address), evm_bytecode_hash); connection .storage_logs_dal() diff --git a/core/node/contract_verification_server/src/tests.rs b/core/node/contract_verification_server/src/tests.rs index c5c1d88b3d0c..88b14db68733 100644 --- a/core/node/contract_verification_server/src/tests.rs +++ b/core/node/contract_verification_server/src/tests.rs @@ -54,7 +54,7 @@ async fn mock_deploy_contract( ) { let bytecode_hash = match kind { BytecodeMarker::EraVm => BytecodeHash::for_bytecode(&[0; 32]).value(), - BytecodeMarker::Evm => BytecodeHash::for_evm_bytecode(&[0; 96]).value(), + BytecodeMarker::Evm => BytecodeHash::for_evm_bytecode(0, &[0; 96]).value(), }; let deploy_log = StorageLog::new_write_log(get_code_key(&address), bytecode_hash); storage diff --git a/core/node/state_keeper/src/io/tests/mod.rs b/core/node/state_keeper/src/io/tests/mod.rs index 536efe82804a..39c9552c2fc5 100644 --- a/core/node/state_keeper/src/io/tests/mod.rs +++ b/core/node/state_keeper/src/io/tests/mod.rs @@ -447,7 +447,7 @@ async fn processing_dynamic_factory_deps_when_sealing_l2_block() { .map(|byte| { let evm_bytecode = vec![byte; 96]; ( - BytecodeHash::for_evm_bytecode(&evm_bytecode).value(), + BytecodeHash::for_raw_evm_bytecode(&evm_bytecode).value(), evm_bytecode, ) })