Intro to Smart Contracts
By Julia Chou
This post is a blog version of a workshop presented at the 2019 Grace Hopper Celebration, the world’s largest gathering of women in technology. Coinbase is committed to building a future that promotes economic freedom and financial inclusion for all — regardless of socioeconomic, cultural, educational, gender, or geopolitical identities or backgrounds. Accordingly, we promote availability and opportunity for access to education around the distributed ledger technology driving the decentralized developers’ ecosystem.
In order to understand what smart contracts are and how they can be useful, it is helpful to take a few steps back to examine some other concepts related to blockchains, otherwise known as distributed ledger technology.
A short history of blockchains and cryptocurrencies
In 2009, Bitcoin took the world by storm as the first established decentralized electronic currency, bringing money into the digital age in a way that increased transparency, availability, and accountability by removing the need to rely on the private records of financial institutions to maintain an accurate ledger of account balances and transactions.
Bitcoin was revolutionary because it introduced a system for a network of peers to come to a consensus on a shared, public record of monetary transactions, essentially determining who has how much money. In this system, users broadcast transactions that a pool of miners will then compete to gather and secure into the next block of the public blockchain. This allows users to send and receive money and keep a log of these transactions without relinquishing control to a centralized bank or financial institution to perform these actions on their behalf. There are myriad advantages to using decentralized cryptocurrencies over government-issued fiat currencies, but prominent among those advantages are faster settlements and lower fees.
Though transactions using fiat currencies such as USD can be initiated electronically fairly quickly, the actual transfer and settling of these transactions behind the scenes can take days or weeks to finalize. The intermediary entities responsible for settling transactions also take a cut along the way, often taking the form of fees levied on retailers that bubble up as price increases for consumers. Naturally, the removal of these intermediaries also results in circumventing their transaction fees. With cryptocurrencies such as Bitcoin, mining fees are paid by transaction initiators and accepted by the miners who do the work of confirming blocks of transactions and adding them to the blockchain. These transactions are settled in a matter of minutes, and the associated mining fees are generally low in comparison to traditional transaction fees.
In 2015, Ethereum was launched as a decentralized, blockchain-based virtual machine running a Turing-complete programming language.
Whereas Bitcoin leverages its blockchain to maintain a global ledger of transaction history, Ethereum leverages its blockchain to maintain a general global state transition system. In this system, not only can users maintain a ledger of account balances and transactions, they can also record computer programs on the blockchain which are capable of executing arbitrary code when invoked. This makes it possible to codify and automate the distribution of value in a way that is transparent to all parties involved. These computer programs, or smart contracts, can facilitate simple transfers of assets from one account to another, manage ownership rights such as domain name registries or patents, power prediction markets, or record election results. Using smart contracts, you can create a token that represents an illiquid asset and trade it freely with others.
For example, NBA point guard Spencer Dinwiddie announced in September 2019 his intentions to tokenize his contract extension, selling digital tokens representing his future earning potential to investors for an immediate return. Purchasing these tokens would entitle investors to receive his paychecks plus interest. It remains to be seen whether he will be able to circumvent NBA regulations to actually carry out this plan.
All of that brings us to our post today, where we will study a simple example of a decentralized app, or dapp, and you will implement it yourself on your local machine.
Dapps are applications written using languages such as JavaScript, HTML, and CSS, leveraging smart contracts on the backend to display and change information. They are an important part of a movement from centralized Web2 architectures to a decentralized Web3 architecture.
This post will take you through writing a smart contract to programming a non-fungible token contract in Solidity, the most popular language for writing Ethereum smart contracts. Then, we will connect to this smart contract using a simple React application.
The dapp you will be implementing today is a small spoof of the popular CryptoKitties collectible dapp.
Development Setup
Make sure you have Node, Git, and Metamask installed and set up. Metamask is an extension that allows you to connect to and interact with Ethereum dapps.
Navigate to this GitHub repository to clone the code you’ll need to get started for this workshop.
$ git clone https://github.com/jp3hou/dapp-demo
$ cd dapp-demo
After you’ve cloned the repository, open up the project in a text editor and navigate to the “contracts/KatCoin.sol” file.
Some Ethereum Basics
Ether is the currency used in the Ethereum ecosystem to pay transaction fees.
Ethereum tracks state through accounts, which can be either externally owned accounts or contract accounts. Externally owned accounts belong to a user and are controlled by a private key, whereas contract accounts are controlled by their contract code.
Each account has a 20-byte address and contains four fields:
- The nonce, a counter to make sure a transaction is only processed once
- The ether balance
- The storage
- The contract code (if this is a contract account)
Transitions from one state to another occur through transactions, which contain a sender, a recipient, the amount of ether being transferred from the sender to the recipient, and gas limits for this transaction.
“Gas” is used to track how much computing power is used. This prevents DoS attacks by requiring that users pay for every resource they consume and canceling functions once a smart contract reaches the allotted gas limit.
Some Solidity Basics
Solidity is the most popular language used for Ethereum smart contract development. It is:
- Statically typed
- Object-oriented
- Compiled into EVM bytecode before being deployed to the blockchain
An Empty KatCoin Template
pragma solidity ^0.4.24;
import './Ownable.sol';
contract KatCoin is Ownable {
event Minted(address owner, uint256 katId, uint256 genes);
event Sent(address from, address to, uint256 katId);
struct Kat {
uint32 color;
uint16 generation;
}
Kat[] kats;
mapping (uint256 => address) private _katOwner;
mapping (address => uint256) private _ownedKatsCount;
constructor() public {
for (uint32 i = 0; i < 30; i++) {
kats.push(Kat((i + 3) * 32, 0));
_mint(address(this), kats.length - 1, i * 32);
}
}
function balanceOf(address owner) public view returns (uint256) {
// FILL ME OUT
}
function ownerOf(uint256 katId) public view returns (address) {
// FILL ME OUT
}
function getKat(uint256 katId) public view returns (
uint32 color,
uint16 generation
) {
// FILL ME OUT
}
function purchase(uint256 katId) public payable {
// FILL ME OUT
}
function _transferFrom(address from, address to, uint256 katId) internal {
// FILL ME OUT
}
function _mint(
address to,
uint256 katId,
uint256 genes
) internal {
// FILL ME OUT
}
function _addKatTo(address to, uint256 katId) internal {
// FILL ME OUT
}
function _removeKatFrom(address from, uint256 katId) internal {
// FILL ME OUT
}
}
You’ll see in this file that we are deriving our KatCoin contract from an Ownable contract in the same directory, which defines some basic auth control and user permission features that our KatCoin inherits. Smart contracts unlock the opportunity to reimagine the architecture of current applications and systems from the ground up, with user data and privacy in mind.
Things to note:
- The “pragma” call in the beginning of the file defines which Solidity compiler version to use
- Events can be defined and thrown/caught. This can be useful for debugging or logging purposes
- We are defining private state variables to keep track of which address owns which KatCoin and how many KatCoins an address owns. The ‘private’ designation ensures that these variables are accessible only to the current smart contract, not any of its descendants
- The constructor will mint 30 KatCoins upon initialization of this contract and assign the owner of each token to the smart contract itself
Filling out the balanceOf and ownerOf functions
function balanceOf(address owner) public view returns (uint256) {
require(owner != address(0));
return _ownedKatsCount[owner];
}
function ownerOf(uint256 katId) public view returns (address) {
address owner = _katOwner[katId];
require(owner != address(0));
return owner;
}
Things to note:
- “require” is a built-in Solidity function that throws an exception if the given condition returns false
- Public functions are part of a contract’s interface, and can be called either internally from within the contract or externally from other contracts/addresses
- View functions are read-only functions, meaning they do not modify the state of a smart contract
Filling out the getKat function
function getKat(uint256 katId) public view returns (
uint32 color,
uint16 generation
) {
Kat storage kat = kats[katId]; color = kat.color;
generation = kat.generation;
}
Things to note:
- A struct type is assigned to a local variable with data location ‘storage’, which stores a reference to the struct
Filling out the _addKatTo and _removeKatFrom functions
function _addKatTo(address to, uint256 katId) internal {
require(_katOwner[katId] == address(0));
_katOwner[katId] = to;
_ownedKatsCount[to]++;
}
function _removeKatFrom(address from, uint256 katId) internal {
require(ownerOf(katId) == from);
_ownedKatsCount[from]--;
_katOwner[katId] = address(0);
}
Things to note:
- Internal functions can only be accessed by the current contract and by its descendants
- _addKatTo validates that the KatCoin does not currently belong to anyone before assigning it to an owner
- _removeKatFrom validates that the owner of the KatCoin is the only person who can transfer ownership
- These functions change the contract state variables to reflect the updated ownership of these KatCoins
Filling out the _mint function
function _mint(address to, uint256 katId, uint256 genes) internal {
require(to != address(0));
_addKatTo(to, katId);
emit Minted(to, katId, genes);
}
Things to note:
- _mint validates that the token is to be assigned to a valid address
- This function leverages our previously implemented internal function to assign ownership of that KatCoin to the given address
- Emits the “Minted” event
Filling out the _transferFrom function
function _transferFrom(
address from,
address to,
uint256 katId
)
internal {
require(from == _katOwner[katId]);
require(to != address(0)); _removeKatFrom(from, katId);
_addKatTo(to, katId); emit Sent(from, to, katId);
}
Things to note:
- _transferFrom validates proper token ownership and valid address
- Leverages our previously implemented internal _removeKatFrom and _addKatTo functions to reassign ownership from the given ‘from’ address to the ‘to’ address
- Emits the “Sent” event
Filling out the purchase function
function purchase(uint256 katId) public payable {
require(katId >= 0 && katId < 30);
require(address(this) == _katOwner[katId]);
_transferFrom(address(this), msg.sender, katId);
owner.transfer(msg.value);
}
Things to note:
- Validates that the token is one of the 30 tokens initialized in the constructor
- Validates that the smart contract is the current owner of the token about to be purchased
- The ‘payable’ modifier indicates that this function can receive ether
- Leverages our previously implemented _transferFrom function to change ownership of this token
- Credit the owner the value of the message sent into the function
Time to compile and deploy!
Open up a terminal window and run the following:
$ npm run blockchain
This will start a local blockchain server on your computer running on port 8545. Make sure to pay attention to the output, as it will spin up a set of test accounts seeded with 100 test ether each for you to work with.
Copy one of the private keys given in the output, open up your Metamask extension, select the “Import Account” option and paste in that private key in order to import that account to interact with your dapp. Make sure you’ve selected your local network (Localhost 8545) to connect to, not the Ethereum mainnet.
Note: These Truffle-generated private keys are intended for development only! Treat the private keys you own for your main accounts with care.
Next, open up a new terminal window and run:
$ npm run migrate
This will compile your code to Ethereum Virtual Machine (EVM) bytecode and deploy your smart contracts onto your blockchain server.
Pay attention to the output of this command, it will tell you how much ether was consumed as part of the gas fee for deploying these contracts onto your blockchain server and also what the contract address for your KatCoin smart contract is.
With the contract address that you’ve copied from the last step, open up the “src/App.js” file in your project and replace the comment that says “CONTRACT ADDRESS GOES HERE” with a string containing that address value. The final results should look like this:
constructor() {
super();
const Kats = window.web3.eth.contract(KatCoin.abi); this.state = {
kats: Kats.at("0x8d37b292DE9c41c7A9969461718b1cA8008F421f"),
loading: true,
cats: []
};
}
Run the frontend!
In your terminal, run:
$ npm run start
This will spin up your react app at http://localhost:3000. You should see the 30 KatCoins that the smart contract has minted, with their ids and their owner addresses.
Click on one of the KatCoins to try to purchase it (which should try to transfer it from the smart contract to the account in your Metamask wallet), and you ought to see a popup in your Metamask extension asking for confirmation for this purchase transaction.
Confirm this transaction to complete your KatCoin purchase!
Congratulations! You should now see that the owner address has changed from the smart contract to your account, and you should be able to see your transaction history in Metamask.
Some Security Concerns
Keep in mind that smart contracts deal with real world valuemoney, and this might put user funds at risk. Bugs in smart contracts will literally cost you.
Smart contracts are immutable once deployed to the blockchain. However, you can program self-destruct functionality into them beforehand if you wish. Check out CryptoFin’s security auditing checklist for an example of some guidelines to follow.
That’s all for this demo! This is only a contrived example to introduce you to the development tools for building a dapp on Ethereum, but there’s a wealth of other resources for you to dive into now that you’ve gotten through this introduction.
Further References
- Ethernaut Security Challenges
- Ethereum White Paper
- Remix, an online Solidity IDE
- Solidity Docs
- Why Decentralization Matters
- The Web3 Stack
This website contains links to third-party websites or other content for information purposes only (“Third-Party Sites”). The Third-Party Sites are not under the control of Coinbase, Inc., and its affiliates (“Coinbase”), and Coinbase is not responsible for the content of any Third-Party Site, including without limitation any link contained in a Third-Party Site, or any changes or updates to a Third-Party Site. Coinbase is not responsible for webcasting or any other form of transmission received from any Third-Party Site. Coinbase is providing these links to you only as a convenience, and the inclusion of any link does not imply endorsement, approval or recommendation by Coinbase of the site or any association with its operators.
Intro to Smart Contracts was originally published in The Coinbase Blog on Medium, where people are continuing the conversation by highlighting and responding to this story.