Company Stock¶
Warning
This is example code for learning purposes. Do not use in production without thorough review and testing.
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)
27
28# Store a ledger of stockholder holdings.
29holdings: HashMap[address, 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.
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
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.
44# Public function to allow external access to _stockAvailable
45@view
46@external
47def stockAvailable() -> uint256:
48 return self._stockAvailable()
149# Find out how much stock the company holds
150@view
151@internal
152def _stockAvailable() -> uint256:
153 return self.holdings[self.company]
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. When called externally
(not as part of a transaction), view functions do not cost gas. 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 its holdings. Because _stockAvailable() is an
internal method, we also include the stockAvailable() method to allow
external access.
Now, let’s take a look at a method that lets a person buy stock from the company’s holding.
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)
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?
68# Public function to allow external access to _getHolding
69@view
70@external
71def getHolding(_stockholder: address) -> uint256:
72 return self._getHolding(_stockholder)
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]
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.
74# Return the amount the company has on hand in cash.
75@view
76@external
77def cash() -> uint256:
78 return self.balance
To check the ether balance of the company, we can simply call the getter method
cash().
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)
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.
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)
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.
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)
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.
129# Public function to allow external access to _debt
130@view
131@external
132def debt() -> uint256:
133 return self._debt()
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
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
139@external
140def worth() -> uint256:
141 return self.balance - self._debt()
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.