diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 94eb5ec04c..baa8decacc 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -6,7 +6,7 @@
### Commit message
-Commit message for the final, squashed PR. (Optional, but reviewers will appreciate it! Please see [our commit message style guide](../../blob/master/docs/style-guide.rst#best-practices-1) for what we would ideally like to see in a commit message.)
+Commit message for the final, squashed PR. (Optional, but reviewers will appreciate it! Please see [our commit message style guide](../../master/docs/style-guide.rst#best-practices-1) for what we would ideally like to see in a commit message.)
### Description for the changelog
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 872d8366e1..c8d7f7d6c4 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -1,4 +1,4 @@
-name: Artifacts
+name: Build artifacts
on:
workflow_dispatch:
@@ -6,10 +6,11 @@ on:
tag:
default: ''
push:
- tags:
- - '*'
branches:
- master
+ pull_request:
+ release:
+ types: [published] # releases and pre-releases (release candidates)
defaults:
run:
@@ -20,10 +21,10 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
- os: [ubuntu-latest, macos-latest]
+ os: [ubuntu-20.04, macos-latest]
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
with:
# grab the commit passed in via `tag`, if any
ref: ${{ github.event.inputs.tag }}
@@ -31,18 +32,20 @@ jobs:
fetch-depth: 0
- name: Python
- uses: actions/setup-python@v2
+ uses: actions/setup-python@v4
with:
- python-version: "3.10"
+ python-version: "3.11"
+ cache: "pip"
- name: Generate Binary
run: >-
- pip install . &&
+ pip install --no-binary pycryptodome --no-binary cbor2 . &&
pip install pyinstaller &&
make freeze
+
- name: Upload Artifact
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
with:
path: dist/vyper.*
@@ -50,7 +53,7 @@ jobs:
runs-on: windows-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
with:
# grab the commit passed in via `tag`, if any
ref: ${{ github.event.inputs.tag }}
@@ -58,9 +61,10 @@ jobs:
fetch-depth: 0
- name: Python
- uses: actions/setup-python@v2
+ uses: actions/setup-python@v4
with:
- python-version: "3.10"
+ python-version: "3.11"
+ cache: "pip"
- name: Generate Binary
run: >-
@@ -69,6 +73,43 @@ jobs:
./make.cmd freeze
- name: Upload Artifact
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
with:
path: dist/vyper.*
+
+ publish-release-assets:
+ needs: [windows-build, unix-build]
+ if: ${{ github.event_name == 'release' }}
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actions/download-artifact@v3
+ with:
+ path: artifacts/
+
+ - name: Upload assets
+ # fun - artifacts are downloaded into "artifact/".
+ working-directory: artifacts/artifact
+ run: |
+ set -Eeuxo pipefail
+ for BIN_NAME in $(ls)
+ do
+ curl -L \
+ --no-progress-meter \
+ -X POST \
+ -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"\
+ -H "Content-Type: application/octet-stream" \
+ "https://uploads.github.com/repos/${{ github.repository }}/releases/${{ github.event.release.id }}/assets?name=${BIN_NAME/+/%2B}" \
+ --data-binary "@${BIN_NAME}"
+ done
+
+ # check build success for pull requests
+ build-success:
+ if: always()
+ runs-on: ubuntu-latest
+ needs: [windows-build, unix-build]
+ steps:
+ - name: check that all builds succeeded
+ if: ${{ contains(needs.*.result, 'failure') }}
+ run: exit 1
diff --git a/.github/workflows/era-tester.yml b/.github/workflows/era-tester.yml
new file mode 100644
index 0000000000..3e0bb3e941
--- /dev/null
+++ b/.github/workflows/era-tester.yml
@@ -0,0 +1,117 @@
+name: Era compiler tester
+
+# run the matter labs compiler test to integrate their test cases
+# this is intended as a diagnostic / spot check to check that we
+# haven't seriously broken the compiler. but, it is not intended as
+# a requirement for merging since we may make changes to our IR
+# which break the downstream backend (at which point, downstream needs
+# to update, which we do not want to be blocked on).
+
+on: [push, pull_request]
+
+concurrency:
+ # cancel older, in-progress jobs from the same PR, same workflow.
+ # use run_id if the job is triggered by a push to ensure
+ # push-triggered jobs to not get canceled.
+ group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
+ cancel-in-progress: true
+
+jobs:
+ era-compiler-tester:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Get latest commit hash
+ run: |
+ echo "ERA_HASH=$( curl -u "u:${{ github.token }}" https://api.github.com/repos/matter-labs/era-compiler-tester/git/ref/heads/main | jq .object.sha | tr -d '"' )" >> $GITHUB_ENV
+ echo "ERA_VYPER_HASH=$( curl -u "u:${{ github.token }}" https://api.github.com/repos/matter-labs/era-compiler-vyper/git/ref/heads/main | jq .object.sha | tr -d '"' )" >> $GITHUB_ENV
+
+ - name: Checkout
+ uses: actions/checkout@v1
+
+ - name: Rust setup
+ uses: actions-rs/toolchain@v1
+ with:
+ toolchain: nightly-2022-11-03
+
+ - name: Set up Python ${{ matrix.python-version[0] }}
+ uses: actions/setup-python@v4
+ with:
+ python-version: ${{ matrix.python-version[0] }}
+ cache: "pip"
+
+ - name: Get cache
+ id: get-cache
+ uses: actions/cache@v3
+ with:
+ path: |
+ ~/.cargo/bin/
+ ~/.cargo/registry/index/
+ ~/.cargo/registry/cache/
+ ~/.cargo/git/db/
+ **/target
+ **/target-llvm
+ **/compiler_tester
+ **/llvm
+ **/era-compiler-tester
+ key: ${{ runner.os }}-${{ env.ERA_HASH }}-${{ env.ERA_VYPER_HASH }}
+
+ - name: Initialize repository and install dependencies
+ if: steps.get-cache.outputs.cache-hit != 'true'
+ run: |
+ git clone --depth 1 https://github.com/matter-labs/era-compiler-tester.git
+ cd era-compiler-tester
+ sed -i 's/ssh:\/\/git@/https:\/\//g' .gitmodules
+ git submodule init
+ git submodule update
+ sudo apt install cmake ninja-build clang-13 lld-13 parallel pkg-config lld
+ cargo install compiler-llvm-builder
+ zkevm-llvm clone && zkevm-llvm build
+ cargo build --release
+
+ - name: Save cache
+ uses: actions/cache/save@v3
+ if: steps.get-cache.outputs.cache-hit != 'true'
+ with:
+ path: |
+ ~/.cargo/bin/
+ ~/.cargo/registry/index/
+ ~/.cargo/registry/cache/
+ ~/.cargo/git/db/
+ **/target
+ **/target-llvm
+ **/compiler_tester
+ **/llvm
+ **/era-compiler-tester
+ key: ${{ runner.os }}-${{ env.ERA_HASH }}-${{ env.ERA_VYPER_HASH }}
+
+ - name: Build Vyper
+ run: |
+ set -Eeuxo pipefail
+ pip install .
+ echo "VYPER_VERSION=$(vyper --version | cut -f1 -d'+')" >> $GITHUB_ENV
+
+ - name: Install Vyper
+ run: |
+ mkdir era-compiler-tester/vyper-bin
+ cp $(which vyper) era-compiler-tester/vyper-bin/vyper-${{ env.VYPER_VERSION }}
+
+ - name: Run tester (fast)
+ # Run era tester with no LLVM optimizations
+ continue-on-error: true
+ if: ${{ github.ref != 'refs/heads/master' }}
+ run: |
+ cd era-compiler-tester
+ cargo run --release --bin compiler-tester -- --path=tests/vyper/ --mode="M0B0 ${{ env.VYPER_VERSION }}"
+
+ - name: Run tester (slow)
+ # Run era tester across the LLVM optimization matrix
+ continue-on-error: true
+ if: ${{ github.ref == 'refs/heads/master' }}
+ run: |
+ cd era-compiler-tester
+ cargo run --release --bin compiler-tester -- --path=tests/vyper/ --mode="M*B* ${{ env.VYPER_VERSION }}"
+
+ - name: Mark as success
+ run: |
+ exit 0
diff --git a/.github/workflows/ghcr.yml b/.github/workflows/ghcr.yml
new file mode 100644
index 0000000000..a35a22e278
--- /dev/null
+++ b/.github/workflows/ghcr.yml
@@ -0,0 +1,75 @@
+name: Deploy docker image to ghcr
+
+# Deploy docker image to ghcr on pushes to master and all releases/tags.
+# Note releases to docker hub are managed separately in another process
+# (github sends webhooks to docker hub which triggers the build there).
+# This workflow is an alternative form of retention for docker images
+# which also allows us to tag and retain every single commit to master.
+
+on:
+ push:
+ branches:
+ - master
+ release:
+ types: [released]
+
+env:
+ REGISTRY: ghcr.io
+ IMAGE_NAME: ${{ github.repository }}
+
+jobs:
+ deploy-ghcr:
+
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ packages: write
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ # need to fetch unshallow so that setuptools_scm can infer the version
+ fetch-depth: 0
+
+ - uses: actions/setup-python@v4
+ name: Install python
+ with:
+ python-version: "3.11"
+ cache: "pip"
+
+ - name: Generate vyper/version.py
+ run: |
+ pip install .
+ echo "VYPER_VERSION=$(PYTHONPATH=. python vyper/cli/vyper_compile.py --version)" >> "$GITHUB_ENV"
+
+ - name: generate tag suffix
+ if: ${{ github.event_name != 'release' }}
+ run: echo "VERSION_SUFFIX=-dev" >> "$GITHUB_ENV"
+
+ - name: Docker meta
+ id: meta
+ uses: docker/metadata-action@v4
+ with:
+ images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
+ tags: |
+ type=ref,event=tag
+ type=raw,value=${{ env.VYPER_VERSION }}${{ env.VERSION_SUFFIX }}
+ type=raw,value=dev,enable=${{ github.ref == 'refs/heads/master' }}
+ type=raw,value=latest,enable=${{ github.event_name == 'release' }}
+
+
+ - name: Login to ghcr.io
+ uses: docker/login-action@v2
+ with:
+ registry: ${{ env.REGISTRY }}
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Build and push
+ uses: docker/build-push-action@v4
+ with:
+ context: .
+ push: true
+ tags: ${{ steps.meta.outputs.tags }}
+ labels: ${{ steps.meta.outputs.labels }}
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index ecc2fa5e1f..f268942e7d 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -1,11 +1,11 @@
-# This workflows will upload a Python Package using Twine when a release is created
+# This workflow will upload a Python Package using Twine when a release is created
# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
-name: Publish
+name: Publish to PyPI
on:
release:
- types: [released]
+ types: [published] # releases and pre-releases (release candidates)
jobs:
@@ -16,9 +16,9 @@ jobs:
- uses: actions/checkout@v2
- name: Python
- uses: actions/setup-python@v2
+ uses: actions/setup-python@v4
with:
- python-version: '3.x'
+ python-version: "3.11"
- name: Install dependencies
run: |
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index f8fcb54f7d..8d23368eb0 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -1,4 +1,4 @@
-name: Test
+name: Run test suite
on: [push, pull_request]
@@ -17,10 +17,11 @@ jobs:
steps:
- uses: actions/checkout@v1
- - name: Set up Python 3.10
- uses: actions/setup-python@v1
+ - name: Set up Python 3.11
+ uses: actions/setup-python@v4
with:
- python-version: "3.10"
+ python-version: "3.11"
+ cache: "pip"
- name: Install Dependencies
run: pip install .[lint]
@@ -42,10 +43,11 @@ jobs:
steps:
- uses: actions/checkout@v1
- - name: Set up Python 3.10
- uses: actions/setup-python@v1
+ - name: Set up Python 3.11
+ uses: actions/setup-python@v4
with:
- python-version: "3.10"
+ python-version: "3.11"
+ cache: "pip"
- name: Install Tox
run: pip install tox
@@ -59,10 +61,11 @@ jobs:
steps:
- uses: actions/checkout@v1
- - name: Set up Python 3.10
- uses: actions/setup-python@v1
+ - name: Set up Python 3.11
+ uses: actions/setup-python@v4
with:
- python-version: "3.10"
+ python-version: "3.11"
+ cache: "pip"
- name: Install Tox
run: pip install tox
@@ -75,11 +78,18 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- python-version: [["3.10", "310", ["3.11", "311"]]]
- # run in default (optimized) and --no-optimize mode
- flag: ["core", "no-opt"]
-
- name: py${{ matrix.python-version[1] }}-${{ matrix.flag }}
+ python-version: [["3.11", "311"]]
+ # run in modes: --optimize [gas, none, codesize]
+ opt-mode: ["gas", "none", "codesize"]
+ debug: [true, false]
+ # run across other python versions.# we don't really need to run all
+ # modes across all python versions - one is enough
+ include:
+ - python-version: ["3.10", "310"]
+ opt-mode: gas
+ debug: false
+
+ name: py${{ matrix.python-version[1] }}-opt-${{ matrix.opt-mode }}${{ matrix.debug && '-debug' || '' }}
steps:
- uses: actions/checkout@v1
@@ -88,12 +98,13 @@ jobs:
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version[0] }}
+ cache: "pip"
- name: Install Tox
run: pip install tox
- name: Run Tox
- run: TOXENV=py${{ matrix.python-version[1] }}-${{ matrix.flag }} tox -r -- --reruns 10 --reruns-delay 1 -r aR tests/
+ run: TOXENV=py${{ matrix.python-version[1] }} tox -r -- --optimize ${{ matrix.opt-mode }} ${{ matrix.debug && '--enable-compiler-debug-mode' || '' }} -r aR tests/
- name: Upload Coverage
uses: codecov/codecov-action@v1
@@ -126,22 +137,23 @@ jobs:
steps:
- uses: actions/checkout@v1
- - name: Set up Python 3.10
- uses: actions/setup-python@v1
+ - name: Set up Python 3.11
+ uses: actions/setup-python@v4
with:
- python-version: "3.10"
+ python-version: "3.11"
+ cache: "pip"
- name: Install Tox
run: pip install tox
# fetch test durations
# NOTE: if the tests get poorly distributed, run this and commit the resulting `.test_durations` file to the `vyper-test-durations` repo.
- # `TOXENV=fuzzing tox -r -- --store-durations --reruns 10 --reruns-delay 1 -r aR tests/`
+ # `TOXENV=fuzzing tox -r -- --store-durations -r aR tests/`
- name: Fetch test-durations
- run: curl --location "https://raw.githubusercontent.com/vyperlang/vyper-test-durations/ac71e77863d7f4e7e7cd19a93cf50a8c39de4845/test_durations" -o .test_durations
+ run: curl --location "https://raw.githubusercontent.com/vyperlang/vyper-test-durations/5982755ee8459f771f2e8622427c36494646e1dd/test_durations" -o .test_durations
- name: Run Tox
- run: TOXENV=fuzzing tox -r -- --splits 60 --group ${{ matrix.group }} --splitting-algorithm least_duration --reruns 10 --reruns-delay 1 -r aR tests/
+ run: TOXENV=fuzzing tox -r -- --splits 60 --group ${{ matrix.group }} --splitting-algorithm least_duration -r aR tests/
- name: Upload Coverage
uses: codecov/codecov-action@v1
@@ -167,10 +179,11 @@ jobs:
steps:
- uses: actions/checkout@v1
- - name: Set up Python 3.10
- uses: actions/setup-python@v1
+ - name: Set up Python 3.11
+ uses: actions/setup-python@v4
with:
- python-version: "3.10"
+ python-version: "3.11"
+ cache: "pip"
- name: Install Tox
run: pip install tox
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 739e977c96..4b416a4414 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -11,7 +11,7 @@ repos:
- id: black
name: black
-- repo: https://gitlab.com/pycqa/flake8
+- repo: https://github.com/PyCQA/flake8
rev: 3.9.2
hooks:
- id: flake8
diff --git a/Dockerfile b/Dockerfile
index c2245ee981..bc5bb607d6 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM python:3.10-slim
+FROM python:3.11-slim
# Specify label-schema specific arguments and labels.
ARG BUILD_DATE
diff --git a/FUNDING.yml b/FUNDING.yml
index 81e82160d0..efb9eb01b7 100644
--- a/FUNDING.yml
+++ b/FUNDING.yml
@@ -1 +1 @@
-custom: https://gitcoin.co/grants/200/vyper-smart-contract-language-2
+custom: https://etherscan.io/address/0x70CCBE10F980d80b7eBaab7D2E3A73e87D67B775
diff --git a/Makefile b/Makefile
index daa1c2bfc9..645b800e79 100644
--- a/Makefile
+++ b/Makefile
@@ -43,7 +43,7 @@ freeze: clean init
echo Generating binary...
export OS="$$(uname -s | tr A-Z a-z)" && \
export VERSION="$$(PYTHONPATH=. python vyper/cli/vyper_compile.py --version)" && \
- pyinstaller --clean --onefile vyper/cli/vyper_compile.py --name "vyper.$${VERSION}.$${OS}" --add-data vyper:vyper
+ pyinstaller --target-architecture=universal2 --clean --onefile vyper/cli/vyper_compile.py --name "vyper.$${VERSION}.$${OS}" --add-data vyper:vyper
clean: clean-build clean-docs clean-pyc clean-test
diff --git a/README.md b/README.md
index f17e693bf5..bad929956d 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,16 @@
+**Vyper compiler security audit competition starts 14th September with $150k worth of bounties.** [See the competition on CodeHawks](https://www.codehawks.com/contests/cll5rujmw0001js08menkj7hc) and find [more details in this blog post](https://mirror.xyz/0xBA41A04A14aeaEec79e2D694B21ba5Ab610982f1/WTZ3l3MLhTz9P4avq6JqipN5d4HJNiUY-d8zT0pfmXg).
-[![Build Status](https://github.com/vyperlang/vyper/workflows/Test/badge.svg)](https://github.com/vyperlang/vyper/actions)
+[![Build Status](https://github.com/vyperlang/vyper/workflows/Test/badge.svg)](https://github.com/vyperlang/vyper/actions/workflows/test.yml)
[![Documentation Status](https://readthedocs.org/projects/vyper/badge/?version=latest)](http://vyper.readthedocs.io/en/latest/?badge=latest "ReadTheDocs")
[![Discord](https://img.shields.io/discord/969926564286459934.svg?label=%23vyper)](https://discord.gg/6tw7PTM7C2)
[![PyPI](https://badge.fury.io/py/vyper.svg)](https://pypi.org/project/vyper "PyPI")
-[![Docker](https://images.microbadger.com/badges/version/vyperlang/vyper.svg)](https://hub.docker.com/r/vyperlang/vyper "DockerHub")
+[![Docker](https://img.shields.io/docker/cloud/build/vyperlang/vyper)](https://hub.docker.com/r/vyperlang/vyper "DockerHub")
[![Coverage Status](https://codecov.io/gh/vyperlang/vyper/branch/master/graph/badge.svg)](https://codecov.io/gh/vyperlang/vyper "Codecov")
-[![Language grade: Python](https://img.shields.io/lgtm/grade/python/g/vyperlang/vyper.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/vyperlang/vyper/context:python)
+[![Language grade: Python](https://github.com/vyperlang/vyper/workflows/CodeQL/badge.svg)](https://github.com/vyperlang/vyper/actions/workflows/codeql.yml)
# Getting Started
See [Installing Vyper](http://vyper.readthedocs.io/en/latest/installing-vyper.html) to install vyper.
diff --git a/SECURITY.md b/SECURITY.md
index c7bdad4ee7..0a054b2c93 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -48,7 +48,7 @@ https://github.com/vyperlang/vyper/security/advisories
If you think you have found a security vulnerability with a project that has used Vyper,
please report the vulnerability to the relevant project's security disclosure program prior
-to reporting to us. If one is not available, please email your vulnerability to security@vyperlang.org.
+to reporting to us. If one is not available, submit it at https://github.com/vyperlang/vyper/security/advisories.
**Please Do Not Log An Issue** mentioning the vulnerability.
diff --git a/docs/built-in-functions.rst b/docs/built-in-functions.rst
index 74e8560498..45cf9ec8c2 100644
--- a/docs/built-in-functions.rst
+++ b/docs/built-in-functions.rst
@@ -184,13 +184,14 @@ Vyper has three built-ins for contract creation; all three contract creation bui
The implementation of ``create_copy_of`` assumes that the code at ``target`` is smaller than 16MB. While this is much larger than the EIP-170 constraint of 24KB, it is a conservative size limit intended to future-proof deployer contracts in case the EIP-170 constraint is lifted. If the code at ``target`` is larger than 16MB, the behavior of ``create_copy_of`` is undefined.
-.. py:function:: create_from_blueprint(target: address, *args, value: uint256 = 0, code_offset=0, [, salt: bytes32]) -> address
+.. py:function:: create_from_blueprint(target: address, *args, value: uint256 = 0, raw_args: bool = False, code_offset: int = 0, [, salt: bytes32]) -> address
Copy the code of ``target`` into memory and execute it as initcode. In other words, this operation interprets the code at ``target`` not as regular runtime code, but directly as initcode. The ``*args`` are interpreted as constructor arguments, and are ABI-encoded and included when executing the initcode.
* ``target``: Address of the blueprint to invoke
* ``*args``: Constructor arguments to forward to the initcode.
* ``value``: The wei value to send to the new contract address (Optional, default 0)
+ * ``raw_args``: If ``True``, ``*args`` must be a single ``Bytes[...]`` argument, which will be interpreted as a raw bytes buffer to forward to the create operation (which is useful for instance, if pre- ABI-encoded data is passed in from elsewhere). (Optional, default ``False``)
* ``code_offset``: The offset to start the ``EXTCODECOPY`` from (Optional, default 0)
* ``salt``: A ``bytes32`` value utilized by the deterministic ``CREATE2`` opcode (Optional, if not supplied, ``CREATE`` is used)
@@ -201,7 +202,7 @@ Vyper has three built-ins for contract creation; all three contract creation bui
@external
def foo(blueprint: address) -> address:
arg1: uint256 = 18
- arg2: String = "some string"
+ arg2: String[32] = "some string"
return create_from_blueprint(blueprint, arg1, arg2, code_offset=1)
.. note::
@@ -226,7 +227,7 @@ Vyper has three built-ins for contract creation; all three contract creation bui
* ``to``: Destination address to call to
* ``data``: Data to send to the destination address
* ``max_outsize``: Maximum length of the bytes array returned from the call. If the returned call data exceeds this length, only this number of bytes is returned. (Optional, default ``0``)
- * ``gas``: The amount of gas to attach to the call. If not set, all remaining gas is forwarded.
+ * ``gas``: The amount of gas to attach to the call. (Optional, defaults to ``msg.gas``).
* ``value``: The wei value to send to the address (Optional, default ``0``)
* ``is_delegate_call``: If ``True``, the call will be sent as ``DELEGATECALL`` (Optional, default ``False``)
* ``is_static_call``: If ``True``, the call will be sent as ``STATICCALL`` (Optional, default ``False``)
@@ -264,6 +265,10 @@ Vyper has three built-ins for contract creation; all three contract creation bui
assert success
return response
+ .. note::
+
+ Regarding "forwarding all gas", note that, while Vyper will provide ``msg.gas`` to the call, in practice, there are some subtleties around forwarding all remaining gas on the EVM which are out of scope of this documentation and could be subject to change. For instance, see the language in EIP-150 around "all but one 64th".
+
.. py:function:: raw_log(topics: bytes32[4], data: Union[Bytes, bytes32]) -> None
Provides low level access to the ``LOG`` opcodes, emitting a log without having to specify an ABI type.
@@ -379,7 +384,11 @@ Cryptography
* ``s``: second 32 bytes of signature
* ``v``: final 1 byte of signature
- Returns the associated address, or ``0`` on error.
+ Returns the associated address, or ``empty(address)`` on error.
+
+ .. note::
+
+ Prior to Vyper ``0.3.10``, the ``ecrecover`` function could return an undefined (possibly nonzero) value for invalid inputs to ``ecrecover``. For more information, please see `GHSA-f5x6-7qgp-jhf3 `_.
.. code-block:: python
@@ -496,7 +505,7 @@ Data Manipulation
* ``b``: ``Bytes`` list to extract from
* ``start``: Start point to extract from
- * ``output_type``: Type of output (``bytes32``, ``integer``, or ``address``). Defaults to ``bytes32``.
+ * ``output_type``: Type of output (``bytesM``, ``integer``, or ``address``). Defaults to ``bytes32``.
Returns a value of the type specified by ``output_type``.
@@ -573,6 +582,24 @@ Math
>>> ExampleContract.foo(3.1337)
4
+.. py:function:: epsilon(typename) -> Any
+
+ Returns the smallest non-zero value for a decimal type.
+
+ * ``typename``: Name of the decimal type (currently only ``decimal``)
+
+ .. code-block:: python
+
+ @external
+ @view
+ def foo() -> decimal:
+ return epsilon(decimal)
+
+ .. code-block:: python
+
+ >>> ExampleContract.foo()
+ Decimal('1E-10')
+
.. py:function:: floor(value: decimal) -> int256
Round a decimal down to the nearest integer.
diff --git a/docs/compiling-a-contract.rst b/docs/compiling-a-contract.rst
index 4a03347536..b529d1efb1 100644
--- a/docs/compiling-a-contract.rst
+++ b/docs/compiling-a-contract.rst
@@ -99,6 +99,11 @@ See :ref:`searching_for_imports` for more information on Vyper's import system.
Online Compilers
================
+Try VyperLang!
+-----------------
+
+`Try VyperLang! `_ is a JupterHub instance hosted by the Vyper team as a sandbox for developing and testing contracts in Vyper. It requires github for login, and supports deployment via the browser.
+
Remix IDE
---------
@@ -108,23 +113,51 @@ Remix IDE
While the Vyper version of the Remix IDE compiler is updated on a regular basis, it might be a bit behind the latest version found in the master branch of the repository. Make sure the byte code matches the output from your local compiler.
+.. _optimization-mode:
+
+Compiler Optimization Modes
+===========================
+
+The vyper CLI tool accepts an optimization mode ``"none"``, ``"codesize"``, or ``"gas"`` (default). It can be set using the ``--optimize`` flag. For example, invoking ``vyper --optimize codesize MyContract.vy`` will compile the contract, optimizing for code size. As a rough summary of the differences between gas and codesize mode, in gas optimized mode, the compiler will try to generate bytecode which minimizes gas (up to a point), including:
+
+* using a sparse selector table which optimizes for gas over codesize
+* inlining some constants, and
+* trying to unroll some loops, especially for data copies.
+
+In codesize optimized mode, the compiler will try hard to minimize codesize by
+
+* using a dense selector table
+* out-lining code, and
+* using more loops for data copies.
+
+
+.. _evm-version:
Setting the Target EVM Version
==============================
-When you compile your contract code, you can specify the Ethereum Virtual Machine version to compile for, to avoid particular features or behaviours.
+When you compile your contract code, you can specify the target Ethereum Virtual Machine version to compile for, to access or avoid particular features. You can specify the version either with a source code pragma or as a compiler option. It is recommended to use the compiler option when you want flexibility (for instance, ease of deploying across different chains), and the source code pragma when you want bytecode reproducibility (for instance, when verifying code on a block explorer).
+
+.. note::
+ If the evm version specified by the compiler options conflicts with the source code pragma, an exception will be raised and compilation will not continue.
+
+For instance, the adding the following pragma to a contract indicates that it should be compiled for the "shanghai" fork of the EVM.
+
+.. code-block:: python
+
+ #pragma evm-version shanghai
.. warning::
- Compiling for the wrong EVM version can result in wrong, strange and failing behaviour. Please ensure, especially if running a private chain, that you use matching EVM versions.
+ Compiling for the wrong EVM version can result in wrong, strange, or failing behavior. Please ensure, especially if running a private chain, that you use matching EVM versions.
-When compiling via ``vyper``, include the ``--evm-version`` flag:
+When compiling via the ``vyper`` CLI, you can specify the EVM version option using the ``--evm-version`` flag:
::
$ vyper --evm-version [VERSION]
-When using the JSON interface, include the ``"evmVersion"`` key within the ``"settings"`` field:
+When using the JSON interface, you can include the ``"evmVersion"`` key within the ``"settings"`` field:
.. code-block:: javascript
@@ -140,24 +173,33 @@ Target Options
The following is a list of supported EVM versions, and changes in the compiler introduced with each version. Backward compatibility is not guaranteed between each version.
-.. py:attribute:: byzantium
+.. py:attribute:: istanbul
- - The oldest EVM version supported by Vyper.
+ - The ``CHAINID`` opcode is accessible via ``chain.id``
+ - The ``SELFBALANCE`` opcode is used for calls to ``self.balance``
+ - Gas estimates changed for ``SLOAD`` and ``BALANCE``
-.. py:attribute:: constantinople
+.. py:attribute:: berlin
- - The ``EXTCODEHASH`` opcode is accessible via ``address.codehash``
- - ``shift`` makes use of ``SHL``/``SHR`` opcodes.
+ - Gas estimates changed for ``EXTCODESIZE``, ``EXTCODECOPY``, ``EXTCODEHASH``, ``SLOAD``, ``SSTORE``, ``CALL``, ``CALLCODE``, ``DELEGATECALL`` and ``STATICCALL``
+ - Functions marked with ``@nonreentrant`` are protected with different values (3 and 2) than contracts targeting pre-berlin.
+ - ``BASEFEE`` is accessible via ``block.basefee``
-.. py:attribute:: petersburg
+.. py:attribute:: paris
- - The compiler behaves the same way as with constantinople.
+ - ``block.difficulty`` is deprecated in favor of its new alias, ``block.prevrandao``.
+
+.. py:attribute:: shanghai (default)
+
+ - The ``PUSH0`` opcode is automatically generated by the compiler instead of ``PUSH1 0``
+
+.. py:attribute:: cancun (experimental)
+
+ - The ``transient`` keyword allows declaration of variables which live in transient storage
+ - Functions marked with ``@nonreentrant`` are protected with TLOAD/TSTORE instead of SLOAD/SSTORE
+ - The ``MCOPY`` opcode will be generated automatically by the compiler for most memory operations.
-.. py:attribute:: istanbul (default)
- - The ``CHAINID`` opcode is accessible via ``chain.id``
- - The ``SELFBALANCE`` opcode is used for calls to ``self.balance``
- - Gas estimates changed for ``SLOAD`` and ``BALANCE``
Compiler Input and Output JSON Description
@@ -204,10 +246,11 @@ The following example describes the expected input format of ``vyper-json``. Com
},
// Optional
"settings": {
- "evmVersion": "istanbul", // EVM version to compile for. Can be byzantium, constantinople, petersburg or istanbul.
- // optional, whether or not optimizations are turned on
- // defaults to true
- "optimize": true,
+ "evmVersion": "shanghai", // EVM version to compile for. Can be istanbul, berlin, paris, shanghai (default) or cancun (experimental!).
+ // optional, optimization mode
+ // defaults to "gas". can be one of "gas", "codesize", "none",
+ // false and true (the last two are for backwards compatibility).
+ "optimize": "gas",
// optional, whether or not the bytecode should include Vyper's signature
// defaults to true
"bytecodeMetadata": true,
diff --git a/docs/control-structures.rst b/docs/control-structures.rst
index a89f36f7cc..873135709a 100644
--- a/docs/control-structures.rst
+++ b/docs/control-structures.rst
@@ -36,8 +36,15 @@ External functions (marked with the ``@external`` decorator) are a part of the c
def add_seven(a: int128) -> int128:
return a + 7
+ @external
+ def add_seven_with_overloading(a: uint256, b: uint256 = 3):
+ return a + b
+
A Vyper contract cannot call directly between two external functions. If you must do this, you can use an :ref:`interface `.
+.. note::
+ For external functions with default arguments like ``def my_function(x: uint256, b: uint256 = 1)`` the Vyper compiler will generate ``N+1`` overloaded function selectors based on ``N`` default arguments.
+
.. _structure-functions-internal:
Internal Functions
@@ -48,13 +55,15 @@ Internal functions (marked with the ``@internal`` decorator) are only accessible
.. code-block:: python
@internal
- def _times_two(amount: uint256) -> uint256:
- return amount * 2
+ def _times_two(amount: uint256, two: uint256 = 2) -> uint256:
+ return amount * two
@external
def calculate(amount: uint256) -> uint256:
return self._times_two(amount)
+.. note::
+ Since calling an ``internal`` function is realized by jumping to its entry label, the internal function dispatcher ensures the correctness of the jumps. Please note that for ``internal`` functions which use more than one default parameter, Vyper versions ``>=0.3.8`` are strongly recommended due to the security advisory `GHSA-ph9x-4vc9-m39g `_.
Mutability
----------
@@ -262,16 +271,25 @@ Ranges are created using the ``range`` function. The following examples are vali
``STOP`` is a literal integer greater than zero. ``i`` begins as zero and increments by one until it is equal to ``STOP``.
+.. code-block:: python
+
+ for i in range(stop, bound=N):
+ ...
+
+Here, ``stop`` can be a variable with integer type, greater than zero. ``N`` must be a compile-time constant. ``i`` begins as zero and increments by one until it is equal to ``stop``. If ``stop`` is larger than ``N``, execution will revert at runtime. In certain cases, you may not have a guarantee that ``stop`` is less than ``N``, but still want to avoid the possibility of runtime reversion. To accomplish this, use the ``bound=`` keyword in combination with ``min(stop, N)`` as the argument to ``range``, like ``range(min(stop, N), bound=N)``. This is helpful for use cases like chunking up operations on larger arrays across multiple transactions.
+
+Another use of range can be with ``START`` and ``STOP`` bounds.
+
.. code-block:: python
for i in range(START, STOP):
...
-``START`` and ``STOP`` are literal integers, with ``STOP`` being a greater value than ``START``. ``i`` begins as ``START`` and increments by one until it is equal to ``STOP``.
+Here, ``START`` and ``STOP`` are literal integers, with ``STOP`` being a greater value than ``START``. ``i`` begins as ``START`` and increments by one until it is equal to ``STOP``.
.. code-block:: python
for i in range(a, a + N):
...
-``a`` is a variable with an integer type and ``N`` is a literal integer greater than zero. ``i`` begins as ``a`` and increments by one until it is equal to ``a + N``.
+``a`` is a variable with an integer type and ``N`` is a literal integer greater than zero. ``i`` begins as ``a`` and increments by one until it is equal to ``a + N``. If ``a + N`` would overflow, execution will revert.
diff --git a/docs/installing-vyper.rst b/docs/installing-vyper.rst
index 2e2d51bd6e..fb2849708d 100644
--- a/docs/installing-vyper.rst
+++ b/docs/installing-vyper.rst
@@ -76,9 +76,13 @@ Each tagged version of vyper is uploaded to `pypi s/`/``/g
+ to convert links to nice rst links:
+ :'<,'>s/\v(https:\/\/github.com\/vyperlang\/vyper\/pull\/)(\d+)/(`#\2 <\1\2>`_)/g
+ ex. in: https://github.com/vyperlang/vyper/pull/3373
+ ex. out: (`#3373 `_)
+ for advisory links:
+ :'<,'>s/\v(https:\/\/github.com\/vyperlang\/vyper\/security\/advisories\/)([-A-Za-z0-9]+)/(`\2 <\1\2>`_)/g
+
+v0.3.10 ("Black Adder")
+***********************
+
+Date released: 2023-10-04
+=========================
+
+v0.3.10 is a performance focused release that additionally ships numerous bugfixes. It adds a ``codesize`` optimization mode (`#3493 `_), adds new vyper-specific ``#pragma`` directives (`#3493 `_), uses Cancun's ``MCOPY`` opcode for some compiler generated code (`#3483 `_), and generates selector tables which now feature O(1) performance (`#3496 `_).
+
+Breaking changes:
+-----------------
+
+- add runtime code layout to initcode (`#3584 `_)
+- drop evm versions through istanbul (`#3470 `_)
+- remove vyper signature from runtime (`#3471 `_)
+- only allow valid identifiers to be nonreentrant keys (`#3605 `_)
+
+Non-breaking changes and improvements:
+--------------------------------------
+
+- O(1) selector tables (`#3496 `_)
+- implement bound= in ranges (`#3537 `_, `#3551 `_)
+- add optimization mode to vyper compiler (`#3493 `_)
+- improve batch copy performance (`#3483 `_, `#3499 `_, `#3525 `_)
+
+Notable fixes:
+--------------
+
+- fix ``ecrecover()`` behavior when signature is invalid (`GHSA-f5x6-7qgp-jhf3 `_, `#3586 `_)
+- fix: order of evaluation for some builtins (`#3583 `_, `#3587 `_)
+- fix: memory allocation in certain builtins using ``msize`` (`#3610 `_)
+- fix: ``_abi_decode()`` input validation in certain complex expressions (`#3626 `_)
+- fix: pycryptodome for arm builds (`#3485 `_)
+- let params of internal functions be mutable (`#3473 `_)
+- typechecking of folded builtins in (`#3490 `_)
+- update tload/tstore opcodes per latest 1153 EIP spec (`#3484 `_)
+- fix: raw_call type when max_outsize=0 is set (`#3572 `_)
+- fix: implements check for indexed event arguments (`#3570 `_)
+- fix: type-checking for ``_abi_decode()`` arguments (`#3626 `_)
+
+Other docs updates, chores and fixes:
+-------------------------------------
+
+- relax restrictions on internal function signatures (`#3573 `_)
+- note on security advisory in release notes for versions ``0.2.15``, ``0.2.16``, and ``0.3.0`` (`#3553 `_)
+- fix: yanked version in release notes (`#3545 `_)
+- update release notes on yanked versions (`#3547 `_)
+- improve error message for conflicting methods IDs (`#3491 `_)
+- document epsilon builtin (`#3552 `_)
+- relax version pragma parsing (`#3511 `_)
+- fix: issue with finding installed packages in editable mode (`#3510 `_)
+- add note on security advisory for ``ecrecover`` in docs (`#3539 `_)
+- add ``asm`` option to cli help (`#3585 `_)
+- add message to error map for repeat range check (`#3542 `_)
+- fix: public constant arrays (`#3536 `_)
+
+
+v0.3.9 ("Common Adder")
+***********************
+
+Date released: 2023-05-29
+
+This is a patch release fix for v0.3.8. @bout3fiddy discovered a codesize regression for blueprint contracts in v0.3.8 which is fixed in this release. @bout3fiddy also discovered a runtime performance (gas) regression for default functions in v0.3.8 which is fixed in this release.
+
+Fixes:
+
+- initcode codesize blowup (`#3450 `_)
+- add back global calldatasize check for contracts with default fn (`#3463 `_)
+
+
+v0.3.8
+******
+
+Date released: 2023-05-23
+
+Non-breaking changes and improvements:
+
+- ``transient`` storage keyword (`#3373 `_)
+- ternary operators (`#3398 `_)
+- ``raw_revert()`` builtin (`#3136 `_)
+- shift operators (`#3019 `_)
+- make ``send()`` gas stipend configurable (`#3158 `_)
+- use new ``push0`` opcode (`#3361 `_)
+- python 3.11 support (`#3129 `_)
+- drop support for python 3.8 and 3.9 (`#3325 `_)
+- build for ``aarch64`` (`#2687 `_)
+
+Note that with the addition of ``push0`` opcode, ``shanghai`` is now the default compilation target for vyper. When deploying to a chain which does not support ``shanghai``, it is recommended to set ``--evm-version`` to ``paris``, otherwise it could result in hard-to-debug errors.
+
+Major refactoring PRs:
+
+- refactor front-end type system (`#2974 `_)
+- merge front-end and codegen type systems (`#3182 `_)
+- simplify ``GlobalContext`` (`#3209 `_)
+- remove ``FunctionSignature`` (`#3390 `_)
+
+Notable fixes:
+
+- assignment when rhs is complex type and references lhs (`#3410 `_)
+- uninitialized immutable values (`#3409 `_)
+- success value when mixing ``max_outsize=0`` and ``revert_on_failure=False`` (`GHSA-w9g2-3w7p-72g9 `_)
+- block certain kinds of storage allocator overflows (`GHSA-mgv8-gggw-mrg6 `_)
+- store-before-load when a dynarray appears on both sides of an assignment (`GHSA-3p37-3636-q8wv `_)
+- bounds check for loops of the form ``for i in range(x, x+N)`` (`GHSA-6r8q-pfpv-7cgj `_)
+- alignment of call-site posargs and kwargs for internal functions (`GHSA-ph9x-4vc9-m39g `_)
+- batch nonpayable check for default functions calldatasize < 4 (`#3104 `_, `#3408 `_, cf. `GHSA-vxmm-cwh2-q762 `_)
+
+Other docs updates, chores and fixes:
+
+- call graph stability (`#3370 `_)
+- fix ``vyper-serve`` output (`#3338 `_)
+- add ``custom:`` natspec tags (`#3403 `_)
+- add missing pc maps to ``vyper_json`` output (`#3333 `_)
+- fix constructor context for internal functions (`#3388 `_)
+- add deprecation warning for ``selfdestruct`` usage (`#3372 `_)
+- add bytecode metadata option to vyper-json (`#3117 `_)
+- fix compiler panic when a ``break`` is outside of a loop (`#3177 `_)
+- fix complex arguments to builtin functions (`#3167 `_)
+- add support for all types in ABI imports (`#3154 `_)
+- disable uadd operator (`#3174 `_)
+- block bitwise ops on decimals (`#3219 `_)
+- raise ``UNREACHABLE`` (`#3194 `_)
+- allow enum as mapping key (`#3256 `_)
+- block boolean ``not`` operator on numeric types (`#3231 `_)
+- enforce that loop's iterators are valid names (`#3242 `_)
+- fix typechecker hotspot (`#3318 `_)
+- rewrite typechecker journal to handle nested commits (`#3375 `_)
+- fix missing pc map for empty functions (`#3202 `_)
+- guard against iterating over empty list in for loop (`#3197 `_)
+- skip enum members during constant folding (`#3235 `_)
+- bitwise ``not`` constant folding (`#3222 `_)
+- allow accessing members of constant address (`#3261 `_)
+- guard against decorators in interface (`#3266 `_)
+- fix bounds for decimals in some builtins (`#3283 `_)
+- length of literal empty bytestrings (`#3276 `_)
+- block ``empty()`` for HashMaps (`#3303 `_)
+- fix type inference for empty lists (`#3377 `_)
+- disallow logging from ``pure``, ``view`` functions (`#3424 `_)
+- improve optimizer rules for comparison operators (`#3412 `_)
+- deploy to ghcr on push (`#3435 `_)
+- add note on return value bounds in interfaces (`#3205 `_)
+- index ``id`` param in ``URI`` event of ``ERC1155ownable`` (`#3203 `_)
+- add missing ``asset`` function to ``ERC4626`` built-in interface (`#3295 `_)
+- clarify ``skip_contract_check=True`` can result in undefined behavior (`#3386 `_)
+- add ``custom`` NatSpec tag to docs (`#3404 `_)
+- fix ``uint256_addmod`` doc (`#3300 `_)
+- document optional kwargs for external calls (`#3122 `_)
+- remove ``slice()`` length documentation caveats (`#3152 `_)
+- fix docs of ``blockhash`` to reflect revert behaviour (`#3168 `_)
+- improvements to compiler error messages (`#3121 `_, `#3134 `_, `#3312 `_, `#3304 `_, `#3240 `_, `#3264 `_, `#3343 `_, `#3307 `_, `#3313 `_ and `#3215 `_)
+
+These are really just the highlights, as many other bugfixes, docs updates and refactoring (over 150 pull requests!) made it into this release! For the full list, please see the `changelog `_. Special thanks to contributions from @tserg, @trocher, @z80dev, @emc415 and @benber86 in this release!
+
+New Contributors:
+
+- @omahs made their first contribution in (`#3128 `_)
+- @ObiajuluM made their first contribution in (`#3124 `_)
+- @trocher made their first contribution in (`#3134 `_)
+- @ozmium22 made their first contribution in (`#3149 `_)
+- @ToonVanHove made their first contribution in (`#3168 `_)
+- @emc415 made their first contribution in (`#3158 `_)
+- @lgtm-com made their first contribution in (`#3147 `_)
+- @tdurieux made their first contribution in (`#3224 `_)
+- @victor-ego made their first contribution in (`#3263 `_)
+- @miohtama made their first contribution in (`#3257 `_)
+- @kelvinfan001 made their first contribution in (`#2687 `_)
+
+
v0.3.7
******
Date released: 2022-09-26
-## Breaking changes:
+Breaking changes:
- chore: drop python 3.7 support (`#3071 `_)
- fix: relax check for statically sized calldata (`#3090 `_)
-## Non-breaking changes and improvements:
+Non-breaking changes and improvements:
-- fix: assert description in Crowdfund.finalize() (`#3058 `_)
+- fix: assert description in ``Crowdfund.finalize()`` (`#3058 `_)
- fix: change mutability of example ERC721 interface (`#3076 `_)
- chore: improve error message for non-checksummed address literal (`#3065 `_)
-- feat: isqrt built-in (`#3074 `_) (`#3069 `_)
-- feat: add `block.prevrandao` as alias for `block.difficulty` (`#3085 `_)
-- feat: epsilon builtin (`#3057 `_)
+- feat: ``isqrt()`` builtin (`#3074 `_) (`#3069 `_)
+- feat: add ``block.prevrandao`` as alias for ``block.difficulty`` (`#3085 `_)
+- feat: ``epsilon()`` builtin (`#3057 `_)
- feat: extend ecrecover signature to accept additional parameter types (`#3084 `_)
- feat: allow constant and immutable variables to be declared public (`#3024 `_)
- feat: optionally disable metadata in bytecode (`#3107 `_)
-## Bugfixes:
+Bugfixes:
- fix: empty nested dynamic arrays (`#3061 `_)
- fix: foldable builtin default args in imports (`#3079 `_) (`#3077 `_)
-## Additional changes and improvements:
+Additional changes and improvements:
- doc: update broken links in SECURITY.md (`#3095 `_)
- chore: update discord link in docs (`#3031 `_)
@@ -40,7 +218,7 @@ Date released: 2022-09-26
- chore: migrate lark grammar (`#3082 `_)
- chore: loosen and upgrade semantic version (`#3106 `_)
-# New Contributors
+New Contributors
- @emilianobonassi made their first contribution in `#3107 `_
- @unparalleled-js made their first contribution in `#3106 `_
@@ -65,6 +243,7 @@ Bugfixes:
v0.3.5
******
+**THIS RELEASE HAS BEEN PULLED**
Date released: 2022-08-05
@@ -213,6 +392,7 @@ Special thanks to @skellet0r for some major features in this release!
v0.3.0
*******
+⚠️ A critical security vulnerability has been discovered in this version and we strongly recommend using version `0.3.1 `_ or higher. For more information, please see the Security Advisory `GHSA-5824-cm3x-3c38 `_.
Date released: 2021-10-04
@@ -245,6 +425,7 @@ Special thanks to contributions from @skellet0r and @benjyz for this release!
v0.2.16
*******
+⚠️ A critical security vulnerability has been discovered in this version and we strongly recommend using version `0.3.1 `_ or higher. For more information, please see the Security Advisory `GHSA-5824-cm3x-3c38 `_.
Date released: 2021-08-27
@@ -269,6 +450,7 @@ Special thanks to contributions from @skellet0r, @sambacha and @milancermak for
v0.2.15
*******
+⚠️ A critical security vulnerability has been discovered in this version and we strongly recommend using version `0.3.1 `_ or higher. For more information, please see the Security Advisory `GHSA-5824-cm3x-3c38 `_.
Date released: 23-07-2021
@@ -281,6 +463,7 @@ Fixes:
v0.2.14
*******
+**THIS RELEASE HAS BEEN PULLED**
Date released: 20-07-2021
@@ -399,6 +582,7 @@ Fixes:
v0.2.6
******
+**THIS RELEASE HAS BEEN PULLED**
Date released: 10-10-2020
@@ -652,7 +836,7 @@ The following VIPs were implemented for Beta 13:
- Add ``vyper-json`` compilation mode (VIP `#1520 `_)
- Environment variables and constants can now be used as default parameters (VIP `#1525 `_)
-- Require unitialized memory be set on creation (VIP `#1493 `_)
+- Require uninitialized memory be set on creation (VIP `#1493 `_)
Some of the bug and stability fixes:
diff --git a/docs/resources.rst b/docs/resources.rst
index 295a104fcf..a3dfa480ed 100644
--- a/docs/resources.rst
+++ b/docs/resources.rst
@@ -3,45 +3,47 @@
Other resources and learning material
#####################################
-Vyper has an active community. You can find third party tutorials,
-examples, courses and other learning material.
+Vyper has an active community. You can find third-party tutorials, examples, courses, and other learning material.
General
-------
-- `Ape Academy - Learn how to build vyper projects by ApeWorX`__
-- `More Vyper by Example by Smart Contract Engineer`__
-- `Vyper cheat Sheet `__
-- `Vyper Hub for development `__
-- `Vyper greatest hits smart contract examples `__
+- `Ape Academy – Learn how to build Vyper projects `_ by ApeWorX
+- `More Vyper by Example `_ by Smart Contract Engineer
+- `Vyper cheat Sheet `_
+- `Vyper Hub for development `_
+- `Vyper greatest hits smart contract examples `_
+- `A curated list of Vyper resources, libraries, tools, and more `_
Frameworks and tooling
----------------------
-- `ApeWorX - The Ethereum development framework for Python Developers, Data Scientists, and Security Professionals `__
-- `Foundry x Vyper - Foundry template to compile Vyper contracts `__
-- `Snekmate - Vyper smart contract building blocks `__
-- `Serpentor - A set of smart contracts tools for governance `__
-- `Smart contract development frameworks and tools for Vyper on Ethreum.org `__
+- `Titanoboa – An experimental Vyper interpreter with pretty tracebacks, forking, debugging features and more `_
+- `ApeWorX – The Ethereum development framework for Python Developers, Data Scientists, and Security Professionals `_
+- `VyperDeployer – A helper smart contract to compile and test Vyper contracts in Foundry `_
+- `🐍 snekmate – Vyper smart contract building blocks `_
+- `Serpentor – A set of smart contracts tools for governance `_
+- `Smart contract development frameworks and tools for Vyper on Ethreum.org `_
Security
--------
-- `VyperPunk - learn to secure and hack Vyper smart contracts `__
-- `VyperExamples - Vyper vulnerability examples `__
+- `VyperPunk – learn to secure and hack Vyper smart contracts `_
+- `VyperExamples – Vyper vulnerability examples `_
Conference presentations
------------------------
-- `Vyper Smart Contract Programming Language by Patrick Collins (2022, 30 mins) `__
-- `Python and DeFi by Curve Finance (2022, 15 mins) `__
-- `My experience with Vyper over the years by Benjamin Scherrey (2022, 15 mins) `__
-- `Short introduction to Vyper by Edison Que (3 mins) `__
+- `Vyper Smart Contract Programming Language by Patrick Collins (2022, 30 mins) `_
+- `Python and DeFi by Curve Finance (2022, 15 mins) `_
+- `My experience with Vyper over the years by Benjamin Scherrey (2022, 15 mins) `_
+- `Short introduction to Vyper by Edison Que (3 mins) `_
Unmaintained
------------
These resources have not been updated for a while, but may still offer interesting content.
-- `Awesome Vyper curated resources `__
-- `Brownie - Python framework for developing smart contracts (deprecated) `__
\ No newline at end of file
+- `Awesome Vyper curated resources `_
+- `Brownie – Python framework for developing smart contracts (deprecated) `_
+- `Foundry x Vyper – Foundry template to compile Vyper contracts `_
diff --git a/docs/structure-of-a-contract.rst b/docs/structure-of-a-contract.rst
index 8eb2c1da78..3861bf4380 100644
--- a/docs/structure-of-a-contract.rst
+++ b/docs/structure-of-a-contract.rst
@@ -9,16 +9,51 @@ This section provides a quick overview of the types of data present within a con
.. _structure-versions:
-Version Pragma
+Pragmas
==============
-Vyper supports a version pragma to ensure that a contract is only compiled by the intended compiler version, or range of versions. Version strings use `NPM `_ style syntax.
+Vyper supports several source code directives to control compiler modes and help with build reproducibility.
+
+Version Pragma
+--------------
+
+The version pragma ensures that a contract is only compiled by the intended compiler version, or range of versions. Version strings use `NPM `_ style syntax. Starting from v0.4.0 and up, version strings will use `PEP440 version specifiers `_.
+
+As of 0.3.10, the recommended way to specify the version pragma is as follows:
+
+.. code-block:: python
+
+ #pragma version ^0.3.0
+
+.. note::
+
+ Both pragma directive versions ``#pragma`` and ``# pragma`` are supported.
+
+The following declaration is equivalent, and, prior to 0.3.10, was the only supported method to specify the compiler version:
.. code-block:: python
- # @version ^0.2.0
+ # @version ^0.3.0
+
+
+In the above examples, the contract will only compile with Vyper versions ``0.3.x``.
+
+Optimization Mode
+-----------------
+
+The optimization mode can be one of ``"none"``, ``"codesize"``, or ``"gas"`` (default). For example, adding the following line to a contract will cause it to try to optimize for codesize:
+
+.. code-block:: python
+
+ #pragma optimize codesize
+
+The optimization mode can also be set as a compiler option, which is documented in :ref:`optimization-mode`. If the compiler option conflicts with the source code pragma, an exception will be raised and compilation will not continue.
+
+EVM Version
+-----------------
+
+The EVM version can be set with the ``evm-version`` pragma, which is documented in :ref:`evm-version`.
-In the above example, the contract only compiles with Vyper versions ``0.2.x``.
.. _structure-state-variables:
diff --git a/examples/crowdfund.vy b/examples/crowdfund.vy
index 3891ad0b74..56b34308f1 100644
--- a/examples/crowdfund.vy
+++ b/examples/crowdfund.vy
@@ -18,15 +18,15 @@ def __init__(_beneficiary: address, _goal: uint256, _timelimit: uint256):
@external
@payable
def participate():
- assert block.timestamp < self.deadline, "deadline not met (yet)"
+ assert block.timestamp < self.deadline, "deadline has expired"
self.funders[msg.sender] += msg.value
# Enough money was raised! Send funds to the beneficiary
@external
def finalize():
- assert block.timestamp >= self.deadline, "deadline has passed"
- assert self.balance >= self.goal, "the goal has not been reached"
+ assert block.timestamp >= self.deadline, "deadline has not expired yet"
+ assert self.balance >= self.goal, "goal has not been reached"
selfdestruct(self.beneficiary)
diff --git a/examples/tokens/ERC1155ownable.vy b/examples/tokens/ERC1155ownable.vy
index 8094225f18..f1070b8f89 100644
--- a/examples/tokens/ERC1155ownable.vy
+++ b/examples/tokens/ERC1155ownable.vy
@@ -214,7 +214,6 @@ def mint(receiver: address, id: uint256, amount:uint256):
@param receiver the account that will receive the minted token
@param id the ID of the token
@param amount of tokens for this ID
- @param data the data associated with this mint. Usually stays empty
"""
assert not self.paused, "The contract has been paused"
assert self.owner == msg.sender, "Only the contract owner can mint"
@@ -232,7 +231,6 @@ def mintBatch(receiver: address, ids: DynArray[uint256, BATCH_SIZE], amounts: Dy
@param receiver the account that will receive the minted token
@param ids array of ids for the tokens
@param amounts amounts of tokens for each ID in the ids array
- @param data the data associated with this mint. Usually stays empty
"""
assert not self.paused, "The contract has been paused"
assert self.owner == msg.sender, "Only the contract owner can mint"
diff --git a/setup.cfg b/setup.cfg
index d18ffe2ac7..dd4a32a3ac 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -31,7 +31,6 @@ addopts = -n auto
--cov-report html
--cov-report xml
--cov=vyper
- --hypothesis-show-statistics
python_files = test_*.py
testpaths = tests
markers =
diff --git a/setup.py b/setup.py
index 0966a8e31a..40efb436c5 100644
--- a/setup.py
+++ b/setup.py
@@ -4,7 +4,7 @@
import re
import subprocess
-from setuptools import find_packages, setup
+from setuptools import setup
extras_require = {
"test": [
@@ -14,8 +14,8 @@
"pytest-xdist>=2.5,<3.0",
"pytest-split>=0.7.0,<1.0",
"pytest-rerunfailures>=10.2,<11",
- "eth-tester[py-evm]>=0.8.0b3,<0.9",
- "py-evm>=0.6.1a2,<0.7",
+ "eth-tester[py-evm]>=0.9.0b1,<0.10",
+ "py-evm>=0.7.0a1,<0.8",
"web3==6.0.0",
"tox>=3.15,<4.0",
"lark==1.1.2",
@@ -28,7 +28,7 @@
"flake8-bugbear==20.1.4",
"flake8-use-fstring==1.1",
"isort==5.9.3",
- "mypy==0.910",
+ "mypy==0.982",
],
"docs": ["recommonmark", "sphinx>=6.0,<7.0", "sphinx_rtd_theme>=1.2,<1.3"],
"dev": ["ipython", "pre-commit", "pyinstaller", "twine"],
@@ -88,17 +88,18 @@ def _global_version(version):
license="Apache License 2.0",
keywords="ethereum evm smart contract language",
include_package_data=True,
- packages=find_packages(exclude=("tests", "docs")),
+ packages=["vyper"],
python_requires=">=3.10,<4",
py_modules=["vyper"],
install_requires=[
+ "cbor2>=5.4.6,<6",
"asttokens>=2.0.5,<3",
"pycryptodome>=3.5.1,<4",
- "semantic-version>=2.10,<3",
+ "packaging>=23.1,<24",
"importlib-metadata",
"wheel",
],
- setup_requires=["pytest-runner", "setuptools_scm"],
+ setup_requires=["pytest-runner", "setuptools_scm>=7.1.0,<8.0.0"],
tests_require=extras_require["test"],
extras_require=extras_require,
entry_points={
diff --git a/tests/abi_types/test_invalid_abi_types.py b/tests/abi_types/test_invalid_abi_types.py
new file mode 100644
index 0000000000..c8566e066f
--- /dev/null
+++ b/tests/abi_types/test_invalid_abi_types.py
@@ -0,0 +1,26 @@
+import pytest
+
+from vyper.abi_types import (
+ ABI_Bytes,
+ ABI_BytesM,
+ ABI_DynamicArray,
+ ABI_FixedMxN,
+ ABI_GIntM,
+ ABI_String,
+)
+from vyper.exceptions import InvalidABIType
+
+cases_invalid_types = [
+ (ABI_GIntM, ((0, False), (7, False), (300, True), (300, False))),
+ (ABI_FixedMxN, ((0, 0, False), (8, 0, False), (256, 81, True), (300, 80, False))),
+ (ABI_BytesM, ((0,), (33,), (-10,))),
+ (ABI_Bytes, ((-1,), (-69,))),
+ (ABI_DynamicArray, ((ABI_GIntM(256, False), -1), (ABI_String(256), -10))),
+]
+
+
+@pytest.mark.parametrize("typ,params_variants", cases_invalid_types)
+def test_invalid_abi_types(assert_compile_failed, typ, params_variants):
+ # double parametrization cannot work because the 2nd dimension is variable
+ for params in params_variants:
+ assert_compile_failed(lambda: typ(*params), InvalidABIType)
diff --git a/tests/ast/nodes/test_evaluate_binop_decimal.py b/tests/ast/nodes/test_evaluate_binop_decimal.py
index c6c69626b8..5c9956caba 100644
--- a/tests/ast/nodes/test_evaluate_binop_decimal.py
+++ b/tests/ast/nodes/test_evaluate_binop_decimal.py
@@ -13,7 +13,7 @@
@pytest.mark.fuzzing
-@settings(max_examples=50, deadline=1000)
+@settings(max_examples=50)
@given(left=st_decimals, right=st_decimals)
@example(left=Decimal("0.9999999999"), right=Decimal("0.0000000001"))
@example(left=Decimal("0.0000000001"), right=Decimal("0.9999999999"))
@@ -52,7 +52,7 @@ def test_binop_pow():
@pytest.mark.fuzzing
-@settings(max_examples=50, deadline=1000)
+@settings(max_examples=50)
@given(
values=st.lists(st_decimals, min_size=2, max_size=10),
ops=st.lists(st.sampled_from("+-*/%"), min_size=11, max_size=11),
diff --git a/tests/ast/nodes/test_evaluate_binop_int.py b/tests/ast/nodes/test_evaluate_binop_int.py
index d632a95461..80c9381c0f 100644
--- a/tests/ast/nodes/test_evaluate_binop_int.py
+++ b/tests/ast/nodes/test_evaluate_binop_int.py
@@ -9,7 +9,7 @@
@pytest.mark.fuzzing
-@settings(max_examples=50, deadline=1000)
+@settings(max_examples=50)
@given(left=st_int32, right=st_int32)
@example(left=1, right=1)
@example(left=1, right=-1)
@@ -42,7 +42,7 @@ def foo(a: int128, b: int128) -> int128:
@pytest.mark.fuzzing
-@settings(max_examples=50, deadline=1000)
+@settings(max_examples=50)
@given(left=st_uint64, right=st_uint64)
@pytest.mark.parametrize("op", "+-*/%")
def test_binop_uint256(get_contract, assert_tx_failed, op, left, right):
@@ -69,7 +69,7 @@ def foo(a: uint256, b: uint256) -> uint256:
@pytest.mark.xfail(reason="need to implement safe exponentiation logic")
@pytest.mark.fuzzing
-@settings(max_examples=50, deadline=1000)
+@settings(max_examples=50)
@given(left=st.integers(min_value=2, max_value=245), right=st.integers(min_value=0, max_value=16))
@example(left=0, right=0)
@example(left=0, right=1)
@@ -89,7 +89,7 @@ def foo(a: uint256, b: uint256) -> uint256:
@pytest.mark.fuzzing
-@settings(max_examples=50, deadline=1000)
+@settings(max_examples=50)
@given(
values=st.lists(st.integers(min_value=-256, max_value=256), min_size=2, max_size=10),
ops=st.lists(st.sampled_from("+-*/%"), min_size=11, max_size=11),
diff --git a/tests/ast/nodes/test_evaluate_boolop.py b/tests/ast/nodes/test_evaluate_boolop.py
index 6bd9ecc6cb..8b70537c39 100644
--- a/tests/ast/nodes/test_evaluate_boolop.py
+++ b/tests/ast/nodes/test_evaluate_boolop.py
@@ -8,7 +8,7 @@
@pytest.mark.fuzzing
-@settings(max_examples=50, deadline=1000)
+@settings(max_examples=50)
@given(values=st.lists(st.booleans(), min_size=2, max_size=10))
@pytest.mark.parametrize("comparator", ["and", "or"])
def test_boolop_simple(get_contract, values, comparator):
@@ -32,7 +32,7 @@ def foo({input_value}) -> bool:
@pytest.mark.fuzzing
-@settings(max_examples=50, deadline=1000)
+@settings(max_examples=50)
@given(
values=st.lists(st.booleans(), min_size=2, max_size=10),
comparators=st.lists(st.sampled_from(["and", "or"]), min_size=11, max_size=11),
diff --git a/tests/ast/nodes/test_evaluate_compare.py b/tests/ast/nodes/test_evaluate_compare.py
index 9ff5cea338..07f8e70de6 100644
--- a/tests/ast/nodes/test_evaluate_compare.py
+++ b/tests/ast/nodes/test_evaluate_compare.py
@@ -8,7 +8,7 @@
# TODO expand to all signed types
@pytest.mark.fuzzing
-@settings(max_examples=50, deadline=1000)
+@settings(max_examples=50)
@given(left=st.integers(), right=st.integers())
@pytest.mark.parametrize("op", ["==", "!=", "<", "<=", ">=", ">"])
def test_compare_eq_signed(get_contract, op, left, right):
@@ -28,7 +28,7 @@ def foo(a: int128, b: int128) -> bool:
# TODO expand to all unsigned types
@pytest.mark.fuzzing
-@settings(max_examples=50, deadline=1000)
+@settings(max_examples=50)
@given(left=st.integers(min_value=0), right=st.integers(min_value=0))
@pytest.mark.parametrize("op", ["==", "!=", "<", "<=", ">=", ">"])
def test_compare_eq_unsigned(get_contract, op, left, right):
@@ -47,7 +47,7 @@ def foo(a: uint128, b: uint128) -> bool:
@pytest.mark.fuzzing
-@settings(max_examples=20, deadline=1000)
+@settings(max_examples=20)
@given(left=st.integers(), right=st.lists(st.integers(), min_size=1, max_size=16))
def test_compare_in(left, right, get_contract):
source = f"""
@@ -76,7 +76,7 @@ def bar(a: int128) -> bool:
@pytest.mark.fuzzing
-@settings(max_examples=20, deadline=1000)
+@settings(max_examples=20)
@given(left=st.integers(), right=st.lists(st.integers(), min_size=1, max_size=16))
def test_compare_not_in(left, right, get_contract):
source = f"""
diff --git a/tests/ast/nodes/test_evaluate_subscript.py b/tests/ast/nodes/test_evaluate_subscript.py
index 3c0fa5d16d..ca50a076a5 100644
--- a/tests/ast/nodes/test_evaluate_subscript.py
+++ b/tests/ast/nodes/test_evaluate_subscript.py
@@ -6,7 +6,7 @@
@pytest.mark.fuzzing
-@settings(max_examples=50, deadline=1000)
+@settings(max_examples=50)
@given(
idx=st.integers(min_value=0, max_value=9),
array=st.lists(st.integers(), min_size=10, max_size=10),
diff --git a/tests/ast/test_folding.py b/tests/ast/test_folding.py
index 22d5f58222..62a7140e97 100644
--- a/tests/ast/test_folding.py
+++ b/tests/ast/test_folding.py
@@ -132,49 +132,6 @@ def test_replace_constant_no(source):
assert vy_ast.compare_nodes(unmodified_ast, folded_ast)
-builtins_modified = [
- "ZERO_ADDRESS",
- "foo = ZERO_ADDRESS",
- "foo: int128[ZERO_ADDRESS] = 42",
- "foo = [ZERO_ADDRESS]",
- "def foo(bar: address = ZERO_ADDRESS): pass",
- "def foo(): bar = ZERO_ADDRESS",
- "def foo(): return ZERO_ADDRESS",
- "log foo(ZERO_ADDRESS)",
- "log foo(42, ZERO_ADDRESS)",
-]
-
-
-@pytest.mark.parametrize("source", builtins_modified)
-def test_replace_builtin_constant(source):
- unmodified_ast = vy_ast.parse_to_ast(source)
- folded_ast = vy_ast.parse_to_ast(source)
-
- folding.replace_builtin_constants(folded_ast)
-
- assert not vy_ast.compare_nodes(unmodified_ast, folded_ast)
-
-
-builtins_unmodified = [
- "ZERO_ADDRESS = 2",
- "ZERO_ADDRESS()",
- "def foo(ZERO_ADDRESS: int128 = 42): pass",
- "def foo(): ZERO_ADDRESS = 42",
- "def ZERO_ADDRESS(): pass",
- "log ZERO_ADDRESS(42)",
-]
-
-
-@pytest.mark.parametrize("source", builtins_unmodified)
-def test_replace_builtin_constant_no(source):
- unmodified_ast = vy_ast.parse_to_ast(source)
- folded_ast = vy_ast.parse_to_ast(source)
-
- folding.replace_builtin_constants(folded_ast)
-
- assert vy_ast.compare_nodes(unmodified_ast, folded_ast)
-
-
userdefined_modified = [
"FOO",
"foo = FOO",
diff --git a/tests/ast/test_metadata_journal.py b/tests/ast/test_metadata_journal.py
new file mode 100644
index 0000000000..34830409fc
--- /dev/null
+++ b/tests/ast/test_metadata_journal.py
@@ -0,0 +1,82 @@
+from vyper.ast.metadata import NodeMetadata
+from vyper.exceptions import VyperException
+
+
+def test_metadata_journal_basic():
+ m = NodeMetadata()
+
+ m["x"] = 1
+ assert m["x"] == 1
+
+
+def test_metadata_journal_commit():
+ m = NodeMetadata()
+
+ with m.enter_typechecker_speculation():
+ m["x"] = 1
+
+ assert m["x"] == 1
+
+
+def test_metadata_journal_exception():
+ m = NodeMetadata()
+
+ m["x"] = 1
+ try:
+ with m.enter_typechecker_speculation():
+ m["x"] = 2
+ m["x"] = 3
+
+ assert m["x"] == 3
+ raise VyperException("dummy exception")
+
+ except VyperException:
+ pass
+
+ # rollback upon exception
+ assert m["x"] == 1
+
+
+def test_metadata_journal_rollback_inner():
+ m = NodeMetadata()
+
+ m["x"] = 1
+ with m.enter_typechecker_speculation():
+ m["x"] = 2
+
+ try:
+ with m.enter_typechecker_speculation():
+ m["x"] = 3
+ m["x"] = 4 # test multiple writes
+
+ assert m["x"] == 4
+ raise VyperException("dummy exception")
+
+ except VyperException:
+ pass
+
+ assert m["x"] == 2
+
+
+def test_metadata_journal_rollback_outer():
+ m = NodeMetadata()
+
+ m["x"] = 1
+ try:
+ with m.enter_typechecker_speculation():
+ m["x"] = 2
+
+ with m.enter_typechecker_speculation():
+ m["x"] = 3
+ m["x"] = 4 # test multiple writes
+
+ assert m["x"] == 4
+
+ m["x"] = 5
+
+ raise VyperException("dummy exception")
+
+ except VyperException:
+ pass
+
+ assert m["x"] == 1
diff --git a/tests/ast/test_natspec.py b/tests/ast/test_natspec.py
index 2e9980b8d7..c2133468aa 100644
--- a/tests/ast/test_natspec.py
+++ b/tests/ast/test_natspec.py
@@ -24,6 +24,7 @@ def doesEat(food: String[30], qty: uint256) -> bool:
@param food The name of a food to evaluate (in English)
@param qty The number of food items to evaluate
@return True if Bugs will eat it, False otherwise
+ @custom:my-custom-tag hello, world!
'''
return True
"""
@@ -51,6 +52,7 @@ def doesEat(food: String[30], qty: uint256) -> bool:
"qty": "The number of food items to evaluate",
},
"returns": {"_0": "True if Bugs will eat it, False otherwise"},
+ "custom:my-custom-tag": "hello, world!",
}
},
"title": "A simulator for Bug Bunny, the most famous Rabbit",
diff --git a/tests/ast/test_pre_parser.py b/tests/ast/test_pre_parser.py
index 8501bb8749..3d072674f6 100644
--- a/tests/ast/test_pre_parser.py
+++ b/tests/ast/test_pre_parser.py
@@ -1,7 +1,9 @@
import pytest
-from vyper.ast.pre_parser import validate_version_pragma
-from vyper.exceptions import VersionException
+from vyper.ast.pre_parser import pre_parse, validate_version_pragma
+from vyper.compiler.phases import CompilerData
+from vyper.compiler.settings import OptimizationLevel, Settings
+from vyper.exceptions import StructureException, VersionException
SRC_LINE = (1, 0) # Dummy source line
COMPILER_VERSION = "0.1.1"
@@ -20,16 +22,9 @@ def set_version(version):
"0.1.1",
">0.0.1",
"^0.1.0",
- "<=1.0.0 >=0.1.0",
- "0.1.0 - 1.0.0",
- "~0.1.0",
- "0.1",
- "0",
- "*",
- "x",
- "0.x",
- "0.1.x",
- "0.2.0 || 0.1.1",
+ "<=1.0.0,>=0.1.0",
+ # "0.1.0 - 1.0.0",
+ "~=0.1.0",
]
invalid_versions = [
"0.1.0",
@@ -43,7 +38,6 @@ def set_version(version):
"1.x",
"0.2.x",
"0.2.0 || 0.1.3",
- "==0.1.1",
"abc",
]
@@ -51,14 +45,14 @@ def set_version(version):
@pytest.mark.parametrize("file_version", valid_versions)
def test_valid_version_pragma(file_version, mock_version):
mock_version(COMPILER_VERSION)
- validate_version_pragma(f" @version {file_version}", (SRC_LINE))
+ validate_version_pragma(f"{file_version}", (SRC_LINE))
@pytest.mark.parametrize("file_version", invalid_versions)
def test_invalid_version_pragma(file_version, mock_version):
mock_version(COMPILER_VERSION)
with pytest.raises(VersionException):
- validate_version_pragma(f" @version {file_version}", (SRC_LINE))
+ validate_version_pragma(f"{file_version}", (SRC_LINE))
prerelease_valid_versions = [
@@ -69,9 +63,10 @@ def test_invalid_version_pragma(file_version, mock_version):
"<0.1.1-rc.1",
">0.1.1a1",
">0.1.1-alpha.1",
- "0.1.1a9 - 0.1.1-rc.10",
+ ">=0.1.1a9,<=0.1.1-rc.10",
"<0.1.1b8",
"<0.1.1rc1",
+ "<0.2.0",
]
prerelease_invalid_versions = [
">0.1.1-beta.9",
@@ -79,30 +74,150 @@ def test_invalid_version_pragma(file_version, mock_version):
"0.1.1b8",
"0.1.1rc2",
"0.1.1-rc.9 - 0.1.1-rc.10",
- "<0.2.0",
- pytest.param(
- "<0.1.1b1",
- marks=pytest.mark.xfail(
- reason="https://github.com/rbarrois/python-semanticversion/issues/100"
- ),
- ),
- pytest.param(
- "<0.1.1a9",
- marks=pytest.mark.xfail(
- reason="https://github.com/rbarrois/python-semanticversion/issues/100"
- ),
- ),
+ "<0.1.1b1",
+ "<0.1.1a9",
]
@pytest.mark.parametrize("file_version", prerelease_valid_versions)
def test_prerelease_valid_version_pragma(file_version, mock_version):
mock_version(PRERELEASE_COMPILER_VERSION)
- validate_version_pragma(f" @version {file_version}", (SRC_LINE))
+ validate_version_pragma(file_version, (SRC_LINE))
@pytest.mark.parametrize("file_version", prerelease_invalid_versions)
def test_prerelease_invalid_version_pragma(file_version, mock_version):
mock_version(PRERELEASE_COMPILER_VERSION)
with pytest.raises(VersionException):
- validate_version_pragma(f" @version {file_version}", (SRC_LINE))
+ validate_version_pragma(file_version, (SRC_LINE))
+
+
+pragma_examples = [
+ (
+ """
+ """,
+ Settings(),
+ Settings(optimize=OptimizationLevel.GAS),
+ ),
+ (
+ """
+ #pragma optimize codesize
+ """,
+ Settings(optimize=OptimizationLevel.CODESIZE),
+ None,
+ ),
+ (
+ """
+ #pragma optimize none
+ """,
+ Settings(optimize=OptimizationLevel.NONE),
+ None,
+ ),
+ (
+ """
+ #pragma optimize gas
+ """,
+ Settings(optimize=OptimizationLevel.GAS),
+ None,
+ ),
+ (
+ """
+ #pragma version 0.3.10
+ """,
+ Settings(compiler_version="0.3.10"),
+ Settings(optimize=OptimizationLevel.GAS),
+ ),
+ (
+ """
+ #pragma evm-version shanghai
+ """,
+ Settings(evm_version="shanghai"),
+ Settings(evm_version="shanghai", optimize=OptimizationLevel.GAS),
+ ),
+ (
+ """
+ #pragma optimize codesize
+ #pragma evm-version shanghai
+ """,
+ Settings(evm_version="shanghai", optimize=OptimizationLevel.CODESIZE),
+ None,
+ ),
+ (
+ """
+ #pragma version 0.3.10
+ #pragma evm-version shanghai
+ """,
+ Settings(evm_version="shanghai", compiler_version="0.3.10"),
+ Settings(evm_version="shanghai", optimize=OptimizationLevel.GAS),
+ ),
+ (
+ """
+ #pragma version 0.3.10
+ #pragma optimize gas
+ """,
+ Settings(compiler_version="0.3.10", optimize=OptimizationLevel.GAS),
+ Settings(optimize=OptimizationLevel.GAS),
+ ),
+ (
+ """
+ #pragma version 0.3.10
+ #pragma evm-version shanghai
+ #pragma optimize gas
+ """,
+ Settings(compiler_version="0.3.10", optimize=OptimizationLevel.GAS, evm_version="shanghai"),
+ Settings(optimize=OptimizationLevel.GAS, evm_version="shanghai"),
+ ),
+]
+
+
+@pytest.mark.parametrize("code, pre_parse_settings, compiler_data_settings", pragma_examples)
+def test_parse_pragmas(code, pre_parse_settings, compiler_data_settings, mock_version):
+ mock_version("0.3.10")
+ settings, _, _ = pre_parse(code)
+
+ assert settings == pre_parse_settings
+
+ compiler_data = CompilerData(code)
+
+ # check what happens after CompilerData constructor
+ if compiler_data_settings is None:
+ # None is sentinel here meaning that nothing changed
+ compiler_data_settings = pre_parse_settings
+
+ assert compiler_data.settings == compiler_data_settings
+
+
+invalid_pragmas = [
+ # evm-versionnn
+ """
+# pragma evm-versionnn cancun
+ """,
+ # bad fork name
+ """
+# pragma evm-version cancunn
+ """,
+ # oppptimize
+ """
+# pragma oppptimize codesize
+ """,
+ # ggas
+ """
+# pragma optimize ggas
+ """,
+ # double specified
+ """
+# pragma optimize gas
+# pragma optimize codesize
+ """,
+ # double specified
+ """
+# pragma evm-version cancun
+# pragma evm-version shanghai
+ """,
+]
+
+
+@pytest.mark.parametrize("code", invalid_pragmas)
+def test_invalid_pragma(code):
+ with pytest.raises(StructureException):
+ pre_parse(code)
diff --git a/tests/base_conftest.py b/tests/base_conftest.py
index 4c3d0136bb..f613ad0f47 100644
--- a/tests/base_conftest.py
+++ b/tests/base_conftest.py
@@ -1,3 +1,5 @@
+import json
+
import pytest
import web3.exceptions
from eth_tester import EthereumTester, PyEVMBackend
@@ -10,6 +12,7 @@
from vyper import compiler
from vyper.ast.grammar import parse_vyper_source
+from vyper.compiler.settings import Settings
class VyperMethod:
@@ -31,7 +34,7 @@ def __prepared_function(self, *args, **kwargs):
if x.get("name") == self._function.function_identifier
].pop()
# To make tests faster just supply some high gas value.
- modifier_dict.update({"gas": fn_abi.get("gas", 0) + 50000})
+ modifier_dict.update({"gas": fn_abi.get("gas", 0) + 500000})
elif len(kwargs) == 1:
modifier, modifier_dict = kwargs.popitem()
if modifier not in self.ALLOWED_MODIFIERS:
@@ -109,16 +112,22 @@ def w3(tester):
return w3
-def _get_contract(w3, source_code, no_optimize, *args, **kwargs):
+def _get_contract(
+ w3, source_code, optimize, *args, override_opt_level=None, input_bundle=None, **kwargs
+):
+ settings = Settings()
+ settings.evm_version = kwargs.pop("evm_version", None)
+ settings.optimize = override_opt_level or optimize
out = compiler.compile_code(
source_code,
- ["abi", "bytecode"],
- interface_codes=kwargs.pop("interface_codes", None),
- no_optimize=no_optimize,
- evm_version=kwargs.pop("evm_version", None),
+ # test that metadata and natspecs get generated
+ output_formats=["abi", "bytecode", "metadata", "userdoc", "devdoc"],
+ settings=settings,
+ input_bundle=input_bundle,
show_gas_estimates=True, # Enable gas estimates for testing
)
parse_vyper_source(source_code) # Test grammar.
+ json.dumps(out["metadata"]) # test metadata is json serializable
abi = out["abi"]
bytecode = out["bytecode"]
value = kwargs.pop("value_in_eth", 0) * 10**18 # Handle deploying with an eth value.
@@ -131,13 +140,14 @@ def _get_contract(w3, source_code, no_optimize, *args, **kwargs):
return w3.eth.contract(address, abi=abi, bytecode=bytecode, ContractFactoryClass=VyperContract)
-def _deploy_blueprint_for(w3, source_code, no_optimize, initcode_prefix=b"", **kwargs):
+def _deploy_blueprint_for(w3, source_code, optimize, initcode_prefix=b"", **kwargs):
+ settings = Settings()
+ settings.evm_version = kwargs.pop("evm_version", None)
+ settings.optimize = optimize
out = compiler.compile_code(
source_code,
- ["abi", "bytecode"],
- interface_codes=kwargs.pop("interface_codes", None),
- no_optimize=no_optimize,
- evm_version=kwargs.pop("evm_version", None),
+ output_formats=["abi", "bytecode", "metadata", "userdoc", "devdoc"],
+ settings=settings,
show_gas_estimates=True, # Enable gas estimates for testing
)
parse_vyper_source(source_code) # Test grammar.
@@ -169,19 +179,19 @@ def factory(address):
@pytest.fixture(scope="module")
-def deploy_blueprint_for(w3, no_optimize):
+def deploy_blueprint_for(w3, optimize):
def deploy_blueprint_for(source_code, *args, **kwargs):
- return _deploy_blueprint_for(w3, source_code, no_optimize, *args, **kwargs)
+ return _deploy_blueprint_for(w3, source_code, optimize, *args, **kwargs)
return deploy_blueprint_for
@pytest.fixture(scope="module")
-def get_contract(w3, no_optimize):
- def get_contract(source_code, *args, **kwargs):
- return _get_contract(w3, source_code, no_optimize, *args, **kwargs)
+def get_contract(w3, optimize):
+ def fn(source_code, *args, **kwargs):
+ return _get_contract(w3, source_code, optimize, *args, **kwargs)
- return get_contract
+ return fn
@pytest.fixture
diff --git a/tests/builtins/folding/test_abs.py b/tests/builtins/folding/test_abs.py
index 58f861ed0c..1c919d7826 100644
--- a/tests/builtins/folding/test_abs.py
+++ b/tests/builtins/folding/test_abs.py
@@ -8,7 +8,7 @@
@pytest.mark.fuzzing
-@settings(max_examples=50, deadline=1000)
+@settings(max_examples=50)
@given(a=st.integers(min_value=-(2**255) + 1, max_value=2**255 - 1))
@example(a=0)
def test_abs(get_contract, a):
@@ -27,7 +27,7 @@ def foo(a: int256) -> int256:
@pytest.mark.fuzzing
-@settings(max_examples=50, deadline=1000)
+@settings(max_examples=50)
@given(a=st.integers(min_value=2**255, max_value=2**256 - 1))
def test_abs_upper_bound_folding(get_contract, a):
source = f"""
diff --git a/tests/builtins/folding/test_addmod_mulmod.py b/tests/builtins/folding/test_addmod_mulmod.py
index 0514dea18a..33dcc62984 100644
--- a/tests/builtins/folding/test_addmod_mulmod.py
+++ b/tests/builtins/folding/test_addmod_mulmod.py
@@ -9,7 +9,7 @@
@pytest.mark.fuzzing
-@settings(max_examples=50, deadline=1000)
+@settings(max_examples=50)
@given(a=st_uint256, b=st_uint256, c=st_uint256)
@pytest.mark.parametrize("fn_name", ["uint256_addmod", "uint256_mulmod"])
def test_modmath(get_contract, a, b, c, fn_name):
diff --git a/tests/builtins/folding/test_bitwise.py b/tests/builtins/folding/test_bitwise.py
index d28e482589..63e733644f 100644
--- a/tests/builtins/folding/test_bitwise.py
+++ b/tests/builtins/folding/test_bitwise.py
@@ -14,7 +14,7 @@
@pytest.mark.fuzzing
-@settings(max_examples=50, deadline=1000)
+@settings(max_examples=50)
@pytest.mark.parametrize("op", ["&", "|", "^"])
@given(a=st_uint256, b=st_uint256)
def test_bitwise_ops(get_contract, a, b, op):
@@ -34,7 +34,7 @@ def foo(a: uint256, b: uint256) -> uint256:
@pytest.mark.fuzzing
-@settings(max_examples=50, deadline=1000)
+@settings(max_examples=50)
@pytest.mark.parametrize("op", ["<<", ">>"])
@given(a=st_uint256, b=st.integers(min_value=0, max_value=256))
def test_bitwise_shift_unsigned(get_contract, a, b, op):
@@ -64,7 +64,7 @@ def foo(a: uint256, b: uint256) -> uint256:
@pytest.mark.fuzzing
-@settings(max_examples=50, deadline=1000)
+@settings(max_examples=50)
@pytest.mark.parametrize("op", ["<<", ">>"])
@given(a=st_sint256, b=st.integers(min_value=0, max_value=256))
def test_bitwise_shift_signed(get_contract, a, b, op):
@@ -92,7 +92,7 @@ def foo(a: int256, b: uint256) -> int256:
@pytest.mark.fuzzing
-@settings(max_examples=50, deadline=1000)
+@settings(max_examples=50)
@given(value=st_uint256)
def test_bitwise_not(get_contract, value):
source = """
diff --git a/tests/builtins/folding/test_floor_ceil.py b/tests/builtins/folding/test_floor_ceil.py
index 763f8fec63..87db23889a 100644
--- a/tests/builtins/folding/test_floor_ceil.py
+++ b/tests/builtins/folding/test_floor_ceil.py
@@ -13,7 +13,7 @@
@pytest.mark.fuzzing
-@settings(max_examples=50, deadline=1000)
+@settings(max_examples=50)
@given(value=st_decimals)
@example(value=Decimal("0.9999999999"))
@example(value=Decimal("0.0000000001"))
diff --git a/tests/builtins/folding/test_fold_as_wei_value.py b/tests/builtins/folding/test_fold_as_wei_value.py
index 11d23bd3bf..210ab51f0d 100644
--- a/tests/builtins/folding/test_fold_as_wei_value.py
+++ b/tests/builtins/folding/test_fold_as_wei_value.py
@@ -19,7 +19,7 @@
@pytest.mark.fuzzing
-@settings(max_examples=10, deadline=1000)
+@settings(max_examples=10)
@given(value=st_decimals)
@pytest.mark.parametrize("denom", denoms)
def test_decimal(get_contract, value, denom):
@@ -38,7 +38,7 @@ def foo(a: decimal) -> uint256:
@pytest.mark.fuzzing
-@settings(max_examples=10, deadline=1000)
+@settings(max_examples=10)
@given(value=st.integers(min_value=0, max_value=2**128))
@pytest.mark.parametrize("denom", denoms)
def test_integer(get_contract, value, denom):
diff --git a/tests/builtins/folding/test_keccak_sha.py b/tests/builtins/folding/test_keccak_sha.py
index 8e283566de..a2fe460dd1 100644
--- a/tests/builtins/folding/test_keccak_sha.py
+++ b/tests/builtins/folding/test_keccak_sha.py
@@ -10,7 +10,7 @@
@pytest.mark.fuzzing
@given(value=st.text(alphabet=alphabet, min_size=0, max_size=100))
-@settings(max_examples=50, deadline=1000)
+@settings(max_examples=50)
@pytest.mark.parametrize("fn_name", ["keccak256", "sha256"])
def test_string(get_contract, value, fn_name):
source = f"""
@@ -29,7 +29,7 @@ def foo(a: String[100]) -> bytes32:
@pytest.mark.fuzzing
@given(value=st.binary(min_size=0, max_size=100))
-@settings(max_examples=50, deadline=1000)
+@settings(max_examples=50)
@pytest.mark.parametrize("fn_name", ["keccak256", "sha256"])
def test_bytes(get_contract, value, fn_name):
source = f"""
@@ -48,7 +48,7 @@ def foo(a: Bytes[100]) -> bytes32:
@pytest.mark.fuzzing
@given(value=st.binary(min_size=1, max_size=100))
-@settings(max_examples=50, deadline=1000)
+@settings(max_examples=50)
@pytest.mark.parametrize("fn_name", ["keccak256", "sha256"])
def test_hex(get_contract, value, fn_name):
source = f"""
diff --git a/tests/builtins/folding/test_min_max.py b/tests/builtins/folding/test_min_max.py
index e2d33237ca..309f7519c0 100644
--- a/tests/builtins/folding/test_min_max.py
+++ b/tests/builtins/folding/test_min_max.py
@@ -18,7 +18,7 @@
@pytest.mark.fuzzing
-@settings(max_examples=50, deadline=1000)
+@settings(max_examples=50)
@given(left=st_decimals, right=st_decimals)
@pytest.mark.parametrize("fn_name", ["min", "max"])
def test_decimal(get_contract, left, right, fn_name):
@@ -37,7 +37,7 @@ def foo(a: decimal, b: decimal) -> decimal:
@pytest.mark.fuzzing
-@settings(max_examples=50, deadline=1000)
+@settings(max_examples=50)
@given(left=st_int128, right=st_int128)
@pytest.mark.parametrize("fn_name", ["min", "max"])
def test_int128(get_contract, left, right, fn_name):
@@ -56,7 +56,7 @@ def foo(a: int128, b: int128) -> int128:
@pytest.mark.fuzzing
-@settings(max_examples=50, deadline=1000)
+@settings(max_examples=50)
@given(left=st_uint256, right=st_uint256)
@pytest.mark.parametrize("fn_name", ["min", "max"])
def test_min_uint256(get_contract, left, right, fn_name):
diff --git a/tests/builtins/folding/test_powmod.py b/tests/builtins/folding/test_powmod.py
index fdc0e300ab..8667ec93fd 100644
--- a/tests/builtins/folding/test_powmod.py
+++ b/tests/builtins/folding/test_powmod.py
@@ -9,7 +9,7 @@
@pytest.mark.fuzzing
-@settings(max_examples=100, deadline=1000)
+@settings(max_examples=100)
@given(a=st_uint256, b=st_uint256)
def test_powmod_uint256(get_contract, a, b):
source = """
diff --git a/tests/cli/vyper_compile/test_compile_files.py b/tests/cli/vyper_compile/test_compile_files.py
index 796976ae0e..2a16efa777 100644
--- a/tests/cli/vyper_compile/test_compile_files.py
+++ b/tests/cli/vyper_compile/test_compile_files.py
@@ -1,12 +1,12 @@
+from pathlib import Path
+
import pytest
from vyper.cli.vyper_compile import compile_files
-def test_combined_json_keys(tmp_path):
- bar_path = tmp_path.joinpath("bar.vy")
- with bar_path.open("w") as fp:
- fp.write("")
+def test_combined_json_keys(tmp_path, make_file):
+ make_file("bar.vy", "")
combined_keys = {
"bytecode",
@@ -19,10 +19,10 @@ def test_combined_json_keys(tmp_path):
"userdoc",
"devdoc",
}
- compile_data = compile_files([bar_path], ["combined_json"], root_folder=tmp_path)
+ compile_data = compile_files(["bar.vy"], ["combined_json"], root_folder=tmp_path)
- assert set(compile_data.keys()) == {"bar.vy", "version"}
- assert set(compile_data["bar.vy"].keys()) == combined_keys
+ assert set(compile_data.keys()) == {Path("bar.vy"), "version"}
+ assert set(compile_data[Path("bar.vy")].keys()) == combined_keys
def test_invalid_root_path():
@@ -30,27 +30,192 @@ def test_invalid_root_path():
compile_files([], [], root_folder="path/that/does/not/exist")
-def test_evm_versions(tmp_path):
- # should compile differently because of SELFBALANCE
- code = """
+FOO_CODE = """
+{}
+
+struct FooStruct:
+ foo_: uint256
+
+@external
+def foo() -> FooStruct:
+ return FooStruct({{foo_: 13}})
+
+@external
+def bar(a: address) -> FooStruct:
+ return {}(a).bar()
+"""
+
+BAR_CODE = """
+struct FooStruct:
+ foo_: uint256
@external
-def foo() -> uint256:
- return self.balance
+def bar() -> FooStruct:
+ return FooStruct({foo_: 13})
"""
- bar_path = tmp_path.joinpath("bar.vy")
- with bar_path.open("w") as fp:
- fp.write(code)
- byzantium_bytecode = compile_files(
- [bar_path], output_formats=["bytecode"], evm_version="byzantium"
- )[str(bar_path)]["bytecode"]
- istanbul_bytecode = compile_files(
- [bar_path], output_formats=["bytecode"], evm_version="istanbul"
- )[str(bar_path)]["bytecode"]
+SAME_FOLDER_IMPORT_STMT = [
+ ("import Bar as Bar", "Bar"),
+ ("import contracts.Bar as Bar", "Bar"),
+ ("from . import Bar", "Bar"),
+ ("from contracts import Bar", "Bar"),
+ ("from ..contracts import Bar", "Bar"),
+ ("from . import Bar as FooBar", "FooBar"),
+ ("from contracts import Bar as FooBar", "FooBar"),
+ ("from ..contracts import Bar as FooBar", "FooBar"),
+]
+
+
+@pytest.mark.parametrize("import_stmt,alias", SAME_FOLDER_IMPORT_STMT)
+def test_import_same_folder(import_stmt, alias, tmp_path, make_file):
+ foo = "contracts/foo.vy"
+ make_file("contracts/foo.vy", FOO_CODE.format(import_stmt, alias))
+ make_file("contracts/Bar.vy", BAR_CODE)
+
+ assert compile_files([foo], ["combined_json"], root_folder=tmp_path)
+
+
+SUBFOLDER_IMPORT_STMT = [
+ ("import other.Bar as Bar", "Bar"),
+ ("import contracts.other.Bar as Bar", "Bar"),
+ ("from other import Bar", "Bar"),
+ ("from contracts.other import Bar", "Bar"),
+ ("from .other import Bar", "Bar"),
+ ("from ..contracts.other import Bar", "Bar"),
+ ("from other import Bar as FooBar", "FooBar"),
+ ("from contracts.other import Bar as FooBar", "FooBar"),
+ ("from .other import Bar as FooBar", "FooBar"),
+ ("from ..contracts.other import Bar as FooBar", "FooBar"),
+]
+
+
+@pytest.mark.parametrize("import_stmt, alias", SUBFOLDER_IMPORT_STMT)
+def test_import_subfolder(import_stmt, alias, tmp_path, make_file):
+ foo = make_file("contracts/foo.vy", (FOO_CODE.format(import_stmt, alias)))
+ make_file("contracts/other/Bar.vy", BAR_CODE)
+
+ assert compile_files([foo], ["combined_json"], root_folder=tmp_path)
+
+
+OTHER_FOLDER_IMPORT_STMT = [
+ ("import interfaces.Bar as Bar", "Bar"),
+ ("from interfaces import Bar", "Bar"),
+ ("from ..interfaces import Bar", "Bar"),
+ ("from interfaces import Bar as FooBar", "FooBar"),
+ ("from ..interfaces import Bar as FooBar", "FooBar"),
+]
+
+
+@pytest.mark.parametrize("import_stmt, alias", OTHER_FOLDER_IMPORT_STMT)
+def test_import_other_folder(import_stmt, alias, tmp_path, make_file):
+ foo = make_file("contracts/foo.vy", FOO_CODE.format(import_stmt, alias))
+ make_file("interfaces/Bar.vy", BAR_CODE)
+
+ assert compile_files([foo], ["combined_json"], root_folder=tmp_path)
+
+
+def test_import_parent_folder(tmp_path, make_file):
+ foo = make_file("contracts/baz/foo.vy", FOO_CODE.format("from ... import Bar", "Bar"))
+ make_file("Bar.vy", BAR_CODE)
+
+ assert compile_files([foo], ["combined_json"], root_folder=tmp_path)
+
+ # perform relative import outside of base folder
+ compile_files([foo], ["combined_json"], root_folder=tmp_path / "contracts")
+
+
+META_IMPORT_STMT = [
+ "import Meta as Meta",
+ "import contracts.Meta as Meta",
+ "from . import Meta",
+ "from contracts import Meta",
+]
+
+
+@pytest.mark.parametrize("import_stmt", META_IMPORT_STMT)
+def test_import_self_interface(import_stmt, tmp_path, make_file):
+ # a contract can access its derived interface by importing itself
+ code = f"""
+{import_stmt}
+
+struct FooStruct:
+ foo_: uint256
+
+@external
+def know_thyself(a: address) -> FooStruct:
+ return Meta(a).be_known()
+
+@external
+def be_known() -> FooStruct:
+ return FooStruct({{foo_: 42}})
+ """
+ meta = make_file("contracts/Meta.vy", code)
+
+ assert compile_files([meta], ["combined_json"], root_folder=tmp_path)
+
+
+DERIVED_IMPORT_STMT_BAZ = ["import Foo as Foo", "from . import Foo"]
+
+DERIVED_IMPORT_STMT_FOO = ["import Bar as Bar", "from . import Bar"]
+
+
+@pytest.mark.parametrize("import_stmt_baz", DERIVED_IMPORT_STMT_BAZ)
+@pytest.mark.parametrize("import_stmt_foo", DERIVED_IMPORT_STMT_FOO)
+def test_derived_interface_imports(import_stmt_baz, import_stmt_foo, tmp_path, make_file):
+ # contracts-as-interfaces should be able to contain import statements
+ baz_code = f"""
+{import_stmt_baz}
+
+struct FooStruct:
+ foo_: uint256
+
+@external
+def foo(a: address) -> FooStruct:
+ return Foo(a).foo()
+
+@external
+def bar(_foo: address, _bar: address) -> FooStruct:
+ return Foo(_foo).bar(_bar)
+ """
+
+ make_file("Foo.vy", FOO_CODE.format(import_stmt_foo, "Bar"))
+ make_file("Bar.vy", BAR_CODE)
+ baz = make_file("Baz.vy", baz_code)
+
+ assert compile_files([baz], ["combined_json"], root_folder=tmp_path)
+
+
+def test_local_namespace(make_file, tmp_path):
+ # interface code namespaces should be isolated
+ # all of these contract should be able to compile together
+ codes = [
+ "import foo as FooBar",
+ "import bar as FooBar",
+ "import foo as BarFoo",
+ "import bar as BarFoo",
+ ]
+ struct_def = """
+struct FooStruct:
+ foo_: uint256
+
+ """
+
+ paths = []
+ for i, code in enumerate(codes):
+ code += struct_def
+ filename = f"code{i}.vy"
+ make_file(filename, code)
+ paths.append(filename)
+
+ for file_name in ("foo.vy", "bar.vy"):
+ make_file(file_name, BAR_CODE)
+
+ assert compile_files(paths, ["combined_json"], root_folder=tmp_path)
+
- assert byzantium_bytecode != istanbul_bytecode
+def test_compile_outside_root_path(tmp_path, make_file):
+ # absolute paths relative to "."
+ foo = make_file("foo.vy", FOO_CODE.format("import bar as Bar", "Bar"))
+ bar = make_file("bar.vy", BAR_CODE)
- # SELFBALANCE opcode is 0x47
- assert "47" not in byzantium_bytecode
- assert "47" in istanbul_bytecode
+ assert compile_files([foo, bar], ["combined_json"], root_folder=".")
diff --git a/tests/cli/vyper_compile/test_import_paths.py b/tests/cli/vyper_compile/test_import_paths.py
deleted file mode 100644
index 81f209113f..0000000000
--- a/tests/cli/vyper_compile/test_import_paths.py
+++ /dev/null
@@ -1,260 +0,0 @@
-import pytest
-
-from vyper.cli.vyper_compile import compile_files, get_interface_file_path
-
-FOO_CODE = """
-{}
-
-struct FooStruct:
- foo_: uint256
-
-@external
-def foo() -> FooStruct:
- return FooStruct({{foo_: 13}})
-
-@external
-def bar(a: address) -> FooStruct:
- return {}(a).bar()
-"""
-
-BAR_CODE = """
-struct FooStruct:
- foo_: uint256
-@external
-def bar() -> FooStruct:
- return FooStruct({foo_: 13})
-"""
-
-
-SAME_FOLDER_IMPORT_STMT = [
- ("import Bar as Bar", "Bar"),
- ("import contracts.Bar as Bar", "Bar"),
- ("from . import Bar", "Bar"),
- ("from contracts import Bar", "Bar"),
- ("from ..contracts import Bar", "Bar"),
- ("from . import Bar as FooBar", "FooBar"),
- ("from contracts import Bar as FooBar", "FooBar"),
- ("from ..contracts import Bar as FooBar", "FooBar"),
-]
-
-
-@pytest.mark.parametrize("import_stmt,alias", SAME_FOLDER_IMPORT_STMT)
-def test_import_same_folder(import_stmt, alias, tmp_path):
- tmp_path.joinpath("contracts").mkdir()
-
- foo_path = tmp_path.joinpath("contracts/foo.vy")
- with foo_path.open("w") as fp:
- fp.write(FOO_CODE.format(import_stmt, alias))
-
- with tmp_path.joinpath("contracts/Bar.vy").open("w") as fp:
- fp.write(BAR_CODE)
-
- assert compile_files([foo_path], ["combined_json"], root_folder=tmp_path)
-
-
-SUBFOLDER_IMPORT_STMT = [
- ("import other.Bar as Bar", "Bar"),
- ("import contracts.other.Bar as Bar", "Bar"),
- ("from other import Bar", "Bar"),
- ("from contracts.other import Bar", "Bar"),
- ("from .other import Bar", "Bar"),
- ("from ..contracts.other import Bar", "Bar"),
- ("from other import Bar as FooBar", "FooBar"),
- ("from contracts.other import Bar as FooBar", "FooBar"),
- ("from .other import Bar as FooBar", "FooBar"),
- ("from ..contracts.other import Bar as FooBar", "FooBar"),
-]
-
-
-@pytest.mark.parametrize("import_stmt, alias", SUBFOLDER_IMPORT_STMT)
-def test_import_subfolder(import_stmt, alias, tmp_path):
- tmp_path.joinpath("contracts").mkdir()
-
- foo_path = tmp_path.joinpath("contracts/foo.vy")
- with foo_path.open("w") as fp:
- fp.write(FOO_CODE.format(import_stmt, alias))
-
- tmp_path.joinpath("contracts/other").mkdir()
- with tmp_path.joinpath("contracts/other/Bar.vy").open("w") as fp:
- fp.write(BAR_CODE)
-
- assert compile_files([foo_path], ["combined_json"], root_folder=tmp_path)
-
-
-OTHER_FOLDER_IMPORT_STMT = [
- ("import interfaces.Bar as Bar", "Bar"),
- ("from interfaces import Bar", "Bar"),
- ("from ..interfaces import Bar", "Bar"),
- ("from interfaces import Bar as FooBar", "FooBar"),
- ("from ..interfaces import Bar as FooBar", "FooBar"),
-]
-
-
-@pytest.mark.parametrize("import_stmt, alias", OTHER_FOLDER_IMPORT_STMT)
-def test_import_other_folder(import_stmt, alias, tmp_path):
- tmp_path.joinpath("contracts").mkdir()
-
- foo_path = tmp_path.joinpath("contracts/foo.vy")
- with foo_path.open("w") as fp:
- fp.write(FOO_CODE.format(import_stmt, alias))
-
- tmp_path.joinpath("interfaces").mkdir()
- with tmp_path.joinpath("interfaces/Bar.vy").open("w") as fp:
- fp.write(BAR_CODE)
-
- assert compile_files([foo_path], ["combined_json"], root_folder=tmp_path)
-
-
-def test_import_parent_folder(tmp_path, assert_compile_failed):
- tmp_path.joinpath("contracts").mkdir()
- tmp_path.joinpath("contracts/baz").mkdir()
-
- foo_path = tmp_path.joinpath("contracts/baz/foo.vy")
- with foo_path.open("w") as fp:
- fp.write(FOO_CODE.format("from ... import Bar", "Bar"))
-
- with tmp_path.joinpath("Bar.vy").open("w") as fp:
- fp.write(BAR_CODE)
-
- assert compile_files([foo_path], ["combined_json"], root_folder=tmp_path)
- # Cannot perform relative import outside of base folder
- with pytest.raises(FileNotFoundError):
- compile_files([foo_path], ["combined_json"], root_folder=tmp_path.joinpath("contracts"))
-
-
-META_IMPORT_STMT = [
- "import Meta as Meta",
- "import contracts.Meta as Meta",
- "from . import Meta",
- "from contracts import Meta",
-]
-
-
-@pytest.mark.parametrize("import_stmt", META_IMPORT_STMT)
-def test_import_self_interface(import_stmt, tmp_path):
- # a contract can access its derived interface by importing itself
- code = f"""
-{import_stmt}
-
-struct FooStruct:
- foo_: uint256
-
-@external
-def know_thyself(a: address) -> FooStruct:
- return Meta(a).be_known()
-
-@external
-def be_known() -> FooStruct:
- return FooStruct({{foo_: 42}})
- """
-
- tmp_path.joinpath("contracts").mkdir()
-
- meta_path = tmp_path.joinpath("contracts/Meta.vy")
- with meta_path.open("w") as fp:
- fp.write(code)
-
- assert compile_files([meta_path], ["combined_json"], root_folder=tmp_path)
-
-
-DERIVED_IMPORT_STMT_BAZ = ["import Foo as Foo", "from . import Foo"]
-
-DERIVED_IMPORT_STMT_FOO = ["import Bar as Bar", "from . import Bar"]
-
-
-@pytest.mark.parametrize("import_stmt_baz", DERIVED_IMPORT_STMT_BAZ)
-@pytest.mark.parametrize("import_stmt_foo", DERIVED_IMPORT_STMT_FOO)
-def test_derived_interface_imports(import_stmt_baz, import_stmt_foo, tmp_path):
- # contracts-as-interfaces should be able to contain import statements
- baz_code = f"""
-{import_stmt_baz}
-
-struct FooStruct:
- foo_: uint256
-
-@external
-def foo(a: address) -> FooStruct:
- return Foo(a).foo()
-
-@external
-def bar(_foo: address, _bar: address) -> FooStruct:
- return Foo(_foo).bar(_bar)
- """
-
- with tmp_path.joinpath("Foo.vy").open("w") as fp:
- fp.write(FOO_CODE.format(import_stmt_foo, "Bar"))
-
- with tmp_path.joinpath("Bar.vy").open("w") as fp:
- fp.write(BAR_CODE)
-
- baz_path = tmp_path.joinpath("Baz.vy")
- with baz_path.open("w") as fp:
- fp.write(baz_code)
-
- assert compile_files([baz_path], ["combined_json"], root_folder=tmp_path)
-
-
-def test_local_namespace(tmp_path):
- # interface code namespaces should be isolated
- # all of these contract should be able to compile together
- codes = [
- "import foo as FooBar",
- "import bar as FooBar",
- "import foo as BarFoo",
- "import bar as BarFoo",
- ]
- struct_def = """
-struct FooStruct:
- foo_: uint256
-
- """
-
- compile_paths = []
- for i, code in enumerate(codes):
- code += struct_def
- path = tmp_path.joinpath(f"code{i}.vy")
- with path.open("w") as fp:
- fp.write(code)
- compile_paths.append(path)
-
- for file_name in ("foo.vy", "bar.vy"):
- with tmp_path.joinpath(file_name).open("w") as fp:
- fp.write(BAR_CODE)
-
- assert compile_files(compile_paths, ["combined_json"], root_folder=tmp_path)
-
-
-def test_get_interface_file_path(tmp_path):
- for file_name in ("foo.vy", "foo.json", "bar.vy", "baz.json", "potato"):
- with tmp_path.joinpath(file_name).open("w") as fp:
- fp.write("")
-
- tmp_path.joinpath("interfaces").mkdir()
- for file_name in ("interfaces/foo.json", "interfaces/bar"):
- with tmp_path.joinpath(file_name).open("w") as fp:
- fp.write("")
-
- base_paths = [tmp_path, tmp_path.joinpath("interfaces")]
- assert get_interface_file_path(base_paths, "foo") == tmp_path.joinpath("foo.vy")
- assert get_interface_file_path(base_paths, "bar") == tmp_path.joinpath("bar.vy")
- assert get_interface_file_path(base_paths, "baz") == tmp_path.joinpath("baz.json")
-
- base_paths = [tmp_path.joinpath("interfaces"), tmp_path]
- assert get_interface_file_path(base_paths, "foo") == tmp_path.joinpath("interfaces/foo.json")
- assert get_interface_file_path(base_paths, "bar") == tmp_path.joinpath("bar.vy")
- assert get_interface_file_path(base_paths, "baz") == tmp_path.joinpath("baz.json")
-
- with pytest.raises(Exception):
- get_interface_file_path(base_paths, "potato")
-
-
-def test_compile_outside_root_path(tmp_path):
- foo_path = tmp_path.joinpath("foo.vy")
- with foo_path.open("w") as fp:
- fp.write(FOO_CODE.format("import bar as Bar", "Bar"))
-
- bar_path = tmp_path.joinpath("bar.vy")
- with bar_path.open("w") as fp:
- fp.write(BAR_CODE)
-
- assert compile_files([foo_path, bar_path], ["combined_json"], root_folder=".")
diff --git a/tests/cli/vyper_compile/test_parse_args.py b/tests/cli/vyper_compile/test_parse_args.py
index a676a7836b..0e8c4e9605 100644
--- a/tests/cli/vyper_compile/test_parse_args.py
+++ b/tests/cli/vyper_compile/test_parse_args.py
@@ -21,7 +21,9 @@ def foo() -> bool:
bar_path = chdir_path.joinpath("bar.vy")
with bar_path.open("w") as fp:
fp.write(code)
+
_parse_args([str(bar_path)]) # absolute path
os.chdir(chdir_path.parent)
+
_parse_args([str(bar_path)]) # absolute path, subfolder of cwd
_parse_args([str(bar_path.relative_to(chdir_path.parent))]) # relative path
diff --git a/tests/cli/vyper_json/test_compile_from_input_dict.py b/tests/cli/vyper_json/test_compile_from_input_dict.py
deleted file mode 100644
index a5a31a522b..0000000000
--- a/tests/cli/vyper_json/test_compile_from_input_dict.py
+++ /dev/null
@@ -1,141 +0,0 @@
-#!/usr/bin/env python3
-
-from copy import deepcopy
-
-import pytest
-
-import vyper
-from vyper.cli.vyper_json import (
- TRANSLATE_MAP,
- compile_from_input_dict,
- exc_handler_raises,
- exc_handler_to_dict,
-)
-from vyper.exceptions import InvalidType, JSONError, SyntaxException
-
-FOO_CODE = """
-import contracts.bar as Bar
-
-@external
-def foo(a: address) -> bool:
- return Bar(a).bar(1)
-
-@external
-def baz() -> uint256:
- return self.balance
-"""
-
-BAR_CODE = """
-@external
-def bar(a: uint256) -> bool:
- return True
-"""
-
-BAD_SYNTAX_CODE = """
-def bar()>:
-"""
-
-BAD_COMPILER_CODE = """
-@external
-def oopsie(a: uint256) -> bool:
- return 42
-"""
-
-BAR_ABI = [
- {
- "name": "bar",
- "outputs": [{"type": "bool", "name": "out"}],
- "inputs": [{"type": "uint256", "name": "a"}],
- "stateMutability": "nonpayable",
- "type": "function",
- "gas": 313,
- }
-]
-
-INPUT_JSON = {
- "language": "Vyper",
- "sources": {
- "contracts/foo.vy": {"content": FOO_CODE},
- "contracts/bar.vy": {"content": BAR_CODE},
- },
- "interfaces": {"contracts/bar.json": {"abi": BAR_ABI}},
- "settings": {"outputSelection": {"*": ["*"]}},
-}
-
-
-def test_root_folder_not_exists():
- with pytest.raises(FileNotFoundError):
- compile_from_input_dict({}, root_folder="/path/that/does/not/exist")
-
-
-def test_wrong_language():
- with pytest.raises(JSONError):
- compile_from_input_dict({"language": "Solidity"})
-
-
-def test_exc_handler_raises_syntax():
- input_json = deepcopy(INPUT_JSON)
- input_json["sources"]["badcode.vy"] = {"content": BAD_SYNTAX_CODE}
- with pytest.raises(SyntaxException):
- compile_from_input_dict(input_json, exc_handler_raises)
-
-
-def test_exc_handler_to_dict_syntax():
- input_json = deepcopy(INPUT_JSON)
- input_json["sources"]["badcode.vy"] = {"content": BAD_SYNTAX_CODE}
- result, _ = compile_from_input_dict(input_json, exc_handler_to_dict)
- assert "errors" in result
- assert len(result["errors"]) == 1
- error = result["errors"][0]
- assert error["component"] == "parser"
- assert error["type"] == "SyntaxException"
-
-
-def test_exc_handler_raises_compiler():
- input_json = deepcopy(INPUT_JSON)
- input_json["sources"]["badcode.vy"] = {"content": BAD_COMPILER_CODE}
- with pytest.raises(InvalidType):
- compile_from_input_dict(input_json, exc_handler_raises)
-
-
-def test_exc_handler_to_dict_compiler():
- input_json = deepcopy(INPUT_JSON)
- input_json["sources"]["badcode.vy"] = {"content": BAD_COMPILER_CODE}
- result, _ = compile_from_input_dict(input_json, exc_handler_to_dict)
- assert sorted(result.keys()) == ["compiler", "errors"]
- assert result["compiler"] == f"vyper-{vyper.__version__}"
- assert len(result["errors"]) == 1
- error = result["errors"][0]
- assert error["component"] == "compiler"
- assert error["type"] == "InvalidType"
-
-
-def test_source_ids_increment():
- input_json = deepcopy(INPUT_JSON)
- input_json["settings"]["outputSelection"] = {"*": ["evm.deployedBytecode.sourceMap"]}
- result, _ = compile_from_input_dict(input_json)
- assert result["contracts/bar.vy"]["source_map"]["pc_pos_map_compressed"].startswith("-1:-1:0")
- assert result["contracts/foo.vy"]["source_map"]["pc_pos_map_compressed"].startswith("-1:-1:1")
-
-
-def test_outputs():
- result, _ = compile_from_input_dict(INPUT_JSON)
- assert sorted(result.keys()) == ["contracts/bar.vy", "contracts/foo.vy"]
- assert sorted(result["contracts/bar.vy"].keys()) == sorted(set(TRANSLATE_MAP.values()))
-
-
-def test_relative_import_paths():
- input_json = deepcopy(INPUT_JSON)
- input_json["sources"]["contracts/potato/baz/baz.vy"] = {"content": """from ... import foo"""}
- input_json["sources"]["contracts/potato/baz/potato.vy"] = {"content": """from . import baz"""}
- input_json["sources"]["contracts/potato/footato.vy"] = {"content": """from baz import baz"""}
- compile_from_input_dict(input_json)
-
-
-def test_evm_version():
- # should compile differently because of SELFBALANCE
- input_json = deepcopy(INPUT_JSON)
- input_json["settings"]["evmVersion"] = "byzantium"
- compiled = compile_from_input_dict(input_json)
- input_json["settings"]["evmVersion"] = "istanbul"
- assert compiled != compile_from_input_dict(input_json)
diff --git a/tests/cli/vyper_json/test_compile_json.py b/tests/cli/vyper_json/test_compile_json.py
index f03006c4ad..732762d72b 100644
--- a/tests/cli/vyper_json/test_compile_json.py
+++ b/tests/cli/vyper_json/test_compile_json.py
@@ -1,12 +1,11 @@
-#!/usr/bin/env python3
-
import json
-from copy import deepcopy
import pytest
-from vyper.cli.vyper_json import compile_from_input_dict, compile_json
-from vyper.exceptions import JSONError
+import vyper
+from vyper.cli.vyper_json import compile_from_input_dict, compile_json, exc_handler_to_dict
+from vyper.compiler import OUTPUT_FORMATS, compile_code
+from vyper.exceptions import InvalidType, JSONError, SyntaxException
FOO_CODE = """
import contracts.bar as Bar
@@ -14,6 +13,10 @@
@external
def foo(a: address) -> bool:
return Bar(a).bar(1)
+
+@external
+def baz() -> uint256:
+ return self.balance
"""
BAR_CODE = """
@@ -22,6 +25,16 @@ def bar(a: uint256) -> bool:
return True
"""
+BAD_SYNTAX_CODE = """
+def bar()>:
+"""
+
+BAD_COMPILER_CODE = """
+@external
+def oopsie(a: uint256) -> bool:
+ return 42
+"""
+
BAR_ABI = [
{
"name": "bar",
@@ -29,23 +42,26 @@ def bar(a: uint256) -> bool:
"inputs": [{"type": "uint256", "name": "a"}],
"stateMutability": "nonpayable",
"type": "function",
- "gas": 313,
}
]
-INPUT_JSON = {
- "language": "Vyper",
- "sources": {
- "contracts/foo.vy": {"content": FOO_CODE},
- "contracts/bar.vy": {"content": BAR_CODE},
- },
- "interfaces": {"contracts/bar.json": {"abi": BAR_ABI}},
- "settings": {"outputSelection": {"*": ["*"]}},
-}
+
+@pytest.fixture(scope="function")
+def input_json():
+ return {
+ "language": "Vyper",
+ "sources": {
+ "contracts/foo.vy": {"content": FOO_CODE},
+ "contracts/bar.vy": {"content": BAR_CODE},
+ },
+ "interfaces": {"contracts/ibar.json": {"abi": BAR_ABI}},
+ "settings": {"outputSelection": {"*": ["*"]}},
+ }
-def test_input_formats():
- assert compile_json(INPUT_JSON) == compile_json(json.dumps(INPUT_JSON))
+# test string and dict inputs both work
+def test_string_input(input_json):
+ assert compile_json(input_json) == compile_json(json.dumps(input_json))
def test_bad_json():
@@ -53,10 +69,146 @@ def test_bad_json():
compile_json("this probably isn't valid JSON, is it")
-def test_keyerror_becomes_jsonerror():
- input_json = deepcopy(INPUT_JSON)
+def test_keyerror_becomes_jsonerror(input_json):
del input_json["sources"]
with pytest.raises(KeyError):
compile_from_input_dict(input_json)
with pytest.raises(JSONError):
compile_json(input_json)
+
+
+def test_compile_json(input_json, make_input_bundle):
+ input_bundle = make_input_bundle({"contracts/bar.vy": BAR_CODE})
+
+ foo = compile_code(
+ FOO_CODE,
+ source_id=0,
+ contract_name="contracts/foo.vy",
+ output_formats=OUTPUT_FORMATS,
+ input_bundle=input_bundle,
+ )
+ bar = compile_code(
+ BAR_CODE, source_id=1, contract_name="contracts/bar.vy", output_formats=OUTPUT_FORMATS
+ )
+
+ compile_code_results = {"contracts/bar.vy": bar, "contracts/foo.vy": foo}
+
+ output_json = compile_json(input_json)
+ assert list(output_json["contracts"].keys()) == ["contracts/foo.vy", "contracts/bar.vy"]
+
+ assert sorted(output_json.keys()) == ["compiler", "contracts", "sources"]
+ assert output_json["compiler"] == f"vyper-{vyper.__version__}"
+
+ for source_id, contract_name in enumerate(["foo", "bar"]):
+ path = f"contracts/{contract_name}.vy"
+ data = compile_code_results[path]
+ assert output_json["sources"][path] == {"id": source_id, "ast": data["ast_dict"]["ast"]}
+ assert output_json["contracts"][path][contract_name] == {
+ "abi": data["abi"],
+ "devdoc": data["devdoc"],
+ "interface": data["interface"],
+ "ir": data["ir_dict"],
+ "userdoc": data["userdoc"],
+ "metadata": data["metadata"],
+ "evm": {
+ "bytecode": {"object": data["bytecode"], "opcodes": data["opcodes"]},
+ "deployedBytecode": {
+ "object": data["bytecode_runtime"],
+ "opcodes": data["opcodes_runtime"],
+ "sourceMap": data["source_map"]["pc_pos_map_compressed"],
+ "sourceMapFull": data["source_map_full"],
+ },
+ "methodIdentifiers": data["method_identifiers"],
+ },
+ }
+
+
+def test_different_outputs(make_input_bundle, input_json):
+ input_json["settings"]["outputSelection"] = {
+ "contracts/bar.vy": "*",
+ "contracts/foo.vy": ["evm.methodIdentifiers"],
+ }
+ output_json = compile_json(input_json)
+ assert list(output_json["contracts"].keys()) == ["contracts/foo.vy", "contracts/bar.vy"]
+
+ assert sorted(output_json.keys()) == ["compiler", "contracts", "sources"]
+ assert output_json["compiler"] == f"vyper-{vyper.__version__}"
+
+ contracts = output_json["contracts"]
+
+ foo = contracts["contracts/foo.vy"]["foo"]
+ bar = contracts["contracts/bar.vy"]["bar"]
+ assert sorted(bar.keys()) == ["abi", "devdoc", "evm", "interface", "ir", "metadata", "userdoc"]
+
+ assert sorted(foo.keys()) == ["evm"]
+
+ # check method_identifiers
+ input_bundle = make_input_bundle({"contracts/bar.vy": BAR_CODE})
+ method_identifiers = compile_code(
+ FOO_CODE,
+ contract_name="contracts/foo.vy",
+ output_formats=["method_identifiers"],
+ input_bundle=input_bundle,
+ )["method_identifiers"]
+ assert foo["evm"]["methodIdentifiers"] == method_identifiers
+
+
+def test_root_folder_not_exists(input_json):
+ with pytest.raises(FileNotFoundError):
+ compile_json(input_json, root_folder="/path/that/does/not/exist")
+
+
+def test_wrong_language():
+ with pytest.raises(JSONError):
+ compile_json({"language": "Solidity"})
+
+
+def test_exc_handler_raises_syntax(input_json):
+ input_json["sources"]["badcode.vy"] = {"content": BAD_SYNTAX_CODE}
+ with pytest.raises(SyntaxException):
+ compile_json(input_json)
+
+
+def test_exc_handler_to_dict_syntax(input_json):
+ input_json["sources"]["badcode.vy"] = {"content": BAD_SYNTAX_CODE}
+ result = compile_json(input_json, exc_handler_to_dict)
+ assert "errors" in result
+ assert len(result["errors"]) == 1
+ error = result["errors"][0]
+ assert error["component"] == "compiler", error
+ assert error["type"] == "SyntaxException"
+
+
+def test_exc_handler_raises_compiler(input_json):
+ input_json["sources"]["badcode.vy"] = {"content": BAD_COMPILER_CODE}
+ with pytest.raises(InvalidType):
+ compile_json(input_json)
+
+
+def test_exc_handler_to_dict_compiler(input_json):
+ input_json["sources"]["badcode.vy"] = {"content": BAD_COMPILER_CODE}
+ result = compile_json(input_json, exc_handler_to_dict)
+ assert sorted(result.keys()) == ["compiler", "errors"]
+ assert result["compiler"] == f"vyper-{vyper.__version__}"
+ assert len(result["errors"]) == 1
+ error = result["errors"][0]
+ assert error["component"] == "compiler"
+ assert error["type"] == "InvalidType"
+
+
+def test_source_ids_increment(input_json):
+ input_json["settings"]["outputSelection"] = {"*": ["evm.deployedBytecode.sourceMap"]}
+ result = compile_json(input_json)
+
+ def get(filename, contractname):
+ return result["contracts"][filename][contractname]["evm"]["deployedBytecode"]["sourceMap"]
+
+ assert get("contracts/foo.vy", "foo").startswith("-1:-1:0")
+ assert get("contracts/bar.vy", "bar").startswith("-1:-1:1")
+
+
+def test_relative_import_paths(input_json):
+ input_json["sources"]["contracts/potato/baz/baz.vy"] = {"content": """from ... import foo"""}
+ input_json["sources"]["contracts/potato/baz/potato.vy"] = {"content": """from . import baz"""}
+ input_json["sources"]["contracts/potato/footato.vy"] = {"content": """from baz import baz"""}
+ compile_from_input_dict(input_json)
diff --git a/tests/cli/vyper_json/test_get_contracts.py b/tests/cli/vyper_json/test_get_contracts.py
deleted file mode 100644
index 86a5052f72..0000000000
--- a/tests/cli/vyper_json/test_get_contracts.py
+++ /dev/null
@@ -1,71 +0,0 @@
-#!/usr/bin/env python3
-
-import pytest
-
-from vyper.cli.vyper_json import get_input_dict_contracts
-from vyper.exceptions import JSONError
-from vyper.utils import keccak256
-
-FOO_CODE = """
-import contracts.bar as Bar
-
-@external
-def foo(a: address) -> bool:
- return Bar(a).bar(1)
-"""
-
-BAR_CODE = """
-@external
-def bar(a: uint256) -> bool:
- return True
-"""
-
-
-def test_no_sources():
- with pytest.raises(KeyError):
- get_input_dict_contracts({})
-
-
-def test_contracts_urls():
- with pytest.raises(JSONError):
- get_input_dict_contracts({"sources": {"foo.vy": {"urls": ["https://foo.code.com/"]}}})
-
-
-def test_contracts_no_content_key():
- with pytest.raises(JSONError):
- get_input_dict_contracts({"sources": {"foo.vy": FOO_CODE}})
-
-
-def test_contracts_keccak():
- hash_ = keccak256(FOO_CODE.encode()).hex()
-
- input_json = {"sources": {"foo.vy": {"content": FOO_CODE, "keccak256": hash_}}}
- get_input_dict_contracts(input_json)
-
- input_json["sources"]["foo.vy"]["keccak256"] = "0x" + hash_
- get_input_dict_contracts(input_json)
-
- input_json["sources"]["foo.vy"]["keccak256"] = "0x1234567890"
- with pytest.raises(JSONError):
- get_input_dict_contracts(input_json)
-
-
-def test_contracts_bad_path():
- input_json = {"sources": {"../foo.vy": {"content": FOO_CODE}}}
- with pytest.raises(JSONError):
- get_input_dict_contracts(input_json)
-
-
-def test_contract_collision():
- # ./foo.vy and foo.vy will resolve to the same path
- input_json = {"sources": {"./foo.vy": {"content": FOO_CODE}, "foo.vy": {"content": FOO_CODE}}}
- with pytest.raises(JSONError):
- get_input_dict_contracts(input_json)
-
-
-def test_contracts_return_value():
- input_json = {
- "sources": {"foo.vy": {"content": FOO_CODE}, "contracts/bar.vy": {"content": BAR_CODE}}
- }
- result = get_input_dict_contracts(input_json)
- assert result == {"foo.vy": FOO_CODE, "contracts/bar.vy": BAR_CODE}
diff --git a/tests/cli/vyper_json/test_get_inputs.py b/tests/cli/vyper_json/test_get_inputs.py
new file mode 100644
index 0000000000..6e323a91bd
--- /dev/null
+++ b/tests/cli/vyper_json/test_get_inputs.py
@@ -0,0 +1,142 @@
+from pathlib import PurePath
+
+import pytest
+
+from vyper.cli.vyper_json import get_compilation_targets, get_inputs
+from vyper.exceptions import JSONError
+from vyper.utils import keccak256
+
+FOO_CODE = """
+import contracts.bar as Bar
+
+@external
+def foo(a: address) -> bool:
+ return Bar(a).bar(1)
+"""
+
+BAR_CODE = """
+@external
+def bar(a: uint256) -> bool:
+ return True
+"""
+
+
+def test_no_sources():
+ with pytest.raises(KeyError):
+ get_inputs({})
+
+
+def test_contracts_urls():
+ with pytest.raises(JSONError):
+ get_inputs({"sources": {"foo.vy": {"urls": ["https://foo.code.com/"]}}})
+
+
+def test_contracts_no_content_key():
+ with pytest.raises(JSONError):
+ get_inputs({"sources": {"foo.vy": FOO_CODE}})
+
+
+def test_contracts_keccak():
+ hash_ = keccak256(FOO_CODE.encode()).hex()
+
+ input_json = {"sources": {"foo.vy": {"content": FOO_CODE, "keccak256": hash_}}}
+ get_inputs(input_json)
+
+ input_json["sources"]["foo.vy"]["keccak256"] = "0x" + hash_
+ get_inputs(input_json)
+
+ input_json["sources"]["foo.vy"]["keccak256"] = "0x1234567890"
+ with pytest.raises(JSONError):
+ get_inputs(input_json)
+
+
+def test_contracts_outside_pwd():
+ input_json = {"sources": {"../foo.vy": {"content": FOO_CODE}}}
+ get_inputs(input_json)
+
+
+def test_contract_collision():
+ # ./foo.vy and foo.vy will resolve to the same path
+ input_json = {"sources": {"./foo.vy": {"content": FOO_CODE}, "foo.vy": {"content": FOO_CODE}}}
+ with pytest.raises(JSONError):
+ get_inputs(input_json)
+
+
+def test_contracts_return_value():
+ input_json = {
+ "sources": {"foo.vy": {"content": FOO_CODE}, "contracts/bar.vy": {"content": BAR_CODE}}
+ }
+ result = get_inputs(input_json)
+ assert result == {
+ PurePath("foo.vy"): {"content": FOO_CODE},
+ PurePath("contracts/bar.vy"): {"content": BAR_CODE},
+ }
+
+
+BAR_ABI = [
+ {
+ "name": "bar",
+ "outputs": [{"type": "bool", "name": "out"}],
+ "inputs": [{"type": "uint256", "name": "a"}],
+ "stateMutability": "nonpayable",
+ "type": "function",
+ }
+]
+
+
+# tests to get interfaces from input dicts
+
+
+def test_interface_collision():
+ input_json = {
+ "sources": {"foo.vy": {"content": FOO_CODE}},
+ "interfaces": {"bar.json": {"abi": BAR_ABI}, "bar.vy": {"content": BAR_CODE}},
+ }
+ with pytest.raises(JSONError):
+ get_inputs(input_json)
+
+
+def test_json_no_abi():
+ input_json = {
+ "sources": {"foo.vy": {"content": FOO_CODE}},
+ "interfaces": {"bar.json": {"content": BAR_ABI}},
+ }
+ with pytest.raises(JSONError):
+ get_inputs(input_json)
+
+
+def test_vy_no_content():
+ input_json = {
+ "sources": {"foo.vy": {"content": FOO_CODE}},
+ "interfaces": {"bar.vy": {"abi": BAR_CODE}},
+ }
+ with pytest.raises(JSONError):
+ get_inputs(input_json)
+
+
+def test_interfaces_output():
+ input_json = {
+ "sources": {"foo.vy": {"content": FOO_CODE}},
+ "interfaces": {
+ "bar.json": {"abi": BAR_ABI},
+ "interface.folder/bar2.vy": {"content": BAR_CODE},
+ },
+ }
+ targets = get_compilation_targets(input_json)
+ assert targets == [PurePath("foo.vy")]
+
+ result = get_inputs(input_json)
+ assert result == {
+ PurePath("foo.vy"): {"content": FOO_CODE},
+ PurePath("bar.json"): {"abi": BAR_ABI},
+ PurePath("interface.folder/bar2.vy"): {"content": BAR_CODE},
+ }
+
+
+# EIP-2678 -- not currently supported
+@pytest.mark.xfail
+def test_manifest_output():
+ input_json = {"interfaces": {"bar.json": {"contractTypes": {"Bar": {"abi": BAR_ABI}}}}}
+ result = get_inputs(input_json)
+ assert isinstance(result, dict)
+ assert result == {"Bar": {"type": "json", "code": BAR_ABI}}
diff --git a/tests/cli/vyper_json/test_get_settings.py b/tests/cli/vyper_json/test_get_settings.py
index ca60d2cf5a..989d4565cd 100644
--- a/tests/cli/vyper_json/test_get_settings.py
+++ b/tests/cli/vyper_json/test_get_settings.py
@@ -1,9 +1,6 @@
-#!/usr/bin/env python3
-
import pytest
from vyper.cli.vyper_json import get_evm_version
-from vyper.evm.opcodes import DEFAULT_EVM_VERSION
from vyper.exceptions import JSONError
@@ -12,16 +9,22 @@ def test_unknown_evm():
get_evm_version({"settings": {"evmVersion": "foo"}})
-@pytest.mark.parametrize("evm_version", ["homestead", "tangerineWhistle", "spuriousDragon"])
+@pytest.mark.parametrize(
+ "evm_version",
+ [
+ "homestead",
+ "tangerineWhistle",
+ "spuriousDragon",
+ "byzantium",
+ "constantinople",
+ "petersburg",
+ ],
+)
def test_early_evm(evm_version):
with pytest.raises(JSONError):
get_evm_version({"settings": {"evmVersion": evm_version}})
-@pytest.mark.parametrize("evm_version", ["byzantium", "constantinople", "petersburg"])
+@pytest.mark.parametrize("evm_version", ["istanbul", "berlin", "paris", "shanghai", "cancun"])
def test_valid_evm(evm_version):
assert evm_version == get_evm_version({"settings": {"evmVersion": evm_version}})
-
-
-def test_default_evm():
- assert get_evm_version({}) == DEFAULT_EVM_VERSION
diff --git a/tests/cli/vyper_json/test_interfaces.py b/tests/cli/vyper_json/test_interfaces.py
deleted file mode 100644
index 7804ae1c3d..0000000000
--- a/tests/cli/vyper_json/test_interfaces.py
+++ /dev/null
@@ -1,126 +0,0 @@
-#!/usr/bin/env python3
-
-import pytest
-
-from vyper.cli.vyper_json import get_input_dict_interfaces, get_interface_codes
-from vyper.exceptions import JSONError
-
-FOO_CODE = """
-import contracts.bar as Bar
-
-@external
-def foo(a: address) -> bool:
- return Bar(a).bar(1)
-"""
-
-BAR_CODE = """
-@external
-def bar(a: uint256) -> bool:
- return True
-"""
-
-BAR_ABI = [
- {
- "name": "bar",
- "outputs": [{"type": "bool", "name": "out"}],
- "inputs": [{"type": "uint256", "name": "a"}],
- "stateMutability": "nonpayable",
- "type": "function",
- "gas": 313,
- }
-]
-
-
-# get_input_dict_interfaces tests
-
-
-def test_no_interfaces():
- result = get_input_dict_interfaces({})
- assert isinstance(result, dict)
- assert not result
-
-
-def test_interface_collision():
- input_json = {"interfaces": {"bar.json": {"abi": BAR_ABI}, "bar.vy": {"content": BAR_CODE}}}
- with pytest.raises(JSONError):
- get_input_dict_interfaces(input_json)
-
-
-def test_interfaces_wrong_suffix():
- input_json = {"interfaces": {"foo.abi": {"content": FOO_CODE}}}
- with pytest.raises(JSONError):
- get_input_dict_interfaces(input_json)
-
- input_json = {"interfaces": {"interface.folder/foo": {"content": FOO_CODE}}}
- with pytest.raises(JSONError):
- get_input_dict_interfaces(input_json)
-
-
-def test_json_no_abi():
- input_json = {"interfaces": {"bar.json": {"content": BAR_ABI}}}
- with pytest.raises(JSONError):
- get_input_dict_interfaces(input_json)
-
-
-def test_vy_no_content():
- input_json = {"interfaces": {"bar.vy": {"abi": BAR_CODE}}}
- with pytest.raises(JSONError):
- get_input_dict_interfaces(input_json)
-
-
-def test_interfaces_output():
- input_json = {
- "interfaces": {
- "bar.json": {"abi": BAR_ABI},
- "interface.folder/bar2.vy": {"content": BAR_CODE},
- }
- }
- result = get_input_dict_interfaces(input_json)
- assert isinstance(result, dict)
- assert result == {
- "bar": {"type": "json", "code": BAR_ABI},
- "interface.folder/bar2": {"type": "vyper", "code": BAR_CODE},
- }
-
-
-def test_manifest_output():
- input_json = {"interfaces": {"bar.json": {"contractTypes": {"Bar": {"abi": BAR_ABI}}}}}
- result = get_input_dict_interfaces(input_json)
- assert isinstance(result, dict)
- assert result == {"Bar": {"type": "json", "code": BAR_ABI}}
-
-
-# get_interface_codes tests
-
-
-def test_interface_codes_from_contracts():
- # interface should be generated from contract
- assert get_interface_codes(
- None, "foo.vy", {"foo.vy": FOO_CODE, "contracts/bar.vy": BAR_CODE}, {}
- )
- assert get_interface_codes(
- None, "foo/foo.vy", {"foo/foo.vy": FOO_CODE, "contracts/bar.vy": BAR_CODE}, {}
- )
-
-
-def test_interface_codes_from_interfaces():
- # existing interface should be given preference over contract-as-interface
- contracts = {"foo.vy": FOO_CODE, "contacts/bar.vy": BAR_CODE}
- result = get_interface_codes(None, "foo.vy", contracts, {"contracts/bar": "bar"})
- assert result["Bar"] == "bar"
-
-
-def test_root_path(tmp_path):
- tmp_path.joinpath("contracts").mkdir()
- with tmp_path.joinpath("contracts/bar.vy").open("w") as fp:
- fp.write("bar")
-
- with pytest.raises(FileNotFoundError):
- get_interface_codes(None, "foo.vy", {"foo.vy": FOO_CODE}, {})
-
- # interface from file system should take lowest priority
- result = get_interface_codes(tmp_path, "foo.vy", {"foo.vy": FOO_CODE}, {})
- assert result["Bar"] == {"code": "bar", "type": "vyper"}
- contracts = {"foo.vy": FOO_CODE, "contracts/bar.vy": BAR_CODE}
- result = get_interface_codes(None, "foo.vy", contracts, {})
- assert result["Bar"] == {"code": BAR_CODE, "type": "vyper"}
diff --git a/tests/cli/vyper_json/test_output_dict.py b/tests/cli/vyper_json/test_output_dict.py
deleted file mode 100644
index e2a3466ccf..0000000000
--- a/tests/cli/vyper_json/test_output_dict.py
+++ /dev/null
@@ -1,38 +0,0 @@
-#!/usr/bin/env python3
-
-import vyper
-from vyper.cli.vyper_json import format_to_output_dict
-from vyper.compiler import OUTPUT_FORMATS, compile_codes
-
-FOO_CODE = """
-@external
-def foo() -> bool:
- return True
-"""
-
-
-def test_keys():
- compiler_data = compile_codes({"foo.vy": FOO_CODE}, output_formats=list(OUTPUT_FORMATS.keys()))
- output_json = format_to_output_dict(compiler_data)
- assert sorted(output_json.keys()) == ["compiler", "contracts", "sources"]
- assert output_json["compiler"] == f"vyper-{vyper.__version__}"
- data = compiler_data["foo.vy"]
- assert output_json["sources"]["foo.vy"] == {"id": 0, "ast": data["ast_dict"]["ast"]}
- assert output_json["contracts"]["foo.vy"]["foo"] == {
- "abi": data["abi"],
- "devdoc": data["devdoc"],
- "interface": data["interface"],
- "ir": data["ir_dict"],
- "userdoc": data["userdoc"],
- "metadata": data["metadata"],
- "evm": {
- "bytecode": {"object": data["bytecode"], "opcodes": data["opcodes"]},
- "deployedBytecode": {
- "object": data["bytecode_runtime"],
- "opcodes": data["opcodes_runtime"],
- "sourceMap": data["source_map"]["pc_pos_map_compressed"],
- "sourceMapFull": data["source_map_full"],
- },
- "methodIdentifiers": data["method_identifiers"],
- },
- }
diff --git a/tests/cli/vyper_json/test_output_selection.py b/tests/cli/vyper_json/test_output_selection.py
index c72f06f5a7..78ad7404f2 100644
--- a/tests/cli/vyper_json/test_output_selection.py
+++ b/tests/cli/vyper_json/test_output_selection.py
@@ -1,54 +1,60 @@
-#!/usr/bin/env python3
+from pathlib import PurePath
import pytest
-from vyper.cli.vyper_json import TRANSLATE_MAP, get_input_dict_output_formats
+from vyper.cli.vyper_json import TRANSLATE_MAP, get_output_formats
from vyper.exceptions import JSONError
def test_no_outputs():
with pytest.raises(KeyError):
- get_input_dict_output_formats({}, {})
+ get_output_formats({}, {})
def test_invalid_output():
input_json = {"settings": {"outputSelection": {"foo.vy": ["abi", "foobar"]}}}
- sources = {"foo.vy": ""}
+ targets = [PurePath("foo.vy")]
with pytest.raises(JSONError):
- get_input_dict_output_formats(input_json, sources)
+ get_output_formats(input_json, targets)
def test_unknown_contract():
input_json = {"settings": {"outputSelection": {"bar.vy": ["abi"]}}}
- sources = {"foo.vy": ""}
+ targets = [PurePath("foo.vy")]
with pytest.raises(JSONError):
- get_input_dict_output_formats(input_json, sources)
+ get_output_formats(input_json, targets)
@pytest.mark.parametrize("output", TRANSLATE_MAP.items())
def test_translate_map(output):
input_json = {"settings": {"outputSelection": {"foo.vy": [output[0]]}}}
- sources = {"foo.vy": ""}
- assert get_input_dict_output_formats(input_json, sources) == {"foo.vy": [output[1]]}
+ targets = [PurePath("foo.vy")]
+ assert get_output_formats(input_json, targets) == {PurePath("foo.vy"): [output[1]]}
def test_star():
input_json = {"settings": {"outputSelection": {"*": ["*"]}}}
- sources = {"foo.vy": "", "bar.vy": ""}
+ targets = [PurePath("foo.vy"), PurePath("bar.vy")]
expected = sorted(set(TRANSLATE_MAP.values()))
- result = get_input_dict_output_formats(input_json, sources)
- assert result == {"foo.vy": expected, "bar.vy": expected}
+ result = get_output_formats(input_json, targets)
+ assert result == {PurePath("foo.vy"): expected, PurePath("bar.vy"): expected}
def test_evm():
input_json = {"settings": {"outputSelection": {"foo.vy": ["abi", "evm"]}}}
- sources = {"foo.vy": ""}
+ targets = [PurePath("foo.vy")]
expected = ["abi"] + sorted(v for k, v in TRANSLATE_MAP.items() if k.startswith("evm"))
- result = get_input_dict_output_formats(input_json, sources)
- assert result == {"foo.vy": expected}
+ result = get_output_formats(input_json, targets)
+ assert result == {PurePath("foo.vy"): expected}
def test_solc_style():
input_json = {"settings": {"outputSelection": {"foo.vy": {"": ["abi"], "foo.vy": ["ir"]}}}}
- sources = {"foo.vy": ""}
- assert get_input_dict_output_formats(input_json, sources) == {"foo.vy": ["abi", "ir_dict"]}
+ targets = [PurePath("foo.vy")]
+ assert get_output_formats(input_json, targets) == {PurePath("foo.vy"): ["abi", "ir_dict"]}
+
+
+def test_metadata():
+ input_json = {"settings": {"outputSelection": {"*": ["metadata"]}}}
+ targets = [PurePath("foo.vy")]
+ assert get_output_formats(input_json, targets) == {PurePath("foo.vy"): ["metadata"]}
diff --git a/tests/cli/vyper_json/test_parse_args_vyperjson.py b/tests/cli/vyper_json/test_parse_args_vyperjson.py
index 08da5f1888..3b0f700c7e 100644
--- a/tests/cli/vyper_json/test_parse_args_vyperjson.py
+++ b/tests/cli/vyper_json/test_parse_args_vyperjson.py
@@ -29,7 +29,6 @@ def bar(a: uint256) -> bool:
"inputs": [{"type": "uint256", "name": "a"}],
"stateMutability": "nonpayable",
"type": "function",
- "gas": 313,
}
]
@@ -39,7 +38,7 @@ def bar(a: uint256) -> bool:
"contracts/foo.vy": {"content": FOO_CODE},
"contracts/bar.vy": {"content": BAR_CODE},
},
- "interfaces": {"contracts/bar.json": {"abi": BAR_ABI}},
+ "interfaces": {"contracts/ibar.json": {"abi": BAR_ABI}},
"settings": {"outputSelection": {"*": ["*"]}},
}
@@ -57,7 +56,7 @@ def test_to_stdout(tmp_path, capfd):
_parse_args([path.absolute().as_posix()])
out, _ = capfd.readouterr()
output_json = json.loads(out)
- assert _no_errors(output_json)
+ assert _no_errors(output_json), (INPUT_JSON, output_json)
assert "contracts/foo.vy" in output_json["sources"]
assert "contracts/bar.vy" in output_json["sources"]
@@ -71,7 +70,7 @@ def test_to_file(tmp_path):
assert output_path.exists()
with output_path.open() as fp:
output_json = json.load(fp)
- assert _no_errors(output_json)
+ assert _no_errors(output_json), (INPUT_JSON, output_json)
assert "contracts/foo.vy" in output_json["sources"]
assert "contracts/bar.vy" in output_json["sources"]
diff --git a/tests/compiler/__init__.py b/tests/compiler/__init__.py
index e69de29bb2..35a11f851b 100644
--- a/tests/compiler/__init__.py
+++ b/tests/compiler/__init__.py
@@ -0,0 +1,2 @@
+# prevent module name collision between tests/compiler/test_pre_parser.py
+# and tests/ast/test_pre_parser.py
diff --git a/tests/compiler/asm/test_asm_optimizer.py b/tests/compiler/asm/test_asm_optimizer.py
new file mode 100644
index 0000000000..47b70a8c70
--- /dev/null
+++ b/tests/compiler/asm/test_asm_optimizer.py
@@ -0,0 +1,103 @@
+import pytest
+
+from vyper.compiler.phases import CompilerData
+from vyper.compiler.settings import OptimizationLevel, Settings
+
+codes = [
+ """
+s: uint256
+
+@internal
+def ctor_only():
+ self.s = 1
+
+@internal
+def runtime_only():
+ self.s = 2
+
+@external
+def bar():
+ self.runtime_only()
+
+@external
+def __init__():
+ self.ctor_only()
+ """,
+ # code with nested function in it
+ """
+s: uint256
+
+@internal
+def runtime_only():
+ self.s = 1
+
+@internal
+def foo():
+ self.runtime_only()
+
+@internal
+def ctor_only():
+ self.s += 1
+
+@external
+def bar():
+ self.foo()
+
+@external
+def __init__():
+ self.ctor_only()
+ """,
+ # code with loop in it, these are harder for dead code eliminator
+ """
+s: uint256
+
+@internal
+def ctor_only():
+ self.s = 1
+
+@internal
+def runtime_only():
+ for i in range(10):
+ self.s += 1
+
+@external
+def bar():
+ self.runtime_only()
+
+@external
+def __init__():
+ self.ctor_only()
+ """,
+]
+
+
+@pytest.mark.parametrize("code", codes)
+def test_dead_code_eliminator(code):
+ c = CompilerData(code, settings=Settings(optimize=OptimizationLevel.NONE))
+ initcode_asm = [i for i in c.assembly if not isinstance(i, list)]
+ runtime_asm = c.assembly_runtime
+
+ ctor_only_label = "_sym_internal_ctor_only___"
+ runtime_only_label = "_sym_internal_runtime_only___"
+
+ # qux reachable from unoptimized initcode, foo not reachable.
+ assert ctor_only_label + "_deploy" in initcode_asm
+ assert runtime_only_label + "_deploy" not in initcode_asm
+
+ # all labels should be in unoptimized runtime asm
+ for s in (ctor_only_label, runtime_only_label):
+ assert s + "_runtime" in runtime_asm
+
+ c = CompilerData(code, settings=Settings(optimize=OptimizationLevel.GAS))
+ initcode_asm = [i for i in c.assembly if not isinstance(i, list)]
+ runtime_asm = c.assembly_runtime
+
+ # ctor only label should not be in runtime code
+ for instr in runtime_asm:
+ if isinstance(instr, str):
+ assert not instr.startswith(ctor_only_label), instr
+
+ # runtime only label should not be in initcode asm
+ for instr in initcode_asm:
+ if isinstance(instr, str):
+ assert not instr.startswith(runtime_only_label), instr
diff --git a/tests/compiler/ir/test_compile_ir.py b/tests/compiler/ir/test_compile_ir.py
index 91007da33a..706c31e0f2 100644
--- a/tests/compiler/ir/test_compile_ir.py
+++ b/tests/compiler/ir/test_compile_ir.py
@@ -68,4 +68,4 @@ def test_pc_debugger():
debugger_ir = ["seq", ["mstore", 0, 32], ["pc_debugger"]]
ir_nodes = IRnode.from_list(debugger_ir)
_, line_number_map = compile_ir.assembly_to_evm(compile_ir.compile_to_assembly(ir_nodes))
- assert line_number_map["pc_breakpoints"][0] == 5
+ assert line_number_map["pc_breakpoints"][0] == 4
diff --git a/tests/compiler/ir/test_optimize_ir.py b/tests/compiler/ir/test_optimize_ir.py
index bac0e18d65..cb46ba238d 100644
--- a/tests/compiler/ir/test_optimize_ir.py
+++ b/tests/compiler/ir/test_optimize_ir.py
@@ -1,9 +1,13 @@
import pytest
from vyper.codegen.ir_node import IRnode
+from vyper.evm.opcodes import EVM_VERSIONS, anchor_evm_version
from vyper.exceptions import StaticAssertionException
from vyper.ir import optimizer
+POST_CANCUN = {k: v for k, v in EVM_VERSIONS.items() if v >= EVM_VERSIONS["cancun"]}
+
+
optimize_list = [
(["eq", 1, 2], [0]),
(["lt", 1, 2], [1]),
@@ -57,6 +61,14 @@
(["le", 0, "x"], [1]),
(["le", 0, ["sload", 0]], None), # no-op
(["ge", "x", 0], [1]),
+ (["le", "x", "x"], [1]),
+ (["ge", "x", "x"], [1]),
+ (["sle", "x", "x"], [1]),
+ (["sge", "x", "x"], [1]),
+ (["lt", "x", "x"], [0]),
+ (["gt", "x", "x"], [0]),
+ (["slt", "x", "x"], [0]),
+ (["sgt", "x", "x"], [0]),
# boundary conditions
(["slt", "x", -(2**255)], [0]),
(["sle", "x", -(2**255)], ["eq", "x", -(2**255)]),
@@ -135,7 +147,9 @@
(["sub", "x", 0], ["x"]),
(["sub", "x", "x"], [0]),
(["sub", ["sload", 0], ["sload", 0]], None),
- (["sub", ["callvalue"], ["callvalue"]], None),
+ (["sub", ["callvalue"], ["callvalue"]], [0]),
+ (["sub", ["msize"], ["msize"]], None),
+ (["sub", ["gas"], ["gas"]], None),
(["sub", -1, ["sload", 0]], ["not", ["sload", 0]]),
(["mul", "x", 1], ["x"]),
(["div", "x", 1], ["x"]),
@@ -202,7 +216,9 @@
(["eq", -1, ["add", -(2**255), 2**255 - 1]], [1]), # test compile-time wrapping
(["eq", -2, ["add", 2**256 - 1, 2**256 - 1]], [1]), # test compile-time wrapping
(["eq", "x", "x"], [1]),
- (["eq", "callvalue", "callvalue"], None),
+ (["eq", "gas", "gas"], None),
+ (["eq", "msize", "msize"], None),
+ (["eq", "callvalue", "callvalue"], [1]),
(["ne", "x", "x"], [0]),
]
@@ -253,3 +269,113 @@ def test_ir_optimizer(ir):
def test_static_assertions(ir, assert_compile_failed):
ir = IRnode.from_list(ir)
assert_compile_failed(lambda: optimizer.optimize(ir), StaticAssertionException)
+
+
+def test_operator_set_values():
+ # some sanity checks
+ assert optimizer.COMPARISON_OPS == {"lt", "gt", "le", "ge", "slt", "sgt", "sle", "sge"}
+ assert optimizer.STRICT_COMPARISON_OPS == {"lt", "gt", "slt", "sgt"}
+ assert optimizer.UNSTRICT_COMPARISON_OPS == {"le", "ge", "sle", "sge"}
+
+
+mload_merge_list = [
+ # copy "backward" with no overlap between src and dst buffers,
+ # OK to become mcopy
+ (
+ ["seq", ["mstore", 32, ["mload", 128]], ["mstore", 64, ["mload", 160]]],
+ ["mcopy", 32, 128, 64],
+ ),
+ # copy with overlap "backwards", OK to become mcopy
+ (["seq", ["mstore", 32, ["mload", 64]], ["mstore", 64, ["mload", 96]]], ["mcopy", 32, 64, 64]),
+ # "stationary" overlap (i.e. a no-op mcopy), OK to become mcopy
+ (["seq", ["mstore", 32, ["mload", 32]], ["mstore", 64, ["mload", 64]]], ["mcopy", 32, 32, 64]),
+ # copy "forward" with no overlap, OK to become mcopy
+ (["seq", ["mstore", 64, ["mload", 0]], ["mstore", 96, ["mload", 32]]], ["mcopy", 64, 0, 64]),
+ # copy "forwards" with overlap by one word, must NOT become mcopy
+ (["seq", ["mstore", 64, ["mload", 32]], ["mstore", 96, ["mload", 64]]], None),
+ # check "forward" overlap by one byte, must NOT become mcopy
+ (["seq", ["mstore", 64, ["mload", 1]], ["mstore", 96, ["mload", 33]]], None),
+ # check "forward" overlap by one byte again, must NOT become mcopy
+ (["seq", ["mstore", 63, ["mload", 0]], ["mstore", 95, ["mload", 32]]], None),
+ # copy 3 words with partial overlap "forwards", partially becomes mcopy
+ # (2 words are mcopied and 1 word is mload/mstored
+ (
+ [
+ "seq",
+ ["mstore", 96, ["mload", 32]],
+ ["mstore", 128, ["mload", 64]],
+ ["mstore", 160, ["mload", 96]],
+ ],
+ ["seq", ["mcopy", 96, 32, 64], ["mstore", 160, ["mload", 96]]],
+ ),
+ # copy 4 words with partial overlap "forwards", becomes 2 mcopies of 2 words each
+ (
+ [
+ "seq",
+ ["mstore", 96, ["mload", 32]],
+ ["mstore", 128, ["mload", 64]],
+ ["mstore", 160, ["mload", 96]],
+ ["mstore", 192, ["mload", 128]],
+ ],
+ ["seq", ["mcopy", 96, 32, 64], ["mcopy", 160, 96, 64]],
+ ),
+ # copy 4 words with 1 byte of overlap, must NOT become mcopy
+ (
+ [
+ "seq",
+ ["mstore", 96, ["mload", 33]],
+ ["mstore", 128, ["mload", 65]],
+ ["mstore", 160, ["mload", 97]],
+ ["mstore", 192, ["mload", 129]],
+ ],
+ None,
+ ),
+ # Ensure only sequential mstore + mload sequences are optimized
+ (
+ [
+ "seq",
+ ["mstore", 0, ["mload", 32]],
+ ["sstore", 0, ["calldataload", 4]],
+ ["mstore", 32, ["mload", 64]],
+ ],
+ None,
+ ),
+ # not-word aligned optimizations (not overlap)
+ (["seq", ["mstore", 0, ["mload", 1]], ["mstore", 32, ["mload", 33]]], ["mcopy", 0, 1, 64]),
+ # not-word aligned optimizations (overlap)
+ (["seq", ["mstore", 1, ["mload", 0]], ["mstore", 33, ["mload", 32]]], None),
+ # not-word aligned optimizations (overlap and not-overlap)
+ (
+ [
+ "seq",
+ ["mstore", 0, ["mload", 1]],
+ ["mstore", 32, ["mload", 33]],
+ ["mstore", 1, ["mload", 0]],
+ ["mstore", 33, ["mload", 32]],
+ ],
+ ["seq", ["mcopy", 0, 1, 64], ["mstore", 1, ["mload", 0]], ["mstore", 33, ["mload", 32]]],
+ ),
+ # overflow test
+ (
+ [
+ "seq",
+ ["mstore", 2**256 - 1 - 31 - 32, ["mload", 0]],
+ ["mstore", 2**256 - 1 - 31, ["mload", 32]],
+ ],
+ ["mcopy", 2**256 - 1 - 31 - 32, 0, 64],
+ ),
+]
+
+
+@pytest.mark.parametrize("ir", mload_merge_list)
+@pytest.mark.parametrize("evm_version", list(POST_CANCUN.keys()))
+def test_mload_merge(ir, evm_version):
+ with anchor_evm_version(evm_version):
+ optimized = optimizer.optimize(IRnode.from_list(ir[0]))
+ if ir[1] is None:
+ # no-op, assert optimizer does nothing
+ expected = IRnode.from_list(ir[0])
+ else:
+ expected = IRnode.from_list(ir[1])
+
+ assert optimized == expected
diff --git a/tests/compiler/test_bytecode_runtime.py b/tests/compiler/test_bytecode_runtime.py
index 86eff70a50..613ee4d2b8 100644
--- a/tests/compiler/test_bytecode_runtime.py
+++ b/tests/compiler/test_bytecode_runtime.py
@@ -1,14 +1,139 @@
-import vyper
+import cbor2
+import pytest
+import vyper
+from vyper.compiler.settings import OptimizationLevel, Settings
-def test_bytecode_runtime():
- code = """
+simple_contract_code = """
@external
def a() -> bool:
return True
- """
+"""
+
+many_functions = """
+@external
+def foo1():
+ pass
+
+@external
+def foo2():
+ pass
+
+@external
+def foo3():
+ pass
+
+@external
+def foo4():
+ pass
+
+@external
+def foo5():
+ pass
+"""
+
+has_immutables = """
+A_GOOD_PRIME: public(immutable(uint256))
+
+@external
+def __init__():
+ A_GOOD_PRIME = 967
+"""
+
+
+def _parse_cbor_metadata(initcode):
+ metadata_ofst = int.from_bytes(initcode[-2:], "big")
+ metadata = cbor2.loads(initcode[-metadata_ofst:-2])
+ return metadata
- out = vyper.compile_code(code, ["bytecode_runtime", "bytecode"])
+
+def test_bytecode_runtime():
+ out = vyper.compile_code(simple_contract_code, output_formats=["bytecode_runtime", "bytecode"])
assert len(out["bytecode"]) > len(out["bytecode_runtime"])
- assert out["bytecode_runtime"][2:] in out["bytecode"][2:]
+ assert out["bytecode_runtime"].removeprefix("0x") in out["bytecode"].removeprefix("0x")
+
+
+def test_bytecode_signature():
+ out = vyper.compile_code(simple_contract_code, output_formats=["bytecode_runtime", "bytecode"])
+
+ runtime_code = bytes.fromhex(out["bytecode_runtime"].removeprefix("0x"))
+ initcode = bytes.fromhex(out["bytecode"].removeprefix("0x"))
+
+ metadata = _parse_cbor_metadata(initcode)
+ runtime_len, data_section_lengths, immutables_len, compiler = metadata
+
+ assert runtime_len == len(runtime_code)
+ assert data_section_lengths == []
+ assert immutables_len == 0
+ assert compiler == {"vyper": list(vyper.version.version_tuple)}
+
+
+def test_bytecode_signature_dense_jumptable():
+ settings = Settings(optimize=OptimizationLevel.CODESIZE)
+
+ out = vyper.compile_code(
+ many_functions, output_formats=["bytecode_runtime", "bytecode"], settings=settings
+ )
+
+ runtime_code = bytes.fromhex(out["bytecode_runtime"].removeprefix("0x"))
+ initcode = bytes.fromhex(out["bytecode"].removeprefix("0x"))
+
+ metadata = _parse_cbor_metadata(initcode)
+ runtime_len, data_section_lengths, immutables_len, compiler = metadata
+
+ assert runtime_len == len(runtime_code)
+ assert data_section_lengths == [5, 35]
+ assert immutables_len == 0
+ assert compiler == {"vyper": list(vyper.version.version_tuple)}
+
+
+def test_bytecode_signature_sparse_jumptable():
+ settings = Settings(optimize=OptimizationLevel.GAS)
+
+ out = vyper.compile_code(
+ many_functions, output_formats=["bytecode_runtime", "bytecode"], settings=settings
+ )
+
+ runtime_code = bytes.fromhex(out["bytecode_runtime"].removeprefix("0x"))
+ initcode = bytes.fromhex(out["bytecode"].removeprefix("0x"))
+
+ metadata = _parse_cbor_metadata(initcode)
+ runtime_len, data_section_lengths, immutables_len, compiler = metadata
+
+ assert runtime_len == len(runtime_code)
+ assert data_section_lengths == [8]
+ assert immutables_len == 0
+ assert compiler == {"vyper": list(vyper.version.version_tuple)}
+
+
+def test_bytecode_signature_immutables():
+ out = vyper.compile_code(has_immutables, output_formats=["bytecode_runtime", "bytecode"])
+
+ runtime_code = bytes.fromhex(out["bytecode_runtime"].removeprefix("0x"))
+ initcode = bytes.fromhex(out["bytecode"].removeprefix("0x"))
+
+ metadata = _parse_cbor_metadata(initcode)
+ runtime_len, data_section_lengths, immutables_len, compiler = metadata
+
+ assert runtime_len == len(runtime_code)
+ assert data_section_lengths == []
+ assert immutables_len == 32
+ assert compiler == {"vyper": list(vyper.version.version_tuple)}
+
+
+# check that deployed bytecode actually matches the cbor metadata
+@pytest.mark.parametrize("code", [simple_contract_code, has_immutables, many_functions])
+def test_bytecode_signature_deployed(code, get_contract, w3):
+ c = get_contract(code)
+ deployed_code = w3.eth.get_code(c.address)
+
+ initcode = c._classic_contract.bytecode
+
+ metadata = _parse_cbor_metadata(initcode)
+ runtime_len, data_section_lengths, immutables_len, compiler = metadata
+
+ assert compiler == {"vyper": list(vyper.version.version_tuple)}
+
+ # runtime_len includes data sections but not immutables
+ assert len(deployed_code) == runtime_len + immutables_len
diff --git a/tests/compiler/test_compile_code.py b/tests/compiler/test_compile_code.py
index cdbf9d1f52..7af133e362 100644
--- a/tests/compiler/test_compile_code.py
+++ b/tests/compiler/test_compile_code.py
@@ -11,4 +11,4 @@ def a() -> bool:
return True
"""
with pytest.warns(vyper.warnings.ContractSizeLimitWarning):
- vyper.compile_code(code, ["bytecode_runtime"])
+ vyper.compile_code(code, output_formats=["bytecode_runtime"])
diff --git a/tests/compiler/test_default_settings.py b/tests/compiler/test_default_settings.py
new file mode 100644
index 0000000000..ca05170b61
--- /dev/null
+++ b/tests/compiler/test_default_settings.py
@@ -0,0 +1,27 @@
+from vyper.codegen import core
+from vyper.compiler.phases import CompilerData
+from vyper.compiler.settings import OptimizationLevel, _is_debug_mode
+
+
+def test_default_settings():
+ source_code = ""
+ compiler_data = CompilerData(source_code)
+ _ = compiler_data.vyper_module # force settings to be computed
+
+ assert compiler_data.settings.optimize == OptimizationLevel.GAS
+
+
+def test_default_opt_level():
+ assert OptimizationLevel.default() == OptimizationLevel.GAS
+
+
+def test_codegen_opt_level():
+ assert core._opt_level == OptimizationLevel.GAS
+ assert core._opt_gas() is True
+ assert core._opt_none() is False
+ assert core._opt_codesize() is False
+
+
+def test_debug_mode(pytestconfig):
+ debug_mode = pytestconfig.getoption("enable_compiler_debug_mode")
+ assert _is_debug_mode() == debug_mode
diff --git a/tests/compiler/test_input_bundle.py b/tests/compiler/test_input_bundle.py
new file mode 100644
index 0000000000..c49c81219b
--- /dev/null
+++ b/tests/compiler/test_input_bundle.py
@@ -0,0 +1,208 @@
+import json
+from pathlib import Path, PurePath
+
+import pytest
+
+from vyper.compiler.input_bundle import ABIInput, FileInput, FilesystemInputBundle, JSONInputBundle
+
+
+# FilesystemInputBundle which uses same search path as make_file
+@pytest.fixture
+def input_bundle(tmp_path):
+ return FilesystemInputBundle([tmp_path])
+
+
+def test_load_file(make_file, input_bundle, tmp_path):
+ make_file("foo.vy", "contents")
+
+ file = input_bundle.load_file(Path("foo.vy"))
+
+ assert isinstance(file, FileInput)
+ assert file == FileInput(0, tmp_path / Path("foo.vy"), "contents")
+
+
+def test_search_path_context_manager(make_file, tmp_path):
+ ib = FilesystemInputBundle([])
+
+ make_file("foo.vy", "contents")
+
+ with pytest.raises(FileNotFoundError):
+ # no search path given
+ ib.load_file(Path("foo.vy"))
+
+ with ib.search_path(tmp_path):
+ file = ib.load_file(Path("foo.vy"))
+
+ assert isinstance(file, FileInput)
+ assert file == FileInput(0, tmp_path / Path("foo.vy"), "contents")
+
+
+def test_search_path_precedence(make_file, tmp_path, tmp_path_factory, input_bundle):
+ # test search path precedence.
+ # most recent search path is the highest precedence
+ tmpdir = tmp_path_factory.mktemp("some_directory")
+ tmpdir2 = tmp_path_factory.mktemp("some_other_directory")
+
+ for i, directory in enumerate([tmp_path, tmpdir, tmpdir2]):
+ with (directory / "foo.vy").open("w") as f:
+ f.write(f"contents {i}")
+
+ ib = FilesystemInputBundle([tmp_path, tmpdir, tmpdir2])
+
+ file = ib.load_file("foo.vy")
+
+ assert isinstance(file, FileInput)
+ assert file == FileInput(0, tmpdir2 / "foo.vy", "contents 2")
+
+ with ib.search_path(tmpdir):
+ file = ib.load_file("foo.vy")
+
+ assert isinstance(file, FileInput)
+ assert file == FileInput(1, tmpdir / "foo.vy", "contents 1")
+
+
+# special rules for handling json files
+def test_load_abi(make_file, input_bundle, tmp_path):
+ contents = json.dumps("some string")
+
+ make_file("foo.json", contents)
+
+ file = input_bundle.load_file("foo.json")
+ assert isinstance(file, ABIInput)
+ assert file == ABIInput(0, tmp_path / "foo.json", "some string")
+
+ # suffix doesn't matter
+ make_file("foo.txt", contents)
+
+ file = input_bundle.load_file("foo.txt")
+ assert isinstance(file, ABIInput)
+ assert file == ABIInput(1, tmp_path / "foo.txt", "some string")
+
+
+# check that unique paths give unique source ids
+def test_source_id_file_input(make_file, input_bundle, tmp_path):
+ make_file("foo.vy", "contents")
+ make_file("bar.vy", "contents 2")
+
+ file = input_bundle.load_file("foo.vy")
+ assert file.source_id == 0
+ assert file == FileInput(0, tmp_path / "foo.vy", "contents")
+
+ file2 = input_bundle.load_file("bar.vy")
+ # source id increments
+ assert file2.source_id == 1
+ assert file2 == FileInput(1, tmp_path / "bar.vy", "contents 2")
+
+ file3 = input_bundle.load_file("foo.vy")
+ assert file3.source_id == 0
+ assert file3 == FileInput(0, tmp_path / "foo.vy", "contents")
+
+
+# check that unique paths give unique source ids
+def test_source_id_json_input(make_file, input_bundle, tmp_path):
+ contents = json.dumps("some string")
+ contents2 = json.dumps(["some list"])
+
+ make_file("foo.json", contents)
+
+ make_file("bar.json", contents2)
+
+ file = input_bundle.load_file("foo.json")
+ assert isinstance(file, ABIInput)
+ assert file == ABIInput(0, tmp_path / "foo.json", "some string")
+
+ file2 = input_bundle.load_file("bar.json")
+ assert isinstance(file2, ABIInput)
+ assert file2 == ABIInput(1, tmp_path / "bar.json", ["some list"])
+
+ file3 = input_bundle.load_file("foo.json")
+ assert isinstance(file3, ABIInput)
+ assert file3 == ABIInput(0, tmp_path / "foo.json", "some string")
+
+
+# test some pathological case where the file changes underneath
+def test_mutating_file_source_id(make_file, input_bundle, tmp_path):
+ make_file("foo.vy", "contents")
+
+ file = input_bundle.load_file("foo.vy")
+ assert file.source_id == 0
+ assert file == FileInput(0, tmp_path / "foo.vy", "contents")
+
+ make_file("foo.vy", "new contents")
+
+ file = input_bundle.load_file("foo.vy")
+ # source id hasn't changed, even though contents have
+ assert file.source_id == 0
+ assert file == FileInput(0, tmp_path / "foo.vy", "new contents")
+
+
+# test the os.normpath behavior of symlink
+# (slightly pathological, for illustration's sake)
+def test_load_file_symlink(make_file, input_bundle, tmp_path, tmp_path_factory):
+ dir1 = tmp_path / "first"
+ dir2 = tmp_path / "second"
+ symlink = tmp_path / "symlink"
+
+ dir1.mkdir()
+ dir2.mkdir()
+ symlink.symlink_to(dir2, target_is_directory=True)
+
+ with (tmp_path / "foo.vy").open("w") as f:
+ f.write("contents of the upper directory")
+
+ with (dir1 / "foo.vy").open("w") as f:
+ f.write("contents of the inner directory")
+
+ # symlink rules would be:
+ # base/symlink/../foo.vy =>
+ # base/first/second/../foo.vy =>
+ # base/first/foo.vy
+ # normpath would be base/symlink/../foo.vy =>
+ # base/foo.vy
+ file = input_bundle.load_file(symlink / ".." / "foo.vy")
+
+ assert file == FileInput(0, tmp_path / "foo.vy", "contents of the upper directory")
+
+
+def test_json_input_bundle_basic():
+ files = {PurePath("foo.vy"): {"content": "some text"}}
+ input_bundle = JSONInputBundle(files, [PurePath(".")])
+
+ file = input_bundle.load_file(PurePath("foo.vy"))
+ assert file == FileInput(0, PurePath("foo.vy"), "some text")
+
+
+def test_json_input_bundle_normpath():
+ files = {PurePath("foo/../bar.vy"): {"content": "some text"}}
+ input_bundle = JSONInputBundle(files, [PurePath(".")])
+
+ expected = FileInput(0, PurePath("bar.vy"), "some text")
+
+ file = input_bundle.load_file(PurePath("bar.vy"))
+ assert file == expected
+
+ file = input_bundle.load_file(PurePath("baz/../bar.vy"))
+ assert file == expected
+
+ file = input_bundle.load_file(PurePath("./bar.vy"))
+ assert file == expected
+
+ with input_bundle.search_path(PurePath("foo")):
+ file = input_bundle.load_file(PurePath("../bar.vy"))
+ assert file == expected
+
+
+def test_json_input_abi():
+ some_abi = ["some abi"]
+ some_abi_str = json.dumps(some_abi)
+ files = {
+ PurePath("foo.json"): {"abi": some_abi},
+ PurePath("bar.txt"): {"content": some_abi_str},
+ }
+ input_bundle = JSONInputBundle(files, [PurePath(".")])
+
+ file = input_bundle.load_file(PurePath("foo.json"))
+ assert file == ABIInput(0, PurePath("foo.json"), some_abi)
+
+ file = input_bundle.load_file(PurePath("bar.txt"))
+ assert file == ABIInput(1, PurePath("bar.txt"), some_abi)
diff --git a/tests/compiler/test_opcodes.py b/tests/compiler/test_opcodes.py
index 67ea10c311..15d2a617ba 100644
--- a/tests/compiler/test_opcodes.py
+++ b/tests/compiler/test_opcodes.py
@@ -8,9 +8,11 @@
@pytest.fixture(params=list(opcodes.EVM_VERSIONS))
def evm_version(request):
default = opcodes.active_evm_version
- opcodes.active_evm_version = opcodes.EVM_VERSIONS[request.param]
- yield request.param
- opcodes.active_evm_version = default
+ try:
+ opcodes.active_evm_version = opcodes.EVM_VERSIONS[request.param]
+ yield request.param
+ finally:
+ opcodes.active_evm_version = default
def test_opcodes():
@@ -20,7 +22,7 @@ def a() -> bool:
return True
"""
- out = vyper.compile_code(code, ["opcodes_runtime", "opcodes"])
+ out = vyper.compile_code(code, output_formats=["opcodes_runtime", "opcodes"])
assert len(out["opcodes"]) > len(out["opcodes_runtime"])
assert out["opcodes_runtime"] in out["opcodes"]
@@ -35,24 +37,30 @@ def test_version_check(evm_version):
assert opcodes.version_check(begin=evm_version)
assert opcodes.version_check(end=evm_version)
assert opcodes.version_check(begin=evm_version, end=evm_version)
- if evm_version not in ("byzantium", "atlantis"):
- assert not opcodes.version_check(end="byzantium")
+ if evm_version not in ("istanbul"):
+ assert not opcodes.version_check(end="istanbul")
istanbul_check = opcodes.version_check(begin="istanbul")
assert istanbul_check == (opcodes.EVM_VERSIONS[evm_version] >= opcodes.EVM_VERSIONS["istanbul"])
def test_get_opcodes(evm_version):
- op = opcodes.get_opcodes()
- if evm_version in ("paris", "berlin"):
- assert "CHAINID" in op
- assert op["SLOAD"][-1] == 2100
- elif evm_version == "istanbul":
- assert "CHAINID" in op
- assert op["SLOAD"][-1] == 800
+ ops = opcodes.get_opcodes()
+
+ assert "CHAINID" in ops
+ assert ops["CREATE2"][-1] == 32000
+
+ if evm_version in ("london", "berlin", "paris", "shanghai", "cancun"):
+ assert ops["SLOAD"][-1] == 2100
else:
- assert "CHAINID" not in op
- assert op["SLOAD"][-1] == 200
- if evm_version in ("byzantium", "atlantis"):
- assert "CREATE2" not in op
+ assert evm_version == "istanbul"
+ assert ops["SLOAD"][-1] == 800
+
+ if evm_version in ("shanghai", "cancun"):
+ assert "PUSH0" in ops
+
+ if evm_version in ("cancun",):
+ for op in ("TLOAD", "TSTORE", "MCOPY"):
+ assert op in ops
else:
- assert op["CREATE2"][-1] == 32000
+ for op in ("TLOAD", "TSTORE", "MCOPY"):
+ assert op not in ops
diff --git a/tests/compiler/test_pre_parser.py b/tests/compiler/test_pre_parser.py
index 4b747bb7d1..1761e74bad 100644
--- a/tests/compiler/test_pre_parser.py
+++ b/tests/compiler/test_pre_parser.py
@@ -1,6 +1,8 @@
-from pytest import raises
+import pytest
-from vyper.exceptions import SyntaxException
+from vyper.compiler import compile_code
+from vyper.compiler.settings import OptimizationLevel, Settings
+from vyper.exceptions import StructureException, SyntaxException
def test_semicolon_prohibited(get_contract):
@@ -10,7 +12,7 @@ def test() -> int128:
return a + b
"""
- with raises(SyntaxException):
+ with pytest.raises(SyntaxException):
get_contract(code)
@@ -70,6 +72,57 @@ def test():
assert get_contract(code)
+def test_version_pragma2(get_contract):
+ # new, `#pragma` way of doing things
+ from vyper import __version__
+
+ installed_version = ".".join(__version__.split(".")[:3])
+
+ code = f"""
+#pragma version {installed_version}
+
+@external
+def test():
+ pass
+ """
+ assert get_contract(code)
+
+
+def test_evm_version_check(assert_compile_failed):
+ code = """
+#pragma evm-version berlin
+ """
+ assert compile_code(code, settings=Settings(evm_version=None)) is not None
+ assert compile_code(code, settings=Settings(evm_version="berlin")) is not None
+ # should fail if compile options indicate different evm version
+ # from source pragma
+ with pytest.raises(StructureException):
+ compile_code(code, settings=Settings(evm_version="shanghai"))
+
+
+def test_optimization_mode_check():
+ code = """
+#pragma optimize codesize
+ """
+ assert compile_code(code, settings=Settings(optimize=None))
+ # should fail if compile options indicate different optimization mode
+ # from source pragma
+ with pytest.raises(StructureException):
+ compile_code(code, settings=Settings(optimize=OptimizationLevel.GAS))
+ with pytest.raises(StructureException):
+ compile_code(code, settings=Settings(optimize=OptimizationLevel.NONE))
+
+
+def test_optimization_mode_check_none():
+ code = """
+#pragma optimize none
+ """
+ assert compile_code(code, settings=Settings(optimize=None))
+ # "none" conflicts with "gas"
+ with pytest.raises(StructureException):
+ compile_code(code, settings=Settings(optimize=OptimizationLevel.GAS))
+
+
def test_version_empty_version(assert_compile_failed, get_contract):
code = """
#@version
@@ -110,5 +163,5 @@ def foo():
convert(
"""
- with raises(SyntaxException):
+ with pytest.raises(SyntaxException):
get_contract(code)
diff --git a/tests/compiler/test_sha3_32.py b/tests/compiler/test_sha3_32.py
index 9fbdf6f000..e1cbf9c843 100644
--- a/tests/compiler/test_sha3_32.py
+++ b/tests/compiler/test_sha3_32.py
@@ -1,9 +1,12 @@
from vyper.codegen.ir_node import IRnode
+from vyper.evm.opcodes import version_check
from vyper.ir import compile_ir, optimizer
def test_sha3_32():
ir = ["sha3_32", 0]
evm = ["PUSH1", 0, "PUSH1", 0, "MSTORE", "PUSH1", 32, "PUSH1", 0, "SHA3"]
+ if version_check(begin="shanghai"):
+ evm = ["PUSH0", "PUSH0", "MSTORE", "PUSH1", 32, "PUSH0", "SHA3"]
assert compile_ir.compile_to_assembly(IRnode.from_list(ir)) == evm
assert compile_ir.compile_to_assembly(optimizer.optimize(IRnode.from_list(ir))) == evm
diff --git a/tests/compiler/test_source_map.py b/tests/compiler/test_source_map.py
index 886596bb80..c9a152b09c 100644
--- a/tests/compiler/test_source_map.py
+++ b/tests/compiler/test_source_map.py
@@ -28,7 +28,7 @@ def foo(a: uint256) -> int128:
def test_jump_map():
- source_map = compile_code(TEST_CODE, ["source_map"])["source_map"]
+ source_map = compile_code(TEST_CODE, output_formats=["source_map"])["source_map"]
pos_map = source_map["pc_pos_map"]
jump_map = source_map["pc_jump_map"]
@@ -46,7 +46,7 @@ def test_jump_map():
def test_pos_map_offsets():
- source_map = compile_code(TEST_CODE, ["source_map"])["source_map"]
+ source_map = compile_code(TEST_CODE, output_formats=["source_map"])["source_map"]
expanded = expand_source_map(source_map["pc_pos_map_compressed"])
pc_iter = iter(source_map["pc_pos_map"][i] for i in sorted(source_map["pc_pos_map"]))
@@ -76,7 +76,7 @@ def test_error_map():
def update_foo():
self.foo += 1
"""
- error_map = compile_code(code, ["source_map"])["source_map"]["error_map"]
+ error_map = compile_code(code, output_formats=["source_map"])["source_map"]["error_map"]
assert "safeadd" in list(error_map.values())
assert "fallback function" in list(error_map.values())
diff --git a/tests/conftest.py b/tests/conftest.py
index e1d0996767..9b10b7c51c 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,6 +1,7 @@
import logging
from functools import wraps
+import hypothesis
import pytest
from eth_tester import EthereumTester, PyEVMBackend
from eth_utils import setup_DEBUG2_logging
@@ -10,6 +11,8 @@
from vyper import compiler
from vyper.codegen.ir_node import IRnode
+from vyper.compiler.input_bundle import FilesystemInputBundle
+from vyper.compiler.settings import OptimizationLevel, _set_debug_mode
from vyper.ir import compile_ir, optimizer
from .base_conftest import VyperContract, _get_contract, zero_gas_price_strategy
@@ -22,6 +25,11 @@
############
+# disable hypothesis deadline globally
+hypothesis.settings.register_profile("ci", deadline=None)
+hypothesis.settings.load_profile("ci")
+
+
def set_evm_verbose_logging():
logger = logging.getLogger("eth.vm.computation.Computation")
setup_DEBUG2_logging()
@@ -36,12 +44,26 @@ def set_evm_verbose_logging():
def pytest_addoption(parser):
- parser.addoption("--no-optimize", action="store_true", help="disable asm and IR optimizations")
+ parser.addoption(
+ "--optimize",
+ choices=["codesize", "gas", "none"],
+ default="gas",
+ help="change optimization mode",
+ )
+ parser.addoption("--enable-compiler-debug-mode", action="store_true")
@pytest.fixture(scope="module")
-def no_optimize(pytestconfig):
- return pytestconfig.getoption("no_optimize")
+def optimize(pytestconfig):
+ flag = pytestconfig.getoption("optimize")
+ return OptimizationLevel.from_string(flag)
+
+
+@pytest.fixture(scope="session", autouse=True)
+def debug(pytestconfig):
+ debug = pytestconfig.getoption("enable_compiler_debug_mode")
+ assert isinstance(debug, bool)
+ _set_debug_mode(debug)
@pytest.fixture
@@ -49,6 +71,34 @@ def keccak():
return Web3.keccak
+@pytest.fixture
+def make_file(tmp_path):
+ # writes file_contents to file_name, creating it in the
+ # tmp_path directory. returns final path.
+ def fn(file_name, file_contents):
+ path = tmp_path / file_name
+ path.parent.mkdir(parents=True, exist_ok=True)
+ with path.open("w") as f:
+ f.write(file_contents)
+
+ return path
+
+ return fn
+
+
+# this can either be used for its side effects (to prepare a call
+# to get_contract), or the result can be provided directly to
+# compile_code / CompilerData.
+@pytest.fixture
+def make_input_bundle(tmp_path, make_file):
+ def fn(sources_dict):
+ for file_name, file_contents in sources_dict.items():
+ make_file(file_name, file_contents)
+ return FilesystemInputBundle([tmp_path])
+
+ return fn
+
+
@pytest.fixture
def bytes_helper():
def bytes_helper(str, length):
@@ -58,13 +108,13 @@ def bytes_helper(str, length):
@pytest.fixture
-def get_contract_from_ir(w3, no_optimize):
+def get_contract_from_ir(w3, optimize):
def ir_compiler(ir, *args, **kwargs):
ir = IRnode.from_list(ir)
- if not no_optimize:
+ if optimize != OptimizationLevel.NONE:
ir = optimizer.optimize(ir)
bytecode, _ = compile_ir.assembly_to_evm(
- compile_ir.compile_to_assembly(ir, no_optimize=no_optimize)
+ compile_ir.compile_to_assembly(ir, optimize=optimize)
)
abi = kwargs.get("abi") or []
c = w3.eth.contract(abi=abi, bytecode=bytecode)
@@ -80,7 +130,7 @@ def ir_compiler(ir, *args, **kwargs):
@pytest.fixture(scope="module")
-def get_contract_module(no_optimize):
+def get_contract_module(optimize):
"""
This fixture is used for Hypothesis tests to ensure that
the same contract is called over multiple runs of the test.
@@ -93,7 +143,7 @@ def get_contract_module(no_optimize):
w3.eth.set_gas_price_strategy(zero_gas_price_strategy)
def get_contract_module(source_code, *args, **kwargs):
- return _get_contract(w3, source_code, no_optimize, *args, **kwargs)
+ return _get_contract(w3, source_code, optimize, *args, **kwargs)
return get_contract_module
@@ -138,9 +188,9 @@ def set_decorator_to_contract_function(w3, tester, contract, source_code, func):
@pytest.fixture
-def get_contract_with_gas_estimation(tester, w3, no_optimize):
+def get_contract_with_gas_estimation(tester, w3, optimize):
def get_contract_with_gas_estimation(source_code, *args, **kwargs):
- contract = _get_contract(w3, source_code, no_optimize, *args, **kwargs)
+ contract = _get_contract(w3, source_code, optimize, *args, **kwargs)
for abi_ in contract._classic_contract.functions.abi:
if abi_["type"] == "function":
set_decorator_to_contract_function(w3, tester, contract, source_code, abi_["name"])
@@ -150,9 +200,9 @@ def get_contract_with_gas_estimation(source_code, *args, **kwargs):
@pytest.fixture
-def get_contract_with_gas_estimation_for_constants(w3, no_optimize):
+def get_contract_with_gas_estimation_for_constants(w3, optimize):
def get_contract_with_gas_estimation_for_constants(source_code, *args, **kwargs):
- return _get_contract(w3, source_code, no_optimize, *args, **kwargs)
+ return _get_contract(w3, source_code, optimize, *args, **kwargs)
return get_contract_with_gas_estimation_for_constants
@@ -192,3 +242,38 @@ def _f(_addr, _salt, _initcode):
return keccak(prefix + addr + salt + keccak(initcode))[12:]
return _f
+
+
+@pytest.fixture
+def side_effects_contract(get_contract):
+ def generate(ret_type):
+ """
+ Generates a Vyper contract with an external `foo()` function, which
+ returns the specified return value of the specified return type, for
+ testing side effects using the `assert_side_effects_invoked` fixture.
+ """
+ code = f"""
+counter: public(uint256)
+
+@external
+def foo(s: {ret_type}) -> {ret_type}:
+ self.counter += 1
+ return s
+ """
+ contract = get_contract(code)
+ return contract
+
+ return generate
+
+
+@pytest.fixture
+def assert_side_effects_invoked():
+ def assert_side_effects_invoked(side_effects_contract, side_effects_trigger, n=1):
+ start_value = side_effects_contract.counter()
+
+ side_effects_trigger()
+
+ end_value = side_effects_contract.counter()
+ assert end_value == start_value + n
+
+ return assert_side_effects_invoked
diff --git a/tests/examples/factory/test_factory.py b/tests/examples/factory/test_factory.py
index 15becc05f1..0c5cf61b04 100644
--- a/tests/examples/factory/test_factory.py
+++ b/tests/examples/factory/test_factory.py
@@ -2,6 +2,7 @@
from eth_utils import keccak
import vyper
+from vyper.compiler.settings import Settings
@pytest.fixture
@@ -30,12 +31,12 @@ def create_exchange(token, factory):
@pytest.fixture
-def factory(get_contract, no_optimize):
+def factory(get_contract, optimize):
with open("examples/factory/Exchange.vy") as f:
code = f.read()
exchange_interface = vyper.compile_code(
- code, output_formats=["bytecode_runtime"], no_optimize=no_optimize
+ code, output_formats=["bytecode_runtime"], settings=Settings(optimize=optimize)
)
exchange_deployed_bytecode = exchange_interface["bytecode_runtime"]
diff --git a/tests/functional/semantics/analysis/test_cyclic_function_calls.py b/tests/functional/semantics/analysis/test_cyclic_function_calls.py
index 086f8ed08c..2a09bd5ed5 100644
--- a/tests/functional/semantics/analysis/test_cyclic_function_calls.py
+++ b/tests/functional/semantics/analysis/test_cyclic_function_calls.py
@@ -6,6 +6,18 @@
from vyper.semantics.analysis.module import ModuleAnalyzer
+def test_self_function_call(namespace):
+ code = """
+@internal
+def foo():
+ self.foo()
+ """
+ vyper_module = parse_to_ast(code)
+ with namespace.enter_scope():
+ with pytest.raises(CallViolation):
+ ModuleAnalyzer(vyper_module, {}, namespace)
+
+
def test_cyclic_function_call(namespace):
code = """
@internal
diff --git a/tests/functional/semantics/analysis/test_for_loop.py b/tests/functional/semantics/analysis/test_for_loop.py
index 71e38d253c..0d61a8f8f8 100644
--- a/tests/functional/semantics/analysis/test_for_loop.py
+++ b/tests/functional/semantics/analysis/test_for_loop.py
@@ -1,7 +1,12 @@
import pytest
from vyper.ast import parse_to_ast
-from vyper.exceptions import ImmutableViolation, TypeMismatch
+from vyper.exceptions import (
+ ArgumentException,
+ ImmutableViolation,
+ StateAccessViolation,
+ TypeMismatch,
+)
from vyper.semantics.analysis import validate_semantics
@@ -59,6 +64,34 @@ def bar():
validate_semantics(vyper_module, {})
+def test_bad_keywords(namespace):
+ code = """
+
+@internal
+def bar(n: uint256):
+ x: uint256 = 0
+ for i in range(n, boundddd=10):
+ x += i
+ """
+ vyper_module = parse_to_ast(code)
+ with pytest.raises(ArgumentException):
+ validate_semantics(vyper_module, {})
+
+
+def test_bad_bound(namespace):
+ code = """
+
+@internal
+def bar(n: uint256):
+ x: uint256 = 0
+ for i in range(n, bound=n):
+ x += i
+ """
+ vyper_module = parse_to_ast(code)
+ with pytest.raises(StateAccessViolation):
+ validate_semantics(vyper_module, {})
+
+
def test_modify_iterator_function_call(namespace):
code = """
@@ -108,14 +141,14 @@ def main():
for j in range(3):
x: uint256 = j
y: uint16 = j
- """, # issue 3212
+ """, # GH issue 3212
"""
@external
def foo():
for i in [1]:
a:uint256 = i
b:uint16 = i
- """, # issue 3374
+ """, # GH issue 3374
"""
@external
def foo():
@@ -123,7 +156,7 @@ def foo():
for j in [1]:
a:uint256 = i
b:uint16 = i
- """, # issue 3374
+ """, # GH issue 3374
"""
@external
def foo():
@@ -131,7 +164,7 @@ def foo():
for j in [1,2,3]:
b:uint256 = j + i
c:uint16 = i
- """, # issue 3374
+ """, # GH issue 3374
]
diff --git a/tests/functional/semantics/types/test_type_from_annotation.py b/tests/functional/semantics/types/test_type_from_annotation.py
index 200d2bbcfc..16a31cc651 100644
--- a/tests/functional/semantics/types/test_type_from_annotation.py
+++ b/tests/functional/semantics/types/test_type_from_annotation.py
@@ -6,6 +6,7 @@
StructureException,
UndeclaredDefinition,
)
+from vyper.semantics.data_locations import DataLocation
from vyper.semantics.types import PRIMITIVE_TYPES, HashMapT, SArrayT
from vyper.semantics.types.utils import type_from_annotation
@@ -14,7 +15,8 @@
@pytest.mark.parametrize("type_str", BASE_TYPES)
-def test_base_types(build_node, type_str):
+@pytest.mark.parametrize("location", iter(DataLocation))
+def test_base_types(build_node, type_str, location):
node = build_node(type_str)
base_t = PRIMITIVE_TYPES[type_str]
@@ -24,7 +26,8 @@ def test_base_types(build_node, type_str):
@pytest.mark.parametrize("type_str", BYTESTRING_TYPES)
-def test_array_value_types(build_node, type_str):
+@pytest.mark.parametrize("location", iter(DataLocation))
+def test_array_value_types(build_node, type_str, location):
node = build_node(f"{type_str}[1]")
base_t = PRIMITIVE_TYPES[type_str](1)
@@ -34,7 +37,8 @@ def test_array_value_types(build_node, type_str):
@pytest.mark.parametrize("type_str", BASE_TYPES)
-def test_base_types_as_arrays(build_node, type_str):
+@pytest.mark.parametrize("location", iter(DataLocation))
+def test_base_types_as_arrays(build_node, type_str, location):
node = build_node(f"{type_str}[3]")
base_t = PRIMITIVE_TYPES[type_str]
@@ -44,7 +48,8 @@ def test_base_types_as_arrays(build_node, type_str):
@pytest.mark.parametrize("type_str", BYTESTRING_TYPES)
-def test_array_value_types_as_arrays(build_node, type_str):
+@pytest.mark.parametrize("location", iter(DataLocation))
+def test_array_value_types_as_arrays(build_node, type_str, location):
node = build_node(f"{type_str}[1][1]")
with pytest.raises(StructureException):
@@ -52,7 +57,8 @@ def test_array_value_types_as_arrays(build_node, type_str):
@pytest.mark.parametrize("type_str", BASE_TYPES)
-def test_base_types_as_multidimensional_arrays(build_node, namespace, type_str):
+@pytest.mark.parametrize("location", iter(DataLocation))
+def test_base_types_as_multidimensional_arrays(build_node, namespace, type_str, location):
node = build_node(f"{type_str}[3][5]")
base_t = PRIMITIVE_TYPES[type_str]
@@ -63,7 +69,8 @@ def test_base_types_as_multidimensional_arrays(build_node, namespace, type_str):
@pytest.mark.parametrize("type_str", ["int128", "String"])
@pytest.mark.parametrize("idx", ["0", "-1", "0x00", "'1'", "foo", "[1]", "(1,)"])
-def test_invalid_index(build_node, idx, type_str):
+@pytest.mark.parametrize("location", iter(DataLocation))
+def test_invalid_index(build_node, idx, type_str, location):
node = build_node(f"{type_str}[{idx}]")
with pytest.raises(
(ArrayIndexException, InvalidType, StructureException, UndeclaredDefinition)
@@ -77,7 +84,7 @@ def test_mapping(build_node, type_str, type_str2):
node = build_node(f"HashMap[{type_str}, {type_str2}]")
types = PRIMITIVE_TYPES
- ann_t = type_from_annotation(node)
+ ann_t = type_from_annotation(node, DataLocation.STORAGE)
k_t = types[type_str]
v_t = types[type_str2]
@@ -91,7 +98,7 @@ def test_multidimensional_mapping(build_node, type_str, type_str2):
node = build_node(f"HashMap[{type_str}, HashMap[{type_str}, {type_str2}]]")
types = PRIMITIVE_TYPES
- ann_t = type_from_annotation(node)
+ ann_t = type_from_annotation(node, DataLocation.STORAGE)
k_t = types[type_str]
v_t = types[type_str2]
diff --git a/tests/fuzzing/test_exponents.py b/tests/fuzzing/test_exponents.py
index 29c1f198ed..5726e4c1ca 100644
--- a/tests/fuzzing/test_exponents.py
+++ b/tests/fuzzing/test_exponents.py
@@ -92,7 +92,7 @@ def foo(a: int16) -> int16:
@example(a=2**127 - 1)
# 256 bits
@example(a=2**256 - 1)
-@settings(max_examples=200, deadline=1000)
+@settings(max_examples=200)
def test_max_exp(get_contract, assert_tx_failed, a):
code = f"""
@external
@@ -127,7 +127,7 @@ def foo(b: uint256) -> uint256:
@example(a=2**63 - 1)
# 128 bits
@example(a=2**127 - 1)
-@settings(max_examples=200, deadline=1000)
+@settings(max_examples=200)
def test_max_exp_int128(get_contract, assert_tx_failed, a):
code = f"""
@external
diff --git a/tests/grammar/test_grammar.py b/tests/grammar/test_grammar.py
index 7e220b58ae..aa0286cfa5 100644
--- a/tests/grammar/test_grammar.py
+++ b/tests/grammar/test_grammar.py
@@ -4,7 +4,7 @@
import hypothesis
import hypothesis.strategies as st
import pytest
-from hypothesis import HealthCheck, assume, given
+from hypothesis import assume, given
from hypothesis.extra.lark import LarkStrategy
from vyper.ast import Module, parse_to_ast
@@ -103,8 +103,9 @@ def has_no_docstrings(c):
@pytest.mark.fuzzing
@given(code=from_grammar().filter(lambda c: utf8_encodable(c)))
-@hypothesis.settings(deadline=400, max_examples=500, suppress_health_check=(HealthCheck.too_slow,))
+@hypothesis.settings(max_examples=500)
def test_grammar_bruteforce(code):
if utf8_encodable(code):
- tree = parse_to_ast(pre_parse(code + "\n")[1])
+ _, _, reformatted_code = pre_parse(code + "\n")
+ tree = parse_to_ast(reformatted_code)
assert isinstance(tree, Module)
diff --git a/tests/parser/ast_utils/test_ast_dict.py b/tests/parser/ast_utils/test_ast_dict.py
index 214af50f9f..1f60c9ac8b 100644
--- a/tests/parser/ast_utils/test_ast_dict.py
+++ b/tests/parser/ast_utils/test_ast_dict.py
@@ -19,7 +19,7 @@ def get_node_ids(ast_struct, ids=None):
elif v is None or isinstance(v, (str, int)):
continue
else:
- raise Exception("Unknown ast_struct provided.")
+ raise Exception(f"Unknown ast_struct provided. {k}, {v}")
return ids
@@ -30,7 +30,7 @@ def test() -> int128:
a: uint256 = 100
return 123
"""
- dict_out = compiler.compile_code(code, ["ast_dict"])
+ dict_out = compiler.compile_code(code, output_formats=["ast_dict"])
node_ids = get_node_ids(dict_out)
assert len(node_ids) == len(set(node_ids))
@@ -40,7 +40,7 @@ def test_basic_ast():
code = """
a: int128
"""
- dict_out = compiler.compile_code(code, ["ast_dict"])
+ dict_out = compiler.compile_code(code, output_formats=["ast_dict"])
assert dict_out["ast_dict"]["ast"]["body"][0] == {
"annotation": {
"ast_type": "Name",
@@ -73,6 +73,7 @@ def test_basic_ast():
"is_constant": False,
"is_immutable": False,
"is_public": False,
+ "is_transient": False,
}
@@ -88,7 +89,7 @@ def foo() -> uint256: view
def foo() -> uint256:
return 1
"""
- dict_out = compiler.compile_code(code, ["ast_dict"])
+ dict_out = compiler.compile_code(code, output_formats=["ast_dict"])
assert dict_out["ast_dict"]["ast"]["body"][1] == {
"col_offset": 0,
"annotation": {
diff --git a/tests/parser/exceptions/test_instantiation_exception.py b/tests/parser/exceptions/test_instantiation_exception.py
new file mode 100644
index 0000000000..0d641f154a
--- /dev/null
+++ b/tests/parser/exceptions/test_instantiation_exception.py
@@ -0,0 +1,81 @@
+import pytest
+
+from vyper.exceptions import InstantiationException
+
+invalid_list = [
+ """
+event Foo:
+ a: uint256
+
+@external
+def foo() -> Foo:
+ return Foo(2)
+ """,
+ """
+event Foo:
+ a: uint256
+
+@external
+def foo() -> (uint256, Foo):
+ return 1, Foo(2)
+ """,
+ """
+a: HashMap[uint256, uint256]
+
+@external
+def foo() -> HashMap[uint256, uint256]:
+ return self.a
+ """,
+ """
+event Foo:
+ a: uint256
+
+@external
+def foo(x: Foo):
+ pass
+ """,
+ """
+@external
+def foo(x: HashMap[uint256, uint256]):
+ pass
+ """,
+ """
+event Foo:
+ a: uint256
+
+foo: Foo
+ """,
+ """
+event Foo:
+ a: uint256
+
+@external
+def foo():
+ f: Foo = Foo(1)
+ pass
+ """,
+ """
+event Foo:
+ a: uint256
+
+b: HashMap[uint256, Foo]
+ """,
+ """
+event Foo:
+ a: uint256
+
+b: HashMap[Foo, uint256]
+ """,
+ """
+b: immutable(HashMap[uint256, uint256])
+
+@external
+def __init__():
+ b = empty(HashMap[uint256, uint256])
+ """,
+]
+
+
+@pytest.mark.parametrize("bad_code", invalid_list)
+def test_instantiation_exception(bad_code, get_contract, assert_compile_failed):
+ assert_compile_failed(lambda: get_contract(bad_code), InstantiationException)
diff --git a/tests/parser/exceptions/test_invalid_reference.py b/tests/parser/exceptions/test_invalid_reference.py
index 3aec6028e4..fe315e5cbf 100644
--- a/tests/parser/exceptions/test_invalid_reference.py
+++ b/tests/parser/exceptions/test_invalid_reference.py
@@ -37,6 +37,24 @@ def foo():
def foo():
int128 = 5
""",
+ """
+a: public(constant(uint256)) = 1
+
+@external
+def foo():
+ b: uint256 = self.a
+ """,
+ """
+a: public(immutable(uint256))
+
+@external
+def __init__():
+ a = 123
+
+@external
+def foo():
+ b: uint256 = self.a
+ """,
]
diff --git a/tests/parser/exceptions/test_structure_exception.py b/tests/parser/exceptions/test_structure_exception.py
index 08794b75f2..97ac2b139d 100644
--- a/tests/parser/exceptions/test_structure_exception.py
+++ b/tests/parser/exceptions/test_structure_exception.py
@@ -56,9 +56,26 @@ def double_nonreentrant():
""",
"""
@external
-@nonreentrant("B")
-@nonreentrant("C")
-def double_nonreentrant():
+@nonreentrant(" ")
+def invalid_nonreentrant_key():
+ pass
+ """,
+ """
+@external
+@nonreentrant("")
+def invalid_nonreentrant_key():
+ pass
+ """,
+ """
+@external
+@nonreentrant("123")
+def invalid_nonreentrant_key():
+ pass
+ """,
+ """
+@external
+@nonreentrant("!123abcd")
+def invalid_nonreentrant_key():
pass
""",
"""
diff --git a/tests/parser/features/decorators/test_nonreentrant.py b/tests/parser/features/decorators/test_nonreentrant.py
index 0577313b88..9e74019250 100644
--- a/tests/parser/features/decorators/test_nonreentrant.py
+++ b/tests/parser/features/decorators/test_nonreentrant.py
@@ -3,6 +3,8 @@
from vyper.exceptions import FunctionDeclarationException
+# TODO test functions in this module across all evm versions
+# once we have cancun support.
def test_nonreentrant_decorator(get_contract, assert_tx_failed):
calling_contract_code = """
interface SpecialContract:
@@ -140,7 +142,7 @@ def set_callback(c: address):
@external
@payable
-@nonreentrant('default')
+@nonreentrant("lock")
def protected_function(val: String[100], do_callback: bool) -> uint256:
self.special_value = val
_amount: uint256 = msg.value
@@ -164,7 +166,7 @@ def unprotected_function(val: String[100], do_callback: bool):
@external
@payable
-@nonreentrant('default')
+@nonreentrant("lock")
def __default__():
pass
"""
diff --git a/tests/parser/features/decorators/test_payable.py b/tests/parser/features/decorators/test_payable.py
index 906ae330c0..55c60236f4 100644
--- a/tests/parser/features/decorators/test_payable.py
+++ b/tests/parser/features/decorators/test_payable.py
@@ -372,3 +372,24 @@ def __default__():
assert_tx_failed(
lambda: w3.eth.send_transaction({"to": c.address, "value": 100, "data": "0x12345678"})
)
+
+
+def test_batch_nonpayable(get_contract, w3, assert_tx_failed):
+ code = """
+@external
+def foo() -> bool:
+ return True
+
+@external
+def __default__():
+ pass
+ """
+
+ c = get_contract(code)
+ w3.eth.send_transaction({"to": c.address, "value": 0, "data": "0x12345678"})
+ data = bytes([1, 2, 3, 4])
+ for i in range(5):
+ calldata = "0x" + data[:i].hex()
+ assert_tx_failed(
+ lambda: w3.eth.send_transaction({"to": c.address, "value": 100, "data": calldata})
+ )
diff --git a/tests/parser/features/decorators/test_private.py b/tests/parser/features/decorators/test_private.py
index b984921835..51e6d90ee1 100644
--- a/tests/parser/features/decorators/test_private.py
+++ b/tests/parser/features/decorators/test_private.py
@@ -304,7 +304,7 @@ def test(a: bytes32) -> (bytes32, uint256, int128):
b: uint256 = 1
c: int128 = 1
d: int128 = 123
- f: bytes32 = EMPTY_BYTES32
+ f: bytes32 = empty(bytes32)
f, b, c = self._test(a)
assert d == 123
return f, b, c
@@ -433,7 +433,7 @@ def i_am_me() -> bool:
return msg.sender == self._whoami()
@external
-@view
+@nonpayable
def whoami() -> address:
log Addr(self._whoami())
return self._whoami()
diff --git a/tests/parser/features/external_contracts/test_external_contract_calls.py b/tests/parser/features/external_contracts/test_external_contract_calls.py
index b3cc6f5576..12fcde2f4f 100644
--- a/tests/parser/features/external_contracts/test_external_contract_calls.py
+++ b/tests/parser/features/external_contracts/test_external_contract_calls.py
@@ -775,9 +775,9 @@ def foo() -> (address, Bytes[3], address): view
@external
def bar(arg1: address) -> (address, Bytes[3], address):
- a: address = ZERO_ADDRESS
+ a: address = empty(address)
b: Bytes[3] = b""
- c: address = ZERO_ADDRESS
+ c: address = empty(address)
a, b, c = Foo(arg1).foo()
return a, b, c
"""
@@ -808,9 +808,9 @@ def foo() -> (address, Bytes[3], address): view
@external
def bar(arg1: address) -> (address, Bytes[3], address):
- a: address = ZERO_ADDRESS
+ a: address = empty(address)
b: Bytes[3] = b""
- c: address = ZERO_ADDRESS
+ c: address = empty(address)
a, b, c = Foo(arg1).foo()
return a, b, c
"""
@@ -841,9 +841,9 @@ def foo() -> (address, Bytes[3], address): view
@external
def bar(arg1: address) -> (address, Bytes[3], address):
- a: address = ZERO_ADDRESS
+ a: address = empty(address)
b: Bytes[3] = b""
- c: address = ZERO_ADDRESS
+ c: address = empty(address)
a, b, c = Foo(arg1).foo()
return a, b, c
"""
@@ -1538,7 +1538,7 @@ def out_literals() -> (int128, address, Bytes[10]) : view
@external
def test(addr: address) -> (int128, address, Bytes[10]):
a: int128 = 0
- b: address = ZERO_ADDRESS
+ b: address = empty(address)
c: Bytes[10] = b""
(a, b, c) = Test(addr).out_literals()
return a, b,c
diff --git a/tests/parser/features/iteration/test_for_in_list.py b/tests/parser/features/iteration/test_for_in_list.py
index bfd960a787..fb01cc98eb 100644
--- a/tests/parser/features/iteration/test_for_in_list.py
+++ b/tests/parser/features/iteration/test_for_in_list.py
@@ -230,7 +230,7 @@ def iterate_return_second() -> address:
count += 1
if count == 2:
return i
- return ZERO_ADDRESS
+ return empty(address)
"""
c = get_contract_with_gas_estimation(code)
diff --git a/tests/parser/features/iteration/test_repeater.py b/tests/parser/features/iteration/test_for_range.py
similarity index 81%
rename from tests/parser/features/iteration/test_repeater.py
rename to tests/parser/features/iteration/test_for_range.py
index 3c95882d2d..ed6235d992 100644
--- a/tests/parser/features/iteration/test_repeater.py
+++ b/tests/parser/features/iteration/test_for_range.py
@@ -14,6 +14,23 @@ def repeat(z: int128) -> int128:
assert c.repeat(9) == 54
+def test_range_bound(get_contract, assert_tx_failed):
+ code = """
+@external
+def repeat(n: uint256) -> uint256:
+ x: uint256 = 0
+ for i in range(n, bound=6):
+ x += i + 1
+ return x
+ """
+ c = get_contract(code)
+ for n in range(7):
+ assert c.repeat(n) == sum(i + 1 for i in range(n))
+
+ # check codegen inserts assertion for n greater than bound
+ assert_tx_failed(lambda: c.repeat(7))
+
+
def test_digit_reverser(get_contract_with_gas_estimation):
digit_reverser = """
@external
@@ -128,6 +145,45 @@ def foo(a: {typ}) -> {typ}:
assert c.foo(100) == 31337
+# test that we can get to the upper range of an integer
+@pytest.mark.parametrize("typ", ["uint8", "int128", "uint256"])
+def test_for_range_edge(get_contract, typ):
+ code = f"""
+@external
+def test():
+ found: bool = False
+ x: {typ} = max_value({typ})
+ for i in range(x, x + 1):
+ if i == max_value({typ}):
+ found = True
+
+ assert found
+
+ found = False
+ x = max_value({typ}) - 1
+ for i in range(x, x + 2):
+ if i == max_value({typ}):
+ found = True
+
+ assert found
+ """
+ c = get_contract(code)
+ c.test()
+
+
+@pytest.mark.parametrize("typ", ["uint8", "int128", "uint256"])
+def test_for_range_oob_check(get_contract, assert_tx_failed, typ):
+ code = f"""
+@external
+def test():
+ x: {typ} = max_value({typ})
+ for i in range(x, x+2):
+ pass
+ """
+ c = get_contract(code)
+ assert_tx_failed(lambda: c.test())
+
+
@pytest.mark.parametrize("typ", ["int128", "uint256"])
def test_return_inside_nested_repeater(get_contract, typ):
code = f"""
diff --git a/tests/parser/features/test_assignment.py b/tests/parser/features/test_assignment.py
index 65fb3a7a0e..cd26659a5c 100644
--- a/tests/parser/features/test_assignment.py
+++ b/tests/parser/features/test_assignment.py
@@ -39,7 +39,118 @@ def augmod(x: int128, y: int128) -> int128:
print("Passed aug-assignment test")
-def test_invalid_assign(assert_compile_failed, get_contract_with_gas_estimation):
+@pytest.mark.parametrize(
+ "typ,in_val,out_val",
+ [
+ ("uint256", 77, 123),
+ ("uint256[3]", [1, 2, 3], [4, 5, 6]),
+ ("DynArray[uint256, 3]", [1, 2, 3], [4, 5, 6]),
+ ("Bytes[5]", b"vyper", b"conda"),
+ ],
+)
+def test_internal_assign(get_contract_with_gas_estimation, typ, in_val, out_val):
+ code = f"""
+@internal
+def foo(x: {typ}) -> {typ}:
+ x = {out_val}
+ return x
+
+@external
+def bar(x: {typ}) -> {typ}:
+ return self.foo(x)
+ """
+ c = get_contract_with_gas_estimation(code)
+
+ assert c.bar(in_val) == out_val
+
+
+def test_internal_assign_struct(get_contract_with_gas_estimation):
+ code = """
+enum Bar:
+ BAD
+ BAK
+ BAZ
+
+struct Foo:
+ a: uint256
+ b: DynArray[Bar, 3]
+ c: String[5]
+
+@internal
+def foo(x: Foo) -> Foo:
+ x = Foo({a: 789, b: [Bar.BAZ, Bar.BAK, Bar.BAD], c: \"conda\"})
+ return x
+
+@external
+def bar(x: Foo) -> Foo:
+ return self.foo(x)
+ """
+ c = get_contract_with_gas_estimation(code)
+
+ assert c.bar((123, [1, 2, 4], "vyper")) == (789, [4, 2, 1], "conda")
+
+
+def test_internal_assign_struct_member(get_contract_with_gas_estimation):
+ code = """
+enum Bar:
+ BAD
+ BAK
+ BAZ
+
+struct Foo:
+ a: uint256
+ b: DynArray[Bar, 3]
+ c: String[5]
+
+@internal
+def foo(x: Foo) -> Foo:
+ x.a = 789
+ x.b.pop()
+ return x
+
+@external
+def bar(x: Foo) -> Foo:
+ return self.foo(x)
+ """
+ c = get_contract_with_gas_estimation(code)
+
+ assert c.bar((123, [1, 2, 4], "vyper")) == (789, [1, 2], "vyper")
+
+
+def test_internal_augassign(get_contract_with_gas_estimation):
+ code = """
+@internal
+def foo(x: int128) -> int128:
+ x += 77
+ return x
+
+@external
+def bar(x: int128) -> int128:
+ return self.foo(x)
+ """
+ c = get_contract_with_gas_estimation(code)
+
+ assert c.bar(123) == 200
+
+
+@pytest.mark.parametrize("typ", ["DynArray[uint256, 3]", "uint256[3]"])
+def test_internal_augassign_arrays(get_contract_with_gas_estimation, typ):
+ code = f"""
+@internal
+def foo(x: {typ}) -> {typ}:
+ x[1] += 77
+ return x
+
+@external
+def bar(x: {typ}) -> {typ}:
+ return self.foo(x)
+ """
+ c = get_contract_with_gas_estimation(code)
+
+ assert c.bar([1, 2, 3]) == [1, 79, 3]
+
+
+def test_invalid_external_assign(assert_compile_failed, get_contract_with_gas_estimation):
code = """
@external
def foo(x: int128):
@@ -48,7 +159,7 @@ def foo(x: int128):
assert_compile_failed(lambda: get_contract_with_gas_estimation(code), ImmutableViolation)
-def test_invalid_augassign(assert_compile_failed, get_contract_with_gas_estimation):
+def test_invalid_external_augassign(assert_compile_failed, get_contract_with_gas_estimation):
code = """
@external
def foo(x: int128):
@@ -220,7 +331,7 @@ def foo():
@external
def foo():
y: int128 = 1
- z: bytes32 = EMPTY_BYTES32
+ z: bytes32 = empty(bytes32)
z = y
""",
"""
@@ -233,7 +344,7 @@ def foo():
@external
def foo():
y: uint256 = 1
- z: bytes32 = EMPTY_BYTES32
+ z: bytes32 = empty(bytes32)
z = y
""",
],
@@ -255,3 +366,139 @@ def foo():
ret : bool = self.bar()
"""
assert_compile_failed(lambda: get_contract_with_gas_estimation(code), InvalidType)
+
+ # GH issue 2418
+
+
+overlap_codes = [
+ """
+@external
+def bug(xs: uint256[2]) -> uint256[2]:
+ # Initial value
+ ys: uint256[2] = xs
+ ys = [ys[1], ys[0]]
+ return ys
+ """,
+ """
+foo: uint256[2]
+@external
+def bug(xs: uint256[2]) -> uint256[2]:
+ # Initial value
+ self.foo = xs
+ self.foo = [self.foo[1], self.foo[0]]
+ return self.foo
+ """,
+ # TODO add transient tests when it's available
+]
+
+
+@pytest.mark.parametrize("code", overlap_codes)
+def test_assign_rhs_lhs_overlap(get_contract, code):
+ c = get_contract(code)
+
+ assert c.bug([1, 2]) == [2, 1]
+
+
+def test_assign_rhs_lhs_partial_overlap(get_contract):
+ # GH issue 2418, generalize when lhs is not only dependency of rhs.
+ code = """
+@external
+def bug(xs: uint256[2]) -> uint256[2]:
+ # Initial value
+ ys: uint256[2] = xs
+ ys = [xs[1], ys[0]]
+ return ys
+ """
+ c = get_contract(code)
+
+ assert c.bug([1, 2]) == [2, 1]
+
+
+def test_assign_rhs_lhs_overlap_dynarray(get_contract):
+ # GH issue 2418, generalize to dynarrays
+ code = """
+@external
+def bug(xs: DynArray[uint256, 2]) -> DynArray[uint256, 2]:
+ ys: DynArray[uint256, 2] = xs
+ ys = [ys[1], ys[0]]
+ return ys
+ """
+ c = get_contract(code)
+ assert c.bug([1, 2]) == [2, 1]
+
+
+def test_assign_rhs_lhs_overlap_struct(get_contract):
+ # GH issue 2418, generalize to structs
+ code = """
+struct Point:
+ x: uint256
+ y: uint256
+
+@external
+def bug(p: Point) -> Point:
+ t: Point = p
+ t = Point({x: t.y, y: t.x})
+ return t
+ """
+ c = get_contract(code)
+ assert c.bug((1, 2)) == (2, 1)
+
+
+mload_merge_codes = [
+ (
+ """
+@external
+def foo() -> uint256[4]:
+ # copy "backwards"
+ xs: uint256[4] = [1, 2, 3, 4]
+
+# dst < src
+ xs[0] = xs[1]
+ xs[1] = xs[2]
+ xs[2] = xs[3]
+
+ return xs
+ """,
+ [2, 3, 4, 4],
+ ),
+ (
+ """
+@external
+def foo() -> uint256[4]:
+ # copy "forwards"
+ xs: uint256[4] = [1, 2, 3, 4]
+
+# src < dst
+ xs[1] = xs[0]
+ xs[2] = xs[1]
+ xs[3] = xs[2]
+
+ return xs
+ """,
+ [1, 1, 1, 1],
+ ),
+ (
+ """
+@external
+def foo() -> uint256[5]:
+ # partial "forward" copy
+ xs: uint256[5] = [1, 2, 3, 4, 5]
+
+# src < dst
+ xs[2] = xs[0]
+ xs[3] = xs[1]
+ xs[4] = xs[2]
+
+ return xs
+ """,
+ [1, 2, 1, 2, 1],
+ ),
+]
+
+
+# functional test that mload merging does not occur when source and dest
+# buffers overlap. (note: mload merging only applies after cancun)
+@pytest.mark.parametrize("code,expected_result", mload_merge_codes)
+def test_mcopy_overlap(get_contract, code, expected_result):
+ c = get_contract(code)
+ assert c.foo() == expected_result
diff --git a/tests/parser/features/test_comparison.py b/tests/parser/features/test_comparison.py
index 1c2f287c10..5a86ffb4b8 100644
--- a/tests/parser/features/test_comparison.py
+++ b/tests/parser/features/test_comparison.py
@@ -4,7 +4,7 @@
def test_3034_verbatim(get_contract):
- # test issue #3034 exactly
+ # test GH issue 3034 exactly
code = """
@view
@external
diff --git a/tests/parser/features/test_immutable.py b/tests/parser/features/test_immutable.py
index bb01b3fc07..47f7fc748e 100644
--- a/tests/parser/features/test_immutable.py
+++ b/tests/parser/features/test_immutable.py
@@ -1,5 +1,7 @@
import pytest
+from vyper.compiler.settings import OptimizationLevel
+
@pytest.mark.parametrize(
"typ,value",
@@ -239,3 +241,140 @@ def get_immutable() -> uint256:
c = get_contract(code, n)
assert c.get_immutable() == n + 2
+
+
+# GH issue 3101
+def test_immutables_initialized(get_contract):
+ dummy_code = """
+@external
+def foo() -> uint256:
+ return 1
+ """
+ dummy_contract = get_contract(dummy_code)
+
+ code = """
+a: public(immutable(uint256))
+b: public(uint256)
+
+@payable
+@external
+def __init__(to_copy: address):
+ c: address = create_copy_of(to_copy)
+ self.b = a
+ a = 12
+ """
+ c = get_contract(code, dummy_contract.address)
+
+ assert c.b() == 0
+
+
+# GH issue 3101, take 2
+def test_immutables_initialized2(get_contract, get_contract_from_ir):
+ dummy_contract = get_contract_from_ir(
+ ["deploy", 0, ["seq"] + ["invalid"] * 600, 0], optimize=OptimizationLevel.NONE
+ )
+
+ # rekt because immutables section extends past allocated memory
+ code = """
+a0: immutable(uint256[10])
+a: public(immutable(uint256))
+b: public(uint256)
+
+@payable
+@external
+def __init__(to_copy: address):
+ c: address = create_copy_of(to_copy)
+ self.b = a
+ a = 12
+ a0 = empty(uint256[10])
+ """
+ c = get_contract(code, dummy_contract.address)
+
+ assert c.b() == 0
+
+
+# GH issue 3292
+def test_internal_functions_called_by_ctor_location(get_contract):
+ code = """
+d: uint256
+x: immutable(uint256)
+
+@external
+def __init__():
+ self.d = 1
+ x = 2
+ self.a()
+
+@external
+def test() -> uint256:
+ return self.d
+
+@internal
+def a():
+ self.d = x
+ """
+ c = get_contract(code)
+ assert c.test() == 2
+
+
+# GH issue 3292, extended to nested internal functions
+def test_nested_internal_function_immutables(get_contract):
+ code = """
+d: public(uint256)
+x: public(immutable(uint256))
+
+@external
+def __init__():
+ self.d = 1
+ x = 2
+ self.a()
+
+@internal
+def a():
+ self.b()
+
+@internal
+def b():
+ self.d = x
+ """
+ c = get_contract(code)
+ assert c.x() == 2
+ assert c.d() == 2
+
+
+# GH issue 3292, test immutable read from both ctor and runtime
+def test_immutable_read_ctor_and_runtime(get_contract):
+ code = """
+d: public(uint256)
+x: public(immutable(uint256))
+
+@external
+def __init__():
+ self.d = 1
+ x = 2
+ self.a()
+
+@internal
+def a():
+ self.d = x
+
+@external
+def thrash():
+ self.d += 5
+
+@external
+def fix():
+ self.a()
+ """
+ c = get_contract(code)
+ assert c.x() == 2
+ assert c.d() == 2
+
+ c.thrash(transact={})
+
+ assert c.x() == 2
+ assert c.d() == 2 + 5
+
+ c.fix(transact={})
+ assert c.x() == 2
+ assert c.d() == 2
diff --git a/tests/parser/features/test_init.py b/tests/parser/features/test_init.py
index feeabe311a..29a466e869 100644
--- a/tests/parser/features/test_init.py
+++ b/tests/parser/features/test_init.py
@@ -15,7 +15,7 @@ def __init__(a: uint256):
assert c.val() == 123
# Make sure the init code does not access calldata
- assembly = vyper.compile_code(code, ["asm"])["asm"].split(" ")
+ assembly = vyper.compile_code(code, output_formats=["asm"])["asm"].split(" ")
ir_return_idx_start = assembly.index("{")
ir_return_idx_end = assembly.index("}")
@@ -53,3 +53,29 @@ def baz() -> uint8:
n = 256
assert_compile_failed(lambda: get_contract(code, n))
+
+
+# GH issue 3206
+def test_nested_internal_call_from_ctor(get_contract):
+ code = """
+x: uint256
+
+@external
+def __init__():
+ self.a()
+
+@internal
+def a():
+ self.x += 1
+ self.b()
+
+@internal
+def b():
+ self.x += 2
+
+@external
+def test() -> uint256:
+ return self.x
+ """
+ c = get_contract(code)
+ assert c.test() == 3
diff --git a/tests/parser/features/test_internal_call.py b/tests/parser/features/test_internal_call.py
index f576dc5ee5..f10d22ec99 100644
--- a/tests/parser/features/test_internal_call.py
+++ b/tests/parser/features/test_internal_call.py
@@ -1,6 +1,9 @@
+import string
from decimal import Decimal
+import hypothesis.strategies as st
import pytest
+from hypothesis import given, settings
from vyper.compiler import compile_code
from vyper.exceptions import ArgumentException, CallViolation
@@ -642,3 +645,62 @@ def bar() -> String[6]:
c = get_contract_with_gas_estimation(contract)
assert c.bar() == "hello"
+
+
+# TODO probably want to refactor these into general test utils
+st_uint256 = st.integers(min_value=0, max_value=2**256 - 1)
+st_string65 = st.text(max_size=65, alphabet=string.printable)
+st_bytes65 = st.binary(max_size=65)
+st_sarray3 = st.lists(st_uint256, min_size=3, max_size=3)
+st_darray3 = st.lists(st_uint256, max_size=3)
+
+internal_call_kwargs_cases = [
+ ("uint256", st_uint256),
+ ("String[65]", st_string65),
+ ("Bytes[65]", st_bytes65),
+ ("uint256[3]", st_sarray3),
+ ("DynArray[uint256, 3]", st_darray3),
+]
+
+
+@pytest.mark.parametrize("typ1,strategy1", internal_call_kwargs_cases)
+@pytest.mark.parametrize("typ2,strategy2", internal_call_kwargs_cases)
+def test_internal_call_kwargs(get_contract, typ1, strategy1, typ2, strategy2):
+ # GHSA-ph9x-4vc9-m39g
+
+ @given(kwarg1=strategy1, default1=strategy1, kwarg2=strategy2, default2=strategy2)
+ @settings(max_examples=5) # len(cases) * len(cases) * 5 * 5
+ def fuzz(kwarg1, kwarg2, default1, default2):
+ code = f"""
+@internal
+def foo(a: {typ1} = {repr(default1)}, b: {typ2} = {repr(default2)}) -> ({typ1}, {typ2}):
+ return a, b
+
+@external
+def test0() -> ({typ1}, {typ2}):
+ return self.foo()
+
+@external
+def test1() -> ({typ1}, {typ2}):
+ return self.foo({repr(kwarg1)})
+
+@external
+def test2() -> ({typ1}, {typ2}):
+ return self.foo({repr(kwarg1)}, {repr(kwarg2)})
+
+@external
+def test3(x1: {typ1}) -> ({typ1}, {typ2}):
+ return self.foo(x1)
+
+@external
+def test4(x1: {typ1}, x2: {typ2}) -> ({typ1}, {typ2}):
+ return self.foo(x1, x2)
+ """
+ c = get_contract(code)
+ assert c.test0() == [default1, default2]
+ assert c.test1() == [kwarg1, default2]
+ assert c.test2() == [kwarg1, kwarg2]
+ assert c.test3(kwarg1) == [kwarg1, default2]
+ assert c.test4(kwarg1, kwarg2) == [kwarg1, kwarg2]
+
+ fuzz()
diff --git a/tests/parser/features/test_memory_alloc.py b/tests/parser/features/test_memory_alloc.py
new file mode 100644
index 0000000000..ee6d15c67c
--- /dev/null
+++ b/tests/parser/features/test_memory_alloc.py
@@ -0,0 +1,16 @@
+import pytest
+
+from vyper.compiler import compile_code
+from vyper.exceptions import MemoryAllocationException
+
+
+def test_memory_overflow():
+ code = """
+@external
+def zzz(x: DynArray[uint256, 2**59]): # 2**64 / 32 bytes per word == 2**59
+ y: uint256[7] = [0,0,0,0,0,0,0]
+
+ y[6] = y[5]
+ """
+ with pytest.raises(MemoryAllocationException):
+ compile_code(code)
diff --git a/tests/parser/features/test_memory_dealloc.py b/tests/parser/features/test_memory_dealloc.py
index de82f03296..814bf0d3bb 100644
--- a/tests/parser/features/test_memory_dealloc.py
+++ b/tests/parser/features/test_memory_dealloc.py
@@ -9,7 +9,7 @@ def sendit(): nonpayable
@external
def foo(target: address) -> uint256[2]:
- log Shimmy(ZERO_ADDRESS, 3)
+ log Shimmy(empty(address), 3)
amount: uint256 = 1
flargen: uint256 = 42
Other(target).sendit()
diff --git a/tests/parser/features/test_string_map_keys.py b/tests/parser/features/test_string_map_keys.py
new file mode 100644
index 0000000000..c52bd72821
--- /dev/null
+++ b/tests/parser/features/test_string_map_keys.py
@@ -0,0 +1,25 @@
+def test_string_map_keys(get_contract):
+ code = """
+f:HashMap[String[1], bool]
+@external
+def test() -> bool:
+ a:String[1] = "a"
+ b:String[1] = "b"
+ self.f[a] = True
+ return self.f[b] # should return False
+ """
+ c = get_contract(code)
+ c.test()
+ assert c.test() is False
+
+
+def test_string_map_keys_literals(get_contract):
+ code = """
+f:HashMap[String[1], bool]
+@external
+def test() -> bool:
+ self.f["a"] = True
+ return self.f["b"] # should return False
+ """
+ c = get_contract(code)
+ assert c.test() is False
diff --git a/tests/parser/features/test_ternary.py b/tests/parser/features/test_ternary.py
new file mode 100644
index 0000000000..c5480286c8
--- /dev/null
+++ b/tests/parser/features/test_ternary.py
@@ -0,0 +1,280 @@
+import pytest
+
+simple_cases = [
+ (
+ """
+@external
+def foo(t: bool, x: uint256, y: uint256) -> uint256:
+ return x if t else y
+ """,
+ (1, 2),
+ ),
+ ( # literal test
+ """
+@external
+def foo(_t: bool, x: uint256, y: uint256) -> uint256:
+ return x if {test} else y
+ """,
+ (1, 2),
+ ),
+ ( # literal body
+ """
+@external
+def foo(t: bool, _x: uint256, y: uint256) -> uint256:
+ return {x} if t else y
+ """,
+ (1, 2),
+ ),
+ ( # literal orelse
+ """
+@external
+def foo(t: bool, x: uint256, _y: uint256) -> uint256:
+ return x if t else {y}
+ """,
+ (1, 2),
+ ),
+ ( # literal body/orelse
+ """
+@external
+def foo(t: bool, _x: uint256, _y: uint256) -> uint256:
+ return {x} if t else {y}
+ """,
+ (1, 2),
+ ),
+ ( # literal everything
+ """
+@external
+def foo(_t: bool, _x: uint256, _y: uint256) -> uint256:
+ return {x} if {test} else {y}
+ """,
+ (1, 2),
+ ),
+ ( # body/orelse in storage and memory
+ """
+s: uint256
+@external
+def foo(t: bool, x: uint256, y: uint256) -> uint256:
+ self.s = x
+ return self.s if t else y
+ """,
+ (1, 2),
+ ),
+ ( # body/orelse in memory and storage
+ """
+s: uint256
+@external
+def foo(t: bool, x: uint256, y: uint256) -> uint256:
+ self.s = x
+ return self.s if t else y
+ """,
+ (1, 2),
+ ),
+ ( # body/orelse in memory and constant
+ """
+S: constant(uint256) = {y}
+@external
+def foo(t: bool, x: uint256, _y: uint256) -> uint256:
+ return x if t else S
+ """,
+ (1, 2),
+ ),
+ ( # dynarray
+ """
+@external
+def foo(t: bool, x: DynArray[uint256, 3], y: DynArray[uint256, 3]) -> DynArray[uint256, 3]:
+ return x if t else y
+ """,
+ ([], [1]),
+ ),
+ ( # variable + literal dynarray
+ """
+@external
+def foo(t: bool, x: DynArray[uint256, 3], _y: DynArray[uint256, 3]) -> DynArray[uint256, 3]:
+ return x if t else {y}
+ """,
+ ([], [1]),
+ ),
+ ( # literal + variable dynarray
+ """
+@external
+def foo(t: bool, _x: DynArray[uint256, 3], y: DynArray[uint256, 3]) -> DynArray[uint256, 3]:
+ return {x} if t else y
+ """,
+ ([], [1]),
+ ),
+ ( # storage dynarray
+ """
+s: DynArray[uint256, 3]
+@external
+def foo(t: bool, x: DynArray[uint256, 3], y: DynArray[uint256, 3]) -> DynArray[uint256, 3]:
+ self.s = y
+ return x if t else self.s
+ """,
+ ([], [1]),
+ ),
+ ( # static array
+ """
+@external
+def foo(t: bool, x: uint256[1], y: uint256[1]) -> uint256[1]:
+ return x if t else y
+ """,
+ ([2], [1]),
+ ),
+ ( # static array literal
+ """
+@external
+def foo(t: bool, x: uint256[1], _y: uint256[1]) -> uint256[1]:
+ return x if t else {y}
+ """,
+ ([2], [1]),
+ ),
+ ( # strings
+ """
+@external
+def foo(t: bool, x: String[10], y: String[10]) -> String[10]:
+ return x if t else y
+ """,
+ ("hello", "world"),
+ ),
+ ( # string literal
+ """
+@external
+def foo(t: bool, x: String[10], _y: String[10]) -> String[10]:
+ return x if t else {y}
+ """,
+ ("hello", "world"),
+ ),
+ ( # bytes
+ """
+@external
+def foo(t: bool, x: Bytes[10], y: Bytes[10]) -> Bytes[10]:
+ return x if t else y
+ """,
+ (b"hello", b"world"),
+ ),
+]
+
+
+@pytest.mark.parametrize("code,inputs", simple_cases)
+@pytest.mark.parametrize("test", [True, False])
+def test_ternary_simple(get_contract, code, test, inputs):
+ x, y = inputs
+ # note: repr to escape strings
+ code = code.format(test=test, x=repr(x), y=repr(y))
+ c = get_contract(code)
+ # careful with order of precedence of `assert` and `if/else` in python!
+ assert c.foo(test, x, y) == (x if test else y)
+
+
+tuple_codes = [
+ """
+@external
+def foo(t: bool, x: uint256, y: uint256) -> (uint256, uint256):
+ return (x, y) if t else (y, x)
+ """,
+ """
+s: uint256
+@external
+def foo(t: bool, x: uint256, y: uint256) -> (uint256, uint256):
+ self.s = x
+ return (self.s, y) if t else (y, self.s)
+ """,
+]
+
+
+@pytest.mark.parametrize("code", tuple_codes)
+@pytest.mark.parametrize("test", [True, False])
+def test_ternary_tuple(get_contract, code, test):
+ c = get_contract(code)
+
+ x, y = 1, 2
+ assert c.foo(test, x, y) == ([x, y] if test else [y, x])
+
+
+@pytest.mark.parametrize("test", [True, False])
+def test_ternary_immutable(get_contract, test):
+ code = """
+IMM: public(immutable(uint256))
+@external
+def __init__(test: bool):
+ IMM = 1 if test else 2
+ """
+ c = get_contract(code, test)
+
+ assert c.IMM() == (1 if test else 2)
+
+
+@pytest.mark.parametrize("test", [True, False])
+@pytest.mark.parametrize("x", list(range(8)))
+@pytest.mark.parametrize("y", list(range(8)))
+def test_complex_ternary_expression(get_contract, test, x, y):
+ code = """
+@external
+def foo(t: bool, x: uint256, y: uint256) -> uint256:
+ return (x * y) if (t and True) else (x + y + convert(t, uint256))
+ """
+ c = get_contract(code)
+
+ assert c.foo(test, x, y) == ((x * y) if (test and True) else (x + y + int(test)))
+
+
+@pytest.mark.parametrize("test", [True, False])
+@pytest.mark.parametrize("x", list(range(8)))
+@pytest.mark.parametrize("y", list(range(8)))
+def test_ternary_precedence(get_contract, test, x, y):
+ code = """
+@external
+def foo(t: bool, x: uint256, y: uint256) -> uint256:
+ return x * y if t else x + y + convert(t, uint256)
+ """
+ c = get_contract(code)
+
+ assert c.foo(test, x, y) == (x * y if test else x + y + int(test))
+
+
+@pytest.mark.parametrize("test1", [True, False])
+@pytest.mark.parametrize("test2", [True, False])
+def test_nested_ternary(get_contract, test1, test2):
+ code = """
+@external
+def foo(t1: bool, t2: bool, x: uint256, y: uint256, z: uint256) -> uint256:
+ return x if t1 else y if t2 else z
+ """
+ c = get_contract(code)
+
+ x, y, z = 1, 2, 3
+ assert c.foo(test1, test2, x, y, z) == (x if test1 else y if test2 else z)
+
+
+@pytest.mark.parametrize("test", [True, False])
+def test_ternary_side_effects(get_contract, test):
+ code = """
+track_taint_x: public(uint256)
+track_taint_y: public(uint256)
+foo_retval: public(uint256)
+
+@internal
+def x() -> uint256:
+ self.track_taint_x += 1
+ return 5
+
+@internal
+def y() -> uint256:
+ self.track_taint_y += 1
+ return 7
+
+@external
+def foo(t: bool):
+ self.foo_retval = self.x() if t else self.y()
+ """
+ c = get_contract(code)
+
+ c.foo(test, transact={})
+ assert c.foo_retval() == (5 if test else 7)
+
+ if test:
+ assert c.track_taint_x() == 1
+ assert c.track_taint_y() == 0
+ else:
+ assert c.track_taint_x() == 0
+ assert c.track_taint_y() == 1
diff --git a/tests/parser/features/test_transient.py b/tests/parser/features/test_transient.py
new file mode 100644
index 0000000000..718f5ae314
--- /dev/null
+++ b/tests/parser/features/test_transient.py
@@ -0,0 +1,62 @@
+import pytest
+
+from vyper.compiler import compile_code
+from vyper.compiler.settings import Settings
+from vyper.evm.opcodes import EVM_VERSIONS
+from vyper.exceptions import StructureException
+
+post_cancun = {k: v for k, v in EVM_VERSIONS.items() if v >= EVM_VERSIONS["cancun"]}
+
+
+@pytest.mark.parametrize("evm_version", list(EVM_VERSIONS.keys()))
+def test_transient_blocked(evm_version):
+ # test transient is blocked on pre-cancun and compiles post-cancun
+ code = """
+my_map: transient(HashMap[address, uint256])
+ """
+ settings = Settings(evm_version=evm_version)
+ if EVM_VERSIONS[evm_version] >= EVM_VERSIONS["cancun"]:
+ assert compile_code(code, settings=settings) is not None
+ else:
+ with pytest.raises(StructureException):
+ compile_code(code, settings=settings)
+
+
+@pytest.mark.parametrize("evm_version", list(post_cancun.keys()))
+def test_transient_compiles(evm_version):
+ # test transient keyword at least generates TLOAD/TSTORE opcodes
+ settings = Settings(evm_version=evm_version)
+ getter_code = """
+my_map: public(transient(HashMap[address, uint256]))
+ """
+ t = compile_code(getter_code, settings=settings, output_formats=["opcodes_runtime"])
+ t = t["opcodes_runtime"].split(" ")
+
+ assert "TLOAD" in t
+ assert "TSTORE" not in t
+
+ setter_code = """
+my_map: transient(HashMap[address, uint256])
+
+@external
+def setter(k: address, v: uint256):
+ self.my_map[k] = v
+ """
+ t = compile_code(setter_code, settings=settings, output_formats=["opcodes_runtime"])
+ t = t["opcodes_runtime"].split(" ")
+
+ assert "TLOAD" not in t
+ assert "TSTORE" in t
+
+ getter_setter_code = """
+my_map: public(transient(HashMap[address, uint256]))
+
+@external
+def setter(k: address, v: uint256):
+ self.my_map[k] = v
+ """
+ t = compile_code(getter_setter_code, settings=settings, output_formats=["opcodes_runtime"])
+ t = t["opcodes_runtime"].split(" ")
+
+ assert "TLOAD" in t
+ assert "TSTORE" in t
diff --git a/tests/parser/functions/test_abi_decode.py b/tests/parser/functions/test_abi_decode.py
index 2f9b93057d..242841e1cf 100644
--- a/tests/parser/functions/test_abi_decode.py
+++ b/tests/parser/functions/test_abi_decode.py
@@ -25,7 +25,7 @@ def test_abi_decode_complex(get_contract):
@external
def abi_decode(x: Bytes[160]) -> (address, int128, bool, decimal, bytes32):
- a: address = ZERO_ADDRESS
+ a: address = empty(address)
b: int128 = 0
c: bool = False
d: decimal = 0.0
@@ -39,7 +39,7 @@ def abi_decode_struct(x: Bytes[544]) -> Human:
name: "",
pet: Animal({
name: "",
- address_: ZERO_ADDRESS,
+ address_: empty(address),
id_: 0,
is_furry: False,
price: 0.0,
@@ -344,6 +344,34 @@ def abi_decode(x: Bytes[96]) -> (uint256, uint256):
assert_tx_failed(lambda: c.abi_decode(input_))
+def test_clamper_nested_uint8(get_contract, assert_tx_failed):
+ # check that _abi_decode clamps on word-types even when it is in a nested expression
+ # decode -> validate uint8 -> revert if input >= 256 -> cast back to uint256
+ contract = """
+@external
+def abi_decode(x: uint256) -> uint256:
+ a: uint256 = convert(_abi_decode(slice(msg.data, 4, 32), (uint8)), uint256)
+ return a
+ """
+ c = get_contract(contract)
+ assert c.abi_decode(255) == 255
+ assert_tx_failed(lambda: c.abi_decode(256))
+
+
+def test_clamper_nested_bytes(get_contract, assert_tx_failed):
+ # check that _abi_decode clamps dynamic even when it is in a nested expression
+ # decode -> validate Bytes[20] -> revert if len(input) > 20 -> convert back to -> add 1
+ contract = """
+@external
+def abi_decode(x: Bytes[96]) -> Bytes[21]:
+ a: Bytes[21] = concat(b"a", _abi_decode(x, Bytes[20]))
+ return a
+ """
+ c = get_contract(contract)
+ assert c.abi_decode(abi.encode("(bytes)", (b"bc",))) == b"abc"
+ assert_tx_failed(lambda: c.abi_decode(abi.encode("(bytes)", (b"a" * 22,))))
+
+
@pytest.mark.parametrize(
"output_typ,input_",
[
diff --git a/tests/parser/functions/test_addmod.py b/tests/parser/functions/test_addmod.py
new file mode 100644
index 0000000000..b3135660bb
--- /dev/null
+++ b/tests/parser/functions/test_addmod.py
@@ -0,0 +1,89 @@
+def test_uint256_addmod(assert_tx_failed, get_contract_with_gas_estimation):
+ uint256_code = """
+@external
+def _uint256_addmod(x: uint256, y: uint256, z: uint256) -> uint256:
+ return uint256_addmod(x, y, z)
+ """
+
+ c = get_contract_with_gas_estimation(uint256_code)
+
+ assert c._uint256_addmod(1, 2, 2) == 1
+ assert c._uint256_addmod(32, 2, 32) == 2
+ assert c._uint256_addmod((2**256) - 1, 0, 2) == 1
+ assert c._uint256_addmod(2**255, 2**255, 6) == 4
+ assert_tx_failed(lambda: c._uint256_addmod(1, 2, 0))
+
+
+def test_uint256_addmod_ext_call(
+ w3, side_effects_contract, assert_side_effects_invoked, get_contract
+):
+ code = """
+@external
+def foo(f: Foo) -> uint256:
+ return uint256_addmod(32, 2, f.foo(32))
+
+interface Foo:
+ def foo(x: uint256) -> uint256: payable
+ """
+
+ c1 = side_effects_contract("uint256")
+ c2 = get_contract(code)
+
+ assert c2.foo(c1.address) == 2
+ assert_side_effects_invoked(c1, lambda: c2.foo(c1.address, transact={}))
+
+
+def test_uint256_addmod_internal_call(get_contract_with_gas_estimation):
+ code = """
+@external
+def foo() -> uint256:
+ return uint256_addmod(self.a(), self.b(), self.c())
+
+@internal
+def a() -> uint256:
+ return 32
+
+@internal
+def b() -> uint256:
+ return 2
+
+@internal
+def c() -> uint256:
+ return 32
+ """
+
+ c = get_contract_with_gas_estimation(code)
+
+ assert c.foo() == 2
+
+
+def test_uint256_addmod_evaluation_order(get_contract_with_gas_estimation):
+ code = """
+a: uint256
+
+@external
+def foo1() -> uint256:
+ self.a = 0
+ return uint256_addmod(self.a, 1, self.bar())
+
+@external
+def foo2() -> uint256:
+ self.a = 0
+ return uint256_addmod(self.a, self.bar(), 3)
+
+@external
+def foo3() -> uint256:
+ self.a = 0
+ return uint256_addmod(1, self.a, self.bar())
+
+@internal
+def bar() -> uint256:
+ self.a = 1
+ return 2
+ """
+
+ c = get_contract_with_gas_estimation(code)
+
+ assert c.foo1() == 1
+ assert c.foo2() == 2
+ assert c.foo3() == 1
diff --git a/tests/parser/functions/test_as_wei_value.py b/tests/parser/functions/test_as_wei_value.py
new file mode 100644
index 0000000000..bab0aed616
--- /dev/null
+++ b/tests/parser/functions/test_as_wei_value.py
@@ -0,0 +1,31 @@
+def test_ext_call(w3, side_effects_contract, assert_side_effects_invoked, get_contract):
+ code = """
+@external
+def foo(a: Foo) -> uint256:
+ return as_wei_value(a.foo(7), "ether")
+
+interface Foo:
+ def foo(x: uint8) -> uint8: nonpayable
+ """
+
+ c1 = side_effects_contract("uint8")
+ c2 = get_contract(code)
+
+ assert c2.foo(c1.address) == w3.to_wei(7, "ether")
+ assert_side_effects_invoked(c1, lambda: c2.foo(c1.address, transact={}))
+
+
+def test_internal_call(w3, get_contract_with_gas_estimation):
+ code = """
+@external
+def foo() -> uint256:
+ return as_wei_value(self.bar(), "ether")
+
+@internal
+def bar() -> uint8:
+ return 7
+ """
+
+ c = get_contract_with_gas_estimation(code)
+
+ assert c.foo() == w3.to_wei(7, "ether")
diff --git a/tests/parser/functions/test_bitwise.py b/tests/parser/functions/test_bitwise.py
index 800803907a..1d62a5be79 100644
--- a/tests/parser/functions/test_bitwise.py
+++ b/tests/parser/functions/test_bitwise.py
@@ -1,7 +1,6 @@
import pytest
from vyper.compiler import compile_code
-from vyper.evm.opcodes import EVM_VERSIONS
from vyper.exceptions import InvalidLiteral, InvalidOperation, TypeMismatch
from vyper.utils import unsigned_to_signed
@@ -32,20 +31,14 @@ def _shr(x: uint256, y: uint256) -> uint256:
"""
-@pytest.mark.parametrize("evm_version", list(EVM_VERSIONS))
-def test_bitwise_opcodes(evm_version):
- opcodes = compile_code(code, ["opcodes"], evm_version=evm_version)["opcodes"]
- if evm_version in ("byzantium", "atlantis"):
- assert "SHL" not in opcodes
- assert "SHR" not in opcodes
- else:
- assert "SHL" in opcodes
- assert "SHR" in opcodes
+def test_bitwise_opcodes():
+ opcodes = compile_code(code, output_formats=["opcodes"])["opcodes"]
+ assert "SHL" in opcodes
+ assert "SHR" in opcodes
-@pytest.mark.parametrize("evm_version", list(EVM_VERSIONS))
-def test_test_bitwise(get_contract_with_gas_estimation, evm_version):
- c = get_contract_with_gas_estimation(code, evm_version=evm_version)
+def test_test_bitwise(get_contract_with_gas_estimation):
+ c = get_contract_with_gas_estimation(code)
x = 126416208461208640982146408124
y = 7128468721412412459
assert c._bitwise_and(x, y) == (x & y)
@@ -59,11 +52,7 @@ def test_test_bitwise(get_contract_with_gas_estimation, evm_version):
assert c._shl(t, s) == (t << s) % (2**256)
-POST_BYZANTIUM = [k for (k, v) in EVM_VERSIONS.items() if v > 0]
-
-
-@pytest.mark.parametrize("evm_version", POST_BYZANTIUM)
-def test_signed_shift(get_contract_with_gas_estimation, evm_version):
+def test_signed_shift(get_contract_with_gas_estimation):
code = """
@external
def _sar(x: int256, y: uint256) -> int256:
@@ -73,7 +62,7 @@ def _sar(x: int256, y: uint256) -> int256:
def _shl(x: int256, y: uint256) -> int256:
return x << y
"""
- c = get_contract_with_gas_estimation(code, evm_version=evm_version)
+ c = get_contract_with_gas_estimation(code)
x = 126416208461208640982146408124
y = 7128468721412412459
cases = [x, y, -x, -y]
@@ -104,8 +93,7 @@ def baz(a: uint256, b: uint256, c: uint256) -> (uint256, uint256):
assert tuple(c.baz(1, 6, 14)) == (1 + 8 | ~6 & 14 * 2, (1 + 8 | ~6) & 14 * 2) == (25, 24)
-@pytest.mark.parametrize("evm_version", list(EVM_VERSIONS))
-def test_literals(get_contract, evm_version):
+def test_literals(get_contract):
code = """
@external
def _shr(x: uint256) -> uint256:
@@ -116,7 +104,7 @@ def _shl(x: uint256) -> uint256:
return x << 3
"""
- c = get_contract(code, evm_version=evm_version)
+ c = get_contract(code)
assert c._shr(80) == 10
assert c._shl(80) == 640
diff --git a/tests/parser/functions/test_ceil.py b/tests/parser/functions/test_ceil.py
index a9bcf62da2..daa9cb7c1b 100644
--- a/tests/parser/functions/test_ceil.py
+++ b/tests/parser/functions/test_ceil.py
@@ -104,3 +104,37 @@ def ceil_param(p: decimal) -> int256:
assert c.fou() == -3
assert c.ceil_param(Decimal("-0.5")) == 0
assert c.ceil_param(Decimal("-7777777.7777777")) == -7777777
+
+
+def test_ceil_ext_call(w3, side_effects_contract, assert_side_effects_invoked, get_contract):
+ code = """
+@external
+def foo(a: Foo) -> int256:
+ return ceil(a.foo(2.5))
+
+interface Foo:
+ def foo(x: decimal) -> decimal: payable
+ """
+
+ c1 = side_effects_contract("decimal")
+ c2 = get_contract(code)
+
+ assert c2.foo(c1.address) == 3
+
+ assert_side_effects_invoked(c1, lambda: c2.foo(c1.address, transact={}))
+
+
+def test_ceil_internal_call(get_contract_with_gas_estimation):
+ code = """
+@external
+def foo() -> int256:
+ return ceil(self.bar())
+
+@internal
+def bar() -> decimal:
+ return 2.5
+ """
+
+ c = get_contract_with_gas_estimation(code)
+
+ assert c.foo() == 3
diff --git a/tests/parser/functions/test_convert.py b/tests/parser/functions/test_convert.py
index d6a72c66af..b5ce613235 100644
--- a/tests/parser/functions/test_convert.py
+++ b/tests/parser/functions/test_convert.py
@@ -22,7 +22,7 @@
BASE_TYPES = set(IntegerT.all()) | set(BytesM_T.all()) | {DecimalT(), AddressT(), BoolT()}
-TEST_TYPES = BASE_TYPES | {BytesT(32)}
+TEST_TYPES = BASE_TYPES | {BytesT(32)} | {StringT(32)}
ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"
@@ -163,6 +163,17 @@ def _cases_for_Bytes(typ):
# would not need this if we tested all Bytes[1]...Bytes[32] types.
for i in range(32):
ret.extend(_cases_for_bytes(BytesM_T(i + 1)))
+
+ ret.append(b"")
+ return uniq(ret)
+
+
+def _cases_for_String(typ):
+ ret = []
+ # would not need this if we tested all Bytes[1]...Bytes[32] types.
+ for i in range(32):
+ ret.extend([str(c, "utf-8") for c in _cases_for_bytes(BytesM_T(i + 1))])
+ ret.append("")
return uniq(ret)
@@ -176,6 +187,8 @@ def interesting_cases_for_type(typ):
return _cases_for_bytes(typ)
if isinstance(typ, BytesT):
return _cases_for_Bytes(typ)
+ if isinstance(typ, StringT):
+ return _cases_for_String(typ)
if isinstance(typ, BoolT):
return _cases_for_bool(typ)
if isinstance(typ, AddressT):
@@ -521,24 +534,6 @@ def foo(a: {typ}) -> Status:
assert_compile_failed(lambda: get_contract_with_gas_estimation(contract), TypeMismatch)
-# TODO CMC 2022-04-06 I think this test is somewhat unnecessary.
-@pytest.mark.parametrize(
- "builtin_constant,out_type,out_value",
- [("ZERO_ADDRESS", "bool", False), ("msg.sender", "bool", True)],
-)
-def test_convert_builtin_constant(
- get_contract_with_gas_estimation, builtin_constant, out_type, out_value
-):
- contract = f"""
-@external
-def convert_builtin_constant() -> {out_type}:
- return convert({builtin_constant}, {out_type})
- """
-
- c = get_contract_with_gas_estimation(contract)
- assert c.convert_builtin_constant() == out_value
-
-
# uint256 conversion is currently valid due to type inference on literals
# not quite working yet
same_type_conversion_blocked = sorted(TEST_TYPES - {UINT256_T})
diff --git a/tests/parser/functions/test_create_functions.py b/tests/parser/functions/test_create_functions.py
index 857173df7e..fa7729d98e 100644
--- a/tests/parser/functions/test_create_functions.py
+++ b/tests/parser/functions/test_create_functions.py
@@ -3,6 +3,9 @@
from eth.codecs import abi
from hexbytes import HexBytes
+import vyper.ir.compile_ir as compile_ir
+from vyper.codegen.ir_node import IRnode
+from vyper.compiler.settings import OptimizationLevel
from vyper.utils import EIP_170_LIMIT, checksum_encode, keccak256
@@ -224,15 +227,25 @@ def test(code_ofst: uint256) -> address:
return create_from_blueprint(BLUEPRINT, code_offset=code_ofst)
"""
- # use a bunch of JUMPDEST + STOP instructions as blueprint code
- # (as any STOP instruction returns valid code, split up with
- # jumpdests as optimization fence)
initcode_len = 100
- f = get_contract_from_ir(["deploy", 0, ["seq"] + ["jumpdest", "stop"] * (initcode_len // 2), 0])
- blueprint_code = w3.eth.get_code(f.address)
- print(blueprint_code)
- d = get_contract(deployer_code, f.address)
+ # deploy a blueprint contract whose contained initcode contains only
+ # zeroes (so no matter which offset, create_from_blueprint will
+ # return empty code)
+ ir = IRnode.from_list(["deploy", 0, ["seq"] + ["stop"] * initcode_len, 0])
+ bytecode, _ = compile_ir.assembly_to_evm(
+ compile_ir.compile_to_assembly(ir, optimize=OptimizationLevel.NONE)
+ )
+ # manually deploy the bytecode
+ c = w3.eth.contract(abi=[], bytecode=bytecode)
+ deploy_transaction = c.constructor()
+ tx_info = {"from": w3.eth.accounts[0], "value": 0, "gasPrice": 0}
+ tx_hash = deploy_transaction.transact(tx_info)
+ blueprint_address = w3.eth.get_transaction_receipt(tx_hash)["contractAddress"]
+ blueprint_code = w3.eth.get_code(blueprint_address)
+ print("BLUEPRINT CODE:", blueprint_code)
+
+ d = get_contract(deployer_code, blueprint_address)
# deploy with code_ofst=0 fine
d.test(0)
@@ -418,3 +431,212 @@ def test2(target: address, salt: bytes32) -> address:
# test2 = c.test2(b"\x01", salt)
# assert HexBytes(test2) == create2_address_of(c.address, salt, vyper_initcode(b"\x01"))
# assert_tx_failed(lambda: c.test2(bytecode, salt))
+
+
+# XXX: these various tests to check the msize allocator for
+# create_copy_of and create_from_blueprint depend on calling convention
+# and variables writing to memory. think of ways to make more robust to
+# changes in calling convention and memory layout
+@pytest.mark.parametrize("blueprint_prefix", [b"", b"\xfe", b"\xfe\71\x00"])
+def test_create_from_blueprint_complex_value(
+ get_contract, deploy_blueprint_for, w3, blueprint_prefix
+):
+ # check msize allocator does not get trampled by value= kwarg
+ code = """
+var: uint256
+
+@external
+@payable
+def __init__(x: uint256):
+ self.var = x
+
+@external
+def foo()-> uint256:
+ return self.var
+ """
+
+ prefix_len = len(blueprint_prefix)
+
+ some_constant = b"\00" * 31 + b"\x0c"
+
+ deployer_code = f"""
+created_address: public(address)
+x: constant(Bytes[32]) = {some_constant}
+
+@internal
+def foo() -> uint256:
+ g:uint256 = 42
+ return 3
+
+@external
+@payable
+def test(target: address):
+ self.created_address = create_from_blueprint(
+ target,
+ x,
+ code_offset={prefix_len},
+ value=self.foo(),
+ raw_args=True
+ )
+ """
+
+ foo_contract = get_contract(code, 12)
+ expected_runtime_code = w3.eth.get_code(foo_contract.address)
+
+ f, FooContract = deploy_blueprint_for(code, initcode_prefix=blueprint_prefix)
+
+ d = get_contract(deployer_code)
+
+ d.test(f.address, transact={"value": 3})
+
+ test = FooContract(d.created_address())
+ assert w3.eth.get_code(test.address) == expected_runtime_code
+ assert test.foo() == 12
+
+
+@pytest.mark.parametrize("blueprint_prefix", [b"", b"\xfe", b"\xfe\71\x00"])
+def test_create_from_blueprint_complex_salt_raw_args(
+ get_contract, deploy_blueprint_for, w3, blueprint_prefix
+):
+ # test msize allocator does not get trampled by salt= kwarg
+ code = """
+var: uint256
+
+@external
+@payable
+def __init__(x: uint256):
+ self.var = x
+
+@external
+def foo()-> uint256:
+ return self.var
+ """
+
+ some_constant = b"\00" * 31 + b"\x0c"
+ prefix_len = len(blueprint_prefix)
+
+ deployer_code = f"""
+created_address: public(address)
+
+x: constant(Bytes[32]) = {some_constant}
+salt: constant(bytes32) = keccak256("kebab")
+
+@internal
+def foo() -> bytes32:
+ g:uint256 = 42
+ return salt
+
+@external
+@payable
+def test(target: address):
+ self.created_address = create_from_blueprint(
+ target,
+ x,
+ code_offset={prefix_len},
+ salt=self.foo(),
+ raw_args= True
+ )
+ """
+
+ foo_contract = get_contract(code, 12)
+ expected_runtime_code = w3.eth.get_code(foo_contract.address)
+
+ f, FooContract = deploy_blueprint_for(code, initcode_prefix=blueprint_prefix)
+
+ d = get_contract(deployer_code)
+
+ d.test(f.address, transact={})
+
+ test = FooContract(d.created_address())
+ assert w3.eth.get_code(test.address) == expected_runtime_code
+ assert test.foo() == 12
+
+
+@pytest.mark.parametrize("blueprint_prefix", [b"", b"\xfe", b"\xfe\71\x00"])
+def test_create_from_blueprint_complex_salt_no_constructor_args(
+ get_contract, deploy_blueprint_for, w3, blueprint_prefix
+):
+ # test msize allocator does not get trampled by salt= kwarg
+ code = """
+var: uint256
+
+@external
+@payable
+def __init__():
+ self.var = 12
+
+@external
+def foo()-> uint256:
+ return self.var
+ """
+
+ prefix_len = len(blueprint_prefix)
+ deployer_code = f"""
+created_address: public(address)
+
+salt: constant(bytes32) = keccak256("kebab")
+
+@external
+@payable
+def test(target: address):
+ self.created_address = create_from_blueprint(
+ target,
+ code_offset={prefix_len},
+ salt=keccak256(_abi_encode(target))
+ )
+ """
+
+ foo_contract = get_contract(code)
+ expected_runtime_code = w3.eth.get_code(foo_contract.address)
+
+ f, FooContract = deploy_blueprint_for(code, initcode_prefix=blueprint_prefix)
+
+ d = get_contract(deployer_code)
+
+ d.test(f.address, transact={})
+
+ test = FooContract(d.created_address())
+ assert w3.eth.get_code(test.address) == expected_runtime_code
+ assert test.foo() == 12
+
+
+def test_create_copy_of_complex_kwargs(get_contract, w3):
+ # test msize allocator does not get trampled by salt= kwarg
+ complex_salt = """
+created_address: public(address)
+
+@external
+def test(target: address) -> address:
+ self.created_address = create_copy_of(
+ target,
+ salt=keccak256(_abi_encode(target))
+ )
+ return self.created_address
+
+ """
+
+ c = get_contract(complex_salt)
+ bytecode = w3.eth.get_code(c.address)
+ c.test(c.address, transact={})
+ test1 = c.created_address()
+ assert w3.eth.get_code(test1) == bytecode
+
+ # test msize allocator does not get trampled by value= kwarg
+ complex_value = """
+created_address: public(address)
+
+@external
+@payable
+def test(target: address) -> address:
+ value: uint256 = 2
+ self.created_address = create_copy_of(target, value = [2,2,2][value])
+ return self.created_address
+
+ """
+
+ c = get_contract(complex_value)
+ bytecode = w3.eth.get_code(c.address)
+
+ c.test(c.address, transact={"value": 2})
+ test1 = c.created_address()
+ assert w3.eth.get_code(test1) == bytecode
diff --git a/tests/parser/functions/test_default_function.py b/tests/parser/functions/test_default_function.py
index cd1f9f39af..4ad68697ac 100644
--- a/tests/parser/functions/test_default_function.py
+++ b/tests/parser/functions/test_default_function.py
@@ -41,7 +41,7 @@ def test_basic_default_default_param_function(w3, get_logs, get_contract_with_ga
@external
@payable
def fooBar(a: int128 = 12345) -> int128:
- log Sent(ZERO_ADDRESS)
+ log Sent(empty(address))
return a
@external
@@ -100,7 +100,9 @@ def __default__():
assert_compile_failed(lambda: get_contract_with_gas_estimation(code))
-def test_zero_method_id(w3, get_logs, get_contract_with_gas_estimation):
+def test_zero_method_id(w3, get_logs, get_contract, assert_tx_failed):
+ # test a method with 0x00000000 selector,
+ # expects at least 36 bytes of calldata.
code = """
event Sent:
sig: uint256
@@ -116,18 +118,108 @@ def blockHashAskewLimitary(v: uint256) -> uint256:
def __default__():
log Sent(1)
"""
-
- c = get_contract_with_gas_estimation(code)
+ c = get_contract(code)
assert c.blockHashAskewLimitary(0) == 7
- logs = get_logs(w3.eth.send_transaction({"to": c.address, "value": 0}), c, "Sent")
- assert 1 == logs[0].args.sig
+ def _call_with_bytes(hexstr):
+ # call our special contract and return the logged value
+ logs = get_logs(
+ w3.eth.send_transaction({"to": c.address, "value": 0, "data": hexstr}), c, "Sent"
+ )
+ return logs[0].args.sig
- logs = get_logs(
- # call blockHashAskewLimitary
- w3.eth.send_transaction({"to": c.address, "value": 0, "data": "0x" + "00" * 36}),
- c,
- "Sent",
- )
- assert 2 == logs[0].args.sig
+ assert 1 == _call_with_bytes("0x")
+
+ # call blockHashAskewLimitary with proper calldata
+ assert 2 == _call_with_bytes("0x" + "00" * 36)
+
+ # call blockHashAskewLimitary with extra trailing bytes in calldata
+ assert 2 == _call_with_bytes("0x" + "00" * 37)
+
+ for i in range(4):
+ # less than 4 bytes of calldata doesn't match the 0 selector and goes to default
+ assert 1 == _call_with_bytes("0x" + "00" * i)
+
+ for i in range(4, 36):
+ # match the full 4 selector bytes, but revert due to malformed (short) calldata
+ assert_tx_failed(lambda: _call_with_bytes("0x" + "00" * i))
+
+
+def test_another_zero_method_id(w3, get_logs, get_contract, assert_tx_failed):
+ # test another zero method id but which only expects 4 bytes of calldata
+ code = """
+event Sent:
+ sig: uint256
+
+@external
+@payable
+# function selector: 0x00000000
+def wycpnbqcyf() -> uint256:
+ log Sent(2)
+ return 7
+
+@external
+def __default__():
+ log Sent(1)
+ """
+ c = get_contract(code)
+
+ assert c.wycpnbqcyf() == 7
+
+ def _call_with_bytes(hexstr):
+ # call our special contract and return the logged value
+ logs = get_logs(
+ w3.eth.send_transaction({"to": c.address, "value": 0, "data": hexstr}), c, "Sent"
+ )
+ return logs[0].args.sig
+
+ assert 1 == _call_with_bytes("0x")
+
+ # call wycpnbqcyf
+ assert 2 == _call_with_bytes("0x" + "00" * 4)
+
+ # too many bytes ok
+ assert 2 == _call_with_bytes("0x" + "00" * 5)
+
+ # "right" method id but by accident - not enough bytes.
+ for i in range(4):
+ assert 1 == _call_with_bytes("0x" + "00" * i)
+
+
+def test_partial_selector_match_trailing_zeroes(w3, get_logs, get_contract):
+ code = """
+event Sent:
+ sig: uint256
+
+@external
+@payable
+# function selector: 0xd88e0b00
+def fow() -> uint256:
+ log Sent(2)
+ return 7
+
+@external
+def __default__():
+ log Sent(1)
+ """
+ c = get_contract(code)
+
+ # sanity check - we can call c.fow()
+ assert c.fow() == 7
+
+ def _call_with_bytes(hexstr):
+ # call our special contract and return the logged value
+ logs = get_logs(
+ w3.eth.send_transaction({"to": c.address, "value": 0, "data": hexstr}), c, "Sent"
+ )
+ return logs[0].args.sig
+
+ # check we can call default function
+ assert 1 == _call_with_bytes("0x")
+
+ # check fow() selector is 0xd88e0b00
+ assert 2 == _call_with_bytes("0xd88e0b00")
+
+ # check calling d88e0b with no trailing zero goes to fallback instead of reverting
+ assert 1 == _call_with_bytes("0xd88e0b")
diff --git a/tests/parser/functions/test_ec.py b/tests/parser/functions/test_ec.py
index be0f6f7ed2..e1d9e3d2ee 100644
--- a/tests/parser/functions/test_ec.py
+++ b/tests/parser/functions/test_ec.py
@@ -45,6 +45,57 @@ def _ecadd3(x: uint256[2], y: uint256[2]) -> uint256[2]:
assert c._ecadd3(G1, negative_G1) == [0, 0]
+def test_ecadd_internal_call(get_contract_with_gas_estimation):
+ code = """
+@internal
+def a() -> uint256[2]:
+ return [1, 2]
+
+@external
+def foo() -> uint256[2]:
+ return ecadd([1, 2], self.a())
+ """
+ c = get_contract_with_gas_estimation(code)
+ assert c.foo() == G1_times_two
+
+
+def test_ecadd_ext_call(w3, side_effects_contract, assert_side_effects_invoked, get_contract):
+ code = """
+interface Foo:
+ def foo(x: uint256[2]) -> uint256[2]: payable
+
+@external
+def foo(a: Foo) -> uint256[2]:
+ return ecadd([1, 2], a.foo([1, 2]))
+ """
+ c1 = side_effects_contract("uint256[2]")
+ c2 = get_contract(code)
+
+ assert c2.foo(c1.address) == G1_times_two
+
+ assert_side_effects_invoked(c1, lambda: c2.foo(c1.address, transact={}))
+
+
+def test_ecadd_evaluation_order(get_contract_with_gas_estimation):
+ code = """
+x: uint256[2]
+
+@internal
+def bar() -> uint256[2]:
+ self.x = ecadd([1, 2], [1, 2])
+ return [1, 2]
+
+@external
+def foo() -> bool:
+ self.x = [1, 2]
+ a: uint256[2] = ecadd([1, 2], [1, 2])
+ b: uint256[2] = ecadd(self.x, self.bar())
+ return a[0] == b[0] and a[1] == b[1]
+ """
+ c = get_contract_with_gas_estimation(code)
+ assert c.foo() is True
+
+
def test_ecmul(get_contract_with_gas_estimation):
ecmuller = """
x3: uint256[2]
@@ -74,3 +125,54 @@ def _ecmul3(x: uint256[2], y: uint256) -> uint256[2]:
assert c._ecmul(G1, 3) == G1_times_three
assert c._ecmul(G1, curve_order - 1) == negative_G1
assert c._ecmul(G1, curve_order) == [0, 0]
+
+
+def test_ecmul_internal_call(get_contract_with_gas_estimation):
+ code = """
+@internal
+def a() -> uint256:
+ return 3
+
+@external
+def foo() -> uint256[2]:
+ return ecmul([1, 2], self.a())
+ """
+ c = get_contract_with_gas_estimation(code)
+ assert c.foo() == G1_times_three
+
+
+def test_ecmul_ext_call(w3, side_effects_contract, assert_side_effects_invoked, get_contract):
+ code = """
+interface Foo:
+ def foo(x: uint256) -> uint256: payable
+
+@external
+def foo(a: Foo) -> uint256[2]:
+ return ecmul([1, 2], a.foo(3))
+ """
+ c1 = side_effects_contract("uint256")
+ c2 = get_contract(code)
+
+ assert c2.foo(c1.address) == G1_times_three
+
+ assert_side_effects_invoked(c1, lambda: c2.foo(c1.address, transact={}))
+
+
+def test_ecmul_evaluation_order(get_contract_with_gas_estimation):
+ code = """
+x: uint256[2]
+
+@internal
+def bar() -> uint256:
+ self.x = ecmul([1, 2], 3)
+ return 3
+
+@external
+def foo() -> bool:
+ self.x = [1, 2]
+ a: uint256[2] = ecmul([1, 2], 3)
+ b: uint256[2] = ecmul(self.x, self.bar())
+ return a[0] == b[0] and a[1] == b[1]
+ """
+ c = get_contract_with_gas_estimation(code)
+ assert c.foo() is True
diff --git a/tests/parser/functions/test_ecrecover.py b/tests/parser/functions/test_ecrecover.py
index 77e9655b3e..8571948c3d 100644
--- a/tests/parser/functions/test_ecrecover.py
+++ b/tests/parser/functions/test_ecrecover.py
@@ -40,3 +40,48 @@ def test_ecrecover_uints2() -> address:
assert c.test_ecrecover_uints2() == local_account.address
print("Passed ecrecover test")
+
+
+def test_invalid_signature(get_contract):
+ code = """
+dummies: HashMap[address, HashMap[address, uint256]]
+
+@external
+def test_ecrecover(hash: bytes32, v: uint8, r: uint256) -> address:
+ # read from hashmap to put garbage in 0 memory location
+ s: uint256 = self.dummies[msg.sender][msg.sender]
+ return ecrecover(hash, v, r, s)
+ """
+ c = get_contract(code)
+ hash_ = bytes(i for i in range(32))
+ v = 0 # invalid v! ecrecover precompile will not write to output buffer
+ r = 0
+ # note web3.py decoding of 0x000..00 address is None.
+ assert c.test_ecrecover(hash_, v, r) is None
+
+
+# slightly more subtle example: get_v() stomps memory location 0,
+# so this tests that the output buffer stays clean during ecrecover()
+# builtin execution.
+def test_invalid_signature2(get_contract):
+ code = """
+
+owner: immutable(address)
+
+@external
+def __init__():
+ owner = 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf
+
+@internal
+def get_v() -> uint256:
+ assert owner == owner # force a dload to write at index 0 of memory
+ return 21
+
+@payable
+@external
+def test_ecrecover() -> bool:
+ assert ecrecover(empty(bytes32), self.get_v(), 0, 0) == empty(address)
+ return True
+ """
+ c = get_contract(code)
+ assert c.test_ecrecover() is True
diff --git a/tests/parser/functions/test_empty.py b/tests/parser/functions/test_empty.py
index 20fd98f543..c3627785dc 100644
--- a/tests/parser/functions/test_empty.py
+++ b/tests/parser/functions/test_empty.py
@@ -1,6 +1,6 @@
import pytest
-from vyper.exceptions import TypeMismatch
+from vyper.exceptions import InstantiationException, TypeMismatch
@pytest.mark.parametrize(
@@ -87,8 +87,8 @@ def foo():
self.foobar = empty(address)
bar = empty(address)
- assert self.foobar == ZERO_ADDRESS
- assert bar == ZERO_ADDRESS
+ assert self.foobar == empty(address)
+ assert bar == empty(address)
""",
"""
@external
@@ -214,12 +214,12 @@ def foo():
self.foobar = empty(address[3])
bar = empty(address[3])
- assert self.foobar[0] == ZERO_ADDRESS
- assert self.foobar[1] == ZERO_ADDRESS
- assert self.foobar[2] == ZERO_ADDRESS
- assert bar[0] == ZERO_ADDRESS
- assert bar[1] == ZERO_ADDRESS
- assert bar[2] == ZERO_ADDRESS
+ assert self.foobar[0] == empty(address)
+ assert self.foobar[1] == empty(address)
+ assert self.foobar[2] == empty(address)
+ assert bar[0] == empty(address)
+ assert bar[1] == empty(address)
+ assert bar[2] == empty(address)
""",
],
)
@@ -376,14 +376,14 @@ def foo():
assert self.foobar.c == False
assert self.foobar.d == 0.0
assert self.foobar.e == 0x0000000000000000000000000000000000000000000000000000000000000000
- assert self.foobar.f == ZERO_ADDRESS
+ assert self.foobar.f == empty(address)
assert bar.a == 0
assert bar.b == 0
assert bar.c == False
assert bar.d == 0.0
assert bar.e == 0x0000000000000000000000000000000000000000000000000000000000000000
- assert bar.f == ZERO_ADDRESS
+ assert bar.f == empty(address)
"""
c = get_contract_with_gas_estimation(code)
@@ -711,4 +711,4 @@ def test():
],
)
def test_invalid_types(contract, get_contract, assert_compile_failed):
- assert_compile_failed(lambda: get_contract(contract), TypeMismatch)
+ assert_compile_failed(lambda: get_contract(contract), InstantiationException)
diff --git a/tests/parser/functions/test_floor.py b/tests/parser/functions/test_floor.py
index dc53545ac3..d2fd993785 100644
--- a/tests/parser/functions/test_floor.py
+++ b/tests/parser/functions/test_floor.py
@@ -108,3 +108,37 @@ def floor_param(p: decimal) -> int256:
assert c.fou() == -4
assert c.floor_param(Decimal("-5.6")) == -6
assert c.floor_param(Decimal("-0.0000000001")) == -1
+
+
+def test_floor_ext_call(w3, side_effects_contract, assert_side_effects_invoked, get_contract):
+ code = """
+@external
+def foo(a: Foo) -> int256:
+ return floor(a.foo(2.5))
+
+interface Foo:
+ def foo(x: decimal) -> decimal: nonpayable
+ """
+
+ c1 = side_effects_contract("decimal")
+ c2 = get_contract(code)
+
+ assert c2.foo(c1.address) == 2
+
+ assert_side_effects_invoked(c1, lambda: c2.foo(c1.address, transact={}))
+
+
+def test_floor_internal_call(get_contract_with_gas_estimation):
+ code = """
+@external
+def foo() -> int256:
+ return floor(self.bar())
+
+@internal
+def bar() -> decimal:
+ return 2.5
+ """
+
+ c = get_contract_with_gas_estimation(code)
+
+ assert c.foo() == 2
diff --git a/tests/parser/functions/test_interfaces.py b/tests/parser/functions/test_interfaces.py
index 5430a39629..d1cebaff89 100644
--- a/tests/parser/functions/test_interfaces.py
+++ b/tests/parser/functions/test_interfaces.py
@@ -1,10 +1,15 @@
+import json
from decimal import Decimal
import pytest
-from vyper.cli.utils import extract_file_interface_imports
-from vyper.compiler import compile_code, compile_codes
-from vyper.exceptions import ArgumentException, InterfaceViolation, StructureException
+from vyper.compiler import compile_code
+from vyper.exceptions import (
+ ArgumentException,
+ InterfaceViolation,
+ NamespaceCollision,
+ StructureException,
+)
def test_basic_extract_interface():
@@ -24,7 +29,7 @@ def allowance(_owner: address, _spender: address) -> (uint256, uint256):
return 1, 2
"""
- out = compile_code(code, ["interface"])
+ out = compile_code(code, output_formats=["interface"])
out = out["interface"]
code_pass = "\n".join(code.split("\n")[:-2] + [" pass"]) # replace with a pass statement.
@@ -55,8 +60,9 @@ def allowance(_owner: address, _spender: address) -> (uint256, uint256): view
def test(_owner: address): nonpayable
"""
- out = compile_codes({"one.vy": code}, ["external_interface"])["one.vy"]
- out = out["external_interface"]
+ out = compile_code(code, contract_name="One.vy", output_formats=["external_interface"])[
+ "external_interface"
+ ]
assert interface.strip() == out.strip()
@@ -67,7 +73,6 @@ def test_basic_interface_implements(assert_compile_failed):
implements: ERC20
-
@external
def test() -> bool:
return True
@@ -76,7 +81,7 @@ def test() -> bool:
assert_compile_failed(lambda: compile_code(code), InterfaceViolation)
-def test_external_interface_parsing(assert_compile_failed):
+def test_external_interface_parsing(make_input_bundle, assert_compile_failed):
interface_code = """
@external
def foo() -> uint256:
@@ -87,7 +92,7 @@ def bar() -> uint256:
pass
"""
- interface_codes = {"FooBarInterface": {"type": "vyper", "code": interface_code}}
+ input_bundle = make_input_bundle({"a.vy": interface_code})
code = """
import a as FooBarInterface
@@ -103,7 +108,7 @@ def bar() -> uint256:
return 2
"""
- assert compile_code(code, interface_codes=interface_codes)
+ assert compile_code(code, input_bundle=input_bundle)
not_implemented_code = """
import a as FooBarInterface
@@ -117,18 +122,17 @@ def foo() -> uint256:
"""
assert_compile_failed(
- lambda: compile_code(not_implemented_code, interface_codes=interface_codes),
- InterfaceViolation,
+ lambda: compile_code(not_implemented_code, input_bundle=input_bundle), InterfaceViolation
)
-def test_missing_event(assert_compile_failed):
+def test_missing_event(make_input_bundle, assert_compile_failed):
interface_code = """
event Foo:
a: uint256
"""
- interface_codes = {"FooBarInterface": {"type": "vyper", "code": interface_code}}
+ input_bundle = make_input_bundle({"a.vy": interface_code})
not_implemented_code = """
import a as FooBarInterface
@@ -141,18 +145,18 @@ def bar() -> uint256:
"""
assert_compile_failed(
- lambda: compile_code(not_implemented_code, interface_codes=interface_codes),
- InterfaceViolation,
+ lambda: compile_code(not_implemented_code, input_bundle=input_bundle), InterfaceViolation
)
-def test_malformed_event(assert_compile_failed):
+# check that event types match
+def test_malformed_event(make_input_bundle, assert_compile_failed):
interface_code = """
event Foo:
a: uint256
"""
- interface_codes = {"FooBarInterface": {"type": "vyper", "code": interface_code}}
+ input_bundle = make_input_bundle({"a.vy": interface_code})
not_implemented_code = """
import a as FooBarInterface
@@ -168,43 +172,103 @@ def bar() -> uint256:
"""
assert_compile_failed(
- lambda: compile_code(not_implemented_code, interface_codes=interface_codes),
- InterfaceViolation,
+ lambda: compile_code(not_implemented_code, input_bundle=input_bundle), InterfaceViolation
+ )
+
+
+# check that event non-indexed arg needs to match interface
+def test_malformed_events_indexed(make_input_bundle, assert_compile_failed):
+ interface_code = """
+event Foo:
+ a: uint256
+ """
+
+ input_bundle = make_input_bundle({"a.vy": interface_code})
+
+ not_implemented_code = """
+import a as FooBarInterface
+
+implements: FooBarInterface
+
+# a should not be indexed
+event Foo:
+ a: indexed(uint256)
+
+@external
+def bar() -> uint256:
+ return 1
+ """
+
+ assert_compile_failed(
+ lambda: compile_code(not_implemented_code, input_bundle=input_bundle), InterfaceViolation
+ )
+
+
+# check that event indexed arg needs to match interface
+def test_malformed_events_indexed2(make_input_bundle, assert_compile_failed):
+ interface_code = """
+event Foo:
+ a: indexed(uint256)
+ """
+
+ input_bundle = make_input_bundle({"a.vy": interface_code})
+
+ not_implemented_code = """
+import a as FooBarInterface
+
+implements: FooBarInterface
+
+# a should be indexed
+event Foo:
+ a: uint256
+
+@external
+def bar() -> uint256:
+ return 1
+ """
+
+ assert_compile_failed(
+ lambda: compile_code(not_implemented_code, input_bundle=input_bundle), InterfaceViolation
)
VALID_IMPORT_CODE = [
# import statement, import path without suffix
- ("import a as Foo", "a"),
- ("import b.a as Foo", "b/a"),
- ("import Foo as Foo", "Foo"),
- ("from a import Foo", "a/Foo"),
- ("from b.a import Foo", "b/a/Foo"),
- ("from .a import Foo", "./a/Foo"),
- ("from ..a import Foo", "../a/Foo"),
+ ("import a as Foo", "a.vy"),
+ ("import b.a as Foo", "b/a.vy"),
+ ("import Foo as Foo", "Foo.vy"),
+ ("from a import Foo", "a/Foo.vy"),
+ ("from b.a import Foo", "b/a/Foo.vy"),
+ ("from .a import Foo", "./a/Foo.vy"),
+ ("from ..a import Foo", "../a/Foo.vy"),
]
-@pytest.mark.parametrize("code", VALID_IMPORT_CODE)
-def test_extract_file_interface_imports(code):
- assert extract_file_interface_imports(code[0]) == {"Foo": code[1]}
+@pytest.mark.parametrize("code,filename", VALID_IMPORT_CODE)
+def test_extract_file_interface_imports(code, filename, make_input_bundle):
+ input_bundle = make_input_bundle({filename: ""})
+
+ assert compile_code(code, input_bundle=input_bundle) is not None
BAD_IMPORT_CODE = [
- "import a", # must alias absolute imports
- "import a as A\nimport a as A", # namespace collisions
- "from b import a\nfrom a import a",
- "from . import a\nimport a as a",
- "import a as a\nfrom . import a",
+ ("import a", StructureException), # must alias absolute imports
+ ("import a as A\nimport a as A", NamespaceCollision),
+ ("from b import a\nfrom . import a", NamespaceCollision),
+ ("from . import a\nimport a as a", NamespaceCollision),
+ ("import a as a\nfrom . import a", NamespaceCollision),
]
-@pytest.mark.parametrize("code", BAD_IMPORT_CODE)
-def test_extract_file_interface_imports_raises(code, assert_compile_failed):
- assert_compile_failed(lambda: extract_file_interface_imports(code), StructureException)
+@pytest.mark.parametrize("code,exception_type", BAD_IMPORT_CODE)
+def test_extract_file_interface_imports_raises(
+ code, exception_type, assert_compile_failed, make_input_bundle
+):
+ input_bundle = make_input_bundle({"a.vy": "", "b/a.vy": ""}) # dummy
+ assert_compile_failed(lambda: compile_code(code, input_bundle=input_bundle), exception_type)
-def test_external_call_to_interface(w3, get_contract):
+def test_external_call_to_interface(w3, get_contract, make_input_bundle):
token_code = """
balanceOf: public(HashMap[address, uint256])
@@ -213,6 +277,8 @@ def transfer(to: address, _value: uint256):
self.balanceOf[to] += _value
"""
+ input_bundle = make_input_bundle({"one.vy": token_code})
+
code = """
import one as TokenCode
@@ -234,9 +300,7 @@ def test():
"""
erc20 = get_contract(token_code)
- test_c = get_contract(
- code, *[erc20.address], interface_codes={"TokenCode": {"type": "vyper", "code": token_code}}
- )
+ test_c = get_contract(code, *[erc20.address], input_bundle=input_bundle)
sender = w3.eth.accounts[0]
assert erc20.balanceOf(sender) == 0
@@ -255,7 +319,7 @@ def test():
("epsilon(decimal)", "decimal", Decimal("1E-10")),
],
)
-def test_external_call_to_interface_kwarg(get_contract, kwarg, typ, expected):
+def test_external_call_to_interface_kwarg(get_contract, kwarg, typ, expected, make_input_bundle):
code_a = f"""
@external
@view
@@ -263,6 +327,8 @@ def foo(_max: {typ} = {kwarg}) -> {typ}:
return _max
"""
+ input_bundle = make_input_bundle({"one.vy": code_a})
+
code_b = f"""
import one as ContractA
@@ -273,11 +339,7 @@ def bar(a_address: address) -> {typ}:
"""
contract_a = get_contract(code_a)
- contract_b = get_contract(
- code_b,
- *[contract_a.address],
- interface_codes={"ContractA": {"type": "vyper", "code": code_a}},
- )
+ contract_b = get_contract(code_b, *[contract_a.address], input_bundle=input_bundle)
assert contract_b.bar(contract_a.address) == expected
@@ -310,9 +372,7 @@ def test():
"""
erc20 = get_contract(token_code)
- test_c = get_contract(
- code, *[erc20.address], interface_codes={"TokenCode": {"type": "vyper", "code": token_code}}
- )
+ test_c = get_contract(code, *[erc20.address])
sender = w3.eth.accounts[0]
assert erc20.balanceOf(sender) == 0
@@ -382,11 +442,7 @@ def test_fail3() -> int256:
"""
bad_c = get_contract(external_contract)
- c = get_contract(
- code,
- bad_c.address,
- interface_codes={"BadCode": {"type": "vyper", "code": external_contract}},
- )
+ c = get_contract(code, bad_c.address)
assert bad_c.ok() == 1
assert bad_c.should_fail() == -(2**255)
@@ -444,7 +500,9 @@ def test_fail2() -> Bytes[3]:
# test data returned from external interface gets clamped
-def test_json_abi_bytes_clampers(get_contract, assert_tx_failed, assert_compile_failed):
+def test_json_abi_bytes_clampers(
+ get_contract, assert_tx_failed, assert_compile_failed, make_input_bundle
+):
external_contract = """
@external
def returns_Bytes3() -> Bytes[3]:
@@ -488,18 +546,15 @@ def test_fail3() -> Bytes[3]:
"""
bad_c = get_contract(external_contract)
- bad_c_interface = {
- "BadJSONInterface": {
- "type": "json",
- "code": compile_code(external_contract, ["abi"])["abi"],
- }
- }
+
+ bad_json_interface = json.dumps(compile_code(external_contract, output_formats=["abi"])["abi"])
+ input_bundle = make_input_bundle({"BadJSONInterface.json": bad_json_interface})
assert_compile_failed(
- lambda: get_contract(should_not_compile, interface_codes=bad_c_interface), ArgumentException
+ lambda: get_contract(should_not_compile, input_bundle=input_bundle), ArgumentException
)
- c = get_contract(code, bad_c.address, interface_codes=bad_c_interface)
+ c = get_contract(code, bad_c.address, input_bundle=input_bundle)
assert bad_c.returns_Bytes3() == b"123"
assert_tx_failed(lambda: c.test_fail1())
@@ -507,7 +562,7 @@ def test_fail3() -> Bytes[3]:
assert_tx_failed(lambda: c.test_fail3())
-def test_units_interface(w3, get_contract):
+def test_units_interface(w3, get_contract, make_input_bundle):
code = """
import balanceof as BalanceOf
@@ -518,49 +573,41 @@ def test_units_interface(w3, get_contract):
def balanceOf(owner: address) -> uint256:
return as_wei_value(1, "ether")
"""
+
interface_code = """
@external
@view
def balanceOf(owner: address) -> uint256:
pass
"""
- interface_codes = {"BalanceOf": {"type": "vyper", "code": interface_code}}
- c = get_contract(code, interface_codes=interface_codes)
+
+ input_bundle = make_input_bundle({"balanceof.vy": interface_code})
+
+ c = get_contract(code, input_bundle=input_bundle)
assert c.balanceOf(w3.eth.accounts[0]) == w3.to_wei(1, "ether")
-def test_local_and_global_interface_namespaces():
+def test_simple_implements(make_input_bundle):
interface_code = """
@external
def foo() -> uint256:
pass
"""
- global_interface_codes = {
- "FooInterface": {"type": "vyper", "code": interface_code},
- "BarInterface": {"type": "vyper", "code": interface_code},
- }
- local_interface_codes = {
- "FooContract": {"FooInterface": {"type": "vyper", "code": interface_code}},
- "BarContract": {"BarInterface": {"type": "vyper", "code": interface_code}},
- }
-
code = """
-import a as {0}
+import a as FooInterface
-implements: {0}
+implements: FooInterface
@external
def foo() -> uint256:
return 1
"""
- codes = {"FooContract": code.format("FooInterface"), "BarContract": code.format("BarInterface")}
+ input_bundle = make_input_bundle({"a.vy": interface_code})
- global_compiled = compile_codes(codes, interface_codes=global_interface_codes)
- local_compiled = compile_codes(codes, interface_codes=local_interface_codes)
- assert global_compiled == local_compiled
+ assert compile_code(code, input_bundle=input_bundle) is not None
def test_self_interface_is_allowed(get_contract):
@@ -666,20 +713,28 @@ def convert_v1_abi(abi):
@pytest.mark.parametrize("type_str", [i[0] for i in type_str_params])
-def test_json_interface_implements(type_str):
+def test_json_interface_implements(type_str, make_input_bundle, make_file):
code = interface_test_code.format(type_str)
- abi = compile_code(code, ["abi"])["abi"]
+ abi = compile_code(code, output_formats=["abi"])["abi"]
+
code = f"import jsonabi as jsonabi\nimplements: jsonabi\n{code}"
- compile_code(code, interface_codes={"jsonabi": {"type": "json", "code": abi}})
- compile_code(code, interface_codes={"jsonabi": {"type": "json", "code": convert_v1_abi(abi)}})
+
+ input_bundle = make_input_bundle({"jsonabi.json": json.dumps(abi)})
+
+ compile_code(code, input_bundle=input_bundle)
+
+ # !!! overwrite the file
+ make_file("jsonabi.json", json.dumps(convert_v1_abi(abi)))
+
+ compile_code(code, input_bundle=input_bundle)
@pytest.mark.parametrize("type_str,value", type_str_params)
-def test_json_interface_calls(get_contract, type_str, value):
+def test_json_interface_calls(get_contract, type_str, value, make_input_bundle, make_file):
code = interface_test_code.format(type_str)
- abi = compile_code(code, ["abi"])["abi"]
+ abi = compile_code(code, output_formats=["abi"])["abi"]
c1 = get_contract(code)
code = f"""
@@ -690,11 +745,13 @@ def test_json_interface_calls(get_contract, type_str, value):
def test_call(a: address, b: {type_str}) -> {type_str}:
return jsonabi(a).test_json(b)
"""
- c2 = get_contract(code, interface_codes={"jsonabi": {"type": "json", "code": abi}})
+ input_bundle = make_input_bundle({"jsonabi.json": json.dumps(abi)})
+
+ c2 = get_contract(code, input_bundle=input_bundle)
assert c2.test_call(c1.address, value) == value
- c3 = get_contract(
- code, interface_codes={"jsonabi": {"type": "json", "code": convert_v1_abi(abi)}}
- )
+
+ make_file("jsonabi.json", json.dumps(convert_v1_abi(abi)))
+ c3 = get_contract(code, input_bundle=input_bundle)
assert c3.test_call(c1.address, value) == value
diff --git a/tests/parser/functions/test_mulmod.py b/tests/parser/functions/test_mulmod.py
new file mode 100644
index 0000000000..96477897b9
--- /dev/null
+++ b/tests/parser/functions/test_mulmod.py
@@ -0,0 +1,107 @@
+def test_uint256_mulmod(assert_tx_failed, get_contract_with_gas_estimation):
+ uint256_code = """
+@external
+def _uint256_mulmod(x: uint256, y: uint256, z: uint256) -> uint256:
+ return uint256_mulmod(x, y, z)
+ """
+
+ c = get_contract_with_gas_estimation(uint256_code)
+
+ assert c._uint256_mulmod(3, 1, 2) == 1
+ assert c._uint256_mulmod(200, 3, 601) == 600
+ assert c._uint256_mulmod(2**255, 1, 3) == 2
+ assert c._uint256_mulmod(2**255, 2, 6) == 4
+ assert_tx_failed(lambda: c._uint256_mulmod(2, 2, 0))
+
+
+def test_uint256_mulmod_complex(get_contract_with_gas_estimation):
+ modexper = """
+@external
+def exponential(base: uint256, exponent: uint256, modulus: uint256) -> uint256:
+ o: uint256 = 1
+ for i in range(256):
+ o = uint256_mulmod(o, o, modulus)
+ if exponent & shift(1, 255 - i) != 0:
+ o = uint256_mulmod(o, base, modulus)
+ return o
+ """
+
+ c = get_contract_with_gas_estimation(modexper)
+ assert c.exponential(3, 5, 100) == 43
+ assert c.exponential(2, 997, 997) == 2
+
+
+def test_uint256_mulmod_ext_call(
+ w3, side_effects_contract, assert_side_effects_invoked, get_contract
+):
+ code = """
+@external
+def foo(f: Foo) -> uint256:
+ return uint256_mulmod(200, 3, f.foo(601))
+
+interface Foo:
+ def foo(x: uint256) -> uint256: nonpayable
+ """
+
+ c1 = side_effects_contract("uint256")
+ c2 = get_contract(code)
+
+ assert c2.foo(c1.address) == 600
+
+ assert_side_effects_invoked(c1, lambda: c2.foo(c1.address, transact={}))
+
+
+def test_uint256_mulmod_internal_call(get_contract_with_gas_estimation):
+ code = """
+@external
+def foo() -> uint256:
+ return uint256_mulmod(self.a(), self.b(), self.c())
+
+@internal
+def a() -> uint256:
+ return 200
+
+@internal
+def b() -> uint256:
+ return 3
+
+@internal
+def c() -> uint256:
+ return 601
+ """
+
+ c = get_contract_with_gas_estimation(code)
+
+ assert c.foo() == 600
+
+
+def test_uint256_mulmod_evaluation_order(get_contract_with_gas_estimation):
+ code = """
+a: uint256
+
+@external
+def foo1() -> uint256:
+ self.a = 1
+ return uint256_mulmod(self.a, 2, self.bar())
+
+@external
+def foo2() -> uint256:
+ self.a = 1
+ return uint256_mulmod(self.bar(), self.a, 2)
+
+@external
+def foo3() -> uint256:
+ self.a = 1
+ return uint256_mulmod(2, self.a, self.bar())
+
+@internal
+def bar() -> uint256:
+ self.a = 7
+ return 5
+ """
+
+ c = get_contract_with_gas_estimation(code)
+
+ assert c.foo1() == 2
+ assert c.foo2() == 1
+ assert c.foo3() == 2
diff --git a/tests/parser/functions/test_raw_call.py b/tests/parser/functions/test_raw_call.py
index 95db070ffa..5bb23447e4 100644
--- a/tests/parser/functions/test_raw_call.py
+++ b/tests/parser/functions/test_raw_call.py
@@ -1,6 +1,7 @@
import pytest
from hexbytes import HexBytes
+from vyper import compile_code
from vyper.builtins.functions import eip1167_bytecode
from vyper.exceptions import ArgumentException, InvalidType, StateAccessViolation
@@ -260,6 +261,68 @@ def __default__():
w3.eth.send_transaction({"to": caller.address, "data": sig})
+# check max_outsize=0 does same thing as not setting max_outsize.
+# compile to bytecode and compare bytecode directly.
+def test_max_outsize_0():
+ code1 = """
+@external
+def test_raw_call(_target: address):
+ raw_call(_target, method_id("foo()"))
+ """
+ code2 = """
+@external
+def test_raw_call(_target: address):
+ raw_call(_target, method_id("foo()"), max_outsize=0)
+ """
+ output1 = compile_code(code1, output_formats=["bytecode", "bytecode_runtime"])
+ output2 = compile_code(code2, output_formats=["bytecode", "bytecode_runtime"])
+ assert output1 == output2
+
+
+# check max_outsize=0 does same thing as not setting max_outsize,
+# this time with revert_on_failure set to False
+def test_max_outsize_0_no_revert_on_failure():
+ code1 = """
+@external
+def test_raw_call(_target: address) -> bool:
+ # compile raw_call both ways, with revert_on_failure
+ a: bool = raw_call(_target, method_id("foo()"), revert_on_failure=False)
+ return a
+ """
+ # same code, but with max_outsize=0
+ code2 = """
+@external
+def test_raw_call(_target: address) -> bool:
+ a: bool = raw_call(_target, method_id("foo()"), max_outsize=0, revert_on_failure=False)
+ return a
+ """
+ output1 = compile_code(code1, output_formats=["bytecode", "bytecode_runtime"])
+ output2 = compile_code(code2, output_formats=["bytecode", "bytecode_runtime"])
+ assert output1 == output2
+
+
+# test functionality of max_outsize=0
+def test_max_outsize_0_call(get_contract):
+ target_source = """
+@external
+@payable
+def bar() -> uint256:
+ return 123
+ """
+
+ caller_source = """
+@external
+@payable
+def foo(_addr: address) -> bool:
+ success: bool = raw_call(_addr, method_id("bar()"), max_outsize=0, revert_on_failure=False)
+ return success
+ """
+
+ target = get_contract(target_source)
+ caller = get_contract(caller_source)
+ assert caller.foo(target.address) is True
+
+
def test_static_call_fails_nonpayable(get_contract, assert_tx_failed):
target_source = """
baz: int128
@@ -363,6 +426,164 @@ def baz(_addr: address, should_raise: bool) -> uint256:
assert caller.baz(target.address, False) == 3
+# XXX: these test_raw_call_clean_mem* tests depend on variables and
+# calling convention writing to memory. think of ways to make more
+# robust to changes to calling convention and memory layout.
+
+
+def test_raw_call_msg_data_clean_mem(get_contract):
+ # test msize uses clean memory and does not get overwritten by
+ # any raw_call() arguments
+ code = """
+identity: constant(address) = 0x0000000000000000000000000000000000000004
+
+@external
+def foo():
+ pass
+
+@internal
+@view
+def get_address()->address:
+ a:uint256 = 121 # 0x79
+ return identity
+@external
+def bar(f: uint256, u: uint256) -> Bytes[100]:
+ # embed an internal call in the calculation of address
+ a: Bytes[100] = raw_call(self.get_address(), msg.data, max_outsize=100)
+ return a
+ """
+
+ c = get_contract(code)
+ assert (
+ c.bar(1, 2).hex() == "ae42e951"
+ "0000000000000000000000000000000000000000000000000000000000000001"
+ "0000000000000000000000000000000000000000000000000000000000000002"
+ )
+
+
+def test_raw_call_clean_mem2(get_contract):
+ # test msize uses clean memory and does not get overwritten by
+ # any raw_call() arguments, another way
+ code = """
+buf: Bytes[100]
+
+@external
+def bar(f: uint256, g: uint256, h: uint256) -> Bytes[100]:
+ # embed a memory modifying expression in the calculation of address
+ self.buf = raw_call(
+ [0x0000000000000000000000000000000000000004,][f-1],
+ msg.data,
+ max_outsize=100
+ )
+ return self.buf
+ """
+ c = get_contract(code)
+
+ assert (
+ c.bar(1, 2, 3).hex() == "9309b76e"
+ "0000000000000000000000000000000000000000000000000000000000000001"
+ "0000000000000000000000000000000000000000000000000000000000000002"
+ "0000000000000000000000000000000000000000000000000000000000000003"
+ )
+
+
+def test_raw_call_clean_mem3(get_contract):
+ # test msize uses clean memory and does not get overwritten by
+ # any raw_call() arguments, and also test order of evaluation for
+ # scope_multi
+ code = """
+buf: Bytes[100]
+canary: String[32]
+
+@internal
+def bar() -> address:
+ self.canary = "bar"
+ return 0x0000000000000000000000000000000000000004
+
+@internal
+def goo() -> uint256:
+ self.canary = "goo"
+ return 0
+
+@external
+def foo() -> String[32]:
+ self.buf = raw_call(self.bar(), msg.data, value = self.goo(), max_outsize=100)
+ return self.canary
+ """
+ c = get_contract(code)
+ assert c.foo() == "goo"
+
+
+def test_raw_call_clean_mem_kwargs_value(get_contract):
+ # test msize uses clean memory and does not get overwritten by
+ # any raw_call() kwargs
+ code = """
+buf: Bytes[100]
+
+# add a dummy function to trigger memory expansion in the selector table routine
+@external
+def foo():
+ pass
+
+@internal
+def _value() -> uint256:
+ x: uint256 = 1
+ return x
+
+@external
+def bar(f: uint256) -> Bytes[100]:
+ # embed a memory modifying expression in the calculation of address
+ self.buf = raw_call(
+ 0x0000000000000000000000000000000000000004,
+ msg.data,
+ max_outsize=100,
+ value=self._value()
+ )
+ return self.buf
+ """
+ c = get_contract(code, value=1)
+
+ assert (
+ c.bar(13).hex() == "0423a132"
+ "000000000000000000000000000000000000000000000000000000000000000d"
+ )
+
+
+def test_raw_call_clean_mem_kwargs_gas(get_contract):
+ # test msize uses clean memory and does not get overwritten by
+ # any raw_call() kwargs
+ code = """
+buf: Bytes[100]
+
+# add a dummy function to trigger memory expansion in the selector table routine
+@external
+def foo():
+ pass
+
+@internal
+def _gas() -> uint256:
+ x: uint256 = msg.gas
+ return x
+
+@external
+def bar(f: uint256) -> Bytes[100]:
+ # embed a memory modifying expression in the calculation of address
+ self.buf = raw_call(
+ 0x0000000000000000000000000000000000000004,
+ msg.data,
+ max_outsize=100,
+ gas=self._gas()
+ )
+ return self.buf
+ """
+ c = get_contract(code, value=1)
+
+ assert (
+ c.bar(15).hex() == "0423a132"
+ "000000000000000000000000000000000000000000000000000000000000000f"
+ )
+
+
uncompilable_code = [
(
"""
diff --git a/tests/parser/functions/test_return_struct.py b/tests/parser/functions/test_return_struct.py
index 425caedb75..cdd8342d8a 100644
--- a/tests/parser/functions/test_return_struct.py
+++ b/tests/parser/functions/test_return_struct.py
@@ -17,7 +17,7 @@ def test() -> Voter:
return a
"""
- out = compile_code(code, ["abi"])
+ out = compile_code(code, output_formats=["abi"])
abi = out["abi"][0]
assert abi["name"] == "test"
@@ -38,7 +38,7 @@ def test() -> Voter:
return a
"""
- out = compile_code(code, ["abi"])
+ out = compile_code(code, output_formats=["abi"])
abi = out["abi"][0]
assert abi["name"] == "test"
diff --git a/tests/parser/functions/test_return_tuple.py b/tests/parser/functions/test_return_tuple.py
index 87b7cdcde3..b375839147 100644
--- a/tests/parser/functions/test_return_tuple.py
+++ b/tests/parser/functions/test_return_tuple.py
@@ -99,7 +99,7 @@ def out_literals() -> (int128, address, Bytes[10]):
@external
def test() -> (int128, address, Bytes[10]):
a: int128 = 0
- b: address = ZERO_ADDRESS
+ b: address = empty(address)
c: Bytes[10] = b""
(a, b, c) = self._out_literals()
return a, b, c
@@ -138,7 +138,7 @@ def test2() -> (int128, address):
@external
def test3() -> (address, int128):
- x: address = ZERO_ADDRESS
+ x: address = empty(address)
self.a, self.c, x, self.d = self._out_literals()
return x, self.a
"""
diff --git a/tests/parser/functions/test_slice.py b/tests/parser/functions/test_slice.py
index 11d834bf42..53e092019f 100644
--- a/tests/parser/functions/test_slice.py
+++ b/tests/parser/functions/test_slice.py
@@ -1,6 +1,9 @@
+import hypothesis.strategies as st
import pytest
+from hypothesis import given, settings
-from vyper.exceptions import ArgumentException
+from vyper.compiler.settings import OptimizationLevel
+from vyper.exceptions import ArgumentException, TypeMismatch
_fun_bytes32_bounds = [(0, 32), (3, 29), (27, 5), (0, 5), (5, 3), (30, 2)]
@@ -9,14 +12,6 @@ def _generate_bytes(length):
return bytes(list(range(length)))
-# good numbers to try
-_fun_numbers = [0, 1, 5, 31, 32, 33, 64, 99, 100, 101]
-
-
-# [b"", b"\x01", b"\x02"...]
-_bytes_examples = [_generate_bytes(i) for i in _fun_numbers if i <= 100]
-
-
def test_basic_slice(get_contract_with_gas_estimation):
code = """
@external
@@ -31,80 +26,102 @@ def slice_tower_test(inp1: Bytes[50]) -> Bytes[50]:
assert x == b"klmnopqrst", x
-@pytest.mark.parametrize("bytesdata", _bytes_examples)
-@pytest.mark.parametrize("start", _fun_numbers)
-@pytest.mark.parametrize("literal_start", (True, False))
-@pytest.mark.parametrize("length", _fun_numbers)
-@pytest.mark.parametrize("literal_length", (True, False))
+# note: optimization boundaries at 32, 64 and 320 depending on mode
+_draw_1024 = st.integers(min_value=0, max_value=1024)
+_draw_1024_1 = st.integers(min_value=1, max_value=1024)
+_bytes_1024 = st.binary(min_size=0, max_size=1024)
+
+
+@pytest.mark.parametrize("use_literal_start", (True, False))
+@pytest.mark.parametrize("use_literal_length", (True, False))
+@pytest.mark.parametrize("opt_level", list(OptimizationLevel))
+@given(start=_draw_1024, length=_draw_1024, length_bound=_draw_1024_1, bytesdata=_bytes_1024)
+@settings(max_examples=100)
@pytest.mark.fuzzing
def test_slice_immutable(
get_contract,
assert_compile_failed,
assert_tx_failed,
+ opt_level,
bytesdata,
start,
- literal_start,
+ use_literal_start,
length,
- literal_length,
+ use_literal_length,
+ length_bound,
):
- _start = start if literal_start else "start"
- _length = length if literal_length else "length"
+ _start = start if use_literal_start else "start"
+ _length = length if use_literal_length else "length"
code = f"""
-IMMUTABLE_BYTES: immutable(Bytes[100])
-IMMUTABLE_SLICE: immutable(Bytes[100])
+IMMUTABLE_BYTES: immutable(Bytes[{length_bound}])
+IMMUTABLE_SLICE: immutable(Bytes[{length_bound}])
@external
-def __init__(inp: Bytes[100], start: uint256, length: uint256):
+def __init__(inp: Bytes[{length_bound}], start: uint256, length: uint256):
IMMUTABLE_BYTES = inp
IMMUTABLE_SLICE = slice(IMMUTABLE_BYTES, {_start}, {_length})
@external
-def do_splice() -> Bytes[100]:
+def do_splice() -> Bytes[{length_bound}]:
return IMMUTABLE_SLICE
"""
+ def _get_contract():
+ return get_contract(code, bytesdata, start, length, override_opt_level=opt_level)
+
if (
- (start + length > 100 and literal_start and literal_length)
- or (literal_length and length > 100)
- or (literal_start and start > 100)
- or (literal_length and length < 1)
+ (start + length > length_bound and use_literal_start and use_literal_length)
+ or (use_literal_length and length > length_bound)
+ or (use_literal_start and start > length_bound)
+ or (use_literal_length and length == 0)
):
- assert_compile_failed(
- lambda: get_contract(code, bytesdata, start, length), ArgumentException
- )
- elif start + length > len(bytesdata):
- assert_tx_failed(lambda: get_contract(code, bytesdata, start, length))
+ assert_compile_failed(lambda: _get_contract(), ArgumentException)
+ elif start + length > len(bytesdata) or (len(bytesdata) > length_bound):
+ # deploy fail
+ assert_tx_failed(lambda: _get_contract())
else:
- c = get_contract(code, bytesdata, start, length)
+ c = _get_contract()
assert c.do_splice() == bytesdata[start : start + length]
@pytest.mark.parametrize("location", ("storage", "calldata", "memory", "literal", "code"))
-@pytest.mark.parametrize("bytesdata", _bytes_examples)
-@pytest.mark.parametrize("start", _fun_numbers)
-@pytest.mark.parametrize("literal_start", (True, False))
-@pytest.mark.parametrize("length", _fun_numbers)
-@pytest.mark.parametrize("literal_length", (True, False))
+@pytest.mark.parametrize("use_literal_start", (True, False))
+@pytest.mark.parametrize("use_literal_length", (True, False))
+@pytest.mark.parametrize("opt_level", list(OptimizationLevel))
+@given(start=_draw_1024, length=_draw_1024, length_bound=_draw_1024_1, bytesdata=_bytes_1024)
+@settings(max_examples=100)
@pytest.mark.fuzzing
-def test_slice_bytes(
+def test_slice_bytes_fuzz(
get_contract,
assert_compile_failed,
assert_tx_failed,
+ opt_level,
location,
bytesdata,
start,
- literal_start,
+ use_literal_start,
length,
- literal_length,
+ use_literal_length,
+ length_bound,
):
+ preamble = ""
if location == "memory":
- spliced_code = "foo: Bytes[100] = inp"
+ spliced_code = f"foo: Bytes[{length_bound}] = inp"
foo = "foo"
elif location == "storage":
+ preamble = f"""
+foo: Bytes[{length_bound}]
+ """
spliced_code = "self.foo = inp"
foo = "self.foo"
elif location == "code":
+ preamble = f"""
+IMMUTABLE_BYTES: immutable(Bytes[{length_bound}])
+@external
+def __init__(foo: Bytes[{length_bound}]):
+ IMMUTABLE_BYTES = foo
+ """
spliced_code = ""
foo = "IMMUTABLE_BYTES"
elif location == "literal":
@@ -116,36 +133,55 @@ def test_slice_bytes(
else:
raise Exception("unreachable")
- _start = start if literal_start else "start"
- _length = length if literal_length else "length"
+ _start = start if use_literal_start else "start"
+ _length = length if use_literal_length else "length"
code = f"""
-foo: Bytes[100]
-IMMUTABLE_BYTES: immutable(Bytes[100])
-@external
-def __init__(foo: Bytes[100]):
- IMMUTABLE_BYTES = foo
+{preamble}
@external
-def do_slice(inp: Bytes[100], start: uint256, length: uint256) -> Bytes[100]:
+def do_slice(inp: Bytes[{length_bound}], start: uint256, length: uint256) -> Bytes[{length_bound}]:
{spliced_code}
return slice({foo}, {_start}, {_length})
"""
- length_bound = len(bytesdata) if location == "literal" else 100
- if (
- (start + length > length_bound and literal_start and literal_length)
- or (literal_length and length > length_bound)
- or (literal_start and start > length_bound)
- or (literal_length and length < 1)
- ):
- assert_compile_failed(lambda: get_contract(code, bytesdata), ArgumentException)
- elif start + length > len(bytesdata):
- c = get_contract(code, bytesdata)
+ def _get_contract():
+ return get_contract(code, bytesdata, override_opt_level=opt_level)
+
+ # length bound is the container size; input_bound is the bound on the input
+ # (which can be different, if the input is a literal)
+ input_bound = length_bound
+ slice_output_too_large = False
+
+ if location == "literal":
+ input_bound = len(bytesdata)
+
+ # ex.:
+ # @external
+ # def do_slice(inp: Bytes[1], start: uint256, length: uint256) -> Bytes[1]:
+ # return slice(b'\x00\x00', 0, length)
+ output_length = length if use_literal_length else input_bound
+ slice_output_too_large = output_length > length_bound
+
+ end = start + length
+
+ compile_time_oob = (
+ (use_literal_length and (length > input_bound or length == 0))
+ or (use_literal_start and start > input_bound)
+ or (use_literal_start and use_literal_length and start + length > input_bound)
+ )
+
+ if compile_time_oob or slice_output_too_large:
+ assert_compile_failed(lambda: _get_contract(), (ArgumentException, TypeMismatch))
+ elif location == "code" and len(bytesdata) > length_bound:
+ # deploy fail
+ assert_tx_failed(lambda: _get_contract())
+ elif end > len(bytesdata) or len(bytesdata) > length_bound:
+ c = _get_contract()
assert_tx_failed(lambda: c.do_slice(bytesdata, start, length))
else:
- c = get_contract(code, bytesdata)
- assert c.do_slice(bytesdata, start, length) == bytesdata[start : start + length], code
+ c = _get_contract()
+ assert c.do_slice(bytesdata, start, length) == bytesdata[start:end], code
def test_slice_private(get_contract):
diff --git a/tests/parser/globals/test_getters.py b/tests/parser/globals/test_getters.py
index 59c91cbeef..5eac074ef6 100644
--- a/tests/parser/globals/test_getters.py
+++ b/tests/parser/globals/test_getters.py
@@ -35,6 +35,7 @@ def test_getter_code(get_contract_with_gas_estimation_for_constants):
c: public(constant(uint256)) = 1
d: public(immutable(uint256))
e: public(immutable(uint256[2]))
+f: public(constant(uint256[2])) = [3, 7]
@external
def __init__():
@@ -68,6 +69,7 @@ def __init__():
assert c.c() == 1
assert c.d() == 1729
assert c.e(0) == 2
+ assert [c.f(i) for i in range(2)] == [3, 7]
def test_getter_mutability(get_contract):
diff --git a/tests/parser/integration/test_escrow.py b/tests/parser/integration/test_escrow.py
index 2982ff9eae..1578f5a418 100644
--- a/tests/parser/integration/test_escrow.py
+++ b/tests/parser/integration/test_escrow.py
@@ -9,7 +9,7 @@ def test_arbitration_code(w3, get_contract_with_gas_estimation, assert_tx_failed
@external
def setup(_seller: address, _arbitrator: address):
- if self.buyer == ZERO_ADDRESS:
+ if self.buyer == empty(address):
self.buyer = msg.sender
self.seller = _seller
self.arbitrator = _arbitrator
@@ -43,7 +43,7 @@ def test_arbitration_code_with_init(w3, assert_tx_failed, get_contract_with_gas_
@external
@payable
def __init__(_seller: address, _arbitrator: address):
- if self.buyer == ZERO_ADDRESS:
+ if self.buyer == empty(address):
self.buyer = msg.sender
self.seller = _seller
self.arbitrator = _arbitrator
diff --git a/tests/parser/parser_utils/test_annotate_and_optimize_ast.py b/tests/parser/parser_utils/test_annotate_and_optimize_ast.py
index 6f2246c6c0..68a07178bb 100644
--- a/tests/parser/parser_utils/test_annotate_and_optimize_ast.py
+++ b/tests/parser/parser_utils/test_annotate_and_optimize_ast.py
@@ -29,7 +29,7 @@ def foo() -> int128:
def get_contract_info(source_code):
- class_types, reformatted_code = pre_parse(source_code)
+ _, class_types, reformatted_code = pre_parse(source_code)
py_ast = python_ast.parse(reformatted_code)
annotate_python_ast(py_ast, reformatted_code, class_types)
diff --git a/tests/parser/syntax/test_abi_decode.py b/tests/parser/syntax/test_abi_decode.py
new file mode 100644
index 0000000000..f05ff429cd
--- /dev/null
+++ b/tests/parser/syntax/test_abi_decode.py
@@ -0,0 +1,45 @@
+import pytest
+
+from vyper import compiler
+from vyper.exceptions import TypeMismatch
+
+fail_list = [
+ (
+ """
+@external
+def foo(j: uint256) -> bool:
+ s: bool = _abi_decode(j, bool, unwrap_tuple= False)
+ return s
+ """,
+ TypeMismatch,
+ ),
+ (
+ """
+@external
+def bar(j: String[32]) -> bool:
+ s: bool = _abi_decode(j, bool, unwrap_tuple= False)
+ return s
+ """,
+ TypeMismatch,
+ ),
+]
+
+
+@pytest.mark.parametrize("bad_code,exc", fail_list)
+def test_abi_encode_fail(bad_code, exc):
+ with pytest.raises(exc):
+ compiler.compile_code(bad_code)
+
+
+valid_list = [
+ """
+@external
+def foo(x: Bytes[32]) -> uint256:
+ return _abi_decode(x, uint256)
+ """
+]
+
+
+@pytest.mark.parametrize("good_code", valid_list)
+def test_abi_encode_success(good_code):
+ assert compiler.compile_code(good_code) is not None
diff --git a/tests/parser/syntax/test_addmulmod.py b/tests/parser/syntax/test_addmulmod.py
new file mode 100644
index 0000000000..ddff4d3e01
--- /dev/null
+++ b/tests/parser/syntax/test_addmulmod.py
@@ -0,0 +1,27 @@
+import pytest
+
+from vyper.exceptions import InvalidType
+
+fail_list = [
+ ( # bad AST nodes given as arguments
+ """
+@external
+def foo() -> uint256:
+ return uint256_addmod(1.1, 1.2, 3.0)
+ """,
+ InvalidType,
+ ),
+ ( # bad AST nodes given as arguments
+ """
+@external
+def foo() -> uint256:
+ return uint256_mulmod(1.1, 1.2, 3.0)
+ """,
+ InvalidType,
+ ),
+]
+
+
+@pytest.mark.parametrize("code,exc", fail_list)
+def test_add_mod_fail(assert_compile_failed, get_contract, code, exc):
+ assert_compile_failed(lambda: get_contract(code), exc)
diff --git a/tests/parser/syntax/test_address_code.py b/tests/parser/syntax/test_address_code.py
index 25fe1be0b4..70ba5cbbf7 100644
--- a/tests/parser/syntax/test_address_code.py
+++ b/tests/parser/syntax/test_address_code.py
@@ -5,6 +5,7 @@
from web3 import Web3
from vyper import compiler
+from vyper.compiler.settings import Settings
from vyper.exceptions import NamespaceCollision, StructureException, VyperException
# For reproducibility, use precompiled data of `hello: public(uint256)` using vyper 0.3.1
@@ -161,7 +162,7 @@ def test_address_code_compile_success(code: str):
compiler.compile_code(code)
-def test_address_code_self_success(get_contract, no_optimize: bool):
+def test_address_code_self_success(get_contract, optimize):
code = """
code_deployment: public(Bytes[32])
@@ -174,8 +175,9 @@ def code_runtime() -> Bytes[32]:
return slice(self.code, 0, 32)
"""
contract = get_contract(code)
+ settings = Settings(optimize=optimize)
code_compiled = compiler.compile_code(
- code, output_formats=["bytecode", "bytecode_runtime"], no_optimize=no_optimize
+ code, output_formats=["bytecode", "bytecode_runtime"], settings=settings
)
assert contract.code_deployment() == bytes.fromhex(code_compiled["bytecode"][2:])[:32]
assert contract.code_runtime() == bytes.fromhex(code_compiled["bytecode_runtime"][2:])[:32]
diff --git a/tests/parser/syntax/test_bool.py b/tests/parser/syntax/test_bool.py
index 09f799d91c..48ed37321a 100644
--- a/tests/parser/syntax/test_bool.py
+++ b/tests/parser/syntax/test_bool.py
@@ -52,7 +52,7 @@ def foo() -> bool:
"""
@external
def foo() -> bool:
- a: address = ZERO_ADDRESS
+ a: address = empty(address)
return a == 1
""",
(
@@ -137,7 +137,7 @@ def foo() -> bool:
"""
@external
def foo2(a: address) -> bool:
- return a != ZERO_ADDRESS
+ return a != empty(address)
""",
]
diff --git a/tests/parser/syntax/test_byte_string.py b/tests/parser/syntax/test_byte_string.py
deleted file mode 100644
index 90bbe197b0..0000000000
--- a/tests/parser/syntax/test_byte_string.py
+++ /dev/null
@@ -1,26 +0,0 @@
-import pytest
-
-from vyper import compiler
-
-valid_list = [
- """
-@external
-def foo() -> String[10]:
- return "badminton"
- """,
- """
-@external
-def foo():
- x: String[11] = "¡très bien!"
- """,
- """
-@external
-def test() -> String[100]:
- return "hello world!"
- """,
-]
-
-
-@pytest.mark.parametrize("good_code", valid_list)
-def test_byte_string_success(good_code):
- assert compiler.compile_code(good_code) is not None
diff --git a/tests/parser/syntax/test_bytes.py b/tests/parser/syntax/test_bytes.py
index d732ec3ea5..a7fb7e77ce 100644
--- a/tests/parser/syntax/test_bytes.py
+++ b/tests/parser/syntax/test_bytes.py
@@ -1,7 +1,13 @@
import pytest
from vyper import compiler
-from vyper.exceptions import InvalidOperation, InvalidType, SyntaxException, TypeMismatch
+from vyper.exceptions import (
+ InvalidOperation,
+ InvalidType,
+ StructureException,
+ SyntaxException,
+ TypeMismatch,
+)
fail_list = [
(
@@ -77,6 +83,14 @@ def test() -> Bytes[1]:
""",
SyntaxException,
),
+ (
+ """
+@external
+def foo():
+ a: Bytes = b"abc"
+ """,
+ StructureException,
+ ),
]
diff --git a/tests/parser/syntax/test_chainid.py b/tests/parser/syntax/test_chainid.py
index eb2ed36325..2b6e08cbc4 100644
--- a/tests/parser/syntax/test_chainid.py
+++ b/tests/parser/syntax/test_chainid.py
@@ -1,8 +1,9 @@
import pytest
from vyper import compiler
+from vyper.compiler.settings import Settings
from vyper.evm.opcodes import EVM_VERSIONS
-from vyper.exceptions import EvmVersionException, InvalidType, TypeMismatch
+from vyper.exceptions import InvalidType, TypeMismatch
@pytest.mark.parametrize("evm_version", list(EVM_VERSIONS))
@@ -12,12 +13,9 @@ def test_evm_version(evm_version):
def foo():
a: uint256 = chain.id
"""
+ settings = Settings(evm_version=evm_version)
- if EVM_VERSIONS[evm_version] < 2:
- with pytest.raises(EvmVersionException):
- compiler.compile_code(code, evm_version=evm_version)
- else:
- compiler.compile_code(code, evm_version=evm_version)
+ assert compiler.compile_code(code, settings=settings) is not None
fail_list = [
@@ -71,10 +69,10 @@ def foo(inp: Bytes[10]) -> Bytes[3]:
def test_chain_fail(bad_code):
if isinstance(bad_code, tuple):
with pytest.raises(bad_code[1]):
- compiler.compile_code(bad_code[0], evm_version="istanbul")
+ compiler.compile_code(bad_code[0])
else:
with pytest.raises(TypeMismatch):
- compiler.compile_code(bad_code, evm_version="istanbul")
+ compiler.compile_code(bad_code)
valid_list = [
@@ -95,7 +93,7 @@ def check_chain_id(c: uint256) -> bool:
@pytest.mark.parametrize("good_code", valid_list)
def test_chain_success(good_code):
- assert compiler.compile_code(good_code, evm_version="istanbul") is not None
+ assert compiler.compile_code(good_code) is not None
def test_chainid_operation(get_contract_with_gas_estimation):
@@ -105,5 +103,5 @@ def test_chainid_operation(get_contract_with_gas_estimation):
def get_chain_id() -> uint256:
return chain.id
"""
- c = get_contract_with_gas_estimation(code, evm_version="istanbul")
+ c = get_contract_with_gas_estimation(code)
assert c.get_chain_id() == 131277322940537 # Default value of py-evm
diff --git a/tests/parser/syntax/test_codehash.py b/tests/parser/syntax/test_codehash.py
index 8c1e430d32..c2d9a2e274 100644
--- a/tests/parser/syntax/test_codehash.py
+++ b/tests/parser/syntax/test_codehash.py
@@ -1,13 +1,13 @@
import pytest
from vyper.compiler import compile_code
+from vyper.compiler.settings import Settings
from vyper.evm.opcodes import EVM_VERSIONS
-from vyper.exceptions import EvmVersionException
from vyper.utils import keccak256
@pytest.mark.parametrize("evm_version", list(EVM_VERSIONS))
-def test_get_extcodehash(get_contract, evm_version, no_optimize):
+def test_get_extcodehash(get_contract, evm_version, optimize):
code = """
a: address
@@ -32,15 +32,8 @@ def foo3() -> bytes32:
def foo4() -> bytes32:
return self.a.codehash
"""
-
- if evm_version in ("byzantium", "atlantis"):
- with pytest.raises(EvmVersionException):
- compile_code(code, evm_version=evm_version)
- return
-
- compiled = compile_code(
- code, ["bytecode_runtime"], evm_version=evm_version, no_optimize=no_optimize
- )
+ settings = Settings(evm_version=evm_version, optimize=optimize)
+ compiled = compile_code(code, output_formats=["bytecode_runtime"], settings=settings)
bytecode = bytes.fromhex(compiled["bytecode_runtime"][2:])
hash_ = keccak256(bytecode)
diff --git a/tests/parser/syntax/test_constants.py b/tests/parser/syntax/test_constants.py
index 15546672c6..ffd2f1faa0 100644
--- a/tests/parser/syntax/test_constants.py
+++ b/tests/parser/syntax/test_constants.py
@@ -69,6 +69,46 @@
"""
VAL: constant(Bytes[4]) = b"t"
VAL: uint256
+ """,
+ NamespaceCollision,
+ ),
+ # global with same type and name
+ (
+ """
+VAL: constant(uint256) = 1
+VAL: uint256
+ """,
+ NamespaceCollision,
+ ),
+ # global with same type and name, different order
+ (
+ """
+VAL: uint256
+VAL: constant(uint256) = 1
+ """,
+ NamespaceCollision,
+ ),
+ # global with same type and name
+ (
+ """
+VAL: immutable(uint256)
+VAL: uint256
+
+@external
+def __init__():
+ VAL = 1
+ """,
+ NamespaceCollision,
+ ),
+ # global with same type and name, different order
+ (
+ """
+VAL: uint256
+VAL: immutable(uint256)
+
+@external
+def __init__():
+ VAL = 1
""",
NamespaceCollision,
),
diff --git a/tests/parser/syntax/test_dynamic_array.py b/tests/parser/syntax/test_dynamic_array.py
index e7dc2d1183..0c23bf67da 100644
--- a/tests/parser/syntax/test_dynamic_array.py
+++ b/tests/parser/syntax/test_dynamic_array.py
@@ -16,6 +16,14 @@
""",
StructureException,
),
+ (
+ """
+@external
+def foo():
+ a: DynArray = [1, 2, 3]
+ """,
+ StructureException,
+ ),
]
diff --git a/tests/parser/syntax/test_enum.py b/tests/parser/syntax/test_enum.py
index c13f27f497..9bb74fb675 100644
--- a/tests/parser/syntax/test_enum.py
+++ b/tests/parser/syntax/test_enum.py
@@ -110,6 +110,20 @@ def foo() -> Roles:
""",
UnknownAttribute,
),
+ (
+ """
+enum A:
+ a
+enum B:
+ a
+ b
+
+@internal
+def foo():
+ a: A = B.b
+ """,
+ TypeMismatch,
+ ),
]
diff --git a/tests/parser/syntax/test_for_range.py b/tests/parser/syntax/test_for_range.py
index b2a9491058..e6f35c1d2d 100644
--- a/tests/parser/syntax/test_for_range.py
+++ b/tests/parser/syntax/test_for_range.py
@@ -12,7 +12,26 @@ def foo():
pass
""",
StructureException,
- )
+ ),
+ (
+ """
+@external
+def bar():
+ for i in range(1,2,bound=2):
+ pass
+ """,
+ StructureException,
+ ),
+ (
+ """
+@external
+def bar():
+ x:uint256 = 1
+ for i in range(x,x+1,bound=2):
+ pass
+ """,
+ StructureException,
+ ),
]
diff --git a/tests/parser/syntax/test_interfaces.py b/tests/parser/syntax/test_interfaces.py
index c0afec5504..9100389dbd 100644
--- a/tests/parser/syntax/test_interfaces.py
+++ b/tests/parser/syntax/test_interfaces.py
@@ -3,6 +3,7 @@
from vyper import compiler
from vyper.exceptions import (
ArgumentException,
+ InterfaceViolation,
InvalidReference,
InvalidType,
StructureException,
@@ -46,7 +47,7 @@ def test():
@external
def test():
- a: address(ERC20) = ZERO_ADDRESS
+ a: address(ERC20) = empty(address)
""",
InvalidType,
),
@@ -106,6 +107,109 @@ def foo(): nonpayable
""",
StructureException,
),
+ (
+ """
+from vyper.interfaces import ERC20
+
+interface A:
+ def f(): view
+
+@internal
+def foo():
+ a: ERC20 = A(empty(address))
+ """,
+ TypeMismatch,
+ ),
+ (
+ """
+interface A:
+ def f(a: uint256): view
+
+implements: A
+
+@external
+@nonpayable
+def f(a: uint256): # visibility is nonpayable instead of view
+ pass
+ """,
+ InterfaceViolation,
+ ),
+ (
+ # `receiver` of `Transfer` event should be indexed
+ """
+from vyper.interfaces import ERC20
+
+implements: ERC20
+
+event Transfer:
+ sender: indexed(address)
+ receiver: address
+ value: uint256
+
+event Approval:
+ owner: indexed(address)
+ spender: indexed(address)
+ value: uint256
+
+name: public(String[32])
+symbol: public(String[32])
+decimals: public(uint8)
+balanceOf: public(HashMap[address, uint256])
+allowance: public(HashMap[address, HashMap[address, uint256]])
+totalSupply: public(uint256)
+
+@external
+def transfer(_to : address, _value : uint256) -> bool:
+ return True
+
+@external
+def transferFrom(_from : address, _to : address, _value : uint256) -> bool:
+ return True
+
+@external
+def approve(_spender : address, _value : uint256) -> bool:
+ return True
+ """,
+ InterfaceViolation,
+ ),
+ (
+ # `value` of `Transfer` event should not be indexed
+ """
+from vyper.interfaces import ERC20
+
+implements: ERC20
+
+event Transfer:
+ sender: indexed(address)
+ receiver: indexed(address)
+ value: indexed(uint256)
+
+event Approval:
+ owner: indexed(address)
+ spender: indexed(address)
+ value: uint256
+
+name: public(String[32])
+symbol: public(String[32])
+decimals: public(uint8)
+balanceOf: public(HashMap[address, uint256])
+allowance: public(HashMap[address, HashMap[address, uint256]])
+totalSupply: public(uint256)
+
+@external
+def transfer(_to : address, _value : uint256) -> bool:
+ return True
+
+@external
+def transferFrom(_from : address, _to : address, _value : uint256) -> bool:
+ return True
+
+@external
+def approve(_spender : address, _value : uint256) -> bool:
+ return True
+ """,
+ InterfaceViolation,
+ ),
]
@@ -202,7 +306,7 @@ def some_func(): nonpayable
@external
def __init__():
- self.my_interface[self.idx] = MyInterface(ZERO_ADDRESS)
+ self.my_interface[self.idx] = MyInterface(empty(address))
""",
"""
interface MyInterface:
@@ -248,6 +352,20 @@ def foo() -> uint256: view
def __init__(x: uint256):
foo = x
""",
+ # no namespace collision of interface after storage variable
+ """
+a: constant(uint256) = 1
+
+interface A:
+ def f(a: uint128): view
+ """,
+ # no namespace collision of storage variable after interface
+ """
+interface A:
+ def f(a: uint256): view
+
+a: constant(uint128) = 1
+ """,
]
@@ -256,7 +374,7 @@ def test_interfaces_success(good_code):
assert compiler.compile_code(good_code) is not None
-def test_imports_and_implements_within_interface():
+def test_imports_and_implements_within_interface(make_input_bundle):
interface_code = """
from vyper.interfaces import ERC20
import foo.bar as Baz
@@ -268,6 +386,8 @@ def foobar():
pass
"""
+ input_bundle = make_input_bundle({"foo.vy": interface_code})
+
code = """
import foo as Foo
@@ -278,9 +398,4 @@ def foobar():
pass
"""
- assert (
- compiler.compile_code(
- code, interface_codes={"Foo": {"type": "vyper", "code": interface_code}}
- )
- is not None
- )
+ assert compiler.compile_code(code, input_bundle=input_bundle) is not None
diff --git a/tests/parser/syntax/test_invalids.py b/tests/parser/syntax/test_invalids.py
index 3c51075e60..33478fcff1 100644
--- a/tests/parser/syntax/test_invalids.py
+++ b/tests/parser/syntax/test_invalids.py
@@ -363,6 +363,13 @@ def a():
UnknownAttribute,
)
+must_fail(
+ """
+a: HashMap
+""",
+ StructureException,
+)
+
@pytest.mark.parametrize("bad_code,exception_type", fail_list)
def test_compilation_fails_with_exception(bad_code, exception_type):
diff --git a/tests/parser/syntax/test_list.py b/tests/parser/syntax/test_list.py
index 6d941fa2df..db41de5526 100644
--- a/tests/parser/syntax/test_list.py
+++ b/tests/parser/syntax/test_list.py
@@ -302,6 +302,13 @@ def foo():
def foo():
self.b[0] = 7.0
""",
+ """
+@external
+def foo():
+ x: DynArray[uint256, 3] = [1, 2, 3]
+ for i in [[], []]:
+ x = i
+ """,
]
diff --git a/tests/parser/syntax/test_logging.py b/tests/parser/syntax/test_logging.py
index 39573642c0..2dd21e7a92 100644
--- a/tests/parser/syntax/test_logging.py
+++ b/tests/parser/syntax/test_logging.py
@@ -1,7 +1,7 @@
import pytest
from vyper import compiler
-from vyper.exceptions import InvalidType, TypeMismatch
+from vyper.exceptions import InvalidType, StructureException, TypeMismatch
fail_list = [
"""
@@ -45,3 +45,19 @@ def test_logging_fail(bad_code):
else:
with pytest.raises(TypeMismatch):
compiler.compile_code(bad_code)
+
+
+@pytest.mark.parametrize("mutability", ["@pure", "@view"])
+@pytest.mark.parametrize("visibility", ["@internal", "@external"])
+def test_logging_from_non_mutable(mutability, visibility):
+ code = f"""
+event Test:
+ n: uint256
+
+{visibility}
+{mutability}
+def test():
+ log Test(1)
+ """
+ with pytest.raises(StructureException):
+ compiler.compile_code(code)
diff --git a/tests/parser/syntax/test_no_none.py b/tests/parser/syntax/test_no_none.py
index 7030a56b18..24c32a46a4 100644
--- a/tests/parser/syntax/test_no_none.py
+++ b/tests/parser/syntax/test_no_none.py
@@ -30,13 +30,13 @@ def foo():
"""
@external
def foo():
- bar: bytes32 = EMPTY_BYTES32
+ bar: bytes32 = empty(bytes32)
bar = None
""",
"""
@external
def foo():
- bar: address = ZERO_ADDRESS
+ bar: address = empty(address)
bar = None
""",
"""
@@ -104,13 +104,13 @@ def foo():
"""
@external
def foo():
- bar: bytes32 = EMPTY_BYTES32
+ bar: bytes32 = empty(bytes32)
assert bar is None
""",
"""
@external
def foo():
- bar: address = ZERO_ADDRESS
+ bar: address = empty(address)
assert bar is None
""",
]
@@ -148,13 +148,13 @@ def foo():
"""
@external
def foo():
- bar: bytes32 = EMPTY_BYTES32
+ bar: bytes32 = empty(bytes32)
assert bar == None
""",
"""
@external
def foo():
- bar: address = ZERO_ADDRESS
+ bar: address = empty(address)
assert bar == None
""",
]
diff --git a/tests/parser/syntax/test_public.py b/tests/parser/syntax/test_public.py
index fd0058cab8..68575ebd41 100644
--- a/tests/parser/syntax/test_public.py
+++ b/tests/parser/syntax/test_public.py
@@ -23,6 +23,27 @@ def __init__():
def foo() -> int128:
return self.x / self.y / self.z
""",
+ # expansion of public user-defined struct
+ """
+struct Foo:
+ a: uint256
+
+x: public(HashMap[uint256, Foo])
+ """,
+ # expansion of public user-defined enum
+ """
+enum Foo:
+ BAR
+
+x: public(HashMap[uint256, Foo])
+ """,
+ # expansion of public user-defined interface
+ """
+interface Foo:
+ def bar(): nonpayable
+
+x: public(HashMap[uint256, Foo])
+ """,
]
diff --git a/tests/parser/syntax/test_self_balance.py b/tests/parser/syntax/test_self_balance.py
index 949cdde324..d22d8a2750 100644
--- a/tests/parser/syntax/test_self_balance.py
+++ b/tests/parser/syntax/test_self_balance.py
@@ -1,6 +1,7 @@
import pytest
from vyper import compiler
+from vyper.compiler.settings import Settings
from vyper.evm.opcodes import EVM_VERSIONS
@@ -18,7 +19,8 @@ def get_balance() -> uint256:
def __default__():
pass
"""
- opcodes = compiler.compile_code(code, ["opcodes"], evm_version=evm_version)["opcodes"]
+ settings = Settings(evm_version=evm_version)
+ opcodes = compiler.compile_code(code, output_formats=["opcodes"], settings=settings)["opcodes"]
if EVM_VERSIONS[evm_version] >= EVM_VERSIONS["istanbul"]:
assert "SELFBALANCE" in opcodes
else:
diff --git a/tests/parser/syntax/test_string.py b/tests/parser/syntax/test_string.py
index 5b5b9e5bb5..6252011bd9 100644
--- a/tests/parser/syntax/test_string.py
+++ b/tests/parser/syntax/test_string.py
@@ -1,6 +1,7 @@
import pytest
from vyper import compiler
+from vyper.exceptions import StructureException
valid_list = [
"""
@@ -27,9 +28,31 @@ def foo() -> bool:
y: String[12] = "test"
return x != y
""",
+ """
+@external
+def test() -> String[100]:
+ return "hello world!"
+ """,
]
@pytest.mark.parametrize("good_code", valid_list)
def test_string_success(good_code):
assert compiler.compile_code(good_code) is not None
+
+
+invalid_list = [
+ (
+ """
+@external
+def foo():
+ a: String = "abc"
+ """,
+ StructureException,
+ )
+]
+
+
+@pytest.mark.parametrize("bad_code,exc", invalid_list)
+def test_string_fail(assert_compile_failed, get_contract_with_gas_estimation, bad_code, exc):
+ assert_compile_failed(lambda: get_contract_with_gas_estimation(bad_code), exc)
diff --git a/tests/parser/syntax/test_structs.py b/tests/parser/syntax/test_structs.py
index 757c46c4b3..b30f7e6098 100644
--- a/tests/parser/syntax/test_structs.py
+++ b/tests/parser/syntax/test_structs.py
@@ -2,6 +2,7 @@
from vyper import compiler
from vyper.exceptions import (
+ InstantiationException,
InvalidType,
StructureException,
TypeMismatch,
@@ -297,7 +298,7 @@ def foo():
a: HashMap[int128, C]
b: int128
""",
- StructureException,
+ InstantiationException,
),
"""
struct C1:
@@ -429,6 +430,16 @@ def foo():
""",
StructureException,
),
+ (
+ """
+event Foo:
+ a: uint256
+
+struct Bar:
+ a: Foo
+ """,
+ InstantiationException,
+ ),
]
diff --git a/tests/parser/syntax/test_ternary.py b/tests/parser/syntax/test_ternary.py
new file mode 100644
index 0000000000..325be3e43b
--- /dev/null
+++ b/tests/parser/syntax/test_ternary.py
@@ -0,0 +1,143 @@
+import pytest
+
+from vyper.compiler import compile_code
+from vyper.exceptions import InvalidType, TypeMismatch
+
+good_list = [
+ # basic test
+ """
+@external
+def foo(a: uint256, b: uint256) -> uint256:
+ return a if a > b else b
+ """,
+ """
+@external
+def foo():
+ a: bool = (True if True else True) or True
+ """,
+ # different locations:
+ """
+b: uint256
+
+@external
+def foo(x: uint256) -> uint256:
+ return x if x > self.b else self.b
+ """,
+ # different kinds of test exprs
+ """
+@external
+def foo(x: uint256, t: bool) -> uint256:
+ return x if t else 1
+ """,
+ """
+@external
+def foo(x: uint256) -> uint256:
+ return x if True else 1
+ """,
+ """
+@external
+def foo(x: uint256) -> uint256:
+ return x if False else 1
+ """,
+ # more complex types
+ """
+@external
+def foo(t: bool) -> DynArray[uint256, 1]:
+ return [2] if t else [1]
+ """,
+ # TODO: get this working, depends #3377
+ # """
+ # @external
+ # def foo(t: bool) -> DynArray[uint256, 1]:
+ # return [] if t else [1]
+ # """,
+ """
+@external
+def foo(t: bool) -> (uint256, uint256):
+ a: uint256 = 0
+ b: uint256 = 1
+ return (a, b) if t else (b, a)
+ """,
+]
+
+
+@pytest.mark.parametrize("code", good_list)
+def test_ternary_good(code):
+ assert compile_code(code) is not None
+
+
+fail_list = [
+ ( # bad test type
+ """
+@external
+def foo() -> uint256:
+ return 1 if 1 else 2
+ """,
+ InvalidType,
+ ),
+ ( # bad test type: constant
+ """
+TEST: constant(uint256) = 1
+@external
+def foo() -> uint256:
+ return 1 if TEST else 2
+ """,
+ InvalidType,
+ ),
+ ( # bad test type: variable
+ """
+TEST: constant(uint256) = 1
+@external
+def foo(t: uint256) -> uint256:
+ return 1 if t else 2
+ """,
+ TypeMismatch,
+ ),
+ ( # mismatched body and orelse: literal
+ """
+@external
+def foo() -> uint256:
+ return 1 if True else 2.0
+ """,
+ TypeMismatch,
+ ),
+ ( # mismatched body and orelse: literal and known type
+ """
+T: constant(uint256) = 1
+@external
+def foo() -> uint256:
+ return T if True else 2.0
+ """,
+ TypeMismatch,
+ ),
+ ( # mismatched body and orelse: both variable
+ """
+@external
+def foo(x: uint256, y: uint8) -> uint256:
+ return x if True else y
+ """,
+ TypeMismatch,
+ ),
+ ( # mismatched tuple types
+ """
+@external
+def foo(a: uint256, b: uint256, c: uint256) -> (uint256, uint256):
+ return (a, b) if True else (a, b, c)
+ """,
+ TypeMismatch,
+ ),
+ ( # mismatched tuple types - other direction
+ """
+@external
+def foo(a: uint256, b: uint256, c: uint256) -> (uint256, uint256):
+ return (a, b, c) if True else (a, b)
+ """,
+ TypeMismatch,
+ ),
+]
+
+
+@pytest.mark.parametrize("code,exc", fail_list)
+def test_functions_call_fail(code, exc):
+ with pytest.raises(exc):
+ compile_code(code)
diff --git a/tests/parser/syntax/test_tuple_assign.py b/tests/parser/syntax/test_tuple_assign.py
index 115499ce8b..49b63ee614 100644
--- a/tests/parser/syntax/test_tuple_assign.py
+++ b/tests/parser/syntax/test_tuple_assign.py
@@ -41,7 +41,7 @@ def out_literals() -> (int128, int128, Bytes[10]):
@external
def test() -> (int128, address, Bytes[10]):
a: int128 = 0
- b: address = ZERO_ADDRESS
+ b: address = empty(address)
a, b = self.out_literals() # tuple count mismatch
return
""",
diff --git a/tests/parser/syntax/test_unbalanced_return.py b/tests/parser/syntax/test_unbalanced_return.py
index 5337b4b677..d1d9732777 100644
--- a/tests/parser/syntax/test_unbalanced_return.py
+++ b/tests/parser/syntax/test_unbalanced_return.py
@@ -56,7 +56,7 @@ def valid_address(sender: address) -> bool:
"""
@internal
def valid_address(sender: address) -> bool:
- if sender == ZERO_ADDRESS:
+ if sender == empty(address):
selfdestruct(sender)
_sender: address = sender
else:
@@ -144,7 +144,7 @@ def test() -> int128:
"""
@external
def test() -> int128:
- x: bytes32 = EMPTY_BYTES32
+ x: bytes32 = empty(bytes32)
if False:
if False:
return 0
diff --git a/tests/parser/syntax/utils/test_function_names.py b/tests/parser/syntax/utils/test_function_names.py
index 90e185558c..5489a4f6a0 100644
--- a/tests/parser/syntax/utils/test_function_names.py
+++ b/tests/parser/syntax/utils/test_function_names.py
@@ -23,6 +23,22 @@ def wei(i: int128) -> int128:
temp_var : int128 = i
return temp_var1
""",
+ # collision between getter and external function
+ """
+foo: public(uint256)
+
+@external
+def foo():
+ pass
+ """,
+ # collision between getter and external function, reverse order
+ """
+@external
+def foo():
+ pass
+
+foo: public(uint256)
+ """,
]
@@ -77,6 +93,30 @@ def append():
def foo():
self.append()
""",
+ # "method id" collisions between internal functions are allowed
+ """
+@internal
+@view
+def gfah():
+ pass
+
+@internal
+@view
+def eexo():
+ pass
+ """,
+ # "method id" collisions between internal+external functions are allowed
+ """
+@internal
+@view
+def gfah():
+ pass
+
+@external
+@view
+def eexo():
+ pass
+ """,
]
diff --git a/tests/parser/test_call_graph_stability.py b/tests/parser/test_call_graph_stability.py
index 6785169ba3..4c85c330f3 100644
--- a/tests/parser/test_call_graph_stability.py
+++ b/tests/parser/test_call_graph_stability.py
@@ -6,16 +6,21 @@
from hypothesis import given, settings
import vyper.ast as vy_ast
+from vyper.ast.identifiers import RESERVED_KEYWORDS
from vyper.compiler.phases import CompilerData
+def _valid_identifier(attr):
+ return attr not in RESERVED_KEYWORDS
+
+
# random names for functions
-@settings(max_examples=20, deadline=None)
+@settings(max_examples=20)
@given(
st.lists(
st.tuples(
st.sampled_from(["@pure", "@view", "@nonpayable", "@payable"]),
- st.text(alphabet=string.ascii_lowercase, min_size=1),
+ st.text(alphabet=string.ascii_lowercase, min_size=1).filter(_valid_identifier),
),
unique_by=lambda x: x[1], # unique on function name
min_size=1,
@@ -65,4 +70,6 @@ def foo():
r = d.args[0].args[0].value
if isinstance(r, str) and r.startswith("internal"):
ir_funcs.append(r)
- assert ir_funcs == [f.internal_function_label for f in sigs.values()]
+ assert ir_funcs == [
+ f._ir_info.internal_function_label(is_ctor_context=False) for f in sigs.values()
+ ]
diff --git a/tests/parser/test_selector_table.py b/tests/parser/test_selector_table.py
new file mode 100644
index 0000000000..161cd480fd
--- /dev/null
+++ b/tests/parser/test_selector_table.py
@@ -0,0 +1,635 @@
+import hypothesis.strategies as st
+import pytest
+from hypothesis import given, settings
+
+import vyper.utils as utils
+from vyper.codegen.jumptable_utils import (
+ generate_dense_jumptable_info,
+ generate_sparse_jumptable_buckets,
+)
+from vyper.compiler.settings import OptimizationLevel
+
+
+def test_dense_selector_table_empty_buckets(get_contract):
+ # some special combination of selectors which can result in
+ # some empty bucket being returned from _mk_buckets (that is,
+ # len(_mk_buckets(..., n_buckets)) != n_buckets
+ code = """
+@external
+def aX61QLPWF()->uint256:
+ return 1
+@external
+def aQHG0P2L1()->uint256:
+ return 2
+@external
+def a2G8ME94W()->uint256:
+ return 3
+@external
+def a0GNA21AY()->uint256:
+ return 4
+@external
+def a4U1XA4T5()->uint256:
+ return 5
+@external
+def aAYLMGOBZ()->uint256:
+ return 6
+@external
+def a0KXRLHKE()->uint256:
+ return 7
+@external
+def aDQS32HTR()->uint256:
+ return 8
+@external
+def aP4K6SA3S()->uint256:
+ return 9
+@external
+def aEB94ZP5S()->uint256:
+ return 10
+@external
+def aTOIMN0IM()->uint256:
+ return 11
+@external
+def aXV2N81OW()->uint256:
+ return 12
+@external
+def a66PP6Y5X()->uint256:
+ return 13
+@external
+def a5MWMTEWN()->uint256:
+ return 14
+@external
+def a5ZFST4Z8()->uint256:
+ return 15
+@external
+def aR13VXULX()->uint256:
+ return 16
+@external
+def aWITH917Y()->uint256:
+ return 17
+@external
+def a59NP6C5O()->uint256:
+ return 18
+@external
+def aJ02590EX()->uint256:
+ return 19
+@external
+def aUAXAAUQ8()->uint256:
+ return 20
+@external
+def aWR1XNC6J()->uint256:
+ return 21
+@external
+def aJABKZOKH()->uint256:
+ return 22
+@external
+def aO1TT0RJT()->uint256:
+ return 23
+@external
+def a41442IOK()->uint256:
+ return 24
+@external
+def aMVXV9FHQ()->uint256:
+ return 25
+@external
+def aNN0KJDZM()->uint256:
+ return 26
+@external
+def aOX965047()->uint256:
+ return 27
+@external
+def a575NX2J3()->uint256:
+ return 28
+@external
+def a16EN8O7W()->uint256:
+ return 29
+@external
+def aSZXLFF7O()->uint256:
+ return 30
+@external
+def aQKQCIPH9()->uint256:
+ return 31
+@external
+def aIP8021DL()->uint256:
+ return 32
+@external
+def aQAV0HSHX()->uint256:
+ return 33
+@external
+def aZVPAD745()->uint256:
+ return 34
+@external
+def aJYBSNST4()->uint256:
+ return 35
+@external
+def aQGWC4NYQ()->uint256:
+ return 36
+@external
+def aFMBB9CXJ()->uint256:
+ return 37
+@external
+def aYWM7ZUH1()->uint256:
+ return 38
+@external
+def aJAZONIX1()->uint256:
+ return 39
+@external
+def aQZ1HJK0H()->uint256:
+ return 40
+@external
+def aKIH9LOUB()->uint256:
+ return 41
+@external
+def aF4ZT80XL()->uint256:
+ return 42
+@external
+def aYQD8UKR5()->uint256:
+ return 43
+@external
+def aP6NCCAI4()->uint256:
+ return 44
+@external
+def aY92U2EAZ()->uint256:
+ return 45
+@external
+def aHMQ49D7P()->uint256:
+ return 46
+@external
+def aMC6YX8VF()->uint256:
+ return 47
+@external
+def a734X6YSI()->uint256:
+ return 48
+@external
+def aRXXPNSMU()->uint256:
+ return 49
+@external
+def aL5XKDTGT()->uint256:
+ return 50
+@external
+def a86V1Y18A()->uint256:
+ return 51
+@external
+def aAUM8PL5J()->uint256:
+ return 52
+@external
+def aBAEC1ERZ()->uint256:
+ return 53
+@external
+def a1U1VA3UE()->uint256:
+ return 54
+@external
+def aC9FGVAHC()->uint256:
+ return 55
+@external
+def aWN81WYJ3()->uint256:
+ return 56
+@external
+def a3KK1Y07J()->uint256:
+ return 57
+@external
+def aAZ6P6OSG()->uint256:
+ return 58
+@external
+def aWP5HCIB3()->uint256:
+ return 59
+@external
+def aVEK161C5()->uint256:
+ return 60
+@external
+def aY0Q3O519()->uint256:
+ return 61
+@external
+def aDHHHFIAE()->uint256:
+ return 62
+@external
+def aGSJBCZKQ()->uint256:
+ return 63
+@external
+def aZQQIUDHY()->uint256:
+ return 64
+@external
+def a12O9QDH5()->uint256:
+ return 65
+@external
+def aRQ1178XR()->uint256:
+ return 66
+@external
+def aDT25C832()->uint256:
+ return 67
+@external
+def aCSB01C4E()->uint256:
+ return 68
+@external
+def aYGBPKZSD()->uint256:
+ return 69
+@external
+def aP24N3EJ8()->uint256:
+ return 70
+@external
+def a531Y9X3C()->uint256:
+ return 71
+@external
+def a4727IKVS()->uint256:
+ return 72
+@external
+def a2EX1L2BS()->uint256:
+ return 73
+@external
+def a6145RN68()->uint256:
+ return 74
+@external
+def aDO1ZNX97()->uint256:
+ return 75
+@external
+def a3R28EU6M()->uint256:
+ return 76
+@external
+def a9BFC867L()->uint256:
+ return 77
+@external
+def aPL1MBGYC()->uint256:
+ return 78
+@external
+def aI6H11O48()->uint256:
+ return 79
+@external
+def aX0248DZY()->uint256:
+ return 80
+@external
+def aE4JBUJN4()->uint256:
+ return 81
+@external
+def aXBDB2ZBO()->uint256:
+ return 82
+@external
+def a7O7MYYHL()->uint256:
+ return 83
+@external
+def aERFF4PB6()->uint256:
+ return 84
+@external
+def aJCUBG6TJ()->uint256:
+ return 85
+@external
+def aQ5ELXM0F()->uint256:
+ return 86
+@external
+def aWDT9UQVV()->uint256:
+ return 87
+@external
+def a7UU40DJK()->uint256:
+ return 88
+@external
+def aH01IT5VS()->uint256:
+ return 89
+@external
+def aSKYTZ0FC()->uint256:
+ return 90
+@external
+def aNX5LYRAW()->uint256:
+ return 91
+@external
+def aUDKAOSGG()->uint256:
+ return 92
+@external
+def aZ86YGAAO()->uint256:
+ return 93
+@external
+def aIHWQGKLO()->uint256:
+ return 94
+@external
+def aKIKFLAR9()->uint256:
+ return 95
+@external
+def aCTPE0KRS()->uint256:
+ return 96
+@external
+def aAD75X00P()->uint256:
+ return 97
+@external
+def aDROUEF2F()->uint256:
+ return 98
+@external
+def a8CDIF6YN()->uint256:
+ return 99
+@external
+def aD2X7TM83()->uint256:
+ return 100
+@external
+def a3W5UUB4L()->uint256:
+ return 101
+@external
+def aG4MOBN4B()->uint256:
+ return 102
+@external
+def aPRS0MSG7()->uint256:
+ return 103
+@external
+def aKN3GHBUR()->uint256:
+ return 104
+@external
+def aGE435RHQ()->uint256:
+ return 105
+@external
+def a4E86BNFE()->uint256:
+ return 106
+@external
+def aYDG928YW()->uint256:
+ return 107
+@external
+def a2HFP5GQE()->uint256:
+ return 108
+@external
+def a5DPMVXKA()->uint256:
+ return 109
+@external
+def a3OFVC3DR()->uint256:
+ return 110
+@external
+def aK8F62DAN()->uint256:
+ return 111
+@external
+def aJS9EY3U6()->uint256:
+ return 112
+@external
+def aWW789JQH()->uint256:
+ return 113
+@external
+def a8AJJN3YR()->uint256:
+ return 114
+@external
+def a4D0MUIDU()->uint256:
+ return 115
+@external
+def a35W41JQR()->uint256:
+ return 116
+@external
+def a07DQOI1E()->uint256:
+ return 117
+@external
+def aFT43YNCT()->uint256:
+ return 118
+@external
+def a0E75I8X3()->uint256:
+ return 119
+@external
+def aT6NXIRO4()->uint256:
+ return 120
+@external
+def aXB2UBAKQ()->uint256:
+ return 121
+@external
+def aHWH55NW6()->uint256:
+ return 122
+@external
+def a7TCFE6C2()->uint256:
+ return 123
+@external
+def a8XYAM81I()->uint256:
+ return 124
+@external
+def aHQTQ4YBY()->uint256:
+ return 125
+@external
+def aGCZEHG6Y()->uint256:
+ return 126
+@external
+def a6LJTKIW0()->uint256:
+ return 127
+@external
+def aBDIXTD9S()->uint256:
+ return 128
+@external
+def aCB83G21P()->uint256:
+ return 129
+@external
+def aZC525N4K()->uint256:
+ return 130
+@external
+def a40LC94U6()->uint256:
+ return 131
+@external
+def a8X9TI93D()->uint256:
+ return 132
+@external
+def aGUG9CD8Y()->uint256:
+ return 133
+@external
+def a0LAERVAY()->uint256:
+ return 134
+@external
+def aXQ0UEX19()->uint256:
+ return 135
+@external
+def aKK9C7NE7()->uint256:
+ return 136
+@external
+def aS2APW8UE()->uint256:
+ return 137
+@external
+def a65NT07MM()->uint256:
+ return 138
+@external
+def aGRMT6ZW5()->uint256:
+ return 139
+@external
+def aILR4U1Z()->uint256:
+ return 140
+ """
+ c = get_contract(code)
+
+ assert c.aX61QLPWF() == 1 # will revert if the header section is misaligned
+
+
+@given(
+ n_methods=st.integers(min_value=1, max_value=100),
+ seed=st.integers(min_value=0, max_value=2**64 - 1),
+)
+@pytest.mark.fuzzing
+@settings(max_examples=10)
+def test_sparse_jumptable_probe_depth(n_methods, seed):
+ sigs = [f"foo{i + seed}()" for i in range(n_methods)]
+ _, buckets = generate_sparse_jumptable_buckets(sigs)
+ bucket_sizes = [len(bucket) for bucket in buckets.values()]
+
+ # generally bucket sizes should be bounded at around 4, but
+ # just test that they don't get really out of hand
+ assert max(bucket_sizes) <= 8
+
+ # generally mean bucket size should be around 1.6, here just
+ # test they don't get really out of hand
+ assert sum(bucket_sizes) / len(bucket_sizes) <= 4
+
+
+@given(
+ n_methods=st.integers(min_value=4, max_value=100),
+ seed=st.integers(min_value=0, max_value=2**64 - 1),
+)
+@pytest.mark.fuzzing
+@settings(max_examples=10)
+def test_dense_jumptable_bucket_size(n_methods, seed):
+ sigs = [f"foo{i + seed}()" for i in range(n_methods)]
+ n = len(sigs)
+ buckets = generate_dense_jumptable_info(sigs)
+ n_buckets = len(buckets)
+
+ # generally should be around 14 buckets per 100 methods, here
+ # we test they don't get really out of hand
+ assert n_buckets / n < 0.4 or n < 10
+
+
+@st.composite
+def generate_methods(draw, max_calldata_bytes):
+ max_default_args = draw(st.integers(min_value=0, max_value=4))
+ default_fn_mutability = draw(st.sampled_from(["", "@pure", "@view", "@nonpayable", "@payable"]))
+
+ return (
+ max_default_args,
+ default_fn_mutability,
+ draw(
+ st.lists(
+ st.tuples(
+ # function id:
+ st.integers(min_value=0),
+ # mutability:
+ st.sampled_from(["@pure", "@view", "@nonpayable", "@payable"]),
+ # n calldata words:
+ st.integers(min_value=0, max_value=max_calldata_bytes // 32),
+ # n bytes to strip from calldata
+ st.integers(min_value=1, max_value=4),
+ # n default args
+ st.integers(min_value=0, max_value=max_default_args),
+ ),
+ unique_by=lambda x: x[0],
+ min_size=1,
+ max_size=100,
+ )
+ ),
+ )
+
+
+@pytest.mark.parametrize("opt_level", list(OptimizationLevel))
+# dense selector table packing boundaries at 256 and 65336
+@pytest.mark.parametrize("max_calldata_bytes", [255, 256, 65336])
+@pytest.mark.fuzzing
+def test_selector_table_fuzz(
+ max_calldata_bytes, opt_level, w3, get_contract, assert_tx_failed, get_logs
+):
+ def abi_sig(func_id, calldata_words, n_default_args):
+ params = [] if not calldata_words else [f"uint256[{calldata_words}]"]
+ params.extend(["uint256"] * n_default_args)
+ paramstr = ",".join(params)
+ return f"foo{func_id}({paramstr})"
+
+ def generate_func_def(func_id, mutability, calldata_words, n_default_args):
+ arglist = [] if not calldata_words else [f"x: uint256[{calldata_words}]"]
+ for j in range(n_default_args):
+ arglist.append(f"x{j}: uint256 = 0")
+ args = ", ".join(arglist)
+ _log_return = f"log _Return({func_id})" if mutability == "@payable" else ""
+
+ return f"""
+@external
+{mutability}
+def foo{func_id}({args}) -> uint256:
+ {_log_return}
+ return {func_id}
+ """
+
+ @given(_input=generate_methods(max_calldata_bytes))
+ @settings(max_examples=125)
+ def _test(_input):
+ max_default_args, default_fn_mutability, methods = _input
+
+ func_defs = "\n".join(
+ generate_func_def(func_id, mutability, calldata_words, n_default_args)
+ for (func_id, mutability, calldata_words, _, n_default_args) in (methods)
+ )
+
+ if default_fn_mutability == "":
+ default_fn_code = ""
+ elif default_fn_mutability in ("@nonpayable", "@payable"):
+ default_fn_code = f"""
+@external
+{default_fn_mutability}
+def __default__():
+ log CalledDefault()
+ """
+ else:
+ # can't log from pure/view functions, just test that it returns
+ default_fn_code = """
+@external
+def __default__():
+ pass
+ """
+
+ code = f"""
+event CalledDefault:
+ pass
+
+event _Return:
+ val: uint256
+
+{func_defs}
+
+{default_fn_code}
+ """
+
+ c = get_contract(code, override_opt_level=opt_level)
+
+ for func_id, mutability, n_calldata_words, n_strip_bytes, n_default_args in methods:
+ funcname = f"foo{func_id}"
+ func = getattr(c, funcname)
+
+ for j in range(n_default_args + 1):
+ args = [[1] * n_calldata_words] if n_calldata_words else []
+ args.extend([1] * j)
+
+ # check the function returns as expected
+ assert func(*args) == func_id
+
+ method_id = utils.method_id(abi_sig(func_id, n_calldata_words, j))
+
+ argsdata = b"\x00" * (n_calldata_words * 32 + j * 32)
+
+ # do payable check
+ if mutability == "@payable":
+ tx = func(*args, transact={"value": 1})
+ (event,) = get_logs(tx, c, "_Return")
+ assert event.args.val == func_id
+ else:
+ hexstr = (method_id + argsdata).hex()
+ txdata = {"to": c.address, "data": hexstr, "value": 1}
+ assert_tx_failed(lambda: w3.eth.send_transaction(txdata))
+
+ # now do calldatasize check
+ # strip some bytes
+ calldata = (method_id + argsdata)[:-n_strip_bytes]
+ hexstr = calldata.hex()
+ tx_params = {"to": c.address, "data": hexstr}
+ if n_calldata_words == 0 and j == 0:
+ # no args, hit default function
+ if default_fn_mutability == "":
+ assert_tx_failed(lambda: w3.eth.send_transaction(tx_params))
+ elif default_fn_mutability == "@payable":
+ # we should be able to send eth to it
+ tx_params["value"] = 1
+ tx = w3.eth.send_transaction(tx_params)
+ logs = get_logs(tx, c, "CalledDefault")
+ assert len(logs) == 1
+ else:
+ tx = w3.eth.send_transaction(tx_params)
+
+ # note: can't emit logs from view/pure functions,
+ # so the logging is not tested.
+ if default_fn_mutability == "@nonpayable":
+ logs = get_logs(tx, c, "CalledDefault")
+ assert len(logs) == 1
+
+ # check default function reverts
+ tx_params["value"] = 1
+ assert_tx_failed(lambda: w3.eth.send_transaction(tx_params))
+ else:
+ assert_tx_failed(lambda: w3.eth.send_transaction(tx_params))
+
+ _test()
diff --git a/tests/parser/test_selector_table_stability.py b/tests/parser/test_selector_table_stability.py
new file mode 100644
index 0000000000..3302ff5009
--- /dev/null
+++ b/tests/parser/test_selector_table_stability.py
@@ -0,0 +1,55 @@
+from vyper.codegen.jumptable_utils import generate_sparse_jumptable_buckets
+from vyper.compiler import compile_code
+from vyper.compiler.settings import OptimizationLevel, Settings
+
+
+def test_dense_jumptable_stability():
+ function_names = [f"foo{i}" for i in range(30)]
+
+ code = "\n".join(f"@external\ndef {name}():\n pass" for name in function_names)
+
+ output = compile_code(
+ code, output_formats=["asm"], settings=Settings(optimize=OptimizationLevel.CODESIZE)
+ )
+
+ # test that the selector table data is stable across different runs
+ # (tox should provide different PYTHONHASHSEEDs).
+ expected_asm = """{ DATA _sym_BUCKET_HEADERS b'\\x0bB' _sym_bucket_0 b'\\n' b'+\\x8d' _sym_bucket_1 b'\\x0c' b'\\x00\\x85' _sym_bucket_2 b'\\x08' } { DATA _sym_bucket_1 b'\\xd8\\xee\\xa1\\xe8' _sym_external_foo6___3639517672 b'\\x05' b'\\xd2\\x9e\\xe0\\xf9' _sym_external_foo0___3533627641 b'\\x05' b'\\x05\\xf1\\xe0_' _sym_external_foo2___99737695 b'\\x05' b'\\x91\\t\\xb4{' _sym_external_foo23___2433332347 b'\\x05' b'np3\\x7f' _sym_external_foo11___1852846975 b'\\x05' b'&\\xf5\\x96\\xf9' _sym_external_foo13___653629177 b'\\x05' b'\\x04ga\\xeb' _sym_external_foo14___73884139 b'\\x05' b'\\x89\\x06\\xad\\xc6' _sym_external_foo17___2298916294 b'\\x05' b'\\xe4%\\xac\\xd1' _sym_external_foo4___3827674321 b'\\x05' b'yj\\x01\\xac' _sym_external_foo7___2036990380 b'\\x05' b'\\xf1\\xe6K\\xe5' _sym_external_foo29___4058401765 b'\\x05' b'\\xd2\\x89X\\xb8' _sym_external_foo3___3532216504 b'\\x05' } { DATA _sym_bucket_2 b'\\x06p\\xffj' _sym_external_foo25___108068714 b'\\x05' b'\\x964\\x99I' _sym_external_foo24___2520029513 b'\\x05' b's\\x81\\xe7\\xc1' _sym_external_foo10___1937893313 b'\\x05' b'\\x85\\xad\\xc11' _sym_external_foo28___2242756913 b'\\x05' b'\\xfa"\\xb1\\xed' _sym_external_foo5___4196577773 b'\\x05' b'A\\xe7[\\x05' _sym_external_foo22___1105681157 b'\\x05' b'\\xd3\\x89U\\xe8' _sym_external_foo1___3548993000 b'\\x05' b'hL\\xf8\\xf3' _sym_external_foo20___1749874931 b'\\x05' } { DATA _sym_bucket_0 b'\\xee\\xd9\\x1d\\xe3' _sym_external_foo9___4007206371 b'\\x05' b'a\\xbc\\x1ch' _sym_external_foo16___1639717992 b'\\x05' b'\\xd3*\\xa7\\x0c' _sym_external_foo21___3542787852 b'\\x05' b'\\x18iG\\xd9' _sym_external_foo19___409552857 b'\\x05' b'\\n\\xf1\\xf9\\x7f' _sym_external_foo18___183630207 b'\\x05' b')\\xda\\xd7`' _sym_external_foo27___702207840 b'\\x05' b'2\\xf6\\xaa\\xda' _sym_external_foo12___855026394 b'\\x05' b'\\xbe\\xb5\\x05\\xf5' _sym_external_foo15___3199534581 b'\\x05' b'\\xfc\\xa7_\\xe6' _sym_external_foo8___4238827494 b'\\x05' b'\\x1b\\x12C8' _sym_external_foo26___454181688 b'\\x05' } }""" # noqa: E501
+ assert expected_asm in output["asm"]
+
+
+def test_sparse_jumptable_stability():
+ function_names = [f"foo{i}()" for i in range(30)]
+
+ # sparse jumptable is not as complicated in assembly.
+ # here just test the data structure is stable
+
+ n_buckets, buckets = generate_sparse_jumptable_buckets(function_names)
+ assert n_buckets == 33
+
+ # the buckets sorted by id are what go into the IR, check equality against
+ # expected:
+ assert sorted(buckets.items()) == [
+ (0, [4238827494, 1639717992]),
+ (1, [1852846975]),
+ (2, [1749874931]),
+ (3, [4007206371]),
+ (4, [2298916294]),
+ (7, [2036990380]),
+ (10, [3639517672, 73884139]),
+ (12, [3199534581]),
+ (13, [99737695]),
+ (14, [3548993000, 4196577773]),
+ (15, [454181688, 702207840]),
+ (16, [3533627641]),
+ (17, [108068714]),
+ (20, [1105681157]),
+ (21, [409552857, 3542787852]),
+ (22, [4058401765]),
+ (23, [2520029513, 2242756913]),
+ (24, [855026394, 183630207]),
+ (25, [3532216504, 653629177]),
+ (26, [1937893313]),
+ (28, [2433332347]),
+ (31, [3827674321]),
+ ]
diff --git a/tests/parser/types/numbers/test_constants.py b/tests/parser/types/numbers/test_constants.py
index 0d5e386dad..25617651ec 100644
--- a/tests/parser/types/numbers/test_constants.py
+++ b/tests/parser/types/numbers/test_constants.py
@@ -12,12 +12,12 @@ def test_builtin_constants(get_contract_with_gas_estimation):
code = """
@external
def test_zaddress(a: address) -> bool:
- return a == ZERO_ADDRESS
+ return a == empty(address)
@external
def test_empty_bytes32(a: bytes32) -> bool:
- return a == EMPTY_BYTES32
+ return a == empty(bytes32)
@external
@@ -81,12 +81,12 @@ def goo() -> int128:
@external
def hoo() -> bytes32:
- bar: bytes32 = EMPTY_BYTES32
+ bar: bytes32 = empty(bytes32)
return bar
@external
def joo() -> address:
- bar: address = ZERO_ADDRESS
+ bar: address = empty(address)
return bar
@external
@@ -206,7 +206,7 @@ def test() -> uint256:
return ret
"""
- ir = compile_code(code, ["ir"])["ir"]
+ ir = compile_code(code, output_formats=["ir"])["ir"]
assert search_for_sublist(
ir, ["mstore", [MemoryPositions.RESERVED_MEMORY], [2**12 * some_prime]]
)
diff --git a/tests/parser/types/numbers/test_isqrt.py b/tests/parser/types/numbers/test_isqrt.py
index ce26d24d06..b734323a6e 100644
--- a/tests/parser/types/numbers/test_isqrt.py
+++ b/tests/parser/types/numbers/test_isqrt.py
@@ -119,7 +119,6 @@ def test(a: uint256) -> (uint256, uint256, uint256, uint256, uint256, String[100
@hypothesis.example(2704)
@hypothesis.example(110889)
@hypothesis.example(32239684)
-@hypothesis.settings(deadline=1000)
def test_isqrt_valid_range(isqrt_contract, value):
vyper_isqrt = isqrt_contract.test(value)
actual_isqrt = math.isqrt(value)
diff --git a/tests/parser/types/numbers/test_sqrt.py b/tests/parser/types/numbers/test_sqrt.py
index 025a3868e9..020a79e7ef 100644
--- a/tests/parser/types/numbers/test_sqrt.py
+++ b/tests/parser/types/numbers/test_sqrt.py
@@ -143,9 +143,8 @@ def test_sqrt_bounds(sqrt_contract, value):
min_value=Decimal(0), max_value=Decimal(SizeLimits.MAX_INT128), places=DECIMAL_PLACES
)
)
-@hypothesis.example(Decimal(SizeLimits.MAX_INT128))
-@hypothesis.example(Decimal(0))
-@hypothesis.settings(deadline=1000)
+@hypothesis.example(value=Decimal(SizeLimits.MAX_INT128))
+@hypothesis.example(value=Decimal(0))
def test_sqrt_valid_range(sqrt_contract, value):
vyper_sqrt = sqrt_contract.test(value)
actual_sqrt = decimal_sqrt(value)
@@ -158,9 +157,8 @@ def test_sqrt_valid_range(sqrt_contract, value):
min_value=Decimal(SizeLimits.MIN_INT128), max_value=Decimal("-1E10"), places=DECIMAL_PLACES
)
)
-@hypothesis.settings(deadline=400)
-@hypothesis.example(Decimal(SizeLimits.MIN_INT128))
-@hypothesis.example(Decimal("-1E10"))
+@hypothesis.example(value=Decimal(SizeLimits.MIN_INT128))
+@hypothesis.example(value=Decimal("-1E10"))
def test_sqrt_invalid_range(sqrt_contract, value):
with pytest.raises(TransactionFailed):
sqrt_contract.test(value)
diff --git a/tests/parser/types/numbers/test_unsigned_ints.py b/tests/parser/types/numbers/test_unsigned_ints.py
index 82c0f8484c..683684e6be 100644
--- a/tests/parser/types/numbers/test_unsigned_ints.py
+++ b/tests/parser/types/numbers/test_unsigned_ints.py
@@ -195,49 +195,6 @@ def foo(x: {typ}, y: {typ}) -> bool:
assert c.foo(x, y) is expected
-# TODO move to tests/parser/functions/test_mulmod.py and test_addmod.py
-def test_uint256_mod(assert_tx_failed, get_contract_with_gas_estimation):
- uint256_code = """
-@external
-def _uint256_addmod(x: uint256, y: uint256, z: uint256) -> uint256:
- return uint256_addmod(x, y, z)
-
-@external
-def _uint256_mulmod(x: uint256, y: uint256, z: uint256) -> uint256:
- return uint256_mulmod(x, y, z)
- """
-
- c = get_contract_with_gas_estimation(uint256_code)
-
- assert c._uint256_addmod(1, 2, 2) == 1
- assert c._uint256_addmod(32, 2, 32) == 2
- assert c._uint256_addmod((2**256) - 1, 0, 2) == 1
- assert c._uint256_addmod(2**255, 2**255, 6) == 4
- assert_tx_failed(lambda: c._uint256_addmod(1, 2, 0))
- assert c._uint256_mulmod(3, 1, 2) == 1
- assert c._uint256_mulmod(200, 3, 601) == 600
- assert c._uint256_mulmod(2**255, 1, 3) == 2
- assert c._uint256_mulmod(2**255, 2, 6) == 4
- assert_tx_failed(lambda: c._uint256_mulmod(2, 2, 0))
-
-
-def test_uint256_modmul(get_contract_with_gas_estimation):
- modexper = """
-@external
-def exponential(base: uint256, exponent: uint256, modulus: uint256) -> uint256:
- o: uint256 = 1
- for i in range(256):
- o = uint256_mulmod(o, o, modulus)
- if exponent & (1 << (255 - i)) != 0:
- o = uint256_mulmod(o, base, modulus)
- return o
- """
-
- c = get_contract_with_gas_estimation(modexper)
- assert c.exponential(3, 5, 100) == 43
- assert c.exponential(2, 997, 997) == 2
-
-
@pytest.mark.parametrize("typ", types)
def test_uint_literal(get_contract, assert_compile_failed, typ):
lo, hi = typ.ast_bounds
diff --git a/tests/parser/types/test_bytes_zero_padding.py b/tests/parser/types/test_bytes_zero_padding.py
index ee938fdffb..f9fcf37b25 100644
--- a/tests/parser/types/test_bytes_zero_padding.py
+++ b/tests/parser/types/test_bytes_zero_padding.py
@@ -26,7 +26,6 @@ def get_count(counter: uint256) -> Bytes[24]:
@pytest.mark.fuzzing
@hypothesis.given(value=hypothesis.strategies.integers(min_value=0, max_value=2**64))
-@hypothesis.settings(deadline=400)
def test_zero_pad_range(little_endian_contract, value):
actual_bytes = value.to_bytes(8, byteorder="little")
contract_bytes = little_endian_contract.get_count(value)
diff --git a/tests/parser/types/test_dynamic_array.py b/tests/parser/types/test_dynamic_array.py
index 04c0688245..9231d1979f 100644
--- a/tests/parser/types/test_dynamic_array.py
+++ b/tests/parser/types/test_dynamic_array.py
@@ -1543,7 +1543,7 @@ def bar(x: int128) -> DynArray[int128, 3]:
assert c.bar(7) == [7, 14]
-def test_nested_struct_of_lists(get_contract, assert_compile_failed, no_optimize):
+def test_nested_struct_of_lists(get_contract, assert_compile_failed, optimize):
code = """
struct nestedFoo:
a1: DynArray[DynArray[DynArray[uint256, 2], 2], 2]
@@ -1584,14 +1584,9 @@ def bar2() -> uint256:
newFoo.b1[1][0][0].a1[0][1][1] + \\
newFoo.b1[0][1][0].a1[0][0][0]
"""
-
- if no_optimize:
- # fails at assembly stage with too many stack variables
- assert_compile_failed(lambda: get_contract(code), Exception)
- else:
- c = get_contract(code)
- assert c.bar() == [[[3, 7], [7, 3]], [[7, 3], [0, 0]]]
- assert c.bar2() == 0
+ c = get_contract(code)
+ assert c.bar() == [[[3, 7], [7, 3]], [[7, 3], [0, 0]]]
+ assert c.bar2() == 0
def test_tuple_of_lists(get_contract):
@@ -1748,3 +1743,95 @@ def foo(i: uint256) -> {return_type}:
return MY_CONSTANT[i]
"""
assert_compile_failed(lambda: get_contract(code), TypeMismatch)
+
+
+dynarray_length_no_clobber_cases = [
+ # GHSA-3p37-3636-q8wv cases
+ """
+a: DynArray[uint256,3]
+
+@external
+def should_revert() -> DynArray[uint256,3]:
+ self.a = [1,2,3]
+ self.a = empty(DynArray[uint256,3])
+ self.a = [self.a[0], self.a[1], self.a[2]]
+
+ return self.a # if bug: returns [1,2,3]
+ """,
+ """
+@external
+def should_revert() -> DynArray[uint256,3]:
+ self.a()
+ return self.b() # if bug: returns [1,2,3]
+
+@internal
+def a():
+ a: uint256 = 0
+ b: uint256 = 1
+ c: uint256 = 2
+ d: uint256 = 3
+
+@internal
+def b() -> DynArray[uint256,3]:
+ a: DynArray[uint256,3] = empty(DynArray[uint256,3])
+ a = [a[0],a[1],a[2]]
+ return a
+ """,
+ """
+a: DynArray[uint256,4]
+
+@external
+def should_revert() -> DynArray[uint256,4]:
+ self.a = [1,2,3]
+ self.a = empty(DynArray[uint256,4])
+ self.a = [4, self.a[0]]
+
+ return self.a # if bug: return [4, 4]
+ """,
+ """
+@external
+def should_revert() -> DynArray[uint256,4]:
+ a: DynArray[uint256, 4] = [1,2,3]
+ a = []
+
+ a = [a.pop()] # if bug: return [1]
+
+ return a
+ """,
+ """
+@external
+def should_revert():
+ c: DynArray[uint256, 1] = []
+ c.append(c[0])
+ """,
+ """
+@external
+def should_revert():
+ c: DynArray[uint256, 1] = [1]
+ c[0] = c.pop()
+ """,
+ """
+@external
+def should_revert():
+ c: DynArray[DynArray[uint256, 1], 2] = [[]]
+ c[0] = c.pop()
+ """,
+ """
+a: DynArray[String[65],2]
+
+@external
+def should_revert() -> DynArray[String[65], 2]:
+ self.a = ["hello", "world"]
+ self.a = []
+ self.a = [self.a[0], self.a[1]]
+
+ return self.a # if bug: return ["hello", "world"]
+ """,
+]
+
+
+@pytest.mark.parametrize("code", dynarray_length_no_clobber_cases)
+def test_dynarray_length_no_clobber(get_contract, assert_tx_failed, code):
+ # check that length is not clobbered before dynarray data copy happens
+ c = get_contract(code)
+ assert_tx_failed(lambda: c.should_revert())
diff --git a/tests/parser/types/test_identifier_naming.py b/tests/parser/types/test_identifier_naming.py
index d0daa6dc05..0a93329848 100755
--- a/tests/parser/types/test_identifier_naming.py
+++ b/tests/parser/types/test_identifier_naming.py
@@ -1,16 +1,12 @@
import pytest
-from vyper.ast.folding import BUILTIN_CONSTANTS
+from vyper.ast.identifiers import RESERVED_KEYWORDS
from vyper.builtins.functions import BUILTIN_FUNCTIONS
from vyper.codegen.expr import ENVIRONMENT_VARIABLES
from vyper.exceptions import NamespaceCollision, StructureException, SyntaxException
-from vyper.semantics.namespace import RESERVED_KEYWORDS
from vyper.semantics.types.primitives import AddressT
-BUILTIN_CONSTANTS = set(BUILTIN_CONSTANTS.keys())
-ALL_RESERVED_KEYWORDS = (
- BUILTIN_CONSTANTS | BUILTIN_FUNCTIONS | RESERVED_KEYWORDS | ENVIRONMENT_VARIABLES
-)
+ALL_RESERVED_KEYWORDS = BUILTIN_FUNCTIONS | RESERVED_KEYWORDS | ENVIRONMENT_VARIABLES
@pytest.mark.parametrize("constant", sorted(ALL_RESERVED_KEYWORDS))
@@ -45,12 +41,8 @@ def test({constant}: int128):
)
-PYTHON_KEYWORDS = {"if", "for", "while", "pass", "def", "assert", "continue", "raise"}
-
SELF_NAMESPACE_MEMBERS = set(AddressT._type_members.keys())
-DISALLOWED_FN_NAMES = (
- SELF_NAMESPACE_MEMBERS | PYTHON_KEYWORDS | RESERVED_KEYWORDS | BUILTIN_CONSTANTS
-)
+DISALLOWED_FN_NAMES = SELF_NAMESPACE_MEMBERS | RESERVED_KEYWORDS
ALLOWED_FN_NAMES = ALL_RESERVED_KEYWORDS - DISALLOWED_FN_NAMES
diff --git a/tests/parser/types/test_lists.py b/tests/parser/types/test_lists.py
index 0715eb3870..832b679e5e 100644
--- a/tests/parser/types/test_lists.py
+++ b/tests/parser/types/test_lists.py
@@ -676,6 +676,18 @@ def ix(i: uint256) -> {type}:
assert_tx_failed(lambda: c.ix(len(value) + 1))
+def test_nested_constant_list_accessor(get_contract):
+ code = """
+@external
+def foo() -> bool:
+ f: uint256 = 1
+ a: bool = 1 == [1,2,4][f] + -1
+ return a
+ """
+ c = get_contract(code)
+ assert c.foo() is True
+
+
# Would be nice to put this somewhere accessible, like in vyper.types or something
integer_types = ["uint8", "int128", "int256", "uint256"]
@@ -745,6 +757,23 @@ def ix(i: uint256) -> address:
assert_tx_failed(lambda: c.ix(len(some_good_address) + 1))
+def test_list_index_complex_expr(get_contract, assert_tx_failed):
+ # test subscripts where the index is not a literal
+ code = """
+@external
+def foo(xs: uint256[257], i: uint8) -> uint256:
+ return xs[i + 1]
+ """
+ c = get_contract(code)
+ xs = [i + 1 for i in range(257)]
+
+ for ix in range(255):
+ assert c.foo(xs, ix) == xs[ix + 1]
+
+ # safemath should fail for uint8: 255 + 1.
+ assert_tx_failed(lambda: c.foo(xs, 255))
+
+
@pytest.mark.parametrize(
"type,value",
[
diff --git a/tests/parser/types/test_string.py b/tests/parser/types/test_string.py
index a5eef66dae..7f1fa71329 100644
--- a/tests/parser/types/test_string.py
+++ b/tests/parser/types/test_string.py
@@ -139,7 +139,7 @@ def out_literals() -> (int128, address, String[10]) : view
@external
def test(addr: address) -> (int128, address, String[10]):
a: int128 = 0
- b: address = ZERO_ADDRESS
+ b: address = empty(address)
c: String[10] = ""
(a, b, c) = Test(addr).out_literals()
return a, b,c
diff --git a/tests/signatures/test_method_id_conflicts.py b/tests/signatures/test_method_id_conflicts.py
index 262348c12a..f3312efeab 100644
--- a/tests/signatures/test_method_id_conflicts.py
+++ b/tests/signatures/test_method_id_conflicts.py
@@ -48,24 +48,11 @@ def OwnerTransferV7b711143(a: uint256):
pass
""",
"""
-# check collision between private method IDs
-@internal
-@view
-def gfah(): pass
-
-@internal
-@view
-def eexo(): pass
- """,
- """
-# check collision between private and public IDs
-@internal
-@view
-def gfah(): pass
+# check collision with ID = 0x00000000
+wycpnbqcyf:public(uint256)
@external
-@view
-def eexo(): pass
+def randallsRevenge_ilxaotc(): pass
""",
]
diff --git a/tox.ini b/tox.ini
index 7fd09ff3f8..c949354dfe 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,6 +1,6 @@
[tox]
envlist =
- py{310,311}-{core,no-opt}
+ py{310,311}
lint
mypy
docs
@@ -8,8 +8,7 @@ envlist =
[testenv]
usedevelop = True
commands =
- core: pytest -m "not fuzzing" --showlocals {posargs:tests/}
- no-opt: pytest -m "not fuzzing" --showlocals --no-optimize {posargs:tests/}
+ pytest -m "not fuzzing" --showlocals {posargs:tests/}
basepython =
py310: python3.10
py311: python3.11
@@ -46,7 +45,7 @@ whitelist_externals = make
basepython = python3
extras = lint
commands =
- black -C -t py310 {toxinidir}/vyper {toxinidir}/tests {toxinidir}/setup.py
+ black -C -t py311 {toxinidir}/vyper {toxinidir}/tests {toxinidir}/setup.py
flake8 {toxinidir}/vyper {toxinidir}/tests
isort {toxinidir}/vyper {toxinidir}/tests {toxinidir}/setup.py
diff --git a/vyper/__init__.py b/vyper/__init__.py
index 35237bd044..482d5c3a60 100644
--- a/vyper/__init__.py
+++ b/vyper/__init__.py
@@ -1,6 +1,6 @@
from pathlib import Path as _Path
-from vyper.compiler import compile_code, compile_codes # noqa: F401
+from vyper.compiler import compile_code # noqa: F401
try:
from importlib.metadata import PackageNotFoundError # type: ignore
diff --git a/vyper/abi_types.py b/vyper/abi_types.py
index b272996aed..051f8db19f 100644
--- a/vyper/abi_types.py
+++ b/vyper/abi_types.py
@@ -1,4 +1,4 @@
-from vyper.exceptions import CompilerPanic
+from vyper.exceptions import InvalidABIType
from vyper.utils import ceil32
@@ -69,7 +69,7 @@ def __repr__(self):
class ABI_GIntM(ABIType):
def __init__(self, m_bits, signed):
if not (0 < m_bits <= 256 and 0 == m_bits % 8):
- raise CompilerPanic("Invalid M provided for GIntM")
+ raise InvalidABIType("Invalid M provided for GIntM")
self.m_bits = m_bits
self.signed = signed
@@ -117,9 +117,9 @@ def selector_name(self):
class ABI_FixedMxN(ABIType):
def __init__(self, m_bits, n_places, signed):
if not (0 < m_bits <= 256 and 0 == m_bits % 8):
- raise CompilerPanic("Invalid M for FixedMxN")
+ raise InvalidABIType("Invalid M for FixedMxN")
if not (0 < n_places and n_places <= 80):
- raise CompilerPanic("Invalid N for FixedMxN")
+ raise InvalidABIType("Invalid N for FixedMxN")
self.m_bits = m_bits
self.n_places = n_places
@@ -142,7 +142,7 @@ def is_complex_type(self):
class ABI_BytesM(ABIType):
def __init__(self, m_bytes):
if not 0 < m_bytes <= 32:
- raise CompilerPanic("Invalid M for BytesM")
+ raise InvalidABIType("Invalid M for BytesM")
self.m_bytes = m_bytes
@@ -173,7 +173,7 @@ def selector_name(self):
class ABI_StaticArray(ABIType):
def __init__(self, subtyp, m_elems):
if not m_elems >= 0:
- raise CompilerPanic("Invalid M")
+ raise InvalidABIType("Invalid M")
self.subtyp = subtyp
self.m_elems = m_elems
@@ -200,7 +200,7 @@ def is_complex_type(self):
class ABI_Bytes(ABIType):
def __init__(self, bytes_bound):
if not bytes_bound >= 0:
- raise CompilerPanic("Negative bytes_bound provided to ABI_Bytes")
+ raise InvalidABIType("Negative bytes_bound provided to ABI_Bytes")
self.bytes_bound = bytes_bound
@@ -234,7 +234,7 @@ def selector_name(self):
class ABI_DynamicArray(ABIType):
def __init__(self, subtyp, elems_bound):
if not elems_bound >= 0:
- raise CompilerPanic("Negative bound provided to DynamicArray")
+ raise InvalidABIType("Negative bound provided to DynamicArray")
self.subtyp = subtyp
self.elems_bound = elems_bound
diff --git a/vyper/ast/__init__.py b/vyper/ast/__init__.py
index 5695ceab7c..e5b81f1e7f 100644
--- a/vyper/ast/__init__.py
+++ b/vyper/ast/__init__.py
@@ -6,7 +6,7 @@
from . import nodes, validation
from .natspec import parse_natspec
from .nodes import compare_nodes
-from .utils import ast_to_dict, parse_to_ast
+from .utils import ast_to_dict, parse_to_ast, parse_to_ast_with_settings
# adds vyper.ast.nodes classes into the local namespace
for name, obj in (
diff --git a/vyper/ast/expansion.py b/vyper/ast/expansion.py
index c5518be405..5471b971a4 100644
--- a/vyper/ast/expansion.py
+++ b/vyper/ast/expansion.py
@@ -2,6 +2,7 @@
from vyper import ast as vy_ast
from vyper.exceptions import CompilerPanic
+from vyper.semantics.types.function import ContractFunctionT
def expand_annotated_ast(vyper_module: vy_ast.Module) -> None:
@@ -48,7 +49,6 @@ def generate_public_variable_getters(vyper_module: vy_ast.Module) -> None:
# the base return statement is an `Attribute` node, e.g. `self.`
# for each input type we wrap it in a `Subscript` to access a specific member
return_stmt = vy_ast.Attribute(value=vy_ast.Name(id="self"), attr=func_type.name)
- return_stmt._metadata["type"] = node._metadata["type"]
for i, type_ in enumerate(input_types):
if not isinstance(annotation, vy_ast.Subscript):
@@ -85,6 +85,10 @@ def generate_public_variable_getters(vyper_module: vy_ast.Module) -> None:
decorator_list=[vy_ast.Name(id="external"), vy_ast.Name(id="view")],
returns=return_node,
)
+
+ with vyper_module.namespace():
+ func_type = ContractFunctionT.from_FunctionDef(expanded)
+
expanded._metadata["type"] = func_type
return_node.set_parent(expanded)
vyper_module.add_to_body(expanded)
diff --git a/vyper/ast/folding.py b/vyper/ast/folding.py
index fbd1dfc2f4..38d58f6fd0 100644
--- a/vyper/ast/folding.py
+++ b/vyper/ast/folding.py
@@ -1,4 +1,3 @@
-import warnings
from typing import Optional, Union
from vyper.ast import nodes as vy_ast
@@ -6,21 +5,6 @@
from vyper.exceptions import UnfoldableNode, UnknownType
from vyper.semantics.types.base import VyperType
from vyper.semantics.types.utils import type_from_annotation
-from vyper.utils import SizeLimits
-
-BUILTIN_CONSTANTS = {
- "EMPTY_BYTES32": (
- vy_ast.Hex,
- "0x0000000000000000000000000000000000000000000000000000000000000000",
- "empty(bytes32)",
- ), # NOQA: E501
- "ZERO_ADDRESS": (vy_ast.Hex, "0x0000000000000000000000000000000000000000", "empty(address)"),
- "MAX_INT128": (vy_ast.Int, 2**127 - 1, "max_value(int128)"),
- "MIN_INT128": (vy_ast.Int, -(2**127), "min_value(int128)"),
- "MAX_DECIMAL": (vy_ast.Decimal, SizeLimits.MAX_AST_DECIMAL, "max_value(decimal)"),
- "MIN_DECIMAL": (vy_ast.Decimal, SizeLimits.MIN_AST_DECIMAL, "min_value(decimal)"),
- "MAX_UINT256": (vy_ast.Int, 2**256 - 1, "max_value(uint256)"),
-}
def fold(vyper_module: vy_ast.Module) -> None:
@@ -32,8 +16,6 @@ def fold(vyper_module: vy_ast.Module) -> None:
vyper_module : Module
Top-level Vyper AST node.
"""
- replace_builtin_constants(vyper_module)
-
changed_nodes = 1
while changed_nodes:
changed_nodes = 0
@@ -138,21 +120,6 @@ def replace_builtin_functions(vyper_module: vy_ast.Module) -> int:
return changed_nodes
-def replace_builtin_constants(vyper_module: vy_ast.Module) -> None:
- """
- Replace references to builtin constants with their literal values.
-
- Arguments
- ---------
- vyper_module : Module
- Top-level Vyper AST node.
- """
- for name, (node, value, replacement) in BUILTIN_CONSTANTS.items():
- found = replace_constant(vyper_module, name, node(value=value), True)
- if found > 0:
- warnings.warn(f"{name} is deprecated. Please use `{replacement}` instead.")
-
-
def replace_user_defined_constants(vyper_module: vy_ast.Module) -> int:
"""
Find user-defined constant assignments, and replace references
diff --git a/vyper/ast/grammar.lark b/vyper/ast/grammar.lark
index cf15e13a8c..ca9979b2a3 100644
--- a/vyper/ast/grammar.lark
+++ b/vyper/ast/grammar.lark
@@ -72,8 +72,8 @@ function_def: [decorators] function_sig ":" body
_EVENT_DECL: "event"
event_member: NAME ":" type
indexed_event_arg: NAME ":" "indexed" "(" type ")"
-event_body: _NEWLINE _INDENT ((event_member | indexed_event_arg) _NEWLINE)+ _DEDENT
// Events which use no args use a pass statement instead
+event_body: _NEWLINE _INDENT (((event_member | indexed_event_arg ) _NEWLINE)+ | _PASS _NEWLINE) _DEDENT
event_def: _EVENT_DECL NAME ":" ( event_body | _PASS )
// Enums
@@ -174,12 +174,15 @@ loop_variable: NAME [":" NAME]
loop_iterator: _expr
for_stmt: "for" loop_variable "in" loop_iterator ":" body
+// ternary operator
+ternary: _expr "if" _expr "else" _expr
// Expressions
_expr: operation
| dict
+ | ternary
-get_item: variable_access "[" _expr "]"
+get_item: (variable_access | list) "[" _expr "]"
get_attr: variable_access "." NAME
call: variable_access "(" [arguments] ")"
?variable_access: NAME -> get_var
diff --git a/vyper/ast/identifiers.py b/vyper/ast/identifiers.py
new file mode 100644
index 0000000000..985b04e5cd
--- /dev/null
+++ b/vyper/ast/identifiers.py
@@ -0,0 +1,111 @@
+import re
+
+from vyper.exceptions import StructureException
+
+
+def validate_identifier(attr, ast_node=None):
+ if not re.match("^[_a-zA-Z][a-zA-Z0-9_]*$", attr):
+ raise StructureException(f"'{attr}' contains invalid character(s)", ast_node)
+ if attr.lower() in RESERVED_KEYWORDS:
+ raise StructureException(f"'{attr}' is a reserved keyword", ast_node)
+
+
+# https://docs.python.org/3/reference/lexical_analysis.html#keywords
+# note we don't technically need to block all python reserved keywords,
+# but do it for hygiene
+_PYTHON_RESERVED_KEYWORDS = {
+ "False",
+ "None",
+ "True",
+ "and",
+ "as",
+ "assert",
+ "async",
+ "await",
+ "break",
+ "class",
+ "continue",
+ "def",
+ "del",
+ "elif",
+ "else",
+ "except",
+ "finally",
+ "for",
+ "from",
+ "global",
+ "if",
+ "import",
+ "in",
+ "is",
+ "lambda",
+ "nonlocal",
+ "not",
+ "or",
+ "pass",
+ "raise",
+ "return",
+ "try",
+ "while",
+ "with",
+ "yield",
+}
+_PYTHON_RESERVED_KEYWORDS = {s.lower() for s in _PYTHON_RESERVED_KEYWORDS}
+
+# Cannot be used for variable or member naming
+RESERVED_KEYWORDS = _PYTHON_RESERVED_KEYWORDS | {
+ # decorators
+ "public",
+ "external",
+ "nonpayable",
+ "constant",
+ "immutable",
+ "transient",
+ "internal",
+ "payable",
+ "nonreentrant",
+ # "class" keywords
+ "interface",
+ "struct",
+ "event",
+ "enum",
+ # EVM operations
+ "unreachable",
+ # special functions (no name mangling)
+ "init",
+ "_init_",
+ "___init___",
+ "____init____",
+ "default",
+ "_default_",
+ "___default___",
+ "____default____",
+ # more control flow and special operations
+ "range",
+ # more special operations
+ "indexed",
+ # denominations
+ "ether",
+ "wei",
+ "finney",
+ "szabo",
+ "shannon",
+ "lovelace",
+ "ada",
+ "babbage",
+ "gwei",
+ "kwei",
+ "mwei",
+ "twei",
+ "pwei",
+ # sentinal constant values
+ # TODO remove when these are removed from the language
+ "zero_address",
+ "empty_bytes32",
+ "max_int128",
+ "min_int128",
+ "max_decimal",
+ "min_decimal",
+ "max_uint256",
+ "zero_wei",
+}
diff --git a/vyper/ast/metadata.py b/vyper/ast/metadata.py
index 30e06e0016..0a419c3732 100644
--- a/vyper/ast/metadata.py
+++ b/vyper/ast/metadata.py
@@ -17,8 +17,11 @@ def __init__(self):
self._node_updates: list[dict[tuple[int, str, Any], NodeMetadata]] = []
def register_update(self, metadata, k):
+ KEY = (id(metadata), k)
+ if KEY in self._node_updates[-1]:
+ return
prev = metadata.get(k, self._NOT_FOUND)
- self._node_updates[-1][(id(metadata), k)] = (metadata, prev)
+ self._node_updates[-1][KEY] = (metadata, prev)
@contextlib.contextmanager
def enter(self):
diff --git a/vyper/ast/natspec.py b/vyper/ast/natspec.py
index e6f0fcd00b..c25fc423f8 100644
--- a/vyper/ast/natspec.py
+++ b/vyper/ast/natspec.py
@@ -88,7 +88,7 @@ def _parse_docstring(
tag, value = match.groups()
err_args = (source, *line_no.offset_to_line(start + match.start(1)))
- if tag not in SINGLE_FIELDS + PARAM_FIELDS:
+ if tag not in SINGLE_FIELDS + PARAM_FIELDS and not tag.startswith("custom:"):
raise NatSpecSyntaxException(f"Unknown NatSpec field '@{tag}'", *err_args)
if tag in invalid_fields:
raise NatSpecSyntaxException(
diff --git a/vyper/ast/nodes.py b/vyper/ast/nodes.py
index 5e6f8473a0..2497928035 100644
--- a/vyper/ast/nodes.py
+++ b/vyper/ast/nodes.py
@@ -1,4 +1,5 @@
import ast as python_ast
+import contextlib
import copy
import decimal
import operator
@@ -338,7 +339,7 @@ def __hash__(self):
def __eq__(self, other):
if not isinstance(other, type(self)):
return False
- if other.node_id != self.node_id:
+ if getattr(other, "node_id", None) != getattr(self, "node_id", None):
return False
for field_name in (i for i in self.get_fields() if i not in VyperNode.__slots__):
if getattr(self, field_name, None) != getattr(other, field_name, None):
@@ -664,6 +665,19 @@ def remove_from_body(self, node: VyperNode) -> None:
self.body.remove(node)
self._children.remove(node)
+ @contextlib.contextmanager
+ def namespace(self):
+ from vyper.semantics.namespace import get_namespace, override_global_namespace
+
+ # kludge implementation for backwards compatibility.
+ # TODO: replace with type_from_ast
+ try:
+ ns = self._metadata["namespace"]
+ except AttributeError:
+ ns = get_namespace()
+ with override_global_namespace(ns):
+ yield
+
class FunctionDef(TopLevel):
__slots__ = ("args", "returns", "decorator_list", "pos")
@@ -1330,7 +1344,15 @@ class VariableDecl(VyperNode):
If true, indicates that the variable is an immutable variable.
"""
- __slots__ = ("target", "annotation", "value", "is_constant", "is_public", "is_immutable")
+ __slots__ = (
+ "target",
+ "annotation",
+ "value",
+ "is_constant",
+ "is_public",
+ "is_immutable",
+ "is_transient",
+ )
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -1338,6 +1360,7 @@ def __init__(self, *args, **kwargs):
self.is_constant = False
self.is_public = False
self.is_immutable = False
+ self.is_transient = False
def _check_args(annotation, call_name):
# do the same thing as `validate_call_args`
@@ -1355,9 +1378,10 @@ def _check_args(annotation, call_name):
# unwrap one layer
self.annotation = self.annotation.args[0]
- if self.annotation.get("func.id") in ("immutable", "constant"):
- _check_args(self.annotation, self.annotation.func.id)
- setattr(self, f"is_{self.annotation.func.id}", True)
+ func_id = self.annotation.get("func.id")
+ if func_id in ("immutable", "constant", "transient"):
+ _check_args(self.annotation, func_id)
+ setattr(self, f"is_{func_id}", True)
# unwrap one layer
self.annotation = self.annotation.args[0]
@@ -1430,6 +1454,10 @@ class If(Stmt):
__slots__ = ("test", "body", "orelse")
+class IfExp(ExprNode):
+ __slots__ = ("test", "body", "orelse")
+
+
class For(Stmt):
__slots__ = ("iter", "target", "body")
_only_empty_fields = ("orelse",)
diff --git a/vyper/ast/nodes.pyi b/vyper/ast/nodes.pyi
index 93563516f3..47c9af8526 100644
--- a/vyper/ast/nodes.pyi
+++ b/vyper/ast/nodes.pyi
@@ -4,6 +4,7 @@ from typing import Any, Optional, Sequence, Type, Union
from .natspec import parse_natspec as parse_natspec
from .utils import ast_to_dict as ast_to_dict
from .utils import parse_to_ast as parse_to_ast
+from .utils import parse_to_ast_with_settings as parse_to_ast_with_settings
NODE_BASE_ATTRIBUTES: Any
NODE_SRC_ATTRIBUTES: Any
@@ -61,6 +62,7 @@ class Module(TopLevel):
def replace_in_tree(self, old_node: VyperNode, new_node: VyperNode) -> None: ...
def add_to_body(self, node: VyperNode) -> None: ...
def remove_from_body(self, node: VyperNode) -> None: ...
+ def namespace(self) -> Any: ... # context manager
class FunctionDef(TopLevel):
args: arguments = ...
@@ -140,6 +142,7 @@ class Expr(VyperNode):
class UnaryOp(ExprNode):
op: VyperNode = ...
+ operand: VyperNode = ...
class USub(VyperNode): ...
class Not(VyperNode): ...
@@ -163,12 +166,15 @@ class BitXor(VyperNode): ...
class BoolOp(ExprNode):
op: VyperNode = ...
+ values: list[VyperNode] = ...
class And(VyperNode): ...
class Or(VyperNode): ...
class Compare(ExprNode):
op: VyperNode = ...
+ left: VyperNode = ...
+ right: VyperNode = ...
class Eq(VyperNode): ...
class NotEq(VyperNode): ...
@@ -177,6 +183,7 @@ class LtE(VyperNode): ...
class Gt(VyperNode): ...
class GtE(VyperNode): ...
class In(VyperNode): ...
+class NotIn(VyperNode): ...
class Call(ExprNode):
args: list = ...
@@ -238,6 +245,11 @@ class If(VyperNode):
body: list = ...
orelse: list = ...
+class IfExp(ExprNode):
+ test: ExprNode = ...
+ body: ExprNode = ...
+ orelse: ExprNode = ...
+
class For(VyperNode): ...
class Break(VyperNode): ...
class Continue(VyperNode): ...
diff --git a/vyper/ast/pre_parser.py b/vyper/ast/pre_parser.py
index f29150a5d3..9d96efea5e 100644
--- a/vyper/ast/pre_parser.py
+++ b/vyper/ast/pre_parser.py
@@ -1,27 +1,16 @@
import io
import re
from tokenize import COMMENT, NAME, OP, TokenError, TokenInfo, tokenize, untokenize
-from typing import Tuple
-from semantic_version import NpmSpec, Version
+from packaging.specifiers import InvalidSpecifier, SpecifierSet
-from vyper.exceptions import SyntaxException, VersionException
-from vyper.typing import ModificationOffsets, ParserPosition
-
-VERSION_ALPHA_RE = re.compile(r"(?<=\d)a(?=\d)") # 0.1.0a17
-VERSION_BETA_RE = re.compile(r"(?<=\d)b(?=\d)") # 0.1.0b17
-VERSION_RC_RE = re.compile(r"(?<=\d)rc(?=\d)") # 0.1.0rc17
-
-
-def _convert_version_str(version_str: str) -> str:
- """
- Convert loose version (0.1.0b17) to strict version (0.1.0-beta.17)
- """
- version_str = re.sub(VERSION_ALPHA_RE, "-alpha.", version_str) # 0.1.0-alpha.17
- version_str = re.sub(VERSION_BETA_RE, "-beta.", version_str) # 0.1.0-beta.17
- version_str = re.sub(VERSION_RC_RE, "-rc.", version_str) # 0.1.0-rc.17
+from vyper.compiler.settings import OptimizationLevel, Settings
- return version_str
+# seems a bit early to be importing this but we want it to validate the
+# evm-version pragma
+from vyper.evm.opcodes import EVM_VERSIONS
+from vyper.exceptions import StructureException, SyntaxException, VersionException
+from vyper.typing import ModificationOffsets, ParserPosition
def validate_version_pragma(version_str: str, start: ParserPosition) -> None:
@@ -30,31 +19,26 @@ def validate_version_pragma(version_str: str, start: ParserPosition) -> None:
"""
from vyper import __version__
- # NOTE: should be `x.y.z.*`
- installed_version = ".".join(__version__.split(".")[:3])
-
- version_arr = version_str.split("@version")
-
- raw_file_version = version_arr[1].strip()
- strict_file_version = _convert_version_str(raw_file_version)
- strict_compiler_version = Version(_convert_version_str(installed_version))
-
- if len(strict_file_version) == 0:
+ if len(version_str) == 0:
raise VersionException("Version specification cannot be empty", start)
+ # X.Y.Z or vX.Y.Z => ==X.Y.Z, ==vX.Y.Z
+ if re.match("[v0-9]", version_str):
+ version_str = "==" + version_str
+ # convert npm to pep440
+ version_str = re.sub("^\\^", "~=", version_str)
+
try:
- npm_spec = NpmSpec(strict_file_version)
- except ValueError:
+ spec = SpecifierSet(version_str)
+ except InvalidSpecifier:
raise VersionException(
- f'Version specification "{raw_file_version}" is not a valid NPM semantic '
- f"version specification",
- start,
+ f'Version specification "{version_str}" is not a valid PEP440 specifier', start
)
- if not npm_spec.match(strict_compiler_version):
+ if not spec.contains(__version__, prereleases=True):
raise VersionException(
- f'Version specification "{raw_file_version}" is not compatible '
- f'with compiler version "{installed_version}"',
+ f'Version specification "{version_str}" is not compatible '
+ f'with compiler version "{__version__}"',
start,
)
@@ -66,12 +50,12 @@ def validate_version_pragma(version_str: str, start: ParserPosition) -> None:
VYPER_EXPRESSION_TYPES = {"log"}
-def pre_parse(code: str) -> Tuple[ModificationOffsets, str]:
+def pre_parse(code: str) -> tuple[Settings, ModificationOffsets, str]:
"""
Re-formats a vyper source string into a python source string and performs
some validation. More specifically,
- * Translates "interface", "struct" and "event" keywords into python "class" keyword
+ * Translates "interface", "struct", "enum, and "event" keywords into python "class" keyword
* Validates "@version" pragma against current compiler version
* Prevents direct use of python "class" keyword
* Prevents use of python semi-colon statement separator
@@ -93,6 +77,7 @@ def pre_parse(code: str) -> Tuple[ModificationOffsets, str]:
"""
result = []
modification_offsets: ModificationOffsets = {}
+ settings = Settings()
try:
code_bytes = code.encode("utf-8")
@@ -108,8 +93,42 @@ def pre_parse(code: str) -> Tuple[ModificationOffsets, str]:
end = token.end
line = token.line
- if typ == COMMENT and "@version" in string:
- validate_version_pragma(string[1:], start)
+ if typ == COMMENT:
+ contents = string[1:].strip()
+ if contents.startswith("@version"):
+ if settings.compiler_version is not None:
+ raise StructureException("compiler version specified twice!", start)
+ compiler_version = contents.removeprefix("@version ").strip()
+ validate_version_pragma(compiler_version, start)
+ settings.compiler_version = compiler_version
+
+ if contents.startswith("pragma "):
+ pragma = contents.removeprefix("pragma ").strip()
+ if pragma.startswith("version "):
+ if settings.compiler_version is not None:
+ raise StructureException("pragma version specified twice!", start)
+ compiler_version = pragma.removeprefix("version ").strip()
+ validate_version_pragma(compiler_version, start)
+ settings.compiler_version = compiler_version
+
+ elif pragma.startswith("optimize "):
+ if settings.optimize is not None:
+ raise StructureException("pragma optimize specified twice!", start)
+ try:
+ mode = pragma.removeprefix("optimize").strip()
+ settings.optimize = OptimizationLevel.from_string(mode)
+ except ValueError:
+ raise StructureException(f"Invalid optimization mode `{mode}`", start)
+ elif pragma.startswith("evm-version "):
+ if settings.evm_version is not None:
+ raise StructureException("pragma evm-version specified twice!", start)
+ evm_version = pragma.removeprefix("evm-version").strip()
+ if evm_version not in EVM_VERSIONS:
+ raise StructureException("Invalid evm version: `{evm_version}`", start)
+ settings.evm_version = evm_version
+
+ else:
+ raise StructureException(f"Unknown pragma `{pragma.split()[0]}`")
if typ == NAME and string in ("class", "yield"):
raise SyntaxException(
@@ -130,4 +149,4 @@ def pre_parse(code: str) -> Tuple[ModificationOffsets, str]:
except TokenError as e:
raise SyntaxException(e.args[0], code, e.args[1][0], e.args[1][1]) from e
- return modification_offsets, untokenize(result).decode("utf-8")
+ return settings, modification_offsets, untokenize(result).decode("utf-8")
diff --git a/vyper/ast/signatures/__init__.py b/vyper/ast/signatures/__init__.py
deleted file mode 100644
index aa9a5c66c6..0000000000
--- a/vyper/ast/signatures/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-from .function_signature import FrameInfo, FunctionSignature
diff --git a/vyper/ast/signatures/function_signature.py b/vyper/ast/signatures/function_signature.py
deleted file mode 100644
index 3f59b95f97..0000000000
--- a/vyper/ast/signatures/function_signature.py
+++ /dev/null
@@ -1,194 +0,0 @@
-from dataclasses import dataclass
-from functools import cached_property
-from typing import Dict, Optional, Tuple
-
-from vyper import ast as vy_ast
-from vyper.exceptions import CompilerPanic, StructureException
-from vyper.semantics.types import VyperType
-from vyper.utils import MemoryPositions, mkalphanum
-
-# dict from function names to signatures
-FunctionSignatures = Dict[str, "FunctionSignature"]
-
-
-@dataclass
-class FunctionArg:
- name: str
- typ: VyperType
- ast_source: vy_ast.VyperNode
-
-
-@dataclass
-class FrameInfo:
- frame_start: int
- frame_size: int
- frame_vars: Dict[str, Tuple[int, VyperType]]
-
- @property
- def mem_used(self):
- return self.frame_size + MemoryPositions.RESERVED_MEMORY
-
-
-# Function signature object
-# TODO: merge with ContractFunction type
-class FunctionSignature:
- def __init__(
- self,
- name,
- args,
- return_type,
- mutability,
- internal,
- nonreentrant_key,
- func_ast_code,
- is_from_json,
- ):
- self.name = name
- self.args = args
- self.return_type = return_type
- self.mutability = mutability
- self.internal = internal
- self.gas_estimate = None
- self.nonreentrant_key = nonreentrant_key
- self.func_ast_code = func_ast_code
- self.is_from_json = is_from_json
-
- self.set_default_args()
-
- # frame info is metadata that will be generated during codegen.
- self.frame_info: Optional[FrameInfo] = None
-
- def __str__(self):
- input_name = "def " + self.name + "(" + ",".join([str(arg.typ) for arg in self.args]) + ")"
- if self.return_type:
- return input_name + " -> " + str(self.return_type) + ":"
- return input_name + ":"
-
- def set_frame_info(self, frame_info):
- if self.frame_info is not None:
- raise CompilerPanic("sig.frame_info already set!")
- self.frame_info = frame_info
-
- @cached_property
- def _ir_identifier(self) -> str:
- # we could do a bit better than this but it just needs to be unique
- visibility = "internal" if self.internal else "external"
- argz = ",".join([str(arg.typ) for arg in self.args])
- ret = f"{visibility} {self.name} ({argz})"
- return mkalphanum(ret)
-
- # calculate the abi signature for a given set of kwargs
- def abi_signature_for_kwargs(self, kwargs):
- args = self.base_args + kwargs
- return self.name + "(" + ",".join([arg.typ.abi_type.selector_name() for arg in args]) + ")"
-
- @cached_property
- def base_signature(self):
- return self.abi_signature_for_kwargs([])
-
- @property
- # common entry point for external function with kwargs
- def external_function_base_entry_label(self):
- assert not self.internal
-
- return self._ir_identifier + "_common"
-
- @property
- def internal_function_label(self):
- assert self.internal, "why are you doing this"
-
- return self._ir_identifier
-
- @property
- def exit_sequence_label(self):
- return self._ir_identifier + "_cleanup"
-
- def set_default_args(self):
- """Split base from kwargs and set member data structures"""
-
- args = self.func_ast_code.args
-
- defaults = getattr(args, "defaults", [])
- num_base_args = len(args.args) - len(defaults)
-
- self.base_args = self.args[:num_base_args]
- self.default_args = self.args[num_base_args:]
-
- # Keep all the value to assign to default parameters.
- self.default_values = dict(zip([arg.name for arg in self.default_args], defaults))
-
- # Get a signature from a function definition
- @classmethod
- def from_definition(
- cls,
- func_ast, # vy_ast.FunctionDef
- global_ctx,
- interface_def=False,
- constant_override=False, # CMC 20210907 what does this do?
- is_from_json=False,
- ):
- name = func_ast.name
-
- args = []
- for arg in func_ast.args.args:
- argname = arg.arg
- argtyp = global_ctx.parse_type(arg.annotation)
-
- args.append(FunctionArg(argname, argtyp, arg))
-
- mutability = "nonpayable" # Assume nonpayable by default
- nonreentrant_key = None
- is_internal = None
-
- # Update function properties from decorators
- # NOTE: Can't import enums here because of circular import
- for dec in func_ast.decorator_list:
- if isinstance(dec, vy_ast.Name) and dec.id in ("payable", "view", "pure"):
- mutability = dec.id
- elif isinstance(dec, vy_ast.Name) and dec.id == "internal":
- is_internal = True
- elif isinstance(dec, vy_ast.Name) and dec.id == "external":
- is_internal = False
- elif isinstance(dec, vy_ast.Call) and dec.func.id == "nonreentrant":
- nonreentrant_key = dec.args[0].s
-
- if constant_override:
- # In case this override is abused, match previous behavior
- if mutability == "payable":
- raise StructureException(f"Function {name} cannot be both constant and payable.")
- mutability = "view"
-
- # Determine the return type and whether or not it's constant. Expects something
- # of the form:
- # def foo(): ...
- # def foo() -> int128: ...
- # If there is no return type, ie. it's of the form def foo(): ...
- # and NOT def foo() -> type: ..., then it's null
- return_type = None
- if func_ast.returns:
- return_type = global_ctx.parse_type(func_ast.returns)
- # sanity check: Output type must be canonicalizable
- assert return_type.abi_type.selector_name()
-
- return cls(
- name,
- args,
- return_type,
- mutability,
- is_internal,
- nonreentrant_key,
- func_ast,
- is_from_json,
- )
-
- @property
- def is_default_func(self):
- return self.name == "__default__"
-
- @property
- def is_init_func(self):
- return self.name == "__init__"
-
- @property
- def is_regular_function(self):
- return not self.is_default_func and not self.is_init_func
diff --git a/vyper/ast/utils.py b/vyper/ast/utils.py
index fc8aad227c..4e669385ab 100644
--- a/vyper/ast/utils.py
+++ b/vyper/ast/utils.py
@@ -1,18 +1,23 @@
import ast as python_ast
-from typing import Dict, List, Optional, Union
+from typing import Any, Dict, List, Optional, Union
from vyper.ast import nodes as vy_ast
from vyper.ast.annotation import annotate_python_ast
from vyper.ast.pre_parser import pre_parse
+from vyper.compiler.settings import Settings
from vyper.exceptions import CompilerPanic, ParserException, SyntaxException
-def parse_to_ast(
+def parse_to_ast(*args: Any, **kwargs: Any) -> vy_ast.Module:
+ return parse_to_ast_with_settings(*args, **kwargs)[1]
+
+
+def parse_to_ast_with_settings(
source_code: str,
source_id: int = 0,
contract_name: Optional[str] = None,
add_fn_node: Optional[str] = None,
-) -> vy_ast.Module:
+) -> tuple[Settings, vy_ast.Module]:
"""
Parses a Vyper source string and generates basic Vyper AST nodes.
@@ -34,7 +39,7 @@ def parse_to_ast(
"""
if "\x00" in source_code:
raise ParserException("No null bytes (\\x00) allowed in the source code.")
- class_types, reformatted_code = pre_parse(source_code)
+ settings, class_types, reformatted_code = pre_parse(source_code)
try:
py_ast = python_ast.parse(reformatted_code)
except SyntaxError as e:
@@ -51,7 +56,9 @@ def parse_to_ast(
annotate_python_ast(py_ast, source_code, class_types, source_id, contract_name)
# Convert to Vyper AST.
- return vy_ast.get_node(py_ast) # type: ignore
+ module = vy_ast.get_node(py_ast)
+ assert isinstance(module, vy_ast.Module) # mypy hint
+ return settings, module
def ast_to_dict(ast_struct: Union[vy_ast.VyperNode, List]) -> Union[Dict, List]:
diff --git a/vyper/ast/validation.py b/vyper/ast/validation.py
index 7742d60c01..36a6a0484c 100644
--- a/vyper/ast/validation.py
+++ b/vyper/ast/validation.py
@@ -48,7 +48,7 @@ def validate_call_args(
arg_count = (arg_count[0], 2**64)
if arg_count[0] == arg_count[1]:
- arg_count == arg_count[0]
+ arg_count = arg_count[0]
if isinstance(node.func, vy_ast.Attribute):
msg = f" for call to '{node.func.attr}'"
diff --git a/vyper/builtins/_convert.py b/vyper/builtins/_convert.py
index 407a32f3e9..e09f5f3174 100644
--- a/vyper/builtins/_convert.py
+++ b/vyper/builtins/_convert.py
@@ -267,7 +267,7 @@ def _literal_decimal(expr, arg_typ, out_typ):
def to_bool(expr, arg, out_typ):
_check_bytes(expr, arg, out_typ, 32) # should we restrict to Bytes[1]?
- if isinstance(arg.typ, BytesT):
+ if isinstance(arg.typ, _BytestringT):
# no clamp. checks for any nonzero bytes.
arg = _bytes_to_num(arg, out_typ, signed=False)
@@ -453,13 +453,13 @@ def to_enum(expr, arg, out_typ):
def convert(expr, context):
- if len(expr.args) != 2:
- raise StructureException("The convert function expects two parameters.", expr)
+ assert len(expr.args) == 2, "bad typecheck: convert"
arg_ast = expr.args[0]
arg = Expr(arg_ast, context).ir_node
original_arg = arg
- out_typ = context.parse_type(expr.args[1])
+
+ out_typ = expr.args[1]._metadata["type"].typedef
if arg.typ._is_prim_word:
arg = unwrap_location(arg)
diff --git a/vyper/builtins/_signatures.py b/vyper/builtins/_signatures.py
index d39a4a085f..2802421129 100644
--- a/vyper/builtins/_signatures.py
+++ b/vyper/builtins/_signatures.py
@@ -74,7 +74,7 @@ def decorator_fn(self, node, context):
return decorator_fn
-class BuiltinFunction:
+class BuiltinFunction(VyperType):
_has_varargs = False
_kwargs: Dict[str, KwargSettings] = {}
diff --git a/vyper/builtins/_utils.py b/vyper/builtins/_utils.py
index 77185f6e53..afc0987b6d 100644
--- a/vyper/builtins/_utils.py
+++ b/vyper/builtins/_utils.py
@@ -23,7 +23,7 @@ def generate_inline_function(code, variables, variables_2, memory_allocator):
# `ContractFunctionT` type to rely on the annotation visitors in semantics
# module.
ast_code.body[0]._metadata["type"] = ContractFunctionT(
- "sqrt_builtin", {}, 0, 0, None, FunctionVisibility.INTERNAL, StateMutability.NONPAYABLE
+ "sqrt_builtin", [], [], None, FunctionVisibility.INTERNAL, StateMutability.NONPAYABLE
)
# The FunctionNodeVisitor's constructor performs semantic checks
# annotate the AST as side effects.
diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py
index ee40f44a6a..8acce0586a 100644
--- a/vyper/builtins/functions.py
+++ b/vyper/builtins/functions.py
@@ -5,7 +5,6 @@
from vyper import ast as vy_ast
from vyper.abi_types import ABI_Tuple
-from vyper.address_space import MEMORY, STORAGE
from vyper.ast.validation import validate_call_args
from vyper.codegen.abi_encoder import abi_encode
from vyper.codegen.context import Context, VariableRecord
@@ -22,14 +21,14 @@
clamp_basetype,
clamp_nonzero,
copy_bytes,
+ dummy_node_for_type,
ensure_in_memory,
eval_once_check,
eval_seq,
get_bytearray_length,
- get_element_ptr,
get_type_for_exact_size,
ir_tuple_from_args,
- needs_external_call_wrap,
+ make_setter,
promote_signed_int,
sar,
shl,
@@ -37,8 +36,9 @@
unwrap_location,
)
from vyper.codegen.expr import Expr
-from vyper.codegen.ir_node import Encoding
+from vyper.codegen.ir_node import Encoding, scope_multi
from vyper.codegen.keccak256_helper import keccak256_helper
+from vyper.evm.address_space import MEMORY, STORAGE
from vyper.exceptions import (
ArgumentException,
CompilerPanic,
@@ -148,15 +148,18 @@ def evaluate(self, node):
@process_inputs
def build_IR(self, expr, args, kwargs, context):
- return IRnode.from_list(
- [
- "if",
- ["slt", args[0], 0],
- ["sdiv", ["sub", args[0], DECIMAL_DIVISOR - 1], DECIMAL_DIVISOR],
- ["sdiv", args[0], DECIMAL_DIVISOR],
- ],
- typ=INT256_T,
- )
+ arg = args[0]
+ with arg.cache_when_complex("arg") as (b1, arg):
+ ret = IRnode.from_list(
+ [
+ "if",
+ ["slt", arg, 0],
+ ["sdiv", ["sub", arg, DECIMAL_DIVISOR - 1], DECIMAL_DIVISOR],
+ ["sdiv", arg, DECIMAL_DIVISOR],
+ ],
+ typ=INT256_T,
+ )
+ return b1.resolve(ret)
class Ceil(BuiltinFunction):
@@ -175,15 +178,18 @@ def evaluate(self, node):
@process_inputs
def build_IR(self, expr, args, kwargs, context):
- return IRnode.from_list(
- [
- "if",
- ["slt", args[0], 0],
- ["sdiv", args[0], DECIMAL_DIVISOR],
- ["sdiv", ["add", args[0], DECIMAL_DIVISOR - 1], DECIMAL_DIVISOR],
- ],
- typ=INT256_T,
- )
+ arg = args[0]
+ with arg.cache_when_complex("arg") as (b1, arg):
+ ret = IRnode.from_list(
+ [
+ "if",
+ ["slt", arg, 0],
+ ["sdiv", arg, DECIMAL_DIVISOR],
+ ["sdiv", ["add", arg, DECIMAL_DIVISOR - 1], DECIMAL_DIVISOR],
+ ],
+ typ=INT256_T,
+ )
+ return b1.resolve(ret)
class Convert(BuiltinFunction):
@@ -467,6 +473,12 @@ def evaluate(self, node):
return vy_ast.Int.from_node(node, value=length)
+ def infer_arg_types(self, node):
+ self._validate_arg_types(node)
+ # return a concrete type
+ typ = get_possible_types_from_node(node.args[0]).pop()
+ return [typ]
+
def build_IR(self, node, context):
arg = Expr(node.args[0], context).ir_node
if arg.value == "~calldata":
@@ -609,7 +621,7 @@ def infer_arg_types(self, node):
@process_inputs
def build_IR(self, expr, args, kwargs, context):
assert len(args) == 1
- return keccak256_helper(expr, args[0], context)
+ return keccak256_helper(args[0], context)
def _make_sha256_call(inp_start, inp_len, out_start, out_len):
@@ -756,87 +768,68 @@ def infer_arg_types(self, node):
@process_inputs
def build_IR(self, expr, args, kwargs, context):
- placeholder_node = IRnode.from_list(
- context.new_internal_variable(BytesT(128)), typ=BytesT(128), location=MEMORY
- )
+ input_buf = context.new_internal_variable(get_type_for_exact_size(128))
+ output_buf = context.new_internal_variable(get_type_for_exact_size(32))
return IRnode.from_list(
[
"seq",
- ["mstore", placeholder_node, args[0]],
- ["mstore", ["add", placeholder_node, 32], args[1]],
- ["mstore", ["add", placeholder_node, 64], args[2]],
- ["mstore", ["add", placeholder_node, 96], args[3]],
- [
- "pop",
- [
- "staticcall",
- ["gas"],
- 1,
- placeholder_node,
- 128,
- MemoryPositions.FREE_VAR_SPACE,
- 32,
- ],
- ],
- ["mload", MemoryPositions.FREE_VAR_SPACE],
+ # clear output memory first, ecrecover can return 0 bytes
+ ["mstore", output_buf, 0],
+ ["mstore", input_buf, args[0]],
+ ["mstore", input_buf + 32, args[1]],
+ ["mstore", input_buf + 64, args[2]],
+ ["mstore", input_buf + 96, args[3]],
+ ["staticcall", "gas", 1, input_buf, 128, output_buf, 32],
+ ["mload", output_buf],
],
typ=AddressT(),
)
-def _getelem(arg, ind):
- return unwrap_location(get_element_ptr(arg, IRnode.from_list(ind, typ=INT128_T)))
+class _ECArith(BuiltinFunction):
+ @process_inputs
+ def build_IR(self, expr, _args, kwargs, context):
+ args_tuple = ir_tuple_from_args(_args)
+ args_t = args_tuple.typ
+ input_buf = IRnode.from_list(
+ context.new_internal_variable(args_t), typ=args_t, location=MEMORY
+ )
+ ret_t = self._return_type
-class ECAdd(BuiltinFunction):
- _id = "ecadd"
- _inputs = [("a", SArrayT(UINT256_T, 2)), ("b", SArrayT(UINT256_T, 2))]
- _return_type = SArrayT(UINT256_T, 2)
+ ret = ["seq"]
+ ret.append(make_setter(input_buf, args_tuple))
- @process_inputs
- def build_IR(self, expr, args, kwargs, context):
- placeholder_node = IRnode.from_list(
- context.new_internal_variable(BytesT(128)), typ=BytesT(128), location=MEMORY
- )
- o = IRnode.from_list(
+ output_buf = context.new_internal_variable(ret_t)
+
+ args_ofst = input_buf
+ args_len = args_t.memory_bytes_required
+ out_ofst = output_buf
+ out_len = ret_t.memory_bytes_required
+
+ ret.append(
[
- "seq",
- ["mstore", placeholder_node, _getelem(args[0], 0)],
- ["mstore", ["add", placeholder_node, 32], _getelem(args[0], 1)],
- ["mstore", ["add", placeholder_node, 64], _getelem(args[1], 0)],
- ["mstore", ["add", placeholder_node, 96], _getelem(args[1], 1)],
- ["assert", ["staticcall", ["gas"], 6, placeholder_node, 128, placeholder_node, 64]],
- placeholder_node,
- ],
- typ=SArrayT(UINT256_T, 2),
- location=MEMORY,
+ "assert",
+ ["staticcall", ["gas"], self._precompile, args_ofst, args_len, out_ofst, out_len],
+ ]
)
- return o
+ ret.append(output_buf)
+ return IRnode.from_list(ret, typ=ret_t, location=MEMORY)
-class ECMul(BuiltinFunction):
+
+class ECAdd(_ECArith):
+ _id = "ecadd"
+ _inputs = [("a", SArrayT(UINT256_T, 2)), ("b", SArrayT(UINT256_T, 2))]
+ _return_type = SArrayT(UINT256_T, 2)
+ _precompile = 0x6
+
+
+class ECMul(_ECArith):
_id = "ecmul"
_inputs = [("point", SArrayT(UINT256_T, 2)), ("scalar", UINT256_T)]
_return_type = SArrayT(UINT256_T, 2)
-
- @process_inputs
- def build_IR(self, expr, args, kwargs, context):
- placeholder_node = IRnode.from_list(
- context.new_internal_variable(BytesT(128)), typ=BytesT(128), location=MEMORY
- )
- o = IRnode.from_list(
- [
- "seq",
- ["mstore", placeholder_node, _getelem(args[0], 0)],
- ["mstore", ["add", placeholder_node, 32], _getelem(args[0], 1)],
- ["mstore", ["add", placeholder_node, 64], args[1]],
- ["assert", ["staticcall", ["gas"], 7, placeholder_node, 96, placeholder_node, 64]],
- placeholder_node,
- ],
- typ=SArrayT(UINT256_T, 2),
- location=MEMORY,
- )
- return o
+ _precompile = 0x7
def _generic_element_getter(op):
@@ -1028,34 +1021,35 @@ def build_IR(self, expr, args, kwargs, context):
value = args[0]
denom_divisor = self.get_denomination(expr)
- if value.typ in (UINT256_T, UINT8_T):
- sub = [
- "with",
- "ans",
- ["mul", value, denom_divisor],
- [
- "seq",
+ with value.cache_when_complex("value") as (b1, value):
+ if value.typ in (UINT256_T, UINT8_T):
+ sub = [
+ "with",
+ "ans",
+ ["mul", value, denom_divisor],
[
- "assert",
- ["or", ["eq", ["div", "ans", value], denom_divisor], ["iszero", value]],
+ "seq",
+ [
+ "assert",
+ ["or", ["eq", ["div", "ans", value], denom_divisor], ["iszero", value]],
+ ],
+ "ans",
],
- "ans",
- ],
- ]
- elif value.typ == INT128_T:
- # signed types do not require bounds checks because the
- # largest possible converted value will not overflow 2**256
- sub = ["seq", ["assert", ["sgt", value, -1]], ["mul", value, denom_divisor]]
- elif value.typ == DecimalT():
- sub = [
- "seq",
- ["assert", ["sgt", value, -1]],
- ["div", ["mul", value, denom_divisor], DECIMAL_DIVISOR],
- ]
- else:
- raise CompilerPanic(f"Unexpected type: {value.typ}")
+ ]
+ elif value.typ == INT128_T:
+ # signed types do not require bounds checks because the
+ # largest possible converted value will not overflow 2**256
+ sub = ["seq", ["assert", ["sgt", value, -1]], ["mul", value, denom_divisor]]
+ elif value.typ == DecimalT():
+ sub = [
+ "seq",
+ ["assert", ["sgt", value, -1]],
+ ["div", ["mul", value, denom_divisor], DECIMAL_DIVISOR],
+ ]
+ else:
+ raise CompilerPanic(f"Unexpected type: {value.typ}")
- return IRnode.from_list(sub, typ=UINT256_T)
+ return IRnode.from_list(b1.resolve(sub), typ=UINT256_T)
zero_value = IRnode.from_list(0, typ=UINT256_T)
@@ -1084,7 +1078,7 @@ def fetch_call_return(self, node):
revert_on_failure = kwargz.get("revert_on_failure")
revert_on_failure = revert_on_failure.value if revert_on_failure is not None else True
- if outsize is None:
+ if outsize is None or outsize.value == 0:
if revert_on_failure:
return None
return BoolT()
@@ -1165,14 +1159,17 @@ def build_IR(self, expr, args, kwargs, context):
outsize,
]
- if delegate_call:
- call_op = ["delegatecall", gas, to, *common_call_args]
- elif static_call:
- call_op = ["staticcall", gas, to, *common_call_args]
- else:
- call_op = ["call", gas, to, value, *common_call_args]
+ gas, value = IRnode.from_list(gas), IRnode.from_list(value)
+ with scope_multi((to, value, gas), ("_to", "_value", "_gas")) as (b1, (to, value, gas)):
+ if delegate_call:
+ call_op = ["delegatecall", gas, to, *common_call_args]
+ elif static_call:
+ call_op = ["staticcall", gas, to, *common_call_args]
+ else:
+ call_op = ["call", gas, to, value, *common_call_args]
- call_ir += [call_op]
+ call_ir += [call_op]
+ call_ir = b1.resolve(call_ir)
# build sequence IR
if outsize:
@@ -1228,7 +1225,9 @@ def build_IR(self, expr, args, kwargs, context):
to, value = args
gas = kwargs["gas"]
context.check_is_not_constant("send ether", expr)
- return IRnode.from_list(["assert", ["call", gas, to, value, 0, 0, 0, 0]])
+ return IRnode.from_list(
+ ["assert", ["call", gas, to, value, 0, 0, 0, 0]], error_msg="send failed"
+ )
class SelfDestruct(BuiltinFunction):
@@ -1349,7 +1348,7 @@ def evaluate(self, node):
validate_call_args(node, 2)
for arg in node.args:
- if not isinstance(arg, vy_ast.Num):
+ if not isinstance(arg, vy_ast.Int):
raise UnfoldableNode
if arg.value < 0 or arg.value >= 2**256:
raise InvalidLiteral("Value out of range for uint256", arg)
@@ -1375,7 +1374,7 @@ def evaluate(self, node):
validate_call_args(node, 2)
for arg in node.args:
- if not isinstance(arg, vy_ast.Num):
+ if not isinstance(arg, vy_ast.Int):
raise UnfoldableNode
if arg.value < 0 or arg.value >= 2**256:
raise InvalidLiteral("Value out of range for uint256", arg)
@@ -1401,7 +1400,7 @@ def evaluate(self, node):
validate_call_args(node, 2)
for arg in node.args:
- if not isinstance(arg, vy_ast.Num):
+ if not isinstance(arg, vy_ast.Int):
raise UnfoldableNode
if arg.value < 0 or arg.value >= 2**256:
raise InvalidLiteral("Value out of range for uint256", arg)
@@ -1422,11 +1421,11 @@ class BitwiseNot(BuiltinFunction):
def evaluate(self, node):
if not self.__class__._warned:
- vyper_warn("`bitwise_not()` is deprecated! Please use the ^ operator instead.")
+ vyper_warn("`bitwise_not()` is deprecated! Please use the ~ operator instead.")
self.__class__._warned = True
validate_call_args(node, 1)
- if not isinstance(node.args[0], vy_ast.Num):
+ if not isinstance(node.args[0], vy_ast.Int):
raise UnfoldableNode
value = node.args[0].value
@@ -1453,7 +1452,7 @@ def evaluate(self, node):
self.__class__._warned = True
validate_call_args(node, 2)
- if [i for i in node.args if not isinstance(i, vy_ast.Num)]:
+ if [i for i in node.args if not isinstance(i, vy_ast.Int)]:
raise UnfoldableNode
value, shift = [i.value for i in node.args]
if value < 0 or value >= 2**256:
@@ -1501,10 +1500,10 @@ class _AddMulMod(BuiltinFunction):
def evaluate(self, node):
validate_call_args(node, 3)
- if isinstance(node.args[2], vy_ast.Num) and node.args[2].value == 0:
+ if isinstance(node.args[2], vy_ast.Int) and node.args[2].value == 0:
raise ZeroDivisionException("Modulo by 0", node.args[2])
for arg in node.args:
- if not isinstance(arg, vy_ast.Num):
+ if not isinstance(arg, vy_ast.Int):
raise UnfoldableNode
if arg.value < 0 or arg.value >= 2**256:
raise InvalidLiteral("Value out of range for uint256", arg)
@@ -1514,9 +1513,14 @@ def evaluate(self, node):
@process_inputs
def build_IR(self, expr, args, kwargs, context):
- return IRnode.from_list(
- ["seq", ["assert", args[2]], [self._opcode, args[0], args[1], args[2]]], typ=UINT256_T
- )
+ x, y, z = args
+ with x.cache_when_complex("x") as (b1, x):
+ with y.cache_when_complex("y") as (b2, y):
+ with z.cache_when_complex("z") as (b3, z):
+ ret = IRnode.from_list(
+ ["seq", ["assert", z], [self._opcode, x, y, z]], typ=UINT256_T
+ )
+ return b1.resolve(b2.resolve(b3.resolve(ret)))
class AddMod(_AddMulMod):
@@ -1592,13 +1596,15 @@ def build_IR(self, expr, context):
# CREATE* functions
+CREATE2_SENTINEL = dummy_node_for_type(BYTES32_T)
+
# create helper functions
# generates CREATE op sequence + zero check for result
-def _create_ir(value, buf, length, salt=None, checked=True):
+def _create_ir(value, buf, length, salt, checked=True):
args = [value, buf, length]
create_op = "create"
- if salt is not None:
+ if salt is not CREATE2_SENTINEL:
create_op = "create2"
args.append(salt)
@@ -1609,7 +1615,9 @@ def _create_ir(value, buf, length, salt=None, checked=True):
if not checked:
return ret
- return clamp_nonzero(ret)
+ ret = clamp_nonzero(ret)
+ ret.set_error_msg(f"{create_op} failed")
+ return ret
# calculate the gas used by create for a given number of bytes
@@ -1714,8 +1722,9 @@ def build_IR(self, expr, args, kwargs, context):
context.check_is_not_constant("use {self._id}", expr)
should_use_create2 = "salt" in [kwarg.arg for kwarg in expr.keywords]
+
if not should_use_create2:
- kwargs["salt"] = None
+ kwargs["salt"] = CREATE2_SENTINEL
ir_builder = self._build_create_IR(expr, args, context, **kwargs)
@@ -1795,17 +1804,23 @@ def _add_gas_estimate(self, args, should_use_create2):
def _build_create_IR(self, expr, args, context, value, salt):
target = args[0]
- with target.cache_when_complex("create_target") as (b1, target):
+ # something we can pass to scope_multi
+ with scope_multi(
+ (target, value, salt), ("create_target", "create_value", "create_salt")
+ ) as (b1, (target, value, salt)):
codesize = IRnode.from_list(["extcodesize", target])
msize = IRnode.from_list(["msize"])
- with codesize.cache_when_complex("target_codesize") as (
+ with scope_multi((codesize, msize), ("target_codesize", "mem_ofst")) as (
b2,
- codesize,
- ), msize.cache_when_complex("mem_ofst") as (b3, mem_ofst):
+ (codesize, mem_ofst),
+ ):
ir = ["seq"]
# make sure there is actually code at the target
- ir.append(["assert", codesize])
+ check_codesize = ["assert", codesize]
+ ir.append(
+ IRnode.from_list(check_codesize, error_msg="empty target (create_copy_of)")
+ )
# store the preamble at msize + 22 (zero padding)
preamble, preamble_len = _create_preamble(codesize)
@@ -1822,7 +1837,7 @@ def _build_create_IR(self, expr, args, context, value, salt):
ir.append(_create_ir(value, buf, buf_len, salt))
- return b1.resolve(b2.resolve(b3.resolve(ir)))
+ return b1.resolve(b2.resolve(ir))
class CreateFromBlueprint(_CreateBase):
@@ -1875,17 +1890,18 @@ def _build_create_IR(self, expr, args, context, value, salt, code_offset, raw_ar
# (since the abi encoder could write to fresh memory).
# it would be good to not require the memory copy, but need
# to evaluate memory safety.
- with target.cache_when_complex("create_target") as (b1, target), argslen.cache_when_complex(
- "encoded_args_len"
- ) as (b2, encoded_args_len), code_offset.cache_when_complex("code_ofst") as (b3, codeofst):
- codesize = IRnode.from_list(["sub", ["extcodesize", target], codeofst])
+ with scope_multi(
+ (target, value, salt, argslen, code_offset),
+ ("create_target", "create_value", "create_salt", "encoded_args_len", "code_offset"),
+ ) as (b1, (target, value, salt, encoded_args_len, code_offset)):
+ codesize = IRnode.from_list(["sub", ["extcodesize", target], code_offset])
# copy code to memory starting from msize. we are clobbering
# unused memory so it's safe.
msize = IRnode.from_list(["msize"], location=MEMORY)
- with codesize.cache_when_complex("target_codesize") as (
- b4,
- codesize,
- ), msize.cache_when_complex("mem_ofst") as (b5, mem_ofst):
+ with scope_multi((codesize, msize), ("target_codesize", "mem_ofst")) as (
+ b2,
+ (codesize, mem_ofst),
+ ):
ir = ["seq"]
# make sure there is code at the target, and that
@@ -1895,13 +1911,17 @@ def _build_create_IR(self, expr, args, context, value, salt, code_offset, raw_ar
# (code_ofst == (extcodesize target) would be empty
# initcode, which we disallow for hygiene reasons -
# same as `create_copy_of` on an empty target).
- ir.append(["assert", ["sgt", codesize, 0]])
+ check_codesize = ["assert", ["sgt", codesize, 0]]
+ ir.append(
+ IRnode.from_list(
+ check_codesize, error_msg="empty target (create_from_blueprint)"
+ )
+ )
# copy the target code into memory.
# layout starting from mem_ofst:
- # 00...00 (22 0's) | preamble | bytecode
- ir.append(["extcodecopy", target, mem_ofst, codeofst, codesize])
-
+ # |
+ ir.append(["extcodecopy", target, mem_ofst, code_offset, codesize])
ir.append(copy_bytes(add_ofst(mem_ofst, codesize), argbuf, encoded_args_len, bufsz))
# theoretically, dst = "msize", but just be safe.
@@ -1915,7 +1935,7 @@ def _build_create_IR(self, expr, args, context, value, salt, code_offset, raw_ar
ir.append(_create_ir(value, mem_ofst, length, salt))
- return b1.resolve(b2.resolve(b3.resolve(b4.resolve(b5.resolve(ir)))))
+ return b1.resolve(b2.resolve(ir))
class _UnsafeMath(BuiltinFunction):
@@ -2350,8 +2370,6 @@ def build_IR(self, expr, args, kwargs, context):
class ABIEncode(BuiltinFunction):
_id = "_abi_encode" # TODO prettier to rename this to abi.encode
# signature: *, ensure_tuple= -> Bytes[]
- # (check the signature manually since we have no utility methods
- # to handle varargs.)
# explanation of ensure_tuple:
# default is to force even a single value into a tuple,
# e.g. _abi_encode(bytes) -> _abi_encode((bytes,))
@@ -2476,6 +2494,8 @@ def fetch_call_return(self, node):
return output_type.typedef
def infer_arg_types(self, node):
+ self._validate_arg_types(node)
+
validate_call_args(node, 2, ["unwrap_tuple"])
data_type = get_exact_type_from_node(node.args[0])
@@ -2512,24 +2532,11 @@ def build_IR(self, expr, args, kwargs, context):
)
data = ensure_in_memory(data, context)
+
with data.cache_when_complex("to_decode") as (b1, data):
data_ptr = bytes_data_ptr(data)
data_len = get_bytearray_length(data)
- # Normally, ABI-encoded data assumes the argument is a tuple
- # (See comments for `wrap_value_for_external_return`)
- # However, we do not want to use `wrap_value_for_external_return`
- # technique as used in external call codegen because in order to be
- # type-safe we would need an extra memory copy. To avoid a copy,
- # we manually add the ABI-dynamic offset so that it is
- # re-interpreted in-place.
- if (
- unwrap_tuple is True
- and needs_external_call_wrap(output_typ)
- and output_typ.abi_type.is_dynamic()
- ):
- data_ptr = add_ofst(data_ptr, 32)
-
ret = ["seq"]
if abi_min_size == abi_size_bound:
@@ -2538,18 +2545,30 @@ def build_IR(self, expr, args, kwargs, context):
# runtime assert: abi_min_size <= data_len <= abi_size_bound
ret.append(clamp2(abi_min_size, data_len, abi_size_bound, signed=False))
- # return pointer to the buffer
- ret.append(data_ptr)
-
- return b1.resolve(
- IRnode.from_list(
- ret,
- typ=output_typ,
- location=data.location,
- encoding=Encoding.ABI,
- annotation=f"abi_decode({output_typ})",
- )
+ to_decode = IRnode.from_list(
+ data_ptr,
+ typ=wrapped_typ,
+ location=data.location,
+ encoding=Encoding.ABI,
+ annotation=f"abi_decode({output_typ})",
)
+ to_decode.encoding = Encoding.ABI
+
+ # TODO optimization: skip make_setter when we don't need
+ # input validation
+
+ output_buf = context.new_internal_variable(wrapped_typ)
+ output = IRnode.from_list(output_buf, typ=wrapped_typ, location=MEMORY)
+
+ # sanity check buffer size for wrapped output type will not buffer overflow
+ assert wrapped_typ.memory_bytes_required == output_typ.memory_bytes_required
+ ret.append(make_setter(output, to_decode))
+
+ ret.append(output)
+ # finalize. set the type and location for the return buffer.
+ # (note: unwraps the tuple type if necessary)
+ ret = IRnode.from_list(ret, typ=output_typ, location=MEMORY)
+ return b1.resolve(ret)
class _MinMaxValue(TypenameFoldedFunction):
@@ -2568,7 +2587,6 @@ def evaluate(self, node):
if isinstance(input_type, IntegerT):
ret = vy_ast.Int.from_node(node, value=val)
- # TODO: to change to known_type once #3213 is merged
ret._metadata["type"] = input_type
return ret
diff --git a/vyper/builtins/interfaces/ERC165.py b/vyper/builtins/interfaces/ERC165.vy
similarity index 75%
rename from vyper/builtins/interfaces/ERC165.py
rename to vyper/builtins/interfaces/ERC165.vy
index 0a75431f3c..a4ca451abd 100644
--- a/vyper/builtins/interfaces/ERC165.py
+++ b/vyper/builtins/interfaces/ERC165.vy
@@ -1,6 +1,4 @@
-interface_code = """
@view
@external
def supportsInterface(interface_id: bytes4) -> bool:
pass
-"""
diff --git a/vyper/builtins/interfaces/ERC20.py b/vyper/builtins/interfaces/ERC20.vy
similarity index 96%
rename from vyper/builtins/interfaces/ERC20.py
rename to vyper/builtins/interfaces/ERC20.vy
index a63408672b..065ca97a9b 100644
--- a/vyper/builtins/interfaces/ERC20.py
+++ b/vyper/builtins/interfaces/ERC20.vy
@@ -1,4 +1,3 @@
-interface_code = """
# Events
event Transfer:
_from: indexed(address)
@@ -37,4 +36,3 @@ def transferFrom(_from: address, _to: address, _value: uint256) -> bool:
@external
def approve(_spender: address, _value: uint256) -> bool:
pass
-"""
diff --git a/vyper/builtins/interfaces/ERC20Detailed.py b/vyper/builtins/interfaces/ERC20Detailed.py
deleted file mode 100644
index 03dd597e8a..0000000000
--- a/vyper/builtins/interfaces/ERC20Detailed.py
+++ /dev/null
@@ -1,22 +0,0 @@
-"""
-NOTE: interface uses `String[1]` where 1 is the lower bound of the string returned by the function.
- For end-users this means they can't use `implements: ERC20Detailed` unless their implementation
- uses a value n >= 1. Regardless this is fine as one can't do String[0] where n == 0.
-"""
-
-interface_code = """
-@view
-@external
-def name() -> String[1]:
- pass
-
-@view
-@external
-def symbol() -> String[1]:
- pass
-
-@view
-@external
-def decimals() -> uint8:
- pass
-"""
diff --git a/vyper/builtins/interfaces/ERC20Detailed.vy b/vyper/builtins/interfaces/ERC20Detailed.vy
new file mode 100644
index 0000000000..7c4f546d45
--- /dev/null
+++ b/vyper/builtins/interfaces/ERC20Detailed.vy
@@ -0,0 +1,18 @@
+#NOTE: interface uses `String[1]` where 1 is the lower bound of the string returned by the function.
+# For end-users this means they can't use `implements: ERC20Detailed` unless their implementation
+# uses a value n >= 1. Regardless this is fine as one can't do String[0] where n == 0.
+
+@view
+@external
+def name() -> String[1]:
+ pass
+
+@view
+@external
+def symbol() -> String[1]:
+ pass
+
+@view
+@external
+def decimals() -> uint8:
+ pass
diff --git a/vyper/builtins/interfaces/ERC4626.py b/vyper/builtins/interfaces/ERC4626.vy
similarity index 98%
rename from vyper/builtins/interfaces/ERC4626.py
rename to vyper/builtins/interfaces/ERC4626.vy
index 21a9ce723a..05865406cf 100644
--- a/vyper/builtins/interfaces/ERC4626.py
+++ b/vyper/builtins/interfaces/ERC4626.vy
@@ -1,4 +1,3 @@
-interface_code = """
# Events
event Deposit:
sender: indexed(address)
@@ -89,4 +88,3 @@ def previewRedeem(shares: uint256) -> uint256:
@external
def redeem(shares: uint256, receiver: address=msg.sender, owner: address=msg.sender) -> uint256:
pass
-"""
diff --git a/vyper/builtins/interfaces/ERC721.py b/vyper/builtins/interfaces/ERC721.vy
similarity index 80%
rename from vyper/builtins/interfaces/ERC721.py
rename to vyper/builtins/interfaces/ERC721.vy
index 29ef5f4c26..464c0e255b 100644
--- a/vyper/builtins/interfaces/ERC721.py
+++ b/vyper/builtins/interfaces/ERC721.vy
@@ -1,19 +1,18 @@
-interface_code = """
# Events
event Transfer:
- _from: address
- _to: address
- _tokenId: uint256
+ _from: indexed(address)
+ _to: indexed(address)
+ _tokenId: indexed(uint256)
event Approval:
- _owner: address
- _approved: address
- _tokenId: uint256
+ _owner: indexed(address)
+ _approved: indexed(address)
+ _tokenId: indexed(uint256)
event ApprovalForAll:
- _owner: address
- _operator: address
+ _owner: indexed(address)
+ _operator: indexed(address)
_approved: bool
# Functions
@@ -66,5 +65,3 @@ def approve(_approved: address, _tokenId: uint256):
@external
def setApprovalForAll(_operator: address, _approved: bool):
pass
-
-"""
diff --git a/vyper/builtins/interfaces/__init__.py b/vyper/builtins/interfaces/__init__.py
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/vyper/cli/utils.py b/vyper/cli/utils.py
deleted file mode 100644
index 1110ecdfdd..0000000000
--- a/vyper/cli/utils.py
+++ /dev/null
@@ -1,58 +0,0 @@
-from pathlib import Path
-from typing import Sequence
-
-from vyper import ast as vy_ast
-from vyper.exceptions import StructureException
-from vyper.typing import InterfaceImports, SourceCode
-
-
-def get_interface_file_path(base_paths: Sequence, import_path: str) -> Path:
- relative_path = Path(import_path)
- for path in base_paths:
- # Find ABI JSON files
- file_path = path.joinpath(relative_path)
- suffix = next((i for i in (".vy", ".json") if file_path.with_suffix(i).exists()), None)
- if suffix:
- return file_path.with_suffix(suffix)
-
- # Find ethPM Manifest files (`from path.to.Manifest import InterfaceName`)
- # NOTE: Use file parent because this assumes that `file_path`
- # coincides with an ABI interface file
- file_path = file_path.parent
- suffix = next((i for i in (".vy", ".json") if file_path.with_suffix(i).exists()), None)
- if suffix:
- return file_path.with_suffix(suffix)
-
- raise FileNotFoundError(f" Cannot locate interface '{import_path}{{.vy,.json}}'")
-
-
-def extract_file_interface_imports(code: SourceCode) -> InterfaceImports:
- ast_tree = vy_ast.parse_to_ast(code)
-
- imports_dict: InterfaceImports = {}
- for node in ast_tree.get_children((vy_ast.Import, vy_ast.ImportFrom)):
- if isinstance(node, vy_ast.Import): # type: ignore
- if not node.alias:
- raise StructureException("Import requires an accompanying `as` statement", node)
- if node.alias in imports_dict:
- raise StructureException(f"Interface with alias {node.alias} already exists", node)
- imports_dict[node.alias] = node.name.replace(".", "/")
- elif isinstance(node, vy_ast.ImportFrom): # type: ignore
- level = node.level # type: ignore
- module = node.module or "" # type: ignore
- if not level and module == "vyper.interfaces":
- # uses a builtin interface, so skip adding to imports
- continue
-
- base_path = ""
- if level > 1:
- base_path = "../" * (level - 1)
- elif level == 1:
- base_path = "./"
- base_path = f"{base_path}{module.replace('.','/')}/"
-
- if node.name in imports_dict and imports_dict[node.name] != f"{base_path}{node.name}":
- raise StructureException(f"Interface with name {node.name} already exists", node)
- imports_dict[node.name] = f"{base_path}{node.name}"
-
- return imports_dict
diff --git a/vyper/cli/vyper_compile.py b/vyper/cli/vyper_compile.py
index 9ab884a6d0..c4f60660cb 100755
--- a/vyper/cli/vyper_compile.py
+++ b/vyper/cli/vyper_compile.py
@@ -3,17 +3,21 @@
import json
import sys
import warnings
-from collections import OrderedDict
from pathlib import Path
-from typing import Dict, Iterable, Iterator, Set, TypeVar
+from typing import Any, Iterable, Iterator, Optional, Set, TypeVar
import vyper
import vyper.codegen.ir_node as ir_node
from vyper.cli import vyper_json
-from vyper.cli.utils import extract_file_interface_imports, get_interface_file_path
-from vyper.compiler.settings import VYPER_TRACEBACK_LIMIT
+from vyper.compiler.input_bundle import FileInput, FilesystemInputBundle
+from vyper.compiler.settings import (
+ VYPER_TRACEBACK_LIMIT,
+ OptimizationLevel,
+ Settings,
+ _set_debug_mode,
+)
from vyper.evm.opcodes import DEFAULT_EVM_VERSION, EVM_VERSIONS
-from vyper.typing import ContractCodes, ContractPath, OutputFormats
+from vyper.typing import ContractPath, OutputFormats
T = TypeVar("T")
@@ -36,9 +40,9 @@
opcodes_runtime - List of runtime opcodes as a string
ir - Intermediate representation in list format
ir_json - Intermediate representation in JSON format
+ir_runtime - Intermediate representation of runtime bytecode in list format
+asm - Output the EVM assembly of the deployable bytecode
hex-ir - Output IR and assembly constants in hex instead of decimal
-no-optimize - Do not optimize (don't use this for production code)
-no-bytecode-metadata - Do not add metadata to bytecode
"""
combined_json_outputs = [
@@ -101,12 +105,18 @@ def _parse_args(argv):
)
parser.add_argument(
"--evm-version",
- help=f"Select desired EVM version (default {DEFAULT_EVM_VERSION})",
+ help=f"Select desired EVM version (default {DEFAULT_EVM_VERSION}). "
+ "note: cancun support is EXPERIMENTAL",
choices=list(EVM_VERSIONS),
- default=DEFAULT_EVM_VERSION,
dest="evm_version",
)
parser.add_argument("--no-optimize", help="Do not optimize", action="store_true")
+ parser.add_argument(
+ "--optimize",
+ help="Optimization flag (defaults to 'gas')",
+ choices=["gas", "codesize", "none"],
+ )
+ parser.add_argument("--debug", help="Compile in debug mode", action="store_true")
parser.add_argument(
"--no-bytecode-metadata", help="Do not add metadata to bytecode", action="store_true"
)
@@ -152,13 +162,31 @@ def _parse_args(argv):
output_formats = tuple(uniq(args.format.split(",")))
+ if args.debug:
+ _set_debug_mode(True)
+
+ if args.no_optimize and args.optimize:
+ raise ValueError("Cannot use `--no-optimize` and `--optimize` at the same time!")
+
+ settings = Settings()
+
+ if args.no_optimize:
+ settings.optimize = OptimizationLevel.NONE
+ elif args.optimize is not None:
+ settings.optimize = OptimizationLevel.from_string(args.optimize)
+
+ if args.evm_version:
+ settings.evm_version = args.evm_version
+
+ if args.verbose:
+ print(f"cli specified: `{settings}`", file=sys.stderr)
+
compiled = compile_files(
args.input_files,
output_formats,
args.root_folder,
args.show_gas_estimates,
- args.evm_version,
- args.no_optimize,
+ settings,
args.storage_layout,
args.no_bytecode_metadata,
)
@@ -190,95 +218,20 @@ def exc_handler(contract_path: ContractPath, exception: Exception) -> None:
raise exception
-def get_interface_codes(root_path: Path, contract_sources: ContractCodes) -> Dict:
- interface_codes: Dict = {}
- interfaces: Dict = {}
-
- for file_path, code in contract_sources.items():
- interfaces[file_path] = {}
- parent_path = root_path.joinpath(file_path).parent
-
- interface_codes = extract_file_interface_imports(code)
- for interface_name, interface_path in interface_codes.items():
- base_paths = [parent_path]
- if not interface_path.startswith(".") and root_path.joinpath(file_path).exists():
- base_paths.append(root_path)
- elif interface_path.startswith("../") and len(Path(file_path).parent.parts) < Path(
- interface_path
- ).parts.count(".."):
- raise FileNotFoundError(
- f"{file_path} - Cannot perform relative import outside of base folder"
- )
-
- valid_path = get_interface_file_path(base_paths, interface_path)
- with valid_path.open() as fh:
- code = fh.read()
- if valid_path.suffix == ".json":
- contents = json.loads(code.encode())
-
- # EthPM Manifest (EIP-2678)
- if "contractTypes" in contents:
- if (
- interface_name not in contents["contractTypes"]
- or "abi" not in contents["contractTypes"][interface_name]
- ):
- raise ValueError(
- f"Could not find interface '{interface_name}'"
- f" in manifest '{valid_path}'."
- )
-
- interfaces[file_path][interface_name] = {
- "type": "json",
- "code": contents["contractTypes"][interface_name]["abi"],
- }
-
- # ABI JSON file (either `List[ABI]` or `{"abi": List[ABI]}`)
- elif isinstance(contents, list) or (
- "abi" in contents and isinstance(contents["abi"], list)
- ):
- interfaces[file_path][interface_name] = {"type": "json", "code": contents}
-
- else:
- raise ValueError(f"Corrupted file: '{valid_path}'")
-
- else:
- interfaces[file_path][interface_name] = {"type": "vyper", "code": code}
-
- return interfaces
-
-
def compile_files(
- input_files: Iterable[str],
+ input_files: list[str],
output_formats: OutputFormats,
root_folder: str = ".",
show_gas_estimates: bool = False,
- evm_version: str = DEFAULT_EVM_VERSION,
- no_optimize: bool = False,
- storage_layout: Iterable[str] = None,
+ settings: Optional[Settings] = None,
+ storage_layout_paths: list[str] = None,
no_bytecode_metadata: bool = False,
-) -> OrderedDict:
+) -> dict:
root_path = Path(root_folder).resolve()
if not root_path.exists():
raise FileNotFoundError(f"Invalid root path - '{root_path.as_posix()}' does not exist")
- contract_sources: ContractCodes = OrderedDict()
- for file_name in input_files:
- file_path = Path(file_name)
- try:
- file_str = file_path.resolve().relative_to(root_path).as_posix()
- except ValueError:
- file_str = file_path.as_posix()
- with file_path.open() as fh:
- # trailing newline fixes python parsing bug when source ends in a comment
- # https://bugs.python.org/issue35107
- contract_sources[file_str] = fh.read() + "\n"
-
- storage_layouts = OrderedDict()
- if storage_layout:
- for storage_file_name, contract_name in zip(storage_layout, contract_sources.keys()):
- storage_file_path = Path(storage_file_name)
- with storage_file_path.open() as sfh:
- storage_layouts[contract_name] = json.load(sfh)
+ input_bundle = FilesystemInputBundle([root_path])
show_version = False
if "combined_json" in output_formats:
@@ -290,21 +243,44 @@ def compile_files(
translate_map = {"abi_python": "abi", "json": "abi", "ast": "ast_dict", "ir_json": "ir_dict"}
final_formats = [translate_map.get(i, i) for i in output_formats]
- compiler_data = vyper.compile_codes(
- contract_sources,
- final_formats,
- exc_handler=exc_handler,
- interface_codes=get_interface_codes(root_path, contract_sources),
- evm_version=evm_version,
- no_optimize=no_optimize,
- storage_layouts=storage_layouts,
- show_gas_estimates=show_gas_estimates,
- no_bytecode_metadata=no_bytecode_metadata,
- )
+ if storage_layout_paths:
+ if len(storage_layout_paths) != len(input_files):
+ raise ValueError(
+ "provided {len(storage_layout_paths)} storage "
+ "layouts, but {len(input_files)} source files"
+ )
+
+ ret: dict[Any, Any] = {}
if show_version:
- compiler_data["version"] = vyper.__version__
+ ret["version"] = vyper.__version__
- return compiler_data
+ for file_name in input_files:
+ file_path = Path(file_name)
+ file = input_bundle.load_file(file_path)
+ assert isinstance(file, FileInput) # mypy hint
+
+ storage_layout_override = None
+ if storage_layout_paths:
+ storage_file_path = storage_layout_paths.pop(0)
+ with open(storage_file_path) as sfh:
+ storage_layout_override = json.load(sfh)
+
+ output = vyper.compile_code(
+ file.source_code,
+ contract_name=str(file.path),
+ source_id=file.source_id,
+ input_bundle=input_bundle,
+ output_formats=final_formats,
+ exc_handler=exc_handler,
+ settings=settings,
+ storage_layout_override=storage_layout_override,
+ show_gas_estimates=show_gas_estimates,
+ no_bytecode_metadata=no_bytecode_metadata,
+ )
+
+ ret[file_path] = output
+
+ return ret
if __name__ == "__main__":
diff --git a/vyper/cli/vyper_ir.py b/vyper/cli/vyper_ir.py
index 6831f39473..1f90badcaa 100755
--- a/vyper/cli/vyper_ir.py
+++ b/vyper/cli/vyper_ir.py
@@ -55,7 +55,7 @@ def compile_to_ir(input_file, output_formats, show_gas_estimates=False):
compiler_data["asm"] = asm
if "bytecode" in output_formats:
- (bytecode, _srcmap) = compile_ir.assembly_to_evm(asm)
+ bytecode, _ = compile_ir.assembly_to_evm(asm)
compiler_data["bytecode"] = "0x" + bytecode.hex()
return compiler_data
diff --git a/vyper/cli/vyper_json.py b/vyper/cli/vyper_json.py
index aa6cf1c2f5..2720f20d23 100755
--- a/vyper/cli/vyper_json.py
+++ b/vyper/cli/vyper_json.py
@@ -4,14 +4,14 @@
import json
import sys
import warnings
-from pathlib import Path
-from typing import Any, Callable, Dict, Hashable, List, Tuple, Union
+from pathlib import Path, PurePath
+from typing import Any, Callable, Hashable, Optional
import vyper
-from vyper.cli.utils import extract_file_interface_imports, get_interface_file_path
-from vyper.evm.opcodes import DEFAULT_EVM_VERSION, EVM_VERSIONS
+from vyper.compiler.input_bundle import FileInput, JSONInputBundle
+from vyper.compiler.settings import OptimizationLevel, Settings
+from vyper.evm.opcodes import EVM_VERSIONS
from vyper.exceptions import JSONError
-from vyper.typing import ContractCodes, ContractPath
from vyper.utils import keccak256
TRANSLATE_MAP = {
@@ -28,7 +28,7 @@
"interface": "interface",
"ir": "ir_dict",
"ir_runtime": "ir_runtime_dict",
- # "metadata": "metadata", # don't include in "*" output for now
+ "metadata": "metadata",
"layout": "layout",
"userdoc": "userdoc",
}
@@ -96,15 +96,15 @@ def _parse_args(argv):
print(output_json)
-def exc_handler_raises(file_path: Union[str, None], exception: Exception, component: str) -> None:
+def exc_handler_raises(file_path: Optional[str], exception: Exception, component: str) -> None:
if file_path:
print(f"Unhandled exception in '{file_path}':")
exception._exc_handler = True # type: ignore
raise exception
-def exc_handler_to_dict(file_path: Union[str, None], exception: Exception, component: str) -> Dict:
- err_dict: Dict = {
+def exc_handler_to_dict(file_path: Optional[str], exception: Exception, component: str) -> dict:
+ err_dict: dict = {
"type": type(exception).__name__,
"component": component,
"severity": "error",
@@ -128,105 +128,98 @@ def exc_handler_to_dict(file_path: Union[str, None], exception: Exception, compo
return output_json
-def _standardize_path(path_str: str) -> str:
- try:
- path = Path(path_str)
-
- if path.is_absolute():
- path = path.resolve()
- else:
- pwd = Path(".").resolve()
- path = path.resolve().relative_to(pwd)
-
- except ValueError:
- raise JSONError(f"{path_str} - path exists outside base folder")
-
- return path.as_posix()
-
-
-def get_evm_version(input_dict: Dict) -> str:
+def get_evm_version(input_dict: dict) -> Optional[str]:
if "settings" not in input_dict:
- return DEFAULT_EVM_VERSION
-
- evm_version = input_dict["settings"].get("evmVersion", DEFAULT_EVM_VERSION)
- if evm_version in ("homestead", "tangerineWhistle", "spuriousDragon"):
- raise JSONError("Vyper does not support pre-byzantium EVM versions")
+ return None
+
+ # TODO: move this validation somewhere it can be reused more easily
+ evm_version = input_dict["settings"].get("evmVersion")
+ if evm_version is None:
+ return None
+
+ if evm_version in (
+ "homestead",
+ "tangerineWhistle",
+ "spuriousDragon",
+ "byzantium",
+ "constantinople",
+ ):
+ raise JSONError("Vyper does not support pre-istanbul EVM versions")
if evm_version not in EVM_VERSIONS:
raise JSONError(f"Unknown EVM version - '{evm_version}'")
return evm_version
-def get_input_dict_contracts(input_dict: Dict) -> ContractCodes:
- contract_sources: ContractCodes = {}
+def get_compilation_targets(input_dict: dict) -> list[PurePath]:
+ # TODO: once we have modules, add optional "compilation_targets" key
+ # which specifies which sources we actually want to compile.
+
+ return [PurePath(p) for p in input_dict["sources"].keys()]
+
+
+def get_inputs(input_dict: dict) -> dict[PurePath, Any]:
+ ret = {}
+ seen = {}
+
for path, value in input_dict["sources"].items():
+ path = PurePath(path)
if "urls" in value:
raise JSONError(f"{path} - 'urls' is not a supported field, use 'content' instead")
if "content" not in value:
raise JSONError(f"{path} missing required field - 'content'")
if "keccak256" in value:
- hash_ = value["keccak256"].lower()
- if hash_.startswith("0x"):
- hash_ = hash_[2:]
+ hash_ = value["keccak256"].lower().removeprefix("0x")
if hash_ != keccak256(value["content"].encode("utf-8")).hex():
raise JSONError(
f"Calculated keccak of '{path}' does not match keccak given in input JSON"
)
- key = _standardize_path(path)
- if key in contract_sources:
- raise JSONError(f"Contract namespace collision: {key}")
- contract_sources[key] = value["content"]
- return contract_sources
-
+ if path.stem in seen:
+ raise JSONError(f"Contract namespace collision: {path}")
-def get_input_dict_interfaces(input_dict: Dict) -> Dict:
- interface_sources: Dict = {}
+ # value looks like {"content":