Skip to content
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

Disallow self.* calls to public functions #1561

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
d666d00
Check constancy when calling to external function
charles-cooper Jun 12, 2019
7dfc110
Add missing parens
charles-cooper Jun 12, 2019
60ccc30
Spruce up error message
charles-cooper Jun 12, 2019
8a7b0e8
include end positions in ast nodes
iamdefinitelyahuman Aug 1, 2019
e3922aa
include end offset in source map
iamdefinitelyahuman Aug 1, 2019
dad469c
disallow self.* calls to public functions
iamdefinitelyahuman Aug 2, 2019
a2b230b
update examples to remove self.* calls to public functions
iamdefinitelyahuman Aug 3, 2019
3fdbce4
update tests
iamdefinitelyahuman Aug 3, 2019
54d6417
update docs
iamdefinitelyahuman Aug 4, 2019
d6ff7c3
use public/_private function pattern
iamdefinitelyahuman Aug 5, 2019
33906a7
update docs
iamdefinitelyahuman Aug 5, 2019
88295ed
Merge branch 'master' into disallow-self-public
iamdefinitelyahuman Aug 7, 2019
0ae4143
add root path, search for imports in multiple folders
iamdefinitelyahuman Aug 8, 2019
5de86e3
Merge branch 'master' of github.com:ethereum/vyper into external_cons…
charles-cooper Aug 8, 2019
492ec1d
Add assert_modifiable to vyper
charles-cooper Aug 8, 2019
ae08d73
Fix an external call test
charles-cooper Aug 8, 2019
f35b896
Fix lint
charles-cooper Aug 8, 2019
62fd1ae
allow local namespaces for interface imports
iamdefinitelyahuman Aug 9, 2019
2c06ad6
Merge remote-tracking branch 'upstream/master' into relative-imports
iamdefinitelyahuman Aug 9, 2019
a97775c
add support for relative imports, disallow aliasing
iamdefinitelyahuman Aug 12, 2019
f3d5813
Merge branch 'master' into relative-imports
iamdefinitelyahuman Aug 15, 2019
9d04442
prevent relative imports outside base folder
iamdefinitelyahuman Aug 18, 2019
552d901
ignore import nodes in extract_sigs - allows derived interfaces
iamdefinitelyahuman Aug 18, 2019
acab11c
Merge branch 'master' into ast-end-offsets
iamdefinitelyahuman Aug 18, 2019
00dff1b
Merge branch 'master' into ast-end-offsets
iamdefinitelyahuman Aug 19, 2019
e6ea594
include all instructions in pc_pos_map, filter before return from get…
iamdefinitelyahuman Aug 19, 2019
affaaa7
Merge branch 'master' into disallow-self-public
iamdefinitelyahuman Aug 19, 2019
e5fc990
raise at compile when attempting self call via interface
iamdefinitelyahuman Aug 20, 2019
e20e540
Merge branch 'master' into relative-imports
iamdefinitelyahuman Aug 19, 2019
df1aa79
add test cases
iamdefinitelyahuman Aug 20, 2019
406d13c
interface test cases
iamdefinitelyahuman Aug 20, 2019
3945f0d
bugfixes
iamdefinitelyahuman Aug 20, 2019
537a0e9
cli/vyper_compile tests
iamdefinitelyahuman Aug 20, 2019
35a6e5a
update docs
iamdefinitelyahuman Aug 20, 2019
8de6f76
add end offsets to BinOp
iamdefinitelyahuman Aug 22, 2019
ae1ffea
include src in ast nodes
iamdefinitelyahuman Aug 22, 2019
ee2c845
add pc_jump_map
iamdefinitelyahuman Aug 22, 2019
36ec5da
add compressed source map
iamdefinitelyahuman Aug 23, 2019
f17ca4e
add missing end offsets, bugfixes - tests passing!
iamdefinitelyahuman Aug 23, 2019
a306646
raise when no args given for private call that expects args
iamdefinitelyahuman Aug 24, 2019
8867fea
add node to pack_arguments inputs, better exception verbosity
iamdefinitelyahuman Aug 24, 2019
709ef53
expand test cases for improper arg counts
iamdefinitelyahuman Aug 24, 2019
2c2a1c2
refactoring
iamdefinitelyahuman Aug 24, 2019
48add3d
source map test cases
iamdefinitelyahuman Aug 24, 2019
40ee664
Merge pull request #1480 from charles-cooper/external_constancy_check
jacqueswww Aug 24, 2019
1ad1ccd
minor edits per @jacqueswww review
iamdefinitelyahuman Aug 24, 2019
219310f
Merge pull request #1578 from iamdefinitelyahuman/relative-imports
fubuloubu Aug 24, 2019
f7e18e6
Merge pull request #1579 from iamdefinitelyahuman/self-call-missing-args
fubuloubu Aug 24, 2019
389897f
Merge pull request #1580 from iamdefinitelyahuman/ast-end-offsets
fubuloubu Aug 24, 2019
8f72a00
disallow self.* calls to public functions
iamdefinitelyahuman Aug 2, 2019
794b32f
update examples to remove self.* calls to public functions
iamdefinitelyahuman Aug 3, 2019
b7a5518
update tests
iamdefinitelyahuman Aug 3, 2019
56515a3
update docs
iamdefinitelyahuman Aug 4, 2019
b744925
use public/_private function pattern
iamdefinitelyahuman Aug 5, 2019
194d95e
update docs
iamdefinitelyahuman Aug 5, 2019
334a049
raise at compile when attempting self call via interface
iamdefinitelyahuman Aug 20, 2019
73cf385
add test cases
iamdefinitelyahuman Aug 20, 2019
93db574
Merge branch 'disallow-self-public' of https://github.com/iamdefinite…
iamdefinitelyahuman Aug 25, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/installing-vyper.rst
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ Additionally, you may try to compile an example contract by running:
vyper examples/crowdfund.vy

