Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: drop evm versions through istanbul #3470

Merged
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 17 additions & 12 deletions docs/compiling-a-contract.rst
Original file line number Diff line number Diff line change
Expand Up @@ -140,24 +140,29 @@ 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
- 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``

- The ``EXTCODEHASH`` opcode is accessible via ``address.codehash``
- ``shift`` makes use of ``SHL``/``SHR`` opcodes.
.. py:attribute:: paris
- ``block.difficulty`` is deprecated in favor of its new alias, ``block.prevrandao``.

.. py:attribute:: petersburg
.. py:attribute:: shanghai
- The ``PUSH0`` opcode is automatically generated by the compiler instead of ``PUSH1 0``

- The compiler behaves the same way as with constantinople.
.. 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

.. 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
Expand Down Expand Up @@ -204,7 +209,7 @@ 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.
"evmVersion": "shanghai", // EVM version to compile for. Can be istanbul, berlin, paris, shanghai (default) or cancun (experimental!).
// optional, whether or not optimizations are turned on
// defaults to true
"optimize": true,
Expand Down
26 changes: 0 additions & 26 deletions tests/cli/vyper_compile/test_compile_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,29 +28,3 @@ def test_combined_json_keys(tmp_path):
def test_invalid_root_path():
with pytest.raises(FileNotFoundError):
compile_files([], [], root_folder="path/that/does/not/exist")


def test_evm_versions(tmp_path):
# should compile differently because of SELFBALANCE
code = """
@external
def foo() -> uint256:
return self.balance
"""

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"]

assert byzantium_bytecode != istanbul_bytecode

# SELFBALANCE opcode is 0x47
assert "47" not in byzantium_bytecode
assert "47" in istanbul_bytecode
9 changes: 0 additions & 9 deletions tests/cli/vyper_json/test_compile_from_input_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,3 @@ def test_relative_import_paths():
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)
14 changes: 12 additions & 2 deletions tests/cli/vyper_json/test_get_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,23 @@ 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}})

Expand Down
33 changes: 15 additions & 18 deletions tests/compiler/test_opcodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,30 +37,27 @@ 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):
ops = opcodes.get_opcodes()
if evm_version in ("paris", "berlin", "shanghai", "cancun"):
assert "CHAINID" in ops

assert "CHAINID" in ops
assert ops["CREATE2"][-1] == 32000

if evm_version in ("london", "berlin", "paris", "shanghai", "cancun"):
assert ops["SLOAD"][-1] == 2100
if evm_version in ("shanghai", "cancun"):
assert "PUSH0" in ops
if evm_version in ("cancun",):
assert "TLOAD" in ops
assert "TSTORE" in ops
elif evm_version == "istanbul":
assert "CHAINID" in ops
assert ops["SLOAD"][-1] == 800
else:
assert "CHAINID" not in ops
assert ops["SLOAD"][-1] == 200
assert evm_version == "istanbul"
assert ops["SLOAD"][-1] == 800

if evm_version in ("byzantium", "atlantis"):
assert "CREATE2" not in ops
else:
assert ops["CREATE2"][-1] == 32000
if evm_version in ("shanghai", "cancun"):
assert "PUSH0" in ops

if evm_version in ("cancun",):
assert "TLOAD" in ops
assert "TSTORE" in ops
13 changes: 3 additions & 10 deletions tests/parser/functions/test_bitwise.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,8 @@ 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
assert "SHL" in opcodes
assert "SHR" in opcodes


@pytest.mark.parametrize("evm_version", list(EVM_VERSIONS))
Expand All @@ -59,10 +55,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)
@pytest.mark.parametrize("evm_version", list(EVM_VERSIONS.keys()))
def test_signed_shift(get_contract_with_gas_estimation, evm_version):
code = """
@external
Expand Down
16 changes: 6 additions & 10 deletions tests/parser/syntax/test_chainid.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from vyper import compiler
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))
Expand All @@ -13,11 +13,7 @@ def foo():
a: uint256 = chain.id
"""

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, evm_version=evm_version) is not None


fail_list = [
Expand Down Expand Up @@ -71,10 +67,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 = [
Expand All @@ -95,7 +91,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):
Expand All @@ -105,5 +101,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
7 changes: 0 additions & 7 deletions tests/parser/syntax/test_codehash.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

from vyper.compiler import compile_code
from vyper.evm.opcodes import EVM_VERSIONS
from vyper.exceptions import EvmVersionException
from vyper.utils import keccak256


Expand Down Expand Up @@ -32,12 +31,6 @@ 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
)
Expand Down
2 changes: 1 addition & 1 deletion vyper/cli/vyper_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ def _parse_args(argv):
parser.add_argument(
"--evm-version",
help=f"Select desired EVM version (default {DEFAULT_EVM_VERSION}). "
" note: cancun support is EXPERIMENTAL",
"note: cancun support is EXPERIMENTAL",
choices=list(EVM_VERSIONS),
default=DEFAULT_EVM_VERSION,
dest="evm_version",
Expand Down
10 changes: 8 additions & 2 deletions vyper/cli/vyper_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,14 @@ def get_evm_version(input_dict: Dict) -> str:
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")
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}'")

