Skip to content

Commit

Permalink
test(zk): Add proxy and NFT script to Cargo test (#527)
Browse files Browse the repository at this point in the history
* add proxy and NFT script

* generalize run script test function

* add list of deps, extra args and expose rich wallets

* Multiple installs in one command

---------

Co-authored-by: Jrigada <[email protected]>
  • Loading branch information
Jrigada and Jrigada authored Aug 20, 2024
1 parent 614b0f9 commit 98dfb5e
Show file tree
Hide file tree
Showing 9 changed files with 290 additions and 53 deletions.
6 changes: 3 additions & 3 deletions crates/forge/tests/fixtures/zk/Factory.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ contract ZkConstructorFactoryScript is Script {
}
}

contract ZkNestedFactoryScript is Script{
contract ZkNestedFactoryScript is Script {
function run() external {
vm.startBroadcast();
MyNestedFactory factory = new MyNestedFactory();
Expand All @@ -33,7 +33,7 @@ contract ZkNestedFactoryScript is Script{
}
}

contract ZkNestedConstructorFactoryScript is Script{
contract ZkNestedConstructorFactoryScript is Script {
function run() external {
vm.startBroadcast();
MyNestedConstructorFactory factory = new MyNestedConstructorFactory(42);
Expand All @@ -55,7 +55,7 @@ contract ZkUserFactoryScript is Script {
}
}

contract ZkUserConstructorFactoryScript is Script{
contract ZkUserConstructorFactoryScript is Script {
function run() external {
vm.startBroadcast();
MyConstructorFactory factory = new MyConstructorFactory(42);
Expand Down
84 changes: 84 additions & 0 deletions crates/forge/tests/fixtures/zk/NFT.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.10;

import "forge-std/Script.sol";
import "solmate/tokens/ERC721.sol";

error MintPriceNotPaid();
error MaxSupply();
error NonExistentTokenURI();
error WithdrawTransfer();

contract NFT is ERC721 {
string public baseURI;
uint256 public currentTokenId;
uint256 public constant TOTAL_SUPPLY = 10_000;
uint256 public constant MINT_PRICE = 0.08 ether;
address public owner;

constructor(string memory _name, string memory _symbol, string memory _baseURI) ERC721(_name, _symbol) {
baseURI = _baseURI;
owner = msg.sender;
}

modifier onlyOwner() {
require(msg.sender == owner, "Not the owner");
_;
}

function mintTo(address recipient) public payable returns (uint256) {
if (msg.value != MINT_PRICE) {
revert MintPriceNotPaid();
}
uint256 newTokenId = ++currentTokenId;
if (newTokenId > TOTAL_SUPPLY) {
revert MaxSupply();
}
_safeMint(recipient, newTokenId);
return newTokenId;
}

function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
if (ownerOf(tokenId) == address(0)) {
revert NonExistentTokenURI();
}
return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, _toString(tokenId))) : "";
}

function withdrawPayments(address payable payee) external onlyOwner {
uint256 balance = address(this).balance;
(bool transferTx,) = payee.call{value: balance}("");
if (!transferTx) {
revert WithdrawTransfer();
}
}

function _toString(uint256 value) internal pure returns (string memory) {
if (value == 0) {
return "0";
}
uint256 temp = value;
uint256 digits;
while (temp != 0) {
digits++;
temp /= 10;
}
bytes memory buffer = new bytes(digits);
while (value != 0) {
digits -= 1;
buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
value /= 10;
}
return string(buffer);
}
}

contract MyScript is Script {
function run() external {
vm.startBroadcast(0x7becc4a46e0c3b512d380ca73a4c868f790d1055a7698f38fb3ca2b2ac97efbb);

new NFT("NFT_tutorial", "TUT", "baseUri");

vm.stopBroadcast();
}
}
32 changes: 32 additions & 0 deletions crates/forge/tests/fixtures/zk/Proxy.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.7 <0.9.0;

import "forge-std/Script.sol";
import "openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol";

contract ProxyScript is Script {
function run() public {
vm.startBroadcast(0x7becc4a46e0c3b512d380ca73a4c868f790d1055a7698f38fb3ca2b2ac97efbb);
//deploy Foo
ERC1967Proxy proxy = new ERC1967Proxy(address(new Foo()), "");

Foo foo = Foo(payable(proxy));
foo.initialize(msg.sender);

console.log("Foo deployed at: ", address(foo));
console.log("Bar: ", foo.getAddress());
vm.stopBroadcast();
}
}

contract Foo {
address bar;

function initialize(address _bar) public {
bar = _bar;
}

function getAddress() public returns (address) {
return bar;
}
}
65 changes: 64 additions & 1 deletion crates/forge/tests/it/test_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use foundry_evm::{
constants::CALLER,
opts::{Env, EvmOpts},
};
use foundry_test_utils::{fd_lock, init_tracing};
use foundry_test_utils::{fd_lock, init_tracing, TestCommand, ZkSyncNode};
use foundry_zksync_compiler::DualCompiledContracts;
use once_cell::sync::Lazy;
use semver::Version;
Expand Down Expand Up @@ -588,3 +588,66 @@ pub fn rpc_endpoints_zk() -> RpcEndpoints {
("rpcEnvAlias", RpcEndpoint::Env("${RPC_ENV_ALIAS}".to_string())),
])
}

pub fn run_zk_script_test(
root: impl AsRef<std::path::Path>,
cmd: &mut TestCommand,
script_path: &str,
contract_name: &str,
dependencies: Option<&str>,
expected_broadcastable_txs: usize,
extra_args: Option<&[&str]>,
) {
let node = ZkSyncNode::start();
let url = node.url();

if let Some(deps) = dependencies {
let mut install_args = vec!["install"];
install_args.extend(deps.split_whitespace());
install_args.push("--no-commit");
cmd.args(&install_args).ensure_execute_success().expect("Installed successfully");
}

cmd.forge_fuse();

let script_path_contract = format!("{script_path}:{contract_name}");
let private_key =
ZkSyncNode::rich_wallets().next().map(|(_, pk, _)| pk).expect("No rich wallets available");
let mut script_args = vec![
"--zk-startup",
&script_path_contract,
"--broadcast",
"--private-key",
private_key,
"--chain",
"260",
"--gas-estimate-multiplier",
"310",
"--rpc-url",
url.as_str(),
"--slow",
"--evm-version",
"shanghai",
];

if let Some(args) = extra_args {
script_args.extend_from_slice(args);
}

cmd.arg("script").args(&script_args);

assert!(cmd.stdout_lossy().contains("ONCHAIN EXECUTION COMPLETE & SUCCESSFUL"));

let run_latest = foundry_common::fs::json_files(root.as_ref().join("broadcast").as_path())
.find(|file| file.ends_with("run-latest.json"))
.expect("No broadcast artifacts");

let content = foundry_common::fs::read_to_string(run_latest).unwrap();

let json: serde_json::Value = serde_json::from_str(&content).unwrap();
assert_eq!(
json["transactions"].as_array().expect("broadcastable txs").len(),
expected_broadcastable_txs
);
cmd.forge_fuse();
}
108 changes: 59 additions & 49 deletions crates/forge/tests/it/zk/factory.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
//! Forge tests for zksync factory contracts.
use forge::revm::primitives::SpecId;
use foundry_test_utils::{forgetest_async, util, Filter, TestCommand, TestProject, ZkSyncNode};
use foundry_test_utils::{forgetest_async, util, Filter, TestProject};

use crate::{config::TestConfig, test_helpers::TEST_DATA_DEFAULT};
use crate::{
config::TestConfig,
test_helpers::{run_zk_script_test, TEST_DATA_DEFAULT},
};

#[tokio::test(flavor = "multi_thread")]
async fn test_zk_can_deploy_in_method() {
Expand Down Expand Up @@ -38,65 +41,72 @@ async fn test_zk_can_use_predeployed_factory() {

forgetest_async!(script_zk_can_deploy_in_method, |prj, cmd| {
setup_factory_prj(&mut prj);
run_factory_script_test(prj.root(), &mut cmd, "ZkClassicFactoryScript", 2);
run_factory_script_test(prj.root(), &mut cmd, "ZkNestedFactoryScript", 2);
run_zk_script_test(
prj.root(),
&mut cmd,
"./script/Factory.s.sol",
"ZkClassicFactoryScript",
None,
2,
None,
);
run_zk_script_test(
prj.root(),
&mut cmd,
"./script/Factory.s.sol",
"ZkNestedFactoryScript",
None,
2,
None,
);
});

forgetest_async!(script_zk_can_deploy_in_constructor, |prj, cmd| {
setup_factory_prj(&mut prj);
run_factory_script_test(prj.root(), &mut cmd, "ZkConstructorFactoryScript", 1);
run_factory_script_test(prj.root(), &mut cmd, "ZkNestedConstructorFactoryScript", 1);
run_zk_script_test(
prj.root(),
&mut cmd,
"./script/Factory.s.sol",
"ZkConstructorFactoryScript",
None,
1,
None,
);
run_zk_script_test(
prj.root(),
&mut cmd,
"./script/Factory.s.sol",
"ZkNestedConstructorFactoryScript",
None,
1,
None,
);
});

forgetest_async!(script_zk_can_use_predeployed_factory, |prj, cmd| {
setup_factory_prj(&mut prj);
run_factory_script_test(prj.root(), &mut cmd, "ZkUserFactoryScript", 3);
run_factory_script_test(prj.root(), &mut cmd, "ZkUserConstructorFactoryScript", 2);
run_zk_script_test(
prj.root(),
&mut cmd,
"./script/Factory.s.sol",
"ZkUserFactoryScript",
None,
3,
None,
);
run_zk_script_test(
prj.root(),
&mut cmd,
"./script/Factory.s.sol",
"ZkUserConstructorFactoryScript",
None,
2,
None,
);
});

fn setup_factory_prj(prj: &mut TestProject) {
util::initialize(prj.root());
prj.add_source("Factory.sol", include_str!("../../../../../testdata/zk/Factory.sol")).unwrap();
prj.add_script("Factory.s.sol", include_str!("../../fixtures/zk/Factory.s.sol")).unwrap();
}

fn run_factory_script_test(
root: impl AsRef<std::path::Path>,
cmd: &mut TestCommand,
name: &str,
expected_broadcastable_txs: usize,
) {
let node = ZkSyncNode::start();

cmd.arg("script").args([
"--zk-startup",
&format!("./script/Factory.s.sol:{name}"),
"--broadcast",
"--private-key",
"0x3d3cbc973389cb26f657686445bcc75662b415b656078503592ac8c1abb8810e",
"--chain",
"260",
"--gas-estimate-multiplier",
"310",
"--rpc-url",
node.url().as_str(),
"--slow",
"--evm-version",
"shanghai",
]);

assert!(cmd.stdout_lossy().contains("ONCHAIN EXECUTION COMPLETE & SUCCESSFUL"));

let run_latest = foundry_common::fs::json_files(root.as_ref().join("broadcast").as_path())
.find(|file| file.ends_with("run-latest.json"))
.expect("No broadcast artifacts");

let content = foundry_common::fs::read_to_string(run_latest).unwrap();

let json: serde_json::Value = serde_json::from_str(&content).unwrap();
assert_eq!(
json["transactions"].as_array().expect("broadcastable txs").len(),
expected_broadcastable_txs
);
cmd.forge_fuse();
}
2 changes: 2 additions & 0 deletions crates/forge/tests/it/zk/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@ mod fork;
mod fuzz;
mod invariant;
mod logs;
mod nft;
mod ownership;
mod proxy;
mod repros;
21 changes: 21 additions & 0 deletions crates/forge/tests/it/zk/nft.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use foundry_test_utils::{forgetest_async, util, TestProject};

use crate::test_helpers::run_zk_script_test;

forgetest_async!(script_zk_can_deploy_nft, |prj, cmd| {
setup_nft_prj(&mut prj);
run_zk_script_test(
prj.root(),
&mut cmd,
"./script/NFT.s.sol",
"MyScript",
Some("transmissions11/solmate@v7 OpenZeppelin/openzeppelin-contracts"),
1,
Some(&["-vvvvv"]),
);
});

fn setup_nft_prj(prj: &mut TestProject) {
util::initialize(prj.root());
prj.add_script("NFT.s.sol", include_str!("../../fixtures/zk/NFT.s.sol")).unwrap();
}
Loading

0 comments on commit 98dfb5e

Please sign in to comment.