If everything works correctly, you are now able to compile your own smart contracts written in Vyper.
If any unexpected errors or exceptions are encountered, please feel free create an issue <https://github.com/ethereum/vyper/issues/new>.
If any unexpected errors or exceptions are encountered, please feel free to `create an issue <https://github.com/ethereum/vyper/issues/new>`_.

.. note::
If you get the error `fatal error: openssl/aes.h: No such file or directory` in the output of `make`, then run `sudo apt-get install libssl-dev1`, then run `make` again.
Expand Down
237 changes: 157 additions & 80 deletions docs/structure-of-a-contract.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,27 +36,52 @@ Functions are the executable units of code within a contract.
// ...

Function calls can happen internally or externally and have different levels of visibility (see
:ref:`structure-decorators`) towards other contracts. Functions must be decorated with either
@public or @private.
:ref:`structure-decorators`) towards other contracts. Functions must be explicitely declared as public or private.

Public Functions
----------------

Public functions (decorated with ``@public``) are a part of the contract interface and may be called via transactions or from other contracts. They cannot be called internally.

Public functions in Vyper are equivalent to external functions in Solidity.

Private Functions
-----------------

Private functions (decorated with ``@private``) are only accessible from other functions within the same contract. They are called via the ``self`` variable:

::

@private
def _times_two(amount: uint256) -> uint256:
return amount * 2

@public
def calculate(amount: uint256) -> uint256:
return self._times_two(amount)

Private functions do not have access to ``msg.sender`` or ``msg.value``. If you require these values within a private function they must be passed as parameters.

.. _structure-decorators:

Decorators
----------

============================= ===========================================
Decorator Description
============================= ===========================================
`@public` Can be called from external contracts.
`@private` Can only be called within current contract.
`@constant` Does not alter contract state.
`@payable` The contract is open to receive Ether.
`@nonreentrant(<unique_key>)` Function can only be called once,
both externally and internally. Used to
prevent reentrancy attacks.
============================= ===========================================
The following decorators are available:

The visibility decorators `@public` or `@private` are mandatory on function declarations, whilst the other decorators(@constant, @payable, @nonreentrant) are optional.
=============================== ===========================================
Decorator Description
=============================== ===========================================
``@public`` Can only be called externally.
``@private`` Can only be called within current contract.
``@constant`` Does not alter contract state.
``@payable`` The contract is open to receive Ether.
``@nonreentrant(<unique_key>)`` Function can only be called once,
both externally and internally. Used to
prevent reentrancy attacks.
=============================== ===========================================

