Vyper: Ethereum's Contract-Oriented Python-Like Scripting Language for the EVMJan 23, 2019, 2:36PM
A general outline of Ethereum's upcoming contracts language, Vyper. Purpose, design philosophy, features, adoption, research and development.
Smart contracts are a micro-service with a set of callable functions for storing and reading on a block-chained ledger in a decentralized environment. It is, technically speaking, a contract between a machine ("world computer") and the rest of the world - an arrangement that it would work in a certain way for as long as the network lives. These defining aspects/features considered, smart contracts ought to be easy to read, write and audit. In general, programming languages differ in multiple dimensions (such as paradigm, type system, etc.). Due to the unusual execution environment of contract code, smart contract languages (SCLs) have different sets of trade-offs and have spawned a number of attempts at creating secure and expressive SCLs.
Vyper is a complementary Ethereum language that aims for such read and write simplicity, usability, and trivial use case scenarios (rather than complex Web 3.0 dApps, as is the case with Solidity). Loosely following Python 3 conventions, it is a contract-oriented scripting language (where Solidity is more object-oriented) targeting the Ethereum Virtual Machine (Serpent was an early Python-like high-level language compiling to EVM bytecode that was deprecated due to security issues with the compiler). Following these design principles and in order to avoid increasing complexity and security risks, Vyper is a fairly stripped down language whose reductive approach does away with features like class inheritance, function and operator overloading, recursive calling, infinite-length loops and other constructs unnecessary for "code as law" contractual agreements and in line with reader auditability, making it maximally difficult to write misleading code.
This bias towards "correctness" at the expense of some flexibility is a desired property in a contracts programming paradigm and allows for avoiding certain pitfalls that have caused serious vulnerabilities in the past. Getting rid of inheritance, for example, is intended to help keep things “on the same page” rather than getting lost in jumping between multiple contract files in the hierarchy of precedence in order to piece together the scattered fragments of what a program is doing under the hood. This minimal and clean composition of contracts and enforcing of self-explanatory code patterns also make Vyper appealing to the more conservative Ethereum Classic community and its "code is law" ethos.
Intended to be fully leveraged with the transition to sharded validation of what came to be referred to as Ethereum 2.0, the sharding and PoS implementations are themselves specified and written in Vyper.
An Ethereum smart contract usually consists of state variables and functions. State variables are values which are permanently stored in contract storage and functions are the executable units of code within a contract.
State Variable Types
State variables are the variables used to describe the "state" of the dynamic system and what defines smart contracts as stateful objects. Global variables (e.g., contracts, which Solidity treats as objects) in Vyper are declared as follows:
address datatype and makes that
Mappings are what Ethereum contracts usually begin with. They initialize the contract storage fields (such as a token balance mapping). In Vyper, mappings can be seen as virtually initialized hash tables whose keys (stored as its keccak256 hash) are used to look up the corresponding values, whose default byte-representation is all zeros. In Solidity, mappings are declared as
mapping(_KeyType => _ValueType).Vyper uses the following syntax:
_KeyType can be almost any type except for mappings, a contract, or a struct, while
_ValueType can be any type, including mappings.
Vyper has just two integer types: uint256 (for non-negative integers) and int128 (for signed integers):
Signed Integers (128 bit)
int128 is a type to store positive and negative integers with values between -2 to the 127 and (2 to the 127 - 1).
Arithmetic operators for integers are
Unsigned Integers (256 bit)
uint256 is a type to store non-negative integers between 0 and (2to the 256 - 1). Bitwise operators include
shift (respectively AND, NOT, OR, XOR and Bitwise Shift).
decimal is a type to store a decimal fixed point value with a precision of 10 decimal places between -2 to the 127 and (2 to the 127 - 1). Comparisons (e.g., less, less or equal, greater than, etc.) return a boolean value.
address type holds an Ethereum address (a wallet with an external owner or a contract) which has hexadecimal notation with a leading 0x. It has the syntax of
_address. where is either balance (querying the balance of an address, returned in wei) or codesize (querying the code size of an address returned in int128).
bool, a boolean is a type storing a logical/truth value. Possible values are True or False. Boolean operators are
!=, describing respectively logical negation, conjunction, disjunction, equivalence, and inequality.
Vyper allows the definition of types with discrete units e.g. meters, seconds, wei, … These types may only be based on either,
decimal. Vyper has two unit types built in: time (
timedelta) and wei (
wei_value, an uint256 giving the amount of Ether in wei). Additionally, custom unit types can be defined (as
32-bit-wide Byte Arrays
bytes32 is a 32-bit-wide byte array.
Operators include length (
len(x)), sha3 hash (
sha3(x)), concatenating multiple inputs (
concat(x, ...)), and slicing of length from a point (
slice(x, start=_start, len=_len)).
Fixed-Size Byte Arrays
bytes denotes a byte array with a fixed size. Fixed-size byte arrays can hold strings (with equal or fewer characters than the maximum length of the byte array defined in the syntax bytes[maxLen]). Operators are the same as with bytes32. Example: exampleString = "String".
Reference Types (that do not fit into 32 bytes)
These include fixed-size lists that hold a finite number of elements that belong to a specified type, declared as
_name: _ValueType[_Integer], and structs which are custom defined types that can group several variables.
Functions (or methods) are defined the same way as in Python (prefaced with def).
Function calls can happen internally or externally relative to the contract object and have different levels of visibility towards other contracts, decorated as either @public or @private.
A contract can have a default function (a construct functioning same as fallback functions in Solidity) which is executed on a call to the contract if no other functions match the identifier (or if none is supplied at all, as when sending ETH). This function is always named __default__ and must be annotated with the @public decorator and cannot have arguments or return anything.
If the function is annotated as @payable, this function is executed whenever the contract sends Ether (without data).
Events may be logged in indexes and data structured allowing clients to efficiently search and register them. Events must be declared before global declarations and function definitions. The basic flow of event logging (as taken from the sample ERC-20 contract) as described in Vyper contract code looks as follows:
@public gives a function public visibility and allows external entities to call it.
@private is the default.
@constant is used to decorate methods that only read a state.
@payable makes any method able to be called with a payment, enabling it to handle transactions.
Other Logical and Syntactical Specifics
self. is used to assert instance variables, adding clarity to the form of how a code is structured and minimizing possible errors.
msg.sender are members of the msg (message) object when sending (state transitioning) transactions on the Ethereum network.
msg.sender, as in Solidity, designates the owner of the contract, while
msg.value contains the amount of wei sent in the transaction.
assert takes a boolean expression, asserting the specified condition - if the condition evaluates it to be true, the code will continue to run. Otherwise, the code will stop operation, reverting to the state before.
The constructor function in Vyper takes the form of the familiar initialize function in Python:
We'll provide an illustrative example using the Vyper port of the basic ERC-20 token contract.
At the start of the contract (usually designated by the
.vy filename extension, or sometimes to preserve Python syntax highlighting,
.v.py), the event logs are declared:
Then the usual ERC-20 token variables (name, ticker, supply, decimal points of divisibility, etc.):
The following is the token contract initialization:
The function for checking the balance of a particular account:
The function for sending
_amount tokens to
_to from your account:
Move tokens allowed from a particular account to another (the trivial by now
_spender to withdraw from your account up to the specified
Get the allowance an address has to spend of a token from another address:
Deployment and Compilation to Bytecode
Vyper scripts directly compile to EVM bytecode (rather than getting interpreted, as with Python). Under the hood, both Vyper and Solidity compile to bytecode in the same fashion, following the same sequence of steps, so they are largely inter-operable (and able to make external calls between each other’s contracts). The Vyper compiler itself is written in Python.
In brief, the high-level code is taken up by a parser which parses it into an abstract syntax tree representation of the OP code instructions (see the yellow paper EVM specification), and from there a type checking process iterates through the tree, assigning their corresponding types. After performing static analysis checks the bytecode is generated.
Vyper contracts can be manually deployed by pasting the generated bytecode, although there are various wrappers and scripts available as well, and integration with the Populus smart contract development framework.
Research and Development. Tooling and Formalizations.
Runtime Verification, Inc. (a well-established software company using runtime verification-based techniques to improve on the safety, reliability, and correctness of software systems) have undertaken the full formalization of Vyper using the K Framework, having announced in December 2017:
Runtime Verification, Inc. (RV) along with the Formal Systems Lab at the University of Illinois (FSL) have announced a joint initiative targeting the full formalization of the Viper [sic] smart contract programming language, using the K Framework to create a full formal definition of this research-oriented smart contract programming language. This effort is intended to yield a number of useful tools and artifacts, and to lay the foundation for the future of principled and formally rigorous smart contract development.
The Ethereum Commonwealth (which maintains and develops Ethereum Classic) is also supporting Vyper development and has stated their desire to adopt Vyper as a default smart contract language. Vitalik Buterin himself makes occasional contributions to Vyper, given his general affinity for Python.
Importantly, as mentioned at the outset of this article, Ethereum’s sharding and Casper implementations are specified and written in Vyper, indicating its importance and likely mainstream adoption with Ethereum's base protocol upgrades and maturation towards Ethereum 2.0.
Resources and Links
A curated list of resources on specialized contracts languages.
A list of Vyper tools and resources.
ChainShot's interactive introduction to Vyper.
Disclaimer: information contained herein is provided without considering your personal circumstances, therefore should not be construed as financial advice, investment recommendation or an offer of, or solicitation for, any transactions in cryptocurrencies.