Expand Down
11 changes: 2 additions & 9 deletions vyper/codegen/arithmetic.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
is_numeric_type,
)
from vyper.codegen.ir_node import IRnode
from vyper.evm.opcodes import version_check
from vyper.exceptions import CompilerPanic, TypeCheckFailure, UnimplementedException


Expand Down Expand Up @@ -243,10 +242,7 @@ def safe_mul(x, y):
# in the above sdiv check, if (r==-1 and l==-2**255),
# -2**255<res> / -1<r> will return -2**255<l>.
# need to check: not (r == -1 and l == -2**255)
if version_check(begin="constantinople"):
upper_bound = ["shl", 255, 1]
else:
upper_bound = -(2**255)
upper_bound = ["shl", 255, 1]

check_x = ["ne", x, upper_bound]
check_y = ["ne", ["not", y], 0]
Expand Down Expand Up @@ -301,10 +297,7 @@ def safe_div(x, y):
with res.cache_when_complex("res") as (b1, res):
# TODO: refactor this condition / push some things into the optimizer
if typ.is_signed and typ.bits == 256:
if version_check(begin="constantinople"):
upper_bound = ["shl", 255, 1]
else:
upper_bound = -(2**255)
upper_bound = ["shl", 255, 1]

if not x.is_literal and not y.is_literal:
ok = ["or", ["ne", y, ["not", 0]], ["ne", x, upper_bound]]
Expand Down
14 changes: 3 additions & 11 deletions vyper/codegen/core.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from vyper import ast as vy_ast
from vyper.codegen.ir_node import Encoding, IRnode
from vyper.evm.address_space import CALLDATA, DATA, IMMUTABLES, MEMORY, STORAGE, TRANSIENT
from vyper.evm.opcodes import version_check
from vyper.exceptions import CompilerPanic, StructureException, TypeCheckFailure, TypeMismatch
from vyper.semantics.types import (
AddressT,
Expand Down Expand Up @@ -997,23 +996,16 @@ def zero_pad(bytez_placeholder):

# convenience rewrites for shr/sar/shl
def shr(bits, x):
if version_check(begin="constantinople"):
return ["shr", bits, x]
return ["div", x, ["exp", 2, bits]]
return ["shr", bits, x]


# convenience rewrites for shr/sar/shl
def shl(bits, x):
if version_check(begin="constantinople"):
return ["shl", bits, x]
return ["mul", x, ["exp", 2, bits]]
return ["shl", bits, x]


def sar(bits, x):
if version_check(begin="constantinople"):
return ["sar", bits, x]

raise NotImplementedError("no SAR emulation for pre-constantinople EVM")
return ["sar", bits, x]


def clamp_bytestring(ir_node):
Expand Down
5 changes: 0 additions & 5 deletions vyper/codegen/expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,10 +242,6 @@ def parse_Attribute(self):
# x.codehash: keccak of address x
elif self.expr.attr == "codehash":
addr = Expr.parse_value_expr(self.expr.value, self.context)
if not version_check(begin="constantinople"):
raise EvmVersionException(
"address.codehash is unavailable prior to constantinople ruleset", self.expr
)
if addr.typ == AddressT():
return IRnode.from_list(["extcodehash", addr], typ=BYTES32_T)
# x.code: codecopy/extcodecopy of address x
Expand Down Expand Up @@ -401,7 +397,6 @@ def parse_BinOp(self):
# TODO implement me. promote_signed_int(op(right, left), bits)
return
op = shr if not left.typ.is_signed else sar
# note: sar NotImplementedError for pre-constantinople
return IRnode.from_list(op(right, left), typ=new_typ)

# enums can only do bit ops, not arithmetic.
Expand Down
3 changes: 1 addition & 2 deletions vyper/codegen/external_call.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,7 @@ def _pack_arguments(fn_type, args, context):
# 32 bytes | args
# 0x..00<method_id_4bytes> | args
# the reason for the left padding is just so the alignment is easier.
# if we were only targeting constantinople, we could align
# to buf (and also keep code size small) by using
# XXX: we could align to buf (and also keep code size small) by using
# (mstore buf (shl signature.method_id 224))
pack_args = ["seq"]
pack_args.append(["mstore", buf, util.method_id_int(abi_signature)])
Expand Down
Loading