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 10 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
55 changes: 40 additions & 15 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 visibility decorators `@public` or `@private` are mandatory on function declarations, whilst the other decorators(@constant, @payable, @nonreentrant) are optional.
The following decorators are available:

=============================== ===========================================
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
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
45 changes: 25 additions & 20 deletions docs/vyper-by-example.rst
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,8 @@ contract, we are provided with a built-in variable ``msg`` and we can access
the public address of any method caller with ``msg.sender``. Similarly, the
amount of ether a user sends can be accessed by calling ``msg.value``.

.. warning:: ``msg.sender`` will change between internal function calls so that
if you're calling a function from the outside, it's correct for the first
function call. But then, for the function calls after, ``msg.sender`` will
reference the contract itself as opposed to the sender of the transaction.
.. note:: ``msg.sender`` and ``msg.value`` can only be accessed from public
functions. If you require these values within a private function they must be passed as parameters.

Here, we first check whether the current time is before the auction's end time
using the ``assert`` function which takes any boolean statement. We also check
Expand Down Expand Up @@ -403,11 +401,9 @@ Let’s move onto the constructor.
:language: python
:pyobject: __init__

.. warning:: Both ``msg.sender`` and ``msg.balance`` change between internal
function calls so that if you're calling a function from the outside, it's
correct for the first function call. But then, for the function calls after,
``msg.sender`` and ``msg.balance`` reference the contract itself as opposed
to the sender of the transaction.
.. note:: ``msg.sender`` and ``msg.value`` can only be accessed from public
functions. If you require these values within a private function they must be
passed as parameters.

In the constructor, we hard-coded the contract to accept an
array argument of exactly two proposal names of type ``bytes32`` for the contracts
Expand All @@ -427,6 +423,8 @@ Now that the initial setup is done, lets take a look at the functionality.
:language: python
:pyobject: giveRightToVote

.. note:: Throughout this contract, we use a pattern where ``@public`` functions return data from ``@private`` functions that have the same name prepended with an underscore. This is because Vyper does not allow calls between public functions within the same contract. The private function handles the logic and allows internal access, while the public function acts as a getter to allow external viewing.

We need a way to control who has the ability to vote. The method
``giveRightToVote()`` is a method callable by only the chairperson by taking
a voter address and granting it the right to vote by incrementing the voter's
Expand Down Expand Up @@ -471,13 +469,15 @@ is a read-only function and we benefit by saving gas fees.

.. literalinclude:: ../examples/voting/ballot.vy
:language: python
:pyobject: winningProposal
:lines: 153-170

The ``winningProposal()`` method returns the key of proposal in the ``proposals``
The ``_winningProposal()`` method returns the key of proposal in the ``proposals``
mapping. We will keep track of greatest number of votes and the winning
proposal with the variables ``winningVoteCount`` and ``winningProposal``,
respectively by looping through all the proposals.

``winningProposal()`` is a public function allowing external access to ``_winningProposal()``.

.. literalinclude:: ../examples/voting/ballot.vy
:language: python
:pyobject: winnerName
Expand Down Expand Up @@ -512,14 +512,16 @@ Let's get started.
:language: python
:linenos:

.. note:: Throughout this contract, we use a pattern where ``@public`` functions return data from ``@private`` functions that have the same name prepended with an underscore. This is because Vyper does not allow calls between public functions within the same contract. The private function handles the logic and allows internal access, while the public function acts as a getter to allow external viewing.

The contract contains a number of methods that modify the contract state as
well as a few 'getter' methods to read it. We first declare several events
that the contract logs. We then declare our global variables, followed by
function definitions.

.. literalinclude:: ../examples/stock/company.vy
:language: python
:lines: 7-13
:lines: 11-18

We initiate the ``company`` variable to be of type ``address`` that's public.
The ``totalShares`` variable is of type ``currency_value``, which in this case
Expand All @@ -539,16 +541,18 @@ company's address is initialized to hold all shares of the company in the

.. literalinclude:: ../examples/stock/company.vy
:language: python
:pyobject: stockAvailable
:lines: 33-44

We will be seeing a few ``@constant`` decorators in this contract—which is
used to decorate methods that simply read the contract state or return a simple
calculation on the contract state without modifying it. Remember, reading the
blockchain is free, writing on it is not. Since Vyper is a statically typed
language, we see an arrow following the definition of the ``stockAvailable()``
language, we see an arrow following the definition of the ``_stockAvailable()``
method, which simply represents the data type which the function is expected
to return. In the method, we simply key into ``self.holdings`` with the
company's address and check it's holdings.
company's address and check it's holdings. Because ``_stockAvailable()`` is a
private method, we also include the public ``stockAvailable()`` method to allow
external access.

