#
Vyper Language
#
Introduction
Vyper is a contract-oriented, pythonic programming language that targets the Ethereum Virtual Machine (EVM).
Principles and Goals:
Security: It should be possible and natural to build secure smart-contracts in Vyper.
Language and compiler simplicity: The language and the compiler implementation should strive to be simple.
Auditability: Vyper code should be maximally human-readable. Furthermore, it should be maximally difficult to write misleading code. Simplicity for the reader is more important than simplicity for the writer, and simplicity for readers with low prior experience with Vyper (and low prior experience with programming in general) is particularly important.
#
Getting Started
Vyper can only be built using Python 3.6 and higher. If you need to know how to install the correct version of python, follow the instructions from the official Python website.
pip install vyper
#
Vyper vs Solidity
# SPDX-License-Identifier: MIT
# @version ^0.3.6
value: public(uint256)
@external
@view
def retrieve() -> uint256:
return self.value
@external
def store(_value: uint256):
self.value = _value
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract StorageSol {
uint256 public value;
function retrieve() public view returns (uint256) {
return value;
}
function store(uint _value) public {
value = _value;
}
}
#
Structure of a Contract
Vyper contracts are contained within files. Each file contains exactly one contract.
graph LR
Z(version) --> A
A(structs) --> B(interface)
B --> C(events)
C --> D(globals and contants)
D --> E(functions)Vyper supports a version pragma to ensure that a contract is only compiled by the intended compiler version, or range of versions. Version strings use NPM style syntax.
# @version ^0.2.0
A struct is a custom defined type that allows you to group several variables together:
struct MyStruct:
value1: int128
value2: decimal
An interface is a set of function definitions used to enable calls between smart contracts. A contract interface defines all of that contract’s externally available functions. By importing the interface, your contract now knows how to call these functions in other contracts.
Interfaces can be added to contracts either through inline definition, or by importing them from a separate file.
interface FooBar:
def calculate() -> uint256: view
def test1(): nonpayable
from foo import FooBar
Once defined, an interface can then be used to make external calls to a given address:
@external
def test(some_address: address):
FooBar(some_address).calculate()
Events provide an interface for the EVM’s logging facilities. Events may be logged with specially indexed data structures that allow clients, including light clients, to efficiently search for them.
event Payment:
amount: int128
sender: indexed(address)
total_paid: int128
@external
@payable
def pay():
self.total_paid += msg.value
log Payment(msg.value, msg.sender)
State variables are values which are permanently stored in contract storage. They are declared outside of the body of any functions, and initially contain the default value for their type.
storedData: int128
Functions are executable units of code within a contract.
@external
def bid():
...
#
Types
Vyper is a statically typed language. The type of each variable (state and local) must be specified or at least known at compile-time. Vyper provides several elementary types which can be combined to form complex types.
#
Value Types
The following types are also called value types because variables of these types will always be passed by value, i.e. they are always copied when they are used as function arguments or in assignments.
Boolean - A boolean is a type to store a logical/truth value.
is_active: bool = TrueSigned Integer (N bit) - A signed integer which can store positive and negative integers. N must be a multiple of 8 between 8 and 256 (inclusive).
value: int128 = 10Unsigned Integer (N bit)
value: uint128 = 10Decimals - A decimal is a type to store a decimal fixed point value.
value: decimal = 100.5Address - The address type holds an Ethereum address.
admin: address = 0x0000000000000000000000000000000000000000M-byte-wide Fixed Size Byte Array - This is an M-byte-wide byte array that is otherwise similar to dynamically sized byte arrays. On an ABI level, it is annotated as bytesM (e.g., bytes32).
some_method_id: bytes4 = 0x01abcdefByte Arrays - A byte array with a max size.
bytes_string: Bytes[100] = b"\x01"Strings - Fixed-size strings can hold strings with equal or fewer characters than the maximum length of the string.
example_str: String[100] = "Test String"Enums - Enums are custom defined types. An enum must have at least one member, and can hold up to a maximum of 256 members. The members are represented by uint256 values in the form of 2^n where n is the index of the member in the range 0 <= n <= 255.
# Defining an enum with two members enum Roles: ADMIN USER # Declaring an enum variable role: Roles = Roles.ADMIN # Returning a member return Roles.ADMIN
#
Reference Types
Reference types are those whose components can be assigned to in-place without copying. For instance, array and struct members can be individually assigned to without overwriting the whole data structure.
Fixed-size Lists - Fixed-size lists hold a finite number of elements which belong to a specified type.
# Defining a list exampleList: int128[3] # Setting values exampleList = [10, 11, 12] exampleList[2] = 42 # Returning a value return exampleList[0]Dynamic Arrays - Dynamic arrays represent bounded arrays whose length can be modified at runtime, up to a bound specified in the type.
# Defining a list exampleList: DynArray[int128, 3] # Setting values exampleList = [] # exampleList.pop() # would revert! exampleList.append(42) # exampleList now has length 1 exampleList.append(120) # exampleList now has length 2 exampleList.append(356) # exampleList now has length 3 # exampleList.append(1) # would revert! myValue: int128 = exampleList.pop() # myValue == 356, exampleList now has length 2 # myValue = exampleList[2] # would revert! # Returning a value return exampleList[0]Structs - Structs are custom defined types that can group several variables. Struct types can be used inside mappings and arrays. Structs can contain arrays and other structs, but not mappings. Struct members can be accessed via
struct.argname.# Defining a struct struct MyStruct: value1: int128 value2: decimal # Declaring a struct variable exampleStruct: MyStruct = MyStruct({value1: 1, value2: 2.0}) # Accessing a value exampleStruct.value1 = 1Mappings - Mappings are hash tables that are virtually initialized such that every possible key exists and is mapped to a value whose byte-representation is all zeros: a type’s default value. The key data is not stored in a mapping. Instead, its
keccak256hash is used to look up a value. For this reason, mappings do not have a length or a concept of a key or value being “set”.# Defining a mapping exampleMapping: HashMap[int128, decimal] # Accessing a value exampleMapping[0] = 10.1
#
Environment Variables and Constants
#
Environment Variables
Environment variables always exist in the namespace and are primarily used to provide information about the blockchain or current transaction.
#
The self Variable
self is an environment variable used to reference a contract from within itself. Along with the normal address members, self allows you to read and write to state variables and to call private functions within the contract.
self is used to access a contract’s state variables, as shown in the following example:
state_var: uint256
@external
def set_var(value: uint256) -> bool:
self.state_var = value
return True
@external
@view
def get_var() -> uint256:
return self.state_var
self is also used to call internal functions within a contract:
@internal
def _times_two(amount: uint256) -> uint256:
return amount * 2
@external
def calculate(amount: uint256) -> uint256:
return self._times_two(amount)
#
Custom Constants
Custom constants can be defined at a global level in Vyper. To define a constant, make use of the constant keyword.
TOTAL_SUPPLY: constant(uint256) = 10000000
total_supply: public(uint256)
@external
def __init__():
self.total_supply = TOTAL_SUPPLY
#
Functions
Functions are executable units of code within a contract. Functions may only be declared within a contract’s module scope.
@external
def bid():
...
Functions may be called internally or externally depending on their visibility. Functions may accept input arguments and return variables in order to pass values between them.
All functions must include exactly one visibility decorator.
#
Visibility
#
External Functions
External functions (marked with the @external decorator) are a part of the contract interface and may only be called via transactions or from other contracts.
@external
def add_seven(a: int128) -> int128:
return a + 7
A Vyper contract cannot call directly between two external functions. If you must do this, you can use an interface.
#
Internal Functions
Internal functions (marked with the @internal decorator) are only accessible from other functions within the same contract. They are called via the self object:
@internal
def _times_two(amount: uint256) -> uint256:
return amount * 2
@external
def calculate(amount: uint256) -> uint256:
return self._times_two(amount)
#
Mutability
You can optionally declare a function’s mutability by using a decorator. There are four mutability levels:
- Pure: does not read from the contract state or any environment variables.
- View: may read from the contract state, but does not alter it.
- Nonpayable: may read from and write to the contract state, but cannot receive Ether.
- Payable: may read from and write to the contract state, and can receive Ether.
@view
@external
def readonly():
# this function cannot write to state
...
@payable
@external
def send_me_money():
# this function can receive ether
...
Functions default to nonpayable when no mutability decorator is used.
Functions marked with @view cannot call mutable (payable or nonpayable) functions. Any external calls are made using the special STATICCALL opcode, which prevents state changes at the EVM level.
Functions marked with @pure cannot call non-pure functions.
#
Utilities
#
as_wei_value(_value, unit: str)→ uint256
Take an amount of ether currency specified by a number and a unit and return the integer quantity of wei equivalent to that amount.
_value: Value for the ether unit. Any numeric type may be used, however the value cannot be negative.unit: Ether unit name (e.g. "wei", "ether", "gwei", etc.) indicating the denomination of _value. Must be given as a literal string.
@external
@view
def foo(s: String[32]) -> uint256:
return as_wei_value(1.337, "ether")
>>>ExampleContract.foo(1)
1337000000000000000