-
-
Notifications
You must be signed in to change notification settings - Fork 803
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
VIP: disable re-entrancy by default #3380
Comments
Also let's add another option @public
@reentrant # can be called again no matter what call out is made
def all_calls_allowed()
@public # no calls to this allowed after call out
def call_A()
@public
@reentrant(call_A,...) # can only call this if call out from inside `call_A`
def only_A_reentrancy_allowed() But in general, 2 seems most flexible since it's at the call site |
Agree with @fubuloubu here, the |
Fully support disabling reentrancy by default. @charles-cooper if you need hard stats, you can simply link to my reentrancy attacks list: https://github.com/pcaversaccio/reentrancy-attacks. Also, for the sake of completeness, I cross-reference my similar initiative in Solidity here: ethereum/solidity#12996. I think the |
inverting the nonreentrant decorator has the benefit of not incurring a performance hit for every function, but it doesn't carry the same level of safety. two thoughts,
|
One comment regarding |
the |
So you want to keep this decorator as well? |
that's not what i meant, i'm just saying in the context of view functions, there are still reasonable semantics for nonreentrancy, they are just slightly modified -- the function cannot be "re-entered" into, i.e. it checks the flag even if it does not set it. |
Btw, no function pointers needed! It would just be the name of the function, it's not really like it's callable or you could pass it to other functions like an object. |
Disabling re-entrancy by default fits with the 'default safe' policies of Vyper. Definitely lets do this. How to deal with the change in backwards compatibility is a bigger question. Maybe a contract level declaration Otherwise - I like
|
For the sake of documentation, linking a very interesting write-up by @0xalpharush on the discussed matter: https://gist.github.com/0xalpharush/15d903ec43334b081caece21a0bd7a20. Also, @jtriley-eth provided interesting thoughts (in Solidity tho) for reflection. |
I support this a lot, but I have a couple ideas/questions, I am not sure how useful this one would be but having |
Do you have a specific use case in mind where this would be useful? |
To be honest I doubt this actually sees much if any use, but think it would be good to have in edge cases. Like for example in the code for a DAO, it may be fine if an |
If I had to code such a solution I would probably use Solidity with inline assembly to optimise it. I don't think we need to cover the 0.0001%, but rather the 99.999% with the above proposal. But, one way to incorporate your idea could be by using an additional kwarg @external
def call_A():
"""
@dev No calls to this function are allowed after call-out.
"""
...
@external
def call_B():
"""
@dev No calls to this function are allowed after call-out.
"""
...
@external
@reentrant(call_A, call_B, max_calls=5)
def only_A_and_B_reentrancy_allowed():
"""
@dev Can only call this function if call-out from inside `call_A` or `call_B`.
Overall maximum of 5 reentering calls allowed.
"""
... But again, complexity is the enemy of security. So I don't know whether this additional feature can be justified. |
I really like the syntax for that, would support implementing it like that |
I like the idea of declarative syntax, especially for meta-data concepts like reentrancy. However they must have the highest semantic expressiveness possible because of the consequences of getting such things wrong. You clearly have already considered this with your warning about complexity and security. Very true. Looking at this from a contract auditor's or exploiter's perspective, specifying some arbitrary counts of the number of times a reentrant call may come through this path without clear semantics of the relevance of that number scares/excites me respectively. But your idea of specifying reentrancy options via certain code paths is actually quite interesting and, if taken correctly, eliminates any need to specify an arbitrary number.
You can imagine several policy enumerations that might be found practical. But note that if you ever need a new call path to be allowed through, all that's necessary is to create another function inside the contract to route it through. This eliminates the need for any numeric declaration as you'll instead have an explicit named code path that can source the reentrancy under limited conditions. This makes reentrancy attacks incredibly difficult to create and auditing of the contract far easier than arbitrary numbers that don't express the semantics of how and why that number was arrived at nor how it could possibly be enforced. It's the code paths that are critical - not the number of times it's passed though. Ultimately, this is some pretty esoteric stuff that may be useful for composition of advanced DAO contracts but I think we should first do something simple and make sure we have optimum code efficiency for that model before extending the language this far. However, doing it as decorators does make it easier to do "take backs" if we want to consider some options as "experimental". |
For the sake of keeping this issue as clean as possible, I'm going to nip discussion of the enumerated reentrancy protection feature in the bud. It is a neat idea, but @ControlCplusControlV as you suspected it would require a different implementation that what I am envisioning, and there just doesn't seem to be a use case (besides theoretical ones) for it. It is such an odd use case that it can and should be implemented in user code. If that changes (i.e. it starts appearing as a common pattern in the wild), we can revisit, but at this time we should not be considering it further. @ControlCplusControlV if you want to continue brainstorming, please use https://github.com/vyperlang/vyper/discussions or the discord until the idea matures further. |
If reentrancy protection provided by default (escaped with decorators), basically once you've made a call into a contract, and it dispatches control flow back to a caller, that caller cannot do any call back including view calls. It would be good to assess what sorts of limits that might place, especially relating to implementing something like flashloans on a token, where the caller might want to see the total supply of the token to make a decision. Im not sure if there are other more critical cases, but it's worth understanding this limitation |
FWIW, dex aggregators like 1inch also use reentrancy as a feaure. Also there, we should conduct an impact analysis. |
it seems the preferred implementation in terms of language design will be to remove the current |
how about read-only functions using such semantics? |
also, starting from 0.4.0, nonreentrant locks should be global only -- see #3760 |
i don't think any special cases need to be considered for view functions. they just behave as before. |
There is such thing as read-only re-entrancy, wonder if there would be any way to help mitigate that with this |
this was already discussed previously in this issue #3380 (comment) |
Given the current state of the EVM, only |
Simple Summary
per title
Motivation
once EIP-1153 is enabled, the cost of disabling re-entrancy into a contract before the selector table is even traversed comes down to two TSTOREs and one TLOAD, so approximately 300 gas, which is a small overhead compared to even the cost of a warm CALL, 200 gas. re-entrancy attacks are one of the biggest sources of vulnerabilities in smart contracts (todo: gather some hard stats on this), and it makes sense from a safety perspective to disable them by default. in the future, i also expect a future EIP will bring the cost of TLOAD/TSTORE down in the general case.
Specification
there are multiple possibilities here:
LOCK
keyword, which creates critical section/non-reentrant blocks. ex.EDIT: it seems the preferred implementation in terms of language design will be to remove the current
@nonreentrant
decorator and replace with an@reentrant
decorator (with inverted semantics)Backwards Compatibility
this fundamentally changes the behavior of existing vyper contracts. to discuss.
Dependencies
will be cleaner to implement syntax-wise with the addition of #2856.
References
Copyright
Copyright and related rights waived via CC0
The text was updated successfully, but these errors were encountered: