Skip to content

Latest commit

 

History

History
191 lines (139 loc) · 15.6 KB

vyper.asciidoc

File metadata and controls

191 lines (139 loc) · 15.6 KB

Vyper: A contract-oriented programming language

Research shows that smart contracts, with trace vulnerabilities, can result in unexpected execution. A recent study analyzed 970,898 contracts. It outlined three basic categories of trace vulnerabilities (which have already resulted in the catastrophic loss of funds for Ethereum users). These categories include
  • Suicidal contracts. Contracts which can be killed by arbitrary addresses

  • Greedy contracts which have no way to release Ether after a certain execution state, and

  • Prodigal contracts which release Ether to arbitrary addresses carelessly

Vyper is an experimental, contract-oriented programming language which targets the Ethereum Virtual Machine (EVM). Vyper strives to provide superior audit-ability by simplifying human readable code. One of the principles of Vyper is to make it near virtually impossible for developers to write misleading code. This is done in a number of ways, which we will describe below.

Comparison to Solidity

This section is a reference for those who are considering developing smart contracts using the Vyper programming language. The section mainly compares and contrasts Vyper against Solidity; outlining, with sound reasoning, why Vyper does NOT include the following traditional Object Oriented Programming (OOP) concepts:

Modifiers

In Solidity you can write a function using modifiers. For example, the following function called changeOwner will run the code in a modifier, called onlyBy, as part of its execution.

function changeOwner(address _newOwner)
    public
    onlyBy(owner)
{
    owner = _newOwner;
}

As we can see below, the modifier called onlyBy enforces a rule in relation to ownership. Whilst modifiers are powerful (able to change what happens in the body of a function), they can also result in the misleading execution of code. For example, the only way to be sure of the changeOwner function’s logic, is to inspect and test the onlyBy modifier every time our code is implemented. Obviously if a modifier is changed in the future, the function which calls it will potentially produce a different result, than originally intended.

modifier onlyBy(address _account)
{
    require(msg.sender == _account);
    _;
}

By and large, the usual use case for a modifier, is to perform single checks before a function’s execution. Given that this is the case, Vyper’s recommendation is to do away with modifiers altogether and simply use in-line checks and asserts as part of a function. Doing this will improve audit-ability and readability, due to the fact that the Vyper function will follow a logical in-line sequence in plain sight, rather than having to reference modifier code which has been written elsewhere.

Class inheritance

TODO explain class inheritance and then paraphrase the following …​ "requires people to jump between multiple files to understand what a program is doing, and understand the rules of precedence in case of conflicts (which class’s function X is the one that’s actually used?). Hence, it makes code too complicated to understand."

Inline assembly

TODO explain inline assembly, why Vyper does not include it, and then paraphrase the following …​ "adding inline assembly would make it no longer possible to Ctrl+F for a variable name to find all instances where that variable is read or modified."

Function overloading

TODO paraphrase the following …​ "having multiple function definitions with the same name and different argument options can cause lots of confusion over which function is called at any given time. With function overloading, it’s easier to write misleading code (foo("hello") logs "hello" but foo("hello", "world") steals your funds). Another problem with function overloading is that it makes the code much harder to search through as you have to keep track on which call refers to which function."

Variable typecasting

Typecasting is a mechanism which allows programmers to convert a variable from one data type to another data type. TODO explain how this has been handled in Vyper.

Pre-conditions and post-conditions

Vyper handles pre-conditions, post-conditions and state changes explicitly. Whilst this produces redundant code, it also allows for maximal readability and safety. When writing a smart contract in Vyper, a developer should observe the following 3 points. Ideally, each of the 3 points should be carefully considered and then thoroughly documented in the code. Doing so will improve code design, through enforced diligence, and in addition will provide superior readability and auditability.

  • Condition - What is the current state/condition of the Ethereum state variables

  • Effects - What effects will this smart contract code have on the condition of the state variables upon execution i.e. what WILL be affected, what WILL NOT be affected? Are these effects congruent with the smart contract’s intentions?

  • Interaction - Now that the first two steps have been exhaustively dealt with, it is time to run the code. Before deployment, logically step through the code and consider all of the possible permanent outcomes, consequences and scenarios of executing the code, including interactions with other contracts

A new programming paradigm

Vyper’s creation opens the door to a new programming paradigm. For example, Vyper is removing class inheritance, as well as other functionality, and therefore it can be said that Vyper is leaning away from the traditional Object Oriented Programming (OOP) paradigm, which is fine.

