Skip to content

VIP: syntactic distinction for external vs internal calls #2856

@charles-cooper

Description

@charles-cooper

Simple Summary

Add a syntactic distinction between internal and external calls. This could be in the form of a keyword, e.g. await SomeInterface(msg.sender).foo(). The keyword could also be call.

Motivation

Internal and external calls have very different semantics. External calls invoke the CALL opcode and pass execution context to another (probably untrusted) contract, while internal calls are "safe" in that they just JUMP around the local contract. This is already reflected in the semantics of internal vs external calls, for instance external calls support the keywords gas=, value=, skip_contract_check=, default_return_value=, which are not supported for internal calls.

Right now, it is relatively easy to tell visually whether a function call is an internal or external call, as internal calls will all use the self namespace (e.g. self.foo(), as opposed to self.bar.foo()). However, as we move towards a more complex module system in vyper (cf. #1954, #2431), it will become more difficult to tell at a glance whether any given call is internal or external, and will require referencing the definition of a function to determine if it is internal or external. This seems to go against vyper's goal of readability/auditability. It would also help the author of a contract, as it forces them to consider the consequences every time they call to an external contract. External calls are expensive to reason about, and the syntax should reflect that! Lastly, this VIP makes it easier to find all external calls made by a contract (which might be done during code review, audit or vulnerability scanning), as it can be done with simple text search.

This VIP proposes the use of the await keyword to signal that a call is external. This keyword is already familiar to Python programmers, and fits well with await's cooperative multitasking semantics - "this will pass execution control to something else, and we may get control back after it returns".

Potential Drawbacks

A drawback of this VIP is that it introduces a code reusability concern. For instance, if HelperContract is initially implemented as an internal module, called as follows

self.helper_contract.foo()

but then later due to code size issues, needs to be factored out into a separately deployed contract, any usages like the above would need to be changed to

await self.helper_contract.foo()

This might be annoying to do. But, maybe this is a feature, not a bug(!), in that it forces the programmer to be explicit about the scope and execution context of the helper contract.

Specification

Calls to external contracts are required to be prefixed with the await keyword. If it is not (or, conversely, if a call to an internal module is prefixed with await), a semantic or typechecker error should be thrown.

Backwards Compatibility

Users will need to syntactically update every call to an external contract.

Dependencies

References

#1954, #2431

Copyright

Copyright and related rights waived via CC0

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions