Skip to content

Commit

Permalink
chore: drop evm versions through istanbul (#3470)
Browse files Browse the repository at this point in the history
drop pre-istanbul versions. per VIP-3365, we could drop through paris,
but since this is the first time starting to enforce this policy, we
don't want to drop too many versions at once.
  • Loading branch information
charles-cooper authored Jun 1, 2023
1 parent 7f18aee commit 07f3cb0
Show file tree
Hide file tree
Showing 16 changed files with 95 additions and 169 deletions.
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

0 comments on commit 07f3cb0

Please sign in to comment.