Skip to content
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
16 changes: 16 additions & 0 deletions core/lib/basic_types/src/bytecode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,22 @@ impl BytecodeMarker {
_ => return None,
})
}

/// Performs the best guess as to which kind of bytecode the provided `raw_bytecode` is.
pub fn detect(raw_bytecode: &[u8]) -> Self {
if validate_bytecode(raw_bytecode).is_err() {
// Certainly not an EraVM bytecode
Self::Evm
} else if raw_bytecode.first() == Some(&0) {
// The vast majority of EraVM bytecodes observed in practice start with the 0 byte.
// (This is the upper byte of the second immediate in the first instruction.)
// OTOH, EVM bytecodes don't usually start with the 0 byte (i.e., STOP opcode). In particular,
// using such a bytecode in state overrides is useless.
Self::EraVm
} else {
Self::Evm
}
}
}

/// Removes padding from the bytecode, if necessary.
Expand Down
2 changes: 1 addition & 1 deletion core/lib/multivm/src/versions/testonly/call_tracer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ pub(crate) fn test_evm_deployment_tx<VM: TestedVmWithCallTracer>() {
&TestEvmContract::counter().abi,
&[Token::Uint(3.into())],
);
vm.vm.push_transaction(tx);
vm.vm.push_transaction(tx.into());

let (res, call_traces) = vm.vm.inspect_with_call_tracer();
assert!(!res.result.is_failed(), "{:#?}", res.result);
Expand Down
2 changes: 1 addition & 1 deletion core/lib/multivm/src/versions/testonly/evm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ pub(crate) fn test_evm_deployment_tx<VM: TestedVm>() {
&TestEvmContract::counter().abi,
&[Token::Uint(initial_counter)],
);
vm.vm.push_transaction(tx);
vm.vm.push_transaction(tx.into());

