Skip to content

Commit

Permalink
Zksync test network (#76)
Browse files Browse the repository at this point in the history
* wip

* feat: zksync support
  • Loading branch information
PatrickAlphaC authored Oct 1, 2024
1 parent ed3df28 commit c81eac1
Show file tree
Hide file tree
Showing 27 changed files with 470 additions and 184 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ default
secrets/
secret/
.DS_Store
era_test_node.log
9 changes: 9 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Issues, feedback, and sharing that you're using Titanoboa and Vyper on social me
- [Table of Contents](#table-of-contents)
- [Setup](#setup)
- [Requirements](#requirements)
- [ZKSync requirements](#zksync-requirements)
- [Installing for local development](#installing-for-local-development)
- [Running Tests](#running-tests)
- [Code Style Guide](#code-style-guide)
Expand All @@ -36,6 +37,14 @@ You must have the following installed to proceed with contributing to this proje
- [just](https://github.com/casey/just)
- You'll know you did it right if you can run `just --version` and you see a response like `just 1.35.0`

### ZKSync requirements
If you wish to run the ZKSync tests, you'll need these as well (ran with `just test-z`)

- [era_test_node](https://github.com/matter-labs/era-test-node)
- You'll know you did it right if you can run `era_test_node --version` and you see a response like `era_test_node 0.1.0 (a178051e8 2024-09-07)`
- [era-compiler-vyper](https://github.com/matter-labs/era-compiler-vyper)
- You'll know you did it right if you can run `zkvyper --version` and you see a response like `Vyper compiler for ZKsync v1.5.4 (LLVM build f9f732c8ebdb88fb8cd4528482a00e4f65bcb8b7)`

## Installing for local development

Follow the steps to clone the repo for you to make changes to this project.
Expand Down
11 changes: 11 additions & 0 deletions docs/source/all_moccasin_toml_parameters.rst
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,17 @@ All possible options
save_abi_path = "abis" # location to save ABIs from the explorer
cov_config = ".coveragerc" # coverage configuration file
dot_env = ".env" # environment variables file
default_network = "pyevm" # default network to use. `pyevm` is the local network. "eravm" is the local ZKSync network
[networks.pyevm]
# The basic EVM local network
# cannot set URL, chain_id, is_fork, is_zksync, prompt_live, explorer_uri, explorer_api_key
default_account_name = "anvil"
[networks.eravm]
# The special ZKSync Era local network
# cannot set URL, chain_id, is_fork, is_zksync, prompt_live, explorer_uri, explorer_api_key
default_account_name = "anvil"
[networks.contracts]
# Default named contract parameters
Expand Down
2 changes: 1 addition & 1 deletion docs/source/script.rst
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,6 @@ Working with dependencies
There are two kinds of dependencies you can work with in your moccasin project:

- :doc:`Smart Contract dependencies <dependencies>`: For contracts that you want to use packages from.
- :doc: `Python dependencies <virtual_environments>`: For python packages that you want to use in your scripts.
- :doc:`Python dependencies <virtual_environments>`: For python packages that you want to use in your scripts.

Each have their own respective documentation.
8 changes: 6 additions & 2 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,20 @@ format-check:

# Run unit and CLI tests, fail on first test failure
test:
uv run pytest -x -s --ignore=tests/data/ --ignore=tests/integration/
uv run pytest -x -s --ignore=tests/data/ --ignore=tests/integration/ --ignore=tests/zksync/

# Run integration tests, read the README.md in the tests/integration directory for more information
test-i:
uv run pytest tests/integration -x -s --ignore=tests/data/
uv run pytest tests/integration -x -s --ignore=tests/data/ --ignore=tests/zksync/

test-z:
uv run pytest tests/zksync -x -s --ignore=tests/data/ --ignore=tests/integration/

# Run both unit and integration tests
test-all:
@just test
@just test-i
@just test-z

# Run tests, fail on first test failure, enter debugger on failure
test-pdb:
Expand Down
7 changes: 7 additions & 0 deletions moccasin/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,13 @@ def generate_main_parser_and_sub_parsers() -> (
help="Optional argument to compile a specific contract.",
)

zksync_ground = compile_parser.add_mutually_exclusive_group()
zksync_ground.add_argument(
"--network", help=f"Alias of the network (from the {CONFIG_NAME})."
)

zksync_ground.add_argument("--is_zksync", nargs="?", const=True, default=None)

# ------------------------------------------------------------------
# TEST COMMAND
# ------------------------------------------------------------------
Expand Down
84 changes: 52 additions & 32 deletions moccasin/_sys_path_and_config_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@

import boa

from moccasin.config import get_config
from moccasin.config import Network, get_config
from moccasin.constants.vars import ERA_DEFAULT_PRIVATE_KEY, ERAVM
from moccasin.logging import logger
from moccasin.moccasin_account import MoccasinAccount

Expand All @@ -22,7 +23,7 @@ def _patch_sys_path(paths: List[str | Path]) -> Iterator[None]:
sys.path = anchor


def _setup_network_and_account_from_args(
def _setup_network_and_account_from_args_and_cli(
network: str = None,
url: str = None,
fork: bool | None = None,
Expand All @@ -31,36 +32,48 @@ def _setup_network_and_account_from_args(
password: str | None = None,
password_file_path: Path | None = None,
prompt_live: bool | None = None,
explorer_uri: str | None = None,
explorer_api_key: str | None = None,
):
"""All the network and account logic in the function parameters are from the CLI.
We will use the order of operations to setup the network:
1. scripts (which, we don't touch here)
2. CLI
3. Config
4. Default Values
All the values passed into this function come from the CLI.
"""
if account is not None and private_key is not None:
raise ValueError("Cannot set both account and private key in the CLI!")

mox_account: MoccasinAccount | None = None
config = get_config()

# Specifically a CLI check
if fork and account:
raise ValueError("Cannot use --fork and --account at the same time")
if network is None:
network = config.default_network

# Setup Network
is_fork_from_cli = fork if fork is not None else None
if network and not url:
config.networks.set_active_network(network, is_fork=is_fork_from_cli)
if url:
config.networks.set_active_network(url, is_fork=is_fork_from_cli)
# 1. Update the network with the CLI values
config.set_active_network(
network,
is_fork=fork,
url=url,
default_account_name=account,
# private_key=private_key, # No private key in networks
# password=password, # No password in networks
password_file_path=password_file_path,
prompt_live=prompt_live,
explorer_uri=explorer_uri,
explorer_api_key=explorer_api_key,
)

active_network = config.get_active_network()
active_network: Network = config.get_active_network()
if active_network is None:
raise ValueError("No active network set. Please set a network.")

# Update parameters if not provided in the CLI
if fork is None:
fork = active_network.is_fork
if password_file_path is None:
password_file_path = active_network.unsafe_password_file
if account is None:
account = active_network.default_account_name
if prompt_live is None:
prompt_live = active_network.prompt_live

if prompt_live:
# 2. Update and set account
if active_network.prompt_live:
if not fork:
response = input(
"The transactions run on this will actually be broadcast/transmitted, spending gas associated with your account. Are you sure you wish to continue?\nType 'y' or 'Y' and hit 'ENTER' or 'RETURN' to continue:\n"
Expand All @@ -69,27 +82,34 @@ def _setup_network_and_account_from_args(
logger.info("Operation cancelled.")
sys.exit(0)

if account:
if active_network.default_account_name and private_key is None:
# This will also attempt to unlock the account with a prompt
# If no password or password file is passed
mox_account = MoccasinAccount(
keystore_path_or_account_name=account,
keystore_path_or_account_name=active_network.default_account_name,
password=password,
password_file_path=password_file_path,
password_file_path=active_network.unsafe_password_file,
)
# Private key overrides the default account
if private_key:
mox_account = MoccasinAccount(
private_key=private_key,
password=password,
password_file_path=password_file_path,
password_file_path=active_network.unsafe_password_file,
)

if mox_account:
if fork:
if active_network.is_fork:
boa.env.eoa = mox_account.address
else:
boa.env.add_account(mox_account, force_eoa=True)
if boa.env.eoa is None:
logger.warning(
"No default EOA account found. Please add an account to the environment before attempting a transaction."
)

if not mox_account and active_network.name is ERAVM:
boa.env.add_account(MoccasinAccount(private_key=ERA_DEFAULT_PRIVATE_KEY))

# Check if it's a fork, pyevm, or eravm
if not active_network.is_testing_network():
if boa.env.eoa is None:
logger.warning(
"No default EOA account found. Please add an account to the environment before attempting a transaction."
)
66 changes: 55 additions & 11 deletions moccasin/commands/compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,13 @@
from vyper.compiler.phases import CompilerData
from vyper.exceptions import VersionException, _BaseVyperException

from moccasin.config import get_config, initialize_global_config
from moccasin.constants.vars import BUILD_FOLDER, CONTRACTS_FOLDER, MOCCASIN_GITHUB
from moccasin.config import Config, get_config, initialize_global_config
from moccasin.constants.vars import (
BUILD_FOLDER,
CONTRACTS_FOLDER,
ERAVM,
MOCCASIN_GITHUB,
)
from moccasin.logging import logger


Expand All @@ -24,22 +29,50 @@ def main(args: Namespace) -> int:
config = get_config()
project_path: Path = config.get_root()

is_zksync: bool = _set_zksync_test_env_if_applicable(args, config)

if args.contract_or_contract_path:
contract_path = config._find_contract(args.contract_or_contract_path)

compile_(
contract_path, project_path.joinpath(config.out_folder), write_data=True
contract_path,
project_path.joinpath(config.out_folder),
is_zksync=is_zksync,
write_data=True,
)
logger.info(f"Done compiling {contract_path.stem}")
else:
compile_project(
project_path,
project_path.joinpath(config.out_folder),
project_path.joinpath(config.contracts_folder),
is_zksync=is_zksync,
write_data=True,
)
return 0


def _set_zksync_test_env_if_applicable(args: Namespace, config: Config) -> bool:
is_zksync = args.is_zksync if args.is_zksync is not None else None

if is_zksync:
config.set_active_network(ERAVM)
return True

if args.network is not None and is_zksync is None:
config.set_active_network(args.network)
is_zksync = config.get_active_network().is_zksync

if config.default_network is not None and is_zksync is None:
config.set_active_network(config.default_network)
is_zksync = config.get_active_network().is_zksync

if is_zksync is None:
is_zksync = False

return is_zksync


def _get_cpu_count():
if hasattr(os, "process_cpu_count"):
# python 3.13+
Expand All @@ -51,6 +84,7 @@ def compile_project(
project_path: Path | None = None,
build_folder: Path | None = None,
contracts_folder: Path | None = None,
is_zksync: bool = False,
write_data: bool = False,
):
if project_path is None:
Expand All @@ -75,7 +109,9 @@ def compile_project(
with multiprocessing.Pool(n_cpus) as pool:
for contract_path in contracts_to_compile:
res = pool.apply_async(
compile_, (contract_path, build_folder), dict(write_data=write_data)
compile_,
(contract_path, build_folder),
dict(is_zksync=is_zksync, write_data=write_data),
)
jobs.append(res)

Expand Down Expand Up @@ -106,6 +142,7 @@ def compile_(
contract_path: Path,
build_folder: Path,
compiler_args: dict | None = None,
is_zksync: bool = False,
write_data: bool = False,
) -> VyperDeployer | None:
logger.debug(f"Compiling contract {contract_path}")
Expand All @@ -132,21 +169,28 @@ def compile_(

abi: list
bytecode: bytes
if isinstance(deployer, VVMDeployer):
abi = deployer.abi
bytecode = deployer.bytecode
vm = "evm"

if is_zksync:
abi = deployer._abi
bytecode = deployer.zkvyper_data.bytecode
vm = "eravm"
else:
compiler_data: CompilerData = deployer.compiler_data
bytecode = compiler_data.bytecode
abi = vyper.compiler.output.build_abi_output(compiler_data)
if isinstance(deployer, VVMDeployer):
abi = deployer.abi
bytecode = deployer.bytecode
else:
compiler_data: CompilerData = deployer.compiler_data
bytecode = compiler_data.bytecode
abi = vyper.compiler.output.build_abi_output(compiler_data)

# Save Compilation Data
contract_name = Path(contract_path).stem

build_data = {
"contract_name": contract_name,
"bytecode": bytecode.hex(),
"abi": abi,
"vm": vm,
}

if write_data:
Expand Down
4 changes: 2 additions & 2 deletions moccasin/commands/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from moccasin._sys_path_and_config_setup import (
_patch_sys_path,
_setup_network_and_account_from_args,
_setup_network_and_account_from_args_and_cli,
)
from moccasin.config import get_config, initialize_global_config
from moccasin.constants.vars import CONSOLE_HISTORY_FILE, DEFAULT_MOCCASIN_FOLDER
Expand All @@ -20,7 +20,7 @@ def main(args: Namespace) -> int:

# Set up the environment (add necessary paths to sys.path, etc.)
with _patch_sys_path([config_root, config_root / config.contracts_folder]):
_setup_network_and_account_from_args(
_setup_network_and_account_from_args_and_cli(
network=args.network,
url=args.url,
fork=args.fork,
Expand Down
4 changes: 2 additions & 2 deletions moccasin/commands/deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from moccasin._sys_path_and_config_setup import (
_patch_sys_path,
_setup_network_and_account_from_args,
_setup_network_and_account_from_args_and_cli,
)
from moccasin.config import get_config, initialize_global_config
from moccasin.logging import logger
Expand All @@ -15,7 +15,7 @@ def main(args: Namespace) -> int:

# Set up the environment (add necessary paths to sys.path, etc.)
with _patch_sys_path([config_root, config_root / config_contracts]):
_setup_network_and_account_from_args(
_setup_network_and_account_from_args_and_cli(
network=args.network,
url=args.url,
fork=args.fork,
Expand Down
Loading

0 comments on commit c81eac1

Please sign in to comment.