The visibility decorators ``@public`` or ``@private`` are mandatory on function declarations, whilst the other decorators(``@constant``, ``@payable``, ``@nonreentrant``) are optional.

Default function
----------------
Expand Down Expand Up @@ -165,120 +190,172 @@ Additional information about Ethereum Natural Specification (NatSpec) can be fou
Contract Interfaces
===================

Vyper supports exporting and importing contract interfaces, this is done using a `import` and `implements` statements.
An interface is a set of function definitions used to enable communication between smart contracts. A contract interface defines all of that contract's publicly available functions. By importing the interface, your contract now knows how to call these functions in other contracts.

::
Defining Interfaces and Making External Calls
---------------------------------------------

import an_interface as FooBarInterface
Interfaces can be added to contracts either through inline definition, or by importing them from a seperate file.

implements: FooBarInterface
The ``contract`` keyword is used to define an inline external interface:

This will import the defined interface in vyper file at `an_interface.vy` (or `an_interface.json` if using ABI json interface type) and make sure the current contract implements all the necessary public functions.
Note that all interface is valid vyper code, without the return type check. Meaning you can use a contract with code in in the function body as interface as well (but default to a function body with a `pass`).
.. code-block:: python

Extracting Interfaces
---------------------
contract FooBar:
def calculate() -> uint256: constant
def test1(): modifying

Vyper has a built-in format option to allow you to make your own vyper interfaces easily.
The defined interface can then be use to make external calls, given a contract address:

.. code-block:: python

@public
def test(some_address: address):
FooBar(some_address).calculate()

The interface name can also be used as a type annotation for storage variables. You then assign an address value to the variable to access that interface. Note that assignment of an address requires the value to be cast using the contract type e.g. ``FooBar(<address_var>)``:

.. code-block:: python

foobar_contract: FooBar

@public
def __init__(foobar_address: address):
self.foobar_contract = FooBar(foobar_address)

@public
def test():
self.foobar_contract.calculate()

Specifying ``modifying`` annotation indicates that the call made to the external contract will be able to alter storage, whereas the ``constant`` call will use a ``STATICCALL`` ensuring no storage can be altered during execution.

::

vyper -f interface examples/voting/ballot.vy
contract FooBar:
def calculate() -> uint256: constant
def test1(): modifying

# Functions
@public
def test(some_address: address):
FooBar(some_address).calculate() # cannot change storage
FooBar(some_address).test1() # storage can be altered


Importing Interfaces
--------------------

Interfaces are imported with ``import`` or ``from ... import`` statements.

Imported interfaces are written using standard Vyper syntax, with the body of each function replaced by a ``pass`` statement:

.. code-block:: python

@constant
@public
def delegated(addr: address) -> bool:
def test1():
pass

# ...
@public
def calculate() -> uint256:
pass

If you want to do an external call to another contract, vyper provides an external contract extract utility as well.
You can also import a fully implemented contract and Vyper will automatically convert it to an interface.

::
Imports via ``import``
~~~~~~~~~~~~~~~~~~~~~~

vyper -f external_interface examples/voting/ballot.vy
With absolute ``import`` statements, you **must** include an alias as a name for the imported package. In the following example, failing to include ``as Foo`` will raise a compile error:

# External Contracts
contract Ballot:
def delegated(addr: address) -> bool: constant
def directlyVoted(addr: address) -> bool: constant
def giveRightToVote(voter: address): modifying
def forwardWeight(delegate_with_weight_to_forward: address): modifying
# ...
.. code-block:: python

The output can then easily be copy-pasted to be consumed.
import contract.foo as Foo

Built-in Interfaces
-------------------
Imports via ``from ... import``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Vyper supports a few built-in interfaces such as ERC20 and ERC721. These are imported from ``vyper.interfaces``:
Using ``from`` you can perform both absolute and relative imports. With ``from`` import statements you **cannot** use an alias - the name of the interface will always be that of the file:

::
.. code-block:: python

from vyper.interfaces import ERC20
from contract import foo

implements: ERC20
Relative imports are possible by prepending dots to the contract name. A single leading dot indicates a relative import starting with the current package. Two leading dots indicate a relative import from the parent of the current package:

