Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Adapt server for new EVM bytecode hash encoding #3396

Merged
merged 9 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 67 additions & 30 deletions core/lib/basic_types/src/bytecode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -157,46 +169,65 @@ 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"
);
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<u8> {
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;

pub const RAW_EVM_BYTECODE: &[u8] = &const_decoder::decode!(
pub const PADDED_EVM_BYTECODE: &[u8] = &const_decoder::decode!(
Decoder::Hex,
b"00000000000000000000000000000000000000000000000000000000000001266080604052348015\
600e575f80fd5b50600436106030575f3560e01c8063816898ff146034578063fb5343f314604c57\
5b5f80fd5b604a60048036038101906046919060a6565b6066565b005b6052606f565b604051605d\
919060d9565b60405180910390f35b805f8190555050565b5f5481565b5f80fd5b5f819050919050\
565b6088816078565b81146091575f80fd5b50565b5f8135905060a0816081565b92915050565b5f\
6020828403121560b85760b76074565b5b5f60c3848285016094565b91505092915050565b60d381\
6078565b82525050565b5f60208201905060ea5f83018460cc565b9291505056fea2646970667358\
221220caca1247066da378f2ec77c310f2ae51576272367b4fa11cc4350af4e9ce4d0964736f6c63\
4300081a00330000000000000000000000000000000000000000000000000000"
b"6080604052348015600e575f80fd5b50600436106030575f3560e01c8063816898ff146034578063\
fb5343f314604c575b5f80fd5b604a60048036038101906046919060a6565b6066565b005b605260\
6f565b604051605d919060d9565b60405180910390f35b805f8190555050565b5f5481565b5f80fd\
5b5f819050919050565b6088816078565b81146091575f80fd5b50565b5f8135905060a081608156\
5b92915050565b5f6020828403121560b85760b76074565b5b5f60c3848285016094565b91505092\
915050565b60d3816078565b82525050565b5f60208201905060ea5f83018460cc565b9291505056\
fea2646970667358221220caca1247066da378f2ec77c310f2ae51576272367b4fa11cc4350af4e9\
ce4d0964736f6c634300081a00330000000000000000000000000000000000000000000000000000\
0000000000000000000000000000000000000000000000000000000000000000"
);

pub const PROCESSED_EVM_BYTECODE: &[u8] = &const_decoder::decode!(
Decoder::Hex,
b"6080604052348015600e575f80fd5b50600436106030575f3560e01c8063816898ff146034578063\
Expand All @@ -213,7 +244,7 @@ pub mod testonly {
#[cfg(test)]
mod tests {
use super::{
testonly::{PROCESSED_EVM_BYTECODE, RAW_EVM_BYTECODE},
testonly::{PADDED_EVM_BYTECODE, PROCESSED_EVM_BYTECODE},
*,
};

Expand All @@ -223,14 +254,20 @@ 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; 96]);
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_evm_bytecode(PROCESSED_EVM_BYTECODE.len(), &PADDED_EVM_BYTECODE);
let prepared = trim_padded_evm_bytecode(bytecode_hash, PADDED_EVM_BYTECODE).unwrap();
assert_eq!(prepared, PROCESSED_EVM_BYTECODE);
}
}
10 changes: 7 additions & 3 deletions core/lib/contract_verifier/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down
31 changes: 3 additions & 28 deletions core/lib/contract_verifier/src/tests/mod.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
//! Tests for the contract verifier.

use std::{
collections::{HashMap, HashSet},
iter,
};
use std::collections::{HashMap, HashSet};

use test_casing::{test_casing, Product};
use tokio::sync::watch;
use zksync_dal::Connection;
use zksync_node_test_utils::{create_l1_batch, create_l2_block};
use zksync_types::{
address_to_h256,
bytecode::BytecodeHash,
bytecode::{pad_evm_bytecode, BytecodeHash},
contract_verification_api::{CompilerVersions, SourceCodeData, VerificationIncomingRequest},
get_code_key, get_known_code_key,
l2::L2Tx,
Expand Down Expand Up @@ -114,28 +111,6 @@ impl TestContract {
}
}

/// Pads an EVM bytecode in the same ways it's done by system contracts.
fn pad_evm_bytecode(deployed_bytecode: &[u8]) -> Vec<u8> {
let mut padded = Vec::with_capacity(deployed_bytecode.len() + 32);
let len = U256::from(deployed_bytecode.len());
padded.extend_from_slice(&[0; 32]);
len.to_big_endian(&mut padded);
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
}

async fn mock_deployment(
storage: &mut Connection<'_, Core>,
address: Address,
Expand Down Expand Up @@ -163,7 +138,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;
}

Expand Down
22 changes: 16 additions & 6 deletions core/lib/multivm/src/versions/testonly/evm_emulator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -131,7 +132,8 @@ pub(crate) fn test_tracing_evm_contract_deployment<VM: TestedVm>() {

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
Expand All @@ -148,7 +150,8 @@ pub(crate) fn test_tracing_evm_contract_deployment<VM: TestedVm>() {
// "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
Expand Down Expand Up @@ -324,7 +327,8 @@ pub(crate) fn test_mock_emulator_with_deployment<VM: TestedVm>(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(
Expand Down Expand Up @@ -402,7 +406,10 @@ pub(crate) fn test_mock_emulator_with_recursive_deployment<VM: TestedVm>() {
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
Expand Down Expand Up @@ -448,7 +455,10 @@ fn test_mock_emulator_with_partial_reverts_and_rng<VM: TestedVm>(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::<bool>()).collect();
Expand Down
29 changes: 22 additions & 7 deletions core/lib/multivm/src/versions/vm_fast/tracers/evm_deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -61,13 +63,26 @@ impl EvmDeployTracer {
return;
}

match ethabi::decode(&[ethabi::ParamType::Bytes], data) {
match ethabi::decode(
&[ethabi::ParamType::Uint(256), 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}"),
}
Expand Down
Loading
Loading