Vyper

Security scanning for Vyper smart contracts. Vyper is a Python-like smart contract language designed for security and simplicity. Its limited feature set...

Last updated: January 14, 2026

Vyper Guide

Security scanning for Vyper smart contracts.

Overview

Vyper is a Python-like smart contract language designed for security and simplicity. Its limited feature set intentionally reduces attack surface.


Supported Versions

Version Support Notes
0.3.x Full Recommended
0.2.x Full Legacy support
0.1.x Limited Basic analysis

Available Scanners

Scanner Focus Description
Vyper Analyzer Core security Vyper-specific vulnerability detection
Moccasin Framework-aware Modern Vyper tooling integration

Vyper Security Features

Vyper has built-in security features:

Bounds Checking

All array accesses are bounds-checked:

@external
def get_item(index: uint256) -> uint256:
    # Automatic bounds check - reverts if out of range
    return self.items[index]

Integer Overflow Protection

Arithmetic operations revert on overflow:

@external
def add(a: uint256, b: uint256) -> uint256:
    # Reverts on overflow automatically
    return a + b

No Recursive Calls

Reentrancy is prevented by design:

# Vyper doesn't allow recursive external calls
# This pattern is not possible in Vyper

No Modifiers

Explicit function logic prevents hidden complexity:

@external
def restricted_function():
    # Access control must be explicit
    assert msg.sender == self.owner, "Not owner"
    # ... rest of function

Project Setup

Directory Structure

project/
├── contracts/          # Vyper contracts
│   ├── Token.vy
│   └── Vault.vy
├── interfaces/         # Interface definitions
│   └── IERC20.vy
└── ape-config.yaml     # Ape framework config

What to Upload

Include:

  • All .vy files
  • Interface files
  • Configuration files

Exclude:

  • Test files (optional)
  • Build artifacts

Common Vulnerability Patterns

Even with Vyper's safety features, vulnerabilities can occur:

Access Control Issues

# BAD: No access control
@external
def set_owner(new_owner: address):
    self.owner = new_owner

# GOOD: Proper access control
@external
def set_owner(new_owner: address):
    assert msg.sender == self.owner, "Not owner"
    self.owner = new_owner

Incorrect Comparisons

# BAD: Using == for address comparison
@external
def is_owner(addr: address) -> bool:
    return addr == empty(address)  # Always check explicitly

# GOOD: Explicit comparison
@external
def is_owner(addr: address) -> bool:
    return addr == self.owner

Unprotected Initialization

# BAD: Can be called multiple times
@external
def initialize(owner: address):
    self.owner = owner

# GOOD: One-time initialization
@external
def initialize(owner: address):
    assert self.owner == empty(address), "Already initialized"
    self.owner = owner

External Call Issues

# BAD: No return value check
@external
def transfer_out(to: address, amount: uint256):
    ERC20(self.token).transfer(to, amount)

# GOOD: Check return value
@external
def transfer_out(to: address, amount: uint256):
    success: bool = ERC20(self.token).transfer(to, amount)
    assert success, "Transfer failed"

Scanner Detection

Vyper Analyzer Detects

Issue Description
Missing access control Functions without ownership checks
Uninitialized storage Storage not set in constructor
External call issues Unchecked return values
Logic errors Incorrect comparisons
State consistency Invariant violations

Moccasin Detects

Issue Description
Framework misuse Incorrect Ape/Brownie patterns
Configuration issues Invalid settings
Dependency problems Version conflicts
Test coverage gaps Untested code paths

Best Practices

Contract Structure

# @version 0.3.9

"""
@title Secure Token
@author Your Name
@notice ERC20 token with security features
"""

from vyper.interfaces import ERC20

implements: ERC20

# State variables (explicit types)
name: public(String[32])
symbol: public(String[8])
decimals: public(uint8)
totalSupply: public(uint256)
balanceOf: public(HashMap[address, uint256])
allowance: public(HashMap[address, HashMap[address, uint256]])

owner: address
initialized: bool

# Events
event Transfer:
    sender: indexed(address)
    receiver: indexed(address)
    amount: uint256

event Approval:
    owner: indexed(address)
    spender: indexed(address)
    amount: uint256

@external
def __init__():
    # Initialize in constructor
    self.owner = msg.sender
    self.initialized = True

@external
def transfer(to: address, amount: uint256) -> bool:
    # Input validation
    assert to != empty(address), "Invalid recipient"
    assert self.balanceOf[msg.sender] >= amount, "Insufficient balance"

    # State changes
    self.balanceOf[msg.sender] -= amount
    self.balanceOf[to] += amount

    # Event emission
    log Transfer(msg.sender, to, amount)

    return True

Security Patterns

  1. Explicit Access Control: Always check msg.sender
  2. Input Validation: Validate all parameters
  3. Event Emission: Log all state changes
  4. Clear Logic: Avoid complex nested conditions
  5. Interface Compliance: Implement standard interfaces fully

Vyper vs Solidity Security

Feature Vyper Solidity
Reentrancy Prevented by design Requires guards
Overflow Built-in protection 0.8.0+ protected
Modifiers Not available Can hide logic
Inheritance Single only Multiple (diamond problem)
Inline Assembly Not available Available (risky)
Recursive Calls Not allowed Allowed

Framework Integration

Ape Framework

# ape-config.yaml
name: my-vyper-project

plugins:
  - name: vyper
  - name: solidity  # For Solidity interfaces

dependencies:
  - name: snekmate
    github: pcaversaccio/snekmate

compiler:
  vyper:
    version: 0.3.9

Brownie

# brownie-config.yaml
compiler:
  vyper:
    version: "0.3.9"

Example Results

Vyper Analyzer Finding

Vault.vy:45 - Missing Access Control
  Function set_fee() can be called by anyone
  Recommendation: Add owner check

Severity: High
Confidence: High

Moccasin Finding

Token.vy:12 - Unchecked External Call
  ERC20.transfer() return value not checked
  Recommendation: Assert success

Severity: Medium
Confidence: High

Troubleshooting

Compilation Issues

"Invalid version pragma"

  • Check # @version comment at top of file
  • Ensure version is supported

"Interface not found"

  • Include interface files in upload
  • Check import paths

Scanner Issues

Low detection rate

  • Vyper's design prevents many issues
  • Fewer findings often indicates secure code
  • Focus on logic and access control

Next Steps