Historically OOP has provided a mechanism for representing real world objects. For example, OOP allows the instantiation of an employee object which can inherit from a person class. However, from a value-transfer and/or smart-contract perspective, those who aspire to the functional programming paradigm would concur that transactional programming in no way lends itself to the aforementioned traditional OOP paradigm. Put simply, transactional computations are worlds apart from real world objects. For example, when was the last time you held a transaction or a forward chaining business rule in your hand?

It seems that Vyper is not full aligned with either the OOP paradigm or the functional programming paradigm (the full list of reasons is beyond the scope of this chapter). For this reason, could we be so bold, at this early stage of development, to coin a new software development paradigm? One which endevours to future proof blockchain executable code. One which prevents the catastrophic loss of funds in an immutable setting. Past events experienced in the blockchain revolution are organically creating new opportunities for further research and development in this space. Perhaps the outcomes of such research and development could eventually result in a new immutability paradigm classification for software development.

Decorators

Decorators like @private @public @constant @payable are declared at the start of each function.

Private decorator

The @private decorator makes the function inaccessible from outside the contract.

Public decorator

The @public decorator makes the function both visible and executable publicly. For example, even the Ethereum wallet will display the public functions when viewing the contract.

Constant decorator

Functions which start with the @constant decorator are not allowed to change state variables, as part of their execution. In fact the compiler will reject the entire program (with an appropriate warning) if the function tries to change a state variable. If the function is meant to change a state variable then the @constant decorator is not used at the start of the function.

Payable decorator

Only functions which declare the @payable decorator at the start will be allowed to transfer value.

Vyper implements the logic of decorators explicitly. For example, the Vyper code compilation process will fail if a function is preceded with both a @payable decorator and a @constant decorator. Of course this makes sense because a constant function (one which only reads from the global state) should never need to partake in a transfer of value. Also, each Vyper function must be preceded with either the @public or the @private decorator to avoid compilation failure. Preceding a Vyper function with both a @public decorator and a @private decorator will also result in a compilation failure.

Online code editor and compiler

Vyper has its own online code editor and compiler at the following URL < https://vyper.online >. This Vyper online compiler allows you to write and then compile your smart contracts into Bytecode, ABI and LLL using only your web browser. The Vyper online compiler has a variety of prewritten smart contracts for your convenience. These include a simple open auction, safe remote purchases, ERC20 token and more.

Compiling using the command line

Each Vyper contract is saved in a single file with the .v.py extension. Once installed Vyper can compile and provide bytecode by running the following command

vyper ~/hello_world.v.py

The human readable ABI code (in JSON format) can be obtained by then running the following command

vyper -f json ~/hello_world.v.py

Reading and writing data

Smart contracts can write data to two places, Ethereum’s global state trie or Ethereum’s chain data. While it is costly to store, read and modify data, these storage operations are a necessary component of most smart contracts.

Global state

The state variables in a given smart contract are stored in Ethereum’s global state trie, a given smart contract can only store, read and modify data specifically in relation to that contract’s address (i.e. smart contracts can not read or write to other smart contracts).

Log

As previously mentioned, a smart contract can also write to Ethereum’s chain data through log events. While Vyper initially employed the __log__ syntax for declaring these events, an update has been made which brings Vyper’s event declaration more in line with Solidity’s original syntax. For example, Vyper’s declaration of an event called MyLog was originally MyLog: __log__({arg1: indexed(bytes[3])}) Vyper’s syntax has now become MyLog: event({arg1: indexed(bytes[3])}). It is important to note that the execution of the log event in Vyper was and still is as follows log.MyLog("123").

While smart contracts can write to Ethereum’s chain data (through log events), smart contracts are unable to read the on-chain log events, which they created. Notwithstanding, one of the advantages of writing to Ethereum’s chain data via log events is that logs can be discovered and read, on the public chain, by light clients. For example, the logsBloom value in a mined block can indicate whether or not a log event was present. Once this has been established the log data can be obtained through the path of logs → data inside a given transaction receipt.

ERC20 token interface implementation

Vyper has implemented ERC20 as a precompiled contract and allows to use it by default. Contracts in Vyper must be declared as global variables. An example for declaring the ERC20 variable can be token: address(ERC20).

OPCODES

The code for smart contracts is mainly written in high level languages like Solidity or Vyper. The compiler is responsible for taking the high level code and creating the lower level interpretation of it, which is then executable on the Ethereum Virtual Machine (EVM). The lowest representation the compiler can distill the code to (prior to execution by the EVM) are opcodes. This being the case, each implementation of a high level language (like Vyper) is required to provide an appropriate compilation mechanism (a compiler) to allow (among other things) the high level code to be compiled into the universally predefined EVM opcodes. A good example of this is Vyper’s implementation of Ethereum’s sharding opcodes.