From db130876d698b0c371f45d5368d8b55db9a0712b Mon Sep 17 00:00:00 2001 From: Francesco Dainese Date: Tue, 27 Aug 2024 16:48:35 +0200 Subject: [PATCH 1/5] test(zk): add create2 address check refactor(test:zk): extract `computeCreate2Address` --- crates/forge/tests/it/zk/contracts.rs | 110 ++++++++++++++++++++++++-- testdata/zk/Contracts.t.sol | 46 +---------- testdata/zk/Create2Utils.sol | 20 +++++ testdata/zk/CustomNumber.sol | 14 ++++ 4 files changed, 139 insertions(+), 51 deletions(-) create mode 100644 testdata/zk/Create2Utils.sol create mode 100644 testdata/zk/CustomNumber.sol diff --git a/crates/forge/tests/it/zk/contracts.rs b/crates/forge/tests/it/zk/contracts.rs index 373fab699..c7586bce4 100644 --- a/crates/forge/tests/it/zk/contracts.rs +++ b/crates/forge/tests/it/zk/contracts.rs @@ -3,7 +3,7 @@ use crate::{config::*, test_helpers::TEST_DATA_DEFAULT}; use forge::revm::primitives::SpecId; use foundry_config::fs_permissions::PathPermission; -use foundry_test_utils::Filter; +use foundry_test_utils::{util, Filter}; #[tokio::test(flavor = "multi_thread")] async fn test_zk_contract_can_call_function() { @@ -52,12 +52,110 @@ async fn test_zk_contract_deployment_balance_transfer() { #[tokio::test(flavor = "multi_thread")] async fn test_zk_contract_create2() { - let mut zk_config = TEST_DATA_DEFAULT.zk_test_data.zk_config.clone(); - zk_config.fs_permissions.add(PathPermission::read_write("./zk/zkout/ConstantNumber.sol")); - let runner = TEST_DATA_DEFAULT.runner_with_zksync_config(zk_config); - let filter = Filter::new("testZkContractsCreate2", "ZkContractsTest", ".*"); + let (prj, mut cmd) = util::setup_forge("test_zk_contract_create2_with_deps", foundry_test_utils::foundry_compilers::PathStyle::Dapptools); + util::initialize(prj.root()); + + cmd.args(["install", "matter-labs/era-contracts", "--no-commit", "--shallow"]).ensure_execute_success().expect("able to install dependencies"); + cmd.forge_fuse(); + + let mut config = cmd.config(); + config.fs_permissions.add(PathPermission::read("./zkout")); + prj.write_config(config); + + prj.add_source("Greeter.sol", include_str!("../../../../../testdata/zk/Greeter.sol")).unwrap(); + + prj.add_source("CustomNumber.sol", include_str!("../../../../../testdata/zk/CustomNumber.sol")).unwrap(); + + prj.add_source("Create2Utils.sol", include_str!("../../../../../testdata/zk/Create2Utils.sol")).unwrap(); + + prj.add_test("Create2.t.sol", + r#" +pragma solidity ^0.8.18; +import "forge-std/Test.sol"; +import {L2ContractHelper} from "era-contracts/l2-contracts/contracts/L2ContractHelper.sol"; // =0.8.20 + +import {Greeter} from "../src/Greeter.sol"; +import {CustomNumber} from "../src/CustomNumber.sol"; + +import {Create2Utils} from "../src/Create2Utils.sol"; + +contract Create2Test is Test { + function getBytecodeHash(string memory path) internal returns (bytes32 bytecodeHash) { + string memory artifact = vm.readFile(path); + bytecodeHash = vm.parseJsonBytes32( + artifact, + '.hash' + ); + } + + function testCanDeployViaCreate2() public { + bytes32 bytecodeHash = getBytecodeHash("zkout/Greeter.sol/Greeter.json"); + address sender = address(0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496); + bytes32 salt = "12345"; + bytes32 constructorInputHash = keccak256(abi.encode()); + + address expectedAddress = + Create2Utils.computeCreate2Address(sender, salt, bytes32(bytecodeHash), constructorInputHash); + + // deploy via create2 + address actualAddress = address(new ConstantNumber{salt: salt}()); + + assertEq(actualAddress, expectedAddress); + } + + + function testComputeCreate2WithNoArgs() external { + bytes32 salt = bytes32(0x0); + + bytes32 bytecodeHash = getBytecodeHash("zkout/Greeter.sol/Greeter.json"); + + address computedAddress = Create2Utils.computeCreate2Address( + address(this), + salt, + bytes32(bytecodeHash), + keccak256(abi.encode()) + ); + address expectedAddress = L2ContractHelper.computeCreate2Address( + address(this), + salt, + bytes32(bytecodeHash), + keccak256(abi.encode()) + ); + + address actualAddress = address(new Greeter{salt: salt}()); + assertEq(actualAddress, expectedAddress); + assertEq(computedAddress, expectedAddress); + } + + function testComputeCreate2WithArgs() external { + bytes32 salt = bytes32(0x0); + uint8 value = 42; + + bytes32 bytecodeHash = getBytecodeHash("zkout/CustomNumber.sol/CustomNumber.json"); + + address computedAddress = Create2Utils.computeCreate2Address( + address(this), + salt, + bytecodeHash, + keccak256(abi.encode(value)) + ); + address expectedAddress = L2ContractHelper.computeCreate2Address( + address(this), + salt, + bytecodeHash, + keccak256(abi.encode(value)) + ); + + CustomNumber num = new CustomNumber{salt: salt}(value); + assertEq(address(num), expectedAddress); + assertEq(computedAddress, expectedAddress); + assertEq(num.number(), value); + } +} +"#).unwrap(); - TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; + cmd.args(["test", "--zk-startup", "--mc", "Create2Test"]); + assert!(cmd.stdout_lossy().contains("Suite result: ok")); } #[tokio::test(flavor = "multi_thread")] diff --git a/testdata/zk/Contracts.t.sol b/testdata/zk/Contracts.t.sol index 651d60e8b..0113def5e 100644 --- a/testdata/zk/Contracts.t.sol +++ b/testdata/zk/Contracts.t.sol @@ -6,6 +6,7 @@ import "../cheats/Vm.sol"; import {ConstantNumber} from "./ConstantNumber.sol"; import {Greeter} from "./Greeter.sol"; +import {CustomNumber} from "./CustomNumber.sol"; import {Globals} from "./Globals.sol"; interface ISystemContractDeployer { @@ -65,18 +66,6 @@ contract PayableFixedNumber { } } -contract CustomNumber { - uint8 value; - - constructor(uint8 _value) { - value = _value; - } - - function number() public view returns (uint8) { - return value; - } -} - contract CustomStorage { uint8 num; string str; @@ -209,23 +198,6 @@ contract ZkContractsTest is DSTest { require(customStorage.getNum() == 10, "era inline contract value mismatch (complex args)"); } - function testZkContractsCreate2() public { - vm.selectFork(forkEra); - - string memory artifact = vm.readFile("zk/zkout/ConstantNumber.sol/ConstantNumber.json"); - bytes32 bytecodeHash = vm.parseJsonBytes32(artifact, ".hash"); - address sender = address(0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496); - bytes32 salt = "12345"; - bytes32 constructorInputHash = keccak256(abi.encode()); - address expectedDeployedAddress = - _computeCreate2Address(sender, salt, bytes32(bytecodeHash), constructorInputHash); - - // deploy via create2 - address actualDeployedAddress = address(new ConstantNumber{salt: salt}()); - - assertEq(expectedDeployedAddress, actualDeployedAddress); - } - function testZkContractsCallSystemContract() public { (bool success,) = address(vm).call(abi.encodeWithSignature("zkVm(bool)", true)); require(success, "zkVm() call failed"); @@ -274,20 +246,4 @@ contract ZkContractsTest is DSTest { assert(vm.getNonce(sender) == startingNonce + 3); vm.stopBroadcast(); } - - function _computeCreate2Address( - address sender, - bytes32 salt, - bytes32 creationCodeHash, - bytes32 constructorInputHash - ) private pure returns (address) { - bytes32 zksync_create2_prefix = keccak256("zksyncCreate2"); - bytes32 address_hash = keccak256( - bytes.concat( - zksync_create2_prefix, bytes32(uint256(uint160(sender))), salt, creationCodeHash, constructorInputHash - ) - ); - - return address(uint160(uint256(address_hash))); - } } diff --git a/testdata/zk/Create2Utils.sol b/testdata/zk/Create2Utils.sol new file mode 100644 index 000000000..6d658731e --- /dev/null +++ b/testdata/zk/Create2Utils.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity >=0.8.7 <0.9.0; + +library Create2Utils { + function computeCreate2Address(address sender, bytes32 salt, bytes32 creationCodeHash, bytes32 constructorInputHash) + internal + pure + returns (address) + { + bytes32 zksync_create2_prefix = keccak256("zksyncCreate2"); + bytes32 address_hash = keccak256( + bytes.concat( + zksync_create2_prefix, bytes32(uint256(uint160(sender))), salt, creationCodeHash, constructorInputHash + ) + ); + + return address(uint160(uint256(address_hash))); + } +} diff --git a/testdata/zk/CustomNumber.sol b/testdata/zk/CustomNumber.sol new file mode 100644 index 000000000..0c82bde9b --- /dev/null +++ b/testdata/zk/CustomNumber.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.8.7 <0.9.0; + +contract CustomNumber { + uint8 value; + + constructor(uint8 _value) { + value = _value; + } + + function number() public view returns (uint8) { + return value; + } +} From 388d81b3eaf88b2ca43970f724719be7e1602a22 Mon Sep 17 00:00:00 2001 From: Francesco Dainese Date: Tue, 27 Aug 2024 18:00:38 +0200 Subject: [PATCH 2/5] refactor(test:zk): move Create2Test to file --- crates/forge/tests/fixtures/zk/Create2.t.sol | 64 +++++++++++++++ crates/forge/tests/it/zk/contracts.rs | 86 +------------------- 2 files changed, 65 insertions(+), 85 deletions(-) create mode 100644 crates/forge/tests/fixtures/zk/Create2.t.sol diff --git a/crates/forge/tests/fixtures/zk/Create2.t.sol b/crates/forge/tests/fixtures/zk/Create2.t.sol new file mode 100644 index 000000000..ee7641449 --- /dev/null +++ b/crates/forge/tests/fixtures/zk/Create2.t.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.18; + +import "forge-std/Test.sol"; +import {L2ContractHelper} from "era-contracts/l2-contracts/contracts/L2ContractHelper.sol"; // =0.8.20 + +import {Greeter} from "../src/Greeter.sol"; +import {CustomNumber} from "../src/CustomNumber.sol"; + +import {Create2Utils} from "../src/Create2Utils.sol"; + +contract Create2Test is Test { + function getBytecodeHash(string memory path) internal returns (bytes32 bytecodeHash) { + string memory artifact = vm.readFile(path); + bytecodeHash = vm.parseJsonBytes32(artifact, ".hash"); + } + + function testCanDeployViaCreate2() public { + bytes32 bytecodeHash = getBytecodeHash("zkout/Greeter.sol/Greeter.json"); + address sender = address(0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496); + bytes32 salt = "12345"; + bytes32 constructorInputHash = keccak256(abi.encode()); + + address expectedAddress = + Create2Utils.computeCreate2Address(sender, salt, bytes32(bytecodeHash), constructorInputHash); + + // deploy via create2 + address actualAddress = address(new ConstantNumber{salt: salt}()); + + assertEq(actualAddress, expectedAddress); + } + + function testComputeCreate2WithNoArgs() external { + bytes32 salt = bytes32(0x0); + + bytes32 bytecodeHash = getBytecodeHash("zkout/Greeter.sol/Greeter.json"); + + address computedAddress = + Create2Utils.computeCreate2Address(address(this), salt, bytes32(bytecodeHash), keccak256(abi.encode())); + address expectedAddress = + L2ContractHelper.computeCreate2Address(address(this), salt, bytes32(bytecodeHash), keccak256(abi.encode())); + + address actualAddress = address(new Greeter{salt: salt}()); + assertEq(actualAddress, expectedAddress); + assertEq(computedAddress, expectedAddress); + } + + function testComputeCreate2WithArgs() external { + bytes32 salt = bytes32(0x0); + uint8 value = 42; + + bytes32 bytecodeHash = getBytecodeHash("zkout/CustomNumber.sol/CustomNumber.json"); + + address computedAddress = + Create2Utils.computeCreate2Address(address(this), salt, bytecodeHash, keccak256(abi.encode(value))); + address expectedAddress = + L2ContractHelper.computeCreate2Address(address(this), salt, bytecodeHash, keccak256(abi.encode(value))); + + CustomNumber num = new CustomNumber{salt: salt}(value); + assertEq(address(num), expectedAddress); + assertEq(computedAddress, expectedAddress); + assertEq(num.number(), value); + } +} diff --git a/crates/forge/tests/it/zk/contracts.rs b/crates/forge/tests/it/zk/contracts.rs index c7586bce4..abc0dffe0 100644 --- a/crates/forge/tests/it/zk/contracts.rs +++ b/crates/forge/tests/it/zk/contracts.rs @@ -68,91 +68,7 @@ async fn test_zk_contract_create2() { prj.add_source("Create2Utils.sol", include_str!("../../../../../testdata/zk/Create2Utils.sol")).unwrap(); - prj.add_test("Create2.t.sol", - r#" -pragma solidity ^0.8.18; -import "forge-std/Test.sol"; -import {L2ContractHelper} from "era-contracts/l2-contracts/contracts/L2ContractHelper.sol"; // =0.8.20 - -import {Greeter} from "../src/Greeter.sol"; -import {CustomNumber} from "../src/CustomNumber.sol"; - -import {Create2Utils} from "../src/Create2Utils.sol"; - -contract Create2Test is Test { - function getBytecodeHash(string memory path) internal returns (bytes32 bytecodeHash) { - string memory artifact = vm.readFile(path); - bytecodeHash = vm.parseJsonBytes32( - artifact, - '.hash' - ); - } - - function testCanDeployViaCreate2() public { - bytes32 bytecodeHash = getBytecodeHash("zkout/Greeter.sol/Greeter.json"); - address sender = address(0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496); - bytes32 salt = "12345"; - bytes32 constructorInputHash = keccak256(abi.encode()); - - address expectedAddress = - Create2Utils.computeCreate2Address(sender, salt, bytes32(bytecodeHash), constructorInputHash); - - // deploy via create2 - address actualAddress = address(new ConstantNumber{salt: salt}()); - - assertEq(actualAddress, expectedAddress); - } - - - function testComputeCreate2WithNoArgs() external { - bytes32 salt = bytes32(0x0); - - bytes32 bytecodeHash = getBytecodeHash("zkout/Greeter.sol/Greeter.json"); - - address computedAddress = Create2Utils.computeCreate2Address( - address(this), - salt, - bytes32(bytecodeHash), - keccak256(abi.encode()) - ); - address expectedAddress = L2ContractHelper.computeCreate2Address( - address(this), - salt, - bytes32(bytecodeHash), - keccak256(abi.encode()) - ); - - address actualAddress = address(new Greeter{salt: salt}()); - assertEq(actualAddress, expectedAddress); - assertEq(computedAddress, expectedAddress); - } - - function testComputeCreate2WithArgs() external { - bytes32 salt = bytes32(0x0); - uint8 value = 42; - - bytes32 bytecodeHash = getBytecodeHash("zkout/CustomNumber.sol/CustomNumber.json"); - - address computedAddress = Create2Utils.computeCreate2Address( - address(this), - salt, - bytecodeHash, - keccak256(abi.encode(value)) - ); - address expectedAddress = L2ContractHelper.computeCreate2Address( - address(this), - salt, - bytecodeHash, - keccak256(abi.encode(value)) - ); - - CustomNumber num = new CustomNumber{salt: salt}(value); - assertEq(address(num), expectedAddress); - assertEq(computedAddress, expectedAddress); - assertEq(num.number(), value); - } -} -"#).unwrap(); + prj.add_test("Create2.t.sol", include_str!("../../fixtures/zk/Create2.t.sol")).unwrap(); cmd.args(["test", "--zk-startup", "--mc", "Create2Test"]); assert!(cmd.stdout_lossy().contains("Suite result: ok")); From 840696d38150537729c4f73467b45148063730d6 Mon Sep 17 00:00:00 2001 From: Francesco Dainese Date: Tue, 27 Aug 2024 18:07:08 +0200 Subject: [PATCH 3/5] chore: fmt --- crates/forge/tests/it/zk/contracts.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/crates/forge/tests/it/zk/contracts.rs b/crates/forge/tests/it/zk/contracts.rs index abc0dffe0..9f1ee665e 100644 --- a/crates/forge/tests/it/zk/contracts.rs +++ b/crates/forge/tests/it/zk/contracts.rs @@ -52,10 +52,15 @@ async fn test_zk_contract_deployment_balance_transfer() { #[tokio::test(flavor = "multi_thread")] async fn test_zk_contract_create2() { - let (prj, mut cmd) = util::setup_forge("test_zk_contract_create2_with_deps", foundry_test_utils::foundry_compilers::PathStyle::Dapptools); + let (prj, mut cmd) = util::setup_forge( + "test_zk_contract_create2_with_deps", + foundry_test_utils::foundry_compilers::PathStyle::Dapptools, + ); util::initialize(prj.root()); - cmd.args(["install", "matter-labs/era-contracts", "--no-commit", "--shallow"]).ensure_execute_success().expect("able to install dependencies"); + cmd.args(["install", "matter-labs/era-contracts", "--no-commit", "--shallow"]) + .ensure_execute_success() + .expect("able to install dependencies"); cmd.forge_fuse(); let mut config = cmd.config(); @@ -64,9 +69,11 @@ async fn test_zk_contract_create2() { prj.add_source("Greeter.sol", include_str!("../../../../../testdata/zk/Greeter.sol")).unwrap(); - prj.add_source("CustomNumber.sol", include_str!("../../../../../testdata/zk/CustomNumber.sol")).unwrap(); + prj.add_source("CustomNumber.sol", include_str!("../../../../../testdata/zk/CustomNumber.sol")) + .unwrap(); - prj.add_source("Create2Utils.sol", include_str!("../../../../../testdata/zk/Create2Utils.sol")).unwrap(); + prj.add_source("Create2Utils.sol", include_str!("../../../../../testdata/zk/Create2Utils.sol")) + .unwrap(); prj.add_test("Create2.t.sol", include_str!("../../fixtures/zk/Create2.t.sol")).unwrap(); From 4963879b389a0979913e114e4b09a70c5a767369 Mon Sep 17 00:00:00 2001 From: Francesco Dainese Date: Tue, 27 Aug 2024 18:52:44 +0200 Subject: [PATCH 4/5] chore: typo --- crates/forge/tests/fixtures/zk/Create2.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/forge/tests/fixtures/zk/Create2.t.sol b/crates/forge/tests/fixtures/zk/Create2.t.sol index ee7641449..7019e6310 100644 --- a/crates/forge/tests/fixtures/zk/Create2.t.sol +++ b/crates/forge/tests/fixtures/zk/Create2.t.sol @@ -25,7 +25,7 @@ contract Create2Test is Test { Create2Utils.computeCreate2Address(sender, salt, bytes32(bytecodeHash), constructorInputHash); // deploy via create2 - address actualAddress = address(new ConstantNumber{salt: salt}()); + address actualAddress = address(new Greeter{salt: salt}()); assertEq(actualAddress, expectedAddress); } From c88b71c76afc41cf8a7da14a4ea2265332670d24 Mon Sep 17 00:00:00 2001 From: Francesco Dainese Date: Wed, 28 Aug 2024 14:54:13 +0200 Subject: [PATCH 5/5] fix(test:zk): specify evm-version chore: use consistent variable name --- crates/forge/tests/fixtures/zk/Create2.t.sol | 4 ++-- crates/forge/tests/it/zk/contracts.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/forge/tests/fixtures/zk/Create2.t.sol b/crates/forge/tests/fixtures/zk/Create2.t.sol index 7019e6310..099c9096f 100644 --- a/crates/forge/tests/fixtures/zk/Create2.t.sol +++ b/crates/forge/tests/fixtures/zk/Create2.t.sol @@ -21,13 +21,13 @@ contract Create2Test is Test { bytes32 salt = "12345"; bytes32 constructorInputHash = keccak256(abi.encode()); - address expectedAddress = + address computedAddress = Create2Utils.computeCreate2Address(sender, salt, bytes32(bytecodeHash), constructorInputHash); // deploy via create2 address actualAddress = address(new Greeter{salt: salt}()); - assertEq(actualAddress, expectedAddress); + assertEq(actualAddress, computedAddress); } function testComputeCreate2WithNoArgs() external { diff --git a/crates/forge/tests/it/zk/contracts.rs b/crates/forge/tests/it/zk/contracts.rs index 9f1ee665e..0f7959c03 100644 --- a/crates/forge/tests/it/zk/contracts.rs +++ b/crates/forge/tests/it/zk/contracts.rs @@ -77,7 +77,7 @@ async fn test_zk_contract_create2() { prj.add_test("Create2.t.sol", include_str!("../../fixtures/zk/Create2.t.sol")).unwrap(); - cmd.args(["test", "--zk-startup", "--mc", "Create2Test"]); + cmd.args(["test", "--zk-startup", "--evm-version", "shanghai", "--mc", "Create2Test"]); assert!(cmd.stdout_lossy().contains("Suite result: ok")); }