Back to glossary

Vyper Functions

Table of Contents

Understanding Vyper functions

In the Vyper programming language, functions are blocks of logic that execute specific tasks within a smart contract. They manage contract-user interactions, access to data, and communication between contracts. Each function is designed for readability, auditability, and security, solidifying Vyper's reputation as a security-first smart contract development language. 

Functions in Vyper help organize contract logic into modular, reusable components. While the language does not enforce single-responsibility per function, its minimalist philosophy encourages small, focused functions. This design approach improves readability, reduces duplication, and aligns with widely accepted software engineering principles more rigorously than in many other smart contract languages.

In decentralized applications (dApps), well-structured functions improve predictability, simplify testing, and help prevent unintended behavior.

Example of defining an external function in Vyper:

@external
def get_balance():
    # Function logic goes here
    pass

What makes Vyper functions unique?

Vyper is a Python-inspired smart contract programming language designed for the Ethereum ecosystem, with a focus on simplicity and security. Compared to Solidity functions, Vyper enforces stricter rules to make contracts more auditable and exploit-resistant:

  • Compared to Solidity, Vyper does not support function overloading, the ability to define multiple functions with the same name but different parameter types or argument counts. As a result, every function in Vyper must have a unique name and signature.
  • Functions must explicitly declare their visibility.
  • Fallback logic is simplified to make token and ETH handling safer and more predictable.
  • Unbounded dynamic types are restricted, which helps ensure predictable gas usage and reduces the risk of denial-of-service vulnerabilities.
  • Vyper also avoids features like inline assembly, dynamic code generation, and custom modifiers, leading to more transparent and verifiable code.

These design constraints reduce ambiguity and the potential for security flaws, although they limit the flexibility of developers who prefer more expressive tooling.

Function visibility in Vyper

To improve contract security, Vyper requires each function to be marked as either @external or @internal. Functions can also specify mutability using @view (read-only access) and @pure (no state access), which can reduce gas costs when functions are called externally and make no state changes.

Example of an external view function:

# External view function for reading contract state
@external
@view
def get_balance(addr: address) -> uint256:
    return self.balances[addr]

External functions

External functions can be called from outside the contract by users or other contracts. They're the public API of your smart contract.

Example of an external function summing two values:

@external
@pure
def calculate_sum(a: uint256, b: uint256) -> uint256:
    return a + b

Internal functions

Internal functions are used within a contract and cannot be accessed externally. Developers use them to break down complex logic into focused, reusable components.

Calling an internal function from an external function:

@internal
def _double(x: uint256) -> uint256:
    return x * 2

@external
def use_internal(x: uint256) -> uint256:
    return self._double(x)

Examples of core Vyper functions

Payable function

Enables the contract to receive Ether (ETH) and is required for handling deposits.

@external
@payable
def deposit():
    # Logic to handle incoming ETH
    pass

Receive function

Automatically triggered when a contract receives ETH without any accompanying function call. Use it to safely log or reject unintended transfers.

event Received:
    sender: indexed(address)
    value: uint256

@external
@payable
def __receive__():
    log Received(msg.sender, msg.value)

Use log to emit structured events, and raise or assert to enforce conditions and revert safely.

Fallback function

Triggered when a function call doesn’t match a defined signature. Use it to handle unknown or erroneous calls and prevent silent failures safely. 

@external
@payable
def __default__():
    revert("Function does not exist")

Note: In Vyper, the __default__() function serves as the fallback function. It is executed when a function call does not match any defined function signature or when the contract receives Ether without accompanying data. This behavior is similar to Solidity's fallback function.

This function ensures that any unexpected calls or direct Ether transfers are safely handled, preventing silent failures.

Store function

Writes data to contract storage. Use it to persist user inputs, settings, or values that must be remembered between transactions. For example, a user-submitted number or configuration setting.

stored_value: public(uint256)

@external
def store(value: uint256):
    self.stored_value = value

Withdraw function

Enables users to withdraw ETH they have previously deposited. This ensures users can reclaim their funds without relying on a third party, as long as their balance covers the requested amount.

The example function below validates that a user has sufficient balance, updates their stored balance, and transfers the specified amount of ETH back to their address.

@external
def withdraw(amount: uint256):
    assert self.balances[msg.sender] >= amount, "Insufficient balance"
    self.balances[msg.sender] -= amount
    send(msg.sender, amount)

