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

transient storage keyword #3373

Merged
merged 17 commits into from
May 18, 2023
Merged
Show file tree
Hide file tree
Changes from 13 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
4 changes: 2 additions & 2 deletions tests/compiler/test_opcodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@ def test_version_check(evm_version):

def test_get_opcodes(evm_version):
ops = opcodes.get_opcodes()
if evm_version in ("paris", "berlin", "shanghai"):
if evm_version in ("paris", "berlin", "shanghai", "cancun"):
assert "CHAINID" in ops
assert ops["SLOAD"][-1] == 2100
if evm_version in ("shanghai",):
if evm_version in ("shanghai", "cancun"):
assert "PUSH0" in ops
elif evm_version == "istanbul":
assert "CHAINID" in ops
Expand Down
1 change: 1 addition & 0 deletions tests/parser/ast_utils/test_ast_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ def test_basic_ast():
"is_constant": False,
"is_immutable": False,
"is_public": False,
"is_transient": False,
}


Expand Down
2 changes: 2 additions & 0 deletions tests/parser/features/decorators/test_nonreentrant.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
18 changes: 14 additions & 4 deletions vyper/ast/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1344,14 +1344,23 @@ 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)

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`
Expand All @@ -1369,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]

Expand Down
3 changes: 2 additions & 1 deletion vyper/cli/vyper_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@ 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",
Expand Down
1 change: 1 addition & 0 deletions vyper/codegen/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class VariableRecord:
defined_at: Any = None
is_internal: bool = False
is_immutable: bool = False
is_transient: bool = False
data_offset: Optional[int] = None

def __hash__(self):
Expand Down
8 changes: 4 additions & 4 deletions vyper/codegen/core.py
Original file line number Diff line number Diff line change
@@ -1,6 +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
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 (
Expand Down Expand Up @@ -562,10 +562,10 @@ def _get_element_ptr_mapping(parent, key):
key = unwrap_location(key)

# TODO when is key None?
if key is None or parent.location != STORAGE:
raise TypeCheckFailure(f"bad dereference on mapping {parent}[{key}]")
if key is None or parent.location not in (STORAGE, TRANSIENT):
raise TypeCheckFailure("bad dereference on mapping {parent}[{key}]")

return IRnode.from_list(["sha3_64", parent, key], typ=subtype, location=STORAGE)
return IRnode.from_list(["sha3_64", parent, key], typ=subtype, location=parent.location)


# Take a value representing a memory or storage location, and descend down to
Expand Down
6 changes: 4 additions & 2 deletions vyper/codegen/expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
)
from vyper.codegen.ir_node import IRnode
from vyper.codegen.keccak256_helper import keccak256_helper
from vyper.evm.address_space import DATA, IMMUTABLES, MEMORY, STORAGE
from vyper.evm.address_space import DATA, IMMUTABLES, MEMORY, STORAGE, TRANSIENT
from vyper.evm.opcodes import version_check
from vyper.exceptions import (
CompilerPanic,
Expand Down Expand Up @@ -259,10 +259,12 @@ def parse_Attribute(self):
# self.x: global attribute
elif isinstance(self.expr.value, vy_ast.Name) and self.expr.value.id == "self":
varinfo = self.context.globals[self.expr.attr]
location = TRANSIENT if varinfo.is_transient else STORAGE

ret = IRnode.from_list(
varinfo.position.position,
typ=varinfo.typ,
location=STORAGE,
location=location,
annotation="self." + self.expr.attr,
)
ret._referenced_variables = {varinfo}
Expand Down
10 changes: 7 additions & 3 deletions vyper/codegen/function_definitions/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ def get_nonreentrant_lock(func_type):

nkey = func_type.reentrancy_key_position.position

LOAD, STORE = "sload", "sstore"
if version_check(begin="cancun"):
LOAD, STORE = "tload", "tstore"

if version_check(begin="berlin"):
# any nonzero values would work here (see pricing as of net gas
# metering); these values are chosen so that downgrading to the
Expand All @@ -16,12 +20,12 @@ def get_nonreentrant_lock(func_type):
else:
final_value, temp_value = 0, 1

check_notset = ["assert", ["ne", temp_value, ["sload", nkey]]]
check_notset = ["assert", ["ne", temp_value, [LOAD, nkey]]]

if func_type.mutability == StateMutability.VIEW:
return [check_notset], [["seq"]]

else:
pre = ["seq", check_notset, ["sstore", nkey, temp_value]]
post = ["sstore", nkey, final_value]
pre = ["seq", check_notset, [STORE, nkey, temp_value]]
post = [STORE, nkey, final_value]
return [pre], [post]
1 change: 1 addition & 0 deletions vyper/evm/address_space.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ def byte_addressable(self) -> bool:

MEMORY = AddrSpace("memory", 32, "mload", "mstore")
STORAGE = AddrSpace("storage", 1, "sload", "sstore")
TRANSIENT = AddrSpace("transient", 1, "tload", "tstore")
CALLDATA = AddrSpace("calldata", 32, "calldataload")
# immutables address space: "immutables" section of memory
# which is read-write in deploy code but then gets turned into
Expand Down
8 changes: 5 additions & 3 deletions vyper/evm/opcodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
from vyper.exceptions import CompilerPanic
from vyper.typing import OpcodeGasCost, OpcodeMap, OpcodeRulesetMap, OpcodeRulesetValue, OpcodeValue

active_evm_version: int = 4

# EVM version rules work as follows:
# 1. Fork rules go from oldest (lowest value) to newest (highest value).
# 2. Fork versions aren't actually tied to anything. They are not a part of our
Expand All @@ -17,7 +15,7 @@
# 6. Yes, this will probably have to be rethought if there's ever conflicting support
# between multiple chains for a specific feature. Let's hope not.
# 7. We support at a maximum 3 hard forks (for any given chain).
EVM_VERSIONS: Dict[str, int] = {
EVM_VERSIONS: dict[str, int] = {
# ETH Forks
"byzantium": 0,
"constantinople": 1,
Expand All @@ -26,11 +24,13 @@
"berlin": 3,
"paris": 4,
"shanghai": 5,
"cancun": 6,
# ETC Forks
"atlantis": 0,
"agharta": 1,
}
DEFAULT_EVM_VERSION: str = "shanghai"
active_evm_version: int = EVM_VERSIONS[DEFAULT_EVM_VERSION]


# opcode as hex value
Expand Down Expand Up @@ -185,6 +185,8 @@
"INVALID": (0xFE, 0, 0, 0),
"DEBUG": (0xA5, 1, 0, 0),
"BREAKPOINT": (0xA6, 0, 0, 0),
"TLOAD": (0xB3, 1, 1, 100),
"TSTORE": (0xB4, 2, 0, 100),
}

PSEUDO_OPCODES: OpcodeMap = {
Expand Down
1 change: 1 addition & 0 deletions vyper/semantics/analysis/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ class VarInfo:
is_constant: bool = False
is_public: bool = False
is_immutable: bool = False
is_transient: bool = False
is_local_var: bool = False
decl_node: Optional[vy_ast.VyperNode] = None

Expand Down
9 changes: 9 additions & 0 deletions vyper/semantics/analysis/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import vyper.builtins.interfaces
from vyper import ast as vy_ast
from vyper.evm.opcodes import version_check
from vyper.exceptions import (
CallViolation,
CompilerPanic,
Expand Down Expand Up @@ -189,17 +190,25 @@ def visit_VariableDecl(self, node):
if node.is_immutable
else DataLocation.UNSET
if node.is_constant
# XXX: needed if we want separate transient allocator
# else DataLocation.TRANSIENT
# if node.is_transient
else DataLocation.STORAGE
)

type_ = type_from_annotation(node.annotation, data_loc)

if node.is_transient and not version_check(begin="cancun"):
raise StructureException("`transient` is not available pre-cancun", node.annotation)

var_info = VarInfo(
type_,
decl_node=node,
location=data_loc,
is_constant=node.is_constant,
is_public=node.is_public,
is_immutable=node.is_immutable,
is_transient=node.is_transient,
)
node.target._metadata["varinfo"] = var_info # TODO maybe put this in the global namespace
node._metadata["type"] = type_
Expand Down
2 changes: 2 additions & 0 deletions vyper/semantics/data_locations.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ class DataLocation(enum.Enum):
STORAGE = 2
CALLDATA = 3
CODE = 4
# XXX: needed for separate transient storage allocator
# TRANSIENT = 5
1 change: 1 addition & 0 deletions vyper/semantics/namespace.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ def validate_identifier(attr):
"nonpayable",
"constant",
"immutable",
"transient",
"internal",
"payable",
"nonreentrant",
Expand Down