External Calls Using Interfaces
-------------------------------
.. code-block:: python

To define external interfaces inline the `contract` keyword is used.
from . import foo
from ..interfaces import baz

Searching For Interface Files
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

When looking for a file to import Vyper will first search relative to the same folder as the contract being compiled. For absolute imports, it also searches relative to the root path for the project. Vyper checks for the file name with a ``.vy`` suffix first, then ``.json``.

When using the command line compiler, the root path defaults to to the current working directory. You can change it with the ``-p`` flag:

::

contract FooBar:
def test1(): modifying
def calculate() -> uint256: constant
$ vyper my_project/contracts/my_contract.vy -p my_project

In the above example, the ``my_project`` folder is set as the root path. A contract cannot perform a relative import that goes beyond the top-level folder.

The defined inline contract can then be use to make external calls, given a contract address.
Built-in Interfaces
-------------------

Specifying `modifying` annotation indicates that the call made to the external contract will be able to alter storage, whereas the `constant` call will use a `STATICCALL` ensuring no storage can be altered during execution.
Vyper includes common built-in interfaces such as `ERC20 <https://eips.ethereum.org/EIPS/eip-20>`_ and `ERC721 <https://eips.ethereum.org/EIPS/eip-721>`_. These are imported from ``vyper.interfaces``:

::
.. code-block:: python

@public
def test(some_address: address):
FooBar(some_address).calculate() # can not change storage
FooBar(some_address).test1() # storage can be altered
from vyper.interfaces import ERC20

An additional utility of storing a contract address in a contract is defined by the ``<global_var>: FooBar`` annotation. Note that assignment of an address requires the address value to be casted using the contract type e.g. ``FooBar(<address_var>)``.
implements: ERC20

::
You can see all the available built-in interfaces in the `Vyper GitHub <https://github.com/ethereum/vyper/tree/master/vyper/interfaces>`_ repo.

foobar_contract: FooBar

@public
def __init__(foobar_address: address):
self.foobar_contract = FooBar(foobar_address)
Implementing an Interface
-------------------------

@public
def call_test1():
# ...
You can define an interface for your contract with the ``implements`` statement:

To import interfaces to be used in externals calls, one uses the interface just as one would use an inlined interface definition.
.. code-block:: python

import an_interface as FooBarInterface

implements: FooBarInterface


This imports the defined interface from the vyper file at ``an_interface.vy`` (or ``an_interface.json`` if using ABI json interface type) and ensures your current contract implements all the necessary public functions. If any interface functions are not included in the contract, it will fail to compile. This is especially useful when developing contracts around well-defined standards such as ERC20.

Extracting Interfaces
---------------------

Vyper has a built-in format option to allow you to make your own vyper interfaces easily.

::

import foo_bar as FooBar
$ vyper -f interface examples/voting/ballot.vy

foobar_contract: FooBar
# Functions

@constant
@public
def __init__(foobar_address: address):
self.foobar_contract = FooBar(foobar_address)
def delegated(addr: address) -> bool:
pass

@public
def test():
self.foobar_contract.one()
# ...

Or alternatively
If you want to do an external call to another contract, vyper provides an external contract extract utility as well.

::

import foo_bar as FooBar
$ vyper -f external_interface examples/voting/ballot.vy

@public
def test(addy: address):
FooBar(addy).one()
# External Contracts
contract Ballot:
def delegated(addr: address) -> bool: constant
def directlyVoted(addr: address) -> bool: constant
def giveRightToVote(voter: address): modifying
def forwardWeight(delegate_with_weight_to_forward: address): modifying
# ...

The output can then easily be copy-pasted to be consumed.
2 changes: 1 addition & 1 deletion docs/testing-contracts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Vyper Contract and Basic Fixtures
This is the base requirement to load a vyper contract and start testing. The last two fixtures are optional and will be
discussed later. The rest of this chapter assumes, that you have this code set up in your ``conftest.py`` file.
Alternatively, you can import the fixtures to ``conftest.py`` or use
`pytest_plugins <https://docs.pytest.org/en/latest/plugins.html>`_.
`pytest plugins <https://docs.pytest.org/en/latest/plugins.html>`_.

Load Contract and Basic Tests
=============================
Expand Down
Loading