Repository: vyperlang/vyper

Issue:
No mention of fallback function anywhere
### What's your issue about?

Searches for "fallback" in Vyper docs and in the repository return no results. If fallback functions are intentionally not allowed for security reasons, this should be mentioned in the README and/or docs.

 If this feature exists or is planned, I would love to know! Its something I consider very useful in Solidity

#### Cute Animal Picture

![image](https://user-images.githubusercontent.com/9343483/38837562-69f3c1ec-41a0-11e8-8378-bd3ec1b379db.png)




Discussion:
We don't implement a fallback function because there is the gas consideration at play, and that may not be obvious to those auditing the code.

I could see potentially supporting it. It is useful for interacting with certain kinds of funding flows and what not. As long as it was properly clear what was happening, I would endorse support.

However, Python doesn't support unnamed functions, so it would have to look something a little bit more like:

```python
spent: map(address, uint256)

# must be payable and public
@public
@payable
def __send__():
    # Must not accept or return any arguments
    self.spent[msg.sender] += msg.value
```

or something like that
I'll edit this post as a VIP later then. I know Vyper is designed around audit friendliness, and the gas consideration is reasonable, but there is such a massive utility to fallback functions I think its worth looking into further. 

I like:

`def __fallback__():`
`fallback` isn't really a descriptive name for newbies, especially considering it's typical use case as the function executing during a transaction with no call data (where the only useful version of this transaction is an ether `send()`). That was the rationale behind my naming scheme.
Might actually be called `received` instead of send, because the contract receives unintended money.
Fair enough lol

Whatever name is chosen, I think it must be implemented as a built-in style e.g. `__receive__()`
Agreed on receive although I also like 

`__default__()`
As discussed, we decided on `__receive__`.
> As discussed, we decided on __receive__.

Why is that?  It's possible that data would be included in the call, and that in some future version of vyper you might enable advanced developers to do something with that data. In that case `__default__` makes more sense to me. 




@maurelian ``__receive__`` should make the meaning more clear as it will be triggered when the contract receives funds. It may have more sophisticated use cases in the future but this naming should make it easier for new coders to understand the meaning of the function.
I think @maurelian may have a point here, we should make it slightly more general, at least whether or not it is `@payable`, which is typically an optional feature for the fallback. I could also imagine it handling call data, but I'm more on the fence about this.

`__default__` makes sense if you think of ethereum call handling as switch..case..default statement
Agreed, I did not consider the case of passing through call data. 🙈
I am happy to make it `__default__` to cover that scenario... 
Having the option to choose if its `@payable` is important because it allows contracts that throw whenever they receive ETH without making an explicit function call. Although its still possible to forward these contracts ETH using selfdestruct() on other contracts. 

Resolving patch:
diff --git a/examples/wallet/wallet.v.py b/examples/wallet/wallet.v.py
--- a/examples/wallet/wallet.v.py
+++ b/examples/wallet/wallet.v.py
@@ -57,3 +57,9 @@ def approve(_seq: int128, to: address, value: wei_value, data: bytes[4096], sigd
     self.seq += 1
     # Use raw_call to send the transaction
     return raw_call(to, data, outsize=4096, gas=3000000, value=value)
+
+
+@public
+@payable
+def __default__():
+    pass
diff --git a/vyper/parser/parser.py b/vyper/parser/parser.py
--- a/vyper/parser/parser.py
+++ b/vyper/parser/parser.py
@@ -333,6 +333,11 @@ def is_initializer(code):
     return code.name == '__init__'
 
 
+# Is a function the default function?
+def is_default_func(code):
+    return code.name == '__default__'
+
+
 # Get ABI signature
 def mk_full_signature(code):
     o = []
@@ -367,7 +372,7 @@ def parse_external_contracts(external_contracts, _contracts):
     return external_contracts
 
 
-def parse_other_functions(o, otherfuncs, _globals, sigs, external_contracts, origcode, _custom_units, runtime_only=False):
+def parse_other_functions(o, otherfuncs, _globals, sigs, external_contracts, origcode, _custom_units, fallback_function, runtime_only):
     sub = ['seq', initializer_lll]
     add_gas = initializer_lll.gas
     for _def in otherfuncs:
@@ -377,6 +382,12 @@ def parse_other_functions(o, otherfuncs, _globals, sigs, external_contracts, ori
         sig = FunctionSignature.from_definition(_def, external_contracts, custom_units=_custom_units)
         sig.gas = sub[-1].total_gas
         sigs[sig.name] = sig
+    # Add fallback function
+    if fallback_function:
+        fallback_func = parse_func(fallback_function[0], _globals, {**{'self': sigs}, **external_contracts}, origcode, _custom_units)
+        sub.append(fallback_func)
+    else:
+        sub.append(LLLnode.from_list(['revert', 0, 0], typ=None, annotation='Default function'))
     if runtime_only:
         return sub
     else:
@@ -393,8 +404,10 @@ def parse_tree_to_lll(code, origcode, runtime_only=False):
         raise VariableDeclarationException("Duplicate function or event name: %s" % [name for name in _names if _names.count(name) > 1][0])
     # Initialization function
     initfunc = [_def for _def in _defs if is_initializer(_def)]
+    # Default function
+    defaultfunc = [_def for _def in _defs if is_default_func(_def)]
     # Regular functions
-    otherfuncs = [_def for _def in _defs if not is_initializer(_def)]
+    otherfuncs = [_def for _def in _defs if not is_initializer(_def) and not is_default_func(_def)]
     sigs = {}
     external_contracts = {}
     # Create the main statement
@@ -408,8 +421,10 @@ def parse_tree_to_lll(code, origcode, runtime_only=False):
         o.append(['seq', initializer_lll])
         o.append(parse_func(initfunc[0], _globals, {**{'self': sigs}, **external_contracts}, origcode, _custom_units))
     # If there are regular functions...
-    if otherfuncs:
-        o = parse_other_functions(o, otherfuncs, _globals, sigs, external_contracts, origcode, _custom_units, runtime_only)
+    if otherfuncs or defaultfunc:
+        o = parse_other_functions(
+            o, otherfuncs, _globals, sigs, external_contracts, origcode, _custom_units, defaultfunc, runtime_only
+        )
     return LLLnode.from_list(o, typ=None)
 
 
@@ -488,6 +503,12 @@ def parse_func(code, _globals, sigs, origcode, _custom_units, _vars=None):
     # Return function body
     if sig.name == '__init__':
         o = LLLnode.from_list(['seq'] + clampers + [parse_body(code.body, context)], pos=getpos(code))
+    elif is_default_func(sig):
+        if len(sig.args) > 0:
+            raise StructureException('Default function may not receive any arguments.', code)
+        if sig.private:
+            raise StructureException('Default function may only be public.', code)
+        o = LLLnode.from_list(['seq'] + clampers + [parse_body(code.body, context)], pos=getpos(code))
     else:
         method_id_node = LLLnode.from_list(sig.method_id, pos=getpos(code), annotation='%s' % sig.name)
         o = LLLnode.from_list(['if',


Test patch:
diff --git a/tests/parser/functions/test_default_function.py b/tests/parser/functions/test_default_function.py
new file mode 100644
--- /dev/null
+++ b/tests/parser/functions/test_default_function.py
@@ -0,0 +1,73 @@
+
+def test_throw_on_sending(w3, assert_tx_failed, get_contract_with_gas_estimation):
+    code = """
+x: public(int128)
+
+@public
+def __init__():
+    self.x = 123
+    """
+    c = get_contract_with_gas_estimation(code)
+
+    assert c.x() == 123
+    assert w3.eth.getBalance(c.address) == 0
+    assert_tx_failed(lambda: w3.eth.sendTransaction({'to': c.address, 'value': w3.toWei(0.1, 'ether')}))
+    assert w3.eth.getBalance(c.address) == 0
+
+
+def test_basic_default(w3, get_logs, get_contract_with_gas_estimation):
+    code = """
+Sent: event({sender: indexed(address)})
+
+@public
+@payable
+def __default__():
+    log.Sent(msg.sender)
+    """
+    c = get_contract_with_gas_estimation(code)
+
+    logs = get_logs(w3.eth.sendTransaction({'to': c.address, 'value': 10**17}), c, 'Sent')
+    assert w3.eth.accounts[0] == logs[0].args.sender
+    assert w3.eth.getBalance(c.address) == w3.toWei(0.1, 'ether')
+
+
+def test_basic_default_not_payable(w3, assert_tx_failed, get_contract_with_gas_estimation):
+    code = """
+Sent: event({sender: indexed(address)})
+
+@public
+def __default__():
+    log.Sent(msg.sender)
+    """
+    c = get_contract_with_gas_estimation(code)
+
+    assert_tx_failed(lambda: w3.eth.sendTransaction({'to': c.address, 'value': 10**17}))
+
+
+def test_multi_arg_default(assert_compile_failed, get_contract_with_gas_estimation):
+    code = """
+@payable
+@public
+def __default__(arg1: int128):
+    pass
+    """
+    assert_compile_failed(lambda: get_contract_with_gas_estimation(code))
+
+
+def test_always_public(assert_compile_failed, get_contract_with_gas_estimation):
+    code = """
+@private
+def __default__():
+    pass
+    """
+    assert_compile_failed(lambda: get_contract_with_gas_estimation(code))
+
+
+def test_always_public_2(assert_compile_failed, get_contract_with_gas_estimation):
+    code = """
+Sent: event({sender: indexed(address)})
+
+def __default__():
+    log.Sent(msg.sender)
+    """
+    assert_compile_failed(lambda: get_contract_with_gas_estimation(code))