Plasma MVP Implementation In Vyper

Ryuya Nakamura
LayerX
Published in
7 min readAug 24, 2018
Merkle Proof Membership Check in Vyper

Ryuya Nakamura(@veryNR)and Osuke Sudo( @zoom_zoomzo) from LayerX R&D Team implemented Minimal Viable Plasma in Vyper, a smart contract language that targets the EVM.

https://github.com/LayerXcom/plasma-mvp-vyper

To our best knowledge, this is the first Plasma implementation in Vyper. We hope this might be helpful for both Vyper and Plasma community.

In this article, we show why and how we developed it step by step and what we learned from this project.

Contents

  • Our motivation of this project
  • Vyper’s fetures
  • What’s Plasma MVP in Vyper like?
  • Experience of development with Vyper

Our motivation of this project

Vyper is a Python-like smart contract language that targets EVM. Vyper aims to be simple and human-readable as much as possible in order to improve the security of smart contracts.

There have been a lot of malicious hacks due to vulnerabilities in smart contracts so far. Vyper is designed to prevent these vulnerabilities.

Vyper’s principles and goals in the document represents this well.

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.

Vyper is still under development but Vyper has a potential to be widely used when we taking these design goals and their importance into considering.

We want to contribute to the Vyper community so decided to implement a Plasma MVP in Vyper. Plasma’s contracts consists of various programs like signature verification, merkle proof, and priority queue so we thought this could be helpful for the development in Vyper in the future and Vyper’s design decision.

Acknowledgement

We implemented Plasma MVP based on OmiseGo’s root chain contracts. We’d like to express thanks to them and their “great” work.

Special thanks to Jacques Wagener and Bryant Eisenbach, Vyper’s core developers who helped us in Gitter many times.

Vyper’s features

Contract code in Vyper

  • 1 file = 1 contracts. There is no declaration like contract {…}.
  • Python-like grammar. Decorators like @payable, @private are used instead of modifers. Constructor is __init__(). Files in Vyper are similar to classes and you can access storage variables like self.myData and functions like self.myFunc().
  • All variables must be typed explicitly in Python’s type hinting’s style.
  • Order of definitions must be external contract interfaces -> events -> storage variables -> functions
  • A function can only call functions that are declared above it (that is, A can call B only if B appears earlier in the code than A does)

Features not provided in Vyper

Following the principles and goals, Vyper does not provide the following features. (see the document for details and reasons)

  • Modifiers
  • Class inheritance
  • Inline assembly
  • Function overloading
  • Operator overloading
  • Recursive calling
  • Infinite-length loops
  • Binary fixed point

What’s Plasma MVP in Vyper like?

Because of the features and restrictions of Vyper, the way we implement Plasma contracts in Vyper is really different from in Solidity.

Differences due to Vyper’s essential features

What makes it different is the Vyper’s features that follows the Vyper’s principles and goals.

(1) Use assert instead of user defined modifiers

In Vyper, you can’t define your own modifiers for readability and auditability. For example, in order to limit the executor to the operator of the child chain, we use assert directly instead of using onlyOperator.

assert msg.sender == self.operator

FYI, unlike Solidity which has require, assert and revert for exception handling, Vyper provides only assert. It’s the same with require in Solidity.

(2) No infinite-length loop

Solidity provides while (condition) {…} but Vyper doesn’t. We can use for loop instead but the number of loop must be determined at compile time, which means range() only accepts literals. In our Plasma MVP, the upper bound of the number of loops are decided by fixed size priority queue. For example, the upper bound of number of loop in finalizeExits() is equal to the size of priority queue.

# 1073741824 is 2^30, max size of priority queue is 2^30 - 1
for i in range(1073741824):
if not exitable_at < as_unitless_number(block.timestamp):
break

(3) Built-in secure operations and functions

In Vyper, normal operators are made to prevent overflow so you don’t need SafeMath library. Also, max(), slice() of byte array and RLP decoder are provided as built-in functions.

(4) No inline assembly

Solidity supports inline assembly. For example, a program which extracts r, s and v from child chain’s tx signature can be written like:

assembly {
r := mload(add(_sig, 32))
s := mload(add(_sig, 64))
v := byte(0, mload(add(_sig, 96)))
}

Vyper doesn’t support inline assembly for readability and auditability. The program above is implemented with built-in function extract32() and slice().

r: uint256 = extract32(_sig, 0, type=uint256)
s: uint256 = extract32(_sig, 32, type=uint256)
v: int128 = convert(slice(_sig, start=64, len=1), "int128")

(5) Others

  • Only integer literals are allowed in array access and arithmetic like exponent.
  • Function arguments are immutable.

