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:

  1. Security — Fewer features means fewer attack vectors
  2. Auditability — Code should be easy to read and understand
  3. 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 types
  • address — 20-byte Ethereum address
  • bool — Boolean value
  • bytes32, Bytes[100] — Fixed and dynamic byte arrays
  • String[100] — Fixed-length strings
  • HashMap[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 false
  • raw_call — Low-level call to another contract
  • create_forwarder_to — Create minimal proxy contract
  • concat — 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 vyper

Compile a contract:

vyper Faucet.vy

Get the ABI:

vyper -f abi Faucet.vy

When 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
Both Solidity and Vyper compile to EVM bytecode. Contracts written in either language can interact with each other seamlessly — the EVM doesn't care what language produced the bytecode.

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.