Skip to content

Commit df2125e

Browse files
vbarMexicanAce
andauthored
fix: hardhat_setCode error handling (#293)
* checking hardhat_setCode arg len; added test * replaced zksync-era functions implementing hardhat_setCode to not panic * silenced warning * added error output * prettier indentation * reformatted * Update e2e-tests/test/hardhat-apis.test.ts * passig hardhat_setCode error to client * added comment to ree-run integration --------- Co-authored-by: Nicolas Villanueva <nicolasvillanueva@msn.com>
1 parent 9c66ac3 commit df2125e

File tree

7 files changed

+100
-38
lines changed

7 files changed

+100
-38
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ categories = ["cryptography"]
1111
publish = false # We don't want to publish our binaries.
1212

1313
[dependencies]
14+
zkevm_opcode_defs = { git = "https://github.com/matter-labs/era-zkevm_opcode_defs.git", branch = "v1.5.0" }
1415
zksync_basic_types = { git = "https://github.com/matter-labs/zksync-era.git", rev = "e10bbdd1e863962552f37e768ae6af649353e4ea" }
1516
zksync_node_fee_model = { git = "https://github.com/matter-labs/zksync-era.git", rev = "e10bbdd1e863962552f37e768ae6af649353e4ea" }
1617
multivm = { git = "https://github.com/matter-labs/zksync-era.git", rev = "e10bbdd1e863962552f37e768ae6af649353e4ea" }

e2e-tests/test/hardhat-apis.test.ts

+19-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { expect } from "chai";
22
import { Wallet } from "zksync-web3";
3-
import { deployContract, getTestProvider } from "../helpers/utils";
3+
import { deployContract, expectThrowsAsync, getTestProvider } from "../helpers/utils";
44
import { RichAccounts } from "../helpers/constants";
55
import { ethers } from "hardhat";
66
import { Deployer } from "@matterlabs/hardhat-zksync-deploy";
@@ -118,6 +118,24 @@ describe("hardhat_setCode", function () {
118118
expect(BigNumber.from(result).toNumber()).to.eq(5);
119119
});
120120

121+
it("Should reject invalid code", async function () {
122+
const action = async () => {
123+
// Arrange
124+
const wallet = new Wallet(RichAccounts[0].PrivateKey);
125+
const deployer = new Deployer(hre, wallet);
126+
127+
const address = "0x1000000000000000000000000000000000001111";
128+
const artifact = await deployer.loadArtifact("Return5");
129+
const contractCode = [...ethers.utils.arrayify(artifact.deployedBytecode)];
130+
const shortCode = contractCode.slice(0, contractCode.length - 1);
131+
132+
// Act
133+
await provider.send("hardhat_setCode", [address, shortCode]);
134+
};
135+
136+
await expectThrowsAsync(action, "bytes must be divisible by 32");
137+
});
138+
121139
it("Should update code with a different smart contract", async function () {
122140
// Arrange
123141
const wallet = new Wallet(RichAccounts[0].PrivateKey);

src/node/hardhat.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::{
55
fork::ForkSource,
66
namespaces::{HardhatNamespaceT, RpcResult},
77
node::InMemoryNode,
8-
utils::{into_jsrpc_error, IntoBoxedFuture},
8+
utils::{into_jsrpc_error, into_jsrpc_error_message, IntoBoxedFuture},
99
};
1010

1111
impl<S: ForkSource + std::fmt::Debug + Clone + Send + Sync + 'static> HardhatNamespaceT
@@ -60,7 +60,7 @@ impl<S: ForkSource + std::fmt::Debug + Clone + Send + Sync + 'static> HardhatNam
6060
self.set_code(address, code)
6161
.map_err(|err| {
6262
tracing::error!("failed setting code: {:?}", err);
63-
into_jsrpc_error(Web3Error::InternalError(err))
63+
into_jsrpc_error_message(err.to_string())
6464
})
6565
.into_boxed_future()
6666
}

src/node/in_memory.rs

+11-5
Original file line numberDiff line numberDiff line change
@@ -1526,11 +1526,17 @@ impl<S: ForkSource + std::fmt::Debug + Clone> InMemoryNode<S> {
15261526
..Default::default()
15271527
};
15281528

1529-
let bytecodes: HashMap<U256, Vec<U256>> = vm
1530-
.get_last_tx_compressed_bytecodes()
1531-
.iter()
1532-
.map(|b| bytecode_to_factory_dep(b.original.clone()))
1533-
.collect();
1529+
let mut bytecodes = HashMap::new();
1530+
for b in vm.get_last_tx_compressed_bytecodes().iter() {
1531+
let hashcode = match bytecode_to_factory_dep(b.original.clone()) {
1532+
Ok(hc) => hc,
1533+
Err(error) => {
1534+
tracing::error!("{}", format!("cannot convert bytecode: {}", error).on_red());
1535+
return Err(error.to_string());
1536+
}
1537+
};
1538+
bytecodes.insert(hashcode.0, hashcode.1);
1539+
}
15341540
if execute_bootloader {
15351541
vm.execute(VmExecutionMode::Bootloader);
15361542
}

src/node/in_memory_ext.rs

+18-17
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,10 @@ impl<S: ForkSource + std::fmt::Debug + Clone + Send + Sync + 'static> InMemoryNo
9696
self.get_inner()
9797
.write()
9898
.map_err(|err| anyhow!("failed acquiring lock: {:?}", err))
99-
.map(|mut writer| {
100-
utils::mine_empty_blocks(&mut writer, 1, 1000);
99+
.and_then(|mut writer| {
100+
utils::mine_empty_blocks(&mut writer, 1, 1000)?;
101101
tracing::info!("👷 Mined block #{}", writer.current_miniblock);
102-
"0x0".to_string()
102+
Ok("0x0".to_string())
103103
})
104104
}
105105

@@ -260,7 +260,7 @@ impl<S: ForkSource + std::fmt::Debug + Clone + Send + Sync + 'static> InMemoryNo
260260
"Number of blocks must be greater than 0".to_string(),
261261
));
262262
}
263-
utils::mine_empty_blocks(&mut writer, num_blocks.as_u64(), interval_ms.as_u64());
263+
utils::mine_empty_blocks(&mut writer, num_blocks.as_u64(), interval_ms.as_u64())?;
264264
tracing::info!("👷 Mined {} blocks", num_blocks);
265265

