Skip to content

VIP: make external entrypoints nonreentrant by default #4509

@cyberthirst

Description

@cyberthirst

Simple Summary

Make external entrypoints (with some exceptions listed below) nonreentrant by default for all evm versions since cancun. The @nonreentrant decorator is replaced by the new defaults and a new @reentrant decorator. The @reentrant decorator designates that the given entrypoint should not read or write the lock to enable reentrancy as a feature/gas optimization.

Motivation

The aim of this proposal is to further align Vyper with it being an easily auditable and security-focused language. Safe and sensible defaults are fundamental to achieving those goals.

Specification

External functions

  • External function is either @reentrant or has the implicit nonreentrant behavior.
@external
@reentrant
def foo():
	# foo doesn't read or write the lock
	...
@external
def foo():
	# foo reads the lock upon invocation and reverts if locked
	# if unlocked, it locks the lock
	...
	# unlocks the lock, after body finishes execution

Pure and view functions

  • view functions are nonreentrant by default, but they only read the lock upon invocation, no write operation is performed
  • pure functions are reentrant by default due to state-acess restrictions

init function

  • __init__ functions are reentrant by default. In the init context, the contract isn't yet deployed, thus we consider this default safe

default function

  • __default__ function is made nonreentrant by default

Public variables (getters)

  • public variables are made reentrant by default
  • It is maintained that read-only reentrancy based on only getters is unlikely. Furthermore, it is hypothesized that many users would make their variables reentrant had this not been the default and thus hinder readability and conciseness of Vyper code. Furthermore, it could even lead to users turning this feature off (see the section on #pragma reentrant)
  • To enable locking of even getters, the following syntax could be introduced: var: nonreentrant(public(uint256))
  • Alternatively, public variables could be blocked altogether in favor of manually written getters

New reentrant pragma

  • This proposal is a breaking change. To allow users to revert back to the current (v0.4.1) behavior, add #pragma reentrant, which enables the current (v0.4.1) reentrancy semantics.

Modules

  • Due to the introduction of the new reentrant pragma, there could be collision between individual modules the contract comprises of
  • Assume a collision happens, meaning that one module uses the pragma and the other one doesn't. We consider the following options as sensible:
    1. Abort compilation
    2. #pragma reentrant maintains its effect only in those modules where it is used, other modules are nonreentrant by default. Additionally, a warning is emitted to the user about the risk of reentrancy as the whole protocol might have been designed with the thought of it being nonreentrant by default.
  • We consider this point to be contentious and welcome feedback from others

Breaking changes

  • All external entrypoints are nonreentrant by default
  • @nonreentrant decorator doesn't work without #pragma reentrant

Metadata

Metadata

Assignees

No one assigned

    Labels

    VIP: DiscussionUsed to denote VIPs and more complex issues that are waiting discussion in a meetingrelease - mustrelease blocker

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions