Smart Contracts with Vyper
An alternative smart contract language focused on security and simplicity
Smart Contracts with Vyper
Vyper is an experimental, contract-oriented programming language for the Ethereum Virtual Machine that strives to provide superior auditability by making it easier for developers to produce intelligible code. One of the core principles of Vyper is to make it virtually impossible for developers to write misleading code.
In this chapter, we will look at common problems with smart contracts, introduce the Vyper contract programming language, and compare it to Solidity.
Vulnerabilities and Vyper
A study analyzing nearly one million deployed Ethereum smart contracts found that many contained serious vulnerabilities. The researchers outlined three basic categories of trace vulnerabilities:
Suicidal contracts — Smart contracts that can be killed by arbitrary addresses
Greedy contracts — Smart contracts that can reach a state in which they cannot release ether
Prodigal contracts — Smart contracts that can be made to release ether to arbitrary addresses
Vyper is designed to make it easier to write secure code, or equally to make it more difficult to accidentally write misleading or vulnerable code.
Comparison to Solidity
Vyper deliberately omits some of Solidity's features to make unsafe code harder to write.
Features Vyper Does Not Have
Modifiers — Vyper does not have modifiers. Instead, inline checks and assertions are used directly in functions. This improves auditability because readers don't have to mentally "wrap" modifier code around functions.
Class inheritance — Vyper does not support inheritance. The Vyper philosophy holds that inheritance requires coders and auditors to jump between multiple files to understand what a program is doing.
Inline assembly — Vyper does not allow inline assembly. This removes a potential source of bugs and exploits while keeping contracts more readable.
Function overloading — Vyper does not allow multiple functions with the same name but different parameter types.
Operator overloading — User-defined operators are not allowed.
Infinite loops — Loops must have bounded iteration. The for loop can only iterate over fixed ranges or arrays.
Why These Omissions?
These features are omitted not because they are bad, but because they can make code harder to understand and audit. Vyper prioritizes:
- Security — Fewer features means fewer attack vectors
- Auditability — Code should be easy to read and understand
- Simplicity — Simple code is less likely to contain bugs
Vyper Syntax
Vyper syntax is inspired by Python, making it familiar to many developers:
# @version ^0.3.7
# State variables
owner: public(address)
balance: public(HashMap[address, uint256])
# Events
event Transfer:
sender: indexed(address)
receiver: indexed(address)
amount: uint256
# Constructor
@external
def __init__():
self.owner = msg.sender
# Public function
@external
def transfer(to: address, amount: uint256) -> bool:
assert self.balance[msg.sender] >= amount, "Insufficient balance"
self.balance[msg.sender] -= amount
self.balance[to] += amount
log Transfer(msg.sender, to, amount)
return True
# View function
@external
@view
def get_balance(addr: address) -> uint256:
return self.balance[addr]Key Syntax Elements
Decorators:
@external— Function can be called from outside the contract@internal— Function can only be called from within the contract@view— Function does not modify state@pure— Function does not read or modify state@payable— Function can receive ether
Types:
uint256,int128, etc. — Integer typesaddress— 20-byte Ethereum addressbool— Boolean valuebytes32,Bytes[100]— Fixed and dynamic byte arraysString[100]— Fixed-length stringsHashMap[key_type, value_type]— Hash maps (like Solidity mappings)DynArray[type, max_len]— Dynamic arrays with maximum length
Built-in functions:
assert— Check condition, revert if falseraw_call— Low-level call to another contractcreate_forwarder_to— Create minimal proxy contractconcat— Concatenate byte strings
A Simple Faucet in Vyper
# @version ^0.3.7
# Faucet - dispenses small amounts of ether
owner: public(address)
event Withdrawal:
to: indexed(address)
amount: uint256
event Deposit:
sender: indexed(address)
amount: uint256
@external
def __init__():
self.owner = msg.sender
@external
def withdraw(amount: uint256):
assert amount <= 100000000000000000, "Max 0.1 ether" # 0.1 ether in wei
assert self.balance >= amount, "Insufficient funds"
send(msg.sender, amount)
log Withdrawal(msg.sender, amount)
@external
@payable
def __default__():
log Deposit(msg.sender, msg.value)
@external
def destroy():
assert msg.sender == self.owner, "Only owner"
selfdestruct(self.owner)Compiling Vyper
Install Vyper:
pip install vyperCompile a contract:
vyper Faucet.vyGet the ABI:
vyper -f abi Faucet.vyWhen to Use Vyper
Vyper is a good choice when:
- Security is paramount — The simpler language reduces attack surface
- Auditability is required — Code is easier for auditors to review
- Team prefers Python — Familiar syntax reduces learning curve
- Contract is relatively simple — Complex inheritance isn't needed
Solidity may be preferable when:
- Ecosystem tooling matters — More tools and libraries support Solidity
- Complex patterns are needed — Inheritance and modifiers can reduce code duplication
- Team knows JavaScript/C++ — Syntax is more familiar
Conclusions
Vyper offers an alternative approach to smart contract development that prioritizes security and auditability over feature richness. While it has a smaller ecosystem than Solidity, its design philosophy makes it an excellent choice for security-critical contracts.
The choice between Vyper and Solidity is not binary — many projects use both, selecting the most appropriate language for each contract based on its requirements.