Lab 10: Using smart contracts on Ethereum
In today’s lab, you will use your VMs to connect to a local “private” Ethereum network, make an account, get some Ethers, interact with an existing smart contract, and deploy your own smart contract. Fun!
Important Disclaimer
Starting today, you will be running actual Ethereum software that could be used to interact with and even mine coins on the full, worldwide cryptocurrency network.
It is probably against many rules to run any of this software on a DoD network, which generally ban cryptocurrency nodes. This is exactly why we are using VMs in a controlled, isolated network. So, stick to the VMs and don’t install any of this software on any other machine that might connect to something that the Navy owns.
Background
Suggestion: start the installation process below while you read through some of this.
Ethereum
(This should be mostly a review from what we already learned in class.)
Ethereum is a blockchain protocol similar in some ways to Bitcoin, but with a number of improvements and new features.
The basic unit of cryptocurrency in this network is called a wei. All transactions are integer amounts of Wei. Think of this as an Ethereum penny.
The next commonly-used unit up is a Gwei, or “giga-wei”, which as you might expect is equal to 1 trillion Wei, or 109 WEI. Actually as of this writing, a Gwei is still worth just a fraction of 1 US cent.
Finally, 1 Ether is equal to 1 trillion Gwei, or 1018 WEI. Right now 1 Ether is worth a few thousand US dollars.
In Ethereum, there are two kinds of accounts:
Personal accounts are similar to a bank account. The account has a public key (called the address) and a private key. Each account also has a balance (in wei).
Anyone with the private key for a personal account can make a transaction that moves any amount of Ethers (well, actually recorded in wei) from their account to any other account. To make a transaction, you need the private key of the sender and the public key of the recipient.
Smart contracts are also accounts in sense that they have an address (like a public key) and a balance.
But smart contracts do not have any private key. Instead, smart contracts are controlled by code that runs on the Ethereum blockchain itself.
The code for a smart contract has a constructor, some fields, and some methods, much like a class in OOP. Any account can call a method of an existing smart contract and cause that method’s code to execute. This “calling a method” is actually just an ordinary transaction in Ethereum, which (like any transaction) can also contain a transfer of Ethers. If you make a transaction with Ethers that calls a method in a smart contract, you are transferring those Ethers to the smart contract account’s balance.
Like any blockchain, transactions are not instantaneous. Instead, a valid, signed transaction is submitted to a miner, who checks its validity (including actually running the code of any smart contract calls), and if it checks out, the transaction is added to the next mined block.
To motivate the miners to do all this work, every transaction has some fee associated with it; these fees in Ethereum are called gas. Basic Ether transfers between personal accounts have a fixed gas price, but smart contract method calls have a gas price that depends on how long it takes to run the code (and update any needed storage that results).
The price of gas in a real Ethereum network depends on how busy the network is, but on our test network the gas price is fixed at only 100 wei, which really means that you can mostly ignore it.
Tools
We will mostly use 3 tools to get work done in Ethereum:
Geth is software that implements the Ethereum network protocol, written in the programming language Go. It is powerful, real software that by default tries to connect with the full global Ethereum network, but in our class we will use our own local test network in the VM environment instead.
PyWeb3 is a standard Python-based interface to interact with Ethereum nodes. This will be what we use to check account balances, make transactions, deploy smart contracts, and see what’s happening in our local network.
Vyber is a programming language (and compiler) for smart contracts in Ethereum. Vyper has the same syntax as Python, but enforces extra rules (such as typing) and has extra features to work on Ethereum.
Your tasks
Install
Log into your vm and run these commands in bash to download geth, web3, and vyper:
sudo add-apt-repository -y ppa:ethereum/ethereum
sudo apt update
sudo apt install ethereum
python3 -m pip install vyper web3
Set up git
You will want to use two git repositories for today’s lab.
First, you want to put your work for today (smart contracts that you might write) into your normal goatcoin repo that we have been using all class. In that repo, make a new directory lab10
for any work you do today.
Second, you will clone a new repo, shared across everyone in the class, to hold the information we need to use the blockchain together. Namely, this repo has the genesis block for our local Ethereum test network, the Vyper source code of smart contracts that are deployed on the network, and public addresses of people and smart contracts that you might want to make transactions with.
To clone that repo, run this:
cd ~
git clone git@gitlab.usna.edu:goats22/eth
Now you should have a directory ~/eth
that contains the shared genesis block file, addresses, and smart contracts. It even has the very document you are reading in lab10.md
- check it out!
Initialize geth and make an account
First you need to initialize geth with the genesis block you downloaded from git. This step is very important to do first, because otherwise geth will try to use the genesis block for the global, actual Ethereum network.
geth --networkid 486 init ~/eth/genesis.json
You should see a bunch of messages; make sure the last line says that it worked successfully!
Next, you will make a personal account to hold your Ethers for our test network. (Why doesn’t it require interaction with any servers in order to create a new account?) Run this command to make the account:
geth account new
You will be prompted for a password. Don’t make it anything too secure, because you will probably end up typing it on your screen a bunch!
Start geth and web3
Remember that geth is the software you will run that actually connects to our test network, and web3
is a Python package that lets you interact with the network via geth.
I like to run these both side by side in the terminal.
First, run these commands to start geth connected to our local testnet:
ip=$(hostname -I | xargs)
benode=$(cat ~/eth/benode)
geth --networkid 486 -verbosity 3 --netrestrict "$ip/24" --nat "extip:$ip" --bootnodes "$benode"
This will print a whole bunch of messages to start up. Then you should see it settle in. If you watch the messages, you should notice that your node has a few peers (probably between 2 and 20), which are the geth instances being run by your instructor and classmates.
You should also see that a new block is mined about every minute. This is slower than the real Ethereum network, but will help us learn better how this all works.
Second, in another terminal, start a Python instance with
python3
and then run these Python commands to connect to your geth node using web3:
from web3 import Web3
from web3.middleware import geth_poa_middleware
w3 = Web3(Web3.IPCProvider('~/.ethereum/geth.ipc'))
w3.middleware_onion.inject(geth_poa_middleware, layer=0)
w3.eth.defaultAccount = w3.eth.accounts[0]
Now you should be connected! You can try a few commands to check on things:
w3.isConnected()
should return True to indicate your web3 Python console is connected to your running geth instance.len(w3.geth.admin.peers())
should show you how many other nodes are running on our test network (should be at least 2 unless there is a problem!)w3.eth.accounts[0]
should show the hex of your own account that you created beforew3.fromWei(w3.eth.getBalance("0xE675078C460C828BA0157bC010aF6E9082b86483"),'ether')
will show you the balance of that account, in Ether. (It’s a lot!)
Add your account to the shared git repo
You should see the hex of your own account from the commands above. Now you want to copy that into a public place so other people can send you money!
Make a new file in the eth
git repo under addrs/personal
, with an appropriate name, and copy this address into the new file.
Use git add
to add that file to the repo, then git pull
and git push
to update and share your address with everyone.
Get someone to transfer you a little spending money
Now you are connected on the network, and you have an account, but your balance is zero. (Check with w3.eth.getBalance(w3.eth.defaultAccount)
if you want!)
First you will have to get someone else to transfer you a few Gwei. Have a classmate (or your instructor, if you’re first) put through a modest transfer to your account by running something like:
txdict = []
txdict['to'] = 'RECIPIENT_ACCOUNT'
txdict['value'] = w3.toWei(100, 'gwei')
w3.geth.personal.unlockAccount(w3.eth.defaultAccount, 'SENDERS_PASSWORD')
txid = w3.eth.send_transaction(txdict)
(Of course, you have to replace RECIPIENT_ACCOUNT
with your account hex and your kind donor has to replace SENDERS_PASSWORD
.)
At this point, you have a transaction id, but remember that it takes up to a minute for the next block to be mined, so things are not instantaneous! You can check the transaction status with
w3.eth.get_transaction(txid)
This will show you the transaction info, but initially the “Block” field will be None
, meaning it hasn’t been added to a block by a miner yet.
Once it goes through, you should see your balance has increased back on your computer!
Get more money from the UBI smart contract
Now it’s time to interact with a real smart contract! I wrote the following simple smart contract, which you can find in the git repo under ubi.vy
.
# @version ^0.3.0
# Universal Basic Income
# Dan Roche, 2022
AMOUNT: constant(uint256) = 10**19 # 10 ether
next_payday: public(HashMap[address, uint256])
@external
@payable
def __init__():
pass
@external
def payme():
# can't get paid until the next payday
assert self.next_payday[msg.sender] <= block.timestamp
# send the paycheck
send(msg.sender, AMOUNT)
# set next payday to 24 hours later
self.next_payday[msg.sender] = block.timestamp + 24*60*60
@external
@payable
def donate():
pass
To interact with this smart contract, you first have to get the ABI, or application binary interface. In python we can use the vyper
module to compile the code and extract the ABI for us like this:
import vyper
ubi_compiled = vyper.compile_code(open('/home/YOUR_USERNAME/eth/contracts/ubi.vy').read(), ['abi', 'bytecode'])
The other thing we need is the address of where the smart contract is deployed. (Remember, in Ethereum, smart contracts are just a special kind of account, so of course they have an address!)
ubi_addr = '0x4a44f79D13AE350756F79b870967a5848eC9b6f4'
(You should find the same address in the git repo as well.)
Now you are ready to create the contract in web3 and call the payme
method from your account:
ubi = w3.eth.contract(address=ubi_addr, abi=ubi_compiled['abi'])
w3.geth.personal.unlockAccount(w3.eth.defaultAccount, 'SENDERS_PASSWORD')
txid = ubi.functions.payme().transact()
Once this transaction goes through, you should check your balance to find 10 shiny new ethers! But beware - you can only get your free 10 ETH once per 24-hour period.
In fact, I encourage you to try making the same smart contract call again! If you do, it should be immediately rejected because you need to wait 24 hours. Interestingly, you find out immediately because your own geth node tries (and fails) to locally verify this transaction will succeed, before even sending it out on the network.
Deploy a smart contract
Time for you to deploy a smart contract of your own! I want you to run the sell.vy
smart contract, which is the one we looked at in class relating to off-chain sales of things like rare books between a buyer and seller that don’t trust each other.
Here’s what you should do:
- Find the
sell.vy
source code in the git repo - Compile and then deploy the smart contract yourself by calling its constructor. Be sure to include some money in the
value
field of the transaction — that will act as the sale price for whatever you are pretending to sell. - Once that transaction goes through, essentially “publishing” your sale on the blockchain, get the address of the contract and send this contract address to the buyer (who should be some classmate).
- Your classmate should complete the next step of the smart contract, by paying twice the sale price into the
buy()
method. (They will have to follow similar directions to above when you called thepayme()
method from the UBI contract.) - Once you deliver the goods, your friend should finally run the
got_it()
method to complete the transaction and close out the contract.
Be sure to double-check that you got paid at the end!
I’m not going to give you every line to copy-paste to get this step done. Most you should be able to figure out from the examples before. But the part that’s new here is about calling the constructor in the first place.
Instead of telling you how to do that for this contract, I will show you how I did it for the UBI contract. These lines of code compile the UBI contract, load it into web3, call the constructor with 10000 ETH, and finally, once the transaction is added to a block, gets the address of this smart contract.
ubi_compiled = vyper.compile_code(open('ubi.vy').read(), ['abi', 'bytecode'])
txdict = {'value': w3.toWei(10000, 'ether')}
txid = w3.eth.contract(**ubi_compiled).constructor().transact(txdict)
# ... wait until transaction goes through... #
ubi_addr = w3.eth.get_transaction_receipt(txid)['contractAddress']
Challenge: fix the sell contract
There is a flaw in the sell.py
contract: what happens if no one wants to buy what the seller is selling? See if you can figure out how to fix it, and then see if you can get that to work!