let result = vm.vm.execute(InspectExecutionMode::OneTx);
assert!(!result.result.is_failed(), "{result:#?}");
Expand Down
4 changes: 2 additions & 2 deletions core/lib/test_contracts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ impl Account {
init_bytecode: Vec<u8>,
contract: &ethabi::Contract,
args: &[Token],
) -> Transaction {
) -> L2Tx {
let fee = Self::default_fee();
let input = if let Some(constructor) = contract.constructor() {
constructor
Expand Down Expand Up @@ -136,7 +136,7 @@ impl Account {
let (req, hash) = TransactionRequest::from_bytes_unverified(&raw).unwrap();
let mut tx = L2Tx::from_request(req, usize::MAX, true).unwrap();
tx.set_input(raw, hash);
tx.into()
tx
}

pub fn default_fee() -> Fee {
Expand Down
167 changes: 107 additions & 60 deletions core/lib/types/src/api/state_override.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use std::collections::{hash_map, HashMap};
use std::{
borrow::Cow,
collections::{hash_map, HashMap},
fmt,
};

use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use zksync_basic_types::{bytecode::BytecodeHash, web3::Bytes, H256, U256};
use zksync_basic_types::{bytecode::validate_bytecode, web3::Bytes, H256, U256};

use crate::{
bytecode::{validate_bytecode, InvalidBytecodeError},
Address,
};
use crate::Address;

/// Collection of overridden accounts.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
Expand Down Expand Up @@ -43,55 +44,94 @@ impl IntoIterator for StateOverride {
}
}

/// Serialized bytecode representation.
#[derive(Debug, Clone, PartialEq)]
pub struct Bytecode(Bytes);

impl Bytecode {
pub fn new(bytes: Vec<u8>) -> Result<Self, InvalidBytecodeError> {
validate_bytecode(&bytes)?;
Ok(Self(Bytes(bytes)))
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(PartialEq))]
pub enum BytecodeOverride {
EraVm(Bytes),
Evm(Bytes),
Unspecified(Bytes),
}

/// Returns the canonical hash of this bytecode.
pub fn hash(&self) -> H256 {
BytecodeHash::for_bytecode(&self.0 .0).value()
impl AsRef<[u8]> for BytecodeOverride {
fn as_ref(&self) -> &[u8] {
let bytes = match self {
Self::EraVm(bytes) | Self::Evm(bytes) | Self::Unspecified(bytes) => bytes,
};
&bytes.0
}
}

/// Converts this bytecode into bytes.
pub fn into_bytes(self) -> Vec<u8> {
self.0 .0
}
// We need a separate type (vs using `#[serde(untagged)]` on the `Unspecified` variant) to make error messages more comprehensive.
#[derive(Debug, Serialize, Deserialize)]
enum SerdeBytecodeOverride<'a> {
#[serde(rename = "eravm")]
EraVm(#[serde(deserialize_with = "deserialize_eravm_bytecode")] Cow<'a, Bytes>),
#[serde(rename = "evm")]
Evm(Cow<'a, Bytes>),
}

impl AsRef<[u8]> for Bytecode {
fn as_ref(&self) -> &[u8] {
&self.0 .0
fn deserialize_eravm_bytecode<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<Cow<'static, Bytes>, D::Error> {
let raw_bytecode = Bytes::deserialize(deserializer)?;
if let Err(err) = validate_bytecode(&raw_bytecode.0) {
return Err(de::Error::custom(format!("invalid EraVM bytecode: {err}")));
}
Ok(Cow::Owned(raw_bytecode))
}

impl Serialize for Bytecode {
impl Serialize for BytecodeOverride {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.0.serialize(serializer)
let serde = match self {
Self::Unspecified(bytes) => return bytes.serialize(serializer),
Self::Evm(bytes) => SerdeBytecodeOverride::Evm(Cow::Borrowed(bytes)),
Self::EraVm(bytes) => SerdeBytecodeOverride::EraVm(Cow::Borrowed(bytes)),
};
serde.serialize(serializer)
}
}

impl<'de> Deserialize<'de> for Bytecode {
impl<'de> Deserialize<'de> for BytecodeOverride {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let bytes = Bytes::deserialize(deserializer)?;
validate_bytecode(&bytes.0).map_err(de::Error::custom)?;
Ok(Self(bytes))
// **Important:** This visitor only works for human-readable deserializers (e.g., JSON).
struct BytesOrMapVisitor;

impl<'v> de::Visitor<'v> for BytesOrMapVisitor {
type Value = BytecodeOverride;

fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.write_str("possibly tagged hex string, like \"0x00\" or { \"evm\": \"0xfe\" }")
}

fn visit_str<E: de::Error>(self, value: &str) -> Result<BytecodeOverride, E> {
let deserializer = de::value::StrDeserializer::new(value);
Bytes::deserialize(deserializer).map(BytecodeOverride::Unspecified)
}

fn visit_map<A: de::MapAccess<'v>>(self, data: A) -> Result<Self::Value, A::Error> {
let deserializer = de::value::MapAccessDeserializer::new(data);
Ok(match SerdeBytecodeOverride::deserialize(deserializer)? {
SerdeBytecodeOverride::Evm(bytes) => BytecodeOverride::Evm(bytes.into_owned()),
SerdeBytecodeOverride::EraVm(bytes) => {
BytecodeOverride::EraVm(bytes.into_owned())
}
})
}
}

deserializer.deserialize_any(BytesOrMapVisitor)
}
}

/// Account override for `eth_estimateGas`.
/// Account override for `eth_call`, `eth_estimateGas` and other VM-invoking methods.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[cfg_attr(test, derive(PartialEq))]
#[serde(rename_all = "camelCase")]
pub struct OverrideAccount {
pub balance: Option<U256>,
pub nonce: Option<U256>,
pub code: Option<Bytecode>,
pub code: Option<BytecodeOverride>,
#[serde(flatten, deserialize_with = "state_deserializer")]
pub state: Option<OverrideState>,
}
Expand Down Expand Up @@ -133,34 +173,37 @@ mod tests {
use super::*;

#[test]
fn deserializing_bytecode() {
let bytecode_str = "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
let json = serde_json::Value::String(bytecode_str.to_owned());
let bytecode: Bytecode = serde_json::from_value(json).unwrap();
assert_ne!(bytecode.hash(), H256::zero());
let bytecode = bytecode.into_bytes();
assert_eq!(bytecode.len(), 32);
assert_eq!(bytecode[0], 0x01);
assert_eq!(bytecode[31], 0xef);
}
fn deserializing_bytecode_override() {
let json = serde_json::json!("0x00");
let bytecode: BytecodeOverride = serde_json::from_value(json.clone()).unwrap();
assert_eq!(bytecode, BytecodeOverride::Unspecified(Bytes(vec![0])));
assert_eq!(serde_json::to_value(&bytecode).unwrap(), json);

#[test]
fn deserializing_invalid_bytecode() {
let invalid_bytecodes = [
"1234", // not 0x-prefixed
"0x1234", // length not divisible by 32
"0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\
0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", // even number of words
];
for bytecode_str in invalid_bytecodes {
let json = serde_json::Value::String(bytecode_str.to_owned());
serde_json::from_value::<Bytecode>(json).unwrap_err();
}
let json = serde_json::json!({ "evm": "0xfe" });
let bytecode: BytecodeOverride = serde_json::from_value(json.clone()).unwrap();
assert_eq!(bytecode, BytecodeOverride::Evm(Bytes(vec![0xfe])));
assert_eq!(serde_json::to_value(&bytecode).unwrap(), json);

let json = serde_json::json!({ "eravm": "0xfe" });
let err = serde_json::from_value::<BytecodeOverride>(json)
.unwrap_err()
.to_string();
assert!(
err.contains("invalid EraVM bytecode") && err.contains("not divisible by 32"),
"{err}"
);

let json = serde_json::json!({ "what": "0xfe" });
let err = serde_json::from_value::<BytecodeOverride>(json)
.unwrap_err()
.to_string();
assert!(err.contains("unknown variant"), "{err}");

let long_bytecode = String::from("0x")
+ &"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef".repeat(65_537);
let json = serde_json::Value::String(long_bytecode);
serde_json::from_value::<Bytecode>(json).unwrap_err();
let json = serde_json::json!("what");
let err = serde_json::from_value::<BytecodeOverride>(json)
.unwrap_err()
.to_string();
assert!(err.contains("expected 0x prefix"));
}

#[test]
Expand All @@ -169,14 +212,16 @@ mod tests {
"0x0123456789abcdef0123456789abcdef01234567": {
"balance": "0x123",
"nonce": "0x1",
"code": "0x00",
},
"0x123456789abcdef0123456789abcdef012345678": {
"stateDiff": {
"0x0000000000000000000000000000000000000000000000000000000000000000":
"0x0000000000000000000000000000000000000000000000000000000000000001",
"0x0000000000000000000000000000000000000000000000000000000000000001":
"0x0000000000000000000000000000000000000000000000000000000000000002",
}
},
"code": { "evm": "0xfe" },
}
});

Expand All @@ -192,6 +237,7 @@ mod tests {
OverrideAccount {
balance: Some(0x123.into()),
nonce: Some(1.into()),
code: Some(BytecodeOverride::Unspecified(Bytes(vec![0]))),
..OverrideAccount::default()
}
);
Expand All @@ -207,6 +253,7 @@ mod tests {
(H256::from_low_u64_be(0), H256::from_low_u64_be(1)),
(H256::from_low_u64_be(1), H256::from_low_u64_be(2)),
]))),
code: Some(BytecodeOverride::Evm(Bytes(vec![0xfe]))),
..OverrideAccount::default()
}
);
Expand Down
17 changes: 1 addition & 16 deletions core/node/api_server/src/execution_sandbox/error.rs
Original file line number Diff line number Diff line change
@@ -1,32 +1,17 @@
use thiserror::Error;
use zksync_multivm::interface::{Halt, TxRevertReason};

