Skip to content

Commit

Permalink
Feat/update zksync boa (#162)
Browse files Browse the repository at this point in the history
* fix: zksync compile issues, python path oddities, new boa changes

* fix: manifest named for when the DB isnt populated, and updated docs

* fix: lint
  • Loading branch information
PatrickAlphaC authored Nov 23, 2024
1 parent 2d0f529 commit 29214c1
Show file tree
Hide file tree
Showing 22 changed files with 96 additions and 64 deletions.
2 changes: 1 addition & 1 deletion docs/source/all_moccasin_toml_parameters.rst
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ All possible options
Environment Variables
---------------------

Additionally, there are a few environment variables that ``moccasin`` will look for, but it's also okay if they are not set. It's important to note, that the `.env` file you set in the config will be ignored for these values.
Additionally, there are a few environment variables that ``moccasin`` will look for, but it's also okay if they are not set. It's important to note, that the ``.env`` file you set in the config will be ignored for these values.

.. code-block:: bash
Expand Down
4 changes: 2 additions & 2 deletions docs/source/core_concepts/dependencies.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ This will create an entry in your ``moccasin.toml`` file that looks like this:
"pcaversaccio/snekmate@0.1.0",
]
Which follows the same syntax that `pip` and `uv` to do installs from GitHub repositories. This will also download the GitHub repository into your `lib` folder.
Which follows the same syntax that ``pip`` and ``uv`` to do installs from GitHub repositories. This will also download the GitHub repository into your ``lib`` folder.

You can then use these packages in your vyper contracts, for example in an miniaml ERC20 vyper contract:

Expand All @@ -60,7 +60,7 @@ You can then use these packages in your vyper contracts, for example in an minia
erc20.__init__("my_token", "MT", 18, "my_token_dapp", "0x02")
ow.__init__()
``moccasin`` is smart enough to know that the `lib/github` and `lib/pypi` folders are part of the search path, but you can also explicitly add your dependencies.
``moccasin`` is smart enough to know that the ``lib/github`` and ``lib/pypi`` folders are part of the search path, but you can also explicitly add your dependencies.

