Vyper by Example¶
Simple Open Auction¶
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.
1#pragma version >0.3.10
2
3# Open Auction
4
5# Auction params
6# Beneficiary receives money from the highest bidder
7beneficiary: public(address)
8auctionStart: public(uint256)
9auctionEnd: public(uint256)
10
11# Current state of auction
12highestBidder: public(address)
13highestBid: public(uint256)
14
15# Set to true at the end, disallows any change
16ended: public(bool)
17
18# Keep track of refunded bids so we can follow the withdraw pattern
19pendingReturns: public(HashMap[address, uint256])
20
21# Create a simple auction with `_auction_start` and
22# `_bidding_time` seconds bidding time on behalf of the
23# beneficiary address `_beneficiary`.
24@deploy
25def __init__(_beneficiary: address, _auction_start: uint256, _bidding_time: uint256):
26 self.beneficiary = _beneficiary
27 self.auctionStart = _auction_start # auction start time can be in the past, present or future
28 self.auctionEnd = self.auctionStart + _bidding_time
29 assert block.timestamp < self.auctionEnd # auction end time should be in the future
30
31# Bid on the auction with the value sent
32# together with this transaction.
33# The value will only be refunded if the
34# auction is not won.
35@external
36@payable
37def bid():
38 # Check if bidding period has started.
39 assert block.timestamp >= self.auctionStart
40 # Check if bidding period is over.
41 assert block.timestamp < self.auctionEnd
42 # Check if bid is high enough
43 assert msg.value > self.highestBid
44 # Track the refund for the previous high bidder
45 self.pendingReturns[self.highestBidder] += self.highestBid
46 # Track new high bid
47 self.highestBidder = msg.sender
48 self.highestBid = msg.value
49
50# Withdraw a previously refunded bid. The withdraw pattern is
51# used here to avoid a security issue. If refunds were directly
52# sent as part of bid(), a malicious bidding contract could block
53# those refunds and thus block new higher bids from coming in.
54@external
55def withdraw():
56 pending_amount: uint256 = self.pendingReturns[msg.sender]
57 self.pendingReturns[msg.sender] = 0
58 send(msg.sender, pending_amount)
59
60# End the auction and send the highest bid
61# to the beneficiary.
62@external
63def endAuction():
64 # It is a good guideline to structure functions that interact
65 # with other contracts (i.e. they call functions or send Ether)
66 # into three phases:
67 # 1. checking conditions
68 # 2. performing actions (potentially changing conditions)
69 # 3. interacting with other contracts
70 # If these phases are mixed up, the other contract could call
71 # back into the current contract and modify the state or cause
72 # effects (Ether payout) to be performed multiple times.
73 # If functions called internally include interaction with external
74 # contracts, they also have to be considered interaction with
75 # external contracts.
76
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
84 self.ended = True
85
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
7beneficiary: public(address)
8auctionStart: public(uint256)
9auctionEnd: public(uint256)
10
11# Current state of auction
12highestBidder: public(address)
13highestBid: public(uint256)
14
15# Set to true at the end, disallows any change
16ended: public(bool)
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.
22# `_bidding_time` seconds bidding time on behalf of the
23# beneficiary address `_beneficiary`.
24@deploy
25def __init__(_beneficiary: address, _auction_start: uint256, _bidding_time: uint256):
26 self.beneficiary = _beneficiary
27 self.auctionStart = _auction_start # auction start time can be in the past, present or future
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.
33# The value will only be refunded if the
34# auction is not won.
35@external
36@payable
37def bid():
38 # Check if bidding period has started.
39 assert block.timestamp >= self.auctionStart
40 # Check if bidding period is over.
41 assert block.timestamp < self.auctionEnd
42 # Check if bid is high enough
43 assert msg.value > self.highestBid
44 # Track the refund for the previous high bidder
45 self.pendingReturns[self.highestBidder] += self.highestBid
46 # Track new high bid
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.
60# End the auction and send the highest bid
61# to the beneficiary.
62@external
63def endAuction():
64 # It is a good guideline to structure functions that interact
65 # with other contracts (i.e. they call functions or send Ether)
66 # into three phases:
67 # 1. checking conditions
68 # 2. performing actions (potentially changing conditions)
69 # 3. interacting with other contracts
70 # If these phases are mixed up, the other contract could call
71 # back into the current contract and modify the state or cause
72 # effects (Ether payout) to be performed multiple times.
73 # If functions called internally include interaction with external
74 # contracts, they also have to be considered interaction with
75 # external contracts.
76
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
84 self.ended = True
85
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 initialization 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.
Whenever you’re ready, let’s turn it up a notch in the next example.
Blind Auction¶
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.
1#pragma version >0.3.10
2
3# Blind Auction. Adapted to Vyper from [Solidity by Example](https://github.com/ethereum/solidity/blob/develop/docs/solidity-by-example.rst#blind-auction-1)
4
5struct 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
11MAX_BIDS: constant(int128) = 128
12
13# Event for logging that auction has ended
14event AuctionEnded:
15 highestBidder: address
16 highestBid: uint256
17
18# Auction parameters
19beneficiary: public(address)
20biddingEnd: public(uint256)
21revealEnd: public(uint256)
22
23# Set to true at the end of auction, disallowing any new bids
24ended: public(bool)
25
26# Final auction state
27highestBid: public(uint256)
28highestBidder: public(address)
29
30# State of the bids
31bids: HashMap[address, Bid[128]]
32bidCounts: HashMap[address, int128]
33
34# Allowed withdrawals of previous bids
35pendingReturns: HashMap[address, uint256]
36
37
38# Create a blinded auction with `_biddingTime` seconds bidding time and
39# `_revealTime` seconds reveal time on behalf of the beneficiary address
40# `_beneficiary`.
41@deploy
42def __init__(_beneficiary: address, _biddingTime: uint256, _revealTime: uint256):
43 self.beneficiary = _beneficiary
44 self.biddingEnd = block.timestamp + _biddingTime
45 self.revealEnd = self.biddingEnd + _revealTime
46
47
48# Place a blinded bid with:
49#
50# _blindedBid = keccak256(concat(
51# convert(value, bytes32),
52# convert(fake, bytes32),
53# secret)
54# )
55#
56# The sent ether is only refunded if the bid is correctly revealed in the
57# revealing phase. The bid is valid if the ether sent together with the bid is
58# at least "value" and "fake" is not true. Setting "fake" to true and sending
59# not the exact amount are ways to hide the real bid but still make the
60# required deposit. The same address can place multiple bids.
61@external
62@payable
63def bid(_blindedBid: bytes32):
64 # Check if bidding period is still open
65 assert block.timestamp < self.biddingEnd
66
67 # Check that payer hasn't already placed maximum number of bids
68 numBids: int128 = self.bidCounts[msg.sender]
69 assert numBids < MAX_BIDS
70
71 # Add bid to mapping of all bids
72 self.bids[msg.sender][numBids] = Bid(
73 blindedBid=_blindedBid,
74 deposit=msg.value
75 )
76 self.bidCounts[msg.sender] += 1
77
78
79# Returns a boolean value, `True` if bid placed successfully, `False` otherwise.
80@internal
81def placeBid(bidder: address, _value: uint256) -> bool:
82 # If bid is less than highest bid, bid fails
83 if (_value <= self.highestBid):
84 return False
85
86 # Refund the previously highest bidder
87 if (self.highestBidder != empty(address)):
88 self.pendingReturns[self.highestBidder] += self.highestBid
89
90 # Place bid successfully and update auction state
91 self.highestBid = _value
92 self.highestBidder = bidder
93
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
100def reveal(_numBids: int128, _values: uint256[128], _fakes: bool[128], _secrets: bytes32[128]):
101 # Check that bidding period is over
102 assert block.timestamp > self.biddingEnd
103
104 # Check that reveal end has not passed
105 assert block.timestamp < self.revealEnd
106
107 # Check that number of bids being revealed matches log for sender
108 assert _numBids == self.bidCounts[msg.sender]
109
110 # Calculate refund for sender
111 refund: uint256 = 0
112 for i: int128 in range(MAX_BIDS):
113 # Note that loop may break sooner than 128 iterations if i >= _numBids
114 if (i >= _numBids):
115 break
116
117 # Get bid to check
118 bidToCheck: Bid = (self.bids[msg.sender])[i]
119
120 # Check against encoded packet
121 value: uint256 = _values[i]
122 fake: bool = _fakes[i]
123 secret: bytes32 = _secrets[i]
124 blindedBid: bytes32 = keccak256(concat(
125 convert(value, bytes32),
126 convert(fake, bytes32),
127 secret
128 ))
129
130 # Bid was not actually revealed
131 # Do not refund deposit
132 assert blindedBid == bidToCheck.blindedBid
133
134 # Add deposit to refund if bid was indeed revealed
135 refund += bidToCheck.deposit
136 if (not fake and bidToCheck.deposit >= value):
137 if (self.placeBid(msg.sender, value)):
138 refund -= value
139
140 # Make it impossible for the sender to re-claim the same deposit
141 zeroBytes32: bytes32 = empty(bytes32)
142 bidToCheck.blindedBid = zeroBytes32
143
144 # Send refund if non-zero
145 if (refund != 0):
146 send(msg.sender, refund)
147
148
149# Withdraw a bid that was overbid.
150@external
151def withdraw():
152 # Check that there is an allowed pending return.
153 pendingAmount: uint256 = self.pendingReturns[msg.sender]
154 if (pendingAmount > 0):
155 # If so, set pending returns to zero to prevent recipient from calling
156 # this function again as part of the receiving call before `transfer`
157 # returns (see the remark above about conditions -> effects ->
158 # interaction).
159 self.pendingReturns[msg.sender] = 0
160
161 # Then send return
162 send(msg.sender, pendingAmount)
163
164
165# End the auction and send the highest bid to the beneficiary.
166@external
167def 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
174 # Log auction ending and set flag
175 log AuctionEnded(highestBidder=self.highestBidder, highestBid=self.highestBid)
176 self.ended = True
177
178 # Transfer funds to beneficiary
179 send(self.beneficiary, self.highestBid)
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.
28highestBidder: public(address)
29
30# State of the bids
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.
Safe Remote Purchases¶
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
3# Safe Remote Purchase
4# Originally from
5# https://github.com/ethereum/solidity/blob/develop/docs/solidity-by-example.rst
6# Ported to vyper and optimized.
7
8# Rundown of the transaction:
9# 1. Seller posts item for sale and posts safety deposit of double the item value.
10# Balance is 2*value.
11# (1.1. Seller can reclaim deposit and close the sale as long as nothing was purchased.)
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
18value: public(uint256) #Value of the item
19seller: public(address)
20buyer: public(address)
21unlocked: public(bool)
22ended: public(bool)
23finalized: public(bool)
24
25@deploy
26@payable
27def __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
36def 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
47def 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
56def 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
72 # purchase price (=value) to the seller.
This is also a moderately short contract, however a little more complex in logic. Let’s break down this contract bit by bit.
16# Seller's deposit (2*value) + items value is returned. Balance is 0.
17
18value: public(uint256) #Value of the item
19seller: public(address)
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.
22ended: public(bool)
23finalized: public(bool)
24
25@deploy
26@payable
27def __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.
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
36def 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.
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
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.
47def 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
56def 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.
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.
1#pragma version >0.3.10
2
3###########################################################################
4## THIS IS EXAMPLE CODE, NOT MEANT TO BE USED IN PRODUCTION! CAVEAT EMPTOR!
5###########################################################################
6
7# example of a crowd funding contract
8
9funders: HashMap[address, uint256]
10beneficiary: address
11deadline: public(uint256)
12goal: public(uint256)
13timelimit: public(uint256)
14finalized: bool
15
16# Setup global variables
17@deploy
18def __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
24
25# Participate in this crowdfunding campaign
26@external
27@payable
28def participate():
29 assert block.timestamp < self.deadline, "deadline has expired"
30 assert not self.finalized
31
32 self.funders[msg.sender] += msg.value
33
34# Enough money was raised! Send funds to the beneficiary
35@external
36def finalize():
37 assert block.timestamp >= self.deadline, "deadline has not expired yet"
38 assert self.balance >= self.goal, "goal has not been reached"
39 assert self.balance > 0
40 self.finalized = True
41
42 send(self.beneficiary, self.balance)
43
44# Let participants withdraw their fund
45@external
46def refund():
47 assert block.timestamp >= self.deadline and self.balance < self.goal
48 assert not self.finalized
49 assert self.funders[msg.sender] > 0
50
51 value: uint256 = self.funders[msg.sender]
52 self.funders[msg.sender] = 0
53
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
7# example of a crowd funding contract
8
9funders: HashMap[address, uint256]
10beneficiary: address
11deadline: public(uint256)
12goal: public(uint256)
13timelimit: public(uint256)
Like other examples, we begin by initiating our variables - except this time,
we’re not calling them with the public function. Variables initiated this
way are, by default, private.
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.
9funders: HashMap[address, uint256]
10beneficiary: address
11deadline: public(uint256)
12goal: public(uint256)
13timelimit: public(uint256)
14finalized: bool
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
18def __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.
25# Participate in this crowdfunding campaign
26@external
27@payable
28def participate():
29 assert block.timestamp < self.deadline, "deadline has expired"
30 assert not self.finalized
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.
33# Enough money was raised! Send funds to the beneficiary
34@external
35def finalize():
36 assert block.timestamp >= self.deadline, "deadline has not expired yet"
37 assert self.balance >= self.goal, "goal has not been reached"
38 assert self.balance > 0
39 self.finalized = True
40
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.
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.
1#pragma version >0.3.10
2
3# Voting with delegation.
4
5# Information about voters
6struct Voter:
7 # weight is accumulated by delegation
8 weight: int128
9 # if true, that person already voted (which includes voting by delegating)
10 voted: bool
11 # person delegated to
12 delegate: address
13 # index of the voted proposal, which is not meaningful unless `voted` is True.
14 vote: int128
15
16# Users can create proposals
17struct Proposal:
18 # short name (up to 32 bytes)
19 name: bytes32
20 # number of accumulated votes
21 voteCount: int128
22
23voters: public(HashMap[address, Voter])
24proposals: public(HashMap[int128, Proposal])
25voterCount: public(int128)
26chairperson: public(address)
27int128Proposals: public(int128)
28
29
30@view
31@internal
32def _delegated(addr: address) -> bool:
33 return self.voters[addr].delegate != empty(address)
34
35
36@view
37@external
38def delegated(addr: address) -> bool:
39 return self._delegated(addr)
40
41
42@view
43@internal
44def _directlyVoted(addr: address) -> bool:
45 return self.voters[addr].voted and (self.voters[addr].delegate == empty(address))
46
47
48@view
49@external
50def directlyVoted(addr: address) -> bool:
51 return self._directlyVoted(addr)
52
53
54# Setup global variables
55@deploy
56def __init__(_proposalNames: bytes32[2]):
57 self.chairperson = msg.sender
58 self.voterCount = 0
59 for i: int128 in range(2):
60 self.proposals[i] = Proposal(
61 name=_proposalNames[i],
62 voteCount=0
63 )
64 self.int128Proposals += 1
65
66# Give a `voter` the right to vote on this ballot.
67# This may only be called by the `chairperson`.
68@external
69def giveRightToVote(voter: address):
70 # Throws if the sender is not the chairperson.
71 assert msg.sender == self.chairperson
72 # Throws if the voter has already voted.
73 assert not self.voters[voter].voted
74 # Throws if the voter's voting weight isn't 0.
75 assert self.voters[voter].weight == 0
76 self.voters[voter].weight = 1
77 self.voterCount += 1
78
79# Used by `delegate` below, callable externally via `forwardWeight`
80@internal
81def _forwardWeight(delegate_with_weight_to_forward: address):
82 assert self._delegated(delegate_with_weight_to_forward)
83 # Throw if there is nothing to do:
84 assert self.voters[delegate_with_weight_to_forward].weight > 0
85
86 target: address = self.voters[delegate_with_weight_to_forward].delegate
87 for i: int128 in range(4):
88 if self._delegated(target):
89 target = self.voters[target].delegate
90 # The following effectively detects cycles of length <= 5,
91 # in which the delegation is given back to the delegator.
92 # This could be done for any int128ber of loops,
93 # or even infinitely with a while loop.
94 # However, cycles aren't actually problematic for correctness;
95 # they just result in spoiled votes.
96 # So, in the production version, this should instead be
97 # the responsibility of the contract's client, and this
98 # check should be removed.
99 assert target != delegate_with_weight_to_forward
100 else:
101 # Weight will be moved to someone who directly voted or
102 # hasn't voted.
103 break
104
105 weight_to_forward: int128 = self.voters[delegate_with_weight_to_forward].weight
106 self.voters[delegate_with_weight_to_forward].weight = 0
107 self.voters[target].weight += weight_to_forward
108
109 if self._directlyVoted(target):
110 self.proposals[self.voters[target].vote].voteCount += weight_to_forward
111 self.voters[target].weight = 0
112
113 # To reiterate: if target is also a delegate, this function will need
114 # to be called again, similarly to as above.
115
116# Public function to call _forwardWeight
117@external
118def forwardWeight(delegate_with_weight_to_forward: address):
119 self._forwardWeight(delegate_with_weight_to_forward)
120
121# Delegate your vote to the voter `to`.
122@external
123def delegate(to: address):
124 # Throws if the sender has already voted
125 assert not self.voters[msg.sender].voted
126 # Throws if the sender tries to delegate their vote to themselves or to
127 # the default address value of 0x0000000000000000000000000000000000000000
128 # (the latter might not be problematic, but I don't want to think about it).
129 assert to != msg.sender
130 assert to != empty(address)
131
132 self.voters[msg.sender].voted = True
133 self.voters[msg.sender].delegate = to
134
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
139# Give your vote (including votes delegated to you)
140# to proposal `proposals[proposal].name`.
141@external
142def vote(proposal: int128):
143 # can't vote twice
144 assert not self.voters[msg.sender].voted
145 # can only vote on legitimate proposals
146 assert proposal < self.int128Proposals
147
148 self.voters[msg.sender].vote = proposal
149 self.voters[msg.sender].voted = True
150
151 # transfer msg.sender's weight to proposal
152 self.proposals[proposal].voteCount += self.voters[msg.sender].weight
153 self.voters[msg.sender].weight = 0
154
155# Computes the winning proposal taking all
156# previous votes into account.
157@view
158@internal
159def _winningProposal() -> int128:
160 winning_vote_count: int128 = 0
161 winning_proposal: int128 = 0
162 for i: int128 in range(2):
163 if self.proposals[i].voteCount > winning_vote_count:
164 winning_vote_count = self.proposals[i].voteCount
165 winning_proposal = i
166 return winning_proposal
167
168@view
169@external
170def winningProposal() -> int128:
171 return self._winningProposal()
172
173
174# Calls winningProposal() function to get the index
175# of the winner contained in the proposals array and then
176# returns the name of the winner
177@view
178@external
179def winnerName() -> bytes32:
180 return self.proposals[self._winningProposal()].name
As we can see, this is the contract of moderate length which we will dissect section by section. Let’s begin!
3# Voting with delegation.
4
5# Information about voters
6struct Voter:
7 # weight is accumulated by delegation
8 weight: int128
9 # if true, that person already voted (which includes voting by delegating)
10 voted: bool
11 # person delegated to
12 delegate: address
13 # index of the voted proposal, which is not meaningful unless `voted` is True.
14 vote: int128
15
16# Users can create proposals
17struct Proposal:
18 # short name (up to 32 bytes)
19 name: bytes32
20 # number of accumulated votes
21 voteCount: int128
22
23voters: public(HashMap[address, Voter])
24proposals: public(HashMap[int128, Proposal])
25voterCount: public(int128)
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.
53# Setup global variables
54@deploy
55def __init__(_proposalNames: bytes32[2]):
56 self.chairperson = msg.sender
57 self.voterCount = 0
58 for i: int128 in range(2):
59 self.proposals[i] = Proposal(
60 name=_proposalNames[i],
61 voteCount=0
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.
66# Give a `voter` the right to vote on this ballot.
67# This may only be called by the `chairperson`.
68@external
69def giveRightToVote(voter: address):
70 # Throws if the sender is not the chairperson.
71 assert msg.sender == self.chairperson
72 # Throws if the voter has already voted.
73 assert not self.voters[voter].voted
74 # Throws if the voter's voting weight isn't 0.
75 assert self.voters[voter].weight == 0
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.
120# Delegate your vote to the voter `to`.
121@external
122def delegate(to: address):
123 # Throws if the sender has already voted
124 assert not self.voters[msg.sender].voted
125 # Throws if the sender tries to delegate their vote to themselves or to
126 # the default address value of 0x0000000000000000000000000000000000000000
127 # (the latter might not be problematic, but I don't want to think about it).
128 assert to != msg.sender
129 assert to != empty(address)
130
131 self.voters[msg.sender].voted = True
132 self.voters[msg.sender].delegate = to
133
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
142def vote(proposal: int128):
143 # can't vote twice
144 assert not self.voters[msg.sender].voted
145 # can only vote on legitimate proposals
146 assert proposal < self.int128Proposals
147
148 self.voters[msg.sender].vote = proposal
149 self.voters[msg.sender].voted = True
150
151 # transfer msg.sender's weight to proposal
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
155# Computes the winning proposal taking all
156# previous votes into account.
157@view
158@internal
159def _winningProposal() -> int128:
160 winning_vote_count: int128 = 0
161 winning_proposal: int128 = 0
162 for i: int128 in range(2):
163 if self.proposals[i].voteCount > winning_vote_count:
164 winning_vote_count = self.proposals[i].voteCount
165 winning_proposal = i
166 return winning_proposal
167
168@view
169@external
170def 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().
175# of the winner contained in the proposals array and then
176# returns the name of the winner
177@view
178@external
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.
Company Stock¶
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.
1#pragma version >0.3.10
2
3# Financial events the contract logs
4
5event Transfer:
6 sender: indexed(address)
7 receiver: indexed(address)
8 value: uint256
9
10event Buy:
11 buyer: indexed(address)
12 buy_order: uint256
13
14event Sell:
15 seller: indexed(address)
16 sell_order: uint256
17
18event Pay:
19 vendor: indexed(address)
20 amount: uint256
21
22
23# Initiate the variables for the company and it's own shares.
24company: public(address)
25totalShares: public(uint256)
26price: public(uint256)
27
28# Store a ledger of stockholder holdings.
29holdings: HashMap[address, uint256]
30
31# Set up the company.
32@deploy
33def __init__(_company: address, _total_shares: uint256, initial_price: uint256):
34 assert _total_shares > 0
35 assert initial_price > 0
36
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
44# Public function to allow external access to _stockAvailable
45@view
46@external
47def stockAvailable() -> uint256:
48 return self._stockAvailable()
49
50# Give some value to the company and get stock in return.
51@external
52@payable
53def 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
58 # Check that there are enough shares to buy.
59 assert self._stockAvailable() >= buy_order
60
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
65 # Log the buy event.
66 log Buy(buyer=msg.sender, buy_order=buy_order)
67
68# Public function to allow external access to _getHolding
69@view
70@external
71def getHolding(_stockholder: address) -> uint256:
72 return self._getHolding(_stockholder)
73
74# Return the amount the company has on hand in cash.
75@view
76@external
77def cash() -> uint256:
78 return self.balance
79
80# Give stock back to the company and get money back as ETH.
81@external
82def 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
90 # Sell the stock, send the proceeds to the user
91 # and put the stock back on the market.
92 self.holdings[msg.sender] -= sell_order
93 self.holdings[self.company] += sell_order
94 send(msg.sender, sell_order * self.price)
95
96 # Log the sell event.
97 log Sell(seller=msg.sender, sell_order=sell_order)
98
99# Transfer stock from one stockholder to another. (Assume that the
100# receiver is given some compensation, but this is not enforced.)
101@external
102def transferStock(receiver: address, transfer_order: uint256):
103 assert transfer_order > 0 # This is similar to sellStock above.
104 # Similarly, you can only trade as much stock as you own.
105 assert self._getHolding(msg.sender) >= transfer_order
106
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
111 # Log the transfer event.
112 log Transfer(sender=msg.sender, receiver=receiver, value=transfer_order)
113
114# Allow the company to pay someone for services rendered.
115@external
116def payBill(vendor: address, amount: uint256):
117 # Only the company can pay people.
118 assert msg.sender == self.company
119 # Also, it can pay only if there's enough to pay them with.
120 assert self.balance >= amount
121
122 # Pay the bill!
123 send(vendor, amount)
124
125 # Log the payment event.
126 log Pay(vendor=vendor, amount=amount)
127
128
129# Public function to allow external access to _debt
130@view
131@external
132def debt() -> uint256:
133 return self._debt()
134
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
140def 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
146def _debt() -> uint256:
147 return (self.totalShares - self._stockAvailable()) * self.price
148
149# Find out how much stock the company holds
150@view
151@internal
152def _stockAvailable() -> uint256:
153 return self.holdings[self.company]
154
155# Find out how much stock any address (that's owned by someone) has.
156@view
157@internal
158def _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.
3# Financial events the contract logs
4
5event Transfer:
6 sender: indexed(address)
7 receiver: indexed(address)
8 value: uint256
9
10event Buy:
11 buyer: indexed(address)
12 buy_order: uint256
13
14event Sell:
15 seller: indexed(address)
16 sell_order: uint256
17
18event Pay:
19 vendor: indexed(address)
20 amount: uint256
21
22
23# Initiate the variables for the company and it's own shares.
24company: public(address)
25totalShares: public(uint256)
26price: 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.
29holdings: HashMap[address, uint256]
30
31# Set up the company.
32@deploy
33def __init__(_company: address, _total_shares: uint256, initial_price: uint256):
34 assert _total_shares > 0
35 assert initial_price > 0
36
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
44# Public function to allow external access to _stockAvailable
45@view
46@external
We will be seeing a few @view decorators in this contract—which is
used to decorate methods that simply read the contract 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 access.
Now, lets take a look at a method that lets a person buy stock from the company’s holding.
51@external
52@payable
53def 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
58 # Check that there are enough shares to buy.
59 assert self._stockAvailable() >= buy_order
60
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
68# Public function to allow external access to _getHolding
69@view
70@external
71def getHolding(_stockholder: address) -> uint256:
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
74# Return the amount the company has on hand in cash.
75@view
76@external
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
82def 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
90 # Sell the stock, send the proceeds to the user
91 # and put the stock back on the market.
92 self.holdings[msg.sender] -= sell_order
93 self.holdings[self.company] += sell_order
94 send(msg.sender, sell_order * self.price)
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.
97 log Sell(seller=msg.sender, sell_order=sell_order)
98
99# Transfer stock from one stockholder to another. (Assume that the
100# receiver is given some compensation, but this is not enforced.)
101@external
102def transferStock(receiver: address, transfer_order: uint256):
103 assert transfer_order > 0 # This is similar to sellStock above.
104 # Similarly, you can only trade as much stock as you own.
105 assert self._getHolding(msg.sender) >= transfer_order
106
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.
112 log Transfer(sender=msg.sender, receiver=receiver, value=transfer_order)
113
114# Allow the company to pay someone for services rendered.
115@external
116def payBill(vendor: address, amount: uint256):
117 # Only the company can pay people.
118 assert msg.sender == self.company
119 # Also, it can pay only if there's enough to pay them with.
120 assert self.balance >= amount
121
122 # Pay the bill!
123 send(vendor, amount)
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.
126 log Pay(vendor=vendor, amount=amount)
127
128
129# Public function to allow external access to _debt
130@view
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().
132def debt() -> uint256:
133 return self._debt()
134
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.