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 4 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
16 changes: 7 additions & 9 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
23 changes: 16 additions & 7 deletions examples/stock/company.vy
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,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.holdings[self.company] >= buy_order
fubuloubu marked this conversation as resolved.
Show resolved Hide resolved

# Take the shares off the market and give them to the stockholder.
self.holdings[self.company] -= buy_order
Expand All @@ -71,9 +71,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.holdings[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 +90,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.holdings[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,24 +105,33 @@ 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)

# Log the payment event.
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.holdings[self.company]) * self.price


# Public function to call _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()
47 changes: 35 additions & 12 deletions examples/voting/ballot.vy
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,28 @@ chairperson: public(address)
int128Proposals: public(int128)


@private
@constant
def _delegated(addr: address) -> bool:
return self.voters[addr].delegate != ZERO_ADDRESS


@public
@constant
def delegated(addr: address) -> bool:
return self.voters[addr].delegate != ZERO_ADDRESS
return self._delegated(addr)


@private
@constant
def _directlyVoted(addr: address) -> bool:
return self.voters[addr].voted and (self.voters[addr].delegate == ZERO_ADDRESS)


@public
@constant
def directlyVoted(addr: address) -> bool:
return self.voters[addr].voted and (self.voters[addr].delegate == ZERO_ADDRESS)
return self._directlyVoted(addr)


# Setup global variables
Expand Down Expand Up @@ -62,16 +74,16 @@ def giveRightToVote(voter: address):
self.voters[voter].weight = 1
self.voterCount += 1

# Used by `delegate` below, and can be called by anyone.
@public
def forwardWeight(delegate_with_weight_to_forward: address):
assert self.delegated(delegate_with_weight_to_forward)
# Used by `delegate` below, callable externally via `forwardWeight`
@private
def _forwardWeight(delegate_with_weight_to_forward: address):
assert self._delegated(delegate_with_weight_to_forward)
# Throw if there is nothing to do:
assert self.voters[delegate_with_weight_to_forward].weight > 0

target: address = self.voters[delegate_with_weight_to_forward].delegate
for i in range(4):
if self.delegated(target):
if self._delegated(target):
target = self.voters[target].delegate
# The following effectively detects cycles of length <= 5,
# in which the delegation is given back to the delegator.
Expand All @@ -92,13 +104,18 @@ def forwardWeight(delegate_with_weight_to_forward: address):
self.voters[delegate_with_weight_to_forward].weight = 0
self.voters[target].weight += weight_to_forward

if self.directlyVoted(target):
if self._directlyVoted(target):
self.proposals[self.voters[target].vote].voteCount += weight_to_forward
self.voters[target].weight = 0

# To reiterate: if target is also a delegate, this function will need
# to be called again, similarly to as above.

# Public function to call _forwardWeight
@public
def forwardWeight(delegate_with_weight_to_forward: address):
self._forwardWeight(delegate_with_weight_to_forward)

# Delegate your vote to the voter `to`.
@public
def delegate(to: address):
Expand All @@ -115,7 +132,7 @@ def delegate(to: address):

# This call will throw if and only if this delegation would cause a loop
# of length <= 5 that ends up delegating back to the delegator.
self.forwardWeight(msg.sender)
self._forwardWeight(msg.sender)

# Give your vote (including votes delegated to you)
# to proposal `proposals[proposal].name`.
Expand All @@ -135,9 +152,9 @@ def vote(proposal: int128):

# Computes the winning proposal taking all
# previous votes into account.
@public
@private
@constant
def winningProposal() -> int128:
def _winningProposal() -> int128:
winning_vote_count: int128 = 0
winning_proposal: int128 = 0
for i in range(2):
Expand All @@ -146,10 +163,16 @@ def winningProposal() -> int128:
winning_proposal = i
return winning_proposal

@public
@constant
def winningProposal() -> int128:
return self._winningProposal()


# Calls winningProposal() function to get the index
# of the winner contained in the proposals array and then
# returns the name of the winner
@public
@constant
def winnerName() -> bytes32:
return self.proposals[self.winningProposal()].name
return self.proposals[self._winningProposal()].name
2 changes: 1 addition & 1 deletion tests/parser/features/decorators/test_private.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ def set_greeting(_greeting: bytes[20]):
if a + b + c + d + 3 == 1337:
self.greeting = _greeting

@public
@private
@constant
def construct(greet: bytes[20]) -> bytes[40]:
return concat(self.greeting, greet)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ def test_call_to_self_struct(w3, get_contract):
e1: decimal
e2: timestamp

@public
@private
@constant
def get_my_struct(_e1: decimal, _e2: timestamp) -> MyStruct:
return MyStruct({e1: _e1, e2: _e2})
Expand Down
Loading