.. code-block:: python
Expand Down
2 changes: 1 addition & 1 deletion docs/source/core_concepts/named_contracts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Let's look at a minimal ``moccasin.toml`` with a ETH mainnet network fork with a
[networks.mainnet-fork.contracts]
usdc = { address = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"}
The `NamedContract` in this case, is `usdc`. And it's this named contract that we can access our scripts!
The ``NamedContract`` in this case, is ``usdc``. And it's this named contract that we can access our scripts!

.. code-block:: python
Expand Down
6 changes: 3 additions & 3 deletions docs/source/core_concepts/named_contracts/manifest_named.rst
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,9 @@ And this is awful! So instead, what we can do, is setup **ALL** of the configura

.. code-block:: toml
# At the top, we can set some default parameters for the `usdc` named contract
# At the top, we can set some default parameters for the ``usdc``` named contract
# Every named contract will use the ERC20.vy contract in the project as it's ABI
# If the contract doesn't exist, it'll deploy with the `deploy_feed.py` script (for example, on a locally running network)
# If the contract doesn't exist, it'll deploy with the ``deploy_feed.py``` script (for example, on a locally running network)
[networks.contracts]
usdc = { abi = "ERC20", deployer_script = "mock_deployer/deploy_feed.py"}
Expand Down Expand Up @@ -104,7 +104,7 @@ And with this, we only need ONE script that works for all of these!
def moccasin_main():
get_decimals()
Then, we just need to adjust the `--network` flag and everything else will work automatically.
Then, we just need to adjust the ``--network`` flag and everything else will work automatically.

.. code-block:: bash
Expand Down
4 changes: 2 additions & 2 deletions docs/source/core_concepts/networks/forked_networks.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ In testing, forking is an essential part of the development process. **Any scrip
chain_id = 11155111
is_fork = true
Running a script with this setup will run your script locally, but pretending to be on the `sepolia` network, with contracts and everything!
Running a script with this setup will run your script locally, but pretending to be on the ``sepolia`` network, with contracts and everything!

It essentially does some "lazy loading", where it'll only download information from the blockchain if/when you need. For example, if you want to get the balance of an account, it'll only download the balance of that account when your script calls for it.

You can also take any non-forked network and run a forked test just by adding the `--fork` flag to the command line.
You can also take any non-forked network and run a forked test just by adding the ``--fork`` flag to the command line.

.. code-block:: toml
Expand Down
2 changes: 1 addition & 1 deletion docs/source/core_concepts/project.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ A typical moccasin project is structured as follows:
Where:

- ``README.md`` is a markdown file that you can use to describe your project.
- ``moccasin.toml`` is a configuration file that `moccasin` uses to manage the project.
- ``moccasin.toml`` is a configuration file that ``moccasin`` uses to manage the project.
- ``script`` is a directory that contains python scripts that you can use to deploy your project.
- ``src``` is a directory that contains your vyper smart contracts.
- ``tests`` is a directory that contains your tests.
Expand Down
4 changes: 2 additions & 2 deletions docs/source/core_concepts/script.rst
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ You can directly import contracts from the ``src`` folder into your scripts, and
Networking
==========

If you have :doc:`networks <networks>` defined in your :doc:`moccasin.toml <moccasin_toml>`, you can directly work with the network in your scripts. For example, if you have a `sepolia` network defined in your ``moccasin.toml``:
If you have :doc:`networks <networks>` defined in your :doc:`moccasin.toml <moccasin_toml>`, you can directly work with the network in your scripts. For example, if you have a ``sepolia`` network defined in your ``moccasin.toml``:

.. code-block:: bash
Expand All @@ -102,7 +102,7 @@ You can learn more about networks in the :doc:`networks documentation <networks>
moccasin_main
=============

In your scripts, the `moccasin_main` function is special, if you have a function with this name in your script, `moccasin` will run this function by default after running the script like a regular python file. For example, you could also do this:
In your scripts, the ``moccasin_main`` function is special, if you have a function with this name in your script, ``moccasin`` will run this function by default after running the script like a regular python file. For example, you could also do this:

.. code-block:: python
Expand Down
2 changes: 1 addition & 1 deletion docs/source/core_concepts/testing/gas_profiling.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Gas Profiling

.. note:: See the `titanoboa gas-profiling documentation for more information. <https://titanoboa.readthedocs.io/en/latest/testing.html#gas-profiling>`_

`moccasin` has a built in gas profiler that can be used to profile your contracts gas usage. It uses `titanoboa's <https://titanoboa.readthedocs.io/en/latest/testing.html#gas-profiling>`_ gas profiling under the hood.
``moccasin`` has a built in gas profiler that can be used to profile your contracts gas usage. It uses `titanoboa's <https://titanoboa.readthedocs.io/en/latest/testing.html#gas-profiling>`_ gas profiling under the hood.

To use the gas profiler, you can run:

Expand Down
2 changes: 1 addition & 1 deletion docs/source/how-tos/stateful_fuzzing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Let's say we have the following contract:
def change_number(new_number: uint256):
self.some_number = new_number
The `invariant` in this contract is that the function `always_returns_input_number` should always return the input number. But as we can see from looking at the function, we notice that if someone were to call ``change_number`` with an input of ``2``, the ``always_returns_input_number`` function will return 0 no matter what.
The `invariant` in this contract is that the function ``always_returns_input_number`` should always return the input number. But as we can see from looking at the function, we notice that if someone were to call ``change_number`` with an input of ``2``, the ``always_returns_input_number`` function will return 0 no matter what.

This is easy for us to "see", but when contracts get sufficiently complicated, spotting these kinds of bugs becomes harder and harder, and this is where our tests come in.

Expand Down
4 changes: 2 additions & 2 deletions docs/source/how-tos/stateless_fuzzing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Let's say you have a contract as such:
return 0
return input_number
The `invariant` in this contract is that the function `always_returns_input_number` should always return the input number. But as we can see from looking at the function, we notice that if the input number is 2, the function will return 0.
The `invariant` in this contract is that the function ``always_returns_input_number`` should always return the input number. But as we can see from looking at the function, we notice that if the input number is 2, the function will return 0.

This is easy for us to "see", but when contracts get sufficiently complicated, spotting these kinds of bugs becomes harder and harder, and this is where our tests come in.

Expand Down Expand Up @@ -59,7 +59,7 @@ To fuzz test this, in ``moccasin`` we'd create a new file in our ``tests`` direc
Essentially, what this will try to do will be:

1. It will deploy a our contract
2. It will call the `always_returns_input_number` function with a random `uint256` input
2. It will call the ``always_returns_input_number`` function with a random ``uint256`` input
3. It will check if the result is the same as the input number

It will continue to do this for 1,000 "fuzz runs", which means 1,000 different random numbers. You can then test it with:
Expand Down
8 changes: 4 additions & 4 deletions docs/source/installing_moccasin.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Prerequisites
Installation with uv
====================

For those unfamiliar, `uv <https://docs.astral.sh/uv/>`_ is a fast python package manager that helps us install moccasin into it's own isolated virtual environment, so we don't get any weird dependency conflicts with other python packages. It's similar to `pip` and `pipx` if you've used them before. It even comes with some `pip` compatibility, will tools like `uv pip install`.
For those unfamiliar, `uv <https://docs.astral.sh/uv/>`_ is a fast python package manager that helps us install moccasin into it's own isolated virtual environment, so we don't get any weird dependency conflicts with other python packages. It's similar to ``pip`` and ``pipx`` if you've used them before. It even comes with some ``pip`` compatibility, will tools like ``uv pip install``.

It's highly recommended you understand how `virtual environments <https://docs.python.org/3/library/venv.html>`_ work as well.

Expand Down Expand Up @@ -133,9 +133,9 @@ To install ``pipx``:
.. note::

You may need to restart your terminal after installing `pipx`.
You may need to restart your terminal after installing ``pipx``.

To install moccasin then with `pipx`:
To install moccasin then with ``pipx``:

.. code-block:: bash
Expand All @@ -153,7 +153,7 @@ Installation with pip

You can install with ``pip``, and if you do so, it's highly recommended you understand how `virtual environments <https://docs.python.org/3/library/venv.html>`_ work.

To install with `pip`:
To install with ``pip``:

.. code-block:: bash
Expand Down
8 changes: 4 additions & 4 deletions docs/source/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,10 @@ You'll get an output like:
├── conftest.py
└── test_counter.py
This is a minimal project structure that `moccasin` creates.
This is a minimal project structure that ``moccasin`` creates.

- ``README.md`` is a markdown file that you can use to describe your project.
- ``moccasin.toml`` is a configuration file that `moccasin` uses to manage the project.
- ``moccasin.toml`` is a configuration file that ``moccasin`` uses to manage the project.
- ``script`` is a directory that contains scripts that you can use to deploy your project.
- ``src`` is a directory that contains your vyper smart contracts.
- ``tests``` is a directory that contains your tests.
Expand All @@ -67,7 +67,7 @@ If you run ``tree . -a``, you'll also see the "hidden" files.

- ``.gitignore`` is a file that tells git which files to ignore.
- ``.gitattributes`` is a file that tells git how to handle line endings.
- ``.coveragerc`` is a file that tells `pytest` how to handle coverage.
- ``.coveragerc`` is a file that tells ``pytest`` how to handle coverage.


Deploying a contract
Expand All @@ -91,7 +91,7 @@ Now, unlike other frameworks, with ``moccasin``, we never need to compile! Mocca
We can see a python script that will:

1. Deploy our `Counter` contract.
1. Deploy our ``Counter`` contract.
2. Print the starting count inside the contract.
3. Increment the count.
4. Print the ending count inside the contract.
Expand Down
12 changes: 12 additions & 0 deletions moccasin/_sys_path_and_config_setup.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import contextlib
import os
import sys
from pathlib import Path
from typing import Iterator, List
Expand Down Expand Up @@ -42,12 +43,23 @@ def get_sys_paths_list(config: Config) -> List[Path]:
def _patch_sys_path(paths: List[Path]) -> Iterator[None]:
str_paths = [str(p) for p in paths]
anchor = sys.path
anchor2 = os.environ.get("PYTHONPATH")
python_path = anchor2
if python_path is None:
python_path = ":".join(str_paths)
else:
python_path = ":".join([*str_paths, python_path])
os.environ["PYTHONPATH"] = python_path
try:
# add these with highest precedence -- conflicts should prefer user modules/code
sys.path = str_paths + sys.path
yield
finally:
sys.path = anchor
if anchor2 is None:
del os.environ["PYTHONPATH"]
else:
os.environ["PYTHONPATH"] = anchor2


# REVIEW: Might be best to just set this as **kwargs
Expand Down
1 change: 1 addition & 0 deletions moccasin/commands/compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ def compile_(

return deployer


# discard the result of the compilation so that we don't need to pickle it
# between processes
def compile_noret(*args, **kwargs) -> None:
Expand Down
12 changes: 10 additions & 2 deletions moccasin/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -560,9 +560,15 @@ def get_or_deploy_named(
vyper_contract: (
ABIContract | VyperContract | ZksyncContract | None
) = None
# REVIEW: This is a bit confusing.
# Right now, if the contract is in the DB, that takes precedence over
# a recently deployed contract. This is because the DB is the source of truth.
vyper_contract = self.get_latest_contract_unchecked(
contract_name=contract_name, chain_id=self.chain_id
)
if vyper_contract is None:
if self._check_valid_deploy(named_contract):
vyper_contract = named_contract.recently_deployed_contract
if vyper_contract is not None:
return vyper_contract
else:
Expand Down Expand Up @@ -702,7 +708,10 @@ def _get_abi_and_deployer_from_params(
else:
contract_path = config.find_contract(abi_like)
deployer = boa.load_partial(str(contract_path))
abi = build_abi_output(deployer.compiler_data)
if isinstance(deployer, VyperDeployer):
abi = build_abi_output(deployer.compiler_data)
elif isinstance(deployer, ZksyncDeployer):
abi = deployer.zkvyper_data
abi = cast(list, abi)
return abi, deployer
if isinstance(abi_like, list):
Expand Down Expand Up @@ -816,7 +825,6 @@ def __init__(self, toml_data: dict, project_root: Path):
address=contract_data.get("address", None),
)
toml_data = self._add_local_network_defaults(toml_data)

for network_name, network_data in toml_data["networks"].items():
# Check for restricted items for pyevm or eravm
if network_name in [PYEVM, ERAVM]:
Expand Down
10 changes: 5 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
[project]
name = "moccasin"
version = "0.3.4b4"
version = "0.3.4b5"
description = "Pythonic smart contract development framework using Titanoboa"
authors = [
{ name = "PatrickAlphac", email = "54278053+PatrickAlphaC@users.noreply.github.com" },
{ name = "charles-cooper", email = "3867501+charles-cooper@users.noreply.github.com" },
]
dependencies = [
"titanoboa>=v0.2.5b1",
"titanoboa>=v0.2.5",
"python-dotenv>=1.0.1",
"titanoboa-zksync>=0.2.7",
"titanoboa-zksync>=0.2.8",
"tqdm>=4.66.5",
"tomlkit>=0.13.2", # For preserving comments when writing to toml
"tomlkit>=0.13.2", # For preserving comments when writing to toml
"tomli-w>=1.0.0",
"pytest-cov>=5.0.0",
"uv>=0.4.15",
"pytest-xdist>=3.6.1",
"vyper==0.4.0", # We will want to get rid of this hard-coded dependency in 0.3.4 of moccasin
"vyper==0.4.0", # We will want to get rid of this hard-coded dependency in 0.3.4 of moccasin
]
readme = "README.md"
requires-python = ">= 3.11, <= 3.13"
Expand Down
6 changes: 3 additions & 3 deletions tests/cli/deployments/test_unit_deployments_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def test_generate_sql_from_args_with_where(blank_tempdir):
sql_query, params = active_network._generate_sql_from_args(
contract_name=contract_name, chain_id=chain_id, limit=limit, db=db
)
expected_sql = "SELECT contract_address,contract_name,rpc,deployer,tx_hash,broadcast_ts,tx_dict,receipt_dict,source_code,abi,session_id,deployment_id FROM deployments WHERE contract_name = ? AND json_extract(tx_dict, '$.chainId') = ? ORDER BY broadcast_ts DESC LIMIT ? "
expected_sql = "SELECT contract_address,contract_name,filename,rpc,deployer,tx_hash,broadcast_ts,tx_dict,receipt_dict,source_code,abi,session_id,deployment_id FROM deployments WHERE contract_name = ? AND json_extract(tx_dict, '$.chainId') = ? ORDER BY broadcast_ts DESC LIMIT ? "
expected_parametrs = ("MockV3Aggregator", "31337", 1)

# Assert
Expand All @@ -39,7 +39,7 @@ def test_generate_sql_from_args_without_where(blank_tempdir):

# Act
sql_query, params = active_network._generate_sql_from_args(db=db)
expected_sql = "SELECT contract_address,contract_name,rpc,deployer,tx_hash,broadcast_ts,tx_dict,receipt_dict,source_code,abi,session_id,deployment_id FROM deployments ORDER BY broadcast_ts DESC "
expected_sql = "SELECT contract_address,contract_name,filename,rpc,deployer,tx_hash,broadcast_ts,tx_dict,receipt_dict,source_code,abi,session_id,deployment_id FROM deployments ORDER BY broadcast_ts DESC "
expected_parametrs = ()

# Assert
Expand All @@ -61,7 +61,7 @@ def test_generate_sql_from_args_without_and(blank_tempdir):
sql_query, params = active_network._generate_sql_from_args(
contract_name=contract_name, limit=limit, db=db
)
expected_sql = "SELECT contract_address,contract_name,rpc,deployer,tx_hash,broadcast_ts,tx_dict,receipt_dict,source_code,abi,session_id,deployment_id FROM deployments WHERE contract_name = ? ORDER BY broadcast_ts DESC LIMIT ? "
expected_sql = "SELECT contract_address,contract_name,filename,rpc,deployer,tx_hash,broadcast_ts,tx_dict,receipt_dict,source_code,abi,session_id,deployment_id FROM deployments WHERE contract_name = ? ORDER BY broadcast_ts DESC LIMIT ? "
expected_parametrs = ("MockV3Aggregator", 1)

# Assert
Expand Down
21 changes: 21 additions & 0 deletions tests/cli/test_cli_inspect.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import os
import subprocess
from pathlib import Path

from moccasin.commands.inspect import inspect_contract


def test_inspect_layout_imports(mox_path, installation_project_config, installation_temp_path):
current_dir = Path.cwd()
try:
os.chdir(current_dir.joinpath(installation_temp_path))
result = subprocess.run(
[mox_path, "install"], check=True, capture_output=True, text=True
)
result = inspect_contract("MyToken", "storage_layout", print_out=False)
finally:
os.chdir(current_dir)
layout = result["storage_layout"]
# Spot check
assert layout["ow"]["owner"] == {'type': 'address', 'n_slots': 1, 'slot': 0}
assert layout["erc20"]["balanceOf"] == {'type': 'HashMap[address, uint256]', 'n_slots': 1, 'slot': 1}
6 changes: 2 additions & 4 deletions tests/cli/test_cli_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@
import subprocess
from pathlib import Path

from tests.conftest import INSTALL_PROJECT_PATH


def test_run_help(mox_path, installation_cleanup_dependencies):
def test_run_help(mox_path, installation_cleanup_dependencies, installation_temp_path):
current_dir = Path.cwd()
try:
os.chdir(INSTALL_PROJECT_PATH)
os.chdir(installation_temp_path)
result = subprocess.run(
[mox_path, "install", "-h"], check=True, capture_output=True, text=True
)
Expand Down
6 changes: 5 additions & 1 deletion tests/data/installation_project/moccasin.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
[project]
dependencies = ["snekmate", "moccasin"]
dependencies = [
"snekmate",
"moccasin",
"PatrickAlphaC/test_repo",
]

# PRESERVE COMMENTS

Expand Down
Loading

0 comments on commit 29214c1

Please sign in to comment.