5/5
## Understanding Visibility in Vyper Visibility in smart contract programming dictates which parts of your contract (state variables and functions) can be accessed and from where. Vyper, a Pythonic language for the Ethereum Virtual Machine (EVM), uses specific keywords and decorators to manage this crucial aspect of contract security and design. Understanding visibility ensures that functions are called only when intended and data is exposed appropriately. Let's explore visibility using a simple example contract, `favorites.vy`: ```vyper # pragma version 0.4.0 # @license MIT my_favorite_number: public(uint256) # 0 @external def store(new_number: uint256): self.my_favorite_number = new_number ``` In this initial state: 1. **`my_favorite_number: public(uint256)`**: This declares a state variable named `my_favorite_number`. The `public` keyword is key here. It automatically instructs the Vyper compiler to generate a *getter function* with the same name (`my_favorite_number()`). This allows anyone (users or other contracts) outside of this contract to read the current value of `my_favorite_number` by calling this auto-generated function. The variable can also be accessed and modified from within the contract's functions (using `self.my_favorite_number`). 2. **`@external def store(...)`**: This defines a function named `store`. The `@external` decorator specifies its visibility. Functions marked `@external` can *only* be called from outside the contract. This means an Externally Owned Account (EOA) or another smart contract can trigger this function, but it cannot be called from another function within the *same* contract using the `self` keyword (e.g., `self.store(...)` would be invalid). ### Implicit Visibility Defaults Vyper has sensible defaults if you omit explicit visibility markers: 1. **State Variables:** If you remove the `public` keyword from a state variable declaration (e.g., `my_favorite_number: uint256`), Vyper does *not* create an automatic getter function. The variable remains accessible internally to the contract's logic (via `self`), but it cannot be directly read from the outside world. Its visibility effectively becomes internal. 2. **Functions:** If you do not add a visibility decorator (`@external` or `@internal`) to a function definition, Vyper **implicitly defaults to `@internal`**. ### Internal Visibility: `@internal` Let's modify our `store` function to explicitly use (or demonstrate the default of) `@internal` visibility. Imagine we also removed `public` from the state variable for this specific scenario: ```vyper # (Assuming 'public' was also removed from my_favorite_number) my_favorite_number: uint256 # 0 @internal def store(new_number: uint256): self.my_favorite_number = new_number ``` The `@internal` decorator signifies that this function can *only* be called from *within* the same contract. Other functions defined in `favorites.vy` could call it using `self.store(...)`. However, it is completely inaccessible from outside the contract. If you were to deploy this version, you would find: * No external interface (like a button in Remix) to call the `store` function directly. * No external interface to read `my_favorite_number` (because `public` was removed). ### Interaction Between Visibility Types Understanding how functions with different visibility levels interact is crucial: * **External Calling Internal (Allowed):** An `@external` function *can* call an `@internal` function using `self`. This is a common pattern for organizing code, where an external entry point uses internal helper functions. ```vyper # Conceptual Example @internal def _update_number(new_number: uint256): # Note: often prefixed with _ self.my_favorite_number = new_number @external def set_favorite_to_seven(): # Valid: External function calling an internal one self._update_number(7) ``` * **Internal Calling External (Disallowed via `self`):** An `@internal` function **cannot** call an `@external` function using `self`. External functions are designed as entry points *from outside* the contract, not for internal control flow initiated via `self`. Attempting this will cause a compilation error. ```vyper # Example leading to an Error @internal def try_invalid_call(): # INVALID: Cannot call an external function via self self.set_favorite_to_seven() # Assuming set_favorite_to_seven is @external @external def set_favorite_to_seven(): # ... (implementation) ... pass ``` Compiling code with an internal function calling an external function via `self` will result in a `CallViolation` error, explicitly stating: `CallViolation: Cannot call external functions via 'self' or via library`. This enforces the intended separation of external interfaces and internal logic. ### Key Takeaways on Vyper Visibility * **State Variables:** Use `public` to automatically create an external getter function. Without `public`, the variable is only accessible internally. * **`@external` Functions:** Can *only* be called from outside the contract. Cannot be called using `self` from within the contract. * **`@internal` Functions:** Can *only* be called from within the contract using `self`. This is the **default** visibility for functions if no decorator is specified. * **`self` Keyword:** Used for internal calls. It can invoke `@internal` functions but *cannot* invoke `@external` functions. * **Purpose:** Visibility controls access, enhancing security and enforcing clear contract interaction patterns. Choosing the correct visibility is essential for robust and secure smart contract development.
Visibility in smart contract programming dictates which parts of your contract (state variables and functions) can be accessed and from where. Vyper, a Pythonic language for the Ethereum Virtual Machine (EVM), uses specific keywords and decorators to manage this crucial aspect of contract security and design. Understanding visibility ensures that functions are called only when intended and data is exposed appropriately.
Let's explore visibility using a simple example contract, favorites.vy
:
In this initial state:
my_favorite_number: public(uint256)
: This declares a state variable named my_favorite_number
. The public
keyword is key here. It automatically instructs the Vyper compiler to generate a getter function with the same name (my_favorite_number()
). This allows anyone (users or other contracts) outside of this contract to read the current value of my_favorite_number
by calling this auto-generated function. The variable can also be accessed and modified from within the contract's functions (using self.my_favorite_number
).
@external def store(...)
: This defines a function named store
. The @external
decorator specifies its visibility. Functions marked @external
can only be called from outside the contract. This means an Externally Owned Account (EOA) or another smart contract can trigger this function, but it cannot be called from another function within the same contract using the self
keyword (e.g., self.store(...)
would be invalid).
Vyper has sensible defaults if you omit explicit visibility markers:
State Variables: If you remove the public
keyword from a state variable declaration (e.g., my_favorite_number: uint256
), Vyper does not create an automatic getter function. The variable remains accessible internally to the contract's logic (via self
), but it cannot be directly read from the outside world. Its visibility effectively becomes internal.
Functions: If you do not add a visibility decorator (@external
or @internal
) to a function definition, Vyper implicitly defaults to @internal
.
@internal
Let's modify our store
function to explicitly use (or demonstrate the default of) @internal
visibility. Imagine we also removed public
from the state variable for this specific scenario:
The @internal
decorator signifies that this function can only be called from within the same contract. Other functions defined in favorites.vy
could call it using self.store(...)
. However, it is completely inaccessible from outside the contract. If you were to deploy this version, you would find:
No external interface (like a button in Remix) to call the store
function directly.
No external interface to read my_favorite_number
(because public
was removed).
Understanding how functions with different visibility levels interact is crucial:
External Calling Internal (Allowed): An @external
function can call an @internal
function using self
. This is a common pattern for organizing code, where an external entry point uses internal helper functions.
Internal Calling External (Disallowed via self
): An @internal
function cannot call an @external
function using self
. External functions are designed as entry points from outside the contract, not for internal control flow initiated via self
. Attempting this will cause a compilation error.
Compiling code with an internal function calling an external function via self
will result in a CallViolation
error, explicitly stating: CallViolation: Cannot call external functions via 'self' or via library
. This enforces the intended separation of external interfaces and internal logic.
State Variables: Use public
to automatically create an external getter function. Without public
, the variable is only accessible internally.
@external
Functions: Can only be called from outside the contract. Cannot be called using self
from within the contract.
@internal
Functions: Can only be called from within the contract using self
. This is the default visibility for functions if no decorator is specified.
self
Keyword: Used for internal calls. It can invoke @internal
functions but cannot invoke @external
functions.
Purpose: Visibility controls access, enhancing security and enforcing clear contract interaction patterns. Choosing the correct visibility is essential for robust and secure smart contract development.
A fundamental exploration to Understanding Visibility in Vyper - Learn how Vyper's `public`, `@external`, and `@internal` keywords control access to contract elements. Understand their impact on security, default behaviors, and how functions interact based on visibility.
Previous lesson
Previous
Next lesson
Next
Give us feedback
Course Overview
About the course
The basics of blockchain transactions, how to send and receive money on a blockchain network.
How to write Python based smart contracts using Vyper.
How to read and understand Vyper smart contracts.
Vyper data structures, arrays, structs, hash maps.
How to build a smart contract application and deploy on ZKsync with Moccasin.
Smart Contract Auditor
$100,000 - $200,000 (avg. salary)
On-chain Data Analyst
$59,000 - $139,000 (avg. salary)
DeFi Developer
$75,000 - $200,000 (avg. salary)
Smart Contract Engineer
$100,000 - $150,000 (avg. salary)
Web3 developer
$60,000 - $150,000 (avg. salary)
Web3 Developer Relations
$85,000 - $125,000 (avg. salary)
Last updated on June 11, 2025
Duration: 2h 08min
Duration: 2h 32min
Duration: 24min
Course Overview
About the course
The basics of blockchain transactions, how to send and receive money on a blockchain network.
How to write Python based smart contracts using Vyper.
How to read and understand Vyper smart contracts.
Vyper data structures, arrays, structs, hash maps.
How to build a smart contract application and deploy on ZKsync with Moccasin.
Smart Contract Auditor
$100,000 - $200,000 (avg. salary)
On-chain Data Analyst
$59,000 - $139,000 (avg. salary)
DeFi Developer
$75,000 - $200,000 (avg. salary)
Smart Contract Engineer
$100,000 - $150,000 (avg. salary)
Web3 developer
$60,000 - $150,000 (avg. salary)
Web3 Developer Relations
$85,000 - $125,000 (avg. salary)
Last updated on June 11, 2025