From cba3f00f7fafc74485fb0e50f85fe97459342312 Mon Sep 17 00:00:00 2001 From: Francesco Dainese Date: Mon, 16 Sep 2024 19:24:54 +0200 Subject: [PATCH 01/14] test(zk): add #565 repro --- crates/forge/tests/it/zk/repros.rs | 12 +++- testdata/zk/repros/Issue565.t.sol | 105 +++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 testdata/zk/repros/Issue565.t.sol diff --git a/crates/forge/tests/it/zk/repros.rs b/crates/forge/tests/it/zk/repros.rs index 23c9b6ee6..7af3a8c6c 100644 --- a/crates/forge/tests/it/zk/repros.rs +++ b/crates/forge/tests/it/zk/repros.rs @@ -5,7 +5,7 @@ use crate::{ config::*, repros::test_repro, - test_helpers::{ForgeTestData, TEST_DATA_DEFAULT}, + test_helpers::{ForgeTestData, ForgeTestProfile, TEST_DATA_DEFAULT}, }; use alloy_primitives::Address; use foundry_config::{fs_permissions::PathPermission, FsPermissions}; @@ -36,3 +36,13 @@ async fn repro_config( // https://github.com/matter-labs/foundry-zksync/issues/497 test_repro!(497); + +#[tokio::test(flavor = "multi_thread")] +async fn issue_565(){ + let mut test_data = ForgeTestData::new(ForgeTestProfile::Default); + // FIXME: just use the inline config + test_data.test_opts.invariant.no_zksync_reserved_addresses = true; + test_data.test_opts.invariant.fail_on_revert = true; + + repro_config(565,false,None.into(), &test_data).await.run().await; +} diff --git a/testdata/zk/repros/Issue565.t.sol b/testdata/zk/repros/Issue565.t.sol new file mode 100644 index 000000000..5af2d529d --- /dev/null +++ b/testdata/zk/repros/Issue565.t.sol @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; +import {Globals} from "../Globals.sol"; + +import "../../default/logs/console.sol"; + +contract Counter { + uint256 public number; + + function inc() public { + number += 1; + } + + function reset() public { + number = 0; + } +} + +contract CounterHandler is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + uint256 public incCounter; + uint256 public resetCounter; + Counter public counter; + + constructor(Counter _counter) { + counter = _counter; + } + + function inc() public { + console.log("inc"); + incCounter += 1; + + vm.deal(tx.origin, 1 ether); // ensure caller has funds + vm.zkVm(true); + counter.inc(); + } + + function reset() public { + console.log("reset"); + resetCounter += 1; + + vm.deal(tx.origin, 1 ether); // ensure caller has funds + vm.zkVm(true); + counter.reset(); + } +} + +// partial from forge-std/StdInvariant.sol +abstract contract StdInvariant { + struct FuzzSelector { + address addr; + bytes4[] selectors; + } + + address[] internal _targetedContracts; + function targetContracts() public view returns (address[] memory) { + return _targetedContracts; + } + + FuzzSelector[] internal _targetedSelectors; + function targetSelectors() public view returns (FuzzSelector[] memory) { + return _targetedSelectors; + } +} + +// https://github.com/matter-labs/foundry-zksync/issues/565 +contract Issue565 is DSTest, StdInvariant { + Vm constant vm = Vm(HEVM_ADDRESS); + Counter cnt; + CounterHandler handler; + + function setUp() public { + cnt = new Counter(); + + vm.zkVm(false); + handler = new CounterHandler(cnt); + + // add the handler selectors to the fuzzing targets + bytes4[] memory selectors = new bytes4[](2); + selectors[0] = CounterHandler.inc.selector; + selectors[1] = CounterHandler.reset.selector; + + _targetedContracts.push(address(handler)); + _targetedSelectors.push(FuzzSelector({addr: address(handler), selectors: selectors})); + } + + //FIXME: seems to not be detected, forcing values in test config + /// forge-config: default.invariant.fail-on-revert = true + /// forge-config: default.invariant.no-zksync-reserved-addresses = true + function invariant_ghostVariables() external { + vm.zkVm(true); + uint256 num = cnt.number(); + + vm.zkVm(false); + if (handler.resetCounter() == 0) { + assert(handler.incCounter() == num); + } else { + assert(handler.incCounter() != 0); + } + } +} From 2160d4e007b3629e95eeb350647d3046f699712f Mon Sep 17 00:00:00 2001 From: Francesco Dainese Date: Tue, 17 Sep 2024 17:35:07 +0200 Subject: [PATCH 02/14] test(zk:565): fix invariant and use zkVmSkip --- crates/forge/tests/it/zk/repros.rs | 1 + testdata/zk/repros/Issue565.t.sol | 11 +++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/crates/forge/tests/it/zk/repros.rs b/crates/forge/tests/it/zk/repros.rs index 7af3a8c6c..09919ab48 100644 --- a/crates/forge/tests/it/zk/repros.rs +++ b/crates/forge/tests/it/zk/repros.rs @@ -43,6 +43,7 @@ async fn issue_565(){ // FIXME: just use the inline config test_data.test_opts.invariant.no_zksync_reserved_addresses = true; test_data.test_opts.invariant.fail_on_revert = true; + test_data.test_opts.invariant.runs = 2; repro_config(565,false,None.into(), &test_data).await.run().await; } diff --git a/testdata/zk/repros/Issue565.t.sol b/testdata/zk/repros/Issue565.t.sol index 5af2d529d..0e01c1ece 100644 --- a/testdata/zk/repros/Issue565.t.sol +++ b/testdata/zk/repros/Issue565.t.sol @@ -24,6 +24,7 @@ contract CounterHandler is DSTest { uint256 public incCounter; uint256 public resetCounter; + bool public isResetLast; Counter public counter; constructor(Counter _counter) { @@ -33,6 +34,7 @@ contract CounterHandler is DSTest { function inc() public { console.log("inc"); incCounter += 1; + isResetLast = false; vm.deal(tx.origin, 1 ether); // ensure caller has funds vm.zkVm(true); @@ -42,6 +44,7 @@ contract CounterHandler is DSTest { function reset() public { console.log("reset"); resetCounter += 1; + isResetLast = true; vm.deal(tx.origin, 1 ether); // ensure caller has funds vm.zkVm(true); @@ -76,7 +79,7 @@ contract Issue565 is DSTest, StdInvariant { function setUp() public { cnt = new Counter(); - vm.zkVm(false); + vm.zkVmSkip(); handler = new CounterHandler(cnt); // add the handler selectors to the fuzzing targets @@ -92,14 +95,14 @@ contract Issue565 is DSTest, StdInvariant { /// forge-config: default.invariant.fail-on-revert = true /// forge-config: default.invariant.no-zksync-reserved-addresses = true function invariant_ghostVariables() external { - vm.zkVm(true); uint256 num = cnt.number(); - vm.zkVm(false); if (handler.resetCounter() == 0) { assert(handler.incCounter() == num); + } else if (handler.isResetLast()){ + assert(num == 0); } else { - assert(handler.incCounter() != 0); + assert(num != 0); } } } From 6dd3697446aa18e59ff653d5f2b6adf31de98793 Mon Sep 17 00:00:00 2001 From: Francesco Dainese Date: Tue, 17 Sep 2024 20:46:34 +0200 Subject: [PATCH 03/14] fix(zk): use constant test addr for zkvm skip test --- crates/cheatcodes/src/inspector.rs | 12 +++++++----- crates/forge/tests/it/zk/logs.rs | 3 ++- crates/zksync/core/src/lib.rs | 6 +++++- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index bbfe7cfd6..cdda6a964 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -35,6 +35,7 @@ use foundry_zksync_compiler::{DualCompiledContract, DualCompiledContracts}; use foundry_zksync_core::{ convert::{ConvertH160, ConvertH256, ConvertRU256, ConvertU256}, get_account_code_key, get_balance_key, get_nonce_key, Call, ZkTransactionMetadata, + TEST_CONTRACT_ADDRESS_ZKSYNC, }; use itertools::Itertools; use revm::{ @@ -1496,11 +1497,12 @@ impl Cheatcodes { return None; } - if let TransactTo::Call(test_contract) = ecx.env.tx.transact_to { - if call.bytecode_address == test_contract { - info!("running call in EVM, instead of zkEVM (Test Contract) {:#?}", ecx.env.tx); - return None - } + if call.bytecode_address == TEST_CONTRACT_ADDRESS_ZKSYNC { + info!( + "running call in EVM, instead of zkEVM (Test Contract) {:#?}", + call.bytecode_address + ); + return None } info!("running call in zkEVM {:#?}", call); diff --git a/crates/forge/tests/it/zk/logs.rs b/crates/forge/tests/it/zk/logs.rs index ce0036457..e7d392df7 100644 --- a/crates/forge/tests/it/zk/logs.rs +++ b/crates/forge/tests/it/zk/logs.rs @@ -5,6 +5,7 @@ use std::collections::BTreeMap; use crate::{config::*, test_helpers::TEST_DATA_DEFAULT}; use forge::revm::primitives::SpecId; use foundry_test_utils::Filter; +use foundry_zksync_core::TEST_CONTRACT_ADDRESS_ZKSYNC; #[tokio::test(flavor = "multi_thread")] async fn test_zk_logs_work_in_call() { @@ -23,7 +24,7 @@ async fn test_zk_logs_work_in_call() { Some(vec![ "print".into(), "outer print".into(), - "0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496".into(), + TEST_CONTRACT_ADDRESS_ZKSYNC.to_string(), "print".into(), "0xff".into(), "print".into(), diff --git a/crates/zksync/core/src/lib.rs b/crates/zksync/core/src/lib.rs index 5173be1e0..88dd52aa6 100644 --- a/crates/zksync/core/src/lib.rs +++ b/crates/zksync/core/src/lib.rs @@ -19,7 +19,7 @@ pub mod vm; pub mod state; use alloy_network::{AnyNetwork, TxSigner}; -use alloy_primitives::{Address, Bytes, U256 as rU256}; +use alloy_primitives::{address, Address, Bytes, U256 as rU256}; use alloy_provider::Provider; use alloy_rpc_types::TransactionRequest; use alloy_serde::WithOtherFields; @@ -54,6 +54,10 @@ pub const EMPTY_CODE: [u8; 32] = [0; 32]; /// The minimum possible address that is not reserved in the zkSync space. const MIN_VALID_ADDRESS: u32 = 2u32.pow(16); +/// The default test contract address in zkVM +pub const TEST_CONTRACT_ADDRESS_ZKSYNC: Address = + address!("7fa9385be102ac3eac297483dd6233d62b3e1496"); + /// Returns the balance key for a provided account address. pub fn get_balance_key(address: Address) -> rU256 { storage_key_for_eth_balance(&address.to_h160()).key().to_ru256() From 0ac687a1e018a929f38a9865e34fa5b158779e9f Mon Sep 17 00:00:00 2001 From: Francesco Dainese Date: Tue, 17 Sep 2024 20:47:43 +0200 Subject: [PATCH 04/14] test(zk): proper basic invariant test --- crates/forge/tests/it/zk/invariant.rs | 12 +++++-- testdata/zk/InvariantDeposit.t.sol | 50 +++++++++++++++++++++------ 2 files changed, 48 insertions(+), 14 deletions(-) diff --git a/crates/forge/tests/it/zk/invariant.rs b/crates/forge/tests/it/zk/invariant.rs index b1519b16c..64ab33e66 100644 --- a/crates/forge/tests/it/zk/invariant.rs +++ b/crates/forge/tests/it/zk/invariant.rs @@ -1,13 +1,19 @@ //! Invariant tests -use crate::{config::*, test_helpers::TEST_DATA_DEFAULT}; +use crate::{config::*, test_helpers::{ForgeTestData, ForgeTestProfile}}; use forge::revm::primitives::SpecId; use foundry_test_utils::Filter; #[tokio::test(flavor = "multi_thread")] async fn test_zk_invariant_deposit() { - let runner = TEST_DATA_DEFAULT.runner_zksync(); - let filter = Filter::new("testZkInvariantDeposit", "ZkInvariantTest", ".*"); + let mut test_data = ForgeTestData::new(ForgeTestProfile::Default); + // FIXME: just use the inline config + test_data.test_opts.invariant.no_zksync_reserved_addresses = true; + test_data.test_opts.invariant.fail_on_revert = true; + test_data.test_opts.invariant.runs = 10; + + let runner = test_data.runner_zksync(); + let filter = Filter::new(".*", "ZkInvariantTest", ".*"); TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; } diff --git a/testdata/zk/InvariantDeposit.t.sol b/testdata/zk/InvariantDeposit.t.sol index b5b1da6fc..373a0c3e0 100644 --- a/testdata/zk/InvariantDeposit.t.sol +++ b/testdata/zk/InvariantDeposit.t.sol @@ -5,25 +5,53 @@ import "ds-test/test.sol"; import "../cheats/Vm.sol"; import "./Deposit.sol"; -contract ZkInvariantTest is DSTest { +// partial from forge-std/StdInvariant.sol +abstract contract StdInvariant { + struct FuzzSelector { + address addr; + bytes4[] selectors; + } + + address[] internal _targetedContracts; + function targetContracts() public view returns (address[] memory) { + return _targetedContracts; + } + + FuzzSelector[] internal _targetedSelectors; + function targetSelectors() public view returns (FuzzSelector[] memory) { + return _targetedSelectors; + } + + address[] internal _targetedSenders; + function targetSenders() public view returns (address[] memory) { + return _targetedSenders; + } +} + +contract ZkInvariantTest is DSTest, StdInvariant { Vm constant vm = Vm(HEVM_ADDRESS); - // forge-config: default.invariant.runs = 2 Deposit deposit; + uint constant dealAmount = 1 ether; + function setUp() external { + // to fund for fees + _targetedSenders.push(address(65536 + 1)); + _targetedSenders.push(address(65536 + 12)); + _targetedSenders.push(address(65536 + 123)); + _targetedSenders.push(address(65536 + 1234)); + + for (uint i = 0; i < _targetedSenders.length; i++) { + vm.deal(_targetedSenders[i], dealAmount); // to pay fees + } + deposit = new Deposit(); - vm.deal(address(deposit), 100 ether); + _targetedContracts.push(address(deposit)); } + //FIXME: seems to not be detected, forcing values in test config // forge-config: default.invariant.runs = 2 - function testZkInvariantDeposit() external payable { - deposit.deposit{value: 1 ether}(); - uint256 balanceBefore = deposit.balance(address(this)); - assertEq(balanceBefore, 1 ether); - deposit.withdraw(); - uint256 balanceAfter = deposit.balance(address(this)); - assertGt(balanceBefore, balanceAfter); - } + function invariant_itWorks() external payable {} receive() external payable {} } From 00abdb30569676d2e558d14c64fc62c07984199e Mon Sep 17 00:00:00 2001 From: Francesco Dainese Date: Tue, 17 Sep 2024 20:54:51 +0200 Subject: [PATCH 05/14] test(zk:invariant): add direct invariant test feat: `InZkVm` utility to detect if contract is running in zkVm --- testdata/zk/Globals.sol | 2 ++ testdata/zk/InZkVm.sol | 31 +++++++++++++++++++++++++++++++ testdata/zk/repros/Issue565.t.sol | 31 ++++++++++++++++++++++++++----- 3 files changed, 59 insertions(+), 5 deletions(-) create mode 100644 testdata/zk/InZkVm.sol diff --git a/testdata/zk/Globals.sol b/testdata/zk/Globals.sol index 5f45e65ca..f8eecc1ac 100644 --- a/testdata/zk/Globals.sol +++ b/testdata/zk/Globals.sol @@ -5,4 +5,6 @@ library Globals { string public constant ETHEREUM_MAINNET_URL = "https://eth-mainnet.alchemyapi.io/v2/Lc7oIGYeL_QvInzI0Wiu_pOZZDEKBrdf"; // trufflehog:ignore string public constant ZKSYNC_MAINNET_URL = "mainnet"; + + address public constant SYSTEM_CONTEXT_ADDR = address(0x000000000000000000000000000000000000800B); } diff --git a/testdata/zk/InZkVm.sol b/testdata/zk/InZkVm.sol new file mode 100644 index 000000000..2bbf663b0 --- /dev/null +++ b/testdata/zk/InZkVm.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.7 <0.9.0; + +import {Globals} from "./Globals.sol"; + +// excerpt from system-contracts +interface ISystemContext { + + function chainId() external view returns (uint256); +} + +library InZkVmLib { + function _inZkVm() internal returns (bool) { + (bool success, bytes memory retdata) = Globals.SYSTEM_CONTEXT_ADDR.call(abi.encodeWithSelector(ISystemContext.chainId.selector)); + + return success; + } +} + +abstract contract InZkVm { + modifier inZkVm() { + require(InZkVmLib._inZkVm(), "must be executed in zkVM"); + _; + } +} + +abstract contract DeployOnlyInZkVm is InZkVm { + constructor() { + require(InZkVmLib._inZkVm(), "must be deployed in zkVM"); + } +} diff --git a/testdata/zk/repros/Issue565.t.sol b/testdata/zk/repros/Issue565.t.sol index 0e01c1ece..751850876 100644 --- a/testdata/zk/repros/Issue565.t.sol +++ b/testdata/zk/repros/Issue565.t.sol @@ -4,17 +4,18 @@ pragma solidity ^0.8.18; import "ds-test/test.sol"; import "cheats/Vm.sol"; import {Globals} from "../Globals.sol"; +import {DeployOnlyInZkVm} from "../InZkVm.sol"; import "../../default/logs/console.sol"; -contract Counter { +contract Counter is DeployOnlyInZkVm { uint256 public number; - function inc() public { + function inc() public inZkVm { number += 1; } - function reset() public { + function reset() public inZkVm { number = 0; } } @@ -37,7 +38,6 @@ contract CounterHandler is DSTest { isResetLast = false; vm.deal(tx.origin, 1 ether); // ensure caller has funds - vm.zkVm(true); counter.inc(); } @@ -47,7 +47,6 @@ contract CounterHandler is DSTest { isResetLast = true; vm.deal(tx.origin, 1 ether); // ensure caller has funds - vm.zkVm(true); counter.reset(); } } @@ -106,3 +105,25 @@ contract Issue565 is DSTest, StdInvariant { } } } + +contract Issue565WithoutHandler is DSTest, StdInvariant { + Vm constant vm = Vm(HEVM_ADDRESS); + Counter cnt; + + function setUp() public { + cnt = new Counter(); + + // add the handler selectors to the fuzzing targets + bytes4[] memory selectors = new bytes4[](2); + selectors[0] = Counter.inc.selector; + selectors[1] = Counter.reset.selector; + + _targetedContracts.push(address(cnt)); + _targetedSelectors.push(FuzzSelector({addr: address(cnt), selectors: selectors})); + } + + //FIXME: seems to not be detected, forcing values in test config + /// forge-config: default.invariant.fail-on-revert = true + /// forge-config: default.invariant.no-zksync-reserved-addresses = true + function invariant_itWorks() external {} +} From ad2e0cadc34fb46dd77ffb3582ca5e5d737dddad Mon Sep 17 00:00:00 2001 From: Francesco Dainese Date: Tue, 17 Sep 2024 21:07:54 +0200 Subject: [PATCH 06/14] chore: lints & formatting --- crates/cheatcodes/src/inspector.rs | 2 +- crates/forge/tests/it/zk/invariant.rs | 5 ++- crates/forge/tests/it/zk/repros.rs | 4 +- testdata/zk/InZkVm.sol | 6 +-- testdata/zk/InvariantDeposit.t.sol | 9 ++-- testdata/zk/repros/Issue565.t.sol | 60 ++++++++++++++------------- 6 files changed, 47 insertions(+), 39 deletions(-) diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index cdda6a964..c519b518e 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -45,7 +45,7 @@ use revm::{ }, primitives::{ AccountInfo, BlockEnv, Bytecode, CreateScheme, EVMError, Env, EvmStorageSlot, - ExecutionResult, HashMap as rHashMap, Output, TransactTo, KECCAK_EMPTY, + ExecutionResult, HashMap as rHashMap, Output, KECCAK_EMPTY, }, EvmContext, InnerEvmContext, Inspector, }; diff --git a/crates/forge/tests/it/zk/invariant.rs b/crates/forge/tests/it/zk/invariant.rs index 64ab33e66..405d4c569 100644 --- a/crates/forge/tests/it/zk/invariant.rs +++ b/crates/forge/tests/it/zk/invariant.rs @@ -1,6 +1,9 @@ //! Invariant tests -use crate::{config::*, test_helpers::{ForgeTestData, ForgeTestProfile}}; +use crate::{ + config::*, + test_helpers::{ForgeTestData, ForgeTestProfile}, +}; use forge::revm::primitives::SpecId; use foundry_test_utils::Filter; diff --git a/crates/forge/tests/it/zk/repros.rs b/crates/forge/tests/it/zk/repros.rs index 09919ab48..782fada84 100644 --- a/crates/forge/tests/it/zk/repros.rs +++ b/crates/forge/tests/it/zk/repros.rs @@ -38,12 +38,12 @@ async fn repro_config( test_repro!(497); #[tokio::test(flavor = "multi_thread")] -async fn issue_565(){ +async fn issue_565() { let mut test_data = ForgeTestData::new(ForgeTestProfile::Default); // FIXME: just use the inline config test_data.test_opts.invariant.no_zksync_reserved_addresses = true; test_data.test_opts.invariant.fail_on_revert = true; test_data.test_opts.invariant.runs = 2; - repro_config(565,false,None.into(), &test_data).await.run().await; + repro_config(565, false, None, &test_data).await.run().await; } diff --git a/testdata/zk/InZkVm.sol b/testdata/zk/InZkVm.sol index 2bbf663b0..3c825da90 100644 --- a/testdata/zk/InZkVm.sol +++ b/testdata/zk/InZkVm.sol @@ -5,13 +5,13 @@ import {Globals} from "./Globals.sol"; // excerpt from system-contracts interface ISystemContext { - - function chainId() external view returns (uint256); + function chainId() external view returns (uint256); } library InZkVmLib { function _inZkVm() internal returns (bool) { - (bool success, bytes memory retdata) = Globals.SYSTEM_CONTEXT_ADDR.call(abi.encodeWithSelector(ISystemContext.chainId.selector)); + (bool success, bytes memory retdata) = + Globals.SYSTEM_CONTEXT_ADDR.call(abi.encodeWithSelector(ISystemContext.chainId.selector)); return success; } diff --git a/testdata/zk/InvariantDeposit.t.sol b/testdata/zk/InvariantDeposit.t.sol index 373a0c3e0..e25189935 100644 --- a/testdata/zk/InvariantDeposit.t.sol +++ b/testdata/zk/InvariantDeposit.t.sol @@ -13,16 +13,19 @@ abstract contract StdInvariant { } address[] internal _targetedContracts; + function targetContracts() public view returns (address[] memory) { return _targetedContracts; } FuzzSelector[] internal _targetedSelectors; + function targetSelectors() public view returns (FuzzSelector[] memory) { return _targetedSelectors; } address[] internal _targetedSenders; + function targetSenders() public view returns (address[] memory) { return _targetedSenders; } @@ -32,7 +35,7 @@ contract ZkInvariantTest is DSTest, StdInvariant { Vm constant vm = Vm(HEVM_ADDRESS); Deposit deposit; - uint constant dealAmount = 1 ether; + uint256 constant dealAmount = 1 ether; function setUp() external { // to fund for fees @@ -41,8 +44,8 @@ contract ZkInvariantTest is DSTest, StdInvariant { _targetedSenders.push(address(65536 + 123)); _targetedSenders.push(address(65536 + 1234)); - for (uint i = 0; i < _targetedSenders.length; i++) { - vm.deal(_targetedSenders[i], dealAmount); // to pay fees + for (uint256 i = 0; i < _targetedSenders.length; i++) { + vm.deal(_targetedSenders[i], dealAmount); // to pay fees } deposit = new Deposit(); diff --git a/testdata/zk/repros/Issue565.t.sol b/testdata/zk/repros/Issue565.t.sol index 751850876..8955b7e6b 100644 --- a/testdata/zk/repros/Issue565.t.sol +++ b/testdata/zk/repros/Issue565.t.sol @@ -21,34 +21,34 @@ contract Counter is DeployOnlyInZkVm { } contract CounterHandler is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - - uint256 public incCounter; - uint256 public resetCounter; - bool public isResetLast; - Counter public counter; - - constructor(Counter _counter) { - counter = _counter; - } - - function inc() public { - console.log("inc"); - incCounter += 1; - isResetLast = false; - - vm.deal(tx.origin, 1 ether); // ensure caller has funds - counter.inc(); - } - - function reset() public { - console.log("reset"); - resetCounter += 1; - isResetLast = true; - - vm.deal(tx.origin, 1 ether); // ensure caller has funds - counter.reset(); - } + Vm constant vm = Vm(HEVM_ADDRESS); + + uint256 public incCounter; + uint256 public resetCounter; + bool public isResetLast; + Counter public counter; + + constructor(Counter _counter) { + counter = _counter; + } + + function inc() public { + console.log("inc"); + incCounter += 1; + isResetLast = false; + + vm.deal(tx.origin, 1 ether); // ensure caller has funds + counter.inc(); + } + + function reset() public { + console.log("reset"); + resetCounter += 1; + isResetLast = true; + + vm.deal(tx.origin, 1 ether); // ensure caller has funds + counter.reset(); + } } // partial from forge-std/StdInvariant.sol @@ -59,11 +59,13 @@ abstract contract StdInvariant { } address[] internal _targetedContracts; + function targetContracts() public view returns (address[] memory) { return _targetedContracts; } FuzzSelector[] internal _targetedSelectors; + function targetSelectors() public view returns (FuzzSelector[] memory) { return _targetedSelectors; } @@ -98,7 +100,7 @@ contract Issue565 is DSTest, StdInvariant { if (handler.resetCounter() == 0) { assert(handler.incCounter() == num); - } else if (handler.isResetLast()){ + } else if (handler.isResetLast()) { assert(num == 0); } else { assert(num != 0); From dafd77974bb13f0466066024b840ce770f469350 Mon Sep 17 00:00:00 2001 From: Francesco Dainese Date: Wed, 18 Sep 2024 16:36:16 +0200 Subject: [PATCH 07/14] refactor(test:zk): edit runner test opts fix(test:zk): pre-select target senders for issue565 w/o handler --- crates/forge/tests/it/zk/invariant.rs | 12 ++++++------ crates/forge/tests/it/zk/repros.rs | 16 ++++++---------- testdata/zk/repros/Issue565.t.sol | 18 ++++++++++++++++++ 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/crates/forge/tests/it/zk/invariant.rs b/crates/forge/tests/it/zk/invariant.rs index 405d4c569..978a7348f 100644 --- a/crates/forge/tests/it/zk/invariant.rs +++ b/crates/forge/tests/it/zk/invariant.rs @@ -2,20 +2,20 @@ use crate::{ config::*, - test_helpers::{ForgeTestData, ForgeTestProfile}, + test_helpers::{TEST_DATA_DEFAULT}, }; use forge::revm::primitives::SpecId; use foundry_test_utils::Filter; #[tokio::test(flavor = "multi_thread")] async fn test_zk_invariant_deposit() { - let mut test_data = ForgeTestData::new(ForgeTestProfile::Default); + let mut runner = TEST_DATA_DEFAULT.runner_zksync(); + // FIXME: just use the inline config - test_data.test_opts.invariant.no_zksync_reserved_addresses = true; - test_data.test_opts.invariant.fail_on_revert = true; - test_data.test_opts.invariant.runs = 10; + runner.test_options.invariant.no_zksync_reserved_addresses = true; + runner.test_options.invariant.fail_on_revert = true; + runner.test_options.invariant.runs = 10; - let runner = test_data.runner_zksync(); let filter = Filter::new(".*", "ZkInvariantTest", ".*"); TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; diff --git a/crates/forge/tests/it/zk/repros.rs b/crates/forge/tests/it/zk/repros.rs index 782fada84..febc28cbd 100644 --- a/crates/forge/tests/it/zk/repros.rs +++ b/crates/forge/tests/it/zk/repros.rs @@ -5,7 +5,7 @@ use crate::{ config::*, repros::test_repro, - test_helpers::{ForgeTestData, ForgeTestProfile, TEST_DATA_DEFAULT}, + test_helpers::{ForgeTestData, TEST_DATA_DEFAULT}, }; use alloy_primitives::Address; use foundry_config::{fs_permissions::PathPermission, FsPermissions}; @@ -37,13 +37,9 @@ async fn repro_config( // https://github.com/matter-labs/foundry-zksync/issues/497 test_repro!(497); -#[tokio::test(flavor = "multi_thread")] -async fn issue_565() { - let mut test_data = ForgeTestData::new(ForgeTestProfile::Default); +test_repro!(565; |cfg| { // FIXME: just use the inline config - test_data.test_opts.invariant.no_zksync_reserved_addresses = true; - test_data.test_opts.invariant.fail_on_revert = true; - test_data.test_opts.invariant.runs = 2; - - repro_config(565, false, None, &test_data).await.run().await; -} + cfg.runner.test_options.invariant.no_zksync_reserved_addresses = true; + cfg.runner.test_options.invariant.fail_on_revert = true; + cfg.runner.test_options.invariant.runs = 2; +}); diff --git a/testdata/zk/repros/Issue565.t.sol b/testdata/zk/repros/Issue565.t.sol index 8955b7e6b..bda54fbd2 100644 --- a/testdata/zk/repros/Issue565.t.sol +++ b/testdata/zk/repros/Issue565.t.sol @@ -69,6 +69,12 @@ abstract contract StdInvariant { function targetSelectors() public view returns (FuzzSelector[] memory) { return _targetedSelectors; } + + address[] internal _targetedSenders; + + function targetSenders() public view returns (address[] memory) { + return _targetedSenders; + } } // https://github.com/matter-labs/foundry-zksync/issues/565 @@ -112,9 +118,21 @@ contract Issue565WithoutHandler is DSTest, StdInvariant { Vm constant vm = Vm(HEVM_ADDRESS); Counter cnt; + uint256 constant dealAmount = 1 ether; + function setUp() public { cnt = new Counter(); + // so we can fund them ahead of time for fees + _targetedSenders.push(address(65536 + 1)); + _targetedSenders.push(address(65536 + 12)); + _targetedSenders.push(address(65536 + 123)); + _targetedSenders.push(address(65536 + 1234)); + + for (uint256 i = 0; i < _targetedSenders.length; i++) { + vm.deal(_targetedSenders[i], dealAmount); + } + // add the handler selectors to the fuzzing targets bytes4[] memory selectors = new bytes4[](2); selectors[0] = Counter.inc.selector; From e2f45bb035fc78be2cbea24f7520bf6da133537b Mon Sep 17 00:00:00 2001 From: Francesco Dainese Date: Wed, 18 Sep 2024 21:00:44 +0200 Subject: [PATCH 08/14] fix(zk): use `get_test_contract_address` --- crates/cheatcodes/src/inspector.rs | 6 ++++-- crates/forge/tests/it/zk/invariant.rs | 5 +---- crates/zksync/core/src/lib.rs | 4 ---- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index fa98b6f36..7c0bef78e 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -38,7 +38,7 @@ use foundry_zksync_compiler::{DualCompiledContract, DualCompiledContracts}; use foundry_zksync_core::{ convert::{ConvertH160, ConvertH256, ConvertRU256, ConvertU256}, get_account_code_key, get_balance_key, get_nonce_key, Call, ZkTransactionMetadata, - DEFAULT_CREATE2_DEPLOYER_ZKSYNC, TEST_CONTRACT_ADDRESS_ZKSYNC, + DEFAULT_CREATE2_DEPLOYER_ZKSYNC, }; use itertools::Itertools; use revm::{ @@ -1530,7 +1530,9 @@ impl Cheatcodes { return None; } - if call.bytecode_address == TEST_CONTRACT_ADDRESS_ZKSYNC { + if let Some(true) = + ecx.db.get_test_contract_address().map(|addr| call.bytecode_address == addr) + { info!( "running call in EVM, instead of zkEVM (Test Contract) {:#?}", call.bytecode_address diff --git a/crates/forge/tests/it/zk/invariant.rs b/crates/forge/tests/it/zk/invariant.rs index 978a7348f..eb7bb23db 100644 --- a/crates/forge/tests/it/zk/invariant.rs +++ b/crates/forge/tests/it/zk/invariant.rs @@ -1,9 +1,6 @@ //! Invariant tests -use crate::{ - config::*, - test_helpers::{TEST_DATA_DEFAULT}, -}; +use crate::{config::*, test_helpers::TEST_DATA_DEFAULT}; use forge::revm::primitives::SpecId; use foundry_test_utils::Filter; diff --git a/crates/zksync/core/src/lib.rs b/crates/zksync/core/src/lib.rs index 829dae006..b93dc4013 100644 --- a/crates/zksync/core/src/lib.rs +++ b/crates/zksync/core/src/lib.rs @@ -59,10 +59,6 @@ const MIN_VALID_ADDRESS: u32 = 2u32.pow(16); pub const DEFAULT_CREATE2_DEPLOYER_ZKSYNC: Address = address!("0000000000000000000000000000000000010000"); -/// The default test contract address in zkVM -pub const TEST_CONTRACT_ADDRESS_ZKSYNC: Address = - address!("7fa9385be102ac3eac297483dd6233d62b3e1496"); - /// Returns the balance key for a provided account address. pub fn get_balance_key(address: Address) -> rU256 { storage_key_for_eth_balance(&address.to_h160()).key().to_ru256() From b527bc721ca05900b89b81beb06f76ef9d31c220 Mon Sep 17 00:00:00 2001 From: Francesco Dainese Date: Wed, 18 Sep 2024 21:00:53 +0200 Subject: [PATCH 09/14] fix(backend): don't set test contract on each init --- crates/evm/core/src/backend/mod.rs | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/crates/evm/core/src/backend/mod.rs b/crates/evm/core/src/backend/mod.rs index 36f184dc2..70d55f048 100644 --- a/crates/evm/core/src/backend/mod.rs +++ b/crates/evm/core/src/backend/mod.rs @@ -24,7 +24,7 @@ use revm::{ precompile::{PrecompileSpecId, Precompiles}, primitives::{ Account, AccountInfo, Bytecode, Env, EnvWithHandlerCfg, EvmState, EvmStorageSlot, - HashMap as Map, Log, ResultAndState, SpecId, TxKind, KECCAK_EMPTY, + HashMap as Map, Log, ResultAndState, SpecId, KECCAK_EMPTY, }, Database, DatabaseCommit, JournaledState, }; @@ -771,18 +771,6 @@ impl Backend { pub(crate) fn initialize(&mut self, env: &EnvWithHandlerCfg) { self.set_caller(env.tx.caller); self.set_spec_id(env.handler_cfg.spec_id); - - let test_contract = match env.tx.transact_to { - TxKind::Call(to) => to, - TxKind::Create => { - let nonce = self - .basic_ref(env.tx.caller) - .map(|b| b.unwrap_or_default().nonce) - .unwrap_or_default(); - env.tx.caller.create(nonce) - } - }; - self.set_test_contract(test_contract); } /// Returns the `EnvWithHandlerCfg` with the current `spec_id` set. From bb84cb82bcb75c7375a6990f44103710f01e2c24 Mon Sep 17 00:00:00 2001 From: Francesco Dainese Date: Wed, 18 Sep 2024 21:12:42 +0200 Subject: [PATCH 10/14] chore: lints --- crates/forge/tests/it/zk/logs.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/forge/tests/it/zk/logs.rs b/crates/forge/tests/it/zk/logs.rs index e7d392df7..ce0036457 100644 --- a/crates/forge/tests/it/zk/logs.rs +++ b/crates/forge/tests/it/zk/logs.rs @@ -5,7 +5,6 @@ use std::collections::BTreeMap; use crate::{config::*, test_helpers::TEST_DATA_DEFAULT}; use forge::revm::primitives::SpecId; use foundry_test_utils::Filter; -use foundry_zksync_core::TEST_CONTRACT_ADDRESS_ZKSYNC; #[tokio::test(flavor = "multi_thread")] async fn test_zk_logs_work_in_call() { @@ -24,7 +23,7 @@ async fn test_zk_logs_work_in_call() { Some(vec![ "print".into(), "outer print".into(), - TEST_CONTRACT_ADDRESS_ZKSYNC.to_string(), + "0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496".into(), "print".into(), "0xff".into(), "print".into(), From db7193fe49538bf2e7c8a92a7b363042533feb28 Mon Sep 17 00:00:00 2001 From: Francesco Dainese Date: Thu, 19 Sep 2024 16:30:54 +0200 Subject: [PATCH 11/14] fix(executor): set_test_contract even w/o setup --- crates/evm/evm/src/executors/mod.rs | 2 +- crates/forge/src/runner.rs | 1 + crates/script/src/runner.rs | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/evm/evm/src/executors/mod.rs b/crates/evm/evm/src/executors/mod.rs index a75b81639..eb658aed1 100644 --- a/crates/evm/evm/src/executors/mod.rs +++ b/crates/evm/evm/src/executors/mod.rs @@ -328,7 +328,7 @@ impl Executor { trace!(?from, ?to, "setting up contract"); let from = from.unwrap_or(CALLER); - self.backend_mut().set_test_contract(to).set_caller(from); + self.backend_mut().set_caller(from); let calldata = Bytes::from_static(&ITest::setUpCall::SELECTOR); let mut res = self.transact_raw(from, to, calldata, U256::ZERO)?; res = res.into_result(rd)?; diff --git a/crates/forge/src/runner.rs b/crates/forge/src/runner.rs index 55fb9b653..2d27fa29f 100644 --- a/crates/forge/src/runner.rs +++ b/crates/forge/src/runner.rs @@ -118,6 +118,7 @@ impl<'a> ContractRunner<'a> { } let address = self.sender.create(self.executor.get_nonce(self.sender)?); + self.executor.backend_mut().set_test_contract(address); // Set the contracts initial balance before deployment, so it is available during // construction diff --git a/crates/script/src/runner.rs b/crates/script/src/runner.rs index 300f57809..a54c5e549 100644 --- a/crates/script/src/runner.rs +++ b/crates/script/src/runner.rs @@ -129,6 +129,7 @@ impl ScriptRunner { }; let address = CALLER.create(self.executor.get_nonce(CALLER)?); + self.executor.backend_mut().set_test_contract(address); // Set the contracts initial balance before deployment, so it is available during the // construction @@ -147,7 +148,6 @@ impl ScriptRunner { // Optionally call the `setUp` function let (success, gas_used, labeled_addresses, transactions) = if !setup { - self.executor.backend_mut().set_test_contract(address); (true, 0, Default::default(), Some(library_transactions)) } else { match self.executor.setup(Some(self.evm_opts.sender), address, None) { From 999c389ef50d28fddb0f18fe6db6eac810b56e37 Mon Sep 17 00:00:00 2001 From: Francesco Dainese Date: Thu, 19 Sep 2024 16:32:18 +0200 Subject: [PATCH 12/14] test(zk): filter test to specific contract --- crates/forge/tests/cli/test_cmd.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/forge/tests/cli/test_cmd.rs b/crates/forge/tests/cli/test_cmd.rs index 205e89737..c06c431c4 100644 --- a/crates/forge/tests/cli/test_cmd.rs +++ b/crates/forge/tests/cli/test_cmd.rs @@ -1048,7 +1048,7 @@ contract CallEmptyCode is Test { "#, ) .unwrap(); - cmd.args(["test", "--zksync", "--evm-version", "shanghai"]); + cmd.args(["test", "--zksync", "--evm-version", "shanghai", "--mc", "CallEmptyCode"]); let output = cmd.stdout_lossy(); assert!(output.contains("call may fail or behave unexpectedly due to empty code")); From 10c35daad1e336bb1c103e839e9889973f3a4599 Mon Sep 17 00:00:00 2001 From: Karrq Date: Fri, 20 Sep 2024 14:39:28 +0200 Subject: [PATCH 13/14] refactor(zk): simplify test contract check Co-authored-by: Nisheeth Barthwal --- crates/cheatcodes/src/inspector.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 7c0bef78e..e1234d693 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -1530,8 +1530,7 @@ impl Cheatcodes { return None; } - if let Some(true) = - ecx.db.get_test_contract_address().map(|addr| call.bytecode_address == addr) + if ecx.db.get_test_contract_address().map(|addr| call.bytecode_address == addr).unwrap_or_default() { info!( "running call in EVM, instead of zkEVM (Test Contract) {:#?}", From 05f2e2e2155857a7200c63f8f98acd53401e9165 Mon Sep 17 00:00:00 2001 From: Francesco Dainese Date: Fri, 20 Sep 2024 15:53:50 +0200 Subject: [PATCH 14/14] chore: fmt --- crates/cheatcodes/src/inspector.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index e1234d693..974c3c8c0 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -1530,7 +1530,11 @@ impl Cheatcodes { return None; } - if ecx.db.get_test_contract_address().map(|addr| call.bytecode_address == addr).unwrap_or_default() + if ecx + .db + .get_test_contract_address() + .map(|addr| call.bytecode_address == addr) + .unwrap_or_default() { info!( "running call in EVM, instead of zkEVM (Test Contract) {:#?}",