Skip to content

Commit

Permalink
Adapt server for new contracts version
Browse files Browse the repository at this point in the history
  • Loading branch information
0xVolosnikov committed Dec 18, 2024
1 parent 3cffdb2 commit 1562233
Show file tree
Hide file tree
Showing 10 changed files with 138 additions and 55 deletions.
73 changes: 54 additions & 19 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,30 +169,48 @@ 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;
Expand Down Expand Up @@ -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);
}
}
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
2 changes: 1 addition & 1 deletion core/lib/contract_verifier/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

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
24 changes: 18 additions & 6 deletions core/lib/multivm/src/versions/vm_fast/evm_deploy_tracer.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 @@ -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}"),
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,16 @@ use crate::{
#[derive(Debug)]
pub(crate) struct EvmDeployTracer<S> {
tracked_signature: [u8; 4],
pending_bytecodes: Vec<Vec<u8>>,
pending_bytecodes: Vec<(usize, Vec<u8>)>,
_phantom: PhantomData<S>,
}

impl<S> EvmDeployTracer<S> {
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,
Expand Down Expand Up @@ -77,10 +79,23 @@ impl<S, H: HistoryMode> DynTracer<S, SimpleMemory<H>> for EvmDeployTracer<S> {
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}"),
}
Expand All @@ -94,8 +109,9 @@ impl<S: WriteStorage, H: HistoryMode> VmTracer<S, H> for EvmDeployTracer<S> {
_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
Expand Down
24 changes: 15 additions & 9 deletions core/node/api_server/src/web3/namespaces/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
};
Expand Down
2 changes: 1 addition & 1 deletion core/node/api_server/src/web3/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion core/node/contract_verification_server/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion core/node/state_keeper/src/io/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
})
Expand Down

0 comments on commit 1562233

Please sign in to comment.