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.