The fastest way to learn Vyper for creating secure Ethereum Smart Contracts
Vyper is a new smart contract programming language with a bright future based on python and created by Vitalik with the goal of having better smart contract code that non-programmers could understand. Because being able to see what the program actually does is critical when transacting on the blockchain where code is law.
In this quick guide you’ll learn the language basics so that you are too able to to create vyper contracts.
Why Vyper
The language’s principals are simple:
- Security: it should be designed in a way that security comes automatically when writing the smart contract without having to do extensive tests and security checks.
- Language and compiler simplicity: both should strive to create simple code that is not overcomplicated with unnecessary functionality.
- Auditability: the ultimate goal is to have a programming language that is simple to read by any person regardless of their experience with computers in order to give them understanding while keeping security a priority by making difficult to write bad code.
That’s why Vyper is limited in functions compared to Solidity where you have less restrictions in place, to help people understand the code while making it secure. Based on those 3 principals, Vyper has a unique set of features for smart contract programming:
- Bounds and overflow checking: On array accesses as well as on arithmetic level. Many of the issues with traditional smart contracts written in solidity is the fact that mathematical calculations are insecure because of the EVM’s natural restrictions. You can’t store an endlessly large number on a uint variable because it would be abused, and miners would have to process excessive computations every time. Vyper aims to solve that by checking overflow and underflow issues when compiling.
- Support for signed integers and decimal fixed-point numbers: in this programming language you can have positive and negative integers with decimals unlike Solidity where decimals are not allowed because of number rounding limitations.
- Decidability: It should be possible to compute a precise upper bound for the gas consumption of any function call. In other words, every function should provide a clear indication of how much gas it consumes at any point.
- Strong typing: Including support for units (e.g. timestamp, timedelta, seconds, wei, wei per second, meters per second squared). Many new types are added to Vyper to provide better readability.
- Small and understandable compiler code: the compiler code is optimized as much as possible in every contract.
- Limited support for pure functions: Anything marked constant is not allowed to change the state which forces developers to guarantee that the code is behaving as expected.
However, to fully implement the desired goals, Vyper is limited in functionality and thus it doesn’t have the following programming capabilities:
- Modifiers: they are not available because they may code tedious to understand since you have to jump back and forth to the modifier definition and because developers could write misleading code with unexpected functionality.
- Class inheritance: when a smart contract inherits from many others, you have to jump back and forth between them to fully understand what is going on. Also, it’s another point where programmers could write misleading code that executes unexpected functionality.
- Inline assembly: assembly code is not searchable since functions and variables change their names to simpler values that are harder to understand and modify.
- Function overloading: it’s a problematic feature where the same function with different parameters could damage users with dangerous code.
- Operator overloading: another problematic feature where developers could for instance, change the + operator to a potentially dangerous function.
- Recursive calling: it makes function gas costs hard to predict and it’s one of the many reasons you can’t fully know how much gas you need to execute every function in solidity. By removing this feature, we also remove the possibility of gas limit attacks such as the reentrancy attack.
- Endless loops: such as while and for loops without restrictions. They make impossible to calculate gas limits and could be used as an attack vector.
- Binary fixed point: decimal fixed point is better, because any decimal fixed point value written as a literal in code has an exact representation, whereas with binary fixed point approximations are often required (e.g. (0.2)10 = (0.001100110011…)2, which needs to be truncated), leading to unintuitive results, e.g. in Python 0.3 + 0.3 + 0.3 + 0.1 != 1.
File structure
In Vyper you don’t have to specify the version of the compiler since the best one is automatically chosen for your smart contract. You can start writing code immediately in a .vy file or using an online compiler such as vyper.online which I recommend to help you with debugging and compiling quickly as you develop.
Variables
Vyper has a wider variety of types of variables because they want to improve readability as much as possible. Therefore, what’s better than a descriptive type of variable?
In Vyper you start with the variable name, then a colon and the variable type with the visibility. Variable’s visibility is the same as in solidity and you specify them like so:
myVariableName: public(uint256)
As you can see, you start with the variable’s name and then you declare the type without a semicolon at the end since this is a python-based language.
Variables in Vyper can either be private or public. Public means that anybody can access that variable including external contracts and private means that the variable can only be modified internally in this contract. To make a variable public, use the keyword public before the type and to make it private, just use the type without the public keyword.
myVariableName: public(uint256) # A public variable
mySecondVariable: uint256 # A private variable
Remember that each smart contract needs to be setup in a single file so it’s independent. These are state variables that get declared at the top of the contract. Note that you can’t give a value to the variable when defining the type of the state variables.
Following are the types of variables available in vyper:
- uints: unsigned-integers which just like in solidity, can only be positive numbers. In vyper you only have the 256-bit version so all your unsigned-integers must be declared as uint256 including the number at the end instead of just uint. For example:
myNumber: uint256
- ints: signed-integers. These integers can be either positive or negative and as such, can only hold up to 128 bits of data so all your ints will be int128. Additionally, you can use the min(x, y) and max(x, y) functions with integers for making quick calculations. For example:
myNumber: int128
- booleans: they can either be true or false. You specify them with the bool keyword. For example:
isValid: bool
When dealing with if statements and conditions using Booleans you have several operators that you need to know such as:
x not y
: Logical negation. Instead of using the!
symbol in front of the boolean, you use thenot
keyword.x and y
: Logical conjunction instead of using the usual&&
symbol.x or y
: Logical disjunction instead of the usual||
symbol.
- decimals: this new type of variable stores a number with a precision of 10 decimal places between -2**127 and (2**127–1). Remember that in order to compare numerical variables, they must be of the same type i.e. both numbers have to be decimals. For example:
myDecimalNumber: decimal
- addresses: just like in solidity, a variable that contains an Ethereum address in hexadecimal notation with a leading 0x marker. For example:
myAddress: address
Note that you can retrieve the balance contained in an address with the balance member variable:
myAddress.balance
- bytes: bytes store pieces of text in hexadecimal format. You can either use bytes32 or a custom bytes size. The larger it is, the longer text you’ll be able to store inside with some clear limitations based on the gas limits. Basically they are strings with limited length. Fixed-size byte arrays can hold strings with equal or fewer characters than the maximum length of the byte array. For instance:
username: public(bytes32)
myArticle: bytes[1000]
The variable myArticle will be able to store a string of up to 1000 characters while the username will be able to store a 32-character text. Custom-size byte arrays have several interesting functions that you’ll often use when dealing with large strings:
len(x)
: returns the length of the string.sha3()
: returns the keccak256 hash as bytes32 of the string.concat(x, . . .)
concatenates multiple byte texts into one.slice(x, start=y, len=z)
: to cut a string into a shorter piece. Starts at y with the length z.
Those are the main variable types available in vyper. After that, you have personalized unit types based on decimals, uints and ints. These are new types that you can freely create to improve the readability of your code by using custom words. Vyper has 3 unit types built in:
- timestamp and timedelta: both measure time in seconds so 1 unit of this type is 1 second. Timestamp represents a point of time i.e. a specific date while timedelta contains the number of seconds. For instance:
whenThisWasDone: timestamp
expirationTime: timedelta
They measure time and are based on the uint256 type.
- wei_value: a measure of ether in the smallest unit, the wei which is one 18th part of 1 ether. Again, based on the uint256 type so it has the same limitations regarding positive-only numbers. For instance:
amountSpent: wei_value
Alternatively, you can create your own custom unit types for improving your code’s readability. Here’s how it would look like:
units: {
centimeter: “centimeter”,
km: “kilometer”
}
Define them at the top of the file. You’ll then be able to use them with a conversion parameter being either uint256, int128 or decimal. For instance:
myCustomMeasure: uint256(centimeter)
distanceFromMars: decimal(km)
Those variables will store the same numerical values as simple uints or decimals, but they now have a specific unit type that give them a deeper meaning that improves readability to the external programmer.
Arrays
Arrays in Vyper are pretty much the same thing as in Solidity. They are called lists since that’s the official python terminology but in my opinion, it’s just easier to call them arrays. You can create multidimensional arrays and simple arrays with square brackets however they all must be fixed with a limited size set beforehand. For instance:
# An array of uints
myNumbers: public(uint256[10])# A multidimensional array of addresses
myAddresses: address[10][20]# An array of 30 texts of 1000 characters each
myArticles: public(bytes[1000][30])
To add elements to your fixed-size arrays, you’ll often have a counter of the latest modified index so that you can update it:
myNumbers[3] = 25# With a counter variable it would look like this
myNumbers[latestNumberIndex] = 25# Set multiple values
myNumbers = [1, 4, 7, 3, 2, 9, 1, 3, 0, 9]# Get the value of a specific index
return myNumbers[4]
Remember that arrays have 4 useful functions to get the length, calculate the hash, concatenate arrays and to slice them as previously seen in the bytes type definition.
Structs
Structs are pretty straightforward. You define them like in solidity but with python’s syntax so it will look like this:
# Create a struct
struct Tree:
age: timestamp
name: bytes32# Create the struct instance variable (inside a function only)
myTrees: Tree[10]# Initialize the struct (inside a function only)
myTrees[0] = Tree({12, “My own tree”})# Accessing a value (inside a function only)
myTrees[0].timestamp = 19182
They are great for creating objects that store complex types of data.
Mappings
Mappings work the same way as in solidity. They just store endless amounts of variables in hash tables that can be retrieved with the index type. You can only declare them as state variables and modify them inside functions. Here’s the syntax:
myMapping: map(int128, bool)# To access a value (can only be done inside functions)
myMapping[28] = True
That’s all about Vyper’s variables. You now know how to use them and how to define them. Let’s take a look at functions and to fully understand a smart contract’s structure.
Events
Events in vyper are pretty straightforward:
# Event definition
MyEvent: event({from: indexed(address), amount: uint256})# Calling the event inside a function
log.MyEvent(msg.sender, 10)
As you can see, the event is similar to how you create it on solidity. You can use the keyword indexed to make that parameter searchable inside the blockchain’s past events. Then, you can execute it with the function log.<yourEvent>().
Note that they must be defined at the top of the file before the state variables and functions.
Functions
Functions in vyper are similar to those on solidity but with python syntax. You still have to specify the visibility, the return values and the parameters. Here’s how a function in vyper looks like:
@public
def sumTwoNumbers(numberA: uint256, numberB: uint256) -> uint256:
return numberA + number
The return value is indicated with the arrow syntax -> instead of using the returns() keyword compared to solidity and the visibility is indicated using decorators which are just special keywords on top of the function.
Functions in vyper can either be public or private. Public means that anybody can access and execute the function while private means that it can only be executed by the contract itself, just like internal functions in solidity.
You can also add the @payable decorator to be able to receive funds with the function call. For instance:
FundsReceived: event({amount: wei_value})@public
@payable
def receiveFunds():
log.FundsReceived(msg.value)
When a function is payable, you can access the ether received with msg.value.
To read of modify state variables inside functions, you must use the self keyword. Here’s how it looks like:
myNumber: uint256@public
def exampleFunction():
self.myNumber = 3
If you don’t, the compiler will think that you want to access the local variable with that name which makes the use of underscores before the variable naming unnecessary for function parameters compared to solidity where you must use underscores to update state variables easily.
The constructor
The constructor is just like in python. Use the __init__ function to setup initial variables or execute internal mechanisms when the contract is deployed. For instance:
@public
def __init__():
# Do something here when the contract is deployed
Obviously, the constructor can’t return any value, so it makes no sense to provide a return value to it.
The fallback function
This function is the one being executed when you send a transaction to the contract without specifying the function that you want to call, basically when making ether transfers to the contract. Here’s how it looks like in vyper:
@public
@payable
def __default__():
# Do something when receiving a transfer
Documenting your functions with NatSpec
The NatSpec documentation is a simple way to explain what your functions are supposed to do. It’s important that you use to facility the understanding of your code for future developers and to improve it in the longer term. Here’s how it looks like:
@public
def addNumbers(numberA: int128, numberB: int128) -> int128:
"""
@author Merunas Grincalaitis
@notice Adds 2 given integers and returns the result
@dev Works with either positive and negative numbers
@param numberA The first number to add
@param numberB The second number to add
@return int128 The number resulting after the addition
"""
return numberA + numberB
As you can see, you have several parameters to describe your functions. @notice is the one used to explain what the function is supposed to do for external readers. Be as concise and clear as possible. @param and @author describe the parameters of the function and the function’s creator.
And @dev is for giving additional information for coders to help understand some internal logic that may be irrelevant to the end users.
Summary
That’s about it! You went through a quick introduction on Vyper in order to be able to create Smart Contracts with this modern programming language. If you want to use it, go to vyper.online and you’ll be able to program without having to install anything on your computer. Make sure to practice by yourself the functions described in this guide and continue reading about the subject to become a full-fledged Ethereum Developer.
Join my mailing list of Ethereum Developers here: http://eepurl.com/dDQ2yX where you’ll receive weekly personal guides and stories written exclusively via email by me about Smart Contracts, Ethereum, blockchain and cryptocurrencies. Things that I’ve been pondering for days to help you move forward in this space faster than anyone else.