Mock/test functions

Simulate contract logic during development and testing without risking real assets.

@external
def mock_transfer(receiver: address, amount: uint256):
    """
    Simulated transfer function for use in test environments only.
    Typically used with frameworks like Foundry for mocking behavior.
    This function should be excluded from production deployments.
    """
    pass

Interfaces in Vyper

Interfaces allow contracts to call functions of other contracts safely and explicitly, enforce type safety, and improve contract modularity.

interface ERC20:
    def transfer(_to: address, _value: uint256) -> bool: nonpayable

@external
def send_token(token: address, to: address, value: uint256):
    ERC20(token).transfer(to, value)

Common Vyper decorators

  • @external: Callable from outside the contract.
  • @internal: Used only within the contract.
  • @view: Reads state, doesn’t modify it.
  • @pure: For functions that use only inputs to compute a result, without accessing contract state.
  • @payable: Allows receiving ETH.
  • @nonreentrant("lock"): Prevents reentrancy attacks using a named lock.
  • In Vyper 0.4, the @nonreentrant decorator applies a global reentrancy lock to functions.
  • @nonpayable (implicit): By default, this rejects ETH unless marked @payable.

Vault smart contract example in Vyper

This Vault contract illustrates a common, secure pattern for handling ETH deposits and withdrawals. It follows the checks-effects-interactions pattern to reduce reentrancy attacks and explicitly rejects unexpected ETH transfers via the __default__ function.

Vault.vy

# Vault contract: Secure deposit and withdrawal system
# Demonstrates checks-effects-interactions pattern to mitigate reentrancy attacks

balances: public(HashMap[address, uint256])
owner: public(address)
reentrancy_lock: bool

@external
def __init__():
    self.owner = msg.sender

@external
@payable
def deposit():
    """
    Receives ETH from the user and updates their internal balance in the vault.
    """
    self.balances[msg.sender] += msg.value

@external
@nonreentrant("lock")
def withdraw(amount: uint256):
    """
    Withdraws the specified amount if the sender has sufficient balance.
    Follows checks-effects-interactions pattern for safety.
    """
    assert self.balances[msg.sender] >= amount, "Insufficient balance"
    self.balances[msg.sender] -= amount
    send(msg.sender, amount)

@external
def __default__():
    """
    Reverts any direct ETH transfers to the contract.
    """
    revert("Direct ETH transfers not allowed")

In the code block above:

  • balances is a public mapping that tracks each user’s deposited ETH.
  • owner stores the address of the account that deployed the contract.
  • __init__() is the constructor, executed once at deployment, and assigns the deployer as the contract’s owner.
  • The deposit() is a payable function that allows a user to send ETH to the contract. The received amount is added to the sender’s balance in the contract’s internal mapping to enable future withdrawals.
  • withdraw(amount) checks the sender’s balance, subtracts the withdrawal amount, and sends the ETH.
  • __default__() is a fallback function that reverts unexpected or direct ETH transfers without function calls.
  • @nonreentrant("lock") ensures a function cannot be re-entered during execution. It uses a named mutex (in this case, lock) and declares a reentrancy_lock: bool variable at the contract level. 

Best practices for writing Vyper functions

Use clear visibility

Always specify @external or @internal for functions to clearly define access and scope. 


Name functions descriptively

Avoid abbreviations and cryptic names. Functions like store_balance or calculate_fee are preferable to short, opaque names like sb() or cf().


Validate inputs early

Use assert statements to catch invalid inputs or contract states before modifying data.

assert amount > 0, "Amount must be positive."

Follow checks-effects-interactions to protect from reentrancy

Use the @nonreentrant decorator to prevent functions from being called again before they complete. Apply reentrancy locks to block recursive calls.  Check conditions first, then update the state and transfer funds last.

reentrancy_lock: bool

@external
@nonreentrant("lock")
def withdraw(amount: uint256):
    assert self.balances[msg.sender] >= amount
    self.balances[msg.sender] -= amount
    send(msg.sender, amount)

Write modular, testable code

Break large functions into smaller internal helpers, and use testing frameworks like Foundry or simple Python mocks to verify Vyper contract behavior safely.

Conclusion

Vyper’s strict function design enforces clarity and security, empowering developers to write smart contracts that are secure, maintainable, and purpose-driven.

To learn more, explore the Vyper and Python courses on Updraft. 

Related Terms

No items found.