Docs Vyperlang Org en Stable
Docs Vyperlang Org en Stable
1 Vyper 3
1.1 Principles and Goals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
2 Installing Vyper 5
2.1 Standalone . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.2 Binaries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.3 PIP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.4 Docker . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.5 nix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
3 Vyper by Example 9
3.1 Simple Open Auction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
3.2 Blind Auction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
3.3 Safe Remote Purchases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
3.4 Crowdfund . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
3.5 Voting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
3.6 Company Stock . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
4 Structure of a Contract 37
4.1 Pragmas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
4.2 Imports . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
4.3 State Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
4.4 Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
4.5 Modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
4.6 Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
4.7 Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
4.8 Structs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
5 Types 43
5.1 Value Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
5.2 Reference Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
5.3 Initial Values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
5.4 Type Conversions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
7 Statements 59
7.1 Control Flow . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
7.2 Event Logging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
i
7.3 Assertions and Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
8 Control Structures 63
8.1 Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
8.2 if statements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
8.3 for loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
10 Built-in Functions 79
10.1 Bitwise Operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
10.2 Chain Interaction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
10.3 Cryptography . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
10.4 Data Manipulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
10.5 Math . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
10.6 Utilities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
11 Modules 97
11.1 Declaring and using modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
11.2 Importing a module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
11.3 Using a module as an interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
11.4 Initializing a module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
11.5 The uses statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
11.6 Initializing a module with dependencies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
11.7 Exporting functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
12 Interfaces 103
12.1 Declaring and using Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
12.2 Built-in Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
12.3 Implementing an Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
12.4 Standalone Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
12.5 Extracting Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
ii
15.8 Vyper Archives . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
15.9 Compiler Input and Output JSON Description . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
iii
20.36 v0.1.0-beta.13 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173
20.37 v0.1.0-beta.12 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173
20.38 v0.1.0-beta.11 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174
20.39 v0.1.0-beta.10 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174
20.40 v0.1.0-beta.9 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175
20.41 Prior to v0.1.0-beta.9 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175
21 Contributing 177
21.1 Types of Contributions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177
21.2 How to Suggest Improvements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177
21.3 How to Report Issues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177
21.4 Fix Bugs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178
21.5 Style Guide . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178
21.6 Workflow for Pull Requests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178
Index 191
iv
Vyper Documentation
CONTENTS 1
Vyper Documentation
2 CONTENTS
CHAPTER
ONE
VYPER
Vyper is a contract-oriented, Pythonic programming language that targets the Ethereum Virtual Machine (EVM). It
prioritizes user safety, encourages clear coding practices via language design and efficient execution. In other words,
Vyper code is safe, clear and efficient!
3
Vyper Documentation
• Inline assembly: Adding inline assembly would make it no longer possible to search for a variable name in order
to find all instances where that variable is read or modified.
• Function overloading: This can cause lots of confusion on which function is called at any given time. Thus 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.
• Operator overloading: Operator overloading makes writing misleading code possible. For example + could be
overloaded so that it executes commands that are not visible at a first glance, such as sending funds the user did
not want to send.
• Recursive calling: Recursive calling makes it impossible to set an upper bound on gas limits, opening the door
for gas limit attacks.
• Infinite-length loops: Similar to recursive calling, infinite-length loops make it impossible to set an upper bound
on gas limits, opening the door for gas limit attacks.
• 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.
4 Chapter 1. Vyper
CHAPTER
TWO
INSTALLING VYPER
Take a deep breath, follow the instructions, and please create an issue if you encounter any errors.
Note: The easiest way to experiment with the language is to use either Try Vyper! (maintained by the Vyper team) or
the Remix online compiler (maintained by the Ethereum Foundation). - To use Try Vyper, go to https://try.vyperlang.org
and log in (requires Github login). - To use remix, go to https://remix.ethereum.org and activate the vyper-remix plugin
in the Plugin manager.
2.1 Standalone
The Vyper CLI can be installed with any pip compatible tool, for example, pipx or uv tool. If you do not have pipx
or uv installed, first, go to the respective tool’s installation page:
• https://github.com/pypa/pipx?tab=readme-ov-file
• https://github.com/astral-sh/uv?tab=readme-ov-file#uv
Then, the command to install Vyper would be
Or,
2.2 Binaries
Alternatively, prebuilt Vyper binaries for Windows, Mac and Linux are available for download from the GitHub releases
page: https://github.com/vyperlang/vyper/releases.
5
Vyper Documentation
2.3 PIP
Vyper can only be built using Python 3.10 and higher. If you need to know how to install the correct version of python,
follow the instructions from the official Python website.
Because pip installations are not isolated by default, this method of installation is meant for more experienced Python
developers who are using Vyper as a library, or want to use it within a Python project with other pip dependencies.
It is strongly recommended to install Vyper in a virtual Python environment, so that new packages installed and
dependencies built are strictly contained in your Vyper project and will not alter or affect your other development
environment set-up. For easy virtualenv management, we recommend either pyenv or Poetry.
Note: To find out more about virtual environments, check out: virtualenv guide.
Each tagged version of vyper is uploaded to pypi, and can be installed using pip:
You can check if Vyper is installed completely or not by typing the following in your terminal/cmd:
vyper --version
2.4 Docker
Alternatively you can log into the docker image and execute vyper on the prompt.
˓→': 441}, {'name': 'test2', 'outputs': [], 'inputs': [{'type': 'uint256', 'name': 'a'}],
Note: If you would like to know how to install Docker, please follow their documentation.
2.5 nix
2.5. nix 7
Vyper Documentation
THREE
VYPER BY EXAMPLE
As an introductory example of a smart contract written in Vyper, we will begin with a simple open auction contract.
As we dive into the code, it is important to remember that all Vyper syntax is valid Python3 syntax, however not all
Python3 functionality is available in Vyper.
In this contract, we will be looking at a simple open auction contract where participants can submit bids during a limited
time period. When the auction period ends, a predetermined beneficiary will receive the amount of the highest bid.
3 # Open Auction
4
5 # Auction params
6 # Beneficiary receives money from the highest bidder
7 beneficiary: public(address)
8 auctionStart: public(uint256)
9 auctionEnd: public(uint256)
10
9
Vyper Documentation
77 # 1. Conditions
78 # Check if auction endtime has been reached
79 assert block.timestamp >= self.auctionEnd
80 # Check if this function has already been called
81 assert not self.ended
82
86 # 3. Interaction
87 send(self.beneficiary, self.highestBid)
As you can see, this example only has a constructor, two methods to call, and a few variables to manage the contract
state. Believe it or not, this is all we need for a basic implementation of an auction smart contract.
Let’s get started!
3 # Open Auction
4
5 # Auction params
6 # Beneficiary receives money from the highest bidder
7 beneficiary: public(address)
8 auctionStart: public(uint256)
9 auctionEnd: public(uint256)
10
We begin by declaring a few variables to keep track of our contract state. We initialize a global variable beneficiary
by calling public on the datatype address. The beneficiary will be the receiver of money from the highest bidder.
We also initialize the variables auctionStart and auctionEnd with the datatype uint256 to manage the open
auction period and highestBid with datatype uint256, the smallest denomination of ether, to manage auction state.
The variable ended is a boolean to determine whether the auction is officially over. The variable pendingReturns is
a map which enables the use of key-value pairs to keep proper track of the auctions withdrawal pattern.
You may notice all of the variables being passed into the public function. By declaring the variable public, the variable
is callable by external contracts. Initializing the variables without the public function defaults to a private declaration
and thus only accessible to methods within the same contract. The public function additionally creates a ‘getter’
function for the variable, accessible through an external call such as contract.beneficiary().
Now, the constructor.
The contract is initialized with three arguments: _beneficiary of type address, _auction_start with type
uint256 and _bidding_time with type uint256, the time difference between the start and end of the auction. We
then store these three pieces of information into the contract variables self.beneficiary, self.auctionStart
and self.auctionEnd respectively. Notice that we have access to the current time by calling block.timestamp.
block is an object available within any Vyper contract and provides information about the block at the time of calling.
Similar to block, another important object available to us within the contract is msg, which provides information on
the method caller as we will soon see.
With initial setup out of the way, lets look at how our users can make bids.
The @payable decorator will allow a user to send some ether to the contract in order to call the decorated method. In
this case, a user wanting to make a bid would call the bid() method while sending an amount equal to their desired
bid (not including gas fees). When calling any method within a contract, we are provided with a built-in variable msg
and we can access the public address of any method caller with msg.sender. Similarly, the amount of ether a user
sends can be accessed by calling msg.value.
Here, we first check whether the current time is within the bidding period by comparing with the auction’s start and
end times using the assert function which takes any boolean statement. We also check to see if the new bid is greater
than the highest bid. If the three assert statements pass, we can safely continue to the next lines; otherwise, the bid()
method will throw an error and revert the transaction. If the two assert statements and the check that the previous bid
is not equal to zero pass, we can safely conclude that we have a valid new highest bid. We will send back the previous
highestBid to the previous highestBidder and set our new highestBid and highestBidder.
77 # 1. Conditions
78 # Check if auction endtime has been reached
79 assert block.timestamp >= self.auctionEnd
80 # Check if this function has already been called
81 assert not self.ended
82
83 # 2. Effects
(continues on next page)
86 # 3. Interaction
87 send(self.beneficiary, self.highestBid)
With the endAuction() method, we check whether our current time is past the auctionEnd time we set upon initial-
ization of the contract. We also check that self.ended had not previously been set to True. We do this to prevent any
calls to the method if the auction had already ended, which could potentially be malicious if the check had not been
made. We then officially end the auction by setting self.ended to True and sending the highest bid amount to the
beneficiary.
And there you have it - an open auction contract. Of course, this is a simplified example with barebones functionality
and can be improved. Hopefully, this has provided some insight into the possibilities of Vyper. As we move on to
exploring more complex examples, we will encounter more design patterns and features of the Vyper language.
And of course, no smart contract tutorial is complete without a note on security.
Note: It’s always important to keep security in mind when designing a smart contract. As any application becomes
more complex, the greater the potential for introducing new risks. Thus, it’s always good practice to keep contracts as
readable and simple as possible.
Before we dive into our other examples, let’s briefly explore another type of auction that you can build with Vyper.
Similar to its counterpart written in Solidity, this blind auction allows for an auction where there is no time pressure
towards the end of the bidding period.
5 struct Bid:
6 blindedBid: bytes32
7 deposit: uint256
8
9 # Note: because Vyper does not allow for dynamic arrays, we have limited the
10 # number of bids that can be placed by one address to 128 in this example
11 MAX_BIDS: constant(int128) = 128
12
18 # Auction parameters
19 beneficiary: public(address)
20 biddingEnd: public(uint256)
(continues on next page)
37
47
78
94 return True
95
96
97 # Reveal your blinded bids. You will get a refund for all correctly blinded
98 # invalid bids and for all bids except for the totally highest.
99 @external
100 def reveal(_numBids: int128, _values: uint256[128], _fakes: bool[128], _secrets:␣
˓→bytes32[128]):
107 # Check that number of bids being revealed matches log for sender
108 assert _numBids == self.bidCounts[msg.sender]
109
140 # Make it impossible for the sender to re-claim the same deposit
141 zeroBytes32: bytes32 = empty(bytes32)
142 bidToCheck.blindedBid = zeroBytes32
143
148
164
165 # End the auction and send the highest bid to the beneficiary.
166 @external
167 def auctionEnd():
168 # Check that reveal end has passed
169 assert block.timestamp > self.revealEnd
170
171 # Check that auction has not already been marked as ended
172 assert not self.ended
173
While this blind auction is almost functionally identical to the blind auction implemented in Solidity, the differences
in their implementations help illustrate the differences between Solidity and Vyper.
28 highestBidder: public(address)
29
One key difference is that, because Vyper does not allow for dynamic arrays, we have limited the number of bids that
can be placed by one address to 128 in this example. Bidders who want to make more than this maximum number of
bids would need to do so from multiple addresses.
In this example, we have an escrow contract implementing a system for a trustless transaction between a buyer and a
seller. In this system, a seller posts an item for sale and makes a deposit to the contract of twice the item’s value. At
this moment, the contract has a balance of 2 * value. The seller can reclaim the deposit and close the sale as long
as a buyer has not yet made a purchase. If a buyer is interested in making a purchase, they would make a payment
and submit an equal amount for deposit (totaling 2 * value) into the contract and locking the contract from further
modification. At this moment, the contract has a balance of 4 * value and the seller would send the item to buyer.
Upon the buyer’s receipt of the item, the buyer will mark the item as received in the contract, thereby returning the
buyer’s deposit (not payment), releasing the remaining funds to the seller, and completing the transaction.
There are certainly others ways of designing a secure escrow system with less overhead for both the buyer and seller,
but for the purpose of this example, we want to explore one way how an escrow system can be implemented trustlessly.
Let’s go!
1 #pragma version >0.3.10
2
12 # 2. Buyer purchases item (value) plus posts an additional safety deposit (Item value).
13 # Balance is 4*value.
14 # 3. Seller ships item.
15 # 4. Buyer confirms receiving the item. Buyer's deposit (value) is returned.
16 # Seller's deposit (2*value) + items value is returned. Balance is 0.
17
25 @deploy
26 @payable
27 def __init__():
28 assert (msg.value % 2) == 0
29 assert msg.value > 0
30 self.value = msg.value // 2 # The seller initializes the contract by
31 # posting a safety deposit of 2*value of the item up for sale.
32 self.seller = msg.sender
33 self.unlocked = True
34
35 @external
36 def abort():
37 assert not self.finalized
38 assert self.unlocked #Is the contract still refundable?
39 assert msg.sender == self.seller # Only the seller can refund
40 # his deposit before any buyer purchases the item.
41 self.finalized = True
42 assert self.balance > 0 and self.balance == 2 * self.value
43 send(self.seller, self.balance)
44
45 @external
46 @payable
47 def purchase():
48 assert not self.finalized
49 assert self.unlocked # Is the contract still open (is the item still up
50 # for sale)?
51 assert msg.value == (2 * self.value) # Is the deposit the correct value?
52 self.buyer = msg.sender
53 self.unlocked = False
54
55 @external
56 def received():
57 # 1. Conditions
58 assert not self.finalized
59 assert not self.unlocked # Is the item already purchased and pending
60 # confirmation from the buyer?
61 assert msg.sender == self.buyer
62 assert not self.ended
63
64 # 2. Effects
65 self.ended = True
66 self.finalized = True
67
68 # 3. Interaction
69 send(self.buyer, self.value) # Return the buyer's deposit (=value) to the buyer.
70 assert self.balance == 3 * self.value
71 send(self.seller, self.balance) # Return the seller's deposit (=2*value) and the
(continues on next page)
This is also a moderately short contract, however a little more complex in logic. Let’s break down this contract bit by
bit.
Like the other contracts, we begin by declaring our global variables public with their respective data types. Remember
that the public function allows the variables to be readable by an external caller, but not writeable.
22 ended: public(bool)
23 finalized: public(bool)
24
25 @deploy
26 @payable
27 def __init__():
28 assert (msg.value % 2) == 0
29 assert msg.value > 0
With a @payable decorator on the constructor, the contract creator will be required to make an initial deposit equal to
twice the item’s value to initialize the contract, which will be later returned. This is in addition to the gas fees needed
to deploy the contract on the blockchain, which is not returned. We assert that the deposit is divisible by 2 to ensure
that the seller deposited a valid amount. The constructor stores the item’s value in the contract variable self.value
and saves the contract creator into self.seller. The contract variable self.unlocked is initialized to True.
35 @external
36 def abort():
The abort() method is a method only callable by the seller and while the contract is still unlocked—meaning it is
callable only prior to any buyer making a purchase. As we will see in the purchase() method that when a buyer calls
the purchase() method and sends a valid amount to the contract, the contract will be locked and the seller will no
longer be able to call abort().
When the seller calls abort() and if the assert statements pass, the contract will call the selfdestruct() function
and refunds the seller and subsequently destroys the contract.
45 @external
Like the constructor, the purchase() method has a @payable decorator, meaning it can be called with a payment.
For the buyer to make a valid purchase, we must first assert that the contract’s unlocked property is True and that
the amount sent is equal to twice the item’s value. We then set the buyer to the msg.sender and lock the contract. At
this point, the contract has a balance equal to 4 times the item value and the seller must send the item to the buyer.
47 def purchase():
48 assert not self.finalized
49 assert self.unlocked # Is the contract still open (is the item still up
50 # for sale)?
51 assert msg.value == (2 * self.value) # Is the deposit the correct value?
52 self.buyer = msg.sender
53 self.unlocked = False
54
55 @external
56 def received():
57 # 1. Conditions
58 assert not self.finalized
59 assert not self.unlocked # Is the item already purchased and pending
60 # confirmation from the buyer?
61 assert msg.sender == self.buyer
Finally, upon the buyer’s receipt of the item, the buyer can confirm their receipt by calling the received() method to
distribute the funds as intended—where the seller receives 3/4 of the contract balance and the buyer receives 1/4.
By calling received(), we begin by checking that the contract is indeed locked, ensuring that a buyer had previously
paid. We also ensure that this method is only callable by the buyer. If these two assert statements pass, we refund the
buyer their initial deposit and send the seller the remaining funds. The contract is finally destroyed and the transaction
is complete.
Whenever we’re ready, let’s move on to the next example.
3.4 Crowdfund
Now, let’s explore a straightforward example for a crowdfunding contract where prospective participants can contribute
funds to a campaign. If the total contribution to the campaign reaches or surpasses a predetermined funding goal, the
funds will be sent to the beneficiary at the end of the campaign deadline. Participants will be refunded their respective
contributions if the total funding does not reach its target goal.
3 ###########################################################################
4 ## THIS IS EXAMPLE CODE, NOT MEANT TO BE USED IN PRODUCTION! CAVEAT EMPTOR!
5 ###########################################################################
6
32 self.funders[msg.sender] += msg.value
33
42 send(self.beneficiary, self.balance)
43
54 send(msg.sender, value)
Most of this code should be relatively straightforward after going through our previous examples. Let’s dive right in.
3 ###########################################################################
4 ## THIS IS EXAMPLE CODE, NOT MEANT TO BE USED IN PRODUCTION! CAVEAT EMPTOR!
5 ###########################################################################
6
Like other examples, we begin by initiating our variables - except this time, we’re not calling them with the public
3.4. Crowdfund 21
Vyper Documentation
Note: Unlike the existence of the function public(), there is no equivalent private() function. Variables simply
default to private if initiated without the public() function.
The funders variable is initiated as a mapping where the key is an address, and the value is a number representing the
contribution of each participant. The beneficiary will be the final receiver of the funds once the crowdfunding period
is over—as determined by the deadline and timelimit variables. The goal variable is the target total contribution
of all participants.
Our constructor function takes 3 arguments: the beneficiary’s address, the goal in wei value, and the difference in time
from start to finish of the crowdfunding. We initialize the arguments as contract variables with their corresponding
names. Additionally, a self.deadline is initialized to set a definitive end time for the crowdfunding period.
Now lets take a look at how a person can participate in the crowdfund.
17 @deploy
18 def __init__(_beneficiary: address, _goal: uint256, _timelimit: uint256):
19 self.beneficiary = _beneficiary
20 self.deadline = block.timestamp + _timelimit
21 self.timelimit = _timelimit
22 assert _goal > 0, "Goal must be non-zero"
23 self.goal = _goal
Once again, we see the @payable decorator on a method, which allows a person to send some ether along with a
call to the method. In this case, the participate() method accesses the sender’s address with msg.sender and
the corresponding amount sent with msg.value. This information is stored into a struct and then saved into the
funders mapping with self.nextFunderIndex as the key. As more participants are added to the mapping, self.
nextFunderIndex increments appropriately to properly index each participant.
The finalize() method is used to complete the crowdfunding process. However, to complete the crowdfunding,
the method first checks to see if the crowdfunding period is over and that the balance has reached/passed its set goal.
If those two conditions pass, the contract calls the selfdestruct() function and sends the collected funds to the
beneficiary.
Note: Notice that we have access to the total amount sent to the contract by calling self.balance, a variable we never
explicitly set. Similar to msg and block, self.balance is a built-in variable that’s available in all Vyper contracts.
We can finalize the campaign if all goes well, but what happens if the crowdfunding campaign isn’t successful? We’re
going to need a way to refund all the participants.
41 send(self.beneficiary, self.balance)
In the refund() method, we first check that the crowdfunding period is indeed over and that the total collected balance
is less than the goal with the assert statement . If those two conditions pass, we let users get their funds back using
the withdraw pattern.
3.5 Voting
In this contract, we will implement a system for participants to vote on a list of proposals. The chairperson of the
contract will be able to give each participant the right to vote, and each participant may choose to vote, or delegate their
vote to another voter. Finally, a winning proposal will be determined upon calling the winningProposals() method,
which iterates through all the proposals and returns the one with the greatest number of votes.
3.5. Voting 23
Vyper Documentation
30 @view
31 @internal
32 def _delegated(addr: address) -> bool:
33 return self.voters[addr].delegate != empty(address)
34
35
36 @view
37 @external
38 def delegated(addr: address) -> bool:
39 return self._delegated(addr)
40
41
42 @view
43 @internal
44 def _directlyVoted(addr: address) -> bool:
45 return self.voters[addr].voted and (self.voters[addr].delegate == empty(address))
46
47
48 @view
49 @external
50 def directlyVoted(addr: address) -> bool:
51 return self._directlyVoted(addr)
52
53
109 if self._directlyVoted(target):
110 self.proposals[self.voters[target].vote].voteCount += weight_to_forward
111 self.voters[target].weight = 0
112
3.5. Voting 25
Vyper Documentation
135 # This call will throw if and only if this delegation would cause a loop
136 # of length <= 5 that ends up delegating back to the delegator.
137 self._forwardWeight(msg.sender)
138
168 @view
169 @external
170 def winningProposal() -> int128:
171 return self._winningProposal()
172
173
As we can see, this is the contract of moderate length which we will dissect section by section. Let’s begin!
The variable voters is initialized as a mapping where the key is the voter’s public address and the value is a struct
describing the voter’s properties: weight, voted, delegate, and vote, along with their respective data types.
Similarly, the proposals variable is initialized as a public mapping with int128 as the key’s datatype and a struct
to represent each proposal with the properties name and vote_count. Like our last example, we can access any value
by key’ing into the mapping with a number just as one would with an index in an array.
Then, voterCount and chairperson are initialized as public with their respective datatypes.
Let’s move onto the constructor.
In the constructor, we hard-coded the contract to accept an array argument of exactly two proposal names of type
bytes32 for the contracts initialization. Because upon initialization, the __init__() method is called by the contract
creator, we have access to the contract creator’s address with msg.sender and store it in the contract variable self.
chairperson. We also initialize the contract variable self.voter_count to zero to initially represent the number
of votes allowed. This value will be incremented as each participant in the contract is given the right to vote by the
method giveRightToVote(), which we will explore next. We loop through the two proposals from the argument and
insert them into proposals mapping with their respective index in the original array as its key.
Now that the initial setup is done, lets take a look at the functionality.
3.5. Voting 27
Vyper Documentation
Note: Throughout this contract, we use a pattern where @external functions return data from @internal functions
that have the same name prepended with an underscore. This is because Vyper does not allow calls between external
functions within the same contract. The internal function handles the logic and allows internal access, while the external
function acts as a getter to allow external viewing.
We need a way to control who has the ability to vote. The method giveRightToVote() is a method callable by only
the chairperson by taking a voter address and granting it the right to vote by incrementing the voter’s weight property.
We sequentially check for 3 conditions using assert. The assert not function will check for falsy boolean values -
in this case, we want to know that the voter has not already voted. To represent voting power, we will set their weight
to 1 and we will keep track of the total number of voters by incrementing voterCount.
134 # This call will throw if and only if this delegation would cause a loop
In the method delegate, firstly, we check to see that msg.sender has not already voted and secondly, that the target
delegate and the msg.sender are not the same. Voters shouldn’t be able to delegate votes to themselves. We, then,
loop through all the voters to determine whether the person delegate to had further delegated their vote to someone
else in order to follow the chain of delegation. We then mark the msg.sender as having voted if they delegated their
vote. We increment the proposal’s voterCount directly if the delegate had already voted or increase the delegate’s
vote weight if the delegate has not yet voted.
139 # Give your vote (including votes delegated to you)
140 # to proposal `proposals[proposal].name`.
141 @external
142 def vote(proposal: int128):
143 # can't vote twice
144 assert not self.voters[msg.sender].voted
145 # can only vote on legitimate proposals
(continues on next page)
Now, let’s take a look at the logic inside the vote() method, which is surprisingly simple. The method takes the key
of the proposal in the proposals mapping as an argument, check that the method caller had not already voted, sets
the voter’s vote property to the proposal key, and increments the proposals voteCount by the voter’s weight.
With all the basic functionality complete, what’s left is simply returning the winning proposal. To do this, we have
two methods: winningProposal(), which returns the key of the proposal, and winnerName(), returning the name
of the proposal. Notice the @view decorator on these two methods. We do this because the two methods only read the
blockchain state and do not modify it. Remember, reading the blockchain state is free; modifying the state costs gas.
By having the @view decorator, we let the EVM know that this is a read-only function and we benefit by saving gas
fees.
153 self.voters[msg.sender].weight = 0
154
168 @view
169 @external
170 def winningProposal() -> int128:
The _winningProposal() method returns the key of proposal in the proposals mapping. We will keep track of
greatest number of votes and the winning proposal with the variables winningVoteCount and winningProposal,
respectively by looping through all the proposals.
winningProposal() is an external function allowing access to _winningProposal().
And finally, the winnerName() method returns the name of the proposal by key’ing into the proposals mapping with
the return result of the winningProposal() method.
And there you have it - a voting contract. Currently, many transactions are needed to assign the rights to vote to all
participants. As an exercise, can we try to optimize this?
Now that we’re familiar with basic contracts. Let’s step up the difficulty.
3.5. Voting 29
Vyper Documentation
This contract is just a tad bit more thorough than the ones we’ve previously encountered. In this example, we are going
to look at a comprehensive contract that manages the holdings of all shares of a company. The contract allows for a
person to buy, sell and transfer shares of a company as well as allowing for the company to pay a person in ether. The
company, upon initialization of the contract, holds all shares of the company at first but can sell them all.
Let’s get started.
5 event Transfer:
6 sender: indexed(address)
7 receiver: indexed(address)
8 value: uint256
9
10 event Buy:
11 buyer: indexed(address)
12 buy_order: uint256
13
14 event Sell:
15 seller: indexed(address)
16 sell_order: uint256
17
18 event Pay:
19 vendor: indexed(address)
20 amount: uint256
21
22
23 # Initiate the variables for the company and it's own shares.
24 company: public(address)
25 totalShares: public(uint256)
26 price: public(uint256)
27
37 self.company = _company
38 self.totalShares = _total_shares
39 self.price = initial_price
40
41 # The company holds all the shares at first, but can sell them all.
42 self.holdings[self.company] = _total_shares
43
61 # Take the shares off the market and give them to the stockholder.
62 self.holdings[self.company] -= buy_order
63 self.holdings[msg.sender] += buy_order
64
80 # Give stock back to the company and get money back as ETH.
81 @external
82 def sellStock(sell_order: uint256):
83 assert sell_order > 0 # Otherwise, this would fail at send() below,
84 # due to an OOG error (there would be zero value available for gas).
85 # You can only sell as much stock as you own.
86 assert self._getHolding(msg.sender) >= sell_order
87 # Check that the company can pay you.
88 assert self.balance >= (sell_order * self.price)
89
107 # Debit the sender's stock and add to the receiver's address.
108 self.holdings[msg.sender] -= transfer_order
109 self.holdings[receiver] += transfer_order
110
128
135 # Return the cash holdings minus the debt of the company.
136 # The share debt or liability only is included here,
137 # but of course all other liabilities can be included.
138 @view
139 @external
140 def worth() -> uint256:
141 return self.balance - self._debt()
142
143 # Return the amount in wei that a company has raised in stock offerings.
144 @view
145 @internal
146 def _debt() -> uint256:
147 return (self.totalShares - self._stockAvailable()) * self.price
148
155 # Find out how much stock any address (that's owned by someone) has.
156 @view
157 @internal
158 def _getHolding(_stockholder: address) -> uint256:
159 return self.holdings[_stockholder]
Note: Throughout this contract, we use a pattern where @external functions return data from @internal functions
that have the same name prepended with an underscore. This is because Vyper does not allow calls between external
functions within the same contract. The internal function handles the logic, while the external function acts as a getter
to allow viewing.
The contract contains a number of methods that modify the contract state as well as a few ‘getter’ methods to read
it. We first declare several events that the contract logs. We then declare our global variables, followed by function
definitions.
5 event Transfer:
6 sender: indexed(address)
7 receiver: indexed(address)
8 value: uint256
9
10 event Buy:
11 buyer: indexed(address)
12 buy_order: uint256
13
14 event Sell:
15 seller: indexed(address)
16 sell_order: uint256
17
18 event Pay:
19 vendor: indexed(address)
20 amount: uint256
21
22
23 # Initiate the variables for the company and it's own shares.
24 company: public(address)
25 totalShares: public(uint256)
26 price: public(uint256)
We initiate the company variable to be of type address that’s public. The totalShares variable is of type uint256,
which in this case represents the total available shares of the company. The price variable represents the wei value of
a share and holdings is a mapping that maps an address to the number of shares the address owns.
37 self.company = _company
38 self.totalShares = _total_shares
39 self.price = initial_price
In the constructor, we set up the contract to check for valid inputs during the initialization of the contract via the two
assert statements. If the inputs are valid, the contract variables are set accordingly and the company’s address is
initialized to hold all shares of the company in the holdings mapping.
42 self.holdings[self.company] = _total_shares
43
We will be seeing a few @view decorators in this contract—which is used to decorate methods that simply read the con-
tract state or return a simple calculation on the contract state without modifying it. Remember, reading the blockchain
is free, writing on it is not. Since Vyper is a statically typed language, we see an arrow following the definition of
the _stockAvailable() method, which simply represents the data type which the function is expected to return.
In the method, we simply key into self.holdings with the company’s address and check it’s holdings. Because
_stockAvailable() is an internal method, we also include the stockAvailable() method to allow external ac-
cess.
Now, lets take a look at a method that lets a person buy stock from the company’s holding.
51 @external
52 @payable
53 def buyStock():
54 # Note: full amount is given to company (no fractional shares),
55 # so be sure to send exact amount to buy shares
56 buy_order: uint256 = msg.value // self.price # rounds down
57
61 # Take the shares off the market and give them to the stockholder.
62 self.holdings[self.company] -= buy_order
63 self.holdings[msg.sender] += buy_order
The buyStock() method is a @payable method which takes an amount of ether sent and calculates the buyOrder
(the stock value equivalence at the time of call). The number of shares is deducted from the company’s holdings and
transferred to the sender’s in the holdings mapping.
Now that people can buy shares, how do we check someone’s holdings?
66 log Buy(buyer=msg.sender, buy_order=buy_order)
67
The _getHolding() is another @view method that takes an address and returns its corresponding stock holdings by
keying into self.holdings. Again, an external function getHolding() is included to allow access.
72 return self._getHolding(_stockholder)
73
To check the ether balance of the company, we can simply call the getter method cash().
78 return self.balance
79
80 # Give stock back to the company and get money back as ETH.
81 @external
82 def sellStock(sell_order: uint256):
83 assert sell_order > 0 # Otherwise, this would fail at send() below,
84 # due to an OOG error (there would be zero value available for gas).
85 # You can only sell as much stock as you own.
86 assert self._getHolding(msg.sender) >= sell_order
87 # Check that the company can pay you.
88 assert self.balance >= (sell_order * self.price)
89
To sell a stock, we have the sellStock() method which takes a number of stocks a person wishes to sell, and sends
the equivalent value in ether to the seller’s address. We first assert that the number of stocks the person wishes to
sell is a value greater than 0. We also assert to see that the user can only sell as much as the user owns and that the
company has enough ether to complete the sale. If all conditions are met, the holdings are deducted from the seller and
given to the company. The ethers are then sent to the seller.
107 # Debit the sender's stock and add to the receiver's address.
108 self.holdings[msg.sender] -= transfer_order
109 self.holdings[receiver] += transfer_order
A stockholder can also transfer their stock to another stockholder with the transferStock() method. The method
takes a receiver address and the number of shares to send. It first asserts that the amount being sent is greater than
0 and asserts whether the sender has enough stocks to send. If both conditions are satisfied, the transfer is made.
The company is also allowed to pay out an amount in ether to an address by calling the payBill() method. This
method should only be callable by the company and thus first checks whether the method caller’s address matches that
of the company. Another important condition to check is that the company has enough funds to pay the amount. If
both conditions satisfy, the contract sends its ether to an address.
128
We can also check how much the company has raised by multiplying the number of shares the company has sold and the
price of each share. Internally, we get this value by calling the _debt() method. Externally it is accessed via debt().
135 # Return the cash holdings minus the debt of the company.
136 # The share debt or liability only is included here,
137 # but of course all other liabilities can be included.
138 @view
Finally, in this worth() method, we can check the worth of a company by subtracting its debt from its ether balance.
This contract has been the most thorough example so far in terms of its functionality and features. Yet despite the
thoroughness of such a contract, the logic remained simple. Hopefully, by now, the Vyper language has convinced you
of its capabilities and readability in writing smart contracts.
FOUR
STRUCTURE OF A CONTRACT
Vyper contracts are contained within files. Each file contains exactly one contract.
This section provides a quick overview of the types of data present within a contract, with links to other sections where
you can obtain more details.
4.1 Pragmas
Vyper supports several source code directives to control compiler modes and help with build reproducibility.
The version pragma ensures that a contract is only compiled by the intended compiler version, or range of versions.
Version strings use NPM style syntax. Starting from v0.4.0 and up, version strings will use PEP440 version specifiers.
As of 0.3.10, the recommended way to specify the version pragma is as follows:
Note: Both pragma directive versions #pragma and # pragma are supported.
The following declaration is equivalent, and, prior to 0.3.10, was the only supported method to specify the compiler
version:
# @version ^0.3.0
In the above examples, the contract will only compile with Vyper versions 0.3.x.
The optimization mode can be one of "none", "codesize", or "gas" (default). For example, adding the following
line to a contract will cause it to try to optimize for codesize:
The optimization mode can also be set as a compiler option, which is documented in Compiler Optimization Modes.
If the compiler option conflicts with the source code pragma, an exception will be raised and compilation will not
continue.
37
Vyper Documentation
The EVM version can be set with the evm-version pragma, which is documented in Setting the Target EVM Version.
The new experimental code generation feature can be activated using the following directive:
#pragma experimental-codegen
Alternatively, you can use the alias "venom-experimental" instead of "experimental-codegen" to enable this
feature.
4.2 Imports
Import statements allow you to import Modules or Interfaces with the import or from ... import syntax.
You may import modules (defined in .vy files) and interfaces (defined in .vyi or .json files) via import statements.
You may use plain or as variants.
# without an alias
import foo
# with an alias
import my_package.foo as bar
Using from you can perform both absolute and relative imports. You may optionally include an alias - if you do not,
the name of the interface will be the same as the file.
# without an alias
from my_package import foo
# with an alias
from my_package import foo as bar
Relative imports are possible by prepending dots to the contract name. A single leading dot indicates a relative import
starting with the current package. Two leading dots indicate a relative import from the parent of the current package:
Further higher directories can be accessed with ..., .... etc., as in Python.
When looking for a file to import, Vyper will first search relative to the same folder as the contract being compiled. It
then checks for the file in the provided search paths, in the precedence provided. Vyper checks for the file name with a
.vy suffix first, then .vyi, then .json.
When using the vyper CLI, the search path defaults to the current working directory, plus the python syspath. You can
append to the search path with the -p flag, e.g.:
In the above example, the my_project folder is set as the root path.
Note: Including the python syspath on the search path means that any Vyper module in the current virtualenv is
discoverable by the Vyper compiler, and Vyper packages can be published to and installed from PyPI and accessed
via import statements with no additional configuration. Keep in mind that best practice is always to install packages
within a virtualenv and not globally!
You can additionally disable the behavior of adding the syspath to the search path with the CLI flag
--disable-sys-path:
When compiling from a .vyz archive file or standard json input, the search path is already part of the bundle, it cannot
be changed from the command line.
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
self.storedData = 123
See the documentation on Types or Scoping and Declarations for more information.
4.4 Functions
@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.
See the Functions documentation for more information.
4.5 Modules
A module is a set of function definitions and variable declarations which enables code reuse. Vyper favors code reuse
through composition, rather than inheritance.
Broadly speaking, a module contains:
• function definitions
• state variable declarations
• type definitions
Therefore, a module encapsulates
• functionality (types and functions), and
• state (variables), which may be tightly coupled with that functionality
Modules can be added to contracts by importing them from a .vy file. Any .vy file is a valid module which can
be imported into another contract! This is a very powerful feature which allows you to assemble contracts via other
contracts as building blocks.
Modules are opt-in by design. That is, any operations involving state or exposing external functions must be explicitly
opted into using the exports, uses or initializes keywords. See the Modules documentation for more information.
4.6 Events
Events provide an interface for the EVM’s logging facilities. Events may be logged with specially indexed data struc-
tures 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)
4.7 Interfaces
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 .vyi file.
interface FooBar:
def calculate() -> uint256: view
def test1(): nonpayable
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()
4.8 Structs
A struct is a custom defined type that allows you to group several variables together:
struct MyStruct:
value1: int128
value2: decimal
4.8. Structs 41
Vyper Documentation
FIVE
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.
In addition, types can interact with each other in expressions containing operators.
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.
5.1.1 Boolean
Keyword: bool
A boolean is a type to store a logical/truth value.
Values
The only possible values are the constants True and False.
Operators
Operator Description
not x Logical negation
x and y Logical conjunction
x or y Logical disjunction
x == y Equality
x != y Inequality
Short-circuiting of boolean operators (or and and) is consistent with the behavior of Python.
43
Vyper Documentation
Values
Operators
Comparisons
Operator Description
x < y Less than
x <= y Less than or equal to
x == y Equals
x != y Does not equal
x >= y Greater than or equal to
x > y Greater than
Arithmetic Operators
Operator Description
x + y Addition
x - y Subtraction
-x Unary minus/Negation
x * y Multiplication
x // y Integer division
x**y Exponentiation
x % y Modulo
44 Chapter 5. Types
Vyper Documentation
Bitwise Operators
Operator Description
x & y Bitwise and
x | y Bitwise or
x ^ y Bitwise xor
Shifts
Operator Description
x << y Left shift
x >> y Right shift
Shifting is only available for 256-bit wide types. That is, x must be int256, and y can be any unsigned integer. The
right shift for int256 compiles to a signed right shift (EVM SAR instruction).
Note: While at runtime shifts are unchecked (that is, they can be for any number of bits), to prevent common mistakes,
the compiler is stricter at compile-time and will prevent out of bounds shifts. For instance, at runtime, 1 << 257 will
evaluate to 0, while that expression at compile-time will raise an OverflowException.
Note: Integer division has different rounding semantics than Python for negative numbers: Vyper rounds towards zero,
while Python rounds towards negative infinity. For example, -1 // 2 will return -1 in Python, but 0 in Vyper. This
preserves the spirit (but not the text!) of the reasoning for Python’s round-towards-negative-infinity behavior, which is
that the behavior of // combined with the behavior of % preserves the following identity no matter if the quantities are
negative or non-negative: (x // y) * y + (x % y) == x.
Values
Note: Integer literals are interpreted as int256 by default. In cases where uint8 is more appropriate, such as
assignment, the literal might be interpreted as uint8. Example: _variable: uint8 = _literal. In order to
explicitly cast a literal to a uint8 use convert(_literal, uint8).
Operators
Comparisons
Operator Description
x < y Less than
x <= y Less than or equal to
x == y Equals
x != y Does not equal
x >= y Greater than or equal to
x > y Greater than
Arithmetic Operators
Operator Description
x + y Addition
x - y Subtraction
x * y Multiplication
x // y Integer division
x**y Exponentiation
x % y Modulo
Bitwise Operators
Operator Description
x & y Bitwise and
x | y Bitwise or
x ^ y Bitwise xor
~x Bitwise not
Note: The Bitwise not operator is currently only available for uint256 type.
46 Chapter 5. Types
Vyper Documentation
Shifts
Operator Description
x << y Left shift
x >> y Right shift
Shifting is only available for 256-bit wide types. That is, x must be uint256, and y can be any unsigned integer. The
right shift for uint256 compiles to a signed right shift (EVM SHR instruction).
Note: While at runtime shifts are unchecked (that is, they can be for any number of bits), to prevent common mistakes,
the compiler is stricter at compile-time and will prevent out of bounds shifts. For instance, at runtime, 1 << 257 will
evaluate to 0, while that expression at compile-time will raise an OverflowException.
5.1.4 Decimals
Keyword: decimal
A decimal is a type to store a decimal fixed point value. As of v0.4.0, decimals must be enabled with the CLI flag
--enable-decimals.
Values
Operators
Comparisons
Operator Description
x < y Less than
x <= y Less or equal
x == y Equals
x != y Does not equal
x >= y Greater or equal
x > y Greater than
Arithmetic Operators
Operator Description
x + y Addition
x - y Subtraction
-x Unary minus/Negation
x * y Multiplication
x / y Decimal division
x % y Modulo
5.1.5 Address
Keyword: address
The address type holds an Ethereum address.
Values
An address type can hold an Ethereum address which equates to 20 bytes or 160 bits. Address literals must be written
in hexadecimal notation with a leading 0x and must be checksummed.
Members
Syntax as follows: _address.<member>, where _address is of the type address and <member> is one of the above
keywords.
Note: Operations such as SELFDESTRUCT and CREATE2 allow for the removal and replacement of bytecode at an
address. You should never assume that values of address members will not change in the future.
Note: _address.code requires the usage of slice to explicitly extract a section of contract bytecode. If the extracted
section exceeds the bounds of bytecode, this will throw. You can check the size of _address.code using _address.
codesize.
48 Chapter 5. Types
Vyper Documentation
Keyword: bytesM 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).
Example:
# Declaration
hash: bytes32
# Assignment
self.hash = _hash
Operators
Keyword Description
keccak256(x) Return the keccak256 hash as bytes32.
concat(x, ...) Concatenate multiple inputs.
slice(x, start=_start, len=_len) Return a slice of _len starting at _start.
Where x is a byte array and _start as well as _len are integer values.
Keyword: Bytes
A byte array with a max size.
The syntax being Bytes[maxLen], where maxLen is an integer which denotes the maximum number of bytes. On the
ABI level the Fixed-size bytes array is annotated as bytes.
Bytes literals may be given as bytes strings or as hex strings.
5.1.8 Strings
Keyword: String
Fixed-size strings can hold strings with equal or fewer characters than the maximum length of the string. On the ABI
level the Fixed-size bytes array is annotated as string.
5.1.9 Flags
Keyword: flag
Flags are custom defined types. A flag 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 2n where n is the index of the member in the range 0
<= n <= 255.
# Returning a member
return Roles.ADMIN
Operators
Comparisons
Operator Description
x == y Equals
x != y Does not equal
x in y x is in y
x not in y x is not in y
Bitwise Operators
Operator Description
x & y Bitwise and
x | y Bitwise or
x ^ y Bitwise xor
~x Bitwise not
Flag members can be combined using the above bitwise operators. While flag members have values that are power of
two, flag member combinations may not.
The in and not in operators can be used in conjunction with flag member combinations to check for membership.
flag Roles:
MANAGER
ADMIN
USER
50 Chapter 5. Types
Vyper Documentation
# Check not in
@external
def bar(a: Roles) -> bool:
return a not in (Roles.MANAGER | Roles.USER)
Note that in is not the same as strict equality (==). in checks that any of the flags on two flag objects are simultaneously
set, while == checks that two flag objects are bit-for-bit equal.
The following code uses bitwise operations to add and revoke permissions from a given Roles object.
@external
def add_user(a: Roles) -> Roles:
ret: Roles = a
ret |= Roles.USER # set the USER bit to 1
return ret
@external
def revoke_user(a: Roles) -> Roles:
ret: Roles = a
ret &= ~Roles.USER # set the USER bit to 0
return ret
@external
def flip_user(a: Roles) -> Roles:
ret: Roles = a
ret ^= Roles.USER # flip the user bit between 0 and 1
return ret
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.
Note: In terms of the calling convention, reference types are passed by value, not by reference. That means that, a
calling function does not need to worry about a callee modifying the data of a passed structure.
Fixed-size lists hold a finite number of elements which belong to a specified type.
Lists can be declared with _name: _ValueType[_Integer], except Bytes[N], String[N] and flags.
# Defining a list
exampleList: int128[3]
# Setting values
exampleList = [10, 11, 12]
exampleList[2] = 42
# Returning a value
return exampleList[0]
Multidimensional lists are also possible. The notation for the declaration is reversed compared to some other languages,
but the access notation is not reversed.
A two dimensional list can be declared with _name: _ValueType[inner_size][outer_size]. Elements can be
accessed with _name[outer_index][inner_index].
# Defining a list with 2 rows and 5 columns and set all values to 0
exampleList2D: int128[5][2] = empty(int128[5][2])
# Setting a value for row the first row (0) and last column (4)
exampleList2D[0][4] = 42
# Setting values
exampleList2D = [[10, 11, 12, 13, 14], [16, 17, 18, 19, 20]]
Note: Defining an array in storage whose size is significantly larger than 2**64 can result in security vulnerabilities
due to risk of overflow.
Dynamic arrays represent bounded arrays whose length can be modified at runtime, up to a bound specified in the type.
They can be declared with _name: DynArray[_Type, _Integer], where _Type can be of value type or reference
type (except mappings).
# 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
(continues on next page)
52 Chapter 5. Types
Vyper Documentation
# Returning a value
return exampleList[0]
Note: Attempting to access data past the runtime length of an array, pop() an empty array or append() to a full array
will result in a runtime REVERT. Attempting to pass an array in calldata which is larger than the array bound will result
in a runtime REVERT.
Note: To keep code easy to reason about, modifying an array while using it as an iterator is disallowed by the language.
For instance, the following usage is not allowed:
In the ABI, they are represented as _Type[]. For instance, DynArray[int128, 3] gets represented as int128[],
and DynArray[DynArray[int128, 3], 3] gets represented as int128[][].
Note: Defining a dynamic array in storage whose size is significantly larger than 2**64 can result in security vulner-
abilities due to risk of overflow.
5.2.3 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
# Accessing a value
exampleStruct.value1 = 1
5.2.4 Mappings
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 keccak256 hash 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”.
Mapping types are declared as HashMap[_KeyType, _ValueType].
• _KeyType can be any base or bytes type. Mappings, arrays or structs are not supported as key types.
• _ValueType can actually be any type, including mappings.
# Defining a mapping
exampleMapping: HashMap[int128, decimal]
# Accessing a value
exampleMapping[0] = 10.1
Unlike most programming languages, Vyper does not have a concept of null. Instead, every variable type has a default
value. To check if a variable is empty, you must compare it to the default value for its given type.
To reset a variable to its default value, assign to it the built-in empty() function which constructs a zero value for that
type.
Note: Memory variables must be assigned a value at the time they are declared.
Here you can find a list of all types and default values:
Note: In Bytes, the array starts with the bytes all set to '\x00'.
54 Chapter 5. Types
Vyper Documentation
Note: In reference types, all the type’s members are set to their initial values.
All type conversions in Vyper must be made explicitly using the built-in convert(a: atype, btype) function.
Type conversions in Vyper are designed to be safe and intuitive. All type conversions will check that the input is in
bounds for the output type. The general principles are:
• Except for conversions involving decimals and bools, the input is bit-for-bit preserved.
• Conversions to bool map all nonzero inputs to 1.
• When converting from decimals to integers, the input is truncated towards zero.
• address types are treated as uint160, except conversions with signed integers and decimals are not allowed.
• Converting between right-padded (bytes, Bytes, String) and left-padded types, results in a rotation to convert
the padding. For instance, converting from bytes20 to address would result in rotating the input by 12 bytes
to the right.
• Converting between signed and unsigned integers reverts if the input is negative.
• Narrowing conversions (e.g., int256 -> int128) check that the input is in bounds for the output type.
• Converting between bytes and int types results in sign-extension if the output type is signed. For instance,
converting 0xff (bytes1) to int8 returns -1.
• Converting between bytes and int types which have different sizes follows the rule of going through the clos-
est integer type, first. For instance, bytes1 -> int16 is like bytes1 -> int8 -> int16 (signextend, then
widen). uint8 -> bytes20 is like uint8 -> uint160 -> bytes20 (rotate left 12 bytes).
• Flags can be converted to and from uint256 only.
A small Python reference implementation is maintained as part of Vyper’s test suite, it can be found here. The motiva-
tion and more detailed discussion of the rules can be found here.
56 Chapter 5. Types
CHAPTER
SIX
Environment variables always exist in the namespace and are primarily used to provide information about the blockchain
or current transaction.
Note: block.prevrandao is an alias for the block.difficulty opcode. Since block.difficulty is considered
deprecated according to EIP-4399 after “The Merge” (Paris hard fork), we recommend using block.prevrandao.
Note: msg.data requires the usage of slice to explicitly extract a section of calldata. If the extracted section exceeds
the bounds of calldata, this will throw. You can check the size of msg.data using len.
57
Vyper Documentation
self is an environment variable used to reference a contract from within itself. Along with the normal address mem-
bers, 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
@internal
def _times_two(amount: uint256) -> uint256:
return amount * 2
@external
def calculate(amount: uint256) -> uint256:
return self._times_two(amount)
Custom constants can be defined at a global level in Vyper. To define a constant, make use of the constant keyword.
@deploy
def __init__():
self.total_supply = TOTAL_SUPPLY
SEVEN
STATEMENTS
Vyper’s statements are syntactically similar to Python, with some notable exceptions.
7.1.1 break
7.1.2 continue
The continue statement begins the next cycle of the nearest enclosing for loop.
In the above example, the for loop begins the next cycle immediately whenever i != a.
7.1.3 pass
pass is a null operation — when it is executed, nothing happens. It is useful as a placeholder when a statement is
required syntactically, but no code needs to be executed:
@external
def foo():
pass
59
Vyper Documentation
7.1.4 return
return leaves the current function call with the expression list (or None) as a return value.
return RETURN_VALUE
If a function has no return type, it is allowed to omit the return statement, otherwise, the function must end with a
return statement, or another terminating action such as raise.
It is not allowed to have additional, unreachable statements after a return statement.
7.2.1 log
log MyEvent(...)
Warning: The evaluation order of arguments passed to log is undefined. The compiler may evaluate them in
any order. Therefore, arguments with side effects should be evaluated in separate statements before the log call to
ensure predictable behavior.
Vyper uses state-reverting exceptions to handle errors. Exceptions trigger the REVERT opcode (0xFD) with the provided
reason given as the error message. When an exception is raised the code stops operation, the contract’s state is reverted
to the state before the transaction took place and the remaining gas is returned to the transaction’s sender. When an
exception happen in a sub-call, it “bubbles up” (i.e., exceptions are rethrown) automatically.
If the reason string is set to UNREACHABLE, an INVALID opcode (0xFE) is used instead of REVERT. In this case, calls
that revert do not receive a gas refund. This is not a recommended practice for general usage, but is available for
interoperability with various tools that use the INVALID opcode to perform dynamic analysis.
7.3.1 raise
The raise statement triggers an exception and reverts the current call.
60 Chapter 7. Statements
Vyper Documentation
7.3.2 assert
The assert statement makes an assertion about a given condition. If the condition evaluates falsely, the transaction is
reverted.
if not cond:
raise "reason"
62 Chapter 7. Statements
CHAPTER
EIGHT
CONTROL STRUCTURES
8.1 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.
8.1.1 Visibility
You can optionally declare a function’s visibility by using a decorator. There are three visibility levels in Vyper:
• @external: exposed in the selector table, can be called by an external call into this contract
• @internal (default): can be invoked only from within this contract. Not available to external callers
• @deploy: constructor code. This is code which is invoked once in the lifetime of a contract, upon its deploy. It
is not available at runtime to either external callers or internal call invocations. At this time, only the __init__()
function may be marked as @deploy.
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
@external
def add_seven_with_overloading(a: uint256, b: uint256 = 3):
return a + b
A Vyper contract cannot call directly between two external functions. If you must do this, you can use an interface.
External functions can use the @raw_return decorator to return raw bytes without ABI-encoding:
63
Vyper Documentation
@external
@payable
@raw_return
def proxy_call(target: address) -> Bytes[128]:
# Forward a call and return the raw response without ABI-encoding
return raw_call(
target,
msg.data,
is_delegate_call=True,
max_outsize=128,
value=msg.value
)
Note: For external functions with default arguments like def my_function(x: uint256, b: uint256 = 1)
the Vyper compiler will generate N+1 overloaded function selectors based on N default arguments. Consequently, the
ABI signature for a function (this includes interface functions) excludes optional arguments when their default values
are used in the function call.
@external
def foo(x: IERC4626):
extcall x.withdraw(0, self, self) # keccak256("withdraw(uint256,address,address)
˓→")[:4] = 0xb460af94
Internal Functions
Internal functions (optionally marked with the @internal decorator) are only accessible from other functions within
the same contract. They are invoked via the self object:
@external
def calculate(amount: uint256) -> uint256:
return self._times_two(amount)
Or for internal functions which are defined in imported modules, they are invoked by prefixing the name of the module
to the function name:
Marking an internal function as payable specifies that the function can interact with msg.value. A nonpayable
internal function can be called from an external payable function, but it cannot access msg.value.
@payable
def _foo() -> uint256:
return msg.value % 2
Note: As of v0.4.0, the @internal decorator is optional. That is, functions with no visibility decorator default to
being internal.
Note: Please note that for internal functions which use more than one default parameter, Vyper versions >=0.3.8
are recommended due to the security advisory GHSA-ph9x-4vc9-m39g.
The __init__() function, also known as the constructor, is a special initialization function that is only called at the
time of deploying a contract. It can be used to set initial values for storage or immutable variables. It must be declared
with the @deploy decorator. A common use case is to set an owner variable with the creator of the contract:
owner: address
@deploy
def __init__():
self.owner = msg.sender
8.1.3 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 (default): 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 and access Ether via msg.value.
@view
@external
def readonly():
# this function cannot write to state
...
@payable
@external
def send_me_money():
# this function can receive ether
...
Note: The @nonpayable decorator is not strictly enforced on internal functions when they are invoked through
an external payable function. As a result, an external payable function can invoke an internal nonpayable
8.1. Functions 65
Vyper Documentation
function. However, the nonpayable internal function cannot have access to msg.value.
The @nonreentrant decorator places a global nonreentrancy lock on a function. An attempt by an external contract
to call back into any other @nonreentrant function causes the transaction to revert.
@external
@nonreentrant
def make_a_call(_addr: address):
# this function is protected from re-entrancy
...
Nonreentrancy locks work by setting a specially allocated storage slot to a <locked> value on function entrance, and
setting it to an <unlocked> value on function exit. On function entrance, if the storage slot is detected to be the
<locked> value, execution reverts.
You cannot put the @nonreentrant decorator on a pure function. You can put it on a view function, but it only
checks that the function is not in a callback (the storage slot is not in the <locked> state), as view functions can only
read the state, not change it.
You can put the @nonreentrant decorator on a __default__ function, but keep in mind that this will result in the
contract rejecting ETH payments from callbacks.
You can view where the nonreentrant key is physically laid out in storage by using vyper with the -f layout option
(e.g., vyper -f layout foo.vy). Unless it is overridden, the compiler will allocate it at slot 0.
Note: A mutable function can protect a view function from being called back into (which is useful for instance, if
a view function would return inconsistent state during a mutable function), but a view function cannot protect itself
from being called back into. Note that mutable functions can never be called from a view function because all external
calls out from a view function are protected by the use of the STATICCALL opcode.
Note: A nonreentrant lock has an <unlocked> value of 3, and a <locked> value of 2. Nonzero values are used to
take advantage of net gas metering - as of the Berlin hard fork, the net cost for utilizing a nonreentrant lock is 2300 gas.
Prior to v0.3.4, the <unlocked> and <locked> values were 0 and 1, respectively.
Note: Prior to 0.4.0, nonreentrancy keys took a “key” argument for fine-grained nonreentrancy control. As of 0.4.0,
only a global nonreentrancy lock is available.
Beginning in 0.4.2, the #pragma nonreentrancy on pragma is available, and it enables nonreentrancy on all external
functions and public getters (except for constants and immutables) in the file. This is to prepare for a future release,
probably in the 0.5.x series, where nonreentrant locks will be enabled by default language-wide.
When the pragma is on, to re-enable reentrancy for a specific function, add the @reentrant decorator. For getters,
add the reentrant() modifier. Here is an example:
# pragma nonreentrancy on
@external
def make_a_call(addr: address):
# this function is protected from re-entrancy
...
@external
@reentrant
def callback(addr: address):
# this function is allowed to be reentered into
...
@external
def __default__():
# this function is nonreentrant!
...
The default is #pragma nonreentrancy off, which can be used to signal specifically that nonreentrancy protection
is off in this file.
Note that the same caveats about nonreentrancy on __default__() as mentioned in the previous section apply here,
since the __default__() function will be nonreentrant by default with the pragma on.
With the pragma on, internal functions remain unlocked by default but can still use the @nonreentrant decorator.
External view functions are protected by default (as before, checking the lock upon entry but only reading its state).
External pure functions do not interact with the lock.
Internal functions, __init__ function and getters for constants and immutables can be marked reentrant. Reen-
trant behavior is the default for these structures anyway, and this feature can be used to explicitly highlight the fact.
Note: All the protected functions share the same, global lock.
Note: Vyper disallows calling a nonreentrant function from another nonreentrant function, since the compiler
implements nonreentrancy as a global lock which is acquired at function entry.
Note: The nonreentrancy on/off pragma is scoped to the current file. If you import a file without the
nonreentrancy on pragma, the functions in that file will behave as the author intended, that is, they will be reentrant
unless marked otherwise.
8.1. Functions 67
Vyper Documentation
Note: The constant and immutable state variable getters don’t check the lock because the value of the variables
can’t change.
A contract can also have a default function, which is executed on a call to the contract if no other functions match
the given function identifier (or if none was supplied at all, such as through someone sending it Eth). It is the same
construct as fallback functions in Solidity.
This function is always named __default__. It must be annotated with @external. It cannot expect any input
arguments.
If the function is annotated as @payable, this function is executed whenever the contract is sent Ether (without data).
This is why the default function cannot accept arguments - it is a design decision of Ethereum to make no differentiation
between sending ether to a contract or a user address.
event Payment:
amount: uint256
sender: indexed(address)
@external
@payable
def __default__():
log Payment(msg.value, msg.sender)
Considerations
Just as in Solidity, Vyper generates a default function if one isn’t found, in the form of a REVERT call. Note that this
rolls back state changes, and thus will not succeed in receiving funds.
Ethereum specifies that the operations will be rolled back if the contract runs out of gas in execution. send calls to the
contract come with a free stipend of 2300 gas, which does not leave much room to perform other operations except basic
logging. However, if the sender includes a higher gas amount through a call instead of send, then more complex
functionality can be run.
It is considered a best practice to ensure your payable default function is compatible with this stipend. The following
operations will consume more than 2300 gas:
• Writing to storage
• Creating a contract
• Calling an external function which consumes a large amount of gas
• Sending Ether
Lastly, although the default function receives no arguments, it can still access the msg object, including:
• the address of who is interacting with the contract (msg.sender)
• the amount of ETH sent (msg.value)
• the gas provided (msg.gas).
Decorator Description
@external Function can only be called externally, it is part of the runtime selector table
@internal Function can only be called within current contract
@deploy Function is called only at deploy time
@pure Function does not read contract state or environment variables
@view Function does not alter contract state
@payable Function is able to receive Ether
@nonreentrant Function cannot be called back into during an external call
@raw_return Function returns raw bytes without ABI-encoding (@external functions only)
The @raw_return decorator allows a function to return raw bytes without ABI-encoding. This is particularly useful
for proxy contracts and other helper contracts where you want to forward the exact output bytes from another contract
call without adding an additional layer of ABI-encoding.
@external
@payable
@raw_return
def forward_call(target: address) -> Bytes[1024]:
# Returns the raw bytes from the external call without ABI-encoding
return raw_call(target, msg.data, max_outsize=1024, value=msg.value, is_delegate_
˓→call=True)
Note: The @raw_return decorator cannot be used in interface definitions (.vyi files). Note that to call a
@raw_return function from another contract, you should use raw_call instead of an interface call, since the return
data may not be ABI-encoded.
Warning: When using @raw_return, ensure all return paths in your function use raw bytes. Having multiple
return statements where some use ABI-encoded data and others don’t can lead to decoding errors.
8.1. Functions 69
Vyper Documentation
8.2 if statements
if CONDITION:
...
CONDITION is a boolean or boolean operation. The boolean is evaluated left-to-right, one expression at a time, until
the condition is found to be true or false. If true, the logic in the body of the if statement is executed.
Note that unlike Python, Vyper does not allow implicit conversion from non-boolean types within the condition of an
if statement. if 1: pass will fail to compile with a type mismatch.
You can also include elif and else statements, to add more conditional statements and a body that executes when
the conditionals are false:
if CONDITION:
...
elif OTHER_CONDITION:
...
else:
...
The for statement is a control flow construct used to iterate over a value:
The iterated value can be a static array, a dynamic array, or generated from the built-in range function.
You can use for to iterate through the values of any array variable:
In the above, example, the loop executes three times with i assigned the values of 4, 23, and then 42.
You can also iterate over a literal array, as long as the annotated type is valid for each item in the array:
Some restrictions:
• You cannot iterate over a multi-dimensional array. i must always be a base type.
• You cannot modify a value in an array while it is being iterated, or call to a function that might modify the array
being iterated.
Ranges are created using the range function. The following examples are valid uses of range:
STOP is a literal integer greater than zero. i begins as zero and increments by one until it is equal to STOP. i must be
of the same type as STOP.
Here, stop can be a variable with integer type, greater than zero. N must be a compile-time constant. i begins as zero
and increments by one until it is equal to stop. If stop is larger than N, execution will revert at runtime. In certain
cases, you may not have a guarantee that stop is less than N, but still want to avoid the possibility of runtime reversion.
To accomplish this, use the bound= keyword in combination with min(stop, N) as the argument to range, like
range(min(stop, N), bound=N). This is helpful for use cases like chunking up operations on larger arrays across
multiple transactions. i, stop and N must be of the same type.
Another use of range can be with START and STOP bounds.
Here, START and STOP are literal integers, with STOP being a greater value than START. i begins as START and incre-
ments by one until it is equal to STOP. i, START and STOP must be of the same type.
Finally, it is possible to use range with runtime start and stop values as long as a constant bound value is provided. In
this case, Vyper checks at runtime that end - start <= bound. N must be a compile-time constant. i, stop and N must
be of the same type.
NINE
The first time a variable is referenced you must declare its type:
data: int128
In the above example, we declare the variable data with a type of int128.
Depending on the active scope, an initial value may or may not be assigned:
• For storage variables (declared in the module scope), an initial value cannot be set
• For memory variables (declared within a function), an initial value must be set
• For calldata variables (function input arguments), a default value may be given
data: public(int128)
The compiler automatically creates getter functions for all public storage variables. For the example above, the compiler
will generate a function called data that does not take any arguments and returns an int128, the value of the state
variable data.
For public arrays, you can only retrieve a single element via the generated getter. This mechanism exists to avoid high
gas costs when returning an entire array. The getter will accept an argument to specify which element to return, for
example data(0).
DATA: immutable(uint256)
@deploy
def __init__(_data: uint256):
DATA = _data
73
Vyper Documentation
Variables declared as immutable are similar to constants, except they are assigned a value in the constructor of the
contract. Immutable values must be assigned a value at construction and cannot be assigned a value after construction.
The contract creation code generated by the compiler will modify the contract’s runtime code before it is returned by
appending all values assigned to immutables to the runtime code returned by the constructor. This is important if you
are comparing the runtime code generated by the compiler with the one actually stored in the blockchain.
You cannot directly declare tuple types. However, in certain cases you can use literal tuples during assignment. For
example, when a function returns multiple values:
@internal
def foo() -> (int128, int128):
return 2, 3
@external
def bar():
a: int128 = 0
b: int128 = 0
Storage variables are located within a smart contract at specific storage slots. By default, the compiler allocates the
first variable to be stored at slot 0; subsequent variables are stored in order after that.
There are cases where it is necessary to override this pattern and to allocate storage variables in custom slots. This
behaviour is often required for upgradeable contracts, to ensure that both contracts (the old contract, and the new
contract) store the same variable within the same slot.
This can be performed when compiling via vyper by including the --storage-layout-file flag.
For example, consider upgrading the following contract:
# old_contract.vy
owner: public(address)
balanceOf: public(HashMap[address, uint256])
# new_contract.vy
owner: public(address)
minter: public(address)
balanceOf: public(HashMap[address, uint256])
This would cause an issue when upgrading, as the balanceOf mapping would be located at slot1 in the old contract,
and slot2 in the new contract.
This issue can be avoided by allocating balanceOf to slot1 using the storage layout overrides. The contract can
be compiled with vyper new_contract.vy --storage-layout-file new_contract_storage.json where
new_contract_storage.json contains the following:
{
"owner": {"type": "address", "n_slots": 1, "slot": 0},
"minter": {"type": "address", "n_slots": 1, "slot": 2},
"balanceOf": {"type": "HashMap[address, uint256]", "n_slots": 1, "slot": 1}
}
When creating a custom storage layout, you must also include n_slots for each storage variable. This tells the compiler
how many 32 byte slots to allocate from the slot storage offset.
For further information on generating the storage layout, see Storage Layout.
Vyper follows C99 scoping rules. Variables are visible from the point right after their declaration until the end of the
smallest block that contains the declaration.
Variables and other items declared outside of a code block (functions, constants, event and struct definitions, . . . ), are
visible even before they were declared. This means you can use module-scoped items before they are declared.
Values that are declared in the module scope of a contract, such as storage variables and functions, are accessed via the
self object:
a: int128
@internal
def foo() -> int128
return 42
@external
def foo() -> int128:
b: int128 = self.foo()
return self.a + b
Name Shadowing
It is not permitted for a memory or calldata variable to shadow the name of an immutable or constant value. The
following examples will not compile:
a: constant(bool) = True
@external
def foo() -> bool:
# memory variable cannot have the same name as a constant or immutable variable
a: bool = False
return a
a: immutable(bool)
@deploy
def __init__():
a = True
@external
def foo(a:bool) -> bool:
# input argument cannot have the same name as a constant or immutable variable
return a
Variables that are declared within a function, or given as function input arguments, are visible within the body of that
function. For example, the following contract is valid because each declaration of a only exists within one function’s
body.
@external
def foo(a: int128):
pass
@external
def bar(a: uint256):
pass
@external
def baz():
a: bool = True
@external
def foo(a: int128):
# `a` has already been declared as an input argument
a: int128 = 21
@external
def foo(a: int128):
a = 4
(continues on next page)
@external
def bar():
# `a` has not been declared within this function
a += 12
Logical blocks created by for and if statements have their own scope. For example, the following contract is valid
because x only exists within the block scopes for each branch of the if statement:
@external
def foo(a: bool) -> int128:
if a:
x: int128 = 3
else:
x: bool = False
In a for statement, the target variable exists within the scope of the loop. For example, the following contract is valid
because i is no longer available upon exiting the loop:
@external
def foo(a: bool) -> int128:
for i: int128 in [1, 2, 3]:
pass
i: bool = False
The following contract fails to compile because a has not been declared outside of the loop.
@external
def foo(a: bool) -> int128:
for i: int128 in [1, 2, 3]:
a: int128 = i
a += 3
TEN
BUILT-IN FUNCTIONS
Vyper provides a collection of built-in functions available in the global namespace of all contracts.
@external
@view
def foo(x: uint256, y: int128) -> uint256:
return shift(x, y)
>>> ExampleContract.foo(2, 8)
512
Note: This function has been deprecated from version 0.3.8 onwards. Please use the << and >> operators instead.
Note: The functions bitwise_and, bitwise_or, bitwise_xor and bitwise_not have been deprecated from
version 0.3.4., and removed in version 0.4.2. Please use their operator versions instead: &, |, ^, ~.
Vyper has four built-ins for contract creation; the first three contract creation built-ins rely on the code to deploy
already being stored on-chain, but differ in call vs deploy overhead, and whether or not they invoke the constructor of
the contract to be deployed. The following list provides a short summary of the differences between them.
• create_minimal_proxy_to(target: address, ...)
– Creates an immutable proxy to target
– Expensive to call (incurs a single DELEGATECALL overhead on every invocation), cheap to create (since
it only deploys EIP-1167 forwarder bytecode)
– Does not have the ability to call a constructor
79
Vyper Documentation
– Does not check that there is code at target (allows one to deploy proxies counterfactually)
• create_copy_of(target: address, ...)
– Creates a byte-for-byte copy of runtime code stored at target
– Cheap to call (no DELEGATECALL overhead), expensive to create (200 gas per deployed byte)
– Does not have the ability to call a constructor
– Performs an EXTCODESIZE check to check there is code at target
• create_from_blueprint(target: address, ...)
– Deploys a contract using the initcode stored at target
– Cheap to call (no DELEGATECALL overhead), expensive to create (200 gas per deployed byte)
– Invokes constructor, requires a special “blueprint” contract to be deployed
– Performs an EXTCODESIZE check to check there is code at target
• raw_create(initcode: Bytes[...], ...)`
– Low-level create. Takes the given initcode, along with the arguments to be abi-encoded, and deploys
the initcode after concatenating the abi-encoded arguments.
create_minimal_proxy_to(target: address, value: uint256 = 0, revert_on_failure: bool = True[, salt: bytes32 ])
→ address
Deploys a small, EIP1167-compliant “minimal proxy contract” that duplicates the logic of the contract at target,
but has its own state since every call to target is made using DELEGATECALL to target. To the end user, this
should be indistinguishable from an independently deployed contract with the same code as target.
• target: Address of the contract to proxy to
• value: The wei value to send to the new contract address (Optional, default 0)
• revert_on_failure: If False, instead of reverting when the create operation fails, return the zero ad-
dress (Optional, default True)
• salt: A bytes32 value utilized by the deterministic CREATE2 opcode (Optional, if not supplied, CREATE
is used)
Returns the address of the newly created proxy contract. If the create operation fails (for instance, in the case of
a CREATE2 collision), execution will revert.
@external
def foo(target: address) -> address:
return create_minimal_proxy_to(target)
Note: It is very important that the deployed contract at target is code you know and trust, and does not implement
the selfdestruct opcode or have upgradeable code as this will affect the operation of the proxy contract.
Note: There is no runtime check that there is code already deployed at target (since a proxy may be deployed
counterfactually). Most applications may want to insert this check.
create_copy_of(target: address, value: uint256 = 0, revert_on_failure: bool = True[, salt: bytes32 ]) → address
Create a physical copy of the runtime code at target. The code at target is byte-for-byte copied into a newly
deployed contract.
• target: Address of the contract to copy
• value: The wei value to send to the new contract address (Optional, default 0)
• revert_on_failure: If False, instead of reverting when the create operation fails, return the zero ad-
dress (Optional, default True)
• salt: A bytes32 value utilized by the deterministic CREATE2 opcode (Optional, if not supplied, CREATE
is used)
Returns the address of the created contract. If the create operation fails (for instance, in the case of a CREATE2
collision), execution will revert. If there is no code at target, execution will revert.
@external
def foo(target: address) -> address:
return create_copy_of(target)
Note: The implementation of create_copy_of assumes that the code at target is smaller than 16MB. While
this is much larger than the EIP-170 constraint of 24KB, it is a conservative size limit intended to future-proof de-
ployer contracts in case the EIP-170 constraint is lifted. If the code at target is larger than 16MB, the behavior of
create_copy_of is undefined.
create_from_blueprint(target: address, *args, value: uint256 = 0, raw_args: bool = False, code_offset: int = 3,
revert_on_failure: bool = True[, salt: bytes32 ]) → address
Copy the code of target into memory and execute it as initcode. In other words, this operation interprets the
code at target not as regular runtime code, but directly as initcode. The *args are interpreted as constructor
arguments, and are ABI-encoded and included when executing the initcode.
• target: Address of the blueprint to invoke
• *args: Constructor arguments to forward to the initcode.
• value: The wei value to send to the new contract address (Optional, default 0)
• raw_args: If True, *args must be a single Bytes[...] argument, which will be interpreted as a raw
bytes buffer to forward to the create operation (which is useful for instance, if pre- ABI-encoded data is
passed in from elsewhere). (Optional, default False)
• code_offset: The offset to start the EXTCODECOPY from (Optional, default 3)
• revert_on_failure: If False, instead of reverting when the create operation fails, return the zero ad-
dress (Optional, default True)
• salt: A bytes32 value utilized by the deterministic CREATE2 opcode (Optional, if not supplied, CREATE
is used)
Returns the address of the created contract. If the create operation fails (for instance, in the case of a CREATE2
collision), execution will revert. If code_offset >= target.codesize (ex. if there is no code at target),
execution will revert.
@external
def foo(blueprint: address) -> address:
arg1: uint256 = 18
(continues on next page)
Note: To properly deploy a blueprint contract, special deploy bytecode must be used. The output of vyper -f
blueprint_bytecode will produce bytecode which deploys an ERC-5202 compatible blueprint.
Warning: It is recommended to deploy blueprints with an ERC-5202 preamble like 0xFE7100 to guard them from
being called as regular contracts. This is particularly important for factories where the constructor has side effects
(including SELFDESTRUCT!), as those could get executed by anybody calling the blueprint contract directly. The
code_offset= kwarg is provided (and defaults to the ERC-5202 default of 3) to enable this pattern:
@external
def foo(blueprint: address) -> address:
# `blueprint` is a blueprint contract with some known preamble b"abcd..."
return create_from_blueprint(blueprint, code_offset=<preamble length>)
raw_create(initcode: Bytes[...], *args, value: uint256 = 0, revert_on_failure: bool = True[, salt: bytes32]) →
address
Create a contract using the given initcode. Provides low-level access to the CREATE and CREATE2 opcodes.
• initcode: Initcode bytes
• value: The wei value to send to the new contract address (Optional, default 0)
• *args: Constructor arguments to forward to the initcode.
• revert_on_failure: If False, instead of reverting when the create operation fails, return the zero ad-
dress (Optional, default True)
• salt: A bytes32 value utilized by the deterministic CREATE2 opcode (Optional, if not supplied, CREATE
is used)
Returns the address of the created contract. If the create operation fails (for instance, in the case of a CREATE2
collision), execution will revert.
@external
def foo() -> address:
# create the bytes of an empty vyper contract
return raw_create(x
˓→"0x61000361000f6000396100036000f35f5ffd855820cd372fb85148700fa88095e3492d3f9f5beb43e555e5ff26d95f
˓→")
raw_call(to: address, data: Bytes, max_outsize: uint256 = 0, gas: uint256 = gasLeft, value: uint256 = 0,
is_delegate_call: bool = False, is_static_call: bool = False, revert_on_failure: bool = True) →
Bytes[max_outsize]
Call to the specified Ethereum address.
• to: Destination address to call to
• data: Data to send to the destination address
• max_outsize: Maximum length of the bytes array returned from the call. If the returned call data exceeds
this length, only this number of bytes is returned. (Optional, default 0)
• gas: The amount of gas to attach to the call. (Optional, defaults to msg.gas).
• value: The wei value to send to the address (Optional, default 0)
• is_delegate_call: If True, the call will be sent as DELEGATECALL (Optional, default False)
• is_static_call: If True, the call will be sent as STATICCALL (Optional, default False)
• revert_on_failure: If True, the call will revert on a failure, otherwise success will be returned (Op-
tional, default True)
Note: Returns the data returned by the call as a Bytes list, with max_outsize as the max length. The actual
size of the returned data may be less than max_outsize. You can use len to obtain the actual size.
Returns nothing if max_outsize is omitted or set to 0.
Returns success in a tuple with return value if revert_on_failure is set to False.
@external
@payable
def foo(_target: address) -> Bytes[32]:
response: Bytes[32] = raw_call(_target, method_id("someMethodName()"), max_
˓→outsize=32, value=msg.value)
return response
@external
@payable
def bar(_target: address) -> Bytes[32]:
success: bool = False
response: Bytes[32] = b""
x: uint256 = 123
success, response = raw_call(
_target,
abi_encode(x, method_id=method_id("someMethodName(uint256)")),
max_outsize=32,
value=msg.value,
revert_on_failure=False
)
assert success
return response
Note: Regarding “forwarding all gas”, note that, while Vyper will provide msg.gas to the call, in practice, there
are some subtleties around forwarding all remaining gas on the EVM which are out of scope of this documentation
and could be subject to change. For instance, see the language in EIP-150 around “all but one 64th”.
@external
def foo(_topic: bytes32, _data: Bytes[100]):
raw_log([_topic], _data)
@external
def foo(_data: Bytes[100]):
raw_revert(_data)
Warning: This method deletes the contract from the blockchain. All non-ether assets associated with this
contract are “burned” and the contract is no longer accessible.
Note: This function has been deprecated from version 0.3.8 onwards. The underlying opcode will eventually
undergo breaking changes, and its use is not recommended.
@external
def do_the_needful():
selfdestruct(msg.sender)
@external
def foo(_receiver: address, _amount: uint256, gas: uint256):
send(_receiver, _amount, gas=gas)
10.3 Cryptography
@external
@view
def foo(x: uint256[2], y: uint256[2]) -> uint256[2]:
return ecadd(x, y)
@external
@view
def foo(point: uint256[2], scalar: uint256) -> uint256[2]:
return ecmul(point, scalar)
Note: Prior to Vyper 0.3.10, the ecrecover function could return an undefined (possibly nonzero) value for
invalid inputs to ecrecover. For more information, please see GHSA-f5x6-7qgp-jhf3.
@external
@view
def foo(hash: bytes32, v: uint8, r:bytes32, s:bytes32) -> address:
return ecrecover(hash, v, r, s)
(continues on next page)
10.3. Cryptography 85
Vyper Documentation
@external
@view
def foo(hash: bytes32, v: uint256, r:uint256, s:uint256) -> address:
return ecrecover(hash, v, r, s)
>>> ExampleContract.foo(
˓→'0x6c9c5e133b8aafb2ea74f524a5263495e7ae5701c7248805f7b511d973dc7055',
28,
78616903610408968922803823221221116251138855211764625814919875002740131251724,
37668412420813231458864536126575229553064045345107737433087067088194345044408
)
'0x9eE53ad38Bb67d745223a4257D7d48cE973FeB7A'
keccak256(_value) → bytes32
Return a keccak256 hash of the given value.
• _value: Value to hash. Can be a String, Bytes, or bytes32.
@external
@view
def foo(_value: Bytes[100]) -> bytes32
return keccak256(_value)
>>> ExampleContract.foo(b"potato")
0x9e159dfcfe557cc1ca6c716e87af98fdcb94cd8c832386d0429b2b7bec02754f
sha256(_value) → bytes32
Return a sha256 (SHA2 256-bit output) hash of the given value.
• _value: Value to hash. Can be a String, Bytes, or bytes32.
@external
@view
def foo(_value: Bytes[100]) -> bytes32
return sha256(_value)
>>> ExampleContract.foo(b"potato")
0xe91c254ad58860a02c788dfb5c1a65d6a8846ab1dc649631c7db16fef4af2dec
@external
@view
(continues on next page)
>>> ExampleContract.foo("why","hello","there")
"why hello there!"
@external
@view
def foo(b: uint256) -> String[78]:
return uint2str(b)
>>> ExampleContract.foo(420)
"420"
@external
@view
def foo(b: Bytes[32]) -> address:
return extract32(b, 0, output_type=address)
>>> ExampleContract.foo(
˓→"0x0000000000000000000000009f8F72aA9304c8B593d555F12eF6589cC3A579A2")
"0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2"
slice(b: Bytes | bytes32 | String, start: uint256, length: uint256) → Bytes | String
Copy a list of bytes and return a specified slice.
• b: value being sliced
• start: start position of the slice
@external
@view
def foo(s: String[32]) -> String[5]:
return slice(s, 4, 5)
10.5 Math
@external
@view
def foo(value: int256) -> int256:
return abs(value)
>>> ExampleContract.foo(-31337)
31337
@external
@view
def foo(x: decimal) -> int256:
return ceil(x)
>>> ExampleContract.foo(3.1337)
4
epsilon(typename) → Any
Returns the smallest non-zero value for a decimal type.
• typename: Name of the decimal type (currently only decimal)
@external
@view
def foo() -> decimal:
return epsilon(decimal)
>>> ExampleContract.foo()
Decimal('1E-10')
@external
@view
def foo(x: decimal) -> int256:
return floor(x)
>>> ExampleContract.foo(3.1337)
3
@external
@view
def foo(a: uint256, b: uint256) -> uint256:
return max(a, b)
max_value(type_) → numeric
Returns the maximum value of the numeric type specified by type_ (e.g., int128, uint256, decimal).
@external
@view
def foo() -> int256:
return max_value(int256)
>>> ExampleContract.foo()
57896044618658097711785492504343953926634992332820282019728792003956564819967
@external
@view
def foo(a: uint256, b: uint256) -> uint256:
return min(a, b)
min_value(type_) → numeric
Returns the minimum value of the numeric type specified by type_ (e.g., int128, uint256, decimal).
10.5. Math 89
Vyper Documentation
@external
@view
def foo() -> int256:
return min_value(int256)
>>> ExampleContract.foo()
-57896044618658097711785492504343953926634992332820282019728792003956564819968
@external
@view
def foo(a: uint256, b: uint256) -> uint256:
return pow_mod256(a, b)
>>> ExampleContract.foo(2, 3)
8
>>> ExampleContract.foo(100, 100)
59041770658110225754900818312084884949620587934026984283048776718299468660736
@external
@view
def foo(d: decimal) -> decimal:
return sqrt(d)
>>> ExampleContract.foo(9.0)
3.0
@external
@view
def foo(x: uint256) -> uint256:
return isqrt(x)
>>> ExampleContract.foo(101)
10
@external
@view
def foo(a: uint256, b: uint256, c: uint256) -> uint256:
return uint256_addmod(a, b, c)
>>> (6 + 13) % 8
3
>>> ExampleContract.foo(6, 13, 8)
3
@external
@view
def foo(a: uint256, b: uint256, c: uint256) -> uint256:
return uint256_mulmod(a, b, c)
>>> (11 * 2) % 5
2
>>> ExampleContract.foo(11, 2, 5)
2
@external
@view
def foo(x: uint8, y: uint8) -> uint8:
return unsafe_add(x, y)
@external
@view
def bar(x: int8, y: int8) -> int8:
return unsafe_add(x, y)
>>> ExampleContract.foo(1, 1)
2
Note: Performance note: for the native word types of the EVM uint256 and int256, this will compile to a single
ADD instruction, since the EVM natively wraps addition on 256-bit words.
10.5. Math 91
Vyper Documentation
@external
@view
def foo(x: uint8, y: uint8) -> uint8:
return unsafe_sub(x, y)
@external
@view
def bar(x: int8, y: int8) -> int8:
return unsafe_sub(x, y)
>>> ExampleContract.foo(4, 3)
1
>>> ExampleContract.foo(0, 1)
255
>>> ExampleContract.bar(-128, 1)
127
Note: Performance note: for the native word types of the EVM uint256 and int256, this will compile to a single
SUB instruction, since the EVM natively wraps subtraction on 256-bit words.
@external
@view
def foo(x: uint8, y: uint8) -> uint8:
return unsafe_mul(x, y)
@external
@view
def bar(x: int8, y: int8) -> int8:
return unsafe_mul(x, y)
>>> ExampleContract.foo(1, 1)
1
Note: Performance note: for the native word types of the EVM uint256 and int256, this will compile to a single
MUL instruction, since the EVM natively wraps multiplication on 256-bit words.
@external
@view
def foo(x: uint8, y: uint8) -> uint8:
return unsafe_div(x, y)
@external
@view
def bar(x: int8, y: int8) -> int8:
return unsafe_div(x, y)
>>> ExampleContract.foo(1, 1)
1
>>> ExampleContract.foo(1, 0)
0
Note: Performance note: this will compile to a single SDIV or DIV instruction, depending on if the inputs are signed
or unsigned (respectively).
10.6 Utilities
@external
@view
def foo(s: String[32]) -> uint256:
return as_wei_value(1.337, "ether")
>>> ExampleContract.foo(1)
1337000000000000000
10.6. Utilities 93
Vyper Documentation
Note: When as_wei_value is given some decimal, the result might be rounded down to the nearest integer, for
example, the following is true: as_wei_value(12.2, "wei") == 12.
Note: The EVM only provides access to the most recent 256 blocks. This function reverts if the block number
is greater than or equal to the current block number or more than 256 blocks behind the current block.
@external
@view
def foo() -> bytes32:
return blockhash(block.number - 16)
>>> ExampleContract.foo()
0xf3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
Note: A versioned hash consists of a single byte representing the version (currently 0x01), followed by the
last 31 bytes of the SHA256 hash of the KZG commitment (EIP-4844). For the case index >= len(tx.
blob_versioned_hashes), blobhash(index: uint256) returns empty(bytes32).
@external
@view
def foo(index: uint256) -> bytes32:
return blobhash(index)
>>> ExampleContract.foo(0)
0xfd28610fb309939bfec12b6db7c4525446f596a5a5a66b8e2cb510b45b2bbeb5
>>> ExampleContract.foo(6)
0x0000000000000000000000000000000000000000000000000000000000000000
empty(typename) → Any
Return a value which is the default (zero-ed) value of its type. Useful for initializing new memory variables.
• typename: Name of the type, except HashMap[_KeyType, _ValueType]
@external
@view
def foo():
x: uint256[2][5] = empty(uint256[2][5])
@external
@view
def foo(s: String[32]) -> uint256:
return len(s)
>>> ExampleContract.foo("hello")
5
@external
@view
def foo() -> Bytes[4]:
return method_id('transfer(address,uint256)', output_type=Bytes[4])
>>> ExampleContract.foo()
0xa9059cbb
@external
@view
def foo() -> Bytes[132]:
x: uint256 = 1
y: Bytes[32] = b"234"
return abi_encode(x, y, method_id=method_id("foo()"))
>>> ExampleContract.foo().hex()
"c2985578"
"0000000000000000000000000000000000000000000000000000000000000001"
"0000000000000000000000000000000000000000000000000000000000000040"
"0000000000000000000000000000000000000000000000000000000000000003"
"3233340000000000000000000000000000000000000000000000000000000000"
10.6. Utilities 95
Vyper Documentation
@external
@view
def foo(someInput: Bytes[128]) -> (uint256, Bytes[32]):
x: uint256 = empty(uint256)
y: Bytes[32] = empty(Bytes[32])
x, y = abi_decode(someInput, (uint256, Bytes[32]))
return x, y
Note: Issuing of the static call is NOT mode-dependent (that is, it is not removed from production code), although the
compiler will issue a warning whenever print is used.
Warning: In Vyper, as of v0.4.0, the order of argument evaluation of builtins is not defined. That means that
the compiler may choose to reorder evaluation of arguments. For example, extract32(x(), y()) may yield
unexpected results if x() and y() both touch the same data. For this reason, it is best to avoid calling functions
with side-effects inside of builtins. For more information, see GHSA-g2xh-c426-v8mf and issue #4019.
ELEVEN
MODULES
A module is a set of function definitions and variable declarations which enables code reuse. Vyper favors code reuse
through composition, rather than inheritance. A module encapsulates everything needed for code reuse, from type and
function declarations to state. It is important to note that functions which make use of defined state must be initialized
in order to use that state, whereas functions that are “pure” do not require this.
The simplest way to define a module is to write a contract. In Vyper, any contract is a valid module! For example, the
following contract is also a valid module.
# ownable.vy
owner: address
@deploy
def __init__():
self.owner = msg.sender
def _check_owner():
assert self.owner == msg.sender
@pure
def _times_two(x: uint256) -> uint256:
return x * 2
@external
def update_owner(new_owner: address):
self._check_owner()
self.owner = new_owner
This contract basically has two bits of functionality which can be reused upon import, the _check_owner() function
and the update_owner() function. The _check_owner() is an internal function which can be used as a helper to
check ownership in importing modules, while the update_owner() is an external function which an importing module
can itself export as an externally facing piece of functionality.
You can use this module’s functionality simply by importing it, however any functionality that you do not use from a
module will not be included in the final compilation target. For example, if you don’t use the initializes statement
to declare a module’s location in the storage layout, you cannot use its state. Similarly, if you don’t explicitly export
an external function from a module, it will not appear in the runtime code.
97
Vyper Documentation
A module can be imported using import or from ... import statements. The following are all equivalent ways to
import the above module:
When importing using the as keyword, the module will be referred to by its alias in the rest of the contract.
The _times_two() helper function in the above module can be immediately used without any further work since it is
“pure” and doesn’t depend on initialized state.
The other functions cannot be used yet, because they touch the ownable module’s state. There are two ways to declare
a module so that its state can be used.
import ownable
an_ownable: ownable.__interface__
In order to use a module’s state, it must be “initialized”. A module can be initialized with the initializes keyword.
This declares the module’s location in the contract’s Storage Layout. It also creates a requirement to invoke the module’s
__init__() function, if it has one. This is a well-formedness requirement, since it does not make sense to access a
module’s state unless its __init__() function has been called.
import ownable
initializes: ownable
@deploy
def __init__():
ownable.__init__()
@external
def get_owner() -> address:
return ownable.owner
Another way of using a contract’s state without directly initializing it is to use the uses keyword. This is a more
advanced usage which is expected to be mostly utilized by library designers. The uses statement allows a module to
use another module’s state but defer its initialization to another module in the compilation tree (most likely a user of
the library in question).
This is best illustrated with an example:
# ownable_2step.vy
import ownable
uses: ownable
@deploy
def __init__():
self.pending_owner = empty(address)
@external
def begin_transfer(new_owner: address):
ownable._check_owner()
self.pending_owner = new_owner
@external
def accept_transfer():
assert msg.sender == self.pending_owner
ownable.owner = new_owner
self.pending_owner = empty(address)
Here, the ownable_2step module does not want to seal off access to calling the ownable module’s __init__()
function. So, it utilizes the uses: ownable statement to get access to the ownable module’s state, without the re-
quirement to initialize it. Note that this is a valid module, but it is not a valid contract (that is, it cannot produce bytecode)
because it does not initialize the ownable module. To make a valid contract, the user of the ownable_2step module
would be responsible for initializing the ownable module themselves (as in the next section: initializing dependencies).
Whether to use or initialize a module is a choice which is left up to the library designer.
This section contains some notes on the design from a language design perspective. It can be safely skipped if you are
just interested in how to use modules, and not necessarily in programming language theory.
The design of the module system takes inspiration from (but is not directly related to) the rust language’s borrow
checker. In the language of type systems, module initialization is modeled as an affine constraint which is promoted to
a linear constraint if the module’s state is touched in the compilation target. In practice, what this means is:
• A module must be “used” or “initialized” before its state can be accessed in an import
• A module may be “used” many times
• A module which is “used” or its state touched must be “initialized” exactly once
To read more about the design background of Vyper’s module system, please see its original design document.
Sometimes, you may encounter a module which itself uses other modules. Vyper’s module system is designed to
allow this, but it requires you make explicit the access to the imported module’s state. The above ownable_2step.vy
contract is an example of this. If you wanted to initialize the ownable_2step module, it would use the special := (aka
“walrus”) syntax, and look something like this:
import ownable
import ownable_2step
initializes: ownable
@deploy
def __init__():
ownable.__init__()
ownable_2step.__init__()
Warning: In normal usage, you should make sure that __init__() functions are called in dependency order. In
the above example, you can get unexpected behavior if ownable_2step.__init__() is called before ownable.
__init__()! The compiler may enforce this behavior in the future.
In Vyper, @external functions are not automatically exposed (i.e., included in the runtime code) in the importing
contract. This is a safety feature, it means that any externally facing functionality must be explicitly defined in the
top-level of the compilation target.
So, exporting external functions from modules is accomplished using the exports keyword. In Vyper, functions can
be exported individually, or, a wholesale export of all the functions in an interface can be done. The special interface
module.__interface__ is a compiler-defined interface, which automatically includes all the functions in a module.
The following are all ways of exporting functions from an imported module.
Note: Any exported interfaces must be implemented by the module. For example, in the above example, base_token
must contain implements: IERC20, or else the compiler will raise an error.
TWELVE
INTERFACES
An interface is a set of function definitions used to enable communication 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.
The interface keyword is used to define an inline external interface:
interface FooBar:
def calculate() -> uint256: view
def test1(): nonpayable
The defined interface can then be used to make external calls, given a contract address:
@external
def test(foobar: FooBar):
extcall foobar.test1()
@external
def test2(foobar: FooBar) -> uint256:
return staticcall foobar.calculate()
The interface name can also be used as a type annotation for storage variables. You then assign an address
value to the variable to access that interface. Note that casting an address to an interface is possible, e.g.
FooBar(<address_var>):
foobar_contract: FooBar
@deploy
def __init__(foobar_address: address):
self.foobar_contract = FooBar(foobar_address)
@external
def test():
extcall self.foobar_contract.test1()
Specifying payable or nonpayable annotation in the interface indicates that the call made to the external contract
will be able to alter storage, whereas view and pure calls will use a STATICCALL ensuring no storage can be altered
during execution. Additionally, payable allows non-zero value to be sent along with the call.
103
Vyper Documentation
Either the extcall or staticcall keyword is required to precede the external call to distinguish it from internal calls.
The keyword must match the visibility of the function, staticcall for pure and view functions, and extcall for
payable and nonpayable functions. Additionally, the output of a staticcall must be assigned to a result.
Warning: If the signature in an interface does not match the actual signature of the called contract, you can get
runtime errors or undefined behavior. For instance, if you accidentally mark a nonpayable function as view,
calling that function may result in the EVM reverting execution in the called contract.
interface FooBar:
def calculate() -> uint256: pure
def query() -> uint256: view
def update(): nonpayable
def pay(): payable
@external
def test(foobar: FooBar):
s: uint256 = staticcall foobar.calculate() # cannot change storage
s = staticcall foobar.query() # cannot change storage, but reads itself
extcall foobar.update() # storage can be altered
extcall foobar.pay(value=1) # storage can be altered, and value can be sent
Vyper offers the option to set the following additional keyword arguments when making external calls:
Keyword Description
gas Specify gas value for the call
value Specify amount of ether sent with the call
skip_contract_check Drop EXTCODESIZE check (but keep RETURNDATASIZE check)
default_return_value Specify a default return value if no value is returned
The default_return_value parameter can be used to handle ERC20 tokens affected by the missing return value bug
in a way similar to OpenZeppelin’s safeTransfer for Solidity:
Vyper includes common built-in interfaces such as IERC20 and IERC721. These are imported from ethereum.ercs:
implements: IERC20
You can see all the available built-in interfaces in the Vyper GitHub repo.
You can define an interface for your contract with the implements statement:
implements: FooBarInterface
This imports the defined interface from the vyper file at an_interface.vyi (or an_interface.json if using ABI
json interface type) and ensures your current contract implements all the necessary external functions. If any interface
functions are not included in the contract, it will fail to compile. This is especially useful when developing contracts
around well-defined standards such as ERC20.
Note: Interfaces that implement functions with return values that require an upper bound (e.g. Bytes, DynArray,
or String), the upper bound defined in the interface represents the lower bound of the implementation. Assuming
a function my_func returns a value String[1] in the interface, this would mean for the implementation function of
my_func that the return value must have at least length 1. This behavior might change in the future.
Note: Prior to v0.4.0, implements required that events defined in an interface were re-defined in the “implementing”
contract. As of v0.4.0, this is no longer required because events can be used just by importing them. Any events used
in a contract will automatically be exported in the ABI output.
Note: An interface function with default parameters (e.g. deposit(assets: uint256, receiver:
address = msg.sender)) implies that the contract being interfaced with supports these default argu-
ments via the ABI-encoded function signatures (e.g. keccak256("deposit(uint256,address)")[:4] and
keccak256("deposit(uint256)")[:4]). It is the responsibility of the callee to implement the behavior associ-
ated with these defaults.
Standalone interfaces are written using a variant of standard Vyper syntax. The body of each function must be an
ellipsis (...). Interface files must have a .vyi suffix in order to be found by an import statement.
Vyper has a built-in format option to allow you to easily export a Vyper interface from a pre-existing contract.
# Functions
@view
@external
def delegated(addr: address) -> bool:
...
(continues on next page)
# ...
If you want to export it as an inline interface, Vyper provides a utility to extract that as well.
# External Contracts
interface Ballot:
def delegated(addr: address) -> bool: view
def directlyVoted(addr: address) -> bool: view
def giveRightToVote(voter: address): nonpayable
def forwardWeight(delegate_with_weight_to_forward: address): nonpayable
# ...
The output can then easily be copy-pasted directly in a regular vyper file.
THIRTEEN
EVENT LOGGING
This example is taken from the sample ERC20 contract and shows the basic flow of event logging:
event Approval:
owner: indexed(address)
spender: indexed(address)
value: uint256
107
Vyper Documentation
In this example, the listening client declares the event to listen for. Any time the contract sends this log event, the
callback will be invoked.
event Transfer:
sender: indexed(address)
receiver: indexed(address)
value: uint256
The EVM currently has five opcodes for emitting event logs: LOG0, LOG1, LOG2, LOG3, and LOG4. These opcodes can
be used to create log records, where each log record consists of both topics and data. Topics are 32-byte ‘’words” that
are used to describe what is happening in an event. While topics are searchable, data is not. Event data is however not
limited, which means that you can include large or complicated data like arrays or strings. Different opcodes (LOG0
through LOG4) allow for different numbers of topics. For instance, LOG1 includes one topic, LOG2 includes two topics,
and so on. Event declarations look similar to struct declarations, containing one or more arguments that are passed to
the event. Typical events will contain two kinds of arguments:
• Indexed arguments (topics), which can be searched for by listeners. Each indexed argument is identified by the
indexed keyword. Here, each indexed argument is an address. You can have up to four indexed arguments
(LOG4), but indexed arguments are not passed directly to listeners, although some of this information (such as
the sender) may be available in the listener’s results object.
• Value arguments (data), which are passed through to listeners. You can have any number of value arguments
and they can have arbitrary names, but each is limited by the EVM to be no more than 32 bytes.
Note that the first topic of a log record consists of the signature of the name of the event that occurred, including the
types of its parameters. It is also possible to create an event with no arguments. In this case, use the pass statement:
Once an event is declared, you can log (send) events. You can send events as many times as you want to. Please note that
events sent do not take state storage and thus do not cost gas: this makes events a good way to save some information.
However, the drawback is that events are not available to contracts, only to clients.
Logging events is done using the log statement:
The order and types of arguments given must match the order of arguments used when declaring the event.
In the example listener above, the result arg actually passes a large amount of information. Here we’re most interested
in result.returnValues. This is an object with properties that match the properties declared in the event. Note that
this object does not contain the indexed properties, which can only be searched in the original myToken.Transfer
that created the callback.
FOURTEEN
NATSPEC METADATA
Vyper contracts can use a special form of docstring to provide rich documentation for functions, return variables and
more. This special form is named the Ethereum Natural Language Specification Format (NatSpec).
This documentation is segmented into developer-focused messages and end-user-facing messages. These messages
may be shown to the end user (the human) at the time that they will interact with the contract (i.e. sign a transaction).
14.1 Example
Vyper supports structured documentation for contracts and external functions using the doxygen notation format.
Note: The compiler does not parse docstrings of internal functions. You are welcome to NatSpec in comments for
internal functions, however they are not processed or included in the compiler output.
"""
@title A simulator for Bug Bunny, the most famous Rabbit
@license MIT
@author Warned Bros
@notice You can use this contract for only the most basic simulation
@dev
Simply chewing a carrot does not count, carrots must pass
the throat to be considered eaten
"""
@external
@payable
def doesEat(food: string[30], qty: uint256) -> bool:
"""
@notice Determine if Bugs will accept `qty` of `food` to eat
@dev Compares the entire string and does not rely on a hash
@param food The name of a food to evaluate (in English)
@param qty The number of food items to evaluate
@return True if Bugs will eat it, False otherwise
"""
111
Vyper Documentation
14.2 Tags
All tags are optional. The following table explains the purpose of each NatSpec tag and where it may be used:
When parsed by the compiler, documentation such as the one from the above example will produce two different JSON
outputs. One is meant to be consumed by the end user as a notice when a function is executed and the other to be used
by the developer.
If the above contract is saved as carrots.vy then you can generate the documentation using:
The above documentation will produce the following user documentation JSON as output:
{
"methods": {
"doesEat(string,uint256)": {
"notice": "Determine if Bugs will accept `qty` of `food` to eat"
}
},
"notice": "You can use this contract for only the most basic simulation"
}
Note that the key by which to find the methods is the function’s canonical signature as defined in the contract ABI, not
simply the function’s name.
Apart from the user documentation file, a developer documentation JSON file should also be produced and should look
like this:
{
"author": "Warned Bros",
"license": "MIT",
"details": "Simply chewing a carrot does not count, carrots must pass the throat to be␣
˓→considered eaten",
"methods": {
"doesEat(string,uint256)": {
"details" : "Compares the entire string and does not rely on a hash",
"params": {
"food": "The name of a food to evaluate (in English)",
"qty": "The number of food items to evaluate"
},
"returns": {
"_0": "True if Bugs will eat it, False otherwise"
}
}
},
"title" : "A simulator for Bug Bunny, the most famous Rabbit"
}
FIFTEEN
COMPILING A CONTRACT
Note: The --help flag gives verbose explanations of how to use each of these scripts.
15.1.1 vyper
vyper provides CLI access to the compiler. It can generate various outputs including simple binaries, ASTs, interfaces
and source mappings.
To compile a contract:
$ vyper yourFileName.vy
Include the -f flag to specify which output formats to return. Use vyper --help for a full list of output options.
$ vyper -f abi,abi_python,bb,bb_runtime,bytecode,bytecode_runtime,blueprint_bytecode,cfg,
˓→cfg_runtime,interface,external_interface,ast,annotated_ast,integrity,ir,ir_json,ir_
˓→runtime,asm,opcodes,opcodes_runtime,source_map,source_map_runtime,archive,solc_json,
˓→method_identifiers,userdoc,devdoc,metadata,combined_json,layout yourFileName.vy
Note: The opcodes and opcodes_runtime output of the compiler has been returning incorrect opcodes since 0.2.0
due to a lack of 0 padding (patched via PR 3735). If you rely on these functions for debugging, please use the latest
patched versions.
The -p flag allows you to set a root path that is used when searching for interface files to import. If none is given, it
will default to the current working directory. See Searching For Imports for more information.
115
Vyper Documentation
Storage Layout
This outputs a JSON object detailing the locations for all state variables as determined by the compiler.
To override the default storage layout for a contract:
The input to the --storage-layout-file flag must match the format of the .storage_layout field from the vyper
-f layout command.
15.1.2 vyper-json
vyper-json provides a JSON interface for the compiler. It expects a JSON formatted input and returns the compilation
result in a JSON formatted output.
To compile from JSON supplied via stdin:
$ vyper-json
$ vyper-json yourProject.json
By default, the output is sent to stdout. To redirect to a file, use the -o flag:
$ vyper-json -o compiled.json
Importing Interfaces
Try VyperLang! is a JupyterHub instance hosted by the Vyper team as a sandbox for developing and testing contracts
in Vyper. It requires github for login, and supports deployment via the browser.
Remix IDE is a compiler and JavaScript VM for developing and testing contracts in Vyper, as well as Solidity.
Note: While the Vyper version of the Remix IDE compiler is updated on a regular basis, it might be a bit behind the
latest version found in the master branch of the repository. Make sure the byte code matches the output from your local
compiler.
The Vyper CLI tool accepts an optimization mode "none", "codesize", or "gas" (default). It can be set using the
--optimize flag. For example, invoking vyper --optimize codesize MyContract.vy will compile the con-
tract, optimizing for code size. As a rough summary of the differences between gas and codesize mode, in gas optimized
mode, the compiler will try to generate bytecode which minimizes gas (up to a point), including:
• using a sparse selector table which optimizes for gas over codesize
• inlining some constants, and
• trying to unroll some loops, especially for data copies.
In codesize optimized mode, the compiler will try hard to minimize codesize by
• using a dense selector table
• out-lining code, and
• using more loops for data copies.
When compiling, you can use the CLI flag --experimental-codegen or its alias --venom to activate the new Venom
IR. Venom IR is inspired by LLVM IR and enables new advanced analysis and optimizations.
When you compile your contract code, you can specify the target Ethereum Virtual Machine version to compile for,
to access or avoid particular features. You can specify the version either with a source code pragma or as a compiler
option. It is recommended to use the compiler option when you want flexibility (for instance, ease of deploying across
different chains), and the source code pragma when you want bytecode reproducibility (for instance, when verifying
code on a block explorer).
Note: If the evm version specified by the compiler options conflicts with the source code pragma, an exception will
be raised and compilation will not continue.
For instance, the adding the following pragma to a contract indicates that it should be compiled for the “prague” fork
of the EVM.
Warning: Compiling for the wrong EVM version can result in wrong, strange, or failing behavior. Please ensure,
especially if running a private chain, that you use matching EVM versions.
When compiling via the vyper CLI, you can specify the EVM version option using the --evm-version flag:
When using the JSON interface, you can include the "evmVersion" key within the "settings" field:
{
"settings": {
"evmVersion": "[VERSION]"
}
}
The following is a list of supported EVM versions, and changes in the compiler introduced with each version. Backward
compatibility is not guaranteed between each version. In general, the compiler team maintains an informal policy that
the compiler will support 3 years of hard fork rulesets, but this policy may be revisited as appropriate.
london
paris
• The transient keyword allows declaration of variables which live in transient storage
• Functions marked with @nonreentrant are protected with TLOAD/TSTORE instead of SLOAD/SSTORE
• The MCOPY opcode will be generated automatically by the compiler for most memory operations.
prague(default)
Vyper allows suppression of warnings via the CLI flag -Wnone, or promotion of (all) warnings to errors via the -Werror
flag.
To help tooling detect whether two builds are the same, Vyper provides the -f integrity output, which outputs the
integrity hash of a contract. The integrity hash is recursively defined as the sha256 of the source code with the integrity
hashes of its dependencies (imports) and storage layout overrides (if provided).
A Vyper archive is a compileable bundle of input sources and settings. Technically, it is a ZIP file, with a special
structure to make it useable as input to the compiler. It can use any suffix, but the convention is to use a .zip suffix or
.vyz suffix. It must contain a MANIFEST/ folder, with the following directory structure.
MANIFEST
cli_settings.txt
compilation_targets
compiler_version
integrity
settings.json
searchpaths
storage_layout.json [OPTIONAL]
• cli_settings.txt is a text representation of the settings that were used on the compilation run that generated
this archive.
• compilation_targets is a newline separated list of compilation targets. Currently only one compilation is
supported
• compiler_version is a text representation of the compiler version used to generate this archive
• integrity is the integrity hash of the input contract
• searchpaths is a newline-separated list of the search paths used on this compilation run
• settings.json is a json representation of the settings used on this compilation run. It is 1:1 with
cli_settings.txt, but both are provided as they are convenient for different workflows (typically, manually
vs automated).
• storage_layout.json is a json representation of the storage layout overrides to be used on this compilation
run. It is optional.
A Vyper archive file can be produced by requesting the -f archive output format. The compiler can also produce
the archive in base64 encoded form using the --base64 flag. The Vyper compiler can accept both .vyz and base64-
encoded Vyper archives directly as input.
JSON input/output is provided for compatibility with solidity, however, the recommended way is to use the afore-
mentioned Vyper archives. So-called “standard json” input can be generated from a contract using the vyper -f
solc_json output format.
Where possible, the Vyper JSON compiler formats follow those of Solidity.
The following example describes the expected input format of vyper-json. (Comments are not normally permitted
in JSON and are used here for explanatory purposes).
{
// Required: Source code language. Must be set to "Vyper".
"language": "Vyper",
// Required
// Source codes given here will be compiled.
"sources": {
"contracts/foo.vy": {
// Optional: keccak256 hash of the source file
"keccak256": "0x234...",
// Required: literal contents of the source file
"content": "@external\ndef foo() -> bool:\n return True"
}
},
// Optional
// Sources given here are made available for import by the contracts
// that are compiled. If the suffix is ".vy", the compiler will expect
// Vyper syntax. If the suffix is "abi" the compiler will expect an
// ABI object.
"interfaces": {
"contracts/bar.vy": {
"content": ""
},
"contracts/baz.json": {
"abi": []
}
},
// Optional
// Storage layout overrides for the contracts that are compiled
"storage_layout_overrides": {
"contracts/foo.vy": {
"a": {"type": "uint256", "slot": 1, "n_slots": 1},
"b": {"type": "uint256", "slot": 0, "n_slots": 1},
}
},
// Optional
"settings": {
"evmVersion": "prague", // EVM version to compile for. Can be london, paris,␣
˓→shanghai, cancun or prague (default).
The following example describes the output format of vyper-json. Comments are of course not permitted and used
here only for explanatory purposes.
{
// The compiler version used to generate the JSON
"compiler": "vyper-0.1.0b12",
// Optional: not present if no errors/warnings were encountered
"errors": [
{
// Optional: Location within the source file.
"sourceLocation": {
"file": "source_file.vy",
"lineno": 5,
"col_offset": 11
},
// Mandatory: Exception type, such as "JSONError", "StructureException", etc.
"type": "TypeMismatch",
// Mandatory: Component where the error originated, such as "json", "compiler",
˓→"vyper", etc.
"component": "compiler",
// Mandatory ("error" or "warning")
"severity": "error",
// Mandatory
"message": "Unsupported type conversion: int128 to bool"
// Optional: the message formatted with source location
"formattedMessage": "line 5:11 Unsupported type conversion: int128 to bool"
}
],
// Optional: not present if there are no storage layout overrides
"storage_layout_overrides": {
"contracts/foo.vy": {
"a": {"type": "uint256", "slot": 1, "n_slots": 1},
"b": {"type": "uint256", "slot": 0, "n_slots": 1},
}
},
// This contains the file-level outputs. Can be limited/filtered by the␣
˓→outputSelection settings.
"sources": {
"source_file.vy": {
// Identifier of the source (used in source maps)
"id": 0,
// The AST object
"ast": {},
}
},
// This contains the contract-level outputs. Can be limited/filtered by the␣
˓→outputSelection settings.
"contracts": {
"source_file.vy": {
// The contract name will always be the file name without a suffix
"source_file": {
(continues on next page)
Errors
Each error includes a component field, indicating the stage at which it occurred:
• json: Errors that occur while parsing the input JSON. Usually, a result of invalid JSON or a required value that
is missing.
• parser: Errors that occur while parsing the contracts. Usually, a result of invalid Vyper syntax.
• compiler: Errors that occur while compiling the contracts.
• vyper: Unexpected errors that occur within Vyper. If you receive an error of this type, please open an issue.
You can also use the --traceback flag to receive a standard Python traceback when an error is encountered.
SIXTEEN
COMPILER EXCEPTIONS
Vyper raises one or more of the following exceptions when an issue is encountered while compiling a contract.
Whenever possible, exceptions include a source highlight displaying the location of the error within the code:
exception ArgumentException
Raises when calling a function with invalid arguments, for example an incorrect number of positional arguments
or an invalid keyword argument.
exception CallViolation
Raises on an illegal function call, such as attempting to call between two external functions.
exception ArrayIndexException
Raises when an array index is out of bounds.
exception EventDeclarationException
Raises when an event declaration is invalid.
exception EvmVersionException
Raises when a contract contains an action that cannot be performed with the active EVM ruleset.
exception FunctionDeclarationException
Raises when a function declaration is invalid, for example because of incorrect or mismatched return values.
exception ImmutableViolation
Raises when attempting to perform a change a variable, constant or definition that cannot be changed. For
example, trying to update a constant, or trying to assign to a function definition.
exception InterfaceViolation
Raises when an interface is not fully implemented.
exception InvalidAttribute
Raises on a reference to an attribute that does not exist.
exception InvalidLiteral
Raises when no valid type can be found for a literal value.
125
Vyper Documentation
@external
def foo():
bar: decimal = 3.123456789123456789
This example raises InvalidLiteral because the given literal value has too many decimal places and so cannot
be assigned any valid Vyper type.
exception InvalidOperation
Raises when using an invalid operator for a given type.
@external
def foo():
a: String[10] = "hello" * 2
This example raises InvalidOperation because multiplication is not possible on string types.
exception InvalidReference
Raises on an invalid reference to an existing definition.
baz: int128
@external
def foo():
bar: int128 = baz
This example raises InvalidReference because baz is a storage variable. The reference to it should be written
as self.baz.
exception InvalidType
Raises when using an invalid literal value for the given type.
@external
def foo():
bar: int128 = 3.5
This example raises InvalidType because 3.5 is a valid literal value, but cannot be cast as int128.
exception IteratorException
Raises when an iterator is constructed or used incorrectly.
exception JSONError
Raises when the compiler JSON input is malformed.
exception NamespaceCollision
Raises when attempting to assign a variable to a name that is already in use.
exception NatSpecSyntaxException
Raises when a contract contains an invalid NatSpec docstring.
exception NonPayableViolation
Raises when attempting to access msg.value from within a function that has not been marked as @payable.
@public
def _foo():
bar: uint256 = msg.value
exception OverflowException
Raises when a numeric value is out of bounds for the given type.
exception StateAccessViolation
Raises when attempting to perform a modifying action within view-only or stateless context. For example,
writing to storage in a @view function, reading from storage in a @pure function.
exception StructureException
Raises on syntax that is parsable, but invalid in some way.
exception SyntaxException
Raises on invalid syntax that cannot be parsed.
exception TypeMismatch
Raises when attempting to perform an action between two or more objects with known, dislike types.
@external
def foo(:
bar: int128 = 3
foo: decimal = 4.2
foo has a type of int128 and bar has a type of decimal, so attempting to add them together raises a
TypeMismatch.
exception UndeclaredDefinition
Raises when attempting to access an object that has not been declared.
exception VariableDeclarationException
Raises on an invalid variable declaration.
127
Vyper Documentation
exception VersionException
Raises when a contract version string is malformed or incompatible with the current compiler version.
exception ZeroDivisionException
Raises when a divide by zero or modulo zero situation arises.
16.1 CompilerPanic
exception CompilerPanic
$ vyper v.vy
Error compiling: v.vy
vyper.exceptions.CompilerPanic: Number of times repeated
must be a constant nonzero positive integer: 0 Please create an issue.
A compiler panic error indicates that there is a problem internally to the compiler and an issue should be reported
right away on the Vyper Github page. Open an issue if you are experiencing this error. Please Open an Issue
SEVENTEEN
DEPLOYING A CONTRACT
Once you are ready to deploy your contract to a public test net or the main net, you have several options:
• Take the bytecode generated by the vyper compiler and manually deploy it through mist or geth:
vyper yourFileName.vy
# returns bytecode
• Take the byte code and ABI and deploy it with your current browser on myetherwallet’s contract menu:
• Use Titanoboa:
import boa
boa.set_network_env(<RPC URL>)
from eth_account import Account
# in a real codebase, always load private keys safely from an encrypted store!
boa.env.add_account(Account(<a private key>))
deployer = boa.load_partial("yourFileName.vy")
deployer.deploy()
• Use the development environment provided at https://try.vyperlang.org to compile and deploy your contract on
your net of choice. try.vyperlang.org comes “batteries-included”, with Titanoboa pre-installed, and browser
signer integration as well.
129
Vyper Documentation
EIGHTEEN
TESTING A CONTRACT
For testing Vyper contracts we recommend the use of pytest along with one of the following packages:
• Titanoboa: A Vyper interpreter, pretty tracebacks, forking, debugging and deployment features. Maintained by
the Vyper team.
• Brownie: A development and testing framework for smart contracts targeting the Ethereum Virtual Machine
Example usage for each package is provided in the sections listed below.
Titanoboa is a Vyper interpreter which is fast and provides a “swiss-army knife” toolkit for developing vyper applica-
tions. The best place to start is at the official docs, and skip down to the testing reference for an overview of testing
strategies. Finally, a more detailed API reference is available in the API reference subsection.
Brownie is a Python-based development and testing framework for smart contracts. It includes a pytest plugin with
fixtures that simplify testing your contract.
This section provides a quick overview of testing with Brownie. To learn more, you can view the Brownie documenta-
tion on writing unit tests or join the Ethereum Python Dev Discord #brownie channel.
In order to use Brownie for testing you must first initialize a new project. Create a new directory for the project, and
from within that directory type:
$ brownie init
This will create an empty project structure within the directory. Store your contract sources within the project’s
contracts/ directory and your tests within tests/.
131
Vyper Documentation
Assume the following simple contract Storage.vy. It has a single integer variable and a function to set that value.
Listing 1: storage.vy
1 #pragma version >0.3.10
2
3 storedData: public(int128)
4
5 @deploy
6 def __init__(_x: int128):
7 self.storedData = _x
8
9 @external
10 def set(_x: int128):
11 self.storedData = _x
We create a test file tests/test_storage.py where we write our tests in pytest style.
Listing 2: test_storage.py
1 import pytest
2
3 INITIAL_VALUE = 4
4
6 @pytest.fixture
7 def storage_contract(Storage, accounts):
8 # deploy the contract with the initial value as a constructor argument
9 yield Storage.deploy(INITIAL_VALUE, {'from': accounts[0]})
10
11
12 def test_initial_state(storage_contract):
13 # Check if the constructor of the contract is set up properly
14 assert storage_contract.storedData() == INITIAL_VALUE
15
16
In this example we are using two fixtures which are provided by Brownie:
• accounts provides access to the Accounts container, containing all of your local accounts
• Storage is a dynamically named fixture that provides access to a ContractContainer object, used to deploy
your contract
Note: To run the tests, use the brownie test command from the root directory of your project.
For the remaining examples, we expand our simple storage contract to include an event and two conditions for a failed
transaction: advanced_storage.vy
Listing 3: advanced_storage.vy
1 #pragma version >0.3.10
2
3 event DataChange:
4 setter: indexed(address)
5 value: int128
6
7 storedData: public(int128)
8
9 @deploy
10 def __init__(_x: int128):
11 self.storedData = _x
12
13 @external
14 def set(_x: int128):
15 assert _x >= 0, "No negative values"
16 assert self.storedData < 100, "Storage is locked when 100 or more is stored"
17 self.storedData = _x
18 log DataChange(setter=msg.sender, value=_x)
19
20 @external
21 def reset():
22 self.storedData = 0
To test events, we examine the TransactionReceipt object which is returned after each successful transaction. It
contains an events member with information about events that fired.
1 import brownie
2
3 INITIAL_VALUE = 4
4
6 @pytest.fixture
7 def adv_storage_contract(AdvancedStorage, accounts):
8 yield AdvancedStorage.deploy(INITIAL_VALUE, {'from': accounts[0]})
9
19 assert len(tx2.events) == 1
20 assert tx2.events[0]['setter'] == accounts[1]
21
Transactions that revert raise a VirtualMachineError exception. To write assertions around this you can use
brownie.reverts as a context manager. It functions very similarly to pytest.raises.
brownie.reverts optionally accepts a string as an argument. If given, the error string returned by the transaction
must match it in order for the test to pass.
1 import brownie
2
3 INITIAL_VALUE = 4
4
6 @pytest.fixture
7 def adv_storage_contract(AdvancedStorage, accounts):
8 yield AdvancedStorage.deploy(INITIAL_VALUE, {'from': accounts[0]})
9
10
16 # Lock the contract by storing more than 100. Then try to change the value
17
NINETEEN
Vyper has an active community. You can find third-party tutorials, examples, courses, and other learning material.
19.1 General
• Titanoboa – A Vyper interpreter with pretty tracebacks, forking, debugging features and more
• ApeWorX – The Ethereum development framework for Python Developers, Data Scientists, and Security Pro-
fessionals
• VyperDeployer – A helper smart contract to compile and test Vyper contracts in Foundry
• snekmate – Vyper smart contract building blocks
• Serpentor – A set of smart contracts tools for governance
• Smart contract development frameworks and tools for Vyper on Ethereum.org
• Vyper Online Compiler - an online platform for compiling and deploying Vyper smart contracts
135
Vyper Documentation
19.3 Security
19.5 Unmaintained
These resources have not been updated for a while, but may still offer interesting content.
• Awesome Vyper curated resources
• Brownie – Python framework for developing smart contracts (deprecated)
• Foundry x Vyper – Foundry template to compile Vyper contracts
TWENTY
RELEASE NOTES
v0.4.3 introduces the @raw_return decorator which allows contracts to return bytes directly without ABI-encoding,
which enables new proxy contract use cases. The default EVM version has been updated to prague, and several
improvements have been made to the Venom optimizer pipeline.
Breaking changes
Tooling / CLI
Venom improvements
137
Vyper Documentation
Docs
v0.4.2 includes a new raw_create() builtin which allows users to build more generic factories in Vyper. It also moves
the sqrt() builtin to a pure Vyper module, involving a refactor which will allow more stdlib functionality to be written
in Vyper in the future.
Additionally, Venom has undergone more improvements, including a CSE elimination pass, dead-store elimination
pass, as well as moving more items in the calling convention to the stack in the venom pipeline. Benchmark contracts
are now typically 5% smaller.
Two low severity GHSAs have been patched in this release.
Tooling / CLI
Bugfixes
Venom improvements
Docs
Misc / Refactor
v0.4.1 is primarily a polishing release, focusing on bug fixes, UX improvements, and security-related fixes (with four
low-to-moderate severity GHSA reports published). However, a substantial amount of effort has also been invested in
improving the Venom pipeline, resulting in better performance and code generation from the Venom pipeline. Venom
can be enabled by passing the --venom or --experimental-codegen flag to the Vyper compiler (they are aliases of
each other). Venom code can now also be compiled directly, using the venom binary (included in this release).
Breaking changes
Tooling / CLI
Bugfixes
Venom improvements
Docs
Misc / Refactor
v0.4.0 represents a major overhaul to the Vyper language. Notably, it overhauls the import system and adds support
for code reuse. It also adds a new, experimental backend to Vyper which lays the foundation for improved analysis,
optimization and integration with third party tools.
Breaking Changes
Module system
Venom
Docs
Bugfixes
Tooling
Performance
• refactor[test]: bypass eth-tester and interface with evm backend directly (#3846)
• feat: Refactor assert_tx_failed into a context (#3706)
• feat[test]: implement abi_decode spec test (#4095)
• feat[test]: add more coverage to abi_decode fuzzer tests (#4153)
• feat[ci]: enable cancun testing (#3861)
• fix: add missing test for memory allocation overflow (#3650)
• chore: fix test for slice (#3633)
• add abi_types unit tests (#3662)
• refactor: test directory structure (#3664)
• chore: test all output formats (#3683)
• chore: deduplicate test files (#3773)
• feat[test]: add more transient storage tests (#3883)
• chore[ci]: fix apt-get failure in era pipeline (#3821)
• chore[ci]: enable python3.12 tests (#3860)
• chore[ci]: refactor jobs to use gh actions (#3863)
• chore[ci]: use --dist worksteal from latest xdist (#3869)
• chore: run mypy as part of lint rule in Makefile (#3771)
• chore[test]: always specify the evm backend (#4006)
• chore: update lint dependencies (#3704)
• chore: add color to mypy output (#3793)
• chore: remove tox rules for lint commands (#3826)
• chore[ci]: roll back GH actions/artifacts version (#3838)
• chore: Upgrade GitHub action dependencies (#3807)
• chore[ci]: pin eth-abi for decode regression (#3834)
• fix[ci]: release artifacts (#3839)
• chore[ci]: merge mypy job into lint (#3840)
• test: parametrize CI over EVM versions (#3842)
Misc / refactor
v0.3.10 is a performance focused release that additionally ships numerous bugfixes. It adds a codesize optimization
mode (#3493), adds new vyper-specific #pragma directives (#3493), uses Cancun’s MCOPY opcode for some compiler
generated code (#3483), and generates selector tables which now feature O(1) performance (#3496).
Breaking changes:
Notable fixes:
20.7 v0.3.8
These are really just the highlights, as many other bugfixes, docs updates and refactoring (over 150 pull requests!) made
it into this release! For the full list, please see the changelog. Special thanks to contributions from @tserg, @trocher,
@z80dev, @emc415 and @benber86 in this release!
New Contributors:
• @omahs made their first contribution in (#3128)
• @ObiajuluM made their first contribution in (#3124)
• @trocher made their first contribution in (#3134)
• @ozmium22 made their first contribution in (#3149)
• @ToonVanHove made their first contribution in (#3168)
• @emc415 made their first contribution in (#3158)
• @lgtm-com made their first contribution in (#3147)
• @tdurieux made their first contribution in (#3224)
• @victor-ego made their first contribution in (#3263)
• @miohtama made their first contribution in (#3257)
• @kelvinfan001 made their first contribution in (#2687)
20.8 v0.3.7
20.9 v0.3.6
20.10 v0.3.5
20.11 v0.3.4
20.12 v0.3.3
20.13 v0.3.2
20.14 v0.3.1
20.15 v0.3.0
A critical security vulnerability has been discovered in this version and we strongly recommend using version 0.3.1 or
higher. For more information, please see the Security Advisory GHSA-5824-cm3x-3c38.
Date released: 2021-10-04
Breaking changes:
• Change ABI encoding of single-struct return values to be compatible with Solidity (#2457)
• Drop Python 3.6 support (#2462)
Non-breaking changes and improvements:
• Rewrite internal calling convention (#2447)
• Allow any ABI-encodable type as function arguments and return types (#2154, #2190)
• Add support for deterministic deployment of minimal proxies using CREATE2 (#2460)
• Optimize code for certain copies (#2468)
• Add -o CLI flag to redirect output to a file (#2452)
• Other docs updates (#2450)
Fixes:
• _abi_encode builtin evaluates arguments multiple times (#2459)
• ABI length is too short for nested tuples (#2458)
• Returndata is not clamped for certain numeric types (#2454)
• __default__ functions do not respect nonreentrancy keys (#2455)
• Clamps for bytestrings in initcode are broken (#2456)
• Missing clamps for decimal args in external functions (GHSA-c7pr-343r-5c46)
• Memory corruption when returning a literal struct with a private function call inside of it (GHSA-xv8x-pr4h-
73jv)
Special thanks to contributions from @skellet0r and @benjyz for this release!
20.16 v0.2.16
A critical security vulnerability has been discovered in this version and we strongly recommend using version 0.3.1 or
higher. For more information, please see the Security Advisory GHSA-5824-cm3x-3c38.
Date released: 2021-08-27
Non-breaking changes and improvements:
• Expose _abi_encode as a user-facing builtin (#2401)
• Export the storage layout as a compiler output option (#2433)
• Add experimental OVM backend (#2416)
• Allow any ABI-encodable type as event arguments (#2403)
• Optimize int128 clamping (#2411)
• Other docs updates (#2405, #2422, #2425)
Fixes:
• Disallow nonreentrant decorator on constructors (#2426)
• Fix bounds checks when handling msg.data (#2419)
• Allow interfaces in lists, structs and maps (#2397)
• Fix trailing newline parse bug (#2412)
Special thanks to contributions from @skellet0r, @sambacha and @milancermak for this release!
20.17 v0.2.15
A critical security vulnerability has been discovered in this version and we strongly recommend using version 0.3.1 or
higher. For more information, please see the Security Advisory GHSA-5824-cm3x-3c38.
Date released: 23-07-2021
Non-breaking changes and improvements - Optimization when returning nested tuples (#2392)
Fixes: - Annotated kwargs for builtins (#2389) - Storage slot allocation bug (#2391)
20.18 v0.2.14
20.19 v0.2.13
20.20 v0.2.12
20.21 v0.2.11
20.22 v0.2.10
20.23 v0.2.9
20.24 v0.2.8
Fixes:
• Allow use of slice on a calldata bytes32 (#2227)
• Explicitly disallow iteration of a list of structs (#2228)
• Improved validation of address checksums (#2229)
• Bytes are always represented as hex within the AST (#2231)
• Allow empty as an argument within a function call (#2234)
• Allow empty static-sized array as an argument within a log statement (#2235)
• Compile-time issue with Bytes variables as a key in a mapping (#2239)
20.25 v0.2.7
20.26 v0.2.6
20.27 v0.2.5
20.28 v0.2.4
20.29 v0.2.3
20.30 v0.2.2
20.31 v0.2.1
20.32 v0.1.0-beta.17
20.33 v0.1.0-beta.16
20.34 v0.1.0-beta.15
• Modify modulus calculations for literals to be consistent with the EVM (#1792)
• Explicitly disallow the use of exponentiation on decimal values (#1792)
• Add compile-time checks for divide by zero and modulo by zero (#1792)
• Fixed some issues with negating constants (#1791)
• Allow relative imports beyond one parent level (#1784)
• Implement SHL/SHR for bitshifting, using Constantinople rules (#1796)
• vyper-json compatibility with solc settings (#1795)
• Simplify the type check when returning lists (#1797)
• Add branch coverage reporting (#1743)
• Fix struct assignment order (#1728)
• Added more words to reserved keyword list (#1741)
• Allow scientific notation for literals (#1721)
• Avoid overflow on sqrt of Decimal upper bound (#1679)
• Refactor ABI encoder (#1723)
• Changed opcode costs per EIP-1884 (#1764)
Special thanks to (@iamdefinitelyahuman) for lots of updates this release!
20.35 v0.1.0-beta.14
20.36 v0.1.0-beta.13
20.37 v0.1.0-beta.12
20.38 v0.1.0-beta.11
20.39 v0.1.0-beta.10
20.40 v0.1.0-beta.9
Prior to this release, we managed our change log in a different fashion. Here is the old changelog:
• 2019.04.05: Add stricter checking of unbalanced return statements. (#590)
• 2019.03.04: create_with_code_of has been renamed to create_forwarder_to. (#1177)
• 2019.02.14: Assigning a persistent contract address can only be done using the bar_contact =
ERC20(<address>) syntax.
• 2019.02.12: ERC20 interface has to be imported using from vyper.interfaces import ERC20 to use.
• 2019.01.30: Byte array literals need to be annotated using b"", strings are represented as “”.
• 2018.12.12: Disallow use of None, disallow use of del, implemented clear() built-in function.
• 2018.11.19: Change mapping syntax to use map(). (VIP564)
• 2018.10.02: Change the convert style to use types instead of string. (VIP1026)
• 2018.09.24: Add support for custom constants.
• 2018.08.09: Add support for default parameters.
• 2018.06.08: Tagged first beta.
• 2018.05.23: Changed wei_value to be uint256.
• 2018.04.03: Changed bytes declaration from bytes <= n to bytes[n].
• 2018.03.27: Renaming signed256 to int256.
• 2018.03.22: Add modifiable and static keywords for external contract calls.
• 2018.03.20: Renaming __log__ to event.
• 2018.02.22: Renaming num to int128, and num256 to uint256.
• 2018.02.13: Ban functions with payable and constant decorators.
• 2018.02.12: Division by num returns decimal type.
• 2018.02.09: Standardize type conversions.
• 2018.02.01: Functions cannot have the same name as globals.
• 2018.01.27: Change getter from get_var to var.
• 2018.01.11: Change version from 0.0.2 to 0.0.3
TWENTYONE
CONTRIBUTING
To suggest an improvement, please create a Vyper Improvement Proposal (VIP for short) using the VIP Template.
To report an issue, please use the GitHub issues tracker. When reporting issues, please mention the following details:
• Which version of Vyper you are using
• What was the source code (if applicable)
• Which platform are you running on
• Your operating system name and version
• Detailed steps to reproduce the issue
• What was the result of the issue
• What the expected behaviour is
Reducing the source code that caused the issue to a bare minimum is always very helpful and sometimes even clarifies
a misunderstanding.
177
Vyper Documentation
Find or report bugs at our issues page. Anything tagged with “bug” is open to whoever wants to implement it.
Our style guide outlines best practices for the Vyper repository. Please ask us on the Vyper (Smart Contract Program-
ming Language) Discord #compiler-dev channel if you have questions about anything that is not outlined in the style
guide.
In order to contribute, please fork off of the master branch and make your changes there. Your commit messages
should detail why you made your change in addition to what you did (unless it is a tiny change).
If you need to pull in any changes from master after making your fork (for example, to resolve potential merge con-
flicts), please avoid using git merge and instead, git rebase your branch.
If you are writing a new feature, please ensure you write appropriate Pytest test cases and place them under tests/.
If you are making a larger change, please consult first with the Vyper (Smart Contract Programming Language) Discord
#compiler-dev channel.
Although we do CI testing, please make sure that the tests pass for supported Python version and ensure that it builds
locally before submitting a pull request.
Thank you for your help!
TWENTYTWO
STYLE GUIDE
This document outlines the code style, project structure and practices followed by the Vyper development team.
Note: Portions of the current codebase do not adhere to this style guide. We are in the process of a large-scale refactor
and this guide is intended to outline the structure and best practices during and beyond this refactor. Refactored code
and added functionality must adhere to this guide. Bugfixes and modifications to existing functionality may adopt the
same style as the related code.
• Each subdirectory within Vyper should be a self-contained package representing a single pass of the compiler
or other logical component.
• Functionality intended to be called from modules outside of a package must be exposed within the base
__init__.py. All other functionality is for internal use only.
• It should be possible to remove any package and replace it with another that exposes the same API, without
breaking functionality in other packages.
All code must conform to the PEP 8 style guide with the following exceptions:
• Maximum line length of 100
We handle code formatting with black with the line-length option set to 80. This ensures a consistent style across the
project and saves time by not having to be opinionated.
179
Vyper Documentation
Leading Underscores
Booleans
Methods
The following conventions should be used when naming functions or methods. Consistent naming provides logical
consistency throughout the codebase and makes it easier for future readers to understand what a method does (and does
not) do.
• get_: For simple data retrieval without any side effects.
• fetch_: For retreivals that may have some sort of side effect.
• build_: For creation of a new object that is derived from some other data.
• set_: For adding a new value or modifying an existing one within an object.
• add_: For adding a new attribute or other value to an object. Raises an exception if the value already exists.
• replace_: For mutating an object. Should return None on success or raise an exception if something is wrong.
• compare_: For comparing values. Returns True or False, does not raise an exception.
• validate_: Returns None or raises an exception if something is wrong.
• from_: For class methods that instantiate an object based on the given input data.
For other functionality, choose names that clearly communicate intent without being overly verbose. Focus on what
the method does, not on how the method does it.
22.2.2 Imports
Standard libraries should be imported absolutely and without aliasing. Importing the library aids readability, as other
users may be familiar with that library.
# Good
import os
os.stat('.')
# Bad
from os import stat
stat('.')
Internal Imports
Internal imports are those between two modules inside the same Vyper package.
• Internal imports may use either import or from .. syntax. The imported value should be a module,
not an object. Importing modules instead of objects avoids circular dependency issues.
• Internal imports may be aliased where it aids readability.
• Internal imports must use absolute paths. Relative imports cause issues when the module is moved.
# Good
import vyper.ast.nodes as nodes
from vyper.ast import nodes
Cross-Package Imports
Cross-package imports are imports between one Vyper package and another.
• Cross-package imports must not request anything beyond the root namespace of the target package.
• Cross-package imports may be aliased where it aids readability.
• Cross-package imports may use from [module] import [package] syntax.
# Good
from vyper.ast import fold
from vyper import ast as vy_ast
22.2.3 Exceptions
We use custom exception classes to indicate what has gone wrong during compilation.
• All raised exceptions must use an exception class that appropriately describes what has gone wrong. When none
fits, or when using a single exception class for an overly broad range of errors, consider creating a new class.
• Builtin Python exceptions must not be raised intentionally. An unhandled builtin exception indicates a bug in
the codebase.
• Use CompilerPanic for errors that are not caused by the user.
22.2.4 Strings
Strings substitutions should be performed via formatted string literals rather than the str.format method or other
techniques.
• All publicly exposed classes and methods should include PEP 484 annotations for all arguments and return
values.
• Type annotations should be included directly in the source. Stub files may be used where there is a valid reason.
Source files using stubs must still be annotated to aid readability.
• Internal methods should include type annotations.
22.3 Tests
• pytest functionality should not be imported with from ... style syntax, particularly pytest.raises. Im-
porting the library itself aids readability.
• Tests must not be interdependent. We use xdist to execute tests in parallel. You cannot rely on which order
tests will execute in, or that two tests will execute in the same process.
• Test cases should be designed with a minimalistic approach. Each test should verify a single behavior. A good
test is one with few assertions, and where it is immediately obvious exactly what is being tested.
• Where logical, tests should be parametrized or use property-based testing.
• Tests must not involve mocking.
Where possible, the test suite should copy the structure of main Vyper package. For example, test cases for vyper/
context/types/ should exist at tests/context/types/.
22.3.3 Filenames
22.3.4 Fixtures
• Fixtures should be stored in conftest.py rather than the test file itself.
• conftest.py files must not exist more than one subdirectory beyond the initial tests/ directory.
• The functionality of a fixture must be fully documented, either via docstrings or comments.
22.4 Documentation
It is important to maintain comprehensive and up-to-date documentation for the Vyper language.
• Documentation must accurately reflect the current state of the master branch on Github.
• New functionality must not be added without corresponding documentation updates.
We use imperative, present tense to describe APIs: “return” not “returns”. One way to test if we have it right is to
complete the following sentence:
“If we call this API it will: . . . ”
For narrative style documentation, we prefer the use of first-person “we” form over second-person “you” form.
Additionally, we recommend the following best practices when writing documentation:
• Use terms consistently.
• Avoid ambiguous pronouns.
• Eliminate unneeded words.
• Establish key points at the start of a document.
• Focus each paragraph on a single topic.
• Focus each sentence on a single idea.
• Use a numbered list when order is important and a bulleted list when order is irrelevant.
• Introduce lists and tables appropriately.
Google’s technical writing courses are a valuable resource. We recommend reviewing them before any significant
documentation work.
22.4.3 Headers
• Each documentation section must begin with a label of the same name as the filename for that section. For
example, this section’s filename is style-guide.rst, so the RST opens with a label _style-guide.
• Section headers should use the following sequence, from top to bottom: #, =, -, *, ^.
Internal documentation is vital to aid other contributors in understanding the layout of the Vyper codebase.
We handle internal documentation in the following ways:
• A README.md must be included in each first-level subdirectory of the Vyper package. The readme explain the
purpose, organization and control flow of the subdirectory.
• All publicly exposed classes and methods must include detailed docstrings.
• Internal methods should include docstrings, or at minimum comments.
• Any code that may be considered “clever” or “magic” must include comments explaining exactly what is hap-
pening.
Docstrings should be formatted according to the NumPy docstring style.
Contributors should adhere to the following standards and best practices when making commits to be merged into the
Vyper codebase.
Maintainers may request a rebase, or choose to squash merge pull requests that do not follow these standards.
Commit messages should adhere to the Conventional Commits standard. A conventional commit message is structured
as follows:
[optional body]
[optional footer]
The commit contains the following elements, to communicate intent to the consumers of your library:
• fix: a commit of the type fix patches a bug in your codebase (this correlates with PATCH in semantic versioning).
• feat: a commit of the type feat introduces a new feature to the codebase (this correlates with MINOR in semantic
versioning).
• BREAKING CHANGE: a commit that has the text BREAKING CHANGE: at the beginning of its optional body or
footer section introduces a breaking API change (correlating with MAJOR in semantic versioning). A BREAKING
CHANGE can be part of commits of any type.
The use of commit types other than fix: and feat: is recommended. For example: docs:, style:, refactor:,
test:, chore:, or improvement:. These tags are not mandated by the specification and have no implicit effect in
semantic versioning.
We recommend the following best practices for commit messages (taken from How To Write a Commit Message):
• Limit the subject line to 50 characters.
• Use imperative, present tense in the subject line.
• Capitalize the subject line.
• Do not end the subject line with a period.
• Separate the subject from the body with a blank line.
• Wrap the body at 72 characters.
• Use the body to explain what and why vs. how.
Here’s an example commit message adhering to the above practices:
Explain the problem that this commit is solving. Focus on why you
are making this change as opposed to how (the code explains that).
Are there side effects or other unintuitive consequences of this
change? Here's the place to explain them.
Resolves: #XXX
See also: #XXY, #XXXZ
TWENTYTHREE
23.1 Motivation
A big part of Vyper’s “public API” is the language grammar. The syntax of the language is the main touchpoint all
parties have with Vyper, so it’s important to discuss changes to the language from the viewpoint of dependability. Users
expect that all contracts written in an earlier version of Vyper will work seamlessly with later versions, or that they will
be reasonably informed when this isn’t possible. The Vyper package itself and its CLI utilities also has a fairly well-
defined public API, which consists of the available features in Vyper’s exported package, the top level modules under
the package, and all CLI scripts.
This guide was adapted from semantic versioning. It defines a format for version numbers that looks like MAJOR.
MINOR.PATCH[-STAGE.DEVNUM]. We will periodically release updates according to this format, with the release de-
cided via the following guidelines.
187
Vyper Documentation
Changes to the grammar cannot be made in a backwards incompatible way without changing Major versions (e.g. v1.x
-> v2.x). It is to be expected that breaking changes to many features will occur when updating to a new Major release,
primarily for Developers that use Vyper to compile their contracts. Major releases will have an audit performed prior
to release (e.g. x.0.0 releases) and all moderate or severe vulnerabilities will be addressed that are reported in the
audit report. minor or informational vulnerabilities should be addressed as well, although this may be left up to
the maintainers of Vyper to decide.
Minor version updates may add new features or fix a moderate or severe vulnerability, and these will be detailed in
the Release Notes for that release. Minor releases may change the features or functionality offered by the package and
CLI scripts in a backwards-incompatible way that requires attention from an integrator. Minor releases are required to
fix a moderate or severe vulnerability, but a minor or informational vulnerability can be fixed in Patch releases,
alongside documentation updates.
Patch version releases will be released to fix documentation issues, usage bugs, and minor or informational vul-
nerabilities found in Vyper. Patch releases should only update error messages and documentation issues relating to its
external API.
As Vyper develops, it is very likely that we will encounter inconsistencies in how certain language features can be
used, and software bugs in the code the compiler generates. Some of them may be quite serious, and can render a
user’s compiled contract vulnerable to exploitation for financial gain. As we become aware of these vulnerabilities, we
will work according to our security policy to resolve these issues, and eventually will publish the details of all reported
vulnerabilities here. Fixes for these issues will also be noted in the Release Notes.
There may be multiple Major versions in the process of development. Work on new features that break compatibility
with the existing grammar can be maintained on a separate branch called next and represents the next Major release
of Vyper (provided in an unaudited state without Release Notes). The work on the current branch will remain on the
master branch with periodic new releases using the process as mentioned above.
Any other branches of work outside of what is being tracked via master will use the -alpha.[release #] (Alpha)
to denote WIP updates, and -beta.[release #] (Beta) to describe work that is eventually intended for release. -rc.
[release #] (Release Candidate) will only be used to denote candidate builds prior to a Major release. An audit will
be solicited for -rc.1 builds, and subsequent releases may incorporate feedback during the audit. The last Release
Candidate will become the next Major release, and will be made available alongside the full audit report summarizing
the findings.
Pull Requests can be opened against either master or next branch, depending on their content. Changes that would
increment a Minor or Patch release should target master, whereas changes to syntax (as detailed above) should be
opened against next. The next branch will be periodically rebased against the master branch to pull in changes made
that were added to the latest supported version of Vyper.
23.4 Communication
Major and Minor versions should be communicated on appropriate communications channels to end users, and Patch
updates will usually not be discussed, unless there is a relevant reason to do so.
A extract32(), 87
abi_decode() floor(), 89
built-in function, 96 isqrt(), 90
abi_encode() keccak256(), 86
built-in function, 95 len(), 94
abs() max(), 89
built-in function, 88 max_value(), 89
ArgumentException, 125 method_id(), 95
ArrayIndexException, 125 min(), 89
arrays, 51 min_value(), 89
as_wei_value() pow_mod256(), 90
built-in function, 93 print(), 96
auction raw_call(), 82
blind, 13 raw_create(), 82
open, 9 raw_log(), 83
raw_revert(), 84
B selfdestruct(), 84
send(), 84
ballot, 23
sha256(), 86
blind auction, 13
shift(), 79
blobhash()
slice(), 87
built-in function, 94
sqrt(), 90
blockhash()
uint256_addmod(), 90
built-in function, 94
uint256_mulmod(), 91
bool, 43
uint2str(), 87
built-in function
unsafe_add(), 91
abi_decode(), 96
unsafe_div(), 93
abi_encode(), 95
unsafe_mul(), 92
abs(), 88
unsafe_sub(), 91
as_wei_value(), 93
built-in;, 77
blobhash(), 94
bytes, 49
blockhash(), 94
ceil(), 88
concat(), 86
C
convert(), 87 CallViolation, 125
create_copy_of(), 80 cancun, 118
create_from_blueprint(), 81 ceil()
create_minimal_proxy_to(), 80 built-in function, 88
ecadd(), 85 company stock, 29
ecmul(), 85 CompilerPanic, 128
ecrecover(), 85 concat()
empty(), 94 built-in function, 86
epsilon(), 88 convert()
191
Vyper Documentation
built-in function, 87 K
create_copy_of() keccak256()
built-in function, 80 built-in function, 86
create_from_blueprint()
built-in function, 81 L
create_minimal_proxy_to()
len()
built-in function, 80
built-in function, 94
crowdfund, 20
london, 118
D M
deploying
mapping, 53
deploying;, 128
max()
dynarrays, 52
built-in function, 89
E max_value()
built-in function, 89
ecadd()
method_id()
built-in function, 85
built-in function, 95
ecmul()
min()
built-in function, 85
built-in function, 89
ecrecover()
min_value()
built-in function, 85
built-in function, 89
empty()
built-in function, 94 N
epsilon()
NamespaceCollision, 126
built-in function, 88
NatSpecSyntaxException, 126
EventDeclarationException, 125
NonPayableViolation, 126
EvmVersionException, 125
extract32()
built-in function, 87
O
open auction, 9
F OverflowException, 127
false, 43
floor() P
built-in function, 89 paris, 118
function, 77 pow_mod256()
FunctionDeclarationException, 125 built-in function, 90
prague, 118
I print()
ImmutableViolation, 125 built-in function, 96
initial, 54 purchases, 17
int, 43
InterfaceViolation, 125 R
intN, 43 raw_call()
InvalidAttribute, 125 built-in function, 82
InvalidLiteral, 125 raw_create()
InvalidOperation, 126 built-in function, 82
InvalidReference, 126 raw_log()
InvalidType, 126 built-in function, 83
isqrt() raw_revert()
built-in function, 90 built-in function, 84
IteratorException, 126 reference, 51
J S
JSONError, 126 selfdestruct()
192 Index
Vyper Documentation
built-in function, 84
send()
built-in function, 84
sha256()
built-in function, 86
shanghai, 118
shift()
built-in function, 79
signed integer, 43
slice()
built-in function, 87
sqrt()
built-in function, 90
StateAccessViolation, 127
stock
company, 29
string, 49
StructureException, 127
SyntaxException, 127
T
true, 43
type, 41
TypeMismatch, 127
U
uint, 45
uint256_addmod()
built-in function, 90
uint256_mulmod()
built-in function, 91
uint2str()
built-in function, 87
uintN, 45
UndeclaredDefinition, 127
unsafe_add()
built-in function, 91
unsafe_div()
built-in function, 93
unsafe_mul()
built-in function, 92
unsafe_sub()
built-in function, 91
unsigned integer, 45
V
value, 43
VariableDeclarationException, 127
VersionException, 128
voting, 23
Z
ZeroDivisionException, 128
Index 193