Now, lets take a look at a method that lets a person buy stock from the
company's holding.
Expand All @@ -566,10 +570,11 @@ Now that people can buy shares, how do we check someone's holdings?

.. literalinclude:: ../examples/stock/company.vy
:language: python
:pyobject: getHolding
:lines: 63-74

The ``getHolding()`` is another ``@constant`` method that takes an ``address``
The ``_getHolding()`` is another ``@constant`` method that takes an ``address``
and returns its corresponding stock holdings by keying into ``self.holdings``.
Again, a public function ``getHolding()`` is included to allow external access.

.. literalinclude:: ../examples/stock/company.vy
:language: python
Expand Down Expand Up @@ -613,11 +618,11 @@ sends its ether to an address.

.. literalinclude:: ../examples/stock/company.vy
:language: python
:pyobject: debt
:lines: 129-140

We can also check how much the company has raised by multiplying the number of
shares the company has sold and the price of each share. We can get this value
by calling the ``debt()`` method.
shares the company has sold and the price of each share. Internally, we get
this value by calling the ``_debt()`` method. Externally it is accessed via ``debt()``.

.. literalinclude:: ../examples/stock/company.vy
:language: python
Expand Down
37 changes: 28 additions & 9 deletions examples/stock/company.vy
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,17 @@ def __init__(_company: address, _total_shares: uint256(currency_value),
# The company holds all the shares at first, but can sell them all.
self.holdings[self.company] = _total_shares

# Find out how much stock the company holds
@private
@constant
def _stockAvailable() -> uint256(currency_value):
return self.holdings[self.company]

# Public function to allow external access to _stockAvailable
@public
@constant
def stockAvailable() -> uint256(currency_value):
return self.holdings[self.company]
return self._stockAvailable()

# Give some value to the company and get stock in return.
@public
Expand All @@ -44,7 +51,7 @@ def buyStock():
buy_order: uint256(currency_value) = msg.value / self.price # rounds down

# Check that there are enough shares to buy.
assert self.stockAvailable() >= buy_order
assert self._stockAvailable() >= buy_order

# Take the shares off the market and give them to the stockholder.
self.holdings[self.company] -= buy_order
Expand All @@ -54,10 +61,16 @@ def buyStock():
log.Buy(msg.sender, buy_order)

# Find out how much stock any address (that's owned by someone) has.
@private
@constant
def _getHolding(_stockholder: address) -> uint256(currency_value):
return self.holdings[_stockholder]

# Public function to allow external access to _getHolding
@public
@constant
def getHolding(_stockholder: address) -> uint256(currency_value):
return self.holdings[_stockholder]
return self._getHolding(_stockholder)

# Return the amount the company has on hand in cash.
@public
Expand All @@ -71,9 +84,9 @@ def sellStock(sell_order: uint256(currency_value)):
assert sell_order > 0 # Otherwise, this would fail at send() below,
# due to an OOG error (there would be zero value available for gas).
# You can only sell as much stock as you own.
assert self.getHolding(msg.sender) >= sell_order
assert self._getHolding(msg.sender) >= sell_order
# Check that the company can pay you.
assert self.cash() >= (sell_order * self.price)
assert self.balance >= (sell_order * self.price)

# Sell the stock, send the proceeds to the user
# and put the stock back on the market.
Expand All @@ -90,7 +103,7 @@ def sellStock(sell_order: uint256(currency_value)):
def transferStock(receiver: address, transfer_order: uint256(currency_value)):
assert transfer_order > 0 # This is similar to sellStock above.
# Similarly, you can only trade as much stock as you own.
assert self.getHolding(msg.sender) >= transfer_order
assert self._getHolding(msg.sender) >= transfer_order

# Debit the sender's stock and add to the receiver's address.
self.holdings[msg.sender] -= transfer_order
Expand All @@ -105,7 +118,7 @@ def payBill(vendor: address, amount: wei_value):
# Only the company can pay people.
assert msg.sender == self.company
# Also, it can pay only if there's enough to pay them with.
assert self.cash() >= amount
assert self.balance >= amount

# Pay the bill!
send(vendor, amount)
Expand All @@ -114,15 +127,21 @@ def payBill(vendor: address, amount: wei_value):
log.Pay(vendor, amount)

# Return the amount in wei that a company has raised in stock offerings.
@private
@constant
def _debt() -> wei_value:
return (self.totalShares - self._stockAvailable()) * self.price

# Public function to allow external access to _debt
@public
@constant
def debt() -> wei_value:
return (self.totalShares - self.holdings[self.company]) * self.price
return self._debt()

# Return the cash holdings minus the debt of the company.
# The share debt or liability only is included here,
# but of course all other liabilities can be included.
@public
@constant
def worth() -> wei_value:
return self.cash() - self.debt()
return self.balance - self._debt()
Loading