266266
Ok(true)
@@ -304,22 +304,23 @@ impl<S: ForkSource + std::fmt::Debug + Clone + Send + Sync + 'static> InMemoryNo
304304
self.get_inner()
305305
.write()
306306
.map_err(|err| anyhow!("failed acquiring lock: {:?}", err))
307-
.map(|mut writer| {
307+
.and_then(|mut writer| {
308308
let code_key = get_code_key(&address);
309309
tracing::info!("set code for address {address:#x}");
310-
let (hash, code) = bytecode_to_factory_dep(code);
311-
let hash = u256_to_h256(hash);
312-
writer.fork_storage.store_factory_dep(
313-
hash,
314-
code.iter()
315-
.flat_map(|entry| {
316-
let mut bytes = vec![0u8; 32];
317-
entry.to_big_endian(&mut bytes);
318-
bytes.to_vec()
319-
})
320-
.collect(),
321-
);
310+
let hashcode = bytecode_to_factory_dep(code)?;
311+
let hash = u256_to_h256(hashcode.0);
312+
let code = hashcode
313+
.1
314+
.iter()
315+
.flat_map(|entry| {
316+
let mut bytes = vec![0u8; 32];
317+
entry.to_big_endian(&mut bytes);
318+
bytes.to_vec()
319+
})
320+
.collect();
321+
writer.fork_storage.store_factory_dep(hash, code);
322322
writer.fork_storage.set_value(code_key, hash);
323+
Ok(())
323324
})
324325
}
325326
}

src/utils.rs

+48-13
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,22 @@ use std::convert::TryInto;
33
use std::fmt;
44
use std::pin::Pin;
55

6+
use anyhow::anyhow;
67
use chrono::{DateTime, Utc};
78
use futures::Future;
89
use jsonrpc_core::{Error, ErrorCode};
910
use multivm::interface::{ExecutionResult, VmExecutionResultAndLogs, VmInterface};
1011
use multivm::vm_latest::HistoryDisabled;
1112
use multivm::vm_latest::Vm;
13+
use zkevm_opcode_defs::utils::bytecode_to_code_hash;
1214
use zksync_basic_types::{H256, U256, U64};
1315
use zksync_state::WriteStorage;
1416
use zksync_types::api::{BlockNumber, DebugCall, DebugCallType};
1517
use zksync_types::l2::L2Tx;
1618
use zksync_types::vm_trace::Call;
1719
use zksync_types::CONTRACT_DEPLOYER_ADDRESS;
20+
use zksync_utils::bytes_to_be_words;
1821
use zksync_utils::u256_to_h256;
19-
use zksync_utils::{bytecode::hash_bytecode, bytes_to_be_words};
2022
use zksync_web3_decl::error::Web3Error;
2123

2224
use crate::deps::storage_view::StorageView;
@@ -55,13 +57,36 @@ pub fn to_human_size(input: U256) -> String {
5557
tmp.iter().rev().collect()
5658
}
5759

