Repository: vyperlang/vyper

Issue:
Runtime error when making an external call to the same contract
This code makes it an error to make an external call to the same contract:

https://github.com/ethereum/vyper/blob/c296b2d7532d913103aad494b749f8179a3acddc/vyper/parser/external_call.py#L75

This is a surprising limitation. It doesn't seem to have a clear benefit, and it could be problematic. As an example, multisig wallets often use self-calls to perform administrative functions. This way the wallet owners have to agree to make a change like lowering the required threshold of signatures. In Vyper, this would produce a runtime error.


Discussion:
We originally wanted to avoid a recursion/stack-exhaustion kind of attack, but I think we relaxed that a bit by just making it unergonomic. If someone wants to write it that way, they can. Just be aware of how the mechanism could be abused.
I think this should just be a compile-time error. I think our latest consensus was to refactor into a private function for these kinds of self calls (and never use public functions like solidity external functions)
Meeting Notes: Remove runtime-check as we have compile time check for this already, then document the behaviour.
cf. https://github.com/ethereum/vyper/pull/1582 thanks @iamdefinitelyahuman !
Meeting Notes: Remove assertion of `dst != self.address` in `raw_call`. Everything else is solved.

Resolving patch:
diff --git a/vyper/parser/external_call.py b/vyper/parser/external_call.py
--- a/vyper/parser/external_call.py
+++ b/vyper/parser/external_call.py
@@ -22,8 +22,7 @@ def external_call(node, context, interface_name, contract_address, pos, value=No
         value = 0
     if gas is None:
         gas = "gas"
-    if contract_address.value == "address":
-        raise StructureException("External calls to self are not permitted.", node)
+
     method_name = node.func.attr
     sig = context.sigs[interface_name][method_name]
     inargs, inargsize, _ = pack_arguments(
@@ -33,7 +32,6 @@ def external_call(node, context, interface_name, contract_address, pos, value=No
     sub = [
         "seq",
         ["assert", ["extcodesize", contract_address]],
-        ["assert", ["ne", "address", contract_address]],
     ]
     if context.is_constant() and not sig.const:
         # TODO this can probably go


Test patch:
diff --git a/tests/parser/features/external_contracts/test_external_contract_calls.py b/tests/parser/features/external_contracts/test_external_contract_calls.py
--- a/tests/parser/features/external_contracts/test_external_contract_calls.py
+++ b/tests/parser/features/external_contracts/test_external_contract_calls.py
@@ -277,9 +277,10 @@ def _expr(x: address) -> int128:
     c2 = get_contract(contract_2)
 
     c2._stmt(c1.address)
-    c2._expr(c1.address)
-    assert_tx_failed(lambda: c2._stmt(c2.address))
-    assert_tx_failed(lambda: c2._expr(c2.address))
+    c2._stmt(c2.address)
+
+    assert c2._expr(c1.address) == 1
+    assert c2._expr(c2.address) == 1
 
 
 def test_invalid_nonexistent_contract_call(w3, assert_tx_failed, get_contract):
diff --git a/tests/parser/functions/test_interfaces.py b/tests/parser/functions/test_interfaces.py
--- a/tests/parser/functions/test_interfaces.py
+++ b/tests/parser/functions/test_interfaces.py
@@ -316,7 +316,7 @@ def foo() -> uint256:
     assert global_compiled == local_compiled
 
 
-def test_self_interface_cannot_compile(assert_compile_failed):
+def test_self_interface_is_allowed(get_contract):
     code = """
 interface Bar:
     def foo() -> uint256: view
@@ -329,10 +329,11 @@ def foo() -> uint256 :
 def bar() -> uint256:
     return Bar(self).foo()
 """
-    assert_compile_failed(lambda: compile_code(code), StructureException)
+    c = get_contract(code)
+    assert c.bar() == 42
 
 
-def test_self_interface_via_storage_raises(get_contract, assert_tx_failed):
+def test_self_interface_via_storage(get_contract):
     code = """
 interface Bar:
     def foo() -> uint256: view
@@ -352,10 +353,10 @@ def bar() -> uint256:
     return self.bar_contract.foo()
     """
     c = get_contract(code)
-    assert_tx_failed(lambda: c.bar())
+    assert c.bar() == 42
 
 
-def test_self_interface_via_calldata_raises(get_contract, assert_tx_failed):
+def test_self_interface_via_calldata(get_contract):
     code = """
 interface Bar:
     def foo() -> uint256: view
@@ -369,7 +370,7 @@ def bar(a: address) -> uint256:
     return Bar(a).foo()
     """
     c = get_contract(code)
-    assert_tx_failed(lambda: c.bar(c.address))
+    assert c.bar(c.address) == 42
 
 
 type_str_params = [