#[derive(Debug, Error)]
#[derive(Debug)]
pub(crate) enum SandboxExecutionError {
#[error("Account validation failed: {0}")]
AccountValidationFailed(String),
#[error("Failed to charge fee: {0}")]
FailedToChargeFee(String),
#[error("Paymaster validation failed: {0}")]
PaymasterValidationFailed(String),
#[error("Pre-paymaster preparation failed: {0}")]
PrePaymasterPreparationFailed(String),
#[error("From is not an account")]
FromIsNotAnAccount,
#[error("Bootloader failure: {0}")]
BootloaderFailure(String),
#[error("Revert: {0}")]
Revert(String, Vec<u8>),
#[error("Failed to pay for the transaction: {0}")]
FailedToPayForTransaction(String),
#[error("Bootloader-based tx failed")]
InnerTxError,
#[error(
"Virtual machine entered unexpected state. Please contact developers and provide transaction details \
that caused this error. Error description: {0}"
)]
UnexpectedVMBehavior(String),
#[error("Transaction failed block.timestamp assertion")]
FailedBlockTimestampAssertion,
}

Expand Down
2 changes: 1 addition & 1 deletion core/node/api_server/src/execution_sandbox/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ impl SandboxExecutor {
let storage = if let Some(state_override) = state_override {
tokio::task::spawn_blocking(|| apply_state_override(storage, state_override))
.await
.context("applying state override failed")?
.context("applying state override panicked")?
} else {
// Do not spawn a new thread in the most frequent case.
StorageWithOverrides::new(storage)
Expand Down
2 changes: 1 addition & 1 deletion core/node/api_server/src/execution_sandbox/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use zksync_types::{
use zksync_vm_executor::oneshot::{BlockInfo, ResolvedBlockInfo};

use self::vm_metrics::SandboxStage;
pub(super) use self::{
pub(crate) use self::{
error::SandboxExecutionError,
execute::{SandboxAction, SandboxExecutionOutput, SandboxExecutor},
validate::ValidationError,
Expand Down
Loading
Loading