58-
pub fn bytecode_to_factory_dep(bytecode: Vec<u8>) -> (U256, Vec<U256>) {
59-
let bytecode_hash = hash_bytecode(&bytecode);
60+
pub fn bytes_to_chunks(bytes: &[u8]) -> Vec<[u8; 32]> {
61+
bytes
62+
.chunks(32)
63+
.map(|el| {
64+
let mut chunk = [0u8; 32];
65+
chunk.copy_from_slice(el);
66+
chunk
67+
})
68+
.collect()
69+
}
70+
71+
pub fn hash_bytecode(code: &[u8]) -> Result<H256, anyhow::Error> {
72+
if code.len() % 32 != 0 {
73+
return Err(anyhow!("bytes must be divisible by 32"));
74+
}
75+
76+
let chunked_code = bytes_to_chunks(code);
77+
match bytecode_to_code_hash(&chunked_code) {
78+
Ok(hash) => Ok(H256(hash)),
79+
Err(_) => Err(anyhow!("invalid bytecode")),
80+
}
81+
}
82+
83+
pub fn bytecode_to_factory_dep(bytecode: Vec<u8>) -> Result<(U256, Vec<U256>), anyhow::Error> {
84+
let bytecode_hash = hash_bytecode(&bytecode)?;
6085
let bytecode_hash = U256::from_big_endian(bytecode_hash.as_bytes());
6186

6287
let bytecode_words = bytes_to_be_words(bytecode);
6388

64-
(bytecode_hash, bytecode_words)
89+
Ok((bytecode_hash, bytecode_words))
6590
}
6691

6792
/// Creates and inserts a given number of empty blocks into the node, with a given interval between them.
@@ -72,7 +97,7 @@ pub fn mine_empty_blocks<S: std::fmt::Debug + ForkSource>(
7297
node: &mut InMemoryNodeInner<S>,
7398
num_blocks: u64,
7499
interval_ms: u64,
75-
) {
100+
) -> Result<(), anyhow::Error> {
76101
// build and insert new blocks
77102
for i in 0..num_blocks {
78103
// roll the vm
@@ -98,11 +123,11 @@ pub fn mine_empty_blocks<S: std::fmt::Debug + ForkSource>(
98123

99124
vm.execute(multivm::interface::VmExecutionMode::Bootloader);
100125

101-
let bytecodes: HashMap<U256, Vec<U256>> = vm
102-
.get_last_tx_compressed_bytecodes()
103-
.iter()
104-
.map(|b| bytecode_to_factory_dep(b.original.clone()))
105-
.collect();
126+
let mut bytecodes = HashMap::new();
127+
for b in vm.get_last_tx_compressed_bytecodes().iter() {
128+
let hashcode = bytecode_to_factory_dep(b.original.clone())?;
129+
bytecodes.insert(hashcode.0, hashcode.1);
130+
}
106131
let modified_keys = storage.borrow().modified_storage_keys().clone();
107132
(modified_keys, bytecodes, block_ctx)
108133
};
@@ -140,6 +165,8 @@ pub fn mine_empty_blocks<S: std::fmt::Debug + ForkSource>(
140165
node.current_miniblock = block_ctx.miniblock;
141166
node.current_timestamp = block_ctx.timestamp;
142167
}
168+
169+
Ok(())
143170
}
144171

145172
/// Returns the actual [U64] block number from [BlockNumber].
@@ -265,6 +292,14 @@ pub fn into_jsrpc_error(err: Web3Error) -> Error {
265292
}
266293
}
267294

295+
pub fn into_jsrpc_error_message(msg: String) -> Error {
296+
Error {
297+
code: ErrorCode::InternalError,
298+
message: msg,
299+
data: None,
300+
}
301+
}
302+
268303
pub fn internal_error(method_name: &'static str, error: impl fmt::Display) -> Web3Error {
269304
tracing::error!("Internal error in method {method_name}: {error}");
270305
Web3Error::InternalError(anyhow::Error::msg(error.to_string()))
@@ -362,7 +397,7 @@ mod tests {
362397

363398
{
364399
let mut writer = inner.write().expect("failed acquiring write lock");
365-
mine_empty_blocks(&mut writer, 1, 1000);
400+
mine_empty_blocks(&mut writer, 1, 1000).unwrap();
366401
}
367402

368403
let reader = inner.read().expect("failed acquiring reader");
@@ -396,7 +431,7 @@ mod tests {
396431

397432
{
398433
let mut writer = inner.write().expect("failed acquiring write lock");
399-
mine_empty_blocks(&mut writer, 2, 1000);
434+
mine_empty_blocks(&mut writer, 2, 1000).unwrap();
400435
}
401436

402437
let reader = inner.read().expect("failed acquiring reader");
@@ -439,7 +474,7 @@ mod tests {
439474

440475
{
441476
let mut writer = inner.write().expect("failed acquiring write lock");
442-
mine_empty_blocks(&mut writer, 2, 1000);
477+
mine_empty_blocks(&mut writer, 2, 1000).unwrap();
443478
}
444479

445480
{

0 commit comments

Comments
 (0)