Current Vyper’s limitation

In Vyper, there are some proposed or discussed features without implementations or decided specification. In this development, we needed to handle these limitations, so we elaborated as below and we hope current limitations might be changed and we really would like to contribute to improvements :)

  • Vyper don’t have library system. We implemented all the functions in the same contract.
  • A contract can’t be imported from other file. The interface of priority_queue contract is written in root_chain contract.
  • create_with_code_of() is used as a substitute for new in Solidity but with this function constructors aren’t executed. We wrote setup() function instead and call it afterwards.
  • Initial value can not be set in a declaration of storage variable so we set initial values in a constructor.
  • Vyper don’t have constant variables so we hard-coded in every occurrence with comments.
  • Vyper don’t have dynamically sized array. We used mappings with int128/uint256 key. Also, Vyper don’t have dynamically sized byte array so we used fixed sized byte array with a appropriate size in the context or bytes[1024] as it’s “probably more than you need”.
  • Vyper has struct type but don’t provide defining new struct type like Solidity. There is a proposal about it.
  • In private function, msg.sender returns the address of the contract itself. This is because Vyper use CALL opcode (not JUMP) in calling private functions currently in considering of the risk of side effects from memory access. This causes another significant problem that it requires more gas cost. (See discussion about it)
  • And others. (Even private function can not return structs, strict handling of units, etc.)

Experience of development with Vyper

Development environment

Vyper compiler is Python module and it is recommended to be installed in a virtual Python environment. (See the document)

There are Vyper plugins for various editors. We used the one for vscode. (See the wiki about Vyper tools and resources.)

Then, let’s code and build it. There are two style about file extention, .vy and .v.py. We adopted the latter because truper (described later) only accepts .v.py.

Tests

We used truffle for testing. Node.js module called truper compiles Vyper contracts to truffle compatible artifacts. Truper names artifacts like MyContract.vyper.json but truffle migration makes MyContract.json so the artifacts aren’t overwritten. We wrote a npm run build script to compile contracts and rename them to have only .json.

Vyper compiler does not create getter functions for public storage variables automatically. Getters required in tests must be defined explicitly.

Because there is no Remix for Vyper ,we used truffle develop to see Vyper code behavior. vyper.online is a web app Vyper compiler. It’s useful just to check a syntax can be compiled but it’s not maintained well. (some relatively new features of Vyper can’t be compiled.)

Hard things

(1) Debug

Debugging Vyper programs is a little bit difficult. Truffle debugger doesn’t work well with Vyper and there is no tool like sol-trace that tells in which line a error occurred.

As a workround for now, we used console.log() in javascript test code and repeated commenting out some code and checking whether it raises error or not.

There is a tool called vyper-debug but it’s under development.(It raises error in pip install)

(2) Deployment of RLP decoder

Built-in RLPList() function is compiled to calling the contract which has the functions of RLP decoding. Therefore, you need to deploy the RLP decoder contract when you use RLPList() in your own ethereum network like truffle test in the following steps.

  1. Send 10 ** 17 wei to an account A (0x39ba083c30fCe59883775Fc729bBE1f9dE4DEe11) for a gas cost in step 2.
  2. Send a transaction signed by the account A which deploys a RLP decoder contract .

In this way, the contract address of RLP decoder contract is fixed to the address hard-coded in Vyper compiler.

We implemented a helper to do these steps. Please use it if you use RLPList() in truffle.

Troubleshooting

There is less information about Vyper compared to Solidity and other programming languages so docs is the best source of information. However, docs is not explaining enough about some features and sometimes it doesn’t reflect latest specification of Vyper.

When we could not find the solution in the docs, we posted questions in Gitter. It’s also helpful to see Vyper compiler. Vyper uses Lisp-like language(LLL) as intermediate representation so Vyper compiler is quite simple. For example, we found which type can be converted to which type with convert() function in the implementation of convert().

Also, other implementation in Vyper is helpful.

Contribution to Vyper

In this project, we contributed to Vyper a little by fixing docs and proposing new features.

In Vyper, proposals of new features is called VIP(Vyper Improvement Proposal). These are examples of VIPs we proposed.

Impressions of Vyper

It was the first time for us to use Vyper. Vyper is quite simple and easy to understand.

For people familiar with Python, what you need to use Vyper is not to learn new syntaxes but to know Python syntaxes not allowed in Vyper.

For now, documents and tools are not so abundunt but we expect Vyper to be widely used in the future. (Vyper lies in Ethereum Foundation’s Grants Program wishlist)

Your welcome to contribute!

Please open issues and make pull requests to plasma-mvp-vyper! Comments on this articles about any mistakes and advices are also welcome.

--

--