dev-resources.site
for different kinds of informations.
Vyper - Write your First Python Smart Contract (Series)
As the blockchain community expands and development tools progress, developers are increasingly interested in writing secure, simple, and auditable code, reflecting a growing trend in the industry.
Different languages have evolved, and Vyper is one of them. So, what is Vyper, and why is it a game-changer?
The Vyper documentation defines Vyper as a contract-oriented, Pythonic programming language that targets the Ethereum Virtual Machine (EVM). It prioritizes user safety and encourages clear coding practices via language design and efficient execution. In other words, Vyper code is not just safe and clean but also highly efficient, providing developers with a reliable tool for their projects.
Statista reports that Python is the third most popular programming language, utilized by 51% of developers globally. Its extensive ecosystem support is a significant advantage.
Why should I choose Vyper?
Many web3 programming languages exist, including Clarity, Rust, Solidity, and many more. Vyper stands out for the following reasons.
Security by Design - Built-in protection against common vulnerabilities like overflows and reentrancy attacks, with no recursive calling and automatic bounds checking, significantly challenging writing exploitable smart contracts.
Python-like Simplicity - Familiar syntax and enforced code clarity through restricted features make writing, reading, and maintaining code more straightforward. The learning curve is gentler for developers from Python backgrounds while maintaining blockchain-specific security features.
DeFi-Optimized Features - Excellent built-in decimal handling, precise state variable management, and predictable gas consumption make it well-suited for financial applications where precision and reliability are crucial.
Improved Auditability - A smaller, more focused codebase with no inheritance or complex features makes contracts easier to audit and verify. Removing features like modifiers and function overloading reduces potential points of failure.
-
Gas Efficiency - Simpler bytecode generation and restricted feature set to result in more predictable and often lower gas costs than equivalent Solidity contracts. The inability to write infinite loops also prevents potential gas-related issues.
Getting started with Vyper
As you delve into Vyper, we will use practical examples from Vyper-by-example as our guide, making Vyper's syntax structure easy to grasp and comfortable to work with.
- What Is a Contract File?
Every Vyper contract lives in its file - just one contract per file and stored with a file extension of .vy
.
- Vyper Compiler
The Vyper compiler plays a key role in the development process, transforming Vyper source code into Ethereum Virtual Machine (EVM) bytecode through several stages which include.
1. Lexical Analysis
The compiler breaks down the source code into tokens, which are the smallest units of the program (e.g., keywords, variable names, operators). This step ensures that the code adheres to Vyper's syntax rules.
2. Parsing
Tokens are structured into a syntax tree (Abstract Syntax Tree or AST), representing the logical structure of the code. This tree helps the compiler understand relationships between components, like variables, functions, and control flow.
3. Semantic Analysis
At this stage, the compiler checks for logical errors and verifies type correctness, variable declarations, and function usage. For example:
- Ensuring
uint256
variables are not assigned negative values. - Validating function parameters and return types.
4. Optimization
The compiler performs code optimizations to reduce gas usage. This includes:
- Removing redundant operations.
- Simplifying expressions.
- Minimizing storage reads and writes.
5. Bytecode Generation
The optimized AST is translated into EVM bytecode. This bytecode is the machine-level instruction set that the Ethereum Virtual Machine understands and executes.
6. ABI (Application Binary Interface) Generation
Along with the bytecode, the compiler generates an ABI, which is a JSON representation of the contract's public interface. This ABI is essential for interacting with the contract, as it defines the structure of functions and events.
7. Error Reporting
If any errors or warnings are encountered during the compilation process, the compiler provides detailed feedback, helping developers correct issues before deployment.
After the Vyper source code is compiled and checked, it is ready for deployment. However, the compiler will fail if there are any issues with the source code.
What Goes Into a Contract?
Consider a contract having different sections that tell it how to work. Let's break them down:
# pragma version ^0.4.0
# Create a string variable that can store maximum 100 characters
greet: public(String[100])
@deploy
def __init__():
self.greet = "Hello World"
@external
def function():
pass
Pragmas: Telling Vyper How to Run
Pragmas are special instructions that tell the Vyper compiler (the thing that makes your code work) how to handle your contract. They're like setting the rules before you start.
Version Pragma: Which Vyper Version to Use
Pragma tells Vyper which version to use. Starting from version 0.3.0, Vyper uses PEP440 to specify versions.
Here's how you write it:
#pragma version ^0.4.0
What does this mean?
- The
^
means "this version or higher but staying in 0.4.0" - This helps make sure your contract works as expected
State Variables
State variables allow storing values that can be assessed by all functions in the codebase. greet: public(String[100])
. This is a variable of type string, and it is public with a maximum storage space of 100
, which means it is visible and can be called.
Let's compile using Remix, an Ethereum editor for writing smart contracts. We can deploy the contract once the compile button is clicked and the source code is passed.
If you want to learn more about the low-level process, a complete documentation can be found here.
Once deployed, we see that we returned Hello World
, but if we check how much gas it costs to call greet, we notice how expensive it is.
But before we go further, let us take a detour to understand gas.
greet: public(constant(String[100])) = "hello world"
Notice we introduce a new term called constant
. What are constants, you might ask?
- Constant
C*onstants* are values that do not change throughout the execution of the contract. They are defined using the constant
keyword and are typically used for gas optimization and better code readability.
Now that we understand constants, we can use them. Since we are not changing the greet
value, it is okay to use it.
Also, there is another term we will come across soon, which is immutable. This is another gas-efficient Vyper function that allows the variable to change once when the contract is called, and it does not change afterwards.
We will look at this in-depth soon.
The Constructor
A constructor is a unique function that initializes the contract's state variables when deployed. The constructor function is defined using the special method init(). This method is called automatically and only once during contract deployment.
@deploy
def __init__():
Key Features of the Constructor in Vyper
- Runs Once: The init method is executed only when the contract is deployed.
- State Initialization: It sets up the initial values of the contract's state variables.
- No Explicit Return: The constructor doesn't return any value.
Once deployed, we see that we returned Hello World
, but if we check how much gas it costs to call greet, we notice how expensive it is.
But before we go further, let us take a detour to understand gas.
What is Gas?
Gas is the unit of computational work required to execute operations on the Ethereum Virtual Machine (EVM). It serves as a mechanism to allocate resources and prevent network abuse by ensuring every operation has a cost.
- Key Concepts of Gas in Vyper
-
Gas Cost:
- Every operation in a Vyper contract (e.g., arithmetic, storage access, control flow) has a predefined gas cost.
- Complex operations like writing to storage or calling external contracts consume significantly more gas than simple arithmetic or accessing memory.
-
Gas Limit:
- A transaction specifies the maximum amount of gas it is willing to consume, called the gas limit.
- If the gas required exceeds the gas limit, the transaction fails, and changes are reverted.
-
Gas Price:
- Users set a gas price (in gwei, a fraction of Ether) to pay for each gas unit. Higher gas prices incentivize miners to process the transaction faster.
-
Gas Optimization in Vyper:
- No Infinite Loops: Vyper disallows constructs like unbounded loops, preventing excessive gas usage.
- Efficient Features: Features like constant and immutable reduce storage access, lowering gas costs.
When greet
is set to the constructor is called, we spent 4716 gas (Cost only applies when called by a contract
.
However, when greet is set to be constant, we spent 295 gas (Cost only applies when called by a contract)
There are many tradeoffs regarding why you should use one or the other, but the key is to know when to use them. This article by Peerdh explains gas efficiency well.
My next article will delve into data types and use cases.
Conclusion
Vyper is redefining smart contract development with its focus on simplicity, security, and efficiency. With its rapidly growing community of developers, Vyper is becoming the go-to for smart contract development, creating a thriving ecosystem in the blockchain technology space.
Featured ones: