Skip to content

Commit

Permalink
Change approach, skip empty code error when dealing with empty callda…
Browse files Browse the repository at this point in the history
…ta with value
  • Loading branch information
Jrigada committed Jan 8, 2025
1 parent 81cb004 commit 85dc278
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 7 deletions.
54 changes: 54 additions & 0 deletions crates/forge/tests/cli/zk_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,57 @@ contract CallEmptyCode is Test {
.stdout_lossy()
.contains("call may fail or behave unexpectedly due to empty code");
});

// Sending ETH to an EOA doesn't trigger empty code warning
forgetest_async!(test_zk_can_send_eth_to_eoa, |prj, cmd| {
foundry_test_utils::util::initialize(prj.root());
prj.add_test(
"SendEthToEOA.t.sol",
r#"
import "forge-std/Test.sol";
contract SendEthToEOA is Test {
function testSendEthToEOA() external {
address eoa = makeAddr("Juan's Account");
vm.deal(address(this), 1 ether);
(bool success,) = eoa.call{value: 1 ether}("");
assertTrue(success, "ETH transfer failed");
}
}
"#,
)
.unwrap();

cmd.args(["test", "--zksync", "--match-test", "testSendEthToEOA"]);
let output = cmd.assert_success().get_output().stdout_lossy();

assert!(!output.contains("call may fail or behave unexpectedly due to empty code"));
});

//Calling an address with Zero value should trigger empty code warning
forgetest_async!(test_zk_can_call_empty_code_with_zero_value, |prj, cmd| {
foundry_test_utils::util::initialize(prj.root());
prj.add_test(
"CallEmptyCodeWithZeroValue.t.sol",
r#"
import "forge-std/Test.sol";
contract CallEmptyCodeWithZeroValue is Test {
function testCallEmptyCodeWithZeroValue() external {
address eoa = makeAddr("Juan's Account");
vm.deal(address(this), 1 ether);
(bool success,) = eoa.call("");
assertTrue(success, "call failed");
}
}
"#,
)
.unwrap();

cmd.args(["test", "--zksync", "--match-test", "testCallEmptyCodeWithZeroValue"]);
let output = cmd.assert_success().get_output().stdout_lossy();

assert!(output.contains("call may fail or behave unexpectedly due to empty code"));
});
28 changes: 21 additions & 7 deletions crates/zksync/core/src/vm/tracers/cheatcode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,11 +164,22 @@ impl CheatcodeTracer {
}

/// Check if the given address's code is empty
fn has_empty_code<S: ReadStorage>(&self, storage: StoragePtr<S>, target: Address) -> bool {
fn has_empty_code<S: ReadStorage>(
&self,
storage: StoragePtr<S>,
target: Address,
calldata: &[u8],
value: rU256,
) -> bool {
// The following addresses are expected to have empty bytecode
let ignored_known_addresses =
[foundry_evm_abi::HARDHAT_CONSOLE_ADDRESS, self.call_context.tx_caller];

// Skip empty code check for empty calldata with non-zero value (Transfers)
if calldata.is_empty() && !value.is_zero() {
return false;
}

let contract_code = storage.borrow_mut().read_value(&get_code_key(&target.to_h160()));

!ignored_known_addresses.contains(&target) &&
Expand Down Expand Up @@ -239,12 +250,13 @@ impl<S: ReadStorage, H: HistoryMode> DynTracer<S, SimpleMemory<H>> for Cheatcode
let call_contract = current.code_address.to_address();
let call_value = U256::from(current.context_u128_value).to_ru256();

let mut had_mocks = false;
if let Some(mocks) = self.mocked_calls.get_mut(&call_contract) {
had_mocks = true;
let ctx = MockCallDataContext {
calldata: Bytes::from(call_input.clone()),
value: Some(call_value),
};

if let Some(return_data_queue) = match mocks.get_mut(&ctx) {
Some(queue) => Some(queue),
None => mocks
Expand Down Expand Up @@ -276,15 +288,17 @@ impl<S: ReadStorage, H: HistoryMode> DynTracer<S, SimpleMemory<H>> for Cheatcode

// if we get here there was no matching mock call,
// so we check if there's no code at the mocked address
if self.has_empty_code(storage, call_contract) {
let has_mocks = self.mocked_calls.contains_key(&call_contract);
if self.has_empty_code(storage, call_contract, &call_input, call_value) {
// issue a more targeted
// error if we already had some mocks there
let had_mocks_message =
if had_mocks { " - please ensure the current calldata is mocked" } else { "" };

tracing::error!(
target = ?call_contract,
calldata = hex::encode(&call_input),
"call may fail or behave unexpectedly due to empty code{}",
// issue a more targeted
// error if we already had some mocks there
if has_mocks { " - please ensure the current calldata is mocked" } else { "" }
had_mocks_message
);
}
}
Expand Down

0 comments on commit 85dc278

Please sign in to comment.