Not sure which is better:
I like that it makes it easier for beginners to look it up when they find such function in the code. This has confused more than one beginner. I prefer not omitting the parentheses because we are still talking about a function.
What about adding a modifier instead, similar to payable.
function foo() fallback {}
my 2 cents: fallback () {}. It's also worth noting that you'll probably want something like
fallback () payable {
}
especially because
fallback payable {
}
looks like a weird anonymous constructor function with two modifiers in front of it.
@decanus Fallback is different to payable/constructor as there can only be a single fallback function. And given its risky use may warrant its on syntactical element.
The current setup has always made sense to me in the sense of the EVM because it's the "empty" function signature. AKA the default in a switch statement.
Right, but it's one of those things that only make sense after you learn about it, and it's not easy to find the right answers.
Good point.
Actually I think we should even split the semantics of the fallback function into two:
One function that is called only if there is value and zero data (the "receive ether trigger function") and one function that is called as a "wildcard", if no other function matches (this should be considered a low-level feature).
I like the idea of splitting it up.
On Mon, Dec 4, 2017 at 12:08 PM chriseth notifications@github.com wrote:
Actually I think we should even split the semantics of the fallback
function into two:One function that is called only if there is value and zero data (the
"receive ether trigger function") and one function that is called as a
"wildcard", if no other function matches (this should be considered a
low-level feature).—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/ethereum/solidity/issues/3198#issuecomment-348930961,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAAJ0LkyhhXChwpUE5xo6y9p5Iecyc2lks5s89KhgaJpZM4QcinE
.
I have been thinking about that too. Utility contracts, for example, may have a single fallback function that decodes parameters and stores or forwards them somewhere else.
I like this idea. It adds the needed "default" variable to the current switch case concept wrt to how functions in a contract currently execute.
@axic what do you think about having a wildcard function and a "receive ether" function?
@chriseth what is a "wildcard function" in this instance?
A wildcard function is one that is executed if a function selector is given, but it does not match any defined functions. The "receive ether" function is run if no calldata is given.
Ahhhh...so it's basically a default logic statement as opposed to a default receive statement. You're splitting the fallback into two portions...which tbf is a good design decision imo for safety, but how to do this gracefully in the language is a different matter. What would the syntax look like?
(I actually thought I've answered this issue.)
@axic what do you think about having a wildcard function and a "receive ether" function?
Sounds good, but not sure about the naming and if the only point of the non-wildcard function is to receive ether, then it would have payable set implicitly or would it still require it to be written explicitly?
I see a couple of different options:
transfer for empty calldata and non-zero valueempty for empty calldata and no matter the valuewildcard for non-empty calldata and fallback for empty calldataThough I think we cannot really use the words fallback and wildcard together as it seems confusing.
Option 1):
contract C {
function f() {}
wildcard() { }
wildcard() payable { }
// allow receiving incoming transfers
transfer() payable { } // payable must be explicitly declared here
transfer() { } // invalid syntax
// default is not accepting incoming transfers if `transfer()` is not declared
Option 2):
contract C {
function f() {}
wildcard() { }
wildcard() payable { }
// allow receiving incoming transfers
empty() payable { }
// explicitly disallow incoming transfers (this the default)
empty() { }
Option 3):
contract C {
function f() {}
wildcard() { }
wildcard() payable { }
// allow receiving incoming transfers
fallback() payable { }
// explicitly disallow incoming transfers (this the default)
fallback() { }
I like the idea of having two separate functions:
receive() for empty calldata and non-zero value, with parentheses and implicit payablefallback() for non-empty calldata and any value, with parenthesesI would prefer receive over transfer, although I would say this is still not the best wording.
Also, fallback should either
bytes (the calldata) and return bytes (the return data)and it should somehow be made clear that this is a very low-level function that should usually not be implemented.
Splitting into two functions is a good idea.
Regarding syntax: I would use parentheses, as in falllback() {} (it seems everybody agreed on that).
Not sure about what's the best names, yet, though.
Using the parentheses would make this a breaking change, right?
Asking because I remember I had some contracts for testing (not on mainnet) that had a function called fallback() and those would no longer behave the same way (I'm guessing that no function ID would make it into the beginning switch case).
function fallback() and fallback() would be parsed differently, so function fallback() should still be valid.
Ooops, completely missed that! 😄
Also relevant: #2630.
It might make sense to not allow the wildcard function together with any other function, but that probably conflicts with upgradable contracts.
We might also have the wildcard directly drop into inline assembly to show that it is a low-level feature.
Some names that have been suggested for
wildcard, catchAll, default, fallbackreceive, receiveEther, deposit, plainCall, wildCardEmpty, emptyName suggestion: accept and fallback. "accept" might be too generic. Could also be acceptPayment - that would be more in line with payable.
Crazy proposal: we could keep everything to fallback and distinguish the purpose based on the signature instead:
fallback() {}: called for empty calldata without value - cannot be payablefallback(uint256 value) payable {}: called for empty calldata with value - has to be payablefallback(bytes4 sig) {}: called for non-matched signature without value - cannot be payablefallback(bytes4 sig, uint256 value) payable {}: called for non-matched signature with value - has to be payableThis would prevent us from flooding the language with more and more keyboards and is still entirely un-ambiguous and explicit...
That sounds like a reasonably crazy idea. Not sure I like that I like it :wink:
I like it too.
But why not omit uint256 value and just use msg.value? Then payable would be the only difference
And then we can skip sig and just use msg.sig and close the issue, since that's what we already have :-D.
Not really, since the functions wouldn't be split then
I wasn't being serious :-). A potential problem in skipping value is that we usually don't allow the same function to both have a "payable" and a "non-payable" version - but fallbacks could just be an exception there, so maybe not a problem...
The problem is that to be entirely un-ambiguous we need to require the value argument to always be > 0 (or to always have msg.value > 0 in the payable case, if we skip the argument). And this problem might be enough to tear the whole idea down, I'm not sure :-).
I would prefer to make it more visible that a contract is doing something weird. A contract should not just react on all signatures. If it does, it should be a proxy and have no other functions and people should really know what they are doing. In contrast, a contract that has some functions and can also receive ether is something "regular" and the function that is called when the contract is sent ether should be recognizable from its name (even with limited prior knowledge about the EVM).
The presence of a fallback(bytes4 sig) version (and/or the payable variant) could just not be allowed, if the contract has any other functions (except other fallbacks maybe)...
But yes, I tend to agree that it's probably better to introduce an easily recognizable name for the "receive ether" function - if we can come up with a good one, of course.
Proposal by @axic: Call the function receiveEther() payable { ... } to make it more explicit. After all, the community should move away from plain Ether anyway, so it is good to make it explicit.
Only called with zero data and non-zero value.
Fallback function (fallback() [payable] [returns (bytes memory)] { ... }) reverts by default. It is called if no other function matches.
If there is a payable fallback function but no receiveEther function, issue a warning.
There can only be at most one fallback function. Also of course at most one receiveEther function.
How about fallback() payable calldata {}?
Probably another name than calldata, as this one already has its use, but I used it for clarifying intent.
No calldata -> empty msg.data.
@loredanacirstea I would really like to avoid the name fallback for the situation of receiving Ether (without data) - what does it have to do with "falling back"? It would be a smaller change, but I don't see a justification apart from the historical one. Please correct me if I misunderstood you.
Ah, I'm not set on the name. It can be called something else. I was suggesting to only use 1 function with two optional modifiers: payable & calldata ("calldata" or another name).
If there is a payable fallback function but no
receiveEtherfunction, issue a warning.
@chriseth @axic I guess this is the incentive to actually implement the receiveEther function? Otherwise people would just keep things the way they are and it'd be the same.
Alternative to receiveEther: we could just call it ether() payable - that's already a keyword and it should be clear that the ether is received...
For the other case I suggested to add arguments to fallback, e.g. fallback(bytes4 sig, bytes calldata data) to make it clear that it is only called with at least a signature - but we had multiple objections against that: (1) it's not entirely clear if data starts at calldata offset 0 or 4 and (2) we would probably need to generally revert for at least one, but less than 4 bytes of calldata and that might be too restrictive.
I'll start implementing this as fallback() and ether() - switching to different names in the process should be trivial.
Another open question: should it be required to annotate any or both of them as external and should it be required to mark ether() as payable?
To clarify, do we now only allow two cases?
1) calldata length > 0 && ether == 0 -> "fallback"
2) calldata length == 0 && ether != 0 -> "ether"
@axic That's what viper does, but I think we don't want to force ether==0 for the "fallback" case, but instead allow "fallback" to be "payable", i.e the basic distinction is
And case 1 is further divided as:
1a. ether > 0 and "fallback" was declared payable ==> fine and executes "fallback"
1b. ether > 0 and "fallback" was not declared payable ==> revert
1c. ether = 0 ==> always fine and executes "fallback"
I'm currently still changing the parser, but there's still some details to iron out - e.g. what happens for "calldata length = 0" and "ether = 0"?
Also still open: should fallback be allowed to (optionally) return bytes memory? That was suggested above, but never definitively decided. I vote for it.
EDIT: we might only do it as second step, though - allowing it later won't be breaking.
I think empty data should always execute the receive function.
I'm voting for optionally returning bytes memory.
@chriseth now suggested to change the semantics slightly - if there is no receive-ether-function, then fallback will also be called for msg.data.length == 0, but we issue a warning suggesting to implement a receive-ether function.
Most helpful comment
my 2 cents:
fallback () {}. It's also worth noting that you'll probably want something likeespecially because
looks like a weird anonymous constructor function with two modifiers in front of it.