Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/vyperlang/vyper into refa…
Browse files Browse the repository at this point in the history
…ctor/folding_alt
  • Loading branch information
tserg committed Oct 20, 2023
2 parents 5642c24 + 3ba1412 commit b960379
Show file tree
Hide file tree
Showing 15 changed files with 351 additions and 93 deletions.
2 changes: 1 addition & 1 deletion FUNDING.yml
Original file line number Diff line number Diff line change
@@ -1 +1 @@
custom: https://gitcoin.co/grants/200/vyper-smart-contract-language-2
custom: https://etherscan.io/address/0x70CCBE10F980d80b7eBaab7D2E3A73e87D67B775
16 changes: 8 additions & 8 deletions docs/release-notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,21 @@ Release Notes
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")
***********************
v0.3.10rc1
**********
v0.3.10 ("Black Adder")
***********************

Date released: 2023-09-06
Date released: 2023-10-04
=========================

v0.3.10 is a performance focused release. It adds a ``codesize`` optimization mode (`#3493 <https://github.com/vyperlang/vyper/pull/3493>`_), adds new vyper-specific ``#pragma`` directives (`#3493 <https://github.com/vyperlang/vyper/pull/3493>`_), uses Cancun's ``MCOPY`` opcode for some compiler generated code (`#3483 <https://github.com/vyperlang/vyper/pull/3483>`_), and generates selector tables which now feature O(1) performance (`#3496 <https://github.com/vyperlang/vyper/pull/3496>`_).
v0.3.10 is a performance focused release that additionally ships numerous bugfixes. It adds a ``codesize`` optimization mode (`#3493 <https://github.com/vyperlang/vyper/pull/3493>`_), adds new vyper-specific ``#pragma`` directives (`#3493 <https://github.com/vyperlang/vyper/pull/3493>`_), uses Cancun's ``MCOPY`` opcode for some compiler generated code (`#3483 <https://github.com/vyperlang/vyper/pull/3483>`_), and generates selector tables which now feature O(1) performance (`#3496 <https://github.com/vyperlang/vyper/pull/3496>`_).

Breaking changes:
-----------------

- add runtime code layout to initcode (`#3584 <https://github.com/vyperlang/vyper/pull/3584>`_)
- drop evm versions through istanbul (`#3470 <https://github.com/vyperlang/vyper/pull/3470>`_)
- remove vyper signature from runtime (`#3471 <https://github.com/vyperlang/vyper/pull/3471>`_)
- only allow valid identifiers to be nonreentrant keys (`#3605 <https://github.com/vyperlang/vyper/pull/3605>`_)

Non-breaking changes and improvements:
--------------------------------------
Expand All @@ -46,12 +43,15 @@ Notable fixes:

- fix ``ecrecover()`` behavior when signature is invalid (`GHSA-f5x6-7qgp-jhf3 <https://github.com/vyperlang/vyper/security/advisories/GHSA-f5x6-7qgp-jhf3>`_, `#3586 <https://github.com/vyperlang/vyper/pull/3586>`_)
- fix: order of evaluation for some builtins (`#3583 <https://github.com/vyperlang/vyper/pull/3583>`_, `#3587 <https://github.com/vyperlang/vyper/pull/3587>`_)
- fix: memory allocation in certain builtins using ``msize`` (`#3610 <https://github.com/vyperlang/vyper/pull/3610>`_)
- fix: ``_abi_decode()`` input validation in certain complex expressions (`#3626 <https://github.com/vyperlang/vyper/pull/3626>`_)
- fix: pycryptodome for arm builds (`#3485 <https://github.com/vyperlang/vyper/pull/3485>`_)
- let params of internal functions be mutable (`#3473 <https://github.com/vyperlang/vyper/pull/3473>`_)
- typechecking of folded builtins in (`#3490 <https://github.com/vyperlang/vyper/pull/3490>`_)
- update tload/tstore opcodes per latest 1153 EIP spec (`#3484 <https://github.com/vyperlang/vyper/pull/3484>`_)
- fix: raw_call type when max_outsize=0 is set (`#3572 <https://github.com/vyperlang/vyper/pull/3572>`_)
- fix: implements check for indexed event arguments (`#3570 <https://github.com/vyperlang/vyper/pull/3570>`_)
- fix: type-checking for ``_abi_decode()`` arguments (`#3626 <https://github.com/vyperlang/vyper/pull/3623>`_)

Other docs updates, chores and fixes:
-------------------------------------
Expand Down
6 changes: 5 additions & 1 deletion docs/structure-of-a-contract.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,18 @@ Vyper supports several source code directives to control compiler modes and help
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 <https://docs.npmjs.com/about-semantic-versioning>`_ style syntax. Starting from v0.4.0 and up, version strings will use `PEP440 version specifiers <https://peps.python.org/pep-0440/#version-specifiers>_`.
The version pragma ensures that a contract is only compiled by the intended compiler version, or range of versions. Version strings use `NPM <https://docs.npmjs.com/about-semantic-versioning>`_ style syntax. Starting from v0.4.0 and up, version strings will use `PEP440 version specifiers <https://peps.python.org/pep-0440/#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
Expand Down
70 changes: 64 additions & 6 deletions tests/ast/test_pre_parser.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import pytest

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 VersionException
from vyper.exceptions import StructureException, VersionException

SRC_LINE = (1, 0) # Dummy source line
COMPILER_VERSION = "0.1.1"
Expand Down Expand Up @@ -96,57 +97,66 @@ def test_prerelease_invalid_version_pragma(file_version, mock_version):
"""
""",
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.GAS),
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),
),
(
"""
Expand All @@ -155,11 +165,59 @@ def test_prerelease_invalid_version_pragma(file_version, mock_version):
#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, expected_pragmas", pragma_examples)
def parse_pragmas(code, expected_pragmas):
pragmas, _, _ = pre_parse(code)
assert pragmas == expected_pragmas
@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)
107 changes: 107 additions & 0 deletions tests/compiler/ir/test_optimize_ir.py
Original file line number Diff line number Diff line change
@@ -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]),
Expand Down Expand Up @@ -272,3 +276,106 @@ def test_operator_set_values():
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
60 changes: 60 additions & 0 deletions tests/parser/features/test_assignment.py
Original file line number Diff line number Diff line change
Expand Up @@ -442,3 +442,63 @@ def bug(p: Point) -> Point:
"""
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
Loading

0 comments on commit b960379

Please sign in to comment.